From 2a3b17e9d08e5327bae327bdb81fcb909bedb62c Mon Sep 17 00:00:00 2001 From: Hafiz Date: Wed, 22 Mar 2023 11:18:06 -0400 Subject: [PATCH 1/6] Updated string resource --- Habitica/res/values/strings.xml | 2 +- .../ui/fragments/social/party/PartySeekingFragment.kt | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml index 45fe6ee65..af72c2414 100644 --- a/Habitica/res/values/strings.xml +++ b/Habitica/res/values/strings.xml @@ -1365,7 +1365,7 @@ Damage to a boss or found collection items will be stored until you resume damage List By Invite - Here’s a list of Habiticans looking to join a Party + There\'s no one looking for a Party right now. You can check back later! Invited Invite with @username or email Send an invite directly to Habiticans you know diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartySeekingFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartySeekingFragment.kt index 827164115..12679cc35 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartySeekingFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartySeekingFragment.kt @@ -9,11 +9,13 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.Divider @@ -31,6 +33,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.fragment.app.viewModels @@ -237,8 +240,11 @@ fun PartySeekingView( fontWeight = FontWeight.Medium ) Text( - stringResource(R.string.habiticans_looking_party), - color = HabiticaTheme.colors.textSecondary + stringResource(R.string.habiticans_looking_party_empty), + textAlign = TextAlign.Center, + color = HabiticaTheme.colors.textSecondary, modifier = Modifier + .width(250.dp) + .align(alignment = Alignment.CenterHorizontally) ) } } From e1fa720c2143d13ea0d923a93f9516ff7fdf3e6d Mon Sep 17 00:00:00 2001 From: Hafiz Date: Sun, 26 Mar 2023 23:37:45 -0400 Subject: [PATCH 2/6] Reschedule alarm updates - use cases around reminder manual reschedule When manually changing the alarm time, nextDue will provide the previously set upcoming alarm - however if the alarm was changed to a upcoming time today (and not before the current time) AND the previous alarm set for today already passed, we want to check if the alarm repeat/days includes today as well. If so - use today's date to schedule an alarm. If not, use nextDue. Alarms automatically rescheduled -> nextDue used. A alarm that already passed today and is rescheduled for a upcoming time today -> Schedule for today (instead of using nextDue) - Update reschedule alarm to handle some use cases around user rescheduling alarm - Zoned Time extension --- .../extensions/ZonedDate-Extensions.kt | 38 +++++++++ .../habitica/helpers/TaskAlarmManager.kt | 6 +- .../android/habitica/models/tasks/Task.kt | 77 ++++++++++--------- 3 files changed, 80 insertions(+), 41 deletions(-) create mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/extensions/ZonedDate-Extensions.kt diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/ZonedDate-Extensions.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/ZonedDate-Extensions.kt new file mode 100644 index 000000000..ab08da6ba --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/ZonedDate-Extensions.kt @@ -0,0 +1,38 @@ +package com.habitrpg.android.habitica.extensions + +import java.time.DayOfWeek +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter +import java.time.format.DateTimeFormatterBuilder +import java.time.format.TextStyle +import java.time.temporal.TemporalAccessor +import java.util.Locale + +fun String.parseToZonedDateTime(): ZonedDateTime? { + val parsed: TemporalAccessor = formatter().parseBest( + this, + ZonedDateTime::from, LocalDateTime::from + ) + return if (parsed is ZonedDateTime) { + parsed + } else { + val defaultZone: ZoneId = ZoneId.of("UTC") + (parsed as LocalDateTime).atZone(defaultZone) + } +} + +/** + * Returns full display name in default Locale (Monday, Tuesday, Wednesday, etc.) + */ +fun ZonedDateTime.dayOfWeekString(): String { + return DayOfWeek.from(this).getDisplayName(TextStyle.FULL, Locale.getDefault()) +} + +fun formatter(): DateTimeFormatter = + DateTimeFormatterBuilder().append(DateTimeFormatter.ISO_LOCAL_DATE) + .appendPattern("['T'][' ']") + .append(DateTimeFormatter.ISO_LOCAL_TIME) + .appendPattern("[XX]") + .toFormatter() \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/TaskAlarmManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/TaskAlarmManager.kt index 1a555169d..245c47ad9 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/TaskAlarmManager.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/TaskAlarmManager.kt @@ -81,10 +81,8 @@ class TaskAlarmManager( } private fun setTimeForDailyReminder(remindersItem: RemindersItem?, task: Task): RemindersItem? { - val newTime = (remindersItem?.let { task.getNextReminderOccurrence(it) } ?: return null) - - remindersItem.time = newTime.withZoneSameLocal(ZoneId.systemDefault()) - .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + val newTime = task.getNextReminderOccurrence(remindersItem, context) + remindersItem?.time = newTime?.withZoneSameLocal(ZoneId.systemDefault())?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) return remindersItem } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt index d68f3d2d3..f9a84840b 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt @@ -1,10 +1,13 @@ package com.habitrpg.android.habitica.models.tasks +import android.content.Context import android.os.Parcel import android.os.Parcelable import android.text.Spanned import com.google.gson.annotations.SerializedName import com.habitrpg.android.habitica.R +import com.habitrpg.android.habitica.extensions.dayOfWeekString +import com.habitrpg.android.habitica.extensions.parseToZonedDateTime import com.habitrpg.android.habitica.models.BaseMainObject import com.habitrpg.android.habitica.models.Tag import com.habitrpg.common.habitica.helpers.ExceptionHandler @@ -22,9 +25,6 @@ import org.json.JSONException import java.time.LocalDateTime import java.time.ZoneId import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter -import java.time.format.DateTimeFormatterBuilder -import java.time.temporal.TemporalAccessor import java.util.Date open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask { @@ -308,50 +308,53 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask { fun checkIfDue(): Boolean = isDue == true - fun getNextReminderOccurrence(remindersItem: RemindersItem): ZonedDateTime? { - remindersItem.time?.let { oldTime -> - val now = ZonedDateTime.now().withZoneSameLocal(ZoneId.systemDefault())?.toInstant() - - // If task !isDisplayedActive or if isDisplayedActive but reminder passed, - // set a updated reminder with nextDate - return if (nextDue?.firstOrNull() != null && (!isDisplayedActive || remindersItem.getLocalZonedDateTimeInstant()?.isBefore(now) == true)) { - val nextDate = LocalDateTime.ofInstant(nextDue?.firstOrNull()?.toInstant(), ZoneId.systemDefault()) + /** + * When manually changing the alarm time, nextDue will provide the previously set upcoming alarm - + * however if the alarm was changed to a upcoming time today (and not before the current time) AND the previous alarm + * set for today already passed, we want to check if the alarm repeat/days includes today as well. + * If so - use today's date to schedule an alarm. If not, use nextDue. + * + * Alarms automatically rescheduled -> nextDue used. + * A alarm that already passed today and is rescheduled for a upcoming time today -> Schedule for today (instead of using nextDue) + */ + fun getNextReminderOccurrence(remindersItem: RemindersItem?, context: Context): ZonedDateTime? { + if (remindersItem == null) { + return null + } + val reminderTime = remindersItem.time?.parseToZonedDateTime() + val zonedDateTimeNow = ZonedDateTime.now() + return if (nextDue?.firstOrNull() != null && (!isDisplayedActive || reminderTime?.isBefore(zonedDateTimeNow) == true) + ) { + val repeatingDays = repeat?.dayStrings(context) + val isScheduledForToday = repeatingDays?.find { day -> day == zonedDateTimeNow.dayOfWeekString() } + if (isScheduledForToday != null) { val currentDateTime = LocalDateTime.now() - val nextDueCalendar: LocalDateTime = LocalDateTime.of( + val updatedDateTime: LocalDateTime = LocalDateTime.of( currentDateTime.year, - currentDateTime.month, // Add one to adjust from zero-based counting. + currentDateTime.month, currentDateTime.dayOfMonth, - nextDate.hour, - nextDate.minute + reminderTime?.hour ?: 0, + reminderTime?.minute ?: 0 ) - nextDueCalendar.atZone(ZoneId.systemDefault()) + updatedDateTime.atZone(ZoneId.systemDefault()) } else { - return parse(oldTime) + val nextDate = LocalDateTime.ofInstant(nextDue?.firstOrNull()?.toInstant(), ZoneId.systemDefault()) + val updatedDateTime: LocalDateTime = LocalDateTime.of( + nextDate.year, + nextDate.month, + nextDate.dayOfMonth, + reminderTime?.hour ?: 0, + reminderTime?.minute ?: 0 + ) + updatedDateTime.atZone(ZoneId.systemDefault()) } - } - return null - } - - fun formatter(): DateTimeFormatter = - DateTimeFormatterBuilder().append(DateTimeFormatter.ISO_LOCAL_DATE) - .appendPattern("['T'][' ']") - .append(DateTimeFormatter.ISO_LOCAL_TIME) - .appendPattern("[XX]") - .toFormatter() - - fun parse(dateTime: String): ZonedDateTime? { - val parsed: TemporalAccessor = formatter().parseBest( - dateTime, - ZonedDateTime::from, LocalDateTime::from - ) - return if (parsed is ZonedDateTime) { - parsed } else { - val defaultZone: ZoneId = ZoneId.of("UTC") - (parsed as LocalDateTime).atZone(defaultZone) + return reminderTime } } + + fun parseMarkdown() { parsedText = MarkdownParser.parseMarkdown(text) parsedNotes = MarkdownParser.parseMarkdown(notes) From 5eb7f980f01167710f1723ad88d911b4aa2d8d91 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Wed, 29 Mar 2023 10:48:36 -0400 Subject: [PATCH 3/6] Show Confirmation modal on successful class change fixes Updated Dialog width to fix multi-line progress text. Finish activity when confirmation dialog "Got it" button was pressed or if dialog was dismissed. Add class that was changed to in confirmation dialog. --- .../ui/activities/ClassSelectionActivity.kt | 25 +++++++++++-------- .../views/dialogs/HabiticaProgressDialog.kt | 4 +-- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.kt index b79cdc72d..19c99b0fb 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.kt @@ -23,6 +23,7 @@ import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaProgressDialog import com.habitrpg.common.habitica.helpers.ExceptionHandler import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import javax.inject.Inject @@ -227,26 +228,28 @@ class ClassSelectionActivity : BaseActivity() { alert.setTitle(getString(R.string.change_class_confirmation)) alert.setMessage(getString(R.string.change_class_equipment_warning, currentClass)) alert.addButton(R.string.choose_class, true) { _, _ -> - selectClass(newClass) - displayClassChanged() + selectClass(newClass, true) } alert.addButton(R.string.dialog_go_back, false) alert.show() } else { val alert = HabiticaAlertDialog(this) alert.setTitle(getString(R.string.class_confirmation, className)) - alert.addButton(R.string.choose_class, true) { _, _ -> selectClass(newClass) } + alert.addButton(R.string.choose_class, true) { _, _ -> selectClass(newClass, false) } alert.addButton(R.string.dialog_go_back, false) alert.show() } } - private fun displayClassChanged() { + private fun displayClassChanged(selectedClass: String) { val alert = HabiticaAlertDialog(this) alert.setTitle(getString(R.string.class_changed, className)) - alert.setMessage(getString(R.string.class_changed_description)) - alert.addButton(getString(R.string.complete_tutorial), true) - alert.enqueue() + alert.setMessage(getString(R.string.class_changed_description, selectedClass)) + alert.addButton(getString(R.string.complete_tutorial), true){ _, _ -> dismiss() } + alert.setOnCancelListener { + dismiss() + } + alert.show() } private fun optOutOfClasses() { @@ -258,17 +261,17 @@ class ClassSelectionActivity : BaseActivity() { } } - private fun selectClass(selectedClass: String) { + private fun selectClass(selectedClass: String, isChanging: Boolean) { shouldFinish = true this.displayProgressDialog(getString(R.string.changing_class_progress)) - lifecycleScope.launch(ExceptionHandler.coroutine()) { + lifecycleScope.launch(Dispatchers.Main) { userRepository.changeClass(selectedClass) - dismiss() + if (isChanging) displayClassChanged(selectedClass) } } private fun displayProgressDialog(progressText: String) { - HabiticaProgressDialog.show(this, progressText) + HabiticaProgressDialog.show(this, progressText, 300) } private fun dismiss() { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/HabiticaProgressDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/HabiticaProgressDialog.kt index 938125ffa..67ba775fd 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/HabiticaProgressDialog.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/HabiticaProgressDialog.kt @@ -10,10 +10,10 @@ class HabiticaProgressDialog(context: Context) : HabiticaAlertDialog(context) { return show(context, context?.getString(titleID)) } - fun show(context: Context?, title: String?): HabiticaProgressDialog? { + fun show(context: Context?, title: String?, dialogWidth: Int = 150): HabiticaProgressDialog? { val dialog = context?.let { HabiticaProgressDialog(it) } context?.let { dialog?.setAdditionalContentView(R.layout.circular_progress) } - dialog?.dialogWidth = 150.dpToPx(context) + dialog?.dialogWidth = dialogWidth.dpToPx(context) dialog?.setTitle(title) dialog?.enqueue() return dialog From bb188657209621c42294c5b45e8c448027c17648 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Wed, 29 Mar 2023 11:13:20 -0400 Subject: [PATCH 4/6] Sync up the change class confirmation messages new combined text: Do you want to change your class to [newClass] for 3 Gems? This will refund your stat points, switch which gear is unlocked in shops, and change your available skills. --- Habitica/res/values/strings.xml | 5 +++-- .../android/habitica/ui/activities/ClassSelectionActivity.kt | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml index e9a2bbec9..b64a9e015 100644 --- a/Habitica/res/values/strings.xml +++ b/Habitica/res/values/strings.xml @@ -314,8 +314,9 @@ I just hatched a %1$s %2$s pet in #Habitica by completing my real-life tasks! I just gained a %1$s mount in #Habitica by completing my real-life tasks! Open Play Store - Are you sure you want to change your class? This will cost 3 gems. - Warning: You will no longer be able to buy equipment from the %s class. + Do you want to change your class for 3 Gems? + Do you want to change your class to %1$s for 3 Gems? + This will refund your stat points, switch which gear is unlocked in shops, and change your available skills. Confirm Market Time Travelers diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.kt index 19c99b0fb..4f6c349b9 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.kt @@ -225,8 +225,8 @@ class ClassSelectionActivity : BaseActivity() { private fun displayConfirmationDialogForClass() { if (!this.isInitialSelection && this.classWasUnset == false) { val alert = HabiticaAlertDialog(this) - alert.setTitle(getString(R.string.change_class_confirmation)) - alert.setMessage(getString(R.string.change_class_equipment_warning, currentClass)) + alert.setTitle(getString(R.string.change_class_selected_confirmation, newClass)) + alert.setMessage(getString(R.string.change_class_equipment_warning)) alert.addButton(R.string.choose_class, true) { _, _ -> selectClass(newClass, true) } From 244582a311024d17691e56413068f443995ba1f9 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Sat, 1 Apr 2023 11:36:34 -0400 Subject: [PATCH 5/6] No Party - Seeking party UI Updates --- Habitica/res/layout/fragment_no_party.xml | 142 +++++++++++++--------- Habitica/res/values-night/colors.xml | 2 +- Habitica/res/values/colors.xml | 2 +- 3 files changed, 88 insertions(+), 58 deletions(-) diff --git a/Habitica/res/layout/fragment_no_party.xml b/Habitica/res/layout/fragment_no_party.xml index ff4baa7f2..8acadefce 100644 --- a/Habitica/res/layout/fragment_no_party.xml +++ b/Habitica/res/layout/fragment_no_party.xml @@ -1,30 +1,33 @@ + android:id="@+id/refreshLayout" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + + + android:layout_width="match_parent" + android:layout_height="wrap_content"> + android:layout_height="wrap_content" /> + + + android:layout_alignParentTop="true" /> + + android:background="@drawable/no_party_background_gradient" /> + + + + android:layout_marginTop="12dp" + android:src="@drawable/no_party_banner" /> + + android:layout_margin="@dimen/spacing_sides" + android:orientation="vertical"> + - + + + android:layout_marginEnd="@dimen/spacing_large" + android:gravity="center" + android:text="@string/no_party_description" + android:textColor="@color/text_quad" /> +