diff --git a/Habitica/AndroidManifest.xml b/Habitica/AndroidManifest.xml
index 41bef2786..66e7a6687 100644
--- a/Habitica/AndroidManifest.xml
+++ b/Habitica/AndroidManifest.xml
@@ -99,7 +99,7 @@
tools:ignore="UnusedAttribute">
@@ -112,14 +112,6 @@
android:name="android.support.PARENT_ACTIVITY"
android:value="com.habitrpg.android.habitica.ui.activities.MainActivity" />
-
-
-
-
diff --git a/Habitica/AndroidManifestTesting.xml b/Habitica/AndroidManifestTesting.xml
index 2ca0894e1..53b3c978f 100644
--- a/Habitica/AndroidManifestTesting.xml
+++ b/Habitica/AndroidManifestTesting.xml
@@ -41,22 +41,12 @@
tools:ignore="UnusedAttribute">
-
-
-
-
+
+
+
diff --git a/Habitica/res/drawable-hdpi/ic_email_color.png b/Habitica/res/drawable-hdpi/ic_email_color.png
new file mode 100644
index 000000000..6beaa5556
Binary files /dev/null and b/Habitica/res/drawable-hdpi/ic_email_color.png differ
diff --git a/Habitica/res/drawable-hdpi/login_background.png b/Habitica/res/drawable-hdpi/login_background.png
new file mode 100644
index 000000000..c3fe8c1a4
Binary files /dev/null and b/Habitica/res/drawable-hdpi/login_background.png differ
diff --git a/Habitica/res/drawable-hdpi/login_email.png b/Habitica/res/drawable-hdpi/login_email.png
new file mode 100644
index 000000000..9828d1f8c
Binary files /dev/null and b/Habitica/res/drawable-hdpi/login_email.png differ
diff --git a/Habitica/res/drawable-hdpi/login_logo.png b/Habitica/res/drawable-hdpi/login_logo.png
new file mode 100644
index 000000000..d168e8577
Binary files /dev/null and b/Habitica/res/drawable-hdpi/login_logo.png differ
diff --git a/Habitica/res/drawable-hdpi/login_logo.webp b/Habitica/res/drawable-hdpi/login_logo.webp
deleted file mode 100644
index 04b6a5326..000000000
Binary files a/Habitica/res/drawable-hdpi/login_logo.webp and /dev/null differ
diff --git a/Habitica/res/drawable-hdpi/login_password.png b/Habitica/res/drawable-hdpi/login_password.png
new file mode 100644
index 000000000..967ac2264
Binary files /dev/null and b/Habitica/res/drawable-hdpi/login_password.png differ
diff --git a/Habitica/res/drawable-hdpi/login_username.png b/Habitica/res/drawable-hdpi/login_username.png
new file mode 100644
index 000000000..a69b5ce13
Binary files /dev/null and b/Habitica/res/drawable-hdpi/login_username.png differ
diff --git a/Habitica/res/drawable-hdpi/stable_background_spring.png b/Habitica/res/drawable-hdpi/stable_background_spring.png
new file mode 100644
index 000000000..f8fe19742
Binary files /dev/null and b/Habitica/res/drawable-hdpi/stable_background_spring.png differ
diff --git a/Habitica/res/drawable-mdpi/border_pixelated.png b/Habitica/res/drawable-mdpi/border_pixelated.png
new file mode 100644
index 000000000..3287d37ec
Binary files /dev/null and b/Habitica/res/drawable-mdpi/border_pixelated.png differ
diff --git a/Habitica/res/drawable-mdpi/header_verify_username.png b/Habitica/res/drawable-mdpi/header_verify_username.png
new file mode 100644
index 000000000..a19e95fdc
Binary files /dev/null and b/Habitica/res/drawable-mdpi/header_verify_username.png differ
diff --git a/Habitica/res/drawable-mdpi/ic_email_color.png b/Habitica/res/drawable-mdpi/ic_email_color.png
new file mode 100644
index 000000000..816a26264
Binary files /dev/null and b/Habitica/res/drawable-mdpi/ic_email_color.png differ
diff --git a/Habitica/res/drawable-mdpi/login_background.png b/Habitica/res/drawable-mdpi/login_background.png
new file mode 100644
index 000000000..c85fadebf
Binary files /dev/null and b/Habitica/res/drawable-mdpi/login_background.png differ
diff --git a/Habitica/res/drawable-mdpi/login_background.webp b/Habitica/res/drawable-mdpi/login_background.webp
deleted file mode 100644
index 5d863ac2b..000000000
Binary files a/Habitica/res/drawable-mdpi/login_background.webp and /dev/null differ
diff --git a/Habitica/res/drawable-mdpi/login_email.png b/Habitica/res/drawable-mdpi/login_email.png
new file mode 100644
index 000000000..ba3ffb7a9
Binary files /dev/null and b/Habitica/res/drawable-mdpi/login_email.png differ
diff --git a/Habitica/res/drawable-mdpi/login_logo.png b/Habitica/res/drawable-mdpi/login_logo.png
new file mode 100644
index 000000000..1baefa80d
Binary files /dev/null and b/Habitica/res/drawable-mdpi/login_logo.png differ
diff --git a/Habitica/res/drawable-mdpi/login_logo.webp b/Habitica/res/drawable-mdpi/login_logo.webp
deleted file mode 100644
index b513ef30c..000000000
Binary files a/Habitica/res/drawable-mdpi/login_logo.webp and /dev/null differ
diff --git a/Habitica/res/drawable-mdpi/login_password.png b/Habitica/res/drawable-mdpi/login_password.png
new file mode 100644
index 000000000..8391bf231
Binary files /dev/null and b/Habitica/res/drawable-mdpi/login_password.png differ
diff --git a/Habitica/res/drawable-mdpi/login_username.png b/Habitica/res/drawable-mdpi/login_username.png
new file mode 100644
index 000000000..737ef4ae5
Binary files /dev/null and b/Habitica/res/drawable-mdpi/login_username.png differ
diff --git a/Habitica/res/drawable-mdpi/stable_background_spring.png b/Habitica/res/drawable-mdpi/stable_background_spring.png
new file mode 100644
index 000000000..951ea8973
Binary files /dev/null and b/Habitica/res/drawable-mdpi/stable_background_spring.png differ
diff --git a/Habitica/res/drawable-xhdpi/border_pixelated.png b/Habitica/res/drawable-xhdpi/border_pixelated.png
new file mode 100644
index 000000000..7d25561f5
Binary files /dev/null and b/Habitica/res/drawable-xhdpi/border_pixelated.png differ
diff --git a/Habitica/res/drawable-xhdpi/header_verify_username.png b/Habitica/res/drawable-xhdpi/header_verify_username.png
new file mode 100644
index 000000000..5277c4ada
Binary files /dev/null and b/Habitica/res/drawable-xhdpi/header_verify_username.png differ
diff --git a/Habitica/res/drawable-xhdpi/ic_email_color.png b/Habitica/res/drawable-xhdpi/ic_email_color.png
new file mode 100644
index 000000000..b3d157299
Binary files /dev/null and b/Habitica/res/drawable-xhdpi/ic_email_color.png differ
diff --git a/Habitica/res/drawable-xhdpi/login_background.png b/Habitica/res/drawable-xhdpi/login_background.png
new file mode 100644
index 000000000..82052a8cf
Binary files /dev/null and b/Habitica/res/drawable-xhdpi/login_background.png differ
diff --git a/Habitica/res/drawable-xhdpi/login_email.png b/Habitica/res/drawable-xhdpi/login_email.png
new file mode 100644
index 000000000..f4d6e0c02
Binary files /dev/null and b/Habitica/res/drawable-xhdpi/login_email.png differ
diff --git a/Habitica/res/drawable-xhdpi/login_logo.png b/Habitica/res/drawable-xhdpi/login_logo.png
new file mode 100644
index 000000000..37802b6ca
Binary files /dev/null and b/Habitica/res/drawable-xhdpi/login_logo.png differ
diff --git a/Habitica/res/drawable-xhdpi/login_logo.webp b/Habitica/res/drawable-xhdpi/login_logo.webp
deleted file mode 100644
index 877d03908..000000000
Binary files a/Habitica/res/drawable-xhdpi/login_logo.webp and /dev/null differ
diff --git a/Habitica/res/drawable-xhdpi/login_password.png b/Habitica/res/drawable-xhdpi/login_password.png
new file mode 100644
index 000000000..e1e4d4586
Binary files /dev/null and b/Habitica/res/drawable-xhdpi/login_password.png differ
diff --git a/Habitica/res/drawable-xhdpi/login_username.png b/Habitica/res/drawable-xhdpi/login_username.png
new file mode 100644
index 000000000..19a7f14f7
Binary files /dev/null and b/Habitica/res/drawable-xhdpi/login_username.png differ
diff --git a/Habitica/res/drawable-xhdpi/stable_background_spring.png b/Habitica/res/drawable-xhdpi/stable_background_spring.png
new file mode 100644
index 000000000..7652a75b6
Binary files /dev/null and b/Habitica/res/drawable-xhdpi/stable_background_spring.png differ
diff --git a/Habitica/res/drawable-xxhdpi/border_pixelated.png b/Habitica/res/drawable-xxhdpi/border_pixelated.png
new file mode 100644
index 000000000..5c2ca9de9
Binary files /dev/null and b/Habitica/res/drawable-xxhdpi/border_pixelated.png differ
diff --git a/Habitica/res/drawable-xxhdpi/header_verify_username.png b/Habitica/res/drawable-xxhdpi/header_verify_username.png
new file mode 100644
index 000000000..9a32db575
Binary files /dev/null and b/Habitica/res/drawable-xxhdpi/header_verify_username.png differ
diff --git a/Habitica/res/drawable-xxhdpi/ic_email_color.png b/Habitica/res/drawable-xxhdpi/ic_email_color.png
new file mode 100644
index 000000000..d02917321
Binary files /dev/null and b/Habitica/res/drawable-xxhdpi/ic_email_color.png differ
diff --git a/Habitica/res/drawable-xxhdpi/login_background.png b/Habitica/res/drawable-xxhdpi/login_background.png
new file mode 100644
index 000000000..e996c4e92
Binary files /dev/null and b/Habitica/res/drawable-xxhdpi/login_background.png differ
diff --git a/Habitica/res/drawable-xxhdpi/login_email.png b/Habitica/res/drawable-xxhdpi/login_email.png
new file mode 100644
index 000000000..013171a21
Binary files /dev/null and b/Habitica/res/drawable-xxhdpi/login_email.png differ
diff --git a/Habitica/res/drawable-xxhdpi/login_logo.png b/Habitica/res/drawable-xxhdpi/login_logo.png
new file mode 100644
index 000000000..de82bff53
Binary files /dev/null and b/Habitica/res/drawable-xxhdpi/login_logo.png differ
diff --git a/Habitica/res/drawable-xxhdpi/login_logo.webp b/Habitica/res/drawable-xxhdpi/login_logo.webp
deleted file mode 100644
index 239a2b37a..000000000
Binary files a/Habitica/res/drawable-xxhdpi/login_logo.webp and /dev/null differ
diff --git a/Habitica/res/drawable-xxhdpi/login_password.png b/Habitica/res/drawable-xxhdpi/login_password.png
new file mode 100644
index 000000000..57e59de4f
Binary files /dev/null and b/Habitica/res/drawable-xxhdpi/login_password.png differ
diff --git a/Habitica/res/drawable-xxhdpi/login_username.png b/Habitica/res/drawable-xxhdpi/login_username.png
new file mode 100644
index 000000000..7ed0c460d
Binary files /dev/null and b/Habitica/res/drawable-xxhdpi/login_username.png differ
diff --git a/Habitica/res/drawable-xxhdpi/stable_background_spring.png b/Habitica/res/drawable-xxhdpi/stable_background_spring.png
new file mode 100644
index 000000000..9158fb0cd
Binary files /dev/null and b/Habitica/res/drawable-xxhdpi/stable_background_spring.png differ
diff --git a/Habitica/res/drawable/activity_launch_background.xml b/Habitica/res/drawable/activity_launch_background.xml
index 4b12dee0b..190f9d1c4 100644
--- a/Habitica/res/drawable/activity_launch_background.xml
+++ b/Habitica/res/drawable/activity_launch_background.xml
@@ -1,13 +1,10 @@
-
-
-
-
\ No newline at end of file
+
diff --git a/Habitica/res/drawable/login_gradient.xml b/Habitica/res/drawable/login_gradient.xml
index e5339aff3..335bd85c4 100644
--- a/Habitica/res/drawable/login_gradient.xml
+++ b/Habitica/res/drawable/login_gradient.xml
@@ -3,8 +3,8 @@
-
\ No newline at end of file
+
diff --git a/Habitica/res/layout/activity_login.xml b/Habitica/res/layout/activity_login.xml
index 9e6027050..5a671cee9 100644
--- a/Habitica/res/layout/activity_login.xml
+++ b/Habitica/res/layout/activity_login.xml
@@ -1,258 +1,11 @@
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_height="match_parent">
+
diff --git a/Habitica/res/layout/activity_setup.xml b/Habitica/res/layout/activity_setup.xml
deleted file mode 100644
index 52f751411..000000000
--- a/Habitica/res/layout/activity_setup.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Habitica/res/layout/avatar_category.xml b/Habitica/res/layout/avatar_category.xml
deleted file mode 100644
index 838f1fd71..000000000
--- a/Habitica/res/layout/avatar_category.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
diff --git a/Habitica/res/layout/avatar_setup_drawer.xml b/Habitica/res/layout/avatar_setup_drawer.xml
deleted file mode 100644
index 7b23d7452..000000000
--- a/Habitica/res/layout/avatar_setup_drawer.xml
+++ /dev/null
@@ -1,93 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Habitica/res/layout/fragment_intro.xml b/Habitica/res/layout/fragment_intro.xml
deleted file mode 100644
index 08df6184e..000000000
--- a/Habitica/res/layout/fragment_intro.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Habitica/res/layout/fragment_setup_avatar.xml b/Habitica/res/layout/fragment_setup_avatar.xml
deleted file mode 100644
index 863f12242..000000000
--- a/Habitica/res/layout/fragment_setup_avatar.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Habitica/res/layout/fragment_setup_tasks.xml b/Habitica/res/layout/fragment_setup_tasks.xml
deleted file mode 100644
index 003ebbee9..000000000
--- a/Habitica/res/layout/fragment_setup_tasks.xml
+++ /dev/null
@@ -1,59 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Habitica/res/layout/fragment_welcome.xml b/Habitica/res/layout/fragment_welcome.xml
deleted file mode 100644
index d260e6157..000000000
--- a/Habitica/res/layout/fragment_welcome.xml
+++ /dev/null
@@ -1,129 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Habitica/res/values-b+es+419/strings.profile.xml b/Habitica/res/values-b+es+419/strings.profile.xml
index a05a8f175..70576d325 100644
--- a/Habitica/res/values-b+es+419/strings.profile.xml
+++ b/Habitica/res/values-b+es+419/strings.profile.xml
@@ -1,8 +1,13 @@
- Mascotas y Monturas
+ Mascotas y monturasMascotas encontradas
- Monturas Domesticadas
+ Monturas domesticadasCargando datos del miembro…Enviar mensaje a %s
+ Mensaje enviado a %s
+ Nivel:
+ Bonus de clase:
+ Asignado:
+ Logros
\ No newline at end of file
diff --git a/Habitica/res/values-b+es+419/strings.sidebar.xml b/Habitica/res/values-b+es+419/strings.sidebar.xml
index 38ff2d208..c520b840e 100644
--- a/Habitica/res/values-b+es+419/strings.sidebar.xml
+++ b/Habitica/res/values-b+es+419/strings.sidebar.xml
@@ -4,5 +4,17 @@
HabilidadesSocialMensajes
- Equipo
+ Grupo
+ Comprar gemas
+ Suscripción
+ Retos
+ Inventario
+ Personalizar personaje
+ Personaje y equipamiento
+ Equipamiento
+ Mascotas y Monturas
+ Noticias
+ Información
+ Tiendas
+ Estadísticas
\ No newline at end of file
diff --git a/Habitica/res/values-b+es+419/strings.tutorial.xml b/Habitica/res/values-b+es+419/strings.tutorial.xml
index cb05733a0..68d7c0d38 100644
--- a/Habitica/res/values-b+es+419/strings.tutorial.xml
+++ b/Habitica/res/values-b+es+419/strings.tutorial.xml
@@ -5,4 +5,18 @@
¡Aquí estamos! He hecho algunas tareas para ti según tus gustos. Intenta agregar algunos propios. Puedes editar cualquier tarea tocando el título.¡Cada vez que hagas un hábito positivo, toca el + para recibir experiencia y oro!¡Dale un tiro! Puede explorar los otros tipos de tareas mediante de la navegación inferior.
+ Usa Tareas Diarias para aquellas tareas que necesitan ser completadas en un horario regular.
+ Ten cuidado — si no completas una, tu avatar recibirá daño por la noche. ¡Completarlas constantemente trae grandes recompensas!
+ Usa las Tareas Pendientes para gestionar las tareas que solo tienes que realizar una vez.
+ ¡Compra equipamiento para tu avatar con el oro que ganas!
+ También puedes crear recompensas personalizadas del mundo real en función de lo que te motiva.
+ Eso es todo por ahora. Si necesitas un recordatorio, consulta la sección de Preguntas Frecuentes (FAQ).
+ ¡Aquí es donde tú y tus amigos pueden estar pendientes del uno al otro para cumplir sus objetivos y luchar contra monstruos junto a sus tareas!
+ Toca el botón gris para asignar muchas de tus estadísticas a la vez, o toca las flechas para agregarlas un punto a la vez.
+ Si tu Tarea Pendiente necesita ser completada en cierta hora, ánade una fecha límite. Parece que puedes tachar una — ¡Adelante!
+ ¡Las Habiliades son habilidades especiales con efectos poderosos! Toca una habilidad para usarla. Costará Maná (la barra azul), que puedes conseguir entrando todos los días y completar tareas en la vida real. ¡Consulta la sección de Preguntas Frecuentes (FAQ) para más información!
+ ¡Bienvenido a tu Grupo!
+\nPuedes encontrar más miembros desde una lista de jugadores que estén buscando un grupo o invitar a tus amigos directamente para conseguir el pergamino de Tareas de la Basi-Lista.
+\n
+\nVe a Soporte para aprender más sobre los Grupos.
\ No newline at end of file
diff --git a/Habitica/res/values-b+es+419/strings.xml b/Habitica/res/values-b+es+419/strings.xml
index d8b57e31e..e1193d3d9 100644
--- a/Habitica/res/values-b+es+419/strings.xml
+++ b/Habitica/res/values-b+es+419/strings.xml
@@ -16,4 +16,15 @@
Recordatorio diarioActivar RecordatorioPrimer día del mes
+ Comprar
+ Objetos especiales
+ M
+ B
+ Fondos increíbles
+ La habilidad para cambiar tu clase antes del nivel 100
+ k
+ Ganaste %s gemas.
+ T
+ Jardín
+ %1$s y %2$s
\ No newline at end of file
diff --git a/Habitica/res/values-de/strings.xml b/Habitica/res/values-de/strings.xml
index 6b70cb247..fd37a0efe 100644
--- a/Habitica/res/values-de/strings.xml
+++ b/Habitica/res/values-de/strings.xml
@@ -246,7 +246,7 @@
Teilen viaIch habe durch das Verbessern meiner realen Lebensgewohnheiten Level %d in #Habitica erreicht!Durch das Erfüllen meiner Real-Life-Aufgaben habe ich ein %1$s %2$s Haustier in #Habitica ausgebrütet!
- Durch das Erfüllen meiner Real-Life-Aufgaben habe ich ein %s Reittier in Habitica erhalten!
+ Durch das Erfüllen meiner Real-Life-Aufgaben habe ich ein %1$s Reittier in Habitica erhalten!Im Play Store öffnenMöchtest du deine Klasse für 3 Edelsteine ändern\?Dies wird deine Statuswerte erstatten, wechselt verfügbare Ausrüstung im Marktplatz und ändert die möglichen Fähigkeiten.
@@ -271,12 +271,12 @@
Neue Aufgabe hinzufügenNeue Gewohnheit hinzufügenNeue Tagesaufgabe hinzufügen
- Neues To Do hinzufügen
+ Neues To-Do hinzufügenNeue Belohnung hinzufügenDu hast alle Deine Tagesaufgaben erledigt. Gut gemacht!Habitica: GewohnheitHabitica: Tagesaufgaben
- Habitica To Do-Liste
+ Habitica To-Do-ListeGoogle Play Dienste konnten nicht gefunden werden.KaufenDas Erwerben von Edelsteinen unterstützt die Entwickler und hilft Habitica am Laufen zu halten
@@ -462,7 +462,7 @@ Die Quest-Schriftrolle wird an den Quest-Besitzer zurückgegeben.
Du wirst mehr mystische Sanduhren benötigen um diesen Gegenstand zu kaufen!Kaufe SanduhrenAbonniere für Sanduhren
- Erhalte eine mystische Sanduhr alle drei Monate für ein aufeinanderfolgendes Abonnement, nutze diese dann, um Limitiertes freizuschalten: Gegenstände, Haustiere oder Reittiere aus der Vergangenheit… und aus der Zukunft!
+ Erhalte jeden Monat deines Abonnements eine mystische Sanduhr, nutze diese dann, um Limitiertes freizuschalten: Gegenstände, Haustiere oder Reittiere aus der Vergangenheit… und aus der Zukunft!Ich möchte ein Abo abschließenDie Großen Galas werden an Sonnenwenden und Tag-Nacht-Gleichen veranstaltet, also komme zu dieser Zeit wieder, um eine lustige Auswahl von speziellen saisonalen Gegenständen zu finden!Komm bald zurück!
@@ -841,7 +841,7 @@ Die Quest-Schriftrolle wird an den Quest-Besitzer zurückgegeben.
%d MonateErhalte einen Purpur Wolpertinger plus die doppelte Menge Einer, Schlüpfelixiere und Futter jeden Tag um deine Haustiersammlung zu vergrößern!Abonniere jetzt, um dieses %s gleich zu bekommen und jeden Monat neue Gegenstände zu erhalten!
- Besonderes Haustier & mehr Beute
+ Besondere Haustiere & Mehr GegenständeKonto erstellen%d Anmeldungenregelmäßig bei Habitica einloggen
@@ -939,9 +939,9 @@ Die Quest-Schriftrolle wird an den Quest-Besitzer zurückgegeben.
\n
\nWenn Du Habitica abonnierst, kannst Du mit Gold sogar eine gewisse, von der Dauer Deines Abonnements abhängige, Anzahl an Edelsteinen kaufen.
Wir lassen immer wieder neue Bugfixes raus. Deshalb solltest Du regelmäßig im Play-Store nach Aktualisierungen ausschau halten.
- Mystische Sanduhren sind eine extrem seltene Währung, die Du nur durch ein Habitica-Abonnement von mindestens drei aufeinanderfolgenden Monaten erhalten kannst. Du brauchst die Sanduhren im Shop der Mystischen Zeitreisenden, um Ausrüstungssets aus vergangenen Zeiten zu kaufen, oder auch Haustiere, Reittiere, animierte Hintergründe oder spezielle Quests.
+ Mystische Sanduhren sind eine extrem seltene Währung, die du nur durch ein Habitica-Abonnement erhalten kannst. Du brauchst die Sanduhren im Shop der Mystischen Zeitreisenden, um Ausrüstungssets aus vergangenen Zeiten zu kaufen, oder auch Haustiere, Reittiere, animierte Hintergründe oder spezielle Quests! Diese extra Vorteile sind toll um dich durch das ganze Jahr motiviert zu halten und dich selbst zu belohnen.
\n
-\nDu kannst bis zu vier Mystische Sanduhren pro Jahr erhalten. Der Zeitpunkt, wann das passiert, hängt von Deiner Abonnements-Erneuerung ab. Die Sanduhren trudeln am ersten Tag des neuen Monats nach der Abonnementszahlung ein, für die Dir eine Sanduhr zusteht. Sie auf der [Abonnement]-Seite nach, wenn Du mehr wissen willst.
+\nAbonnenten erhalten eine Mystische Sanduhr, zusammen mit vielen anderen Vorteilen, zu beginn jeden Monats an dem sie Abonnement-Vorteile haben. Schau dir unsere Abo-Optionen an, falls dich interessiert was die Zeitreisenden so anbieten!Edelsteine sind eine Währung, die Du mit echtem Geld kaufst. Sie ermöglichen Dir, zusätzliche Inhalte in Habitica zu kaufen und sind neben Abonnements eine der Haupteinnahmequellen für das Habitica-Team.
\n
\nAlle Inhalte, die mit Edelsteinen gekauft werden, sind rein kosmetischer Natur oder können mit der Zeit auch kostenlos erhalten werden.
@@ -1095,7 +1095,7 @@ Die Quest-Schriftrolle wird an den Quest-Besitzer zurückgegeben.Setze alle Aufgaben auf einen neutralen Wert zurück (gelbe Farbe) und stelle alle verlorene Gesundheit wieder her%s hat dich als Gewinner gewählt! Dein Sieg wurde in deinen Erfolgen vermerkt.Bist Du sicher, dass Du den Quest verlassen möchtest\? All Dein Fortschritt wird verloren gehen.
- Ein blockierte Spieler*in kann dir keine privaten Nachrichten schicken, aber du wirst seine Nachrichten in der Taverne oder in Gilden weiterhin sehen.
+ Ein blockierte Spieler*in kann dir keine privaten Nachrichten schicken, aber du wirst seine Nachrichten in der Taverne oder in Gilden weiterhin sehenStartbildschirmNeuerSaisonale Artikel verfügbar
@@ -1133,7 +1133,7 @@ Die Quest-Schriftrolle wird an den Quest-Besitzer zurückgegeben.
VerbindenHinzufügenE-Mail und Passwort hinzufügen
- Die eingegebenen Passwörter stimmen nicht überein.
+ Die eingegebenen Passwörter stimmen nicht übereinUngültige E-Mail-AdresseVerwerfenMöchtest Du Deine Klasse in %1$s ändern für %2$d Edelsteine\?
@@ -1203,7 +1203,7 @@ Die Quest-Schriftrolle wird an den Quest-Besitzer zurückgegeben.
Aber Du kannst sie alle mit harter Arbeit zurückerlangen! Viel Erfolg - Du wirst das schaffen.Zerstörte Ausrüstungsgegenstände können
\nim Belohnungen-Bereich zurückgekauft werden
- Du fällst zurück auf Level %1$d, verlierst %2$d Gold und ein Ausrüstungsgegenstand wird zerstört…
+ Du fällst zurück auf Level %1$d, verlierst %2$d Gold und ein Ausrüstungsgegenstand wird zerstört… Du kannst sie alle mit harter Arbeit wieder verdienen!Account zurücksetzenAvatar Haut AnpassungAvatar Bart Anpassung
@@ -1263,4 +1263,281 @@ Die Quest-Schriftrolle wird an den Quest-Besitzer zurückgegeben.
positivTitelnegativ
-
+ Dadurch wird deine aktuelle Klasse entfernt, du bekommst alle Attributspunkte zurück und kannst eine neue Klasse wählen
+ Behalte deinen Fortschritt mit einer sofortigen Heilung pro Tag, wenn deine Gesundheit aufgebraucht ist!
+ Wähle eine Klasse, um Fähigkeiten, Werte und Mana zu aktivieren
+ Klasse wählen
+ Willst du deine Klasse zu %1$s ändern\?
+ Deine Tags
+ Herausforderungs-Tags
+ Gruppen-Tags
+ Wecker und Erinnerungen deaktiviert
+ In diesem Fall wird dir keine Klasse zugewiesen. Du kannst jederzeit wieder eine Klasse auswählen, indem du das Klassensystem in den Einstellungen aktivierst.
+ Bekomme eine zweite Chance
+ Du wirst alle deine Level, Gold und Erfahrung verlieren. Alle deine Aufgaben und deren historischen Daten werden gelöscht (Herausforderungsaufgaben bleiben erhalten). Du wirst alle Ausrüstungsgegenstände verlieren, mit Ausnahme von Abonnenten-Gegenständen und kostenlosen Erinnerungsgegenständen, aber du wirst sie zurückkaufen können. Du wirst der richtigen Klasse angehören müssen, um klassenspezifische Ausrüstung erneut zu kaufen. Du behältst deine aktuelle Klasse, deine Errungenschaften sowie deine Haus- und Reittiere. Um das Zurücksetzen zu bestätigen, gib RESET in das Textfeld unten ein.
+ Du solltest eine Herausforderung nur melden, wenn sie gegen die [Community Richtlinien](https://habitica.com/static/community-guidelines) und/oder [AGB](https://habitica.com/static/terms) verstößt. Eine Falschmeldung ist ein Verstoß gegen Habiticas Community Richtlinien.
+ Herausforderungen
+ %d Anmeldungen
+ Du kannst uns kontaktieren und ein Mitglied unseres Teams wird sein Bestes tun, um dir zu helfen!
+
+ %d Tag
+ %d Tage
+
+
+ %d Stunde
+ %d Stunden
+
+
+ %d Minute
+ %d Minuten
+
+ Rüste dich mit der neuesten exklusiven Ausrüstung aus. Abonniere jetzt um %1$ss %2$s zu erhalten!
+ Sanduhr in 3 Monaten
+ Dies wird auch @%s blockieren
+ %1$s
+\n@%2$s
+ %s gemeldet
+ Dieser Gegenstand ist nur für %s verfügbar. Öffne die Einstellungen, um deine Klasse zu ändern.
+ Dieser Gegenstand ist nur für %s verfügbar. Nach Level 10 kannst du eine Klasse wählen.
+ Du stimmst unseren Nutzungsbedingungen zu und hast unsere Datenschutzbestimmungen gelesen.
+ Begründung der Meldung
+ Du wirst alle deine Level, Gold und Erfahrung verlieren. Alle deine Aufgaben und deren historischen Daten werden gelöscht (Herausforderungsaufgaben bleiben erhalten). Du wirst alle Ausrüstungsgegenstände verlieren, mit Ausnahme von Abonnenten-Gegenständen und kostenlosen Erinnerungsgegenständen, aber du wirst sie zurückkaufen können. Du wirst der richtigen Klasse angehören müssen, um klassenspezifische Ausrüstung erneut zu kaufen. Du behältst deine aktuelle Klasse, deine Errungenschaften sowie deine Haus- und Reittiere. Um das Zurücksetzen zu bestätigen, gib dein Passwort unten ein.
+ Du solltest nur Beiträge melden, die die [Community Richtlinien](https://habitica.com/static/community-guidelines) and/or [Terms of Service](https://habitica.com/static/terms) verletzen. Eine Falschmeldung ist ein Verstoß gegen Habiticas Community Richtlinien.
+ <font color=#925CF3>%2$s</font> hat dich eingeladen der Gruppe <b>%1$s</b> beizutreten
+ %s melden\?
+ Spieler*in melden
+ Du solltest einen Spieler nur melden, wenn er gegen die [Community Richtlinien](https://habitica.com/static/community-guidelines) und/oder [AGB](https://habitica.com/static/terms) verstößt. Eine Falschmeldung ist ein Verstoß gegen Habiticas Community Richtlinien.
+ Nachricht melden
+ Melden fehlgeschlagen, bitte versuche es später erneut
+ Warum meldest du diese Herausforderung\?
+ Du möchtest kein Abonnement mehr\? Aufgrund deines gewählten Zahlungsverfahrens musst du das Abonnement in den Einstellungen deines iOS Gerätes mit deiner Apple ID kündigen.
+ Warum meldest du diese Nachricht\?
+ Warum meldest du diese/n Spieler*in\?
+ Erinnerungen können verspätet erscheinen, da die Berechtigung nicht gegeben wurde. Klicke um Berechtigungen anzuzeigen und zu ändern.
+ Erlaube \"Wecker und Erinnerungen\" in der Einstellungen-App, damit Erinnerungen exakt zur richtigen Zeit erscheinen
+ Bist du dir sicher, dass du %s verkaufen möchtest\?
+ Bist du sicher, dass du diese Party verlassen willst\?
+ Du kannst dieser Party ohne Einladung nicht erneut beitreten.
+\n
+\nWenn du diese Party verlässt, nimmst du auch nicht mehr an deiner aktuellen Quest teil.
+ Level %d
+ Dieser Gegenstand ist nur für eine bestimmte Klasse verfügbar. Nach Level 10 kannst du eine Klasse wählen.
+ Dadurch wird die Ausrüstung in den Geschäften freigeschaltet und die verfügbaren Fertigkeiten ändern sich
+ Du kannst dich dieser Party nur wieder anschließen, wenn du eingeladen wirst.
+\n
+\nDurch das Verlassen der Party, wird auch die aktuelle Quest abgebrochen.
+ Siehe weitere Optionen fürs Abonnement
+ Einladung verschicken
+ Melde %s
+ Möchten Sie weiterhin die Vorteile eines Abonnements nutzen\? Sie können ein wiederkehrendes Abonnement abschließen, um Ihre Vorteile beizubehalten.
+ Abonnenten erhalten mystische Sanduhren innerhalb der ersten drei Tage des Monats.
+ Zugewiesen zu
+ Erledigt am %s
+ Ende der Vorteile %s
+ Benutzername oder E-Mail-Adresse
+ ABO-VORTEIL
+ Dir zugeordnet von @%1$s in %2$s
+ Bosse fügen dir weiterhin Schaden zu, wenn andere Gruppenmitglieder ihre Tagesaufgaben verpapssen
+ Du suchst eine Party!
+ Erhalte jedes Mal zwei Chancen, neue Ausrüstung zu bekommen, wenn du den Verzauberten Schrank öffnest!
+ Abonniere, um kostenlos erneut zu öffnen!
+ Nicht gesetzt
+ Möchtest du den Zugriff zum Chat für diesen Spieler widerrufen\?
+ Unsichtbar stumm, ausgeblendet
+ Dies ist eine **%1$s** Aufgabe die sich **%2$s%3$s** wiederholt.
+ am %1$s %2$s des Monats
+ Der seltene Jubilierende Greifatrix kommt zur Geburtstagsfeier! Verpasse nicht deine Chance auf dieses exklusive animierte Haustier.
+ Vier für umsonst
+ Dein Quest-Fortschritt wird wieder angewandt
+ Zähler für tägliche Strähnen und Gewohnheiten steigen nur, wenn dies eingeschaltet ist
+ Der Fortschritt deines Quests verbleibt bei Ausstehend
+ Per Einladung
+ Eingeladen
+ Sende eine Einladung direkt an Spieler*innen die du kennst
+ Versende Einladungen per @Benutzername oder E-Mail
+ Beende Aufgaben, kaufe einen Schrank oder gehe zum [Marktplatz](/shops/market) um mehr zu erhalten!
+ Erhalte jede Mal eine zusätzliche Chance auf den Schrank, wenn du ihn mit einem Abonnement kaufst
+ Schaden pausieren
+ Jedes Mal, wenn Du den Schrank öffnest, kannst Du ihn erneut kostenlos öffnen!
+ Besuche den Individualisierungsmarkt, um die vielen Möglichkeiten zur Anpassung Deines Avatar zu durchstöbern!
+ Set
+ Erhalte nach Abschluss deines ersten 12-monatigen Abonnements sofort 12 Mystische Sanduhren!
+ Abonniere erneut, um dort weiterzumachen, wo Du aufgehört hast!
+ %1$d/%2$d Edelsteinobergrenze
+ Habitica Webseite öffnen
+ Limitierte Ausgabe
+ Dies ist eine **%1$s** Aufgabe, fällig **%2$s**.
+ Zuweisen
+ Zuweisungen bearbeiten
+ Zuweisen zu…
+ Spieler*in bannen
+ Zum Manager befördern
+ Mitgliederliste
+ Möchtest du diese Spieler*in unsichtbar stummschalten\?
+ Stripe
+ Am %s
+ An Wochentagen
+ An Wochenenden
+ An jedem Tag der Woche
+ zweiten
+ dritten
+ vierten
+ fünften
+ Jubilierender Greifatrix Haustier
+ Jede menge Tränke
+ Danke für deine Unterstützung!
+ Für %s kaufen
+ Kaufen für
+ Wir bringen 10 eurer liebsten magischen Schlüpfelixiere zurück. Gehe zum Marktplatz um dein Inventar zu füllen!
+ Hintergrund
+ Bosse fügen weiterhin Schaden zu wenn
+\nandere Mitglieder ihre Tagesaufgaben verpassen
+ Deine Strähnen und Zähler werden nicht zurückgesetzt
+ Schau im [Marktplatz](/shops/market) nach um Futter oder einen Sattel zu kaufen, der dein Haustier sofort großzieht!
+ Schau im [Quest-Shop](/shops/quests) vorbei, um zu sehen, was verfügbar ist!
+ Schau im [Markt](/shops/market) vorbei, um genau die Eier zu kaufen, die du brauchst!
+ Schau im [Markt](/shops/market) vorbei, um Standard- und saisonale Bruttränke zu kaufen!
+ Du hast deine zweite Chance heute bereits genutzt. Sie ist wieder verfügbar in %s
+ Quest-Gegenstände sammeln
+ Bosse schlagen zurück, wenn tägliche Aufgaben verpasst werden
+ Kaufe Edelsteine mit Gold
+ \@Erwähnungen in Gruppenplänen
+ Positiv und Negativ
+ Finde Mitglieder
+ Wechsel Klasse zu %s
+ Möchtest du mehr Quests\?
+ Profil öffnen
+ Erneut kostenlos öffnen!
+ 12 Mystische Sanduhren
+ Danke für dein Abonnement
+ Heimlich Stummschalten
+ Spieler*in stumm schalten
+ Willst du diese Spieler*in bannen\?
+ Willst du diese Spieler*in entbannen\?
+ Diese Ausrüstung ist aufgrund seiner Klasse gesperrt
+ Manager*in
+ %1$s und %2$s
+ am %s
+ Zuerst
+ Suchst du nach einer Party\?
+ Suche eine Party
+ Du besitzt bereits alle %s-Ausrüstung
+ Bekomme Quests vom Leveln, Anmeldebonus oder dem [Quest Shop](/shops/quests)!
+ Erhalte Verwandlungsgegenstände während der saisonalen Galas, und Mystery-Boxen mit Abonnenten-Ausrüstung werden zu Beginn jedes Monats geliefert!
+ Schau im [Markt](/shops/market) vorbei, um genau das zu kaufen, was du brauchst!
+ Abonnenten erhalten jeden Tag eine zweite Chance im Leben und diese weiteren Vorteile!
+ Schau dir eine Werbung an, um mit 1HP durchzuhalten!
+ Wut
+ Zu Gruppenplan eingeladen
+ Silvester
+ Valentinstag
+ Ausrüstung suchen
+ Tag %d
+ Markt besuchen
+ Schaden ist aktiv. Mehr erfahren.
+ Schaden an einem Boss oder gefundene Sammelgegenstände wird gespeichert bis du Schaden wieder einschaltest
+ Liste
+ Möchtest du einer Party mit anderen beitreten, kennst aber niemanden\? Lasse Party-Leiter*innen wissen, dass du auf der Suche bist!
+ Abonnenten erhalten zusätzliche Chancen auf den Schrank und diese weiteren Vorteile!
+ Dies ist eine **%1$s** Aufgabe die keine Fälligkeit hat.
+ Du hast den Jubilierenden Greifatrix verschenkt!
+ Möchtest du diese Spieler*in wieder Zugriff zum Chat geben\?
+ Möchtest du die unsichtbare Stummschaltung aufheben\?
+ Ausstehender Schaden oder Sammelgegenstände werden beim nächsten Tages-Reset angewandt
+ Öffne Einstellungen
+ Eine Party-Robe
+ Schließe Aufgaben oder Elixier-Quests ab, oder gehe zum [Marktplatz](/shops/market) um mehr zu erhalten!
+ Schließe Aufgaben und Ei-Quests ab oder besuche den [Markt](/shops/market), um dich einzudecken!
+ Bekommst du nicht das richtige %s aus Aufgaben\?
+ Du hast eine zweite Chance mit 1HP erhalten!
+ Wenn du auf einer Sammel-Quest bist, schließe deine Aufgaben ab, um eine zufällige Chance zu haben, einen Quest-Gegenstand zu finden. Ausstehende Gegenstände werden beim nächsten Tages-Reset angewendet. Wahrnehmung erhöht die Wahrscheinlichkeit, Gegenstände zu finden.
+ Unbekannter Fehler bei der Authentifizierung.
+ 20 Edelsteine
+ Feiere Habiticas 10. Geburtstag mit Geschenken und exklusiven Gegenständen!
+ Pausiere Schaden
+ "Deine verpassten Tagesaufgaben fügen dir keinen Schaden zu "
+ Quest-Mechaniken
+ Einem Boss Schaden zufügen
+ Schließe jede Art von Aufgabe ab oder nutze Fähigkeiten, um ausstehende Schäden zu sammeln! Der Schaden wird beim nächsten Tages-Reset angewendet. Stärke beeinflusst, wie viel Schaden du anrichtest.
+ Schaden ist pausiert.
+ Hier ist die Liste der Spieler*innen, welche einer Party beitreten möchten
+ Gerade sucht niemand nach einer Party. Du kannst später noch mal nachschauen!
+ Halte Ausschau nach Einladungen oder starte jederzeit eine eigene Party
+ Kein %s
+ Tränke
+ Avatar anpassen
+ Avatar teilen
+ Doppelte Schrank-Belohnungen
+ Zweite Chance: Halte durch mit 1HP!
+ Mit einem Abonnement erhälst du jeden Tag eine zweite Chance, um zu verhindern, dass dir die HP ausgehen
+ Im Anpassungsshop findest du noch mehr Möglichkeiten, deinen Avatar individuell zu gestalten!
+ Truthahntag
+ Fehler beim Abrufen der Anmeldedaten zur Authentifizierung.
+ Erfüllst du eine Tagesaufgabe nicht, wird deine Strähne und der Zähler normal zurückgesetzt
+ Edelsteinobergrenze
+ Ungültige Anmeldedaten erhalten.
+
+ %d Gegenstand ausstehend
+ %d Gegenstände ausstehend
+
+ Dies ist ein zeitlich begrenztes Event das am %1$s startet und am %2$s endet. Die Aktion gilt nur für Geschenke an andere Benutzer. Wenn du oder der Empfänger des Geschenks bereits ein Abonnement haben, werden die Monate als Guthaben an das Abonnement angehängt und benutzt, sobald das aktuelle Abonnement endet oder gekündigt wird.
+ Niemals
+ Finde mehr Mitglieder
+ Gehe zu Einstellungen
+ Schaden fortsetzen
+ Deine verpassten Tagesaufgaben werden dir keinen Schaden zufügen
+ Deine Strähnen und Zähler werden zurückgesetzt
+ Neue Ausrüstung wird zu saisonalen Galas veröffentlicht. Bis dahin gibt es %d Ausrüstungsgegenstände im Verzauberten Schrank zu finden!
+ Neue Ausrüstung wird zu saisonalen Galas veröffentlicht. Auch der Verzauberte Schrank bekommt monatlich neue Gegenstände!
+ Schalte %s Ausrüstung und Fähigkeiten frei
+ Beende Einladung
+ Aufgehoben
+ Einladung ausstehend
+ Eingabe ungültig
+ Darf nur die Buchstaben a-z, Nummern, Bindestrich und Unterstrich enthalten
+ Mitwirkenden-Level
+ Wenn du einen Account mit farbigem Benutzernamen und einem Symbol siehst, dann handelt es sich um einen Mitwirkenden-Rang der Person. Ränge werden an Personen vergeben, die im Zusammenhang mit Habitica mitgeholfen haben, z.B. durch Übersetzungen, Code oder hilfreiches Verhalten. Je höher der Rang, desto mehr hat die Person zu Habitica beigetragen.
+ Besuche FAQ
+ Spieler*in-Bann aufheben
+ Status
+ Regulärer Zugriff
+ Nachricht %d mal gemeldet.
+ Amazon
+ Apple Pay
+ Google Pay
+ PayPal
+ Entferne unsichtbare Stummschaltung
+ Stummschaltung aufheben
+ Um die Party in Schwung zu halten, verschenken wir Party-Roben, 20 Edelsteine sowie ein limitiertes Umhang-Set und Hintergrund!
+ Dies ist eine zeitlich begrenzte Veranstaltung, die am %1$s beginnt und am %2$s endet. Die limitierte Ausgabe des Jubilierenden Greifatrix und der zehn Magischen Schlüpfelixiere können in dieser Zeit gekauft werden. Die anderen Geschenke im Gratis-Bereich werden automatisch an alle Accounts ausgegeben, die in den 30 Tagen vor dem Datum der Geschenkübergabe aktiv waren. Accounts, die nach der Geschenkübergabe erstellt wurden, haben keinen Anspruch darauf.
+ Markt besuchen
+ Exklusive Gegenstände und Geschenke warten
+ Endet in %s
+ Mehr anzeigen
+ Du hast den Jubilierenden Greifatrix gekauft!
+ Rückgängig
+ Geburtstags-Set
+ Du hast dich mit %s ausgerüstet
+ Kaufe den Jubilierenden Greifatrix für %d Edelsteine\?
+
+ jeden Tag
+ alle %d Tage
+
+
+ jede Woche
+ alle %d Wochen
+
+
+ jeden Monat
+ alle %d Monate
+
+
+ jedes Jahr
+ alle %d Jahre
+
+ Du fällst auf Level %1$d zurück, verlierst %2$d Gold und deine Erfahrung ... Du kannst mit harter Arbeit alles wieder zurückerlangen!
+ Diese Aktion gilt nur während des zeitlich begrenzten Events. Das Event beginnt am %1$s (%2$s) und endet am %3$s (%4$s). Die Aktion gilt nur für Edelsteine welche du für dich selbst kaufst.
+ Zusammenfassung der Aufgaben
+ Dies ist eine **%1$s** Gewohnheit, die **%2$s** ist.
+ Verwende Fähigkeit
+
\ No newline at end of file
diff --git a/Habitica/res/values-es/strings.xml b/Habitica/res/values-es/strings.xml
index 5f3e5cc53..04dbe5489 100644
--- a/Habitica/res/values-es/strings.xml
+++ b/Habitica/res/values-es/strings.xml
@@ -1015,9 +1015,9 @@
Puedes realistarte al gremio desde el submenú \"Gremios > Descubre Gremios\"No podrás volver a unirte al equipo a no ser que te vuelvan a invitar.Disponible durante %s
- Los relojes de arena Místicos son una forma de moneda extremadamente rara que solo puedes recibir por suscribirte a Habitica durante tres meses consecutivos o más. Se utilizan en la tienda del Viajero del tiempo para comprar conjuntos de equipamiento, mascotas, monturas, fondos animados o incluso misiones especiales.
+ Los Relojes de arena Místicos son un tipo de divisa extremadamente raro que solo puedes recibir por suscribirte a Habitica. Se utilizan en la Tienda de los Viajeros del Tiempo para comprar Conjuntos de Equipamiento por Suscripción de meses pasados, Macostas exclusivas, Monturas, Fondos animados o incluso ¡Misiones especiales! Estos beneficios adicionales son una excelente manera de mantenerte motivado durante el año y gratificarte por un trabajo bien hecho.
\n
-\nPuedes recibir hasta cuatro elojes de arena Místicos al año. El tiempo que reciben las recompensas se basa en el calendario de renovación de su suscripción. Se envían el primer día de un nuevo mes después de su último pago de suscripción que lo calificó para un reloj de arena. Consulte la página [Suscripción] para obtener más detalles.
+\nLos suscriptores reciben un Reloj de arena Místico al principio de cada mes en el cual se disfruta de los beneficios de suscriptor, además de muchos otros beneficios. ¡Comprueba nuestras opciones de suscripción si estás interesado en lo que los Viajeros del Tiempo tienen para ofrecerte!Abandonar y borrar tareasDivisas premiumA veces un nuevo comienzo es la mejor opción, ¡Habitica puede ayudarte!
@@ -1618,4 +1618,5 @@
Próximo cambio en %sEstás de acuerdo con nuestros Terminos de Servicio y has leído nuestra Política de Privacidad.
+ Usar Habilidad
\ No newline at end of file
diff --git a/Habitica/res/values-fr/strings.xml b/Habitica/res/values-fr/strings.xml
index 0cf99b1cd..fec7ae59b 100644
--- a/Habitica/res/values-fr/strings.xml
+++ b/Habitica/res/values-fr/strings.xml
@@ -1620,4 +1620,10 @@
Prochaine sélection dans %sVous acceptez nos Conditions d\'Utilisation et avez lu notre Politique de Confidentialité.
+ Utiliser une Compétence
+ Vous pouvez ajouter une image sur votre profil Habitica qui pourra être vue par les autres en ajoutant un lien vers l\'image ici.
+ Enregistrer le lien vers la Photo
+ C\'est l\'adresse mail que vous utilisez pour vous connecter à Habitica, ainsi que pour recevoir des notifications.
+ Les noms d\'utilisat·eur·rices doivent comprendre entre 1 et 20 caractères, composés de lettres de A à Z, de numéros de 0 à 9, de trait d\'union et/ou de tiret bas.
+ Changer le Pseudo
\ No newline at end of file
diff --git a/Habitica/res/values-hu/strings.xml b/Habitica/res/values-hu/strings.xml
index 6a3a009cf..120be89d4 100755
--- a/Habitica/res/values-hu/strings.xml
+++ b/Habitica/res/values-hu/strings.xml
@@ -259,8 +259,8 @@
A csapat tájékoztatása az állapotrólCsapatprojekt befejezése%d küldetési tárgyat találtál
- A Megbűvölt komódban kutakodva élelmet találsz. Hogy került ez ide\?
- Megharcolsz a Megbűvölt komóddal és tapasztalatot szerzel. Ez az!
+ A Megbűvölt komódban kutakodva élelmet találtál. Hogy került ez ide\?
+ Megharcoltál a Megbűvölt komóddal és tapasztalatot szereztél. Ez az!Biztosan el akarod adni ezt: %s\?EladásCsapat meghívása
@@ -294,7 +294,7 @@
%d óra%d óra
- Találsz egy ritka felszerelést a Megbűvölt komódban!
+ Találtál egy ritka felszerelést a Megbűvölt komódban!%d nap%d nap
@@ -308,8 +308,8 @@
EsedékesElőfizetés lemondásaHabitica weboldal meglátogatása
- Előfizetett hónapok
- Havi gyémánt keret
+ hónapja előfizetve
+ havi gyémánt keretNem szeretnél tovább előfizetni\? A fizetési módod miatt az előfizetésedet az Apple ID előfizetési beállításain keresztül kell lemondanod az iOS eszközödön.Szekrény rendszerezése%d. szint
@@ -531,7 +531,7 @@
Ez a tárgy csak egy adott kaszt számára érhető el. Kasztot a 10. szint után választhatsz.Ez a tárgy csak %s számára érhető el. Kasztot a 10. szint után választhatsz.Törlés
- A készülékeden nem található egyetlen támogatott fizetési mód sem. Kérlek, használd a Habitica weboldalát, ha gyémántokat szeretnél vásárolni.
+ Az eszközödön nincs elérhető támogatott fizetési mód. Ha gyémántot szeretnél vásárolni, használd a Habitica weboldalát.Nem mentett változások%d aranyTöbb feladatot kell teljesítened, mielőtt megengedhetnéd magadnak ezt a tárgyat!
@@ -604,10 +604,10 @@
Üdv újra ittBejelentkezés a fogadóbaTöbb gyémántra lesz szükséged a vásárláshoz!
- A Habitica célja, hogy barátságos környezetet biztosítson minden korkaszt és háttér számára. Ha kérdésed van, kérlek, olvasd el az irányelveinket.
+ A Habitica arra törekszik, hogy minden korosztály és háttér előtt nyitott, barátságos környezetet teremtsen. Ha kérdésed van, olvasd el az irányelveinket.Homokórák beszerzéseZárd be a navigációs menüt
- A készülékeden nem található egyetlen támogatott fizetési mód sem. Kérlek, használd a Habitica weboldalát, ha előfizetést szeretnél vásárolni.
+ Az eszközödön nincs elérhető támogatott fizetési mód. Ha előfizetést szeretnél vásárolni, használd a Habitica weboldalát.A csevegésben megjelenő színes felhasználónevek a közreműködők rangját jelzik. Minél magasabb a rang, annál többet járult hozzá a játékhoz művészettel, kóddal, a közösség támogatásával vagy más módon!Küldetésindító jutalmaiFőellenség
@@ -644,7 +644,7 @@
Indította: %sTartalom újratöltveElérhető eddig: %s
- Még elérhető: %s
+ Elérhető eddig: %sHavi gyémántok: %1$d/%2$d maradtNem fogsz tudni újra csatlakozni ehhez a csapathoz, hacsak meg nem hívnak újra.Havi gyémántok: %d maradt
@@ -1203,7 +1203,7 @@
Válassz egy kasztot, amely illik a játékstílusodhoz, és oldj fel speciális képességeket és páncélokat, hogy segítsenek az utadonElküldtél egy %s kártyátRögzítés feloldása
- Vissza akarok menni
+ Vigyél visszaKitüntetésekÚj üzenet érkezett %1$s felhasználótól%1$d új üzenet érkezett %2$s felhasználótól
@@ -1233,7 +1233,7 @@
Szeretnéd folytatni az előnyeid élvezetét\? Indíthatsz új előfizetést, mielőtt a jelenlegi lejár, így folyamatosan aktív marad.AjándékozvaA(z) %d. szint elérésével oldható fel
- Fejezd be a(z) %d. küldetést
+ %d. küldetés után%d. szintNem veszel résztKüldetés teljesítve!
diff --git a/Habitica/res/values-in/strings.xml b/Habitica/res/values-in/strings.xml
index 1b51063f3..b13bf976a 100644
--- a/Habitica/res/values-in/strings.xml
+++ b/Habitica/res/values-in/strings.xml
@@ -429,12 +429,12 @@
Me-reset AkunMenghapus AkunKamu perlu menyelesaikan lebih banyak tugas sebelum bisa membeli item ini!
- Kamu perlu lebih banyak Permata untuk membeli item ini!
+ Anda memerlukan lebih banyak Permata untuk membeli ini!Beli permataKamu perlu lebih banyak Jam Pasir Mistis untuk membeli item ini!Dapatkan jam pasirBerlangganan untuk Jam Pasir
- Dapatkan satu Jam Pasir Mistis setiap tiga bulan berturut-turut kamu berlangganan, lalu gunakan untuk membuka item-item edisi terbatas, peliharaan, dan tunggangan dari masa lalu… dan masa depan!
+ Dapatkan satu Mystic Hourglass untuk setiap bulan berlangganan, lalu gunakan untuk membuka item edisi terbatas, hewan peliharaan, dan tunggangan dari masa lalu… dan masa depan!Aku mau BerlanggananGrand Gala diadakan dekat dengan titik balik matahari dan ekuinoks, jadi cek kembali nanti untuk menemukan sekumpulan seru item musiman yang spesial!Datang kembali nanti!
@@ -486,12 +486,12 @@
PerlengkapanKamu sudah punya semua perlengkapan! Lebih banyak lagi tersedia sewaktu Grand Gala, dekat titik balik matahari dan ekuinoks.Tanpa Pekerjaan
- Item ini hanya tersedia untuk pekerjaan tetentu. \nKamu bisa menggantu pekerjaanmu dari Pengaturan.
+ Item ini hanya tersedia untuk kelas tertentu. Anda dapat mengubah kelas Anda dari Pengaturan.Kamu hanya bisa membeli perlengkapan untuk pekerjaanmu yang sekarangMasuk ke PenginapanPedoman KomunitasLihat Pedoman Komunitas
- Habitica mengusahakan untuk membuat sebuah lingkungan yang ramah untuk pengguna segala usia dan latar belakang, khususnya di tempat umum seperti Kedai Minuman. Jika kamu ada pertanyaan, silahkan periksa pedoman kami.
+ Habitica berupaya menciptakan lingkungan yang ramah bagi pemain dari segala usia dan latar belakang. Jika Anda memiliki pertanyaan, silakan baca panduan kami.Tautan BergunaLihat FAQLaporkan Gangguan
@@ -507,7 +507,7 @@
Apakah itu Tingkatan Kemarahan?Sang %s telah Diserang!%1$s kita yang tercinta hancur sewaktu sang %2$s meremukkan %3$s. Cepat, kalahkan tugas-tugasmu untuk mengalahkan sang monster dan bantu membangun kembali!!
- Toko Misi
+ PencarianAlex sang PedagangDaniel sang penjaga penginapanMatt sang penguasa hewan buas
@@ -651,13 +651,13 @@
Nama pengguna harus sesuai dengan Syarat Layanan dan Pedoman Komunitas. Jika kamu sebelumnya belum membuat nama masuk, nama penggunamu akan dibuat otomatis.Semua karakter di Habitica memiliki 4 status (stat) yang berpengaruh pada permainan di Habitica.
\n
-\n**Kekuatan (KUAT)** berpengaruh pada pukulan kritikal dan menaikkan dampak kerusakan pada Misi Boss. Prajurit dan Pencuri mendapat KUAT dari perlengkapan kelas mereka.
+\n**Kekuatan (STR)** berpengaruh pada pukulan kritikal dan menaikkan dampak kerusakan pada Boss misi. Profesi Prajurit dan Pencuri mendapat STR dari perlengkapan profesi mereka.
\n
-\n**Ketahanan (THN)** berpengaruh pada kenaikan HP dan membuatmu lebih tahan serangan. Penyembuh dan Prajurit mendapat THN dari perlengkapan kelas mereka.
+\n**Ketahanan (CON)** berpengaruh pada kenaikan HP dan membuatmu lebih tahan serangan. Profesi Penyembuh dan Prajurit mendapat CON dari perlengkapan profesi mereka.
\n
-\n**Kecerdasan (CRDS)** berpengaruh pada kenaikan EXP yang didapat dan memberimu lebih banyak Mana. Penyihir dan Penyembuh mendapat CRDS dari perlengkapan kelas mereka.
+\n**Kecerdasan (INT)** berpengaruh pada kenaikan EXP yang didapat dan memberimu lebih banyak Mana. Profesi Penyihir dan Penyembuh mendapat INT dari perlengkapan profesi mereka.
\n
-\n**Persepsi (PERS)** berpengaruh pada banyaknya emas yang didapat dan seberapa sering mendapat item yang dijatuhkan. Pencuri dan Penyihir mendapat PERS dari perlengkapan kelas mereka.
+\n**Persepsi (PER)** berpengaruh pada banyaknya emas yang didapat dan seberapa sering mendapat item yang dijatuhkan. Profesi Pencuri dan Penyihir mendapat PER dari perlengkapan profesi mereka.
\n
\nSetelah mencapai level 10, kamu mendapat 1 Poin Stat pada setiap kenaikan level yang dapat kamu alokasikan pada stat yang kamu mau. Kamu juga bisa memakai perlengkapan dengan kombinasi kenaikan stat yang berbeda-beda.Pergi ke tab Temukan untuk mencari yang lain untuk bergabung!
@@ -692,13 +692,13 @@
Sesekali evaluasi kembalilah tugasmu agar kamu tetap di jalur yang benar.Kami secara konsisten berusaha agar aplikasi kami lebih baik berdasarkan feedback dari pemain namun terkadang beberapa bug muncul…Hubungi Kami
- Masih ingin melanjutkan keuntunganmu\? Kamu dapat memulai berlangganan baru sebelum masa berlangganan ini selesai agar keuntunganmu tetap aktif.
+ Masih ingin melanjutkan keuntunganmu\? Kamu dapat memulai langganan baru sebelum masa berlangganan ini selesai agar keuntunganmu tetap aktif.Apakah kamu yakin ingin mengeluarkan %s dari grup\?Ini akan membuat %s sebagai pemimpin baru Party
- Memindahkan Kepimpinan\?
+ Memindahkan Kepemimpinan\?Konfirmasi penghapusanApakah kamu ingin tetap berpartisipasi dalam Tantangan saat meninggalkan Perkumpulan\?
- Apakah kamu yakin ingin keluar dari perkumpulan ini\?
+ Apakah kamu yakin ingin keluar dari guild ini\?Kamu mengirimkan %1$s berlangganan %2$s-bulan Habitica.Kamu mengirimkan %1$s sebuah berlangganan %2$s-bulan dan berlangganan yang sama telah diterapkan pada akunmu melalui promo kami Beri Satu Dapat Satu!Update tersedia: %1$s (%2$d)
@@ -736,9 +736,9 @@
Kerusakan ditundaTelusuriPilihlah paket permata yang ingin kamu hadiahkan!
- Pilihlah berlangganan yang ingin kamu berikan dibawah! Pembelian ini tidak akan diperbarui secara otomatis.
+ Pilih langganan yang ingin Anda berikan sebagai hadiah! Pembelian ini tidak akan diperpanjang.Selama promosi ini aktif, kamu akan mendapatkan berlangganan yang sama secara otomatis setelah kamu memberikannya.
- Berikan satu ,Dapatkan satu!
+ Hadiah satu, Dapatkan satuKepada siapa ingin kamu hadiahkan\?Even beri satu berlangganan gratis satu berlangganan sedang berlangsung!Hadiahkan Berlangganan
@@ -746,10 +746,10 @@
Anggota GuildRingkasan GuildAtau hapus ini dari layar edit
- Sentuh disini untuk mengedit ini menjadi kebiasaan buruk yang ingin kamu hentikan
+ Sentuh disini untuk mengubah ini menjadi kebiasaan buruk yang ingin kamu tinggalkanApakah itu Kebiasaan, Harian, atau To DoTambahkan tugas ke Habitica
- Menggunakan
+ biayaKamu bergabung dengan guildKamu meninggalkan guildKamu dapat bergabung kembali dengan guild ini melalui halaman Telusuri Guild
@@ -765,30 +765,30 @@
Butuh bantuan lebih lanjut\?Post sebuah pesan di %s agar pertanyaanmu dapat dijawab oleh pemain lain.Dari Saldo
- Membeli
+ Beli\@Mentions di PartyDapatkan yang lebih banyak lagi di Habitica
- Habitica tidak akan pernah meminta kamu untuk memberikan permata kepada pemain lain. Mengemis orang lain agar diberi permata adalah tindakan pelanggaran pada Pedoman Komunitas dan akan dilaporkan pada admin@habitica.com.
- Ingin menganugerahi Permata berkilauan kepada orang lain\?
- Memberikan
-\nBerlangganan
- Memberikan
+ Habitica tidak akan pernah mengharuskan kamu untuk memberi permata kepada pemain lain. Mengemis permata ke orang ain adalah tindakan pelanggaran pada Pedoman Komunitas dan akan dilaporkan pada admin@habitica.com.
+ Ingin memberikan Permata berkilauan kepada orang lain\?
+ Hadiahkan
+\npaket langganan
+ Hadiahkan
\nPermata
- Mengirimkan
+ Kirim
\nPesan
- Memberikan Permata
- Lihat opsi lainnya
- Jadilah Berlangganan untuk membeli Permata dengan emas, mendapatkan item misteri bulanan, meningkatkan drop caps dan masih banyak lagi!
+ Hadiahkan Permata
+ Lihat pilihan lainnya
+ Jadilah pelanggan untuk membeli Permata dengan emas, mendapatkan item misteri bulanan, tingkatkan drop caps dan masih banyak lagi!Butuh Permata\?Aktifitas Party
- Mencari tugas
+ cari tugasCariSelesai
- Menolak
- Menerima
- Menggunakan otomatis perlengkapan baru
- Meninggalkan Tantangan dan Menghapus Tugas
- Meninggalkan Tantangan
+ Tolak
+ Terima
+ Otomatis gunakan perlengkapan baru
+ Tinggalkan Tantangan dan hapus Tugas
+ Tinggalkan TantanganMenyimpan TantanganKamu mengundang seorang teman (atau banyak teman) untuk bertualang bersamamu!Mengundang Teman
@@ -806,26 +806,26 @@
Party OnKamu bergabung dengan anggota party!Party Up
- Kamu mendapatkan Pencapaian!
- Melihat Pencapaian
+ Kamu mendapatkan sebuah Pencapaian!
+ lihat PencapaianLihat apa yang baru
- Kamumembuka kotak dan menerima %s
- %s telah dibuang dari grup
+ Kamu membuka kotak dan menerima %s
+ %s telah dikeluarkan dari grupMemindahkan
- Kepimpinan dipindahkan ke %s
+ Kepemimpinan dipindahkan ke %sMengirim PesanKeluarkan Anggota
- Memindahkan Kepimpinan
- Membuang
+ serahkan Kepemimpinan
+ hapus%d pesan baru%1$d Pesan Baru dari %2$sPesan Baru dari %1$sKamu mengirimkan sebuah %sTema App
- Membagikan Tantangan kepada
+ bagikan Tantangan denganDetail
- Berpindah ke grid view
- Berpindah ke list view
+ Berganti ke grid view
+ berganti ke list viewPencapaian SpesialPencapaian MusimanPencapaian Dasar
@@ -833,37 +833,37 @@
Bawa aku KembaliHapus pinPin
- Sistem Pekerjaan Terbuka!
- Pilihlah kelas yang sesuai dengan gaya permainanmu dan buka kemampuan khusus dan baju pelindung untuk membantumu di perjalanan
+ Sistem kelas Terbuka!
+ Pilihlah kelas yang sesuai dengan gaya permainanmu dan buka skill spesial dan baju pelindung untuk membantu di perjalananmuTidak sekarang
- Menghapus Pengingat
- Menghapus Tugas
- Mengundang ke Party
+ hapus Pengingat
+ hapus Tugas
+ undang ke PartyBank GuildSaya setuju untuk mengikuti pedomanTamanBergabung dengan GuildMengundang ke GuildDeskripsi Guild
- Sentuh \"Memberikan Berlangganan\" dan ketik akun nama pengguna yang kamu berikan. Kemudian, pilih jangka waktu berlangganan yang ingin kamu berikan dan bayar. Akunmu secara otomatis akan diberikan hadiah berlangganan yang sama.
+ Sentuh \"Hadiahkan langganan\" dan ketik nama pengguna akun yang ingin kamu hadiahkan. Kemudian, pilih jangka waktu langganan yang ingin kamu hadiahkan dan bayar. Akunmu secara otomatis akan diberikan hadiah langganan yang sama.Bagaimana cara kerjanya
- Berikan Berlangganan
- Sebagai penghormatan musim pemberian ini kami menghadirkan kembali sebuah promosi yang sangat spesial. Sekarang ketika kamu memberikan seseorang sebuah berlangganan, kamu akan mendapatkan berlangganan yang sama secara cuma-cuma!
+ Hadiahkan langganan
+ Sebagai penghormatan musim pemberian ini kami menghadirkan kembali sebuah promosi yang sangat spesial. Sekarang ketika kamu memberikan seseorang sebuah langganan, kamu akan mendapatkan langganan yang sama secara cuma-cuma!DiskonEvent Beri Satu, Dapat Satu
- Berikan berlangganan sekarang dan dapatkan berlangganan yang sama gratis!
+ Berikan langganan sekarang dan dapatkan langganan yang sama dengan gratis!EventAnggota Rencana GrupAnggotaPemilik
- Kamu memiliki berlangganan gratis karena kamu adalah pemilik Party atau Perkumpulan yang memiliki Rencana Grup. Ini dapat berakhir jika kamu membatalkan rencana tersebut. Bulan berlangganan ekstra yang tersisa yang kamu punya akan dimasukkan ke dalam Rencana Grup. Kamu dapat membatalkannya melalui situs Habitica.
- Kamu memiliki berlangganan gratis karena kamu adalah anggota dari Party atau Perkumpulan yang mempunyai Rencana Grup. Ini dapat dibatalkan jika kamu meninggalkan grup atau Rencana Grup dibatalkan oleh sang pemilik. Bulan ekstra berlangganan tersisa yang kamu punya akan dimasukkan pada akhir Rencana Grup.
+ Kamu memiliki langganan gratis karena kamu adalah pemilik Party atau Guild yang memiliki Rencana Grup. Langganan dapat berakhir jika kamu membatalkan rencana tersebut. Bulan-bulan langganan milikmu yang tersisa akan dimasukkan ke dalam Rencana Grup. Kamu dapat membatalkannya melalui situs Habitica.
+ Kamu memiliki langganan gratis karena kamu adalah anggota dari Party atau Guild yang mempunyai Rencana Grup. Langganan dapat dibatalkan jika kamu meninggalkan atau Rencana Grup dibatalkan oleh pemilik. Bulan-bulan ekstra dari langganan milikmu yang tersisa akan dimasukkan pada akhir Rencana Grup.Rencana Grup
- Kredit Berlangganan
+ Kredit langgananHemat 20%
- Telah diberikan
+ Telah dihadiahkanBerlangganan Kembali
- Memperbarui Berlangganan
+ perbarui langgananTerima kasih telah berlanggananPilih masa Berlangganan yang kamu inginkanBerakhir pada %s
@@ -879,7 +879,7 @@
%1$s mengundangmu untuk bergabung dengan grup %2$sKata sandimu harus setidaknya %d karakterSalin Nama Pengguna
- Salin ID Pengguna ke clipboard
+ ID Pengguna di salin ke clipboardSalin ID Pengguna\@Mentions di Guild yang tidak diikuti\@Mentions di Guild yang diikuti
@@ -914,7 +914,7 @@
Pelajari lebih lanjut17 Des sampai 6 JanEvent terbatas
- Promo ini hanya berlaku jika kamu telah menghadiahkan pada Habitican lain. Jika kamu atau penerima hadiah telah berlangganan, maka hadiah berlangganan akan ditambahkan pada kredit yang hanya akan digunakan setelah masa berlangganan saat ini selesai atau dibatalkan.
+ Promo ini hanya berlaku jika kamu telah menghadiahkan pada Habitican lain. Jika kamu atau penerima hadiah telah berlangganan, maka hadiah langganan akan menambahkan kredit bulan yang hanya akan digunakan setelah masa berlangganan saat ini selesai atau dibatalkan.BatasanKamu harus berada di dalam suatu Party sebelum memulai MisiParty-nya %s
@@ -928,18 +928,18 @@
\n**Pulihkan Nyawa** dengan naik level, menggunakan Ramuan Penyembuh, atau dengan kemampuan penyembuh.
\n
\nJika **Nyawa kamu sampai 0** kamu akan kehilangan satu level, semua Koin Emas, dan sebuah perlengkapan. Perlengkapan yang hilang bisa dibeli lagi nanti.
- Poin Pengalaman / EXP merepresentasikan progressmu dan mempebolehkanmu untuk naik level. Kamu pada intinya akan **mendapatkan EXP** dengan menyelesaikan tugas atau misi, namun ada beberapa kelas yang dapat memberikan EXP.
+ Poin Pengalaman / EXP merepresentasikan progressmu dan mempebolehkanmu untuk naik level. Kamu pada intinya akan **mendapatkan EXP** dengan menyelesaikan tugas atau misi, namun ada beberapa profesi yang dapat memberikan EXP.
\n
\nTugas dengan kesulitan yang lebih tinggi, atau yang diwarnai merah akan memberikanmu **EXP lebih banyak**. **Stat Kecerdasan** yang kamu miliki juga berpengaruh pada pemerolehan EXP.Koin Emas adalah **mata uang utama** dalam Habitica dan dapat kamu gunakan untuk membeli perlengkapan, misi, items, atau bahkan hadiah custom yang kamu buat sendiri.
\n
-\n**Koin Emas diperoleh** dengan cara menyelesaikan tugas atau misi, atau melalui beberapa skill Kelas. **Stat Persepsi (Perception)** yang lebih tinggi membuatmu memperoleh Emas lebih banyak.
+\n**Peroleh koin emas** dengan cara menyelesaikan tugas atau misi, atau melalui beberapa skill profesi. **Stat Persepsi (Perception)** yang lebih tinggi membuatmu memperoleh lebih banyak emas.
\n
-\nJika kamu berlangganan Habitica, maka kamu bahkan dapat menggunakan Koin Emas untuk membeli Permata yang jumlahnya bergantung dengan seberapa lama kamu telah berlangganan.
+\nJika kamu berlangganan ke Habitica, maka kamu bahkan dapat menggunakan Koin Emas untuk membeli Permata yang jumlahnya bergantung dengan seberapa lama kamu telah berlangganan.Poin Mana
- Poin Mana (MP) akan terbuka dengan sistem kelas pada level 10 dan membuatmu dapat **menggunakan Kemampuan** setelah kamu mempelajarinya pada level 11.
+ Poin Mana (MP) akan terbuka dengan sistem profesi pada level 10 dan membuatmu dapat **menggunakan skill** setelah kamu mempelajarinya pada level 11.
\n
-\nBeberapa **MP terisi** setiap pergantian hari, tetapi kamu dapat memperoleh lebih banyak lagi dengan menyelesaikan tugas atau menggunakan Kemampuan Penyihir.
+\nBeberapa **MP terisi** setiap pergantian hari, tetapi kamu dapat memperoleh lebih banyak dengan menyelesaikan tugas atau menggunakan skill profesi Penyihir.Alokasi StatStandarMata Uang Premium
@@ -960,11 +960,11 @@
Memilih ProfesiLv. %d
- rb
- jt
+ K
+ mApakah kamu mau mengganti profesimu ke %1$s dengan %2$d permata\?Beli Semua
- mil
+ bPerbolehkan notifikasi Habitica di aplikasi Pengaturan untuk menerima notifikasiPerbolehkan notifikasi Habitica di aplikasi Pengaturan untuk menerima pengingatNotifikasi Dinonaktifkan
@@ -996,4 +996,224 @@
Tidak ingin berlangganan lagi\? Karena metode pembayaran Anda, Anda harus berhenti berlangganan melalui pengaturan langganan ID Apple di perangkat iOS Anda.Perubahan yang belum disimpanGunakan Item pada anggota party>
-
\ No newline at end of file
+ Apakah Anda yakin ingin meninggalkan pesta ini\?
+ Anda tidak akan dapat bergabung kembali dengan kelompok ini kecuali diundang.
+\n
+\nMeninggalkan kelompok juga akan membuat Anda meninggalkan misi yang sedang Anda jalani.
+ Anda tidak akan dapat bergabung kembali dengan kelompok ini kecuali diundang.
+\n
+\nMeninggalkan kelompok juga akan membatalkan misi saat ini.
+ misalnya, habitrabbit atau gryphon@example.com
+ Anda akan kehilangan semua level, Emas, dan Pengalaman. Semua tugas dan data historisnya akan dihapus (tugas Tantangan akan tetap ada). Anda akan kehilangan semua peralatan, kecuali item Pelanggan dan item peringatan gratis, tetapi Anda akan dapat membelinya kembali. Anda harus memiliki kelas yang tepat untuk membeli kembali perlengkapan khusus kelas. Anda akan tetap memiliki kelas, Prestasi, dan Hewan Peliharaan serta Tunggangan Anda saat ini. Untuk mengonfirmasi pengaturan ulang, ketik kata sandi Anda di bawah ini.
+ Anda akan kehilangan semua level, Emas, dan Pengalaman. Semua tugas dan data historisnya akan dihapus (tugas Tantangan akan tetap ada). Anda akan kehilangan semua peralatan, kecuali item Pelanggan dan item peringatan gratis, tetapi Anda akan dapat membelinya kembali. Anda harus memiliki kelas yang tepat untuk membeli kembali perlengkapan khusus kelas. Anda akan tetap memiliki kelas, Prestasi, dan Hewan Peliharaan serta Tunggangan Anda saat ini. Untuk mengonfirmasi pengaturan ulang, ketik RESET di bawah.
+ Item ini hanya tersedia untuk %s. Buka Pengaturan untuk mengubah kelas.
+ Apakah Anda yakin ingin mengatur ulang\?
+ Apakah Anda yakin ingin membuang perubahan pada tugas ini\?
+ Pengiriman Jam Pasir Berikutnya
+ Konfirmasi pengaturan ulang
+ Mengapa Anda melaporkan pemain ini\?
+ %s Dilaporkan
+ Laporan gagal, silakan coba lagi nanti
+ <font color=#925CF3>%2$s</font> telah mengundang Anda untuk bergabung dengan grup <b>%1$s</b>
+ Mengapa Anda melaporkan tantangan ini\?
+ Laporan Pemutar
+ Laporkan %s\?
+ %1$s
+\n@%2$s
+ Anda dapat menghubungi kami dan anggota tim kami akan melakukan yang terbaik untuk membantu!
+ Anda hanya boleh melaporkan pemain yang melanggar [Pedoman Komunitas](https://habitica.com/static/community-guidelines) dan/atau [Persyaratan Layanan](https://habitica.com/static/terms). Mengirimkan laporan palsu merupakan pelanggaran terhadap Pedoman Komunitas Habitica.
+ Item ini hanya tersedia untuk %s. Anda dapat memilih kelas setelah level 10.
+ Anda hanya boleh melaporkan kiriman yang melanggar [Pedoman Komunitas](https://habitica.com/static/community-guidelines) dan/atau [Persyaratan Layanan](https://habitica.com/static/terms). Mengirimkan laporan palsu merupakan pelanggaran terhadap Pedoman Komunitas Habitica.
+ Anda hanya boleh melaporkan tantangan yang melanggar [Pedoman Komunitas](https://habitica.com/static/community-guidelines) dan/atau [Persyaratan Layanan](https://habitica.com/static/terms). Mengirimkan laporan palsu merupakan pelanggaran terhadap Pedoman Komunitas Habitica.
+ Laporkan Pesan
+ Anda menyetujui Persyaratan Layanan kami dan telah membaca Kebijakan Privasi kami.
+ Alasan laporan
+ Mengapa Anda melaporkan pesan ini\?
+ Ini juga akan memblokir @%s
+ %d Checkin
+ Item ini hanya tersedia untuk kelas tertentu. Anda dapat memilih kelas setelah level 10.
+ Sesuaikan Penghitung
+ Setel Ulang Penghitung
+ Anda memperoleh %s permata.
+ Anda mengirim @%1$s %2$s permata.
+ Anda sekarang berlangganan selama 1 bulan
+ Anda sekarang berlangganan selama %s bulan
+ Mystic Hourglass adalah mata uang yang amat sangat langka yang hanya dapat kamu peroleh melalui berlangganan Habitica selama 3 bulan beruntun atau lebih. Kamu dapat menggunakannya di toko Penjelajah Waktu untuk membeli perlengkapan langganan yang telah lampau, peliharaan dan tunggangan eksklusif, background animasi, atau bahkan misi spesial! Keuntungan ekstra ini adalah cara yang bagus untuk tetap termotivasi sepanjang tahun dan menghadiahi dirimu untuk menyelesaikan tugas dengan baik.
+\n
+\nPelanggan akan menerima Mystic Hourglass pada awal setiap bulan, dengan banyak keuntungan lainya. Pastikan untuk melihat pilihan langganan kami jika kamu tertarik dengan apa yang penjelajah waktu tawarkan!
+ Terkadang aplikasi tidak memperbarui konten secara otomatis. Coba tarik untuk menyegarkan atau menutup paksa aplikasi dan membukanya kembali.
+ Tugas ini adalah satu dari %1$d tugas dari Tantangan \"%2$s\". Kamu harus meninggalkan Tantangan untuk menghapus tugas ini.
+ Kamu masih dapat mengirimkan pesan, namun orang lain tidak mengirimkan pesan padamu. Kamu bisa mengaktifkan pesan kembali dari Pengaturan.
+ Gala Musim Gugur sedang berlangsung jadi kami pikir ini adalah waktu yang tepat untuk Diskon Permata! Sekarang kamu dapat memperoleh lebih banyak Permata daripada sebelumnya dalam setiap pembelian.
+
+ setiap %d bulan
+
+ Selesaikan target-target ini dan kamu akan mendapatkan <b>5 Pencapaian</b> dan <font color=#EE9109><b>100 Emas</b></font> setelah kamu selesai!
+ Ayo mulai
+ Menetaskan peliharaan
+ Memberi makan peliharaan
+ Ada banyak sekali Peliharaan untuk dikoleksi, kamu pasti punya satu yang favorit. Jika kamu beri makan, mereka bisa saja bertumbuh…
+ Pergi ke Item milikmu dan cobalah mengombinasikan telur barumu dengan Ramuan Penetas!
+ laporkan%s
+ Pelanggan menerima jam pasir mistik pada tiga hari pertama setiap bulan.
+ Ingin terus menerima keuntungan langganan\? Kamu dapat memulai langganan berulang untuk menjaga keuntunganmu aktif.
+ Pertanyaan Habitica
+ Tidak dapat menemukan pemain
+ Ikat kepala
+ Diskon Permata kembali menghantui hingga akhir Gala Musim Gugur tahun ini! Ini adalah kesempatan terakhir untuk mendapat Permata lebih banyak dari biasanya, jadi timbun sebanyak-banyaknya selagi bisa!
+ Buat Guild
+ Efeknya langsung berlaku setelah pembelian!
+ Centang tugas manapun untuk memperoleh hadiah
+ Lanjutkan! Jika kamu perlu bantuan untuk merencanakan tugas, coba pikirkan hal apa yang ingin kamu lakukan pada waktu tertentu dalam sehari
+ Lihat Tugas Pengenalan
+ Blokir
+ Akhiri Tantangan
+ Untuk mengakhiri Tantangan, masuk ke situs Habitica kemudian ketuk tombol \"Akhiri Tantangan\" pada layar Tantangan di sebelah kanan.
+ Kamu memenangkan Tantangan
+ Buka Situs
+ Setiap Peliharaan punya satu jenis makanan yang mereka sukai! Bereksperimenlah untuk mencari tahu mana yang tercepat menumbuhkan Peliharaanmu
+ Menyelesaikan tugas memberimu kesempatan untuk memperoleh telur, ramuan penetas, dan makanan peliharaan.
+ %1$s ke %2$s
+ Antara %1$s dan %2$s, cukup beli paket Permata mana saja dan akumu akan dikirimkan Permata dengan jumlah permata promosi. Semakin banyak Permata untuk digunakan, dibagikan, atau disimpan untuk kebutuhan mendatang!
+ Sinkronisasi manual atau mulai ulang
+ Perbarui Aplikasi
+ Tetaskan Peliharaanmu
+ Bagaimana cara kerjanya
+ Selesai
+ Kamu
+ Tampilan daftar Tugas
+ Gunakan kemampuan
+ Kami terus merilis perbaikan baru, jadi pastikan untuk memeriksa Play Store untuk melihat apakah ada pembaruan yang tersedia.
+ Gunakan pelana
+ Hapus Tugas Tantangan\?
+ Tinggalkan & Hapus Tugas
+ Kamu tidak dapat membeli sebanyak itu.
+ Masih ada pertanyaan\?
+ Beri makan peliharaan
+ Sebuah tugas bisa berupa Kebiasaan, harian, atau To do. Lanjutkan menyelesaikan mereka untuk mendapatkan berbagai hadiah!
+ Lihat pilihan berlangganan lainnya
+ Keuntungan berakhir pada %s
+ Dukungan
+ Kamu membutuhkan sebuah %1$s dan ramuan %2$s untuk menetaskan hewan peliharaan ini lagi
+ Tetaskan Peliharaan
+ Ramuan Ajaib
+ Ramuan Ajaib
+ Tugas ini adalah satu dari %d Tugas yang merupakan bagian dari Tantangan yang sudah tidak lagi aktif. Apa yang ingin kamu lakukan pada Tugas ini\?
+ Pesan Pribadi dinonaktifkan
+ Promosi ini hanya berlaku pada waktu event terbatas. event ini mulai pada %1$s (%2$s) dan akan berakhir pada %3$s (%4$s). tawaran promosi hanya tersedia ketika membeli permata untukmu sendiri.
+ Dengan mendaftar, kamu menyatakan bahwa kamu telah membaca dan menyetujui Persyaratan Layanan dan Kebijakan Privasi.
+ Kamu diundang untuk bergabung dengan Party
+ Mengembalikan semua tugas ke nilai netral (warna kuning), dan mengembalikan semua Nyawa yang hilang
+ Selamat!
+ %s memilihmu sebagai pemenang! Kemenanganmu telah tersimpan di Pencapaian.
+
+ %d barang tertunda
+
+ Ini dalah event waktu terbatas yang mulai pada %1$s dan akan berakhir pada %2$s. promosi ini hanya berlaku ketika kamu menghadiahkan ke pemain Habitica lain. Jika kamu atau penerima hadiahmu sudah mempunyai langganan,hadiah langganan akanditambahkan dlam bentuk kredit bulanan yang hanya akan digunakan setelah langganan saat ini dibatalkan atau kadaluarsa.
+ bersihkan Cache
+ Untuk membersihkan cache, buka Setelan di ponsel. Buka Penyimpanan > App > Habitica, lalu ketuk Hapus Cache.
+ Ekor Hewan
+ Hapus…
+ Mulai
+ Tinggalkan & Hapus %d Tugas
+ Tantangan gagal
+ Pertanyaan umum
+ %1$d / %2$d
+ Kamu masih membutuhkansebuah ramuan %s untuk menetaskan hewan peliharaan ini
+ Mata Uang Pelanggan
+ Tugas Pengenalan
+ Ketuk \"Hadiahkan fitur Langganan\" dan ketik nama pengguna akun yang ingin kamu berikan. Kemudian, pilih jangka masa langganan yang ingin kamu berikan dan bayar. Akunmu secara otomatis akan diberikan hadiah langganan yang serupa.
+
+ setiap %d hari
+
+ Aksesoris
+ Selesaikan untuk memperoleh <font color=#EE9109><b>100 Koin Emas</b></font>!
+ %d%% Terselesaikan
+ Tetaskan peliharaan baru
+ Perlengkapan adalah sebuah cara untuk mengkustomisasi avatarmu dan meningkatkan stat
+ Tugas pertamamu dibuat
+ menyelesaikan tugas
+ Perlengkapan dapat berguna atau hanya sebagai penampilan semata. Tingkatkan stat milikmu untuk mendapatkan berbagai macam keuntungan pada avatarmu
+ Jika kamu menginginkan lebih banyak, lihat bagian Pencapaian dan mulailah mengumpulkannya!
+ Kamu menyelesaikan Tugas Pengenalanmu!
+ Item Berlebih
+ Beli %d
+ Pakai
+ Lepaskan
+ Kamu masih membutuhkan sebuah telur %s untuk menetaskan hewan peliharaan ini
+ Kamu membutuhkan sebuah %1$s dan ramuan %2$s untuk menetaskan hewan peliharaan ini
+ Kamu masih membutuhkan sebuah telur %s untuk menetaskan hewan peliharaan ini lagi
+ Kombinasikan Telur %1$s-mu dan Ramuan %2$s untuk menetaskan peliharaan ini!
+ Peliharaan yang Belum Ditetaskan
+ Tetaskan Peliharaan lagi
+ Antara %1$s dan %2$s, cukup beli paket Permata mana saja dan akumu akan dikirimkan Permata dengan jumlah promosi permata! Semakin banyak Permata untuk digunakan, dibagikan, atau disimpan untuk kebutuhan mendatang!
+ Pemain yang terblokir tidak dapat mengirimkan Pesan Pribadi kepadamu tapi kamu masih tetap bisa melihat postingan mereka.
+ Tetaskan
+ Simpan %d Tugas
+ Tugas Tantangan %s
+ Peliharaan %s
+ Tunggangan %s
+ Blokir Pemain
+ Buka Blokir Pemain
+ Blokir %s\?
+ Pemain yang terblokir tidak dapat mengirimkan Pesan Pribadi kepadamu tapi kamu masih tetap bisa melihat postingan mereka.
+ Permata adalah mata uang yang dibeli dengan uang asli yang membolehkan kamu untuk membeli konten ekstra di Habitica dan adalah salah satu sumber dukungan finansial untuk tim Habitica disamping paket langganan.
+\n
+\nSemua Konten yang dibeli dengan Permata adalah murni fitur kosmetik atau bisa di peroleh secara gratis seiring waktu.
+\n
+\nkamu juga bisa menerima Permata dari hadiah yang diberikan pemain lain, hadiah tantangan, berkontribusi ke Habitica, atau berlangganan.
+ Telinga Hewan
+
+ setiap %d minggu
+
+ Beli Perlengkapan
+ Kamu mendapatkan <b>5 Pencapaian</b> dan <font color=#EE9109><b>100 Koin Emas</b></font> atas usahamu.
+ Untuk membuat Guild, masuk ke situs Habitica dan ketuk tombol \"Buat\" pada layar \"Guild Saya\".
+ Hore!
+ Selesaikan tugas untuk mendapatkan makanan! Kamu dapat memberinya ke peliharaanmu dari Peliharaan & Tunggangan
+ Selesaikan tugas untuk mendapatkan Ramuan Penetas dan Telur kemudian tetaskan peliharaanmu!
+ Ramuan Penguat
+ Hapus %d Tugas
+ Mekanika Game
+ Tambahkan tugas untuk hal yang ingin kamu tuntaskan pekan ini
+ Kamu hanya perlu %1$d %2$s untuk menetaskan semua hewan peliharaan yang mungkin. Apakah kamu yakin ingin membeli %3$d\?
+ Klaim %d Permata
+ Baca Lebih Lanjut
+ Nonaktifkan Pesan Pribadi
+ Biasanya %d Permata
+ Kamu sudah memiliki semua yang kamu butuhkan untuk semua hewan peliharaan %1$s. Apakah kamu yakin ingin membeli %2$d %3$ss\?
+ Perlengkapan dibeli
+ Aneh
+
+ %d orang
+
+ Kemajuanmu
+ Buka Item
+ Kamu menemukan item baru!
+ Kamu masih membutuhkan sebuah ramuan %s untuk menetaskan hewan peliharaan ini lagi
+ Batasan
+ Lihat Paket Permata
+ Mode Gelap
+ Mode Tema
+ Warna Tema
+ Kirimkan Umpan Balik
+ Kamu memblokir pemain ini
+ Kamu terpilih sebagai pemenang! Kemenanganmu telah tersimpan di Pencapaian.
+ Untuk menghormati musim pemberian kami menghadirkan kembali sebuah promosi yang sangat spesial. Sekarang kapanpun kamu memberikan seseorang langganan, kamu akan dapat langganan yang serupa secara cuma-cuma!
+
+ setiap %d tahun
+
+ 12 bulan
+ Apakah kamu yakin kamu mau meninggalkan Misi ini\? Kamu tidak akan bisa berpartisipasi lagi.
+ Baru
+ Layar Utama
+ Tidak bisa menyukai pesanmu sendiri
+ Apakah kamu yakin kamu mau meninggalkan misi\? Semua kemajuan misimu akan hilang.
+ Tim
+ Informasi Tim
+ Kamu membuka Kotak Misteri dan menemukan…
+ 3 bulan
+ 1 bulan
+ hadiahkan langganan dan dapatkan langganan secara gratis sampai %s
+ 6 bulan
+
\ No newline at end of file
diff --git a/Habitica/res/values-iw/strings.xml b/Habitica/res/values-iw/strings.xml
index 674dc0472..d119f02cd 100644
--- a/Habitica/res/values-iw/strings.xml
+++ b/Habitica/res/values-iw/strings.xml
@@ -192,16 +192,16 @@
שנו מקצועבאמצעות אימיילהזמינו משתמשים קיימים
- שילחו
- חפשו משתתפים
- אם חברים מצטרפים להביטיקה באמצעות הזמנת אימייל מכם, הם יקבלו הזמנה לחבורה שלכם באופן אוטומטי!
- הוסיפו מוזמנים
- אימייל
- שתפו באמצעות
- בדיוק הבקעתי ביצת חיית מחמד של %1$s %2$s בהביטיקה על ידי השלמת משימות מהחיים האמיתיים!
- בדיוק זכיתי ב%1$s רכיבה ב#הביטיקה על ידי השלמת המשימות שלי מהחיים האמיתיים!
+ שליחה
+ איתור משתתפים
+ אם חברים מצטרפים להביטיקה דרך כתובת הדואר האלקטרוני שלך, הם יוזמנו אוטומטית לחבורה שלך!
+ הזמנת חברים נוספים
+ דואר אלקטרוני
+ שיתוף באמצעות
+ הרגע הבקעתי ביצת חיית מחמד של %1$s %2$s במשחק #הביטיקה בכך שהשלמתי משימות בחיים האמיתיים!
+ הרגע זכיתי בחיית הרכיבה %1$s במשחק #הביטיקה בכך שהשלמתי משימות בחיים האמיתיים!פתיחה בחנות Play
- ברצונכם לשנות מקצוע בעבור 3 אבני חן\?
+ לשנות מקצוע בעבור 3 אבני חן\?אישורשוקנוסעים בזמן
@@ -285,7 +285,7 @@
דרגה %d%2$s בדרגה %1$dבחרו מקצוע
- ברצונכם לשנות מקצוע ל%1$s\?
+ לשנות מקצוע ל%1$s\?אין לכם הודעות. תוכלו לשלוח הודעה חדשה למשתמש אחר דרך הודעות הצ\'ט הציבוריות שלהם!שחררו על ידי כניסה להביטיקה באופן קבועשחררו על ידי יצירת משתמש
@@ -304,15 +304,15 @@
בחרו מצוע על מנת להפעיל שימוש ביכולות, מדדים, ומאנההחבורה של %sתכיר אנשים
- זה ישנה את הציוד הזמין בחנויות וישנה את היכולות שלכם
+ פעולה זו תחליף את הציוד הזמין בחנויות וישנה את היכולות שלךאתם תישארו ללא מקצוע. תוכלו לבחור מקצוע בכל עת על ידי הפעלת מערכת המקצועות בהגדרות.
- זה יבטל את המקצוע הנוכחי שלכם ויזכה אתכם בכל נקודות המדדים. לאחר מכן תוכלו לבחור מקצוע חדש
- זה יזכה את נקודות המדדים שלכם, ישנה את הציוד הזמין בחנויות, וישנה את היכולות שלכם.
+ פעולה זו תסיר ממך את המקצוע הנוכחי ותזכה אותך בכל נקודות המדדים, ואז יהיה אפשר לבחור מקצוע חדש
+ פעולה זו תזכה את נקודות המדדים שלך, תחליף את הציוד הזמין בחנויות, ותשנה את היכולות שלך.השלמתם את כל המטלות היומיות שלכם. כל הכבוד!הינכם %s!כעת תוכלו להשתמש ב%s יכולות ולרכוש ציוד מחנויות. עלו בדרגות על מנת לזכות בנקודות מדדים נוספות, על מנת לחזק את היכולות שלכם.
- אם יש לכם חברים שמשתמשים בהביטיקה, תוכלו להזמין אותם באמצעות שם משתמש כאן.
- הגעתי לדרגה %d ב#הביטיקה בכך ששיפרתי את ההרגלים שלי מהחיים האמיתיים!
+ אם יש לך חברים שמשתמשים בהביטיקה, אפשר להזמין אותם באמצעות שם משתמש כאן.
+ הגעתי לרמה %d במשחק #הביטיקה בכך ששיפרתי את ההרגלים בחיים האמיתיים!הוספת הרגלהוספת יומיתפתיחת השוק
diff --git a/Habitica/res/values-nl/strings.tutorial.xml b/Habitica/res/values-nl/strings.tutorial.xml
index 8fdcab8ac..9ccb9bbd2 100644
--- a/Habitica/res/values-nl/strings.tutorial.xml
+++ b/Habitica/res/values-nl/strings.tutorial.xml
@@ -1,18 +1,21 @@
-
+
- Daar zijn we dan! Ik heb enkele taken voor je ingevuld op basis van je interesses. Probeer er zelf enkele aan toe te voegen. Je kan elke taak bewerken door op de titel te tikken.
- Als eerste de gewoontes. Dit kunnen goede gewoontes zijn die je wilt verbeteren of slechte gewoontes die je wilt afleren.
- Iedere keer dat je een goede gewoonte doet, tik je op de + om ervaring en goud te krijgen!
- Als je toegeeft en een slechte gewoonte doet, tik je op de - en verliest je avatar gezondheid, dus neem verantwoordelijkheid en voorkom slechte gewoontes.
- Probeer het maar! Je kan de andere soorten taken doornemen met de onderstaande navigatie.
- Maak dagelijkse taken voor tijdgevoelige taken die volgens een vast schema gedaan moeten worden.
- Wees voorzichtig - als je er een mist, verliest je avatar gezondheid de volgende dag. Vink ze regelmatig af voor grootse beloningen!
- Gebruik To Do\'s om taken bij te houden die je slechts één keer moet doen.
- Als je To Do tegen een bepaalde dag afgewerkt moet zijn, voer dan een einddatum in. Je kan er een afvinken - doe maar!
- Koop een uitrusting voor je avatar met het goud dat je verdient!
- Je kan ook zelf beloningen maken voor in de echte wereld, gebaseerd op wat je motiveert.
- Dat was het voorlopig. Als je iets vergeet, kijk dan in de FAQ sectie.
+ Daar zijn we dan! Ik heb enkele taken voor je ingevuld op basis van je interesses. Probeer er zelf enkele aan toe te voegen. Je kan elke taak bewerken door op de titel te tikken.
+ Als eerste de gewoontes. Dit kunnen goede gewoontes zijn die je wilt verbeteren of slechte gewoontes die je wilt afleren.
+ Iedere keer dat je een goede gewoonte doet, tik je op de + om ervaring en goud te krijgen!
+ Als je toegeeft en een slechte gewoonte doet, tik je op de - en verliest je avatar gezondheid, dus neem verantwoordelijkheid en voorkom slechte gewoontes.
+ Probeer het maar! Je kan de andere soorten taken doornemen met de onderstaande navigatie.
+ Maak dagelijkse taken voor tijdgevoelige taken die volgens een vast schema gedaan moeten worden.
+ Wees voorzichtig - als je er een mist, verliest je avatar gezondheid de volgende dag. Vink ze regelmatig af voor grootse beloningen!
+ Gebruik To Do\'s om taken bij te houden die je slechts één keer moet doen.
+ Als je To Do tegen een bepaalde dag afgewerkt moet zijn, voer dan een einddatum in. Je kan er een afvinken - doe maar!
+ Koop een uitrusting voor je avatar met het goud dat je verdient!
+ Je kan ook zelf beloningen maken voor in de echte wereld, gebaseerd op wat je motiveert.
+ Dat was het voorlopig. Als je iets vergeet, kijk dan in de FAQ sectie.Vaardigheiden zijn speciale talenten die krachtige effecten hebben! Tik op een vaardigheid om ze te gebruiken. Het kost je Mana (de blauwe balk), dat je kan verdienen door elke dag in te checken en je taken in de echte wereld te voltooien. Kijk in de FAQ in het menu voor meer informatie!
- Dit is waar jij en je vrienden elkaar verantwoordelijk kunnen houden voor het bereiken van je doelen en je samen monsters kan bevechten met je taken!
+ Dit is waar jij en je vrienden elkaar verantwoordelijk kunnen houden voor het bereiken van je doelen en je samen monsters kan bevechten met je taken!Tik de grijze knop aan om meerdere punten ineens aan te wijzen, of tik op de pijlen om ze één punt per keer toe te voegen.
-
+ Welkom bij je Groep! Je kunt meer leden vinden via een lijst met spelers die opzoek zijn naar een Groep, of nodig vrienden direct uit on de Basi-List Queeste Scroll te verdienen.
+\n
+\nGa naar Support om meer te leren over Groepen.
+
\ No newline at end of file
diff --git a/Habitica/res/values-nl/strings.xml b/Habitica/res/values-nl/strings.xml
index d47b1c429..d99fb8a25 100644
--- a/Habitica/res/values-nl/strings.xml
+++ b/Habitica/res/values-nl/strings.xml
@@ -995,4 +995,10 @@
%s\'s GroepJouw tagsUitdagingen tags
-
+ Herinneringen kunnen vertraagd zijn omdat de machtigingen niet zijn ingeschakeld. Tik om de machtigingen te bekijken en bij te werken.
+ `Alarm & Herinneringen` uitgeschakeld`
+ %1$s en %2$s
+ Sta`Alarmen & Herinneringen` toe in de app Instellingen om ervoor te zorgen dat de herinneringen op precies de juiste tijd verschijnen
+ Je moet in een Groep zitten voordat je een Queeste kunt beginnen
+ Als je eenmaal in een Groep zit, kan je zelf Queestes doen of vrienden uitnodigen om ook met hen Queestes te doen!
+
\ No newline at end of file
diff --git a/Habitica/res/values-pl/strings.sidebar.xml b/Habitica/res/values-pl/strings.sidebar.xml
index a7550aaa0..58dc7a583 100644
--- a/Habitica/res/values-pl/strings.sidebar.xml
+++ b/Habitica/res/values-pl/strings.sidebar.xml
@@ -17,4 +17,4 @@
SubskrypcjaZakup KlejnotówAwatar i Wyposażenie
-
+
\ No newline at end of file
diff --git a/Habitica/res/values-pl/strings.xml b/Habitica/res/values-pl/strings.xml
index 2652afc6d..267c13907 100644
--- a/Habitica/res/values-pl/strings.xml
+++ b/Habitica/res/values-pl/strings.xml
@@ -3,11 +3,11 @@
OdświeżDoświadczenieZdrowie
- Mana
+ MocUstawieniaIdentyfikator użytkownika
- Twój identyfikator
+ Twój identyfikator użytkownikaToken APITwój Token APIJęzyk
@@ -16,21 +16,21 @@
KontoPierwszy Dzień TygodniaPierwszy dzień tygodnia we wszystkich kalendarzach
- Przypominienie
- Aktywuj przypomnienie
- Ustaw czas przypomnienia
- Własny początek dnia
+ Codzienne Przypomnienie
+ Aktywuj Przypomnienie
+ Ustaw czas Przypomnienia
+ Własny Początek DniaUżyj PowiadomieńPowiadomieniaUstaw swoje ustawienia powiadomień push
- Ukończono wyzwanie!
- Otrzymano prywatną wiadomość
- Otrzymane diamenty
- Otrzymałeś subskrypcję
- Otrzymano zaproszenie do drużyny
- Otrzymano zaproszenie do gildii
- Twoja misja się rozpoczęła
- Zaproszono Cię na misję
+ Wygrano Wyzwanie!
+ Otrzymano Prywatną Wiadomość
+ Otrzymane Klejnoty
+ Otrzymano Subskrypcję
+ Otrzymano zaproszenie do Drużyny
+ Otrzymano zaproszenie do Gildii
+ Twoja Misja się Rozpoczęła
+ Zaproszono Cię na MisjęBibliotekiHabitica jest dostępna jako otwarte oprogramowanie na platformie Github
@@ -69,13 +69,13 @@
Wypełniając swoje cele życiowe, udało Ci się zyskać kolejny poziom i pełne uzdrowienie postaci!Hurra!Nie rozpaczaj!
- Możesz stracić całe Zdrowie!
- Uzupełnij Zdrowie & Spróbuj jeszcze raz
+ Straciłeś całe Zdrowie!
+ Uzupełnij Zdrowie i Spróbuj Jeszcze RazFiltrUżyto %1$s.nowa część zadaniaOdblokuj na poziomie 11
- Zapomniane hasło
+ Zapomniałem hasłaReaktywuj swoje codzienneZapauzuj swoje codzienneKup
@@ -111,7 +111,7 @@
Anuluj ZaproszeniePrzerwij QuestWersja %1$s (%2$d)
- Pomoc i FAQ
+ WsparcieJasne!Przypomnij mi późniejWitaj w
@@ -137,7 +137,7 @@
Przeglądnąć e-maileNajważniejsze zadanieProjekt zawodowy
- 10 min cardio
+ 10 min kardioRozciągnąć sięUstalić plan ćwiczeńJeść zdrowe / śmieciowe jedzenie
@@ -222,7 +222,7 @@
Jesteś %s!Wybierz klasę postaciWróć
- Czy na pewno chcesz wyjść?
+ Czy chcesz wyjść z systemu klas\?Zmień swoją klasęZmień klasę i odzyskaj punkty statystyk za 3 klejnoty.Odblokuj System Klas
@@ -289,23 +289,23 @@
Należy do wyzwaniaMa powiadomienieMa tag
- Subskrypcja wspiera nasz skromny zespół i pozwala utrzymać Habitica. Dziękujemy!
- Subskrybuj aby uzyskać te wyjątkowe korzyści!
- Złoto na Klejnoty
- Mistyczne Klepsydry
- Comiesięczne Przedmioty Mistyczne
+ Subskrypcja wspiera deweloperów i pozwala utrzymać Habitikę
+ Zmotywuj się jeszcze bardziej z nagrodami subskrybenta
+ Darmowe Miesięczne Klejnoty
+ Miesięczne Mistyczne Klepsydry
+ Miesięczne Mistyczne PrzedmiotySpecjalny Chowaniec SubskrybentaSubskrypcjePowtarzaj co %sSubskrybuj
- Będziesz mieć możliwość zakupu Klejnotów na Targu za 20 sztuk złota każdy!
+ Zdobądź do 50 Klejnotów, które można kupić za Złoto na Rynku i kup za nie Przygody, Personalizacje, Zwierzęta i wiele więcej!Zostań Subskrybentem aby uzyskać teraz elitarny zestaw oraz otrzymywać nowe przedmioty w każdym miesiącu!
- +%d Mistycznej Klepsydry
+ %d KlepsydryMetoda płatnościSubskrypcjaAktywneAnuluj Subskrypcję
- Z subskrybcji możesz zrezygnować w sklepie Google Play. Jakiekolwiek uznania z miesięcznych subskrypcji zostaną zastosowane po zakończeniu obecnej subskrypcji.
+ Nie chcesz już subskrybować\? Możesz znaleźć opcję anulowania subskrypcji, przechodząc do sekcji Płatności i subskrypcje w aplikacji Google Play Store. Wszelkie pozostałe miesiące kredytu subskrypcji zostaną dodane do daty zakończenia po anulowaniu.Nie chcesz dłużej subskrybować\? Z powodu ograniczeń w płatnościach mobilnych, musisz anulować subskrypcję poprzez stronę webową. Aby to zrobić, tapnij poniższy przycisk, zaloguj się na swoje konto, tapnij ikonę Użytkownika w prawym, górnym rogu i przejdź do Subskrypcji. Wszelkie uznania z miesięcznych subskrypcji zostaną zastosowane po zakończeniu Twojej subskrypcji. Będzie nam Ciebie brakowało!Odwiedź Stronę HabitikiObecne Dodatki
@@ -473,7 +473,7 @@
Przydzielanie PunktówEkwipunekBez klasy
- Ten przedmiot jest dostępny tylko dla konkretnej klasy. \nMożesz zmienić swoją klasę w Ustawieniach.
+ Ten przedmiot jest dostępny tylko dla konkretnej klasy. Możesz zmienić swoją klasę w Ustawieniach.Możesz kupić wyposażenie wyłącznie dla swojej obecnej klasyZamelduj się w KarczmieZasady Społeczności
@@ -533,18 +533,18 @@
Twój prezent został wysłany!OdkryjWażne Ogłoszenia
- Pamiętaj aby odznaczyć swoje zadania
+ Pamiętaj, aby odznaczyć swoje zadania%d minut temu%d godzin temuOswoiłeś %s, idź na przejażdżkę!Wycofywanie sięNa %s
- Zdobądź Mistyczną Klepsydrę na zakupy w Sklepie Podróżników w Czasie!
+ Nigdy więcej nie przegap żadnego przedmiotu dzięki 1 Mistycznej Klepsydrze miesięcznie do wykorzystania w Sklepie Podróżników w Czasie!Otrzymaj ekskluzywnego chowańca: Purpurowego, Królewskiego Jackalope, kiedy zostaniesz nowym Subskrybentem.
- Limit 25 klejnotów
- Limit 3 klejnotów
- Limit 35 klejnotów
- Limit 45 klejnotów
+ 25 Klejnotów miesięcznie
+ 30 Klejnotów miesięcznie
+ 35 Klejnotów miesięcznie
+ 45 Klejnotów miesięcznieDoDołącz do wyzwania aby dodać wybrany zestaw zadań do twojej listy i rywalizuj z innymi Habitikanami aby zdobyć osiągnięcia a nawet klejnoty!Oh, musisz byś to nowy. Jestem Justin, będę twoim przewodnikiem po Habitice.
@@ -574,7 +574,7 @@
Teraz jesteś na bieżąco!Wróżki powiadomień składają ci gromkie brawa. Dobra robota!Odrzuć wszystko
- Nowości od Bailey
+ Nowości od Bailey!%1$s ma nowe posty]]>%1$s ma nowe posty]]>nieprzydzielone Punkty Atrybutów: %1$s]]>
@@ -626,7 +626,7 @@
Odwiedzaj Habitikę regularniestwórz konto%d Wizyt
- Podwój Łup
+ Wyjątkowe Zwierzaki & Więcej NagródSubskrybuj teraz, aby zdobyć %s i otrzymywać nowe przedmioty każdego miesiąca!Wykonuj zadania aby zadawać obrażenia BossowiBoss Świata jest specjalnym wydarzeniem, kiedy cała społeczność, wypełniając swoje zadania, dąży wspólnie do pokonania bardzo silnego potwora!
@@ -665,7 +665,7 @@
Przekaż członkowi grupy poniższą nazwę użytkownika, aby mógł Ci wysłać zaproszenieRazem z przyjaciółmi lub samodzielnie staw czoło misjom. Walcz z potworami, twórz wyzwania i pomóż sobie być odpowiedzialnym z pomocą Drużyn.Kolorowe nazwy użytkowników, które możesz zobaczyć w czacie, reprezentują poziom osób wnoszących wkład. Im wyższy poziom, tym większy wkład wniósł dany użytkownik do Habitica poprzez grafikę, kod, społeczność i inne!
- Odkryj więcej przedmiotów w Habitica z podwojoną szansą na dzienne łupy.
+ Zdobądź Królewsko Fioletowego Szakala, a także dwa razy więcej Jaj, Mikstur Wylęgowych i Pożywienia każdego dnia, aby powiększyć swoją kolekcję Zwierząt!Wyślij wiadomość do %s, aby inny gracz odpowiedział na twoje pytania.Dostępna aktualizacja: %1$s (%2$d)Seria
@@ -771,9 +771,9 @@
Jesteś pewien, że chcesz porzucić zmiany w tym zadaniu\?Poziom %dCzy chcesz zmienić klasę na: %1$s\?
- Możesz teraz używać umiejętności i kupować wyposażenie klasy: %s!
- Włącz powiadomienia
- Powiadomienia wyłączone
+ Możesz teraz używać %s umiejętności i kupować wyposażenie ze sklepów. Podnoś poziomy aby zbierać punkty statystyk które możesz wykorzystać do podnoszenia swoich umiejętnosci.
+ Włącz Powiadomienia
+ Powiadomienia WyłączoneTytułpozytywnenegatywne
@@ -781,8 +781,7 @@
Dołączyłeś(-aś) do drużyny z inną osobą!Czy na pewno chcesz zresetować konto\?Potwierdź zresetowanie
- Ten przedmiot jest dostępny tylko dla konkretnej klasy.
-\nWybór klasy zostanie odblokowany na Poziomie 10.
+ Ten przedmiot jest dostępny wyłącznie dla konkretnej klasy. Wybór klasy zostanie odblokowany na Poziomie 10.Zresetuj LicznikRutyna Cię dobija\?Czasem łatwiej jest zacząć od zera. Habitica może Ci w tym pomóc!
@@ -943,8 +942,8 @@
Jest tak wiele Chowańców do zebrania, że z pewnością niedługo natrafisz na swojego ulubieńca. A jeśli je nakarmisz, być może urosną duże i silne…Ktoślokalne
- Zezwól Habitica na wysyłanie powiadomień w ustawieniach telefonu, aby otrzymywać powiadomienia push
- Zezwól Habitica na wysyłanie powiadomień w ustawieniach telefonu, aby otrzymywać przypomnienia
+ Zezwól Habitica na wysyłanie powiadomień w Ustawieniach telefonu, aby otrzymywać powiadomienia push
+ Pozwól Habitica na wysyłanie przypomnieńDrużyna %sNakarm ChowańcaKup ekwipunek
@@ -1114,7 +1113,7 @@
Aby zakończyć Wyzwanie, zaloguj się na stronie Habitica, a następnie naciśnij przycisk “Zakończ Wyzwanie” znajdujący się po prawej stronie ekranu Wyzwania.Wygrałeś(-aś) WyzwanieGratulacje!
- Zostałeś(-aś) zwycięzcą Wyzwania! Twoja wygrana została zapisana w Twoich Osiągnięciach
+ Zostałeś zwycięzcą Wyzwania! Twoja wygrana została zapisana w Twoich Osiągnięciach.Odbierz %d KlejnotówPodaruj Subskrypcję i otrzymaj darmową Subskrybcję do: %s1 miesiąc jednorazowej Subskrypcji
@@ -1128,7 +1127,7 @@
Tagi WyzwańTagi Grup%1$s i %2$s
- Alarmy i Przypomnienia wyłączone
+ Alarmy i Przypomnienia WyłączoneCzy na pewno chcesz sprzedać %s\?Przypomnienia mogą być opóźnione, ponieważ nie mają wymaganych uprawnień. Dotknij aby zobaczyć i zmienić uprawnienia.Zezwól na Alarmy i Przypomnienia w Ustawieniach, aby mieć pewność, że przypomnienia pojawią się dokładnie o zaplanowanej porze
@@ -1144,4 +1143,20 @@
Co %d dniCo %d dni
+
+ %d Godziny
+ %d Godzin
+ %d Godzin
+ %d Godzin
+
+ Wystrój się w najnowszy ekskluzywny sprzęt. Subskrybuj teraz, aby otrzymać %2$s od %1$s!
+ Uniknij utraty postępów dzięki natychmiastowemu uleczeniu, gdy raz dziennie zabraknie Ci punktów zdrowia!
+ Zdobąć Drugą Szansę
+ Klepsydra za 3 miesiące
+
+ %d Minuta
+ %d Minut
+ %d Minuty
+ %d Minuty
+
\ No newline at end of file
diff --git a/Habitica/res/values-pt-rBR/strings.xml b/Habitica/res/values-pt-rBR/strings.xml
index 62e415390..8c13062ae 100644
--- a/Habitica/res/values-pt-rBR/strings.xml
+++ b/Habitica/res/values-pt-rBR/strings.xml
@@ -710,7 +710,7 @@
Equipar novos equipamentos automaticamenteAbandonar DesafiosManter Desafios
- Você convidou um(a) amigo(a) (ou amigos) que se juntaram à você em sua aventura!
+ Você convidou um amigo ou mais que se juntaram a você em sua aventura!Convidou um(a) amigo(a)Você colocou-se à prova participando de um Desafio!Entrou em um Desafio
@@ -1032,7 +1032,7 @@
Bloquear %s\?Desbloquear UsuárioBloquear Usuário
- A Promoção de Gemas está de volta para assombrar o final do Festival de Outono deste ano! Esta é a última chance de conseguir mais Gemas do que nunca, então estoque enquanto dura!
+ A Promoção de Gemas está de volta para assombrar o final do Festival de Outono deste ano! Esta é a última chance de conseguir mais Gemas do que nunca, então aproveite enquanto duram os estoques!Entre %1$s e %2$s, basta comprar qualquer pacote de Gemas como de costume e sua conta será creditada com a quantidade promocional de Gemas. Mais Gemas para gastar, compartilhar ou economizar para lançamentos futuros!Ver Pacotes de GemasO Festival de Outono está em pleno andamento, então pensamos que era o momento perfeito para apresentar nossa primeira Promoção de Gemas! Agora você obterá mais Gemas com cada compra do que nunca.
@@ -1181,7 +1181,7 @@
Confirmar reinicioex. habitrabbit ou gryphon@exemplo.comVocê precisa estar em um Grupo antes de começar uma Missão
- Quando estiver em um Grupo, poderá fazer Missões por conta própria ou convidar amigos para fazerem com você!
+ Quando estiver em um Grupo, você poderá fazer Missões por conta própria ou convidar amigos para completarem Missões juntos!Você encontrou um Equipamento raro no Armário!Você vasculhou o Armário e encontrou comida. O que isso faz aqui\?Vender
diff --git a/Habitica/res/values-zh-rTW/strings.xml b/Habitica/res/values-zh-rTW/strings.xml
index 5e84d06ad..ada83cfa9 100644
--- a/Habitica/res/values-zh-rTW/strings.xml
+++ b/Habitica/res/values-zh-rTW/strings.xml
@@ -1245,4 +1245,6 @@
正面負面%1$s 與 %2$s
+ 如果未開啓權限,提醒將可能會延遲。點擊查看和設置權限。
+ 在設置內允許`警告和提醒`權限以保證提醒能準時觸發
\ No newline at end of file
diff --git a/Habitica/res/values-zh/strings.xml b/Habitica/res/values-zh/strings.xml
index 9af7a6a12..be13afa82 100644
--- a/Habitica/res/values-zh/strings.xml
+++ b/Habitica/res/values-zh/strings.xml
@@ -298,7 +298,7 @@
有提醒有标签购买订阅支持我们的小团队以及帮助维持Habitica运作
- 成为订阅者即可享受这些专属福利!
+ 订阅即享更多奖励,助您时刻保持动力月度免费宝石月度神秘沙漏月度神秘物品
@@ -306,7 +306,7 @@
订阅每%s订阅一次订阅
- 最多可获取50个商店宝石,用于购买任务,装扮,宠物等!
+ 最多可获取50个可用于购买金币的商店宝石,用于购买任务,装扮,宠物等!不要错过每月可在时空旅行者商店中购买一次价格为1个神秘沙漏的物品的机会!首次订阅可获得皇家紫色鹿角兔宠物。每月25颗宝石
@@ -318,7 +318,7 @@
订阅进行中取消订阅
- 你可以通过GooglePlay商店取消订阅。任何结存的订阅将会在你的定期订阅结束后生效。
+ 不想继续订阅了?你可以在GooglePlay商店的付款和订阅栏目找到取消订阅的选项。任何结存的订阅将会被累加到你取消前的最后一天。不想继续订阅了?由于手机支付的限制,您只能通过我们的网站取消订阅。请点击下方按钮登录您的账号,点击右上角的用户图标,然后转到订阅。任何结存的订阅都将在订阅结束后生效。我们会想念您的!访问Habitica网页版现有的奖励
@@ -453,7 +453,7 @@
你需要更多的神秘沙漏才能购买此物品!获取沙漏订阅以获取沙漏
- 每连续订阅三个月即可一个神秘沙漏,然后用它们解锁过去和未来的限定版物品、宠物和坐骑!
+ 订阅期间每月可获得一个神秘沙漏,以解锁过去和未来的限定版物品、宠物和坐骑!我想订阅盛典会在春分、夏至、秋分、冬至前后举行,到时再来看看各种有趣的特殊季节商品吧!很快回来!
@@ -619,9 +619,9 @@
赠送订阅订阅赠一得一活动正在进行中!你想将礼物送给谁?
- 赠一得一!
+ 赠一得一在此促销活动期间,你将在送出礼物后自动收到相同的订阅。
- 在下方选择你想要赠送的订阅时长!此购买不会自动续订。
+ 请选择你想要赠送的订阅时长!此购买不会自动续订。赠送礼物服务器你的礼物已送出!
@@ -904,9 +904,9 @@
\n难度较高的任务或红色任务会给你**更多经验值**。**智力属性**也会提高你的经验值获取速度。经验值有时,应用程序不会自动更新内容。请尝试滑动刷新或强制关闭应用再重新打开它。
- 神秘沙漏是一种极为稀有的货币,只有连续订阅Habitica三个月或更长时间才能获得。它们可用于在时空旅行者商店中购买过去的装备套装、宠物、坐骑、动画背景甚至特殊副本。
+ 神秘沙漏是一种极为稀有的货币,仅限订阅用户获取。你可在时空旅行者商店中购买往期订阅专属装备套装、专属宠物、坐骑、动画背景甚至特殊副本!这些超值特权将助您全年保持高效,为每个成就加冕!
\n
-\n你每年最多可以收到四个神秘沙漏。收到神秘沙漏的时间取决于你的续订时间。它们将在你支付了使你有资格获得沙漏的订阅费用后的次月月初发送。详情请查看[订阅]页面。
+\n订阅用户将在每个享有订阅福利的月初收到1枚神秘沙漏,并享有多项特权。若对时空旅者提供的服务感兴趣,请务必查看我们的订阅选项!要清除缓存,请打开手机的应用设置。前往存储>应用>Habitica,然后点击清除缓存。清除缓存手动同步或重启
@@ -1107,10 +1107,10 @@
你打开神秘礼盒,得到了……团队信息团队
- 十二个月
- 六个月
- 三个月
- 一个月
+ 12个月
+ 6个月
+ 3个月
+ 1个月赠一得一活动持续至%s服务条款隐私政策
@@ -1134,9 +1134,9 @@
添加密码连接添加
- 复制令牌。小心,这相当于密码!
+ 开发者及第三方工具专用密码令牌。已添加%s身份验证
- 必须正确输入两次密码
+ 输入的密码不匹配邮箱地址无效你确定要放弃对此任务的更改吗?你向@%1$s赠送了%2$s颗宝石。
@@ -1281,7 +1281,7 @@
这将切换商店中解锁的装备,并改变你可使用的技能你的标签挑战标签
- 用1点生命值撑住!
+ 获得第二次机会你将会失去所有的等级、金币和经验。你设置的所有任务及其任务历史数据将会被删除(挑战任务除外)。你将失去除了订阅者物品和免费纪念物品外的所有装备,但你之后可以重新购买被删除的物品。重新购买职业专属装备需要对应的职业。你当前的职业、成就、宠物和坐骑将会保留。如果你已确定要重置数据,请在下方输入你的密码。你可以联系我们,我们的团队成员会尽力提供帮助!举报%s?
@@ -1334,7 +1334,7 @@
为了让派对继续下去,我们将送出派对长袍、20 颗宝石、限定版披风套装和背景!访问市场于%s结束
- 当你的生命值耗尽时,你可以得到第二次机会。
+ 当你的生命值耗尽时,你可以得到第二次机会!访问市场专属物品和礼物在等着你查看更多
@@ -1455,7 +1455,7 @@
免费再次开启!续命机制:1HP极限锁血!订阅特权
- 订阅即享每日1次免死特权,避免生命值归零!
+ 订阅即享每日1次免死特权,避免生命值归零订阅会员额外奖励双倍宝箱奖励关注对您的邀请或创建自己的队伍
@@ -1480,7 +1480,7 @@
副本机制收集副本中,完成任务将有随机几率获得副本物品。获得中的副本物品将在每日重置时发放。感知属性可提高物品掉率。查看更多订阅方案
- 订阅会员权利:若您的生命值耗尽,每日可获得一次复活机会。
+ 订阅会员专属特权:若您的生命值耗尽,每日可获得一次复活机会您被邀请加入团队计划在团队计划中被@提及命悬一线?订阅立即锁住最后1HP!
@@ -1524,7 +1524,7 @@
前往[市场](/shops/market)选购所需物品!本次限时活动期限为 %1$s 至,%2$s。此促销仅适用于向其他Habitica用户赠送订阅。如您或受赠人为订阅用户,则所获赠时长将转为预存权益,将于现订阅服务终止后启用。通过邀请
- 这些玩家正在寻找队伍:
+ 这些玩家正在寻找队伍当前暂无玩家申请组队,稍后再来看看吧!已邀请通过@用户名或邮箱进行邀请
@@ -1572,7 +1572,7 @@
怒气值部分高难度Boss拥有橙色怒气条(位于血条下方)。当参与者未完成日常任务时,怒气值会持续累积。当怒气条攒满后,首领将发动致命一击,造成额外伤害!每次开启宝箱后,可立即获得一次免费再开机会!
- 随时间累积获得时之沙漏,即可在时空旅人商店兑换物品。
+ 随时间累积获得时之沙漏,即可在时空旅人商店兑换物品宝石持有上限立即以最大宝石持有上限开始游戏访问Habitica网站
@@ -1591,4 +1591,33 @@
订阅后每次购买宝箱可额外获得一次开箱机会订阅者可获得额外的宝箱机会和其他福利!完成任务、孵化药水副本,或直接前往 [Market](/shops/market)去购买所需物品吧!
+ 下次切换在%s后
+ 重新订阅即可继续您的旅程!
+ 您同意我们的 服务条款 并且已经阅读了我们的 隐私政策.
+ 热门
+ 享受你的订阅者专属奖励
+ Felicitus
+ 他们将立即以最大宝石持有上限开始游戏
+ 他们每月可在商店解锁%d颗宝石
+ 他们在订阅期间每月可获取+2宝石
+ 使用技能
+ 此邮箱用于登录 Habitica 及接收系统通知。
+ 用户名长度应为 1-20 个字符,仅可使用:英文小写字母 a 至 z、数字 0 至 9、连字符 (-) 或下划线 (_)。
+ 修改昵称
+ 此名称将作为您在 Habitica 中的角色名称显示。与用户名不同,昵称无需唯一。
+ 密码需要至少为8位字符。修改密码后,系统将自动登出您在其他设备及第三方工具中的所有会话。
+ 确认新密码
+ 在队伍中使用
+ 保存个人简介
+ 添加个人简介,它将显示于 Habitica 个人资料中供其他玩家查看。
+ API 令牌
+ 你的API令牌功能相当于密码
+ 如您需要新的API令牌
+ 切勿公开分享此令牌。 他人可能要求您提供用户ID,但绝对不可在 GitHub 等公开平台发布API令牌。
+ 复制令牌
+ 保存照片链接
+ 您可在 Habitica 个人资料中添加图片链接,向其他玩家展示您的专属形象。
+ 开始畅聊吧!
+ 通过修改密码即可重置令牌。重置后需要:在所有设备重新登录Habitica、向您使用的第三方工具提供新令牌。
+ 请务必保持友善并遵循社区准则。
\ No newline at end of file
diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml
index 6dffe6972..b196b73d7 100644
--- a/Habitica/res/values/strings.xml
+++ b/Habitica/res/values/strings.xml
@@ -1438,7 +1438,7 @@
InvitedInvite with @username or emailSend an invite directly to players you know
- Username or email address
+ Username or emailWant to join a Party with others but don’t know any other players? Let Party leaders know you’re looking for an invite!Looking for a Party?Look for a Party
diff --git a/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/activities/IntroActivityTest.kt b/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/activities/IntroActivityTest.kt
deleted file mode 100644
index ec7055559..000000000
--- a/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/activities/IntroActivityTest.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-
-import androidx.test.ext.junit.rules.ActivityScenarioRule
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.LargeTest
-import com.habitrpg.android.habitica.R
-import com.habitrpg.android.habitica.ui.activities.ActivityTestCase
-import com.habitrpg.android.habitica.ui.activities.IntroActivity
-import io.github.kakaocup.kakao.screen.Screen
-import io.github.kakaocup.kakao.text.KButton
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-class IntroActivityScreen : Screen() {
- val skipButton = KButton { withId(R.id.skipButton) }
- val finishButton = KButton { withId(R.id.finishButton) }
-}
-
-@LargeTest
-@RunWith(AndroidJUnit4::class)
-class IntroActivityTest : ActivityTestCase() {
- @Rule
- @JvmField
- var mActivityTestRule = ActivityScenarioRule(IntroActivity::class.java)
-
- val screen = IntroActivityScreen()
-
- @Test
- fun introActivityTest() {
- screen {
- device.activities.isCurrent(IntroActivity::class.java)
- skipButton {
- isVisible()
- }
- finishButton {
- isNotDisplayed()
- }
- }
- }
-}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt
index 526f147b5..b12f03562 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt
@@ -35,7 +35,7 @@ import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManag
import com.habitrpg.android.habitica.models.user.User
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.activities.OnboardingActivity
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.common.habitica.extensions.setupCoil
import com.habitrpg.common.habitica.helpers.ExceptionHandler
@@ -349,7 +349,7 @@ abstract class HabiticaBaseApplication : Application(), Application.ActivityLife
instance?.lazyApiHelper?.updateAuthenticationCredentials(null, null)
Wearable.getCapabilityClient(context).removeLocalCapability("provide_auth")
- startActivity(LoginActivity::class.java, context)
+ startActivity(OnboardingActivity::class.java, context)
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt
index cc1de1710..e0e8aebce 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt
@@ -178,7 +178,6 @@ class ApiClientImpl(
|| errField.equals("invalidCredentials", ignoreCase = true)
|| msgField.contains("invalidCredentials", ignoreCase = true)
|| msgField.contains("Missing authentication headers", ignoreCase = true)
- || msgField.contains("There is no account that uses those credentials", ignoreCase = true)
if (shouldLogout) {
HabiticaBaseApplication.logout(context)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/DeviceCommunicationService.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/DeviceCommunicationService.kt
index 4a88ea35c..5f04a9665 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/DeviceCommunicationService.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/DeviceCommunicationService.kt
@@ -4,7 +4,7 @@ import android.content.Intent
import com.google.android.gms.wearable.MessageEvent
import com.google.android.gms.wearable.Wearable
import com.google.android.gms.wearable.WearableListenerService
-import com.habitrpg.android.habitica.ui.activities.LoginActivity
+import com.habitrpg.android.habitica.ui.activities.OnboardingActivity
import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.android.habitica.ui.activities.TaskFormActivity
import com.habitrpg.common.habitica.api.HostConfig
@@ -23,8 +23,8 @@ class DeviceCommunicationService : WearableListenerService() {
super.onMessageReceived(event)
when (event.path) {
DeviceCommunication.REQUEST_AUTH -> processAuthRequest(event)
- DeviceCommunication.SHOW_REGISTER -> openActivity(event, LoginActivity::class.java)
- DeviceCommunication.SHOW_LOGIN -> openActivity(event, LoginActivity::class.java)
+ DeviceCommunication.SHOW_REGISTER -> openActivity(event, OnboardingActivity::class.java)
+ DeviceCommunication.SHOW_LOGIN -> openActivity(event, OnboardingActivity::class.java)
DeviceCommunication.SHOW_RYA -> openActivity(event, MainActivity::class.java)
DeviceCommunication.SHOW_TASK_EDIT -> openTaskForm(event)
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/IntroActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/IntroActivity.kt
deleted file mode 100644
index 750c13820..000000000
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/IntroActivity.kt
+++ /dev/null
@@ -1,161 +0,0 @@
-package com.habitrpg.android.habitica.ui.activities
-
-import android.content.Intent
-import android.os.Build
-import android.os.Bundle
-import android.view.View
-import androidx.core.content.ContextCompat
-import androidx.core.content.res.ResourcesCompat
-import androidx.core.view.WindowCompat
-import androidx.fragment.app.Fragment
-import androidx.lifecycle.lifecycleScope
-import androidx.viewpager2.adapter.FragmentStateAdapter
-import androidx.viewpager2.widget.ViewPager2
-import com.google.android.material.tabs.TabLayoutMediator
-import com.habitrpg.android.habitica.R
-import com.habitrpg.android.habitica.data.ContentRepository
-import com.habitrpg.android.habitica.databinding.ActivityIntroBinding
-import com.habitrpg.android.habitica.extensions.setNavigationBarDarkIcons
-import com.habitrpg.android.habitica.ui.fragments.setup.IntroFragment
-import com.habitrpg.common.habitica.helpers.ExceptionHandler
-import dagger.hilt.android.AndroidEntryPoint
-import kotlinx.coroutines.launch
-import javax.inject.Inject
-
-@AndroidEntryPoint
-class IntroActivity : BaseActivity() {
- private lateinit var binding: ActivityIntroBinding
-
- @Inject
- lateinit var contentRepository: ContentRepository
-
- override fun getLayoutResId(): Int {
- return R.layout.activity_intro
- }
-
- override fun getContentView(layoutResId: Int?): View {
- binding = ActivityIntroBinding.inflate(layoutInflater)
- return binding.root
- }
-
- public override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- setupIntro()
-
- binding.skipButton.setOnClickListener { finishIntro() }
- binding.finishButton.setOnClickListener { finishIntro() }
-
- lifecycleScope.launch(ExceptionHandler.coroutine()) {
- contentRepository.retrieveContent()
- }
- }
-
- override fun onResume() {
- super.onResume()
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- window.isNavigationBarContrastEnforced = false
- val controller = WindowCompat.getInsetsController(window, window.decorView)
- controller.isAppearanceLightNavigationBars = false
- controller.isAppearanceLightStatusBars = false
- window.setNavigationBarDarkIcons(false)
- }
- }
-
- private fun setupIntro() {
- setViewPagerAdapter()
- binding.viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
- override fun onPageSelected(position: Int) {
- super.onPageSelected(position)
- if (position == 2) {
- binding.finishButton.visibility = View.VISIBLE
- } else {
- binding.finishButton.visibility = View.GONE
- }
- }
- })
- }
-
- private fun finishIntro() {
- val intent = Intent(this, LoginActivity::class.java)
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
-
- this.startActivity(intent)
- overridePendingTransition(0, R.anim.activity_fade_out)
- finish()
- }
-
- private fun setViewPagerAdapter() {
- val fragmentManager = supportFragmentManager
- val viewPagerAdapter = object : FragmentStateAdapter(fragmentManager, lifecycle) {
- override fun createFragment(position: Int): Fragment {
- val fragment = IntroFragment()
- configureFragment(fragment, position)
- return fragment
- }
-
- override fun getItemCount(): Int {
- return 3
- }
- }
- binding.viewPager.adapter = viewPagerAdapter
- TabLayoutMediator(binding.viewPagerIndicator, binding.viewPager) { tab, position -> }.attach()
- }
-
- private fun configureFragment(
- fragment: IntroFragment,
- position: Int
- ) {
- when (position) {
- 0 -> {
- fragment.setImage(ResourcesCompat.getDrawable(resources, R.drawable.intro_1, null))
- fragment.setSubtitle(getString(R.string.intro_1_subtitle))
- fragment.setTitleImage(
- ResourcesCompat.getDrawable(
- resources,
- R.drawable.intro_1_title,
- null
- )
- )
- fragment.setDescription(
- getString(
- R.string.intro_1_description,
- getString(R.string.habitica_user_count)
- )
- )
- fragment.setBackgroundColor(
- ContextCompat.getColor(
- this@IntroActivity,
- R.color.brand_300
- )
- )
- }
-
- 1 -> {
- fragment.setImage(ResourcesCompat.getDrawable(resources, R.drawable.intro_2, null))
- fragment.setSubtitle(getString(R.string.intro_2_subtitle))
- fragment.setTitle(getString(R.string.intro_2_title))
- fragment.setDescription(getString(R.string.intro_2_description))
- fragment.setBackgroundColor(
- ContextCompat.getColor(
- this@IntroActivity,
- R.color.blue_10
- )
- )
- }
-
- 2 -> {
- fragment.setImage(ResourcesCompat.getDrawable(resources, R.drawable.intro_3, null))
- fragment.setSubtitle(getString(R.string.intro_3_subtitle))
- fragment.setTitle(getString(R.string.intro_3_title))
- fragment.setDescription(getString(R.string.intro_3_description))
- fragment.setBackgroundColor(
- ContextCompat.getColor(
- this@IntroActivity,
- R.color.red_100
- )
- )
- }
- }
- }
-}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt
deleted file mode 100644
index 3ef95ce92..000000000
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt
+++ /dev/null
@@ -1,457 +0,0 @@
-package com.habitrpg.android.habitica.ui.activities
-
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.animation.AnimatorSet
-import android.animation.ObjectAnimator
-import android.animation.ValueAnimator
-import android.content.Context
-import android.content.Intent
-import android.content.SharedPreferences
-import android.os.Build
-import android.os.Bundle
-import android.text.InputType
-import android.text.SpannableString
-import android.text.method.LinkMovementMethod
-import android.text.style.UnderlineSpan
-import android.view.View
-import android.view.Window
-import android.view.inputmethod.EditorInfo
-import android.widget.EditText
-import android.widget.LinearLayout
-import androidx.activity.SystemBarStyle
-import androidx.activity.addCallback
-import androidx.activity.viewModels
-import androidx.core.content.ContextCompat
-import androidx.core.view.WindowCompat
-import androidx.core.view.isVisible
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.preference.PreferenceManager
-import com.google.android.gms.tasks.Tasks
-import com.google.android.gms.wearable.CapabilityClient
-import com.google.android.gms.wearable.MessageClient
-import com.google.android.gms.wearable.Wearable
-import com.habitrpg.android.habitica.R
-import com.habitrpg.android.habitica.databinding.ActivityLoginBinding
-import com.habitrpg.android.habitica.extensions.addCancelButton
-import com.habitrpg.android.habitica.extensions.addOkButton
-import com.habitrpg.android.habitica.extensions.lifecycleLaunchWhen
-import com.habitrpg.android.habitica.extensions.updateStatusBarColor
-import com.habitrpg.android.habitica.helpers.AppConfigManager
-import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard
-import com.habitrpg.android.habitica.extensions.AuthenticationErrors
-import com.habitrpg.android.habitica.extensions.setNavigationBarDarkIcons
-import com.habitrpg.android.habitica.ui.viewmodels.AuthenticationViewModel
-import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
-import com.habitrpg.common.habitica.helpers.launchCatching
-import com.habitrpg.common.habitica.models.auth.UserAuthResponse
-import dagger.hilt.android.AndroidEntryPoint
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.launch
-import javax.inject.Inject
-
-@AndroidEntryPoint
-class LoginActivity : BaseActivity() {
- private lateinit var binding: ActivityLoginBinding
- val viewModel by viewModels()
-
- @Inject
- lateinit var configManager: AppConfigManager
-
- private var isShowingForm: Boolean = false
-
- override fun getLayoutResId(): Int {
- return R.layout.activity_login
- }
-
- override fun getContentView(layoutResId: Int?): View {
- binding = ActivityLoginBinding.inflate(layoutInflater)
- return binding.root
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- supportActionBar?.hide()
- // Set default values to avoid null-responses when requesting unedited settings
- PreferenceManager.setDefaultValues(this, R.xml.preferences_fragment, false)
-
- configureSpecialUI()
- binding.backgroundContainer.isScrollable = false
-
- setupOnClickListeners()
- setupViewmodelObserving()
- }
-
- override fun onResume() {
- super.onResume()
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- window.isNavigationBarContrastEnforced = false
- val controller = WindowCompat.getInsetsController(window, window.decorView)
- controller.isAppearanceLightNavigationBars = false
- controller.isAppearanceLightStatusBars = false
- window.setNavigationBarDarkIcons(false)
- }
- }
-
- private fun setupViewmodelObserving() {
- lifecycleLaunchWhen(Lifecycle.State.RESUMED) {
- viewModel.showAuthProgress.collect { showProgress ->
- binding.progressView.isVisible = showProgress
- }
- }
- lifecycleLaunchWhen(Lifecycle.State.RESUMED) {
- viewModel.isRegistering.collect { isRegistering ->
- if (isRegistering) {
- configureForRegistering()
- } else {
- configureForLogin()
- }
- }
- }
- lifecycleLaunchWhen(Lifecycle.State.RESUMED) {
- viewModel.authenticationError
- .filterNotNull()
- .collect { showError(it) }
- }
- lifecycleLaunchWhen(Lifecycle.State.RESUMED) {
- viewModel.authenticationSuccess
- .filterNotNull()
- .collect { didRegister ->
- if (didRegister) {
- startSetupActivity()
- } else {
- startMainActivity()
- }
- }
- }
- }
-
- private fun setupOnClickListeners() {
- onBackPressedDispatcher.addCallback(this) {
- if (isShowingForm) {
- hideForm()
- } else {
- finish()
- }
- }
- binding.newGameButton.setOnClickListener { newGameButtonClicked() }
- binding.showLoginButton.setOnClickListener { showLoginButtonClicked() }
- binding.backButton.setOnClickListener { backButtonClicked() }
- binding.forgotPassword.setOnClickListener { onForgotPasswordClicked() }
- binding.googleLoginButton.setOnClickListener {
- viewModel.startGoogleAuth(this)
- }
- binding.submitButton.setOnClickListener {
- if (viewModel.isRegistering.value) {
- registerWithPassword()
- } else {
- loginWithPassword()
- }
- }
- }
-
- private fun configureSpecialUI() {
- val content = SpannableString(binding.forgotPassword.text)
- content.setSpan(UnderlineSpan(), 0, content.length, 0)
- binding.forgotPassword.text = content
- binding.privacyPolicy.movementMethod = LinkMovementMethod.getInstance()
-
- binding.backgroundContainer.post {
- binding.backgroundContainer.scrollTo(
- 0,
- binding.backgroundContainer.bottom,
- )
- }
- }
-
- private fun resetLayout() {
- val isRegistering = viewModel.isRegistering.value
- binding.email.isVisible = isRegistering
- binding.confirmPassword.isVisible = isRegistering
- }
-
- private fun startMainActivity() {
- val intent = Intent(this@LoginActivity, MainActivity::class.java)
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
- startActivity(intent)
- finish()
- }
-
- private fun startSetupActivity() {
- val intent = Intent(this@LoginActivity, SetupActivity::class.java)
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
- startActivity(intent)
- finish()
- }
-
- private fun configureForRegistering() {
- binding.submitButton.text = getString(R.string.register_btn)
- binding.username.setHint(R.string.username)
- binding.username.setAutofillHints("newUsername")
- binding.password.setAutofillHints("newPassword")
- binding.password.imeOptions = EditorInfo.IME_ACTION_NEXT
- binding.googleLoginButton.setText(R.string.register_btn_google)
-
- this.resetLayout()
- }
-
- private fun configureForLogin() {
- binding.submitButton.text = getString(R.string.login_btn)
- binding.username.setHint(R.string.email_username)
- binding.username.setAutofillHints("username")
- binding.password.setAutofillHints("password")
- binding.password.imeOptions = EditorInfo.IME_ACTION_DONE
- binding.googleLoginButton.setText(R.string.login_btn_google)
- this.resetLayout()
- }
-
- private fun loginWithPassword() {
- val username: String = binding.username.text.toString()
- val password: String = binding.password.text.toString()
- viewModel.validateInputs(username, password)?.let {
- showError(it)
- return
- }
- viewModel.login(username, password)
- }
-
- private fun registerWithPassword() {
- val username = binding.username.text.toString()
- val email = binding.email.text.toString()
- val password = binding.password.text.toString()
- val confirmPassword = binding.confirmPassword.text.toString()
- viewModel.validateInputs(username, password, email, confirmPassword)?.let {
- showError(it)
- return
- }
- viewModel.register(username, email, password, confirmPassword)
- }
-
- private fun sendAuthToWearables(response: UserAuthResponse) {
- lifecycleScope.launch(Dispatchers.IO) {
- val messageClient: MessageClient = Wearable.getMessageClient(this@LoginActivity)
- val capabilityClient: CapabilityClient = Wearable.getCapabilityClient(this@LoginActivity)
- try {
- val info =
- Tasks.await(
- capabilityClient.getCapability(
- "receive_message",
- CapabilityClient.FILTER_REACHABLE,
- ),
- )
- info.nodes.forEach {
- Tasks.await(
- messageClient.sendMessage(
- it.id,
- "/auth",
- "${response.id}:${response.apiToken}".toByteArray(),
- ),
- )
- }
- } catch (_: Exception) {
- // Wearable API is not available on this device.
- }
- }
- }
-
- private fun showError(error: AuthenticationErrors) {
- val alert = HabiticaAlertDialog(this)
- if (error.isValidationError) {
- alert.setTitle(R.string.login_validation_error_title)
- } else {
- alert.setTitle(R.string.authentication_error_title)
- }
- alert.setMessage(error.translatedMessage(this))
- alert.addOkButton()
- alert.show()
- }
-
- private fun newGameButtonClicked() {
- viewModel.isRegistering.value = true
- showForm()
- }
-
- private fun showLoginButtonClicked() {
- viewModel.isRegistering.value = false
- showForm()
- }
-
- private fun backButtonClicked() {
- hideForm()
- }
-
- private fun showForm() {
- isShowingForm = true
- val panAnimation =
- ObjectAnimator.ofInt(binding.backgroundContainer, "scrollY", 0).setDuration(1000)
- val newGameAlphaAnimation =
- ObjectAnimator.ofFloat(binding.newGameButton, View.ALPHA, 0.toFloat())
- val showLoginAlphaAnimation =
- ObjectAnimator.ofFloat(binding.showLoginButton, View.ALPHA, 0.toFloat())
- val scaleLogoAnimation =
- ValueAnimator.ofInt(
- binding.logoView.measuredHeight,
- (binding.logoView.measuredHeight * 0.75).toInt(),
- )
- scaleLogoAnimation.addUpdateListener { valueAnimator ->
- val value = valueAnimator.animatedValue as? Int ?: 0
- val layoutParams = binding.logoView.layoutParams
- layoutParams.height = value
- binding.logoView.layoutParams = layoutParams
- }
- if (viewModel.isRegistering.value) {
- newGameAlphaAnimation.startDelay = 600
- newGameAlphaAnimation.duration = 400
- showLoginAlphaAnimation.duration = 400
- newGameAlphaAnimation.addListener(
- object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- binding.newGameButton.visibility = View.GONE
- binding.showLoginButton.visibility = View.GONE
- binding.loginScrollview.visibility = View.VISIBLE
- binding.loginScrollview.alpha = 1f
- }
- },
- )
- } else {
- showLoginAlphaAnimation.startDelay = 600
- showLoginAlphaAnimation.duration = 400
- newGameAlphaAnimation.duration = 400
- showLoginAlphaAnimation.addListener(
- object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- binding.newGameButton.visibility = View.GONE
- binding.showLoginButton.visibility = View.GONE
- binding.loginScrollview.visibility = View.VISIBLE
- binding.loginScrollview.alpha = 1f
- }
- },
- )
- }
- val backAlphaAnimation =
- ObjectAnimator.ofFloat(binding.backButton, View.ALPHA, 1.toFloat()).setDuration(800)
- val showAnimation = AnimatorSet()
- showAnimation.playTogether(
- panAnimation,
- newGameAlphaAnimation,
- showLoginAlphaAnimation,
- scaleLogoAnimation,
- )
- showAnimation.play(backAlphaAnimation).after(panAnimation)
- for (i in 0 until binding.formWrapper.childCount) {
- val view = binding.formWrapper.getChildAt(i)
- view.alpha = 0f
- val animator = ObjectAnimator.ofFloat(view, View.ALPHA, 1.toFloat()).setDuration(400)
- animator.startDelay = (100 * i).toLong()
- showAnimation.play(animator).after(panAnimation)
- }
-
- showAnimation.start()
- }
-
- private fun hideForm() {
- if (!isShowingForm) {
- return
- }
- isShowingForm = false
- val panAnimation =
- ObjectAnimator.ofInt(
- binding.backgroundContainer,
- "scrollY",
- binding.backgroundContainer.bottom,
- ).setDuration(1000)
- val newGameAlphaAnimation =
- ObjectAnimator.ofFloat(binding.newGameButton, View.ALPHA, 1.toFloat()).setDuration(700)
- val showLoginAlphaAnimation =
- ObjectAnimator.ofFloat(binding.showLoginButton, View.ALPHA, 1.toFloat())
- .setDuration(700)
- val scaleLogoAnimation =
- ValueAnimator.ofInt(
- binding.logoView.measuredHeight,
- (binding.logoView.measuredHeight * 1.333333).toInt(),
- )
- scaleLogoAnimation.addUpdateListener { valueAnimator ->
- val value = valueAnimator.animatedValue as? Int
- val layoutParams = binding.logoView.layoutParams
- layoutParams.height = value ?: 0
- binding.logoView.layoutParams = layoutParams
- }
- showLoginAlphaAnimation.startDelay = 300
- val scrollViewAlphaAnimation =
- ObjectAnimator.ofFloat(binding.loginScrollview, View.ALPHA, 0.toFloat())
- .setDuration(800)
- scrollViewAlphaAnimation.addListener(
- object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- binding.newGameButton.visibility = View.VISIBLE
- binding.showLoginButton.visibility = View.VISIBLE
- binding.loginScrollview.visibility = View.INVISIBLE
- }
- },
- )
- val backAlphaAnimation =
- ObjectAnimator.ofFloat(binding.backButton, View.ALPHA, 0.toFloat()).setDuration(800)
- val showAnimation = AnimatorSet()
- showAnimation.playTogether(
- panAnimation,
- scrollViewAlphaAnimation,
- backAlphaAnimation,
- scaleLogoAnimation,
- )
- showAnimation.play(newGameAlphaAnimation).after(scrollViewAlphaAnimation)
- showAnimation.play(showLoginAlphaAnimation).after(scrollViewAlphaAnimation)
- showAnimation.start()
- dismissKeyboard()
- }
-
- private fun onForgotPasswordClicked() {
- val input = EditText(this)
- input.setAutofillHints(EditText.AUTOFILL_HINT_EMAIL_ADDRESS)
- input.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
- input.hint = getString(R.string.forgot_password_hint_example)
- input.textSize = 16f
- val lp =
- LinearLayout.LayoutParams(
- LinearLayout.LayoutParams.MATCH_PARENT,
- LinearLayout.LayoutParams.MATCH_PARENT,
- )
- input.layoutParams = lp
- val alertDialog = HabiticaAlertDialog(this)
- alertDialog.setTitle(R.string.forgot_password_title)
- alertDialog.setMessage(R.string.forgot_password_description)
- alertDialog.setAdditionalContentView(input)
- alertDialog.addButton(R.string.send, true) { _, _ ->
- lifecycleScope.launchCatching {
- userRepository.sendPasswordResetEmail(input.text.toString())
- showPasswordEmailConfirmation()
- }
- }
- alertDialog.addCancelButton()
- alertDialog.show()
- }
-
- private fun showPasswordEmailConfirmation() {
- val alert = HabiticaAlertDialog(this)
- alert.setMessage(R.string.forgot_password_confirmation)
- alert.addOkButton()
- alert.show()
- }
-
- override fun finish() {
- dismissKeyboard()
- super.finish()
- }
-}
-
-fun AuthenticationErrors.translatedMessage(context: Context): String {
- return when (this) {
- AuthenticationErrors.GET_CREDENTIALS_ERROR -> context.getString(R.string.auth_get_credentials_error)
- AuthenticationErrors.INVALID_CREDENTIALS -> context.getString(R.string.auth_invalid_credentials)
-
- AuthenticationErrors.MISSING_FIELDS -> context.getString(R.string.login_validation_error_fieldsmissing)
- AuthenticationErrors.PASSWORD_MISMATCH -> context.getString(R.string.password_not_matching)
- AuthenticationErrors.PASSWORD_TOO_SHORT -> context.getString(R.string.password_too_short, minPasswordLength)
- }
-}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt
index 7cc5bec89..7a7d3d782 100755
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt
@@ -236,7 +236,7 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
DataBindingUtils.configManager = appConfigManager
if (!viewModel.isAuthenticated) {
- val intent = Intent(this, IntroActivity::class.java)
+ val intent = Intent(this, OnboardingActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
return
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/OnboardingActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/OnboardingActivity.kt
new file mode 100644
index 000000000..dd3ce833d
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/OnboardingActivity.kt
@@ -0,0 +1,240 @@
+package com.habitrpg.android.habitica.ui.activities
+
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.text.InputType
+import android.view.View
+import android.widget.EditText
+import android.widget.LinearLayout
+import androidx.activity.viewModels
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.expandIn
+import androidx.compose.animation.expandVertically
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.scaleIn
+import androidx.compose.animation.slideInHorizontally
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutHorizontally
+import androidx.compose.animation.slideOutVertically
+import androidx.compose.animation.togetherWith
+import androidx.compose.animation.with
+import androidx.compose.material3.Text
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.graphics.TransformOrigin
+import androidx.core.view.WindowCompat
+import androidx.lifecycle.lifecycleScope
+import androidx.preference.PreferenceManager
+import com.google.android.gms.tasks.Tasks
+import com.google.android.gms.wearable.CapabilityClient
+import com.google.android.gms.wearable.MessageClient
+import com.google.android.gms.wearable.Wearable
+import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.databinding.ActivityLoginBinding
+import com.habitrpg.android.habitica.extensions.AuthenticationErrors
+import com.habitrpg.android.habitica.extensions.addCancelButton
+import com.habitrpg.android.habitica.extensions.addOkButton
+import com.habitrpg.android.habitica.extensions.setNavigationBarDarkIcons
+import com.habitrpg.android.habitica.helpers.AppConfigManager
+import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard
+import com.habitrpg.android.habitica.ui.viewmodels.AuthenticationViewModel
+import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
+import com.habitrpg.android.habitica.ui.views.intro.IntroScreen
+import com.habitrpg.android.habitica.ui.views.login.LoginScreen
+import com.habitrpg.android.habitica.ui.views.setup.SetupScreen
+import com.habitrpg.android.habitica.ui.views.setup.UsernameSelectionScreen
+import com.habitrpg.common.habitica.helpers.launchCatching
+import com.habitrpg.common.habitica.models.auth.UserAuthResponse
+import com.habitrpg.common.habitica.theme.HabiticaTheme
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+import kotlin.math.roundToInt
+
+enum class OnboardingSteps {
+ INTRO,
+ LOGIN,
+ USERNAME,
+ SETUP
+}
+
+@AndroidEntryPoint
+class OnboardingActivity: BaseActivity() {
+ private lateinit var binding: ActivityLoginBinding
+
+ val authenticationViewModel: AuthenticationViewModel by viewModels()
+
+ val currentStep = mutableStateOf(OnboardingSteps.SETUP)
+
+ @Inject
+ lateinit var configManager: AppConfigManager
+
+ override fun getLayoutResId(): Int {
+ return R.layout.activity_login
+ }
+
+ override fun getContentView(layoutResId: Int?): View {
+ binding = ActivityLoginBinding.inflate(layoutInflater)
+ return binding.root
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ supportActionBar?.hide()
+ // Set default values to avoid null-responses when requesting unedited settings
+ PreferenceManager.setDefaultValues(this, R.xml.preferences_fragment, false)
+
+ binding.composeView.setContent {
+ val step by currentStep
+ HabiticaTheme {
+ AnimatedContent(step,
+ transitionSpec = {
+ (expandVertically(
+ initialHeight = { fullHeight -> (fullHeight * 0.3f).roundToInt() }
+ )+fadeIn())
+ .togetherWith(
+ slideOutVertically(
+ targetOffsetY = { fullHeight -> (-fullHeight * 0.1f).roundToInt() }
+ )
+ )
+ },) {
+ when (it) {
+ OnboardingSteps.INTRO -> IntroScreen({
+ currentStep.value = OnboardingSteps.LOGIN
+ })
+ OnboardingSteps.LOGIN -> LoginScreen(authenticationViewModel,{ newUser ->
+ if (newUser) {
+ currentStep.value = OnboardingSteps.USERNAME
+ } else {
+ startMainActivity()
+ }
+ })
+ OnboardingSteps.USERNAME -> UsernameSelectionScreen(authenticationViewModel,
+ {
+ currentStep.value = OnboardingSteps.LOGIN
+ }, {
+ currentStep.value = OnboardingSteps.SETUP
+ }
+ )
+ OnboardingSteps.SETUP -> SetupScreen({
+ startMainActivity()
+ })
+ }
+ }
+ }
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ window.isNavigationBarContrastEnforced = false
+ val controller = WindowCompat.getInsetsController(window, window.decorView)
+ controller.isAppearanceLightNavigationBars = false
+ controller.isAppearanceLightStatusBars = false
+ window.setNavigationBarDarkIcons(false)
+ }
+ }
+
+ private fun startMainActivity() {
+ val intent = Intent(this@OnboardingActivity, MainActivity::class.java)
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
+ startActivity(intent)
+ finish()
+ }
+
+ private fun sendAuthToWearables(response: UserAuthResponse) {
+ lifecycleScope.launch(Dispatchers.IO) {
+ val messageClient: MessageClient = Wearable.getMessageClient(this@OnboardingActivity)
+ val capabilityClient: CapabilityClient = Wearable.getCapabilityClient(this@OnboardingActivity)
+ try {
+ val info =
+ Tasks.await(
+ capabilityClient.getCapability(
+ "receive_message",
+ CapabilityClient.FILTER_REACHABLE,
+ ),
+ )
+ info.nodes.forEach {
+ Tasks.await(
+ messageClient.sendMessage(
+ it.id,
+ "/auth",
+ "${response.id}:${response.apiToken}".toByteArray(),
+ ),
+ )
+ }
+ } catch (_: Exception) {
+ // Wearable API is not available on this device.
+ }
+ }
+ }
+
+ private fun showError(error: AuthenticationErrors) {
+ val alert = HabiticaAlertDialog(this)
+ if (error.isValidationError) {
+ alert.setTitle(R.string.login_validation_error_title)
+ } else {
+ alert.setTitle(R.string.authentication_error_title)
+ }
+ alert.setMessage(error.translatedMessage(this))
+ alert.addOkButton()
+ alert.show()
+ }
+
+ private fun onForgotPasswordClicked() {
+ val input = EditText(this)
+ input.setAutofillHints(EditText.AUTOFILL_HINT_EMAIL_ADDRESS)
+ input.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
+ input.hint = getString(R.string.forgot_password_hint_example)
+ input.textSize = 16f
+ val lp =
+ LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ )
+ input.layoutParams = lp
+ val alertDialog = HabiticaAlertDialog(this)
+ alertDialog.setTitle(R.string.forgot_password_title)
+ alertDialog.setMessage(R.string.forgot_password_description)
+ alertDialog.setAdditionalContentView(input)
+ alertDialog.addButton(R.string.send, true) { _, _ ->
+ lifecycleScope.launchCatching {
+ userRepository.sendPasswordResetEmail(input.text.toString())
+ showPasswordEmailConfirmation()
+ }
+ }
+ alertDialog.addCancelButton()
+ alertDialog.show()
+ }
+
+ private fun showPasswordEmailConfirmation() {
+ val alert = HabiticaAlertDialog(this)
+ alert.setMessage(R.string.forgot_password_confirmation)
+ alert.addOkButton()
+ alert.show()
+ }
+
+ override fun finish() {
+ dismissKeyboard()
+ super.finish()
+ }
+}
+
+fun AuthenticationErrors.translatedMessage(context: Context): String {
+ return when (this) {
+ AuthenticationErrors.GET_CREDENTIALS_ERROR -> context.getString(R.string.auth_get_credentials_error)
+ AuthenticationErrors.INVALID_CREDENTIALS -> context.getString(R.string.auth_invalid_credentials)
+
+ AuthenticationErrors.MISSING_FIELDS -> context.getString(R.string.login_validation_error_fieldsmissing)
+ AuthenticationErrors.PASSWORD_MISMATCH -> context.getString(R.string.password_not_matching)
+ AuthenticationErrors.PASSWORD_TOO_SHORT -> context.getString(R.string.password_too_short, minPasswordLength)
+ }
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt
deleted file mode 100644
index 0057a583e..000000000
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt
+++ /dev/null
@@ -1,299 +0,0 @@
-package com.habitrpg.android.habitica.ui.activities
-
-import android.content.Intent
-import android.graphics.drawable.Drawable
-import android.os.Build
-import android.os.Bundle
-import android.view.View
-import android.view.inputmethod.InputMethodManager
-import androidx.appcompat.content.res.AppCompatResources
-import androidx.core.content.ContextCompat
-import androidx.core.content.edit
-import androidx.core.view.ViewCompat
-import androidx.core.view.WindowInsetsCompat
-import androidx.core.view.updatePadding
-import androidx.fragment.app.Fragment
-import androidx.lifecycle.lifecycleScope
-import androidx.preference.PreferenceManager
-import androidx.viewpager2.adapter.FragmentStateAdapter
-import androidx.viewpager2.widget.ViewPager2
-import com.google.android.material.tabs.TabLayoutMediator
-import com.habitrpg.android.habitica.R
-import com.habitrpg.android.habitica.data.ApiClient
-import com.habitrpg.android.habitica.data.InventoryRepository
-import com.habitrpg.android.habitica.data.TaskRepository
-import com.habitrpg.android.habitica.databinding.ActivitySetupBinding
-import com.habitrpg.android.habitica.extensions.consumeWindowInsetsAbove30
-import com.habitrpg.android.habitica.helpers.Analytics
-import com.habitrpg.android.habitica.helpers.EventCategory
-import com.habitrpg.android.habitica.helpers.HitType
-import com.habitrpg.android.habitica.models.user.User
-import com.habitrpg.android.habitica.ui.fragments.setup.AvatarSetupFragment
-import com.habitrpg.android.habitica.ui.fragments.setup.TaskSetupFragment
-import com.habitrpg.android.habitica.ui.fragments.setup.WelcomeFragment
-import com.habitrpg.common.habitica.extensions.dpToPx
-import com.habitrpg.common.habitica.helpers.ExceptionHandler
-import com.habitrpg.common.habitica.helpers.launchCatching
-import dagger.hilt.android.AndroidEntryPoint
-import kotlinx.coroutines.flow.debounce
-import kotlinx.coroutines.launch
-import java.util.Calendar
-import java.util.Locale
-import javax.inject.Inject
-import kotlin.time.Duration.Companion.milliseconds
-
-@AndroidEntryPoint
-class SetupActivity : BaseActivity() {
- private lateinit var binding: ActivitySetupBinding
-
- @Inject
- lateinit var apiClient: ApiClient
-
- @Inject
- lateinit var inventoryRepository: InventoryRepository
-
- @Inject
- lateinit var taskRepository: TaskRepository
-
- internal var welcomeFragment: WelcomeFragment? = null
- internal var avatarSetupFragment: AvatarSetupFragment? = null
- internal var taskSetupFragment: TaskSetupFragment? = null
- internal var user: User? = null
- private var completedSetup = false
- private var createdTasks = false
-
- private val isLastPage: Boolean
- get() =
- binding.viewPager.adapter == null || binding.viewPager.currentItem == (
- binding.viewPager.adapter?.itemCount
- ?: 0
- ) - 1
-
- override fun getLayoutResId(): Int {
- return R.layout.activity_setup
- }
-
- override fun getContentView(layoutResId: Int?): View {
- binding = ActivitySetupBinding.inflate(layoutInflater)
- return binding.root
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- lifecycleScope.launch(ExceptionHandler.coroutine()) {
- userRepository.getUser()
- .debounce(500.milliseconds)
- .collect { onUserReceived(it) }
- }
- lifecycleScope.launch(ExceptionHandler.coroutine()) {
- userRepository.retrieveUser(true, true)
- }
- val additionalData = HashMap()
- additionalData["status"] = "displayed"
- Analytics.sendEvent("setup", EventCategory.BEHAVIOUR, HitType.EVENT, additionalData)
-
- val currentDeviceLanguage = Locale.getDefault().language
- for (language in resources.getStringArray(R.array.LanguageValues)) {
- if (language == currentDeviceLanguage) {
- lifecycleScope.launchCatching {
- apiClient.registrationLanguage(currentDeviceLanguage)
- }
- }
- }
-
- binding.viewPager.isUserInputEnabled = false
-
- binding.previousButton.setOnClickListener { previousClicked() }
- binding.nextButton.setOnClickListener { nextClicked() }
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- window.isNavigationBarContrastEnforced = false
- }
-
- ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, windowInsets ->
- val insets = windowInsets.getInsets(
- WindowInsetsCompat.Type.systemBars()
- + WindowInsetsCompat.Type.displayCutout()
- )
- binding.viewPager.updatePadding(
- left = insets.left,
- right = insets.right,
- top = insets.top
- )
- binding.bottomBar.updatePadding(
- bottom = insets.bottom
- )
- binding.bottomBar.layoutParams.height = 56.dpToPx(this) + insets.bottom
- consumeWindowInsetsAbove30(windowInsets)
- }
- }
-
- override fun onDestroy() {
- userRepository.close()
- super.onDestroy()
- }
-
- private fun setupViewpager() {
- setViewPagerAdapter()
- binding.viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
- override fun onPageSelected(position: Int) {
- super.onPageSelected(position)
- when {
- position == 0 -> {
- setPreviousButtonEnabled(false)
- binding.nextButton.text = getString(R.string.next_button)
- }
- isLastPage -> {
- setPreviousButtonEnabled(true)
- binding.nextButton.text = getString(R.string.finish)
- }
- else -> {
- setPreviousButtonEnabled(true)
- binding.nextButton.text = getString(R.string.next_button)
- }
- }
- }
- })
- }
-
- private fun nextClicked() {
- val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
- sharedPreferences.edit {
- putString("FirstDayOfTheWeek", Calendar.getInstance().firstDayOfWeek.toString())
- }
- if (isLastPage) {
- if (this.taskSetupFragment == null) {
- return
- }
- if (createdTasks) {
- onUserReceived(user)
- return
- }
- val newTasks = this.taskSetupFragment?.createSampleTasks()
- this.completedSetup = true
- createdTasks = true
- newTasks?.let {
- lifecycleScope.launchCatching {
- taskRepository.createTasks(it)
- }
- }
- } else if (binding.viewPager.currentItem == 0) {
- confirmNames(welcomeFragment?.displayName ?: "", welcomeFragment?.username ?: "")
-
- val imm = getSystemService(INPUT_METHOD_SERVICE) as? InputMethodManager
- imm?.hideSoftInputFromWindow(currentFocus?.windowToken, 0)
- }
- binding.viewPager.currentItem = binding.viewPager.currentItem + 1
- }
-
- private fun previousClicked() {
- binding.viewPager.currentItem = binding.viewPager.currentItem - 1
- }
-
- private fun setPreviousButtonEnabled(enabled: Boolean) {
- val leftDrawable: Drawable?
- if (enabled) {
- binding.previousButton.setText(R.string.action_back)
- leftDrawable = AppCompatResources.getDrawable(this, R.drawable.back_arrow_enabled)
- } else {
- binding.previousButton.text = null
- leftDrawable = AppCompatResources.getDrawable(this, R.drawable.back_arrow_disabled)
- }
- binding.previousButton.setCompoundDrawablesWithIntrinsicBounds(
- leftDrawable,
- null,
- null,
- null
- )
- }
-
- private fun setNextButtonEnabled(enabled: Boolean) {
- binding.nextButton.isEnabled = enabled
- val rightDrawable = AppCompatResources.getDrawable(this, R.drawable.forward_arrow_enabled)
- if (enabled) {
- binding.nextButton.setTextColor(ContextCompat.getColor(this, R.color.white))
- rightDrawable?.alpha = 255
- } else {
- binding.nextButton.setTextColor(ContextCompat.getColor(this, R.color.white_50_alpha))
- rightDrawable?.alpha = 127
- }
- binding.nextButton.setCompoundDrawablesWithIntrinsicBounds(null, null, rightDrawable, null)
- }
-
- private var hasCompleted = false
-
- private fun onUserReceived(user: User?) {
- if (completedSetup && !hasCompleted) {
- val additionalData = HashMap()
- additionalData["status"] = "completed"
- Analytics.sendEvent("setup", EventCategory.BEHAVIOUR, HitType.EVENT, additionalData)
- hasCompleted = true
- lifecycleScope.launchCatching {
- userRepository.updateUser("flags.welcomed", true)
- userRepository.retrieveUser(true, true)
- startMainActivity()
- }
- return
- }
- this.user = user
- if (binding.viewPager.adapter == null) {
- setupViewpager()
- }
- avatarSetupFragment?.setUser(user)
- taskSetupFragment?.setUser(user)
- }
-
- private fun startMainActivity() {
- val intent = Intent(this@SetupActivity, MainActivity::class.java)
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
- startActivity(intent)
- finish()
- }
-
- private fun confirmNames(
- displayName: String,
- username: String
- ) {
- lifecycleScope.launch(ExceptionHandler.coroutine()) {
- userRepository.updateUser("profile.name", displayName)
- userRepository.updateLoginName(username)
- }
- }
-
- private fun setViewPagerAdapter() {
- val fragmentManager = supportFragmentManager
- val viewPagerAdapter = object : FragmentStateAdapter(fragmentManager, lifecycle) {
- override fun createFragment(position: Int): Fragment {
- return when (position) {
- 1 -> {
- val fragment = AvatarSetupFragment()
- fragment.activity = this@SetupActivity
- fragment.setUser(user)
- fragment.width = binding.viewPager.width
- avatarSetupFragment = fragment
- fragment
- }
-
- 2 -> {
- val fragment = TaskSetupFragment()
- fragment.setUser(user)
- taskSetupFragment = fragment
- fragment
- }
-
- else -> {
- val fragment = WelcomeFragment()
- welcomeFragment = fragment
- welcomeFragment?.onNameValid = { setNextButtonEnabled(it == true) }
- fragment
- }
- }
- }
-
- override fun getItemCount(): Int {
- return 3
- }
- }
- binding.viewPager.adapter = viewPagerAdapter
- TabLayoutMediator(binding.viewPagerIndicator, binding.viewPager) { tab, position -> }.attach()
- }
-}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/AvatarSetupFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/AvatarSetupFragment.kt
deleted file mode 100644
index ba579ef53..000000000
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/AvatarSetupFragment.kt
+++ /dev/null
@@ -1,349 +0,0 @@
-package com.habitrpg.android.habitica.ui.fragments.setup
-
-import android.os.Bundle
-import android.util.TypedValue
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.RelativeLayout
-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.data.InventoryRepository
-import com.habitrpg.android.habitica.data.SetupCustomizationRepository
-import com.habitrpg.android.habitica.data.UserRepository
-import com.habitrpg.android.habitica.databinding.FragmentSetupAvatarBinding
-import com.habitrpg.android.habitica.extensions.applyScrollContentWindowInsets
-import com.habitrpg.android.habitica.models.SetupCustomization
-import com.habitrpg.android.habitica.models.user.User
-import com.habitrpg.android.habitica.ui.activities.SetupActivity
-import com.habitrpg.android.habitica.ui.adapter.setup.CustomizationSetupAdapter
-import com.habitrpg.android.habitica.ui.fragments.BaseFragment
-import com.habitrpg.android.habitica.ui.views.setup.AvatarCategoryView
-import com.habitrpg.common.habitica.helpers.launchCatching
-import dagger.hilt.android.AndroidEntryPoint
-import java.util.Random
-import javax.inject.Inject
-
-@AndroidEntryPoint
-class AvatarSetupFragment : BaseFragment() {
- @Inject
- lateinit var customizationRepository: SetupCustomizationRepository
-
- @Inject
- lateinit var userRepository: UserRepository
-
- @Inject
- lateinit var inventoryRepository: InventoryRepository
-
- override var binding: FragmentSetupAvatarBinding? = null
-
- override fun createBinding(
- inflater: LayoutInflater,
- container: ViewGroup?
- ): FragmentSetupAvatarBinding {
- return FragmentSetupAvatarBinding.inflate(inflater, container, false)
- }
-
- var activity: SetupActivity? = null
- var width: Int = 0
-
- internal var adapter: CustomizationSetupAdapter? = null
-
- private var user: User? = null
- private var subcategories: List = emptyList()
- private var activeButton: AvatarCategoryView? = null
- private var activeCategory: String? = null
- private var activeSubCategory: String? = null
- private var random = Random()
-
- override fun onViewCreated(
- view: View,
- savedInstanceState: Bundle?
- ) {
- super.onViewCreated(view, savedInstanceState)
-
- this.adapter = CustomizationSetupAdapter()
- this.adapter?.userSize = this.user?.preferences?.size ?: "slim"
- adapter?.onUpdateUser = {
- lifecycleScope.launchCatching {
- userRepository.updateUser(it)
- }
- }
- adapter?.onEquipGear = {
- lifecycleScope.launchCatching {
- inventoryRepository.equip("equipped", it)
- }
- }
-
- this.adapter?.user = this.user
- val layoutManager = LinearLayoutManager(activity)
- layoutManager.orientation = LinearLayoutManager.HORIZONTAL
- binding?.customizationDrawer?.binding?.customizationList?.layoutManager = layoutManager
-
- binding?.customizationDrawer?.binding?.customizationList?.adapter = this.adapter
-
- binding?.customizationDrawer?.binding?.subcategoryTabs?.addOnTabSelectedListener(
- object :
- TabLayout.OnTabSelectedListener {
- override fun onTabSelected(tab: TabLayout.Tab) {
- val position = tab.position
- if (position < subcategories.size) {
- activeSubCategory = subcategories[position]
- }
- loadCustomizations()
- }
-
- override fun onTabUnselected(tab: TabLayout.Tab) { // no-on
- }
-
- override fun onTabReselected(tab: TabLayout.Tab) { // no-on
- }
- }
- )
-
- binding?.customizationDrawer?.binding?.bodyButton?.setOnClickListener { selectedBodyCategory() }
- binding?.customizationDrawer?.binding?.skinButton?.setOnClickListener { selectedSkinCategory() }
- binding?.customizationDrawer?.binding?.hairButton?.setOnClickListener { selectedHairCategory() }
- binding?.customizationDrawer?.binding?.extrasButton?.setOnClickListener { selectedExtrasCategory() }
- binding?.randomizeButton?.setOnClickListener { randomizeCharacter() }
-
- this.selectedBodyCategory()
-
- if (this.user != null) {
- this.updateAvatar()
- }
- binding?.contentWrapper?.let { applyScrollContentWindowInsets(it) }
- }
-
- override fun onResume() {
- super.onResume()
- if (this.user != null) {
- this.updateAvatar()
- }
- this.selectedBodyCategory()
- if (context != null) {
- binding?.speechBubble?.animateText(
- context?.getString(R.string.avatar_setup_description) ?: ""
- )
- }
- }
-
- private fun loadCustomizations() {
- val user = this.user ?: return
- val activeCategory = this.activeCategory ?: return
-
- this.adapter?.setCustomizationList(
- customizationRepository.getCustomizations(
- activeCategory,
- activeSubCategory,
- user
- )
- )
- }
-
- fun setUser(user: User?) {
- this.user = user
- if (binding?.avatarView != null) {
- updateAvatar()
- }
- if (this.adapter != null) {
- this.adapter?.user = user
- this.adapter?.notifyDataSetChanged()
- loadCustomizations()
- }
- }
-
- private fun updateAvatar() {
- user?.let {
- binding?.avatarView?.setAvatar(it)
- }
- }
-
- private fun selectedBodyCategory() {
- activateButton(binding?.customizationDrawer?.binding?.bodyButton)
- this.activeCategory = SetupCustomizationRepository.CATEGORY_BODY
- binding?.customizationDrawer?.binding?.subcategoryTabs?.removeAllTabs()
- this.subcategories =
- listOf(
- SetupCustomizationRepository.SUBCATEGORY_SIZE,
- SetupCustomizationRepository.SUBCATEGORY_SHIRT
- )
- binding?.customizationDrawer?.binding?.subcategoryTabs?.newTab()
- ?.setText(R.string.avatar_size)
- ?.let { binding?.customizationDrawer?.binding?.subcategoryTabs?.addTab(it) }
- binding?.customizationDrawer?.binding?.subcategoryTabs?.newTab()
- ?.setText(R.string.avatar_shirt)
- ?.let { binding?.customizationDrawer?.binding?.subcategoryTabs?.addTab(it) }
- loadCustomizations()
- }
-
- private fun selectedSkinCategory() {
- activateButton(binding?.customizationDrawer?.binding?.skinButton)
- this.activeCategory = SetupCustomizationRepository.CATEGORY_SKIN
- binding?.customizationDrawer?.binding?.subcategoryTabs?.removeAllTabs()
- this.subcategories = listOf(SetupCustomizationRepository.SUBCATEGORY_COLOR)
- binding?.customizationDrawer?.binding?.subcategoryTabs?.newTab()
- ?.setText(R.string.avatar_skin_color)
- ?.let { binding?.customizationDrawer?.binding?.subcategoryTabs?.addTab(it) }
- loadCustomizations()
- }
-
- private fun selectedHairCategory() {
- activateButton(binding?.customizationDrawer?.binding?.hairButton)
- this.activeCategory = SetupCustomizationRepository.CATEGORY_HAIR
- binding?.customizationDrawer?.binding?.subcategoryTabs?.removeAllTabs()
- this.subcategories =
- listOf(
- SetupCustomizationRepository.SUBCATEGORY_BANGS,
- SetupCustomizationRepository.SUBCATEGORY_COLOR,
- SetupCustomizationRepository.SUBCATEGORY_PONYTAIL
- )
- binding?.customizationDrawer?.binding?.subcategoryTabs?.newTab()
- ?.setText(R.string.avatar_hair_bangs)
- ?.let { binding?.customizationDrawer?.binding?.subcategoryTabs?.addTab(it) }
- binding?.customizationDrawer?.binding?.subcategoryTabs?.newTab()
- ?.setText(R.string.avatar_hair_color)
- ?.let { binding?.customizationDrawer?.binding?.subcategoryTabs?.addTab(it) }
- binding?.customizationDrawer?.binding?.subcategoryTabs?.newTab()
- ?.setText(R.string.avatar_hair_ponytail)
- ?.let { binding?.customizationDrawer?.binding?.subcategoryTabs?.addTab(it) }
- loadCustomizations()
- }
-
- private fun selectedExtrasCategory() {
- activateButton(binding?.customizationDrawer?.binding?.extrasButton)
- this.activeCategory = SetupCustomizationRepository.CATEGORY_EXTRAS
- binding?.customizationDrawer?.binding?.subcategoryTabs?.removeAllTabs()
- this.subcategories =
- listOf(
- SetupCustomizationRepository.SUBCATEGORY_GLASSES,
- SetupCustomizationRepository.SUBCATEGORY_FLOWER,
- SetupCustomizationRepository.SUBCATEGORY_WHEELCHAIR
- )
- binding?.customizationDrawer?.binding?.subcategoryTabs?.newTab()
- ?.setText(R.string.avatar_glasses)
- ?.let { binding?.customizationDrawer?.binding?.subcategoryTabs?.addTab(it) }
- binding?.customizationDrawer?.binding?.subcategoryTabs?.newTab()
- ?.setText(R.string.avatar_flower)
- ?.let { binding?.customizationDrawer?.binding?.subcategoryTabs?.addTab(it) }
- binding?.customizationDrawer?.binding?.subcategoryTabs?.newTab()
- ?.setText(R.string.avatar_wheelchair)
- ?.let { binding?.customizationDrawer?.binding?.subcategoryTabs?.addTab(it) }
- loadCustomizations()
- }
-
- private fun randomizeCharacter() {
- val user = this.user ?: return
- val updateData = HashMap()
- updateData["preferences.size"] =
- chooseRandomKey(
- customizationRepository.getCustomizations(
- SetupCustomizationRepository.CATEGORY_BODY,
- SetupCustomizationRepository.SUBCATEGORY_SIZE,
- user
- ),
- false
- )
- updateData["preferences.shirt"] =
- chooseRandomKey(
- customizationRepository.getCustomizations(
- SetupCustomizationRepository.CATEGORY_BODY,
- SetupCustomizationRepository.SUBCATEGORY_SHIRT,
- user
- ),
- false
- )
- updateData["preferences.skin"] =
- chooseRandomKey(
- customizationRepository.getCustomizations(
- SetupCustomizationRepository.CATEGORY_SKIN,
- SetupCustomizationRepository.SUBCATEGORY_COLOR,
- user
- ),
- false
- )
- updateData["preferences.hair.color"] =
- chooseRandomKey(
- customizationRepository.getCustomizations(
- SetupCustomizationRepository.CATEGORY_HAIR,
- SetupCustomizationRepository.SUBCATEGORY_COLOR,
- user
- ),
- false
- )
- updateData["preferences.hair.base"] =
- chooseRandomKey(
- customizationRepository.getCustomizations(
- SetupCustomizationRepository.CATEGORY_HAIR,
- SetupCustomizationRepository.SUBCATEGORY_PONYTAIL,
- user
- ),
- false
- )
- updateData["preferences.hair.bangs"] =
- chooseRandomKey(
- customizationRepository.getCustomizations(
- SetupCustomizationRepository.CATEGORY_HAIR,
- SetupCustomizationRepository.SUBCATEGORY_BANGS,
- user
- ),
- false
- )
- updateData["preferences.hair.flower"] =
- chooseRandomKey(
- customizationRepository.getCustomizations(
- SetupCustomizationRepository.CATEGORY_EXTRAS,
- SetupCustomizationRepository.SUBCATEGORY_FLOWER,
- user
- ),
- true
- )
- updateData["preferences.chair"] =
- chooseRandomKey(
- customizationRepository.getCustomizations(
- SetupCustomizationRepository.CATEGORY_EXTRAS,
- SetupCustomizationRepository.SUBCATEGORY_WHEELCHAIR,
- user
- ),
- true
- )
- lifecycleScope.launchCatching {
- userRepository.updateUser(updateData)
- }
- }
-
- @Suppress("ReturnCount")
- private fun chooseRandomKey(
- customizations: List,
- weighFirstOption: Boolean
- ): String {
- if (customizations.isEmpty()) {
- return ""
- }
- if (weighFirstOption) {
- if (random.nextInt(10) > 3) {
- return customizations[0].key
- }
- }
- return customizations[random.nextInt(customizations.size)].key
- }
-
- private fun activateButton(button: AvatarCategoryView?) {
- if (this.activeButton != null) {
- this.activeButton?.setActive(false)
- }
- this.activeButton = button
- this.activeButton?.setActive(true)
- val location = IntArray(2)
- val params =
- binding?.customizationDrawer?.binding?.caretView?.layoutParams as? RelativeLayout.LayoutParams
- this.activeButton?.getLocationOnScreen(location)
- val r = resources
- val px =
- TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 40f, r.displayMetrics).toInt()
- params?.marginStart = location[0] + px
- binding?.customizationDrawer?.binding?.caretView?.layoutParams = params
- }
-}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/IntroFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/IntroFragment.kt
deleted file mode 100644
index 910663b4c..000000000
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/IntroFragment.kt
+++ /dev/null
@@ -1,92 +0,0 @@
-package com.habitrpg.android.habitica.ui.fragments.setup
-
-import android.graphics.drawable.Drawable
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import com.habitrpg.android.habitica.databinding.FragmentIntroBinding
-import com.habitrpg.android.habitica.ui.fragments.BaseFragment
-import dagger.hilt.android.AndroidEntryPoint
-
-@AndroidEntryPoint
-class IntroFragment : BaseFragment() {
- override var binding: FragmentIntroBinding? = null
-
- override fun createBinding(
- inflater: LayoutInflater,
- container: ViewGroup?
- ): FragmentIntroBinding {
- return FragmentIntroBinding.inflate(inflater, container, false)
- }
-
- private var image: Drawable? = null
- private var titleImage: Drawable? = null
- private var subtitle: String? = null
- private var title: String? = null
- private var description: String? = null
- private var backgroundColor: Int? = null
-
- override fun onViewCreated(
- view: View,
- savedInstanceState: Bundle?
- ) {
- super.onViewCreated(view, savedInstanceState)
-
- if (this.image != null) {
- binding?.imageView?.setImageDrawable(this.image)
- }
-
- if (this.titleImage != null) {
- binding?.titleImageView?.setImageDrawable(this.titleImage)
- }
-
- if (this.subtitle != null) {
- binding?.subtitleTextView?.text = this.subtitle
- }
-
- if (this.title != null) {
- binding?.titleTextView?.text = this.title
- }
-
- if (this.description != null) {
- binding?.descriptionTextView?.text = this.description
- }
-
- backgroundColor?.let {
- binding?.containerView?.setBackgroundColor(it)
- }
- }
-
- fun setImage(image: Drawable?) {
- this.image = image
- if (image != null) {
- binding?.imageView?.setImageDrawable(image)
- }
- }
-
- fun setTitleImage(image: Drawable?) {
- this.titleImage = image
- binding?.titleImageView?.setImageDrawable(image)
- }
-
- fun setSubtitle(text: String?) {
- this.subtitle = text
- binding?.subtitleTextView?.text = text
- }
-
- fun setTitle(text: String?) {
- this.title = text
- binding?.titleTextView?.text = text
- }
-
- fun setDescription(text: String?) {
- this.description = text
- binding?.descriptionTextView?.text = text
- }
-
- fun setBackgroundColor(color: Int) {
- this.backgroundColor = color
- binding?.containerView?.setBackgroundColor(color)
- }
-}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/TaskSetupFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/TaskSetupFragment.kt
deleted file mode 100644
index e8b62f0d2..000000000
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/TaskSetupFragment.kt
+++ /dev/null
@@ -1,274 +0,0 @@
-package com.habitrpg.android.habitica.ui.fragments.setup
-
-import android.graphics.drawable.BitmapDrawable
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.recyclerview.widget.GridLayoutManager
-import com.habitrpg.android.habitica.R
-import com.habitrpg.android.habitica.databinding.FragmentSetupTasksBinding
-import com.habitrpg.android.habitica.extensions.applyScrollContentWindowInsets
-import com.habitrpg.android.habitica.models.tasks.Days
-import com.habitrpg.android.habitica.models.tasks.Task
-import com.habitrpg.android.habitica.models.user.User
-import com.habitrpg.android.habitica.ui.activities.SetupActivity
-import com.habitrpg.android.habitica.ui.adapter.setup.TaskSetupAdapter
-import com.habitrpg.android.habitica.ui.fragments.BaseFragment
-import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
-import com.habitrpg.shared.habitica.models.tasks.Frequency
-import com.habitrpg.shared.habitica.models.tasks.TaskType
-import dagger.hilt.android.AndroidEntryPoint
-import java.util.Date
-import java.util.UUID
-
-@AndroidEntryPoint
-class TaskSetupFragment : BaseFragment() {
- var activity: SetupActivity? = null
- var width: Int = 0
-
- override var binding: FragmentSetupTasksBinding? = null
-
- override fun createBinding(
- inflater: LayoutInflater,
- container: ViewGroup?
- ): FragmentSetupTasksBinding {
- return FragmentSetupTasksBinding.inflate(inflater, container, false)
- }
-
- internal var adapter: TaskSetupAdapter = TaskSetupAdapter()
- private var taskGroups: List> = listOf()
- private var tasks: List> = listOf()
- private var user: User? = null
-
- override fun onViewCreated(
- view: View,
- savedInstanceState: Bundle?
- ) {
- super.onViewCreated(view, savedInstanceState)
-
- this.setTasks()
-
- this.adapter = TaskSetupAdapter()
- this.adapter.setTaskList(this.taskGroups)
- binding?.recyclerView?.layoutManager = GridLayoutManager(activity, 2)
- binding?.recyclerView?.adapter = this.adapter
-
- if (this.user != null) {
- this.updateAvatar()
- }
-
- binding?.heartIcon?.setImageDrawable(
- BitmapDrawable(
- resources,
- HabiticaIconsHelper.imageOfHeartLightBg()
- )
- )
-
- binding?.contentWrapper?.let { applyScrollContentWindowInsets(it) }
- }
-
- override fun onResume() {
- super.onResume()
- if (context != null) {
- binding?.speechBubble?.animateText(
- context?.getString(R.string.task_setup_description) ?: ""
- )
- }
- }
-
- fun setUser(user: User?) {
- this.user = user
- if (binding?.avatarView != null) {
- updateAvatar()
- }
- }
-
- private fun updateAvatar() {
- user?.let {
- binding?.avatarView?.setAvatar(it)
- }
- }
-
- private fun setTasks() {
- this.taskGroups =
- listOf(
- listOf(getString(R.string.setup_group_work), TYPE_WORK),
- listOf(getString(R.string.setup_group_exercise), TYPE_EXERCISE),
- listOf(getString(R.string.setup_group_health), TYPE_HEALTH),
- listOf(getString(R.string.setup_group_school), TYPE_SCHOOL),
- listOf(getString(R.string.setup_group_teams), TYPE_TEAMS),
- listOf(getString(R.string.setup_group_chores), TYPE_CHORES),
- listOf(getString(R.string.setup_group_creativity), TYPE_CREATIVITY),
- listOf(getString(R.string.setuP_group_other), TYPE_OTHER)
- )
-
- this.tasks =
- listOf(
- listOf(TYPE_WORK, TaskType.HABIT, getString(R.string.setup_task_work_1), true, false),
- listOf(TYPE_WORK, TaskType.DAILY, getString(R.string.setup_task_work_2)),
- listOf(TYPE_WORK, TaskType.TODO, getString(R.string.setup_task_work_3)),
- listOf(
- TYPE_EXERCISE,
- TaskType.HABIT,
- getString(R.string.setup_task_exercise_1),
- true,
- false
- ),
- listOf(TYPE_EXERCISE, TaskType.DAILY, getString(R.string.setup_task_exercise_2)),
- listOf(TYPE_EXERCISE, TaskType.TODO, getString(R.string.setup_task_exercise_3)),
- listOf(
- TYPE_HEALTH,
- TaskType.HABIT,
- getString(R.string.setup_task_healthWellness_1),
- true,
- true
- ),
- listOf(TYPE_HEALTH, TaskType.DAILY, getString(R.string.setup_task_healthWellness_2)),
- listOf(TYPE_HEALTH, TaskType.TODO, getString(R.string.setup_task_healthWellness_3)),
- listOf(
- TYPE_SCHOOL,
- TaskType.HABIT,
- getString(R.string.setup_task_school_1),
- true,
- true
- ),
- listOf(TYPE_SCHOOL, TaskType.DAILY, getString(R.string.setup_task_school_2)),
- listOf(TYPE_SCHOOL, TaskType.TODO, getString(R.string.setup_task_school_3)),
- listOf(TYPE_TEAMS, TaskType.HABIT, getString(R.string.setup_task_teams_1), true, false),
- listOf(TYPE_TEAMS, TaskType.DAILY, getString(R.string.setup_task_teams_2)),
- listOf(TYPE_TEAMS, TaskType.TODO, getString(R.string.setup_task_teams_3)),
- listOf(
- TYPE_CHORES,
- TaskType.HABIT,
- getString(R.string.setup_task_chores_1),
- true,
- false
- ),
- listOf(TYPE_CHORES, TaskType.DAILY, getString(R.string.setup_task_chores_2)),
- listOf(TYPE_CHORES, TaskType.TODO, getString(R.string.setup_task_chores_3)),
- listOf(
- TYPE_CREATIVITY,
- TaskType.HABIT,
- getString(R.string.setup_task_creativity_1),
- true,
- false
- ),
- listOf(TYPE_CREATIVITY, TaskType.DAILY, getString(R.string.setup_task_creativity_2)),
- listOf(TYPE_CREATIVITY, TaskType.TODO, getString(R.string.setup_task_creativity_3))
- )
- }
-
- fun createSampleTasks(): List {
- val groups = ArrayList()
- for ((i, checked) in this.adapter.checkedList.withIndex()) {
- if (checked) {
- groups.add(this.taskGroups[i][1])
- }
- }
- val tasks = ArrayList()
- for (task in this.tasks) {
- val taskGroup = task[0] as? String
- if (groups.contains(taskGroup)) {
- val taskObject: Task =
- if (task.size == 5) {
- this.makeTaskObject(
- task[1] as? TaskType,
- task[2] as? String,
- task[3] as? Boolean,
- task[4] as? Boolean
- )
- } else {
- this.makeTaskObject(task[1] as? TaskType, task[2] as? String, null, null)
- }
- tasks.add(taskObject)
- }
- }
- tasks.add(
- makeTaskObject(
- TaskType.HABIT,
- getString(R.string.setup_task_habit_1),
- true,
- false,
- getString(R.string.setup_task_habit_1_notes)
- )
- )
- tasks.add(
- makeTaskObject(
- TaskType.HABIT,
- getString(R.string.setup_task_habit_2),
- false,
- true,
- getString(R.string.setup_task_habit_2_notes)
- )
- )
- tasks.add(
- makeTaskObject(
- TaskType.REWARD,
- getString(R.string.setup_task_reward),
- null,
- null,
- getString(R.string.setup_task_reward_notes)
- )
- )
- tasks.add(
- makeTaskObject(
- TaskType.TODO,
- getString(R.string.setup_task_join_habitica),
- null,
- null,
- getString(R.string.setup_task_join_habitica_notes)
- )
- )
- return tasks
- }
-
- private fun makeTaskObject(
- type: TaskType?,
- text: String?,
- up: Boolean?,
- down: Boolean?,
- notes: String? = null
- ): Task {
- val task = Task()
- task.id = UUID.randomUUID().toString()
- task.text = text ?: ""
- task.notes = notes
- task.priority = 1.0f
- task.type = type ?: TaskType.HABIT
- task.frequency = Frequency.DAILY
-
- if (type == TaskType.HABIT) {
- task.up = up
- task.down = down
- }
-
- if (type == TaskType.DAILY) {
- task.frequency = Frequency.WEEKLY
- task.startDate = Date()
- task.everyX = 1
- val days = Days()
- days.m = true
- days.t = true
- days.w = true
- days.th = true
- days.f = true
- days.s = true
- days.su = true
- task.repeat = days
- }
-
- return task
- }
-
- companion object {
- const val TYPE_EXERCISE = "exercise"
- const val TYPE_HEALTH = "healthWellness"
- const val TYPE_WORK = "work"
- const val TYPE_SCHOOL = "school"
- const val TYPE_TEAMS = "teams"
- const val TYPE_CHORES = "chores"
- const val TYPE_CREATIVITY = "creativity"
- const val TYPE_OTHER = "other"
- }
-}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/WelcomeFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/WelcomeFragment.kt
deleted file mode 100644
index b232668d2..000000000
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/WelcomeFragment.kt
+++ /dev/null
@@ -1,147 +0,0 @@
-package com.habitrpg.android.habitica.ui.fragments.setup
-
-import android.graphics.drawable.BitmapDrawable
-import android.graphics.drawable.Drawable
-import android.graphics.drawable.VectorDrawable
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.core.content.ContextCompat
-import androidx.lifecycle.lifecycleScope
-import com.habitrpg.android.habitica.R
-import com.habitrpg.android.habitica.data.UserRepository
-import com.habitrpg.android.habitica.databinding.FragmentWelcomeBinding
-import com.habitrpg.android.habitica.extensions.OnChangeTextWatcher
-import com.habitrpg.android.habitica.ui.fragments.BaseFragment
-import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
-import com.habitrpg.common.habitica.helpers.ExceptionHandler
-import com.habitrpg.common.habitica.helpers.launchCatching
-import dagger.hilt.android.AndroidEntryPoint
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.firstOrNull
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.launch
-import javax.inject.Inject
-import androidx.core.graphics.drawable.toDrawable
-
-@AndroidEntryPoint
-class WelcomeFragment : BaseFragment() {
- var onNameValid: ((Boolean?) -> Unit)? = null
-
- @Inject
- lateinit var userRepository: UserRepository
-
- override var binding: FragmentWelcomeBinding? = null
-
- override fun createBinding(
- inflater: LayoutInflater,
- container: ViewGroup?
- ): FragmentWelcomeBinding {
- return FragmentWelcomeBinding.inflate(inflater, container, false)
- }
-
- private val displayNameVerificationEvents = MutableStateFlow(null)
- private val usernameVerificationEvents = MutableStateFlow(null)
-
- private val checkmarkIcon: Drawable by lazy {
- context?.let {
- HabiticaIconsHelper.imageOfCheckmark(
- ContextCompat.getColor(it, R.color.green_50),
- 1f
- ).toDrawable(resources)
- } ?: VectorDrawable()
- }
- private val alertIcon: Drawable by lazy {
- HabiticaIconsHelper.imageOfAlertIcon().toDrawable(resources)
- }
- val username: String
- get() = binding?.usernameEditText?.text?.toString() ?: ""
- val displayName: String
- get() = binding?.displayNameEditText?.text?.toString() ?: ""
-
- override fun onViewCreated(
- view: View,
- savedInstanceState: Bundle?
- ) {
- super.onViewCreated(view, savedInstanceState)
-
- binding?.speechBubble?.animateText(context?.getString(R.string.welcome_text) ?: "")
-
- super.onCreate(savedInstanceState)
-
- binding?.displayNameEditText?.addTextChangedListener(
- OnChangeTextWatcher { p0, _, _, _ ->
- displayNameVerificationEvents.value = p0.toString()
- }
- )
- binding?.usernameEditText?.addTextChangedListener(
- OnChangeTextWatcher { p0, _, _, _ ->
- usernameVerificationEvents.value = p0.toString()
- }
- )
-
- lifecycleScope.launchCatching {
- displayNameVerificationEvents
- .map { it?.length in 1..30 }
- .collect {
- if (it) {
- binding?.displayNameEditText?.setCompoundDrawablesWithIntrinsicBounds(
- null,
- null,
- checkmarkIcon,
- null
- )
- binding?.issuesTextView?.visibility = View.GONE
- } else {
- binding?.displayNameEditText?.setCompoundDrawablesWithIntrinsicBounds(
- null,
- null,
- alertIcon,
- null
- )
- binding?.issuesTextView?.visibility = View.VISIBLE
- binding?.issuesTextView?.text =
- context?.getString(R.string.display_name_length_error)
- }
- }
- }
- lifecycleScope.launchCatching {
- usernameVerificationEvents
- .filter { it?.length in 1..30 }
- .filterNotNull()
- .map { userRepository.verifyUsername(it) }
- .collect {
- if (it?.isUsable == true) {
- binding?.usernameEditText?.setCompoundDrawablesWithIntrinsicBounds(
- null,
- null,
- checkmarkIcon,
- null
- )
- binding?.issuesTextView?.visibility = View.GONE
- } else {
- binding?.usernameEditText?.setCompoundDrawablesWithIntrinsicBounds(
- null,
- null,
- alertIcon,
- null
- )
- binding?.issuesTextView?.visibility = View.VISIBLE
- binding?.issuesTextView?.text = it?.issues?.joinToString("\n")
- }
- onNameValid?.invoke(it?.isUsable)
- }
- }
-
- lifecycleScope.launch(ExceptionHandler.coroutine()) {
- val user = userRepository.getUser().firstOrNull()
- binding?.displayNameEditText?.setText(user?.profile?.name)
- displayNameVerificationEvents.value = user?.profile?.name ?: ""
- binding?.usernameEditText?.setText(user?.authentication?.localAuthentication?.username)
- usernameVerificationEvents.value = user?.username ?: ""
- }
- }
-}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/AuthenticationViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/AuthenticationViewModel.kt
index ffa20c593..d0849ef59 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/AuthenticationViewModel.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/AuthenticationViewModel.kt
@@ -6,6 +6,8 @@ import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import android.util.Log
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
import androidx.core.content.edit
import androidx.credentials.CredentialManager
import androidx.credentials.CustomCredential
@@ -41,6 +43,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.tasks.await
@@ -56,6 +59,10 @@ class AuthenticationViewModel @Inject constructor(
val hostConfig: HostConfig,
private val keyHelper: KeyHelper?,
) : ViewModel() {
+ val email = mutableStateOf("")
+ val password = mutableStateOf("")
+ val username = mutableStateOf("")
+
private val _showAuthProgress = MutableStateFlow(false)
val showAuthProgress: Flow = _showAuthProgress
val isRegistering = MutableStateFlow(false)
@@ -63,8 +70,13 @@ class AuthenticationViewModel @Inject constructor(
val authenticationError: Flow = _authenticationError
.onEach { _showAuthProgress.value = false }
private val _authenticationSuccess = MutableStateFlow(null)
- val authenticationSuccess: Flow = _authenticationSuccess
+ val authenticationSuccess: Flow = _authenticationSuccess
+ .filterNotNull()
.onEach { _showAuthProgress.value = false }
+ private val _isUsernameValid = MutableStateFlow(null)
+ val isUsernameValid: Flow = _isUsernameValid
+ private var _usernameIssues = MutableStateFlow(null)
+ val usernameIssues: Flow = _usernameIssues
fun validateInputs(
username: String,
@@ -91,6 +103,24 @@ class AuthenticationViewModel @Inject constructor(
return null
}
+ fun checkUsername(username: String) {
+ viewModelScope.launch {
+ try {
+ val response = apiClient.verifyUsername(username)
+ _isUsernameValid.value = response?.isUsable == true
+ _usernameIssues.value = response?.issues?.joinToString("\n") { it }
+ } catch (e: Exception) {
+ _isUsernameValid.value = null
+ Analytics.logException(e)
+ }
+ }
+ }
+
+ fun invalidateUsernameState() {
+ _isUsernameValid.value = null
+ _usernameIssues.value = null
+ }
+
fun login(username: String, password: String) {
_showAuthProgress.value = true
viewModelScope.launch {
@@ -119,7 +149,7 @@ class AuthenticationViewModel @Inject constructor(
suspend fun removeSocialAuth(network: String) {
apiClient.disconnectSocial(network)
- userRepository.retrieveUser(true, true)
+ userRepository.retrieveUser(true, forced = true)
}
private fun authenticationError(error: AuthenticationErrors? = null) {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/LoginScreenField.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/LoginScreenField.kt
new file mode 100644
index 000000000..6f0b4d7fe
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/LoginScreenField.kt
@@ -0,0 +1,149 @@
+package com.habitrpg.android.habitica.ui.views
+
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.text.selection.TextSelectionColors
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
+import androidx.compose.material3.TextFieldDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.input.PasswordVisualTransformation
+import androidx.compose.ui.text.input.VisualTransformation
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.Wallpapers
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.habitrpg.android.habitica.R
+import com.habitrpg.common.habitica.theme.HabiticaTheme
+import com.habitrpg.common.habitica.views.HabiticaCircularProgressView
+
+enum class LoginFieldState {
+ DEFAULT,
+ VALID,
+ ERROR,
+ LOADING,
+}
+
+@Composable
+fun LoginScreenField(
+ label: String,
+ value: String,
+ onValueChange: (String) -> Unit,
+ modifier: Modifier = Modifier,
+ icon: @Composable (() -> Unit)? = null,
+ prefix: @Composable () -> Unit = {},
+ state: LoginFieldState = LoginFieldState.DEFAULT,
+ hideInput: Boolean = false,
+) {
+ val containerColor = colorResource(R.color.brand_100)
+ TextField(
+ value = value,
+ onValueChange = onValueChange,
+ placeholder = { Text(label, fontSize = 18.sp, fontWeight = FontWeight.Normal) },
+ isError = state == LoginFieldState.ERROR,
+ suffix = {
+ AnimatedContent(state) {
+ if (it == LoginFieldState.ERROR) {
+ Image(
+ painterResource(R.drawable.ic_close_white_18dp),
+ contentDescription = null,
+ colorFilter = ColorFilter.tint(colorResource(R.color.red_100))
+ )
+ } else if (it == LoginFieldState.VALID) {
+ Image(
+ painterResource(R.drawable.checkmark),
+ contentDescription = null,
+ colorFilter = ColorFilter.tint(colorResource(R.color.green_50))
+ )
+ } else if (it == LoginFieldState.LOADING) {
+ HabiticaCircularProgressView(indicatorSize = 20.dp, strokeWidth = 2.dp)
+ }
+ }
+
+ },
+ singleLine = true,
+ trailingIcon = icon,
+ prefix = prefix,
+ textStyle = TextStyle(
+ fontSize = 18.sp,
+ fontWeight = FontWeight.Normal
+ ),
+ colors = TextFieldDefaults.colors(
+ unfocusedContainerColor = containerColor,
+ focusedContainerColor = containerColor,
+ errorContainerColor = containerColor,
+ unfocusedTextColor = Color.White,
+ focusedTextColor = Color.White,
+ errorTextColor = colorResource(R.color.red_100),
+ unfocusedPlaceholderColor = colorResource(R.color.brand_600),
+ focusedPlaceholderColor = colorResource(R.color.brand_600).copy(alpha = 0.5f),
+ unfocusedIndicatorColor = Color.Transparent,
+ focusedIndicatorColor = Color.Transparent,
+ errorIndicatorColor = Color.Transparent,
+ unfocusedTrailingIconColor = colorResource(R.color.brand_100),
+ cursorColor = Color.White,
+ selectionColors = TextSelectionColors(
+ handleColor = colorResource(R.color.brand_600),
+ backgroundColor = colorResource(R.color.brand_600).copy(alpha = 0.3f)
+ ),
+ ),
+ shape = HabiticaTheme.shapes.large,
+ modifier = modifier.fillMaxWidth().heightIn(min = 60.dp),
+ visualTransformation = if (hideInput) PasswordVisualTransformation() else VisualTransformation.None,
+ )
+}
+
+@Preview(wallpaper = Wallpapers.BLUE_DOMINATED_EXAMPLE)
+@Composable
+fun LoginScreenFieldPreview() {
+ Column(verticalArrangement = Arrangement.spacedBy(10.dp), modifier = Modifier.padding(8.dp)) {
+ LoginScreenField(
+ label = "Email",
+ value = "",
+ icon = {
+ Image(painterResource(R.drawable.login_email), contentDescription = null)
+ }, onValueChange = {}
+ )
+ LoginScreenField(
+ label = "Email",
+ value = "test@test.com",
+ icon = {
+ Image(painterResource(R.drawable.login_email), contentDescription = null)
+ }, onValueChange = {}, state = LoginFieldState.VALID
+ )
+ LoginScreenField(
+ label = "Email",
+ value = "test@test.com",
+ icon = {
+ Image(painterResource(R.drawable.login_email), contentDescription = null)
+ }, onValueChange = {}, state = LoginFieldState.ERROR
+ )
+ LoginScreenField(
+ label = "Username",
+ value = "Bla",
+ icon = {
+ Image(painterResource(R.drawable.login_username), contentDescription = null)
+ }, onValueChange = {}, state = LoginFieldState.LOADING
+ )
+ LoginScreenField(
+ label = "Password",
+ value = "abcd",
+ hideInput = true,
+ icon = {
+ Image(painterResource(R.drawable.login_password), contentDescription = null)
+ }, onValueChange = {}
+ )
+ }
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/Typewriter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/Typewriter.kt
index 38a2fc041..1146d7406 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/Typewriter.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/Typewriter.kt
@@ -1,10 +1,29 @@
package com.habitrpg.android.habitica.ui.views
import android.content.Context
+import android.os.Build
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan
import android.util.AttributeSet
+import androidx.annotation.RequiresApi
+import androidx.compose.foundation.layout.Box
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.unit.TextUnit
import androidx.core.content.ContextCompat
import com.habitrpg.android.habitica.R
import kotlinx.coroutines.Dispatchers
@@ -12,6 +31,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
+import kotlin.streams.toList
// http://stackoverflow.com/a/6700718/1315039
class Typewriter : androidx.appcompat.widget.AppCompatTextView {
@@ -70,3 +90,74 @@ class Typewriter : androidx.appcompat.widget.AppCompatTextView {
index = stringBuilder?.length ?: 0
}
}
+
+// https://medium.com/make-apps-simple/typewriter-animation-in-jetpack-compose-2b0c7ee323c2
+@Composable
+fun TypewriterText(
+ text: String,
+ modifier: Modifier = Modifier,
+ color: Color = Color.Unspecified,
+ fontSize: TextUnit = TextUnit.Unspecified,
+ fontStyle: FontStyle? = null,
+ fontWeight: FontWeight? = null,
+ fontFamily: FontFamily? = null,
+ letterSpacing: TextUnit = TextUnit.Unspecified,
+ textDecoration: TextDecoration? = null,
+ textAlign: TextAlign? = null,
+ lineHeight: TextUnit = TextUnit.Unspecified,
+ delay: Long = 30L,
+) {
+ var textToDisplay by remember {
+ mutableStateOf("")
+ }
+ val textCharsList = remember {
+ text.splitToCodePoints()
+ }
+
+ LaunchedEffect(text,) {
+ textCharsList.forEachIndexed { charIndex, _ ->
+ textToDisplay = textCharsList
+ .take(
+ n = charIndex + 1,
+ ).joinToString(
+ separator = "",
+ )
+ delay(delay)
+ }
+ }
+
+ Box(modifier = modifier) {
+ Text(
+ text = text,
+ color = Color.Transparent,
+ fontSize = fontSize,
+ fontStyle = fontStyle,
+ fontWeight = fontWeight,
+ fontFamily = fontFamily,
+ letterSpacing = letterSpacing,
+ textDecoration = textDecoration,
+ textAlign = textAlign,
+ lineHeight = lineHeight,
+ )
+ Text(
+ text = textToDisplay,
+ color = color,
+ fontSize = fontSize,
+ fontStyle = fontStyle,
+ fontWeight = fontWeight,
+ fontFamily = fontFamily,
+ letterSpacing = letterSpacing,
+ textDecoration = textDecoration,
+ textAlign = textAlign,
+ lineHeight = lineHeight,
+ )
+ }
+}
+
+fun String.splitToCodePoints(): List {
+ return codePoints()
+ .toList()
+ .map {
+ String(Character.toChars(it))
+ }
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/intro/IntroScreen.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/intro/IntroScreen.kt
new file mode 100644
index 000000000..80eee0b36
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/intro/IntroScreen.kt
@@ -0,0 +1,239 @@
+package com.habitrpg.android.habitica.ui.views.intro
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.systemBars
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.rememberPagerState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.ProvideTextStyle
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.rotate
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.compositeOver
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.TextStyle
+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
+import androidx.core.graphics.ColorUtils
+import com.habitrpg.android.habitica.R
+import com.habitrpg.common.habitica.helpers.launchCatching
+
+@Composable
+fun IntroPage(
+ page: Int,
+ title: @Composable () -> Unit,
+ subtitle: @Composable () -> Unit,
+ description: @Composable () -> Unit,
+ image: @Composable () -> Unit,
+ background: Brush,
+) {
+ Box(modifier = Modifier
+ .fillMaxSize()
+ .padding(horizontal = 40.dp), contentAlignment = Alignment.Center) {
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ ProvideTextStyle(
+ TextStyle(
+ fontSize = 18.sp,
+ fontWeight = FontWeight.Normal,
+ textAlign = TextAlign.Center,
+ color = Color.White
+ )
+ ) {
+ subtitle()
+ }
+ Spacer(modifier = Modifier.height(12.dp))
+ ProvideTextStyle(
+ TextStyle(
+ fontSize = 28.sp,
+ fontWeight = FontWeight.Bold,
+ textAlign = TextAlign.Center,
+ color = Color.White
+ )
+ ) {
+ title()
+ }
+ Spacer(modifier = Modifier.height(50.dp))
+ image()
+ Spacer(modifier = Modifier.height(50.dp))
+ ProvideTextStyle(
+ TextStyle(
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal,
+ textAlign = TextAlign.Center,
+ color = Color.White,
+ lineHeight = 20.sp
+ )
+ ) {
+ description()
+ }
+ }
+ }
+}
+
+fun Color.blend(topColor: Color, ratio: Float = 0.5f): Color {
+ if (ratio == 0f) return this
+ if (ratio == 1f) return topColor
+ val intColor = ColorUtils.blendARGB(toArgb(), topColor.toArgb(), ratio)
+ return Color(intColor)
+}
+
+@Composable
+fun IntroScreen(onNextOnboardingStep: () -> Unit) {
+ val pagerState = rememberPagerState(pageCount = { 3 })
+ val coroutineScope = rememberCoroutineScope()
+ val pageOffset by remember { derivedStateOf { pagerState.currentPageOffsetFraction } }
+ var topColor: Color
+ var bottomColor: Color
+ print(pageOffset)
+ if (pagerState.currentPage < 1) {
+ if (pageOffset > 0) {
+ topColor = colorResource(R.color.brand_400).blend(colorResource(R.color.blue_100), pageOffset)
+ bottomColor = colorResource(R.color.brand_100).blend(colorResource(R.color.blue_10), pageOffset)
+ } else {
+ topColor = colorResource(R.color.brand_400)
+ bottomColor = colorResource(R.color.brand_100)
+ }
+ } else if (pagerState.currentPage < 2) {
+ if (pageOffset > 0) {
+ topColor = colorResource(R.color.blue_100).blend(colorResource(R.color.red_100), pageOffset)
+ bottomColor = colorResource(R.color.blue_10).blend(colorResource(R.color.red_10), pageOffset)
+ } else {
+ topColor = colorResource(R.color.blue_100).blend(colorResource(R.color.brand_400), -pageOffset)
+ bottomColor = colorResource(R.color.blue_10).blend(colorResource(R.color.brand_200), -pageOffset)
+ }
+ } else {
+ if (pageOffset > 0) {
+ topColor = colorResource(R.color.blue_100).blend(colorResource(R.color.red_100), pageOffset)
+ bottomColor = colorResource(R.color.blue_10).blend(colorResource(R.color.red_10), pageOffset)
+ } else {
+ topColor = colorResource(R.color.red_100).blend(colorResource(R.color.blue_100), -pageOffset)
+ bottomColor = colorResource(R.color.red_10).blend(colorResource(R.color.blue_10), -pageOffset)
+ }
+ }
+ Box(Modifier.fillMaxSize().background(
+ Brush.verticalGradient(
+ listOf(topColor, bottomColor)
+ )
+ )) {
+ HorizontalPager(pagerState) { page ->
+ when (page) {
+ 0 -> IntroPage(
+ page = page,
+ title = { Image(painterResource(R.drawable.intro_1_title), contentDescription = null) },
+ subtitle = { Text(stringResource(R.string.intro_1_subtitle)) },
+ description = { Text(stringResource(R.string.intro_1_description)) },
+ image = { Image(painterResource(R.drawable.intro_1), contentDescription = null) },
+ background = Brush.verticalGradient(listOf(colorResource(R.color.brand_400), colorResource(R.color.brand_200)))
+ )
+
+ 1 -> IntroPage(
+ page = page,
+ title = { Text(stringResource(R.string.intro_2_title)) },
+ subtitle = { Text(stringResource(R.string.intro_2_subtitle)) },
+ description = { Text(stringResource(R.string.intro_2_description)) },
+ image = { Image(painterResource(R.drawable.intro_2), contentDescription = null) },
+ background = Brush.verticalGradient(listOf(colorResource(R.color.blue_100), colorResource(R.color.blue_50)))
+ )
+
+ 2 -> IntroPage(
+ page = page,
+ title = { Text(stringResource(R.string.intro_3_title)) },
+ subtitle = { Text(stringResource(R.string.intro_3_subtitle)) },
+ description = { Text(stringResource(R.string.intro_3_description)) },
+ image = { Image(painterResource(R.drawable.intro_3), contentDescription = null) },
+ background = Brush.verticalGradient(listOf(colorResource(R.color.red_100), colorResource(R.color.red_50)))
+ )
+ }
+ }
+ Button(
+ onClick = {
+ onNextOnboardingStep()
+ },
+ modifier = Modifier
+ .align(Alignment.TopEnd)
+ .padding(WindowInsets.systemBars.asPaddingValues()),
+ colors = ButtonDefaults.textButtonColors(),
+ ) {
+ Text(stringResource(R.string.skip_button), color = Color.White, fontSize = 18.sp)
+ }
+
+ Column(Modifier
+ .align(Alignment.BottomCenter)
+ .padding(WindowInsets.systemBars.asPaddingValues())
+ .padding(horizontal = 20.dp)) {
+ Row(
+ Modifier
+ .wrapContentHeight()
+ .fillMaxWidth()
+ .padding(bottom = 8.dp),
+ horizontalArrangement = Arrangement.Center
+ ) {
+ repeat(pagerState.pageCount) { iteration ->
+ val color = (if (pagerState.currentPage == iteration) Color.Black else Color.White).copy(alpha = 0.6f)
+ Box(
+ modifier = Modifier
+ .padding(8.dp)
+ .rotate(45f)
+ .clip(RectangleShape)
+ .background(color)
+ .size(8.dp)
+ )
+ }
+ }
+ Button(
+ {
+ if (pagerState.currentPage < pagerState.pageCount - 1) {
+ coroutineScope.launchCatching {
+ pagerState.animateScrollToPage(pagerState.currentPage + 1)
+ }
+ } else {
+ onNextOnboardingStep()
+ }
+ },
+ colors = ButtonDefaults.buttonColors(
+ containerColor = Color.Black.copy(alpha = 0.4f),
+ contentColor = Color.White
+ ),
+
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ if (pagerState.currentPage < pagerState.pageCount - 1) {
+ Text(stringResource(R.string.next_button), fontSize = 18.sp, fontWeight = FontWeight.Normal)
+ } else {
+ Text(stringResource(R.string.lets_go), fontSize = 18.sp, fontWeight = FontWeight.Normal)
+ }
+ }
+ }
+ }
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/login/LoginBackgroundView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/login/LoginBackgroundView.kt
index 5ad4a6252..0ac7aadf4 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/login/LoginBackgroundView.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/login/LoginBackgroundView.kt
@@ -14,7 +14,13 @@ import android.widget.RelativeLayout
import com.habitrpg.android.habitica.R
import java.util.Random
-class LoginBackgroundView(context: Context, attrs: AttributeSet?) : RelativeLayout(context, attrs) {
+class LoginBackgroundView
+ @JvmOverloads
+ constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0
+) : RelativeLayout(context, attrs, defStyleAttr) {
private val random: Random = Random()
private lateinit var leftCloudView: ImageView
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/login/LoginForm.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/login/LoginForm.kt
new file mode 100644
index 000000000..8254d5d59
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/login/LoginForm.kt
@@ -0,0 +1,237 @@
+package com.habitrpg.android.habitica.ui.views.login
+
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.ProvideTextStyle
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.ui.views.LoginFieldState
+import com.habitrpg.android.habitica.ui.views.LoginScreenField
+import com.habitrpg.common.habitica.theme.HabiticaTheme
+import com.habitrpg.common.habitica.views.HabiticaCircularProgressView
+
+@Composable
+fun LoginForm(
+ onToggleFormType: () -> Unit,
+ email: String,
+ emailFieldState: LoginFieldState,
+ onEmailChange: (String) -> Unit,
+ password: String,
+ passwordFieldState: LoginFieldState,
+ onPasswordChange: (String) -> Unit,
+ isRegistering: Boolean,
+ onSubmit: () -> Unit,
+ showLoading: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ var confirmPassword by remember { mutableStateOf("") }
+ Column(
+ horizontalAlignment = Alignment.Companion.CenterHorizontally,
+ modifier = modifier.widthIn(max = 480.dp)
+ ) {
+ Button(
+ {
+ onToggleFormType()
+ },
+ colors = ButtonDefaults.textButtonColors(),
+ contentPadding = PaddingValues(15.dp),
+ modifier = Modifier.Companion.fillMaxWidth().padding(top = 24.dp, bottom = 8.dp)
+ ) {
+ ProvideTextStyle(TextStyle(fontSize=18.sp)) {
+ AnimatedContent(isRegistering) {
+ if (it) {
+ Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
+ Text(
+ stringResource(R.string.already_have_an_account),
+ color = colorResource(R.color.brand_600)
+ )
+ Text(
+ stringResource(R.string.login_btn),
+ color = colorResource(R.color.white)
+ )
+ }
+ } else {
+ Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
+ Text(
+ stringResource(R.string.need_an_account),
+ color = colorResource(R.color.brand_600)
+ )
+ Text(
+ stringResource(R.string.register_btn),
+ color = colorResource(R.color.white)
+ )
+ }
+ }
+ }
+ }
+ }
+ LoginScreenField(
+ label = if (isRegistering) stringResource(R.string.email) else stringResource(R.string.username_or_email),
+ value = email,
+ state = emailFieldState,
+ onValueChange = onEmailChange,
+ icon = {
+ AnimatedContent(isRegistering) {
+ if (it) {
+ Image(
+ painterResource(R.drawable.login_email),
+ contentDescription = stringResource(R.string.email)
+ )
+ } else {
+ Image(
+ painterResource(R.drawable.login_username),
+ contentDescription = stringResource(R.string.email)
+ )
+ }
+ }
+
+ },
+ modifier = Modifier.Companion.fillMaxWidth().padding(bottom = 10.dp),
+ )
+ LoginScreenField(
+ label = stringResource(R.string.password),
+ value = password,
+ state = passwordFieldState,
+ onValueChange = onPasswordChange,
+ hideInput = true,
+ icon = {
+ Image(
+ painterResource(R.drawable.login_password),
+ contentDescription = stringResource(R.string.password)
+ )
+ },
+ modifier = Modifier.Companion.fillMaxWidth(),
+ )
+ AnimatedVisibility(isRegistering) {
+ LoginScreenField(
+ label = stringResource(R.string.confirmpassword),
+ value = confirmPassword,
+ onValueChange = { confirmPassword = it },
+ hideInput = true,
+ state = when {
+ confirmPassword.isEmpty() -> LoginFieldState.DEFAULT
+ confirmPassword == password && passwordFieldState == LoginFieldState.VALID -> LoginFieldState.VALID
+ else -> LoginFieldState.ERROR
+ },
+ icon = {
+ Image(
+ painterResource(R.drawable.login_password),
+ contentDescription = stringResource(R.string.confirmpassword)
+ )
+ },
+ modifier = Modifier.Companion.fillMaxWidth().padding(top = 10.dp),
+ )
+ }
+ AnimatedContent(showLoading) { isLoading ->
+ if (isLoading) {
+ HabiticaCircularProgressView(indicatorSize = 64.dp, modifier = Modifier.padding(top = 30.dp))
+ } else {
+ Button(
+ {
+ onSubmit()
+ },
+ colors = ButtonDefaults.buttonColors(
+ containerColor = Color.Companion.White,
+ contentColor = colorResource(R.color.gray_50),
+ disabledContainerColor = Color.White.copy(alpha = 0.5f),
+ ),
+ shape = HabiticaTheme.shapes.large,
+ contentPadding = PaddingValues(15.dp),
+ enabled = if (isRegistering) {
+ (emailFieldState == LoginFieldState.VALID && passwordFieldState == LoginFieldState.VALID && password == confirmPassword)
+ } else {
+ (email.isNotBlank() && password.isNotBlank())
+ },
+ modifier = Modifier.Companion.fillMaxWidth().padding(top = 30.dp)
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(10.dp),
+ verticalAlignment = Alignment.Companion.CenterVertically
+ ) {
+ if (isRegistering) {
+ Text(
+ stringResource(R.string.action_continue),
+ fontWeight = FontWeight.Companion.Bold,
+ fontSize = 18.sp
+ )
+ } else {
+ Text(
+ stringResource(R.string.login_btn), fontWeight = FontWeight.Companion.Bold,
+ fontSize = 18.sp
+ )
+ }
+ }
+ }
+ }
+ }
+ AnimatedVisibility(!isRegistering) {
+ Button(
+ {
+ },
+ colors = ButtonDefaults.buttonColors(
+ containerColor = Color.Companion.White,
+ contentColor = colorResource(R.color.gray_50)
+ ),
+ shape = HabiticaTheme.shapes.large,
+ contentPadding = PaddingValues(15.dp),
+ modifier = Modifier.Companion.widthIn(max = 480.dp).fillMaxWidth().padding(top = 10.dp)
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(10.dp),
+ verticalAlignment = Alignment.Companion.CenterVertically
+ ) {
+ Image(
+ painterResource(R.drawable.googleg_standard_color_18),
+ contentDescription = null
+ )
+ Text(
+ stringResource(R.string.continue_with_google),
+ fontWeight = FontWeight.Companion.Bold,
+ fontSize = 18.sp
+ )
+ }
+ }
+ }
+ AnimatedVisibility(!isRegistering) {
+ Button(
+ {
+ onToggleFormType()
+ },
+ colors = ButtonDefaults.textButtonColors(),
+ contentPadding = PaddingValues(15.dp),
+ modifier = Modifier.Companion.fillMaxWidth()
+ ) {
+ Text(
+ stringResource(R.string.forgot_pw_btn),
+ color = colorResource(R.color.white),
+ fontSize = 18.sp
+ )
+ }
+ }
+ }
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/login/LoginInitialButtons.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/login/LoginInitialButtons.kt
new file mode 100644
index 000000000..03b8789cd
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/login/LoginInitialButtons.kt
@@ -0,0 +1,105 @@
+package com.habitrpg.android.habitica.ui.views.login
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.habitrpg.android.habitica.R
+import com.habitrpg.common.habitica.theme.HabiticaTheme
+
+@Composable
+fun LoginInitialButtons(onLoginClicked: () -> Unit,
+ onRegisterClicked: () -> Unit,
+ modifier: Modifier = Modifier.Companion
+) {
+ Column(
+ horizontalAlignment = Alignment.Companion.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(10.dp),
+ modifier = modifier.fillMaxWidth()
+ ) {
+ Button(
+ {
+ },
+ colors = ButtonDefaults.buttonColors(
+ containerColor = Color.Companion.White,
+ contentColor = colorResource(R.color.gray_50)
+ ),
+ shape = HabiticaTheme.shapes.large,
+ contentPadding = PaddingValues(15.dp),
+ modifier = Modifier.Companion.widthIn(max = 480.dp).fillMaxWidth()
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(10.dp),
+ verticalAlignment = Alignment.Companion.CenterVertically
+ ) {
+ Image(
+ painterResource(R.drawable.googleg_standard_color_18),
+ contentDescription = null
+ )
+ Text(
+ stringResource(R.string.continue_with_google),
+ fontWeight = FontWeight.Companion.Bold,
+ fontSize = 18.sp
+ )
+ }
+ }
+ Button(
+ {
+ onRegisterClicked()
+ },
+ colors = ButtonDefaults.buttonColors(
+ containerColor = Color.Companion.White,
+ contentColor = colorResource(R.color.gray_50)
+ ),
+ shape = HabiticaTheme.shapes.large,
+ contentPadding = PaddingValues(15.dp),
+ modifier = Modifier.Companion.widthIn(max = 480.dp).fillMaxWidth()
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(10.dp),
+ verticalAlignment = Alignment.Companion.CenterVertically
+ ) {
+ Image(painterResource(R.drawable.ic_email_color), contentDescription = null)
+ Text(
+ stringResource(R.string.continue_with_email),
+ fontWeight = FontWeight.Companion.Bold,
+ fontSize = 18.sp
+ )
+ }
+ }
+ Button(
+ {
+ onLoginClicked()
+ },
+ colors = ButtonDefaults.textButtonColors(),
+ contentPadding = PaddingValues(15.dp),
+ modifier = Modifier.Companion.fillMaxWidth()
+ ) {
+ Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
+ Text(
+ stringResource(R.string.already_have_an_account),
+ color = colorResource(R.color.brand_600),
+ fontSize = 18.sp
+ )
+ Text(stringResource(R.string.login_btn), color = colorResource(R.color.white),
+ fontSize = 18.sp)
+ }
+ }
+ }
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/login/LoginScreen.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/login/LoginScreen.kt
new file mode 100644
index 000000000..f965914da
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/login/LoginScreen.kt
@@ -0,0 +1,234 @@
+package com.habitrpg.android.habitica.ui.views.login
+
+import android.util.Patterns
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.EaseInOut
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.expandVertically
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.shrinkVertically
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.systemBars
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.ProvideTextStyle
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.TextStyle
+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
+import androidx.compose.ui.viewinterop.AndroidView
+import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.ui.viewmodels.AuthenticationViewModel
+import com.habitrpg.android.habitica.ui.views.LoginFieldState
+import com.habitrpg.common.habitica.extensions.layoutInflater
+
+enum class LoginScreenState {
+ INITIAL,
+ LOGIN,
+ REGISTER,
+}
+
+@Composable
+fun LoginScreen(authenticationViewModel: AuthenticationViewModel, onNextOnboardingStep: (Boolean) -> Unit, modifier: Modifier = Modifier) {
+ val showLoading by authenticationViewModel.showAuthProgress.collectAsState(false)
+ val authenticationError by authenticationViewModel.authenticationError.collectAsState(null)
+
+ LaunchedEffect(authenticationViewModel) {
+ authenticationViewModel.authenticationSuccess.collect { isRegistering ->
+ onNextOnboardingStep(isRegistering)
+ }
+ }
+
+ var loginScreenState by remember { mutableStateOf(LoginScreenState.INITIAL) }
+ var password by authenticationViewModel.password
+ var passwordFieldState by remember { mutableStateOf(LoginFieldState.DEFAULT) }
+ var email by authenticationViewModel.email
+ var emailFieldState by remember { mutableStateOf(LoginFieldState.DEFAULT) }
+ Box(modifier.fillMaxSize()) {
+ AndroidView(
+ factory = { context ->
+ val layout = context.layoutInflater.inflate(R.layout.login_background, null)
+ (layout as? LockableScrollView)?.isScrollable = false
+ layout
+ },
+ modifier = Modifier.fillMaxSize(),
+ )
+ Column(
+ modifier = Modifier.fillMaxSize().align(Alignment.BottomCenter)
+ ) {
+ Spacer(modifier = Modifier.weight(1f))
+ Image(
+ painterResource(R.drawable.login_background),
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier = Modifier.width(800.dp)
+ )
+ AnimatedVisibility(
+ loginScreenState == LoginScreenState.INITIAL,
+ enter = expandVertically(tween(300, 400), expandFrom = Alignment.Top),
+ exit = shrinkVertically(shrinkTowards = Alignment.Top),
+ ) {
+ Box(
+ modifier = Modifier.fillMaxWidth().height(200.dp).background(
+ Brush.verticalGradient(
+ listOf(
+ Color(0xFFA995EA),
+ colorResource(R.color.brand_400)
+ )
+ )
+ )
+ )
+ }
+ }
+ AnimatedVisibility(
+ loginScreenState != LoginScreenState.INITIAL,
+ enter = fadeIn(), exit = fadeOut(),
+ modifier = Modifier.padding(WindowInsets.systemBars.asPaddingValues())
+ ) {
+ Button(
+ {
+ loginScreenState = LoginScreenState.INITIAL
+ },
+ colors = ButtonDefaults.textButtonColors(contentColor = Color.White),
+ modifier = Modifier.align(Alignment.TopStart)
+ ) {
+ Image(painterResource(R.drawable.arrow_back), contentDescription = null)
+ }
+ }
+ val logoPadding by animateDpAsState(
+ if (loginScreenState == LoginScreenState.INITIAL) {
+ 120.dp
+ } else {
+ 50.dp
+ },
+ animationSpec = tween(
+ delayMillis = if (loginScreenState == LoginScreenState.INITIAL) 400 else 0,
+ easing = EaseInOut
+ ),
+ label = "padding"
+ )
+ ProvideTextStyle(TextStyle(fontSize = 18.sp)) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.fillMaxWidth().padding(WindowInsets.systemBars.asPaddingValues()).padding(horizontal = 20.dp)
+ ) {
+ Image(
+ painterResource(R.drawable.login_logo),
+ contentDescription = null,
+ modifier = Modifier.padding(top = logoPadding)
+ )
+ AnimatedVisibility(
+ loginScreenState == LoginScreenState.INITIAL,
+ enter = fadeIn(tween(300, 500)) + expandVertically(tween(300, 500)),
+ exit = fadeOut()
+ ) {
+ Text(
+ stringResource(R.string.enjoy_getting_things_done),
+ fontSize = 24.sp,
+ lineHeight = 30.sp,
+ fontWeight = FontWeight.SemiBold,
+ color = colorResource(R.color.brand_600),
+ textAlign = TextAlign.Center,
+ modifier = Modifier.padding(top = 30.dp)
+ .padding(horizontal = 30.dp)
+ )
+ }
+ AnimatedVisibility(
+ loginScreenState != LoginScreenState.INITIAL,
+ enter = fadeIn(tween(300, 400)),
+ exit = fadeOut()
+ ) {
+ LoginForm(
+ onToggleFormType = {
+ loginScreenState =
+ if (loginScreenState == LoginScreenState.LOGIN) LoginScreenState.REGISTER else LoginScreenState.LOGIN
+ },
+ email = email,
+ emailFieldState = emailFieldState,
+ onEmailChange = {
+ email = it
+ if (loginScreenState == LoginScreenState.REGISTER) {
+ emailFieldState = if (it.isEmpty()) {
+ LoginFieldState.DEFAULT
+ } else if (Patterns.EMAIL_ADDRESS.matcher(it).matches()) {
+ LoginFieldState.VALID
+ } else {
+ LoginFieldState.ERROR
+ }
+ } else {
+ emailFieldState = LoginFieldState.DEFAULT
+ }
+ },
+ password = password,
+ passwordFieldState = passwordFieldState,
+ onPasswordChange = {
+ password = it
+ if (loginScreenState == LoginScreenState.REGISTER) {
+ passwordFieldState = if (it.isEmpty()) {
+ LoginFieldState.DEFAULT
+ } else if (it.length >= 8) {
+ LoginFieldState.VALID
+ } else {
+ LoginFieldState.ERROR
+ }
+ } else {
+ passwordFieldState = LoginFieldState.DEFAULT
+ }
+ },
+ isRegistering = loginScreenState == LoginScreenState.REGISTER,
+ onSubmit = {
+ if (loginScreenState == LoginScreenState.REGISTER) {
+ authenticationViewModel.register("", email, password, password)
+ } else {
+ authenticationViewModel.login(email, password)
+ }
+ },
+ showLoading = showLoading
+ )
+ }
+ Spacer(modifier = Modifier.weight(1f))
+ AnimatedVisibility(
+ loginScreenState == LoginScreenState.INITIAL,
+ enter = fadeIn(tween(300, 500)) + expandVertically(tween(300, 400)),
+ exit = fadeOut() + shrinkVertically(tween(300, 200))
+ ) {
+ LoginInitialButtons({
+ loginScreenState = LoginScreenState.LOGIN
+ }, {
+ loginScreenState = LoginScreenState.REGISTER
+ })
+ }
+ }
+ }
+ }
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/setup/AvatarCategoryView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/setup/AvatarCategoryView.kt
deleted file mode 100644
index e946aed9c..000000000
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/setup/AvatarCategoryView.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-package com.habitrpg.android.habitica.ui.views.setup
-
-import android.content.Context
-import android.graphics.PorterDuff
-import android.graphics.drawable.Drawable
-import android.util.AttributeSet
-import android.view.View
-import android.widget.LinearLayout
-import android.widget.TextView
-import androidx.core.content.ContextCompat
-import com.habitrpg.android.habitica.R
-import com.habitrpg.common.habitica.extensions.setTintWith
-
-class AvatarCategoryView(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) {
- private val icon: Drawable?
- private val textView: TextView
-
- init {
- View.inflate(context, R.layout.avatar_category, this)
-
- textView = findViewById(R.id.text_view)
- val a =
- context.theme.obtainStyledAttributes(
- attrs,
- R.styleable.AvatarCategoryView,
- 0,
- 0
- )
-
- textView.text = a.getText(R.styleable.AvatarCategoryView_categoryTitle)
-
- icon = a.getDrawable(R.styleable.AvatarCategoryView_iconDrawable)
- if (icon != null) {
- textView.setCompoundDrawablesWithIntrinsicBounds(null, icon, null, null)
- }
- setActive(false)
- }
-
- fun setActive(active: Boolean) {
- val color: Int =
- if (active) {
- ContextCompat.getColor(context, R.color.white)
- } else {
- ContextCompat.getColor(context, R.color.white_50_alpha)
- }
- textView.setTextColor(color)
- if (icon != null) {
- icon.setTintWith(color, PorterDuff.Mode.MULTIPLY)
- textView.setCompoundDrawablesWithIntrinsicBounds(null, icon, null, null)
- }
- }
-}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/setup/AvatarCustomizationDrawer.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/setup/AvatarCustomizationDrawer.kt
deleted file mode 100644
index 58ec343c7..000000000
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/setup/AvatarCustomizationDrawer.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.habitrpg.android.habitica.ui.views.setup
-
-import android.content.Context
-import android.util.AttributeSet
-import android.widget.LinearLayout
-import com.habitrpg.android.habitica.databinding.AvatarSetupDrawerBinding
-import com.habitrpg.common.habitica.extensions.layoutInflater
-
-class AvatarCustomizationDrawer(context: Context, attrs: AttributeSet?) :
- LinearLayout(context, attrs) {
- val binding = AvatarSetupDrawerBinding.inflate(context.layoutInflater, this, true)
-}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/setup/SetupScreen.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/setup/SetupScreen.kt
new file mode 100644
index 000000000..85358bd59
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/setup/SetupScreen.kt
@@ -0,0 +1,607 @@
+package com.habitrpg.android.habitica.ui.views.setup
+
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.AnimatedContentTransitionScope
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.animateColor
+import androidx.compose.animation.animateContentSize
+import androidx.compose.animation.core.LinearOutSlowInEasing
+import androidx.compose.animation.core.animateDp
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.core.updateTransition
+import androidx.compose.animation.expandVertically
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.scaleIn
+import androidx.compose.animation.togetherWith
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.horizontalScroll
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.systemBars
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.ProvideTextStyle
+import androidx.compose.material3.Tab
+import androidx.compose.material3.TabRow
+import androidx.compose.material3.TabRowDefaults
+import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.draw.rotate
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.ImageShader
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.ShaderBrush
+import androidx.compose.ui.graphics.TileMode
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.imageResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.TextStyle
+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
+import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.ui.views.TypewriterText
+import com.habitrpg.common.habitica.theme.HabiticaTheme
+import com.habitrpg.common.habitica.views.ComposableAvatarView
+
+@Composable
+fun SetupScreen(onNextOnboardingStep: () -> Unit) {
+ var currentStep by remember { mutableIntStateOf(0) }
+ var selectedCustomizationCategory by remember { mutableStateOf("skin") }
+
+ val density = LocalDensity.current
+ val img = ImageBitmap.imageResource(R.drawable.border_pixelated)
+
+ val bgColorTop = colorResource(R.color.brand_100)
+ val bgColorMiddle = colorResource(R.color.brand_200)
+ val bgColorBottom = colorResource(R.color.brand_300)
+ val pixelImage = remember {
+ ShaderBrush(
+ ImageShader(
+ img,
+ TileMode.Repeated,
+ TileMode.Repeated
+ )
+ )
+ }
+ val text = if (currentStep == 0) {
+ stringResource(R.string.onboarding_step_avatar)
+ } else {
+ stringResource(R.string.onboarding_step_tasks)
+ }
+ Box(
+ Modifier
+ .fillMaxSize()
+ ) {
+ Column(
+ verticalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier
+ .background(Color(0xFFC7E7FD))
+ .padding(
+ top = WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
+ )
+ .padding(top = 48.dp)
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(bgColorMiddle)
+ .weight(1f)
+ ) {
+ Box(
+ Modifier
+ .fillMaxWidth()
+ .height(186.dp)
+ .background(
+ ShaderBrush(
+ ImageShader(
+ ImageBitmap.imageResource(R.drawable.stable_background_spring),
+ TileMode.Repeated,
+ TileMode.Repeated
+ )
+ )
+ )
+ ) {
+ Box(
+ Modifier
+ .align(Alignment.BottomCenter)
+ .fillMaxWidth()
+ .height(18.dp)
+ .drawBehind {
+ drawRect(
+ pixelImage,
+ colorFilter = ColorFilter.tint(if (currentStep == 0) bgColorTop else bgColorMiddle)
+ )
+ }
+ )
+ }
+
+ AnimatedVisibility(currentStep == 0) {
+ Box(
+ Modifier
+ .fillMaxWidth()
+ .background(bgColorTop)
+ .padding(top = 170.dp)
+ .padding(bottom = 26.dp)
+ ) {
+ CustomizationCategoryView(selectedCustomizationCategory)
+ }
+ }
+ AnimatedContent(currentStep, modifier = Modifier.weight(1f)) {
+ if (it == 0) {
+ CustomizationCategorySelector(
+ selectedCustomizationCategory,
+ {
+ selectedCustomizationCategory = it
+ }
+ )
+ } else {
+ OnboardingTaskSelector(modifier = Modifier.padding(top = 160.dp))
+ }
+ }
+ }
+ Box(
+ Modifier
+ .fillMaxWidth()
+ .height(18.dp)
+ .background(bgColorMiddle)
+ .drawBehind {
+ drawRect(
+ pixelImage,
+ colorFilter = ColorFilter.tint(bgColorBottom)
+ )
+ }
+ )
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier
+ .fillMaxWidth()
+ .background(bgColorBottom)
+ .padding(
+ bottom = WindowInsets.systemBars.asPaddingValues()
+ .calculateBottomPadding()
+ )
+ .animateContentSize()
+ ) {
+ Row(
+ Modifier
+ .wrapContentHeight()
+ .fillMaxWidth()
+ .padding(vertical = 16.dp),
+ horizontalArrangement = Arrangement.Center
+ ) {
+ repeat(2) { iteration ->
+ val color =
+ (if (currentStep == iteration) Color.White else Color.Black).copy(
+ alpha = 0.4f
+ )
+ Box(
+ modifier = Modifier
+ .padding(8.dp)
+ .rotate(45f)
+ .clip(RectangleShape)
+ .background(color)
+ .size(8.dp)
+ )
+ }
+ }
+ Button(
+ {
+ if (currentStep == 0) {
+ currentStep = 1
+ } else {
+ currentStep = 2
+ }
+ },
+ colors = ButtonDefaults.buttonColors(
+ containerColor = colorResource(R.color.white),
+ contentColor = colorResource(R.color.gray_50)
+ ),
+ shape = HabiticaTheme.shapes.large,
+ contentPadding = PaddingValues(15.dp),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 12.dp)
+ .padding(bottom = 18.dp)
+ ) {
+ Text(
+ stringResource(if (currentStep == 0) R.string.next_button else R.string.finish),
+ fontSize = 18.sp,
+ fontWeight = FontWeight.Bold
+ )
+ }
+ }
+ }
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 8.dp)
+ .padding(
+ top = WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
+ )
+ ) {
+ Text(
+ "Username",
+ color = colorResource(R.color.brand_600),
+ fontSize = 18.sp,
+ textAlign = TextAlign.Center,
+ modifier = Modifier
+ .border(
+ 6.dp,
+ colorResource(R.color.brand_400),
+ shape = RoundedCornerShape(14.dp)
+ )
+ .padding(6.dp)
+ .background(
+ colorResource(R.color.brand_50),
+ shape = RoundedCornerShape(8.dp)
+ )
+ .padding(horizontal = 20.dp, vertical = 12.dp)
+ .widthIn(min = 120.dp)
+ )
+ ComposableAvatarView(null, null)
+ }
+ SpeechBubble(
+ text, { Text("Justin") }, modifier = Modifier
+ .padding(horizontal = 36.dp)
+ .padding(top = 210.dp)
+ .padding(
+ top = WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
+ )
+ )
+ AnimatedVisibility(
+ currentStep != 0,
+ enter = fadeIn(), exit = fadeOut(),
+ modifier = Modifier
+ .padding(top = 12.dp)
+ .padding(
+ top = WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
+ )
+ ) {
+ Button(
+ {
+ currentStep = 0
+ },
+ colors = ButtonDefaults.textButtonColors(),
+ modifier = Modifier.align(Alignment.TopStart)
+ ) {
+ Image(
+ painterResource(R.drawable.arrow_back),
+ contentDescription = null,
+ colorFilter = ColorFilter.tint(colorResource(R.color.brand_50)),
+ )
+ }
+ }
+ AnimatedVisibility(
+ currentStep == 2,
+ enter = expandVertically(expandFrom = Alignment.Bottom) + fadeIn(),
+ modifier = Modifier.align(Alignment.BottomCenter)
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(bgColorBottom)
+ )
+ }
+ }
+}
+
+@Composable
+fun OnboardingTaskSelector(modifier: Modifier = Modifier) {
+ LazyVerticalGrid(
+ columns = GridCells.Adaptive(minSize = 180.dp),
+ horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.CenterHorizontally),
+ verticalArrangement = Arrangement.spacedBy(10.dp, Alignment.CenterVertically),
+ contentPadding = PaddingValues(horizontal = 19.dp),
+ modifier = modifier
+ .fillMaxSize()
+ ) {
+ items(7) { index ->
+ Text(
+ "Test ${index}",
+ textAlign = TextAlign.Center,
+ fontSize = 18.sp,
+ fontWeight = FontWeight.Medium,
+ color = colorResource(R.color.white),
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(colorResource(R.color.brand_100), RoundedCornerShape(30.dp))
+ .padding(20.dp)
+ )
+ }
+ }
+}
+
+@Composable
+fun CustomizationCategoryView(selectedCategory: String, modifier: Modifier = Modifier) {
+ var selectedTabIndex by remember { mutableIntStateOf(0) }
+ val unselected = Color.White.copy(0.5f)
+ Column(verticalArrangement = Arrangement.spacedBy(26.dp)) {
+ AnimatedContent(selectedCategory) {
+ ProvideTextStyle(
+ TextStyle(
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal
+ )
+ ) {
+ TabRow(
+ selectedTabIndex,
+ containerColor = Color.Transparent,
+ contentColor = Color.White,
+ divider = {},
+ indicator = { positions ->
+ if (selectedTabIndex < positions.size) {
+ TabRowDefaults.PrimaryIndicator(
+ Modifier.tabIndicatorOffset(positions[selectedTabIndex]),
+ width = 60.dp,
+ height = 2.dp,
+ color = colorResource(R.color.brand_400)
+ )
+ }
+ }) {
+ when (it) {
+ "skin" -> {
+ Tab(
+ selectedTabIndex == 0,
+ unselectedContentColor = unselected,
+ onClick = { selectedTabIndex = 0 }) {
+ Text(
+ stringResource(R.string.avatar_skin_color).uppercase(),
+ modifier = Modifier.padding(bottom = 8.dp)
+ )
+ }
+ }
+
+ "hair" -> {
+ Tab(
+ selectedTabIndex == 0,
+ unselectedContentColor = unselected,
+ onClick = { selectedTabIndex = 0 }) {
+ Text(
+ stringResource(R.string.avatar_hair_color).uppercase(),
+ modifier = Modifier.padding(bottom = 8.dp)
+ )
+ }
+ Tab(
+ selectedTabIndex == 1,
+ unselectedContentColor = unselected,
+ onClick = { selectedTabIndex = 1 }) {
+ Text(
+ stringResource(R.string.avatar_hair_bangs).uppercase(),
+ modifier = Modifier.padding(bottom = 8.dp)
+ )
+ }
+ Tab(
+ selectedTabIndex == 2,
+ unselectedContentColor = unselected,
+ onClick = { selectedTabIndex = 2 }) {
+ Text(
+ stringResource(R.string.avatar_hair_styles).uppercase(),
+ modifier = Modifier.padding(bottom = 8.dp)
+ )
+ }
+ Tab(
+ selectedTabIndex == 3,
+ unselectedContentColor = unselected,
+ onClick = { selectedTabIndex = 3 }) {
+ Text(
+ stringResource(R.string.avatar_hair_ponytail).uppercase(),
+ modifier = Modifier.padding(bottom = 8.dp)
+ )
+ }
+ }
+
+ "clothes" -> {
+ Tab(
+ selectedTabIndex == 0,
+ unselectedContentColor = unselected,
+ onClick = { selectedTabIndex = 0 }) {
+ Text(
+ stringResource(R.string.avatar_shirt).uppercase(),
+ modifier = Modifier.padding(bottom = 8.dp)
+ )
+ }
+ }
+
+ "accessories" -> {
+ Tab(
+ selectedTabIndex == 0,
+ unselectedContentColor = unselected,
+ onClick = { selectedTabIndex = 0 }) {
+ Text(
+ stringResource(R.string.avatar_wheelchair).uppercase(),
+ modifier = Modifier.padding(bottom = 8.dp)
+ )
+ }
+ Tab(
+ selectedTabIndex == 0,
+ unselectedContentColor = unselected,
+ onClick = { selectedTabIndex = 0 }) {
+ Text(
+ stringResource(R.string.avatar_flower).uppercase(),
+ modifier = Modifier.padding(bottom = 8.dp)
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+ AnimatedContent(selectedCategory,
+ transitionSpec = {
+ (fadeIn(animationSpec = tween(220, delayMillis = 400)) +
+ scaleIn(initialScale = 0.92f, animationSpec = tween(220, delayMillis = 400)))
+ .togetherWith(fadeOut(animationSpec = tween(90)))
+ }) {
+ val scrollState = rememberScrollState()
+ var selectedItem by remember { mutableIntStateOf(0) }
+ AnimatedContent(
+ selectedTabIndex,
+ transitionSpec = {
+ slideIntoContainer(
+ if (targetState > initialState) {
+ AnimatedContentTransitionScope.SlideDirection.End
+ } else {
+ AnimatedContentTransitionScope.SlideDirection.Start
+ },
+ tween(400, 300)
+ )
+ .togetherWith(
+ slideOutOfContainer(
+ if (targetState > initialState) {
+ AnimatedContentTransitionScope.SlideDirection.End
+ } else {
+ AnimatedContentTransitionScope.SlideDirection.Start
+ }, tween(400, easing = LinearOutSlowInEasing)
+ )
+ )
+ },
+ ) { it ->
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = modifier
+ .fillMaxWidth()
+ .padding(top = 12.dp)
+ .horizontalScroll(scrollState)
+ ) {
+ Spacer(modifier = Modifier.width(20.dp))
+ repeat(8) { index ->
+ val transition = updateTransition(index == selectedItem)
+ val borderColor by transition.animateColor { if (it) colorResource(R.color.brand_400) else Color.Transparent }
+ val borderWidth by transition.animateDp({
+ tween(300)
+ }) { if (it) 4.dp else 0.dp }
+ Image(
+ painterResource(R.drawable.creator_hair_bangs_1_black),
+ contentDescription = null,
+ contentScale = ContentScale.None,
+ modifier = Modifier
+ .size(68.dp)
+ .border(borderWidth, borderColor, CircleShape)
+ .padding(4.dp)
+ .background(Color.White, CircleShape)
+ .clickable {
+ selectedItem = index
+ }
+ )
+ }
+ Spacer(modifier = Modifier.width(20.dp))
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun CustomizationCategorySelector(
+ selectedCategory: String,
+ onCategorySelected: (String) -> Unit,
+ modifier: Modifier = Modifier
+) {
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = modifier
+ .fillMaxWidth()
+ .padding(horizontal = 31.dp)
+ ) {
+ val categories = listOf(
+ Pair("skin", Pair(stringResource(R.string.avatar_skin), R.drawable.icon_skin)),
+ Pair("hair", Pair(stringResource(R.string.avatar_hair), R.drawable.icon_hair)),
+ Pair("clothes", Pair(stringResource(R.string.avatar_body), R.drawable.icon_body)),
+ Pair(
+ "accessories",
+ Pair(stringResource(R.string.avatar_extras), R.drawable.icon_extras)
+ )
+ )
+ categories.forEach { category ->
+ val isSelected = selectedCategory == category.first
+ val color = if (isSelected) Color.White else Color.White.copy(0.5f)
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier
+ .clip(RoundedCornerShape(12.dp))
+ .clickable {
+ onCategorySelected(category.first)
+ }
+ .padding(8.dp)) {
+ Image(
+ painterResource(category.second.second), contentDescription = null,
+ colorFilter = ColorFilter.tint(color)
+ )
+ Text(
+ category.second.first.uppercase(),
+ color = if (isSelected) Color.White else colorResource(R.color.brand_500),
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal,
+ textAlign = TextAlign.Center,
+ letterSpacing = 1.3.sp,
+ modifier = Modifier.padding(top = 18.dp)
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun SpeechBubble(
+ text: String,
+ npcName: @Composable () -> Unit,
+ modifier: Modifier = Modifier,
+ npc: @Composable (() -> Unit)? = null
+) {
+ TypewriterText(
+ text, fontSize = 16.sp,
+ fontWeight = FontWeight.Medium,
+ lineHeight = 21.sp,
+ color = colorResource(R.color.yellow_1),
+ modifier = modifier
+ .border(
+ width = 4.dp,
+ colorResource(R.color.yellow_10),
+ shape = RoundedCornerShape(12.dp)
+ )
+ .padding(4.dp)
+ .background(Color.White, shape = RoundedCornerShape(8.dp))
+ .padding(24.dp)
+ )
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/setup/UsernameSelectionScreen.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/setup/UsernameSelectionScreen.kt
new file mode 100644
index 000000000..98a6913b1
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/setup/UsernameSelectionScreen.kt
@@ -0,0 +1,224 @@
+package com.habitrpg.android.habitica.ui.views.setup
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.ime
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.systemBars
+import androidx.compose.foundation.layout.union
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextLinkStyles
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.fromHtml
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.ui.viewmodels.AuthenticationViewModel
+import com.habitrpg.android.habitica.ui.views.LoginFieldState
+import com.habitrpg.android.habitica.ui.views.LoginScreenField
+import com.habitrpg.common.habitica.theme.HabiticaTheme
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+
+@Composable
+fun T.useDebounce(
+ delayMillis: Long = 300L,
+ coroutineScope: CoroutineScope = rememberCoroutineScope(),
+ onChange: (T) -> Unit
+): T{
+ val state by rememberUpdatedState(this)
+
+ DisposableEffect(state){
+ val job = coroutineScope.launch {
+ delay(delayMillis)
+ onChange(state)
+ }
+ onDispose {
+ job.cancel()
+ }
+ }
+ return state
+}
+
+@Composable
+fun UsernameSelectionScreen(
+ authenticationViewModel: AuthenticationViewModel,
+ onPreviousOnboardingStep: () -> Unit,
+ onNextOnboardingStep: () -> Unit
+) {
+ var username by authenticationViewModel.username
+ val isUsernameValid by authenticationViewModel.isUsernameValid.collectAsState(null)
+ val usernameIssues by authenticationViewModel.usernameIssues.collectAsState(null)
+
+ username.useDebounce {
+ if (it.length > 2) {
+ authenticationViewModel.checkUsername(it)
+ }
+ }
+
+ var acceptedTerms by remember { mutableStateOf(false) }
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(colorResource(R.color.brand_300))
+ .padding(WindowInsets.ime.union(WindowInsets.systemBars).asPaddingValues())
+ ) {
+ Button(
+ {
+ onPreviousOnboardingStep()
+ },
+ colors = ButtonDefaults.textButtonColors(contentColor = Color.White),
+ modifier = Modifier.align(Alignment.TopStart)
+ ) {
+ Image(painterResource(R.drawable.arrow_back), contentDescription = null)
+ }
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(10.dp, Alignment.CenterVertically),
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(top = 50.dp)
+ .padding(horizontal = 20.dp)
+ ) {
+ Image(
+ painter = painterResource(R.drawable.header_verify_username),
+ contentDescription = null,
+ modifier = Modifier
+ .padding(bottom = 16.dp)
+ )
+ Text(
+ text = stringResource(R.string.what_should_call_you),
+ fontSize = 24.sp,
+ fontWeight = FontWeight.SemiBold,
+ color = Color.White,
+ modifier = Modifier
+ )
+ LoginScreenField(
+ stringResource(R.string.username),
+ value = username,
+ prefix = {
+ Text("@", fontSize = 16.sp, color = colorResource(R.color.brand_600), modifier = Modifier.padding(end = 8.dp))
+ },
+ state = when (isUsernameValid) {
+ true -> LoginFieldState.VALID
+ false -> LoginFieldState.ERROR
+ else -> LoginFieldState.DEFAULT
+ },
+ onValueChange = {
+ username = it
+ authenticationViewModel.invalidateUsernameState()
+ },
+ modifier = Modifier
+ )
+ AnimatedVisibility(usernameIssues?.isNotBlank() == true) {
+ Text(
+ text = usernameIssues ?: "",
+ fontSize = 16.sp,
+ color = colorResource(R.color.red_500),
+ textAlign = TextAlign.Center,
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ .padding(bottom = 12.dp)
+ )
+ }
+ Text(
+ text = stringResource(R.string.username_description),
+ fontSize = 16.sp,
+ color = colorResource(R.color.brand_600),
+ textAlign = TextAlign.Center,
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ )
+ Spacer(Modifier.weight(1f))
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(12.dp),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(bottom = 4.dp)
+ ) {
+ Box(
+ modifier = Modifier
+ .size(30.dp)
+ .clickable {
+ acceptedTerms = !acceptedTerms
+ }
+ .background(colorResource(R.color.brand_100), shape = HabiticaTheme.shapes.small)) {
+ if (acceptedTerms) {
+ Image(
+ painter = painterResource(R.drawable.checkmark),
+ contentDescription = null,
+ colorFilter = ColorFilter.tint(Color.White),
+ modifier = Modifier
+ .align(Alignment.Center)
+ )
+ }
+ }
+ Text(
+ AnnotatedString.fromHtml(
+ stringResource(R.string.register_tos_confirm),
+ linkStyles = TextLinkStyles(style = SpanStyle(
+ fontWeight = FontWeight.Bold,
+ color = colorResource(R.color.white)
+ ))
+ ),
+ fontSize = 16.sp,
+ color = colorResource(R.color.brand_600),
+ fontWeight = FontWeight.Normal,
+ )
+ }
+ Button(
+ onClick = onNextOnboardingStep,
+ colors = ButtonDefaults.buttonColors(
+ containerColor = Color.White,
+ contentColor = colorResource(R.color.gray_50),
+ disabledContainerColor = Color.White.copy(alpha = 0.5f),
+ disabledContentColor = colorResource(R.color.gray_50)
+ ),
+ enabled = isUsernameValid == true && acceptedTerms,
+ shape = HabiticaTheme.shapes.large,
+ contentPadding = PaddingValues(15.dp),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(bottom = 16.dp)
+ ) {
+ Text(stringResource(R.string.get_started), fontSize = 18.sp, fontWeight = FontWeight.Bold)
+ }
+ }
+ }
+}
diff --git a/Habitica/src/main/res/layout/login_background.xml b/Habitica/src/main/res/layout/login_background.xml
new file mode 100644
index 000000000..6fdc6bbaa
--- /dev/null
+++ b/Habitica/src/main/res/layout/login_background.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Habitica/src/main/res/values/strings.xml b/Habitica/src/main/res/values/strings.xml
new file mode 100644
index 000000000..53e36ce29
--- /dev/null
+++ b/Habitica/src/main/res/values/strings.xml
@@ -0,0 +1,13 @@
+
+
+ Continue with Google
+ Continue with email
+ Already have an account?
+ Need an account?
+ Enjoy having fun getting things done
+ What should we call you?
+ Get Started
+ This is a unique name you can change later at any time!
+ You must be new here. I’m Justin, I’ll be your guide in Habitica.\n\nSo how would you like to look? Don’t worry, you can change this later.
+ Looking good! Now, choose any categories you’d like to work on and we’ll create a few tasks to get started.\n\nWe’ll go to your task board after this!
+
diff --git a/common/build.gradle.kts b/common/build.gradle.kts
index 8eba2e3f0..6fb33b120 100644
--- a/common/build.gradle.kts
+++ b/common/build.gradle.kts
@@ -120,5 +120,4 @@ dependencies {
implementation(libs.text.google.fonts)
implementation(libs.ui.tooling)
implementation(libs.material3)
- implementation(libs.accompanist.theme)
}
diff --git a/common/src/main/java/com/habitrpg/common/habitica/theme/HabiticaTheme.kt b/common/src/main/java/com/habitrpg/common/habitica/theme/HabiticaTheme.kt
index 62bcd6db2..d3c9ddf22 100644
--- a/common/src/main/java/com/habitrpg/common/habitica/theme/HabiticaTheme.kt
+++ b/common/src/main/java/com/habitrpg/common/habitica/theme/HabiticaTheme.kt
@@ -7,89 +7,93 @@ import androidx.compose.material3.Typography
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.res.colorResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import com.google.accompanist.themeadapter.material3.createMdc3Theme
+import com.habitrpg.common.habitica.R
+import com.habitrpg.common.habitica.extensions.getThemeColor
@Composable
fun HabiticaTheme(
content: @Composable () -> Unit
) {
val context = LocalContext.current
- val layoutDirection = LocalLayoutDirection.current
- val (colors, _, _) =
- createMdc3Theme(
- context = context,
- layoutDirection = layoutDirection,
- setTextColors = true
- )
+ val colors = MaterialTheme.colorScheme.copy(
+ primary = Color(context.getThemeColor(R.attr.colorPrimary)),
+ secondary = Color(context.getThemeColor(R.attr.colorSecondary)),
+ tertiary = Color(context.getThemeColor(R.attr.colorTertiary)),
+ error = Color(context.getThemeColor(R.attr.colorError)),
+ background = Color(context.getThemeColor(R.attr.backgroundColor)),
+ onBackground = colorResource(R.color.text_primary),
+ surface = Color(context.getThemeColor(R.attr.colorSurface)),
+ onSurface = Color(context.getThemeColor(R.attr.colorOnSurface)),
+ )
MaterialTheme(
- colorScheme = colors ?: MaterialTheme.colorScheme,
+ colorScheme = colors,
typography =
- Typography(
- displayLarge =
- TextStyle(
- fontWeight = FontWeight.Medium,
- fontSize = 20.sp,
- letterSpacing = (0.05).sp
+ Typography(
+ displayLarge =
+ TextStyle(
+ fontWeight = FontWeight.Medium,
+ fontSize = 20.sp,
+ letterSpacing = (0.05).sp
+ ),
+ displayMedium =
+ TextStyle(
+ fontWeight = FontWeight.Normal,
+ fontSize = 28.sp,
+ letterSpacing = (0.05).sp
+ ),
+ titleLarge =
+ TextStyle(
+ fontWeight = FontWeight.Medium,
+ fontSize = 16.sp
+ ),
+ titleMedium =
+ TextStyle(
+ fontWeight = FontWeight.Normal,
+ fontSize = 16.sp,
+ letterSpacing = 0.1.sp
+ ),
+ titleSmall =
+ TextStyle(
+ fontWeight = FontWeight.SemiBold,
+ fontSize = 14.sp
+ ),
+ bodyLarge =
+ TextStyle(
+ fontWeight = FontWeight.Medium,
+ fontSize = 14.sp,
+ letterSpacing = 0.35.sp,
+ lineHeight = 16.sp
+ ),
+ bodyMedium =
+ TextStyle(
+ fontWeight = FontWeight.Normal,
+ fontSize = 14.sp,
+ letterSpacing = 0.2.sp,
+ lineHeight = 16.sp
+ ),
+ labelMedium =
+ TextStyle(
+ fontWeight = FontWeight.Medium,
+ fontSize = 14.sp,
+ letterSpacing = 1.25.sp
+ ),
+ labelSmall =
+ TextStyle(
+ fontWeight = FontWeight.Bold,
+ fontSize = 12.sp
+ )
),
- displayMedium =
- TextStyle(
- fontWeight = FontWeight.Normal,
- fontSize = 28.sp,
- letterSpacing = (0.05).sp
- ),
- titleLarge =
- TextStyle(
- fontWeight = FontWeight.Medium,
- fontSize = 16.sp
- ),
- titleMedium =
- TextStyle(
- fontWeight = FontWeight.Normal,
- fontSize = 16.sp,
- letterSpacing = 0.1.sp
- ),
- titleSmall =
- TextStyle(
- fontWeight = FontWeight.SemiBold,
- fontSize = 14.sp
- ),
- bodyLarge =
- TextStyle(
- fontWeight = FontWeight.Medium,
- fontSize = 14.sp,
- letterSpacing = 0.35.sp,
- lineHeight = 16.sp
- ),
- bodyMedium =
- TextStyle(
- fontWeight = FontWeight.Normal,
- fontSize = 14.sp,
- letterSpacing = 0.2.sp,
- lineHeight = 16.sp
- ),
- labelMedium =
- TextStyle(
- fontWeight = FontWeight.Medium,
- fontSize = 14.sp,
- letterSpacing = 1.25.sp
- ),
- labelSmall =
- TextStyle(
- fontWeight = FontWeight.Bold,
- fontSize = 12.sp
- )
- ),
shapes =
- Shapes(
- RoundedCornerShape(4.dp),
- RoundedCornerShape(8.dp),
- RoundedCornerShape(12.dp)
- ),
+ Shapes(
+ RoundedCornerShape(4.dp),
+ RoundedCornerShape(8.dp),
+ RoundedCornerShape(12.dp)
+ ),
content = content
)
}
diff --git a/common/src/main/res/values-b+es+419/strings.xml b/common/src/main/res/values-b+es+419/strings.xml
index 2b4ccea23..b6ffb8eb3 100644
--- a/common/src/main/res/values-b+es+419/strings.xml
+++ b/common/src/main/res/values-b+es+419/strings.xml
@@ -4,7 +4,7 @@
MBT
- Relojes de Arena Místicos
+ Relojes de arena MísticosDiariamenteMensualmenteSemanalmente
@@ -35,8 +35,9 @@
%d Tareas diarias
- %d Quehacer
- %d Quehaceres
+ %d Tarea pendiente
+
+ %d Tareas pendientesGemasoro
@@ -47,7 +48,7 @@
DifícilHábitosTareas diarias
- Quehaceres
+ Tareas pendientesNivel %dIniciar sesiónRegistrarse
@@ -56,19 +57,23 @@
fácilintermediodifícil
- Quehacer
- Nueva Tarea
- Avatar
+ Tarea pendiente
+ Nueva tarea
+ PersonajeNivel %d
- Usuario
- Correo/Usuario
+ Nombre de usuario
+ Correo/Nombre de usuarioCorreo electrónicoIniciar sesión con GoogleSalir de tu cuentaError de validaciónQd
- Completar una Tarea
- crea una tarea
- crear
- Nuevo %s
+ Completa una tarea
+ Crea una tarea
+ Crear %s
+ Nueva %s
+ Guardar %s
+ Reintentar
+ Tarea
+ Error al cargar
\ No newline at end of file
diff --git a/common/src/main/res/values-hu/strings.xml b/common/src/main/res/values-hu/strings.xml
index 941030693..48b46fd62 100644
--- a/common/src/main/res/values-hu/strings.xml
+++ b/common/src/main/res/values-hu/strings.xml
@@ -19,7 +19,7 @@
mrdStatisztikákb
- Gyémántok
+ GyémántaranyAranyMisztikus homokóra
diff --git a/common/src/main/res/values-id/strings.xml b/common/src/main/res/values-id/strings.xml
index 3df6b41b8..9ca493c38 100644
--- a/common/src/main/res/values-id/strings.xml
+++ b/common/src/main/res/values-id/strings.xml
@@ -1,14 +1,14 @@
- rb
- jt
- mil
+ K
+ m
+ btPermataKoin EmasEmasJam Pasir Mistik
- harian
+ Sehari-harianBulananMingguanRemeh
@@ -36,7 +36,7 @@
Nama PenggunaKata sandiAlamat Email
- Konfirmasi kata sandi
+ Ulangi kata sandiMasuk melalui GoogleKeluarKeluar dari akunmu
diff --git a/common/src/main/res/values-pl/strings.xml b/common/src/main/res/values-pl/strings.xml
index a8e36177e..f2db58aa9 100644
--- a/common/src/main/res/values-pl/strings.xml
+++ b/common/src/main/res/values-pl/strings.xml
@@ -30,16 +30,16 @@
poz %dPoziom %dEdytuj
- Anuluj Zaproszenie
+ AnulujZaloguj się
- Rejestracja
+ Zarejestruj sięNazwa użytkownikaHasłoAdres e-mailPotwierdź hasłoZaloguj się przez GoogleWyloguj
- Wyloguj się
+ Wyloguj się z kontaWitajO nasBłąd walidacji
@@ -55,7 +55,7 @@
Utwórz %sNowy %sNa pewno\?
- Zadania
+ ZadanieE-mail/Nazwa użytkownikaZapisz %sSpróbuj ponownie
diff --git a/common/src/main/res/values-zh/strings.xml b/common/src/main/res/values-zh/strings.xml
index 7c72d46f5..39cb32365 100644
--- a/common/src/main/res/values-zh/strings.xml
+++ b/common/src/main/res/values-zh/strings.xml
@@ -43,7 +43,7 @@
邮箱地址/用户名密码邮箱地址
- 确认密码
+ 再次输入密码使用Google登录退出欢迎
diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml
index 3a03bd1de..c0ff8faa3 100644
--- a/common/src/main/res/values/strings.xml
+++ b/common/src/main/res/values/strings.xml
@@ -43,7 +43,7 @@
Email/UsernamePasswordEmail address
- Confirm password
+ Repeat passwordSign in with GoogleLogout
@@ -84,4 +84,4 @@
Failed to loadRetry
-
\ No newline at end of file
+
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 3a44d1a0d..e0c74b4fa 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -3,21 +3,20 @@ minSdk = "26"
targetSdk = "36"
wearOsTargetSdk = "34"
-accompanist = "0.34.0"
-agp = "8.10.1"
-amplitude = "1.21.2"
+agp = "8.10.0"
+amplitude = "1.20.7"
androidTest = "1.6.1"
androidTestMonitor = "1.7.2"
androidTestRunner = "1.6.2"
annotationApi = "1.3.2"
appcompat = "1.7.1"
billing = "7.1.1"
-coil = "3.2.0"
-compose = "1.8.2"
+coil = "3.1.0"
+compose = "1.8.1"
composeActivity = "1.10.1"
compose_compiler = "1.5.14"
constraintlayout = "2.2.1"
-converterMoshi = "3.0.0"
+converterMoshi = "2.11.0"
coordinatorlayout = "1.3.0"
coreSplashscreen = "1.1.0-rc01"
core_ktx = "1.16.0"
@@ -27,9 +26,8 @@ credentials = "1.5.0"
daggerhilt = "2.56.2"
desuggar = "2.1.5"
detekt = "1.23.8"
-deviceNames = "2.1.1"
firebase-perf = "1.4.2"
-firebase_bom = "33.15.0"
+firebase_bom = "33.13.0"
flexbox = "3.0.0"
fragmentKtx = "1.8.8"
fragmentTesting = "1.8.8"
@@ -69,7 +67,6 @@ wear = "1.3.0"
wearInput = "1.1.0"
[libraries]
-accompanist-theme = { group = "com.google.accompanist", name = "accompanist-themeadapter-material3", version.ref = "accompanist" }
activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "composeActivity" }
amplitude-analytic = { group = "com.amplitude", name = "analytics-android", version.ref = "amplitude" }
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
@@ -89,7 +86,6 @@ coroutine-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutine
credentials = { group = "androidx.credentials", name = "credentials", version.ref = "credentials" }
credentials-playServicesAuth = { group = "androidx.credentials", name = "credentials-play-services-auth", version.ref = "credentials" }
desugar = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "desuggar" }
-device-names = { group = "com.jaredrummler", name = "android-device-names", version.ref = "deviceNames" }
firebase-analytic = { group = "com.google.firebase", name = "firebase-analytics-ktx" }
firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebase_bom" }
firebase-config = { group = "com.google.firebase", name = "firebase-config-ktx" }
diff --git a/translations/store_strings-es_419.xml b/translations/store_strings-es_419.xml
index 04bf4be08..d9b23f468 100644
--- a/translations/store_strings-es_419.xml
+++ b/translations/store_strings-es_419.xml
@@ -1,9 +1,9 @@
-
+
- Convierte a tus tareas en un juego
- ¡Trata tu vida como un juego para mantenerte motivado y organizado!
- ¡Trata tu vida como un juego para mantenerte motivado y organizado! Habitica hace que cumplir objetivos sea entretenido.
+ Convierte tus tareas en un juego
+ ¡Trata tu vida como un juego para mantenerte motivado y organizado!
+ ¡Trata tu vida como un juego para mantenerte motivado y organizado! Habitica hace que cumplir objetivos sea entretenido.
Escribe tus hábitos, tus metas diarias y tu lista de pendientes, y luego crea un avatar personalizado. ¡Marca tus tareas para subir de nivel y desbloquear objetos tales como armaduras, mascotas, habilidades, e incluso misiones! Lucha contra monstruos con tus amigos para mantenerse responsables mutuamente frente a sus tareas, y usa tu oro para comprar recompensas en el juego, como equipamiento, o premios personalizados, como ver un episodio de tu serie favorita. Flexible, social y divertido, Habitica es la mejor manera de motivarte para lograr cualquier cosa.
Si tienes alguna duda, ¡siéntete libre de comunicárnosla a mobile@habitica.com ! Y si te gusta nuestra aplicación, nos encantaría que nos dejaras un comentario.
-
+
\ No newline at end of file
diff --git a/wearos/src/main/res/values-b+es+419/strings.xml b/wearos/src/main/res/values-b+es+419/strings.xml
index c61ef4fe3..23897ad53 100644
--- a/wearos/src/main/res/values-b+es+419/strings.xml
+++ b/wearos/src/main/res/values-b+es+419/strings.xml
@@ -2,39 +2,42 @@
OKSincronizar cuenta desde el teléfono
- ¿Cómo lo hago\?
- Editar en teléfono
- Ingresar
- Crear listas de tareas y tacha tu progreso con tu personaje del juego
- Entra en tu teléfono
+ ¿Cómo te fue\?
+ Editar en móvil
+ Iniciar sesión
+ Crea listas de tareas y sigue tu progreso con tu personaje del juego
+ Iniciar sesión en móvilOtras opciones
- Crear Cuenta
+ Crear cuentaContinuar
- Te quedaste sin PV
- Perdiste un nivel, experiencia y oro. ¡No te rindas!
- Hay tareas diarias incompletas de ayer
- Revisión diaria
- Tacha en el celular
- Tareas con lista de tareas están disponibles en el celular
- Comenzar día
+ Te quedaste sin HP
+ Perdiste un Nivel, Experiencia y Oro. ¡No te rindas!
+ Hay Tareas diarias incompletas de ayer
+ Revisar Tareas diarias
+ Comprobar en el móvil
+ Las listas de tareas están disponibles en el móvil
+ Iniciar díaIniciar nuevo día
- Tacha todo lo que hiciste ayer:
- Tipo de tareas
- Escribe el titulo de la tarea
+ Marca todo lo que hiciste ayer:
+ Tipo de tarea
+ Añadir título de la TareaGuardarSincronizar datos
- Ocultar recompensas de la tarea
+ Esconder recompensas de la tareaVersión %1$s (%2$d)
- ¿Quieres cerrar sesión en Habitica\?
- Crea un %s para añadirlo a esta lista
- Un huevo
- Una Poción
+ ¿Cerrar sesión en Habitica\?
+ Crea una %s para añadir a esta lista
+ Un Huevo
+ una PociónUn poco de comida
- Continua con tu celular
- ¡Subiste de nivel gracias a tu trabajo duro!
- ¡Ya esta todo hoy!
+ Continuar en tu teléfono
+ ¡Subiste de nivel gracias a tu esfuerzo!
+ ¡Todo listo por hoy!Desconectado
- empezando tu Día...
+ Empezando tu día...Encontraste…
- Objeto de la misión
+ 1 objeto de misión
+ %d Objetos de misión
+ %1$s y %2$s
+ Algunos %s
\ No newline at end of file
diff --git a/wearos/src/main/res/values-nl/strings.xml b/wearos/src/main/res/values-nl/strings.xml
index 4f1fe0967..0d7b59192 100644
--- a/wearos/src/main/res/values-nl/strings.xml
+++ b/wearos/src/main/res/values-nl/strings.xml
@@ -1,4 +1,43 @@
OK
+ Synchroniseren met Account van Telefoon
+ Hoe deed je het\?
+ Je vond…
+ 1 Queeste voorwerp
+ %d Queeste voorwerpen
+ %1$s en %2$s
+ Een aantal%s
+ Bewerken op telefoon
+ Doorgaan
+ Je hebt geen HP meer
+ Je verliest een niveau, Xp en Goud. Niet opgeven!
+ Inloggen
+ Maak een taak lijst en volg progressie met jouw gegamificeerd personage
+ Inloggen op je telefoon
+ Andere opties
+ Maak account
+ Er zijn niet-gemaakte Dagelijkse taken van gisteren
+ Bekijk Dagelijkse taken
+ Bekijk op telefoon
+ Takenlijsten zijn beschikbaar op je telefoon
+ Verberg taak beloningen
+ Begin nieuwe dag
+ Streep af welke je gisteren hebt gedaan:
+ Taak Type
+ Dag start
+ Voer Taak Titel in
+ Opslaan
+ Synchroniseer Gegevens
+ Dankzij al je harde werk heb je een hoger niveau bereikt!
+ Klaar voor vandaag!
+ Je dag beginnen...
+ Versie %1$s (%2$d)
+ Maak een %s om aan deze lijst toe te voegen
+ een Ei
+ een Toverdrank
+ Wat Voedsel
+ Uitloggen bij Habitica\?
+ Ga verder op je telefoon
+ Verbinding verbroken
\ No newline at end of file