Challenge fixes

This commit is contained in:
Phillip Thelen 2024-04-23 17:34:59 +02:00
parent bf62f5ac16
commit 22ac1e9a28
26 changed files with 98 additions and 157 deletions

View file

@ -40,7 +40,7 @@ dependencies {
compileOnly 'javax.annotation:javax.annotation-api:1.3.2'
//App Compatibility and Material Design
implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation 'com.google.android.material:material:1.10.0'
implementation 'com.google.android.material:material:1.11.0'
implementation "androidx.recyclerview:recyclerview:$recyclerview_version"
implementation "androidx.preference:preference-ktx:$preferences_version"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
@ -51,7 +51,7 @@ dependencies {
implementation('com.jaredrummler:android-device-names:2.1.1')
// IAP Handling / Verification
implementation "com.android.billingclient:billing-ktx:6.1.0"
implementation "com.android.billingclient:billing-ktx:6.2.1"
implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1@aar'
implementation("io.coil-kt:coil-compose:$coil_version")
@ -111,11 +111,11 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
implementation "com.google.accompanist:accompanist-themeadapter-material:$accompanist_version"
implementation "androidx.compose.material3:material3:1.2.0"
implementation "androidx.compose.material3:material3:1.2.1"
implementation "com.google.accompanist:accompanist-systemuicontroller:$accompanist_version"
implementation 'com.google.android.play:core:1.10.3'
implementation 'androidx.activity:activity-compose:1.8.2'
implementation 'androidx.activity:activity-compose:1.9.0'
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.animation:animation:$compose_version"
@ -144,7 +144,7 @@ android {
defaultConfig {
minSdkVersion min_sdk
compileSdk 34
compileSdk target_sdk
applicationId "com.habitrpg.android.habitica"
vectorDrawables.useSupportLibrary = true
buildConfigField "String", "STORE", "\"google\""
@ -167,11 +167,12 @@ android {
viewBinding true
compose true
renderScript true
buildConfig = true
aidl true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.3"
kotlinCompilerExtensionVersion = rootProject.compose_compiler
}
signingConfigs {

View file

@ -16,7 +16,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
style="@style/Caption4"
style="@style/Caption2"
tools:text="Section Header"
android:textColor="?textColorSecondary"
android:layout_marginStart="16dp"

View file

@ -215,7 +215,7 @@ class RealmSocialLocalRepository(realm: Realm) :
user: User?,
newValue: Boolean,
) {
executeTransaction { user?.party?.quest?.RSVPNeeded = newValue }
executeTransaction { user?.party?.quest?.rsvpNeeded = newValue }
}
override fun likeMessage(

View file

@ -189,6 +189,7 @@ class AppConfigManager(contentRepository: ContentRepository?) :
}
fun enableCustomizationShop(): Boolean {
if (BuildConfig.DEBUG) return true
return remoteConfig.getBoolean("enableCustomizationShop")
}
}

View file

@ -29,6 +29,6 @@ class Shop {
const val QUEST_SHOP = "questShop"
const val TIME_TRAVELERS_SHOP = "timeTravelersShop"
const val SEASONAL_SHOP = "seasonalShop"
const val CUSTOMIZATIONS = "customizations"
const val CUSTOMIZATIONS = "customizationsShop"
}
}

View file

@ -212,10 +212,10 @@ class ChallengeFormActivity : BaseActivity() {
}
override fun onCreate(savedInstanceState: Bundle?) {
addHabit = createTask(resources.getString(R.string.add_habit))
addDaily = createTask(resources.getString(R.string.add_daily))
addTodo = createTask(resources.getString(R.string.add_todo))
addReward = createTask(resources.getString(R.string.add_reward))
addHabit = createTask("addhabit", resources.getString(R.string.add_habit))
addDaily = createTask("adddaily", resources.getString(R.string.add_daily))
addTodo = createTask("addtodo", resources.getString(R.string.add_todo))
addReward = createTask("addreward", resources.getString(R.string.add_reward))
super.onCreate(savedInstanceState)
@ -579,10 +579,10 @@ class ChallengeFormActivity : BaseActivity() {
companion object {
const val CHALLENGE_ID_KEY = "challengeId"
private fun createTask(taskName: String): Task {
private fun createTask(taskid: String, taskName: String): Task {
val t = Task()
t.id = "addtask"
t.id = taskid
t.text = taskName
return t

View file

@ -862,7 +862,7 @@ class TaskFormActivity : BaseActivity() {
}
val alert = HabiticaAlertDialog(this)
alert.setTitle(R.string.are_you_sure)
alert.addButton(R.string.delete_task, false, isDestructive = true) { _, _ ->
alert.addButton(R.string.delete_task, true, isDestructive = true) { _, _ ->
if (task?.isValid != true) return@addButton
task?.id?.let {
lifecycleScope.launch(Dispatchers.Main) {

View file

@ -31,14 +31,6 @@ class InboxAdapter(private var user: User?) :
return if (isPositionIntroMessage(position)) FIRST_MESSAGE else NORMAL_MESSAGE
}
override fun getItemId(position: Int): Long {
return if (isPositionIntroMessage(position)) -1 else super.getItemId(position)
}
override fun getItem(position: Int): ChatMessage? {
return if (isPositionIntroMessage(position)) ChatMessage() else super.getItem(position)
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int,

View file

@ -39,7 +39,7 @@ class ChallengeTasksRecyclerViewAdapter(
TaskType.DAILY -> TYPE_DAILY
TaskType.TODO -> TYPE_TODO
TaskType.REWARD -> TYPE_REWARD
else -> if (task?.id == "addtask") TYPE_ADD_ITEM else TYPE_HEADER
else -> if (task?.id?.startsWith("add") == true) TYPE_ADD_ITEM else TYPE_HEADER
}
}
@ -55,6 +55,18 @@ class ChallengeTasksRecyclerViewAdapter(
return position
}
override fun onBindViewHolder(holder: BindableViewHolder<Task>, position: Int) {
if (holder is RewardViewHolder) {
val task = filteredContent?.get(position)
holder.isLocked = true
if (task != null) {
holder.bind(task, position, true, "normal", null)
}
} else {
super.onBindViewHolder(holder, position)
}
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int,

View file

@ -473,8 +473,8 @@ private fun AvatarCustomizationView(
item(span = { GridItemSpan(3) }) {
Text(
typeName.uppercase(),
fontSize = 12.sp,
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
fontWeight = FontWeight.SemiBold,
color = colorResource(id = R.color.text_ternary),
textAlign = TextAlign.Center,
modifier = Modifier.padding(10.dp),

View file

@ -15,6 +15,7 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.paging.PagingData
import androidx.recyclerview.widget.RecyclerView
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.SocialRepository
@ -115,7 +116,9 @@ class InboxMessageListFragment : BaseMainFragment<FragmentInboxMessageListBindin
viewModel.messages.observe(viewLifecycleOwner) {
markMessagesAsRead(it)
chatAdapter?.submitList(it)
lifecycleScope.launchCatching {
chatAdapter?.submitData(it)
}
}
binding?.chatBarView?.sendAction = { sendMessage(it) }
@ -170,8 +173,8 @@ class InboxMessageListFragment : BaseMainFragment<FragmentInboxMessageListBindin
return super.onOptionsItemSelected(item)
}
private fun markMessagesAsRead(messages: List<ChatMessage>) {
socialRepository.markSomePrivateMessagesAsRead(viewModel.user.value, messages)
private fun markMessagesAsRead(messages: PagingData<ChatMessage>) {
//socialRepository.markSomePrivateMessagesAsRead(viewModel.user.value, messages)
}
private fun refreshConversation() {

View file

@ -119,7 +119,7 @@ class QuestDetailFragment : BaseMainFragment<FragmentQuestDetailBinding>() {
val user = userViewModel.user.value
if (binding?.questResponseWrapper != null) {
if (userViewModel.userID != party?.quest?.leader && user?.party?.quest?.key == group.quest?.key && user?.party?.quest?.RSVPNeeded == false) {
if (userViewModel.userID != party?.quest?.leader && user?.party?.quest?.key == group.quest?.key && user?.party?.quest?.rsvpNeeded == false) {
binding?.questLeaveButton?.visibility = View.VISIBLE
} else {
binding?.questLeaveButton?.visibility = View.GONE

View file

@ -120,7 +120,7 @@ class AuthenticationViewModel
// the app access to the account, but the user can fix this.
// Forward the user to an activity in Google Play services.
if (!activity.isFinishing) {
val intent = e.intent
val intent = e.intent ?: return
recoverFromPlayServicesErrorResult.launch(intent)
}
}

View file

@ -9,7 +9,9 @@ import androidx.paging.DataSource
import androidx.paging.PagedList
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import androidx.paging.PagingState
import androidx.paging.liveData
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.data.UserRepository
@ -48,27 +50,14 @@ class InboxViewModel
val memberIDState: StateFlow<String?> = memberIDFlow
private val config =
PagedList.Config.Builder()
.setPageSize(10)
.setEnablePlaceholders(false)
.build()
private val dataSourceFactory =
MessagesDataSourceFactory(socialRepository, recipientID, ChatMessage())
val messages: LiveData<PagedList<ChatMessage>> =
Pager<Int, ChatMessage>(
PagingConfig(
config.pageSize,
config.prefetchDistance,
config.enablePlaceholders,
config.initialLoadSizeHint,
config.maxSize,
),
PagingConfig(pageSize = 10, enablePlaceholders = false)
val messages: LiveData<PagingData<ChatMessage>> =
Pager(
config,
null,
dataSourceFactory.asPagingSourceFactory(
ArchTaskExecutor.getIOThreadExecutor().asCoroutineDispatcher(),
),
).liveData
) {
MessagesDataSource(socialRepository, recipientID, ChatMessage())
}.liveData
private val member =
memberIDFlow
.filterNotNull()
@ -84,7 +73,7 @@ class InboxViewModel
get() = memberIDFlow.value
fun invalidateDataSource() {
dataSourceFactory.sourceLiveData.value?.invalidate()
}
init {
@ -95,7 +84,8 @@ class InboxViewModel
val member = socialRepository.retrieveMember(recipientUsername, false)
setMemberID(member?.id ?: "")
invalidateDataSource()
dataSourceFactory.updateRecipientID(memberID)
// dataSourceFactory.updateRecipientID(memberID)
}
}
}
@ -105,87 +95,30 @@ class MessagesDataSource(
val socialRepository: SocialRepository,
var recipientID: String?,
var footer: ChatMessage?,
) :
PagingSource<Int, T>() {
) : PagingSource<Int, ChatMessage>() {
private var lastFetchWasEnd = false
override fun loadRange(
params: LoadRangeParams,
callback: LoadRangeCallback<ChatMessage>,
) {
override fun getRefreshKey(state: PagingState<Int, ChatMessage>): Int? {
TODO("Not yet implemented")
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, ChatMessage> {
if (lastFetchWasEnd) {
callback.onResult(emptyList())
return
return LoadResult.Page(emptyList(), null, null)
}
MainScope().launch(Dispatchers.Main.immediate) {
if (recipientID?.isNotBlank() != true) {
return@launch
}
val page = ceil(params.startPosition.toFloat() / params.loadSize.toFloat()).toInt()
val messages =
socialRepository.retrieveInboxMessages(recipientID ?: "", page) ?: return@launch
if (messages.size < 10) {
lastFetchWasEnd = true
callback.onResult(messages)
} else {
callback.onResult(messages)
}
if (recipientID?.isNotBlank() != true) {
return LoadResult.Error(Exception("Recipient ID is blank"))
}
}
override fun loadInitial(
params: LoadInitialParams,
callback: LoadInitialCallback<ChatMessage>,
) {
lastFetchWasEnd = false
MainScope().launch(Dispatchers.Main.immediate) {
socialRepository.getInboxMessages(recipientID)
.map { socialRepository.getUnmanagedCopy(it) }
.take(1)
.flatMapLatest {
if (it.isEmpty()) {
if (recipientID?.isNotBlank() != true) {
return@flatMapLatest flowOf(it)
}
val messages =
socialRepository.retrieveInboxMessages(recipientID ?: "", 0)
?: return@flatMapLatest emptyFlow()
if (messages.size < 10) {
lastFetchWasEnd = true
}
flowOf(messages)
} else {
flowOf(it)
}
}
.collect {
if (it.size < 10 && footer != null) {
callback.onResult(it.plusElement(footer!!), 0)
} else {
callback.onResult(it, 0)
}
}
val page = params.key ?: 0
val messages =
socialRepository.retrieveInboxMessages(recipientID ?: "", page) ?: return LoadResult.Error(
Exception("Failed to retrieve messages")
)
val nextPage = if (messages.size < 10) {
null
} else {
page + 1
}
}
}
class MessagesDataSourceFactory(
val socialRepository: SocialRepository,
var recipientID: String?,
val footer: ChatMessage?,
) :
DataSource.Factory<Int, ChatMessage>() {
val sourceLiveData = MutableLiveData<MessagesDataSource>()
var latestSource: MessagesDataSource = MessagesDataSource(socialRepository, recipientID, footer)
fun updateRecipientID(newID: String?) {
recipientID = newID
latestSource.recipientID = newID
}
override fun create(): DataSource<Int, ChatMessage> {
latestSource = MessagesDataSource(socialRepository, recipientID, footer)
sourceLiveData.postValue(latestSource)
return latestSource
return LoadResult.Page(messages, if (page > 0) page - 1 else null, nextPage)
}
}

View file

@ -158,7 +158,7 @@ open class NotificationsViewModel
)
val quest = user.party?.quest
if (quest != null && quest.RSVPNeeded) {
if (quest != null && quest.rsvpNeeded) {
val notification = Notification()
notification.id = "custom-quest-invitation-" + user.party?.id
notification.type = Notification.Type.QUEST_INVITATION.type

View file

@ -78,7 +78,7 @@ class PartyViewModel
fun showParticipantButtons(): Boolean {
val user = user.value
return !(user?.party == null || user.party?.quest == null) && !isQuestActive && user.party?.quest?.RSVPNeeded == true
return !(user?.party == null || user.party?.quest == null) && !isQuestActive && user.party?.quest?.rsvpNeeded == true
}
fun loadPartyID() {

View file

@ -66,7 +66,7 @@ class MemberSerialization : JsonDeserializer<Member> {
if (!obj.get("party").asJsonObject.get("quest").asJsonObject.has("RSVPNeeded")) {
val quest = realm.where(Quest::class.java).equalTo("id", member.id).findFirst()
if (quest != null && quest.isValid) {
member.party?.quest?.RSVPNeeded = quest.RSVPNeeded
member.party?.quest?.rsvpNeeded = quest.rsvpNeeded
}
}
}

View file

@ -64,7 +64,7 @@ class QuestDeserializer : JsonDeserializer<Quest> {
quest.leader = obj.get("leader").asString
}
if (obj.has("RSVPNeeded")) {
quest.RSVPNeeded = obj.get("RSVPNeeded").asBoolean
quest.rsvpNeeded = obj.get("RSVPNeeded").asBoolean
}
if (obj.has("members")) {

View file

@ -81,7 +81,7 @@ class UserDeserializer : JsonDeserializer<User> {
val realm = Realm.getDefaultInstance()
val quest = realm.where(Quest::class.java).equalTo("id", user.id).findFirst()
if (quest != null && quest.isValid) {
user.party?.quest?.RSVPNeeded = quest.RSVPNeeded
user.party?.quest?.rsvpNeeded = quest.rsvpNeeded
}
}
if (partyObj.getAsJsonObject("quest").has("completed")) {

View file

@ -5,7 +5,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
buildscript {
ext {
min_sdk = 21
target_sdk = 33
target_sdk = 34
app_version_name = ''
app_version_code = 0
@ -13,13 +13,14 @@ buildscript {
amplitude_version = '1.6.1'
appcompat_version = '1.6.1'
coil_version = '2.4.0'
compose_version = '1.6.2'
core_ktx_version = '1.12.0'
compose_version = '1.6.6'
compose_compiler = '1.5.12'
core_ktx_version = '1.13.0'
coroutines_version = '1.7.3'
daggerhilt_version = '2.51.1'
firebase_bom = '31.3.0'
kotest_version = '5.6.2'
kotlin_version = '1.9.10'
kotlin_version = '1.9.23'
ktlint_version = '1.2.1'
lifecycle_version = '2.7.0'
markwon_version = '4.6.2'
@ -42,7 +43,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.3.1'
classpath 'com.android.tools.build:gradle:8.3.2'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
classpath 'com.google.gms:google-services:4.4.1'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9'

View file

@ -40,10 +40,11 @@ android {
buildFeatures {
viewBinding = true
compose = true
buildConfig = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.3"
kotlinCompilerExtensionVersion = rootProject.extra.get("compose_compiler") as String
}
compileOptions {

View file

@ -67,7 +67,7 @@ class KeyHelper
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (keyStore?.containsAlias(KEY_ALIAS) == false) {
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, AndroidKeyStore)
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE)
keyGenerator.init(
KeyGenParameterSpec.Builder(
KEY_ALIAS,
@ -94,7 +94,7 @@ class KeyHelper
.setStartDate(start.time)
.setEndDate(end.time)
.build()
val kpg = KeyPairGenerator.getInstance("RSA", AndroidKeyStore)
val kpg = KeyPairGenerator.getInstance("RSA", ANDROID_KEY_STORE)
kpg.initialize(spec)
kpg.generateKeyPair()
}

View file

@ -7,7 +7,6 @@ org.gradle.jvmargs=-Xmx6656M
org.gradle.warning.mode=all
kotlin.mpp.stability.nowarn=true
kotlin.mpp.androidSourceSetLayoutVersion=2
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false
android.enableR8.fullMode=false

View file

@ -1,6 +1,6 @@
#Sun May 08 23:57:52 EDT 2022
#Mon Apr 22 16:17:19 CEST 2024
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View file

@ -15,7 +15,9 @@ allprojects {
kotlin {
android()
ios()
iosX64()
iosArm64()
iosSimulatorArm64()
js(IR) {
browser()
@ -24,25 +26,21 @@ kotlin {
}
sourceSets {
val commonMain by getting {
commonMain {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${rootProject.extra.get("coroutines_version")}")
}
}
val commonTest by getting {
commonTest {
dependencies {
implementation(kotlin("test")) // This brings all the platform dependencies automatically
}
}
val androidMain by getting
val androidUnitTest by getting
val iosMain by getting
val iosTest by getting
}
}
android {
compileSdk = 33
compileSdk = rootProject.extra.get("target_sdk") as Int
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdk = 21

View file

@ -1,2 +1,2 @@
NAME=4.3.6
CODE=7221
NAME=4.3.7
CODE=7221