diff --git a/Habitica/res/layout/notification_item_actionable.xml b/Habitica/res/layout/notification_item_actionable.xml
new file mode 100644
index 000000000..4af757530
--- /dev/null
+++ b/Habitica/res/layout/notification_item_actionable.xml
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Habitica/res/values-bg/strings.xml b/Habitica/res/values-bg/strings.xml
index 938bfdcf9..d788a514c 100644
--- a/Habitica/res/values-bg/strings.xml
+++ b/Habitica/res/values-bg/strings.xml
@@ -822,4 +822,17 @@
Подареният от Вас абонамент беше изпратен, и Вие също получихте абонамент.
Откриване
Щетите са спрени
+ Известия
+ Няма непрочетени известия!
+ Феите на известията Ви ръкопляскат бурно! Добра работа!
+ Премахване на всички
+ Ново обновление от Бейли!
+ %1$s]]>
+ %1$s]]>
+ %1$s неразпределени показателни точки]]>
+ тайнствен предмет]]>
+ %1$s]]>
+ %1$s]]>
+ %1$s]]>
+ %1$s]]>
diff --git a/Habitica/res/values-de/strings.xml b/Habitica/res/values-de/strings.xml
index 168577889..cf81a7f0f 100644
--- a/Habitica/res/values-de/strings.xml
+++ b/Habitica/res/values-de/strings.xml
@@ -790,15 +790,27 @@ Die Quest-Schriftrolle wird an den Quest-Besitzer zurückgegeben.
Schau Fernsehen, spiel ein Spiel, iss etwas Süßes, es liegt ganz an Dir!
Internetseite besuchen
Wen würdest Du gerne beschenken?
-Mehr lesen
- Weniger anzeigen
- Letzte Anmeldung
- Gesamtanmeldungen
- Zweihändig
- Benutzernamen bestätigen
- \@%s ・Lvl %d
- Eines dieser Veteranen-Haustiere wartet auf Dich wenn Du die Bestätigung abgeschlossen hast!
- Abonnement verschenken
- Gruppe erstellen
-
+ Mehr lesen
+ Weniger anzeigen
+ Letzte Anmeldung
+ Gesamtanmeldungen
+ Zweihändig
+ Benutzernamen bestätigen
+ \@%s ・Lvl %d
+ Eines dieser Veteranen-Haustiere wartet auf Dich wenn Du die Bestätigung abgeschlossen hast!
+ Abonnement verschenken
+ Gruppe erstellen
+ Mitteilungen
+ Du bist auf dem Laufenden!
+ Die Mitteilungsfeen geben dir eine heftige Runde Applaus! Gut gemacht!
+ Alle entfernen
+ Neues Update von Bailey!
+ %1$s hat neue Beiträge]]>
+ %1$s, hat neue Beiträge]]>
+ %1$s Attributpunkt(e) verteilen]]>
+ Überraschungsgegenstand]]>
+ %1$s eingeladen]]>
+ %1$s beizutreten.]]>
+ %1$s beizutreten.]]>
+ %1$s eingeladen]]>
diff --git a/Habitica/res/values-en-rGB/strings.xml b/Habitica/res/values-en-rGB/strings.xml
index 0d3ec58ef..4276f2cf5 100644
--- a/Habitica/res/values-en-rGB/strings.xml
+++ b/Habitica/res/values-en-rGB/strings.xml
@@ -574,4 +574,17 @@
Never mind
Resetting Account
Deleting Account
+ Notifications
+ You’re all caught up!
+ The notification fairies give you a raucous round of applause! Well done!
+ Dismiss All
+ New Bailey Update!
+ %1$s has new posts]]>
+ %1$s, has new posts]]>
+ %1$s unallocated Stat Points]]>
+ Mystery Items]]>
+ %1$s]]>
+ %1$s]]>
+ %1$s]]>
+ %1$s]]>
diff --git a/Habitica/res/values-es/strings.xml b/Habitica/res/values-es/strings.xml
index eeb092a60..1815762c5 100644
--- a/Habitica/res/values-es/strings.xml
+++ b/Habitica/res/values-es/strings.xml
@@ -823,4 +823,17 @@
Tu suscripción regalo ha sido enviada y tu suscripción ha sido aplicada a tu cuenta.
Descubre
Daño pausado
+ Notificaciones
+ ¡Estás al día!
+ Las hadas de las notificaciones te aplauden atronadoramente. ¡Bien hecho!
+ Ignorar todas
+ ¡Nueva Actualización de Bailey!
+ %1$s tiene nuevos mensajes]]>
+ %1$s, tiene nuevos mensajes]]>
+ %1$s Puntos de Estadísticas sin asignar]]>
+ Objetos Misteriosos nuevos]]>
+ %1$s]]>
+ %1$s]]>
+ %1$s]]>
+ %1$s]]>
diff --git a/Habitica/res/values-fr/strings.xml b/Habitica/res/values-fr/strings.xml
index b3c2d1dfb..7dc779a49 100644
--- a/Habitica/res/values-fr/strings.xml
+++ b/Habitica/res/values-fr/strings.xml
@@ -820,4 +820,17 @@
Votre cadeau a été envoyé !
Votre abonnement offert a été envoyé, et votre abonnement a été rattaché à votre compte.
Votre abonnement offert a été envoyé, et votre abonnement a été rattaché à votre compte.
+ Notifications
+ Vous êtes à jour !
+ Les fées de la notification vous font un tonnerre d\'applaudissements ! Bien joué !
+ Tout effacer
+ Nouvelles informations de Bailey !
+ %1$s a de nouveaux messages]]>
+ %1$s, a de nouveaux messages]]>
+ %1$s points d\'attribut non alloués]]>
+ objets mystère]]>
+ %1$s]]>
+ %1$s]]>
+ %1$s]]>
+ %1$s]]>
diff --git a/Habitica/res/values-it/strings.xml b/Habitica/res/values-it/strings.xml
index 210a385a1..7c231204c 100644
--- a/Habitica/res/values-it/strings.xml
+++ b/Habitica/res/values-it/strings.xml
@@ -823,4 +823,17 @@ Le Dailies mancate e le cattive Abitudini non li danneggiano molto, e hanno semp
L\'abbonamento regalato è stato ricevuto e l\'abbonamento corrispondente applicato al tuo profilo.
Scopri
Danni sospesi
+ Notifiche
+ Sei in pari!
+ Le fate delle notifche ti danno un rauco turno di applausi! Ben fatto!
+ Nascondi tutto
+ Nuovo aggiornamento da Bailey!
+ %1$s ha nuovi messaggi]]>
+ %1$s, ha nuovi messaggi]]>
+ %1$s Punti Statistica non allocati]]>
+ Oggetti Misteriosi]]>
+ %1$s]]>
+ %1$s]]>
+ %1$s]]>
+ %1$s]]>
diff --git a/Habitica/res/values-ja/strings.xml b/Habitica/res/values-ja/strings.xml
index 8677e40c4..101768ca2 100644
--- a/Habitica/res/values-ja/strings.xml
+++ b/Habitica/res/values-ja/strings.xml
@@ -822,4 +822,17 @@
サーバー
プレゼントを贈りました!
探す
+ 通知
+ 全ての通知をチェックしました!
+ 通知の妖精はパチパチとあなたに拍手を送っています! えらい!
+ すべて閉じる
+ Baileyから新しいお知らせがあります!
+ %1$s に新しい投稿があります]]>
+ %1$s に新しい投稿があります]]>
+ %1$sポイントが割り当てできます。]]>
+ ミステリー アイテム]]>
+ %1$s」に招待されました。]]>
+ %1$s プライベートギルドに招待されました。]]>
+ %1$s ギルドに招待されました。]]>
+ %1$s クエストに招待されました。]]>
diff --git a/Habitica/res/values-nl/strings.xml b/Habitica/res/values-nl/strings.xml
index c27f94066..8a4b80fec 100644
--- a/Habitica/res/values-nl/strings.xml
+++ b/Habitica/res/values-nl/strings.xml
@@ -804,4 +804,17 @@
Verzend geschenk
server
Je geschenk is verzonden!
+ Meldingen
+ Je bent volledig klaar
+ De meldingsfeeën geven je een staande ovatie! Goed gedaan!
+ Allen afwijzen
+ Nieuwe update van Bailey
+ %1$s heeft nieuwe berichten]]>
+ %1$s, heeft nieuwe berichten]]>
+ %1$s niet toegekende statuspunten]]>
+ Mysterieuze items]]>
+ %1$s]]>
+ %1$s]]>
+ %1$s]]>
+ %1$s]]>
diff --git a/Habitica/res/values-pl/strings.xml b/Habitica/res/values-pl/strings.xml
index 8d5c0a113..37a5cea96 100644
--- a/Habitica/res/values-pl/strings.xml
+++ b/Habitica/res/values-pl/strings.xml
@@ -676,4 +676,17 @@
Ian Przewodnik Misji
Sezonowa Czarodziejka
Atak szału:
+ Powiadomienia
+ Teraz jesteś na bieżąco!
+ Wróżki powiadomień składają ci gromkie brawa. Dobra robota!
+ Odrzuć wszystko
+ Nowości od Bailey
+ %1$s ma nowe posty]]>
+ %1$s ma nowe posty]]>
+ nieprzydzielone Punkty Atrybutów: %1$s]]>
+ Tajemniczy Przedmiot]]>
+ %1$s]]>
+ %1$s]]>
+ %1$s]]>
+ %1$s]]>
diff --git a/Habitica/res/values-pt-rBR/strings.xml b/Habitica/res/values-pt-rBR/strings.xml
index a77527f4d..3747375db 100644
--- a/Habitica/res/values-pt-rBR/strings.xml
+++ b/Habitica/res/values-pt-rBR/strings.xml
@@ -821,4 +821,17 @@
Seu presente foi enviado!
Sua assinatura de presente foi enviada e uma outra assinatura foi aplicada à sua conta.
Sua assinatura de presente foi enviada e uma outra assinatura foi aplicada à sua conta.
+ Notificações
+ Você está com tudo em dia!
+ As fadas da notificação lhe deram uma rodada de aplausos barulhentos! Muito bem!
+ Dispensar Tudo
+ Nova atualização da Bailey!
+ %1$s tem novas publicações]]>
+ %1$s, tem novas publicações]]>
+ %1$s Pontos de Atributos não distribuidos]]>
+ Itens Surpresa]]>
+ %1$s]]>
+ %1$s]]>
+ %1$s]]>
+ %1$s]]>
diff --git a/Habitica/res/values-pt-rPT/strings.xml b/Habitica/res/values-pt-rPT/strings.xml
index ee250aeda..792fef950 100644
--- a/Habitica/res/values-pt-rPT/strings.xml
+++ b/Habitica/res/values-pt-rPT/strings.xml
@@ -494,4 +494,17 @@
Recolha de Missão
Recompensas do Dono de Missão
Bem-vindo à Pousada! Puxe uma cadeira para conversar, ou efetue uma pausa das suas tarefas.
+ Notificações
+ Já sabes tudo o que se passa!
+ As fadas das notificações dão-te uma ruidosa salva de palmas! Bom trabalho!
+ Dispensar Todos
+ Nova atualização de Bailey!
+ %1$s tem novas mensagens]]>
+ %1$s, tem novas mensagens]]>
+ %1$s Ponto(s) de Atributo por alocar]]>
+ Items Mistério novos]]>
+ %1$s]]>
+ %1$s]]>
+ %1$s]]>
+ %1$s]]>
diff --git a/Habitica/res/values-ru/strings.xml b/Habitica/res/values-ru/strings.xml
index ba4b230ff..c008c6498 100644
--- a/Habitica/res/values-ru/strings.xml
+++ b/Habitica/res/values-ru/strings.xml
@@ -798,4 +798,17 @@
ТЕЛ:
ИНТ:
ВОС:
+ Уведомления
+ Вы все вспомнили!
+ Феи уведомлений вам тихо рукоплещут! Отлично сработано!
+ Сбросить все уведомления
+ Обновление от Бэйли!
+ %1$s, есть новые сообщения]]>
+ %1$s есть новые сообщения]]>
+ %1$s очков]]>
+ Таинственный предмет]]>
+ %1$s]]>
+ %1$s]]>
+ %1$s]]>
+ %1$s]]>
diff --git a/Habitica/res/values-tr/strings.xml b/Habitica/res/values-tr/strings.xml
index 1087183cd..b607fde3c 100644
--- a/Habitica/res/values-tr/strings.xml
+++ b/Habitica/res/values-tr/strings.xml
@@ -808,4 +808,17 @@
websiteyi ziyaret et
Abonelik Hediye Et
Hediye Gönder
+ Bildirimler
+ Bildirimleri temizledin!
+ Bildirim perileri seni şevkle alkışlıyor! Bravo!
+ Okundu Olarak İşaretle
+ Yeni Bailey Güncellemesi!
+ %1$s adlı Loncada yeni mesajlar var]]>
+ %1$s adlı Takımında yeni mesajlar var]]>
+ %1$s adet dağıtılmamış Nitelik Puanın var.]]>
+ Gizemli Eşyaların var]]>
+ %1$s Takımına davet edildin]]>
+ %1$s adlı özel Loncaya davet edildin]]>
+ %1$s adlı Loncaya davet edildin]]>
+ %1$s Görevine davet edildin]]>
diff --git a/Habitica/res/values-zh-rTW/strings.xml b/Habitica/res/values-zh-rTW/strings.xml
index 2df1dd6ac..7a4d7d8a7 100644
--- a/Habitica/res/values-zh-rTW/strings.xml
+++ b/Habitica/res/values-zh-rTW/strings.xml
@@ -739,4 +739,17 @@
簡短的名稱
分配屬性點在10等時解鎖
管理員
+ 通知
+ 已查看所有新訊息!
+ 通知仙女給您一陣熱烈的掌聲!做得好!
+ 清除全部
+ Bailey的新信息!
+ %1$s有新的貼文]]>
+ %1$s有新的貼文]]>
+ %1$s點未分配屬性點]]>
+ 神秘裝備]]>
+ %1$s隊伍]]>
+ %1$s私人公會]]>
+ %1$s您受邀加入]]>
+ %1$s副本]]>
diff --git a/Habitica/res/values-zh/strings.xml b/Habitica/res/values-zh/strings.xml
index cfe7be5ea..e576b1325 100644
--- a/Habitica/res/values-zh/strings.xml
+++ b/Habitica/res/values-zh/strings.xml
@@ -729,4 +729,17 @@
今天完成任务了么?
升级后有许多可解锁和发现的,所以继续你的任务。祝玩得愉快!
双手握持
+ 通知
+ 您全部都完成了!
+ 小信使仙女们围着你欢快得鼓起了热烈的掌声!恭喜,做得太棒了!
+ 关闭全部
+ 新的贝利更新!
+ %1$s 有新消息]]>
+ %1$s 有新消息]]>
+ %1$s没分配的属性点]]>
+ 神秘物品]]>
+ %1$s]]>
+ %1$s]]>
+ %1$s]]>
+ %1$s]]>
diff --git a/Habitica/res/values/dimens.xml b/Habitica/res/values/dimens.xml
index 23538df1b..3f9de9a4f 100644
--- a/Habitica/res/values/dimens.xml
+++ b/Habitica/res/values/dimens.xml
@@ -139,6 +139,7 @@
8dp
4dp
38dp
+ 24dp
16sp
26dp
16dp
diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml
index d406a81bf..25925e927 100644
--- a/Habitica/res/values/strings.xml
+++ b/Habitica/res/values/strings.xml
@@ -867,6 +867,11 @@
%1$s, has new posts]]>
%1$s unallocated Stat Points]]>
Mystery Items]]>
+ %1$s]]>
+ %1$s]]>
+ %1$s]]>
+ %1$s]]>
+ Defeat
Create
Only leader can create Challenges
Create Party
diff --git a/Habitica/res/values/styles.xml b/Habitica/res/values/styles.xml
index 279b207ce..67b329f11 100644
--- a/Habitica/res/values/styles.xml
+++ b/Habitica/res/values/styles.xml
@@ -464,6 +464,14 @@
- ?textColorPrimaryDark
+
+
+
+
@@ -490,6 +502,10 @@
- @drawable/layout_rounded_bg_green
+
+
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/NotificationsManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/NotificationsManager.kt
index d1a232c5f..863e86f94 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/NotificationsManager.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/NotificationsManager.kt
@@ -39,7 +39,9 @@ class NotificationsManager (private val context: Context) {
}
fun getNotifications(): Flowable> {
- return this.notifications.toFlowable(BackpressureStrategy.LATEST)
+ return this.notifications
+ .startWith(emptyList())
+ .toFlowable(BackpressureStrategy.LATEST)
}
fun getNotification(id: String): Notification? {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/Notification.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/Notification.kt
index 0c3567243..3669e5413 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/Notification.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/Notification.kt
@@ -4,13 +4,19 @@ import com.habitrpg.android.habitica.models.notifications.*
class Notification {
enum class Type(val type: String) {
+ // Notification types coming from the server
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");
+ UNALLOCATED_STATS_POINTS("UNALLOCATED_STATS_POINTS"),
+
+ // Custom notification types (created by this app)
+ GUILD_INVITATION("GUILD_INVITATION"),
+ PARTY_INVITATION("PARTY_INVITATION"),
+ QUEST_INVITATION("QUEST_INVITATION");
}
var id: String = ""
@@ -28,6 +34,9 @@ class Notification {
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
+ Type.GUILD_INVITATION.type -> GuildInvitationData::class.java
+ Type.PARTY_INVITATION.type -> PartyInvitationData::class.java
+ Type.QUEST_INVITATION.type -> QuestInvitationData::class.java
else -> null
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/invitations/GuildInvite.java b/Habitica/src/main/java/com/habitrpg/android/habitica/models/invitations/GuildInvite.java
index b1695db84..361ebe6be 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/invitations/GuildInvite.java
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/invitations/GuildInvite.java
@@ -11,6 +11,8 @@ public class GuildInvite extends RealmObject {
@PrimaryKey
private String id;
+ private Boolean publicGuild;
+
/**
* @return The inviter
*/
@@ -52,4 +54,12 @@ public class GuildInvite extends RealmObject {
public void setId(String id) {
this.id = id;
}
+
+ public Boolean getPublicGuild() {
+ return publicGuild;
+ }
+
+ public void setPublicGuild(Boolean publicGuild) {
+ this.publicGuild = publicGuild;
+ }
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/invitations/Invitations.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/invitations/Invitations.kt
index 73cb59c45..0ea9e6cb7 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/invitations/Invitations.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/invitations/Invitations.kt
@@ -12,13 +12,9 @@ open class Invitations : RealmObject() {
var userId: String? = null
internal var user: User? = null
- /**
- * @return The party invite
- */
- /**
- * @param party The party
- */
+
var party: PartyInvite? = null
+ var parties: RealmList? = null
private var guilds: RealmList? = null
/**
@@ -38,10 +34,14 @@ open class Invitations : RealmObject() {
fun removeInvitation(groupID: String) {
if (party?.id == groupID) {
party = null
- } else {
- guilds?.removeAll {
- it.id == groupID
- }
+ }
+
+ guilds?.removeAll {
+ it.id == groupID
+ }
+
+ parties?.removeAll {
+ it.id == groupID
}
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/notifications/GuildInvitationData.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/notifications/GuildInvitationData.kt
new file mode 100644
index 000000000..67fc11c6a
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/notifications/GuildInvitationData.kt
@@ -0,0 +1,9 @@
+package com.habitrpg.android.habitica.models.notifications
+
+import com.habitrpg.android.habitica.models.invitations.GuildInvite
+
+open class GuildInvitationData : NotificationData {
+
+ var invitation: GuildInvite? = null
+
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/notifications/PartyInvitationData.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/notifications/PartyInvitationData.kt
new file mode 100644
index 000000000..894303781
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/notifications/PartyInvitationData.kt
@@ -0,0 +1,9 @@
+package com.habitrpg.android.habitica.models.notifications
+
+import com.habitrpg.android.habitica.models.invitations.PartyInvite
+
+open class PartyInvitationData : NotificationData {
+
+ var invitation: PartyInvite? = null
+
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/notifications/QuestInvitationData.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/notifications/QuestInvitationData.kt
new file mode 100644
index 000000000..0b8c4e015
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/notifications/QuestInvitationData.kt
@@ -0,0 +1,7 @@
+package com.habitrpg.android.habitica.models.notifications
+
+open class QuestInvitationData : NotificationData {
+
+ var questKey: String? = null
+
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt
index 84f6f9e0e..8f1aac526 100755
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt
@@ -399,6 +399,18 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction {
MainNavigationController
)
}
+
+ if (resultCode == NOTIFICATION_ACCEPT && data?.hasExtra("notificationId") == true) {
+ notificationsViewModel?.accept(
+ data.getStringExtra("notificationId")
+ )
+ }
+
+ if (resultCode == NOTIFICATION_REJECT && data?.hasExtra("notificationId") == true) {
+ notificationsViewModel?.reject(
+ data.getStringExtra("notificationId")
+ )
+ }
}
// region Events
@@ -747,5 +759,7 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction {
const val SELECT_CLASS_RESULT = 11
const val GEM_PURCHASE_REQUEST = 111
const val NOTIFICATION_CLICK = 222
+ const val NOTIFICATION_ACCEPT = 223
+ const val NOTIFICATION_REJECT = 224
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt
index 7659eb0ef..62c48b1fd 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt
@@ -4,26 +4,32 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.text.Html
+import android.text.TextUtils
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 android.widget.*
import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModelProviders
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.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.Notification
+import com.habitrpg.android.habitica.models.inventory.QuestContent
import com.habitrpg.android.habitica.models.notifications.*
+import com.habitrpg.android.habitica.ui.activities.MainActivity.Companion.NOTIFICATION_ACCEPT
import com.habitrpg.android.habitica.ui.activities.MainActivity.Companion.NOTIFICATION_CLICK
+import com.habitrpg.android.habitica.ui.activities.MainActivity.Companion.NOTIFICATION_REJECT
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel
import io.reactivex.functions.Consumer
import kotlinx.android.synthetic.main.activity_notifications.*
+import javax.inject.Inject
class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener {
+ @Inject
+ lateinit var inventoryRepository: InventoryRepository
+
lateinit var viewModel: NotificationsViewModel
lateinit var inflater: LayoutInflater
@@ -108,11 +114,15 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
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
+ Notification.Type.PARTY_INVITATION.type -> createPartyInvitationNotification(it)
+ Notification.Type.GUILD_INVITATION.type -> createGuildInvitationNotification(it)
+ Notification.Type.QUEST_INVITATION.type -> createQuestInvitationNotification(it)
else -> null
}
- notification_items.addView(item)
+ if (item != null) {
+ notification_items.addView(item)
+ }
}
}
@@ -132,7 +142,7 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
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(
+ return createDismissableNotificationItem(
notification,
fromHtml(getString(stringId, data?.group?.name))
)
@@ -142,7 +152,7 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
val data = notification.data as? NewStuffData
val text = fromHtml("" + getString(R.string.new_bailey_update) + "
" + data?.title)
- return createNotificationItem(
+ return createDismissableNotificationItem(
notification,
text,
R.drawable.notifications_bailey
@@ -152,7 +162,7 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
private fun createUnallocatedStatsNotification(notification: Notification): View? {
val data = notification.data as? UnallocatedPointsData
- return createNotificationItem(
+ return createDismissableNotificationItem(
notification,
fromHtml(getString(R.string.unallocated_stats_points, data?.points.toString())),
R.drawable.notification_stat_sparkles
@@ -160,7 +170,7 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
}
private fun createMysteryItemsNotification(notification: Notification): View? {
- return createNotificationItem(
+ return createDismissableNotificationItem(
notification,
fromHtml(getString(R.string.new_subscriber_item)),
R.drawable.notification_mystery_item
@@ -171,7 +181,7 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
val data = notification.data as? GroupTaskNeedsWorkData
val message = convertGroupMessageHtml(data?.message ?: "")
- return createNotificationItem(
+ return createDismissableNotificationItem(
notification,
fromHtml(message),
null,
@@ -183,7 +193,7 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
val data = notification.data as? GroupTaskApprovedData
val message = convertGroupMessageHtml(data?.message ?: "")
- return createNotificationItem(
+ return createDismissableNotificationItem(
notification,
fromHtml(message),
null,
@@ -204,7 +214,7 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
return message.replace(pattern, "strong")
}
- private fun createNotificationItem(
+ private fun createDismissableNotificationItem(
notification: Notification,
messageText: CharSequence,
imageResourceId: Int? = null,
@@ -239,6 +249,100 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
return item
}
+ private fun createPartyInvitationNotification(notification: Notification): View? {
+ val data = notification.data as? PartyInvitationData
+
+ return createActionableNotificationItem(
+ notification,
+ fromHtml(getString(R.string.invited_to_party_notification, data?.invitation?.name))
+ )
+ }
+
+ private fun createGuildInvitationNotification(notification: Notification): View? {
+ val data = notification.data as? GuildInvitationData
+ val stringId = if (data?.invitation?.publicGuild == false) R.string.invited_to_private_guild else R.string.invited_to_public_guild
+
+ return createActionableNotificationItem(
+ notification,
+ fromHtml(getString(stringId, data?.invitation?.name))
+ )
+ }
+
+ private fun createQuestInvitationNotification(notification: Notification): View? {
+ val data = notification.data as? QuestInvitationData
+
+ val view = createActionableNotificationItem(notification, "")
+
+ // hide view until we have loaded quest data and populated the values
+ view.visibility = View.GONE
+
+ compositeSubscription.add(inventoryRepository.getQuestContent(data?.questKey ?: "")
+ .firstElement()
+ .subscribe(Consumer {
+ updateQuestInvitationView(view, it)
+ }, RxErrorHandler.handleEmptyError()))
+
+ return view
+ }
+
+ private fun updateQuestInvitationView(view: View, questContent: QuestContent) {
+ val messageTextView = view.findViewById(R.id.message_text) as? TextView
+ messageTextView?.text = fromHtml(getString(R.string.invited_to_quest, questContent.text))
+
+ val questObjectiveLabelView = view.findViewById(R.id.quest_objective_label) as? TextView
+ val questObjectiveTextView = view.findViewById(R.id.quest_objective_text) as? TextView
+ val questDifficultyLabelView = view.findViewById(R.id.difficulty_label) as? TextView
+ questDifficultyLabelView?.text = getText(R.string.difficulty)
+ questDifficultyLabelView?.append(":")
+ val questDifficultyView = view.findViewById(R.id.quest_difficulty) as? RatingBar
+
+ if (questContent.isBossQuest) {
+ questObjectiveLabelView?.text = getString(R.string.defeat)
+ questObjectiveTextView?.text = questContent.boss?.name
+
+ questDifficultyView?.rating = questContent.boss?.str ?: 1f
+ } else {
+ questObjectiveLabelView?.text = getString(R.string.collect)
+ val collectionList = questContent.collect?.map { it.count.toString() + " " + it.text }
+ questObjectiveTextView?.text = TextUtils.join(", ", collectionList)
+
+ questDifficultyView?.rating = 1f
+ }
+
+ questObjectiveLabelView?.append(":")
+ val questDetailView = view.findViewById(R.id.quest_detail_view) as? View
+ questDetailView?.visibility = View.VISIBLE
+ view.visibility = View.VISIBLE
+ }
+
+ private fun createActionableNotificationItem(
+ notification: Notification,
+ messageText: CharSequence
+ ): View {
+ val item = inflater.inflate(R.layout.notification_item_actionable, notification_items, false)
+
+ val acceptButton = item.findViewById(R.id.accept_button) as? Button
+ acceptButton?.setOnClickListener {
+ val resultIntent = Intent()
+ resultIntent.putExtra("notificationId", notification.id)
+ setResult(NOTIFICATION_ACCEPT, resultIntent)
+ finish()
+ }
+
+ val rejectButton = item.findViewById(R.id.reject_button) as? Button
+ rejectButton?.setOnClickListener {
+ val resultIntent = Intent()
+ resultIntent.putExtra("notificationId", notification.id)
+ setResult(NOTIFICATION_REJECT, resultIntent)
+ finish()
+ }
+
+ val messageTextView = item.findViewById(R.id.message_text) as? TextView
+ messageTextView?.text = messageText
+
+ 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)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/NotificationsViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/NotificationsViewModel.kt
index 5392a83be..5e5575517 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/NotificationsViewModel.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/NotificationsViewModel.kt
@@ -3,15 +3,24 @@ package com.habitrpg.android.habitica.ui.viewmodels
import android.os.Bundle
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
+import com.habitrpg.android.habitica.data.SocialRepository
+import com.habitrpg.android.habitica.extensions.notNull
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.GuildInvitationData
import com.habitrpg.android.habitica.models.notifications.NewChatMessageData
+import com.habitrpg.android.habitica.models.notifications.PartyInvitationData
+import com.habitrpg.android.habitica.models.notifications.QuestInvitationData
import com.habitrpg.android.habitica.models.social.UserParty
+import com.habitrpg.android.habitica.models.user.User
+import io.reactivex.BackpressureStrategy
import io.reactivex.Flowable
import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.functions.BiFunction
import io.reactivex.functions.Consumer
+import io.reactivex.subjects.BehaviorSubject
import java.util.*
import javax.inject.Inject
@@ -19,9 +28,12 @@ open class NotificationsViewModel : BaseViewModel() {
@Inject
lateinit var notificationsManager: NotificationsManager
+ @Inject
+ lateinit var socialRepository: SocialRepository
+
/**
* A list of notification types handled by this component.
- * NOTE: Those not listed here won't be shown in the notification panel
+ * NOTE: Those not listed here won't be shown in the notification panel (except the custom ones)
*/
private val supportedNotificationTypes = listOf(
Notification.Type.NEW_STUFF.type,
@@ -32,28 +44,54 @@ open class NotificationsViewModel : BaseViewModel() {
Notification.Type.UNALLOCATED_STATS_POINTS.type
)
+ /**
+ * A list of notification types that are "actionable" (ones that have accept/reject buttons).
+ */
+ private val actionableNotificationTypes = listOf(
+ Notification.Type.GUILD_INVITATION.type,
+ Notification.Type.PARTY_INVITATION.type,
+ Notification.Type.QUEST_INVITATION.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
+ /**
+ * Custom notification types created by this class (from user data).
+ * Will be combined with the notifications coming from server.
+ */
+ private val customNotifications: BehaviorSubject>
+
override fun inject(component: UserComponent) {
component.inject(this)
}
init {
+ customNotifications = BehaviorSubject.create()
+ customNotifications.onNext(emptyList())
+
disposable.add(userRepository.getUser()
.subscribe(Consumer {
party = it.party
+ customNotifications.onNext(convertInvitationsToNotifications(it))
}, RxErrorHandler.handleEmptyError()))
}
fun getNotifications(): Flowable> {
- return notificationsManager.getNotifications()
+ val serverNotifications = notificationsManager.getNotifications()
.map { filterSupportedTypes(it) }
- .observeOn(AndroidSchedulers.mainThread())
+
+ return Flowable.combineLatest(
+ serverNotifications,
+ customNotifications.toFlowable(BackpressureStrategy.LATEST),
+ BiFunction, List, List> {
+ serverNotificationsList, customNotificationsList -> serverNotificationsList + customNotificationsList
+ }
+ ).observeOn(AndroidSchedulers.mainThread())
}
fun getNotificationCount(): Flowable {
@@ -64,7 +102,7 @@ open class NotificationsViewModel : BaseViewModel() {
fun allNotificationsSeen(): Flowable {
return getNotifications()
- .map { it.all { notification -> notification.seen == true } }
+ .map { it.all { notification -> notification.seen != false } }
.distinctUntilChanged()
}
@@ -87,6 +125,44 @@ open class NotificationsViewModel : BaseViewModel() {
return notifications.filter { supportedNotificationTypes.contains(it.type) }
}
+ private fun convertInvitationsToNotifications(user: User): List {
+ val notifications = arrayListOf()
+
+ notifications.addAll(user.invitations?.parties?.map {
+ val notification = Notification()
+ notification.id = "custom-party-invitation-" + it.id
+ notification.type = Notification.Type.PARTY_INVITATION.type
+ val data = PartyInvitationData()
+ data.invitation = it
+ notification.data = data
+ notification
+ } ?: emptyList())
+
+ notifications.addAll(user.invitations?.getGuilds()?.map {
+ val notification = Notification()
+ notification.id = "custom-guild-invitation-" + it.id
+ notification.type = Notification.Type.GUILD_INVITATION.type
+ val data = GuildInvitationData()
+ data.invitation = it
+ notification.data = data
+ notification
+ } ?: emptyList())
+
+ val quest = user.party?.quest
+ if (quest != null && quest.RSVPNeeded) {
+ val notification = Notification()
+ notification.id = "custom-quest-invitation-" + user.party?.id
+ notification.type = Notification.Type.QUEST_INVITATION.type
+ val data = QuestInvitationData()
+ data.questKey = quest.key
+ notification.data = data
+
+ notifications.add(notification)
+ }
+
+ return notifications
+ }
+
fun isPartyMessage(data: NewChatMessageData?): Boolean {
if (party == null || data?.group?.id == null) {
return false
@@ -95,26 +171,45 @@ open class NotificationsViewModel : BaseViewModel() {
return party?.id == data.group?.id
}
+ /**
+ * Is the given notification an "artificial" custom notification (created by this class)
+ * instead of one of the ones coming from server.
+ */
+ private fun isCustomNotification(notification: Notification): Boolean {
+ return notification.id.startsWith("custom-")
+ }
+
fun dismissNotification(notification: Notification) {
+ if (isCustomNotification(notification)) {
+ return
+ }
+
disposable.add(userRepository.readNotification(notification.id)
.subscribe(Consumer {}, RxErrorHandler.handleEmptyError()))
}
fun dismissAllNotifications(notifications: List) {
- if (notifications.isEmpty()) {
+ val dismissableIds = notifications
+ .filter { !isCustomNotification(it) }
+ .filter { !actionableNotificationTypes.contains(it.type) }
+ .map { it.id }
+
+ if (dismissableIds.isEmpty()) {
return
}
val notificationIds = HashMap>()
- notificationIds["notificationIds"] = notifications.map { notification -> notification.id }
+ notificationIds["notificationIds"] = dismissableIds
disposable.add(userRepository.readNotifications(notificationIds)
.subscribe(Consumer {}, RxErrorHandler.handleEmptyError()))
}
fun markNotificationsAsSeen(notifications: List) {
- val unseenIds = notifications.filter { notification -> notification.seen != true }
- .map { notification -> notification.id }
+ val unseenIds = notifications
+ .filter { !isCustomNotification(it) }
+ .filter { it.seen == false }
+ .map { it.id }
if (unseenIds.isEmpty()) {
return
@@ -155,4 +250,71 @@ open class NotificationsViewModel : BaseViewModel() {
}
}
+ fun accept(notificationId: String) {
+ val notification = customNotifications.value?.find { it.id == notificationId } ?: return
+
+ when (notification.type) {
+ Notification.Type.GUILD_INVITATION.type -> {
+ val data = notification.data as GuildInvitationData
+ acceptGroupInvitation(data.invitation?.id)
+ }
+ Notification.Type.PARTY_INVITATION.type -> {
+ val data = notification.data as PartyInvitationData
+ acceptGroupInvitation(data.invitation?.id)
+ }
+ Notification.Type.QUEST_INVITATION.type -> acceptQuestInvitation()
+ }
+ }
+
+ fun reject(notificationId: String) {
+ val notification = customNotifications.value?.find { it.id == notificationId } ?: return
+
+ when (notification.type) {
+ Notification.Type.GUILD_INVITATION.type -> {
+ val data = notification.data as GuildInvitationData
+ rejectGroupInvite(data.invitation?.id)
+ }
+ Notification.Type.PARTY_INVITATION.type -> {
+ val data = notification.data as PartyInvitationData
+ rejectGroupInvite(data.invitation?.id)
+ }
+ Notification.Type.QUEST_INVITATION.type -> rejectQuestInvitation()
+ }
+ }
+
+ fun acceptGroupInvitation(groupId: String?) {
+ groupId.notNull {
+ disposable.add(socialRepository.joinGroup(it)
+ .subscribe(Consumer {
+ refreshNotifications()
+ }, RxErrorHandler.handleEmptyError()))
+ }
+ }
+
+ fun rejectGroupInvite(groupId: String?) {
+ groupId.notNull {
+ disposable.add(socialRepository.rejectGroupInvite(it)
+ .subscribe(Consumer {
+ refreshNotifications()
+ }, RxErrorHandler.handleEmptyError()))
+ }
+ }
+
+ private fun acceptQuestInvitation() {
+ party?.id.notNull {
+ disposable.add(socialRepository.acceptQuest(null, it)
+ .subscribe(Consumer {
+ refreshNotifications()
+ }, RxErrorHandler.handleEmptyError()))
+ }
+ }
+
+ private fun rejectQuestInvitation() {
+ party?.id.notNull {
+ disposable.add(socialRepository.rejectQuest(null, it)
+ .subscribe(Consumer {
+ refreshNotifications()
+ }, RxErrorHandler.handleEmptyError()))
+ }
+ }
}