Various settings fixes

This commit is contained in:
Phillip Thelen 2021-11-19 11:42:49 +01:00
parent af01b9738a
commit ea6adb6f16
40 changed files with 523 additions and 272 deletions

View file

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

View file

@ -1,55 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.textfield.TextInputLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingHorizontal="@dimen/spacing_medium">
<com.habitrpg.android.habitica.ui.views.ValidatingEditText
android:id="@+id/email_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/TextInputLayoutAppearanceTheme"
style="@style/TextInputLayoutAppearance"
android:hint="@string/email">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/emailTitleTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textEmailAddress"
android:paddingBottom="16dp"
android:paddingTop="16dp"
android:paddingStart="@dimen/spacing_large"
android:paddingEnd="@dimen/spacing_large" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:inputType="textEmailAddress"
app:hint="@string/email" />
<com.habitrpg.android.habitica.ui.views.ValidatingEditText
android:id="@+id/password_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/TextInputLayoutAppearanceTheme"
style="@style/TextInputLayoutAppearance"
android:hint="@string/password"
android:layout_marginTop="@dimen/content_section_spacing">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/passwordEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:paddingBottom="16dp"
android:paddingTop="16dp"
android:paddingStart="@dimen/spacing_large"
android:paddingEnd="@dimen/spacing_large"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:inputType="textPassword"
app:hint="@string/new_password"
android:layout_marginTop="@dimen/content_section_spacing" />
<com.habitrpg.android.habitica.ui.views.ValidatingEditText
android:id="@+id/password_repeat_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/TextInputLayoutAppearanceTheme"
style="@style/TextInputLayoutAppearance"
android:hint="@string/confirmpassword"
android:layout_marginTop="@dimen/content_section_spacing">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/passwordRepeatEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:paddingBottom="16dp"
android:paddingTop="16dp"
android:paddingStart="@dimen/spacing_large"
android:paddingEnd="@dimen/spacing_large" />
</com.google.android.material.textfield.TextInputLayout>
android:inputType="textPassword"
app:hint="@string/new_password_repeat"
android:layout_marginTop="@dimen/content_section_spacing" />
</LinearLayout>

View file

@ -1,43 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.textfield.TextInputLayout
<com.habitrpg.android.habitica.ui.views.ValidatingEditText
android:id="@+id/old_password_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/old_password"
android:theme="@style/TextInputLayoutAppearanceTheme"
style="@style/TextInputLayoutAppearance">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:inputType="textPassword"
app:hint="@string/old_password"/>
<com.habitrpg.android.habitica.ui.views.ValidatingEditText
android:id="@+id/new_password_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/TextInputLayoutAppearanceTheme"
style="@style/TextInputLayoutAppearance"
android:hint="@string/new_password"
android:layout_marginTop="@dimen/content_section_spacing">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/passwordEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:inputType="textPassword"
app:hint="@string/new_password"
android:layout_marginTop="@dimen/content_section_spacing" />
<com.habitrpg.android.habitica.ui.views.ValidatingEditText
android:id="@+id/new_password_repeat_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/TextInputLayoutAppearanceTheme"
style="@style/TextInputLayoutAppearance"
android:hint="@string/new_password_repeat"
android:layout_marginTop="@dimen/content_section_spacing">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/passwordRepeatEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
android:inputType="textPassword"
app:hint="@string/new_password_repeat"
android:layout_marginTop="@dimen/content_section_spacing" />
</LinearLayout>

View file

@ -1,32 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/input_layout"
<com.habitrpg.android.habitica.ui.views.ValidatingEditText
android:id="@+id/email_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/TextInputLayoutAppearanceTheme"
style="@style/TextInputLayoutAppearance">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/spacing_large"
android:paddingEnd="@dimen/spacing_large" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:inputType="textEmailAddress"
app:hint="@string/email" />
<com.habitrpg.android.habitica.ui.views.ValidatingEditText
android:id="@+id/password_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password"
android:theme="@style/TextInputLayoutAppearanceTheme"
style="@style/TextInputLayoutAppearance"
android:layout_marginTop="@dimen/content_section_spacing">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/passwordEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/spacing_large"
android:paddingEnd="@dimen/spacing_large" />
</com.google.android.material.textfield.TextInputLayout>
android:inputType="textPassword"
app:hint="@string/password"
android:layout_marginTop="@dimen/content_section_spacing" />
</LinearLayout>

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Layout for a visually child-like Preference in a PreferenceActivity. -->
<!--suppress AndroidDomInspection -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:baselineAligned="false"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeight"
android:paddingEnd="16dip"
android:paddingStart="16dip"
android:orientation="horizontal">
<RelativeLayout
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_marginBottom="6dip"
android:layout_marginStart="16dip"
android:layout_marginEnd="6dip"
android:layout_marginTop="6dip"
android:layout_weight="1" >
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="@color/text_red"
tools:text="Title"/>
<TextView
android:id="@android:id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignStart="@android:id/title"
android:layout_below="@android:id/title"
android:maxLines="4"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary" />
</RelativeLayout>
<LinearLayout android:id="@android:id/widget_frame"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="vertical"
android:paddingStart="16dp" />
</LinearLayout>

