Merge branch 'version/3.6' into Fixes#1523
|
|
@ -30,6 +30,9 @@
|
|||
<meta-data
|
||||
android:name="firebase_performance_logcat_enabled"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.ads.APPLICATION_ID"
|
||||
android:value="ca-app-pub-3940256099942544~3347511713"/>
|
||||
<activity
|
||||
android:name=".ui.activities.MainActivity"
|
||||
android:label="@string/app_name"
|
||||
|
|
@ -67,6 +70,13 @@
|
|||
android:pathPattern="/settings/.*"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.activities.ArmoireActivity"
|
||||
android:parentActivityName=".ui.activities.MainActivity"
|
||||
android:label="@string/armoire"
|
||||
android:screenOrientation="unspecified"
|
||||
tools:ignore="UnusedAttribute">
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.activities.NotificationsActivity"
|
||||
android:parentActivityName=".ui.activities.MainActivity"
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ buildscript {
|
|||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.1.2'
|
||||
classpath 'com.android.tools.build:gradle:7.1.3'
|
||||
classpath 'net.sourceforge.pmd:pmd-java:5.5.3'
|
||||
}
|
||||
}
|
||||
|
|
@ -121,6 +121,7 @@ dependencies {
|
|||
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-ads:20.6.0'
|
||||
implementation 'com.google.android.gms:play-services-auth:20.1.0'
|
||||
implementation 'com.nex3z:flow-layout:1.2.2'
|
||||
|
||||
|
|
@ -128,8 +129,8 @@ dependencies {
|
|||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1"
|
||||
implementation "androidx.lifecycle:lifecycle-common-java8:2.4.1"
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.4.1'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.4.1'
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.4.2'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.4.2'
|
||||
implementation "androidx.fragment:fragment-ktx:1.4.1"
|
||||
implementation "androidx.paging:paging-runtime-ktx:3.1.1"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0'
|
||||
|
|
@ -144,7 +145,7 @@ dependencies {
|
|||
attribute(Bundling.BUNDLING_ATTRIBUTE, getObjects().named(Bundling, Bundling.EXTERNAL))
|
||||
}
|
||||
}
|
||||
androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.6.10"
|
||||
androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.6.20"
|
||||
}
|
||||
|
||||
android {
|
||||
|
|
@ -165,8 +166,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 3274
|
||||
versionName "3.5.1.3"
|
||||
versionCode 3320
|
||||
versionName "3.6"
|
||||
|
||||
targetSdkVersion 32
|
||||
|
||||
|
|
@ -178,7 +179,6 @@ android {
|
|||
viewBinding true
|
||||
}
|
||||
|
||||
|
||||
signingConfigs {
|
||||
release
|
||||
}
|
||||
|
|
@ -386,14 +386,6 @@ jacoco {
|
|||
toolVersion = "0.8.7"
|
||||
}
|
||||
|
||||
// packages to exclude for example generated classes, R class and models package, add all packages that you wish to exclude from test coverage
|
||||
def fileFilter = [
|
||||
'**/*$ViewInjector*.*','**/*$ViewBinder*.*', '**/HabiticaIcons*.*', '**/DeviceName.*', '**/databinding/*Binding.*',
|
||||
'**/R.class', '**/R.styleable', '**/R$*.class', '**/BuildConfig.*', '**/EmojiMap.*',
|
||||
'**/Manifest*.*', 'android/**/*.*', '**/*RealmProxy*.*', '**/io/realm/*']
|
||||
def debugTree = fileTree(dir: "${buildDir}/intermediates/asm_instrumented_project_classes/prodDebug", excludes: fileFilter)
|
||||
def mainSrc = "${project.projectDir}/src/main/java"
|
||||
|
||||
task ktlint(type: JavaExec, group: "verification") {
|
||||
description = "Check Kotlin code style."
|
||||
classpath = configurations.ktlint
|
||||
|
|
|
|||
6
Habitica/res/color/text_input_box_stroke.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="?attr/colorPrimary" android:state_focused="true"/>
|
||||
<item android:color="?attr/colorPrimary" android:state_hovered="true"/>
|
||||
<item android:color="?attr/colorPrimary"/>
|
||||
</selector>
|
||||
BIN
Habitica/res/drawable-hdpi/armoire_background.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
Habitica/res/drawable-hdpi/armoire_circle.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
Habitica/res/drawable-hdpi/armoire_experience.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
6
Habitica/res/drawable-night/armoire_gold_background.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="#4DFEDEAD" />
|
||||
<corners android:radius="20dip"/>
|
||||
<padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />
|
||||
</shape>
|
||||
BIN
Habitica/res/drawable-xhdpi/armoire_background.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
Habitica/res/drawable-xhdpi/armoire_circle.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
Habitica/res/drawable-xhdpi/armoire_experience.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
Habitica/res/drawable-xxhdpi/armoire_background.png
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
Habitica/res/drawable-xxhdpi/armoire_circle.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
Habitica/res/drawable-xxhdpi/armoire_experience.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
BIN
Habitica/res/drawable-xxxhdpi/armoire_background.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
Habitica/res/drawable-xxxhdpi/armoire_circle.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
Habitica/res/drawable-xxxhdpi/armoire_experience.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
27
Habitica/res/drawable/ad_button_background.xml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<item>
|
||||
<!-- create gradient you want to use with the angle you want to use -->
|
||||
<shape android:shape="rectangle" >
|
||||
<gradient
|
||||
android:angle="0"
|
||||
android:startColor="@color/green_100"
|
||||
android:endColor="@color/green_500" />
|
||||
<corners android:radius="8dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item
|
||||
android:bottom="3dp"
|
||||
android:left="3dp"
|
||||
android:right="3dp"
|
||||
android:top="3dp">
|
||||
<shape
|
||||
android:shape="rectangle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<solid android:color="@color/brand_400" />
|
||||
<corners android:radius="6dp" />
|
||||
</shape>
|
||||
<ripple android:color="@color/white" />
|
||||
</item>
|
||||
</layer-list>
|
||||
7
Habitica/res/drawable/ad_button_background_disabled.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape android:shape="rectangle"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<stroke android:color="@color/brand_500" android:width="3dp" />
|
||||
<corners android:radius="8dp" />
|
||||
|
||||
</shape>
|
||||
6
Habitica/res/drawable/armoire_gold_background.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="#66FEDEAD"/>
|
||||
<corners android:radius="20dip"/>
|
||||
<padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />
|
||||
</shape>
|
||||
|
|
@ -1,10 +1,8 @@
|
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<solid android:color="@color/transparent" />
|
||||
<corners android:radius="@dimen/rounded_button_radius" />
|
||||
<stroke
|
||||
android:width="0.5dip"
|
||||
android:color="#1f000000"/>
|
||||
|
||||
</shape>
|
||||
|
|
|
|||
5
Habitica/res/drawable/bottom_sheet_background.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<solid android:color="@color/content_background" />
|
||||
<corners android:topLeftRadius="@dimen/bottom_sheet_radius" android:topRightRadius="@dimen/bottom_sheet_radius" />
|
||||
</shape>
|
||||
|
|
@ -2,4 +2,5 @@
|
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/window_background" />
|
||||
<corners android:topLeftRadius="@dimen/bottom_sheet_radius" android:topRightRadius="@dimen/bottom_sheet_radius" />
|
||||
</shape>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="?colorAccent"/>
|
||||
<corners android:radius="20dip"/>
|
||||
<corners android:radius="8dip"/>
|
||||
<padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />
|
||||
</shape>
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/content_background"/>
|
||||
<corners android:radius="20dip"/>
|
||||
<stroke android:color="@color/separator" android:width="1dp" />
|
||||
<padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />
|
||||
<solid android:color="@color/offset_background"/>
|
||||
<corners android:radius="8dip"/>
|
||||
</shape>
|
||||
6
Habitica/res/drawable/pill_bg_yellow_500.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/yellow_500"/>
|
||||
<corners android:radius="20dip"/>
|
||||
<padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />
|
||||
</shape>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_checked="true" android:color="@color/white"/>
|
||||
<item android:color="@color/days_gray"/>
|
||||
<item android:color="@color/text_dimmed"/>
|
||||
</selector>
|
||||
129
Habitica/res/layout/activity_armoire.xml
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:background="@color/content_background">
|
||||
<FrameLayout
|
||||
android:id="@+id/confetti_anchor"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal"
|
||||
android:paddingTop="19dp">
|
||||
|
||||
<com.habitrpg.android.habitica.ui.views.CurrencyView
|
||||
android:id="@+id/gold_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="30dp"
|
||||
android:background="@drawable/armoire_gold_background"
|
||||
android:gravity="center"
|
||||
android:paddingStart="6dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:textStyle="bold"
|
||||
android:textSize="20sp"
|
||||
tools:text="118"/>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="165dp"
|
||||
android:layout_height="158dp"
|
||||
android:background="@drawable/armoire_circle"
|
||||
android:layout_marginTop="23dp"
|
||||
android:layout_marginBottom="23dp">
|
||||
<ImageView
|
||||
android:id="@+id/icon_view"
|
||||
android:layout_width="136dp"
|
||||
android:layout_height="136dp"
|
||||
android:layout_gravity="center"/>
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="+21 Experience"
|
||||
android:textStyle="bold"
|
||||
android:textSize="28sp"
|
||||
android:textColor="@color/text_primary"/>
|
||||
<TextView
|
||||
android:id="@+id/subtitle_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="You wrestle with the Armoire and gain Experience. Take that!"
|
||||
android:layout_marginHorizontal="50dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textSize="20sp"
|
||||
/>
|
||||
<Space
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/armoire_background"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:paddingHorizontal="12dp"
|
||||
android:paddingTop="28dp">
|
||||
<TextView
|
||||
android:id="@+id/equipment_count_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/equipment_remaining"
|
||||
android:textColor="@color/white"
|
||||
style="@style/Headline"
|
||||
android:textStyle="bold"/>
|
||||
<TextView
|
||||
android:id="@+id/no_equipment_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/new_pieces_added_every_month"
|
||||
style="@style/Body2"
|
||||
android:textColor="@color/white"/>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp">
|
||||
<Button
|
||||
android:id="@+id/equip_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="60dp"
|
||||
android:text="@string/equip"
|
||||
android:textStyle="bold"
|
||||
style="@style/HabiticaButton.White"
|
||||
android:layout_marginEnd="12dp"/>
|
||||
<Button
|
||||
android:id="@+id/close_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="60dp"
|
||||
android:text="@string/close"
|
||||
android:textStyle="bold"
|
||||
style="@style/HabiticaButton.White"/>
|
||||
</LinearLayout>
|
||||
|
||||
<com.habitrpg.android.habitica.ui.views.ads.AdButton
|
||||
android:id="@+id/ad_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
app:text="@string/watch_ad_to_open"
|
||||
android:layout_marginTop="4dp"
|
||||
app:currency="gold" />
|
||||
<TextView
|
||||
android:id="@+id/drop_rate_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/armoire_drop_rates"
|
||||
android:textColor="@color/brand_500"
|
||||
android:layout_marginTop="22dp"
|
||||
style="@style/Body2"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
|
@ -74,7 +74,7 @@
|
|||
android:layout_toStartOf="@id/toolbar_accessory_container"
|
||||
android:layout_alignParentStart="true"
|
||||
style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"
|
||||
tools:text="Habitica"/>
|
||||
tools:text="Habitica" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/toolbar_accessory_container"
|
||||
|
|
@ -103,17 +103,29 @@
|
|||
app:tabGravity="fill"
|
||||
app:tabIndicatorColor="?colorPrimary"
|
||||
app:tabMode="fixed" />
|
||||
<TextView
|
||||
android:id="@+id/connection_issue_textview"
|
||||
<FrameLayout
|
||||
android:id="@+id/connection_issue_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/white"
|
||||
android:background="@color/text_primary"
|
||||
android:paddingVertical="2dp"
|
||||
android:paddingHorizontal="@dimen/spacing_medium"
|
||||
android:background="@color/error_banner_background"
|
||||
tools:visibility="visible"
|
||||
android:visibility="gone"
|
||||
>
|
||||
<TextView
|
||||
android:id="@+id/connection_issue_textview"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/maroon_500"
|
||||
android:textSize="14sp"
|
||||
android:gravity="center_horizontal"
|
||||
android:padding="@dimen/spacing_small"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/internal_error_api"
|
||||
android:visibility="gone"/>
|
||||
app:drawableStartCompat="@drawable/ic_warning_black"
|
||||
app:drawableTint="@color/maroon_500"
|
||||
android:drawablePadding="4dp"/>
|
||||
</FrameLayout>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<RelativeLayout
|
||||
|
|
@ -127,6 +139,7 @@
|
|||
android:layout_height="400dp"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:paddingBottom="50dp"/>
|
||||
|
||||
<com.habitrpg.android.habitica.ui.views.navigation.HabiticaBottomNavigationView
|
||||
android:id="@+id/bottom_navigation"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
|
|
@ -45,7 +45,8 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/TaskFormTextInputLayoutAppearance"
|
||||
app:boxStrokeColor="?attr/colorPrimary"
|
||||
app:boxStrokeWidth="3dp"
|
||||
app:boxStrokeWidthFocused="1dp"
|
||||
app:hintTextColor="?colorPrimaryText"
|
||||
android:backgroundTint="?attr/colorPrimaryText"
|
||||
android:hint="@string/task_title"
|
||||
|
|
@ -59,10 +60,11 @@
|
|||
</com.google.android.material.textfield.TextInputLayout>
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/notes_input_layout"
|
||||
style="@style/TaskFormTextInputLayoutAppearance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:boxStrokeColor="?attr/colorPrimary"
|
||||
style="@style/TaskFormTextInputLayoutAppearance"
|
||||
app:boxStrokeWidth="3dp"
|
||||
app:boxStrokeWidthFocused="2dp"
|
||||
android:backgroundTint="?attr/colorPrimaryText"
|
||||
app:hintTextColor="?colorPrimaryText"
|
||||
android:hint="@string/notes"
|
||||
|
|
|
|||
27
Habitica/res/layout/ad_button.xml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:parentTag="android.widget.LinearLayout">
|
||||
<TextView
|
||||
android:id="@+id/text_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textStyle="bold"
|
||||
android:textSize="16sp" />
|
||||
<com.habitrpg.android.habitica.ui.views.CurrencyView
|
||||
android:id="@+id/currency_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:currency="gold"
|
||||
android:layout_marginStart="24dp" />
|
||||
<ProgressBar
|
||||
android:id="@+id/loading_indicator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:indeterminateBehavior="cycle"
|
||||
android:indeterminateTint="@color/white"/>
|
||||
</merge>
|
||||
60
Habitica/res/layout/armoire_drop_rate_dialog.xml
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginHorizontal="@dimen/spacing_xlarge"
|
||||
android:layout_marginVertical="@dimen/spacing_large">
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/enchanted_armoire_drop_rates"
|
||||
style="@style/Title1"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@color/text_primary"
|
||||
android:layout_marginBottom="18dp"
|
||||
/>
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/text_primary"
|
||||
android:text="@string/armoire_rate_equipment_title"
|
||||
style="@style/Subheader1" />
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:text="@string/armoire_rate_equipment_description"
|
||||
style="@style/Caption2"
|
||||
android:layout_marginBottom="12dp"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/text_primary"
|
||||
android:text="@string/armoire_rate_food_title"
|
||||
style="@style/Subheader1" />
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:text="@string/armoire_rate_food_description"
|
||||
style="@style/Caption2"
|
||||
android:layout_marginBottom="12dp"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/text_primary"
|
||||
android:text="@string/armoire_rate_experience_title"
|
||||
style="@style/Subheader1" />
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:text="@string/armoire_rate_experience_description"
|
||||
style="@style/Caption2"
|
||||
/>
|
||||
</LinearLayout>
|
||||
203
Habitica/res/layout/bottom_sheet_backgrounds_filter.xml
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:paddingStart="@dimen/bottom_sheet_inset"
|
||||
android:paddingEnd="@dimen/bottom_sheet_inset">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/spacing_large">
|
||||
|
||||
<TextView
|
||||
style="@style/Headline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/background_filters"
|
||||
android:textColor="@color/text_ternary"
|
||||
android:layout_gravity="center_vertical"/>
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content" />
|
||||
<Button
|
||||
android:id="@+id/clear_button"
|
||||
style="@style/Body1_Button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:text="@string/clear"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="16sp"
|
||||
android:gravity="end|center_vertical"
|
||||
android:paddingEnd="0dp"
|
||||
android:paddingStart="0dp"/>
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:id="@+id/task_type_title"
|
||||
style="@style/Caption3"
|
||||
android:textColor="@color/text_quad"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/show_me"
|
||||
android:textAllCaps="true"/>
|
||||
<RadioGroup
|
||||
android:id="@+id/show_me_wrapper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginBottom="28dp">
|
||||
<RadioButton
|
||||
android:id="@+id/show_all_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/all"
|
||||
style="@style/TaskFilterRadioButton"
|
||||
android:checked="true"
|
||||
/>
|
||||
<RadioButton
|
||||
android:id="@+id/show_purchased_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/purchased"
|
||||
style="@style/TaskFilterRadioButton"
|
||||
android:layout_marginStart="8dp" />
|
||||
</RadioGroup>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sort_by_title"
|
||||
style="@style/Caption3"
|
||||
android:textColor="@color/text_quad"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/sort_by"
|
||||
android:textAllCaps="true"/>
|
||||
<RadioGroup
|
||||
android:id="@+id/sort_by_wrapper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginBottom="28dp">
|
||||
<RadioButton
|
||||
android:id="@+id/newest_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/newest"
|
||||
style="@style/TaskFilterRadioButton"
|
||||
android:checked="true"
|
||||
/>
|
||||
<RadioButton
|
||||
android:id="@+id/oldest_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/oldest"
|
||||
style="@style/TaskFilterRadioButton"
|
||||
android:layout_marginStart="8dp" />
|
||||
</RadioGroup>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/month_released_title"
|
||||
style="@style/Caption3"
|
||||
android:textColor="@color/text_quad"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/sort_by"
|
||||
android:textAllCaps="true"/>
|
||||
<LinearLayout
|
||||
android:id="@+id/month_released_wrapper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginBottom="24dp">
|
||||
<CheckBox
|
||||
android:id="@+id/january_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:text="@string/january"
|
||||
android:layout_marginStart="-6dp"
|
||||
/>
|
||||
<CheckBox
|
||||
android:id="@+id/febuary_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:text="@string/febuary"
|
||||
android:layout_marginStart="-6dp"
|
||||
/>
|
||||
<CheckBox
|
||||
android:id="@+id/march_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:text="@string/march"
|
||||
android:layout_marginStart="-6dp"
|
||||
/>
|
||||
<CheckBox
|
||||
android:id="@+id/april_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:text="@string/april"
|
||||
android:layout_marginStart="-6dp"
|
||||
/>
|
||||
<CheckBox
|
||||
android:id="@+id/may_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:text="@string/may"
|
||||
android:layout_marginStart="-6dp"
|
||||
/>
|
||||
<CheckBox
|
||||
android:id="@+id/june_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:text="@string/june"
|
||||
android:layout_marginStart="-6dp"
|
||||
/>
|
||||
<CheckBox
|
||||
android:id="@+id/july_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:text="@string/july"
|
||||
android:layout_marginStart="-6dp"
|
||||
/>
|
||||
<CheckBox
|
||||
android:id="@+id/august_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:text="@string/august"
|
||||
android:layout_marginStart="-6dp"
|
||||
/>
|
||||
<CheckBox
|
||||
android:id="@+id/september_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:text="@string/september"
|
||||
android:layout_marginStart="-6dp"
|
||||
/>
|
||||
<CheckBox
|
||||
android:id="@+id/october_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:text="@string/october"
|
||||
android:layout_marginStart="-6dp"
|
||||
/>
|
||||
<CheckBox
|
||||
android:id="@+id/november_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:text="@string/november"
|
||||
android:layout_marginStart="-6dp"
|
||||
/>
|
||||
<CheckBox
|
||||
android:id="@+id/december_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:text="@string/december"
|
||||
android:layout_marginStart="-6dp"
|
||||
/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
18
Habitica/res/layout/bottom_sheet_wrapper.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
<View
|
||||
android:id="@+id/grabber"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="4dp"
|
||||
android:background="@drawable/layout_rounded_bg_gray"
|
||||
android:layout_margin="@dimen/spacing_large"
|
||||
android:layout_gravity="center_horizontal"
|
||||
/>
|
||||
<FrameLayout
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
|
|
@ -1,28 +1,41 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.habitrpg.android.habitica.ui.MaxHeightLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
<LinearLayout android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
||||
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:paddingStart="@dimen/bottom_sheet_inset"
|
||||
android:paddingEnd="@dimen/bottom_sheet_inset"
|
||||
android:orientation="vertical"
|
||||
app:maxHeightMultiplier="0.7">
|
||||
|
||||
<!-- margins can't be at the first LinearLayout, it has to be the inner one -->
|
||||
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="@dimen/dialog_marginLeftRight"
|
||||
android:layout_marginStart="@dimen/dialog_marginLeftRight"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/spacing_large">
|
||||
|
||||
android:orientation="vertical">
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="#1f000000" />
|
||||
<TextView
|
||||
style="@style/Headline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/filters"
|
||||
android:textColor="@color/text_primary"
|
||||
android:layout_gravity="center_vertical"/>
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content" />
|
||||
<Button
|
||||
android:id="@+id/clear_button"
|
||||
style="@style/Body1_Button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:text="@string/clear"
|
||||
android:textColor="?colorPrimary"
|
||||
android:textSize="16sp"
|
||||
android:gravity="end|center_vertical"
|
||||
android:paddingEnd="0dp"
|
||||
android:paddingStart="0dp"
|
||||
android:visibility="invisible"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -84,7 +97,8 @@
|
|||
android:checked="true"
|
||||
android:text="@string/owned"
|
||||
android:textColor="@color/text_primary"
|
||||
android:paddingStart="8dp" />
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="0dp"/>
|
||||
|
||||
<CheckBox
|
||||
style="@style/Subheader2"
|
||||
|
|
@ -94,6 +108,6 @@
|
|||
android:checked="true"
|
||||
android:text="@string/not_owned"
|
||||
android:textColor="@color/text_primary"
|
||||
android:paddingStart="8dp"/>
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="0dp"/>
|
||||
</LinearLayout>
|
||||
</com.habitrpg.android.habitica.ui.MaxHeightLinearLayout>
|
||||
|
|
@ -4,7 +4,38 @@
|
|||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="16dp">
|
||||
android:layout_marginBottom="16dp"
|
||||
android:paddingStart="@dimen/bottom_sheet_inset"
|
||||
android:paddingEnd="@dimen/bottom_sheet_inset">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/spacing_large">
|
||||
|
||||
<TextView
|
||||
style="@style/Headline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/filters"
|
||||
android:textColor="@color/text_primary"
|
||||
android:layout_gravity="center_vertical"/>
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content" />
|
||||
<Button
|
||||
android:id="@+id/clear_button"
|
||||
style="@style/Body1_Button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:text="@string/clear"
|
||||
android:textColor="?colorPrimary"
|
||||
android:textSize="16sp"
|
||||
android:gravity="end|center_vertical"
|
||||
android:paddingEnd="0dp"
|
||||
android:paddingStart="0dp"/>
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:id="@+id/task_type_title"
|
||||
style="@style/Caption3"
|
||||
|
|
@ -42,10 +73,6 @@
|
|||
android:text="@string/strong"
|
||||
style="@style/TaskFilterRadioButton"/>
|
||||
</RadioGroup>
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/separator" />
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/RowWrapper"
|
||||
android:id="@+id/gear_container">
|
||||
android:id="@+id/gear_container"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/gear_icon_background_view"
|
||||
|
|
@ -16,14 +17,14 @@
|
|||
<ImageView
|
||||
android:layout_width="@dimen/gear_image_size"
|
||||
android:layout_height="@dimen/gear_image_size"
|
||||
android:layout_gravity="center"
|
||||
android:id="@+id/gear_image"/>
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/BottomMenu"
|
||||
android:clickable="false"
|
||||
android:background="@drawable/rounded_avatar_bg">
|
||||
android:clickable="false">
|
||||
<TextView
|
||||
android:id="@+id/title_view"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@
|
|||
android:id="@+id/imageView"
|
||||
android:layout_width="@dimen/shopitem_image_size"
|
||||
android:layout_height="@dimen/shopitem_image_size"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:scaleType="center"/>
|
||||
android:layout_marginEnd="8dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/titleTextView"
|
||||
|
|
|
|||
9
Habitica/res/menu/menu_list_customizations.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context="com.habitrpg.android.habitica.ui.activities.MainActivity">
|
||||
<item android:id="@+id/action_filter"
|
||||
android:icon="@drawable/ic_action_filter_list"
|
||||
android:title="@string/filter"
|
||||
app:showAsAction="ifRoom" />
|
||||
</menu>
|
||||
|
|
@ -35,6 +35,11 @@
|
|||
app:argType="string"
|
||||
app:nullable="true"
|
||||
android:defaultValue=""/>
|
||||
<argument
|
||||
android:name="ownerID"
|
||||
app:argType="string"
|
||||
app:nullable="true"
|
||||
android:defaultValue=""/>
|
||||
<deepLink app:uri="habitica.com/user/tasks/{taskType}" />
|
||||
<deepLink app:uri="habitica.com/tasks" />
|
||||
<deepLink app:uri="habitica.com" />
|
||||
|
|
@ -142,6 +147,20 @@
|
|||
android:id="@+id/openMountDetail"
|
||||
app:destination="@id/mountDetailRecyclerFragment" />
|
||||
</fragment>
|
||||
<activity
|
||||
android:id="@+id/armoireActivity"
|
||||
android:name="com.habitrpg.android.habitica.ui.activities.ArmoireActivity"
|
||||
android:label="@string/armoire" >
|
||||
<argument
|
||||
android:name="type"
|
||||
app:argType="string" />
|
||||
<argument
|
||||
android:name="text"
|
||||
app:argType="string" />
|
||||
<argument
|
||||
android:name="key"
|
||||
app:argType="string" />
|
||||
</activity>
|
||||
<activity
|
||||
android:id="@+id/subscriptionPurchaseActivity"
|
||||
android:name="com.habitrpg.android.habitica.ui.activities.GemPurchaseActivity"
|
||||
|
|
@ -399,6 +418,9 @@
|
|||
<action
|
||||
android:id="@+id/openProfileActivity"
|
||||
app:destination="@id/fullProfileActivity" />
|
||||
<action
|
||||
android:id="@+id/openArmoireActivity"
|
||||
app:destination="@id/armoireActivity" />
|
||||
<fragment
|
||||
android:id="@+id/achievementsFragment"
|
||||
android:name="com.habitrpg.android.habitica.ui.fragments.AchievementsFragment"
|
||||
|
|
@ -466,12 +488,4 @@
|
|||
app:argType="string" />
|
||||
<deepLink app:uri="habitica.com/promo/web?url={url}" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/teamBoardFragment"
|
||||
android:name="com.habitrpg.android.habitica.ui.fragments.tasks.TeamBoardFragment"
|
||||
android:label="TeamBoardFragment" >
|
||||
<argument
|
||||
android:name="teamID"
|
||||
app:argType="string" />
|
||||
</fragment>
|
||||
</navigation>
|
||||
|
|
@ -27,6 +27,8 @@
|
|||
<attr name="headerOffsetColor" format="color" />
|
||||
<attr name="headerTextColor" format="color" />
|
||||
<attr name="widgetBackgroundRadius" format="dimension" />
|
||||
<attr name="currency" format="string" />
|
||||
|
||||
<declare-styleable name="AvatarView">
|
||||
<attr name="showBackground" format="boolean" />
|
||||
<attr name="showMount" format="boolean" />
|
||||
|
|
@ -77,7 +79,7 @@
|
|||
<attr name="barBackgroundColor" />
|
||||
</declare-styleable>
|
||||
<declare-styleable name="CurrencyView">
|
||||
<attr name="currency" format="string" />
|
||||
<attr name="currency" />
|
||||
<attr name="hasLightBackground" />
|
||||
</declare-styleable>
|
||||
<declare-styleable name="CurrencyViews">
|
||||
|
|
@ -153,4 +155,8 @@
|
|||
<attr name="android:inputType" />
|
||||
<attr name="android:maxLines" />
|
||||
</declare-styleable>
|
||||
<declare-styleable name="AdButton">
|
||||
<attr name="text" />
|
||||
<attr name="currency" />
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -212,4 +212,5 @@
|
|||
<color name="text_brand_white">@color/brand_300</color>
|
||||
<color name="lightly_tinted_background">@color/brand_700</color>
|
||||
<color name="dialog_background">@color/white</color>
|
||||
<color name="error_banner_background">@color/maroon_5</color>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -118,4 +118,6 @@
|
|||
<dimen name="widget_rounding">5dp</dimen>
|
||||
<dimen name="task_spacing_vertical">4dp</dimen>
|
||||
<dimen name="task_spacing_horizontal">10dp</dimen>
|
||||
<dimen name="bottom_sheet_radius">20dp</dimen>
|
||||
<dimen name="bottom_sheet_inset">16dp</dimen>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -276,8 +276,8 @@
|
|||
<string name="mounts">Mounts</string>
|
||||
|
||||
<string name="quest_items_found">You\'ve found %d quest items</string>
|
||||
<string name="armoireEquipment">You found %s in the Armoire</string>
|
||||
<string name="armoireFood">You rummage in the Armoire and find%1$s %2$s. What\'s that doing in here?</string>
|
||||
<string name="armoireEquipment_new">You find a piece of rare Equipment in the Armoire!</string>
|
||||
<string name="armoireFood_new">You rummage in the Armoire and find food. What\'s that doing in here?</string>
|
||||
<string name="armoireExp">You wrestle with the Armoire and gain Experience. Take that!</string>
|
||||
<string name="sell">Sell (%d Gold)</string>
|
||||
<string name="hatch_with_potion">Hatch with potion</string>
|
||||
|
|
@ -1230,4 +1230,39 @@
|
|||
<string name="compact">Compact</string>
|
||||
<string name="minimal">Minimal</string>
|
||||
<string name="are_you_sure_you_want_to_delete">Are you sure you want to delete?</string>
|
||||
<string name="armoire_drop_rates">Armoire drop rates</string>
|
||||
<string name="armoire">Armoire</string>
|
||||
<string name="equipment_remaining">Equipment Remaining: %d</string>
|
||||
<string name="new_pieces_added_every_month">New pieces added every month</string>
|
||||
<string name="watch_ad">Watch Ad</string>
|
||||
<string name="available_in">Available in %s</string>
|
||||
<string name="watch_ad_to_open">Watch ad to open again</string>
|
||||
<string name="watch_ad_to_revive">Watch Ad to revive</string>
|
||||
<string name="enchanted_armoire_drop_rates">Enchanted Armoire Drop Rates</string>
|
||||
<string name="armoire_rate_equipment_description">New Equipment pieces are added every month. If you own all pieces of equipment, then you\'ll get Food or Ezperience, 50/50 odds.</string>
|
||||
<string name="armoire_rate_equipment_title">60% Piece of Equipment</string>
|
||||
<string name="armoire_rate_food_title">20% Piece of Food</string>
|
||||
<string name="armoire_rate_food_description">During special events, normal food items will change to their cake or candy counterparts.</string>
|
||||
<string name="armoire_rate_experience_title">20% Experience points</string>
|
||||
<string name="armoire_rate_experience_description">The amount gained varies randomly from 10 to 50</string>
|
||||
<string name="day_start_adjustment">Day Start Adjustment</string>
|
||||
<string name="purchased">Purchased</string>
|
||||
<string name="show_me">Show Me</string>
|
||||
<string name="background_filters">Background Filters</string>
|
||||
<string name="newest">Newest</string>
|
||||
<string name="oldest">Oldest</string>
|
||||
<string name="sort_by">Sort By</string>
|
||||
<string name="january">January</string>
|
||||
<string name="febuary">Febuary</string>
|
||||
<string name="march">March</string>
|
||||
<string name="april">April</string>
|
||||
<string name="may">May</string>
|
||||
<string name="june">June</string>
|
||||
<string name="july">July</string>
|
||||
<string name="august">August</string>
|
||||
<string name="september">September</string>
|
||||
<string name="october">October</string>
|
||||
<string name="november">November</string>
|
||||
<string name="december">December</string>
|
||||
<string name="cds_subtitle">Adjust when your day switches over past the default time of midnight.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -714,6 +714,11 @@
|
|||
<item name="android:backgroundTint">@color/yellow_100</item>
|
||||
</style>
|
||||
|
||||
<style name="HabiticaButton.White" parent="HabiticaButton">
|
||||
<item name="android:backgroundTint">@color/white</item>
|
||||
<item name="android:textColor">@color/brand_400</item>
|
||||
</style>
|
||||
|
||||
<style name="CurrencyTextView">
|
||||
<item name="android:textSize">12sp</item>
|
||||
<item name="android:layout_marginLeft">4dp</item>
|
||||
|
|
@ -755,6 +760,7 @@
|
|||
<style name="TaskFormTextInputLayoutAppearance" parent="Widget.MaterialComponents.TextInputLayout.FilledBox">
|
||||
<!-- reference our hint & error styles -->
|
||||
<item name="boxBackgroundColor">@color/white</item>
|
||||
<item name="boxStrokeColor">@color/text_input_box_stroke</item>
|
||||
<item name="android:textColor">?attr/colorPrimaryText</item>
|
||||
<item name="android:textColorHint">?colorPrimaryText</item>
|
||||
<item name="colorControlNormal">?attr/colorPrimary</item>
|
||||
|
|
@ -861,5 +867,40 @@
|
|||
<item name="android:textStyle">bold</item>
|
||||
</style>
|
||||
|
||||
<style name="SheetDialog" parent="Theme.Design.Light.BottomSheetDialog">
|
||||
<!--<item name="android:windowCloseOnTouchOutside">false</item>-->
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="android:colorBackground">@color/content_background</item>
|
||||
<item name="android:backgroundDimEnabled">true</item>
|
||||
<item name="android:backgroundDimAmount">0.3</item>
|
||||
<item name="android:windowFrame">@null</item>
|
||||
<item name="android:windowIsFloating">true</item>
|
||||
<item name="shapeAppearanceOverlay">@style/CustomShapeAppearanceBottomSheetDialog</item>
|
||||
<item name="bottomSheetStyle">@style/Widget.App.BottomSheet.Modal</item>
|
||||
|
||||
<item name="colorPrimary">@color/brand</item>
|
||||
<item name="colorPrimaryDark">@color/brand_50</item>
|
||||
<item name="colorAccent">@color/color_accent</item>
|
||||
<item name="android:colorPrimary">@color/brand</item>
|
||||
<item name="android:colorPrimaryDark">@color/brand_50</item>
|
||||
<item name="android:colorAccent">@color/brand_400</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.App.BottomSheet.Modal" parent="Widget.MaterialComponents.BottomSheet.Modal">
|
||||
<item name="shapeAppearanceOverlay">@style/CustomShapeAppearanceBottomSheetDialog</item>
|
||||
<item name="android:layout_marginStart">4dp</item>
|
||||
<item name="android:layout_marginEnd">4dp</item>
|
||||
</style>
|
||||
|
||||
|
||||
<style name="CustomShapeAppearanceBottomSheetDialog" parent="">
|
||||
<item name="cornerFamily">rounded</item>
|
||||
<item name="cornerSizeTopRight">20dp</item>
|
||||
<item name="cornerSizeTopLeft">20dp</item>
|
||||
<item name="cornerSizeBottomRight">0dp</item>
|
||||
<item name="cornerSizeBottomLeft">0dp</item>
|
||||
</style>
|
||||
|
||||
<dimen name="my_background_radius_dimen">12dp</dimen>
|
||||
</resources>
|
||||
|
|
@ -28,6 +28,39 @@
|
|||
<item>1</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="cds_labels">
|
||||
<item>Default (12:00 AM)</item>
|
||||
<item>+1 Hour (01:00 AM)</item>
|
||||
<item>+2 Hours (02:00 AM)</item>
|
||||
<item>+3 Hours (03:00 AM)</item>
|
||||
<item>+4 Hours (04:00 AM)</item>
|
||||
<item>+5 Hours (05:00 AM)</item>
|
||||
<item>+6 Hours (06:00 AM)</item>
|
||||
<item>+7 Hours (07:00 AM)</item>
|
||||
<item>+8 Hours (08:00 AM)</item>
|
||||
<item>+9 Hours (09:00 AM)</item>
|
||||
<item>+10 Hours (10:00 AM)</item>
|
||||
<item>+11 Hours (11:00 AM)</item>
|
||||
<item>+12 Hours (12:00 PM)</item>
|
||||
|
||||
</string-array>
|
||||
|
||||
<string-array name="cds_values">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
<item>5</item>
|
||||
<item>6</item>
|
||||
<item>7</item>
|
||||
<item>8</item>
|
||||
<item>9</item>
|
||||
<item>10</item>
|
||||
<item>11</item>
|
||||
<item>12</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="avatar_sizes">
|
||||
<item>@string/avatar_size_slim</item>
|
||||
<item>@string/avatar_size_broad</item>
|
||||
|
|
|
|||
|
|
@ -182,6 +182,13 @@
|
|||
android:layout="@layout/preference_child_summary"
|
||||
android:summary="@string/pref_first_day_of_the_week_summary"
|
||||
android:title="@string/pref_first_day_of_the_week_title" />
|
||||
|
||||
<com.habitrpg.android.habitica.ui.views.HabiticaListPreference
|
||||
android:key="cds_time"
|
||||
android:entries="@array/cds_labels"
|
||||
android:entryValues="@array/cds_values"
|
||||
android:layout="@layout/preference_child_summary"
|
||||
android:title="@string/day_start_adjustment"/>
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
|
|
@ -202,17 +209,6 @@
|
|||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/pref_cds_header"
|
||||
android:layout="@layout/preference_category">
|
||||
|
||||
<com.habitrpg.android.habitica.prefs.TimePreference
|
||||
android:key="cds_time"
|
||||
android:defaultValue="00:00"
|
||||
android:layout="@layout/preference_child_summary" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/push_notifications"
|
||||
android:layout="@layout/preference_category">
|
||||
|
|
|
|||
|
|
@ -104,5 +104,26 @@
|
|||
<key>enableTeamBoards</key>
|
||||
<value>false</value>
|
||||
</entry>
|
||||
<entry>
|
||||
<key>hideFacebook</key>
|
||||
<value>false</value>
|
||||
</entry>
|
||||
|
||||
<entry>
|
||||
<key>enableNewArmoire</key>
|
||||
<value>true</value>
|
||||
</entry>
|
||||
<entry>
|
||||
<key>enableArmoireAds</key>
|
||||
<value>true</value>
|
||||
</entry>
|
||||
<entry>
|
||||
<key>enableFaintAds</key>
|
||||
<value>false</value>
|
||||
</entry>
|
||||
<entry>
|
||||
<key>enableSpellAds</key>
|
||||
<value>false</value>
|
||||
</entry>
|
||||
</defaultsMap>
|
||||
<!-- END xml_defaults -->
|
||||
|
|
@ -34,6 +34,7 @@ import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings
|
|||
import com.habitrpg.android.habitica.components.AppComponent
|
||||
import com.habitrpg.android.habitica.components.UserComponent
|
||||
import com.habitrpg.android.habitica.data.ApiClient
|
||||
import com.habitrpg.android.habitica.helpers.AdHandler
|
||||
import com.habitrpg.android.habitica.helpers.LanguageHelper
|
||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||
import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager
|
||||
|
|
@ -73,6 +74,7 @@ abstract class HabiticaBaseApplication : Application(), Application.ActivityLife
|
|||
setLocale()
|
||||
setupRemoteConfig()
|
||||
setupNotifications()
|
||||
setupAdHandler()
|
||||
HabiticaIconsHelper.init(this)
|
||||
MarkdownParser.setup(this)
|
||||
|
||||
|
|
@ -110,6 +112,10 @@ abstract class HabiticaBaseApplication : Application(), Application.ActivityLife
|
|||
checkIfNewVersion()
|
||||
}
|
||||
|
||||
private fun setupAdHandler() {
|
||||
AdHandler.setup(sharedPrefs, analyticsManager)
|
||||
}
|
||||
|
||||
private fun setLocale() {
|
||||
val resources = resources
|
||||
val configuration: Configuration = resources.configuration
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.habitrpg.android.habitica.receivers.NotificationPublisher;
|
|||
import com.habitrpg.android.habitica.receivers.TaskAlarmBootReceiver;
|
||||
import com.habitrpg.android.habitica.receivers.TaskReceiver;
|
||||
import com.habitrpg.android.habitica.ui.activities.AdventureGuideActivity;
|
||||
import com.habitrpg.android.habitica.ui.activities.ArmoireActivity;
|
||||
import com.habitrpg.android.habitica.ui.activities.ChallengeFormActivity;
|
||||
import com.habitrpg.android.habitica.ui.activities.ClassSelectionActivity;
|
||||
import com.habitrpg.android.habitica.ui.activities.FixCharacterValuesActivity;
|
||||
|
|
@ -98,13 +99,13 @@ import com.habitrpg.android.habitica.ui.fragments.support.FAQOverviewFragment;
|
|||
import com.habitrpg.android.habitica.ui.fragments.support.SupportMainFragment;
|
||||
import com.habitrpg.android.habitica.ui.fragments.tasks.TaskRecyclerViewFragment;
|
||||
import com.habitrpg.android.habitica.ui.fragments.tasks.TasksFragment;
|
||||
import com.habitrpg.android.habitica.ui.fragments.tasks.TeamBoardFragment;
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.AuthenticationViewModel;
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.GroupViewModel;
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.InboxViewModel;
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.MainActivityViewModel;
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel;
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel;
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel;
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.inventory.equipment.EquipmentOverviewViewModel;
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.PetSuggestHatchDialog;
|
||||
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientGemsDialog;
|
||||
|
|
@ -337,8 +338,6 @@ public interface UserComponent {
|
|||
|
||||
void inject(PromoInfoFragment promoInfoFragment);
|
||||
|
||||
void inject(@NotNull TeamBoardFragment teamBoardFragment);
|
||||
|
||||
void inject(@NotNull GuildOverviewFragment guildOverviewFragment);
|
||||
|
||||
void inject(@NotNull PromoWebFragment promoWebFragment);
|
||||
|
|
@ -358,4 +357,8 @@ public interface UserComponent {
|
|||
void inject(@NotNull MainUserViewModel mainUserViewModel);
|
||||
|
||||
void inject(@NotNull PetSuggestHatchDialog petSuggestHatchDialog);
|
||||
|
||||
void inject(@NotNull ArmoireActivity armoireActivity);
|
||||
|
||||
void inject(@NotNull TasksViewModel tasksViewModel);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,13 +58,6 @@ import io.reactivex.rxjava3.core.Flowable
|
|||
import io.reactivex.rxjava3.core.FlowableTransformer
|
||||
import io.reactivex.rxjava3.functions.Consumer
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import java.io.IOException
|
||||
import java.net.SocketException
|
||||
import java.net.SocketTimeoutException
|
||||
import java.net.UnknownHostException
|
||||
import java.util.GregorianCalendar
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.net.ssl.SSLException
|
||||
import okhttp3.Cache
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
|
|
@ -73,6 +66,13 @@ import retrofit2.HttpException
|
|||
import retrofit2.Retrofit
|
||||
import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import java.io.IOException
|
||||
import java.net.SocketException
|
||||
import java.net.SocketTimeoutException
|
||||
import java.net.UnknownHostException
|
||||
import java.util.GregorianCalendar
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.net.ssl.SSLException
|
||||
|
||||
class ApiClientImpl(
|
||||
private val gsonConverter: GsonConverterFactory,
|
||||
|
|
@ -94,6 +94,9 @@ class ApiClientImpl(
|
|||
habitResponse.notifications?.let {
|
||||
notificationsManager.setNotifications(it)
|
||||
}
|
||||
if (hadError) {
|
||||
hideConnectionProblemDialog()
|
||||
}
|
||||
habitResponse.data
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
|
|
@ -102,6 +105,7 @@ class ApiClientImpl(
|
|||
}
|
||||
private var languageCode: String? = null
|
||||
private var lastAPICallURL: String? = null
|
||||
private var hadError = false
|
||||
|
||||
init {
|
||||
HabiticaBaseApplication.userComponent?.inject(this)
|
||||
|
|
@ -306,12 +310,21 @@ class ApiClientImpl(
|
|||
resourceTitleString: String?,
|
||||
resourceMessageString: String
|
||||
) {
|
||||
hadError = true
|
||||
val application = (context as? HabiticaBaseApplication)
|
||||
?: (context.applicationContext as? HabiticaBaseApplication)
|
||||
application?.currentActivity?.get()
|
||||
?.showConnectionProblem(resourceTitleString, resourceMessageString)
|
||||
}
|
||||
|
||||
private fun hideConnectionProblemDialog() {
|
||||
hadError = false
|
||||
val application = (context as? HabiticaBaseApplication)
|
||||
?: (context.applicationContext as? HabiticaBaseApplication)
|
||||
application?.currentActivity?.get()
|
||||
?.hideConnectionProblem()
|
||||
}
|
||||
|
||||
/*
|
||||
This function is used with Observer.compose to reuse transformers across the application.
|
||||
See here for more info: http://blog.danlew.net/2015/03/02/dont-break-the-chain/
|
||||
|
|
|
|||
|
|
@ -4,16 +4,12 @@ import android.content.res.Resources
|
|||
import com.habitrpg.android.habitica.R
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import kotlin.math.round
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.ExperimentalTime
|
||||
import kotlin.time.milliseconds
|
||||
import kotlin.time.toDuration
|
||||
|
||||
class DateUtils {
|
||||
|
||||
companion object {
|
||||
|
||||
fun createDate(year: Int, month: Int, day: Int): Date {
|
||||
val cal = Calendar.getInstance()
|
||||
cal.set(Calendar.YEAR, year)
|
||||
|
|
@ -33,11 +29,11 @@ fun Date.getAgoString(res: Resources): String {
|
|||
}
|
||||
|
||||
fun Long.getAgoString(res: Resources): String {
|
||||
val diff = Date().time - this
|
||||
val diff = (Date().time - this).toDuration(DurationUnit.MILLISECONDS)
|
||||
|
||||
val diffMinutes = diff / (60 * 1000) % 60
|
||||
val diffHours = diff / (60 * 60 * 1000) % 24
|
||||
val diffDays = diff / (24 * 60 * 60 * 1000)
|
||||
val diffMinutes = diff.inWholeMinutes
|
||||
val diffHours = diff.inWholeHours
|
||||
val diffDays = diff.inWholeDays
|
||||
val diffWeeks = diffDays / 7
|
||||
val diffMonths = diffDays / 30
|
||||
|
||||
|
|
@ -63,30 +59,29 @@ fun Date.getRemainingString(res: Resources): String {
|
|||
return this.time.getRemainingString(res)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
fun Long.getRemainingString(res: Resources): String {
|
||||
val diff = (this - Date().time).milliseconds
|
||||
val diff = (this - Date().time).toDuration(DurationUnit.MILLISECONDS)
|
||||
|
||||
val diffMinutes = diff.inMinutes
|
||||
val diffHours = diff.inHours
|
||||
val diffDays = diff.inDays
|
||||
val diffWeeks = diffDays / 7f
|
||||
val diffMonths = diffDays / 30f
|
||||
val diffMinutes = diff.inWholeMinutes
|
||||
val diffHours = diff.inWholeHours
|
||||
val diffDays = diff.inWholeDays
|
||||
val diffWeeks = diffDays / 7
|
||||
val diffMonths = diffDays / 30
|
||||
|
||||
return when {
|
||||
diffMonths != 0.0 -> if (round(diffMonths) == 1.0) {
|
||||
diffMonths != 0L -> if (diffMonths == 1L) {
|
||||
res.getString(R.string.remaining_1month)
|
||||
} else res.getString(R.string.remaining_months, round(diffMonths).toInt())
|
||||
diffWeeks != 0.0 -> if (round(diffWeeks) == 1.0) {
|
||||
} else res.getString(R.string.remaining_months, diffMonths)
|
||||
diffWeeks != 0L -> if (diffWeeks == 1L) {
|
||||
res.getString(R.string.remaining_1week)
|
||||
} else res.getString(R.string.remaining_weeks, round(diffWeeks).toInt())
|
||||
diffDays != 0.0 -> if (diffDays == 1.0) {
|
||||
} else res.getString(R.string.remaining_weeks, diffWeeks)
|
||||
diffDays != 0L -> if (diffDays == 1L) {
|
||||
res.getString(R.string.remaining_1day)
|
||||
} else res.getString(R.string.remaining_days, diffDays)
|
||||
diffHours != 0.0 -> if (diffHours == 1.0) {
|
||||
diffHours != 0L -> if (diffHours == 1L) {
|
||||
res.getString(R.string.remaining_1hour)
|
||||
} else res.getString(R.string.remaining_hours, diffHours)
|
||||
diffMinutes == 1.0 -> res.getString(R.string.remaining_1Minute)
|
||||
diffMinutes == 1L -> res.getString(R.string.remaining_1Minute)
|
||||
else -> res.getString(R.string.remaining_minutes, diffMinutes)
|
||||
}
|
||||
}
|
||||
|
|
@ -95,16 +90,15 @@ fun Date.getShortRemainingString(): String {
|
|||
return time.getShortRemainingString()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
fun Long.getShortRemainingString(): String {
|
||||
var diff = Duration.milliseconds((this - Date().time))
|
||||
var diff = (this - Date().time).toDuration(DurationUnit.MILLISECONDS)
|
||||
|
||||
val diffDays = diff.toInt(DurationUnit.DAYS)
|
||||
diff -= Duration.days(diffDays)
|
||||
diff -= diffDays.toDuration(DurationUnit.DAYS)
|
||||
val diffHours = diff.toInt(DurationUnit.HOURS)
|
||||
diff -= Duration.hours(diffHours)
|
||||
diff -= diffDays.toDuration(DurationUnit.HOURS)
|
||||
val diffMinutes = diff.toInt(DurationUnit.MINUTES)
|
||||
diff -= Duration.minutes(diffMinutes)
|
||||
diff -= diffMinutes.toDuration(DurationUnit.MINUTES)
|
||||
val diffSeconds = diff.toInt(DurationUnit.SECONDS)
|
||||
|
||||
var str = "${diffMinutes}m"
|
||||
|
|
@ -119,3 +113,7 @@ fun Long.getShortRemainingString(): String {
|
|||
}
|
||||
return str
|
||||
}
|
||||
|
||||
fun Duration.getMinuteOrSeconds(): DurationUnit {
|
||||
return if (this.inWholeHours < 1) DurationUnit.SECONDS else DurationUnit.MINUTES
|
||||
}
|
||||
|
|
@ -0,0 +1,224 @@
|
|||
package com.habitrpg.android.habitica.helpers
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.os.bundleOf
|
||||
import com.google.android.gms.ads.AdRequest
|
||||
import com.google.android.gms.ads.FullScreenContentCallback
|
||||
import com.google.android.gms.ads.LoadAdError
|
||||
import com.google.android.gms.ads.MobileAds
|
||||
import com.google.android.gms.ads.OnUserEarnedRewardListener
|
||||
import com.google.android.gms.ads.RequestConfiguration
|
||||
import com.google.android.gms.ads.rewarded.RewardItem
|
||||
import com.google.android.gms.ads.rewarded.RewardedAd
|
||||
import com.google.android.gms.ads.rewarded.RewardedAdLoadCallback
|
||||
import com.google.firebase.analytics.FirebaseAnalytics
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.habitrpg.android.habitica.BuildConfig
|
||||
import com.habitrpg.android.habitica.proxy.AnalyticsManager
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.security.MessageDigest
|
||||
import java.util.Date
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.toDuration
|
||||
|
||||
enum class AdType {
|
||||
ARMOIRE,
|
||||
SPELL,
|
||||
FAINT;
|
||||
|
||||
val adUnitID: String
|
||||
get() {
|
||||
if (BuildConfig.DEBUG || BuildConfig.TESTING_LEVEL == "staff" || BuildConfig.TESTING_LEVEL == "alpha") {
|
||||
return "ca-app-pub-3940256099942544/5224354917"
|
||||
}
|
||||
return when (this) {
|
||||
ARMOIRE -> "ca-app-pub-5911973472413421/9392092486"
|
||||
SPELL -> "ca-app-pub-5911973472413421/1738504765"
|
||||
FAINT -> "ca-app-pub-5911973472413421/1738504765"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun String.md5(): String? {
|
||||
try {
|
||||
val md = MessageDigest.getInstance("MD5")
|
||||
val array = md.digest(this.toByteArray())
|
||||
val sb = StringBuffer()
|
||||
for (i in array.indices) {
|
||||
sb.append(Integer.toHexString(array[i].toInt() and 0xFF or 0x100).substring(1, 3))
|
||||
}
|
||||
return sb.toString()
|
||||
} catch (e: java.security.NoSuchAlgorithmException) {
|
||||
} catch (ex: UnsupportedEncodingException) {
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
class AdHandler(val activity: Activity, val type: AdType, val rewardAction: (Boolean) -> Unit): OnUserEarnedRewardListener {
|
||||
private var rewardedAd: RewardedAd? = null
|
||||
|
||||
companion object {
|
||||
private enum class AdStatus {
|
||||
UNINITIALIZED,
|
||||
INITIALIZING,
|
||||
READY,
|
||||
DISABLED
|
||||
}
|
||||
|
||||
private lateinit var analyticsManager: AnalyticsManager
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
const val TAG = "AdHandler"
|
||||
|
||||
private var currentAdStatus = AdStatus.UNINITIALIZED
|
||||
|
||||
private var nextAdAllowed: MutableMap<AdType, Date> = mutableMapOf()
|
||||
|
||||
fun nextAdAllowedDate(type: AdType): Date? {
|
||||
return nextAdAllowed[type]
|
||||
}
|
||||
|
||||
fun isAllowed(type: AdType): Boolean {
|
||||
return nextAdAllowedDate(type)?.after(Date()) == true
|
||||
}
|
||||
|
||||
fun setNextAllowedDate(type: AdType, date: Date) {
|
||||
nextAdAllowed[type] = date
|
||||
sharedPreferences.edit {
|
||||
putLong("nextAd${type.name}", date.time)
|
||||
}
|
||||
}
|
||||
|
||||
fun initialize(context: Context, onComplete: () -> Unit) {
|
||||
if (currentAdStatus != AdStatus.UNINITIALIZED) return
|
||||
|
||||
if (BuildConfig.DEBUG || BuildConfig.TESTING_LEVEL == "staff" || BuildConfig.TESTING_LEVEL == "alpha") {
|
||||
val android_id: String =
|
||||
Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID)
|
||||
val deviceId: String = android_id.md5()?.uppercase() ?: ""
|
||||
val configuration = RequestConfiguration.Builder().setTestDeviceIds(listOf(deviceId)).build()
|
||||
MobileAds.setRequestConfiguration(configuration)
|
||||
}
|
||||
|
||||
currentAdStatus = AdStatus.INITIALIZING
|
||||
MobileAds.initialize(context) {
|
||||
currentAdStatus = AdStatus.READY
|
||||
onComplete()
|
||||
FirebaseCrashlytics.getInstance().recordException(Throwable("Ads Initialized"))
|
||||
}
|
||||
}
|
||||
|
||||
fun whenAdsInitialized(context: Context, onComplete: () -> Unit) {
|
||||
when (currentAdStatus) {
|
||||
AdStatus.READY -> {
|
||||
onComplete()
|
||||
}
|
||||
AdStatus.DISABLED -> {
|
||||
return
|
||||
}
|
||||
AdStatus.UNINITIALIZED -> {
|
||||
initialize(context) {
|
||||
onComplete()
|
||||
}
|
||||
}
|
||||
AdStatus.INITIALIZING -> {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setup(sharedPrefs: SharedPreferences, analyticsManager: AnalyticsManager) {
|
||||
this.sharedPreferences = sharedPrefs
|
||||
this.analyticsManager = analyticsManager
|
||||
|
||||
for (type in AdType.values()) {
|
||||
val time = sharedPrefs.getLong("nextAd${type.name}", 0)
|
||||
if (time > 0) {
|
||||
nextAdAllowed[type] = Date(time)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun prepare(onComplete: ((Boolean) -> Unit)? = null) {
|
||||
whenAdsInitialized(activity) {
|
||||
val adRequest = AdRequest.Builder()
|
||||
.build()
|
||||
|
||||
if (BuildConfig.DEBUG || BuildConfig.TESTING_LEVEL == "staff" || BuildConfig.TESTING_LEVEL == "alpha") {
|
||||
if (!adRequest.isTestDevice(activity)) {
|
||||
// users in this group need to be configured as Test device. better to fail if they aren't
|
||||
// currentAdStatus = AdStatus.DISABLED
|
||||
FirebaseCrashlytics.getInstance().recordException(Throwable("Device not test device"))
|
||||
}
|
||||
}
|
||||
|
||||
RewardedAd.load(activity, type.adUnitID, adRequest, object : RewardedAdLoadCallback() {
|
||||
override fun onAdFailedToLoad(adError: LoadAdError) {
|
||||
FirebaseCrashlytics.getInstance().recordException(Throwable(adError.message))
|
||||
rewardAction(false)
|
||||
onComplete?.invoke(false)
|
||||
}
|
||||
|
||||
override fun onAdLoaded(rewardedAd: RewardedAd) {
|
||||
this@AdHandler.rewardedAd = rewardedAd
|
||||
configureReward()
|
||||
onComplete?.invoke(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fun show() {
|
||||
when (currentAdStatus) {
|
||||
AdStatus.READY -> {
|
||||
showRewardedAd()
|
||||
}
|
||||
AdStatus.DISABLED -> {
|
||||
rewardAction(false)
|
||||
return
|
||||
}
|
||||
AdStatus.UNINITIALIZED -> {
|
||||
initialize(activity) {
|
||||
showRewardedAd()
|
||||
}
|
||||
}
|
||||
AdStatus.INITIALIZING -> {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun configureReward() {
|
||||
rewardedAd?.run {
|
||||
fullScreenContentCallback = object : FullScreenContentCallback() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showRewardedAd() {
|
||||
if (nextAdAllowedDate(type)?.after(Date()) == true) {
|
||||
return
|
||||
}
|
||||
if (rewardedAd != null) {
|
||||
rewardedAd?.show(activity, this)
|
||||
setNextAllowedDate(type, Date(Date().time + 1.toDuration(DurationUnit.HOURS).inWholeMilliseconds))
|
||||
} else {
|
||||
Log.d(TAG, "The rewarded ad wasn't ready yet.")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onUserEarnedReward(rewardItem: RewardItem) {
|
||||
analyticsManager.logEvent("adRewardEarned", bundleOf(
|
||||
Pair("type", type.name)
|
||||
))
|
||||
FirebaseAnalytics.getInstance(activity).logEvent("adRewardEarned", bundleOf(
|
||||
Pair("type", type.name)
|
||||
))
|
||||
rewardAction(true)
|
||||
}
|
||||
}
|
||||
|
|
@ -136,6 +136,29 @@ class AppConfigManager(contentRepository: ContentRepository?) {
|
|||
}
|
||||
|
||||
fun enableTeamBoards(): Boolean {
|
||||
if (BuildConfig.DEBUG) {
|
||||
return true
|
||||
}
|
||||
return remoteConfig.getBoolean("enableTeamBoards")
|
||||
}
|
||||
|
||||
fun enableArmoireAds(): Boolean {
|
||||
return remoteConfig.getBoolean("enableArmoireAds")
|
||||
}
|
||||
|
||||
fun enableFaintAds(): Boolean {
|
||||
return remoteConfig.getBoolean("enableFaintAds")
|
||||
}
|
||||
|
||||
fun enableSpellAds(): Boolean {
|
||||
return remoteConfig.getBoolean("enableSpellAds")
|
||||
}
|
||||
|
||||
fun enableNewArmoire(): Boolean {
|
||||
return remoteConfig.getBoolean("enableNewArmoire")
|
||||
}
|
||||
|
||||
fun hideFacebook(): Boolean {
|
||||
return remoteConfig.getBoolean("hideFacebook")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@ class LanguageHelper(languageSharedPref: String?) {
|
|||
locale = Locale("pt", "PT")
|
||||
languageCode = "pt"
|
||||
}
|
||||
"uk" -> {
|
||||
locale = Locale("uk", "UA")
|
||||
languageCode = "uk"
|
||||
}
|
||||
else -> {
|
||||
locale = if (pref.contains("_")) {
|
||||
val languageCodeParts = pref.split("_".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ import java.text.DecimalFormat
|
|||
|
||||
object NumberAbbreviator {
|
||||
|
||||
fun abbreviate(context: Context, number: Double, numberOfDecimals: Int = 2): String {
|
||||
fun abbreviate(context: Context, number: Double, numberOfDecimals: Int = 2, minForAbbrevation: Int = 0): String {
|
||||
var usedNumber = number
|
||||
var counter = 0
|
||||
while (usedNumber >= 1000) {
|
||||
while (usedNumber >= 1000 && number >= minForAbbrevation) {
|
||||
counter++
|
||||
usedNumber /= 1000
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,125 +1,5 @@
|
|||
package com.habitrpg.android.habitica.helpers
|
||||
|
||||
import com.habitrpg.android.habitica.models.tasks.Task
|
||||
import com.habitrpg.android.habitica.models.tasks.TaskType
|
||||
import io.realm.Case
|
||||
import io.realm.OrderedRealmCollection
|
||||
import io.realm.RealmQuery
|
||||
import io.realm.Sort
|
||||
|
||||
class TaskFilterHelper {
|
||||
var searchQuery: String? = null
|
||||
private val activeFilters = HashMap<TaskType, String>()
|
||||
|
||||
var tags: MutableList<String> = mutableListOf()
|
||||
|
||||
fun howMany(type: TaskType?): Int {
|
||||
return this.tags.size + if (isTaskFilterActive(type)) 1 else 0
|
||||
}
|
||||
|
||||
private fun isTaskFilterActive(type: TaskType?): Boolean {
|
||||
if (activeFilters[type] == null) {
|
||||
return false
|
||||
}
|
||||
return if (TaskType.TODO == type) {
|
||||
Task.FILTER_ACTIVE != activeFilters[type]
|
||||
} else {
|
||||
Task.FILTER_ALL != activeFilters[type]
|
||||
}
|
||||
}
|
||||
|
||||
fun filter(tasks: List<Task>): List<Task> {
|
||||
if (tasks.isEmpty()) {
|
||||
return tasks
|
||||
}
|
||||
val filtered = ArrayList<Task>()
|
||||
var activeFilter: String? = null
|
||||
if (activeFilters.size > 0) {
|
||||
activeFilter = activeFilters[tasks[0].type]
|
||||
}
|
||||
for (task in tasks) {
|
||||
if (isFiltered(task, activeFilter)) {
|
||||
filtered.add(task)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
||||
private fun isFiltered(task: Task, activeFilter: String?): Boolean {
|
||||
if (!task.containsAllTagIds(tags)) {
|
||||
return false
|
||||
}
|
||||
return if (activeFilter != null && activeFilter != Task.FILTER_ALL) {
|
||||
when (activeFilter) {
|
||||
Task.FILTER_ACTIVE -> if (task.type == TaskType.DAILY) {
|
||||
task.isDisplayedActive
|
||||
} else {
|
||||
!task.completed
|
||||
}
|
||||
Task.FILTER_GRAY -> task.completed || !task.isDisplayedActive
|
||||
Task.FILTER_WEAK -> task.value < 1
|
||||
Task.FILTER_STRONG -> task.value >= 1
|
||||
Task.FILTER_DATED -> task.dueDate != null
|
||||
Task.FILTER_COMPLETED -> task.completed
|
||||
else -> true
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fun setActiveFilter(type: TaskType, activeFilter: String) {
|
||||
activeFilters[type] = activeFilter
|
||||
}
|
||||
|
||||
fun getActiveFilter(type: TaskType?): String? {
|
||||
return if (activeFilters.containsKey(type)) {
|
||||
activeFilters[type]
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun createQuery(unfilteredData: OrderedRealmCollection<Task>): RealmQuery<Task>? {
|
||||
if (!unfilteredData.isValid) {
|
||||
return null
|
||||
}
|
||||
var query = unfilteredData.where()
|
||||
|
||||
if (unfilteredData.size != 0) {
|
||||
val taskType = unfilteredData[0].type
|
||||
val activeFilter = getActiveFilter(taskType)
|
||||
|
||||
if (tags.size > 0) {
|
||||
query = query.`in`("tags.id", tags.toTypedArray())
|
||||
}
|
||||
if (searchQuery?.isNotEmpty() == true) {
|
||||
query = query
|
||||
.beginGroup()
|
||||
.contains("text", searchQuery ?: "", Case.INSENSITIVE)
|
||||
.or()
|
||||
.contains("notes", searchQuery ?: "", Case.INSENSITIVE)
|
||||
.endGroup()
|
||||
}
|
||||
if (activeFilter != null && activeFilter != Task.FILTER_ALL) {
|
||||
when (activeFilter) {
|
||||
Task.FILTER_ACTIVE -> query = if (TaskType.DAILY == taskType) {
|
||||
query.equalTo("completed", false).equalTo("isDue", true)
|
||||
} else {
|
||||
query.equalTo("completed", false)
|
||||
}
|
||||
Task.FILTER_GRAY -> query = query.equalTo("completed", true).or().equalTo("isDue", false)
|
||||
Task.FILTER_WEAK -> query = query.lessThan("value", 1.0)
|
||||
Task.FILTER_STRONG -> query = query.greaterThanOrEqualTo("value", 1.0)
|
||||
Task.FILTER_DATED -> query = query.isNotNull("dueDate").equalTo("completed", false).sort("dueDate")
|
||||
Task.FILTER_COMPLETED -> query = query.equalTo("completed", true)
|
||||
}
|
||||
}
|
||||
if (activeFilter != Task.FILTER_DATED) {
|
||||
query = query.sort("position", Sort.ASCENDING, "dateCreated", Sort.DESCENDING)
|
||||
}
|
||||
}
|
||||
return query
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
package com.habitrpg.android.habitica.models
|
||||
|
||||
data class CustomizationFilter(
|
||||
var onlyPurchased: Boolean = false,
|
||||
var ascending: Boolean = false,
|
||||
var months: MutableList<String> = mutableListOf()
|
||||
) {
|
||||
val isFiltering: Boolean
|
||||
get() {
|
||||
return onlyPurchased || months.isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
package com.habitrpg.android.habitica.ui.activities
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.animation.AccelerateInterpolator
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.components.UserComponent
|
||||
import com.habitrpg.android.habitica.data.InventoryRepository
|
||||
import com.habitrpg.android.habitica.databinding.ActivityArmoireBinding
|
||||
import com.habitrpg.android.habitica.extensions.observeOnce
|
||||
import com.habitrpg.android.habitica.helpers.AdHandler
|
||||
import com.habitrpg.android.habitica.helpers.AdType
|
||||
import com.habitrpg.android.habitica.helpers.AppConfigManager
|
||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||
import com.habitrpg.android.habitica.ui.helpers.loadImage
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
|
||||
import com.habitrpg.android.habitica.ui.views.ads.AdButton
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaBottomSheetDialog
|
||||
import com.plattysoft.leonids.ParticleSystem
|
||||
import javax.inject.Inject
|
||||
|
||||
class ArmoireActivity: BaseActivity() {
|
||||
|
||||
private var equipmentKey: String? = null
|
||||
private var gold: Double? = null
|
||||
private var hasAnimatedChanges: Boolean = false
|
||||
private lateinit var binding: ActivityArmoireBinding
|
||||
|
||||
@Inject
|
||||
internal lateinit var inventoryRepository: InventoryRepository
|
||||
@Inject
|
||||
internal lateinit var appConfigManager: AppConfigManager
|
||||
@Inject
|
||||
lateinit var userViewModel: MainUserViewModel
|
||||
|
||||
override fun getLayoutResId(): Int = R.layout.activity_armoire
|
||||
|
||||
override fun injectActivity(component: UserComponent?) {
|
||||
component?.inject(this)
|
||||
}
|
||||
|
||||
override fun getContentView(): View {
|
||||
binding = ActivityArmoireBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding.goldView.currency = "gold"
|
||||
binding.goldView.animationDuration = 1000
|
||||
binding.goldView.animationDelay = 500
|
||||
binding.goldView.minForAbbrevation = 1000000
|
||||
binding.goldView.decimals = 0
|
||||
|
||||
userViewModel.user.observeOnce(this) { user ->
|
||||
gold = user?.stats?.gp
|
||||
val remaining = inventoryRepository.getArmoireRemainingCount()
|
||||
binding.equipmentCountView.text = getString(R.string.equipment_remaining, remaining)
|
||||
binding.noEquipmentView.visibility = if (remaining > 0) View.GONE else View.VISIBLE
|
||||
}
|
||||
|
||||
if (appConfigManager.enableArmoireAds()) {
|
||||
val handler = AdHandler(this, AdType.ARMOIRE) {
|
||||
if (!it) {
|
||||
return@AdHandler
|
||||
}
|
||||
Log.d("AdHandler", "Giving Armoire")
|
||||
val user = userViewModel.user.value ?: return@AdHandler
|
||||
val currentGold = user.stats?.gp ?: return@AdHandler
|
||||
compositeSubscription.add(userRepository.updateUser("stats.gp", currentGold + 100)
|
||||
.flatMap { inventoryRepository.buyItem(user, "armoire", 100.0, 1) }
|
||||
.subscribe({
|
||||
configure(it.armoire["type"] ?: "",
|
||||
it.armoire["dropKey"] ?: "",
|
||||
it.armoire["dropText"] ?: "")
|
||||
binding.adButton.updateForAdType(AdType.ARMOIRE, lifecycleScope)
|
||||
hasAnimatedChanges = false
|
||||
gold = null
|
||||
}, RxErrorHandler.handleEmptyError()))
|
||||
}
|
||||
handler.prepare {
|
||||
if (it && binding.adButton.state == AdButton.State.EMPTY) {
|
||||
binding.adButton.state = AdButton.State.READY
|
||||
} else if (!it) {
|
||||
binding.adButton.visibility = View.INVISIBLE
|
||||
}
|
||||
}
|
||||
binding.adButton.updateForAdType(AdType.ARMOIRE, lifecycleScope)
|
||||
binding.adButton.setOnClickListener {
|
||||
binding.adButton.state = AdButton.State.LOADING
|
||||
handler.show()
|
||||
}
|
||||
} else {
|
||||
binding.adButton.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.closeButton.setOnClickListener {
|
||||
finish()
|
||||
}
|
||||
binding.equipButton.setOnClickListener {
|
||||
equipmentKey?.let { it1 -> inventoryRepository.equip("gear", it1).subscribe() }
|
||||
finish()
|
||||
}
|
||||
binding.dropRateButton.setOnClickListener {
|
||||
showDropRateDialog()
|
||||
}
|
||||
intent.extras?.let {
|
||||
val args = ArmoireActivityArgs.fromBundle(it)
|
||||
equipmentKey = args.key
|
||||
configure(args.type, args.key, args.text)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
startAnimation()
|
||||
}
|
||||
|
||||
private fun startAnimation() {
|
||||
val gold = gold?.toInt()
|
||||
if (hasAnimatedChanges) return
|
||||
if (gold != null) {
|
||||
binding.goldView.value = (gold).toDouble()
|
||||
binding.goldView.value = (gold - 100).toDouble()
|
||||
}
|
||||
|
||||
val container = binding.confettiAnchor
|
||||
container.postDelayed(
|
||||
{
|
||||
createParticles(container, R.drawable.confetti_blue)
|
||||
createParticles(container, R.drawable.confetti_red)
|
||||
createParticles(container, R.drawable.confetti_yellow)
|
||||
createParticles(container, R.drawable.confetti_purple)
|
||||
},
|
||||
500
|
||||
)
|
||||
hasAnimatedChanges = true
|
||||
}
|
||||
|
||||
private fun createParticles(container: FrameLayout, resource: Int) {
|
||||
ParticleSystem(
|
||||
container,
|
||||
30,
|
||||
ContextCompat.getDrawable(this, resource),
|
||||
6000
|
||||
)
|
||||
.setRotationSpeed(144f)
|
||||
.setScaleRange(1.0f, 1.6f)
|
||||
.setSpeedByComponentsRange(-0.15f, 0.15f, 0.15f, 0.45f)
|
||||
.setFadeOut(200, AccelerateInterpolator())
|
||||
.emitWithGravity(binding.confettiAnchor, Gravity.TOP, 15, 2000)
|
||||
}
|
||||
|
||||
fun configure(type: String, key: String, text: String) {
|
||||
binding.titleView.text = text
|
||||
binding.equipButton.visibility = if (type == "gear") View.VISIBLE else View.GONE
|
||||
when (type) {
|
||||
"gear" -> {
|
||||
binding.subtitleView.text = getString(R.string.armoireEquipment_new)
|
||||
binding.iconView.loadImage("shop_$key")
|
||||
}
|
||||
"food" -> {
|
||||
binding.subtitleView.text = getString(R.string.armoireFood_new)
|
||||
binding.iconView.loadImage("Pet_Food_$key")
|
||||
}
|
||||
else -> {
|
||||
binding.subtitleView.text = getString(R.string.armoireExp)
|
||||
binding.iconView.setImageResource(R.drawable.armoire_experience)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun showDropRateDialog() {
|
||||
val dialog = HabiticaBottomSheetDialog(this)
|
||||
dialog.setContentView(R.layout.armoire_drop_rate_dialog)
|
||||
dialog.show()
|
||||
}
|
||||
}
|
||||
|
|
@ -236,6 +236,10 @@ abstract class BaseActivity : AppCompatActivity() {
|
|||
alert.enqueue()
|
||||
}
|
||||
|
||||
open fun hideConnectionProblem() {
|
||||
|
||||
}
|
||||
|
||||
fun shareContent(identifier: String, message: String, image: Bitmap? = null) {
|
||||
analyticsManager.logEvent("shared", bundleOf(Pair("identifier", identifier)))
|
||||
val sharingIntent = Intent(Intent.ACTION_SEND)
|
||||
|
|
|
|||
|
|
@ -36,16 +36,17 @@ import com.habitrpg.android.habitica.modules.AppModule
|
|||
import com.habitrpg.android.habitica.ui.adapter.social.challenges.ChallengeTasksRecyclerViewAdapter
|
||||
import com.habitrpg.android.habitica.ui.fragments.social.challenges.ChallengesOverviewFragmentDirections
|
||||
import com.habitrpg.android.habitica.ui.helpers.ToolbarColorHelper
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaProgressDialog
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
class ChallengeFormActivity : BaseActivity() {
|
||||
|
||||
|
|
@ -223,7 +224,7 @@ class ChallengeFormActivity : BaseActivity() {
|
|||
val bundle = intent.extras
|
||||
|
||||
ChallengeTasksRecyclerViewAdapter(
|
||||
null, 0, this, "",
|
||||
TasksViewModel(), 0, this, "",
|
||||
openTaskDisabled = false,
|
||||
taskActionsDisabled = true
|
||||
).also { challengeTasks = it }
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ class LoginActivity : BaseActivity() {
|
|||
}
|
||||
binding.password.imeOptions = EditorInfo.IME_ACTION_DONE
|
||||
binding.fbLoginButton.setText(R.string.login_btn_fb)
|
||||
binding.fbLoginButton.visibility = View.VISIBLE
|
||||
binding.fbLoginButton.visibility = if (configManager.hideFacebook()) View.GONE else View.VISIBLE
|
||||
binding.googleLoginButton.setText(R.string.login_btn_google)
|
||||
}
|
||||
this.resetLayout()
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import android.content.pm.PackageManager
|
|||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.KeyEvent
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
|
|
@ -17,6 +18,7 @@ import androidx.activity.viewModels
|
|||
import androidx.appcompat.app.ActionBarDrawerToggle
|
||||
import androidx.core.view.children
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.NavDestination
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
|
|
@ -35,6 +37,8 @@ import com.habitrpg.android.habitica.extensions.hideKeyboard
|
|||
import com.habitrpg.android.habitica.extensions.isUsingNightModeResources
|
||||
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
|
||||
import com.habitrpg.android.habitica.extensions.updateStatusBarColor
|
||||
import com.habitrpg.android.habitica.helpers.AdHandler
|
||||
import com.habitrpg.android.habitica.helpers.AdType
|
||||
import com.habitrpg.android.habitica.helpers.AmplitudeManager
|
||||
import com.habitrpg.android.habitica.helpers.AppConfigManager
|
||||
import com.habitrpg.android.habitica.helpers.MainNavigationController
|
||||
|
|
@ -63,10 +67,13 @@ import com.habitrpg.android.habitica.widget.AvatarStatsWidgetProvider
|
|||
import com.habitrpg.android.habitica.widget.DailiesWidgetProvider
|
||||
import com.habitrpg.android.habitica.widget.HabitButtonWidgetProvider
|
||||
import com.habitrpg.android.habitica.widget.TodoListWidgetProvider
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.toDuration
|
||||
|
||||
open class MainActivity : BaseActivity(), SnackbarActivity {
|
||||
private var launchScreen: String? = null
|
||||
|
|
@ -445,6 +452,7 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
|
|||
}
|
||||
|
||||
if (this.faintDialog == null && !this.isFinishing) {
|
||||
|
||||
val binding = DialogFaintBinding.inflate(this.layoutInflater)
|
||||
binding.hpBar.setLightBackground(true)
|
||||
binding.hpBar.setIcon(HabiticaIconsHelper.imageOfHeartLightBg())
|
||||
|
|
@ -457,6 +465,20 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
|
|||
faintDialog = null
|
||||
userRepository.revive().subscribe({ }, RxErrorHandler.handleEmptyError())
|
||||
}
|
||||
if (AdHandler.isAllowed(AdType.FAINT)) {
|
||||
val handler = AdHandler(this, AdType.FAINT) {
|
||||
Log.d("AdHandler", "Reviving user")
|
||||
compositeSubscription.add(
|
||||
userRepository.updateUser("stats.hp", 50)
|
||||
.subscribe({}, RxErrorHandler.handleEmptyError())
|
||||
)
|
||||
}
|
||||
handler.prepare()
|
||||
faintDialog?.addButton(R.string.watch_ad_to_revive, true) { _, _ ->
|
||||
faintDialog = null
|
||||
handler.show()
|
||||
}
|
||||
}
|
||||
soundManager.loadAndPlayAudio(SoundManager.SoundDeath)
|
||||
this.faintDialog?.enqueue()
|
||||
}
|
||||
|
|
@ -528,22 +550,33 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
|
|||
return snackbarContainer
|
||||
}
|
||||
|
||||
private var errorJob: Job? = null
|
||||
|
||||
override fun showConnectionProblem(title: String?, message: String) {
|
||||
if (title != null) {
|
||||
super.showConnectionProblem(title, message)
|
||||
} else {
|
||||
binding.connectionIssueTextview.visibility = View.VISIBLE
|
||||
if (errorJob?.isCancelled == false) {
|
||||
// a new error resets the timer to hide the error message
|
||||
errorJob?.cancel()
|
||||
}
|
||||
binding.connectionIssueView.visibility = View.VISIBLE
|
||||
binding.connectionIssueTextview.text = message
|
||||
compositeSubscription.add(
|
||||
Observable.just("")
|
||||
.delay(500, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
binding.connectionIssueTextview.visibility = View.GONE
|
||||
},
|
||||
{}
|
||||
)
|
||||
)
|
||||
errorJob = lifecycleScope.launch(Dispatchers.Main) {
|
||||
delay(1.toDuration(DurationUnit.MINUTES))
|
||||
binding.connectionIssueView.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun hideConnectionProblem() {
|
||||
if (errorJob?.isCancelled == false) {
|
||||
errorJob?.cancel()
|
||||
}
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
if (binding.connectionIssueView.visibility == View.VISIBLE) {
|
||||
binding.connectionIssueView.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,8 +94,8 @@ class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int) : Recycl
|
|||
notifyItemRemoved(x)
|
||||
}
|
||||
for ((index, team) in teams.withIndex()) {
|
||||
val item = HabiticaDrawerItem(R.id.teamBoardFragment, team.id, team.summary)
|
||||
item.bundle = bundleOf(Pair("teamID", team.id))
|
||||
val item = HabiticaDrawerItem(R.id.tasksFragment, team.id, team.summary)
|
||||
item.bundle = bundleOf(Pair("ownerID", team.id))
|
||||
val newIndex = teamHeaderIndex + index + 1
|
||||
items.add(newIndex, item)
|
||||
notifyItemInserted(newIndex)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import android.widget.Button
|
|||
import android.widget.TextView
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.components.UserComponent
|
||||
import com.habitrpg.android.habitica.helpers.TaskFilterHelper
|
||||
import com.habitrpg.android.habitica.models.tasks.Task
|
||||
import com.habitrpg.android.habitica.models.tasks.TaskType
|
||||
import com.habitrpg.android.habitica.ui.adapter.tasks.BaseTasksRecyclerViewAdapter
|
||||
|
|
@ -17,18 +16,19 @@ import com.habitrpg.android.habitica.ui.viewHolders.tasks.DailyViewHolder
|
|||
import com.habitrpg.android.habitica.ui.viewHolders.tasks.HabitViewHolder
|
||||
import com.habitrpg.android.habitica.ui.viewHolders.tasks.RewardViewHolder
|
||||
import com.habitrpg.android.habitica.ui.viewHolders.tasks.TodoViewHolder
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel
|
||||
import io.reactivex.rxjava3.core.BackpressureStrategy
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject
|
||||
|
||||
class ChallengeTasksRecyclerViewAdapter(
|
||||
taskFilterHelper: TaskFilterHelper?,
|
||||
viewModel: TasksViewModel,
|
||||
layoutResource: Int,
|
||||
newContext: Context,
|
||||
userID: String,
|
||||
private val openTaskDisabled: Boolean,
|
||||
private val taskActionsDisabled: Boolean
|
||||
) : BaseTasksRecyclerViewAdapter<BindableViewHolder<Task>>(TaskType.HABIT, taskFilterHelper, layoutResource, newContext, userID) {
|
||||
) : BaseTasksRecyclerViewAdapter<BindableViewHolder<Task>>(TaskType.HABIT, viewModel, layoutResource, newContext, userID) {
|
||||
|
||||
private val addItemSubject = PublishSubject.create<Task>()
|
||||
|
||||
|
|
|
|||
|
|
@ -7,17 +7,17 @@ import android.view.ViewGroup
|
|||
import com.habitrpg.android.habitica.HabiticaBaseApplication
|
||||
import com.habitrpg.android.habitica.components.UserComponent
|
||||
import com.habitrpg.android.habitica.data.TaskRepository
|
||||
import com.habitrpg.android.habitica.helpers.TaskFilterHelper
|
||||
import com.habitrpg.android.habitica.models.tasks.Task
|
||||
import com.habitrpg.android.habitica.models.tasks.TaskType
|
||||
import com.habitrpg.android.habitica.proxy.AnalyticsManager
|
||||
import com.habitrpg.android.habitica.ui.adapter.BaseRecyclerViewAdapter
|
||||
import com.habitrpg.android.habitica.ui.viewHolders.BindableViewHolder
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class BaseTasksRecyclerViewAdapter<VH : BindableViewHolder<Task>>(
|
||||
var taskType: TaskType,
|
||||
private val taskFilterHelper: TaskFilterHelper?,
|
||||
private val viewModel: TasksViewModel,
|
||||
private val layoutResource: Int,
|
||||
newContext: Context,
|
||||
private val userID: String?
|
||||
|
|
@ -73,12 +73,12 @@ abstract class BaseTasksRecyclerViewAdapter<VH : BindableViewHolder<Task>>(
|
|||
}
|
||||
|
||||
fun filter() {
|
||||
if (this.taskFilterHelper == null || this.taskFilterHelper.howMany(taskType) == 0) {
|
||||
if (this.viewModel.howMany(taskType) == 0) {
|
||||
filteredContent = content
|
||||
} else {
|
||||
filteredContent = ArrayList()
|
||||
content?.let {
|
||||
filteredContent?.addAll(this.taskFilterHelper.filter(it))
|
||||
filteredContent?.addAll(this.viewModel.filter(it))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ package com.habitrpg.android.habitica.ui.adapter.tasks
|
|||
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.habitrpg.android.habitica.helpers.TaskFilterHelper
|
||||
import com.habitrpg.android.habitica.ui.viewHolders.tasks.DailyViewHolder
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel
|
||||
|
||||
class DailiesRecyclerViewHolder(layoutResource: Int, taskFilterHelper: TaskFilterHelper) : RealmBaseTasksRecyclerViewAdapter(layoutResource, taskFilterHelper) {
|
||||
class DailiesRecyclerViewHolder(layoutResource: Int, viewModel: TasksViewModel) : RealmBaseTasksRecyclerViewAdapter(layoutResource, viewModel) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return if (viewType == 0) {
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ package com.habitrpg.android.habitica.ui.adapter.tasks
|
|||
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.habitrpg.android.habitica.helpers.TaskFilterHelper
|
||||
import com.habitrpg.android.habitica.ui.viewHolders.tasks.HabitViewHolder
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel
|
||||
|
||||
class HabitsRecyclerViewAdapter(layoutResource: Int, taskFilterHelper: TaskFilterHelper) : RealmBaseTasksRecyclerViewAdapter(layoutResource, taskFilterHelper) {
|
||||
class HabitsRecyclerViewAdapter(layoutResource: Int, viewModel: TasksViewModel) : RealmBaseTasksRecyclerViewAdapter(layoutResource, viewModel) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return if (viewType == 0) {
|
||||
|
|
|
|||
|
|
@ -11,13 +11,13 @@ import com.habitrpg.android.habitica.R
|
|||
import com.habitrpg.android.habitica.databinding.AdventureGuideMenuBannerBinding
|
||||
import com.habitrpg.android.habitica.extensions.dpToPx
|
||||
import com.habitrpg.android.habitica.extensions.layoutInflater
|
||||
import com.habitrpg.android.habitica.helpers.TaskFilterHelper
|
||||
import com.habitrpg.android.habitica.models.responses.TaskDirection
|
||||
import com.habitrpg.android.habitica.models.tasks.ChecklistItem
|
||||
import com.habitrpg.android.habitica.models.tasks.Task
|
||||
import com.habitrpg.android.habitica.models.user.User
|
||||
import com.habitrpg.android.habitica.ui.adapter.BaseRecyclerViewAdapter
|
||||
import com.habitrpg.android.habitica.ui.viewHolders.tasks.BaseTaskViewHolder
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
|
||||
import io.reactivex.rxjava3.core.BackpressureStrategy
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
|
|
@ -27,7 +27,7 @@ import io.realm.OrderedRealmCollection
|
|||
|
||||
abstract class RealmBaseTasksRecyclerViewAdapter(
|
||||
private val layoutResource: Int,
|
||||
private val taskFilterHelper: TaskFilterHelper?
|
||||
private val viewModel: TasksViewModel
|
||||
) : BaseRecyclerViewAdapter<Task, RecyclerView.ViewHolder>(), TaskRecyclerViewAdapter {
|
||||
override var canScoreTasks = true
|
||||
private var unfilteredData: List<Task>? = null
|
||||
|
|
@ -120,8 +120,8 @@ abstract class RealmBaseTasksRecyclerViewAdapter(
|
|||
final override fun filter() {
|
||||
val unfilteredData = this.unfilteredData ?: return
|
||||
|
||||
if (taskFilterHelper != null && unfilteredData is OrderedRealmCollection) {
|
||||
val query = taskFilterHelper.createQuery(unfilteredData)
|
||||
if (unfilteredData is OrderedRealmCollection) {
|
||||
val query = viewModel.createQuery(unfilteredData)
|
||||
if (query != null) {
|
||||
data = query.findAll()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ package com.habitrpg.android.habitica.ui.adapter.tasks
|
|||
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.habitrpg.android.habitica.helpers.TaskFilterHelper
|
||||
import com.habitrpg.android.habitica.ui.viewHolders.tasks.TodoViewHolder
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel
|
||||
|
||||
class TodosRecyclerViewAdapter(layoutResource: Int, taskFilterHelper: TaskFilterHelper) : RealmBaseTasksRecyclerViewAdapter(layoutResource, taskFilterHelper) {
|
||||
class TodosRecyclerViewAdapter(layoutResource: Int, viewModel: TasksViewModel) : RealmBaseTasksRecyclerViewAdapter(layoutResource, viewModel) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return if (viewType == 0) {
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.habitrpg.android.habitica.HabiticaBaseApplication
|
||||
import com.habitrpg.android.habitica.components.UserComponent
|
||||
import com.habitrpg.android.habitica.data.TutorialRepository
|
||||
|
|
@ -18,7 +18,7 @@ import io.reactivex.rxjava3.functions.Consumer
|
|||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class BaseDialogFragment<VB : ViewBinding> : DialogFragment() {
|
||||
abstract class BaseDialogFragment<VB : ViewBinding> : BottomSheetDialogFragment() {
|
||||
|
||||
var isModal: Boolean = false
|
||||
abstract var binding: VB?
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import com.habitrpg.android.habitica.data.InventoryRepository
|
|||
import com.habitrpg.android.habitica.data.SocialRepository
|
||||
import com.habitrpg.android.habitica.data.UserRepository
|
||||
import com.habitrpg.android.habitica.databinding.DrawerMainBinding
|
||||
import com.habitrpg.android.habitica.extensions.getMinuteOrSeconds
|
||||
import com.habitrpg.android.habitica.extensions.getRemainingString
|
||||
import com.habitrpg.android.habitica.extensions.getShortRemainingString
|
||||
import com.habitrpg.android.habitica.extensions.getThemeColor
|
||||
|
|
@ -49,16 +50,18 @@ import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel
|
|||
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.ExperimentalTime
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.time.toDuration
|
||||
|
||||
class NavigationDrawerFragment : DialogFragment() {
|
||||
|
||||
|
|
@ -227,11 +230,9 @@ class NavigationDrawerFragment : DialogFragment() {
|
|||
|
||||
subscriptions?.add(
|
||||
Flowable.combineLatest(
|
||||
contentRepository.getWorldState(), inventoryRepository.getAvailableLimitedItems(),
|
||||
{ state, items ->
|
||||
return@combineLatest Pair(state, items)
|
||||
}
|
||||
).subscribe(
|
||||
contentRepository.getWorldState(), inventoryRepository.getAvailableLimitedItems()) { state, items ->
|
||||
return@combineLatest Pair(state, items)
|
||||
}.subscribe(
|
||||
{ pair ->
|
||||
val gearEvent = pair.first.events.firstOrNull { it.gear }
|
||||
createUpdatingJob("seasonal", {
|
||||
|
|
@ -332,6 +333,10 @@ class NavigationDrawerFragment : DialogFragment() {
|
|||
}
|
||||
|
||||
private fun updateUser(user: User) {
|
||||
binding?.avatarView?.setOnClickListener {
|
||||
MainNavigationController.navigate(R.id.openProfileActivity, bundleOf(Pair("userID", user.id)))
|
||||
}
|
||||
|
||||
setMessagesCount(user.inbox)
|
||||
setSettingsCount(if (user.flags?.verifiedUsername != true) 1 else 0)
|
||||
setDisplayName(user.profile?.name)
|
||||
|
|
@ -675,8 +680,8 @@ class NavigationDrawerFragment : DialogFragment() {
|
|||
createUpdatingJob(activePromo.promoType.name, {
|
||||
activePromo.isActive
|
||||
}, {
|
||||
val diff = activePromo.endDate.time - Date().time
|
||||
if (diff < (Duration.hours(1).inWholeMilliseconds)) Duration.seconds(1) else Duration.minutes(1)
|
||||
val diff = (activePromo.endDate.time - Date().time).toDuration(DurationUnit.SECONDS)
|
||||
1.toDuration(diff.getMinuteOrSeconds())
|
||||
}) {
|
||||
if (activePromo.isActive) {
|
||||
promotedItem.subtitle = context?.getString(R.string.sale_ends_in, activePromo.endDate.getShortRemainingString())
|
||||
|
|
|
|||
|
|
@ -1,29 +1,47 @@
|
|||
package com.habitrpg.android.habitica.ui.fragments.inventory.customization
|
||||
|
||||
import android.graphics.PorterDuff
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.CheckBox
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.components.UserComponent
|
||||
import com.habitrpg.android.habitica.data.CustomizationRepository
|
||||
import com.habitrpg.android.habitica.data.InventoryRepository
|
||||
import com.habitrpg.android.habitica.databinding.BottomSheetBackgroundsFilterBinding
|
||||
import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding
|
||||
import com.habitrpg.android.habitica.extensions.getThemeColor
|
||||
import com.habitrpg.android.habitica.extensions.setTintWith
|
||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||
import com.habitrpg.android.habitica.models.CustomizationFilter
|
||||
import com.habitrpg.android.habitica.models.inventory.Customization
|
||||
import com.habitrpg.android.habitica.models.user.OwnedCustomization
|
||||
import com.habitrpg.android.habitica.models.user.User
|
||||
import com.habitrpg.android.habitica.ui.adapter.CustomizationRecyclerViewAdapter
|
||||
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
|
||||
import com.habitrpg.android.habitica.ui.helpers.MarginDecoration
|
||||
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaBottomSheetDialog
|
||||
import io.reactivex.rxjava3.core.BackpressureStrategy
|
||||
import io.reactivex.rxjava3.kotlin.combineLatest
|
||||
import io.reactivex.rxjava3.subjects.BehaviorSubject
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject
|
||||
import javax.inject.Inject
|
||||
|
||||
class AvatarCustomizationFragment :
|
||||
BaseMainFragment<FragmentRefreshRecyclerviewBinding>(),
|
||||
SwipeRefreshLayout.OnRefreshListener {
|
||||
|
||||
private var filterMenuItem: MenuItem? = null
|
||||
override var binding: FragmentRefreshRecyclerviewBinding? = null
|
||||
|
||||
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRefreshRecyclerviewBinding {
|
||||
|
|
@ -44,6 +62,9 @@ class AvatarCustomizationFragment :
|
|||
internal var adapter: CustomizationRecyclerViewAdapter = CustomizationRecyclerViewAdapter()
|
||||
internal var layoutManager: GridLayoutManager = GridLayoutManager(activity, 2)
|
||||
|
||||
private val currentFilter = BehaviorSubject.create<CustomizationFilter>()
|
||||
private val ownedCustomizations = PublishSubject.create<List<OwnedCustomization>>()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
|
@ -115,6 +136,7 @@ class AvatarCustomizationFragment :
|
|||
this.loadCustomizations()
|
||||
|
||||
userViewModel.user.observe(viewLifecycleOwner) { updateUser(it) }
|
||||
currentFilter.onNext(CustomizationFilter())
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
|
@ -122,6 +144,42 @@ class AvatarCustomizationFragment :
|
|||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.menu_list_customizations, menu)
|
||||
|
||||
filterMenuItem = menu.findItem(R.id.action_filter)
|
||||
updateFilterIcon()
|
||||
}
|
||||
|
||||
private fun updateFilterIcon() {
|
||||
if (currentFilter.value?.isFiltering != true) {
|
||||
filterMenuItem?.setIcon(R.drawable.ic_action_filter_list)
|
||||
context?.let {
|
||||
val filterIcon = ContextCompat.getDrawable(it, R.drawable.ic_action_filter_list)
|
||||
filterIcon?.setTintWith(it.getThemeColor(R.attr.headerTextColor), PorterDuff.Mode.MULTIPLY)
|
||||
filterMenuItem?.setIcon(filterIcon)
|
||||
}
|
||||
} else {
|
||||
context?.let {
|
||||
val filterIcon = ContextCompat.getDrawable(it, R.drawable.ic_filters_active)
|
||||
filterIcon?.setTintWith(it.getThemeColor(R.attr.textColorPrimaryDark), PorterDuff.Mode.MULTIPLY)
|
||||
filterMenuItem?.setIcon(filterIcon)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("ReturnCount")
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.action_filter -> {
|
||||
showFilterDialog()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun injectFragment(component: UserComponent) {
|
||||
component.inject(this)
|
||||
}
|
||||
|
|
@ -129,10 +187,44 @@ class AvatarCustomizationFragment :
|
|||
private fun loadCustomizations() {
|
||||
val type = this.type ?: return
|
||||
compositeSubscription.add(
|
||||
customizationRepository.getCustomizations(type, category, false).subscribe(
|
||||
{
|
||||
adapter.setCustomizations(if (type == "background") { it.reversed() } else { it })
|
||||
},
|
||||
customizationRepository.getCustomizations(type, category, false)
|
||||
.combineLatest(currentFilter.toFlowable(BackpressureStrategy.DROP),
|
||||
ownedCustomizations.toFlowable(BackpressureStrategy.DROP))
|
||||
.subscribe(
|
||||
{ (customizations, filter, ownedCustomizations) ->
|
||||
if (filter.isFiltering) {
|
||||
val displayedCustomizations = mutableListOf<Customization>()
|
||||
for (customization in customizations) {
|
||||
if (filter.onlyPurchased) {
|
||||
if (ownedCustomizations.find { it.key == customization.identifier } == null) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if (filter.months.isNotEmpty()) {
|
||||
if (!filter.months.contains(customization.customizationSetName?.substringAfter('.'))) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
displayedCustomizations.add(customization)
|
||||
}
|
||||
adapter.setCustomizations(
|
||||
if (!filter.ascending) {
|
||||
displayedCustomizations.reversed()
|
||||
} else {
|
||||
displayedCustomizations
|
||||
}
|
||||
)
|
||||
} else {
|
||||
adapter.setCustomizations(
|
||||
if (!filter.ascending) {
|
||||
customizations.reversed()
|
||||
} else {
|
||||
customizations
|
||||
}
|
||||
)
|
||||
}
|
||||
adapter.ownedCustomizations = ownedCustomizations.map { it.key + "_" + it.type + "_" + it.category }
|
||||
},
|
||||
RxErrorHandler.handleEmptyError()
|
||||
)
|
||||
)
|
||||
|
|
@ -154,9 +246,7 @@ class AvatarCustomizationFragment :
|
|||
fun updateUser(user: User?) {
|
||||
if (user == null) return
|
||||
this.updateActiveCustomization(user)
|
||||
val ownedCustomizations = ArrayList<String>()
|
||||
user.purchased?.customizations?.filter { it.type == this.type && it.purchased }?.mapTo(ownedCustomizations) { it.key + "_" + it.type + "_" + it.category }
|
||||
adapter.updateOwnership(ownedCustomizations)
|
||||
ownedCustomizations.onNext(user.purchased?.customizations?.filter { it.type == this.type && it.purchased })
|
||||
this.adapter.userSize = user.preferences?.size
|
||||
this.adapter.hairColor = user.preferences?.hair?.color
|
||||
this.adapter.gemBalance = user.gemCount
|
||||
|
|
@ -192,7 +282,7 @@ class AvatarCustomizationFragment :
|
|||
|
||||
override fun onRefresh() {
|
||||
compositeSubscription.add(
|
||||
userRepository.retrieveUser(false, true).subscribe(
|
||||
userRepository.retrieveUser(withTasks = false, forced = true).subscribe(
|
||||
{
|
||||
binding?.refreshLayout?.isRefreshing = false
|
||||
},
|
||||
|
|
@ -200,4 +290,60 @@ class AvatarCustomizationFragment :
|
|||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun showFilterDialog() {
|
||||
val filter = currentFilter.value ?: CustomizationFilter()
|
||||
val context = context ?: return
|
||||
val dialog = HabiticaBottomSheetDialog(context)
|
||||
val binding = BottomSheetBackgroundsFilterBinding.inflate(layoutInflater)
|
||||
binding.showMeWrapper.check(if (filter.onlyPurchased) R.id.show_purchased_button else R.id.show_all_button)
|
||||
binding.showMeWrapper.setOnCheckedChangeListener { _, checkedId ->
|
||||
filter.onlyPurchased = checkedId == R.id.show_purchased_button
|
||||
currentFilter.onNext(filter)
|
||||
}
|
||||
binding.clearButton.setOnClickListener {
|
||||
currentFilter.onNext(CustomizationFilter())
|
||||
dialog.dismiss()
|
||||
}
|
||||
if (type == "background") {
|
||||
binding.sortByWrapper.check(if (filter.ascending) R.id.oldest_button else R.id.newest_button)
|
||||
binding.sortByWrapper.setOnCheckedChangeListener { _, checkedId ->
|
||||
filter.ascending = checkedId == R.id.oldest_button
|
||||
currentFilter.onNext(filter)
|
||||
}
|
||||
configureMonthFilterButton(binding.januaryButton, 1, filter)
|
||||
configureMonthFilterButton(binding.febuaryButton, 2, filter)
|
||||
configureMonthFilterButton(binding.marchButton, 3, filter)
|
||||
configureMonthFilterButton(binding.aprilButton, 4, filter)
|
||||
configureMonthFilterButton(binding.mayButton, 5, filter)
|
||||
configureMonthFilterButton(binding.juneButton, 6, filter)
|
||||
configureMonthFilterButton(binding.julyButton, 7, filter)
|
||||
configureMonthFilterButton(binding.augustButton, 8, filter)
|
||||
configureMonthFilterButton(binding.septemberButton, 9, filter)
|
||||
configureMonthFilterButton(binding.octoberButton, 10, filter)
|
||||
configureMonthFilterButton(binding.novemberButton, 11, filter)
|
||||
configureMonthFilterButton(binding.decemberButton, 12, filter)
|
||||
} else {
|
||||
binding.sortByTitle.visibility = View.GONE
|
||||
binding.sortByWrapper.visibility = View.GONE
|
||||
binding.monthReleasedTitle.visibility = View.GONE
|
||||
binding.monthReleasedWrapper.visibility = View.GONE
|
||||
}
|
||||
dialog.setContentView(binding.root)
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
private fun configureMonthFilterButton(button: CheckBox, value: Int, filter: CustomizationFilter) {
|
||||
val identifier = value.toString().padStart(2, '0')
|
||||
button.isChecked = filter.months.contains(identifier)
|
||||
button.text
|
||||
button.setOnCheckedChangeListener { _, isChecked ->
|
||||
if (!isChecked && filter.months.contains(identifier)) {
|
||||
filter.months.remove(identifier)
|
||||
} else if (isChecked && !filter.months.contains(identifier)) {
|
||||
filter.months.add(identifier)
|
||||
}
|
||||
currentFilter.onNext(filter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,14 +70,6 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare
|
|||
val useReminder = preferenceManager.sharedPreferences?.getBoolean("use_reminder", false)
|
||||
timePreference?.isEnabled = useReminder ?: false
|
||||
|
||||
pushNotificationsPreference = findPreference("pushNotifications") as? PreferenceScreen
|
||||
val usePushNotifications = preferenceManager.sharedPreferences?.getBoolean("usePushNotifications", true)
|
||||
pushNotificationsPreference?.isEnabled = usePushNotifications ?: false
|
||||
|
||||
emailNotificationsPreference = findPreference("emailNotifications") as? PreferenceScreen
|
||||
val useEmailNotifications = preferenceManager.sharedPreferences?.getBoolean("useEmailNotifications", true)
|
||||
emailNotificationsPreference?.isEnabled = useEmailNotifications ?: false
|
||||
|
||||
classSelectionPreference = findPreference("choose_class")
|
||||
|
||||
val weekdayPreference = findPreference("FirstDayOfTheWeek") as? ListPreference
|
||||
|
|
@ -193,23 +185,24 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare
|
|||
"usePushNotifications" -> {
|
||||
val userPushNotifications = sharedPreferences.getBoolean(key, false)
|
||||
pushNotificationsPreference?.isEnabled = userPushNotifications
|
||||
userRepository.updateUser("preferences.pushNotifications.unsubscribeFromAll", userPushNotifications).subscribe()
|
||||
if (userPushNotifications) {
|
||||
pushNotificationManager.addPushDeviceUsingStoredToken()
|
||||
} else {
|
||||
pushNotificationManager.removePushDeviceUsingStoredToken()
|
||||
}
|
||||
}
|
||||
"useEmailNotifications" -> {
|
||||
"useEmails" -> {
|
||||
val useEmailNotifications = sharedPreferences.getBoolean(key, false)
|
||||
emailNotificationsPreference?.isEnabled = useEmailNotifications
|
||||
userRepository.updateUser("preferences.emailNotifications.unsubscribeFromAll", useEmailNotifications).subscribe()
|
||||
}
|
||||
"cds_time" -> {
|
||||
val timeval = sharedPreferences.getString("cds_time", "00:00")
|
||||
val pieces = timeval?.split(":".toRegex())?.dropLastWhile { it.isEmpty() }?.toTypedArray()
|
||||
if (pieces != null) {
|
||||
val hour = Integer.parseInt(pieces[0])
|
||||
userRepository.changeCustomDayStart(hour).subscribe({ }, RxErrorHandler.handleEmptyError())
|
||||
}
|
||||
val timeval = sharedPreferences.getString("cds_time", "0") ?: "0"
|
||||
val hour = Integer.parseInt(timeval)
|
||||
userRepository.changeCustomDayStart(hour).subscribe({ }, RxErrorHandler.handleEmptyError())
|
||||
val preference = findPreference<ListPreference>(key)
|
||||
preference?.summary = preference?.entry
|
||||
}
|
||||
"language" -> {
|
||||
val languageHelper = LanguageHelper(sharedPreferences.getString(key, "en"))
|
||||
|
|
@ -316,8 +309,9 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare
|
|||
} else {
|
||||
classSelectionPreference?.isVisible = false
|
||||
}
|
||||
val cdsTimePreference = findPreference("cds_time") as? TimePreference
|
||||
cdsTimePreference?.text = user?.preferences?.dayStart.toString() + ":00"
|
||||
val cdsTimePreference = findPreference("cds_time") as? ListPreference
|
||||
cdsTimePreference?.value = user?.preferences?.dayStart.toString()
|
||||
cdsTimePreference?.summary = cdsTimePreference?.entry
|
||||
findPreference<Preference>("dailyDueDefaultView")?.setDefaultValue(user?.preferences?.dailyDueDefaultView)
|
||||
val languagePreference = findPreference("language") as? ListPreference
|
||||
languagePreference?.value = user?.preferences?.language
|
||||
|
|
@ -345,6 +339,18 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare
|
|||
val inbox = user?.inbox
|
||||
disablePMsPreference?.isChecked = inbox?.optOut ?: true
|
||||
|
||||
val usePushPreference = findPreference("usePushNotifications") as? CheckBoxPreference
|
||||
pushNotificationsPreference = findPreference("pushNotifications") as? PreferenceScreen
|
||||
val usePushNotifications = user?.preferences?.pushNotifications?.unsubscribeFromAll ?: false
|
||||
pushNotificationsPreference?.isEnabled = usePushNotifications
|
||||
usePushPreference?.isChecked = usePushNotifications
|
||||
|
||||
val useEmailPreference = findPreference("useEmails") as? CheckBoxPreference
|
||||
emailNotificationsPreference = findPreference("emailNotifications") as? PreferenceScreen
|
||||
val useEmailNotifications = user?.preferences?.emailNotifications?.unsubscribeFromAll ?: false
|
||||
emailNotificationsPreference?.isEnabled = useEmailNotifications
|
||||
useEmailPreference?.isChecked = useEmailNotifications
|
||||
|
||||
if (configManager.testingLevel() == AppTestingLevel.STAFF || BuildConfig.DEBUG) {
|
||||
serverUrlPreference?.isVisible = true
|
||||
taskListPreference?.isVisible = true
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.habitrpg.android.habitica.R
|
|||
import com.habitrpg.android.habitica.databinding.DialogChallengeFilterBinding
|
||||
import com.habitrpg.android.habitica.models.social.Group
|
||||
import com.habitrpg.android.habitica.ui.adapter.social.challenges.ChallengesFilterRecyclerViewAdapter
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaBottomSheetDialog
|
||||
import com.habitrpg.android.habitica.utils.Action1
|
||||
|
||||
internal class ChallengeFilterDialogHolder private constructor(
|
||||
|
|
@ -28,13 +29,10 @@ internal class ChallengeFilterDialogHolder private constructor(
|
|||
}
|
||||
|
||||
fun bind(
|
||||
builder: AlertDialog.Builder,
|
||||
filterGroups: List<Group>,
|
||||
currentFilter: ChallengeFilterOptions?,
|
||||
selectedGroupsCallback: Action1<ChallengeFilterOptions>
|
||||
) {
|
||||
builder.setPositiveButton(context.getString(R.string.done)) { _, _ -> doneClicked() }
|
||||
.show()
|
||||
this.filterGroups = filterGroups
|
||||
this.currentFilter = currentFilter
|
||||
this.selectedGroupsCallback = selectedGroupsCallback
|
||||
|
|
@ -86,11 +84,11 @@ internal class ChallengeFilterDialogHolder private constructor(
|
|||
|
||||
val challengeFilterDialogHolder = ChallengeFilterDialogHolder(dialogLayout, activity)
|
||||
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.filter)
|
||||
.setView(dialogLayout)
|
||||
val sheet = HabiticaBottomSheetDialog(activity)
|
||||
sheet.setContentView(dialogLayout)
|
||||
|
||||
challengeFilterDialogHolder.bind(builder, filterGroups, currentFilter, selectedGroupsCallback)
|
||||
challengeFilterDialogHolder.bind(filterGroups, currentFilter, selectedGroupsCallback)
|
||||
sheet.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import androidx.core.content.ContextCompat
|
|||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.data.InventoryRepository
|
||||
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
|
||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||
import com.habitrpg.android.habitica.models.shops.ShopItem
|
||||
|
|
@ -22,6 +23,7 @@ import com.habitrpg.android.habitica.ui.adapter.tasks.RewardsRecyclerViewAdapter
|
|||
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
|
||||
import io.reactivex.rxjava3.functions.Consumer
|
||||
import javax.inject.Inject
|
||||
|
||||
class RewardsRecyclerviewFragment : TaskRecyclerViewFragment() {
|
||||
|
||||
|
|
@ -87,6 +89,11 @@ class RewardsRecyclerviewFragment : TaskRecyclerViewFragment() {
|
|||
)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
inventoryRepository.close()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun getLayoutManager(context: Context?): LinearLayoutManager {
|
||||
return GridLayoutManager(context, 4)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,18 +5,15 @@ import android.content.Intent
|
|||
import android.content.SharedPreferences
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.os.Bundle
|
||||
import android.text.format.DateUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.edit
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.NO_POSITION
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.components.UserComponent
|
||||
import com.habitrpg.android.habitica.data.ApiClient
|
||||
import com.habitrpg.android.habitica.data.InventoryRepository
|
||||
import com.habitrpg.android.habitica.data.TaskRepository
|
||||
import com.habitrpg.android.habitica.data.UserRepository
|
||||
|
|
@ -24,13 +21,11 @@ import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBind
|
|||
import com.habitrpg.android.habitica.extensions.observeOnce
|
||||
import com.habitrpg.android.habitica.extensions.setScaledPadding
|
||||
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
|
||||
import com.habitrpg.android.habitica.helpers.AmplitudeManager
|
||||
import com.habitrpg.android.habitica.helpers.AppConfigManager
|
||||
import com.habitrpg.android.habitica.helpers.HapticFeedbackManager
|
||||
import com.habitrpg.android.habitica.helpers.MainNavigationController
|
||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||
import com.habitrpg.android.habitica.helpers.SoundManager
|
||||
import com.habitrpg.android.habitica.helpers.TaskFilterHelper
|
||||
import com.habitrpg.android.habitica.models.responses.TaskDirection
|
||||
import com.habitrpg.android.habitica.models.responses.TaskScoringResult
|
||||
import com.habitrpg.android.habitica.models.tasks.Task
|
||||
|
|
@ -47,16 +42,21 @@ import com.habitrpg.android.habitica.ui.fragments.BaseFragment
|
|||
import com.habitrpg.android.habitica.ui.helpers.EmptyItem
|
||||
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
|
||||
import com.habitrpg.android.habitica.ui.viewHolders.tasks.BaseTaskViewHolder
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.disposables.Disposable
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBinding>(), androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener {
|
||||
var viewModel: TasksViewModel? = null
|
||||
|
||||
private var taskSubscription: Disposable? = null
|
||||
internal var canEditTasks: Boolean = true
|
||||
internal var canScoreTaks: Boolean = true
|
||||
override var binding: FragmentRefreshRecyclerviewBinding? = null
|
||||
|
|
@ -69,10 +69,6 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
|
|||
var recyclerAdapter: TaskRecyclerViewAdapter? = null
|
||||
var itemAnimator = SafeDefaultItemAnimator()
|
||||
@Inject
|
||||
lateinit var apiClient: ApiClient
|
||||
@Inject
|
||||
lateinit var taskFilterHelper: TaskFilterHelper
|
||||
@Inject
|
||||
lateinit var userRepository: UserRepository
|
||||
@Inject
|
||||
lateinit var inventoryRepository: InventoryRepository
|
||||
|
|
@ -90,8 +86,6 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
|
|||
internal var taskType: TaskType = TaskType.HABIT
|
||||
private var itemTouchCallback: ItemTouchHelper.Callback? = null
|
||||
|
||||
var refreshAction: ((() -> Unit) -> Unit)? = null
|
||||
|
||||
internal val className: TaskType
|
||||
get() = this.taskType
|
||||
|
||||
|
|
@ -103,18 +97,19 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
|
|||
recyclerSubscription.dispose()
|
||||
}
|
||||
recyclerSubscription = CompositeDisposable()
|
||||
val adapter: BaseRecyclerViewAdapter<*, *>? = when (this.taskType) {
|
||||
TaskType.HABIT -> HabitsRecyclerViewAdapter(R.layout.habit_item_card, taskFilterHelper)
|
||||
TaskType.DAILY -> DailiesRecyclerViewHolder(R.layout.daily_item_card, taskFilterHelper)
|
||||
TaskType.TODO -> TodosRecyclerViewAdapter(R.layout.todo_item_card, taskFilterHelper)
|
||||
TaskType.REWARD -> RewardsRecyclerViewAdapter(null, R.layout.reward_item_card)
|
||||
else -> null
|
||||
viewModel?.let { viewModel ->
|
||||
val adapter: BaseRecyclerViewAdapter<*, *>? = when (this.taskType) {
|
||||
TaskType.HABIT -> HabitsRecyclerViewAdapter(R.layout.habit_item_card, viewModel)
|
||||
TaskType.DAILY -> DailiesRecyclerViewHolder(R.layout.daily_item_card, viewModel)
|
||||
TaskType.TODO -> TodosRecyclerViewAdapter(R.layout.todo_item_card, viewModel)
|
||||
TaskType.REWARD -> RewardsRecyclerViewAdapter(null, R.layout.reward_item_card)
|
||||
else -> null
|
||||
}
|
||||
|
||||
recyclerAdapter = adapter as? TaskRecyclerViewAdapter
|
||||
recyclerAdapter?.canScoreTasks = canScoreTaks
|
||||
binding?.recyclerView?.adapter = adapter
|
||||
}
|
||||
|
||||
recyclerAdapter = adapter as? TaskRecyclerViewAdapter
|
||||
recyclerAdapter?.canScoreTasks = canScoreTaks
|
||||
binding?.recyclerView?.adapter = adapter
|
||||
|
||||
context?.let { recyclerAdapter?.taskDisplayMode = configManager.taskDisplayMode(it) }
|
||||
|
||||
recyclerAdapter?.errorButtonEvents?.subscribe(
|
||||
|
|
@ -136,14 +131,11 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
|
|||
recyclerAdapter?.brokenTaskEvents?.subscribeWithErrorHandler { showBrokenChallengeDialog(it) }?.let { recyclerSubscription.add(it) }
|
||||
recyclerAdapter?.adventureGuideOpenEvents?.subscribeWithErrorHandler { MainNavigationController.navigate(R.id.adventureGuideActivity) }?.let { recyclerSubscription.add(it) }
|
||||
|
||||
recyclerSubscription.add(
|
||||
taskRepository.getTasks(this.taskType).subscribe(
|
||||
{
|
||||
this.recyclerAdapter?.updateUnfilteredData(it)
|
||||
},
|
||||
RxErrorHandler.handleEmptyError()
|
||||
)
|
||||
)
|
||||
viewModel?.ownerID?.observe(viewLifecycleOwner) {
|
||||
canEditTasks = viewModel?.isPersonalBoard ?: true
|
||||
canScoreTaks = viewModel?.isPersonalBoard ?: true
|
||||
updateTaskSubscription(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleTaskResult(result: TaskScoringResult, value: Int) {
|
||||
|
|
@ -190,7 +182,6 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
|
|||
|
||||
override fun onDestroy() {
|
||||
userRepository.close()
|
||||
inventoryRepository.close()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
|
|
@ -265,7 +256,7 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
|
|||
) {
|
||||
if (validTaskId != null) {
|
||||
var newPosition = viewHolder.absoluteAdapterPosition
|
||||
if (taskFilterHelper.howMany(taskType) > 0) {
|
||||
if ((viewModel?.howMany(taskType) ?: 0) > 0) {
|
||||
newPosition = if ((newPosition + 1) == recyclerAdapter?.data?.size) {
|
||||
recyclerAdapter?.data?.get(newPosition - 1)?.position ?: newPosition
|
||||
} else {
|
||||
|
|
@ -318,6 +309,16 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
|
|||
)
|
||||
}
|
||||
|
||||
|
||||
private fun updateTaskSubscription(ownerID: String?) {
|
||||
taskSubscription = taskRepository.getTasks(this.taskType, ownerID).subscribe(
|
||||
{
|
||||
this.recyclerAdapter?.updateUnfilteredData(it)
|
||||
},
|
||||
RxErrorHandler.handleEmptyError()
|
||||
)
|
||||
}
|
||||
|
||||
protected fun showBrokenChallengeDialog(task: Task) {
|
||||
context?.let {
|
||||
if (!task.isValid) {
|
||||
|
|
@ -354,7 +355,7 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
|
|||
}
|
||||
|
||||
private fun setEmptyLabels() {
|
||||
binding?.recyclerView?.emptyItem = if (taskFilterHelper.howMany(taskType) > 0) {
|
||||
binding?.recyclerView?.emptyItem = if ((viewModel?.howMany(taskType) ?: 0) > 0) {
|
||||
when (this.taskType) {
|
||||
TaskType.HABIT -> {
|
||||
EmptyItem(
|
||||
|
|
@ -422,21 +423,9 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
|
|||
}
|
||||
|
||||
private fun scoreTask(task: Task, direction: TaskDirection) {
|
||||
compositeSubscription.add(
|
||||
taskRepository.taskChecked(null, task.id ?: "", direction == TaskDirection.UP, false) { result ->
|
||||
handleTaskResult(result, task.value.toInt())
|
||||
if (!DateUtils.isToday(sharedPreferences.getLong("last_task_reporting", 0))) {
|
||||
AmplitudeManager.sendEvent(
|
||||
"task scored",
|
||||
AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR,
|
||||
AmplitudeManager.EVENT_HITTYPE_EVENT
|
||||
)
|
||||
sharedPreferences.edit {
|
||||
putLong("last_task_reporting", Date().time)
|
||||
}
|
||||
}
|
||||
}.subscribe()
|
||||
)
|
||||
viewModel?.scoreTask(task, direction) { result, value ->
|
||||
handleTaskResult(result, value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
|
|
@ -449,7 +438,7 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
|
|||
|
||||
override fun onRefresh() {
|
||||
binding?.refreshLayout?.isRefreshing = true
|
||||
refreshAction?.invoke {
|
||||
viewModel?.refreshData {
|
||||
binding?.refreshLayout?.isRefreshing = false
|
||||
}
|
||||
}
|
||||
|
|
@ -459,13 +448,13 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
|
|||
(activity as? MainActivity)?.viewModel?.user?.observeOnce(this) {
|
||||
if (it != null) {
|
||||
when (taskType) {
|
||||
TaskType.TODO -> taskFilterHelper.setActiveFilter(
|
||||
TaskType.TODO -> viewModel?.setActiveFilter(
|
||||
TaskType.TODO,
|
||||
Task.FILTER_ACTIVE
|
||||
)
|
||||
TaskType.DAILY -> {
|
||||
if (it.isValid && it.preferences?.dailyDueDefaultView == true) {
|
||||
taskFilterHelper.setActiveFilter(TaskType.DAILY, Task.FILTER_ACTIVE)
|
||||
viewModel?.setActiveFilter(TaskType.DAILY, Task.FILTER_ACTIVE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -481,7 +470,7 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
|
|||
}
|
||||
|
||||
fun setActiveFilter(activeFilter: String) {
|
||||
taskFilterHelper.setActiveFilter(taskType, activeFilter)
|
||||
viewModel?.setActiveFilter(taskType, activeFilter)
|
||||
recyclerAdapter?.filter()
|
||||
|
||||
setEmptyLabels()
|
||||
|
|
@ -512,9 +501,10 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
|
|||
companion object {
|
||||
private const val CLASS_TYPE_KEY = "CLASS_TYPE_KEY"
|
||||
|
||||
fun newInstance(context: Context?, classType: TaskType): TaskRecyclerViewFragment {
|
||||
fun newInstance(context: Context?, classType: TaskType, viewModel: TasksViewModel): TaskRecyclerViewFragment {
|
||||
val fragment = TaskRecyclerViewFragment()
|
||||
fragment.taskType = classType
|
||||
fragment.viewModel = viewModel
|
||||
var tutorialTexts: List<String>? = null
|
||||
if (context != null) {
|
||||
when (fragment.taskType) {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.habitrpg.android.habitica.ui.fragments.tasks
|
|||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.PorterDuff
|
||||
import android.os.Bundle
|
||||
import android.text.format.DateUtils
|
||||
|
|
@ -16,51 +15,38 @@ import androidx.activity.result.contract.ActivityResultContracts
|
|||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.habitrpg.android.habitica.HabiticaBaseApplication
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.components.UserComponent
|
||||
import com.habitrpg.android.habitica.data.TagRepository
|
||||
import com.habitrpg.android.habitica.databinding.FragmentViewpagerBinding
|
||||
import com.habitrpg.android.habitica.extensions.getThemeColor
|
||||
import com.habitrpg.android.habitica.extensions.setTintWith
|
||||
import com.habitrpg.android.habitica.helpers.AmplitudeManager
|
||||
import com.habitrpg.android.habitica.helpers.AppConfigManager
|
||||
import com.habitrpg.android.habitica.helpers.MainNavigationController
|
||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||
import com.habitrpg.android.habitica.helpers.TaskFilterHelper
|
||||
import com.habitrpg.android.habitica.models.tasks.TaskType
|
||||
import com.habitrpg.android.habitica.modules.AppModule
|
||||
import com.habitrpg.android.habitica.ui.activities.TaskFormActivity
|
||||
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel
|
||||
import com.habitrpg.android.habitica.ui.views.navigation.HabiticaBottomNavigationViewListener
|
||||
import com.habitrpg.android.habitica.ui.views.tasks.TaskFilterDialog
|
||||
import io.reactivex.rxjava3.disposables.Disposable
|
||||
import java.util.Date
|
||||
import java.util.WeakHashMap
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.OnQueryTextListener, HabiticaBottomNavigationViewListener {
|
||||
|
||||
internal val viewModel: TasksViewModel by viewModels()
|
||||
override var binding: FragmentViewpagerBinding? = null
|
||||
|
||||
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentViewpagerBinding {
|
||||
return FragmentViewpagerBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
@field:[Inject Named(AppModule.NAMED_USER_ID)]
|
||||
lateinit var userID: String
|
||||
@Inject
|
||||
lateinit var taskFilterHelper: TaskFilterHelper
|
||||
@Inject
|
||||
lateinit var tagRepository: TagRepository
|
||||
@Inject
|
||||
lateinit var appConfigManager: AppConfigManager
|
||||
@Inject
|
||||
lateinit var sharedPreferences: SharedPreferences
|
||||
|
||||
private var refreshItem: MenuItem? = null
|
||||
internal var viewFragmentsDictionary: MutableMap<Int, TaskRecyclerViewFragment>? = WeakHashMap()
|
||||
|
||||
|
|
@ -93,11 +79,16 @@ class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.O
|
|||
arguments?.let {
|
||||
val args = TasksFragmentArgs.fromBundle(it)
|
||||
val taskTypeValue = args.taskType
|
||||
if (args.ownerID?.isNotBlank() == true) {
|
||||
viewModel.ownerID.value = args.ownerID ?: viewModel.userID
|
||||
} else {
|
||||
viewModel.ownerID.value = viewModel.userID
|
||||
}
|
||||
if (taskTypeValue?.isNotBlank() == true) {
|
||||
val taskType = TaskType.from(taskTypeValue)
|
||||
switchToTaskTab(taskType)
|
||||
} else {
|
||||
when (sharedPreferences.getString("launch_screen", "")) {
|
||||
when (viewModel.sharedPreferences.getString("launch_screen", "")) {
|
||||
"/user/tasks/habits" -> onTabSelected(TaskType.HABIT, false)
|
||||
"/user/tasks/dailies" -> onTabSelected(TaskType.DAILY, false)
|
||||
"/user/tasks/todos" -> onTabSelected(TaskType.TODO, false)
|
||||
|
|
@ -105,6 +96,10 @@ class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.O
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.ownerID.observe(viewLifecycleOwner) {
|
||||
updateBoardDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
|
@ -118,7 +113,11 @@ class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.O
|
|||
}
|
||||
binding?.viewPager?.currentItem = binding?.viewPager?.currentItem ?: 0
|
||||
bottomNavigation?.listener = this
|
||||
bottomNavigation?.canAddTasks = true
|
||||
bottomNavigation?.canAddTasks = viewModel.isPersonalBoard
|
||||
|
||||
activity?.binding?.toolbarTitle?.setOnClickListener {
|
||||
viewModel.cycleOwnerIDs()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
|
|
@ -128,17 +127,16 @@ class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.O
|
|||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
tagRepository.close()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun injectFragment(component: UserComponent) {
|
||||
component.inject(this)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.menu_main_activity, menu)
|
||||
if (viewModel.isPersonalBoard) {
|
||||
inflater.inflate(R.menu.menu_main_activity, menu)
|
||||
} else {
|
||||
inflater.inflate(R.menu.menu_team_board, menu)
|
||||
}
|
||||
|
||||
filterMenuItem = menu.findItem(R.id.action_filter)
|
||||
updateFilterIcon()
|
||||
|
|
@ -167,7 +165,7 @@ class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.O
|
|||
}
|
||||
|
||||
override fun onQueryTextChange(newText: String?): Boolean {
|
||||
taskFilterHelper.searchQuery = newText
|
||||
viewModel.searchQuery = newText
|
||||
viewFragmentsDictionary?.values?.forEach { values -> values.recyclerAdapter?.filter() }
|
||||
return true
|
||||
}
|
||||
|
|
@ -180,7 +178,11 @@ class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.O
|
|||
}
|
||||
R.id.action_reload -> {
|
||||
refreshItem = item
|
||||
refresh()
|
||||
viewModel.refreshData { }
|
||||
true
|
||||
}
|
||||
R.id.action_team_info -> {
|
||||
MainNavigationController.navigate(R.id.guildFragment, bundleOf(Pair("groupID", viewModel.ownerID)))
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
|
|
@ -191,15 +193,15 @@ class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.O
|
|||
context?.let {
|
||||
val disposable: Disposable
|
||||
val dialog = TaskFilterDialog(it, HabiticaBaseApplication.userComponent)
|
||||
disposable = tagRepository.getTags().subscribe({ tagsList -> dialog.setTags(tagsList) }, RxErrorHandler.handleEmptyError())
|
||||
dialog.setActiveTags(taskFilterHelper.tags)
|
||||
disposable = viewModel.tagRepository.getTags().subscribe({ tagsList -> dialog.setTags(tagsList) }, RxErrorHandler.handleEmptyError())
|
||||
dialog.setActiveTags(viewModel.tags)
|
||||
|
||||
// There are some cases where these things might not be correctly set after the app resumes. This is just to catch that as best as possible
|
||||
val navigation = bottomNavigation ?: activity?.binding?.bottomNavigation
|
||||
val taskType = navigation?.activeTaskType ?: activeFragment?.taskType
|
||||
|
||||
if (taskType != null) {
|
||||
dialog.setTaskType(taskType, taskFilterHelper.getActiveFilter(taskType))
|
||||
dialog.setTaskType(taskType, viewModel.getActiveFilter(taskType))
|
||||
}
|
||||
dialog.setListener(object : TaskFilterDialog.OnFilterCompletedListener {
|
||||
override fun onFilterCompleted(
|
||||
|
|
@ -209,7 +211,7 @@ class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.O
|
|||
if (viewFragmentsDictionary == null) {
|
||||
return
|
||||
}
|
||||
taskFilterHelper.tags = activeTags
|
||||
viewModel.tags = activeTags
|
||||
if (activeTaskFilter != null) {
|
||||
activeFragment?.setActiveFilter(activeTaskFilter)
|
||||
}
|
||||
|
|
@ -226,34 +228,18 @@ class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.O
|
|||
}
|
||||
}
|
||||
|
||||
private fun refresh() {
|
||||
activeFragment?.onRefresh()
|
||||
}
|
||||
|
||||
private fun loadTaskLists() {
|
||||
val fragmentManager = childFragmentManager
|
||||
|
||||
binding?.viewPager?.adapter = object : FragmentStateAdapter(fragmentManager, lifecycle) {
|
||||
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
val fragment: TaskRecyclerViewFragment = when (position) {
|
||||
0 -> TaskRecyclerViewFragment.newInstance(context, TaskType.HABIT)
|
||||
1 -> TaskRecyclerViewFragment.newInstance(context, TaskType.DAILY)
|
||||
0 -> TaskRecyclerViewFragment.newInstance(context, TaskType.HABIT, viewModel)
|
||||
1 -> TaskRecyclerViewFragment.newInstance(context, TaskType.DAILY, viewModel)
|
||||
3 -> RewardsRecyclerviewFragment.newInstance(context, TaskType.REWARD, true)
|
||||
else -> TaskRecyclerViewFragment.newInstance(context, TaskType.TODO)
|
||||
}
|
||||
fragment.refreshAction = {
|
||||
compositeSubscription.add(
|
||||
userRepository.retrieveUser(
|
||||
withTasks = true,
|
||||
forced = true
|
||||
).doOnTerminate {
|
||||
it()
|
||||
}.subscribe({ }, RxErrorHandler.handleEmptyError())
|
||||
)
|
||||
else -> TaskRecyclerViewFragment.newInstance(context, TaskType.TODO, viewModel)
|
||||
}
|
||||
viewFragmentsDictionary?.put(position, fragment)
|
||||
|
||||
return fragment
|
||||
}
|
||||
|
||||
|
|
@ -271,7 +257,7 @@ class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.O
|
|||
}
|
||||
|
||||
private fun updateFilterIcon() {
|
||||
val filterCount = taskFilterHelper.howMany(activeFragment?.taskType)
|
||||
val filterCount = viewModel.howMany(activeFragment?.taskType)
|
||||
|
||||
filterMenuItem?.isVisible = activeFragment?.taskType != TaskType.REWARD
|
||||
if (filterCount == 0) {
|
||||
|
|
@ -361,7 +347,7 @@ class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.O
|
|||
|
||||
val bundle = Bundle()
|
||||
bundle.putString(TaskFormActivity.TASK_TYPE_KEY, type.value)
|
||||
bundle.putStringArrayList(TaskFormActivity.SELECTED_TAGS_KEY, ArrayList(taskFilterHelper.tags))
|
||||
bundle.putStringArrayList(TaskFormActivity.SELECTED_TAGS_KEY, ArrayList(viewModel.tags))
|
||||
|
||||
val intent = Intent(activity, TaskFormActivity::class.java)
|
||||
intent.putExtras(bundle)
|
||||
|
|
@ -391,13 +377,13 @@ class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.O
|
|||
}
|
||||
}
|
||||
|
||||
if (!DateUtils.isToday(sharedPreferences.getLong("last_creation_reporting", 0))) {
|
||||
if (!DateUtils.isToday(viewModel.sharedPreferences.getLong("last_creation_reporting", 0))) {
|
||||
AmplitudeManager.sendEvent(
|
||||
"task created",
|
||||
AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR,
|
||||
AmplitudeManager.EVENT_HITTYPE_EVENT
|
||||
)
|
||||
sharedPreferences.edit {
|
||||
viewModel.sharedPreferences.edit {
|
||||
putLong("last_creation_reporting", Date().time)
|
||||
}
|
||||
}
|
||||
|
|
@ -448,4 +434,10 @@ class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.O
|
|||
override fun onAdd(taskType: TaskType) {
|
||||
openNewTaskActivity(taskType)
|
||||
}
|
||||
|
||||
private fun updateBoardDisplay() {
|
||||
activity?.title = viewModel.ownerTitle
|
||||
val isPersonalBoard = viewModel.isPersonalBoard
|
||||
bottomNavigation?.canAddTasks = isPersonalBoard
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,391 +0,0 @@
|
|||
package com.habitrpg.android.habitica.ui.fragments.tasks
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.graphics.PorterDuff
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.habitrpg.android.habitica.HabiticaBaseApplication
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.components.UserComponent
|
||||
import com.habitrpg.android.habitica.data.TagRepository
|
||||
import com.habitrpg.android.habitica.databinding.FragmentViewpagerBinding
|
||||
import com.habitrpg.android.habitica.extensions.getThemeColor
|
||||
import com.habitrpg.android.habitica.extensions.setTintWith
|
||||
import com.habitrpg.android.habitica.helpers.AmplitudeManager
|
||||
import com.habitrpg.android.habitica.helpers.AppConfigManager
|
||||
import com.habitrpg.android.habitica.helpers.MainNavigationController
|
||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||
import com.habitrpg.android.habitica.helpers.TaskFilterHelper
|
||||
import com.habitrpg.android.habitica.models.tasks.TaskType
|
||||
import com.habitrpg.android.habitica.ui.activities.TaskFormActivity
|
||||
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
|
||||
import com.habitrpg.android.habitica.ui.views.navigation.HabiticaBottomNavigationViewListener
|
||||
import com.habitrpg.android.habitica.ui.views.tasks.TaskFilterDialog
|
||||
import io.reactivex.rxjava3.disposables.Disposable
|
||||
import java.util.Date
|
||||
import java.util.WeakHashMap
|
||||
import javax.inject.Inject
|
||||
|
||||
class TeamBoardFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.OnQueryTextListener, HabiticaBottomNavigationViewListener {
|
||||
|
||||
override var binding: FragmentViewpagerBinding? = null
|
||||
|
||||
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentViewpagerBinding {
|
||||
return FragmentViewpagerBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
var teamID: String = ""
|
||||
|
||||
@Inject
|
||||
lateinit var taskFilterHelper: TaskFilterHelper
|
||||
@Inject
|
||||
lateinit var tagRepository: TagRepository
|
||||
@Inject
|
||||
lateinit var appConfigManager: AppConfigManager
|
||||
|
||||
private var refreshItem: MenuItem? = null
|
||||
internal var viewFragmentsDictionary: MutableMap<Int, TaskRecyclerViewFragment>? = WeakHashMap()
|
||||
|
||||
private var filterMenuItem: MenuItem? = null
|
||||
|
||||
private val activeFragment: TaskRecyclerViewFragment?
|
||||
get() {
|
||||
var fragment = viewFragmentsDictionary?.get(binding?.viewPager?.currentItem)
|
||||
if (fragment == null) {
|
||||
if (isAdded) {
|
||||
fragment = (childFragmentManager.findFragmentByTag("android:switcher:" + R.id.viewPager + ":" + binding?.viewPager?.currentItem) as? TaskRecyclerViewFragment)
|
||||
}
|
||||
}
|
||||
return fragment
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
this.usesTabLayout = false
|
||||
this.hidesToolbar = true
|
||||
this.usesBottomNavigation = true
|
||||
return super.onCreateView(inflater, container, savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
arguments?.let {
|
||||
val args = TeamBoardFragmentArgs.fromBundle(it)
|
||||
teamID = args.teamID
|
||||
}
|
||||
|
||||
compositeSubscription.add(
|
||||
userRepository.getTeamPlan(teamID)
|
||||
.subscribe(
|
||||
{
|
||||
activity?.title = it.name
|
||||
},
|
||||
RxErrorHandler.handleEmptyError()
|
||||
)
|
||||
)
|
||||
|
||||
compositeSubscription.add(userRepository.retrieveTeamPlan(teamID).subscribe({ }, RxErrorHandler.handleEmptyError()))
|
||||
|
||||
loadTaskLists()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
bottomNavigation?.activeTaskType = when (binding?.viewPager?.currentItem) {
|
||||
0 -> TaskType.HABIT
|
||||
1 -> TaskType.DAILY
|
||||
2 -> TaskType.TODO
|
||||
3 -> TaskType.REWARD
|
||||
else -> TaskType.HABIT
|
||||
}
|
||||
bottomNavigation?.listener = this
|
||||
bottomNavigation?.canAddTasks = false
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
if (bottomNavigation?.listener == this) {
|
||||
bottomNavigation?.listener = null
|
||||
}
|
||||
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
tagRepository.close()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun injectFragment(component: UserComponent) {
|
||||
component.inject(this)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.menu_team_board, menu)
|
||||
|
||||
filterMenuItem = menu.findItem(R.id.action_filter)
|
||||
updateFilterIcon()
|
||||
|
||||
val item = menu.findItem(R.id.action_search)
|
||||
tintMenuIcon(item)
|
||||
val sv = item.actionView as? SearchView
|
||||
sv?.setOnQueryTextListener(this)
|
||||
sv?.setIconifiedByDefault(false)
|
||||
item.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
|
||||
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
|
||||
filterMenuItem?.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
|
||||
// Do something when expanded
|
||||
filterMenuItem?.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onQueryTextChange(newText: String?): Boolean {
|
||||
taskFilterHelper.searchQuery = newText
|
||||
viewFragmentsDictionary?.values?.forEach { values -> values.recyclerAdapter?.filter() }
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.action_filter -> {
|
||||
showFilterDialog()
|
||||
true
|
||||
}
|
||||
R.id.action_reload -> {
|
||||
refreshItem = item
|
||||
refresh()
|
||||
true
|
||||
}
|
||||
R.id.action_team_info -> {
|
||||
MainNavigationController.navigate(R.id.guildFragment, bundleOf(Pair("groupID", teamID)))
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showFilterDialog() {
|
||||
context?.let {
|
||||
val disposable: Disposable
|
||||
val dialog = TaskFilterDialog(it, HabiticaBaseApplication.userComponent)
|
||||
disposable = tagRepository.getTags().subscribe({ tagsList -> dialog.setTags(tagsList) }, RxErrorHandler.handleEmptyError())
|
||||
dialog.setActiveTags(taskFilterHelper.tags)
|
||||
if (activeFragment != null) {
|
||||
val taskType = activeFragment?.taskType
|
||||
if (taskType != null) {
|
||||
dialog.setTaskType(taskType, taskFilterHelper.getActiveFilter(taskType))
|
||||
}
|
||||
}
|
||||
dialog.setListener(object : TaskFilterDialog.OnFilterCompletedListener {
|
||||
override fun onFilterCompleted(
|
||||
activeTaskFilter: String?,
|
||||
activeTags: MutableList<String>
|
||||
) {
|
||||
if (viewFragmentsDictionary == null) {
|
||||
return
|
||||
}
|
||||
taskFilterHelper.tags = activeTags
|
||||
if (activeTaskFilter != null) {
|
||||
activeFragment?.setActiveFilter(activeTaskFilter)
|
||||
}
|
||||
viewFragmentsDictionary?.values?.forEach { values -> values.recyclerAdapter?.filter() }
|
||||
updateFilterIcon()
|
||||
}
|
||||
})
|
||||
dialog.setOnDismissListener {
|
||||
if (!disposable.isDisposed) {
|
||||
disposable.dispose()
|
||||
}
|
||||
}
|
||||
dialog.show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun refresh() {
|
||||
activeFragment?.onRefresh()
|
||||
}
|
||||
|
||||
private fun loadTaskLists() {
|
||||
val fragmentManager = childFragmentManager
|
||||
|
||||
binding?.viewPager?.adapter = object : FragmentStateAdapter(fragmentManager, lifecycle) {
|
||||
|
||||
override fun createFragment(position: Int): androidx.fragment.app.Fragment {
|
||||
val fragment: TaskRecyclerViewFragment = when (position) {
|
||||
0 -> TaskRecyclerViewFragment.newInstance(context, TaskType.HABIT)
|
||||
1 -> TaskRecyclerViewFragment.newInstance(context, TaskType.DAILY)
|
||||
3 -> RewardsRecyclerviewFragment.newInstance(context, TaskType.REWARD, false)
|
||||
else -> TaskRecyclerViewFragment.newInstance(context, TaskType.TODO)
|
||||
}
|
||||
fragment.canEditTasks = false
|
||||
fragment.canScoreTaks = false
|
||||
fragment.refreshAction = {
|
||||
compositeSubscription.add(
|
||||
userRepository.retrieveTeamPlan(teamID)
|
||||
.doOnTerminate {
|
||||
it()
|
||||
}.subscribe({ }, RxErrorHandler.handleEmptyError())
|
||||
)
|
||||
}
|
||||
|
||||
viewFragmentsDictionary?.put(position, fragment)
|
||||
|
||||
return fragment
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = 4
|
||||
}
|
||||
|
||||
binding?.viewPager?.registerOnPageChangeCallback(object :
|
||||
ViewPager2.OnPageChangeCallback() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
bottomNavigation?.selectedPosition = position
|
||||
updateFilterIcon()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun updateFilterIcon() {
|
||||
if (filterMenuItem == null) {
|
||||
return
|
||||
}
|
||||
var filterCount = 0
|
||||
if (activeFragment != null) {
|
||||
filterCount = taskFilterHelper.howMany(activeFragment?.taskType)
|
||||
}
|
||||
if (filterCount == 0) {
|
||||
filterMenuItem?.setIcon(R.drawable.ic_action_filter_list)
|
||||
context?.let {
|
||||
val filterIcon = ContextCompat.getDrawable(it, R.drawable.ic_action_filter_list)
|
||||
filterIcon?.setTintWith(it.getThemeColor(R.attr.headerTextColor), PorterDuff.Mode.MULTIPLY)
|
||||
filterMenuItem?.setIcon(filterIcon)
|
||||
}
|
||||
} else {
|
||||
context?.let {
|
||||
val filterIcon = ContextCompat.getDrawable(it, R.drawable.ic_filters_active)
|
||||
filterIcon?.setTintWith(it.getThemeColor(R.attr.textColorPrimaryDark), PorterDuff.Mode.MULTIPLY)
|
||||
filterMenuItem?.setIcon(filterIcon)
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
private fun openNewTaskActivity(type: TaskType) {
|
||||
if (Date().time - (lastTaskFormOpen?.time ?: 0) < 2000) {
|
||||
return
|
||||
}
|
||||
|
||||
val additionalData = HashMap<String, Any>()
|
||||
additionalData["created task type"] = type
|
||||
additionalData["viewed task type"] = when (binding?.viewPager?.currentItem) {
|
||||
0 -> TaskType.HABIT
|
||||
1 -> TaskType.DAILY
|
||||
2 -> TaskType.TODO
|
||||
3 -> TaskType.REWARD
|
||||
else -> ""
|
||||
}
|
||||
AmplitudeManager.sendEvent("open create task form", AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, AmplitudeManager.EVENT_HITTYPE_EVENT, additionalData)
|
||||
|
||||
val bundle = Bundle()
|
||||
bundle.putString(TaskFormActivity.TASK_TYPE_KEY, type.value)
|
||||
bundle.putStringArrayList(TaskFormActivity.SELECTED_TAGS_KEY, ArrayList(taskFilterHelper.tags))
|
||||
|
||||
val intent = Intent(activity, TaskFormActivity::class.java)
|
||||
intent.putExtras(bundle)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
|
||||
if (this.isAdded) {
|
||||
lastTaskFormOpen = Date()
|
||||
taskCreatedResult.launch(intent)
|
||||
}
|
||||
}
|
||||
|
||||
//endregion Events
|
||||
|
||||
private val taskCreatedResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
onTaskCreatedResult(it.resultCode, it.data)
|
||||
}
|
||||
|
||||
private fun onTaskCreatedResult(resultCode: Int, data: Intent?) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
val taskTypeValue = data?.getStringExtra(TaskFormActivity.TASK_TYPE_KEY)
|
||||
if (taskTypeValue != null) {
|
||||
val taskType = TaskType.from(taskTypeValue)
|
||||
switchToTaskTab(taskType)
|
||||
|
||||
val index = indexForTaskType(taskType)
|
||||
if (index != -1) {
|
||||
val fragment = viewFragmentsDictionary?.get(index)
|
||||
fragment?.binding?.recyclerView?.scrollToPosition(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun switchToTaskTab(taskType: TaskType?) {
|
||||
val index = indexForTaskType(taskType)
|
||||
if (binding?.viewPager != null && index != -1) {
|
||||
binding?.viewPager?.currentItem = index
|
||||
}
|
||||
}
|
||||
|
||||
private fun indexForTaskType(taskType: TaskType?): Int {
|
||||
if (taskType != null) {
|
||||
for (index in 0 until (viewFragmentsDictionary?.size ?: 0)) {
|
||||
val fragment = viewFragmentsDictionary?.get(index)
|
||||
if (fragment != null && taskType == fragment.className) {
|
||||
return index
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
override val displayedClassName: String?
|
||||
get() = null
|
||||
|
||||
override fun addToBackStack(): Boolean = false
|
||||
|
||||
companion object {
|
||||
var lastTaskFormOpen: Date? = null
|
||||
}
|
||||
|
||||
override fun onTabSelected(taskType: TaskType, smooth: Boolean) {
|
||||
val newItem = when (taskType) {
|
||||
TaskType.HABIT -> 0
|
||||
TaskType.DAILY -> 1
|
||||
TaskType.TODO -> 2
|
||||
TaskType.REWARD -> 3
|
||||
else -> 0
|
||||
}
|
||||
binding?.viewPager?.setCurrentItem(newItem, smooth)
|
||||
}
|
||||
|
||||
override fun onAdd(taskType: TaskType) {
|
||||
openNewTaskActivity(taskType)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,21 +2,16 @@ package com.habitrpg.android.habitica.ui.menu
|
|||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.habitrpg.android.habitica.databinding.MenuBottomSheetBinding
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaBottomSheetDialog
|
||||
|
||||
class BottomSheetMenu(context: Context) : BottomSheetDialog(context), View.OnClickListener {
|
||||
class BottomSheetMenu(context: Context) : HabiticaBottomSheetDialog(context), View.OnClickListener {
|
||||
private var binding = MenuBottomSheetBinding.inflate(layoutInflater)
|
||||
private var runnable: ((Int) -> Unit)? = null
|
||||
|
||||
init {
|
||||
setContentView(binding.root)
|
||||
binding.titleView.visibility = View.GONE
|
||||
|
||||
val behavior = BottomSheetBehavior.from(binding.root.parent as View)
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
behavior.peekHeight = 0
|
||||
}
|
||||
|
||||
fun setSelectionRunnable(runnable: (Int) -> Unit) {
|
||||
|
|
@ -26,12 +21,14 @@ class BottomSheetMenu(context: Context) : BottomSheetDialog(context), View.OnCli
|
|||
override fun setTitle(title: CharSequence?) {
|
||||
binding.titleView.text = title
|
||||
binding.titleView.visibility = View.VISIBLE
|
||||
grabberVisibility = View.GONE
|
||||
}
|
||||
|
||||
fun addMenuItem(menuItem: BottomSheetMenuItem) {
|
||||
val item = menuItem.inflate(this.context, layoutInflater, this.binding.menuItems)
|
||||
item.setOnClickListener(this)
|
||||
this.binding.menuItems.addView(item)
|
||||
binding.root.requestLayout()
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ class MainUserViewModel(val userRepository: UserRepository) {
|
|||
get() = user.value?.id
|
||||
val username: CharSequence
|
||||
get() = user.value?.username ?: ""
|
||||
val displayName: CharSequence
|
||||
get() = user.value?.profile?.name ?: ""
|
||||
val partyID: String?
|
||||
get() = user.value?.party?.id
|
||||
val isUserFainted: Boolean
|
||||
|
|
|
|||
|
|
@ -0,0 +1,232 @@
|
|||
package com.habitrpg.android.habitica.ui.viewmodels
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.text.format.DateUtils
|
||||
import androidx.core.content.edit
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.habitrpg.android.habitica.components.UserComponent
|
||||
import com.habitrpg.android.habitica.data.TagRepository
|
||||
import com.habitrpg.android.habitica.data.TaskRepository
|
||||
import com.habitrpg.android.habitica.helpers.AmplitudeManager
|
||||
import com.habitrpg.android.habitica.helpers.AppConfigManager
|
||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||
import com.habitrpg.android.habitica.helpers.TaskFilterHelper
|
||||
import com.habitrpg.android.habitica.models.responses.TaskDirection
|
||||
import com.habitrpg.android.habitica.models.responses.TaskScoringResult
|
||||
import com.habitrpg.android.habitica.models.tasks.Task
|
||||
import com.habitrpg.android.habitica.models.tasks.TaskType
|
||||
import com.habitrpg.android.habitica.modules.AppModule
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.realm.Case
|
||||
import io.realm.OrderedRealmCollection
|
||||
import io.realm.RealmQuery
|
||||
import io.realm.Sort
|
||||
import java.util.Date
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
class TasksViewModel: BaseViewModel() {
|
||||
private var compositeSubscription: CompositeDisposable = CompositeDisposable()
|
||||
|
||||
override fun inject(component: UserComponent) {
|
||||
component.inject(this)
|
||||
}
|
||||
|
||||
@field:[Inject Named(AppModule.NAMED_USER_ID)]
|
||||
lateinit var userID: String
|
||||
@Inject
|
||||
lateinit var taskRepository: TaskRepository
|
||||
@Inject
|
||||
lateinit var taskFilterHelper: TaskFilterHelper
|
||||
@Inject
|
||||
lateinit var tagRepository: TagRepository
|
||||
@Inject
|
||||
lateinit var appConfigManager: AppConfigManager
|
||||
@Inject
|
||||
lateinit var sharedPreferences: SharedPreferences
|
||||
|
||||
private var owners: List<Pair<String, CharSequence>> = listOf()
|
||||
|
||||
val ownerID: MutableLiveData<String?> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
val isPersonalBoard: Boolean
|
||||
get() {
|
||||
return ownerID.value == userID
|
||||
}
|
||||
val ownerTitle: CharSequence
|
||||
get() {
|
||||
return owners.firstOrNull { it.first == ownerID.value }?.second ?: ""
|
||||
}
|
||||
|
||||
init {
|
||||
compositeSubscription.add(userRepository.getTeamPlans()
|
||||
.subscribe({
|
||||
owners = listOf(Pair(userID ?: "", userViewModel.displayName)) + it.map { Pair(it.id, it.summary) }
|
||||
}, RxErrorHandler.handleEmptyError()))
|
||||
compositeSubscription.add(userRepository.retrieveTeamPlans().subscribe({}, RxErrorHandler.handleEmptyError()))
|
||||
}
|
||||
|
||||
internal fun refreshData(onComplete: () -> Unit) {
|
||||
if (isPersonalBoard) {
|
||||
compositeSubscription.add(
|
||||
userRepository.retrieveUser(
|
||||
withTasks = true,
|
||||
forced = true
|
||||
).doOnTerminate {
|
||||
onComplete()
|
||||
}.subscribe({ }, RxErrorHandler.handleEmptyError())
|
||||
)
|
||||
} else {
|
||||
compositeSubscription.add(
|
||||
userRepository.retrieveTeamPlan(ownerID.value ?: "")
|
||||
.doOnTerminate {
|
||||
onComplete()
|
||||
}.subscribe({ }, RxErrorHandler.handleEmptyError())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun cycleOwnerIDs() {
|
||||
val nextIndex = owners.indexOfFirst { it.first == ownerID.value } + 1
|
||||
if (nextIndex < owners.size) {
|
||||
ownerID.value = owners[nextIndex].first
|
||||
} else {
|
||||
ownerID.value = owners[0].first
|
||||
}
|
||||
}
|
||||
|
||||
fun scoreTask(task: Task, direction: TaskDirection, onResult: (TaskScoringResult, Int) -> Unit) {
|
||||
compositeSubscription.add(
|
||||
taskRepository.taskChecked(null, task.id ?: "", direction == TaskDirection.UP, false) { result ->
|
||||
onResult(result, task.value.toInt())
|
||||
if (!DateUtils.isToday(sharedPreferences.getLong("last_task_reporting", 0))) {
|
||||
AmplitudeManager.sendEvent(
|
||||
"task scored",
|
||||
AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR,
|
||||
AmplitudeManager.EVENT_HITTYPE_EVENT
|
||||
)
|
||||
sharedPreferences.edit {
|
||||
putLong("last_task_reporting", Date().time)
|
||||
}
|
||||
}
|
||||
}.subscribe({}, RxErrorHandler.handleEmptyError())
|
||||
)
|
||||
}
|
||||
|
||||
var searchQuery: String? = null
|
||||
private val activeFilters = HashMap<TaskType, String>()
|
||||
|
||||
var tags: MutableList<String> = mutableListOf()
|
||||
|
||||
fun howMany(type: TaskType?): Int {
|
||||
return this.tags.size + if (isTaskFilterActive(type)) 1 else 0
|
||||
}
|
||||
|
||||
private fun isTaskFilterActive(type: TaskType?): Boolean {
|
||||
if (activeFilters[type] == null) {
|
||||
return false
|
||||
}
|
||||
return if (TaskType.TODO == type) {
|
||||
Task.FILTER_ACTIVE != activeFilters[type]
|
||||
} else {
|
||||
Task.FILTER_ALL != activeFilters[type]
|
||||
}
|
||||
}
|
||||
|
||||
fun filter(tasks: List<Task>): List<Task> {
|
||||
if (tasks.isEmpty()) {
|
||||
return tasks
|
||||
}
|
||||
val filtered = ArrayList<Task>()
|
||||
var activeFilter: String? = null
|
||||
if (activeFilters.size > 0) {
|
||||
activeFilter = activeFilters[tasks[0].type]
|
||||
}
|
||||
for (task in tasks) {
|
||||
if (isFiltered(task, activeFilter)) {
|
||||
filtered.add(task)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
||||
private fun isFiltered(task: Task, activeFilter: String?): Boolean {
|
||||
if (!task.containsAllTagIds(tags)) {
|
||||
return false
|
||||
}
|
||||
return if (activeFilter != null && activeFilter != Task.FILTER_ALL) {
|
||||
when (activeFilter) {
|
||||
Task.FILTER_ACTIVE -> if (task.type == TaskType.DAILY) {
|
||||
task.isDisplayedActive
|
||||
} else {
|
||||
!task.completed
|
||||
}
|
||||
Task.FILTER_GRAY -> task.completed || !task.isDisplayedActive
|
||||
Task.FILTER_WEAK -> task.value < 1
|
||||
Task.FILTER_STRONG -> task.value >= 1
|
||||
Task.FILTER_DATED -> task.dueDate != null
|
||||
Task.FILTER_COMPLETED -> task.completed
|
||||
else -> true
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fun setActiveFilter(type: TaskType, activeFilter: String) {
|
||||
activeFilters[type] = activeFilter
|
||||
}
|
||||
|
||||
fun getActiveFilter(type: TaskType?): String? {
|
||||
return if (activeFilters.containsKey(type)) {
|
||||
activeFilters[type]
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun createQuery(unfilteredData: OrderedRealmCollection<Task>): RealmQuery<Task>? {
|
||||
if (!unfilteredData.isValid) {
|
||||
return null
|
||||
}
|
||||
var query = unfilteredData.where()
|
||||
|
||||
if (unfilteredData.size != 0) {
|
||||
val taskType = unfilteredData[0].type
|
||||
val activeFilter = getActiveFilter(taskType)
|
||||
|
||||
if (tags.size > 0) {
|
||||
query = query.`in`("tags.id", tags.toTypedArray())
|
||||
}
|
||||
if (searchQuery?.isNotEmpty() == true) {
|
||||
query = query
|
||||
.beginGroup()
|
||||
.contains("text", searchQuery ?: "", Case.INSENSITIVE)
|
||||
.or()
|
||||
.contains("notes", searchQuery ?: "", Case.INSENSITIVE)
|
||||
.endGroup()
|
||||
}
|
||||
if (activeFilter != null && activeFilter != Task.FILTER_ALL) {
|
||||
when (activeFilter) {
|
||||
Task.FILTER_ACTIVE -> query = if (TaskType.DAILY == taskType) {
|
||||
query.equalTo("completed", false).equalTo("isDue", true)
|
||||
} else {
|
||||
query.equalTo("completed", false)
|
||||
}
|
||||
Task.FILTER_GRAY -> query = query.equalTo("completed", true).or().equalTo("isDue", false)
|
||||
Task.FILTER_WEAK -> query = query.lessThan("value", 1.0)
|
||||
Task.FILTER_STRONG -> query = query.greaterThanOrEqualTo("value", 1.0)
|
||||
Task.FILTER_DATED -> query = query.isNotNull("dueDate").equalTo("completed", false).sort("dueDate")
|
||||
Task.FILTER_COMPLETED -> query = query.equalTo("completed", true)
|
||||
}
|
||||
}
|
||||
if (activeFilter != Task.FILTER_DATED) {
|
||||
query = query.sort("position", Sort.ASCENDING, "dateCreated", Sort.DESCENDING)
|
||||
}
|
||||
}
|
||||
return query
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.habitrpg.android.habitica.ui.views
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
|
|
@ -7,6 +8,7 @@ import android.util.AttributeSet
|
|||
import android.util.TypedValue
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import androidx.core.animation.doOnEnd
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.extensions.isUsingNightModeResources
|
||||
|
|
@ -40,6 +42,7 @@ class CurrencyView : androidx.appcompat.widget.AppCompatTextView {
|
|||
} catch (_: ArrayIndexOutOfBoundsException) {
|
||||
!context.isUsingNightModeResources()
|
||||
}
|
||||
currency = attributes?.getString(R.styleable.CurrencyView_currency)
|
||||
visibility = GONE
|
||||
}
|
||||
|
||||
|
|
@ -105,13 +108,38 @@ class CurrencyView : androidx.appcompat.widget.AppCompatTextView {
|
|||
}
|
||||
}
|
||||
|
||||
var minForAbbrevation = 0
|
||||
var decimals = 2
|
||||
var animationDuration = 500L
|
||||
var animationDelay = 0L
|
||||
|
||||
private fun update(value: Double) {
|
||||
text = NumberAbbreviator.abbreviate(context, value, decimals, minForAbbrevation = minForAbbrevation)
|
||||
}
|
||||
|
||||
private fun endUpdate() {
|
||||
contentDescription = "$text $currencyContentDescription"
|
||||
updateVisibility()
|
||||
}
|
||||
|
||||
var value = 0.0
|
||||
set(value) {
|
||||
if (text.isEmpty() || animationDuration == 0L) {
|
||||
update(value)
|
||||
endUpdate()
|
||||
} else {
|
||||
val animator = ValueAnimator.ofFloat(field.toFloat(), value.toFloat())
|
||||
animator.duration = animationDuration
|
||||
animator.startDelay = animationDelay
|
||||
animator.addUpdateListener {
|
||||
update((it.animatedValue as Float).toDouble())
|
||||
}
|
||||
animator.doOnEnd {
|
||||
endUpdate()
|
||||
}
|
||||
animator.start()
|
||||
}
|
||||
field = value
|
||||
val abbreviatedValue = NumberAbbreviator.abbreviate(context, value)
|
||||
text = abbreviatedValue
|
||||
contentDescription = "$abbreviatedValue $currencyContentDescription"
|
||||
updateVisibility()
|
||||
}
|
||||
|
||||
var isLocked = false
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
package com.habitrpg.android.habitica.ui.views
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.TextView
|
||||
import androidx.preference.ListPreference
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.extensions.setScaledPadding
|
||||
|
||||
class HabiticaListPreference: ListPreference {
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) :
|
||||
super(context, attrs, defStyleAttr, defStyleRes)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
|
||||
super(context,attrs,defStyleAttr)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
|
||||
constructor(context: Context) : super(context)
|
||||
|
||||
override fun onClick() {
|
||||
val subtitleText = TextView(context)
|
||||
subtitleText.setText(R.string.cds_subtitle)
|
||||
val builder = AlertDialog.Builder(context).setSingleChoiceItems(entries,getValueIndex())
|
||||
{ dialog, index ->
|
||||
if (callChangeListener(entryValues[index].toString())) {
|
||||
setValueIndex(index)
|
||||
}
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() }
|
||||
.setTitle(title)
|
||||
|
||||
val dialog = builder.create()
|
||||
subtitleText.setScaledPadding(context, 24, 0, 24, 8)
|
||||
dialog.listView.addHeaderView(subtitleText)
|
||||
dialog.window?.decorView?.setBackgroundResource(R.color.window_background)
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
private fun getValueIndex() = entryValues.indexOf(value)
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.habitrpg.android.habitica.ui.views
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
|
|
@ -164,9 +165,23 @@ class ValueBar(context: Context, attrs: AttributeSet?) : FrameLayout(context, at
|
|||
binding.descriptionTextView.setTextColor(textColor)
|
||||
}
|
||||
|
||||
var animationDuration = 500L
|
||||
var animationDelay = 0L
|
||||
|
||||
fun set(value: Double, valueMax: Double) {
|
||||
if (currentValue != value || maxValue != valueMax) {
|
||||
currentValue = value
|
||||
if (animationDuration == 0L || binding.valueTextView.text.isEmpty()) {
|
||||
currentValue = value
|
||||
} else {
|
||||
val animator = ValueAnimator.ofInt(currentValue.toInt(), value.toInt())
|
||||
animator.duration = animationDuration
|
||||
animator.startDelay = animationDelay
|
||||
animator.addUpdateListener {
|
||||
currentValue = (it.animatedValue as Int).toDouble()
|
||||
updateBar()
|
||||
}
|
||||
animator.start()
|
||||
}
|
||||
maxValue = valueMax
|
||||
updateBar()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,116 @@
|
|||
package com.habitrpg.android.habitica.ui.views.ads
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import android.widget.LinearLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.databinding.AdButtonBinding
|
||||
import com.habitrpg.android.habitica.extensions.getMinuteOrSeconds
|
||||
import com.habitrpg.android.habitica.extensions.getShortRemainingString
|
||||
import com.habitrpg.android.habitica.extensions.layoutInflater
|
||||
import com.habitrpg.android.habitica.helpers.AdHandler
|
||||
import com.habitrpg.android.habitica.helpers.AdType
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Date
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.toDuration
|
||||
|
||||
class AdButton @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null
|
||||
) : LinearLayout(context, attrs) {
|
||||
var state: State = State.EMPTY
|
||||
set(value) {
|
||||
field = value
|
||||
updateViews()
|
||||
}
|
||||
|
||||
enum class State {
|
||||
EMPTY,
|
||||
READY,
|
||||
LOADING,
|
||||
UNAVAILABLE
|
||||
}
|
||||
|
||||
private var updateJob: Job? = null
|
||||
private var nextAdDate: Date? = null
|
||||
private val binding = AdButtonBinding.inflate(context.layoutInflater, this)
|
||||
|
||||
var text: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
updateViews()
|
||||
}
|
||||
|
||||
init {
|
||||
context.theme?.obtainStyledAttributes(
|
||||
attrs,
|
||||
R.styleable.AdButton,
|
||||
0, 0
|
||||
)?.let { attributes ->
|
||||
text = attributes.getString(R.styleable.AdButton_text) ?: ""
|
||||
binding.currencyView.currency = attributes.getString(R.styleable.AdButton_currency)
|
||||
}
|
||||
binding.textView.setTextColor(ContextCompat.getColor(context, R.color.white))
|
||||
binding.currencyView.setTextColor(ContextCompat.getColor(context, R.color.white))
|
||||
binding.currencyView.value = 0.0
|
||||
gravity = Gravity.CENTER
|
||||
state = State.EMPTY
|
||||
}
|
||||
|
||||
private fun updateViews() {
|
||||
when (state) {
|
||||
State.READY -> {
|
||||
binding.loadingIndicator.visibility = GONE
|
||||
binding.textView.text = text
|
||||
binding.textView.alpha = 1.0f
|
||||
binding.textView.visibility = VISIBLE
|
||||
binding.currencyView.visibility = VISIBLE
|
||||
setBackgroundResource(R.drawable.ad_button_background)
|
||||
}
|
||||
State.UNAVAILABLE -> {
|
||||
binding.loadingIndicator.visibility = GONE
|
||||
binding.textView.text = context.getString(R.string.available_in, nextAdDate?.getShortRemainingString() ?: "")
|
||||
binding.textView.alpha = 0.75f
|
||||
binding.textView.visibility = VISIBLE
|
||||
binding.currencyView.visibility = GONE
|
||||
setBackgroundResource(R.drawable.ad_button_background_disabled)
|
||||
}
|
||||
State.EMPTY -> {
|
||||
binding.loadingIndicator.visibility = GONE
|
||||
binding.textView.visibility = GONE
|
||||
binding.currencyView.visibility = GONE
|
||||
}
|
||||
State.LOADING -> {
|
||||
binding.loadingIndicator.visibility = VISIBLE
|
||||
binding.textView.visibility = GONE
|
||||
binding.currencyView.visibility = GONE
|
||||
}
|
||||
}
|
||||
isEnabled = state == State.READY
|
||||
}
|
||||
|
||||
fun updateForAdType(type: AdType, lifecycleScope: LifecycleCoroutineScope) {
|
||||
if (updateJob?.isActive == true) {
|
||||
updateJob?.cancel()
|
||||
}
|
||||
nextAdDate = AdHandler.nextAdAllowedDate(type)
|
||||
if (nextAdDate?.after(Date()) == true) {
|
||||
updateJob = lifecycleScope.launch(Dispatchers.Main) {
|
||||
while (nextAdDate?.after(Date()) == true) {
|
||||
val remaining = ((nextAdDate?.time ?: 0L) - Date().time).toDuration(DurationUnit.MILLISECONDS)
|
||||
state = if (remaining.isNegative()) State.READY else State.UNAVAILABLE
|
||||
updateViews()
|
||||
delay(1.toDuration(remaining.getMinuteOrSeconds()))
|
||||
}
|
||||
state = State.READY
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.habitrpg.android.habitica.ui.views.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.databinding.BottomSheetWrapperBinding
|
||||
|
||||
open class HabiticaBottomSheetDialog(context: Context) : BottomSheetDialog(context, R.style.SheetDialog) {
|
||||
private val wrapperBinding = BottomSheetWrapperBinding.inflate(layoutInflater)
|
||||
|
||||
init {
|
||||
behavior.peekHeight = context.resources.displayMetrics.heightPixels / 2
|
||||
}
|
||||
|
||||
var grabberVisibility: Int
|
||||
get() = wrapperBinding.grabber.visibility
|
||||
set(value) {
|
||||
wrapperBinding.grabber.visibility = value
|
||||
}
|
||||
|
||||
override fun setContentView(view: View) {
|
||||
wrapperBinding.container.addView(view)
|
||||
super.setContentView(wrapperBinding.root)
|
||||
}
|
||||
|
||||
override fun setContentView(layoutResId: Int) {
|
||||
layoutInflater.inflate(layoutResId, wrapperBinding.container)
|
||||
super.setContentView(wrapperBinding.root)
|
||||
}
|
||||
}
|
||||
|
|
@ -27,6 +27,8 @@ import com.habitrpg.android.habitica.models.shops.Shop
|
|||
import com.habitrpg.android.habitica.models.shops.ShopItem
|
||||
import com.habitrpg.android.habitica.models.user.OwnedItem
|
||||
import com.habitrpg.android.habitica.models.user.User
|
||||
import com.habitrpg.android.habitica.ui.activities.ArmoireActivityArgs
|
||||
import com.habitrpg.android.habitica.ui.activities.ArmoireActivityDirections
|
||||
import com.habitrpg.android.habitica.ui.views.CurrencyView
|
||||
import com.habitrpg.android.habitica.ui.views.CurrencyViews
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
|
||||
|
|
@ -357,12 +359,10 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop
|
|||
return
|
||||
} else if ("gold" == shopItem.currency && "gem" != shopItem.key) {
|
||||
observable = inventoryRepository.buyItem(user, shopItem.key, shopItem.value.toDouble(), quantity).map { buyResponse ->
|
||||
if (shopItem.key == "armoire") {
|
||||
snackbarText[0] = when {
|
||||
buyResponse.armoire["type"] == "gear" -> context.getString(R.string.armoireEquipment, buyResponse.armoire["dropText"])
|
||||
buyResponse.armoire["type"] == "food" -> context.getString(R.string.armoireFood, buyResponse.armoire["dropArticle"] ?: "", buyResponse.armoire["dropText"])
|
||||
else -> context.getString(R.string.armoireExp)
|
||||
}
|
||||
if (shopItem.key == "armoire" && configManager.enableNewArmoire()) {
|
||||
MainNavigationController.navigate(R.id.armoireActivity, ArmoireActivityDirections.openArmoireActivity(buyResponse.armoire["type"] ?: "",
|
||||
buyResponse.armoire["dropText"] ?: "",
|
||||
buyResponse.armoire["dropKey"] ?: "").arguments)
|
||||
}
|
||||
buyResponse
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,9 +12,7 @@ import android.widget.Button
|
|||
import android.widget.EditText
|
||||
import android.widget.ImageButton
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.RadioButton
|
||||
import android.widget.RadioGroup
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.appcompat.widget.AppCompatCheckBox
|
||||
import androidx.core.content.ContextCompat
|
||||
|
|
@ -22,32 +20,24 @@ import androidx.core.widget.CompoundButtonCompat
|
|||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.components.UserComponent
|
||||
import com.habitrpg.android.habitica.data.TagRepository
|
||||
import com.habitrpg.android.habitica.databinding.DialogTaskFilterBinding
|
||||
import com.habitrpg.android.habitica.extensions.OnChangeTextWatcher
|
||||
import com.habitrpg.android.habitica.extensions.getThemeColor
|
||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||
import com.habitrpg.android.habitica.models.Tag
|
||||
import com.habitrpg.android.habitica.models.tasks.Task
|
||||
import com.habitrpg.android.habitica.models.tasks.TaskType
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaBottomSheetDialog
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAlertDialog(context), RadioGroup.OnCheckedChangeListener {
|
||||
|
||||
private var clearButton: Button
|
||||
class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaBottomSheetDialog(context), RadioGroup.OnCheckedChangeListener {
|
||||
private val binding = DialogTaskFilterBinding.inflate(layoutInflater)
|
||||
|
||||
@Inject
|
||||
lateinit var repository: TagRepository
|
||||
|
||||
private var taskTypeTitle: TextView
|
||||
private var taskFilters: RadioGroup
|
||||
private var allTaskFilter: RadioButton
|
||||
private var secondTaskFilter: RadioButton
|
||||
private var thirdTaskFilter: RadioButton
|
||||
private var tagsEditButton: Button
|
||||
private var tagsList: LinearLayout
|
||||
|
||||
private var taskType: TaskType? = null
|
||||
private var listener: OnFilterCompletedListener? = null
|
||||
|
||||
|
|
@ -65,22 +55,12 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
|
|||
component?.inject(this)
|
||||
addIcon = ContextCompat.getDrawable(context, R.drawable.ic_add_purple_300_36dp)
|
||||
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val view = inflater.inflate(R.layout.dialog_task_filter, null)
|
||||
setTitle(R.string.filters)
|
||||
this.setAdditionalContentView(view)
|
||||
this.setContentView(binding.root)
|
||||
|
||||
taskTypeTitle = view.findViewById(R.id.task_type_title)
|
||||
taskFilters = view.findViewById(R.id.task_filter_wrapper)
|
||||
allTaskFilter = view.findViewById(R.id.all_task_filter)
|
||||
secondTaskFilter = view.findViewById(R.id.second_task_filter)
|
||||
thirdTaskFilter = view.findViewById(R.id.third_task_filter)
|
||||
tagsEditButton = view.findViewById(R.id.tag_edit_button)
|
||||
tagsList = view.findViewById(R.id.tags_list)
|
||||
binding.taskFilterWrapper.setOnCheckedChangeListener(this)
|
||||
|
||||
taskFilters.setOnCheckedChangeListener(this)
|
||||
|
||||
clearButton = addButton(R.string.clear, false, false, false) { _, _ ->
|
||||
binding.clearButton.setOnClickListener {
|
||||
if (isEditing) {
|
||||
stopEditing()
|
||||
}
|
||||
|
|
@ -88,16 +68,12 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
|
|||
setActiveTags(null)
|
||||
}
|
||||
|
||||
addButton(R.string.done, false) { _, _ ->
|
||||
if (isEditing) {
|
||||
stopEditing()
|
||||
}
|
||||
listener?.onFilterCompleted(filterType, activeTags)
|
||||
this.dismiss()
|
||||
}
|
||||
buttonAxis = LinearLayout.HORIZONTAL
|
||||
binding.tagEditButton.setOnClickListener { editButtonClicked() }
|
||||
}
|
||||
|
||||
tagsEditButton.setOnClickListener { editButtonClicked() }
|
||||
override fun dismiss() {
|
||||
listener?.onFilterCompleted(filterType, activeTags)
|
||||
super.dismiss()
|
||||
}
|
||||
|
||||
override fun show() {
|
||||
|
|
@ -111,7 +87,7 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
|
|||
}
|
||||
|
||||
private fun createTagViews() {
|
||||
tagsList.removeAllViews()
|
||||
binding.tagsList.removeAllViews()
|
||||
val colorStateList = ColorStateList(
|
||||
arrayOf(
|
||||
intArrayOf(-android.R.attr.state_checked), // disabled
|
||||
|
|
@ -149,7 +125,7 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
|
|||
}
|
||||
filtersChanged()
|
||||
}
|
||||
tagsList.addView(tagCheckbox)
|
||||
binding.tagsList.addView(tagCheckbox)
|
||||
}
|
||||
createAddTagButton()
|
||||
}
|
||||
|
|
@ -164,7 +140,7 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
|
|||
}
|
||||
button.setBackgroundResource(R.drawable.layout_rounded_bg_lighter_gray)
|
||||
button.setTextColor(ContextCompat.getColor(context, R.color.text_secondary))
|
||||
tagsList.addView(button)
|
||||
binding.tagsList.addView(button)
|
||||
}
|
||||
|
||||
private fun createTag() {
|
||||
|
|
@ -177,17 +153,17 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
|
|||
|
||||
private fun startEditing() {
|
||||
isEditing = true
|
||||
tagsList.removeAllViews()
|
||||
binding.tagsList.removeAllViews()
|
||||
createTagEditViews()
|
||||
tagsEditButton.setText(R.string.done)
|
||||
binding.tagEditButton.setText(R.string.done)
|
||||
this.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
|
||||
}
|
||||
|
||||
private fun stopEditing() {
|
||||
isEditing = false
|
||||
tagsList.removeAllViews()
|
||||
binding.tagsList.removeAllViews()
|
||||
createTagViews()
|
||||
tagsEditButton.setText(R.string.edit_tag_btn_edit)
|
||||
binding.tagEditButton.setText(R.string.edit_tag_btn_edit)
|
||||
this.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN)
|
||||
repository.updateTags(editedTags.values).toObservable().flatMap { tags -> Observable.fromIterable(tags) }.subscribe({ tag -> editedTags.remove(tag.id) }, RxErrorHandler.handleEmptyError())
|
||||
repository.createTags(createdTags.values).toObservable().flatMap { tags -> Observable.fromIterable(tags) }.subscribe({ tag -> createdTags.remove(tag.id) }, RxErrorHandler.handleEmptyError())
|
||||
|
|
@ -204,7 +180,7 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
|
|||
}
|
||||
|
||||
private fun createTagEditView(inflater: LayoutInflater, index: Int, tag: Tag) {
|
||||
val wrapper = inflater.inflate(R.layout.edit_tag_item, tagsList, false) as? LinearLayout
|
||||
val wrapper = inflater.inflate(R.layout.edit_tag_item, binding.tagsList, false) as? LinearLayout
|
||||
val tagEditText = wrapper?.findViewById<View>(R.id.edit_text) as? EditText
|
||||
tagEditText?.setText(tag.name)
|
||||
tagEditText?.addTextChangedListener(
|
||||
|
|
@ -233,9 +209,9 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
|
|||
}
|
||||
activeTags.remove(tag.id)
|
||||
tags.remove(tag)
|
||||
tagsList.removeView(wrapper)
|
||||
binding.tagsList.removeView(wrapper)
|
||||
}
|
||||
tagsList.addView(wrapper)
|
||||
binding.tagsList.addView(wrapper)
|
||||
}
|
||||
|
||||
fun setActiveTags(tagIds: MutableList<String>?) {
|
||||
|
|
@ -244,13 +220,13 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
|
|||
} else {
|
||||
this.activeTags = tagIds
|
||||
}
|
||||
for (index in 0 until tagsList.childCount - 1) {
|
||||
(tagsList.getChildAt(index) as? AppCompatCheckBox)?.isChecked = false
|
||||
for (index in 0 until binding.tagsList.childCount - 1) {
|
||||
(binding.tagsList.getChildAt(index) as? AppCompatCheckBox)?.isChecked = false
|
||||
}
|
||||
for (tagId in this.activeTags) {
|
||||
val index = indexForId(tagId)
|
||||
if (index >= 0) {
|
||||
(tagsList.getChildAt(index) as? AppCompatCheckBox)?.isChecked = true
|
||||
(binding.tagsList.getChildAt(index) as? AppCompatCheckBox)?.isChecked = true
|
||||
}
|
||||
}
|
||||
filtersChanged()
|
||||
|
|
@ -269,22 +245,22 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
|
|||
this.taskType = taskType
|
||||
when (taskType) {
|
||||
TaskType.HABIT -> {
|
||||
taskTypeTitle.setText(R.string.habits)
|
||||
allTaskFilter.setText(R.string.all)
|
||||
secondTaskFilter.setText(R.string.weak)
|
||||
thirdTaskFilter.setText(R.string.strong)
|
||||
binding.taskTypeTitle.setText(R.string.habits)
|
||||
binding.allTaskFilter.setText(R.string.all)
|
||||
binding.secondTaskFilter.setText(R.string.weak)
|
||||
binding.thirdTaskFilter.setText(R.string.strong)
|
||||
}
|
||||
TaskType.DAILY -> {
|
||||
taskTypeTitle.setText(R.string.dailies)
|
||||
allTaskFilter.setText(R.string.all)
|
||||
secondTaskFilter.setText(R.string.due)
|
||||
thirdTaskFilter.setText(R.string.gray)
|
||||
binding.taskTypeTitle.setText(R.string.dailies)
|
||||
binding.allTaskFilter.setText(R.string.all)
|
||||
binding.secondTaskFilter.setText(R.string.due)
|
||||
binding.thirdTaskFilter.setText(R.string.gray)
|
||||
}
|
||||
TaskType.TODO -> {
|
||||
taskTypeTitle.setText(R.string.todos)
|
||||
allTaskFilter.setText(R.string.active)
|
||||
secondTaskFilter.setText(R.string.dated)
|
||||
thirdTaskFilter.setText(R.string.completed)
|
||||
binding.taskTypeTitle.setText(R.string.todos)
|
||||
binding.allTaskFilter.setText(R.string.active)
|
||||
binding.secondTaskFilter.setText(R.string.dated)
|
||||
binding.thirdTaskFilter.setText(R.string.completed)
|
||||
}
|
||||
}
|
||||
setActiveFilter(activeFilter)
|
||||
|
|
@ -307,7 +283,7 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
|
|||
}
|
||||
}
|
||||
}
|
||||
taskFilters.check(checkedId)
|
||||
binding.taskFilterWrapper.check(checkedId)
|
||||
filtersChanged()
|
||||
}
|
||||
|
||||
|
|
@ -345,9 +321,9 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
|
|||
}
|
||||
|
||||
private fun filtersChanged() {
|
||||
clearButton.isEnabled = hasActiveFilters()
|
||||
clearButton.setTextColor(
|
||||
if (clearButton.isEnabled) {
|
||||
binding.clearButton.isEnabled = hasActiveFilters()
|
||||
binding.clearButton.setTextColor(
|
||||
if (binding.clearButton.isEnabled) {
|
||||
context.getThemeColor(R.attr.colorAccent)
|
||||
} else {
|
||||
ContextCompat.getColor(context, R.color.text_dimmed)
|
||||
|
|
@ -356,7 +332,7 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
|
|||
}
|
||||
|
||||
private fun hasActiveFilters(): Boolean {
|
||||
return taskFilters.checkedRadioButtonId != R.id.all_task_filter || activeTags.size > 0
|
||||
return binding.taskFilterWrapper.checkedRadioButtonId != R.id.all_task_filter || activeTags.size > 0
|
||||
}
|
||||
|
||||
fun setListener(listener: OnFilterCompletedListener) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ buildscript {
|
|||
maven { url "https://plugins.gradle.org/m2/" }
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.0.2'
|
||||
classpath 'com.android.tools.build:gradle:7.1.3'
|
||||
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.1'
|
||||
|
|
|
|||
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
|
@ -1,6 +1,6 @@
|
|||
#Sun Apr 17 15:16:40 EDT 2022
|
||||
#Fri Apr 22 14:22:42 CEST 2022
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
|
|
|||