Merge branch 'version/4.2' into Fiz/choose-class-header-adjustments

This commit is contained in:
Phillip Thelen 2023-04-24 15:08:59 +02:00 committed by GitHub
commit 54b2caf3b0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 197 additions and 126 deletions

View file

@ -1,30 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/refreshLayout">
android:id="@+id/refreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbarSize="3dp"
android:scrollbarThumbVertical="@color/scrollbarThumb"
android:scrollbars="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/invitationWrapper"
style="@style/FlatCardView"
android:id="@+id/invitationWrapper">
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.habitrpg.android.habitica.ui.views.social.InvitationsView
android:id="@+id/invitations_view"
style="@style/CardContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/CardContent" />
android:layout_height="wrap_content" />
</FrameLayout>
<LinearLayout
@ -32,125 +35,151 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="158dp"
android:layout_marginBottom="@dimen/spacing_medium">
<ImageView
android:id="@+id/no_party_background"
android:layout_width="match_parent"
android:layout_height="124dp"
android:layout_alignParentTop="true"/>
android:layout_alignParentTop="true" />
<View
android:layout_width="match_parent"
android:layout_height="124dp"
android:background="@drawable/no_party_background_gradient"/>
android:background="@drawable/no_party_background_gradient" />
<View
android:layout_width="match_parent"
android:layout_height="124dp"
android:rotation="-180"
android:background="@drawable/no_party_background_gradient" />
<ImageView
android:layout_width="332dp"
android:layout_height="146dp"
android:layout_marginTop="12dp"
android:layout_centerHorizontal="true"
android:src="@drawable/no_party_banner"/>
android:layout_marginTop="12dp"
android:src="@drawable/no_party_banner" />
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_margin="@dimen/spacing_sides">
android:layout_margin="@dimen/spacing_sides"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Headline"
android:textSize="16sp"
android:textColor="@color/text_ternary"
android:text="@string/no_party_title"
android:gravity="center"
android:fontFamily="sans-serif-medium"
android:layout_marginBottom="@dimen/spacing_medium"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Body2"
android:text="@string/no_party_description"
android:textColor="@color/text_quad"
android:layout_marginBottom="@dimen/spacing_medium"
android:fontFamily="sans-serif-medium"
android:gravity="center"
android:text="@string/no_party_title"
android:textColor="@color/text_ternary"
android:textSize="16sp" />
<TextView
style="@style/Body2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_large"
android:layout_marginEnd="@dimen/spacing_large"/>
android:layout_marginEnd="@dimen/spacing_large"
android:gravity="center"
android:text="@string/no_party_description"
android:textColor="@color/text_quad" />
<Button
android:id="@+id/createPartyButton"
style="@style/HabiticaButton.Gray"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/HabiticaButton.Gray"
android:text="@string/create_party"
android:layout_marginTop="@dimen/spacing_large"/>
android:layout_marginTop="@dimen/spacing_large"
android:text="@string/create_party" />
</LinearLayout>
<LinearLayout
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
app:cardBackgroundColor="@color/window_background"
app:strokeWidth="0dp"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="100dp"
android:paddingHorizontal="@dimen/spacing_sides"
android:paddingTop="@dimen/spacing_sides"
android:paddingBottom="60dp"
app:cardBackgroundColor="@color/window_background">
android:paddingTop="@dimen/spacing_sides">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/party_seeking"
android:layout_marginBottom="@dimen/spacing_large"/>
android:layout_marginBottom="@dimen/spacing_large"
android:src="@drawable/party_seeking" />
<TextView
style="@style/Headline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Headline"
android:layout_marginBottom="@dimen/spacing_medium"
android:fontFamily="sans-serif-medium"
android:textSize="16sp"
android:text="@string/seeking_party_title"
android:gravity="center"
android:layout_marginBottom="@dimen/spacing_medium"/>
android:text="@string/seeking_party_title"
android:textSize="16sp" />
<TextView
android:id="@+id/join_party_description_textview"
style="@style/Body2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Body2"
android:text="@string/seeking_party_description"
android:textColor="@color/text_quad"
android:textColorLink="@color/text_brand_neon"
android:gravity="center"
android:layout_marginStart="@dimen/spacing_large"
android:layout_marginEnd="@dimen/spacing_large"
android:layout_marginBottom="@dimen/spacing_large"/>
android:layout_marginBottom="@dimen/spacing_large"
android:gravity="center"
android:text="@string/seeking_party_description"
android:textColor="@color/text_green1_green500"
android:textColorLink="@color/text_brand_neon" />
<Button
android:id="@+id/seek_party_button"
style="@style/HabiticaButton.Primary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/HabiticaButton.Primary"
android:text="@string/look_for_party"/>
android:text="@string/look_for_party" />
<LinearLayout
android:id="@+id/seeking_party_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
style="@style/Body1_Button"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@drawable/success_border"
android:text="@string/you_re_looking_for_party"
android:textColor="@color/text_green10_green500"
style="@style/Body1_Button"
android:textAllCaps="false"
android:background="@drawable/success_border"/>
android:textColor="@color/text_green1_green500"
android:textSize="16sp" />
<TextView
style="@style/Body1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:fontFamily="@string/font_family_regular"
android:gravity="center"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:text="@string/seeking_hint"
android:textColor="@color/text_green10_green500" />
android:textColor="@color/text_green1_green500" />
<Button
android:id="@+id/leave_seeking_button"
@ -158,15 +187,16 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/leave_party_finder"
android:textColor="@color/text_red_maroon"
android:textSize="14sp" />
android:textColor="@color/text_red_maroon" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