View file

@ -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" />
</RelativeLayout>
</LinearLayout>

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:parentTag="LinearLayout"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/TextInputLayoutAppearanceTheme"
style="@style/TextInputLayoutAppearance">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textEmailAddress" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/error_text"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Caption2.Regular"
android:layout_marginTop="@dimen/spacing_small"
android:textColor="@color/text_red"
android:visibility="gone"
tools:text="Error text"
tools:visibility="visible"/>
</merge>

View file

@ -10,7 +10,7 @@
<color name="text_inverted">@color/gray_10</color>
<color name="text_brand">@color/brand_400</color>
<color name="text_brand_neon">@color/brand_500</color>
<color name="text_red">@color/red_10</color>
<color name="text_red">@color/red_100</color>
<color name="text_orange">@color/orange_100</color>
<color name="text_yellow">@color/yellow_100</color>
<color name="text_green">@color/green_100</color>

View file

@ -1147,7 +1147,7 @@
<string name="leave_guild_confirmation">Вы уверены, что хотите покинуть команду\?</string>
<string name="report_explanation">Жалуйтесь ** только ** на те публикации, которые нарушают [Принципы сообщества] (https://habitica.com/static/community-guidelines) и / или [Условия предоставления услуг] (https://habitica.com/static/terms ). Ложное сообщение о нарушении может привести к наказанию.</string>
<string name="gift_gems_subtitle">Выберите ниже набор самоцветов, который хотите подарить!</string>
<string name="available_for">Доступно для %@</string>
<string name="available_for">Доступно для %s</string>
<string name="reloaded_content">Обновить содержимое</string>
<string name="rejoin_party">Вы не сможете снова присоединиться к этой команде, пока вас не пригласят.</string>
<string name="leave_party_challenges_confirmation">Вы хотите продолжить участвовать в испытании, покинув команду\?</string>

View file

@ -147,4 +147,8 @@
<attr name="dayTextColor" format="color" />
<attr name="nightTextColor" format="color" />
</declare-styleable>
<declare-styleable name="ValidatingEditText">
<attr name="hint" format="string" />
<attr name="android:inputType" />
</declare-styleable>
</resources>

View file

@ -161,7 +161,7 @@
<color name="text_inverted">@color/gray_700</color>
<color name="text_brand">@color/brand_300</color>
<color name="text_brand_neon">@color/brand_400</color>
<color name="text_red">@color/red_10</color>
<color name="text_red">@color/maroon_100</color>
<color name="text_orange">@color/orange_10</color>
<color name="text_yellow">@color/yellow_10</color>
<color name="text_green">@color/green_10</color>

View file

@ -53,7 +53,7 @@
<dimen name="row_title_size">16sp</dimen>
<dimen name="row_text_size">14sp</dimen>
<dimen name="row_title_bottommargin">2dp</dimen>
<dimen name="pet_width">84dp</dimen>
<dimen name="pet_width">88dp</dimen>
<dimen name="mount_width">120dp</dimen>
<dimen name="bottom_menu_padding">20dp</dimen>
<dimen name="pet_image_width">68dp</dimen>

View file

@ -1196,4 +1196,12 @@
<string name="connect">Connect</string>
<string name="disconnect">Disconnect</string>
<string name="add">Add</string>
<string name="apitoken_summary">Copy Token. Be careful, this is a password!</string>
<string name="added_social_auth">Added %s authentication</string>
<string name="copied_to_clipboard">Copied %s to clipboard</string>
<string name="removed_social_auth">Disconnected %s</string>
<string name="password_added">Password saved</string>
<string name="add_email_and_password">Add Email and Password</string>
<string name="password_not_matching">Password needs to be typed correctly twice</string>
<string name="email_invalid">Invalid Email address</string>
</resources>

View file

@ -63,23 +63,20 @@
android:title="@string/public_profile"
android:key="public_profile"
android:layout="@layout/preference_category">
<EditTextPreference
<Preference
android:key="display_name"
android:layout="@layout/preference_child_summary"
android:negativeButtonText="@string/cancel"
android:positiveButtonText="@string/save"
android:persistent="false"
android:title="@string/display_name" />
<EditTextPreference
<Preference
android:key="about"
android:layout="@layout/preference_child_summary"
android:negativeButtonText="@string/cancel"
android:positiveButtonText="@string/save"
android:persistent="false"
android:title="@string/about_me" />
<EditTextPreference
<Preference
android:key="photo_url"
android:layout="@layout/preference_child_summary"
android:negativeButtonText="@string/cancel"
android:positiveButtonText="@string/save"
android:persistent="false"
android:title="@string/photo_url" />
</PreferenceCategory>
<PreferenceCategory
@ -87,21 +84,23 @@
android:key="api"
android:layout="@layout/preference_category">
<Preference
android:key="user_id"
android:key="@string/SP_userID"
android:title="@string/SP_userID_title"
android:selectable="true"
android:persistent="false"
android:shouldDisableView="false"
android:summary="@string/SP_userID_summary"
android:layout="@layout/preference_child_summary"/>
<Preference
android:key="api_token"
android:key="@string/SP_APIToken"
android:title="@string/SP_APIToken_title"
android:selectable="true"
android:persistent="false"
android:shouldDisableView="false"
android:summary="@string/SP_APIToken_summary"
android:summary="@string/apitoken_summary"
android:layout="@layout/preference_child_summary"/>
<Preference android:title="@string/fix_character_values"
app:key="fixCharacterValues"
android:layout="@layout/preference_child_summary"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/danger_zone"
@ -109,11 +108,11 @@
<Preference android:title="@string/reset_account"
android:persistent="false"
android:key="reset_account"
android:layout="@layout/preference_child_summary" />
android:layout="@layout/preference_child_summary_danger" />
<Preference android:title="@string/delete_account"
android:persistent="false"
android:key="delete_account"
android:layout="@layout/preference_child_summary" />
android:layout="@layout/preference_child_summary_danger" />
</PreferenceCategory>
</PreferenceScreen>
@ -122,13 +121,9 @@
tools:title="Change Class"
android:layout="@layout/preference_child_summary"
app:isPreferenceVisible="false"/>
<Preference android:title="@string/fix_character_values"
app:key="fixCharacterValues"
android:layout="@layout/preference_child_summary"/>
<Preference android:title="@string/logout"
android:key="logout"
android:summary="@string/logout_description"
android:layout="@layout/preference_child_summary"/>
android:layout="@layout/preference_child_summary_danger"/>
</PreferenceCategory>

View file

@ -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<RealmList<OwnedMount>>() {}.getType();
Type achievementsListType = new TypeToken<List<Achievement>>() {}.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);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<Preference>("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<Preference>("confirm_username")?.isVisible = user.flags?.verifiedUsername != true
val passwordPref = findPreference<ExtraLabelPreference>("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<ExtraLabelPreference>("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<ExtraLabelPreference>("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<ExtraLabelPreference>("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<EditText>(R.id.editText)
val passwordEditText = view?.findViewById<EditText>(R.id.passwordEditText)
val passwordRepeatEditText = view?.findViewById<EditText>(R.id.passwordRepeatEditText)
val oldPasswordEditText = view?.findViewById<ValidatingEditText>(R.id.old_password_edit_text)
val passwordEditText = view?.findViewById<ValidatingEditText>(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<ValidatingEditText>(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<EditText>(R.id.editText)
oldPasswordEditText?.visibility = View.GONE
val passwordEditText = view?.findViewById<EditText>(R.id.passwordEditText)
val passwordRepeatEditText = view?.findViewById<EditText>(R.id.passwordRepeatEditText)
val view = inflater?.inflate(R.layout.dialog_edittext_add_local_auth, null)
val emailEditText = view?.findViewById<ValidatingEditText>(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<ValidatingEditText>(R.id.password_edit_text)
passwordEditText?.validator = { (it?.length ?: 0) >= 8 }
passwordEditText?.errorText = getString(R.string.password_too_short, 8)
val passwordRepeatEditText = view?.findViewById<ValidatingEditText>(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<EditText>(R.id.editText)
emailEditText?.setText(user?.authentication?.localAuthentication?.email)
val emailEditText = view?.findViewById<ValidatingEditText>(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<TextInputLayout>(R.id.input_layout)?.hint = context?.getString(R.string.email)
val passwordEditText = view?.findViewById<EditText>(R.id.passwordEditText)
val passwordEditText = view?.findViewById<ValidatingEditText>(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<EditText>(R.id.editText)
loginNameEditText?.setText(user?.authentication?.localAuthentication?.username)
view?.findViewById<TextInputLayout>(R.id.input_layout)?.hint = context?.getString(R.string.username)
val editText = view?.findViewById<EditText>(R.id.editText)
editText?.setText(value)
view?.findViewById<TextInputLayout>(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?) {
}
}

View file

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

View file

@ -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<Intent>?,
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<Intent>
) {
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)
}
}
}

View file

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

View file

@ -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<SocialAuthentication> {
@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
}
}

View file

@ -169,7 +169,6 @@ class AvatarStatsWidgetFactory(
}
}
override fun getLoadingView() = RemoteViews(context.packageName, R.layout.widget_avatar_stats)
override fun getViewTypeCount(): Int {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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