Minor improvements

This commit is contained in:
Phillip Thelen 2023-03-29 12:11:28 +02:00
parent d5a8e1fe42
commit fb17a97e14
97 changed files with 391 additions and 431 deletions

View file

@ -85,21 +85,14 @@
android:layout_marginTop="@dimen/spacing_large"/>
</LinearLayout>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:cardBackgroundColor="@color/window_background"
app:cardCornerRadius="16dp"
app:strokeWidth="0dp">
<LinearLayout
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingHorizontal="@dimen/spacing_sides"
android:paddingTop="@dimen/spacing_sides"
android:paddingBottom="60dp">
android:paddingBottom="60dp"
app:cardBackgroundColor="@color/window_background">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -171,10 +164,9 @@
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

View file

@ -3,6 +3,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:background="@drawable/layout_rounded_bg_window"
android:layout_marginHorizontal="12dp"
@ -35,13 +36,17 @@
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/offset_background"
android:background="@color/offset_background_30"
android:padding="8dp">
<com.habitrpg.android.habitica.ui.views.CurrencyView
android:id="@+id/currency_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="@color/text_quad"
tools:text="150"
style="@style/Body1"
android:textSize="15sp"
app:currency="gold" />
</FrameLayout>
</LinearLayout>

View file

@ -70,9 +70,10 @@
style="@style/Body1"
android:layout_marginBottom="1dp"/>
<TextView
android:id="@+id/change_class_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/equipment_class_locked"
tools:text="@string/unlock_gear_and_skills"
android:textColor="?textColorSecondary"
style="@style/Body2"/>
</LinearLayout>

View file

@ -69,8 +69,8 @@
</fragment>
<fragment
android:id="@+id/partyInvitationFragment"
android:name="com.habitrpg.android.habitica.ui.fragments.social.party.PartyInvitePagerFragment"
android:label="@string/sidebar_party">
android:name="com.habitrpg.android.habitica.ui.fragments.social.party.PartySeekingFragment"
android:label="@string/find_more_members">
</fragment>
<fragment
android:id="@+id/questDetailFragment"

View file

@ -253,7 +253,7 @@
<string name="opting_out_progress">Renunciando</string>
<string name="class_confirmation">¿Estás seguro de que quieres ser un %s\?</string>
<string name="class_changed">¡Ahora eres un %s!</string>
<string name="class_changed_description">¡Tienes nuevo Equipamiento de Combate predeterminado que puedes cambiar en Equipo!</string>
<string name="choose_class">Elegir Clase</string>
<string name="dialog_go_back">Volver Atrás</string>
<string name="opt_out_confirmation">¿Estás seguro de que quieres renunciar\?</string>
@ -905,4 +905,4 @@
<string name="next_hourglass">Siguiente Reloj de Arena</string>
<string name="quest_party_required_title">Tienes que formar parte de un Grupo antes de comenzar una misión</string>
<string name="reset_account_title">¿Estás seguro de que quieres reiniciar\?</string>
</resources>
</resources>

View file

@ -233,7 +233,7 @@
<string name="opt_out_class">Отказване</string>
<string name="class_confirmation">Наистина ли искате да бъдете %s?</string>
<string name="class_changed">Вие вече сте %s!</string>
<string name="class_changed_description">Вече имате ново бойно снаряжение по подразбиране, което можете да намерите в „Екипировка“!</string>
<string name="choose_class">Избиране на класа</string>
<string name="dialog_go_back">Назад</string>
<string name="opt_out_confirmation">Наистина ли искате да се откажете?</string>
@ -647,4 +647,4 @@
<string name="invited_to_private_guild"><![CDATA[Получихте покана за присъединяване към частната гилдия <b>%1$s</b>]]></string>
<string name="invited_to_public_guild"><![CDATA[Получихте покана за присъединяване към гилдията <b>%1$s</b>]]></string>
<string name="invited_to_quest"><![CDATA[Получихте покана за присъединяване в мисията <b>%1$s</b>]]></string>
</resources>
</resources>

View file

@ -183,7 +183,7 @@
<string name="opt_out_class">No optar per res</string>
<string name="class_confirmation">Segur que vols ser un %s?</string>
<string name="class_changed">Ara ets un %s!</string>
<string name="class_changed_description">Tens un nou Equip de Batalla per defecte que pots canviar a Equipament.</string>
<string name="choose_class">Escull la classe</string>
<string name="dialog_go_back">Torna enrere</string>
<string name="opt_out_confirmation">Estàs segur que no vols optar per res?</string>
@ -278,4 +278,4 @@
<string name="open">Obrir</string>
<string name="checkInRewardEarned">Has guanyat un/a %1$s com a recompensa per a la teva devoció per millorar la teva vida.</string>
<string name="nextPrizeUnlocks" tools:ignore="PluralsCandidate">El pròxim premi es desbloquejarà d\'aquí a %1$d inicis de sessió</string>
</resources>
</resources>

View file

@ -212,7 +212,7 @@
<string name="opt_out_class">Neúčastnit se</string>
<string name="class_confirmation">Opravdu chceš být %s?</string>
<string name="class_changed">Nyní jsi %s!</string>
<string name="class_changed_description">Máš novou výchozí Bojovou výzbroj, kterou si můžeš změnit ve Vybavení!</string>
<string name="choose_class">Vybrat třidu</string>
<string name="dialog_go_back">Jít zpět</string>
<string name="opt_out_confirmation">Jsi si jist, že se chceš neúčastnit?</string>
@ -494,4 +494,4 @@
<string name="challenge_create_error_enough_gems">Nemáte dostatek drahokamů k vytvoření výzvy.</string>
<string name="identify_your_challenge_with_a_tag">Dejte své výzvě krátké a výstižné jméno</string>
<string name="challenge_create_error_tag">K vytvoření této výzvy potřebujete zvolit jméno.</string>
</resources>
</resources>

View file

@ -232,7 +232,7 @@
<string name="opt_out_class">Später entscheiden</string>
<string name="class_confirmation">Bist Du sicher, dass Du %s werden willst?</string>
<string name="class_changed">Du bist jetzt ein/eine %s!</string>
<string name="class_changed_description">Du hast neue Basis-Kampfausrüstung, die Du im Ausrüstungsreiter wechseln kannst!</string>
<string name="choose_class">Klasse wählen</string>
<string name="dialog_go_back">Zurück</string>
<string name="opt_out_confirmation">Bist Du sicher, dass Du Dich noch nicht entscheiden willst?</string>
@ -1265,4 +1265,4 @@ Die Quest-Schriftrolle wird an den Quest-Besitzer zurückgegeben.</string>
<item quantity="one">Du</item>
<item quantity="other">Du und %d andere</item>
</plurals>
</resources>
</resources>

View file

@ -213,7 +213,7 @@
<string name="opt_out_class">Opt Out</string>
<string name="class_confirmation">Are you sure you want to be a %s?</string>
<string name="class_changed">You are now a %s!</string>
<string name="class_changed_description">You have new default Battle Gear that you can change under Equipment!</string>
<string name="choose_class">Choose Class</string>
<string name="dialog_go_back">Go Back</string>
<string name="opt_out_confirmation">Are you sure you want to Opt Out?</string>
@ -1053,4 +1053,4 @@
<string name="spooky_promo_info_instructions">Between October 29th and November 2nd, simply purchase any Gem bundle like usual and your account will be credited with the promotional amount of Gems. More Gems to spend, share, or save for any future releases!</string>
<string name="view_gem_bundles">View Gem Bundles</string>
<string name="fall_promo_info_prompt">The Fall Gala is in full swing so we thought it was the perfect time for a Gem Sale! Now you will get more Gems with each purchase than ever before.</string>
</resources>
</resources>

View file

@ -233,7 +233,7 @@
<string name="opt_out_class">No elegir</string>
<string name="class_confirmation">¿Seguro que quieres ser %s?</string>
<string name="class_changed">¡Ahora eres un %s!</string>
<string name="class_changed_description">¡Tienes un nuevo Equipo de Combate que puedes ponerte en tu Equipamiento!</string>
<string name="choose_class">Elegir clase</string>
<string name="dialog_go_back">Volver</string>
<string name="opt_out_confirmation">¿Seguro que no quieres elegir?</string>
@ -1142,4 +1142,4 @@
<string name="subscription_confirmation_multiple">Ahora estás suscrito por %s meses</string>
<string name="gem_purchase_confirmation">Ganaste %s gemas.</string>
<string name="usernames_party">Equipo de %s</string>
</resources>
</resources>

View file

@ -232,7 +232,7 @@
<string name="opt_out_class">Désactiver</string>
<string name="class_confirmation">Êtes vous sûr de vouloir devenir un %s \?</string>
<string name="class_changed">Vous êtes maintenant un %s !</string>
<string name="class_changed_description">Vous avez une nouvelle tenue de combat par défaut que vous pouvez changer dans l\'équipement !</string>
<string name="choose_class">Choisissez une classe</string>
<string name="dialog_go_back">Revenir</string>
<string name="opt_out_confirmation">Confirmez-vous vouloir désactiver votre compte ?</string>
@ -1263,4 +1263,4 @@
<item quantity="one">%d personne</item>
<item quantity="other">%d personnes</item>
</plurals>
</resources>
</resources>

View file

@ -232,7 +232,7 @@
<string name="opt_out_class">Izaberi kasnije</string>
<string name="class_confirmation">Jesi li siguran/na da želiš biti %s?</string>
<string name="class_changed">Sada si %s!</string>
<string name="class_changed_description">Imaš novu zadanu Ratnu Opremu koju možeš promijeniti pod Opremom!</string>
<string name="choose_class">Odaberi Klasu </string>
<string name="dialog_go_back">Idi natrag</string>
<string name="opt_out_confirmation">Jesi li siguran/na da želiš izabrati kasnije?</string>
@ -1140,4 +1140,4 @@
<string name="task_list_mode">Vrste popisa zadataka</string>
<string name="compact">Kompaktno</string>
<string name="minimal">Minimalno</string>
</resources>
</resources>

View file

@ -220,7 +220,7 @@
<string name="opt_out_class">Matikan Fitur</string>
<string name="class_confirmation">Apakah kamu yakin ingin menjadi seorang %s?</string>
<string name="class_changed">Kamu sekarang seorang %s! </string>
<string name="class_changed_description">Kamu memiliki Baju Perang baru yang dapat diganti pada menu Perlengkapan! </string>
<string name="choose_class">Pilih Kelas</string>
<string name="dialog_go_back">Kembali</string>
<string name="opt_out_confirmation">Apakah kamu yakin kamu ingin mematikan fitur?</string>
@ -959,4 +959,4 @@
<string name="armoireFood_new">Kamu mengobrak-abrik isi Peti dan menemukan makanan. Kenapa bisa ada di sini\?</string>
<string name="sell_no_price">Jual</string>
<string name="create_new_party">Buat party baru</string>
</resources>
</resources>

View file

@ -232,7 +232,7 @@
<string name="opt_out_class">Rinuncia</string>
<string name="class_confirmation">Vuoi davvero essere un %s?</string>
<string name="class_changed">Ora sei un %s!</string>
<string name="class_changed_description">Hai del nuovo equipaggiamento da battaglia che puoi indossare, vai nella sezione Equipaggiamento!</string>
<string name="choose_class">Scegli classe</string>
<string name="dialog_go_back">Ritorna</string>
<string name="opt_out_confirmation">Vuoi davvero rinunciare?</string>
@ -1260,4 +1260,4 @@
<item quantity="one">Tu</item>
<item quantity="other">Voi, %d altri</item>
</plurals>
</resources>
</resources>

View file

@ -232,7 +232,7 @@
<string name="opt_out_class">選ばない</string>
<string name="class_confirmation">%s になります。いいですか?</string>
<string name="class_changed">あなたは%s に転職した!</string>
<string name="class_changed_description">新たな標準の武装を手に入れました。「装備」で取り換えることもできます!</string>
<string name="choose_class">クラスを選ぶ</string>
<string name="dialog_go_back">戻る</string>
<string name="opt_out_confirmation">選ばないでいい?</string>
@ -1261,4 +1261,4 @@
<plurals name="you_x_others">
<item quantity="other">あなたと他 %d</item>
</plurals>
</resources>
</resources>

View file

@ -220,7 +220,7 @@
<string name="opt_out_class">선택 유예</string>
<string name="class_confirmation">%s 이(가) 되고싶은 것이 확실한가요\?</string>
<string name="class_changed">%s이(가) 되었습니다!</string>
<string name="class_changed_description">장비 메뉴에서 교체할 수 있는 새로운 기본 전투 장비를 얻었습니다!</string>
<string name="choose_class">직업 선택</string>
<string name="dialog_go_back">뒤로 가기</string>
<string name="opt_out_confirmation">선택을 유예하고 싶은 것이 확실한가요?</string>
@ -1259,4 +1259,4 @@
<string name="copy_shared_tasks">공유 중인 과제 복사</string>
<string name="group_plan_settings">그룹 계획 설정</string>
<string name="copy_tasks_description">할당된 과제와 열려 있는 과제를 개인 과제 목록에 표시</string>
</resources>
</resources>