View file

@ -51,6 +51,7 @@
<color name="text_brand_white">@color/white</color>
<color name="text_red_maroon">@color/red_100</color>
<color name="lightly_tinted_background">@color/brand_50</color>
<color name="text_green1_green500">@color/green_500</color>
<color name="dialog_background">@color/gray_10</color>
<color name="armoire_text">@color/gray_400</color>

View file

@ -123,6 +123,7 @@
<color name="lightly_tinted_background">@color/brand_700</color>
<color name="dialog_background">@color/white</color>
<color name="error_banner_background">@color/maroon_5</color>
<color name="text_green1_green500">@color/green_1</color>
<color name="text_gold">@color/yellow_1</color>
<color name="text_green10_green500">@color/green_10</color>

View file

@ -314,8 +314,9 @@
<string name="share_hatched">I just hatched a %1$s %2$s pet in #Habitica by completing my real-life tasks!</string>
<string name="share_raised">I just gained a %1$s mount in #Habitica by completing my real-life tasks!</string>
<string name="open_in_store">Open Play Store</string>
<string name="change_class_confirmation">Are you sure you want to change your class? This will cost 3 gems.</string>
<string name="change_class_equipment_warning">Warning: You will no longer be able to buy equipment from the %s class.</string>
<string name="change_class_confirmation">Do you want to change your class for 3 Gems?</string>
<string name="change_class_selected_confirmation">Do you want to change your class to %1$s for 3 Gems?</string>
<string name="change_class_equipment_warning">This will refund your stat points, switch which gear is unlocked in shops, and change your available skills.</string>
<string name="confirm">Confirm</string>
<string name="market">Market</string>
<string name="timeTravelers">Time Travelers</string>

View file

@ -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()

View file

@ -82,10 +82,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
}

View file

@ -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)

View file

@ -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
@ -224,28 +225,30 @@ 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)
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, className))
alert.addButton(getString(R.string.complete_tutorial), true)
alert.setMessage(getString(R.string.class_changed_description, selectedClass))
alert.addButton(getString(R.string.complete_tutorial), true){ _, _ -> dismiss() }
alert.setOnCancelListener {
dismiss()
}
alert.enqueue()
}
@ -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() {

View file

@ -67,8 +67,7 @@ class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int) : Recycl
fun updateItem(item: HabiticaDrawerItem) {
val position = getItemPosition(item.identifier)
items[position] = item
val visiblePosition = getVisibleItemPosition(item.identifier)
notifyItemChanged(visiblePosition)
notifyDataSetChanged()
}
fun updateItems(newItems: List<HabiticaDrawerItem>) {

View file

@ -296,7 +296,7 @@ class NavigationDrawerFragment : DialogFragment() {
}
item.isVisible = true
}
updateItem(item)
adapter.updateItem(item)
}
val statsItem = getItemWithIdentifier(SIDEBAR_STATS)
if (statsItem != null) {
@ -310,7 +310,7 @@ class NavigationDrawerFragment : DialogFragment() {
} else {
statsItem.isVisible = false
}
updateItem(statsItem)
adapter.updateItem(statsItem)
}
val subscriptionItem = getItemWithIdentifier(SIDEBAR_SUBSCRIPTION)
@ -336,12 +336,12 @@ class NavigationDrawerFragment : DialogFragment() {
subscriptionItem?.subtitle = context?.getString(R.string.more_out_of_habitica)
}
subscriptionItem?.let { updateItem(it) }
subscriptionItem?.let { adapter.updateItem(it) }
val promoItem = getItemWithIdentifier(SIDEBAR_SUBSCRIPTION_PROMO)
if (promoItem != null) {
promoItem.isVisible = !user.isSubscribed
updateItem(promoItem)
adapter.updateItem(promoItem)
}
getItemWithIdentifier(SIDEBAR_NEWS)?.let {
it.showBubble = user.flags?.newStuff ?: false
@ -689,10 +689,6 @@ class NavigationDrawerFragment : DialogFragment() {
private fun getItemWithIdentifier(identifier: String): HabiticaDrawerItem? =
adapter.getItemWithIdentifier(identifier)
private fun updateItem(item: HabiticaDrawerItem) {
adapter.updateItem(item)
}
private fun setDisplayName(name: String?) {
if (name != null && name.isNotEmpty()) {
binding?.toolbarTitle?.text = name
@ -793,17 +789,17 @@ class NavigationDrawerFragment : DialogFragment() {
R.string.sale_ends_in,
activePromo.endDate.getShortRemainingString()
)
updateItem(promotedItem)
adapter.updateItem(promotedItem)
} else {
promotedItem.subtitle = null
promotedItem.pillText = null
updateItem(promotedItem)
adapter.updateItem(promotedItem)
}
}
} ?: run {
promoItem.isVisible = false
}
updateItem(promoItem)
adapter.updateItem(promoItem)
}
companion object {

View file

@ -9,6 +9,7 @@ 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

View file

@ -11,10 +11,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 = 300): HabiticaProgressDialog? {
val dialog = context?.let { HabiticaProgressDialog(it) }
dialog?.setAdditionalContentView(R.layout.circular_progress)
dialog?.dialogWidth = 300.dpToPx(context)
dialog?.dialogWidth = dialogWidth.dpToPx(context)
dialog?.setTitle(title)
dialog?.enqueue()
return dialog