diff --git a/Habitica/res/layout/activity_main_content.xml b/Habitica/res/layout/activity_main_content.xml index 803ea4575..08a09a097 100644 --- a/Habitica/res/layout/activity_main_content.xml +++ b/Habitica/res/layout/activity_main_content.xml @@ -95,7 +95,7 @@ app:layout_anchorGravity="bottom" app:layout_collapseMode="pin" app:tabGravity="fill" - app:tabIndicatorColor="?colorPrimary" + app:tabIndicatorColor="?textColorPrimary" app:tabMode="fixed" /> + android:layout_gravity="center" + android:visibility="gone"/> \ No newline at end of file diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml index 5d3ffe647..bd43e139e 100644 --- a/Habitica/res/values/strings.xml +++ b/Habitica/res/values/strings.xml @@ -1319,7 +1319,7 @@ The rare, mystical Gryphatrice joins the birthday bash! Don’t miss your chance to own this exclusive animated Pet. Thanks for your support! Plenty of Potions - For for Free + Four for Free Buy for %s Buy for We’re bringing back 10 of the community’s favorite Magic Hatching Potions. Head over to the Market to fill out your collection! @@ -1332,6 +1332,12 @@ You purchased the Jubilant Gryphatrice! You gifted the Jubilant Gryphatrice! Open Settings + Undo + Day %d + A Party Robe + 20 Gems + Background + Birthday Set You diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.kt index 561386e74..a09102e2a 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.kt @@ -459,4 +459,7 @@ interface ApiService { @GET("hall/heroes/{memberID}") suspend fun getHallMember(@Path("memberID") memberID: String): HabitResponse + + @POST("tasks/{taskID}/needs-work/{userID}") + suspend fun markTaskNeedsWork(@Path("taskID") taskID: String, @Path("userID") userID: String): HabitResponse } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt index 2f6060b29..d6de7a6a0 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt @@ -275,4 +275,5 @@ interface ApiClient { suspend fun unassignFromTask(taskId: String, userID: String): Task? suspend fun updateMember(memberID: String, updateData: Map): Member? suspend fun getHallMember(userId: String): Member? + suspend fun markTaskNeedsWork(taskID: String, userID: String): Task? } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/TaskRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/TaskRepository.kt index 9ae35d9d0..b768f2623 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/TaskRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/TaskRepository.kt @@ -65,6 +65,7 @@ interface TaskRepository : BaseRepository { suspend fun retrieveCompletedTodos(userId: String? = null): TaskList? suspend fun syncErroredTasks(): List? suspend fun unlinkAllTasks(challengeID: String?, keepOption: String): Void? - fun getTasksForChallenge(challengeID: String?): Flow> + fun getTasksForChallenge(challengeID: String?): Flow> suspend fun bulkScoreTasks(data: List>): BulkTaskScoringData? + suspend fun markTaskNeedsWork(task: Task, userID: String) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt index 446bf4c41..86f5ced97 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt @@ -606,14 +606,22 @@ class ApiClientImpl( return process { apiService.leaveQuest(groupId) } } + private val lastPurchaseValidation: Date? = null override suspend fun validatePurchase(request: PurchaseValidationRequest): PurchaseValidationResult? { - return process { apiService.validatePurchase(request) } + // make sure a purchase attempt doesn't happen + return if (lastPurchaseValidation == null || Date().time - lastPurchaseValidation.time > 5000) { + return process { apiService.validatePurchase(request) } + } else null } override suspend fun changeCustomDayStart(updateObject: Map): User? { return process { apiService.changeCustomDayStart(updateObject) } } + override suspend fun markTaskNeedsWork(taskID: String, userID: String): Task? { + return process { apiService.markTaskNeedsWork(taskID, userID) } + } + override suspend fun getMember(memberId: String) = processResponse(apiService.getMember(memberId)) override suspend fun getMemberWithUsername(username: String) = processResponse(apiService.getMemberWithUsername(username)) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt index a1f5985c3..c08f94933 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt @@ -147,6 +147,16 @@ class TaskRepositoryImpl( bgTask.counterDown = (bgTask.counterDown ?: 0) + 1 } } + + if (bgTask.isGroupTask) { + val entry = bgTask.group?.assignedUsersDetail?.firstOrNull { it.assignedUserID == user.id } + entry?.completed = up + if (up) { + entry?.completedDate = Date() + } else { + entry?.completedDate = null + } + } } res._tmp?.drop?.key?.let { key -> val type = when (res._tmp?.drop?.type?.lowercase(Locale.US)) { @@ -183,6 +193,14 @@ class TaskRepositoryImpl( } } + override suspend fun markTaskNeedsWork(task: Task, userID: String) { + val savedTask = apiClient.markTaskNeedsWork(task.id ?: "", userID) + if (savedTask != null) { + savedTask.position = task.position + localRepository.save(savedTask) + } + } + override suspend fun taskChecked( user: User?, taskId: String, @@ -319,7 +337,7 @@ class TaskRepositoryImpl( val savedTask = apiClient.assignToTask(taskID, assignments) ?: return@let savedTask.id = task.id savedTask.position = task.position - localRepository.save(task) + localRepository.save(savedTask) } assignChanges["unassign"]?.let { unassignments -> @@ -330,7 +348,7 @@ class TaskRepositoryImpl( if (savedTask != null) { savedTask.id = task.id savedTask.position = task.position - localRepository.save(task) + localRepository.save(savedTask) } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseTypes.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseTypes.kt index 0752272be..1977fc29f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseTypes.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseTypes.kt @@ -1,7 +1,7 @@ package com.habitrpg.android.habitica.helpers object PurchaseTypes { - const val JubilantGrphatrice = "com.habitrpg.android.habitica.iap.gryphatrice" + const val JubilantGrphatrice = "com.habitrpg.android.habitica.iap.pets.gryphatrice_jubilant" const val Purchase4Gems = "com.habitrpg.android.habitica.iap.4gems" const val Purchase21Gems = "com.habitrpg.android.habitica.iap.21gems" const val Purchase42Gems = "com.habitrpg.android.habitica.iap.42gems" diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/Group.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/Group.kt index eb5dcceca..594e454f1 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/Group.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/Group.kt @@ -3,6 +3,7 @@ package com.habitrpg.android.habitica.models.social import com.google.gson.annotations.SerializedName import com.habitrpg.android.habitica.models.BaseMainObject import com.habitrpg.android.habitica.models.inventory.Quest +import com.habitrpg.android.habitica.models.user.SubscriptionPlan import com.habitrpg.shared.habitica.models.tasks.TasksOrder import io.realm.RealmList import io.realm.RealmObject @@ -11,6 +12,10 @@ import io.realm.annotations.PrimaryKey open class Group : RealmObject(), BaseMainObject { + val isGroupPlan: Boolean + get() { + return purchased?.isActive == true + } override val realmClass: Class get() = Group::class.java override val primaryIdentifier: String? @@ -38,6 +43,7 @@ open class Group : RealmObject(), BaseMainObject { var leaderOnlyChallenges: Boolean = false var leaderOnlyGetGems: Boolean = false var categories: RealmList? = null + var purchased: SubscriptionPlan? = null @Ignore var tasksOrder: TasksOrder? = null diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/SubscriptionPlan.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/SubscriptionPlan.kt index f47e7a6f3..366f59dfc 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/SubscriptionPlan.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/SubscriptionPlan.kt @@ -18,6 +18,7 @@ open class SubscriptionPlan : RealmObject(), BaseObject { @JvmField var planId: String? = null + var active: Boolean? = null var gemsBought: Int? = null var extraMonths: Int? = null var quantity: Int? = null @@ -34,7 +35,7 @@ open class SubscriptionPlan : RealmObject(), BaseObject { val isActive: Boolean get() { val today = Date() - return customerId != null && (dateTerminated == null || dateTerminated!!.after(today)) + return customerId != null && (dateTerminated == null || dateTerminated!!.after(today) || active == true) } val totalNumberOfGems: Int diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BirthdayActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BirthdayActivity.kt index d6c028796..e3805a069 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BirthdayActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BirthdayActivity.kt @@ -19,10 +19,10 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.Button import androidx.compose.material.ButtonDefaults +import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.ProvideTextStyle import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -59,7 +59,10 @@ import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.ui.theme.HabiticaTheme import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.views.CurrencyText +import com.habitrpg.android.habitica.ui.views.PixelArtView import com.habitrpg.common.habitica.extensions.DataBindingUtils +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.map import java.text.SimpleDateFormat import java.util.Date @@ -68,13 +71,17 @@ import javax.inject.Inject class BirthdayActivity : BaseActivity() { @Inject lateinit var userViewModel: MainUserViewModel + @Inject lateinit var purchaseHandler: PurchaseHandler + @Inject lateinit var inventoryRepository: InventoryRepository + @Inject lateinit var configManager: AppConfigManager + private val isPurchasing = mutableStateOf(false) private val price = mutableStateOf("") private val hasGryphatrice = mutableStateOf(false) private var gryphatriceProductDetails: ProductDetails? = null @@ -86,20 +93,33 @@ class BirthdayActivity : BaseActivity() { val event = configManager.getBirthdayEvent() setContent { HabiticaTheme { - val user = userViewModel.user.observeAsState() - BirthdayActivityView(hasGryphatrice.value, price.value, event?.start ?: Date(), event?.end ?: Date(), { - gryphatriceProductDetails?.let { - purchaseHandler.purchase(this, it) - } - }, { - lifecycleScope.launchCatching { - inventoryRepository.purchaseItem("", "Gryphatrice-Jubilant", 1) - } - }, { - lifecycleScope.launchCatching { - inventoryRepository.equip("pets", "Gryphatrice-Jubilant") - } - }) + BirthdayActivityView( + isPurchasing.value, + hasGryphatrice.value, + price.value, + event?.start ?: Date(), + event?.end ?: Date(), + { + gryphatriceProductDetails?.let { + isPurchasing.value = true + purchaseHandler.purchase(this, it) + } + }, + { + lifecycleScope.launchCatching({ + isPurchasing.value = false + }) { + isPurchasing.value = true + inventoryRepository.purchaseItem("pets", "Gryphatrice-Jubilant", 1) + userRepository.retrieveUser(false, true) + isPurchasing.value = false + } + }, + { + lifecycleScope.launchCatching { + inventoryRepository.equip("pet", "Gryphatrice-Jubilant") + } + }) } } @@ -112,6 +132,11 @@ class BirthdayActivity : BaseActivity() { hasGryphatrice.value = (it?.trained ?: 0) >= 5 } } + + lifecycleScope.launchCatching { + gryphatriceProductDetails = purchaseHandler.getGryphatriceSKU() + price.value = gryphatriceProductDetails?.oneTimePurchaseOfferDetails?.formattedPrice ?: "" + } } override fun injectActivity(component: UserComponent?) { @@ -150,9 +175,17 @@ fun BirthdayTitle(text: String) { } } - @Composable -fun BirthdayActivityView(hasGryphatrice: Boolean, price: String, startDate: Date, endDate: Date, onPurchaseClick: () -> Unit, onGemPurchaseClick: () -> Unit, onEquipClick: () -> Unit) { +fun BirthdayActivityView( + isPurchasing: Boolean, + hasGryphatrice: Boolean, + price: String, + startDate: Date, + endDate: Date, + onPurchaseClick: () -> Unit, + onGemPurchaseClick: () -> Unit, + onEquipClick: () -> Unit +) { val activity = LocalContext.current as? Activity val dateFormat = SimpleDateFormat("MMM dd", java.util.Locale.getDefault()) val textColor = Color.White @@ -192,8 +225,13 @@ fun BirthdayActivityView(hasGryphatrice: Boolean, price: String, startDate: Date Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .padding(horizontal = 20.dp) - .fillMaxWidth()) { - Image(painterResource(R.drawable.birthday_header), null, Modifier.padding(bottom = 8.dp)) + .fillMaxWidth() + ) { + Image( + painterResource(R.drawable.birthday_header), + null, + Modifier.padding(bottom = 8.dp) + ) Row(verticalAlignment = Alignment.CenterVertically) { Image(painterResource(R.drawable.birthday_gifts), null) Column( @@ -207,7 +245,11 @@ fun BirthdayActivityView(hasGryphatrice: Boolean, price: String, startDate: Date fontWeight = FontWeight.Bold ) Text( - stringResource(R.string.x_to_y, dateFormat.format(startDate), dateFormat.format(endDate)), + stringResource( + R.string.x_to_y, + dateFormat.format(startDate), + dateFormat.format(endDate) + ), fontSize = 12.sp, color = textColor, fontWeight = FontWeight.Bold @@ -266,6 +308,8 @@ fun BirthdayActivityView(hasGryphatrice: Boolean, price: String, startDate: Date ) { Text(stringResource(R.string.equip)) } + } else if (isPurchasing) { + CircularProgressIndicator() } else { Text(buildAnnotatedString { append("Buy for ") @@ -281,7 +325,7 @@ fun BirthdayActivityView(hasGryphatrice: Boolean, price: String, startDate: Date Color.White, colorResource(R.color.brand_200), { - onPurchaseClick() + onPurchaseClick() }, modifier = Modifier.padding(top = 20.dp) ) { @@ -291,7 +335,7 @@ fun BirthdayActivityView(hasGryphatrice: Boolean, price: String, startDate: Date Color.White, colorResource(R.color.brand_200), { - onGemPurchaseClick() + onGemPurchaseClick() }, modifier = Modifier.padding(top = 20.dp) ) { @@ -314,7 +358,11 @@ fun BirthdayActivityView(hasGryphatrice: Boolean, price: String, startDate: Date Color.White, colorResource(R.color.brand_200), { - onEquipClick() + MainScope().launchCatching { + activity?.finish() + delay(500) + MainNavigationController.navigate(R.id.marketFragment) + } }, modifier = Modifier.padding(top = 20.dp) ) { @@ -328,6 +376,42 @@ fun BirthdayActivityView(hasGryphatrice: Boolean, price: String, startDate: Date textAlign = TextAlign.Center, lineHeight = 20.sp ) + Column(verticalArrangement = Arrangement.spacedBy(14.dp), modifier = Modifier.padding(vertical = 20.dp)) { + Row( + horizontalArrangement = Arrangement.spacedBy(14.dp), + modifier = Modifier.fillMaxWidth() + ) { + FourFreeItem( + day = 1, + title = stringResource(R.string.a_party_robe), + imageName = "", + modifier = Modifier.weight(1f) + ) + FourFreeItem( + day = 1, + title = stringResource(R.string.twenty_gems), + imageName = "", + modifier = Modifier.weight(1f) + ) + } + Row( + horizontalArrangement = Arrangement.spacedBy(14.dp), + modifier = Modifier.fillMaxWidth() + ) { + FourFreeItem( + day = 5, + title = stringResource(R.string.birthday_set), + imageName = "", + modifier = Modifier.weight(1f) + ) + FourFreeItem( + day = 10, + title = stringResource(R.string.background), + imageName = "", + modifier = Modifier.weight(1f) + ) + } + } } Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -369,15 +453,24 @@ fun PotionGrid() { "Peppermint", "Shimmer" ).windowed(4, 4, true) - Column(verticalArrangement = Arrangement.spacedBy(8.dp), horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(top = 20.dp)) { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(top = 20.dp) + ) { for (potionGroup in potions) { Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { for (potion in potionGroup) { Box( Modifier .size(68.dp) - .background(colorResource(R.color.brand_50), RoundedCornerShape(8.dp))) { - AsyncImage(model = DataBindingUtils.BASE_IMAGE_URL + DataBindingUtils.getFullFilename("Pet_HatchingPotion_$potion"), null, Modifier.size(68.dp)) + .background(colorResource(R.color.brand_50), RoundedCornerShape(8.dp)) + ) { + AsyncImage( + model = DataBindingUtils.BASE_IMAGE_URL + DataBindingUtils.getFullFilename( + "Pet_HatchingPotion_$potion" + ), null, Modifier.size(68.dp) + ) } } } @@ -385,6 +478,34 @@ fun PotionGrid() { } } +@Composable +fun FourFreeItem( + day: Int, + title: String, + imageName: String, + modifier: Modifier = Modifier +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(18.dp), + modifier = modifier + .background(colorResource(R.color.brand_50), HabiticaTheme.shapes.medium) + .padding(16.dp) + ) { + Text( + stringResource(R.string.day_x, day).uppercase(), + color = colorResource(R.color.yellow_50), + fontSize = 12.sp, + fontWeight = FontWeight.Bold + ) + PixelArtView(imageName, + Modifier + .size(121.dp, 84.dp) + .background(colorResource(R.color.brand_100), HabiticaTheme.shapes.medium)) + Text(title, color = Color.White, fontSize = 16.sp) + } +} + @Composable fun HabiticaButton( background: Color, @@ -413,7 +534,7 @@ fun HabiticaButton( @Preview(device = Devices.PIXEL_4) @Composable private fun Preview() { - BirthdayActivityView(false, "", Date(), Date(), { + BirthdayActivityView(true, false, "", Date(), Date(), { }, {}, {}) } \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt index 4b6ccca79..78cbee974 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt @@ -19,7 +19,6 @@ import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.widget.CheckBox -import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.appcompat.widget.AppCompatCheckBox import androidx.compose.runtime.mutableStateListOf @@ -236,6 +235,12 @@ class TaskFormActivity : BaseActivity() { { showAssignDialog() }, + { + taskCompletedMap.remove(it) + lifecycleScope.launchCatching { + task?.let { it1 -> taskRepository.markTaskNeedsWork(it1, it) } + } + }, showEditButton = true ) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RewardsRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RewardsRecyclerViewAdapter.kt index 3eae1407f..4c36548cc 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RewardsRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RewardsRecyclerViewAdapter.kt @@ -100,7 +100,7 @@ class RewardsRecyclerViewAdapter( } override fun getItemViewType(position: Int): Int { - return if (customRewards != null && position < customRewardCount) { + return if ((customRewards != null && position < customRewardCount) || (customRewardCount == 0 && inAppRewardCount == 0)) { VIEWTYPE_CUSTOM_REWARD } else { VIEWTYPE_IN_APP_REWARD @@ -139,6 +139,6 @@ class RewardsRecyclerViewAdapter( companion object { private const val VIEWTYPE_CUSTOM_REWARD = 0 - private const val VIEWTYPE_IN_APP_REWARD = 2 + private const val VIEWTYPE_IN_APP_REWARD = 3 } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt index 77211baf3..a0ecd55cf 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt @@ -104,7 +104,7 @@ class NavigationDrawerFragment : DialogFragment() { val context = context adapter = if (context != null) { NavigationDrawerAdapter( - context.getThemeColor(R.attr.colorPrimary), + context.getThemeColor(R.attr.colorPrimaryText), context.getThemeColor(R.attr.colorPrimaryOffset) ) } else { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt index 353e8eb5a..14541b6fe 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt @@ -7,7 +7,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import com.habitrpg.android.habitica.MainNavDirections @@ -32,7 +31,7 @@ import javax.inject.Inject import kotlin.time.DurationUnit import kotlin.time.toDuration -class ChatFragment() : BaseFragment() { +class ChatFragment : BaseFragment() { override var binding: FragmentChatBinding? = null @@ -40,9 +39,7 @@ class ChatFragment() : BaseFragment() { return FragmentChatBinding.inflate(inflater, container, false) } - val viewModel: GroupViewModel by viewModels( - ownerProducer = { requireParentFragment() } - ) + lateinit var viewModel: GroupViewModel @Inject lateinit var configManager: AppConfigManager diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernFragment.kt index 7ae349903..6d91ad36a 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernFragment.kt @@ -91,7 +91,9 @@ class TavernFragment : BaseMainFragment() { TavernDetailFragment() } 1 -> { - ChatFragment() + val fragment = ChatFragment() + fragment.viewModel = viewModel + fragment } else -> Fragment() } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt index 3645a9ec5..c06830de5 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt @@ -8,6 +8,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import com.habitrpg.android.habitica.MainNavDirections import com.habitrpg.android.habitica.R @@ -197,6 +198,8 @@ class GuildDetailFragment : BaseFragment() { binding?.guildBankText?.text = guild?.gemCount.toString() binding?.guildSummary?.setMarkdown(guild?.summary) binding?.guildDescription?.setMarkdown(guild?.description) + + binding?.inviteButton?.isVisible = guild?.isGroupPlan == true } companion object { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildFragment.kt index d2ed0f720..1725970f3 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildFragment.kt @@ -154,6 +154,7 @@ class GuildFragment : BaseMainFragment() { } 1 -> { chatFragment = ChatFragment() + chatFragment?.viewModel = viewModel fragment = chatFragment } else -> fragment = Fragment() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyFragment.kt index d416fd700..0f5d48b8e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyFragment.kt @@ -196,6 +196,7 @@ class PartyFragment : BaseMainFragment() { } 1 -> { chatFragment = ChatFragment() + chatFragment?.viewModel = viewModel chatFragment } else -> Fragment() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt index cd86dd2e4..870429bb7 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt @@ -45,7 +45,7 @@ class RewardsRecyclerviewFragment : TaskRecyclerViewFragment() { (layoutManager as? GridLayoutManager)?.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { override fun getSpanSize(position: Int): Int { - return if ((recyclerAdapter?.getItemViewType(position) ?: 0) < 2) { + return if ((recyclerAdapter?.getItemViewType(position) ?: 0) < 3) { (layoutManager as? GridLayoutManager)?.spanCount ?: 1 } else { 1 diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/ChecklistedViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/ChecklistedViewHolder.kt index 0941f0e78..704c29203 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/ChecklistedViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/ChecklistedViewHolder.kt @@ -207,7 +207,7 @@ abstract class ChecklistedViewHolder( override fun onLeftActionTouched() { super.onLeftActionTouched() - if (task?.isValid == true) { + if (task?.isValid == true && !isLocked) { onCheckedChanged(!(task?.completed(userID) ?: false)) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/HabitViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/HabitViewHolder.kt index 10662021b..cca29990d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/HabitViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/HabitViewHolder.kt @@ -137,12 +137,16 @@ class HabitViewHolder( override fun onLeftActionTouched() { super.onLeftActionTouched() - onPlusButtonClicked() + if (!isLocked) { + onPlusButtonClicked() + } } override fun onRightActionTouched() { super.onRightActionTouched() - onMinusButtonClicked() + if (!isLocked) { + onMinusButtonClicked() + } } private fun onPlusButtonClicked() { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt index 06f351c1b..8e9d34100 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt @@ -202,10 +202,13 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali } fun likeMessage(message: ChatMessage) { - val index = _chatMessages.value?.indexOf(message) - if (index == null || index < 0) return viewModelScope.launchCatching { val message = socialRepository.likeMessage(message) + val index = _chatMessages.value?.indexOfFirst { it.id == message?.id } + if (index == null || index < 0) { + retrieveGroupChat { } + return@launchCatching + } val list = _chatMessages.value?.toMutableList() if (message != null) { list?.set(index, message) @@ -246,7 +249,10 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali } fun retrieveGroupChat(onComplete: () -> Unit) { - val groupID = groupID + var groupID = groupID + if (groupViewType == GroupViewType.PARTY) { + groupID = "party" + } if (groupID.isNullOrEmpty()) { onComplete() return diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/UserRow.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/UserRow.kt index 164798779..76633d458 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/UserRow.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/UserRow.kt @@ -26,6 +26,7 @@ fun UserRow( username: String, avatar: Avatar?, modifier: Modifier = Modifier, + mainContentModifier: Modifier = Modifier, extraContent: @Composable (() -> Unit)? = null, endContent: @Composable (() -> Unit)? = null, color: Color? = null @@ -46,7 +47,7 @@ fun UserRow( } } - Column { + Column(mainContentModifier) { Text( "@$username", fontSize = 16.sp, diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/AssignedView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/AssignedView.kt index 839c3cc4f..6c76f2203 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/AssignedView.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/AssignedView.kt @@ -3,23 +3,31 @@ package com.habitrpg.android.habitica.ui.views.tasks import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.models.Assignable +import com.habitrpg.android.habitica.ui.theme.HabiticaTheme import com.habitrpg.android.habitica.ui.views.CompletedAt import com.habitrpg.android.habitica.ui.views.UserRow import java.util.Date @@ -31,6 +39,7 @@ fun AssignedView( backgroundColor: Color, color: Color, onEditClick: () -> Unit, + onUndoClick: (String) -> Unit, modifier: Modifier = Modifier, showEditButton: Boolean = false ) { @@ -41,14 +50,18 @@ fun AssignedView( backgroundColor, MaterialTheme.shapes.medium ) - .padding(15.dp, 12.dp) - .heightIn(min = 24.dp) + .heightIn(min = 66.dp) + .padding(start = 16.dp) .fillMaxWidth() + .clip(MaterialTheme.shapes.medium) for (assignable in assigned) { UserRow( username = assignable.identifiableName, avatar = assignable.avatar, modifier = rowModifier, + mainContentModifier = Modifier + .padding(vertical = 12.dp) + .heightIn(min = 24.dp), color = color, extraContent = { completedAt[assignable.id]?.let { CompletedAt(completedAt = it) } @@ -56,7 +69,9 @@ fun AssignedView( endContent = { completedAt[assignable.id]?.let { if (showEditButton) { - UndoTaskCompletion() + UndoTaskCompletion(Modifier.clickable { + assignable.id?.let { it1 -> onUndoClick(it1) } + }) } } } @@ -90,5 +105,28 @@ fun AssignedView( } @Composable -fun UndoTaskCompletion() { +fun UndoTaskCompletion(modifier: Modifier = Modifier) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + modifier = modifier + .width(51.dp) + .heightIn(min = 66.dp) + .fillMaxHeight() + .background(HabiticaTheme.colors.contentBackgroundOffset) + ) { + Image( + painterResource(R.drawable.checkmark), + null, + contentScale = ContentScale.None, + modifier = Modifier + .size(24.dp) + .background(HabiticaTheme.colors.windowBackground, HabiticaTheme.shapes.small) + ) + Text( + stringResource(R.string.undo), + fontSize = 12.sp, + color = HabiticaTheme.colors.textSecondary + ) + } } diff --git a/version.properties b/version.properties index 8c685bc20..91ea3ae7f 100644 --- a/version.properties +++ b/version.properties @@ -1,2 +1,2 @@ NAME=4.1 -CODE=4991 \ No newline at end of file +CODE=5031 \ No newline at end of file