Merge branch '794-global-notifications' of https://github.com/cvuorinen/habitica-android into cvuorinen-794-global-notifications
# Conflicts: # Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt # Habitica/src/main/java/com/habitrpg/android/habitica/modules/ApiModule.java # Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt
|
|
@ -64,6 +64,13 @@
|
||||||
android:pathPattern="/settings/.*"/>
|
android:pathPattern="/settings/.*"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".ui.activities.NotificationsActivity"
|
||||||
|
android:parentActivityName=".ui.activities.MainActivity"
|
||||||
|
android:label="@string/notifications"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
tools:ignore="UnusedAttribute">
|
||||||
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.activities.FixCharacterValuesActivity"
|
android:name=".ui.activities.FixCharacterValuesActivity"
|
||||||
android:parentActivityName=".ui.activities.PrefsActivity"
|
android:parentActivityName=".ui.activities.PrefsActivity"
|
||||||
|
|
|
||||||
BIN
Habitica/res/drawable-hdpi/no_notifications.webp
Normal file
|
After Width: | Height: | Size: 4 KiB |
BIN
Habitica/res/drawable-hdpi/notification_close.webp
Normal file
|
After Width: | Height: | Size: 154 B |
BIN
Habitica/res/drawable-hdpi/notification_mystery_item.webp
Normal file
|
After Width: | Height: | Size: 956 B |
BIN
Habitica/res/drawable-hdpi/notification_stat_sparkles.webp
Normal file
|
After Width: | Height: | Size: 712 B |
BIN
Habitica/res/drawable-hdpi/notifications_bailey.webp
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
Habitica/res/drawable-mdpi/menu_notifications.webp
Normal file
|
After Width: | Height: | Size: 164 B |
BIN
Habitica/res/drawable-mdpi/no_notifications.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
Habitica/res/drawable-mdpi/notification_close.webp
Normal file
|
After Width: | Height: | Size: 138 B |
BIN
Habitica/res/drawable-mdpi/notification_mystery_item.webp
Normal file
|
After Width: | Height: | Size: 174 B |
BIN
Habitica/res/drawable-mdpi/notification_stat_sparkles.webp
Normal file
|
After Width: | Height: | Size: 522 B |
BIN
Habitica/res/drawable-mdpi/notifications_bailey.webp
Normal file
|
After Width: | Height: | Size: 432 B |
BIN
Habitica/res/drawable-xhdpi/menu_notifications.webp
Normal file
|
After Width: | Height: | Size: 222 B |
BIN
Habitica/res/drawable-xhdpi/no_notifications.webp
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
Habitica/res/drawable-xhdpi/notification_close.webp
Normal file
|
After Width: | Height: | Size: 190 B |
BIN
Habitica/res/drawable-xhdpi/notification_mystery_item.webp
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Habitica/res/drawable-xhdpi/notification_stat_sparkles.webp
Normal file
|
After Width: | Height: | Size: 960 B |
BIN
Habitica/res/drawable-xhdpi/notifications_bailey.webp
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
Habitica/res/drawable-xxhdpi/menu_notifications.webp
Normal file
|
After Width: | Height: | Size: 292 B |
BIN
Habitica/res/drawable-xxhdpi/no_notifications.webp
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
Habitica/res/drawable-xxhdpi/notification_close.webp
Normal file
|
After Width: | Height: | Size: 224 B |
BIN
Habitica/res/drawable-xxhdpi/notification_mystery_item.webp
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Habitica/res/drawable-xxhdpi/notification_stat_sparkles.webp
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Habitica/res/drawable-xxhdpi/notifications_bailey.webp
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
|
|
@ -4,10 +4,5 @@
|
||||||
<corners
|
<corners
|
||||||
android:radius="10dip"/>
|
android:radius="10dip"/>
|
||||||
<solid
|
<solid
|
||||||
android:color="@color/red_50" />
|
android:color="@color/brand_400" />
|
||||||
<padding
|
|
||||||
android:left="5dip"
|
|
||||||
android:right="5dip"
|
|
||||||
android:top="5dip"
|
|
||||||
android:bottom="5dip" />
|
|
||||||
</shape>
|
</shape>
|
||||||
11
Habitica/res/drawable/badge_gray.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<corners
|
||||||
|
android:radius="100dip"/>
|
||||||
|
<solid
|
||||||
|
android:color="@color/gray_600" />
|
||||||
|
<padding
|
||||||
|
android:left="6dip"
|
||||||
|
android:right="6dip" />
|
||||||
|
</shape>
|
||||||
54
Habitica/res/layout/activity_notifications.xml
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout 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"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
tools:context=".ui.activities.NotificationsActivity">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="?attr/actionBarSize"
|
||||||
|
android:theme="@style/Toolbar"
|
||||||
|
app:layout_scrollFlags="scroll|enterAlways"
|
||||||
|
app:popupTheme="@style/ThemeOverlay.AppCompat.Dark" />
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/fragment_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="top|center"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
|
|
||||||
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/notifications_refresh_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/notification_items"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:divider="?android:listDivider"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:showDividers="middle" />
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="64dp"
|
android:layout_height="64dp"
|
||||||
android:paddingLeft="16dp"
|
android:paddingLeft="16dp"
|
||||||
android:paddingRight="16dp"
|
android:paddingRight="1dp"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
@ -58,10 +58,42 @@
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/messagesButtonWrapper"
|
android:id="@+id/notificationsButtonWrapper"
|
||||||
android:layout_width="45dp"
|
android:layout_width="33dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginLeft="16dp">
|
android:layout_marginLeft="16dp">
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/notificationsButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/transparent"
|
||||||
|
android:src="@drawable/menu_notifications"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:clickable="false" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/notificationsBadge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_alignLeft="@id/notificationsButton"
|
||||||
|
android:layout_alignTop="@id/notificationsButton"
|
||||||
|
android:layout_marginLeft="13dp"
|
||||||
|
android:layout_marginTop="-13dp"
|
||||||
|
android:background="@drawable/badge_circle"
|
||||||
|
android:gravity="center"
|
||||||
|
android:minWidth="20dp"
|
||||||
|
android:paddingTop="0dp"
|
||||||
|
android:textColor="#FFF"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:text="1"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
</RelativeLayout>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/messagesButtonWrapper"
|
||||||
|
android:layout_width="33dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginLeft="8dp">
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/messagesButton"
|
android:id="@+id/messagesButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
@ -70,21 +102,24 @@
|
||||||
android:src="@drawable/menu_messages"
|
android:src="@drawable/menu_messages"
|
||||||
android:layout_centerVertical="true"
|
android:layout_centerVertical="true"
|
||||||
android:clickable="false" />
|
android:clickable="false" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/messagesBadge"
|
android:id="@+id/messagesBadge"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="20dp"
|
android:layout_height="20dp"
|
||||||
android:paddingTop="0dp"
|
|
||||||
android:layout_alignTop="@id/messagesButton"
|
|
||||||
android:layout_alignLeft="@id/messagesButton"
|
android:layout_alignLeft="@id/messagesButton"
|
||||||
tools:text="1"
|
android:layout_alignTop="@id/messagesButton"
|
||||||
|
android:layout_marginLeft="13dp"
|
||||||
|
android:layout_marginTop="-12dp"
|
||||||
|
android:background="@drawable/badge_circle"
|
||||||
|
android:gravity="center"
|
||||||
|
android:minWidth="20dp"
|
||||||
|
android:paddingTop="0dp"
|
||||||
android:textColor="#FFF"
|
android:textColor="#FFF"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:background="@drawable/badge_circle"
|
|
||||||
android:layout_marginTop="-12dp"
|
|
||||||
android:layout_marginLeft="13dp"
|
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible"/>
|
tools:text="1"
|
||||||
|
tools:visibility="visible" />
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/settingsButtonWrapper"
|
android:id="@+id/settingsButtonWrapper"
|
||||||
|
|
@ -98,21 +133,25 @@
|
||||||
android:background="@color/transparent"
|
android:background="@color/transparent"
|
||||||
android:src="@drawable/menu_settings"
|
android:src="@drawable/menu_settings"
|
||||||
android:layout_centerVertical="true"/>
|
android:layout_centerVertical="true"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/settingsBadge"
|
android:id="@+id/settingsBadge"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="20dp"
|
android:layout_height="20dp"
|
||||||
android:paddingTop="0dp"
|
|
||||||
android:layout_alignTop="@id/settingsButton"
|
|
||||||
android:layout_alignLeft="@id/settingsButton"
|
android:layout_alignLeft="@id/settingsButton"
|
||||||
tools:text="1"
|
android:layout_alignTop="@id/settingsButton"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginLeft="13dp"
|
||||||
|
android:layout_marginTop="-12dp"
|
||||||
|
android:background="@drawable/badge_circle"
|
||||||
|
android:gravity="center"
|
||||||
|
android:minWidth="20dp"
|
||||||
|
android:paddingTop="0dp"
|
||||||
android:textColor="#FFF"
|
android:textColor="#FFF"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:background="@drawable/badge_circle"
|
|
||||||
android:layout_marginTop="-12dp"
|
|
||||||
android:layout_marginLeft="13dp"
|
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible"/>
|
tools:text="1"
|
||||||
|
tools:visibility="visible" />
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
|
||||||
52
Habitica/res/layout/no_notifications.xml
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
<?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="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="@dimen/spacing_large">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingLeft="@dimen/spacing_medium"
|
||||||
|
android:paddingTop="44dp"
|
||||||
|
android:paddingRight="@dimen/spacing_medium">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/noNotifications"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/transparent"
|
||||||
|
android:clickable="false"
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@drawable/no_notifications" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingLeft="40dp"
|
||||||
|
android:paddingTop="26dp"
|
||||||
|
android:paddingRight="40dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/SectionTitle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/no_notifications_title" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:lineSpacingExtra="5dp"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:text="@string/no_notifications_text" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
42
Habitica/res/layout/notification_item.xml
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/notification_item"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingLeft="20dp"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
android:paddingRight="20dp"
|
||||||
|
android:paddingBottom="10dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/notification_image"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginRight="20dp"
|
||||||
|
android:background="@color/transparent"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/message_text"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:layout_marginBottom="6dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:lineSpacingExtra="5dp"
|
||||||
|
android:text="Message" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/dismiss_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="10dp"
|
||||||
|
android:background="@color/transparent"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:src="@drawable/notification_close" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
53
Habitica/res/layout/notifications_header.xml
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
<?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:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingLeft="20dp"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
android:paddingRight="20dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingTop="@dimen/spacing_medium">
|
||||||
|
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@android:id/title"
|
||||||
|
style="@style/Body1"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="16dip"
|
||||||
|
android:background="@color/transparent"
|
||||||
|
android:text="@string/notifications"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:textColor="@color/gray_300"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/notifications_title_badge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginLeft="10dp"
|
||||||
|
android:background="@drawable/badge_gray"
|
||||||
|
android:gravity="center"
|
||||||
|
android:minWidth="24dp"
|
||||||
|
android:textColor="@color/gray_300"
|
||||||
|
android:textSize="12sp"
|
||||||
|
tools:text="1" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/dismiss_all_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/transparent"
|
||||||
|
android:text="@string/dismiss_all"
|
||||||
|
android:textColor="@color/brand_400" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
@ -210,6 +210,11 @@
|
||||||
app:argType="string"
|
app:argType="string"
|
||||||
app:nullable="true" />
|
app:nullable="true" />
|
||||||
</fragment>
|
</fragment>
|
||||||
|
<activity
|
||||||
|
android:id="@+id/notificationsActivity"
|
||||||
|
android:name="com.habitrpg.android.habitica.ui.activities.NotificationsActivity"
|
||||||
|
android:label="@string/notifications">
|
||||||
|
</activity>
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/petDetailRecyclerFragment"
|
android:id="@+id/petDetailRecyclerFragment"
|
||||||
android:name="com.habitrpg.android.habitica.ui.fragments.inventory.stable.PetDetailRecyclerFragment"
|
android:name="com.habitrpg.android.habitica.ui.fragments.inventory.stable.PetDetailRecyclerFragment"
|
||||||
|
|
|
||||||
|
|
@ -534,6 +534,7 @@
|
||||||
<string name="open_settings">Open Settings</string>
|
<string name="open_settings">Open Settings</string>
|
||||||
<string name="dont_keep_activities_warning">It seems like you have the Developer option \“Don\'t keep Activities\” active. Currently this option causes issues with the habitica app, so we suggest disabling it.</string>
|
<string name="dont_keep_activities_warning">It seems like you have the Developer option \“Don\'t keep Activities\” active. Currently this option causes issues with the habitica app, so we suggest disabling it.</string>
|
||||||
<string name="inbox">Messages</string>
|
<string name="inbox">Messages</string>
|
||||||
|
<string name="notifications">Notifications</string>
|
||||||
<string name="FAQ">Frequently Asked Questions</string>
|
<string name="FAQ">Frequently Asked Questions</string>
|
||||||
<string name="special">Special</string>
|
<string name="special">Special</string>
|
||||||
<string name="gem_for_gold_description">Because you subscribe to Habitica, you can purchase a number of Gems each month using Gold.</string>
|
<string name="gem_for_gold_description">Because you subscribe to Habitica, you can purchase a number of Gems each month using Gold.</string>
|
||||||
|
|
@ -858,6 +859,14 @@
|
||||||
<string name="discover">Discover</string>
|
<string name="discover">Discover</string>
|
||||||
<string name="damage_paused">Damage paused</string>
|
<string name="damage_paused">Damage paused</string>
|
||||||
<string name="preference_push_important_announcements">Important Announcements</string>
|
<string name="preference_push_important_announcements">Important Announcements</string>
|
||||||
|
<string name="no_notifications_title">You’re all caught up!</string>
|
||||||
|
<string name="no_notifications_text">The notification fairies give you a raucous round of applause! Well done!</string>
|
||||||
|
<string name="dismiss_all">Dismiss All</string>
|
||||||
|
<string name="new_bailey_update">New Bailey Update!</string>
|
||||||
|
<string name="new_msg_guild"><![CDATA[<b>%1$s</b> has new posts]]></string>
|
||||||
|
<string name="new_msg_party"><![CDATA[Your Party, <b>%1$s</b>, has new posts]]></string>
|
||||||
|
<string name="unallocated_stats_points"><![CDATA[You have <b>%1$s unallocated Stat Points</b>]]></string>
|
||||||
|
<string name="new_subscriber_item"><![CDATA[You have new <b>Mystery Items</b>]]></string>
|
||||||
<string name="create">Create</string>
|
<string name="create">Create</string>
|
||||||
<string name="only_leader_create_challenge">Only leader can create Challenges</string>
|
<string name="only_leader_create_challenge">Only leader can create Challenges</string>
|
||||||
<string name="create_party">Create Party</string>
|
<string name="create_party">Create Party</string>
|
||||||
|
|
|
||||||
|
|
@ -348,6 +348,12 @@ public interface ApiService {
|
||||||
@POST("notifications/{notificationId}/read")
|
@POST("notifications/{notificationId}/read")
|
||||||
Flowable<HabitResponse<List>> readNotification(@Path("notificationId") String notificationId);
|
Flowable<HabitResponse<List>> readNotification(@Path("notificationId") String notificationId);
|
||||||
|
|
||||||
|
@POST("notifications/read")
|
||||||
|
Flowable<HabitResponse<List>> readNotifications(@Body Map<String, List<String>> notificationIds);
|
||||||
|
|
||||||
|
@POST("notifications/see")
|
||||||
|
Flowable<HabitResponse<List>> seeNotifications(@Body Map<String, List<String>> notificationIds);
|
||||||
|
|
||||||
@POST("user/open-mystery-item")
|
@POST("user/open-mystery-item")
|
||||||
Flowable<HabitResponse<Equipment>> openMysteryItem();
|
Flowable<HabitResponse<Equipment>> openMysteryItem();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import com.google.gson.reflect.TypeToken;
|
||||||
import com.habitrpg.android.habitica.models.Achievement;
|
import com.habitrpg.android.habitica.models.Achievement;
|
||||||
import com.habitrpg.android.habitica.models.ContentResult;
|
import com.habitrpg.android.habitica.models.ContentResult;
|
||||||
import com.habitrpg.android.habitica.models.FAQArticle;
|
import com.habitrpg.android.habitica.models.FAQArticle;
|
||||||
|
import com.habitrpg.android.habitica.models.Notification;
|
||||||
import com.habitrpg.android.habitica.models.Skill;
|
import com.habitrpg.android.habitica.models.Skill;
|
||||||
import com.habitrpg.android.habitica.models.Tag;
|
import com.habitrpg.android.habitica.models.Tag;
|
||||||
import com.habitrpg.android.habitica.models.TutorialStep;
|
import com.habitrpg.android.habitica.models.TutorialStep;
|
||||||
|
|
@ -45,6 +46,7 @@ import com.habitrpg.android.habitica.utils.GroupSerialization;
|
||||||
import com.habitrpg.android.habitica.utils.MemberSerialization;
|
import com.habitrpg.android.habitica.utils.MemberSerialization;
|
||||||
import com.habitrpg.android.habitica.utils.OwnedItemListDeserializer;
|
import com.habitrpg.android.habitica.utils.OwnedItemListDeserializer;
|
||||||
import com.habitrpg.android.habitica.utils.OwnedMountListDeserializer;
|
import com.habitrpg.android.habitica.utils.OwnedMountListDeserializer;
|
||||||
|
import com.habitrpg.android.habitica.utils.NotificationDeserializer;
|
||||||
import com.habitrpg.android.habitica.utils.OwnedPetListDeserializer;
|
import com.habitrpg.android.habitica.utils.OwnedPetListDeserializer;
|
||||||
import com.habitrpg.android.habitica.utils.PurchasedDeserializer;
|
import com.habitrpg.android.habitica.utils.PurchasedDeserializer;
|
||||||
import com.habitrpg.android.habitica.utils.QuestCollectDeserializer;
|
import com.habitrpg.android.habitica.utils.QuestCollectDeserializer;
|
||||||
|
|
@ -118,6 +120,7 @@ public class GSonFactoryCreator {
|
||||||
.registerTypeAdapter(Member.class, new MemberSerialization())
|
.registerTypeAdapter(Member.class, new MemberSerialization())
|
||||||
.registerTypeAdapter(WorldState.class, new WorldStateSerialization())
|
.registerTypeAdapter(WorldState.class, new WorldStateSerialization())
|
||||||
.registerTypeAdapter(FindUsernameResult.class, new FindUsernameResultDeserializer())
|
.registerTypeAdapter(FindUsernameResult.class, new FindUsernameResultDeserializer())
|
||||||
|
.registerTypeAdapter(Notification.class, new NotificationDeserializer())
|
||||||
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
|
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
|
||||||
.create();
|
.create();
|
||||||
return GsonConverterFactory.create(gson);
|
return GsonConverterFactory.create(gson);
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import com.habitrpg.android.habitica.ui.activities.IntroActivity;
|
||||||
import com.habitrpg.android.habitica.ui.activities.LoginActivity;
|
import com.habitrpg.android.habitica.ui.activities.LoginActivity;
|
||||||
import com.habitrpg.android.habitica.ui.activities.MainActivity;
|
import com.habitrpg.android.habitica.ui.activities.MainActivity;
|
||||||
import com.habitrpg.android.habitica.ui.activities.MaintenanceActivity;
|
import com.habitrpg.android.habitica.ui.activities.MaintenanceActivity;
|
||||||
|
import com.habitrpg.android.habitica.ui.activities.NotificationsActivity;
|
||||||
import com.habitrpg.android.habitica.ui.activities.GroupInviteActivity;
|
import com.habitrpg.android.habitica.ui.activities.GroupInviteActivity;
|
||||||
import com.habitrpg.android.habitica.ui.activities.PrefsActivity;
|
import com.habitrpg.android.habitica.ui.activities.PrefsActivity;
|
||||||
import com.habitrpg.android.habitica.ui.activities.ReportMessageActivity;
|
import com.habitrpg.android.habitica.ui.activities.ReportMessageActivity;
|
||||||
|
|
@ -94,6 +95,7 @@ import com.habitrpg.android.habitica.ui.fragments.social.party.PartyMemberListFr
|
||||||
import com.habitrpg.android.habitica.ui.fragments.tasks.TaskRecyclerViewFragment;
|
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.TasksFragment;
|
||||||
import com.habitrpg.android.habitica.ui.viewmodels.GroupViewModel;
|
import com.habitrpg.android.habitica.ui.viewmodels.GroupViewModel;
|
||||||
|
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel;
|
||||||
import com.habitrpg.android.habitica.ui.views.social.ChatBarView;
|
import com.habitrpg.android.habitica.ui.views.social.ChatBarView;
|
||||||
import com.habitrpg.android.habitica.ui.views.stats.BulkAllocateStatsDialog;
|
import com.habitrpg.android.habitica.ui.views.stats.BulkAllocateStatsDialog;
|
||||||
import com.habitrpg.android.habitica.ui.views.shops.PurchaseDialog;
|
import com.habitrpg.android.habitica.ui.views.shops.PurchaseDialog;
|
||||||
|
|
@ -134,6 +136,8 @@ public interface AppComponent {
|
||||||
|
|
||||||
void inject(PrefsActivity prefsActivity);
|
void inject(PrefsActivity prefsActivity);
|
||||||
|
|
||||||
|
void inject(NotificationsActivity notificationsActivity);
|
||||||
|
|
||||||
void inject(SetupActivity setupActivity);
|
void inject(SetupActivity setupActivity);
|
||||||
|
|
||||||
void inject(SkillTasksActivity skillTasksActivity);
|
void inject(SkillTasksActivity skillTasksActivity);
|
||||||
|
|
@ -302,6 +306,8 @@ public interface AppComponent {
|
||||||
|
|
||||||
void inject(@NotNull GroupViewModel viewModel);
|
void inject(@NotNull GroupViewModel viewModel);
|
||||||
|
|
||||||
|
void inject(@NotNull NotificationsViewModel viewModel);
|
||||||
|
|
||||||
void inject(@NotNull ChatFragment chatFragment);
|
void inject(@NotNull ChatFragment chatFragment);
|
||||||
|
|
||||||
void inject(@NotNull GiftIAPActivity giftIAPActivity);
|
void inject(@NotNull GiftIAPActivity giftIAPActivity);
|
||||||
|
|
|
||||||
|
|
@ -212,6 +212,8 @@ interface ApiClient {
|
||||||
|
|
||||||
// Notifications
|
// Notifications
|
||||||
fun readNotification(notificationId: String): Flowable<List<*>>
|
fun readNotification(notificationId: String): Flowable<List<*>>
|
||||||
|
fun readNotifications(notificationIds: Map<String, List<String>>): Flowable<List<*>>
|
||||||
|
fun seeNotifications(notificationIds: Map<String, List<String>>): Flowable<List<*>>
|
||||||
|
|
||||||
fun getErrorResponse(throwable: HttpException): ErrorResponse
|
fun getErrorResponse(throwable: HttpException): ErrorResponse
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,8 @@ interface UserRepository : BaseRepository {
|
||||||
fun runCron()
|
fun runCron()
|
||||||
|
|
||||||
fun readNotification(id: String): Flowable<List<*>>
|
fun readNotification(id: String): Flowable<List<*>>
|
||||||
|
fun readNotifications(notificationIds: Map<String, List<String>>): Flowable<List<*>>
|
||||||
|
fun seeNotifications(notificationIds: Map<String, List<String>>): Flowable<List<*>>
|
||||||
|
|
||||||
fun changeCustomDayStart(dayStartTime: Int): Flowable<User>
|
fun changeCustomDayStart(dayStartTime: Int): Flowable<User>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -172,6 +172,12 @@ class UserRepositoryImpl(localRepository: UserLocalRepository, apiClient: ApiCli
|
||||||
|
|
||||||
override fun readNotification(id: String): Flowable<List<*>> = apiClient.readNotification(id)
|
override fun readNotification(id: String): Flowable<List<*>> = apiClient.readNotification(id)
|
||||||
|
|
||||||
|
override fun readNotifications(notificationIds: Map<String, List<String>>): Flowable<List<*>> =
|
||||||
|
apiClient.readNotifications(notificationIds)
|
||||||
|
|
||||||
|
override fun seeNotifications(notificationIds: Map<String, List<String>>): Flowable<List<*>> =
|
||||||
|
apiClient.seeNotifications(notificationIds)
|
||||||
|
|
||||||
override fun changeCustomDayStart(dayStartTime: Int): Flowable<User> {
|
override fun changeCustomDayStart(dayStartTime: Int): Flowable<User> {
|
||||||
val updateObject = HashMap<String, Any>()
|
val updateObject = HashMap<String, Any>()
|
||||||
updateObject["dayStart"] = dayStartTime
|
updateObject["dayStart"] = dayStartTime
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
package com.habitrpg.android.habitica.helpers
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import io.reactivex.BackpressureStrategy
|
||||||
|
import io.reactivex.Flowable
|
||||||
|
import io.reactivex.subjects.BehaviorSubject
|
||||||
|
|
||||||
|
import com.habitrpg.android.habitica.R
|
||||||
|
import com.habitrpg.android.habitica.data.ApiClient
|
||||||
|
import com.habitrpg.android.habitica.events.ShowCheckinDialog
|
||||||
|
import com.habitrpg.android.habitica.events.ShowSnackbarEvent
|
||||||
|
import com.habitrpg.android.habitica.models.Notification
|
||||||
|
import com.habitrpg.android.habitica.models.notifications.LoginIncentiveData
|
||||||
|
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
|
||||||
|
import io.reactivex.functions.Consumer
|
||||||
|
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
|
||||||
|
import java.util.HashMap
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by krh12 on 12/9/2016.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class NotificationsManager (private val context: Context) {
|
||||||
|
// @TODO: A queue for displaying alert dialogues
|
||||||
|
|
||||||
|
private val seenNotifications: MutableMap<String, Boolean>
|
||||||
|
private var apiClient: ApiClient? = null
|
||||||
|
|
||||||
|
private val notifications: BehaviorSubject<List<Notification>>
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.seenNotifications = HashMap()
|
||||||
|
this.notifications = BehaviorSubject.create()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setNotifications(current: List<Notification>) {
|
||||||
|
this.notifications.onNext(current)
|
||||||
|
|
||||||
|
this.handlePopupNotifications(current)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getNotifications(): Flowable<List<Notification>> {
|
||||||
|
return this.notifications.toFlowable(BackpressureStrategy.LATEST)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getNotification(id: String): Notification? {
|
||||||
|
return this.notifications.value?.find { it.id == id }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setApiClient(apiClient: ApiClient?) {
|
||||||
|
this.apiClient = apiClient
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handlePopupNotifications(notifications: List<Notification>): Boolean? {
|
||||||
|
notifications
|
||||||
|
.filter { !this.seenNotifications.containsKey(it.id) }
|
||||||
|
.map {
|
||||||
|
val notificationDisplayed = when (it.type) {
|
||||||
|
Notification.Type.LOGIN_INCENTIVE.type -> displayLoginIncentiveNotification(it)
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notificationDisplayed == true) {
|
||||||
|
this.seenNotifications[it.id] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun displayLoginIncentiveNotification(notification: Notification): Boolean? {
|
||||||
|
val notificationData = notification.data as LoginIncentiveData?
|
||||||
|
val nextUnlockText = context.getString(R.string.nextPrizeUnlocks, notificationData!!.nextRewardAt)
|
||||||
|
if (notificationData.rewardKey != null) {
|
||||||
|
val event = ShowCheckinDialog()
|
||||||
|
event.notification = notification
|
||||||
|
event.nextUnlockText = nextUnlockText
|
||||||
|
EventBus.getDefault().post(event)
|
||||||
|
} else {
|
||||||
|
val event = ShowSnackbarEvent()
|
||||||
|
event.title = notificationData.message
|
||||||
|
event.text = nextUnlockText
|
||||||
|
event.type = HabiticaSnackbar.SnackbarDisplayType.BLUE
|
||||||
|
EventBus.getDefault().post(event)
|
||||||
|
if (apiClient != null) {
|
||||||
|
// @TODO: This should be handled somewhere else? MAybe we notifiy via event
|
||||||
|
apiClient!!.readNotification(notification.id)
|
||||||
|
.subscribe(Consumer {}, RxErrorHandler.handleEmptyError())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
package com.habitrpg.android.habitica.models;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by krh12 on 11/28/2016.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import com.habitrpg.android.habitica.models.notifications.NotificationData;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class Notification {
|
|
||||||
|
|
||||||
public NotificationData data;
|
|
||||||
private String type;
|
|
||||||
private String createdAt;
|
|
||||||
private String id;
|
|
||||||
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The type
|
|
||||||
*/
|
|
||||||
public String getType() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param type The type
|
|
||||||
*/
|
|
||||||
public void setType(String type) {
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The createdAt
|
|
||||||
*/
|
|
||||||
public String getCreatedAt() {
|
|
||||||
return createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param createdAt The createdAt
|
|
||||||
*/
|
|
||||||
public void setCreatedAt(String createdAt) {
|
|
||||||
this.createdAt = createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* The data
|
|
||||||
*/
|
|
||||||
// public T getData() {
|
|
||||||
// return data;
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param data
|
|
||||||
* The data
|
|
||||||
*/
|
|
||||||
// public void setData(T data) {
|
|
||||||
// this.data = data;
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The id
|
|
||||||
*/
|
|
||||||
public String getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param id The id
|
|
||||||
*/
|
|
||||||
public void setId(String id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, Object> getAdditionalProperties() {
|
|
||||||
return this.additionalProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAdditionalProperty(String name, Object value) {
|
|
||||||
this.additionalProperties.put(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.habitrpg.android.habitica.models
|
||||||
|
|
||||||
|
import com.habitrpg.android.habitica.models.notifications.*
|
||||||
|
|
||||||
|
class Notification {
|
||||||
|
enum class Type(val type: String) {
|
||||||
|
LOGIN_INCENTIVE("LOGIN_INCENTIVE"),
|
||||||
|
NEW_STUFF("NEW_STUFF"),
|
||||||
|
NEW_CHAT_MESSAGE("NEW_CHAT_MESSAGE"),
|
||||||
|
NEW_MYSTERY_ITEMS("NEW_MYSTERY_ITEMS"),
|
||||||
|
GROUP_TASK_NEEDS_WORK("GROUP_TASK_NEEDS_WORK"),
|
||||||
|
GROUP_TASK_APPROVED("GROUP_TASK_APPROVED"),
|
||||||
|
UNALLOCATED_STATS_POINTS("UNALLOCATED_STATS_POINTS");
|
||||||
|
}
|
||||||
|
|
||||||
|
var id: String = ""
|
||||||
|
|
||||||
|
var type: String? = null
|
||||||
|
var seen: Boolean? = null
|
||||||
|
|
||||||
|
var data: NotificationData? = null
|
||||||
|
|
||||||
|
fun getDataType(): java.lang.reflect.Type? {
|
||||||
|
return when (type) {
|
||||||
|
Type.LOGIN_INCENTIVE.type -> LoginIncentiveData::class.java
|
||||||
|
Type.NEW_STUFF.type -> NewStuffData::class.java
|
||||||
|
Type.NEW_CHAT_MESSAGE.type -> NewChatMessageData::class.java
|
||||||
|
Type.GROUP_TASK_NEEDS_WORK.type -> GroupTaskNeedsWorkData::class.java
|
||||||
|
Type.GROUP_TASK_APPROVED.type -> GroupTaskApprovedData::class.java
|
||||||
|
Type.UNALLOCATED_STATS_POINTS.type -> UnallocatedPointsData::class.java
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.habitrpg.android.habitica.models.notifications
|
||||||
|
|
||||||
|
open class GroupTaskApprovedData : NotificationData {
|
||||||
|
var groupId: String = ""
|
||||||
|
|
||||||
|
var message: String? = null
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.habitrpg.android.habitica.models.notifications
|
||||||
|
|
||||||
|
open class GroupTaskNeedsWorkData : NotificationData {
|
||||||
|
var group: NotificationGroup? = null
|
||||||
|
|
||||||
|
var message: String? = null
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.habitrpg.android.habitica.models.notifications
|
||||||
|
|
||||||
|
open class LoginIncentiveData : NotificationData {
|
||||||
|
|
||||||
|
var message: String? = null
|
||||||
|
var nextRewardAt: Int? = null
|
||||||
|
var rewardText: String? = null
|
||||||
|
var rewardKey: List<String>? = null
|
||||||
|
var reward: List<Reward>? = null
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.habitrpg.android.habitica.models.notifications
|
||||||
|
|
||||||
|
open class NewChatMessageData : NotificationData {
|
||||||
|
var group: NotificationGroup? = null
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.habitrpg.android.habitica.models.notifications
|
||||||
|
|
||||||
|
open class NewStuffData : NotificationData {
|
||||||
|
var title: String? = null
|
||||||
|
}
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
package com.habitrpg.android.habitica.models.notifications;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by krh12 on 11/30/2016.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class NotificationData {
|
|
||||||
|
|
||||||
public String groupId;
|
|
||||||
public String message;
|
|
||||||
public Integer nextRewardAt;
|
|
||||||
public String rewardText;
|
|
||||||
public List<String> rewardKey;
|
|
||||||
public List<Reward> reward;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
package com.habitrpg.android.habitica.models.notifications
|
||||||
|
|
||||||
|
interface NotificationData
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.habitrpg.android.habitica.models.notifications
|
||||||
|
|
||||||
|
open class NotificationGroup {
|
||||||
|
var id: String = ""
|
||||||
|
|
||||||
|
var name: String? = null
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.habitrpg.android.habitica.models.notifications
|
||||||
|
|
||||||
|
open class UnallocatedPointsData : NotificationData {
|
||||||
|
var points: Int? = null
|
||||||
|
}
|
||||||
|
|
@ -7,8 +7,8 @@ import com.habitrpg.android.habitica.api.HostConfig;
|
||||||
import com.habitrpg.android.habitica.api.MaintenanceApiService;
|
import com.habitrpg.android.habitica.api.MaintenanceApiService;
|
||||||
import com.habitrpg.android.habitica.data.ApiClient;
|
import com.habitrpg.android.habitica.data.ApiClient;
|
||||||
import com.habitrpg.android.habitica.data.implementation.ApiClientImpl;
|
import com.habitrpg.android.habitica.data.implementation.ApiClientImpl;
|
||||||
|
import com.habitrpg.android.habitica.helpers.NotificationsManager;
|
||||||
import com.habitrpg.android.habitica.helpers.KeyHelper;
|
import com.habitrpg.android.habitica.helpers.KeyHelper;
|
||||||
import com.habitrpg.android.habitica.helpers.PopupNotificationsManager;
|
|
||||||
import com.habitrpg.android.habitica.proxy.CrashlyticsProxy;
|
import com.habitrpg.android.habitica.proxy.CrashlyticsProxy;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
@ -36,14 +36,14 @@ public class ApiModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
public PopupNotificationsManager providesPopupNotificationsManager(Context context) {
|
public NotificationsManager providesPopupNotificationsManager(Context context) {
|
||||||
return new PopupNotificationsManager(context);
|
return new NotificationsManager(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
public ApiClient providesApiHelper(GsonConverterFactory gsonConverter, HostConfig hostConfig, CrashlyticsProxy crashlyticsProxy, PopupNotificationsManager popupNotificationsManager, Context context) {
|
public ApiClient providesApiHelper(GsonConverterFactory gsonConverter, HostConfig hostConfig, CrashlyticsProxy crashlyticsProxy, NotificationsManager notificationsManager, Context context) {
|
||||||
return new ApiClientImpl(gsonConverter, hostConfig, crashlyticsProxy, popupNotificationsManager, context);
|
return new ApiClientImpl(gsonConverter, hostConfig, crashlyticsProxy, notificationsManager, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,250 @@
|
||||||
|
package com.habitrpg.android.habitica.ui.activities
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.Html
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.lifecycle.ViewModelProviders
|
||||||
|
import com.habitrpg.android.habitica.R
|
||||||
|
import com.habitrpg.android.habitica.components.AppComponent
|
||||||
|
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||||
|
import com.habitrpg.android.habitica.models.Notification
|
||||||
|
import com.habitrpg.android.habitica.models.notifications.*
|
||||||
|
import com.habitrpg.android.habitica.ui.activities.MainActivity.Companion.NOTIFICATION_CLICK
|
||||||
|
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel
|
||||||
|
import io.reactivex.functions.Consumer
|
||||||
|
import kotlinx.android.synthetic.main.activity_notifications.*
|
||||||
|
|
||||||
|
class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener {
|
||||||
|
|
||||||
|
lateinit var viewModel: NotificationsViewModel
|
||||||
|
|
||||||
|
lateinit var inflater: LayoutInflater
|
||||||
|
|
||||||
|
override fun getLayoutResId(): Int = R.layout.activity_notifications
|
||||||
|
|
||||||
|
private var notifications: List<Notification> = emptyList()
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
setupToolbar(toolbar)
|
||||||
|
|
||||||
|
inflater = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
||||||
|
|
||||||
|
viewModel = ViewModelProviders.of(this)
|
||||||
|
.get(NotificationsViewModel::class.java)
|
||||||
|
|
||||||
|
compositeSubscription.add(viewModel.getNotifications().subscribe(Consumer {
|
||||||
|
this.setNotifications(it)
|
||||||
|
viewModel.markNotificationsAsSeen(it)
|
||||||
|
}, RxErrorHandler.handleEmptyError()))
|
||||||
|
|
||||||
|
notifications_refresh_layout?.setOnRefreshListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun injectActivity(component: AppComponent?) {
|
||||||
|
component?.inject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSupportNavigateUp(): Boolean {
|
||||||
|
if (supportFragmentManager.backStackEntryCount > 0) {
|
||||||
|
onBackPressed()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return super.onSupportNavigateUp()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRefresh() {
|
||||||
|
notifications_refresh_layout.isRefreshing = true
|
||||||
|
|
||||||
|
compositeSubscription.add(viewModel.refreshNotifications().subscribe(Consumer {
|
||||||
|
notifications_refresh_layout.isRefreshing = false
|
||||||
|
}, RxErrorHandler.handleEmptyError()))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setNotifications(notifications: List<Notification>) {
|
||||||
|
this.notifications = notifications
|
||||||
|
|
||||||
|
if (notification_items == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
notification_items.removeAllViewsInLayout()
|
||||||
|
|
||||||
|
when {
|
||||||
|
notifications.isEmpty() -> displayNoNotificationsView()
|
||||||
|
else -> displayNotificationsListView(notifications)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun displayNoNotificationsView() {
|
||||||
|
notification_items.showDividers = LinearLayout.SHOW_DIVIDER_NONE
|
||||||
|
|
||||||
|
notification_items.addView(
|
||||||
|
inflater.inflate(R.layout.no_notifications, notification_items, false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun displayNotificationsListView(notifications: List<Notification>) {
|
||||||
|
notification_items.showDividers = LinearLayout.SHOW_DIVIDER_MIDDLE or LinearLayout.SHOW_DIVIDER_END
|
||||||
|
|
||||||
|
notification_items.addView(
|
||||||
|
createNotificationsHeaderView(notifications.count())
|
||||||
|
)
|
||||||
|
|
||||||
|
notifications.map {
|
||||||
|
val item: View? = when (it.type) {
|
||||||
|
Notification.Type.NEW_CHAT_MESSAGE.type -> createNewChatMessageNotification(it)
|
||||||
|
Notification.Type.NEW_STUFF.type -> createNewStuffNotification(it)
|
||||||
|
Notification.Type.UNALLOCATED_STATS_POINTS.type -> createUnallocatedStatsNotification(it)
|
||||||
|
Notification.Type.NEW_MYSTERY_ITEMS.type -> createMysteryItemsNotification(it)
|
||||||
|
Notification.Type.GROUP_TASK_NEEDS_WORK.type -> createGroupTaskNeedsWorkNotification(it)
|
||||||
|
Notification.Type.GROUP_TASK_APPROVED.type -> createGroupTaskApprovedNotification(it)
|
||||||
|
//TODO rest of the notification types
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
notification_items.addView(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createNotificationsHeaderView(notificationCount: Int): View? {
|
||||||
|
val header = inflater.inflate(R.layout.notifications_header, notification_items, false)
|
||||||
|
|
||||||
|
val badge = header?.findViewById(R.id.notifications_title_badge) as? TextView
|
||||||
|
badge?.text = notificationCount.toString()
|
||||||
|
|
||||||
|
val dismissAllButton = header?.findViewById(R.id.dismiss_all_button) as? Button
|
||||||
|
dismissAllButton?.setOnClickListener { viewModel.dismissAllNotifications(notifications) }
|
||||||
|
|
||||||
|
return header
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createNewChatMessageNotification(notification: Notification): View? {
|
||||||
|
val data = notification.data as? NewChatMessageData
|
||||||
|
val stringId = if (viewModel.isPartyMessage(data)) R.string.new_msg_party else R.string.new_msg_guild
|
||||||
|
|
||||||
|
return createNotificationItem(
|
||||||
|
notification,
|
||||||
|
fromHtml(getString(stringId, data?.group?.name))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createNewStuffNotification(notification: Notification): View? {
|
||||||
|
val data = notification.data as? NewStuffData
|
||||||
|
val text = fromHtml("<b>" + getString(R.string.new_bailey_update) + "</b><br>" + data?.title)
|
||||||
|
|
||||||
|
return createNotificationItem(
|
||||||
|
notification,
|
||||||
|
text,
|
||||||
|
R.drawable.notifications_bailey
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createUnallocatedStatsNotification(notification: Notification): View? {
|
||||||
|
val data = notification.data as? UnallocatedPointsData
|
||||||
|
|
||||||
|
return createNotificationItem(
|
||||||
|
notification,
|
||||||
|
fromHtml(getString(R.string.unallocated_stats_points, data?.points.toString())),
|
||||||
|
R.drawable.notification_stat_sparkles
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createMysteryItemsNotification(notification: Notification): View? {
|
||||||
|
return createNotificationItem(
|
||||||
|
notification,
|
||||||
|
fromHtml(getString(R.string.new_subscriber_item)),
|
||||||
|
R.drawable.notification_mystery_item
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createGroupTaskNeedsWorkNotification(notification: Notification): View? {
|
||||||
|
val data = notification.data as? GroupTaskNeedsWorkData
|
||||||
|
val message = convertGroupMessageHtml(data?.message ?: "")
|
||||||
|
|
||||||
|
return createNotificationItem(
|
||||||
|
notification,
|
||||||
|
fromHtml(message),
|
||||||
|
null,
|
||||||
|
R.color.yellow_5
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createGroupTaskApprovedNotification(notification: Notification): View? {
|
||||||
|
val data = notification.data as? GroupTaskApprovedData
|
||||||
|
val message = convertGroupMessageHtml(data?.message ?: "")
|
||||||
|
|
||||||
|
return createNotificationItem(
|
||||||
|
notification,
|
||||||
|
fromHtml(message),
|
||||||
|
null,
|
||||||
|
R.color.green_10
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Group task notifications have the message text in the notification data as HTML
|
||||||
|
* with <span class="notification-bold"> tags around emphasized words. So we just
|
||||||
|
* convert the span-tags to strong-tags to display correct parts as bold, since
|
||||||
|
* Html.fromHtml does not support CSS.
|
||||||
|
*/
|
||||||
|
private fun convertGroupMessageHtml(message: String): String {
|
||||||
|
// Using positive lookbehind to make sure "span" is preceded by "<" or "</"
|
||||||
|
val pattern = "(?<=</?)span".toRegex()
|
||||||
|
|
||||||
|
return message.replace(pattern, "strong")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createNotificationItem(
|
||||||
|
notification: Notification,
|
||||||
|
messageText: CharSequence,
|
||||||
|
imageResourceId: Int? = null,
|
||||||
|
textColor: Int? = null
|
||||||
|
): View? {
|
||||||
|
val item = inflater.inflate(R.layout.notification_item, notification_items, false)
|
||||||
|
|
||||||
|
val container = item?.findViewById(R.id.notification_item) as? View
|
||||||
|
container?.setOnClickListener {
|
||||||
|
val resultIntent = Intent()
|
||||||
|
resultIntent.putExtra("notificationId", notification.id)
|
||||||
|
setResult(NOTIFICATION_CLICK, resultIntent)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
val dismissButton = item?.findViewById(R.id.dismiss_button) as? ImageView
|
||||||
|
dismissButton?.setOnClickListener { viewModel.dismissNotification(notification) }
|
||||||
|
|
||||||
|
val messageTextView = item?.findViewById(R.id.message_text) as? TextView
|
||||||
|
messageTextView?.text = messageText
|
||||||
|
|
||||||
|
if (imageResourceId != null) {
|
||||||
|
val notificationImage = item?.findViewById(R.id.notification_image) as? ImageView
|
||||||
|
notificationImage?.setImageResource(imageResourceId)
|
||||||
|
notificationImage?.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textColor != null) {
|
||||||
|
messageTextView?.setTextColor(ContextCompat.getColor(this, textColor))
|
||||||
|
}
|
||||||
|
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fromHtml(text: String): CharSequence {
|
||||||
|
return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
|
||||||
|
Html.fromHtml(text, Html.FROM_HTML_MODE_LEGACY)
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
Html.fromHtml(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,11 +2,15 @@ package com.habitrpg.android.habitica.ui.fragments
|
||||||
|
|
||||||
|
|
||||||
import android.app.ActionBar
|
import android.app.ActionBar
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.graphics.drawable.GradientDrawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.GravityCompat
|
import androidx.core.view.GravityCompat
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import com.habitrpg.android.habitica.HabiticaBaseApplication
|
import com.habitrpg.android.habitica.HabiticaBaseApplication
|
||||||
|
|
@ -21,6 +25,8 @@ import com.habitrpg.android.habitica.models.inventory.Quest
|
||||||
import com.habitrpg.android.habitica.models.inventory.QuestContent
|
import com.habitrpg.android.habitica.models.inventory.QuestContent
|
||||||
import com.habitrpg.android.habitica.models.social.Group
|
import com.habitrpg.android.habitica.models.social.Group
|
||||||
import com.habitrpg.android.habitica.ui.activities.MainActivity
|
import com.habitrpg.android.habitica.ui.activities.MainActivity
|
||||||
|
import com.habitrpg.android.habitica.ui.activities.MainActivity.Companion.NOTIFICATION_CLICK
|
||||||
|
import com.habitrpg.android.habitica.ui.activities.NotificationsActivity
|
||||||
import com.habitrpg.android.habitica.ui.adapter.NavigationDrawerAdapter
|
import com.habitrpg.android.habitica.ui.adapter.NavigationDrawerAdapter
|
||||||
import com.habitrpg.android.habitica.ui.fragments.social.TavernDetailFragment
|
import com.habitrpg.android.habitica.ui.fragments.social.TavernDetailFragment
|
||||||
import com.habitrpg.android.habitica.ui.helpers.NavbarUtils
|
import com.habitrpg.android.habitica.ui.helpers.NavbarUtils
|
||||||
|
|
@ -97,6 +103,7 @@ class NavigationDrawerFragment : DialogFragment() {
|
||||||
|
|
||||||
messagesBadge.visibility = View.GONE
|
messagesBadge.visibility = View.GONE
|
||||||
settingsBadge.visibility = View.GONE
|
settingsBadge.visibility = View.GONE
|
||||||
|
notificationsBadge.visibility = View.GONE
|
||||||
|
|
||||||
/* Reenable this once the boss art can be displayed correctly.
|
/* Reenable this once the boss art can be displayed correctly.
|
||||||
|
|
||||||
|
|
@ -192,6 +199,7 @@ class NavigationDrawerFragment : DialogFragment() {
|
||||||
|
|
||||||
messagesButtonWrapper.setOnClickListener { setSelection(R.id.inboxFragment) }
|
messagesButtonWrapper.setOnClickListener { setSelection(R.id.inboxFragment) }
|
||||||
settingsButtonWrapper.setOnClickListener { setSelection(R.id.prefsActivity) }
|
settingsButtonWrapper.setOnClickListener { setSelection(R.id.prefsActivity) }
|
||||||
|
notificationsButtonWrapper.setOnClickListener { startNotificationsActivity() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
|
@ -245,6 +253,18 @@ class NavigationDrawerFragment : DialogFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun startNotificationsActivity() {
|
||||||
|
closeDrawer()
|
||||||
|
|
||||||
|
val activity = activity as? MainActivity
|
||||||
|
if (activity != null) {
|
||||||
|
// NotificationsActivity will return a result intent with a notificationId if a
|
||||||
|
// notification item was clicked
|
||||||
|
val intent = Intent(activity, NotificationsActivity::class.java)
|
||||||
|
activity.startActivityForResult(intent, NOTIFICATION_CLICK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Users of this fragment must call this method to set UP the navigation drawer interactions.
|
* Users of this fragment must call this method to set UP the navigation drawer interactions.
|
||||||
*
|
*
|
||||||
|
|
@ -305,6 +325,24 @@ class NavigationDrawerFragment : DialogFragment() {
|
||||||
outState.putInt(STATE_SELECTED_POSITION, mCurrentSelectedPosition)
|
outState.putInt(STATE_SELECTED_POSITION, mCurrentSelectedPosition)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setNotificationsCount(unreadNotifications: Int) {
|
||||||
|
if (unreadNotifications == 0) {
|
||||||
|
notificationsBadge.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
notificationsBadge.visibility = View.VISIBLE
|
||||||
|
notificationsBadge.text = unreadNotifications.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setNotificationsSeen(allSeen: Boolean) {
|
||||||
|
context.notNull {
|
||||||
|
val colorId = if (allSeen) R.color.gray_200 else R.color.brand_400
|
||||||
|
|
||||||
|
val bg = notificationsBadge.background as GradientDrawable
|
||||||
|
bg.color = ColorStateList.valueOf(ContextCompat.getColor(it, colorId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun setMessagesCount(unreadMessages: Int) {
|
fun setMessagesCount(unreadMessages: Int) {
|
||||||
if (unreadMessages == 0) {
|
if (unreadMessages == 0) {
|
||||||
messagesBadge.visibility = View.GONE
|
messagesBadge.visibility = View.GONE
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
package com.habitrpg.android.habitica.ui.viewmodels
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import com.habitrpg.android.habitica.R
|
||||||
|
import com.habitrpg.android.habitica.components.AppComponent
|
||||||
|
import com.habitrpg.android.habitica.helpers.MainNavigationController
|
||||||
|
import com.habitrpg.android.habitica.helpers.NotificationsManager
|
||||||
|
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||||
|
import com.habitrpg.android.habitica.models.Notification
|
||||||
|
import com.habitrpg.android.habitica.models.notifications.NewChatMessageData
|
||||||
|
import com.habitrpg.android.habitica.models.social.UserParty
|
||||||
|
import io.reactivex.Flowable
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.functions.Consumer
|
||||||
|
import java.util.HashMap
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
open class NotificationsViewModel : BaseViewModel() {
|
||||||
|
@Inject
|
||||||
|
lateinit var notificationsManager: NotificationsManager
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of notification types handled by this component.
|
||||||
|
* NOTE: Those not listed here won't be shown in the notification panel
|
||||||
|
*/
|
||||||
|
private val supportedNotificationTypes = listOf(
|
||||||
|
Notification.Type.NEW_STUFF.type,
|
||||||
|
Notification.Type.NEW_CHAT_MESSAGE.type,
|
||||||
|
Notification.Type.NEW_MYSTERY_ITEMS.type,
|
||||||
|
Notification.Type.GROUP_TASK_NEEDS_WORK.type,
|
||||||
|
Notification.Type.GROUP_TASK_APPROVED.type,
|
||||||
|
Notification.Type.UNALLOCATED_STATS_POINTS.type
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep track of users party so we can determine which chat notifications are party chat
|
||||||
|
* instead of guild chat notifications.
|
||||||
|
*/
|
||||||
|
private var party: UserParty? = null
|
||||||
|
|
||||||
|
override fun inject(component: AppComponent) {
|
||||||
|
component.inject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
disposable.add(userRepository.getUser()
|
||||||
|
.subscribe(Consumer {
|
||||||
|
party = it.party
|
||||||
|
}, RxErrorHandler.handleEmptyError()))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun getNotifications(): Flowable<List<Notification>> {
|
||||||
|
return notificationsManager.getNotifications()
|
||||||
|
.map { filterSupportedTypes(it) }
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getNotificationCount(): Flowable<Int> {
|
||||||
|
return getNotifications()
|
||||||
|
.map { it.count() }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun allNotificationsSeen(): Flowable<Boolean> {
|
||||||
|
return getNotifications()
|
||||||
|
.map { it.all { notification -> notification.seen == true } }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refreshNotifications(): Flowable<*> {
|
||||||
|
return userRepository.retrieveUser(withTasks = false, forced = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun filterSupportedTypes(notifications: List<Notification>): List<Notification> {
|
||||||
|
return notifications.filter { supportedNotificationTypes.contains(it.type) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isPartyMessage(data: NewChatMessageData?): Boolean {
|
||||||
|
if (party == null || data?.group?.id == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return party?.id == data.group?.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dismissNotification(notification: Notification) {
|
||||||
|
disposable.add(userRepository.readNotification(notification.id)
|
||||||
|
.subscribe(Consumer {}, RxErrorHandler.handleEmptyError()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dismissAllNotifications(notifications: List<Notification>) {
|
||||||
|
if (notifications.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val notificationIds = HashMap<String, List<String>>()
|
||||||
|
notificationIds["notificationIds"] = notifications.map { notification -> notification.id }
|
||||||
|
|
||||||
|
disposable.add(userRepository.readNotifications(notificationIds)
|
||||||
|
.subscribe(Consumer {}, RxErrorHandler.handleEmptyError()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun markNotificationsAsSeen(notifications: List<Notification>) {
|
||||||
|
val unseenIds = notifications.filter { notification -> notification.seen != true }
|
||||||
|
.map { notification -> notification.id }
|
||||||
|
|
||||||
|
if (unseenIds.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val notificationIds = HashMap<String, List<String>>()
|
||||||
|
notificationIds["notificationIds"] = unseenIds
|
||||||
|
|
||||||
|
disposable.add(userRepository.seeNotifications(notificationIds)
|
||||||
|
.subscribe(Consumer {}, RxErrorHandler.handleEmptyError()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun click(notificationId: String, navController: MainNavigationController) {
|
||||||
|
val notification = notificationsManager.getNotification(notificationId) ?: return
|
||||||
|
|
||||||
|
dismissNotification(notification)
|
||||||
|
|
||||||
|
when (notification.type) {
|
||||||
|
Notification.Type.NEW_STUFF.type -> navController.navigate(R.id.newsFragment)
|
||||||
|
Notification.Type.NEW_CHAT_MESSAGE.type -> clickNewChatMessage(notification, navController)
|
||||||
|
Notification.Type.NEW_MYSTERY_ITEMS.type -> navController.navigate(R.id.itemsFragment)
|
||||||
|
Notification.Type.UNALLOCATED_STATS_POINTS.type -> navController.navigate(R.id.statsFragment)
|
||||||
|
// Group tasks should go to Group tasks view if that is added to this app at some point
|
||||||
|
Notification.Type.GROUP_TASK_APPROVED.type -> navController.navigate(R.id.tasksFragment)
|
||||||
|
Notification.Type.GROUP_TASK_NEEDS_WORK.type -> navController.navigate(R.id.tasksFragment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clickNewChatMessage(notification: Notification, navController: MainNavigationController) {
|
||||||
|
val data = notification.data as? NewChatMessageData
|
||||||
|
if (isPartyMessage(data)) {
|
||||||
|
navController.navigate(R.id.partyFragment)
|
||||||
|
} else {
|
||||||
|
val bundle = Bundle()
|
||||||
|
bundle.putString("groupID", data?.group?.id)
|
||||||
|
bundle.putBoolean("isMember", true) // safe to assume user is member since they got the notification
|
||||||
|
navController.navigate(R.id.guildFragment, bundle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.habitrpg.android.habitica.utils
|
||||||
|
|
||||||
|
import com.google.gson.JsonDeserializationContext
|
||||||
|
import com.google.gson.JsonDeserializer
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import com.google.gson.JsonParseException
|
||||||
|
import com.habitrpg.android.habitica.models.Notification
|
||||||
|
import java.lang.reflect.Type
|
||||||
|
|
||||||
|
class NotificationDeserializer : JsonDeserializer<Notification> {
|
||||||
|
@Throws(JsonParseException::class)
|
||||||
|
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Notification {
|
||||||
|
val notification = Notification()
|
||||||
|
val obj = json.asJsonObject
|
||||||
|
|
||||||
|
if (obj.has("id")) {
|
||||||
|
notification.id = obj.get("id").asString
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.has("type")) {
|
||||||
|
notification.type = obj.get("type").asString
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.has("seen")) {
|
||||||
|
notification.seen = obj.get("seen").asBoolean
|
||||||
|
}
|
||||||
|
|
||||||
|
val dataType = notification.getDataType()
|
||||||
|
if (obj.has("data") && dataType != null) {
|
||||||
|
notification.data = context.deserialize(obj.getAsJsonObject("data"), dataType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return notification
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
//import com.habitrpg.android.habitica.data.implementation.ApiClientImpl;
|
//import com.habitrpg.android.habitica.data.implementation.ApiClientImpl;
|
||||||
//import com.habitrpg.android.habitica.helpers.PopupNotificationsManager;
|
//import com.habitrpg.android.habitica.helpers.NotificationsManager;
|
||||||
//import com.habitrpg.android.habitica.proxy.implementation.EmptyCrashlyticsProxy;
|
//import com.habitrpg.android.habitica.proxy.implementation.EmptyCrashlyticsProxy;
|
||||||
//import com.habitrpg.android.habitica.data.ApiClient;
|
//import com.habitrpg.android.habitica.data.ApiClient;
|
||||||
//import com.habitrpg.android.habitica.BuildConfig;
|
//import com.habitrpg.android.habitica.BuildConfig;
|
||||||
|
|
@ -62,7 +62,7 @@
|
||||||
// BuildConfig.PORT,
|
// BuildConfig.PORT,
|
||||||
// "",
|
// "",
|
||||||
// "");
|
// "");
|
||||||
// //apiClient = new ApiClientImpl(ApiClientImpl.createGsonFactory(), hostConfig, new EmptyCrashlyticsProxy(), new PopupNotificationsManager(context), context);
|
// //apiClient = new ApiClientImpl(ApiClientImpl.createGsonFactory(), hostConfig, new EmptyCrashlyticsProxy(), new NotificationsManager(context), context);
|
||||||
// //generateUser();
|
// //generateUser();
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,19 @@
|
||||||
package com.habitrpg.android.habitica.helpers;
|
package com.habitrpg.android.habitica.helpers;
|
||||||
|
|
||||||
import com.habitrpg.android.habitica.data.implementation.ApiClientImpl;
|
|
||||||
import com.habitrpg.android.habitica.BuildConfig;
|
import com.habitrpg.android.habitica.BuildConfig;
|
||||||
import com.habitrpg.android.habitica.HabiticaApplication;
|
|
||||||
import com.habitrpg.android.habitica.api.HostConfig;
|
import com.habitrpg.android.habitica.api.HostConfig;
|
||||||
import com.habitrpg.android.habitica.proxy.implementation.EmptyCrashlyticsProxy;
|
|
||||||
import com.habitrpg.android.habitica.data.ApiClient;
|
|
||||||
import com.habitrpg.android.habitica.models.Notification;
|
import com.habitrpg.android.habitica.models.Notification;
|
||||||
import com.habitrpg.android.habitica.models.notifications.NotificationData;
|
import com.habitrpg.android.habitica.models.notifications.LoginIncentiveData;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
import org.robolectric.Robolectric;
|
|
||||||
import org.robolectric.RobolectricTestRunner;
|
import org.robolectric.RobolectricTestRunner;
|
||||||
import org.robolectric.RuntimeEnvironment;
|
import org.robolectric.RuntimeEnvironment;
|
||||||
import org.robolectric.annotation.Config;
|
import org.robolectric.annotation.Config;
|
||||||
import org.robolectric.shadows.ShadowAlertDialog;
|
import org.robolectric.shadows.ShadowAlertDialog;
|
||||||
import org.robolectric.shadows.ShadowApplication;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
@ -42,7 +35,7 @@ public class PopupNotificationsManagerTest {
|
||||||
|
|
||||||
public HostConfig hostConfig;
|
public HostConfig hostConfig;
|
||||||
private Context context;
|
private Context context;
|
||||||
private PopupNotificationsManager popupNotificationsManager;
|
private NotificationsManager notificationsManager;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
|
|
@ -51,13 +44,13 @@ public class PopupNotificationsManagerTest {
|
||||||
BuildConfig.PORT,
|
BuildConfig.PORT,
|
||||||
"",
|
"",
|
||||||
"");
|
"");
|
||||||
popupNotificationsManager =new PopupNotificationsManager(context);
|
notificationsManager =new NotificationsManager(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void itDoesNothingWhenNotificationsListIsEmpty() {
|
public void itDoesNothingWhenNotificationsListIsEmpty() {
|
||||||
List<Notification> notifications = new ArrayList<>();
|
List<Notification> notifications = new ArrayList<>();
|
||||||
popupNotificationsManager.showNotificationDialog(notifications);
|
notificationsManager.handlePopupNotifications(notifications);
|
||||||
|
|
||||||
AlertDialog alert =
|
AlertDialog alert =
|
||||||
ShadowAlertDialog.getLatestAlertDialog();
|
ShadowAlertDialog.getLatestAlertDialog();
|
||||||
|
|
@ -74,13 +67,13 @@ public class PopupNotificationsManagerTest {
|
||||||
|
|
||||||
notifications.add(notification);
|
notifications.add(notification);
|
||||||
|
|
||||||
final PopupNotificationsManager testClass = Mockito.mock(PopupNotificationsManager.class);
|
final NotificationsManager testClass = Mockito.mock(NotificationsManager.class);
|
||||||
Mockito.when(testClass.displayNotification(notification)).thenReturn(true);
|
Mockito.when(testClass.displayLoginIncentiveNotification(notification)).thenReturn(true);
|
||||||
Mockito.when(testClass.showNotificationDialog(notifications)).thenCallRealMethod();
|
Mockito.when(testClass.handlePopupNotifications(notifications)).thenCallRealMethod();
|
||||||
|
|
||||||
testClass.showNotificationDialog(notifications);
|
testClass.handlePopupNotifications(notifications);
|
||||||
|
|
||||||
verify(testClass, times(0)).displayNotification(notification);
|
verify(testClass, times(0)).displayLoginIncentiveNotification(notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -89,22 +82,22 @@ public class PopupNotificationsManagerTest {
|
||||||
|
|
||||||
List<Notification> notifications = new ArrayList<>();
|
List<Notification> notifications = new ArrayList<>();
|
||||||
|
|
||||||
NotificationData notificationData = new NotificationData();
|
LoginIncentiveData notificationData = new LoginIncentiveData();
|
||||||
notificationData.message = testTitle;
|
notificationData.setMessage(testTitle);
|
||||||
|
|
||||||
Notification notification = new Notification();
|
Notification notification = new Notification();
|
||||||
notification.setType("LOGIN_INCENTIVE");
|
notification.setType("LOGIN_INCENTIVE");
|
||||||
notification.data = notificationData;
|
notification.setData(notificationData);
|
||||||
|
|
||||||
notifications.add(notification);
|
notifications.add(notification);
|
||||||
|
|
||||||
final PopupNotificationsManager testClass = Mockito.mock(PopupNotificationsManager.class);
|
final NotificationsManager testClass = Mockito.mock(NotificationsManager.class);
|
||||||
Mockito.when(testClass.displayNotification(notification)).thenReturn(true);
|
Mockito.when(testClass.displayLoginIncentiveNotification(notification)).thenReturn(true);
|
||||||
Mockito.when(testClass.showNotificationDialog(notifications)).thenCallRealMethod();
|
Mockito.when(testClass.handlePopupNotifications(notifications)).thenCallRealMethod();
|
||||||
|
|
||||||
testClass.showNotificationDialog(notifications);
|
testClass.handlePopupNotifications(notifications);
|
||||||
|
|
||||||
verify(testClass, times(1)).displayNotification(notification);
|
verify(testClass, times(1)).displayLoginIncentiveNotification(notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -113,22 +106,22 @@ public class PopupNotificationsManagerTest {
|
||||||
|
|
||||||
List<Notification> notifications = new ArrayList<>();
|
List<Notification> notifications = new ArrayList<>();
|
||||||
|
|
||||||
NotificationData notificationData = new NotificationData();
|
LoginIncentiveData notificationData = new LoginIncentiveData();
|
||||||
notificationData.message = testTitle;
|
notificationData.setMessage(testTitle);
|
||||||
|
|
||||||
Notification notification = new Notification();
|
Notification notification = new Notification();
|
||||||
notification.setType("LOGIN_INCENTIVE");
|
notification.setType("LOGIN_INCENTIVE");
|
||||||
notification.data = notificationData;
|
notification.setData(notificationData);
|
||||||
|
|
||||||
notifications.add(notification);
|
notifications.add(notification);
|
||||||
notifications.add(notification);
|
notifications.add(notification);
|
||||||
|
|
||||||
final PopupNotificationsManager testClass = Mockito.mock(PopupNotificationsManager.class);
|
final NotificationsManager testClass = Mockito.mock(NotificationsManager.class);
|
||||||
Mockito.when(testClass.displayNotification(notification)).thenReturn(true);
|
Mockito.when(testClass.displayLoginIncentiveNotification(notification)).thenReturn(true);
|
||||||
Mockito.when(testClass.showNotificationDialog(notifications)).thenCallRealMethod();
|
Mockito.when(testClass.handlePopupNotifications(notifications)).thenCallRealMethod();
|
||||||
|
|
||||||
testClass.showNotificationDialog(notifications);
|
testClass.handlePopupNotifications(notifications);
|
||||||
|
|
||||||
verify(testClass, times(1)).displayNotification(notification);
|
verify(testClass, times(1)).displayLoginIncentiveNotification(notification);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||