diff --git a/Habitica/build.gradle b/Habitica/build.gradle
index f6551fab6..e3dadc526 100644
--- a/Habitica/build.gradle
+++ b/Habitica/build.gradle
@@ -50,9 +50,8 @@ dependencies {
implementation 'com.google.dagger:dagger:2.39.1'
kapt 'com.google.dagger:dagger-compiler:2.39.1'
compileOnly 'javax.annotation:javax.annotation-api:1.3.2'
- compileOnly 'com.github.pengrad:jdk9-deps:1.0'
//App Compatibility and Material Design
- implementation 'androidx.appcompat:appcompat:1.3.1'
+ implementation 'androidx.appcompat:appcompat:1.4.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation "androidx.preference:preference-ktx:1.1.1"
@@ -103,14 +102,13 @@ dependencies {
//Leak Detection
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
//Push Notifications
- implementation platform('com.google.firebase:firebase-bom:28.3.0')
- implementation 'com.google.firebase:firebase-crashlytics'
+ implementation platform('com.google.firebase:firebase-bom:29.0.0')
+ implementation 'com.google.firebase:firebase-crashlytics-ktx'
implementation 'com.google.firebase:firebase-core'
- implementation 'com.google.firebase:firebase-messaging'
- implementation 'com.google.firebase:firebase-config'
- implementation 'com.google.firebase:firebase-perf'
+ implementation 'com.google.firebase:firebase-messaging-ktx'
+ implementation 'com.google.firebase:firebase-config-ktx'
+ implementation 'com.google.firebase:firebase-perf-ktx'
implementation 'com.google.android.gms:play-services-auth:19.2.0'
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31"
implementation 'com.nex3z:flow-layout:1.2.2'
implementation 'androidx.core:core-ktx:1.7.0'
@@ -119,9 +117,9 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-common-java8:2.4.0"
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
- implementation "androidx.fragment:fragment-ktx:1.3.6"
- implementation "androidx.paging:paging-runtime-ktx:3.0.1"
implementation 'com.plattysoft.leonids:LeonidsLib:1.3.2'
+ implementation "androidx.fragment:fragment-ktx:1.4.0"
+ implementation "androidx.paging:paging-runtime-ktx:3.1.0"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
@@ -153,8 +151,8 @@ android {
buildConfigField "String", "TESTING_LEVEL", "\"production\""
resConfigs "en", "bg", "de", "en-rGB", "es", "fr", "hr-rHR", "in", "it", "iw", "ja", "ko", "lt", "nl", "pl", "pt-rBR", "pt-rPT", "ru", "tr", "zh", "zh-rTW"
- versionCode 3093
- versionName "3.4.1.1"
+ versionCode 3102
+ versionName "3.4.2"
targetSdkVersion 31
diff --git a/Habitica/res/layout/dialog_edittext_add_local_auth.xml b/Habitica/res/layout/dialog_edittext_add_local_auth.xml
index ee1b6e33d..0b71217b3 100644
--- a/Habitica/res/layout/dialog_edittext_add_local_auth.xml
+++ b/Habitica/res/layout/dialog_edittext_add_local_auth.xml
@@ -1,55 +1,28 @@
-
+
-
-
-
+
-
-
-
+
-
-
+ android:inputType="textPassword"
+ app:hint="@string/new_password_repeat"
+ android:layout_marginTop="@dimen/content_section_spacing" />
\ No newline at end of file
diff --git a/Habitica/res/layout/dialog_edittext_change_pw.xml b/Habitica/res/layout/dialog_edittext_change_pw.xml
index 4a15a7ac5..12a142026 100644
--- a/Habitica/res/layout/dialog_edittext_change_pw.xml
+++ b/Habitica/res/layout/dialog_edittext_change_pw.xml
@@ -1,43 +1,27 @@
-
-
-
-
+
-
-
-
+
-
-
+ android:inputType="textPassword"
+ app:hint="@string/new_password_repeat"
+ android:layout_marginTop="@dimen/content_section_spacing" />
\ No newline at end of file
diff --git a/Habitica/res/layout/dialog_edittext_confirm_pw.xml b/Habitica/res/layout/dialog_edittext_confirm_pw.xml
index dc9d6c843..a74c31d89 100644
--- a/Habitica/res/layout/dialog_edittext_confirm_pw.xml
+++ b/Habitica/res/layout/dialog_edittext_confirm_pw.xml
@@ -1,32 +1,20 @@
-
-
-
-
+
-
-
+ android:inputType="textPassword"
+ app:hint="@string/password"
+ android:layout_marginTop="@dimen/content_section_spacing" />
\ No newline at end of file
diff --git a/Habitica/res/layout/preference_child_summary_danger.xml b/Habitica/res/layout/preference_child_summary_danger.xml
new file mode 100644
index 000000000..7f8cba022
--- /dev/null
+++ b/Habitica/res/layout/preference_child_summary_danger.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Habitica/res/layout/preference_child_summary_error.xml b/Habitica/res/layout/preference_child_summary_error.xml
index 1fb6acc58..5f29f5888 100644
--- a/Habitica/res/layout/preference_child_summary_error.xml
+++ b/Habitica/res/layout/preference_child_summary_error.xml
@@ -38,6 +38,6 @@
android:layout_below="@android:id/title"
android:maxLines="4"
android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="@color/red_10" />
+ android:textColor="@color/text_red" />
\ No newline at end of file
diff --git a/Habitica/res/layout/validating_edit_text.xml b/Habitica/res/layout/validating_edit_text.xml
new file mode 100644
index 000000000..b08fcc8cf
--- /dev/null
+++ b/Habitica/res/layout/validating_edit_text.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Habitica/res/values-night/colors.xml b/Habitica/res/values-night/colors.xml
index cabdf4adc..d2adefd8b 100644
--- a/Habitica/res/values-night/colors.xml
+++ b/Habitica/res/values-night/colors.xml
@@ -10,7 +10,7 @@
@color/gray_10
@color/brand_400
@color/brand_500
- @color/red_10
+ @color/red_100
@color/orange_100
@color/yellow_100
@color/green_100
diff --git a/Habitica/res/values-ru/strings.xml b/Habitica/res/values-ru/strings.xml
index 481ca6593..e079882f7 100644
--- a/Habitica/res/values-ru/strings.xml
+++ b/Habitica/res/values-ru/strings.xml
@@ -1147,7 +1147,7 @@
Вы уверены, что хотите покинуть команду\?
Жалуйтесь ** только ** на те публикации, которые нарушают [Принципы сообщества] (https://habitica.com/static/community-guidelines) и / или [Условия предоставления услуг] (https://habitica.com/static/terms ). Ложное сообщение о нарушении может привести к наказанию.
Выберите ниже набор самоцветов, который хотите подарить!
- Доступно для %@
+ Доступно для %s
Обновить содержимое
Вы не сможете снова присоединиться к этой команде, пока вас не пригласят.
Вы хотите продолжить участвовать в испытании, покинув команду\?
diff --git a/Habitica/res/values/attrs.xml b/Habitica/res/values/attrs.xml
index ad92dd442..e09e02790 100644
--- a/Habitica/res/values/attrs.xml
+++ b/Habitica/res/values/attrs.xml
@@ -147,4 +147,8 @@
+
+
+
+
diff --git a/Habitica/res/values/colors.xml b/Habitica/res/values/colors.xml
index cbaae2796..56665d057 100644
--- a/Habitica/res/values/colors.xml
+++ b/Habitica/res/values/colors.xml
@@ -161,7 +161,7 @@
@color/gray_700
@color/brand_300
@color/brand_400
- @color/red_10
+ @color/maroon_100
@color/orange_10
@color/yellow_10
@color/green_10
diff --git a/Habitica/res/values/dimens.xml b/Habitica/res/values/dimens.xml
index 23b956352..68a3250b4 100644
--- a/Habitica/res/values/dimens.xml
+++ b/Habitica/res/values/dimens.xml
@@ -53,7 +53,7 @@
16sp
14sp
2dp
- 84dp
+ 88dp
120dp
20dp
68dp
diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml
index 26103d02d..adb4f95f7 100644
--- a/Habitica/res/values/strings.xml
+++ b/Habitica/res/values/strings.xml
@@ -1196,4 +1196,12 @@
Connect
Disconnect
Add
+ Copy Token. Be careful, this is a password!
+ Added %s authentication
+ Copied %s to clipboard
+ Disconnected %s
+ Password saved
+ Add Email and Password
+ Password needs to be typed correctly twice
+ Invalid Email address
diff --git a/Habitica/res/xml/preferences_fragment.xml b/Habitica/res/xml/preferences_fragment.xml
index aed0579de..ceadf3bb3 100644
--- a/Habitica/res/xml/preferences_fragment.xml
+++ b/Habitica/res/xml/preferences_fragment.xml
@@ -63,23 +63,20 @@
android:title="@string/public_profile"
android:key="public_profile"
android:layout="@layout/preference_category">
-
-
-
+
+ android:layout="@layout/preference_child_summary_danger" />
+ android:layout="@layout/preference_child_summary_danger" />
@@ -122,13 +121,9 @@
tools:title="Change Class"
android:layout="@layout/preference_child_summary"
app:isPreferenceVisible="false"/>
-
+ android:layout="@layout/preference_child_summary_danger"/>
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/api/GSonFactoryCreator.java b/Habitica/src/main/java/com/habitrpg/android/habitica/api/GSonFactoryCreator.java
index 68871196b..8d7a7649a 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/api/GSonFactoryCreator.java
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/api/GSonFactoryCreator.java
@@ -28,6 +28,7 @@ import com.habitrpg.android.habitica.models.user.OwnedMount;
import com.habitrpg.android.habitica.models.user.OwnedPet;
import com.habitrpg.android.habitica.models.user.Purchases;
import com.habitrpg.android.habitica.models.user.User;
+import com.habitrpg.android.habitica.models.user.auth.SocialAuthentication;
import com.habitrpg.android.habitica.utils.AchievementListDeserializer;
import com.habitrpg.android.habitica.utils.BooleanAsIntAdapter;
import com.habitrpg.android.habitica.utils.ChallengeDeserializer;
@@ -51,6 +52,7 @@ import com.habitrpg.android.habitica.utils.QuestCollectDeserializer;
import com.habitrpg.android.habitica.utils.QuestDeserializer;
import com.habitrpg.android.habitica.utils.QuestDropItemsListSerialization;
import com.habitrpg.android.habitica.utils.SkillDeserializer;
+import com.habitrpg.android.habitica.utils.SocialAuthenticationDeserializer;
import com.habitrpg.android.habitica.utils.TaskListDeserializer;
import com.habitrpg.android.habitica.utils.TaskSerializer;
import com.habitrpg.android.habitica.utils.TaskTagDeserializer;
@@ -84,7 +86,6 @@ public class GSonFactoryCreator {
Type ownedMountListType = new TypeToken>() {}.getType();
Type achievementsListType = new TypeToken>() {}.getType();
-
Gson gson = new GsonBuilder()
.registerTypeAdapter(taskTagClassListType, new TaskTagDeserializer())
.registerTypeAdapter(Boolean.class, new BooleanAsIntAdapter())
@@ -117,6 +118,7 @@ public class GSonFactoryCreator {
.registerTypeAdapter(WorldState.class, new WorldStateSerialization())
.registerTypeAdapter(FindUsernameResult.class, new FindUsernameResultDeserializer())
.registerTypeAdapter(Notification.class, new NotificationDeserializer())
+ .registerTypeAdapter(SocialAuthentication.class, new SocialAuthenticationDeserializer())
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
.create();
return GsonConverterFactory.create(gson);
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/events/ShowSnackbarEvent.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/events/ShowSnackbarEvent.kt
index 6f9a15ab7..cf1b44af8 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/events/ShowSnackbarEvent.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/events/ShowSnackbarEvent.kt
@@ -3,16 +3,23 @@ package com.habitrpg.android.habitica.events
import android.graphics.drawable.Drawable
import android.view.View
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.SnackbarDisplayType
+import org.greenrobot.eventbus.EventBus
/**
* Created by phillip on 26.06.17.
*/
class ShowSnackbarEvent {
- constructor(title: String, type: SnackbarDisplayType) {
+ constructor(title: String?, type: SnackbarDisplayType) {
this.title = title
this.type = type
}
+ constructor(title: String?, text: String?, type: SnackbarDisplayType) {
+ this.title = title
+ this.text = text
+ this.type = type
+ }
+
constructor()
var leftImage: Drawable? = null
@@ -23,4 +30,8 @@ class ShowSnackbarEvent {
var rightIcon: Drawable? = null
var rightTextColor = 0
var rightText: String? = null
+
+ fun post() {
+ EventBus.getDefault().post(this)
+ }
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/PendingIntent-Extensions.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/PendingIntent-Extensions.kt
new file mode 100644
index 000000000..b0840558d
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/PendingIntent-Extensions.kt
@@ -0,0 +1,13 @@
+package com.habitrpg.android.habitica.extensions
+
+import android.app.PendingIntent
+import android.content.Context
+import android.os.Build
+
+fun withImmutableFlag(flags: Int): Int {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ flags + PendingIntent.FLAG_IMMUTABLE
+ } else {
+ flags
+ }
+}
\ 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 017bd9563..07b5fd08f 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
@@ -7,6 +7,7 @@ import android.content.Intent
import android.os.Build
import androidx.preference.PreferenceManager
import com.habitrpg.android.habitica.data.TaskRepository
+import com.habitrpg.android.habitica.extensions.withImmutableFlag
import com.habitrpg.android.habitica.models.tasks.RemindersItem
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.receivers.NotificationPublisher
@@ -93,13 +94,13 @@ class TaskAlarmManager(private var context: Context, private var taskRepository:
val intentId = remindersItem.id?.hashCode() ?: 0 and 0xfffffff
// Cancel alarm if already exists
- val previousSender = PendingIntent.getBroadcast(context, intentId, intent, PendingIntent.FLAG_NO_CREATE)
+ val previousSender = PendingIntent.getBroadcast(context, intentId, intent, withImmutableFlag(PendingIntent.FLAG_NO_CREATE))
if (previousSender != null) {
previousSender.cancel()
am?.cancel(previousSender)
}
- val sender = PendingIntent.getBroadcast(context, intentId, intent, PendingIntent.FLAG_CANCEL_CURRENT)
+ val sender = PendingIntent.getBroadcast(context, intentId, intent, withImmutableFlag(PendingIntent.FLAG_CANCEL_CURRENT))
setAlarm(context, cal.timeInMillis, sender)
}
@@ -108,7 +109,7 @@ class TaskAlarmManager(private var context: Context, private var taskRepository:
val intent = Intent(context, TaskReceiver::class.java)
intent.action = remindersItem.id
val intentId = remindersItem.id?.hashCode() ?: 0 and 0xfffffff
- val sender = PendingIntent.getBroadcast(context, intentId, intent, PendingIntent.FLAG_UPDATE_CURRENT)
+ val sender = PendingIntent.getBroadcast(context, intentId, intent, withImmutableFlag(PendingIntent.FLAG_UPDATE_CURRENT))
val am = context.getSystemService(Context.ALARM_SERVICE) as? AlarmManager
sender.cancel()
am?.cancel(sender)
@@ -140,13 +141,13 @@ class TaskAlarmManager(private var context: Context, private var taskRepository:
notificationIntent.putExtra(NotificationPublisher.CHECK_DAILIES, false)
val alarmManager = context?.getSystemService(Context.ALARM_SERVICE) as? AlarmManager
- val previousSender = PendingIntent.getBroadcast(context, 0, notificationIntent, PendingIntent.FLAG_NO_CREATE)
+ val previousSender = PendingIntent.getBroadcast(context, 0, notificationIntent, withImmutableFlag(PendingIntent.FLAG_NO_CREATE))
if (previousSender != null) {
previousSender.cancel()
alarmManager?.cancel(previousSender)
}
- val pendingIntent = PendingIntent.getBroadcast(context, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT)
+ val pendingIntent = PendingIntent.getBroadcast(context, 0, notificationIntent, withImmutableFlag(PendingIntent.FLAG_UPDATE_CURRENT))
if (context != null) {
setAlarm(context, triggerTime, pendingIntent)
@@ -157,7 +158,7 @@ class TaskAlarmManager(private var context: Context, private var taskRepository:
fun removeDailyReminder(context: Context?) {
val notificationIntent = Intent(context, NotificationPublisher::class.java)
val alarmManager = context?.getSystemService(Context.ALARM_SERVICE) as? AlarmManager
- val displayIntent = PendingIntent.getBroadcast(context, 0, notificationIntent, 0)
+ val displayIntent = PendingIntent.getBroadcast(context, 0, notificationIntent, withImmutableFlag(0))
alarmManager?.cancel(displayIntent)
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/GroupActivityNotification.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/GroupActivityNotification.kt
index 2b4db0b6e..d74f03049 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/GroupActivityNotification.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/GroupActivityNotification.kt
@@ -11,6 +11,7 @@ import androidx.core.app.Person
import androidx.core.app.RemoteInput
import androidx.core.os.bundleOf
import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.extensions.withImmutableFlag
import com.habitrpg.android.habitica.receivers.LocalNotificationActionReceiver
import com.habitrpg.android.habitica.ui.helpers.EmojiParser
import java.text.SimpleDateFormat
@@ -75,7 +76,7 @@ class GroupActivityNotification(context: Context, identifier: String?) : Habitic
PendingIntent.getBroadcast(
context, groupID.hashCode(),
intent,
- PendingIntent.FLAG_UPDATE_CURRENT
+ withImmutableFlag(PendingIntent.FLAG_UPDATE_CURRENT)
)
val action: NotificationCompat.Action =
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/GuildInviteLocalNotification.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/GuildInviteLocalNotification.kt
index 80c5e0421..e830c1833 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/GuildInviteLocalNotification.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/GuildInviteLocalNotification.kt
@@ -4,6 +4,7 @@ import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.extensions.withImmutableFlag
import com.habitrpg.android.habitica.receivers.LocalNotificationActionReceiver
/**
@@ -29,7 +30,7 @@ class GuildInviteLocalNotification(context: Context, identifier: String?) : Habi
context,
groupID.hashCode(),
acceptInviteIntent,
- PendingIntent.FLAG_UPDATE_CURRENT
+ withImmutableFlag(PendingIntent.FLAG_UPDATE_CURRENT)
)
notificationBuilder.addAction(0, "Accept", pendingIntentAccept)
@@ -41,7 +42,7 @@ class GuildInviteLocalNotification(context: Context, identifier: String?) : Habi
context,
groupID.hashCode() + 1,
rejectInviteIntent,
- PendingIntent.FLAG_UPDATE_CURRENT
+ withImmutableFlag(PendingIntent.FLAG_UPDATE_CURRENT)
)
notificationBuilder.addAction(0, "Reject", pendingIntentReject)
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaLocalNotification.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaLocalNotification.kt
index f44f7d0b8..3724a31f2 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaLocalNotification.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaLocalNotification.kt
@@ -8,6 +8,7 @@ import androidx.annotation.CallSuper
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.extensions.withImmutableFlag
import com.habitrpg.android.habitica.ui.activities.MainActivity
import java.util.*
@@ -63,7 +64,7 @@ abstract class HabiticaLocalNotification(protected var context: Context, protect
context,
3000,
intent,
- PendingIntent.FLAG_UPDATE_CURRENT
+ withImmutableFlag(PendingIntent.FLAG_UPDATE_CURRENT)
)
notificationBuilder.setContentIntent(pendingIntent)
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PartyInviteLocalNotification.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PartyInviteLocalNotification.kt
index 21c0cc3f7..3e393b3a4 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PartyInviteLocalNotification.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PartyInviteLocalNotification.kt
@@ -4,6 +4,7 @@ import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.extensions.withImmutableFlag
import com.habitrpg.android.habitica.receivers.LocalNotificationActionReceiver
/**
@@ -24,7 +25,7 @@ class PartyInviteLocalNotification(context: Context, identifier: String?) : Habi
context,
groupID.hashCode(),
acceptInviteIntent,
- PendingIntent.FLAG_UPDATE_CURRENT
+ withImmutableFlag(PendingIntent.FLAG_UPDATE_CURRENT)
)
notificationBuilder.addAction(0, context.getString(R.string.accept), pendingIntentAccept)
@@ -36,7 +37,7 @@ class PartyInviteLocalNotification(context: Context, identifier: String?) : Habi
context,
groupID.hashCode() + 1,
rejectInviteIntent,
- PendingIntent.FLAG_UPDATE_CURRENT
+ withImmutableFlag(PendingIntent.FLAG_UPDATE_CURRENT)
)
notificationBuilder.addAction(0, context.getString(R.string.reject), pendingIntentReject)
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/QuestInviteLocalNotification.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/QuestInviteLocalNotification.kt
index 1c3bb5906..cdfe782d4 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/QuestInviteLocalNotification.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/QuestInviteLocalNotification.kt
@@ -3,6 +3,7 @@ package com.habitrpg.android.habitica.helpers.notifications
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
+import android.os.Build
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.receivers.LocalNotificationActionReceiver
@@ -22,11 +23,16 @@ class QuestInviteLocalNotification(context: Context, identifier: String?) : Habi
val acceptInviteIntent = Intent(context, LocalNotificationActionReceiver::class.java)
acceptInviteIntent.action = res.getString(R.string.accept_quest_invite)
acceptInviteIntent.putExtra("NOTIFICATION_ID", notificationId)
+ val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ PendingIntent.FLAG_UPDATE_CURRENT + PendingIntent.FLAG_IMMUTABLE
+ } else {
+ PendingIntent.FLAG_UPDATE_CURRENT
+ }
val pendingIntentAccept = PendingIntent.getBroadcast(
context,
3001,
acceptInviteIntent,
- PendingIntent.FLAG_UPDATE_CURRENT
+ flags
)
notificationBuilder.addAction(0, "Accept", pendingIntentAccept)
@@ -37,7 +43,7 @@ class QuestInviteLocalNotification(context: Context, identifier: String?) : Habi
context,
2001,
rejectInviteIntent,
- PendingIntent.FLAG_UPDATE_CURRENT
+ flags
)
notificationBuilder.addAction(0, "Reject", pendingIntentReject)
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/ReceivedPrivateMessageLocalNotification.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/ReceivedPrivateMessageLocalNotification.kt
index 1f432b1d1..98d2af601 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/ReceivedPrivateMessageLocalNotification.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/ReceivedPrivateMessageLocalNotification.kt
@@ -10,6 +10,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.RemoteInput
import androidx.core.os.bundleOf
import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.extensions.withImmutableFlag
import com.habitrpg.android.habitica.receivers.LocalNotificationActionReceiver
import com.habitrpg.android.habitica.ui.helpers.EmojiParser
@@ -74,7 +75,7 @@ class ReceivedPrivateMessageLocalNotification(context: Context, identifier: Stri
PendingIntent.getBroadcast(
context, senderID.hashCode(),
intent,
- PendingIntent.FLAG_UPDATE_CURRENT
+ withImmutableFlag(PendingIntent.FLAG_UPDATE_CURRENT)
)
val action: NotificationCompat.Action =
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/Authentication.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/Authentication.kt
index b5dc64574..038efbccf 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/Authentication.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/Authentication.kt
@@ -30,11 +30,11 @@ open class Authentication : RealmObject(), BaseObject {
var facebookAuthentication: SocialAuthentication? = null
val hasGoogleAuth: Boolean
- get() = googleAuthentication?.emails?.isEmpty() == false
+ get() = googleAuthentication?.emails?.isEmpty() != true
val hasAppleAuth: Boolean
- get() = appleAuthentication?.emails?.isEmpty() == false
+ get() = appleAuthentication?.emails?.isEmpty() != true
val hasFacebookAuth: Boolean
- get() = facebookAuthentication?.emails?.isEmpty() == false
+ get() = facebookAuthentication?.emails?.isEmpty() != true
var timestamps: AuthenticationTimestamps? = null
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/NotificationPublisher.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/NotificationPublisher.kt
index 5c3ba5c20..a82c14919 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/NotificationPublisher.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/NotificationPublisher.kt
@@ -14,6 +14,7 @@ import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.data.UserRepository
+import com.habitrpg.android.habitica.extensions.withImmutableFlag
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.TaskAlarmManager
import com.habitrpg.android.habitica.models.tasks.Task
@@ -126,7 +127,7 @@ class NotificationPublisher : BroadcastReceiver() {
val intent = PendingIntent.getActivity(
thisContext, 0,
- notificationIntent, 0
+ notificationIntent, withImmutableFlag(0)
)
builder.setContentIntent(intent)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskReceiver.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskReceiver.kt
index 2425fd7f9..2a0813640 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskReceiver.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskReceiver.kt
@@ -12,6 +12,7 @@ import androidx.core.app.NotificationManagerCompat
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.TaskRepository
+import com.habitrpg.android.habitica.extensions.withImmutableFlag
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.TaskAlarmManager
import com.habitrpg.android.habitica.models.tasks.Task
@@ -61,7 +62,7 @@ class TaskReceiver : BroadcastReceiver() {
HLogger.log(LogLevel.INFO, this::javaClass.name, "Create Notification")
intent.putExtra("notificationIdentifier", "task_reminder")
- val pendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), intent, 0)
+ val pendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), intent, withImmutableFlag(0))
val soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
var notificationBuilder = NotificationCompat.Builder(context, "default")
@@ -88,7 +89,7 @@ class TaskReceiver : BroadcastReceiver() {
context,
task.id.hashCode(),
completeIntent,
- PendingIntent.FLAG_UPDATE_CURRENT
+ withImmutableFlag(PendingIntent.FLAG_UPDATE_CURRENT)
)
notificationBuilder.addAction(0, context.getString(R.string.complete), pendingIntentComplete)
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt
index fd66dfb5b..482e30389 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt
@@ -33,6 +33,7 @@ import com.habitrpg.android.habitica.extensions.addOkButton
import com.habitrpg.android.habitica.extensions.updateStatusBarColor
import com.habitrpg.android.habitica.helpers.*
import com.habitrpg.android.habitica.models.auth.UserAuthResponse
+import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard
import com.habitrpg.android.habitica.ui.viewmodels.AuthenticationViewModel
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
@@ -229,9 +230,17 @@ class LoginActivity : BaseActivity() {
}
private fun handleAuthResponse(response: UserAuthResponse) {
+ viewModel.handleAuthResponse(response)
+ compositeSubscription.add(userRepository.retrieveUser(true)
+ .subscribe({
+ handleAuthResponse(it, response.newUser)
+ }, RxErrorHandler.handleEmptyError())
+ )
+ }
+
+ private fun handleAuthResponse(user: User, isNew: Boolean) {
hideProgress()
dismissKeyboard()
- viewModel.handleAuthResponse(response)
if (isRegistering) {
FirebaseAnalytics.getInstance(this).logEvent("user_registered", null)
@@ -240,7 +249,7 @@ class LoginActivity : BaseActivity() {
userRepository.retrieveUser(withTasks = true, forced = true)
.subscribe(
{
- if (response.newUser) {
+ if (isNew) {
this.startSetupActivity()
} else {
this.startMainActivity()
@@ -288,8 +297,8 @@ class LoginActivity : BaseActivity() {
private val pickAccountResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
viewModel.googleEmail = it?.data?.getStringExtra(AccountManager.KEY_ACCOUNT_NAME)
- viewModel.handleGoogleLoginResult(this, recoverFromPlayServicesErrorResult) {
- handleAuthResponse(it)
+ viewModel.handleGoogleLoginResult(this, recoverFromPlayServicesErrorResult) { user, isNew ->
+ handleAuthResponse(user, isNew)
}
}
}
@@ -297,8 +306,8 @@ class LoginActivity : BaseActivity() {
private val recoverFromPlayServicesErrorResult = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode != Activity.RESULT_CANCELED) {
- viewModel.handleGoogleLoginResult(this, null) {
- handleAuthResponse(it)
+ viewModel.handleGoogleLoginResult(this, null) { user, isNew ->
+ handleAuthResponse(user, isNew)
}
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt
index 39c62ddb6..f98319fb4 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt
@@ -8,28 +8,37 @@ import android.content.SharedPreferences
import android.os.Bundle
import android.text.InputType
import android.view.View
+import android.view.ViewGroup
import android.widget.EditText
import android.widget.LinearLayout
-import android.widget.Toast
+import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
+import androidx.core.util.PatternsCompat
+import androidx.core.widget.addTextChangedListener
+import androidx.core.widget.doAfterTextChanged
import androidx.preference.EditTextPreference
import androidx.preference.Preference
-import androidx.preference.PreferenceCategory
import com.google.android.material.textfield.TextInputLayout
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.api.HostConfig
import com.habitrpg.android.habitica.data.ApiClient
+import com.habitrpg.android.habitica.events.ShowSnackbarEvent
import com.habitrpg.android.habitica.extensions.addCancelButton
import com.habitrpg.android.habitica.extensions.dpToPx
import com.habitrpg.android.habitica.extensions.layoutInflater
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.user.User
+import com.habitrpg.android.habitica.ui.activities.FixCharacterValuesActivity
+import com.habitrpg.android.habitica.ui.helpers.KeyboardUtil
import com.habitrpg.android.habitica.ui.viewmodels.AuthenticationViewModel
import com.habitrpg.android.habitica.ui.views.ExtraLabelPreference
+import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
+import com.habitrpg.android.habitica.ui.views.ValidatingEditText
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaProgressDialog
+import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
class AccountPreferenceFragment: BasePreferencesFragment(),
@@ -52,8 +61,6 @@ class AccountPreferenceFragment: BasePreferencesFragment(),
super.onCreate(savedInstanceState)
viewModel = AuthenticationViewModel()
findPreference("confirm_username")?.isVisible = user?.flags?.verifiedUsername == false
-
- viewModel.setupFacebookLogin { viewModel.handleAuthResponse(it) }
}
override fun setupPreferences() {
@@ -74,12 +81,12 @@ class AccountPreferenceFragment: BasePreferencesFragment(),
private fun updateUserFields() {
val user = user ?: return
configurePreference(findPreference("username"), user.authentication?.localAuthentication?.username)
- configurePreference(findPreference("email"), user.authentication?.localAuthentication?.email)
+ configurePreference(findPreference("email"), user.authentication?.localAuthentication?.email ?: getString(R.string.not_set))
findPreference("confirm_username")?.isVisible = user.flags?.verifiedUsername != true
val passwordPref = findPreference("password")
if (user.authentication?.hasPassword == true) {
- passwordPref?.summary = "··········"
+ passwordPref?.summary = "✴✴✴✴✴✴✴✴✴✴✴"
passwordPref?.extraText = getString(R.string.change_password)
} else {
passwordPref?.summary = getString(R.string.not_set)
@@ -87,16 +94,17 @@ class AccountPreferenceFragment: BasePreferencesFragment(),
}
val googlePref = findPreference("google_auth")
if (user.authentication?.hasGoogleAuth == true) {
- googlePref?.summary = user.authentication?.googleAuthentication?.emails?.first()
+ googlePref?.summary = user.authentication?.googleAuthentication?.emails?.firstOrNull()
googlePref?.extraText = getString(R.string.disconnect)
googlePref?.extraTextColor = context?.let { ContextCompat.getColor(it, R.color.text_red) }
} else {
googlePref?.summary = getString(R.string.not_connected)
googlePref?.extraText = getString(R.string.connect)
+ googlePref?.extraTextColor = context?.let { ContextCompat.getColor(it, R.color.text_ternary) }
}
val applePref = findPreference("apple_auth")
- if (user.authentication?.hasGoogleAuth == true) {
- applePref?.summary = user.authentication?.appleAuthentication?.emails?.first()
+ if (user.authentication?.hasAppleAuth == true) {
+ applePref?.summary = user.authentication?.appleAuthentication?.emails?.firstOrNull()
applePref?.extraText = getString(R.string.disconnect)
applePref?.extraTextColor = context?.let { ContextCompat.getColor(it, R.color.text_red) }
} else {
@@ -104,7 +112,7 @@ class AccountPreferenceFragment: BasePreferencesFragment(),
}
val facebookPref = findPreference("facebook_auth")
if (user.authentication?.hasFacebookAuth == true) {
- facebookPref?.summary = user.authentication?.facebookAuthentication?.emails?.first()
+ facebookPref?.summary = user.authentication?.facebookAuthentication?.emails?.firstOrNull()
facebookPref?.extraText = getString(R.string.disconnect)
facebookPref?.extraTextColor = context?.let { ContextCompat.getColor(it, R.color.text_red) }
} else {
@@ -127,42 +135,38 @@ class AccountPreferenceFragment: BasePreferencesFragment(),
preference?.summary = value
}
- override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String) {
- val profileCategory = findPreference("profile") as? PreferenceCategory
- configurePreference(profileCategory?.findPreference(key), sharedPreferences?.getString(key, ""))
- if (sharedPreferences != null) {
- val newValue = sharedPreferences.getString(key, "") ?: return
- when (key) {
- "display_name" -> updateUser("profile.name", newValue, user?.profile?.name)
- "photo_url" -> updateUser("profile.imageUrl", newValue, user?.profile?.imageUrl)
- "about" -> updateUser("profile.blurb", newValue, user?.profile?.blurb)
- }
- }
- }
-
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
when(preference?.key) {
"username" -> showLoginNameDialog()
"confirm_username" -> showConfirmUsernameDialog()
- "email" -> showEmailDialog()
+ "email" -> {
+ if (user?.authentication?.hasPassword == true) {
+ showEmailDialog()
+ } else {
+ showAddPasswordDialog(true)
+ }
+ }
"password" -> {
if (user?.authentication?.hasPassword == true) {
showChangePasswordDialog()
} else {
- showAddPasswordDialog()
+ showAddPasswordDialog(true)
}
}
"UserID" -> {
copyValue(getString(R.string.SP_userID), user?.id)
return true
}
- "ApiToken" -> {
+ "APIToken" -> {
copyValue(getString(R.string.SP_APIToken_title), hostConfig.apiKey)
return true
}
+ "display_name" -> updateUser("profile.name", user?.profile?.name, getString(R.string.display_name))
+ "photo_url" -> updateUser("profile.imageUrl", user?.profile?.imageUrl, getString(R.string.photo_url))
+ "about" -> updateUser("profile.blurb", user?.profile?.blurb, getString(R.string.about))
"google_auth" -> {
if (user?.authentication?.hasGoogleAuth == true) {
- apiClient.disconnectSocial("google").subscribe({}, RxErrorHandler.handleEmptyError())
+ disconnect("google", "Google")
} else {
activity?.let {
viewModel.handleGoogleLogin(it, pickAccountResult)
@@ -171,7 +175,7 @@ class AccountPreferenceFragment: BasePreferencesFragment(),
}
"apple_auth" -> {
if (user?.authentication?.hasAppleAuth == true) {
- apiClient.disconnectSocial("apple").subscribe({}, RxErrorHandler.handleEmptyError())
+ disconnect("apple", "Apple")
} else {
viewModel.connectApple(parentFragmentManager) {
viewModel.handleAuthResponse(it)
@@ -180,17 +184,33 @@ class AccountPreferenceFragment: BasePreferencesFragment(),
}
"facebook_auth" -> {
if (user?.authentication?.hasFacebookAuth == true) {
- apiClient.disconnectSocial("facebook").subscribe({}, RxErrorHandler.handleEmptyError())
- } else {
- viewModel.handleFacebookLogin(this)
+ disconnect("facebook", "Facebook")
}
}
"reset_account" -> showAccountResetConfirmation()
"delete_account" -> showAccountDeleteConfirmation()
+ "fixCharacterValues" -> {
+ val intent = Intent(activity, FixCharacterValuesActivity::class.java)
+ activity?.startActivity(intent)
+ }
}
return super.onPreferenceTreeClick(preference)
}
+ private fun disconnect(network: String, networkName: String) {
+ context?.let { context ->
+ val dialog = HabiticaAlertDialog(context)
+ dialog.setTitle(R.string.are_you_sure)
+ dialog.addButton(R.string.disconnect, true) { _, _ ->
+ apiClient.disconnectSocial(network)
+ .flatMap { userRepository.retrieveUser(true, true) }
+ .subscribe({ displayDisconnectSuccess(networkName) }, RxErrorHandler.handleEmptyError())
+ }
+ dialog.addCancelButton()
+ dialog.show()
+ }
+ }
+
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
viewModel.onActivityResult(requestCode, resultCode, data) {
@@ -202,47 +222,71 @@ class AccountPreferenceFragment: BasePreferencesFragment(),
if (it.resultCode == Activity.RESULT_OK) {
viewModel.googleEmail = it?.data?.getStringExtra(AccountManager.KEY_ACCOUNT_NAME)
activity?.let { it1 ->
- viewModel.handleGoogleLoginResult(it1, recoverFromPlayServicesErrorResult) {
- viewModel.handleAuthResponse(it)
+ viewModel.handleGoogleLoginResult(it1, recoverFromPlayServicesErrorResult) { _, _ ->
+ displayAuthenticationSuccess(getString(R.string.google))
}
}
}
}
+ private fun displayAuthenticationSuccess(network: String) {
+ ShowSnackbarEvent(null, context?.getString(R.string.added_social_auth, network), HabiticaSnackbar.SnackbarDisplayType.SUCCESS).post()
+ }
+
+ private fun displayDisconnectSuccess(network: String) {
+ val event = ShowSnackbarEvent()
+ event.text = context?.getString(R.string.removed_social_auth, network)
+ event.type = HabiticaSnackbar.SnackbarDisplayType.SUCCESS
+ EventBus.getDefault().post(event)
+ }
+
private val recoverFromPlayServicesErrorResult = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode != Activity.RESULT_CANCELED) {
activity?.let { it1 ->
- viewModel.handleGoogleLoginResult(it1, null) {
- viewModel.handleAuthResponse(it)
+ viewModel.handleGoogleLoginResult(it1, null) { _, _ ->
+ displayAuthenticationSuccess(getString(R.string.google))
}
}
}
}
- private fun updateUser(path: String, newValue: String, oldValue: String?) {
- if (newValue != oldValue) {
- compositeSubscription.add(userRepository.updateUser(path, newValue).subscribe({}, RxErrorHandler.handleEmptyError()))
+ private fun updateUser(path: String, value: String?, title: String) {
+ showSingleEntryDialog(value, title) {
+ if (value != it) {
+ userRepository.updateUser(path, it ?: "")
+ .subscribe({}, RxErrorHandler.handleEmptyError())
+ }
}
}
private fun showChangePasswordDialog() {
val inflater = context?.layoutInflater
val view = inflater?.inflate(R.layout.dialog_edittext_change_pw, null)
- val oldPasswordEditText = view?.findViewById(R.id.editText)
- val passwordEditText = view?.findViewById(R.id.passwordEditText)
- val passwordRepeatEditText = view?.findViewById(R.id.passwordRepeatEditText)
+ val oldPasswordEditText = view?.findViewById(R.id.old_password_edit_text)
+ val passwordEditText = view?.findViewById(R.id.new_password_edit_text)
+ passwordEditText?.validator = { (it?.length ?: 0) >= 8 }
+ passwordEditText?.errorText = getString(R.string.password_too_short, 8)
+ val passwordRepeatEditText = view?.findViewById(R.id.new_password_repeat_edit_text)
+ passwordRepeatEditText?.validator = { it == passwordEditText?.text }
+ passwordRepeatEditText?.errorText = getString(R.string.password_not_matching)
context?.let { context ->
val dialog = HabiticaAlertDialog(context)
dialog.setTitle(R.string.change_password)
- dialog.addButton(R.string.change, true) { _, _ ->
- userRepository.updatePassword(oldPasswordEditText?.text.toString(), passwordEditText?.text.toString(), passwordRepeatEditText?.text.toString())
+ dialog.addButton(R.string.change, true, false, false) { dialog, _ ->
+ KeyboardUtil.dismissKeyboard(activity)
+ if (passwordEditText?.isValid != true || passwordRepeatEditText?.isValid != true) return@addButton
+ userRepository.updatePassword(oldPasswordEditText?.text ?: "",
+ passwordEditText.text ?: "",
+ passwordRepeatEditText.text ?: "")
+ .flatMap { userRepository.retrieveUser(true, true) }
.subscribe(
{
- Toast.makeText(activity, R.string.password_changed, Toast.LENGTH_SHORT).show()
+ ShowSnackbarEvent(null, context.getString(R.string.password_changed), HabiticaSnackbar.SnackbarDisplayType.SUCCESS).post()
},
RxErrorHandler.handleEmptyError()
)
+ dialog.dismiss()
}
dialog.addCancelButton()
dialog.setAdditionalContentView(view)
@@ -251,25 +295,39 @@ class AccountPreferenceFragment: BasePreferencesFragment(),
}
}
- private fun showAddPasswordDialog() {
+ private fun showAddPasswordDialog(showEmail: Boolean) {
val inflater = context?.layoutInflater
- val view = inflater?.inflate(R.layout.dialog_edittext_change_pw, null)
- val oldPasswordEditText = view?.findViewById(R.id.editText)
- oldPasswordEditText?.visibility = View.GONE
- val passwordEditText = view?.findViewById(R.id.passwordEditText)
- val passwordRepeatEditText = view?.findViewById(R.id.passwordRepeatEditText)
+ val view = inflater?.inflate(R.layout.dialog_edittext_add_local_auth, null)
+ val emailEditText = view?.findViewById(R.id.email_edit_text)
+ emailEditText?.visibility = if (showEmail) View.VISIBLE else View.GONE
+ emailEditText?.validator = { PatternsCompat.EMAIL_ADDRESS.matcher(it ?: "").matches() }
+ emailEditText?.errorText = getString(R.string.email_invalid)
+ val passwordEditText = view?.findViewById(R.id.password_edit_text)
+ passwordEditText?.validator = { (it?.length ?: 0) >= 8 }
+ passwordEditText?.errorText = getString(R.string.password_too_short, 8)
+ val passwordRepeatEditText = view?.findViewById(R.id.password_repeat_edit_text)
+ passwordRepeatEditText?.validator = { it == passwordEditText?.text }
+ passwordRepeatEditText?.errorText = getString(R.string.password_not_matching)
context?.let { context ->
val dialog = HabiticaAlertDialog(context)
- dialog.setTitle(R.string.add_password)
- dialog.addButton(R.string.add, true) { _, _ ->
- val email = user?.authentication?.findFirstSocialEmail()
- apiClient.registerUser(user?.username ?: "", email ?: "", passwordEditText?.text.toString(), passwordRepeatEditText?.text.toString())
+ if (showEmail) {
+ dialog.setTitle(R.string.add_email_and_password)
+ } else {
+ dialog.setTitle(R.string.add_password)
+ }
+ dialog.addButton(R.string.add, true, false, false) { dialog, _ ->
+ KeyboardUtil.dismissKeyboard(activity)
+ if (emailEditText?.isValid != true || passwordEditText?.isValid != true || passwordRepeatEditText?.isValid != true) return@addButton
+ val email = if (showEmail) emailEditText.text else user?.authentication?.findFirstSocialEmail()
+ apiClient.registerUser(user?.username ?: "", email ?: "", passwordEditText.text ?: "", passwordRepeatEditText?.text ?: "")
+ .flatMap { userRepository.retrieveUser(true, true) }
.subscribe(
{
- Toast.makeText(activity, R.string.password_changed, Toast.LENGTH_SHORT).show()
+ ShowSnackbarEvent(null, context.getString(R.string.password_added), HabiticaSnackbar.SnackbarDisplayType.SUCCESS).post()
},
RxErrorHandler.handleEmptyError()
)
+ dialog.dismiss()
}
dialog.addCancelButton()
dialog.setAdditionalContentView(view)
@@ -281,21 +339,27 @@ class AccountPreferenceFragment: BasePreferencesFragment(),
private fun showEmailDialog() {
val inflater = context?.layoutInflater
val view = inflater?.inflate(R.layout.dialog_edittext_confirm_pw, null)
- val emailEditText = view?.findViewById(R.id.editText)
- emailEditText?.setText(user?.authentication?.localAuthentication?.email)
+ val emailEditText = view?.findViewById(R.id.email_edit_text)
+ emailEditText?.text = user?.authentication?.localAuthentication?.email
+ emailEditText?.validator = { PatternsCompat.EMAIL_ADDRESS.matcher(it ?: "").matches() }
+ emailEditText?.errorText = getString(R.string.email_invalid)
view?.findViewById(R.id.input_layout)?.hint = context?.getString(R.string.email)
- val passwordEditText = view?.findViewById(R.id.passwordEditText)
+ val passwordEditText = view?.findViewById(R.id.password_edit_text)
context?.let { context ->
val dialog = HabiticaAlertDialog(context)
dialog.setTitle(R.string.change_email)
- dialog.addButton(R.string.change, true) { _, _ ->
- userRepository.updateEmail(emailEditText?.text.toString(), passwordEditText?.text.toString())
+ dialog.addButton(R.string.change, true, false, false) { dialog, _ ->
+ KeyboardUtil.dismissKeyboard(activity)
+ if (passwordEditText?.isValid != true || emailEditText?.isValid != true) return@addButton
+ userRepository.updateEmail(emailEditText.text.toString(), passwordEditText.text.toString())
+ .flatMap { userRepository.retrieveUser(true, true) }
.subscribe(
{
- configurePreference(findPreference("email"), emailEditText?.text.toString())
+ configurePreference(findPreference("email"), emailEditText.text.toString())
},
RxErrorHandler.handleEmptyError()
)
+ dialog.dismiss()
}
dialog.addCancelButton()
dialog.setAdditionalContentView(view)
@@ -305,22 +369,23 @@ class AccountPreferenceFragment: BasePreferencesFragment(),
}
private fun showLoginNameDialog() {
+ showSingleEntryDialog(user?.username, getString(R.string.username)) {
+ userRepository.updateLoginName(it ?: "")
+ .subscribe({}, RxErrorHandler.handleEmptyError())
+ }
+ }
+
+ private fun showSingleEntryDialog(value: String?, title: String, onChange: (String?) -> Unit) {
val inflater = context?.layoutInflater
val view = inflater?.inflate(R.layout.dialog_edittext, null)
- val loginNameEditText = view?.findViewById(R.id.editText)
- loginNameEditText?.setText(user?.authentication?.localAuthentication?.username)
- view?.findViewById(R.id.input_layout)?.hint = context?.getString(R.string.username)
+ val editText = view?.findViewById(R.id.editText)
+ editText?.setText(value)
+ view?.findViewById(R.id.input_layout)?.hint = title
context?.let { context ->
val dialog = HabiticaAlertDialog(context)
- dialog.setTitle(R.string.change_username)
+ dialog.setTitle(title)
dialog.addButton(R.string.save, true) { _, _ ->
- userRepository.updateLoginName(loginNameEditText?.text.toString())
- .subscribe(
- {
- configurePreference(findPreference("username"), loginNameEditText?.text.toString())
- },
- RxErrorHandler.handleEmptyError()
- )
+ onChange(editText?.text?.toString())
}
dialog.addCancelButton()
dialog.setAdditionalContentView(view)
@@ -408,6 +473,12 @@ class AccountPreferenceFragment: BasePreferencesFragment(),
private fun copyValue(name: String, value: CharSequence?) {
ClipData.newPlainText(name, value)
- Toast.makeText(activity, "Copied $name to clipboard.", Toast.LENGTH_SHORT).show()
+ val event = ShowSnackbarEvent()
+ event.text = context?.getString(R.string.copied_to_clipboard, name)
+ event.type = HabiticaSnackbar.SnackbarDisplayType.SUCCESS
+ EventBus.getDefault().post(event)
+ }
+
+ override fun onSharedPreferenceChanged(p0: SharedPreferences?, p1: String?) {
}
}
\ No newline at end of file
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt
index 60bfb6b01..671f5b264 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt
@@ -17,6 +17,7 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.ContentRepository
import com.habitrpg.android.habitica.events.ShowSnackbarEvent
+import com.habitrpg.android.habitica.extensions.addCancelButton
import com.habitrpg.android.habitica.helpers.*
import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager
import com.habitrpg.android.habitica.models.user.User
@@ -26,6 +27,7 @@ import com.habitrpg.android.habitica.ui.activities.FixCharacterValuesActivity
import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.android.habitica.ui.activities.PrefsActivity
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
+import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import org.greenrobot.eventbus.EventBus
import java.util.*
import javax.inject.Inject
@@ -109,8 +111,7 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare
override fun onPreferenceTreeClick(preference: Preference): Boolean {
when (preference.key) {
"logout" -> {
- context?.let { HabiticaBaseApplication.logout(it) }
- activity?.finish()
+ logout()
}
"choose_class" -> {
val bundle = Bundle()
@@ -148,19 +149,28 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare
RxErrorHandler.handleEmptyError()
)
}
- "fixCharacterValues" -> {
- val intent = Intent(activity, FixCharacterValuesActivity::class.java)
- activity?.startActivity(intent)
- }
}
return super.onPreferenceTreeClick(preference)
}
+ private fun logout() {
+ context?.let { context ->
+ val dialog = HabiticaAlertDialog(context)
+ dialog.setTitle(R.string.are_you_sure)
+ dialog.addButton(R.string.logout, true) { _, _ ->
+ HabiticaBaseApplication.logout(context)
+ activity?.finish()
+ }
+ dialog.addCancelButton()
+ dialog.show()
+ }
+ }
+
private val classSelectionResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
userRepository.retrieveUser(true, forced = true)
}
- override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
when (key) {
"use_reminder" -> {
val useReminder = sharedPreferences.getBoolean(key, false)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/AuthenticationViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/AuthenticationViewModel.kt
index 63b0da2b6..b7fb3f59e 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/AuthenticationViewModel.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/AuthenticationViewModel.kt
@@ -9,7 +9,6 @@ import android.os.Build
import androidx.activity.result.ActivityResultLauncher
import androidx.core.content.edit
import androidx.fragment.app.Fragment
-import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import com.facebook.AccessToken
import com.facebook.CallbackManager
@@ -32,12 +31,14 @@ import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.api.HostConfig
import com.habitrpg.android.habitica.data.ApiClient
+import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.extensions.addCloseButton
import com.habitrpg.android.habitica.helpers.KeyHelper
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.SignInWithAppleResult
import com.habitrpg.android.habitica.helpers.SignInWithAppleService
import com.habitrpg.android.habitica.models.auth.UserAuthResponse
+import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.proxy.AnalyticsManager
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.willowtreeapps.signinwithapplebutton.SignInWithAppleConfiguration
@@ -52,6 +53,8 @@ class AuthenticationViewModel() {
@Inject
internal lateinit var apiClient: ApiClient
@Inject
+ internal lateinit var userRepository: UserRepository
+ @Inject
internal lateinit var sharedPrefs: SharedPreferences
@Inject
internal lateinit var hostConfig: HostConfig
@@ -168,10 +171,11 @@ class AuthenticationViewModel() {
fun handleGoogleLoginResult(
activity: Activity,
recoverFromPlayServicesErrorResult: ActivityResultLauncher?,
- onSuccess: (UserAuthResponse) -> Unit
+ onSuccess: (User, Boolean) -> Unit
) {
val scopesString = Scopes.PROFILE + " " + Scopes.EMAIL
val scopes = "oauth2:$scopesString"
+ var newUser = false
compositeSubscription.add(
Flowable.defer {
try {
@@ -187,9 +191,14 @@ class AuthenticationViewModel() {
}
.subscribeOn(Schedulers.io())
.flatMap { token -> apiClient.connectSocial("google", googleEmail ?: "", token) }
+ .doOnNext {
+ newUser = it.newUser
+ handleAuthResponse(it)
+ }
+ .flatMap { userRepository.retrieveUser(true, true) }
.subscribe(
{
- onSuccess(it)
+ onSuccess(it, newUser)
},
{ throwable ->
if (recoverFromPlayServicesErrorResult == null) return@subscribe
@@ -211,26 +220,24 @@ class AuthenticationViewModel() {
recoverFromPlayServicesErrorResult: ActivityResultLauncher
) {
if (e is GooglePlayServicesAvailabilityException) {
- // The Google Play services APK is old, disabled, or not present.
- // Show a dialog created by Google Play services that allows
- // the user to update the APK
- val statusCode = e
- .connectionStatusCode
GoogleApiAvailability.getInstance()
- @Suppress("DEPRECATION")
GooglePlayServicesUtil.showErrorDialogFragment(
- statusCode,
+ e.connectionStatusCode,
activity,
+ null,
AuthenticationViewModel.REQUEST_CODE_RECOVER_FROM_PLAY_SERVICES_ERROR
) {
}
+ return
} else if (e is UserRecoverableAuthException) {
// Unable to authenticate, such as when the user has not yet granted
// the app access to the account, but the user can fix this.
// Forward the user to an activity in Google Play services.
val intent = e.intent
recoverFromPlayServicesErrorResult.launch(intent)
+ return
}
+
}
@@ -257,7 +264,6 @@ class AuthenticationViewModel() {
}
HabiticaBaseApplication.reloadUserComponent()
-
}
@Throws(Exception::class)
@@ -276,7 +282,7 @@ class AuthenticationViewModel() {
putString(user, encryptedKey)
} else {
// Something might have gone wrong with encryption, so fall back to this.
- putString("ApiToken", api)
+ putString("APIToken", api)
}
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ValidatingEditText.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ValidatingEditText.kt
new file mode 100644
index 000000000..c81274863
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ValidatingEditText.kt
@@ -0,0 +1,55 @@
+package com.habitrpg.android.habitica.ui.views
+
+import android.content.Context
+import android.text.InputType
+import android.util.AttributeSet
+import android.view.View
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.databinding.ValidatingEditTextBinding
+import com.habitrpg.android.habitica.extensions.layoutInflater
+
+class ValidatingEditText @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null
+) : LinearLayout(context, attrs) {
+
+ private var binding: ValidatingEditTextBinding = ValidatingEditTextBinding.inflate(context.layoutInflater, this)
+
+ var text: String?
+ get() = binding.editText.text?.toString()
+ set(value) = binding.editText.setText(value)
+ var errorText: String?
+ get() = binding.errorText.text?.toString()
+ set(value) {
+ binding.errorText.text = value
+ }
+
+ var validator: ((String?) -> Boolean)? = null
+
+ val isValid: Boolean
+ get() = validator?.invoke(text) == true
+
+ init {
+ orientation = VERTICAL
+ context.theme?.obtainStyledAttributes(
+ attrs,
+ R.styleable.ValidatingEditText,
+ 0, 0
+ )?.let { attributes ->
+ binding.inputLayout.hint = attributes.getString(R.styleable.ValidatingEditText_hint)
+ binding.editText.inputType = attributes.getInt(R.styleable.ValidatingEditText_android_inputType, InputType.TYPE_CLASS_TEXT)
+ }
+
+
+ binding.editText.setOnFocusChangeListener { _, isEditing ->
+ if (isEditing) return@setOnFocusChangeListener
+ if (validator?.invoke(text) == true) {
+ binding.errorText.visibility = View.GONE
+ } else {
+ binding.errorText.visibility = View.VISIBLE
+ }
+ (this.parent as? ViewGroup)?.forceLayout()
+ }
+ }
+}
\ No newline at end of file
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/utils/SocialAuthenticationDeserializer.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/utils/SocialAuthenticationDeserializer.kt
new file mode 100644
index 000000000..1024d8bd5
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/utils/SocialAuthenticationDeserializer.kt
@@ -0,0 +1,29 @@
+package com.habitrpg.android.habitica.utils
+
+import com.google.gson.JsonDeserializationContext
+import com.google.gson.JsonDeserializer
+import com.google.gson.JsonElement
+import com.google.gson.JsonParseException
+import com.habitrpg.android.habitica.extensions.getAsString
+import com.habitrpg.android.habitica.models.TutorialStep
+import com.habitrpg.android.habitica.models.user.auth.SocialAuthentication
+import java.lang.reflect.Type
+
+class SocialAuthenticationDeserializer : JsonDeserializer {
+ @Throws(JsonParseException::class)
+ override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): SocialAuthentication {
+ val authentication = SocialAuthentication()
+ val obj = json.asJsonObject
+ if (obj.has("emails")) {
+ val emailJson = obj.getAsJsonArray("emails")
+ for (entry in emailJson) {
+ if (entry.isJsonPrimitive) {
+ authentication.emails.add(entry.asString)
+ } else if (entry.isJsonObject) {
+ authentication.emails.add(entry.asJsonObject.getAsString("value"))
+ }
+ }
+ }
+ return authentication
+ }
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetFactory.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetFactory.kt
index 0344488ce..fa8842ea7 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetFactory.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetFactory.kt
@@ -169,7 +169,6 @@ class AvatarStatsWidgetFactory(
}
}
-
override fun getLoadingView() = RemoteViews(context.packageName, R.layout.widget_avatar_stats)
override fun getViewTypeCount(): Int {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetProvider.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetProvider.kt
index 25af7aec0..ad0f810c7 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetProvider.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetProvider.kt
@@ -9,6 +9,7 @@ import android.os.Bundle
import android.widget.RemoteViews
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.extensions.withImmutableFlag
import com.habitrpg.android.habitica.ui.activities.MainActivity
class AvatarStatsWidgetProvider : BaseWidgetProvider() {
@@ -37,7 +38,7 @@ class AvatarStatsWidgetProvider : BaseWidgetProvider() {
intent.data = Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))
val openAppIntent = Intent(context.applicationContext, MainActivity::class.java)
- val openApp = PendingIntent.getActivity(context, 0, openAppIntent, PendingIntent.FLAG_UPDATE_CURRENT)
+ val openApp = PendingIntent.getActivity(context, 0, openAppIntent, withImmutableFlag(PendingIntent.FLAG_UPDATE_CURRENT))
val remoteViews = RemoteViews(context.packageName, R.layout.widget_main_avatar_stats)
remoteViews.setRemoteAdapter(R.id.widget_avatar_list, intent)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/BaseWidgetProvider.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/BaseWidgetProvider.kt
index ecaa6e5b4..b29cc969c 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/BaseWidgetProvider.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/BaseWidgetProvider.kt
@@ -39,8 +39,6 @@ abstract class BaseWidgetProvider : AppWidgetProvider() {
protected var context: Context? = null
-
-
override fun onAppWidgetOptionsChanged(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int, newOptions: Bundle) {
this.context = context
val options = appWidgetManager.getAppWidgetOptions(appWidgetId)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/HabitButtonWidgetService.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/HabitButtonWidgetService.kt
index f6a6f70d0..9e2a0c23a 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/HabitButtonWidgetService.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/HabitButtonWidgetService.kt
@@ -15,6 +15,7 @@ import androidx.core.content.ContextCompat
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.TaskRepository
+import com.habitrpg.android.habitica.extensions.withImmutableFlag
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.responses.TaskDirection
import com.habitrpg.android.habitica.models.tasks.Task
@@ -107,7 +108,7 @@ class HabitButtonWidgetService : Service() {
taskIntent.putExtra(HabitButtonWidgetProvider.TASK_DIRECTION, direction)
return PendingIntent.getBroadcast(
context, widgetId + direction.hashCode(), taskIntent,
- PendingIntent.FLAG_UPDATE_CURRENT
+ withImmutableFlag(PendingIntent.FLAG_UPDATE_CURRENT)
)
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/TaskListWidgetProvider.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/TaskListWidgetProvider.kt
index 689a718e3..fab2dd367 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/TaskListWidgetProvider.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/TaskListWidgetProvider.kt
@@ -10,6 +10,7 @@ import android.widget.RemoteViews
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.TaskRepository
+import com.habitrpg.android.habitica.extensions.withImmutableFlag
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.ui.activities.MainActivity
import javax.inject.Inject
@@ -80,7 +81,7 @@ abstract class TaskListWidgetProvider : BaseWidgetProvider() {
// if the user click on the title: open App
val openAppIntent = Intent(context.applicationContext, MainActivity::class.java)
- val openApp = PendingIntent.getActivity(context, 0, openAppIntent, PendingIntent.FLAG_UPDATE_CURRENT)
+ val openApp = PendingIntent.getActivity(context, 0, openAppIntent, withImmutableFlag(PendingIntent.FLAG_UPDATE_CURRENT))
rv.setOnClickPendingIntent(R.id.widget_title, openApp)
val taskIntent = Intent(context, providerClass)
@@ -89,7 +90,7 @@ abstract class TaskListWidgetProvider : BaseWidgetProvider() {
intent.data = Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))
val toastPendingIntent = PendingIntent.getBroadcast(
context, 0, taskIntent,
- PendingIntent.FLAG_UPDATE_CURRENT
+ withImmutableFlag(PendingIntent.FLAG_UPDATE_CURRENT)
)
rv.setPendingIntentTemplate(R.id.list_view, toastPendingIntent)
diff --git a/build.gradle b/build.gradle
index ffe6ad3b3..51a01feef 100644
--- a/build.gradle
+++ b/build.gradle
@@ -13,7 +13,7 @@ buildscript {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
classpath 'com.google.gms:google-services:4.3.10'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.0'
- classpath "io.realm:realm-gradle-plugin:10.8.0"
+ classpath "io.realm:realm-gradle-plugin:10.8.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.18.1"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5"
diff --git a/shared/build.gradle b/shared/build.gradle
index 411baeefb..bd0129fec 100644
--- a/shared/build.gradle
+++ b/shared/build.gradle
@@ -3,12 +3,11 @@ apply plugin: 'kotlin-multiplatform'
apply plugin: 'kotlin-kapt'
android {
- compileSdkVersion 30
+ compileSdkVersion 31
defaultConfig {
-
minSdkVersion 21
- targetSdkVersion 30
+ targetSdkVersion 31
}
buildTypes {
release {