View file

@ -292,7 +292,7 @@
<string name="warrior_description">Warriors surinko daugiau ir geresnių kritinių paspaudimų, kurie atsitiktinai suteikia premiją „Gold“, „Experience“ ir „drop“ šansams atlikti užduotį. Jie taip pat susiduria su sunkia boso monstrų žala. Žaisti Warrior, jei radote motyvaciją iš neprognozuojamų jackpoto stiliaus apdovanojimų, arba norėsite išvalyti skausmą bosas Quests!</string>
<string name="opting_out_progress">Atsisakymas</string>
<string name="class_changed">Dabar esate %s!</string>
<string name="class_changed_description">Turite naują numatytąjį „Battle Gear“, kurį galite keisti pagal „Įranga“!</string>
<string name="change_class_description">Pakeiskite savo klasę ir grąžinkite savo statistinius taškus už 3 brangakmenius.</string>
<string name="invite_username_description">Jei turite draugų, kurie jau naudojasi Habitica, pakvieskite juos pagal vartotojo vardą.</string>
<string name="share_levelup">Habiticoje pasiekiau %d lygį, gerindamas savo tikrojo gyvenimo įpročius!</string>
@ -352,4 +352,4 @@
<string name="subscribe_prompt_thanks">Užsisakydami gaunate šias naudingas išmokas:</string>
<string name="subscription_status">Prenumeratos būsena</string>
<string name="challenge_leave_title">Palikite iššūkį</string>
</resources>
</resources>

View file

@ -233,7 +233,7 @@
<string name="opt_out_class">Afmelden</string>
<string name="class_confirmation">Weet je zeker dat je een %s wilt zijn?</string>
<string name="class_changed">Je bent nu een %s!</string>
<string name="class_changed_description">Je hebt een nieuwe standaard gevechtsuitrusting die je kan veranderen onder Uitrusting!</string>
<string name="choose_class">Kies een Klasse</string>
<string name="dialog_go_back">Ga terug</string>
<string name="opt_out_confirmation">Weet je zeker dat je je wil afmelden?</string>
@ -994,4 +994,4 @@
<string name="hourglasses_description">Mystieke zandlopers zijn een zeldzaam soort betaalmiddel dat je alleen ontvangt bij een abonnement op Habitica dat minstens drie opeenvolgende maanden duurt. Je kunt er in de Tijdreiziger\'s winkel voorafgaande uitrustingssets, huisdieren, rijdieren, geanimeerde acthergronden of zelfs speciale queestes voor kopen.
\n
\nJe kunt maximaal vier mystieke zandlopers per jaar ontvangen. Het tijdstip dat je ze krijgt hangt af van wanneer je abonnement wordt herniewd. Ze worden uitgezonden op de eerste dag van een nieuwe maand nadat je laatste abonnementsbetaling je heeft gequalificeerd voor een zandloper. Zie de [Abonnementen]-pagina voor meer details.</string>
</resources>
</resources>

View file

@ -202,7 +202,7 @@
<string name="opt_out_class">Reserver Deg</string>
<string name="class_confirmation">Er du sikker på at du vil være en %s?</string>
<string name="class_changed">Du er nå en %s!</string>
<string name="class_changed_description">Du har nytt standard stridsutstyr som du kan bytte til under Utstyr!</string>
<string name="choose_class">Velg Klasse</string>
<string name="dialog_go_back">Gå Tilbake</string>
<string name="opt_out_confirmation">Er du sikker på at du vil reservere deg?</string>
@ -417,4 +417,4 @@
<string name="change_email">Endre e-post adresse</string>
<string name="change">Endre</string>
<string name="strength">Styrke</string>
</resources>
</resources>

View file

@ -223,7 +223,7 @@
<string name="opt_out_class">Wycofaj się</string>
<string name="class_confirmation">Jesteś pewien, że chcesz być %s?</string>
<string name="class_changed">Jesteś teraz %s!</string>
<string name="class_changed_description">Masz nowe domyślne wyposażenie bojowe, które możesz zmienić pod Wyposażeniem!</string>
<string name="choose_class">Wybierz klasę postaci</string>
<string name="dialog_go_back">Wróć</string>
<string name="opt_out_confirmation">Czy na pewno chcesz wyjść?</string>
@ -781,4 +781,4 @@
<string name="available_for">Dostępne dla %s</string>
<string name="unsaved_changes">Niezapisane zmiany</string>
<string name="discard_changes_to_task_message">Jesteś pewien, że chcesz porzucić zmiany w tym zadaniu\?</string>
</resources>
</resources>

View file

@ -232,7 +232,7 @@
<string name="opt_out_class">Decidir mais tarde</string>
<string name="class_confirmation">Você tem certeza que quer ser um(a) %s\?</string>
<string name="class_changed">Você é um(a) %s agora!</string>
<string name="class_changed_description">Você tem um novo Equipamento de Batalha padrão, que pode ser trocado em Equipamentos!</string>
<string name="choose_class">Escolha a sua Classe</string>
<string name="dialog_go_back">Voltar</string>
<string name="opt_out_confirmation">Você tem certeza de que ainda não quer escolher\?</string>
@ -1262,4 +1262,4 @@
<item quantity="one">Você</item>
<item quantity="other">Vocês, %d outros</item>
</plurals>
</resources>
</resources>

View file

@ -364,7 +364,7 @@
\n • Seus itens de missão de dano ou coleta de chefe permanecerão pendentes até o check-out</string>
<string name="warrior_description">Guerreiros obtêm mais Golpes Críticos e infligem maior dano aos Monstros. Joga como Guerreiro se quiseres derrotar Monstros mais facilmente! Guerreiros beneficiam de uma elevada estatística de Força.</string>
<string name="opting_out_progress">Optando por</string>
<string name="class_changed_description">Você tem um novo Battle Gear padrão que pode ser alterado em Equipamento!</string>
<string name="change_class_description">Mude sua classe e devolva seus pontos de status por 3 gemas.</string>
<string name="notifications">Notificações</string>
<string name="no_notifications_title">Já sabes tudo o que se passa!</string>
@ -718,4 +718,4 @@
<string name="use_item">Usar item em membro de equipa</string>&gt;
<string name="unsaved_changes">Alterações não gravadas</string>
<string name="discard_changes_to_task_message">Tens a certeza de que queres descartar as alterações nesta tarefa\?</string>
</resources>
</resources>

View file

@ -208,7 +208,7 @@
<string name="opt_out_class">Alege să nu iei parte</string>
<string name="class_confirmation">Ești sigur că vrei să fii %s?</string>
<string name="class_changed">De acum ești %s!</string>
<string name="class_changed_description">Acum ai Echipament de Luptă de bază, pe care îl poți schimba de la Echipament!</string>
<string name="choose_class">Alege Clasa</string>
<string name="dialog_go_back">Mergi înapoi</string>
<string name="opt_out_confirmation">Ești sigur că vrei să nu iei parte?</string>
@ -509,4 +509,4 @@
<string name="questShop_owner_long">Ian Ghidul Expedițiilor</string>
<string name="seasonalShop_owner_long">Vrăjitoarea Sezonului</string>
<string name="rage_attack">Atac furios:</string>
</resources>
</resources>

View file

@ -230,7 +230,7 @@
<string name="opt_out_class">Отказаться</string>
<string name="class_confirmation">Вы уверены, что хотите продолжить как %s\?</string>
<string name="class_changed">Теперь вы %s!</string>
<string name="class_changed_description">У вас есть новая боевая экипировка по умолчанию, которую вы можете сменить в разделе Снаряжение!</string>
<string name="choose_class">Выбрать класс</string>
<string name="dialog_go_back">Вернуться назад</string>
<string name="opt_out_confirmation">Вы уверены, что хотите отказаться?</string>
@ -1266,4 +1266,4 @@
<item quantity="many"></item>
<item quantity="other"></item>
</plurals>
</resources>
</resources>

View file

@ -202,7 +202,7 @@
<string name="opt_out_class">Välj bort</string>
<string name="class_confirmation">Är du säker på att du vill vara en %s?</string>
<string name="class_changed">Du är nu en %s!</string>
<string name="class_changed_description">Du har ny standard Stridsutrustning som du kan ändra under Utrustning!</string>
<string name="choose_class">Välj klass</string>
<string name="dialog_go_back">Gå tillbaka</string>
<string name="opt_out_confirmation">Är du säker på att du vill välja bort?</string>
@ -634,4 +634,4 @@
<string name="owner">Ägar</string>
<string name="stat_allocation">Egenskapsfördelning</string>
<string name="experience_points">Erfarenhetspoäng</string>
</resources>
</resources>

View file

@ -233,7 +233,7 @@
<string name="opt_out_class">Çekil</string>
<string name="class_confirmation">Bir %s olmak istediğine emin misin?</string>
<string name="class_changed">Yeni sınıfın: %s!</string>
<string name="class_changed_description">Ekipmanlar menüsü altında değiştirebileceğin yeni varsayılan Savaş Ekipmanın var!</string>
<string name="choose_class">Sınıf Seç</string>
<string name="dialog_go_back">Geri Dön</string>
<string name="opt_out_confirmation">Çekilmek istediğinden emin misin?</string>
@ -1151,4 +1151,4 @@
<string name="won_achievement_description_noname">Kazanan olarak seçildiniz! Zaferiniz Başarımlarınıza kaydedildi.</string>
<string name="rejoin_party">Davet edilmedikçe bu Takıma tekrar katılamazsınız.</string>
<string name="you_won_challenge">Bir Meydan Okuma kazandınız</string>
</resources>
</resources>

View file

@ -296,7 +296,7 @@
<string name="opt_out_confirmation">Ви дійсно хочете відмовитись\?</string>
<string name="dialog_go_back">Назад</string>
<string name="choose_class">Виберіть клас</string>
<string name="class_changed_description">У вас є нове бойове спорядження за замовчуванням, яке ви можете змінити в розділі Спорядження!</string>
<string name="class_changed">Тепер ви - %s!</string>
<string name="class_confirmation">Ви впевнені, що хочете бути %s\?</string>
<string name="opting_out_progress">Не приймати участь</string>
@ -1190,4 +1190,4 @@
<string name="watch_ad_to_open">Перегляньте рекламу, щоб відкрити знову</string>
<string name="enchanted_armoire_drop_rates">Шанси випадіння з чарівної скрині</string>
<string name="watch_ad_to_revive">Перегляньте рекламу, щоб відродитись</string>
</resources>
</resources>

View file

@ -288,7 +288,7 @@
<string name="change_class_confirmation">Bạn có chắc là muốn thay đổi chức nghiệp của mình không\? Điều này sẽ tốn 3 gem đấy.</string>
<string name="open_in_store">Mở trong cửa hàng</string>
<string name="change_class_description">Thay đổi chức nghiệp của bạn và hoàn lại điểm chỉ số với 3 gem.</string>
<string name="class_changed_description">Bạn có Trang bị Chiến đấu mặc định mới mà bạn có thể thay đổi ở dưới phần Trang bị!</string>
<string name="setup_task_teams_2">Cập nhập đội trên Trạng thái</string>
<string name="all_dailies_completed">Bạn đã hoàn thành tất cả Việc hằng ngày của mình. Giỏi lắm!</string>
<string name="add_reward">Thêm Phần thưởng</string>
@ -1076,4 +1076,4 @@
<string name="dark_mode">Chế độ Ban đêm</string>
<string name="user_is_blocked_explanation">Một người dùng bị chặn không thể gửi bạn Tin nhắn Riêng tư nhưng bạn vẫn sẽ thấy bài đăng của họ ở Quán rượu hay Bang hội.</string>
<string name="finish">Hoàn thành</string>
</resources>
</resources>

View file

@ -229,7 +229,7 @@
<string name="opt_out_class">退出</string>
<string name="class_confirmation">你確定想要成為%1$s嗎</string>
<string name="class_changed">你成為了%s</string>
<string name="class_changed_description">你有新的默認的戰鬥裝備,你可以在裝備裡替換!</string>
<string name="choose_class">選擇職業</string>
<string name="dialog_go_back">返回</string>
<string name="opt_out_confirmation">你確定要退出嗎?</string>
@ -1229,4 +1229,4 @@
<string name="delete_account_subscription_active">在訂閱時,你無法刪除你的帳號。請先取消訂閱。</string>
<string name="cds_subtitle">調整你的日子什麼時候切換到默認的午夜。</string>
<string name="death_description">但是你可以通過你的努力把它們贏回來!加油--你可以的。</string>
</resources>
</resources>

View file

@ -233,7 +233,7 @@
<string name="opt_out_class">退出</string>
<string name="class_confirmation">你确定想要成为%s吗</string>
<string name="class_changed">你现在是 %s级了</string>
<string name="class_changed_description">你有新的默认战斗装备,你可以在装备栏换上它们!</string>
<string name="choose_class">选择职业</string>
<string name="dialog_go_back">返回</string>
<string name="opt_out_confirmation">你确定要退出吗?</string>
@ -1262,4 +1262,4 @@
<plurals name="people">
<item quantity="other">%d人</item>
</plurals>
</resources>
</resources>

View file

@ -291,9 +291,9 @@
<string name="opt_out_class">Opt Out</string>
<string name="opting_out_progress">Opting Out</string>
<string name="class_confirmation">Are you sure you want to be a %s?</string>
<string name="class_confirmation_price">Do you want to change your class to %s for %d gems?</string>
<string name="class_confirmation_price">Do you want to change your class to %s for %d Gems?</string>
<string name="class_changed">You are now a %s!</string>
<string name="class_changed_description">You have new default Battle Gear that you can change under Equipment!</string>
<string name="class_changed_description">You can use %s skills and purchase gear from shops!</string>
<string name="choose_class">Choose Class</string>
<string name="dialog_go_back">Go Back</string>
<string name="opt_out_confirmation">Are you sure you want to Opt Out?</string>
@ -1381,6 +1381,7 @@
<string name="change_class_to_x">Change class to %s</string>
<string name="shop_armoire_title">You own all %s gear</string>
<string name="shop_armoire_description">New gear is released during the seasonal Galas. Until then, theres %d pieces of gear in the Enchanted Armoire to find!</string>
<string name="unlock_gear_and_skills">Unlock %s gear and skills</string>
<plurals name="you_x_others">
<item quantity="zero">You</item>

View file

@ -4,21 +4,12 @@ import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.launchActivity
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.ui.fragments.NavigationDrawerFragment
import com.habitrpg.android.habitica.ui.fragments.tasks.TaskRecyclerViewFragment
import com.habitrpg.android.habitica.ui.fragments.tasks.TasksFragment
import com.habitrpg.android.habitica.ui.viewmodels.MainActivityViewModel
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel
import io.github.kakaocup.kakao.common.views.KView
import io.github.kakaocup.kakao.screen.Screen
import io.github.kakaocup.kakao.text.KButton
import io.github.kakaocup.kakao.toolbar.KToolbar
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
import org.junit.After
import org.junit.Before
import org.junit.Test
@ -43,16 +34,6 @@ class MainActivityTest : ActivityTestCase() {
@Before
fun setup() {
every { hostConfig.hasAuthentication() } returns true
val mockComponent: UserComponent = mockk(relaxed = true)
every { mockComponent.inject(any<MainActivity>()) } answers { initializeInjects(this.args.first()) }
every { mockComponent.inject(any<MainActivityViewModel>()) } answers { initializeInjects(this.args.first()) }
every { mockComponent.inject(any<NotificationsViewModel>()) } answers { initializeInjects(this.args.first()) }
every { mockComponent.inject(any<TasksFragment>()) } answers { initializeInjects(this.args.first()) }
every { mockComponent.inject(any<TaskRecyclerViewFragment>()) } answers { initializeInjects(this.args.first()) }
every { mockComponent.inject(any<NavigationDrawerFragment>()) } answers { initializeInjects(this.args.first()) }
every { mockComponent.inject(any<TaskFormActivity>()) } answers { initializeInjects(this.args.first()) }
mockkObject(HabiticaBaseApplication)
every { HabiticaBaseApplication.userComponent } returns mockComponent
}
@Test

View file

@ -10,7 +10,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.shared.habitica.models.tasks.Frequency
import com.habitrpg.shared.habitica.models.tasks.TaskType
@ -65,10 +64,7 @@ class TaskFormActivityTest : ActivityTestCase() {
@Before
fun setup() {
every { sharedPreferences.getString("FirstDayOfTheWeek", any()) } returns "-1"
val mockComponent: UserComponent = mockk(relaxed = true)
every { mockComponent.inject(any<TaskFormActivity>()) } answers { initializeInjects(this.args.first()) }
mockkObject(HabiticaBaseApplication)
every { HabiticaBaseApplication.userComponent } returns mockComponent
}
private fun hasBasicTaskEditingViews() {

View file

@ -8,7 +8,6 @@ import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.databinding.FragmentRecyclerviewBinding
import com.habitrpg.android.habitica.interactors.HatchPetUseCase
import com.habitrpg.android.habitica.models.inventory.Food
@ -78,11 +77,6 @@ internal class ItemRecyclerFragmentTest : FragmentTestCase<ItemRecyclerFragment,
}
fragment = spyk()
fragment.shouldInitializeComponent = false
val mockComponent: UserComponent = mockk(relaxed = true)
every { mockComponent.inject(any<ItemDialogFragment>()) } answers { initializeInjects(this.args.first()) }
mockkObject(HabiticaBaseApplication)
every { HabiticaBaseApplication.userComponent } returns mockComponent
}
override fun launchFragment(args: Bundle?) {

View file

@ -4,7 +4,6 @@ import android.os.Bundle
import androidx.fragment.app.testing.launchFragmentInContainer
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.databinding.FragmentRecyclerviewBinding
import com.habitrpg.android.habitica.interactors.FeedPetUseCase
import com.habitrpg.android.habitica.models.user.OwnedItem
@ -52,11 +51,6 @@ internal class PetDetailRecyclerFragmentTest :
fragment = spyk()
fragment.shouldInitializeComponent = false
val mockComponent: UserComponent = mockk(relaxed = true)
every { mockComponent.inject(any<ItemDialogFragment>()) } answers { initializeInjects(this.args.first()) }
mockkObject(HabiticaBaseApplication)
every { HabiticaBaseApplication.userComponent } returns mockComponent
}
override fun launchFragment(args: Bundle?) {

View file

@ -1,8 +1,4 @@
package com.habitrpg.android.habitica;
import com.habitrpg.android.habitica.components.AppComponent;
import com.habitrpg.android.habitica.components.DaggerAppComponent;
import com.habitrpg.android.habitica.modules.AppModule;
public class HabiticaApplication extends HabiticaBaseApplication {
}

View file

@ -27,6 +27,7 @@ import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.helpers.AdHandler
import com.habitrpg.android.habitica.helpers.AmplitudeManager
import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager
import com.habitrpg.android.habitica.modules.AuthenticationHandler
import com.habitrpg.android.habitica.ui.activities.BaseActivity
import com.habitrpg.android.habitica.ui.activities.LoginActivity
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
@ -53,6 +54,8 @@ abstract class HabiticaBaseApplication : Application(), Application.ActivityLife
internal lateinit var analyticsManager: AnalyticsManager
@Inject
internal lateinit var pushNotificationManager: PushNotificationManager
@Inject
internal lateinit var authenticationHandler: AuthenticationHandler
/**
* For better performance billing class should be used as singleton
*/

View file

@ -1,19 +0,0 @@
package com.habitrpg.android.habitica.components
import com.habitrpg.android.habitica.modules.ApiModule
import com.habitrpg.android.habitica.modules.AppModule
import com.habitrpg.android.habitica.modules.DeveloperModule
import com.habitrpg.android.habitica.modules.RepositoryModule
import com.habitrpg.android.habitica.modules.UserModule
import com.habitrpg.android.habitica.modules.UserRepositoryModule
import dagger.Component
import javax.inject.Singleton
@Singleton
@Component(modules = [DeveloperModule::class, AppModule::class, ApiModule::class, RepositoryModule::class])
interface AppComponent {
fun plus(
userModule : UserModule?,
userRepositoryModule : UserRepositoryModule?
) : UserComponent?
}

View file

@ -1,13 +0,0 @@
package com.habitrpg.android.habitica.components
import com.habitrpg.android.habitica.modules.UserModule
import com.habitrpg.android.habitica.modules.UserRepositoryModule
import dagger.Subcomponent
import dagger.hilt.android.scopes.ActivityRetainedScoped
import dagger.hilt.android.scopes.ServiceScoped
@ActivityRetainedScoped
@ServiceScoped
@Subcomponent(modules = [UserModule::class, UserRepositoryModule::class])
interface UserComponent {
}

View file

@ -57,8 +57,7 @@ interface TaskRepository : BaseRepository {
fun createTaskInBackground(task: Task, assignChanges: Map<String, MutableList<String>>)
fun getTaskCopies(userId: String): Flow<List<Task>>
fun getTaskCopies(): Flow<List<Task>>
fun getTaskCopies(tasks: List<Task>): List<Task>
suspend fun retrieveDailiesFromDate(date: Date): TaskList?

View file

@ -4,13 +4,17 @@ import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.BaseRepository
import com.habitrpg.android.habitica.data.local.BaseLocalRepository
import com.habitrpg.android.habitica.models.BaseObject
import com.habitrpg.android.habitica.modules.AuthenticationHandler
abstract class BaseRepositoryImpl<T : BaseLocalRepository>(
protected val localRepository: T,
protected val apiClient: ApiClient,
protected val userID: String = ""
protected val authenticationHandler : AuthenticationHandler
) : BaseRepository {
val currentUserID : String
get() = authenticationHandler.currentUserID ?: ""
override fun close() {
this.localRepository.close()
}

View file

@ -8,6 +8,7 @@ import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.social.ChallengeMembership
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.tasks.TaskList
import com.habitrpg.android.habitica.modules.AuthenticationHandler
import com.habitrpg.shared.habitica.models.tasks.TaskType
import com.habitrpg.shared.habitica.models.tasks.TasksOrder
import kotlinx.coroutines.flow.Flow
@ -15,19 +16,19 @@ import kotlinx.coroutines.flow.Flow
class ChallengeRepositoryImpl(
localRepository: ChallengeLocalRepository,
apiClient: ApiClient,
userID: String
) : BaseRepositoryImpl<ChallengeLocalRepository>(localRepository, apiClient, userID), ChallengeRepository {
authenticationHandler: AuthenticationHandler
) : BaseRepositoryImpl<ChallengeLocalRepository>(localRepository, apiClient, authenticationHandler), ChallengeRepository {
override fun isChallengeMember(challengeID: String): Flow<Boolean> {
return localRepository.isChallengeMember(userID, challengeID)
return localRepository.isChallengeMember(currentUserID, challengeID)
}
override fun getChallengepMembership(id: String): Flow<ChallengeMembership> {
return localRepository.getChallengeMembership(userID, id)
return localRepository.getChallengeMembership(currentUserID, id)
}
override fun getChallengeMemberships(): Flow<List<ChallengeMembership>> {
return localRepository.getChallengeMemberships(userID)
return localRepository.getChallengeMemberships(currentUserID)
}
override fun getChallenge(challengeId: String): Flow<Challenge> {
@ -130,26 +131,26 @@ class ChallengeRepositoryImpl(
}
override fun getUserChallenges(userId: String?): Flow<List<Challenge>> {
return localRepository.getUserChallenges(userId ?: userID)
return localRepository.getUserChallenges(userId ?: currentUserID)
}
override suspend fun retrieveChallenges(page: Int, memberOnly: Boolean): List<Challenge>? {
val challenges = apiClient.getUserChallenges(page, memberOnly)
if (challenges != null) {
localRepository.saveChallenges(challenges, page == 0, memberOnly, userID)
localRepository.saveChallenges(challenges, page == 0, memberOnly, currentUserID)
}
return challenges
}
override suspend fun leaveChallenge(challenge: Challenge, keepTasks: String): Void? {
apiClient.leaveChallenge(challenge.id ?: "", LeaveChallengeBody(keepTasks))
localRepository.setParticipating(userID, challenge.id ?: "", false)
localRepository.setParticipating(currentUserID, challenge.id ?: "", false)
return null
}
override suspend fun joinChallenge(challenge: Challenge): Challenge? {
val returnedChallenge = apiClient.joinChallenge(challenge.id ?: "") ?: return null
localRepository.setParticipating(userID, returnedChallenge.id ?: "", true)
localRepository.setParticipating(currentUserID, returnedChallenge.id ?: "", true)
return returnedChallenge
}
}

View file

@ -8,6 +8,7 @@ import com.habitrpg.android.habitica.helpers.AprilFoolsHandler
import com.habitrpg.android.habitica.models.ContentResult
import com.habitrpg.android.habitica.models.WorldState
import com.habitrpg.android.habitica.models.inventory.SpecialItem
import com.habitrpg.android.habitica.modules.AuthenticationHandler
import io.realm.RealmList
import kotlinx.coroutines.flow.Flow
import java.util.Date
@ -15,8 +16,9 @@ import java.util.Date
class ContentRepositoryImpl<T : ContentLocalRepository>(
localRepository: T,
apiClient: ApiClient,
context: Context
) : BaseRepositoryImpl<T>(localRepository, apiClient), ContentRepository {
context: Context,
authenticationHandler: AuthenticationHandler
) : BaseRepositoryImpl<T>(localRepository, apiClient, authenticationHandler), ContentRepository {
private val mysteryItem = SpecialItem.makeMysteryItem(context)

View file

@ -4,13 +4,14 @@ import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.CustomizationRepository
import com.habitrpg.android.habitica.data.local.CustomizationLocalRepository
import com.habitrpg.android.habitica.models.inventory.Customization
import com.habitrpg.android.habitica.modules.AuthenticationHandler
import kotlinx.coroutines.flow.Flow
class CustomizationRepositoryImpl(
localRepository: CustomizationLocalRepository,
apiClient: ApiClient,
userID: String
) : BaseRepositoryImpl<CustomizationLocalRepository>(localRepository, apiClient, userID), CustomizationRepository {
authenticationHandler: AuthenticationHandler
) : BaseRepositoryImpl<CustomizationLocalRepository>(localRepository, apiClient, authenticationHandler), CustomizationRepository {
override fun getCustomizations(type: String, category: String?, onlyAvailable: Boolean): Flow<List<Customization>> {
return localRepository.getCustomizations(type, category, onlyAvailable)

View file

@ -4,14 +4,20 @@ import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.FAQRepository
import com.habitrpg.android.habitica.data.local.FAQLocalRepository
import com.habitrpg.android.habitica.models.FAQArticle
import com.habitrpg.android.habitica.modules.AuthenticationHandler
import kotlinx.coroutines.flow.Flow
class FAQRepositoryImpl(localRepository: FAQLocalRepository, apiClient: ApiClient, userID: String) : BaseRepositoryImpl<FAQLocalRepository>(localRepository, apiClient, userID), FAQRepository {
override fun getArticle(position: Int): Flow<FAQArticle> {
class FAQRepositoryImpl(
localRepository : FAQLocalRepository,
apiClient : ApiClient,
authenticationHandler : AuthenticationHandler
) : BaseRepositoryImpl<FAQLocalRepository>(localRepository, apiClient, authenticationHandler),
FAQRepository {
override fun getArticle(position : Int) : Flow<FAQArticle> {
return localRepository.getArticle(position)
}
override fun getArticles(): Flow<List<FAQArticle>> {
override fun getArticles() : Flow<List<FAQArticle>> {
return localRepository.articles
}
}

View file

@ -1,3 +1,5 @@
@file:OptIn(ExperimentalCoroutinesApi::class)
package com.habitrpg.android.habitica.data.implementation
import com.habitrpg.android.habitica.data.ApiClient
@ -21,16 +23,19 @@ import com.habitrpg.android.habitica.models.user.OwnedItem
import com.habitrpg.android.habitica.models.user.OwnedMount
import com.habitrpg.android.habitica.models.user.OwnedPet
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.modules.AuthenticationHandler
import com.habitrpg.shared.habitica.models.responses.FeedResponse
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flatMapLatest
class InventoryRepositoryImpl(
localRepository: InventoryLocalRepository,
apiClient: ApiClient,
userID: String,
authenticationHandler: AuthenticationHandler,
var appConfigManager: AppConfigManager
) : BaseRepositoryImpl<InventoryLocalRepository>(localRepository, apiClient, userID), InventoryRepository {
) : BaseRepositoryImpl<InventoryLocalRepository>(localRepository, apiClient, authenticationHandler), InventoryRepository {
override fun getQuestContent(keys: List<String>) = localRepository.getQuestContent(keys)
override fun getQuestContent(key: String) = localRepository.getQuestContent(key)
@ -72,11 +77,11 @@ class InventoryRepositoryImpl(
}
override fun getOwnedItems(itemType: String, includeZero: Boolean): Flow<List<OwnedItem>> {
return localRepository.getOwnedItems(itemType, userID, includeZero)
return authenticationHandler.userIDFlow.flatMapLatest { localRepository.getOwnedItems(itemType, it, includeZero) }
}
override fun getOwnedItems(includeZero: Boolean): Flow<Map<String, OwnedItem>> {
return localRepository.getOwnedItems(userID, includeZero)
return authenticationHandler.userIDFlow.flatMapLatest { localRepository.getOwnedItems(it, includeZero) }
}
override fun getItems(itemClass: Class<out Item>, keys: Array<String>): Flow<List<Item>> {
@ -115,7 +120,7 @@ class InventoryRepositoryImpl(
}
override fun getOwnedMounts(): Flow<List<OwnedMount>> {
return localRepository.getOwnedMounts(userID)
return authenticationHandler.userIDFlow.flatMapLatest { localRepository.getOwnedMounts(it) }
}
override fun getPets(): Flow<List<Pet>> {
@ -127,7 +132,7 @@ class InventoryRepositoryImpl(
}
override fun getOwnedPets(): Flow<List<OwnedPet>> {
return localRepository.getOwnedPets(userID)
return authenticationHandler.userIDFlow.flatMapLatest { localRepository.getOwnedPets(it) }
}
override fun updateOwnedEquipment(user: User) {
@ -135,11 +140,11 @@ class InventoryRepositoryImpl(
}
override suspend fun changeOwnedCount(type: String, key: String, amountToAdd: Int) {
localRepository.changeOwnedCount(type, key, userID, amountToAdd)
localRepository.changeOwnedCount(type, key, currentUserID, amountToAdd)
}
override suspend fun sellItem(type: String, key: String): User? {
val item = localRepository.getOwnedItem(userID, type, key, true).firstOrNull() ?: return null
val item = localRepository.getOwnedItem(currentUserID, type, key, true).firstOrNull() ?: return null
return sellItem(item)
}
@ -162,7 +167,7 @@ class InventoryRepositoryImpl(
liveItem?.numberOwned = (liveItem?.numberOwned ?: 0) - 1
}
val user = apiClient.sellItem(item.type, item.key) ?: return null
return localRepository.soldItem(userID, user)
return localRepository.soldItem(currentUserID, user)
}
override suspend fun equipGear(equipment: String, asCostume: Boolean): Items? {
@ -170,7 +175,7 @@ class InventoryRepositoryImpl(
}
override suspend fun equip(type: String, key: String): Items? {
val liveUser = localRepository.getLiveUser(userID)
val liveUser = localRepository.getLiveUser(currentUserID)
if (liveUser != null) {
localRepository.modify(liveUser) { user ->
@ -214,17 +219,17 @@ class InventoryRepositoryImpl(
override suspend fun feedPet(pet: Pet, food: Food): FeedResponse? {
val feedResponse = apiClient.feedPet(pet.key ?: "", food.key) ?: return null
localRepository.feedPet(food.key, pet.key ?: "", feedResponse.value ?: 0, userID)
localRepository.feedPet(food.key, pet.key ?: "", feedResponse.value ?: 0, currentUserID)
return feedResponse
}
override suspend fun hatchPet(egg: Egg, hatchingPotion: HatchingPotion, successFunction: () -> Unit): Items? {
if (appConfigManager.enableLocalChanges()) {
localRepository.hatchPet(egg.key, hatchingPotion.key, userID)
localRepository.hatchPet(egg.key, hatchingPotion.key, currentUserID)
successFunction()
}
val items = apiClient.hatchPet(egg.key, hatchingPotion.key) ?: return null
localRepository.save(items, userID)
localRepository.save(items, currentUserID)
if (!appConfigManager.enableLocalChanges()) {
successFunction()
}
@ -233,13 +238,13 @@ class InventoryRepositoryImpl(
override suspend fun inviteToQuest(quest: QuestContent): Quest? {
val newQuest = apiClient.inviteToQuest("party", quest.key)
localRepository.changeOwnedCount("quests", quest.key, userID, -1)
localRepository.changeOwnedCount("quests", quest.key, currentUserID, -1)
return newQuest
}
override suspend fun buyItem(user: User?, id: String, value: Double, purchaseQuantity: Int): BuyResponse? {
val buyResponse = apiClient.buyItem(id, purchaseQuantity) ?: return null
val foundUser = user ?: localRepository.getLiveUser(userID) ?: return buyResponse
val foundUser = user ?: localRepository.getLiveUser(currentUserID) ?: return buyResponse
val copiedUser = localRepository.getUnmanagedCopy(foundUser)
if (buyResponse.items != null) {
copiedUser.items = buyResponse.items

View file

@ -1,3 +1,5 @@
@file:OptIn(ExperimentalCoroutinesApi::class)
package com.habitrpg.android.habitica.data.implementation
import com.habitrpg.android.habitica.BuildConfig
@ -14,17 +16,20 @@ import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.social.GroupMembership
import com.habitrpg.android.habitica.models.social.InboxConversation
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.modules.AuthenticationHandler
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flatMapLatest
import java.util.UUID
class SocialRepositoryImpl(
localRepository: SocialLocalRepository,
apiClient: ApiClient,
userID: String
) : BaseRepositoryImpl<SocialLocalRepository>(localRepository, apiClient, userID), SocialRepository {
authenticationHandler: AuthenticationHandler
) : BaseRepositoryImpl<SocialLocalRepository>(localRepository, apiClient, authenticationHandler), SocialRepository {
override suspend fun transferGroupOwnership(groupID: String, userID: String): Group? {
val group = localRepository.getGroup(groupID).first()?.let { localRepository.getUnmanagedCopy(it) }
group?.leaderID = userID
@ -52,10 +57,10 @@ class SocialRepositoryImpl(
return apiClient.retrievePartySeekingUsers(page)
}
override fun getGroupMembership(id: String) = localRepository.getGroupMembership(userID, id)
override fun getGroupMembership(id: String) = authenticationHandler.userIDFlow.flatMapLatest { localRepository.getGroupMembership(it, id) }
override fun getGroupMemberships(): Flow<List<GroupMembership>> {
return localRepository.getGroupMemberships(userID)
return authenticationHandler.userIDFlow.flatMapLatest { localRepository.getGroupMemberships(it) }
}
override suspend fun retrieveGroupChat(groupId: String): List<ChatMessage>? {
@ -75,7 +80,7 @@ class SocialRepositoryImpl(
override suspend fun flagMessage(chatMessageID: String, additionalInfo: String, groupID: String?): Void? {
return when {
chatMessageID.isBlank() -> return null
userID == BuildConfig.ANDROID_TESTING_UUID -> return null
currentUserID == BuildConfig.ANDROID_TESTING_UUID -> return null
else -> {
val data = mutableMapOf<String, String>()
data["comment"] = additionalInfo
@ -92,8 +97,8 @@ class SocialRepositoryImpl(
if (chatMessage.id.isBlank()) {
return null
}
val liked = chatMessage.userLikesMessage(userID)
localRepository.likeMessage(chatMessage, userID, !liked)
val liked = chatMessage.userLikesMessage(currentUserID)
localRepository.likeMessage(chatMessage, currentUserID, !liked)
val message = apiClient.likeMessage(chatMessage.groupId ?: "", chatMessage.id)
message?.groupId = chatMessage.groupId
message?.let { localRepository.save(it) }
@ -142,7 +147,7 @@ class SocialRepositoryImpl(
}
apiClient.leaveGroup(id, if (keepChallenges) "remain-in-challenges" else "leave-challenges")
localRepository.updateMembership(userID, id, false)
localRepository.updateMembership(currentUserID, id, false)
return localRepository.getGroup(id).firstOrNull()
}
@ -152,7 +157,7 @@ class SocialRepositoryImpl(
}
val group = apiClient.joinGroup(id)
group?.let {
localRepository.updateMembership(userID, id, true)
localRepository.updateMembership(currentUserID, id, true)
localRepository.save(group)
}
return group
@ -200,9 +205,9 @@ class SocialRepositoryImpl(
val groups = apiClient.listGroups(type) ?: return null
if ("guilds" == type) {
val memberships = groups.map {
GroupMembership(userID, it.id)
GroupMembership(currentUserID, it.id)
}
localRepository.saveGroupMemberships(userID, memberships)
localRepository.saveGroupMemberships(currentUserID, memberships)
}
localRepository.save(groups)
return groups
@ -212,22 +217,22 @@ class SocialRepositoryImpl(
override fun getPublicGuilds() = localRepository.getPublicGuilds()
override fun getInboxConversations() = localRepository.getInboxConversation(userID)
override fun getInboxConversations() = authenticationHandler.userIDFlow.flatMapLatest { localRepository.getInboxConversation(it) }
override fun getInboxMessages(replyToUserID: String?) = localRepository.getInboxMessages(userID, replyToUserID)
override fun getInboxMessages(replyToUserID: String?) = authenticationHandler.userIDFlow.flatMapLatest { localRepository.getInboxMessages(it, replyToUserID) }
override suspend fun retrieveInboxMessages(uuid: String, page: Int): List<ChatMessage>? {
val messages = apiClient.retrieveInboxMessages(uuid, page) ?: return null
messages.forEach {
it.isInboxMessage = true
}
localRepository.saveInboxMessages(userID, uuid, messages, page)
localRepository.saveInboxMessages(currentUserID, uuid, messages, page)
return messages
}
override suspend fun retrieveInboxConversations(): List<InboxConversation>? {
val conversations = apiClient.retrieveInboxConversations() ?: return null
localRepository.saveInboxConversations(userID, conversations)
localRepository.saveInboxConversations(currentUserID, conversations)
return conversations
}
@ -306,7 +311,7 @@ class SocialRepositoryImpl(
}
}
override fun getUserGroups(type: String?) = localRepository.getUserGroups(userID, type)
override fun getUserGroups(type: String?) = authenticationHandler.userIDFlow.flatMapLatest { localRepository.getUserGroups(it, type) }
override suspend fun acceptQuest(user: User?, partyId: String): Void? {
apiClient.acceptQuest(partyId)
@ -342,7 +347,7 @@ class SocialRepositoryImpl(
override suspend fun rejectGroupInvite(groupId: String): Void? {
apiClient.rejectGroupInvite(groupId)
localRepository.rejectGroupInvitation(userID, groupId)
localRepository.rejectGroupInvitation(currentUserID, groupId)
return null
}

View file

@ -1,54 +1,62 @@
@file:OptIn(ExperimentalCoroutinesApi::class)
package com.habitrpg.android.habitica.data.implementation
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.TagRepository
import com.habitrpg.android.habitica.data.local.TagLocalRepository
import com.habitrpg.android.habitica.models.Tag
import com.habitrpg.android.habitica.modules.AuthenticationHandler
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapLatest
class TagRepositoryImpl(localRepository: TagLocalRepository, apiClient: ApiClient, userID: String) : BaseRepositoryImpl<TagLocalRepository>(localRepository, apiClient, userID), TagRepository {
class TagRepositoryImpl(
localRepository : TagLocalRepository,
apiClient : ApiClient,
authenticationHandler : AuthenticationHandler
) : BaseRepositoryImpl<TagLocalRepository>(localRepository, apiClient, authenticationHandler),
TagRepository {
override fun getTags(): Flow<List<Tag>> {
return getTags(userID)
}
override fun getTags() = authenticationHandler.userIDFlow.flatMapLatest { getTags(it) }
override fun getTags(userId: String): Flow<List<Tag>> {
override fun getTags(userId : String) : Flow<List<Tag>> {
return localRepository.getTags(userId)
}
override suspend fun createTag(tag: Tag): Tag? {
override suspend fun createTag(tag : Tag) : Tag? {
val savedTag = apiClient.createTag(tag) ?: return null
savedTag.userId = userID
savedTag.userId = currentUserID
localRepository.save(savedTag)
return savedTag
}
override suspend fun updateTag(tag: Tag): Tag? {
override suspend fun updateTag(tag : Tag) : Tag? {
val savedTag = apiClient.updateTag(tag.id, tag) ?: return null
savedTag.userId = userID
savedTag.userId = currentUserID
localRepository.save(savedTag)
return savedTag
}
override suspend fun deleteTag(id: String): Void? {
override suspend fun deleteTag(id : String) : Void? {
apiClient.deleteTag(id)
localRepository.deleteTag(id)
return null
}
override suspend fun createTags(tags: Collection<Tag>): List<Tag> {
override suspend fun createTags(tags : Collection<Tag>) : List<Tag> {
return tags.mapNotNull {
createTag(it)
}
}
override suspend fun updateTags(tags: Collection<Tag>): List<Tag> {
override suspend fun updateTags(tags : Collection<Tag>) : List<Tag> {
return tags.mapNotNull {
updateTag(it)
}
}
override suspend fun deleteTags(tagIds: Collection<String>): List<Void> {
override suspend fun deleteTags(tagIds : Collection<String>) : List<Void> {
return tagIds.mapNotNull {
deleteTag(it)
}

View file

@ -13,6 +13,7 @@ import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.tasks.TaskList
import com.habitrpg.android.habitica.models.user.OwnedItem
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.modules.AuthenticationHandler
import com.habitrpg.common.habitica.helpers.AnalyticsManager
import com.habitrpg.common.habitica.helpers.launchCatching
import com.habitrpg.shared.habitica.models.responses.TaskDirection
@ -20,26 +21,29 @@ import com.habitrpg.shared.habitica.models.responses.TaskDirectionData
import com.habitrpg.shared.habitica.models.responses.TaskScoringResult
import com.habitrpg.shared.habitica.models.tasks.TaskType
import com.habitrpg.shared.habitica.models.tasks.TasksOrder
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.UUID
@ExperimentalCoroutinesApi
class TaskRepositoryImpl(
localRepository: TaskLocalRepository,
apiClient: ApiClient,
userID: String,
authenticationHandler : AuthenticationHandler,
val appConfigManager: AppConfigManager,
val analyticsManager: AnalyticsManager
) : BaseRepositoryImpl<TaskLocalRepository>(localRepository, apiClient, userID), TaskRepository {
) : BaseRepositoryImpl<TaskLocalRepository>(localRepository, apiClient, authenticationHandler), TaskRepository {
private var lastTaskAction: Long = 0
override fun getTasks(taskType: TaskType, userID: String?, includedGroupIDs: Array<String>): Flow<List<Task>> =
this.localRepository.getTasks(taskType, userID ?: this.userID, includedGroupIDs)
this.localRepository.getTasks(taskType, userID ?: authenticationHandler.currentUserID ?: "", includedGroupIDs)
override fun saveTasks(userId: String, order: TasksOrder, tasks: TaskList) {
localRepository.saveTasks(userId, order, tasks)
@ -54,7 +58,7 @@ class TaskRepositoryImpl(
override suspend fun retrieveCompletedTodos(userId: String?): TaskList? {
val taskList = this.apiClient.getTasks("completedTodos") ?: return null
val tasks = taskList.tasks
this.localRepository.saveCompletedTodos(userId ?: this.userID, tasks.values)
this.localRepository.saveCompletedTodos(userId ?: authenticationHandler.currentUserID ?: "", tasks.values)
return taskList
}
@ -92,7 +96,7 @@ class TaskRepositoryImpl(
lastTaskAction = now
val res = this.apiClient.postTaskDirection(id, (if (up) TaskDirection.UP else TaskDirection.DOWN).text) ?: return null
// There are cases where the user object is not set correctly. So the app refetches it as a fallback
val thisUser = user ?: localRepository.getUser(userID).firstOrNull() ?: return null
val thisUser = user ?: localRepository.getUser(authenticationHandler.currentUserID ?: "").firstOrNull() ?: return null
// save local task changes
analyticsManager.logEvent(
@ -132,7 +136,7 @@ class TaskRepositoryImpl(
if (bgTask.type != TaskType.REWARD && (bgTask.value - localDelta) + res.delta != bgTask.value) {
bgTask.value = (bgTask.value - localDelta) + res.delta
if (TaskType.DAILY == bgTask.type || TaskType.TODO == bgTask.type) {
bgTask.completeForUser(userID, up)
bgTask.completeForUser(authenticationHandler.currentUserID ?: "", up)
if (TaskType.DAILY == bgTask.type) {
if (up) {
bgTask.streak = (bgTask.streak ?: 0) + 1
@ -243,7 +247,7 @@ class TaskRepositoryImpl(
task.ownerID = if (task.isGroupTask) {
task.group?.groupID ?: ""
} else {
userID
authenticationHandler.currentUserID ?: ""
}
if (task.id == null) {
task.id = UUID.randomUUID().toString()
@ -362,8 +366,9 @@ class TaskRepositoryImpl(
}
}
override fun getTaskCopies(userId: String): Flow<List<Task>> =
localRepository.getTasks(userId).map { localRepository.getUnmanagedCopy(it) }
override fun getTaskCopies(): Flow<List<Task>> = authenticationHandler.userIDFlow.flatMapLatest {
localRepository.getTasks(it)
}.map { localRepository.getUnmanagedCopy(it) }
override fun getTaskCopies(tasks: List<Task>): List<Task> = localRepository.getUnmanagedCopy(tasks)
@ -373,7 +378,7 @@ class TaskRepositoryImpl(
}
override suspend fun syncErroredTasks(): List<Task>? {
val tasks = localRepository.getErroredTasks(userID).firstOrNull()
val tasks = localRepository.getErroredTasks(currentUserID ?: "").firstOrNull()
return tasks?.map { localRepository.getUnmanagedCopy(it) }?.mapNotNull {
if (it.isCreating) {
createTask(it, true)
@ -388,6 +393,6 @@ class TaskRepositoryImpl(
}
override fun getTasksForChallenge(challengeID: String?): Flow<List<Task>> {
return localRepository.getTasksForChallenge(challengeID, userID)
return localRepository.getTasksForChallenge(challengeID, currentUserID ?: "")
}
}

View file

@ -4,13 +4,14 @@ import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.TutorialRepository
import com.habitrpg.android.habitica.data.local.TutorialLocalRepository
import com.habitrpg.android.habitica.models.TutorialStep
import com.habitrpg.android.habitica.modules.AuthenticationHandler
import kotlinx.coroutines.flow.Flow
class TutorialRepositoryImpl(
localRepository: TutorialLocalRepository,
apiClient: ApiClient,
userID: String
) : BaseRepositoryImpl<TutorialLocalRepository>(localRepository, apiClient, userID), TutorialRepository {
authenticationHandler: AuthenticationHandler
) : BaseRepositoryImpl<TutorialLocalRepository>(localRepository, apiClient, authenticationHandler), TutorialRepository {
override fun getTutorialStep(key: String): Flow<TutorialStep> =
localRepository.getTutorialStep(key)

View file

@ -17,33 +17,37 @@ import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.models.user.UserQuestStatus
import com.habitrpg.android.habitica.modules.AuthenticationHandler
import com.habitrpg.common.habitica.helpers.AnalyticsManager
import com.habitrpg.shared.habitica.models.responses.TaskDirection
import com.habitrpg.shared.habitica.models.tasks.Attribute
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import java.util.Date
import java.util.GregorianCalendar
import java.util.concurrent.TimeUnit
@OptIn(ExperimentalCoroutinesApi::class)
class UserRepositoryImpl(
localRepository: UserLocalRepository,
apiClient: ApiClient,
userID: String,
authenticationHandler: AuthenticationHandler,
private val taskRepository: TaskRepository,
private val appConfigManager: AppConfigManager,
private val analyticsManager: AnalyticsManager
) : BaseRepositoryImpl<UserLocalRepository>(localRepository, apiClient, userID), UserRepository {
) : BaseRepositoryImpl<UserLocalRepository>(localRepository, apiClient, authenticationHandler), UserRepository {
companion object {
private var lastReadNotification: String? = null
private var lastSync: Date? = null
}
override fun getUser(): Flow<User?> = getUser(userID)
override fun getUser(): Flow<User?> = authenticationHandler.userIDFlow.flatMapLatest { getUser(it) }
override fun getUser(userID: String): Flow<User?> = localRepository.getUser(userID)
private suspend fun updateUser(userID: String, updateData: Map<String, Any?>): User? {
@ -57,11 +61,11 @@ class UserRepositoryImpl(
}
override suspend fun updateUser(updateData: Map<String, Any?>): User? {
return updateUser(userID, updateData)
return updateUser(currentUserID, updateData)
}
override suspend fun updateUser(key : String, value : Any?): User? {
return updateUser(userID, key, value)
return updateUser(currentUserID, key, value)
}
@Suppress("ReturnCount")
@ -156,7 +160,7 @@ class UserRepositoryImpl(
override suspend fun unlockPath(path: String, price: Int): UnlockResponse? {
val unlockResponse = apiClient.unlockPath(path) ?: return null
val user = localRepository.getUser(userID).firstOrNull() ?: return unlockResponse
val user = localRepository.getUser(currentUserID).firstOrNull() ?: return unlockResponse
localRepository.modify(user) { liveUser ->
unlockResponse.preferences?.let { liveUser.preferences = it }
liveUser.purchased = unlockResponse.purchased
@ -176,7 +180,7 @@ class UserRepositoryImpl(
return apiClient.readNotification(id)
}
override fun getUserQuestStatus(): Flow<UserQuestStatus> {
return localRepository.getUserQuestStatus(userID)
return localRepository.getUserQuestStatus(currentUserID)
}
override suspend fun reroll(): User? {
@ -214,7 +218,7 @@ class UserRepositoryImpl(
} else {
apiClient.updateUsername(newLoginName.trim())
}
val user = localRepository.getUser(userID).firstOrNull() ?: return null
val user = localRepository.getUser(currentUserID).firstOrNull() ?: return null
localRepository.modify(user) { liveUser ->
liveUser.authentication?.localAuthentication?.username = newLoginName
liveUser.flags?.verifiedUsername = true
@ -344,7 +348,7 @@ class UserRepositoryImpl(
}
override suspend fun retrieveAchievements(): List<Achievement>? {
val achievements = apiClient.getMemberAchievements(userID) ?: return null
val achievements = apiClient.getMemberAchievements(currentUserID) ?: return null
localRepository.save(achievements)
return achievements
}
@ -354,18 +358,18 @@ class UserRepositoryImpl(
}
override fun getQuestAchievements(): Flow<List<QuestAchievement>> {
return localRepository.getQuestAchievements(userID)
return localRepository.getQuestAchievements(currentUserID)
}
override suspend fun retrieveTeamPlans(): List<TeamPlan>? {
val teams = apiClient.getTeamPlans() ?: return null
teams.forEach { it.userID = userID }
teams.forEach { it.userID = currentUserID }
localRepository.save(teams)
return teams
}
override fun getTeamPlans(): Flow<List<TeamPlan>> {
return localRepository.getTeamPlans(userID)
return localRepository.getTeamPlans(currentUserID)
}
override suspend fun retrieveTeamPlan(teamID: String): Group? {
@ -391,7 +395,7 @@ class UserRepositoryImpl(
}
private suspend fun getLiveUser(): User? {
val user = localRepository.getUser(userID).firstOrNull() ?: return null
val user = localRepository.getUser(currentUserID).firstOrNull() ?: return null
return localRepository.getLiveObject(user)
}

View file

@ -10,6 +10,7 @@ import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.extensions.withImmutableFlag
import com.habitrpg.android.habitica.models.tasks.RemindersItem
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.modules.AuthenticationHandler
import com.habitrpg.android.habitica.receivers.NotificationPublisher
import com.habitrpg.android.habitica.receivers.TaskReceiver
import com.habitrpg.common.habitica.helpers.ExceptionHandler
@ -30,7 +31,7 @@ import java.util.Date
class TaskAlarmManager(
private var context: Context,
private var taskRepository: TaskRepository,
private var userId: String
private var authenticationHandler : AuthenticationHandler
) {
private val am: AlarmManager? = context.getSystemService(Context.ALARM_SERVICE) as? AlarmManager
@ -68,7 +69,7 @@ class TaskAlarmManager(
}
suspend fun scheduleAllSavedAlarms(preventDailyReminder: Boolean) {
val tasks = taskRepository.getTaskCopies(userId).firstOrNull()
val tasks = taskRepository.getTaskCopies().firstOrNull()
tasks?.forEach { this.setAlarmsForTask(it) }
if (!preventDailyReminder) {

View file

@ -4,6 +4,7 @@ import android.content.Context
import android.content.SharedPreferences
import android.content.res.Resources
import androidx.preference.PreferenceManager
import com.habitrpg.android.habitica.BuildConfig
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.ContentRepository
import com.habitrpg.android.habitica.helpers.AppConfigManager
@ -62,6 +63,17 @@ class AppModule {
} else getInstance(context, sharedPreferences, keyStore)
}
@Provides
@Singleton
fun providesAuthenticationHandler(sharedPreferences: SharedPreferences): AuthenticationHandler {
return if (BuildConfig.DEBUG && BuildConfig.TEST_USER_ID.isNotEmpty()) {
AuthenticationHandler(BuildConfig.TEST_USER_ID)
} else {
AuthenticationHandler(sharedPreferences)
}
}
@Provides
fun providesResources(@ApplicationContext context: Context): Resources {
return context.resources
@ -87,8 +99,4 @@ class AppModule {
fun providesRemoteConfigManager(contentRepository: ContentRepository?): AppConfigManager {
return AppConfigManager(contentRepository)
}
companion object {
const val NAMED_USER_ID = "userId"
}
}

View file

@ -17,25 +17,27 @@ import io.realm.Realm
@Module
open class RepositoryModule {
@Provides
open fun providesRealm(): Realm? {
open fun providesRealm(): Realm {
return Realm.getDefaultInstance()
}
@Provides
fun providesContentLocalRepository(realm: Realm?): ContentLocalRepository {
return RealmContentLocalRepository(realm!!)
fun providesContentLocalRepository(realm: Realm): ContentLocalRepository {
return RealmContentLocalRepository(realm)
}
@Provides
fun providesContentRepository(
contentLocalRepository: ContentLocalRepository,
apiClient: ApiClient,
@ApplicationContext context: Context
@ApplicationContext context: Context,
authenticationHandler : AuthenticationHandler
): ContentRepository {
return ContentRepositoryImpl(
contentLocalRepository,
apiClient,
context
context,
authenticationHandler
)
}
}

View file

@ -2,7 +2,6 @@ package com.habitrpg.android.habitica.modules
import android.content.Context
import android.content.SharedPreferences
import com.habitrpg.android.habitica.BuildConfig
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.data.UserRepository
@ -13,9 +12,34 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Named
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filterNotNull
import javax.inject.Singleton
class AuthenticationHandler {
fun updateUserID(userID: String) {
_userIDFlow.value = userID
}
private val _userIDFlow = MutableStateFlow<String?>(null)
val userIDFlow: Flow<String> = _userIDFlow.filterNotNull()
val currentUserID: String?
get() = _userIDFlow.value
val isAuthenticated: Boolean
get() = currentUserID != null
constructor(sharedPreferences : SharedPreferences) {
_userIDFlow.value = sharedPreferences.getString("UserID", "") ?: ""
}
constructor(userID: String) {
_userIDFlow.value = userID
}
}
@InstallIn(SingletonComponent::class)
@Module
class UserModule {
@ -23,30 +47,16 @@ class UserModule {
fun providesTaskAlarmManager(
@ApplicationContext context: Context,
taskRepository: TaskRepository,
@Named(NAMED_USER_ID) userId: String
authenticationHandler: AuthenticationHandler
): TaskAlarmManager {
return TaskAlarmManager(context, taskRepository, userId)
}
@Provides
@Named(NAMED_USER_ID)
fun providesUserID(sharedPreferences: SharedPreferences): String {
return if (BuildConfig.DEBUG && BuildConfig.TEST_USER_ID.isNotEmpty()) {
BuildConfig.TEST_USER_ID
} else {
sharedPreferences.getString("UserID", "") ?: ""
}
return TaskAlarmManager(context, taskRepository, authenticationHandler)
}
@Provides
@Singleton
fun providesUserViewModel(
@Named(NAMED_USER_ID) userID: String,
authenticationHandler: AuthenticationHandler,
userRepository: UserRepository,
socialRepository: SocialRepository
) = MainUserViewModel(userID, userRepository, socialRepository)
companion object {
const val NAMED_USER_ID = "userId"
}
) = MainUserViewModel(authenticationHandler, userRepository, socialRepository)
}

View file

@ -50,77 +50,76 @@ import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import io.realm.Realm
import javax.inject.Named
import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
@Module
class UserRepositoryModule {
@Provides
fun providesSetupCustomizationRepository(@ApplicationContext context: Context?): SetupCustomizationRepository {
return SetupCustomizationRepositoryImpl(context!!)
fun providesSetupCustomizationRepository(@ApplicationContext context: Context): SetupCustomizationRepository {
return SetupCustomizationRepositoryImpl(context)
}
@Provides
fun providesTaskLocalRepository(realm: Realm?): TaskLocalRepository {
return RealmTaskLocalRepository(realm!!)
fun providesTaskLocalRepository(realm: Realm): TaskLocalRepository {
return RealmTaskLocalRepository(realm)
}
@Provides
fun providesTaskRepository(
localRepository: TaskLocalRepository,
apiClient: ApiClient,
@Named(AppModule.NAMED_USER_ID) userId: String,
authenticationHandler : AuthenticationHandler,
appConfigManager: AppConfigManager,
analyticsManager: AnalyticsManager
): TaskRepository {
return TaskRepositoryImpl(
localRepository,
apiClient,
userId,
authenticationHandler,
appConfigManager,
analyticsManager
)
}
@Provides
fun providesTagLocalRepository(realm: Realm?): TagLocalRepository {
return RealmTagLocalRepository(realm!!)
fun providesTagLocalRepository(realm: Realm): TagLocalRepository {
return RealmTagLocalRepository(realm)
}
@Provides
fun providesTagRepository(
localRepository: TagLocalRepository?,
apiClient: ApiClient?,
@Named(AppModule.NAMED_USER_ID) userId: String?
localRepository: TagLocalRepository,
apiClient: ApiClient,
authenticationHandler : AuthenticationHandler
): TagRepository {
return TagRepositoryImpl(localRepository!!, apiClient!!, userId!!)
return TagRepositoryImpl(localRepository, apiClient, authenticationHandler)
}
@Provides
fun provideChallengeLocalRepository(realm: Realm?): ChallengeLocalRepository {
return RealmChallengeLocalRepository(realm!!)
fun provideChallengeLocalRepository(realm: Realm): ChallengeLocalRepository {
return RealmChallengeLocalRepository(realm)
}
@Provides
fun providesChallengeRepository(
localRepository: ChallengeLocalRepository?,
apiClient: ApiClient?,
@Named(AppModule.NAMED_USER_ID) userId: String?
localRepository: ChallengeLocalRepository,
apiClient: ApiClient,
authenticationHandler : AuthenticationHandler
): ChallengeRepository {
return ChallengeRepositoryImpl(localRepository!!, apiClient!!, userId!!)
return ChallengeRepositoryImpl(localRepository, apiClient, authenticationHandler)
}
@Provides
fun providesUserLocalRepository(realm: Realm?): UserLocalRepository {
return RealmUserLocalRepository(realm!!)
fun providesUserLocalRepository(realm: Realm): UserLocalRepository {
return RealmUserLocalRepository(realm)
}
@Provides
fun providesUserRepository(
localRepository: UserLocalRepository,
apiClient: ApiClient,
@Named(AppModule.NAMED_USER_ID) userId: String,
authenticationHandler : AuthenticationHandler,
taskRepository: TaskRepository,
appConfigManager: AppConfigManager,
analyticsManager: AnalyticsManager
@ -128,7 +127,7 @@ class UserRepositoryModule {
return UserRepositoryImpl(
localRepository,
apiClient,
userId,
authenticationHandler,
taskRepository,
appConfigManager,
analyticsManager
@ -136,75 +135,75 @@ class UserRepositoryModule {
}
@Provides
fun providesSocialLocalRepository(realm: Realm?): SocialLocalRepository {
return RealmSocialLocalRepository(realm!!)
fun providesSocialLocalRepository(realm: Realm): SocialLocalRepository {
return RealmSocialLocalRepository(realm)
}
@Provides
fun providesSocialRepository(
localRepository: SocialLocalRepository?,
apiClient: ApiClient?,
@Named(AppModule.NAMED_USER_ID) userId: String?
localRepository: SocialLocalRepository,
apiClient: ApiClient,
authenticationHandler : AuthenticationHandler
): SocialRepository {
return SocialRepositoryImpl(localRepository!!, apiClient!!, userId!!)
return SocialRepositoryImpl(localRepository, apiClient, authenticationHandler)
}
@Provides
fun providesInventoryLocalRepository(
realm: Realm?): InventoryLocalRepository {
return RealmInventoryLocalRepository(realm!!)
realm: Realm): InventoryLocalRepository {
return RealmInventoryLocalRepository(realm)
}
@Provides
fun providesInventoryRepository(
localRepository: InventoryLocalRepository?,
apiClient: ApiClient?,
@Named(AppModule.NAMED_USER_ID) userId: String?,
remoteConfig: AppConfigManager?
localRepository: InventoryLocalRepository,
apiClient: ApiClient,
authenticationHandler : AuthenticationHandler,
remoteConfig: AppConfigManager
): InventoryRepository {
return InventoryRepositoryImpl(localRepository!!, apiClient!!, userId!!, remoteConfig!!)
return InventoryRepositoryImpl(localRepository, apiClient, authenticationHandler, remoteConfig)
}
@Provides
fun providesFAQLocalRepository(realm: Realm?): FAQLocalRepository {
return RealmFAQLocalRepository(realm!!)
fun providesFAQLocalRepository(realm: Realm): FAQLocalRepository {
return RealmFAQLocalRepository(realm)
}
@Provides
fun providesFAQRepository(
localRepository: FAQLocalRepository?,
apiClient: ApiClient?,
@Named(AppModule.NAMED_USER_ID) userId: String?
localRepository: FAQLocalRepository,
apiClient: ApiClient,
authenticationHandler : AuthenticationHandler
): FAQRepository {
return FAQRepositoryImpl(localRepository!!, apiClient!!, userId!!)
return FAQRepositoryImpl(localRepository, apiClient, authenticationHandler)
}
@Provides
fun providesTutorialLocalRepository(realm: Realm?): TutorialLocalRepository {
return RealmTutorialLocalRepository(realm!!)
fun providesTutorialLocalRepository(realm: Realm): TutorialLocalRepository {
return RealmTutorialLocalRepository(realm)
}
@Provides
fun providesTutorialRepository(
localRepository: TutorialLocalRepository?,
apiClient: ApiClient?,
@Named(AppModule.NAMED_USER_ID) userId: String?
localRepository: TutorialLocalRepository,
apiClient: ApiClient,
authenticationHandler : AuthenticationHandler
): TutorialRepository {
return TutorialRepositoryImpl(localRepository!!, apiClient!!, userId!!)
return TutorialRepositoryImpl(localRepository, apiClient, authenticationHandler)
}
@Provides
fun providesCustomizationLocalRepository(realm: Realm?): CustomizationLocalRepository {
return RealmCustomizationLocalRepository(realm!!)
fun providesCustomizationLocalRepository(realm: Realm): CustomizationLocalRepository {
return RealmCustomizationLocalRepository(realm)
}
@Provides
fun providesCustomizationRepository(
localRepository: CustomizationLocalRepository?,
apiClient: ApiClient?,
@Named(AppModule.NAMED_USER_ID) userId: String?
localRepository: CustomizationLocalRepository,
apiClient: ApiClient,
authenticationHandler : AuthenticationHandler
): CustomizationRepository {
return CustomizationRepositoryImpl(localRepository!!, apiClient!!, userId!!)
return CustomizationRepositoryImpl(localRepository, apiClient, authenticationHandler)
}
@Provides

View file

@ -12,20 +12,16 @@ import com.google.android.material.tabs.TabLayoutMediator
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.ActivityPartyInviteBinding
import com.habitrpg.android.habitica.modules.AppModule
import com.habitrpg.android.habitica.ui.fragments.social.party.PartyInviteFragment
import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import javax.inject.Named
@AndroidEntryPoint
class GroupInviteActivity : BaseActivity() {
private lateinit var binding: ActivityPartyInviteBinding
@field:[Inject Named(AppModule.NAMED_USER_ID)]
lateinit var userId: String
@Inject
lateinit var socialRepository: SocialRepository

View file

@ -11,7 +11,6 @@ import androidx.recyclerview.widget.LinearLayoutManager
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.databinding.WidgetConfigureHabitButtonBinding
import com.habitrpg.android.habitica.modules.AppModule
import com.habitrpg.android.habitica.ui.adapter.SkillTasksRecyclerViewAdapter
import com.habitrpg.android.habitica.widget.HabitButtonWidgetProvider
import com.habitrpg.common.habitica.helpers.ExceptionHandler
@ -23,7 +22,6 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import javax.inject.Inject
import javax.inject.Named
@AndroidEntryPoint
class HabitButtonWidgetActivity : BaseActivity() {
@ -33,8 +31,6 @@ class HabitButtonWidgetActivity : BaseActivity() {
@Inject
lateinit var taskRepository: TaskRepository
@field:[Inject Named(AppModule.NAMED_USER_ID)]
lateinit var userId: String
private var widgetId: Int = 0
private var adapter: SkillTasksRecyclerViewAdapter? = null
@ -78,7 +74,7 @@ class HabitButtonWidgetActivity : BaseActivity() {
binding.recyclerView.adapter = adapter
CoroutineScope(Dispatchers.Main + job).launch(ExceptionHandler.coroutine()) {
adapter?.data = taskRepository.getTasks(TaskType.HABIT, userId, emptyArray()).firstOrNull() ?: listOf()
adapter?.data = taskRepository.getTasks(TaskType.HABIT, includedGroupIDs = emptyArray()).firstOrNull() ?: listOf()
}
}

View file

@ -18,7 +18,6 @@ import com.habitrpg.android.habitica.ui.fragments.skills.SkillTasksRecyclerViewF
import com.habitrpg.shared.habitica.models.tasks.TaskType
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import javax.inject.Named
@AndroidEntryPoint
class SkillTasksActivity : BaseActivity() {
@ -26,8 +25,6 @@ class SkillTasksActivity : BaseActivity() {
@Inject
lateinit var taskRepository: TaskRepository
@field:[Inject Named(AppModule.NAMED_USER_ID)]
lateinit var userId: String
internal var viewFragmentsDictionary = SparseArray<SkillTasksRecyclerViewFragment>()

View file

@ -34,7 +34,6 @@ import androidx.core.view.iterator
import androidx.core.widget.NestedScrollView
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.ChallengeRepository
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.data.TagRepository

View file

@ -44,8 +44,10 @@ class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int) : Recycl
internal val items: MutableList<HabiticaDrawerItem> = ArrayList()
var selectedItem: Int? = null
set(value) {
val oldValue = field
field = value
notifyDataSetChanged()
oldValue?.let { notifyItemChanged(it) }
value?.let { notifyItemChanged(it) }
}
var itemSelectedEvents: ((HabiticaDrawerItem) -> Unit)? = null

View file

@ -144,6 +144,7 @@ class ShopRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<Vi
}
sectionHolder.switchClassButton?.visibility = View.VISIBLE
sectionHolder.switchClassLabel?.text = context.getString(R.string.change_class_to_x, getTranslatedClassName(context.resources, selectedGearCategory))
sectionHolder.switchClassDescription?.text = context.getString(R.string.unlock_gear_and_skills, getTranslatedClassName(context.resources, selectedGearCategory))
sectionHolder.switchClassCurrency?.value = 3.0
} else {
sectionHolder.switchClassButton?.visibility = View.GONE

View file

@ -19,15 +19,11 @@ import com.habitrpg.android.habitica.modules.AppModule
import com.habitrpg.common.habitica.extensions.DataBindingUtils
import com.plattysoft.leonids.ParticleSystem
import javax.inject.Inject
import javax.inject.Named
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class AboutFragment : BaseMainFragment<FragmentAboutBinding>() {
@field:[Inject Named(AppModule.NAMED_USER_ID)]
lateinit var userId: String
@Inject
lateinit var appConfigManager: AppConfigManager

View file

@ -6,17 +6,15 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.webkit.WebChromeClient
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.databinding.FragmentNewsBinding
import com.habitrpg.android.habitica.modules.AppModule
import javax.inject.Inject
import javax.inject.Named
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class PromoWebFragment : BaseMainFragment<FragmentNewsBinding>() {
@field:[Inject Named(AppModule.NAMED_USER_ID)]
lateinit var userID: String
@Inject
lateinit var userViewModel: MainUserViewModel
override var binding: FragmentNewsBinding? = null
@ -44,7 +42,7 @@ class PromoWebFragment : BaseMainFragment<FragmentNewsBinding>() {
arguments?.let {
val args = PromoWebFragmentArgs.fromBundle(it)
var url = args.url
url = url.replace("USER_ID", userID)
url = url.replace("USER_ID", userViewModel.userID)
binding?.newsWebview?.loadUrl(url)
}
}

View file

@ -7,7 +7,6 @@ import android.view.ViewGroup
import android.view.Window
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.data.UserRepository

View file

@ -11,7 +11,6 @@ import androidx.core.os.bundleOf
import androidx.lifecycle.lifecycleScope
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
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.data.SocialRepository
import com.habitrpg.android.habitica.data.UserRepository

View file

@ -27,6 +27,7 @@ import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.CurrencyText
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.android.habitica.ui.views.getTranslatedClassName
import com.habitrpg.android.habitica.ui.views.shops.PurchaseDialog
import com.habitrpg.common.habitica.helpers.ExceptionHandler
import com.habitrpg.common.habitica.helpers.RecyclerViewState
@ -202,10 +203,11 @@ open class ShopFragment : BaseMainFragment<FragmentRefreshRecyclerviewBinding>()
private fun showClassChangeDialog(classIdentifier: String) {
context?.let { context ->
val alert = HabiticaAlertDialog(context)
alert.setTitle(getString(R.string.class_confirmation_price, classIdentifier, 3))
alert.setTitle(getString(R.string.class_confirmation_price, getTranslatedClassName(requireContext().resources, classIdentifier), 3))
alert.addButton(R.string.choose_class, true) { _, _ ->
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.changeClass(classIdentifier)
adapter?.notifyDataSetChanged()
}
}
alert.addButton(R.string.dialog_go_back, false)

View file

@ -18,6 +18,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@ -622,34 +623,40 @@ fun PauseResumeDamageView(
Text(
stringResource(R.string.pause_damage_1_title),
color = HabiticaTheme.colors.textPrimary,
fontWeight = FontWeight.Normal,
fontSize = 16.sp
)
Text(
stringResource(R.string.pause_damage_1_description),
color = HabiticaTheme.colors.textSecondary,
fontSize = 14.sp,
fontWeight = FontWeight.Normal,
modifier = Modifier.padding(bottom = 12.dp)
)
Text(
stringResource(R.string.pause_damage_2_title),
color = HabiticaTheme.colors.textPrimary,
fontWeight = FontWeight.Normal,
fontSize = 16.sp
)
Text(
stringResource(R.string.pause_damage_2_description),
color = HabiticaTheme.colors.textSecondary,
fontSize = 14.sp,
fontWeight = FontWeight.Normal,
modifier = Modifier.padding(bottom = 12.dp)
)
Text(
stringResource(R.string.pause_damage_3_title),
color = HabiticaTheme.colors.textPrimary,
fontWeight = FontWeight.Normal,
fontSize = 16.sp
)
Text(
stringResource(R.string.pause_damage_3_description),
color = HabiticaTheme.colors.textSecondary,
fontSize = 14.sp,
fontWeight = FontWeight.Normal,
modifier = Modifier.padding(bottom = 18.dp)
)
HabiticaButton(

View file

@ -15,7 +15,6 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.android.billingclient.api.ProductDetails
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentGemPurchaseBinding
import com.habitrpg.android.habitica.extensions.addCancelButton

View file

@ -8,7 +8,6 @@ import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
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.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentGiftGemBalanceBinding

View file

@ -6,7 +6,6 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.android.billingclient.api.ProductDetails
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.FragmentGiftGemPurchaseBinding
import com.habitrpg.android.habitica.helpers.PurchaseHandler

View file

@ -16,7 +16,6 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.android.billingclient.api.ProductDetails
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.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentSubscriptionBinding

View file

@ -10,7 +10,6 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.tabs.TabLayout
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.data.SetupCustomizationRepository
import com.habitrpg.android.habitica.data.UserRepository

View file

@ -5,7 +5,6 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.databinding.FragmentIntroBinding
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import dagger.hilt.android.AndroidEntryPoint

View file

@ -7,7 +7,6 @@ import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.GridLayoutManager
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.databinding.FragmentSetupTasksBinding
import com.habitrpg.android.habitica.models.tasks.Days
import com.habitrpg.android.habitica.models.tasks.Task

View file

@ -10,7 +10,6 @@ import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentWelcomeBinding
import com.habitrpg.android.habitica.extensions.OnChangeTextWatcher

View file

@ -6,7 +6,6 @@ import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.asLiveData
import androidx.recyclerview.widget.LinearLayoutManager
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.databinding.FragmentRecyclerviewBinding
import com.habitrpg.android.habitica.models.tasks.Task

View file

@ -12,7 +12,6 @@ import androidx.core.content.ContextCompat
import androidx.core.text.toHtml
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.FragmentQuestDetailBinding
@ -37,7 +36,6 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import javax.inject.Inject
import javax.inject.Named
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
@ -49,9 +47,6 @@ class QuestDetailFragment : BaseMainFragment<FragmentQuestDetailBinding>() {
@Inject
lateinit var inventoryRepository: InventoryRepository
@field:[Inject Named(AppModule.NAMED_USER_ID)]
lateinit var userId: String
@Inject
lateinit var userViewModel: MainUserViewModel
@ -123,7 +118,7 @@ class QuestDetailFragment : BaseMainFragment<FragmentQuestDetailBinding>() {
val user = userViewModel.user.value
if (binding?.questResponseWrapper != null) {
if (userId != party?.quest?.leader && user?.party?.quest?.key == group.quest?.key && user?.party?.quest?.RSVPNeeded == false) {
if (userViewModel.userID != party?.quest?.leader && user?.party?.quest?.key == group.quest?.key && user?.party?.quest?.RSVPNeeded == false) {
binding?.questLeaveButton?.visibility = View.VISIBLE
} else {
binding?.questLeaveButton?.visibility = View.GONE
@ -143,7 +138,7 @@ class QuestDetailFragment : BaseMainFragment<FragmentQuestDetailBinding>() {
}
private fun showLeaderButtons(): Boolean {
return userId == party?.quest?.leader || userId == party?.leaderID
return userViewModel.userID == party?.quest?.leader || userViewModel.userID == party?.leaderID
}
private fun updateQuestContent(questContent: QuestContent) {

View file

@ -13,7 +13,6 @@ import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.data.UserRepository

View file

@ -12,7 +12,6 @@ import androidx.fragment.app.viewModels
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.google.android.material.tabs.TabLayoutMediator
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.databinding.FragmentViewpagerBinding
import com.habitrpg.android.habitica.models.social.Group

View file

@ -7,7 +7,6 @@ import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.ChallengeRepository
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.data.UserRepository
@ -15,18 +14,17 @@ import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBind
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.modules.AppModule
import com.habitrpg.android.habitica.ui.adapter.social.ChallengesListViewAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.common.habitica.helpers.ExceptionHandler
import com.habitrpg.common.habitica.helpers.launchCatching
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
import javax.inject.Inject
import javax.inject.Named
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class ChallengeListFragment :
@ -35,15 +33,12 @@ class ChallengeListFragment :
@Inject
lateinit var challengeRepository: ChallengeRepository
@Inject
lateinit var socialRepository: SocialRepository
@Inject
lateinit var userRepository: UserRepository
@field:[Inject Named(AppModule.NAMED_USER_ID)]
lateinit var userId: String
@Inject
lateinit var userViewModel: MainUserViewModel
override var binding: FragmentRefreshRecyclerviewBinding? = null
@ -77,7 +72,7 @@ class ChallengeListFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
challengeAdapter = ChallengesListViewAdapter(viewUserChallengesOnly, userId)
challengeAdapter = ChallengesListViewAdapter(viewUserChallengesOnly, userViewModel.userID)
challengeAdapter?.onOpenChallengeFragment = { openDetailFragment(it) }
binding?.refreshLayout?.setOnRefreshListener(this)

View file

@ -15,7 +15,6 @@ import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.google.android.material.tabs.TabLayoutMediator
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.ChallengeRepository
import com.habitrpg.android.habitica.databinding.FragmentViewpagerBinding
import com.habitrpg.android.habitica.ui.activities.ChallengeFormActivity

View file

@ -8,7 +8,6 @@ import androidx.appcompat.widget.SearchView
import androidx.lifecycle.lifecycleScope
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
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.databinding.FragmentRefreshRecyclerviewBinding
import com.habitrpg.android.habitica.ui.adapter.social.GuildListAdapter

View file

@ -16,7 +16,6 @@ import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.google.android.material.tabs.TabLayoutMediator
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.databinding.FragmentViewpagerBinding
import com.habitrpg.android.habitica.extensions.addCloseButton

View file

@ -12,7 +12,6 @@ import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.ChallengeRepository
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.SocialRepository
@ -26,7 +25,6 @@ import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.modules.AppModule
import com.habitrpg.android.habitica.ui.activities.FullProfileActivity
import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
@ -43,13 +41,12 @@ import com.habitrpg.common.habitica.helpers.ExceptionHandler
import com.habitrpg.common.habitica.helpers.launchCatching
import com.habitrpg.common.habitica.helpers.setMarkdown
import com.habitrpg.common.habitica.views.AvatarView
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import javax.inject.Inject
import javax.inject.Named
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class PartyDetailFragment : BaseFragment<FragmentPartyDetailBinding>() {
@ -74,10 +71,6 @@ class PartyDetailFragment : BaseFragment<FragmentPartyDetailBinding>() {
@Inject
lateinit var inventoryRepository: InventoryRepository
@field:[Inject Named(AppModule.NAMED_USER_ID)]
lateinit var userId: String
override fun onDestroyView() {
inventoryRepository.close()
super.onDestroyView()
@ -252,7 +245,7 @@ class PartyDetailFragment : BaseFragment<FragmentPartyDetailBinding>() {
binding?.questProgressView?.setData(questContent, viewModel?.getGroupData()?.value?.quest?.progress)
val questParticipants = viewModel?.getGroupData()?.value?.quest?.members
if (questParticipants?.find { it.key == userId } != null) {
if (questParticipants?.find { it.key == viewModel?.userViewModel?.userID } != null) {
binding?.questParticipationView?.text = context?.getString(R.string.number_participants, questParticipants.size)
} else {
binding?.questParticipationView?.text = context?.getString(R.string.not_participating)
@ -286,13 +279,13 @@ class PartyDetailFragment : BaseFragment<FragmentPartyDetailBinding>() {
FullProfileActivity.open(member.id ?: "")
}
viewHolder.sendMessageEvent = {
member.id?.let { showSendMessageToUserDialog(it, member.displayName) }
member.id.let { showSendMessageToUserDialog(it, member.displayName) }
}
viewHolder.transferOwnershipEvent = {
member.id?.let { showTransferOwnerShipDialog(it, member.displayName) }
member.id.let { showTransferOwnerShipDialog(it, member.displayName) }
}
viewHolder.removeMemberEvent = {
member.id?.let { showRemoveMemberDialog(it, member.displayName) }
member.id.let { showRemoveMemberDialog(it, member.displayName) }
}
}
}

View file

@ -81,6 +81,7 @@ class PartySeekingViewModel @Inject constructor(
seekingUsers = Pager(
config = PagingConfig(
pageSize = 30,
prefetchDistance = 10
),
pagingSourceFactory = {
PartySeekingPagingSource(socialRepository)
@ -250,8 +251,7 @@ fun PartySeekingView(
textAlign = TextAlign.Center,
color = HabiticaTheme.colors.textSecondary, modifier = Modifier
.width(250.dp)
.align(alignment = Alignment.CenterHorizontally)
)
.align(alignment = Alignment.CenterHorizontally))
}
}
} else {
@ -276,6 +276,7 @@ fun PartySeekingView(
)
}
}
}
items(
items = pageData
) {
@ -329,10 +330,10 @@ fun PartySeekingView(
Box(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
.padding(12.dp),
contentAlignment = Alignment.Center
) {
HabiticaCircularProgressView()
HabiticaCircularProgressView(indicatorSize = 32.dp)
}
}
}
@ -367,7 +368,7 @@ class PartySeekingPagingSource(
LoadResult.Page(
data = response ?: emptyList(),
prevKey = if (page == 0) null else page.minus(1),
nextKey = if (response?.isEmpty() != false) null else page.plus(1),
nextKey = if ((response?.size ?: 0) < 30) null else page.plus(1),
)
} catch (e : Exception) {
LoadResult.Error(e)

View file

@ -15,7 +15,6 @@ import com.habitrpg.android.habitica.databinding.FragmentSupportBugFixBinding
import com.habitrpg.android.habitica.databinding.KnownIssueBinding
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.modules.AppModule
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.common.habitica.extensions.layoutInflater
@ -25,7 +24,6 @@ import com.jaredrummler.android.device.DeviceName
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import javax.inject.Inject
import javax.inject.Named
@AndroidEntryPoint
class BugFixFragment : BaseMainFragment<FragmentSupportBugFixBinding>() {
@ -39,9 +37,6 @@ class BugFixFragment : BaseMainFragment<FragmentSupportBugFixBinding>() {
return FragmentSupportBugFixBinding.inflate(inflater, container, false)
}
@field:[Inject Named(AppModule.NAMED_USER_ID)]
lateinit var userId: String
@Inject
lateinit var appConfigManager: AppConfigManager
@Inject
@ -121,7 +116,7 @@ class BugFixFragment : BaseMainFragment<FragmentSupportBugFixBinding>() {
if (appConfigManager.testingLevel().name != AppTestingLevel.PRODUCTION.name) {
bodyOfEmail += newLine + Uri.encode(appConfigManager.testingLevel().name)
}
bodyOfEmail += newLine + Uri.encode("User ID: $userId")
bodyOfEmail += newLine + Uri.encode("User ID: $userViewModel.userID")
userViewModel.user.value?.let { user ->
bodyOfEmail += newLine + Uri.encode("Level: " + (user.stats?.lvl ?: 0)) +

View file

@ -6,7 +6,6 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.FAQRepository
import com.habitrpg.android.habitica.databinding.FragmentFaqDetailBinding
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment

View file

@ -9,7 +9,6 @@ import android.widget.ImageView
import androidx.core.os.bundleOf
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.FAQRepository
import com.habitrpg.android.habitica.databinding.FragmentFaqOverviewBinding
import com.habitrpg.android.habitica.databinding.SupportFaqItemBinding

View file

@ -12,14 +12,12 @@ import com.habitrpg.android.habitica.data.FAQRepository
import com.habitrpg.android.habitica.databinding.FragmentSupportMainBinding
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.modules.AppModule
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.common.habitica.helpers.ExceptionHandler
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import javax.inject.Inject
import javax.inject.Named
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class SupportMainFragment : BaseMainFragment<FragmentSupportMainBinding>() {
@ -29,8 +27,6 @@ class SupportMainFragment : BaseMainFragment<FragmentSupportMainBinding>() {
return FragmentSupportMainBinding.inflate(inflater, container, false)
}
@field:[Inject Named(AppModule.NAMED_USER_ID)]
lateinit var userId: String
@Inject
lateinit var faqRepository: FAQRepository
@Inject

View file

@ -22,6 +22,7 @@ class SectionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val selectionSpinner: Spinner? = itemView.findViewById(R.id.classSelectionSpinner)
val switchClassButton: LinearLayout? = itemView.findViewById(R.id.change_class_button)
val switchClassLabel: TextView? = itemView.findViewById(R.id.change_class_label)
val switchClassDescription: TextView? = itemView.findViewById(R.id.change_class_description)
val switchClassCurrency: CurrencyView? = itemView.findViewById(R.id.change_class_currency_view)
internal val notesView: TextView? = itemView.findViewById(R.id.headerNotesView)
private val countPill: TextView? = itemView.findViewById(R.id.count_pill)

View file

@ -20,6 +20,7 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.extensions.addCloseButton
import com.habitrpg.android.habitica.modules.AuthenticationHandler
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.common.habitica.api.HostConfig
import com.habitrpg.common.habitica.helpers.AnalyticsManager
@ -34,6 +35,7 @@ class AuthenticationViewModel @Inject constructor(
val apiClient : ApiClient,
val userRepository : UserRepository,
val sharedPrefs : SharedPreferences,
val authenticationHandler : AuthenticationHandler,
val hostConfig : HostConfig,
val analyticsManager : AnalyticsManager,
private val keyHelper : KeyHelper?
@ -142,6 +144,7 @@ class AuthenticationViewModel @Inject constructor(
@Throws(Exception::class)
private fun saveTokens(api : String, user : String) {
this.apiClient.updateAuthenticationCredentials(user, api)
authenticationHandler.updateUserID(user)
sharedPrefs.edit {
putString("UserID", user)
val encryptedKey =

View file

@ -8,6 +8,7 @@ import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.models.TeamPlan
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.modules.AuthenticationHandler
import com.habitrpg.common.habitica.helpers.ExceptionHandler
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.MainScope
@ -19,12 +20,12 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.launch
import javax.inject.Inject
class MainUserViewModel @Inject constructor(private val providedUserID: String, val userRepository: UserRepository, val socialRepository: SocialRepository) {
class MainUserViewModel @Inject constructor(private val authenticationHandler : AuthenticationHandler, val userRepository: UserRepository, val socialRepository: SocialRepository) {
val formattedUsername: CharSequence?
get() = user.value?.formattedUsername
val userID: String
get() = user.value?.id ?: providedUserID
get() = user.value?.id ?: authenticationHandler.currentUserID ?: ""
val username: CharSequence
get() = user.value?.username ?: ""
val displayName: CharSequence

View file

@ -14,6 +14,7 @@ import android.widget.Button
import android.widget.LinearLayout
import android.widget.RelativeLayout
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.children
import androidx.lifecycle.lifecycleScope
@ -24,7 +25,9 @@ import com.habitrpg.android.habitica.ui.activities.BaseActivity
import com.habitrpg.common.habitica.extensions.dpToPx
import com.habitrpg.common.habitica.extensions.layoutInflater
import com.plattysoft.leonids.ParticleSystem
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.lang.ref.WeakReference
@ -68,6 +71,17 @@ open class HabiticaAlertDialog(context: Context) : AlertDialog(context, R.style.
binding.dialogWrapper.layoutParams = layoutParams
}
// Used when a dialog has an action that neeeds to complete even when the dialog is alrady closed
val longLivingScope: CoroutineScope
get() {
val activity = getActivity()
return if (activity is AppCompatActivity) {
activity.lifecycleScope
} else {
MainScope()
}
}
init {
setView(binding.root)
binding.closeButton.setOnClickListener { dismiss() }

View file

@ -15,12 +15,10 @@ import com.habitrpg.android.habitica.models.inventory.Animal
import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.android.habitica.models.inventory.HatchingPotion
import com.habitrpg.android.habitica.models.inventory.Item
import com.habitrpg.android.habitica.ui.activities.BaseActivity
import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.common.habitica.extensions.DataBindingUtils
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.helpers.launchCatching
import kotlinx.coroutines.MainScope
import java.util.Locale
import javax.inject.Inject
@ -136,16 +134,20 @@ class PetSuggestHatchDialog(context: Context) : HabiticaAlertDialog(context) {
val activity = (getActivity() as? MainActivity) ?: return@addButton
val thisPotion = potion ?: return@addButton
val thisEgg = egg ?: return@addButton
lifecycleScope.launchCatching {
if (!hasEgg) {
activity.inventoryRepository.purchaseItem("eggs", thisEgg.key, 1)
longLivingScope.launchCatching {
if (!hasEgg) {
activity.inventoryRepository.purchaseItem("eggs", thisEgg.key, 1)
}
if (!hasPotion) {
activity.inventoryRepository.purchaseItem(
"hatchingPotions",
thisPotion.key,
1
)
}
activity.userRepository.retrieveUser(true, forced = true)
hatchPet(thisPotion, thisEgg)
}
if (!hasPotion) {
activity.inventoryRepository.purchaseItem("hatchingPotions", thisPotion.key, 1)
}
activity.userRepository.retrieveUser(true, forced = true)
hatchPet(thisPotion, thisEgg)
}
}
}
@ -158,22 +160,20 @@ class PetSuggestHatchDialog(context: Context) : HabiticaAlertDialog(context) {
DataBindingUtils.loadImage(context, imageName) {
val resources = context.resources ?: return@loadImage
val drawable = if (hasMount) it else BitmapDrawable(resources, it.toBitmap().extractAlpha())
MainScope().launchCatching {
lifecycleScope.launchCatching {
binding.petView.bitmap = drawable.toBitmap()
}
}
}
private fun hatchPet(potion: HatchingPotion, egg: Egg) {
(getActivity() as? BaseActivity)?.let {
it.lifecycleScope.launchCatching {
hatchPetUseCase.callInteractor(
HatchPetUseCase.RequestValues(
potion, egg,
it
)
longLivingScope.launchCatching {
hatchPetUseCase.callInteractor(
HatchPetUseCase.RequestValues(
potion, egg,
context
)
}
)
}
}

View file

@ -1,7 +1,5 @@
package com.habitrpg.android.habitica;
import com.habitrpg.android.habitica.components.AppComponent;
import com.habitrpg.android.habitica.components.DaggerAppComponent;
import com.habitrpg.android.habitica.modules.AppModule;
public class HabiticaApplication extends HabiticaBaseApplication {