From 47a6e11cdda4d0f6d55a7aa57299490148800f2a Mon Sep 17 00:00:00 2001 From: "Any Association [bot]" <139948520+any-association@users.noreply.github.com> Date: Mon, 3 Feb 2025 21:56:44 +0100 Subject: [PATCH 01/78] l10n | Enhancement (#2062) --- localization/src/main/res/values-de-rDE/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/localization/src/main/res/values-de-rDE/strings.xml b/localization/src/main/res/values-de-rDE/strings.xml index 410a2ab83b..98380ac6fc 100644 --- a/localization/src/main/res/values-de-rDE/strings.xml +++ b/localization/src/main/res/values-de-rDE/strings.xml @@ -46,7 +46,7 @@ Zufällige Volltonfarbe verwenden Bild hochladen Bild hochladen - Remove image + Bild entfernen Space löschen Du kannst bis zu %1$s deiner Dateien auf unserem verschlüsselten und kostenlosen Backup-Knoten speichern. Wenn das Limit erreicht wurde, werden Dateien nur noch lokal gespeichert. Um Platz auf deinem Gerät zu sparen, können alle deine Dateien auf unseren verschlüsselten Backup-Knoten ausgelagert werden. Die Dateien werden beim Öffnen dann wieder heruntergeladen. From e13333c99796626fad28e3dce062c2ebeb3f68e8 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Mon, 3 Feb 2025 19:16:14 +0100 Subject: [PATCH 02/78] DROID-3331 Protocol | Enhancement | MW 0.39.5 (#2058) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3567ee9eed..74044192dc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -middlewareVersion = "v0.39.4" +middlewareVersion = "v0.39.5" kotlinVersion = '2.0.21' kspVersion = "2.0.21-1.0.25" From d88fd568db26106fe8ae4a3eb340f623936596a1 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Mon, 3 Feb 2025 20:24:04 +0100 Subject: [PATCH 03/78] DROID-3297 Spaces | Fix | Checking last opened space status on splash screen (#2060) --- .../presentation/splash/SplashViewModel.kt | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModel.kt index bf92930fa5..2c307b931d 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModel.kt @@ -326,22 +326,26 @@ class SplashViewModel( .take(1) } .collect { view -> - val chat = view.chatId - if (chat.isNullOrEmpty() || !ChatConfig.isChatAllowed(space.id)) { - commands.emit( - Command.NavigateToWidgets( - space = space.id, - deeplink = deeplink + if (view.isActive) { + val chat = view.chatId + if (chat.isNullOrEmpty() || !ChatConfig.isChatAllowed(space.id)) { + commands.emit( + Command.NavigateToWidgets( + space = space.id, + deeplink = deeplink + ) ) - ) + } else { + commands.emit( + Command.NavigateToSpaceLevelChat( + space = space.id, + chat = chat, + deeplink = deeplink + ) + ) + } } else { - commands.emit( - Command.NavigateToSpaceLevelChat( - space = space.id, - chat = chat, - deeplink = deeplink - ) - ) + commands.emit(Command.NavigateToVault(deeplink)) } } } else { From 9c562edf77d9760fe552df9938692e3b64b58dfc Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Mon, 3 Feb 2025 20:24:21 +0100 Subject: [PATCH 04/78] DROID-3328 Vault | Fix | Disable welcome-to-the-vault tip (#2059) --- .../anytype/ui/vault/IntroduceVaultFragment.kt | 1 + .../anytype/ui/vault/IntroduceVaultScreen.kt | 1 + .../anytype/ui/vault/VaultFragment.kt | 7 ------- app/src/main/res/navigation/graph.xml | 3 --- .../core_models/settings/VaultSettings.kt | 1 + .../presentation/vault/VaultViewModel.kt | 18 ------------------ 6 files changed, 3 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/com/anytypeio/anytype/ui/vault/IntroduceVaultFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/vault/IntroduceVaultFragment.kt index 9b254f617e..bb62fcb265 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/vault/IntroduceVaultFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/vault/IntroduceVaultFragment.kt @@ -10,6 +10,7 @@ import androidx.compose.ui.platform.ViewCompositionStrategy import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment import com.anytypeio.anytype.ui.settings.typography +@Deprecated("Outdated. To be deleted soon.") class IntroduceVaultFragment : BaseBottomSheetComposeFragment() { override fun onCreateView( diff --git a/app/src/main/java/com/anytypeio/anytype/ui/vault/IntroduceVaultScreen.kt b/app/src/main/java/com/anytypeio/anytype/ui/vault/IntroduceVaultScreen.kt index 90b3876c5b..aa404a0205 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/vault/IntroduceVaultScreen.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/vault/IntroduceVaultScreen.kt @@ -34,6 +34,7 @@ import com.anytypeio.anytype.core_ui.views.ButtonSize import com.anytypeio.anytype.core_ui.views.HeadlineHeading import kotlinx.coroutines.launch +@Deprecated("To be deleted") @Composable fun IntroduceVaultScreen( onDoneClicked: () -> Unit diff --git a/app/src/main/java/com/anytypeio/anytype/ui/vault/VaultFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/vault/VaultFragment.kt index 6d00e0617f..bb524ab30a 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/vault/VaultFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/vault/VaultFragment.kt @@ -117,13 +117,6 @@ class VaultFragment : BaseComposeFragment() { Timber.e(it, "Error while opening profile settings from vault") } } - is Command.ShowIntroduceVault -> { - runCatching { - findNavController().navigate(R.id.actionShowIntroduceVaultScreen) - }.onFailure { - Timber.e(it, "Error while opening introduce-vault-screen from vault") - } - } is Command.Deeplink.Invite -> { findNavController().navigate( R.id.requestJoinSpaceScreen, diff --git a/app/src/main/res/navigation/graph.xml b/app/src/main/res/navigation/graph.xml index e5089aa9ec..61abe8f2f4 100644 --- a/app/src/main/res/navigation/graph.xml +++ b/app/src/main/res/navigation/graph.xml @@ -272,9 +272,6 @@ - = emptyList(), val isRelativeDates: Boolean, diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/vault/VaultViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/vault/VaultViewModel.kt index b31f68d3e2..bc4c532917 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/vault/VaultViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/vault/VaultViewModel.kt @@ -186,23 +186,6 @@ class VaultViewModel( ) ) } - viewModelScope.launch { - getVaultSettings.async(Unit) - .onSuccess { settings -> - if (settings.showIntroduceVault) { - commands.emit(Command.ShowIntroduceVault) - setVaultSettings.async( - params = settings.copy( - showIntroduceVault = false - ) - ).onFailure { - Timber.e(it, "Error while setting vault settings") - } - } - }.onFailure { - Timber.e(it, "Error while getting vault settings") - } - } viewModelScope.launch { when (deeplink) { is DeepLinkResolver.Action.Import.Experience -> { @@ -397,7 +380,6 @@ class VaultViewModel( data class EnterSpaceLevelChat(val space: Space, val chat: Id): Command() data object CreateNewSpace: Command() data object OpenProfileSettings: Command() - data object ShowIntroduceVault : Command() sealed class Deeplink : Command() { data object DeepLinkToObjectNotWorking: Deeplink() From b24decc6859fd43030d548e59b2e420f6f3fde23 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Mon, 3 Feb 2025 20:24:57 +0100 Subject: [PATCH 05/78] DROID- 3291 Widgets | Fix | Fix favorite objects filtering for widgets with list layout (#2061) --- .../presentation/widgets/ListWidgetContainer.kt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/ListWidgetContainer.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/ListWidgetContainer.kt index 75fab1739e..b822fbd473 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/ListWidgetContainer.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/ListWidgetContainer.kt @@ -96,17 +96,15 @@ class ListWidgetContainer( space = SpaceId(widget.config.space), subscription = subscription, keys = keys, - targets = order.keys - .sortedBy { key -> order[key] } - .take(resolveLimit()), + targets = order.keys.sortedBy { key -> order[key] } ) ).map { objects -> buildWidgetViewWithElements( objects = objects - .filter { obj -> - obj.isArchived != true && obj.isDeleted != true - } - .sortedBy { obj -> order[obj.id] }, + .filter { obj -> obj.notDeletedNorArchived } + .sortedBy { obj -> order[obj.id] } + .take(resolveLimit()) + , fieldParser = fieldParser ) } From c4f40d15c442e377bed2330e3036a3f74f98e49f Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Mon, 3 Feb 2025 21:36:42 +0100 Subject: [PATCH 06/78] DROID-3219 Widgets | Fix | Fix member count text color --- .../java/com/anytypeio/anytype/ui/home/HomeScreenFragment.kt | 1 - .../java/com/anytypeio/anytype/ui/home/HomeScreenToolbar.kt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenFragment.kt index 1a60584c8a..cb7b82ed4b 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenFragment.kt @@ -140,7 +140,6 @@ class HomeScreenFragment : BaseComposeFragment(), ) } } - BackHandler { vm.onBackClicked( isSpaceRoot = isSpaceRootScreen() diff --git a/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenToolbar.kt b/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenToolbar.kt index 6b471213ca..c7aa3c6bcd 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenToolbar.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenToolbar.kt @@ -95,7 +95,7 @@ fun HomeScreenToolbar( } else stringResource(id = R.string.three_dots_text_placeholder), style = Relations2, - color = colorResource(R.color.text_secondary), + color = colorResource(R.color.transparent_active), modifier = Modifier .align(Alignment.BottomStart) .padding( From f8bda4ad5fc2cdebcb6487cb4a3c496758982a49 Mon Sep 17 00:00:00 2001 From: "Any Association [bot]" <139948520+any-association@users.noreply.github.com> Date: Wed, 5 Feb 2025 17:46:57 +0100 Subject: [PATCH 07/78] l10n | Enhancement (#2064) --- localization/src/main/res/values-fr-rFR/strings.xml | 6 +++--- localization/src/main/res/values-it-rIT/strings.xml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/localization/src/main/res/values-fr-rFR/strings.xml b/localization/src/main/res/values-fr-rFR/strings.xml index 9aab3fc394..ecaa5ef6e7 100644 --- a/localization/src/main/res/values-fr-rFR/strings.xml +++ b/localization/src/main/res/values-fr-rFR/strings.xml @@ -46,7 +46,7 @@ Appliquer une couleur solide aléatoire Upload image Upload image - Remove image + Supprimer l’image Supprimer l\'espace Vous pouvez stocker gratuitement jusqu\'à %1$s de vos fichiers sur notre nœud de sauvegarde chiffré. Si vous atteignez la limite, les fichiers ne seront stockés que localement. Afin d\'économiser de l\'espace sur votre appareil, vous pouvez décharger tous vos fichiers vers notre nœud de sauvegarde chiffré. Les fichiers seront rechargés lorsque vous les ouvrirez. @@ -371,7 +371,7 @@ Faites défiler pour sélectionner une nouvelle position Ajouter en dessous Back button - Share button + Bouton de partage Home button Search button Add doc @@ -1215,7 +1215,7 @@ Demande approuvée Votre demande de rejoindre l\'espace %1$s a été approuvée avec des droits d\'accès en lecture seule. L\'espace sera bientôt disponible sur votre appareil. Votre demande de rejoindre l\'espace %1$s a été approuvée avec les droits d\'accès en modification. L\'espace sera bientôt disponible sur votre appareil. - The space is no longer accessible. + L\'espace n\'est plus accessible. Vous avez été retiré de l\'espace \'\' , ou l\'espace a été supprimé par le propriétaire. Permissions modifiées Vos droits d\'accès ont été changés en lecture seule dans l\'espace %1$s. diff --git a/localization/src/main/res/values-it-rIT/strings.xml b/localization/src/main/res/values-it-rIT/strings.xml index 5a92511f89..e349be6bf5 100644 --- a/localization/src/main/res/values-it-rIT/strings.xml +++ b/localization/src/main/res/values-it-rIT/strings.xml @@ -46,7 +46,7 @@ Applica colore tinta unita casuale Upload image Upload image - Remove image + Rimuovi immagine Elimina spazio Puoi archiviare gratuitamente fino a %1$s di file personali sul nostro nodo di backup crittografato. Se raggiungi il limite, i file verranno archiviati solo localmente. Per risparmiare spazio sul tuo dispositivo locale, puoi liberare tutti i tuoi file trasferendoli al nostro nodo di backup criptato. I file verranno ricaricati quando li apri @@ -371,7 +371,7 @@ Scroll to select new position Aggiungi sotto Back button - Share button + Pulsante condivisione Home button Search button Add doc @@ -1216,7 +1216,7 @@ Richiesta approvata La tua richiesta di unirti allo spazio %1$s è stata approvata con diritti di accesso in sola lettura. Lo spazio sarà disponibile sul tuo dispositivo a breve. La tua richiesta di unirti allo spazio %1$s è stata approvata con diritti di accesso per la modifica. Lo spazio sarà disponibile sul tuo dispositivo a breve. - The space is no longer accessible. + Lo spazio non è più accessibile. Sei stato rimosso dallo spazio, oppure lo spazio è stato eliminato dal proprietario. Autorizzazioni cambiate I tuoi diritti di accesso sono stati impostati in sola lettura nello spazio %1$s. From b455c68e2bf1469268270fd2a41a3cbfaae7b1a7 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Wed, 5 Feb 2025 17:46:39 +0100 Subject: [PATCH 08/78] DROID-3198 Deep links | Fix | Fix wording for error when handling deep link to object from private space (#2066) --- localization/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/localization/src/main/res/values/strings.xml b/localization/src/main/res/values/strings.xml index 7758b202f0..978f803acb 100644 --- a/localization/src/main/res/values/strings.xml +++ b/localization/src/main/res/values/strings.xml @@ -1420,7 +1420,7 @@ Changing permissions is not allowed Sharing limit reached Deeplink to your object - This deeplink is not working. Object might be deleted or there might be issues with space access. + Object is not available. Ask the owner to share it. Collaborate on spaces 1. Tap the Space widget to access settings 2. Open Share section From da4453d0915ef446ffa2c9616654760f8755fc05 Mon Sep 17 00:00:00 2001 From: "Any Association [bot]" <139948520+any-association@users.noreply.github.com> Date: Wed, 5 Feb 2025 22:08:32 +0100 Subject: [PATCH 09/78] l10n | Enhancement (#2067) --- localization/src/main/res/values-be-rBY/strings.xml | 2 +- localization/src/main/res/values-de-rDE/strings.xml | 2 +- localization/src/main/res/values-es-rES/strings.xml | 2 +- localization/src/main/res/values-fr-rFR/strings.xml | 2 +- localization/src/main/res/values-in-rID/strings.xml | 2 +- localization/src/main/res/values-it-rIT/strings.xml | 2 +- localization/src/main/res/values-nl-rNL/strings.xml | 2 +- localization/src/main/res/values-no-rNO/strings.xml | 2 +- localization/src/main/res/values-pt-rBR/strings.xml | 2 +- localization/src/main/res/values-ru-rRU/strings.xml | 2 +- localization/src/main/res/values-tr-rTR/strings.xml | 2 +- localization/src/main/res/values-uk-rUA/strings.xml | 2 +- localization/src/main/res/values-zh-rCN/strings.xml | 2 +- localization/src/main/res/values-zh-rTW/strings.xml | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/localization/src/main/res/values-be-rBY/strings.xml b/localization/src/main/res/values-be-rBY/strings.xml index e21aa3e38d..3197e3664f 100644 --- a/localization/src/main/res/values-be-rBY/strings.xml +++ b/localization/src/main/res/values-be-rBY/strings.xml @@ -1235,7 +1235,7 @@ Змяненне правоў доступу забаронена Дасягнуты ліміт абагульвання Deeplink на ваш аб\'ект - Гэта deeplink не працуе. Аб\'ект можа быць выдалены або могуць узнікнуць праблемы з доступам да прасторы. + Object is not available. Ask the owner to share it. Сумесная праца ў прасторы 1. Націсніце віджэт \"Прастора\", каб атрымаць доступ да налад 2. Адкрыйце раздзел \"Абагуліць\" diff --git a/localization/src/main/res/values-de-rDE/strings.xml b/localization/src/main/res/values-de-rDE/strings.xml index 98380ac6fc..01e7323efb 100644 --- a/localization/src/main/res/values-de-rDE/strings.xml +++ b/localization/src/main/res/values-de-rDE/strings.xml @@ -1227,7 +1227,7 @@ Ändern von Berechtigungen ist nicht erlaubt Freigabelimit erreicht Deeplink zu deinem Objekt - Dieser Deeplink funktioniert nicht. Das Objekt könnte gelöscht worden sein oder es gibt Probleme mit dem Zugriff auf den Space. + Objekt ist nicht verfügbar. Bitte den Eigentümer, es zu teilen. Zusammenarbeiten in Spaces 1. Tippe auf das Widget des aktiven Space, um die Einstellungen für den Space anzupassen 2. Teilen-Bereich öffnen diff --git a/localization/src/main/res/values-es-rES/strings.xml b/localization/src/main/res/values-es-rES/strings.xml index e3ca57ea47..4a8712eaae 100644 --- a/localization/src/main/res/values-es-rES/strings.xml +++ b/localization/src/main/res/values-es-rES/strings.xml @@ -1227,7 +1227,7 @@ No está permitido cambiar permisos No se puede compartir más Enlace directo al objeto - Este enlace no funciona. Puede que se haya borrado el objeto o que existan problemas con el acceso al espacio. + Ese objeto no está disponible. Pide a su propietario que lo comparta. Colabora en espacios 1. Toca el widget del espacio para acceder a los ajustes. 2. Abre la sección Compartir. diff --git a/localization/src/main/res/values-fr-rFR/strings.xml b/localization/src/main/res/values-fr-rFR/strings.xml index ecaa5ef6e7..6cc2a2d7a9 100644 --- a/localization/src/main/res/values-fr-rFR/strings.xml +++ b/localization/src/main/res/values-fr-rFR/strings.xml @@ -1227,7 +1227,7 @@ La modification des permissions n\'est pas autorisée Limite de partage atteinte Lien profond vers votre objet - Ce lien profond ne fonctionne pas. L\'objet peut être supprimé ou il peut y avoir des problèmes d\'accès à l\'espace. + L\'objet n\'est pas disponible. Demandez au propriétaire de le partager. Collaborez dans les espaces 1. Appuyez sur le widget de l’espace pour accéder aux paramètres 2. Ouvrez la section Partage diff --git a/localization/src/main/res/values-in-rID/strings.xml b/localization/src/main/res/values-in-rID/strings.xml index 7074fa0b56..d5783f030b 100644 --- a/localization/src/main/res/values-in-rID/strings.xml +++ b/localization/src/main/res/values-in-rID/strings.xml @@ -1223,7 +1223,7 @@ Mengubah izin tidak diperbolehkan Batas berbagi tercapai Deeplink to your object - This deeplink is not working. Object might be deleted or there might be issues with space access. + Objek tidak tersedia. Mintalah pemiliknya untuk membagikannya. Collaborate on spaces 1. Ketuk gawit Ruang untuk mengakses setelan 2. Buka bagian Bagikan diff --git a/localization/src/main/res/values-it-rIT/strings.xml b/localization/src/main/res/values-it-rIT/strings.xml index e349be6bf5..5bcc269cf5 100644 --- a/localization/src/main/res/values-it-rIT/strings.xml +++ b/localization/src/main/res/values-it-rIT/strings.xml @@ -1228,7 +1228,7 @@ Non è consentito modificare i permessi Limite di condivisione raggiunto Collegamento profondo al tuo oggetto - Questo collegamento profondo non funziona. L\'oggetto potrebbe essere eliminato o potrebbero esserci problemi con l\'accesso allo spazio. + L\'oggetto non è disponibile. Chiedere al proprietario di condividerlo. Collabora sugli spazi 1. Tocca il widget Spazio per accedere alle impostazioni 2. Apri sezione Condividi diff --git a/localization/src/main/res/values-nl-rNL/strings.xml b/localization/src/main/res/values-nl-rNL/strings.xml index 418288ce0e..4d2edd587d 100644 --- a/localization/src/main/res/values-nl-rNL/strings.xml +++ b/localization/src/main/res/values-nl-rNL/strings.xml @@ -1227,7 +1227,7 @@ Het wijzigen van machtigingen is niet toegestaan Deellimiet bereikt Deeplink naar je object - Deze deeplink werkt niet. Het object is mogelijk verwijderd of er zijn problemen met de toegang tot de ruimte. + Object is niet beschikbaar. Vraag de eigenaar om het te delen. Samenwerken aan ruimtes 1. Tik op de ruimtewidget om de instellingen te openen 2. Open het gedeelte Delen diff --git a/localization/src/main/res/values-no-rNO/strings.xml b/localization/src/main/res/values-no-rNO/strings.xml index 0dd1ce0aa5..00a9c248b1 100644 --- a/localization/src/main/res/values-no-rNO/strings.xml +++ b/localization/src/main/res/values-no-rNO/strings.xml @@ -1227,7 +1227,7 @@ Changing permissions is not allowed Sharing limit reached Deeplink to your object - This deeplink is not working. Object might be deleted or there might be issues with space access. + Object is not available. Ask the owner to share it. Collaborate on spaces 1. Tap the Space widget to access settings 2. Open Share section diff --git a/localization/src/main/res/values-pt-rBR/strings.xml b/localization/src/main/res/values-pt-rBR/strings.xml index aa48a3b67b..eb52888fe5 100644 --- a/localization/src/main/res/values-pt-rBR/strings.xml +++ b/localization/src/main/res/values-pt-rBR/strings.xml @@ -1227,7 +1227,7 @@ Não é permitido alterar as permissões Limite de compartilhamento atingido Desenvolver o seu objeto - Este deeplink não está funcionando. Objeto pode ser apagado ou pode haver problemas com o acesso ao espaço. + Object is not available. Ask the owner to share it. Colaborar em espaços Toque no espaço para acessar as configurações Abrir a seção de Compartilhamento diff --git a/localization/src/main/res/values-ru-rRU/strings.xml b/localization/src/main/res/values-ru-rRU/strings.xml index 065e7a8be8..e4e72ee522 100644 --- a/localization/src/main/res/values-ru-rRU/strings.xml +++ b/localization/src/main/res/values-ru-rRU/strings.xml @@ -1235,7 +1235,7 @@ Изменение прав доступа не разрешено Достигнут лимит на совместное использование Прямая ссылка на ваш объект - Эта прямая ссылка не работает. Объект может быть удален или могут быть проблемы с доступом к пространству. + Объект не доступен. Попросите владельца поделиться им. Сотрудничайте в пространствах 1. Нажмите на виджет Пространства, чтобы получить доступ к настройкам 2. Откройте раздел «Доступ» diff --git a/localization/src/main/res/values-tr-rTR/strings.xml b/localization/src/main/res/values-tr-rTR/strings.xml index 12441ac876..291cb08bae 100644 --- a/localization/src/main/res/values-tr-rTR/strings.xml +++ b/localization/src/main/res/values-tr-rTR/strings.xml @@ -1227,7 +1227,7 @@ İzinlerin değiştirilmesine izin verilmiyor Paylaşım sınırına ulaşıldı Nesnenize derin linkleme ekleyin - Bu derin linkleme çalışmıyor. Nesne silinmiş olabilir veya alan erişimiyle ilgili sorunlar olabilir. + Nesne mevcut değil. Nesnenin sahibinden paylaşmasını isteyin. Alanlar üzerinde işbirliği yapın 1. Ayarlara erişmek için Alan widget\'ına dokunun 2. Paylaşım bölümünü açın diff --git a/localization/src/main/res/values-uk-rUA/strings.xml b/localization/src/main/res/values-uk-rUA/strings.xml index da0571c4c0..6011a11631 100644 --- a/localization/src/main/res/values-uk-rUA/strings.xml +++ b/localization/src/main/res/values-uk-rUA/strings.xml @@ -1235,7 +1235,7 @@ Changing permissions is not allowed Sharing limit reached Deeplink to your object - This deeplink is not working. Object might be deleted or there might be issues with space access. + Object is not available. Ask the owner to share it. Collaborate on spaces 1. Tap the Space widget to access settings 2. Open Share section diff --git a/localization/src/main/res/values-zh-rCN/strings.xml b/localization/src/main/res/values-zh-rCN/strings.xml index feae6baa83..c569c82ebf 100644 --- a/localization/src/main/res/values-zh-rCN/strings.xml +++ b/localization/src/main/res/values-zh-rCN/strings.xml @@ -1223,7 +1223,7 @@ 不允许更改权限 已达到共享限制 深度链接到您的对象 - 这个深度链接不可用。对象可能被删除,或者可能存在空间访问问题。 + 对象不可用。请所有者分享一下。 在空间中协作 1. 点击空间小部件来访问设置 2. 打开分享栏 diff --git a/localization/src/main/res/values-zh-rTW/strings.xml b/localization/src/main/res/values-zh-rTW/strings.xml index e93a553e01..2298acc5d3 100644 --- a/localization/src/main/res/values-zh-rTW/strings.xml +++ b/localization/src/main/res/values-zh-rTW/strings.xml @@ -1223,7 +1223,7 @@ 不允許更改權限 已達到共享限制 多重連結到您的物件 - 此多重連結不可用。 物件可能被刪除或空間存取發生問題。 + Object is not available. Ask the owner to share it. 在空間進行協作 1. 點擊空間小工具來進行設置 2. 開放共享區塊 From ed92fa0eccc63de2363510f0cb51977b8cccc836 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Wed, 5 Feb 2025 22:50:49 +0100 Subject: [PATCH 10/78] DROID-3293 Notifications | Fix | Fix wording (#2068) --- .../main/java/com/anytypeio/anytype/app/Notifications.kt | 8 +++++--- localization/src/main/res/values-be-rBY/strings.xml | 2 -- localization/src/main/res/values-de-rDE/strings.xml | 2 -- localization/src/main/res/values-es-rES/strings.xml | 2 -- localization/src/main/res/values-fr-rFR/strings.xml | 2 -- localization/src/main/res/values-in-rID/strings.xml | 2 -- localization/src/main/res/values-it-rIT/strings.xml | 2 -- localization/src/main/res/values-nl-rNL/strings.xml | 2 -- localization/src/main/res/values-no-rNO/strings.xml | 2 -- localization/src/main/res/values-pt-rBR/strings.xml | 2 -- localization/src/main/res/values-ru-rRU/strings.xml | 2 -- localization/src/main/res/values-tr-rTR/strings.xml | 2 -- localization/src/main/res/values-uk-rUA/strings.xml | 2 -- localization/src/main/res/values-zh-rCN/strings.xml | 2 -- localization/src/main/res/values-zh-rTW/strings.xml | 2 -- localization/src/main/res/values/strings.xml | 4 ++-- 16 files changed, 7 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/com/anytypeio/anytype/app/Notifications.kt b/app/src/main/java/com/anytypeio/anytype/app/Notifications.kt index 0b4735a503..9950bf6c44 100644 --- a/app/src/main/java/com/anytypeio/anytype/app/Notifications.kt +++ b/app/src/main/java/com/anytypeio/anytype/app/Notifications.kt @@ -84,10 +84,12 @@ class AnytypeNotificationService @Inject constructor( } is NotificationPayload.ParticipantRemove -> { val body = context.resources.getString( - R.string.multiplayer_notification_member_removed_from_space + R.string.multiplayer_notification_member_removed_from_space, + payload.spaceName ) val title = context.resources.getString( - R.string.multiplayer_notification_member_removed_from_space_title + R.string.multiplayer_notification_member_removed_from_space_title, + payload.spaceName ) showBasicNotification( tag = notification.id, @@ -143,7 +145,7 @@ class AnytypeNotificationService @Inject constructor( R.string.multiplayer_notification_request_declined ) val body = context.resources.getString( - com.anytypeio.anytype.core_ui.R.string.multiplayer_notification_member_join_request_declined, + R.string.multiplayer_notification_member_join_request_declined, payload.spaceName.ifEmpty { placeholder } ) showBasicNotification( diff --git a/localization/src/main/res/values-be-rBY/strings.xml b/localization/src/main/res/values-be-rBY/strings.xml index 3197e3664f..17daf44258 100644 --- a/localization/src/main/res/values-be-rBY/strings.xml +++ b/localization/src/main/res/values-be-rBY/strings.xml @@ -1223,8 +1223,6 @@ Запыт ухвалены Ваш запыт на далучэнне да прасторы %1$s быў ухвалены з правамі доступу толькі для чытання. Прастора будзе даступна на вашай прыладзе ў бліжэйшы час. Ваш запыт на далучэнне да прасторы %1$s быў ухвалены з правамі доступу да рэдагавання. Прастора будзе даступна на вашай прыладзе ў бліжэйшы час. - The space is no longer accessible. - Вы былі выдалены з прасторы, або прастора была выдалена ўладальнікам. Дазволы зменены Вашы правы доступу былі зменены толькі на доступ для чытання ў прасторы %1$s. Вашы правы доступу былі зменены на рэдагаванне ў прасторы %1$s. diff --git a/localization/src/main/res/values-de-rDE/strings.xml b/localization/src/main/res/values-de-rDE/strings.xml index 01e7323efb..3786a34886 100644 --- a/localization/src/main/res/values-de-rDE/strings.xml +++ b/localization/src/main/res/values-de-rDE/strings.xml @@ -1215,8 +1215,6 @@ Anfrage genehmigt Deine Anfrage, dem Space %1$s beizutreten, wurde mit Nur-Lese-Rechten genehmigt. Der Bereich wird in Kürze auf deinem Gerät verfügbar sein. Deine Anfrage, dem Space %1$s beizutreten, wurde mit Bearbeitungszugriffsrechten genehmigt. Der Space wird in Kürze auf deinem Gerät verfügbar sein. - The space is no longer accessible. - Du wurdest aus dem Space entfernt oder der Space wurde vom Eigentümer/von der Eigentümerin gelöscht. Berechtigungen geändert Deine Zugriffsrechte wurden im Space %1$s auf „Nur Lesen“ geändert. Deine Zugriffsrechte wurden im Space %1$s auf „Bearbeiten“ geändert. diff --git a/localization/src/main/res/values-es-rES/strings.xml b/localization/src/main/res/values-es-rES/strings.xml index 4a8712eaae..fea4264c6a 100644 --- a/localization/src/main/res/values-es-rES/strings.xml +++ b/localization/src/main/res/values-es-rES/strings.xml @@ -1215,8 +1215,6 @@ Solicitud aprobada Tu solicitud para acceder al espacio %1$s se ha aprobado con derechos de solo lectura. El espacio estará pronto disponible en tu dispositivo. Tu solicitud de acceso al espacio %1$s se ha aprobado con derechos de edición. El espacio estará pronto disponible en tu dispositivo. - El espacio ya no es accesible. - Te han eliminado de ese espacio o el propietario ha eliminado ese espacio. Permisos cambiados Tus derechos de acceso al espacio %1$s ahora son de solo lectura. Tus derechos de acceso al espacio %1$s ahora son de edición. diff --git a/localization/src/main/res/values-fr-rFR/strings.xml b/localization/src/main/res/values-fr-rFR/strings.xml index 6cc2a2d7a9..28d2837184 100644 --- a/localization/src/main/res/values-fr-rFR/strings.xml +++ b/localization/src/main/res/values-fr-rFR/strings.xml @@ -1215,8 +1215,6 @@ Demande approuvée Votre demande de rejoindre l\'espace %1$s a été approuvée avec des droits d\'accès en lecture seule. L\'espace sera bientôt disponible sur votre appareil. Votre demande de rejoindre l\'espace %1$s a été approuvée avec les droits d\'accès en modification. L\'espace sera bientôt disponible sur votre appareil. - L\'espace n\'est plus accessible. - Vous avez été retiré de l\'espace \'\' , ou l\'espace a été supprimé par le propriétaire. Permissions modifiées Vos droits d\'accès ont été changés en lecture seule dans l\'espace %1$s. Vos droits d\'accès ont été modifiés pour éditer dans l\'espace %1$s. diff --git a/localization/src/main/res/values-in-rID/strings.xml b/localization/src/main/res/values-in-rID/strings.xml index d5783f030b..e15ae03de4 100644 --- a/localization/src/main/res/values-in-rID/strings.xml +++ b/localization/src/main/res/values-in-rID/strings.xml @@ -1211,8 +1211,6 @@ Permintaan disetujui Permintaanmu bergabug ke ruang %1$s telah disetujui dengan hak akses pembaca. Ruang ini akan tersedia di perangkatmu sebentar lagi. Permintaanmu bergabung ke ruang %1$s telah disetujui dengan hak akses penyunting. Ruang ini akan tersedia di perangkatmu sebentar lagi. - The space is no longer accessible. - Kamu telah dikeluarkan dari ruang ini, atau ruang tersebut telah dihapus oleh pemiliknya. Izin diubah Hak aksesmu di ruang %1$s telah diganti menjadi pembaca. Hak aksesmu di ruang %1$s telah diganti menjadi penyunting. diff --git a/localization/src/main/res/values-it-rIT/strings.xml b/localization/src/main/res/values-it-rIT/strings.xml index 5bcc269cf5..e93fb1d4b2 100644 --- a/localization/src/main/res/values-it-rIT/strings.xml +++ b/localization/src/main/res/values-it-rIT/strings.xml @@ -1216,8 +1216,6 @@ Richiesta approvata La tua richiesta di unirti allo spazio %1$s è stata approvata con diritti di accesso in sola lettura. Lo spazio sarà disponibile sul tuo dispositivo a breve. La tua richiesta di unirti allo spazio %1$s è stata approvata con diritti di accesso per la modifica. Lo spazio sarà disponibile sul tuo dispositivo a breve. - Lo spazio non è più accessibile. - Sei stato rimosso dallo spazio, oppure lo spazio è stato eliminato dal proprietario. Autorizzazioni cambiate I tuoi diritti di accesso sono stati impostati in sola lettura nello spazio %1$s. I tuoi diritti di accesso sono stati impostati per la modifica nello spazio %1$s. diff --git a/localization/src/main/res/values-nl-rNL/strings.xml b/localization/src/main/res/values-nl-rNL/strings.xml index 4d2edd587d..5d6a44e037 100644 --- a/localization/src/main/res/values-nl-rNL/strings.xml +++ b/localization/src/main/res/values-nl-rNL/strings.xml @@ -1215,8 +1215,6 @@ Verzoek goedgekeurd Je verzoek om deel te nemen aan de ruimte %1$s is goedgekeurd met alleen-lezen toegangsrechten. De ruimte zal binnenkort beschikbaar zijn op je apparaat. Je verzoek om deel te nemen aan de ruimte %1$s is goedgekeurd met bewerktoegangsrechten. De ruimte zal binnenkort beschikbaar zijn op je apparaat. - The space is no longer accessible. - Je bent verwijderd uit de ruimte, of de ruimte is verwijderd door de eigenaar. Machtigingen gewijzigd Je toegangsrechten zijn gewijzigd naar alleen-lezen in de ruimte %1$s. Je toegangsrechten zijn gewijzigd naar bewerken in de ruimte %1$s. diff --git a/localization/src/main/res/values-no-rNO/strings.xml b/localization/src/main/res/values-no-rNO/strings.xml index 00a9c248b1..b1af45ca39 100644 --- a/localization/src/main/res/values-no-rNO/strings.xml +++ b/localization/src/main/res/values-no-rNO/strings.xml @@ -1215,8 +1215,6 @@ Request approved Your request to join the %1$s space has been approved with read-only access rights. The space will be available on your device soon. Your request to join the %1$s space has been approved with edit access rights. The space will be available on your device soon. - The space is no longer accessible. - You have been removed from the space, or the space was deleted by the owner. Permissions changed Your access rights were changed to read-only in the %1$s space. Your access rights were changed to edit in the %1$s space. diff --git a/localization/src/main/res/values-pt-rBR/strings.xml b/localization/src/main/res/values-pt-rBR/strings.xml index eb52888fe5..153157837a 100644 --- a/localization/src/main/res/values-pt-rBR/strings.xml +++ b/localization/src/main/res/values-pt-rBR/strings.xml @@ -1215,8 +1215,6 @@ Solicitação aprovada Seu pedido para participar do espaço %1$s foi aprovado com direitos de acesso somente, leitura. O espaço estará disponível no seu dispositivo em breve. Seu pedido para ingressar no espaço %1$s foi aprovado com direitos de acesso para edição. O espaço estará disponível no seu dispositivo em breve. - The space is no longer accessible. - Você foi removido do espaço ou o espaço foi excluído pelo proprietário. Permissões alteradas Seus direitos de acesso foram alterados para somente leitura no espaço %1$s. Seus direitos de acesso foram alterados para editar no espaço %1$s. diff --git a/localization/src/main/res/values-ru-rRU/strings.xml b/localization/src/main/res/values-ru-rRU/strings.xml index e4e72ee522..e804e2981f 100644 --- a/localization/src/main/res/values-ru-rRU/strings.xml +++ b/localization/src/main/res/values-ru-rRU/strings.xml @@ -1223,8 +1223,6 @@ Запрос одобрен Ваш запрос на присоединение к пространству %1$s был одобрен с правами доступа только для чтения. Пространство будет доступно на вашем устройстве в ближайшее время. Ваш запрос на присоединение к пространству %1$s был одобрен с правами на редактирование. Пространство будет доступно в ближайшее время на устройстве. - The space is no longer accessible. - Вы были удалены из пространства, или оно было удалено владельцем. Разрешения изменены Ваши права доступа были изменены на только для чтения в пространстве %1$s. Права доступа были изменены на редактирование в пространстве %1$s. diff --git a/localization/src/main/res/values-tr-rTR/strings.xml b/localization/src/main/res/values-tr-rTR/strings.xml index 291cb08bae..3e746a8f14 100644 --- a/localization/src/main/res/values-tr-rTR/strings.xml +++ b/localization/src/main/res/values-tr-rTR/strings.xml @@ -1215,8 +1215,6 @@ Talep kabul edildi %1$s alanına katılma isteğiniz salt okunur erişim haklarıyla onaylandı. Alan yakında cihazınızda kullanılabilir olacak. %1$s alanına katılma isteğiniz düzenleme erişim haklarıyla onaylandı. Alan yakında cihazınızda kullanılabilir olacak. - Alan artık erişilebilir değil. - Alandan çıkarıldınız veya alan, sahibi tarafından silindi. İzinler değiştirildi %1$s alanındaki erişim haklarınız salt okunur olarak değiştirildi. %1$s alanındaki erişim haklarınız düzenleyici olarak değiştirildi. diff --git a/localization/src/main/res/values-uk-rUA/strings.xml b/localization/src/main/res/values-uk-rUA/strings.xml index 6011a11631..38554f8a81 100644 --- a/localization/src/main/res/values-uk-rUA/strings.xml +++ b/localization/src/main/res/values-uk-rUA/strings.xml @@ -1223,8 +1223,6 @@ Request approved Your request to join the %1$s space has been approved with read-only access rights. The space will be available on your device soon. Your request to join the %1$s space has been approved with edit access rights. The space will be available on your device soon. - The space is no longer accessible. - You have been removed from the space, or the space was deleted by the owner. Permissions changed Your access rights were changed to read-only in the %1$s space. Your access rights were changed to edit in the %1$s space. diff --git a/localization/src/main/res/values-zh-rCN/strings.xml b/localization/src/main/res/values-zh-rCN/strings.xml index c569c82ebf..7abba9e9ad 100644 --- a/localization/src/main/res/values-zh-rCN/strings.xml +++ b/localization/src/main/res/values-zh-rCN/strings.xml @@ -1211,8 +1211,6 @@ 请求已批准 您加入 %1$s 空间的请求已获批准并获得了只读访问权限,该空间很快就会在您的设备中显示。 您加入 %1$s 空间的请求已获批准并获得了编辑访问权限,该空间很快就会在您的设备中显示。 - The space is no longer accessible. - 您已经被从此空间里移除,或者此空间已被所有者删除。 权限更改成功 您在 %1$s 空间中的访问权限已更改为只读。 您在 %1$s 空间中的访问权限已更改为可编辑。 diff --git a/localization/src/main/res/values-zh-rTW/strings.xml b/localization/src/main/res/values-zh-rTW/strings.xml index 2298acc5d3..c9a0f21438 100644 --- a/localization/src/main/res/values-zh-rTW/strings.xml +++ b/localization/src/main/res/values-zh-rTW/strings.xml @@ -1211,8 +1211,6 @@ 請求已批准 您請求加入的 %1$s 空間已獲得批准,並具有唯讀存取權限。 該空間很快就可以在您的裝置上使用。 您請求加入的 %1$s 空間已獲得批准,並具有編輯存取權限。 該空間很快就可以在您的裝置上使用。 - The space is no longer accessible. - 您已從該空間中移除,或該空間已被擁有者刪除。 權限已變更 您在 %1$s 空間的存取權限已變更為唯讀存取。 您在 %1$s 空間的存取權限已變更為編輯存取。 diff --git a/localization/src/main/res/values/strings.xml b/localization/src/main/res/values/strings.xml index 978f803acb..7c6761eec4 100644 --- a/localization/src/main/res/values/strings.xml +++ b/localization/src/main/res/values/strings.xml @@ -1408,8 +1408,8 @@ Request approved Your request to join the %1$s space has been approved with read-only access rights. The space will be available on your device soon. Your request to join the %1$s space has been approved with edit access rights. The space will be available on your device soon. - The space is no longer accessible. - You have been removed from the space, or the space was deleted by the owner. + The space \"%1$s\" is no longer accessible. + You have been removed from the space \"%1$s\", or the space was deleted by the owner. Permissions changed Your access rights were changed to read-only in the %1$s space. Your access rights were changed to edit in the %1$s space. From fee84fbdd91f3f6a29cc095435341a18f8ebc8c2 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Wed, 5 Feb 2025 23:03:54 +0100 Subject: [PATCH 11/78] DROID-3181 Vault | Fix | Space with joining should not be considered as being in loading state (#2069) --- .../java/com/anytypeio/anytype/core_models/ObjectWrapper.kt | 1 + .../com/anytypeio/anytype/presentation/vault/VaultViewModel.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/ObjectWrapper.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/ObjectWrapper.kt index 0ff50435d0..135307c067 100644 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/ObjectWrapper.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/ObjectWrapper.kt @@ -292,6 +292,7 @@ sealed class ObjectWrapper { return spaceLocalStatus == SpaceStatus.LOADING && spaceAccountStatus != SpaceStatus.SPACE_REMOVING && spaceAccountStatus != SpaceStatus.SPACE_DELETED + && spaceAccountStatus != SpaceStatus.SPACE_JOINING } val isActive: Boolean diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/vault/VaultViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/vault/VaultViewModel.kt index bc4c532917..c83eafc316 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/vault/VaultViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/vault/VaultViewModel.kt @@ -103,7 +103,7 @@ class VaultViewModel( } .combine(observeVaultSettings.flow()) { spaces, settings -> spaces - .filter { space -> space.isActive || space.isLoading } + .filter { space -> (space.isActive || space.isLoading) } .distinctBy { it.id } .map { space -> VaultSpaceView( From 32bdd0b2f1dd902f8761484a4aabfa1408efb4b8 Mon Sep 17 00:00:00 2001 From: Konstantin Ivanov <54908981+konstantiniiv@users.noreply.github.com> Date: Wed, 5 Feb 2025 23:18:23 +0100 Subject: [PATCH 12/78] DROID-3072 Tech | Photo picker, integration, part 2 (#2070) --- app/src/main/AndroidManifest.xml | 3 + .../device/DeviceCoverCollectionProvider.kt | 26 --------- .../anytype/device/PhotoPickerExt.kt | 56 +++++++++++++++++++ .../anytype/other/MediaPermissionHelper.kt | 3 + .../anytype/ui/editor/EditorFragment.kt | 27 +++++---- .../cover/SelectCoverGalleryFragment.kt | 14 ++--- .../editor/modals/IconPickerFragmentBase.kt | 14 ++--- .../ui/settings/ProfileSettingsFragment.kt | 14 ++--- .../anytype/core_utils/ext/FilePickerUtils.kt | 3 +- 9 files changed, 98 insertions(+), 62 deletions(-) delete mode 100644 app/src/main/java/com/anytypeio/anytype/device/DeviceCoverCollectionProvider.kt create mode 100644 app/src/main/java/com/anytypeio/anytype/device/PhotoPickerExt.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 41d84d1ad8..0eebdf755f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,6 +11,9 @@ + + + diff --git a/app/src/main/java/com/anytypeio/anytype/device/DeviceCoverCollectionProvider.kt b/app/src/main/java/com/anytypeio/anytype/device/DeviceCoverCollectionProvider.kt deleted file mode 100644 index e8798f32df..0000000000 --- a/app/src/main/java/com/anytypeio/anytype/device/DeviceCoverCollectionProvider.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.anytypeio.anytype.device - -import android.content.Context -import com.anytypeio.anytype.core_utils.ext.getJsonDataFromAsset -import com.anytypeio.anytype.domain.cover.CoverCollectionProvider -import com.anytypeio.anytype.domain.cover.CoverImage -import com.google.gson.Gson - -class DeviceCoverCollectionProvider( - private val context: Context, - private val gson: Gson -) : CoverCollectionProvider { - - override fun provide(): List { - val json = context.getJsonDataFromAsset(COVER_FILE) - return if (json != null) { - gson.fromJson(json, Array::class.java).toList() - } else { - emptyList() - } - } - - companion object { - const val COVER_FILE = "covers.json" - } -} \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/device/PhotoPickerExt.kt b/app/src/main/java/com/anytypeio/anytype/device/PhotoPickerExt.kt new file mode 100644 index 0000000000..ad314aa369 --- /dev/null +++ b/app/src/main/java/com/anytypeio/anytype/device/PhotoPickerExt.kt @@ -0,0 +1,56 @@ +package com.anytypeio.anytype.device + +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia +import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType +import androidx.fragment.app.Fragment +import com.anytypeio.anytype.core_utils.ext.Mimetype +import com.anytypeio.anytype.other.MediaPermissionHelper +import com.anytypeio.anytype.ui.editor.PickerDelegate +import timber.log.Timber + +/** + * Launches a media picker (for images or videos) in a [Fragment]. + * + * This function checks if the device supports the photo picker. If available, + * it launches the [pickMedia] launcher with a request for the specified [mediaType]. + * If the picker is not available, it falls back to opening a file picker using [pickerDelegate] + * with the provided [fallbackMimeType]. + * + * @param pickMedia The [ActivityResultLauncher] used to launch the media picker. + * @param pickerDelegate A delegate to open a fallback file picker when the media picker is unavailable. + * @param mediaType The type of media to pick (e.g. [PickVisualMedia.ImageOnly] or [PickVisualMedia.VideoOnly]). + * @param fallbackMimeType The MIME type to use with the fallback file picker (e.g. [Mimetype.MIME_IMAGE_ALL] or [Mimetype.MIME_VIDEO_ALL]). + */ +fun Fragment.launchMediaPicker( + pickMedia: ActivityResultLauncher, + pickerDelegate: PickerDelegate, + mediaType: VisualMediaType, + fallbackMimeType: Mimetype +) { + context?.let { ctx -> + if (PickVisualMedia.isPhotoPickerAvailable(ctx)) { + pickMedia.launch(PickVisualMediaRequest(mediaType)) + } else { + Timber.w("$mediaType picker is not available, using pickerDelegate") + pickerDelegate.openFilePicker(fallbackMimeType, null) + } + } +} + +fun Fragment.launchMediaPicker( + pickMedia: ActivityResultLauncher, + permissionHelper: MediaPermissionHelper, + mediaType: VisualMediaType, + fallbackMimeType: Mimetype +) { + context?.let { ctx -> + if (PickVisualMedia.isPhotoPickerAvailable(ctx)) { + pickMedia.launch(PickVisualMediaRequest(mediaType)) + } else { + Timber.w("$mediaType picker is not available, using pickerDelegate") + permissionHelper.openFilePicker(fallbackMimeType, null) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/other/MediaPermissionHelper.kt b/app/src/main/java/com/anytypeio/anytype/other/MediaPermissionHelper.kt index 8119086069..761cabd4e6 100644 --- a/app/src/main/java/com/anytypeio/anytype/other/MediaPermissionHelper.kt +++ b/app/src/main/java/com/anytypeio/anytype/other/MediaPermissionHelper.kt @@ -44,6 +44,7 @@ class MediaPermissionHelper( } fun openFilePicker(mimeType: Mimetype, requestCode: Int?) { + Timber.d("openFilePicker, mimeType:$mimeType, requestCode:$requestCode") if (isRequestInProgress) { Timber.w("Permission request already in progress") return @@ -62,10 +63,12 @@ class MediaPermissionHelper( val hasPermission = mimeType.hasPermission(context) if (hasPermission) { + Timber.d("Permission already granted") onPermissionSuccess(mimeType, requestCode) isRequestInProgress = false } else { val permissions = mimeType.getPermissionToRequestByMime() + Timber.d("Requesting permissions: $permissions") if (permissions.isNotEmpty()) { permissionReadStorage.launch(permissions) } else { diff --git a/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt index 998ce32882..7612faa832 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt @@ -121,6 +121,7 @@ import com.anytypeio.anytype.core_utils.ext.toast import com.anytypeio.anytype.core_utils.ext.visible import com.anytypeio.anytype.core_utils.ui.showActionableSnackBar import com.anytypeio.anytype.databinding.FragmentEditorBinding +import com.anytypeio.anytype.device.launchMediaPicker import com.anytypeio.anytype.di.common.componentManager import com.anytypeio.anytype.di.feature.DefaultComponentParam import com.anytypeio.anytype.ext.extractMarks @@ -956,22 +957,20 @@ open class EditorFragment : NavigationFragment(R.layout.f ).showChildFragment() } is Command.OpenPhotoPicker -> { - try { - pickMedia.launch(PickVisualMediaRequest(PickVisualMedia.ImageOnly)) - } catch (e: Exception) { - Timber.w(e, "Error while opening photo picker") - toast("Error while opening photo picker") - pickerDelegate.openFilePicker(Mimetype.MIME_IMAGE_ALL, null) - } + launchMediaPicker( + pickMedia = pickMedia, + pickerDelegate = pickerDelegate, + mediaType = PickVisualMedia.ImageOnly, + fallbackMimeType = Mimetype.MIME_IMAGE_ALL + ) } is Command.OpenVideoPicker -> { - try { - pickMedia.launch(PickVisualMediaRequest(PickVisualMedia.VideoOnly)) - } catch (e: Exception) { - Timber.e(e, "Error while opening video picker") - toast("Error while opening video picker") - pickerDelegate.openFilePicker(Mimetype.MIME_VIDEO_ALL, null) - } + launchMediaPicker( + pickMedia = pickMedia, + pickerDelegate = pickerDelegate, + mediaType = PickVisualMedia.VideoOnly, + fallbackMimeType = Mimetype.MIME_VIDEO_ALL + ) } is Command.OpenFilePicker -> { pickerDelegate.openFilePicker(Mimetype.MIME_FILE_ALL, null) diff --git a/app/src/main/java/com/anytypeio/anytype/ui/editor/cover/SelectCoverGalleryFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/editor/cover/SelectCoverGalleryFragment.kt index 4e12c7cd32..4ae6ed4c5c 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/editor/cover/SelectCoverGalleryFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/editor/cover/SelectCoverGalleryFragment.kt @@ -28,6 +28,7 @@ import com.anytypeio.anytype.core_utils.ext.subscribe import com.anytypeio.anytype.core_utils.ext.toast import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetFragment import com.anytypeio.anytype.databinding.FragmentDocCoverGalleryBinding +import com.anytypeio.anytype.device.launchMediaPicker import com.anytypeio.anytype.di.common.componentManager import com.anytypeio.anytype.di.feature.DefaultComponentParam import com.anytypeio.anytype.other.MediaPermissionHelper @@ -104,13 +105,12 @@ abstract class SelectCoverGalleryFragment : binding.btnUpload.clicks() .onEach { - try { - pickMedia.launch(PickVisualMediaRequest(PickVisualMedia.ImageOnly)) - } catch (e: Exception) { - Timber.w(e, "Error while opening photo picker") - toast("Error while opening photo picker") - permissionHelper.openFilePicker(Mimetype.MIME_IMAGE_ALL, null) - } + launchMediaPicker( + pickMedia = pickMedia, + permissionHelper = permissionHelper, + mediaType = PickVisualMedia.ImageOnly, + fallbackMimeType = Mimetype.MIME_IMAGE_ALL + ) } .launchIn(lifecycleScope) diff --git a/app/src/main/java/com/anytypeio/anytype/ui/editor/modals/IconPickerFragmentBase.kt b/app/src/main/java/com/anytypeio/anytype/ui/editor/modals/IconPickerFragmentBase.kt index b9b99025b1..44cd15ee19 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/editor/modals/IconPickerFragmentBase.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/editor/modals/IconPickerFragmentBase.kt @@ -23,6 +23,7 @@ import com.anytypeio.anytype.core_utils.ext.toast import com.anytypeio.anytype.core_utils.ext.visible import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetTextInputFragment import com.anytypeio.anytype.databinding.FragmentPageIconPickerBinding +import com.anytypeio.anytype.device.launchMediaPicker import com.anytypeio.anytype.library_page_icon_picker_widget.ui.DocumentEmojiIconPickerAdapter import com.anytypeio.anytype.other.MediaPermissionHelper import com.anytypeio.anytype.presentation.editor.picker.EmojiPickerView.Companion.HOLDER_EMOJI_CATEGORY_HEADER @@ -85,13 +86,12 @@ abstract class IconPickerFragmentBase : btnRemoveIcon.setOnClickListener { vm.onRemoveClicked(target) } tvTabRandom.setOnClickListener { vm.onRandomEmoji(target) } tvTabUpload.setOnClickListener { - try { - pickMedia.launch(PickVisualMediaRequest(PickVisualMedia.ImageOnly)) - } catch (e: Exception) { - Timber.w(e, "Error while opening photo picker") - toast("Error while opening photo picker") - permissionHelper.openFilePicker(Mimetype.MIME_IMAGE_ALL, 0) - } + launchMediaPicker( + pickMedia = pickMedia, + permissionHelper = permissionHelper, + mediaType = PickVisualMedia.ImageOnly, + fallbackMimeType = Mimetype.MIME_IMAGE_ALL + ) } } skipCollapsed() diff --git a/app/src/main/java/com/anytypeio/anytype/ui/settings/ProfileSettingsFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/settings/ProfileSettingsFragment.kt index c8cd219d89..99867b116c 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/settings/ProfileSettingsFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/settings/ProfileSettingsFragment.kt @@ -28,6 +28,7 @@ import com.anytypeio.anytype.core_utils.ext.subscribe import com.anytypeio.anytype.core_utils.ext.toast import com.anytypeio.anytype.core_utils.tools.FeatureToggles import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment +import com.anytypeio.anytype.device.launchMediaPicker import com.anytypeio.anytype.di.common.componentManager import com.anytypeio.anytype.other.MediaPermissionHelper import com.anytypeio.anytype.ui.profile.KeychainPhraseDialog @@ -140,13 +141,12 @@ class ProfileSettingsFragment : BaseBottomSheetComposeFragment() { } private fun proceedWithIconClick() { - try { - pickMedia.launch(PickVisualMediaRequest(PickVisualMedia.ImageOnly)) - } catch (e: Exception) { - Timber.w(e, "Error while opening photo picker") - toast("Error while opening photo picker") - permissionHelper.openFilePicker(Mimetype.MIME_IMAGE_ALL, null) - } + launchMediaPicker( + pickMedia = pickMedia, + permissionHelper = permissionHelper, + mediaType = PickVisualMedia.ImageOnly, + fallbackMimeType = Mimetype.MIME_IMAGE_ALL + ) } private fun openGallery() { diff --git a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/FilePickerUtils.kt b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/FilePickerUtils.kt index ea236d1af8..4a9ecfaccd 100644 --- a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/FilePickerUtils.kt +++ b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/FilePickerUtils.kt @@ -10,6 +10,7 @@ import timber.log.Timber object FilePickerUtils { fun Mimetype.hasPermission(context: Context): Boolean { + Timber.d("hasPermission check, mimetype:$this") return when (this) { Mimetype.MIME_VIDEO_ALL -> context.isPermissionGranted(getPermissionToRequestForVideos()) Mimetype.MIME_IMAGE_ALL -> context.isPermissionGranted(getPermissionToRequestForImages()) @@ -26,7 +27,7 @@ object FilePickerUtils { private fun Context.isPermissionGranted(permission: Array): Boolean { val hasPermission = permission.isNotEmpty() && ContextCompat.checkSelfPermission(this, permission[0]) == PackageManager.PERMISSION_GRANTED - Timber.d("hasExternalStoragePermission, hasPermission:$hasPermission for permission:$permission") + Timber.d("hasExternalStoragePermission, hasPermission:$hasPermission for permission:${permission.getOrNull(0)}") return hasPermission } From d3c3bb60641c91d95724e72c1004adf20f4aa881 Mon Sep 17 00:00:00 2001 From: Konstantin Ivanov <54908981+konstantiniiv@users.noreply.github.com> Date: Wed, 5 Feb 2025 23:29:52 +0100 Subject: [PATCH 13/78] DROID-3292 Multiplayer | Fix | Update buttons text, 2 attempt (#2071) --- localization/src/main/res/values-ru-rRU/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/localization/src/main/res/values-ru-rRU/strings.xml b/localization/src/main/res/values-ru-rRU/strings.xml index e804e2981f..7e831e36c7 100644 --- a/localization/src/main/res/values-ru-rRU/strings.xml +++ b/localization/src/main/res/values-ru-rRU/strings.xml @@ -1170,9 +1170,9 @@ ✦ Обновите, чтобы добавить больше участников ✦ Обновите, чтобы добавить больше пространств Участники - Оставить запрос + Запрос на выход Запрос на присоединение - Утверждено + Подтвердить Посмотреть запрос Владелец Can edit From 1c1396fe375b04888a53345beeff473795f57a2a Mon Sep 17 00:00:00 2001 From: "Any Association [bot]" <139948520+any-association@users.noreply.github.com> Date: Thu, 6 Feb 2025 16:48:08 +0100 Subject: [PATCH 14/78] l10n | Enhancement (#2072) Co-authored-by: Evgenii Kozlov --- .../src/main/res/values-be-rBY/strings.xml | 2 ++ .../src/main/res/values-de-rDE/strings.xml | 2 ++ .../src/main/res/values-es-rES/strings.xml | 2 ++ .../src/main/res/values-fr-rFR/strings.xml | 2 ++ .../src/main/res/values-in-rID/strings.xml | 2 ++ .../src/main/res/values-it-rIT/strings.xml | 2 ++ .../src/main/res/values-nl-rNL/strings.xml | 2 ++ .../src/main/res/values-no-rNO/strings.xml | 2 ++ .../src/main/res/values-pt-rBR/strings.xml | 22 ++++++++++--------- .../src/main/res/values-ru-rRU/strings.xml | 2 ++ .../src/main/res/values-tr-rTR/strings.xml | 2 ++ .../src/main/res/values-uk-rUA/strings.xml | 2 ++ .../src/main/res/values-zh-rCN/strings.xml | 2 ++ .../src/main/res/values-zh-rTW/strings.xml | 2 ++ 14 files changed, 38 insertions(+), 10 deletions(-) diff --git a/localization/src/main/res/values-be-rBY/strings.xml b/localization/src/main/res/values-be-rBY/strings.xml index 17daf44258..24b3a65db5 100644 --- a/localization/src/main/res/values-be-rBY/strings.xml +++ b/localization/src/main/res/values-be-rBY/strings.xml @@ -1223,6 +1223,8 @@ Запыт ухвалены Ваш запыт на далучэнне да прасторы %1$s быў ухвалены з правамі доступу толькі для чытання. Прастора будзе даступна на вашай прыладзе ў бліжэйшы час. Ваш запыт на далучэнне да прасторы %1$s быў ухвалены з правамі доступу да рэдагавання. Прастора будзе даступна на вашай прыладзе ў бліжэйшы час. + The space \"%1$s\" is no longer accessible. + You have been removed from the space \"%1$s\", or the space was deleted by the owner. Дазволы зменены Вашы правы доступу былі зменены толькі на доступ для чытання ў прасторы %1$s. Вашы правы доступу былі зменены на рэдагаванне ў прасторы %1$s. diff --git a/localization/src/main/res/values-de-rDE/strings.xml b/localization/src/main/res/values-de-rDE/strings.xml index 3786a34886..3c65f007af 100644 --- a/localization/src/main/res/values-de-rDE/strings.xml +++ b/localization/src/main/res/values-de-rDE/strings.xml @@ -1215,6 +1215,8 @@ Anfrage genehmigt Deine Anfrage, dem Space %1$s beizutreten, wurde mit Nur-Lese-Rechten genehmigt. Der Bereich wird in Kürze auf deinem Gerät verfügbar sein. Deine Anfrage, dem Space %1$s beizutreten, wurde mit Bearbeitungszugriffsrechten genehmigt. Der Space wird in Kürze auf deinem Gerät verfügbar sein. + The space \"%1$s\" is no longer accessible. + You have been removed from the space \"%1$s\", or the space was deleted by the owner. Berechtigungen geändert Deine Zugriffsrechte wurden im Space %1$s auf „Nur Lesen“ geändert. Deine Zugriffsrechte wurden im Space %1$s auf „Bearbeiten“ geändert. diff --git a/localization/src/main/res/values-es-rES/strings.xml b/localization/src/main/res/values-es-rES/strings.xml index fea4264c6a..49c24590f3 100644 --- a/localization/src/main/res/values-es-rES/strings.xml +++ b/localization/src/main/res/values-es-rES/strings.xml @@ -1215,6 +1215,8 @@ Solicitud aprobada Tu solicitud para acceder al espacio %1$s se ha aprobado con derechos de solo lectura. El espacio estará pronto disponible en tu dispositivo. Tu solicitud de acceso al espacio %1$s se ha aprobado con derechos de edición. El espacio estará pronto disponible en tu dispositivo. + El espacio «%1$s» ya no está accesible. + Te han eliminado del espacio «%1$s» o el propietario ha eliminado ese espacio. Permisos cambiados Tus derechos de acceso al espacio %1$s ahora son de solo lectura. Tus derechos de acceso al espacio %1$s ahora son de edición. diff --git a/localization/src/main/res/values-fr-rFR/strings.xml b/localization/src/main/res/values-fr-rFR/strings.xml index 28d2837184..ac5742b4eb 100644 --- a/localization/src/main/res/values-fr-rFR/strings.xml +++ b/localization/src/main/res/values-fr-rFR/strings.xml @@ -1215,6 +1215,8 @@ Demande approuvée Votre demande de rejoindre l\'espace %1$s a été approuvée avec des droits d\'accès en lecture seule. L\'espace sera bientôt disponible sur votre appareil. Votre demande de rejoindre l\'espace %1$s a été approuvée avec les droits d\'accès en modification. L\'espace sera bientôt disponible sur votre appareil. + L\'espace \"%1$s\" n\'est plus accessible. + Vous avez été retiré de l\'espace \"%1$s\", ou l\'espace a été supprimé par le propriétaire. Permissions modifiées Vos droits d\'accès ont été changés en lecture seule dans l\'espace %1$s. Vos droits d\'accès ont été modifiés pour éditer dans l\'espace %1$s. diff --git a/localization/src/main/res/values-in-rID/strings.xml b/localization/src/main/res/values-in-rID/strings.xml index e15ae03de4..cc3e499339 100644 --- a/localization/src/main/res/values-in-rID/strings.xml +++ b/localization/src/main/res/values-in-rID/strings.xml @@ -1211,6 +1211,8 @@ Permintaan disetujui Permintaanmu bergabug ke ruang %1$s telah disetujui dengan hak akses pembaca. Ruang ini akan tersedia di perangkatmu sebentar lagi. Permintaanmu bergabung ke ruang %1$s telah disetujui dengan hak akses penyunting. Ruang ini akan tersedia di perangkatmu sebentar lagi. + The space \"%1$s\" is no longer accessible. + You have been removed from the space \"%1$s\", or the space was deleted by the owner. Izin diubah Hak aksesmu di ruang %1$s telah diganti menjadi pembaca. Hak aksesmu di ruang %1$s telah diganti menjadi penyunting. diff --git a/localization/src/main/res/values-it-rIT/strings.xml b/localization/src/main/res/values-it-rIT/strings.xml index e93fb1d4b2..9d4a23f86d 100644 --- a/localization/src/main/res/values-it-rIT/strings.xml +++ b/localization/src/main/res/values-it-rIT/strings.xml @@ -1216,6 +1216,8 @@ Richiesta approvata La tua richiesta di unirti allo spazio %1$s è stata approvata con diritti di accesso in sola lettura. Lo spazio sarà disponibile sul tuo dispositivo a breve. La tua richiesta di unirti allo spazio %1$s è stata approvata con diritti di accesso per la modifica. Lo spazio sarà disponibile sul tuo dispositivo a breve. + The space \"%1$s\" is no longer accessible. + You have been removed from the space \"%1$s\", or the space was deleted by the owner. Autorizzazioni cambiate I tuoi diritti di accesso sono stati impostati in sola lettura nello spazio %1$s. I tuoi diritti di accesso sono stati impostati per la modifica nello spazio %1$s. diff --git a/localization/src/main/res/values-nl-rNL/strings.xml b/localization/src/main/res/values-nl-rNL/strings.xml index 5d6a44e037..90d3ddac39 100644 --- a/localization/src/main/res/values-nl-rNL/strings.xml +++ b/localization/src/main/res/values-nl-rNL/strings.xml @@ -1215,6 +1215,8 @@ Verzoek goedgekeurd Je verzoek om deel te nemen aan de ruimte %1$s is goedgekeurd met alleen-lezen toegangsrechten. De ruimte zal binnenkort beschikbaar zijn op je apparaat. Je verzoek om deel te nemen aan de ruimte %1$s is goedgekeurd met bewerktoegangsrechten. De ruimte zal binnenkort beschikbaar zijn op je apparaat. + The space \"%1$s\" is no longer accessible. + You have been removed from the space \"%1$s\", or the space was deleted by the owner. Machtigingen gewijzigd Je toegangsrechten zijn gewijzigd naar alleen-lezen in de ruimte %1$s. Je toegangsrechten zijn gewijzigd naar bewerken in de ruimte %1$s. diff --git a/localization/src/main/res/values-no-rNO/strings.xml b/localization/src/main/res/values-no-rNO/strings.xml index b1af45ca39..25cf0af824 100644 --- a/localization/src/main/res/values-no-rNO/strings.xml +++ b/localization/src/main/res/values-no-rNO/strings.xml @@ -1215,6 +1215,8 @@ Request approved Your request to join the %1$s space has been approved with read-only access rights. The space will be available on your device soon. Your request to join the %1$s space has been approved with edit access rights. The space will be available on your device soon. + The space \"%1$s\" is no longer accessible. + You have been removed from the space \"%1$s\", or the space was deleted by the owner. Permissions changed Your access rights were changed to read-only in the %1$s space. Your access rights were changed to edit in the %1$s space. diff --git a/localization/src/main/res/values-pt-rBR/strings.xml b/localization/src/main/res/values-pt-rBR/strings.xml index 153157837a..b4631c6274 100644 --- a/localization/src/main/res/values-pt-rBR/strings.xml +++ b/localization/src/main/res/values-pt-rBR/strings.xml @@ -250,7 +250,7 @@ Vinculado a Remover link Criar novo objeto - Dates + Datas Objetos Criar objeto Pesquisar @@ -295,7 +295,7 @@ Inserir valor Relações Adicionar ordenação - Apply + Aplicar Adicionar um filtro Selecionar vários Número desconhecido de objetos @@ -470,7 +470,7 @@ Código aberto Recarregar conteúdo do objeto Abrir link - Open file + Abrir arquivo Copiar e-mail Copiar número de telefone Enviar email @@ -1215,6 +1215,8 @@ Solicitação aprovada Seu pedido para participar do espaço %1$s foi aprovado com direitos de acesso somente, leitura. O espaço estará disponível no seu dispositivo em breve. Seu pedido para ingressar no espaço %1$s foi aprovado com direitos de acesso para edição. O espaço estará disponível no seu dispositivo em breve. + The space \"%1$s\" is no longer accessible. + You have been removed from the space \"%1$s\", or the space was deleted by the owner. Permissões alteradas Seus direitos de acesso foram alterados para somente leitura no espaço %1$s. Seus direitos de acesso foram alterados para editar no espaço %1$s. @@ -1225,12 +1227,12 @@ Não é permitido alterar as permissões Limite de compartilhamento atingido Desenvolver o seu objeto - Object is not available. Ask the owner to share it. + O objeto não está disponível. Peça ao proprietário para compartilhá-lo. Colaborar em espaços Toque no espaço para acessar as configurações Abrir a seção de Compartilhamento Gerar um link de convite e compartilhá-lo - Edit Profile + Editar Perfil %1$d membro %1$d membros @@ -1507,12 +1509,12 @@ forneça detalhes específicos das suas necessidades aqui. Erro ao obter histórico de versões Erro ao obter membros Novo objeto - My Spaces - All Objects + Meus Espaços + Todos os Objetos Apenas desvinculados Objetos não vinculados que não tenham um link direto ou backlink com outros objetos no gráfico. Visualizar lixeira - Pages + Páginas Groups Mídias Marca página @@ -1528,7 +1530,7 @@ forneça detalhes específicos das suas necessidades aqui. Data Criada Date last used Nome - All Objects + Todos os Objetos Hoje Ontem Últimos 7 dias @@ -1537,7 +1539,7 @@ forneça detalhes específicos das suas necessidades aqui. You can now easily reorder your Spaces using drag and drop and navigate them more easily. By default, we blur the icon of your space as a background. If needed, you can change it in the space-settings. Welcome to the Vault - + It’s empty here. Create your first objects to get started. No results found. diff --git a/localization/src/main/res/values-ru-rRU/strings.xml b/localization/src/main/res/values-ru-rRU/strings.xml index 7e831e36c7..a2b63585c2 100644 --- a/localization/src/main/res/values-ru-rRU/strings.xml +++ b/localization/src/main/res/values-ru-rRU/strings.xml @@ -1223,6 +1223,8 @@ Запрос одобрен Ваш запрос на присоединение к пространству %1$s был одобрен с правами доступа только для чтения. Пространство будет доступно на вашем устройстве в ближайшее время. Ваш запрос на присоединение к пространству %1$s был одобрен с правами на редактирование. Пространство будет доступно в ближайшее время на устройстве. + The space \"%1$s\" is no longer accessible. + You have been removed from the space \"%1$s\", or the space was deleted by the owner. Разрешения изменены Ваши права доступа были изменены на только для чтения в пространстве %1$s. Права доступа были изменены на редактирование в пространстве %1$s. diff --git a/localization/src/main/res/values-tr-rTR/strings.xml b/localization/src/main/res/values-tr-rTR/strings.xml index 3e746a8f14..1f65946640 100644 --- a/localization/src/main/res/values-tr-rTR/strings.xml +++ b/localization/src/main/res/values-tr-rTR/strings.xml @@ -1215,6 +1215,8 @@ Talep kabul edildi %1$s alanına katılma isteğiniz salt okunur erişim haklarıyla onaylandı. Alan yakında cihazınızda kullanılabilir olacak. %1$s alanına katılma isteğiniz düzenleme erişim haklarıyla onaylandı. Alan yakında cihazınızda kullanılabilir olacak. + The space \"%1$s\" is no longer accessible. + You have been removed from the space \"%1$s\", or the space was deleted by the owner. İzinler değiştirildi %1$s alanındaki erişim haklarınız salt okunur olarak değiştirildi. %1$s alanındaki erişim haklarınız düzenleyici olarak değiştirildi. diff --git a/localization/src/main/res/values-uk-rUA/strings.xml b/localization/src/main/res/values-uk-rUA/strings.xml index 38554f8a81..7ab8e8e680 100644 --- a/localization/src/main/res/values-uk-rUA/strings.xml +++ b/localization/src/main/res/values-uk-rUA/strings.xml @@ -1223,6 +1223,8 @@ Request approved Your request to join the %1$s space has been approved with read-only access rights. The space will be available on your device soon. Your request to join the %1$s space has been approved with edit access rights. The space will be available on your device soon. + The space \"%1$s\" is no longer accessible. + You have been removed from the space \"%1$s\", or the space was deleted by the owner. Permissions changed Your access rights were changed to read-only in the %1$s space. Your access rights were changed to edit in the %1$s space. diff --git a/localization/src/main/res/values-zh-rCN/strings.xml b/localization/src/main/res/values-zh-rCN/strings.xml index 7abba9e9ad..cefb4b21f7 100644 --- a/localization/src/main/res/values-zh-rCN/strings.xml +++ b/localization/src/main/res/values-zh-rCN/strings.xml @@ -1211,6 +1211,8 @@ 请求已批准 您加入 %1$s 空间的请求已获批准并获得了只读访问权限,该空间很快就会在您的设备中显示。 您加入 %1$s 空间的请求已获批准并获得了编辑访问权限,该空间很快就会在您的设备中显示。 + The space \"%1$s\" is no longer accessible. + You have been removed from the space \"%1$s\", or the space was deleted by the owner. 权限更改成功 您在 %1$s 空间中的访问权限已更改为只读。 您在 %1$s 空间中的访问权限已更改为可编辑。 diff --git a/localization/src/main/res/values-zh-rTW/strings.xml b/localization/src/main/res/values-zh-rTW/strings.xml index c9a0f21438..1cb9a25bc4 100644 --- a/localization/src/main/res/values-zh-rTW/strings.xml +++ b/localization/src/main/res/values-zh-rTW/strings.xml @@ -1211,6 +1211,8 @@ 請求已批准 您請求加入的 %1$s 空間已獲得批准,並具有唯讀存取權限。 該空間很快就可以在您的裝置上使用。 您請求加入的 %1$s 空間已獲得批准,並具有編輯存取權限。 該空間很快就可以在您的裝置上使用。 + The space \"%1$s\" is no longer accessible. + You have been removed from the space \"%1$s\", or the space was deleted by the owner. 權限已變更 您在 %1$s 空間的存取權限已變更為唯讀存取。 您在 %1$s 空間的存取權限已變更為編輯存取。 From e66d4b2a213ea57d4573a4379fcab88daa161543 Mon Sep 17 00:00:00 2001 From: "Any Association [bot]" <139948520+any-association@users.noreply.github.com> Date: Thu, 6 Feb 2025 18:06:28 +0100 Subject: [PATCH 15/78] l10n | Enhancement (#2073) --- .../src/main/res/values-pt-rBR/strings.xml | 32 +++++++++---------- .../src/main/res/values-tr-rTR/strings.xml | 4 +-- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/localization/src/main/res/values-pt-rBR/strings.xml b/localization/src/main/res/values-pt-rBR/strings.xml index b4631c6274..0162ffc8d0 100644 --- a/localization/src/main/res/values-pt-rBR/strings.xml +++ b/localization/src/main/res/values-pt-rBR/strings.xml @@ -43,10 +43,10 @@ Espaço Nome do espaço Entrar no Espaço - Apply random solid color + Aplicar cor sólida aleatória Carregue uma imagem Carregue uma imagem - Remove image + Remover imagem Excluir Espaço Você pode armazenar até %1$s de seus arquivos em nosso nó de backup criptografado gratuitamente. Se você atingir o limite, os arquivos serão armazenados apenas localmente. Para economizar espaço no seu dispositivo local, você pode descarregar todos os seus arquivos no nosso servidor de backup criptografado. Os arquivos serão carregados novamente quando forem abertos. @@ -223,7 +223,7 @@ Mais Concluído Para iniciar, selecione os blocos - Plain text + Texto sem formatação Desfazer Refazer Copiar @@ -371,7 +371,7 @@ Role para selecionar uma nova posição Adicionar abaixo Back button - Share button + Botão de compartilhar Home button Search button Add doc @@ -681,7 +681,7 @@ Hoje Ontem Amanhã - Open selected date + Abrir data selecionada Sem data Dia exato Reordenar @@ -850,7 +850,7 @@ Arquivos sincronizados Entrar Biblioteca Anytype - Your trash is empty. + Sua lixeira está vazia Looks like you’re all tidy and organized! Estilo @@ -1525,7 +1525,7 @@ forneça detalhes específicos das suas necessidades aqui. Z → A A → Z Mais recente primeiro - Oldest first + Mais antigo primeiro Atualização de data Data Criada Date last used @@ -1536,9 +1536,9 @@ forneça detalhes específicos das suas necessidades aqui. Últimos 7 dias Últimos 14 dias Vault is the new home for all your Spaces.\nCreate, join, or share Spaces – everything is securely stored here. - You can now easily reorder your Spaces using drag and drop and navigate them more easily. - By default, we blur the icon of your space as a background. If needed, you can change it in the space-settings. - Welcome to the Vault + Agora você pode facilmente reordenar seus Espaços usando arrastar e soltar e navegar com mais facilidade. + Por padrão, desfocamos o ícone do seu espaço como um plano de fundo. Se necessário, você pode alterá-lo nas configurações do espaço. + Bem-vindo ao Cofre It’s empty here. Create your first objects to get started. @@ -1568,14 +1568,14 @@ forneça detalhes específicos das suas necessidades aqui. There is no messages yet.\nBe the first to start a discussion. Escreva uma mensagem... Mentions - There is nothing here for this date yet + Não há nada aqui para esta data ainda The selected date is out of the valid range (years %1$d to %2$d). Please select a date within this range. by Select existing object Upload file Upload media Fazer upload - Reply + Responder Adicionar reação Monday Tuesday @@ -1596,9 +1596,9 @@ forneça detalhes específicos das suas necessidades aqui. Symbols Flags Recently used - No reactions yet - Probably someone has just removed the reaction or technical issue happened + Nenhuma reação ainda + Provavelmente alguém acabou de remover a reação ou ocorreu um problema técnico Human - It cannot be restored after confirmation - Delete this message? + Não pode ser restaurado após a confirmação + Excluir esta mensagem? diff --git a/localization/src/main/res/values-tr-rTR/strings.xml b/localization/src/main/res/values-tr-rTR/strings.xml index 1f65946640..460c16ba83 100644 --- a/localization/src/main/res/values-tr-rTR/strings.xml +++ b/localization/src/main/res/values-tr-rTR/strings.xml @@ -1215,8 +1215,8 @@ Talep kabul edildi %1$s alanına katılma isteğiniz salt okunur erişim haklarıyla onaylandı. Alan yakında cihazınızda kullanılabilir olacak. %1$s alanına katılma isteğiniz düzenleme erişim haklarıyla onaylandı. Alan yakında cihazınızda kullanılabilir olacak. - The space \"%1$s\" is no longer accessible. - You have been removed from the space \"%1$s\", or the space was deleted by the owner. + \"%1$s\" alanı artık erişilebilir değil. + \"%1$s\" alanından çıkarıldınız veya alan sahibi tarafından silindi. İzinler değiştirildi %1$s alanındaki erişim haklarınız salt okunur olarak değiştirildi. %1$s alanındaki erişim haklarınız düzenleyici olarak değiştirildi. From 316d772472ef58b065a9a62c113a56b9bc47dc97 Mon Sep 17 00:00:00 2001 From: "Any Association [bot]" <139948520+any-association@users.noreply.github.com> Date: Fri, 7 Feb 2025 14:04:14 +0100 Subject: [PATCH 16/78] l10n | Enhancement (#2075) --- localization/src/main/res/values-ru-rRU/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/localization/src/main/res/values-ru-rRU/strings.xml b/localization/src/main/res/values-ru-rRU/strings.xml index a2b63585c2..d519f487c3 100644 --- a/localization/src/main/res/values-ru-rRU/strings.xml +++ b/localization/src/main/res/values-ru-rRU/strings.xml @@ -1170,9 +1170,9 @@ ✦ Обновите, чтобы добавить больше участников ✦ Обновите, чтобы добавить больше пространств Участники - Запрос на выход + Оставить запрос Запрос на присоединение - Подтвердить + Утверждено Посмотреть запрос Владелец Can edit From f929e33946f9f45450fdb2e8b1df63006cd5f41a Mon Sep 17 00:00:00 2001 From: "Any Association [bot]" <139948520+any-association@users.noreply.github.com> Date: Mon, 10 Feb 2025 09:55:17 +0100 Subject: [PATCH 17/78] l10n | Enhancement (#2076) --- localization/src/main/res/values-be-rBY/strings.xml | 2 +- localization/src/main/res/values-de-rDE/strings.xml | 2 +- localization/src/main/res/values-in-rID/strings.xml | 8 ++++---- localization/src/main/res/values-it-rIT/strings.xml | 2 +- localization/src/main/res/values-nl-rNL/strings.xml | 2 +- localization/src/main/res/values-no-rNO/strings.xml | 2 +- localization/src/main/res/values-pt-rBR/strings.xml | 2 +- localization/src/main/res/values-ru-rRU/strings.xml | 2 +- localization/src/main/res/values-uk-rUA/strings.xml | 2 +- localization/src/main/res/values-zh-rCN/strings.xml | 2 +- localization/src/main/res/values-zh-rTW/strings.xml | 2 +- 11 files changed, 14 insertions(+), 14 deletions(-) diff --git a/localization/src/main/res/values-be-rBY/strings.xml b/localization/src/main/res/values-be-rBY/strings.xml index 24b3a65db5..5615957da9 100644 --- a/localization/src/main/res/values-be-rBY/strings.xml +++ b/localization/src/main/res/values-be-rBY/strings.xml @@ -173,7 +173,7 @@ Пераключыць блакіраванне Уставіць спасылку Пусты. Націсніце, каб стварыць новы блок - Бяз назвы + Untitled Набор без назвы Калекцыя без назвы Заархівавана diff --git a/localization/src/main/res/values-de-rDE/strings.xml b/localization/src/main/res/values-de-rDE/strings.xml index 3c65f007af..6cd9445c45 100644 --- a/localization/src/main/res/values-de-rDE/strings.xml +++ b/localization/src/main/res/values-de-rDE/strings.xml @@ -173,7 +173,7 @@ Block wechseln Link einfügen Leer. Tippen, um einen neuen Block zu erstellen - Unbenannt + Untitled Unbenanntes Set Unbenannte Sammlung Archiviert diff --git a/localization/src/main/res/values-in-rID/strings.xml b/localization/src/main/res/values-in-rID/strings.xml index cc3e499339..3d689bc492 100644 --- a/localization/src/main/res/values-in-rID/strings.xml +++ b/localization/src/main/res/values-in-rID/strings.xml @@ -46,7 +46,7 @@ Beri warna polos acak Upload image Upload image - Remove image + Hapus gambar Hapus ruang Kamu bisa menyimpan berkasmu hingga %1$s di simpul cadangan terenkripsi kami secara gratis. Jika melewati batas, berkasmu hanya akan disimpan secara lokal. Untuk menghemat penyimpanan ruang pada perangkat lokal, anda dapat melepaskan semua berkas ke simpul cadangan terenkripsi kami. Berkas - berkas akan diunduh kembali saat kamu membuka mereka. @@ -370,7 +370,7 @@ Scroll to select new position Tambah di bawah Tombol kembali - Share button + Tombol bagikan Tombol beranda Tombol cari Add doc @@ -1211,8 +1211,8 @@ Permintaan disetujui Permintaanmu bergabug ke ruang %1$s telah disetujui dengan hak akses pembaca. Ruang ini akan tersedia di perangkatmu sebentar lagi. Permintaanmu bergabung ke ruang %1$s telah disetujui dengan hak akses penyunting. Ruang ini akan tersedia di perangkatmu sebentar lagi. - The space \"%1$s\" is no longer accessible. - You have been removed from the space \"%1$s\", or the space was deleted by the owner. + Ruang \"%1$s\" tidak dapat diakses lagi. + Kamu telah dikeluarkan dari ruang \"%1$s\", atau ruang tersebut telah dihapus oleh pemiliknya. Izin diubah Hak aksesmu di ruang %1$s telah diganti menjadi pembaca. Hak aksesmu di ruang %1$s telah diganti menjadi penyunting. diff --git a/localization/src/main/res/values-it-rIT/strings.xml b/localization/src/main/res/values-it-rIT/strings.xml index 9d4a23f86d..ed841d9b2c 100644 --- a/localization/src/main/res/values-it-rIT/strings.xml +++ b/localization/src/main/res/values-it-rIT/strings.xml @@ -173,7 +173,7 @@ Blocco espandibile Incolla link Vuoto. Tocca per creare un nuovo blocco - Senza titolo + Untitled Insieme senza titolo Collezione senza titolo Archiviato diff --git a/localization/src/main/res/values-nl-rNL/strings.xml b/localization/src/main/res/values-nl-rNL/strings.xml index 90d3ddac39..8b788caad2 100644 --- a/localization/src/main/res/values-nl-rNL/strings.xml +++ b/localization/src/main/res/values-nl-rNL/strings.xml @@ -173,7 +173,7 @@ Open/sluit blok Plak koppeling Leeg. Tik om een nieuw blok aan te maken - Naamloos + Untitled Naamloze set Naamloze collectie Gearchiveerd diff --git a/localization/src/main/res/values-no-rNO/strings.xml b/localization/src/main/res/values-no-rNO/strings.xml index 25cf0af824..34e7d64553 100644 --- a/localization/src/main/res/values-no-rNO/strings.xml +++ b/localization/src/main/res/values-no-rNO/strings.xml @@ -173,7 +173,7 @@ Ekspanderbar blokk Lim inn lenke Empty. Tap to create a new block - Uten tittel + Untitled Navnløst sett Navnløs samling Arkivert diff --git a/localization/src/main/res/values-pt-rBR/strings.xml b/localization/src/main/res/values-pt-rBR/strings.xml index 0162ffc8d0..a3f168c955 100644 --- a/localization/src/main/res/values-pt-rBR/strings.xml +++ b/localization/src/main/res/values-pt-rBR/strings.xml @@ -173,7 +173,7 @@ Alternar bloco Colar link Vazio. Toque para criar um novo bloco - Sem título + Untitled Conjunto sem título Coleção sem título Arquivado diff --git a/localization/src/main/res/values-ru-rRU/strings.xml b/localization/src/main/res/values-ru-rRU/strings.xml index d519f487c3..9e1f98f7c9 100644 --- a/localization/src/main/res/values-ru-rRU/strings.xml +++ b/localization/src/main/res/values-ru-rRU/strings.xml @@ -173,7 +173,7 @@ Переключить блокировку Вставить ссылку Пусто. Нажмите, чтобы создать новый блок - Без названия + Untitled Набор без названия Коллекция без названия В архиве diff --git a/localization/src/main/res/values-uk-rUA/strings.xml b/localization/src/main/res/values-uk-rUA/strings.xml index 7ab8e8e680..eee354d14a 100644 --- a/localization/src/main/res/values-uk-rUA/strings.xml +++ b/localization/src/main/res/values-uk-rUA/strings.xml @@ -173,7 +173,7 @@ Перемкнути блок Вставити посилання Empty. Tap to create a new block - Без назви + Untitled Безіменний набір Безіменна колекція Archived diff --git a/localization/src/main/res/values-zh-rCN/strings.xml b/localization/src/main/res/values-zh-rCN/strings.xml index cefb4b21f7..c7a79c5ecf 100644 --- a/localization/src/main/res/values-zh-rCN/strings.xml +++ b/localization/src/main/res/values-zh-rCN/strings.xml @@ -173,7 +173,7 @@ 切换块 粘贴链接 空的。点击创建一个新块 - 无标题 + Untitled 无标题集 无标题集锦 已归档 diff --git a/localization/src/main/res/values-zh-rTW/strings.xml b/localization/src/main/res/values-zh-rTW/strings.xml index 1cb9a25bc4..f6aa065fc8 100644 --- a/localization/src/main/res/values-zh-rTW/strings.xml +++ b/localization/src/main/res/values-zh-rTW/strings.xml @@ -173,7 +173,7 @@ 摺疊區塊 貼上連結 空的。點擊以新建區塊 - 未命名 + Untitled 未命名的集合 未命名集錦 已封存 From a8e00b3f62105b4afd13ce9a706c65c5f81c91f8 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Mon, 10 Feb 2025 10:40:03 +0100 Subject: [PATCH 18/78] DROID-3366 Protocol | Enhancement | MW 0.39.6 (#2077) --- gradle/libs.versions.toml | 2 +- protocol/src/main/proto/localstore.proto | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 74044192dc..ced32ca4d0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -middlewareVersion = "v0.39.5" +middlewareVersion = "v0.39.6" kotlinVersion = '2.0.21' kspVersion = "2.0.21-1.0.25" diff --git a/protocol/src/main/proto/localstore.proto b/protocol/src/main/proto/localstore.proto index 3eeecb18b4..7979ebe164 100644 --- a/protocol/src/main/proto/localstore.proto +++ b/protocol/src/main/proto/localstore.proto @@ -64,4 +64,5 @@ message ObjectStoreChecksums { int32 linksErase = 14; int32 marketplaceForceReindexCounter = 15; int32 reindexDeletedObjects = 16; + int32 reindexParticipants = 17; } From cf36b0b1a16a05e68a070697d300ec1ad02e926f Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Mon, 10 Feb 2025 13:23:00 +0100 Subject: [PATCH 19/78] DROID-3226 Analytics | Enhancement | Add analytics for the new navigation flow (#2078) --- .../analytics/base/EventsDictionary.kt | 2 + .../anytypeio/anytype/ui/home/HomeScreen.kt | 12 ++--- .../anytype/ui/home/HomeScreenFragment.kt | 8 ++-- .../presentation/editor/EditorViewModel.kt | 10 ++++ .../presentation/home/HomeScreenViewModel.kt | 20 ++++---- .../presentation/navigation/NavPanelState.kt | 47 +++++++++++++++++++ .../presentation/sets/ObjectSetViewModel.kt | 14 ++++-- .../spaces/SpaceSettingsViewModel.kt | 34 ++++++++++++++ .../widgets/collection/CollectionViewModel.kt | 9 ++-- 9 files changed, 126 insertions(+), 30 deletions(-) diff --git a/analytics/src/main/java/com/anytypeio/anytype/analytics/base/EventsDictionary.kt b/analytics/src/main/java/com/anytypeio/anytype/analytics/base/EventsDictionary.kt index 3b8843eb6b..e32421e7f4 100644 --- a/analytics/src/main/java/com/anytypeio/anytype/analytics/base/EventsDictionary.kt +++ b/analytics/src/main/java/com/anytypeio/anytype/analytics/base/EventsDictionary.kt @@ -191,6 +191,8 @@ object EventsDictionary { const val CLICK_ONBOARDING_TOOLTIP_TYPE_CLOSE = "Close" // Sharing spaces + + const val clickQuote = "ClickQuote" const val shareSpace = "ShareSpace" const val screenSettingsSpaceShare = "ScreenSettingsSpaceShare" const val screenStopShare = "ScreenStopShare" diff --git a/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreen.kt b/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreen.kt index 178b3e66f5..d1e684c1e0 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreen.kt @@ -91,11 +91,11 @@ fun HomeScreen( onHomeButtonClicked: () -> Unit, onCreateNewObjectClicked: () -> Unit, onCreateNewObjectLongClicked: () -> Unit, - onShareButtonClicked: () -> Unit, + onNavBarShareButtonClicked: () -> Unit, onObjectCheckboxClicked: (Id, Boolean) -> Unit, onSpaceWidgetClicked: () -> Unit, onMove: (List, FromIndex, ToIndex) -> Unit, - onSpaceShareIconClicked: (ObjectWrapper.SpaceView) -> Unit, + onSpaceWidgetShareIconClicked: (ObjectWrapper.SpaceView) -> Unit, onSeeAllObjectsClicked: (WidgetView.Gallery) -> Unit, onCreateObjectInsideWidget: (Id) -> Unit, onCreateDataViewObject: (WidgetId, ViewId?) -> Unit @@ -116,7 +116,7 @@ fun HomeScreen( onSpaceWidgetClicked = onSpaceWidgetClicked, onMove = onMove, onObjectCheckboxClicked = onObjectCheckboxClicked, - onSpaceShareIconClicked = onSpaceShareIconClicked, + onSpaceWidgetShareIconClicked = onSpaceWidgetShareIconClicked, onSeeAllObjectsClicked = onSeeAllObjectsClicked, onCreateWidget = onCreateWidget, onCreateObjectInsideWidget = onCreateObjectInsideWidget, @@ -162,7 +162,7 @@ fun HomeScreen( searchClick = onSearchClicked, addDocClick = onCreateNewObjectClicked, addDocLongClick = onCreateNewObjectLongClicked, - onShareButtonClicked = onShareButtonClicked, + onShareButtonClicked = onNavBarShareButtonClicked, onHomeButtonClicked = onHomeButtonClicked ) } @@ -187,7 +187,7 @@ private fun WidgetList( onMove: (List, FromIndex, ToIndex) -> Unit, onObjectCheckboxClicked: (Id, Boolean) -> Unit, onSpaceWidgetClicked: () -> Unit, - onSpaceShareIconClicked: (ObjectWrapper.SpaceView) -> Unit, + onSpaceWidgetShareIconClicked: (ObjectWrapper.SpaceView) -> Unit, onSeeAllObjectsClicked: (WidgetView.Gallery) -> Unit, onCreateWidget: () -> Unit, onCreateObjectInsideWidget: (Id) -> Unit, @@ -229,7 +229,7 @@ private fun WidgetList( name = item.space.name.orEmpty(), icon = item.icon, spaceType = item.type, - onSpaceShareIconClicked = { onSpaceShareIconClicked(item.space) }, + onSpaceShareIconClicked = { onSpaceWidgetShareIconClicked(item.space) }, isShared = item.isShared, membersCount = item.membersCount ) diff --git a/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenFragment.kt index cb7b82ed4b..52740891da 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenFragment.kt @@ -112,7 +112,7 @@ class HomeScreenFragment : BaseComposeFragment(), ) { HomeScreenToolbar( spaceIconView = view?.icon ?: SpaceIconView.Loading, - onSpaceIconClicked = vm::onSpaceSettingsClicked, + onSpaceIconClicked = vm::onSpaceWidgetClicked, membersCount = view?.membersCount ?: 0, name = view?.space?.name.orEmpty(), onBackButtonClicked = { @@ -176,16 +176,16 @@ class HomeScreenFragment : BaseComposeFragment(), onClick = { vm.onCreateNewObjectLongClicked() } ), onSpaceWidgetClicked = throttledClick( - onClick = vm::onSpaceSettingsClicked + onClick = vm::onSpaceWidgetClicked ), onBundledWidgetClicked = vm::onBundledWidgetClicked, onMove = vm::onMove, onObjectCheckboxClicked = vm::onObjectCheckboxClicked, - onSpaceShareIconClicked = vm::onSpaceShareIconClicked, + onSpaceWidgetShareIconClicked = vm::onSpaceWidgetShareIconClicked, onSeeAllObjectsClicked = vm::onSeeAllObjectsClicked, onCreateObjectInsideWidget = vm::onCreateObjectInsideWidget, onCreateDataViewObject = vm::onCreateDataViewObject, - onShareButtonClicked = vm::onSpaceShareIconClicked, + onNavBarShareButtonClicked = vm::onNavBarShareIconClicked, navPanelState = vm.navPanelState.collectAsStateWithLifecycle().value, onHomeButtonClicked = vm::onHomeButtonClicked, ) diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt index d20d01ad70..6f006a7c27 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt @@ -265,6 +265,7 @@ import com.anytypeio.anytype.presentation.extension.getObjRelationsViews import com.anytypeio.anytype.presentation.extension.getRecommendedRelations import com.anytypeio.anytype.presentation.extension.getUrlForFileContent import com.anytypeio.anytype.presentation.navigation.NavPanelState +import com.anytypeio.anytype.presentation.navigation.leftButtonClickAnalytics import com.anytypeio.anytype.presentation.objects.getCreateObjectParams import com.anytypeio.anytype.presentation.objects.getObjectTypeViewsForSBPage import com.anytypeio.anytype.presentation.objects.getProperType @@ -297,6 +298,7 @@ import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -1208,12 +1210,14 @@ class EditorViewModel( } fun onShareButtonClicked() { + proceedWithLeftButtonAnalytics() dispatch( Command.OpenShareScreen(vmParams.space) ) } fun onHomeButtonClicked() { + proceedWithLeftButtonAnalytics() Timber.d("onHomeButtonClicked, ") if (stateData.value == ViewState.NotExist) { exitToSpaceHome() @@ -1226,6 +1230,12 @@ class EditorViewModel( exitBack() } + private fun proceedWithLeftButtonAnalytics() { + viewModelScope.launch { + navPanelState.firstOrNull()?.leftButtonClickAnalytics(analytics) + } + } + private fun exitBack() { when (session.value) { Session.ERROR -> navigate(EventWrapper(AppNavigation.Command.Exit)) diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt index 35bda38c68..b11f588079 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt @@ -91,6 +91,7 @@ import com.anytypeio.anytype.presentation.home.Command.ChangeWidgetType.Companio import com.anytypeio.anytype.presentation.navigation.DeepLinkToObjectDelegate import com.anytypeio.anytype.presentation.navigation.NavPanelState import com.anytypeio.anytype.presentation.navigation.NavigationViewModel +import com.anytypeio.anytype.presentation.navigation.leftButtonClickAnalytics import com.anytypeio.anytype.presentation.objects.getCreateObjectParams import com.anytypeio.anytype.presentation.search.Subscriptions import com.anytypeio.anytype.presentation.sets.prefillNewObjectDetails @@ -1764,7 +1765,7 @@ class HomeScreenViewModel( } } - fun onSpaceShareIconClicked(spaceView: ObjectWrapper.SpaceView) { + fun onSpaceWidgetShareIconClicked(spaceView: ObjectWrapper.SpaceView) { viewModelScope.launch { val space = spaceView.targetSpaceId if (space != null) { @@ -1775,19 +1776,20 @@ class HomeScreenViewModel( } } - fun onSpaceShareIconClicked() { + fun onNavBarShareIconClicked() { viewModelScope.launch { - commands.emit( - Command.ShareSpace(SpaceId(spaceManager.get())) - ) + navPanelState.value.leftButtonClickAnalytics(analytics) + } + viewModelScope.launch { + commands.emit(Command.ShareSpace(SpaceId(spaceManager.get()))) } } fun onHomeButtonClicked() { - // Do nothing + // Do nothing, as home button is not visible on space home screen. } - fun onSpaceSettingsClicked() { + fun onSpaceWidgetClicked() { viewModelScope.launch { commands.emit( Command.OpenSpaceSettings( @@ -1825,10 +1827,6 @@ class HomeScreenViewModel( } } - fun onBackLongClicked() { - navigate(destination = Navigation.OpenSpaceSwitcher) - } - override fun onCleared() { super.onCleared() Timber.d("onCleared") diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/NavPanelState.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/NavPanelState.kt index 770785be20..5fd319e0b2 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/NavPanelState.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/NavPanelState.kt @@ -1,5 +1,10 @@ package com.anytypeio.anytype.presentation.navigation +import com.anytypeio.anytype.analytics.base.Analytics +import com.anytypeio.anytype.analytics.base.EventsDictionary +import com.anytypeio.anytype.analytics.base.EventsPropertiesKey +import com.anytypeio.anytype.analytics.base.sendEvent +import com.anytypeio.anytype.analytics.props.Props import com.anytypeio.anytype.core_models.multiplayer.SpaceMemberPermissions sealed class NavPanelState { @@ -68,4 +73,46 @@ sealed class NavPanelState { } } } +} + +suspend fun NavPanelState.leftButtonClickAnalytics(analytics: Analytics) { + when (val state = this) { + is NavPanelState.Default -> { + when (state.leftButtonState) { + is NavPanelState.LeftButtonState.AddMembers -> { + analytics.sendEvent( + eventName = EventsDictionary.screenSettingsSpaceShare, + props = Props( + mapOf( + EventsPropertiesKey.route to EventsDictionary.Routes.navigation + ) + ) + ) + } + + is NavPanelState.LeftButtonState.Comment -> { + analytics.sendEvent(eventName = EventsDictionary.clickQuote) + } + + NavPanelState.LeftButtonState.Home -> { + // Do nothing. + } + + NavPanelState.LeftButtonState.ViewMembers -> { + analytics.sendEvent( + eventName = EventsDictionary.screenSettingsSpaceMembers, + props = Props( + mapOf( + EventsPropertiesKey.route to EventsDictionary.Routes.navigation + ) + ) + ) + } + } + } + + NavPanelState.Init -> { + // Do nothing. + } + } } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModel.kt index 935d87630f..33638fb263 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModel.kt @@ -78,6 +78,7 @@ import com.anytypeio.anytype.core_models.SupportedLayouts import com.anytypeio.anytype.core_models.TimeInMillis import com.anytypeio.anytype.presentation.extension.getObject import com.anytypeio.anytype.presentation.navigation.NavPanelState +import com.anytypeio.anytype.presentation.navigation.leftButtonClickAnalytics import com.anytypeio.anytype.presentation.objects.getCreateObjectParams import com.anytypeio.anytype.presentation.objects.isCreateObjectAllowed import com.anytypeio.anytype.presentation.objects.isTemplatesAllowed @@ -126,6 +127,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest @@ -1619,6 +1621,9 @@ class ObjectSetViewModel( } fun onHomeButtonClicked() { + viewModelScope.launch { + navPanelState.firstOrNull()?.leftButtonClickAnalytics(analytics) + } viewModelScope.launch { dispatch(AppNavigation.Command.ExitToSpaceHome) } @@ -1626,11 +1631,10 @@ class ObjectSetViewModel( fun onShareButtonClicked() { viewModelScope.launch { - dispatch( - AppNavigation.Command.OpenShareScreen( - vmParams.space - ) - ) + navPanelState.firstOrNull()?.leftButtonClickAnalytics(analytics) + } + viewModelScope.launch { + dispatch(AppNavigation.Command.OpenShareScreen(vmParams.space)) } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/spaces/SpaceSettingsViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/spaces/SpaceSettingsViewModel.kt index 7b68edee05..287e33ad53 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/spaces/SpaceSettingsViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/spaces/SpaceSettingsViewModel.kt @@ -13,7 +13,9 @@ import com.anytypeio.anytype.core_models.Block import com.anytypeio.anytype.core_models.Filepath import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.ObjectWrapper +import com.anytypeio.anytype.core_models.PRIVATE_SPACE_TYPE import com.anytypeio.anytype.core_models.Relations +import com.anytypeio.anytype.core_models.SHARED_SPACE_TYPE import com.anytypeio.anytype.core_models.SpaceType import com.anytypeio.anytype.core_models.UNKNOWN_SPACE_TYPE import com.anytypeio.anytype.core_models.asSpaceType @@ -289,6 +291,38 @@ class SpaceSettingsViewModel( } fun onSharePrivateSpaceClicked() { + viewModelScope.launch { + val data = spaceViewState.value + when(data) { + is ViewState.Success -> { + when(data.data.spaceType) { + PRIVATE_SPACE_TYPE -> { + analytics.sendEvent( + eventName = EventsDictionary.screenSettingsSpaceShare, + props = Props( + mapOf( + EventsPropertiesKey.route to EventsDictionary.Routes.settings + ) + ) + ) + } + SHARED_SPACE_TYPE -> { + analytics.sendEvent( + eventName = EventsDictionary.screenSettingsSpaceMembers, + props = Props( + mapOf( + EventsPropertiesKey.route to EventsDictionary.Routes.settings + ) + ) + ) + } + } + } + else -> { + // Do nothing. + } + } + } viewModelScope.launch { val data = spaceViewState.value if (data is ViewState.Success) { diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/collection/CollectionViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/collection/CollectionViewModel.kt index 4a02556b9f..cb7cb69e88 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/collection/CollectionViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/collection/CollectionViewModel.kt @@ -50,6 +50,7 @@ import com.anytypeio.anytype.presentation.home.OpenObjectNavigation import com.anytypeio.anytype.presentation.home.navigation import com.anytypeio.anytype.presentation.navigation.DefaultObjectView import com.anytypeio.anytype.presentation.navigation.NavPanelState +import com.anytypeio.anytype.presentation.navigation.leftButtonClickAnalytics import com.anytypeio.anytype.presentation.objects.ObjectAction import com.anytypeio.anytype.presentation.objects.getCreateObjectParams import com.anytypeio.anytype.presentation.objects.mapFileObjectToView @@ -74,6 +75,7 @@ import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map @@ -874,13 +876,12 @@ class CollectionViewModel( } fun onShareButtonClicked() { + viewModelScope.launch { + navPanelState.value.leftButtonClickAnalytics(analytics) + } launch { commands.emit(OpenShareScreen(vmParams.spaceId)) } } - fun onBackLongClicked() { - launch { commands.emit(ExitToSpaceWidgets) } - } - fun onSearchClicked(space: Id) { viewModelScope.sendEvent( analytics = analytics, From 258b705c7941e20b8f96b96b87ec4c24985ccd12 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Mon, 10 Feb 2025 13:29:05 +0100 Subject: [PATCH 20/78] DROID-3293 Notification | Fix | Misc. fixes (#2079) --- app/src/main/java/com/anytypeio/anytype/app/Notifications.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/anytypeio/anytype/app/Notifications.kt b/app/src/main/java/com/anytypeio/anytype/app/Notifications.kt index 9950bf6c44..01626c1c89 100644 --- a/app/src/main/java/com/anytypeio/anytype/app/Notifications.kt +++ b/app/src/main/java/com/anytypeio/anytype/app/Notifications.kt @@ -83,13 +83,14 @@ class AnytypeNotificationService @Inject constructor( ) } is NotificationPayload.ParticipantRemove -> { + val placeholder = context.resources.getString(R.string.untitled) val body = context.resources.getString( R.string.multiplayer_notification_member_removed_from_space, - payload.spaceName + payload.spaceName.ifEmpty { placeholder } ) val title = context.resources.getString( R.string.multiplayer_notification_member_removed_from_space_title, - payload.spaceName + payload.spaceName.ifEmpty { placeholder } ) showBasicNotification( tag = notification.id, From b97e9d9b855d1a7be004a8faa944ceb97dd08110 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Mon, 10 Feb 2025 13:46:56 +0100 Subject: [PATCH 21/78] DROID-3278 Navigation | Fix | Share/members button must look like a 'share' button and be inaccessible in Entry space (#2080) --- .../components/BottomNavigationMenu.kt | 15 +++++++--- .../presentation/home/HomeScreenViewModel.kt | 30 ++++++++++++++----- .../presentation/navigation/NavPanelState.kt | 6 ++-- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/foundation/components/BottomNavigationMenu.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/foundation/components/BottomNavigationMenu.kt index f816d3dd7a..59b6859d26 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/foundation/components/BottomNavigationMenu.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/foundation/components/BottomNavigationMenu.kt @@ -174,18 +174,25 @@ fun BottomNavigationMenu( verticalAlignment = Alignment.CenterVertically ) { if (state is NavPanelState.Default) { - when (state.leftButtonState) { + when (val left = state.leftButtonState) { is NavPanelState.LeftButtonState.AddMembers -> { MenuItem( modifier = Modifier .width(72.dp) - .height(52.dp), + .height(52.dp) + .alpha( + if (left.isActive) + FULL_ALPHA + else + DEFAULT_DISABLED_ALPHA + ) + , contentDescription = stringResource(id = R.string.main_navigation_content_desc_members_button), res = BottomNavigationItem.ADD_MEMBERS.res, - onClick = onShareButtonClicked + onClick = onShareButtonClicked, + enabled = left.isActive ) } - is NavPanelState.LeftButtonState.Comment -> { // TODO } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt index b11f588079..ff73588efb 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt @@ -368,15 +368,29 @@ class HomeScreenViewModel( private fun proceedWithNavPanelState() { viewModelScope.launch { - userPermissions - .map { permission -> - NavPanelState.fromPermission( - permission = permission, - forceHome = false - ) - }.collect { - navPanelState.value = it + val spaceAccessType = views + .map { + val space = it.firstOrNull { it is WidgetView.SpaceWidget.View } + if (space is WidgetView.SpaceWidget.View) { + space.space + .spaceAccessType + } else { + null + } } + .distinctUntilChanged() + combine( + spaceAccessType, + userPermissions + ) { type, permission -> + NavPanelState.fromPermission( + permission = permission, + forceHome = false, + spaceAccessType = type + ) + }.collect { + navPanelState.value = it + } } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/NavPanelState.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/NavPanelState.kt index 5fd319e0b2..d5ae6c5e7b 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/NavPanelState.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/NavPanelState.kt @@ -5,6 +5,7 @@ import com.anytypeio.anytype.analytics.base.EventsDictionary import com.anytypeio.anytype.analytics.base.EventsPropertiesKey import com.anytypeio.anytype.analytics.base.sendEvent import com.anytypeio.anytype.analytics.props.Props +import com.anytypeio.anytype.core_models.multiplayer.SpaceAccessType import com.anytypeio.anytype.core_models.multiplayer.SpaceMemberPermissions sealed class NavPanelState { @@ -26,7 +27,8 @@ sealed class NavPanelState { companion object { fun fromPermission( permission: SpaceMemberPermissions?, - forceHome: Boolean = true + forceHome: Boolean = true, + spaceAccessType: SpaceAccessType? = null ) : NavPanelState { return when(permission) { SpaceMemberPermissions.READER -> { @@ -54,7 +56,7 @@ sealed class NavPanelState { LeftButtonState.Home else LeftButtonState.AddMembers( - isActive = true + isActive = spaceAccessType != SpaceAccessType.DEFAULT ) ) } From d0a0bcdaa20cbf40f67c64f8eb322bf03ed10ed6 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Mon, 10 Feb 2025 15:07:07 +0100 Subject: [PATCH 22/78] DROID-3067 Space settings | Fix | Fix broken behavior for removing space icon (#2081) --- .../anytype/presentation/spaces/SpaceSettingsViewModel.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/spaces/SpaceSettingsViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/spaces/SpaceSettingsViewModel.kt index 287e33ad53..1ed06f50ff 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/spaces/SpaceSettingsViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/spaces/SpaceSettingsViewModel.kt @@ -182,8 +182,7 @@ class SpaceSettingsViewModel( space = params.space, details = mapOf( Relations.ICON_OPTION to spaceGradientProvider.randomId().toDouble(), - Relations.ICON_IMAGE to null, - Relations.ICON_EMOJI to null + Relations.ICON_IMAGE to "", ) ) ) From 93d59016c16c5d94084ffc878fbd49a788b0e051 Mon Sep 17 00:00:00 2001 From: "Any Association [bot]" <139948520+any-association@users.noreply.github.com> Date: Mon, 10 Feb 2025 20:50:40 +0100 Subject: [PATCH 23/78] l10n | Enhancement (#2082) --- localization/src/main/res/values-tr-rTR/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/localization/src/main/res/values-tr-rTR/strings.xml b/localization/src/main/res/values-tr-rTR/strings.xml index 460c16ba83..ad917abcd9 100644 --- a/localization/src/main/res/values-tr-rTR/strings.xml +++ b/localization/src/main/res/values-tr-rTR/strings.xml @@ -1512,7 +1512,7 @@ Lütfen ihtiyaçlarınızla ilgili özel ayrıntıları burada belirtin.Sürüm geçmişi alınırken hata oluştu Üyeler alınırken hata oluştu Yeni nesne - Benim Alanlarım + Alanlarım Tüm Nesneler Sadece bağlantısız Grafikteki diğer nesnelerle doğrudan bağlantısı veya geri bağlantısı olmayan bağlantısız nesneler. From 5c520be4ec1a03d27205e2263f6e5fee71dfbfb9 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Mon, 10 Feb 2025 20:55:23 +0100 Subject: [PATCH 24/78] DROID-3308 Space-level chat | Enhancement | Enable mention query while user is typing (#2084) --- .../presentation/ChatViewModel.kt | 118 +++++++++++++----- .../anytype/feature_chats/ui/ChatScreen.kt | 44 ++++--- 2 files changed, 115 insertions(+), 47 deletions(-) diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt index eb0646fc43..58baad5707 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt @@ -263,31 +263,63 @@ class ChatViewModel @Inject constructor( selection: IntRange, text: String ) { + val query = resolveMentionQuery( + text = text, + selectionStart = selection.start + ) if (isMentionTriggered(text, selection.start)) { - mentionPanelState.value = MentionPanelState.Visible( - results = members.get().let { store -> - when(store) { - is Store.Data -> { - store.members.map { member -> - MentionPanelState.Member( - member.id, - name = member.name.orEmpty(), - icon = SpaceMemberIconView.icon( - obj = member, - urlBuilder = urlBuilder - ) - ) - } - } - Store.Empty -> { - emptyList() + val results = getMentionedMembers(query) + if (query != null) { + mentionPanelState.value = MentionPanelState.Visible( + results = results, + query = query + ) + } else { + Timber.w("Query is empty when mention is triggered") + } + } else if (shouldHideMention(text, selection.start)) { + mentionPanelState.value = MentionPanelState.Hidden + } else { + val results = getMentionedMembers(query) + if (results.isNotEmpty() && query != null) { + mentionPanelState.value = MentionPanelState.Visible( + results = results, + query = query + ) + } else { + mentionPanelState.value = MentionPanelState.Hidden + } + } + } + + private fun getMentionedMembers(query: MentionPanelState.Query?): List { + val results = members.get().let { store -> + when (store) { + is Store.Data -> { + store.members.map { member -> + MentionPanelState.Member( + member.id, + name = member.name.orEmpty(), + icon = SpaceMemberIconView.icon( + obj = member, + urlBuilder = urlBuilder + ) + ) + }.filter { m -> + if (query != null) { + m.name.contains(query.query, true) + } else { + true } } } - ) - } else { - mentionPanelState.value = MentionPanelState.Hidden + + Store.Empty -> { + emptyList() + } + } } + return results } fun onMessageSent(msg: String, markup: List) { @@ -356,7 +388,7 @@ class ChatViewModel @Inject constructor( params = Command.ChatCommand.AddMessage( chat = vmParams.ctx, message = Chat.Message.new( - text = msg, + text = msg.trim(), attachments = attachments, marks = markup ) @@ -379,7 +411,7 @@ class ChatViewModel @Inject constructor( chat = vmParams.ctx, message = Chat.Message.updated( id = mode.msg, - text = msg, + text = msg.trim(), attachments = editedMessage?.attachments.orEmpty() ) ) @@ -398,7 +430,7 @@ class ChatViewModel @Inject constructor( params = Command.ChatCommand.AddMessage( chat = vmParams.ctx, message = Chat.Message.new( - text = msg, + text = msg.trim(), replyToMessageId = mode.msg, attachments = attachments, marks = markup @@ -632,19 +664,32 @@ class ChatViewModel @Inject constructor( } } - private fun isMentionTriggered(text: String, selectionStart: Int): Boolean { - // Ensure selectionStart is valid and not out of bounds - if (selectionStart <= 0 || selectionStart > text.length) { - return false - } - - // Check the character before the cursor position + fun isMentionTriggered(text: String, selectionStart: Int): Boolean { + if (selectionStart <= 0 || selectionStart > text.length) return false val previousChar = text[selectionStart - 1] - - // Trigger mention if the previous character is '@' return previousChar == '@' + && (selectionStart == 1 || !text[selectionStart - 2].isLetterOrDigit()) } + fun shouldHideMention(text: String, selectionStart: Int): Boolean { + if (selectionStart > text.length) return false + // Check if the current character is a space + val currentChar = if (selectionStart > 0) text[selectionStart - 1] else null + // Hide mention when a space is typed, or '@' character has been deleted (even if it was the first character) + val atCharExists = text.lastIndexOf('@', selectionStart - 1) != -1 + return currentChar == ' ' || !atCharExists + } + + fun resolveMentionQuery(text: String, selectionStart: Int): MentionPanelState.Query? { + val atIndex = text.lastIndexOf('@', selectionStart - 1) + if (atIndex == -1 || (atIndex > 0 && text[atIndex - 1].isLetterOrDigit())) return null + val endIndex = text.indexOf(' ', atIndex).takeIf { it != -1 } ?: text.length + val query = text.substring(atIndex + 1, endIndex) + // Allow empty queries if there's no space after '@' + return MentionPanelState.Query(query, atIndex until endIndex) + } + + sealed class ViewModelCommand { data object Exit : ViewModelCommand() data object OpenWidgets : ViewModelCommand() @@ -672,13 +717,20 @@ class ChatViewModel @Inject constructor( sealed class MentionPanelState { data object Hidden : MentionPanelState() - data class Visible(val results: List) : MentionPanelState() + data class Visible( + val results: List, + val query: Query + ) : MentionPanelState() data class Member( val id: Id, val name: String, val icon: SpaceMemberIconView, val isUser: Boolean = false ) + data class Query( + val query: String, + val range: IntRange + ) } sealed class HeaderView { diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt index d77d6ded18..aa3e7c0a7b 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt @@ -355,35 +355,53 @@ fun ChatScreen( modifier = Modifier .fillMaxWidth() .noRippleClickable { - val start = text.selection.start - val end = text.selection.end + + val query = mentionPanelState.query val input = text.text - val adjustedStart = (start - 1).coerceAtLeast(0) - val replacementText = member.name + " " + + val lengthDifference = replacementText.length - (query.range.last - query.range.first + 1) + val updatedText = input.replaceRange( - startIndex = adjustedStart, - endIndex = end, - replacement = replacementText + query.range, + replacementText ) + // After inserting a mention, all existing spans after the insertion point are shifted based on the text length difference. + + val updatedSpans = spans.map { span -> + if (span.start > query.range.last) { + when(span) { + is ChatBoxSpan.Mention -> { + span.copy( + start = span.start + lengthDifference, + end = span.end + lengthDifference + ) + } + } + } else { + span + } + } + text = text.copy( text = updatedText, selection = TextRange( - index = (adjustedStart + replacementText.length)) + index = (query.range.start + replacementText.length) + ) ) val mentionSpan = ChatBoxSpan.Mention( - start = adjustedStart, - end = adjustedStart + member.name.length, + start = query.range.start, + end = query.range.start + member.name.length, style = SpanStyle( textDecoration = TextDecoration.Underline ), param = member.id ) - spans = spans + mentionSpan + spans = updatedSpans + mentionSpan onTextChanged(text) } @@ -427,9 +445,7 @@ fun ChatScreen( onValueChange = { t, s -> text = t spans = s - onTextChanged( - t - ) + onTextChanged(t) }, text = text, spans = spans From 14b2e564bee9ea320dd3c1e657182183fd39d232 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Mon, 10 Feb 2025 21:57:00 +0100 Subject: [PATCH 25/78] trimDROID-3308 Space-level chat | Enhancement | Enable mention query while user is typing - Hotfix (#2084) --- .../anytype/feature_chats/presentation/ChatViewModel.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt index 58baad5707..9005572dcf 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt @@ -269,13 +269,13 @@ class ChatViewModel @Inject constructor( ) if (isMentionTriggered(text, selection.start)) { val results = getMentionedMembers(query) - if (query != null) { + if (query != null && results.isNotEmpty()) { mentionPanelState.value = MentionPanelState.Visible( results = results, query = query ) } else { - Timber.w("Query is empty when mention is triggered") + Timber.w("Query is empty or results are empty when mention is triggered") } } else if (shouldHideMention(text, selection.start)) { mentionPanelState.value = MentionPanelState.Hidden From efcc7876f4f93dd4cc2f8ad8df18a883880b0116 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Tue, 11 Feb 2025 19:29:21 +0100 Subject: [PATCH 26/78] DROID-3283 Space-level chat | Enhancement | Loading state for chat box attachments + blocking send action while uploading (#2086) --- .../feature_chats/presentation/ChatView.kt | 13 +- .../presentation/ChatViewModel.kt | 90 ++++++- .../anytype/feature_chats/ui/ChatBox.kt | 135 ++-------- .../feature_chats/ui/ChatBoxAttachments.kt | 233 ++++++++++++++++++ .../anytype/feature_chats/ui/ChatPreviews.kt | 6 +- .../ic_chat_box_attachment_error_circle.xml | 17 ++ 6 files changed, 365 insertions(+), 129 deletions(-) create mode 100644 feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBoxAttachments.kt create mode 100644 feature-chats/src/main/res/drawable/ic_chat_box_attachment_error_circle.xml diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatView.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatView.kt index 8fad21094e..3412c276e3 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatView.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatView.kt @@ -69,17 +69,26 @@ sealed interface ChatView { sealed class ChatBoxAttachment { data class Media( - val uri: String + val uri: String, + val state: State = State.Idle ): ChatBoxAttachment() data class File( val uri: String, val name: String, - val size: Int + val size: Int, + val state: State = State.Idle ): ChatBoxAttachment() data class Link( val target: Id, val wrapper: GlobalSearchItemView ): ChatBoxAttachment() + + sealed class State { + data object Idle : State() + data object Uploading : State() + data object Uploaded : State() + data object Failed : State() + } } data class Reaction( diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt index 9005572dcf..b60909e8be 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt @@ -74,7 +74,7 @@ class ChatViewModel @Inject constructor( val commands = MutableSharedFlow() val uXCommands = MutableSharedFlow() val navigation = MutableSharedFlow() - val chatBoxMode = MutableStateFlow(ChatBoxMode.Default) + val chatBoxMode = MutableStateFlow(ChatBoxMode.Default()) val mentionPanelState = MutableStateFlow(MentionPanelState.Hidden) private val dateFormatter = SimpleDateFormat("d MMMM YYYY") @@ -327,8 +327,10 @@ class ChatViewModel @Inject constructor( Timber.d("DROID-2635 OnMessageSent, markup: $markup}") } viewModelScope.launch { + chatBoxMode.value = chatBoxMode.value.updateIsSendingBlocked(isBlocked = true) val attachments = buildList { - chatBoxAttachments.value.forEach { attachment -> + val currAttachments = chatBoxAttachments.value + currAttachments.forEachIndexed { idx, attachment -> when(attachment) { is ChatView.Message.ChatBoxAttachment.Link -> { add( @@ -339,6 +341,14 @@ class ChatViewModel @Inject constructor( ) } is ChatView.Message.ChatBoxAttachment.Media -> { + chatBoxAttachments.value = currAttachments.toMutableList().apply { + set( + index = idx, + element = attachment.copy( + state = ChatView.Message.ChatBoxAttachment.State.Uploading + ) + ) + } uploadFile.async( UploadFile.Params( space = vmParams.space, @@ -352,6 +362,23 @@ class ChatViewModel @Inject constructor( type = Chat.Message.Attachment.Type.Image ) ) + chatBoxAttachments.value = currAttachments.toMutableList().apply { + set( + index = idx, + element = attachment.copy( + state = ChatView.Message.ChatBoxAttachment.State.Uploaded + ) + ) + } + }.onFailure { + chatBoxAttachments.value = currAttachments.toMutableList().apply { + set( + index = idx, + element = attachment.copy( + state = ChatView.Message.ChatBoxAttachment.State.Uploading + ) + ) + } } } is ChatView.Message.ChatBoxAttachment.File -> { @@ -359,6 +386,14 @@ class ChatViewModel @Inject constructor( copyFileToCacheDirectory.copy(attachment.uri) } if (path != null) { + chatBoxAttachments.value = currAttachments.toMutableList().apply { + set( + index = idx, + element = attachment.copy( + state = ChatView.Message.ChatBoxAttachment.State.Uploading + ) + ) + } uploadFile.async( UploadFile.Params( space = vmParams.space, @@ -373,8 +408,24 @@ class ChatViewModel @Inject constructor( type = Chat.Message.Attachment.Type.File ) ) + chatBoxAttachments.value = currAttachments.toMutableList().apply { + set( + index = idx, + element = attachment.copy( + state = ChatView.Message.ChatBoxAttachment.State.Uploaded + ) + ) + } }.onFailure { Timber.e(it, "Error while uploading file as attachment") + chatBoxAttachments.value = currAttachments.toMutableList().apply { + set( + index = idx, + element = attachment.copy( + state = ChatView.Message.ChatBoxAttachment.State.Failed + ) + ) + } } } } @@ -401,6 +452,7 @@ class ChatViewModel @Inject constructor( }.onFailure { Timber.e(it, "Error while adding message") } + chatBoxMode.value = ChatBoxMode.Default() } is ChatBoxMode.EditMessage -> { val editedMessage = data.value.find { @@ -422,7 +474,7 @@ class ChatViewModel @Inject constructor( }.onFailure { Timber.e(it, "Error while adding message") }.onSuccess { - chatBoxMode.value = ChatBoxMode.Default + chatBoxMode.value = ChatBoxMode.Default() } } is ChatBoxMode.Reply -> { @@ -444,7 +496,7 @@ class ChatViewModel @Inject constructor( }.onFailure { Timber.e(it, "Error while adding message") } - chatBoxMode.value = ChatBoxMode.Default + chatBoxMode.value = ChatBoxMode.Default() } } } @@ -474,7 +526,7 @@ class ChatViewModel @Inject constructor( fun onClearReplyClicked() { viewModelScope.launch { - chatBoxMode.value = ChatBoxMode.Default + chatBoxMode.value = ChatBoxMode.Default() } } @@ -522,7 +574,8 @@ class ChatViewModel @Inject constructor( "" } }, - author = msg.author + author = msg.author, + isSendingMessageBlocked = false ) } } @@ -586,7 +639,7 @@ class ChatViewModel @Inject constructor( fun onExitEditMessageMode() { viewModelScope.launch { - chatBoxMode.value = ChatBoxMode.Default + chatBoxMode.value = ChatBoxMode.Default() } } @@ -706,15 +759,32 @@ class ChatViewModel @Inject constructor( } sealed class ChatBoxMode { - data object Default : ChatBoxMode() - data class EditMessage(val msg: Id) : ChatBoxMode() + + abstract val isSendingMessageBlocked: Boolean + + data class Default( + override val isSendingMessageBlocked: Boolean = false + ) : ChatBoxMode() + data class EditMessage( + val msg: Id, + override val isSendingMessageBlocked: Boolean = false + ) : ChatBoxMode() data class Reply( val msg: Id, val text: String, - val author: String + val author: String, + override val isSendingMessageBlocked: Boolean = false ): ChatBoxMode() } + fun ChatBoxMode.updateIsSendingBlocked(isBlocked: Boolean): ChatBoxMode { + return when (this) { + is ChatBoxMode.Default -> copy(isSendingMessageBlocked = isBlocked) + is ChatBoxMode.EditMessage -> copy(isSendingMessageBlocked = isBlocked) + is ChatBoxMode.Reply -> copy(isSendingMessageBlocked = isBlocked) + } + } + sealed class MentionPanelState { data object Hidden : MentionPanelState() data class Visible( diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBox.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBox.kt index 4c7d88462e..f054de2a65 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBox.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBox.kt @@ -29,6 +29,7 @@ import androidx.compose.foundation.text.BasicTextField import androidx.compose.material.DropdownMenu import androidx.compose.material.MaterialTheme import androidx.compose.material.Text +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.DropdownMenuItem import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -38,6 +39,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester @@ -52,6 +54,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import coil.compose.rememberAsyncImagePainter +import com.anytypeio.anytype.core_ui.common.DefaultPreviews import com.anytypeio.anytype.core_ui.foundation.Divider import com.anytypeio.anytype.core_ui.foundation.noRippleClickable import com.anytypeio.anytype.core_ui.views.BodyRegular @@ -71,7 +74,7 @@ import timber.log.Timber fun ChatBox( text: TextFieldValue, spans: List, - mode: ChatBoxMode = ChatBoxMode.Default, + mode: ChatBoxMode = ChatBoxMode.Default(), modifier: Modifier = Modifier, chatBoxFocusRequester: FocusRequester, onMessageSent: (String, List) -> Unit, @@ -124,114 +127,10 @@ fun ChatBox( onExitClicked = onExitEditMessageMode ) } - LazyRow( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - horizontalArrangement = Arrangement.spacedBy(12.dp), - ) { - attachments.forEach { attachment -> - when(attachment) { - is ChatView.Message.ChatBoxAttachment.Link -> { - item { - Box { - AttachedObject( - modifier = Modifier - .padding( - top = 12.dp, - end = 4.dp - ) - .width(216.dp), - title = attachment.wrapper.title, - type = attachment.wrapper.type, - icon = attachment.wrapper.icon, - onAttachmentClicked = { - // TODO - } - ) - Image( - painter = painterResource(id = R.drawable.ic_clear_chatbox_attachment), - contentDescription = "Close icon", - modifier = Modifier - .align( - Alignment.TopEnd - ) - .padding(top = 6.dp) - .noRippleClickable { - onClearAttachmentClicked(attachment) - } - ) - } - } - } - is ChatView.Message.ChatBoxAttachment.Media -> { - item { - Box(modifier = Modifier.padding()) { - Image( - painter = rememberAsyncImagePainter(attachment.uri), - contentDescription = null, - modifier = Modifier - .padding( - top = 12.dp, - end = 4.dp - ) - .size(72.dp) - .clip(RoundedCornerShape(8.dp)) - - , - contentScale = ContentScale.Crop - ) - Image( - painter = painterResource(R.drawable.ic_clear_chatbox_attachment), - contentDescription = "Clear attachment icon", - modifier = Modifier - .align(Alignment.TopEnd) - .padding(top = 6.dp) - .noRippleClickable { - onClearAttachmentClicked(attachment) - } - ) - } - } - } - is ChatView.Message.ChatBoxAttachment.File -> { - item { - Box { - AttachedObject( - modifier = Modifier - .padding( - top = 12.dp, - end = 4.dp - ) - .width(216.dp), - title = attachment.name, - type = stringResource(R.string.file), - icon = ObjectIcon.File( - mime = null, - fileName = null - ), - onAttachmentClicked = { - // TODO - } - ) - Image( - painter = painterResource(id = R.drawable.ic_clear_chatbox_attachment), - contentDescription = "Close icon", - modifier = Modifier - .align( - Alignment.TopEnd - ) - .padding(top = 6.dp) - .noRippleClickable { - onClearAttachmentClicked(attachment) - } - ) - } - } - } - } - } - } + ChatBoxAttachments( + attachments = attachments, + onClearAttachmentClicked = onClearAttachmentClicked + ) when(mode) { is ChatBoxMode.Default -> { @@ -396,11 +295,18 @@ fun ChatBox( modifier = Modifier .padding(horizontal = 4.dp, vertical = 8.dp) .clip(CircleShape) - .clickable { - onMessageSent(text.text, spans) - clearText() - resetScroll() - } + .then( + if (mode.isSendingMessageBlocked) { + Modifier + } else { + Modifier + .clickable { + onMessageSent(text.text, spans) + clearText() + resetScroll() + } + } + ) ) { Image( painter = painterResource(id = R.drawable.ic_send_message), @@ -408,6 +314,7 @@ fun ChatBox( modifier = Modifier .align(Alignment.Center) .padding(horizontal = 4.dp, vertical = 4.dp) + .alpha(if (mode.isSendingMessageBlocked) 0.5f else 1f) ) } } diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBoxAttachments.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBoxAttachments.kt new file mode 100644 index 0000000000..3c0ed5526d --- /dev/null +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBoxAttachments.kt @@ -0,0 +1,233 @@ +package com.anytypeio.anytype.feature_chats.ui + +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.BoxScope +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +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.unit.dp +import coil.compose.rememberAsyncImagePainter +import com.anytypeio.anytype.core_ui.common.DefaultPreviews +import com.anytypeio.anytype.core_ui.foundation.noRippleClickable +import com.anytypeio.anytype.feature_chats.R +import com.anytypeio.anytype.feature_chats.presentation.ChatReactionViewModel +import com.anytypeio.anytype.feature_chats.presentation.ChatView +import com.anytypeio.anytype.presentation.objects.ObjectIcon + +@Composable +internal fun ChatBoxAttachments( + attachments: List, + onClearAttachmentClicked: (ChatView.Message.ChatBoxAttachment) -> Unit +) { + LazyRow( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + attachments.forEach { attachment -> + when (attachment) { + is ChatView.Message.ChatBoxAttachment.Link -> { + item { + Box { + AttachedObject( + modifier = Modifier + .padding( + top = 12.dp, + end = 4.dp + ) + .width(216.dp), + title = attachment.wrapper.title, + type = attachment.wrapper.type, + icon = attachment.wrapper.icon, + onAttachmentClicked = { + // TODO + } + ) + Image( + painter = painterResource(id = R.drawable.ic_clear_chatbox_attachment), + contentDescription = "Close icon", + modifier = Modifier + .align( + Alignment.TopEnd + ) + .padding(top = 6.dp) + .noRippleClickable { + onClearAttachmentClicked(attachment) + } + ) + } + } + } + + is ChatView.Message.ChatBoxAttachment.Media -> { + item { + Box(modifier = Modifier.padding()) { + Image( + painter = rememberAsyncImagePainter(attachment.uri), + contentDescription = null, + modifier = Modifier + .padding( + top = 12.dp, + end = 4.dp + ) + .size(72.dp) + .clip(RoundedCornerShape(8.dp)) + .alpha( + if (attachment.state is ChatView.Message.ChatBoxAttachment.State.Uploading) { + 0.3f + } else { + 1f + } + ) + , + contentScale = ContentScale.Crop + ) + Image( + painter = painterResource(R.drawable.ic_clear_chatbox_attachment), + contentDescription = "Clear attachment icon", + modifier = Modifier + .align(Alignment.TopEnd) + .padding(top = 6.dp) + .noRippleClickable { + onClearAttachmentClicked(attachment) + } + ) + ChatBoxAttachmentState(attachment.state) + } + } + } + + is ChatView.Message.ChatBoxAttachment.File -> { + item { + Box { + AttachedObject( + modifier = Modifier + .padding( + top = 12.dp, + end = 4.dp + ) + .width(216.dp), + title = attachment.name, + type = stringResource(R.string.file), + icon = ObjectIcon.File( + mime = null, + fileName = null + ), + onAttachmentClicked = { + // TODO + } + ) + Image( + painter = painterResource(id = R.drawable.ic_clear_chatbox_attachment), + contentDescription = "Close icon", + modifier = Modifier + .align( + Alignment.TopEnd + ) + .padding(top = 6.dp) + .noRippleClickable { + onClearAttachmentClicked(attachment) + } + ) + ChatBoxAttachmentState(attachment.state) + } + } + } + } + } + } +} + +@Composable +private fun BoxScope.ChatBoxAttachmentState(state: ChatView.Message.ChatBoxAttachment.State) { + when (state) { + ChatView.Message.ChatBoxAttachment.State.Failed -> { + Image( + painter = painterResource(R.drawable.ic_chat_box_attachment_error_circle), + contentDescription = null, + modifier = Modifier + .padding( + top = 12.dp, + end = 4.dp + ) + .align(alignment = Alignment.Center) + ) + } + + ChatView.Message.ChatBoxAttachment.State.Uploaded -> { + // Do nothing. + } + ChatView.Message.ChatBoxAttachment.State.Uploading -> { + CircularProgressIndicator( + modifier = Modifier + .padding( + top = 12.dp, + end = 4.dp + ) + .align(alignment = Alignment.Center) + .size(36.dp), + color = colorResource(R.color.glyph_active), + trackColor = colorResource(R.color.glyph_active).copy(alpha = 0.5f), + strokeWidth = 4.dp + ) + } + + ChatView.Message.ChatBoxAttachment.State.Idle -> { + // Do nothing. + } + } +} + +@DefaultPreviews +@Composable +fun ChatBoxAttachmentsInUploadingStatePreview() { + ChatBoxAttachments( + attachments = listOf( + ChatView.Message.ChatBoxAttachment.Media( + state = ChatView.Message.ChatBoxAttachment.State.Uploading, + uri = "Uri" + ), + ChatView.Message.ChatBoxAttachment.Media( + state = ChatView.Message.ChatBoxAttachment.State.Uploading, + uri = "Uri" + ) + ), + onClearAttachmentClicked = {} + ) +} + +@DefaultPreviews +@Composable +fun ChatBoxAttachmentsInFailedStatePreview() { + ChatBoxAttachments( + attachments = listOf( + ChatView.Message.ChatBoxAttachment.Media( + state = ChatView.Message.ChatBoxAttachment.State.Failed, + uri = "Uri" + ), + ChatView.Message.ChatBoxAttachment.Media( + state = ChatView.Message.ChatBoxAttachment.State.Failed, + uri = "Uri" + ) + ), + onClearAttachmentClicked = {} + ) +} \ No newline at end of file diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatPreviews.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatPreviews.kt index 99f2640af9..39fc762e9f 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatPreviews.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatPreviews.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.lazy.LazyListState import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview +import com.anytypeio.anytype.core_ui.common.DefaultPreviews import com.anytypeio.anytype.feature_chats.R import com.anytypeio.anytype.feature_chats.presentation.ChatView import com.anytypeio.anytype.feature_chats.presentation.ChatViewModel @@ -116,7 +117,7 @@ fun ChatScreenPreview() { onMarkupLinkClicked = {}, onAttachObjectClicked = {}, onReplyMessage = {}, - chatBoxMode = ChatViewModel.ChatBoxMode.Default, + chatBoxMode = ChatViewModel.ChatBoxMode.Default(), onClearReplyClicked = {}, onChatBoxMediaPicked = {}, onChatBoxFilePicked = {}, @@ -188,8 +189,7 @@ fun BubbleEditedPreview() { ) } -@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, name = "Light Mode") -@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO, name = "Dark Mode") +@DefaultPreviews @Composable fun BubbleWithAttachmentPreview() { Bubble( diff --git a/feature-chats/src/main/res/drawable/ic_chat_box_attachment_error_circle.xml b/feature-chats/src/main/res/drawable/ic_chat_box_attachment_error_circle.xml new file mode 100644 index 0000000000..362269759a --- /dev/null +++ b/feature-chats/src/main/res/drawable/ic_chat_box_attachment_error_circle.xml @@ -0,0 +1,17 @@ + + + + + From 0238d77c658e0270b7fc71185acb9547775ab6fc Mon Sep 17 00:00:00 2001 From: "Any Association [bot]" <139948520+any-association@users.noreply.github.com> Date: Tue, 11 Feb 2025 19:29:46 +0100 Subject: [PATCH 27/78] l10n | Enhancement (#2085) Co-authored-by: Evgenii Kozlov --- localization/src/main/res/values-de-rDE/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/localization/src/main/res/values-de-rDE/strings.xml b/localization/src/main/res/values-de-rDE/strings.xml index 6cd9445c45..3c65f007af 100644 --- a/localization/src/main/res/values-de-rDE/strings.xml +++ b/localization/src/main/res/values-de-rDE/strings.xml @@ -173,7 +173,7 @@ Block wechseln Link einfügen Leer. Tippen, um einen neuen Block zu erstellen - Untitled + Unbenannt Unbenanntes Set Unbenannte Sammlung Archiviert From 24f7f62bb425557cfe478b2f472104cedd478c54 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Wed, 12 Feb 2025 12:33:07 +0100 Subject: [PATCH 28/78] Revert "l10n | Enhancement (#2075)" This reverts commit 316d772472ef58b065a9a62c113a56b9bc47dc97. --- localization/src/main/res/values-ru-rRU/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/localization/src/main/res/values-ru-rRU/strings.xml b/localization/src/main/res/values-ru-rRU/strings.xml index 9e1f98f7c9..fdc647aecc 100644 --- a/localization/src/main/res/values-ru-rRU/strings.xml +++ b/localization/src/main/res/values-ru-rRU/strings.xml @@ -1170,9 +1170,9 @@ ✦ Обновите, чтобы добавить больше участников ✦ Обновите, чтобы добавить больше пространств Участники - Оставить запрос + Запрос на выход Запрос на присоединение - Утверждено + Подтвердить Посмотреть запрос Владелец Can edit From 4b36bf58321969b5a8a179b3a0fc3930657c1bf0 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Wed, 12 Feb 2025 13:25:48 +0100 Subject: [PATCH 29/78] DROID-3232 App | Tech | Script for preparing Android App manifest for APK distribution (#2087) --- .github/workflows/release.yml | 3 +++ Makefile | 5 ++++- scripts/release/apk.sh | 19 +++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100755 scripts/release/apk.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1817fa400d..deaa02d2c7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -55,6 +55,9 @@ jobs: -Pcom.anytype.ci=true \ -Dorg.gradle.unsafe.configuration-cache=false" + - name: Prepare Android Manifest for APKs + run: ./scripts/release/apk.sh + - name: Build release APKS run: ./gradlew :app:assembleRelease diff --git a/Makefile b/Makefile index 29ce2c0d3f..5c2c525cd1 100644 --- a/Makefile +++ b/Makefile @@ -47,4 +47,7 @@ clean_protos: update_mw: download_mw_artefacts normalize_mw_imports clean_protos # Update mw from custom build (download only library, you have to update your proto files manually) -update_mw_custom: download_mw_artefacts_custom \ No newline at end of file +update_mw_custom: download_mw_artefacts_custom + +prepare_app_manifest_for_release_apk: + ./scripts/release/apk.sh diff --git a/scripts/release/apk.sh b/scripts/release/apk.sh new file mode 100755 index 0000000000..bd5543ab10 --- /dev/null +++ b/scripts/release/apk.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Script for preparing Android Manifest for APK distribution. + +# Define the file to be modified +FILE="app/src/main/AndroidManifest.xml" + +# Check if the system is macOS or Linux +if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS (sed requires an empty string argument for in-place editing) + sed -i '' 's|||' "$FILE" + sed -i '' 's|||' "$FILE" +else + # Linux (sed works without the empty string argument for in-place editing) + sed -i 's|||' "$FILE" + sed -i 's|||' "$FILE" +fi + +echo "Lines uncommented successfully." \ No newline at end of file From b6242bf2d76eda7079d43d0b7fe8a52ea23dc2ca Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Wed, 12 Feb 2025 21:18:42 +0100 Subject: [PATCH 30/78] DROID-3385 Protocol | Enhancement | MW 0.39.9 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ced32ca4d0..e5b822f3d6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -middlewareVersion = "v0.39.6" +middlewareVersion = "v0.39.9" kotlinVersion = '2.0.21' kspVersion = "2.0.21-1.0.25" From 2a114b604afca555e1d86a3b32ad9cceb030906b Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Thu, 13 Feb 2025 13:39:26 +0100 Subject: [PATCH 31/78] DROID-3387 Protocol | Enhancement | MW 0.39.10 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e5b822f3d6..42043bbff1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -middlewareVersion = "v0.39.9" +middlewareVersion = "v0.39.10" kotlinVersion = '2.0.21' kspVersion = "2.0.21-1.0.25" From 1e67042abf9834faf59e6942ceef1a17264e3de7 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Thu, 13 Feb 2025 16:51:23 +0100 Subject: [PATCH 32/78] DROID-3217 DROID-2876 Space-level chat | Fix | Add (you) in mention menu and in bubble-reactions cards near current user space member card (#2091) --- .../di/feature/chats/ChatReactionDI.kt | 2 + .../presentation/ChatReactionViewModel.kt | 15 ++++- .../presentation/ChatViewModel.kt | 16 ++++- .../feature_chats/ui/ChatReactionScreen.kt | 59 ++++++++++++++++--- .../anytype/feature_chats/ui/ChatScreen.kt | 1 + localization/src/main/res/values/strings.xml | 2 +- 6 files changed, 82 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/chats/ChatReactionDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/chats/ChatReactionDI.kt index 5bb8654b8f..fee6b9ad75 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/chats/ChatReactionDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/chats/ChatReactionDI.kt @@ -3,6 +3,7 @@ package com.anytypeio.anytype.di.feature.chats import androidx.lifecycle.ViewModelProvider import com.anytypeio.anytype.core_utils.di.scope.PerScreen import com.anytypeio.anytype.di.common.ComponentDependencies +import com.anytypeio.anytype.domain.auth.repo.AuthRepository import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers import com.anytypeio.anytype.domain.block.repo.BlockRepository import com.anytypeio.anytype.domain.misc.UrlBuilder @@ -50,6 +51,7 @@ object ChatReactionModule { interface ChatReactionDependencies : ComponentDependencies { fun dispatchers(): AppCoroutineDispatchers fun repo(): BlockRepository + fun auth(): AuthRepository fun urlBuilder(): UrlBuilder fun members(): ActiveSpaceMemberSubscriptionContainer } \ No newline at end of file diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatReactionViewModel.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatReactionViewModel.kt index 0a743d617f..8fe3e3afd5 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatReactionViewModel.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatReactionViewModel.kt @@ -5,7 +5,9 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.anytypeio.anytype.core_models.Command import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.domain.auth.interactor.GetAccount import com.anytypeio.anytype.domain.base.getOrDefault +import com.anytypeio.anytype.domain.base.onFailure import com.anytypeio.anytype.domain.chats.GetChatMessagesByIds import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.multiplayer.ActiveSpaceMemberSubscriptionContainer @@ -15,10 +17,12 @@ import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import timber.log.Timber class ChatReactionViewModel @Inject constructor( private val vmParams: Params, private val getChatMessagesByIds: GetChatMessagesByIds, + private val getAccount: GetAccount, private val members: ActiveSpaceMemberSubscriptionContainer, private val urlBuilder: UrlBuilder ) : BaseViewModel() { @@ -27,6 +31,13 @@ class ChatReactionViewModel @Inject constructor( init { viewModelScope.launch { + val account = getAccount + .async(Unit) + .onFailure { + Timber.e(it, "Failed to get account for chat reaction screen") + } + .getOrNull() + val result = getChatMessagesByIds .async( Command.ChatCommand.GetMessagesByIds( @@ -53,7 +64,7 @@ class ChatReactionViewModel @Inject constructor( urlBuilder = urlBuilder ), name = member.name.orEmpty(), - isUser = false + isUser = account?.id == member.identity ) } else { null @@ -86,6 +97,7 @@ class ChatReactionViewModel @Inject constructor( class Factory @Inject constructor( private val vmParams: Params, private val getChatMessagesByIds: GetChatMessagesByIds, + private val getAccount: GetAccount, private val members: ActiveSpaceMemberSubscriptionContainer, private val urlBuilder: UrlBuilder ) : ViewModelProvider.Factory { @@ -93,6 +105,7 @@ class ChatReactionViewModel @Inject constructor( override fun create(modelClass: Class): T = ChatReactionViewModel( vmParams = vmParams, getChatMessagesByIds = getChatMessagesByIds, + getAccount = getAccount, members = members, urlBuilder = urlBuilder ) as T diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt index b60909e8be..0be7d07737 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt @@ -80,6 +80,8 @@ class ChatViewModel @Inject constructor( private val dateFormatter = SimpleDateFormat("d MMMM YYYY") private val data = MutableStateFlow>(emptyList()) + private var account: Id = "" + init { viewModelScope.launch { spaceViews @@ -98,9 +100,16 @@ class ChatViewModel @Inject constructor( } } viewModelScope.launch { - val account = requireNotNull(getAccount.async(Unit).getOrNull()) + getAccount + .async(Unit) + .onSuccess { acc -> + account = acc.id + } + .onFailure { + Timber.e("Failed to find account for space-level chat") + } proceedWithObservingChatMessages( - account = account.id, + account = account, chat = vmParams.ctx ) } @@ -303,7 +312,8 @@ class ChatViewModel @Inject constructor( icon = SpaceMemberIconView.icon( obj = member, urlBuilder = urlBuilder - ) + ), + isUser = member.identity == account ) }.filter { m -> if (query != null) { diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatReactionScreen.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatReactionScreen.kt index 4542766784..9c38b398dc 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatReactionScreen.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatReactionScreen.kt @@ -16,7 +16,9 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.anytypeio.anytype.core_ui.common.DefaultPreviews import com.anytypeio.anytype.core_ui.features.multiplayer.SpaceMemberIcon @@ -24,6 +26,8 @@ import com.anytypeio.anytype.core_ui.foundation.Divider import com.anytypeio.anytype.core_ui.foundation.Dragger import com.anytypeio.anytype.core_ui.views.BodyCallout import com.anytypeio.anytype.core_ui.views.BodyRegular +import com.anytypeio.anytype.core_ui.views.PreviewTitle2Medium +import com.anytypeio.anytype.core_ui.views.PreviewTitle2Regular import com.anytypeio.anytype.core_ui.views.Relations3 import com.anytypeio.anytype.feature_chats.R import com.anytypeio.anytype.feature_chats.presentation.ChatReactionViewModel.ViewState @@ -67,7 +71,8 @@ fun ChatReactionScreen( val member = viewState.members[idx] ChatMemberItem( name = member.name, - icon = member.icon + icon = member.icon, + isUser = member.isUser ) if (idx != viewState.members.lastIndex) { Divider() @@ -100,7 +105,8 @@ fun ChatReactionScreen( fun ChatMemberItem( modifier: Modifier = Modifier, name: String, - icon: SpaceMemberIconView + icon: SpaceMemberIconView, + isUser: Boolean = false ) { Box( modifier = modifier @@ -121,12 +127,25 @@ fun ChatMemberItem( .align(Alignment.CenterStart) .padding(start = 60.dp) ) { - Text( - text = name.ifEmpty { - stringResource(R.string.untitled) - }, - color = colorResource(R.color.text_primary) - ) + Row { + Text( + text = name.ifEmpty { stringResource(R.string.untitled) }, + color = colorResource(R.color.text_primary), + style = PreviewTitle2Regular, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f, fill = false) + ) + if (isUser) { + Spacer(modifier = Modifier.width(4.dp)) + val youAsMemberText = stringResource(id = R.string.multiplayer_you_as_member) + Text( + text = "($youAsMemberText)", + style = PreviewTitle2Regular, + color = colorResource(id = R.color.text_secondary), + ) + } + } Text( text = stringResource(R.string.object_types_human), style = Relations3, @@ -207,6 +226,30 @@ private fun MemberPreview() { ) } +@DefaultPreviews +@Composable +private fun MemberUserPreview() { + ChatMemberItem( + name = "Walter Benjamin Walter Walter Walter Walter", + icon = SpaceMemberIconView.Placeholder( + name = "Walter" + ), + isUser = true + ) +} + +@DefaultPreviews +@Composable +private fun MemberOverflowPreview() { + ChatMemberItem( + name = "Walter Benjamin Walter Walter Walter Walter", + icon = SpaceMemberIconView.Placeholder( + name = "Walter" + ), + isUser = true + ) +} + @DefaultPreviews @Composable private fun EmojiToolbarPreview() { diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt index aa3e7c0a7b..6cf27cc3ee 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt @@ -352,6 +352,7 @@ fun ChatScreen( ChatMemberItem( name = member.name, icon = member.icon, + isUser = member.isUser, modifier = Modifier .fillMaxWidth() .noRippleClickable { diff --git a/localization/src/main/res/values/strings.xml b/localization/src/main/res/values/strings.xml index 7c6761eec4..dfa192f112 100644 --- a/localization/src/main/res/values/strings.xml +++ b/localization/src/main/res/values/strings.xml @@ -1439,7 +1439,7 @@ %1$d requests - you + You Spaces From bd52b38b4b6e24d2ff56e794ae68515500af0a56 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Thu, 13 Feb 2025 17:54:35 +0100 Subject: [PATCH 33/78] DROID-2893 Space-level chat | Enhancement | Restrict max user reaction count per bubble (#2093) --- .../feature_chats/presentation/ChatView.kt | 5 +++ .../presentation/ChatViewModel.kt | 18 ++++++---- .../anytype/feature_chats/ui/ChatBubble.kt | 6 ++-- .../anytype/feature_chats/ui/ChatScreen.kt | 1 + .../anytype/feature_chats/ui/Reactions.kt | 35 +++++++++++++++---- .../src/main/res/drawable/ic_add_reaction.xml | 10 ++++++ .../anytype/presentation/confgs/Confgs.kt | 3 ++ 7 files changed, 63 insertions(+), 15 deletions(-) create mode 100644 feature-chats/src/main/res/drawable/ic_add_reaction.xml diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatView.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatView.kt index 3412c276e3..2e8faf1ff8 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatView.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatView.kt @@ -4,6 +4,7 @@ import com.anytypeio.anytype.core_models.Block import com.anytypeio.anytype.core_models.Hash import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.ObjectWrapper +import com.anytypeio.anytype.presentation.confgs.ChatConfig import com.anytypeio.anytype.presentation.objects.ObjectIcon import com.anytypeio.anytype.presentation.search.GlobalSearchItemView @@ -28,6 +29,10 @@ sealed interface ChatView { val reply: Reply? = null ) : ChatView { + val isMaxReactionCountReached: Boolean = + reactions.size >= ChatConfig.MAX_REACTION_COUNT || + reactions.count { it.isSelected } >= ChatConfig.MAX_USER_REACTION_COUNT + data class Content(val msg: String, val parts: List) { data class Part( val part: String, diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt index 0be7d07737..d2b7511cb7 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt @@ -27,6 +27,7 @@ import com.anytypeio.anytype.domain.multiplayer.SpaceViewSubscriptionContainer import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.feature_chats.BuildConfig import com.anytypeio.anytype.presentation.common.BaseViewModel +import com.anytypeio.anytype.presentation.confgs.ChatConfig import com.anytypeio.anytype.presentation.home.OpenObjectNavigation import com.anytypeio.anytype.presentation.home.navigation import com.anytypeio.anytype.presentation.mapper.objectIcon @@ -202,13 +203,16 @@ class ChatViewModel @Inject constructor( creator = member?.id, isUserAuthor = msg.creator == account, isEdited = msg.modifiedAt > msg.createdAt, - reactions = msg.reactions.map { (emoji, ids) -> - ChatView.Message.Reaction( - emoji = emoji, - count = ids.size, - isSelected = ids.contains(account) - ) - }, + reactions = msg.reactions + .map { (emoji, ids) -> + ChatView.Message.Reaction( + emoji = emoji, + count = ids.size, + isSelected = ids.contains(account) + ) + } + .take(ChatConfig.MAX_REACTION_COUNT) + , attachments = msg.attachments.map { attachment -> when (attachment.type) { Chat.Message.Attachment.Type.Image -> { diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBubble.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBubble.kt index 8d78fa0563..9f99c47b91 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBubble.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBubble.kt @@ -51,7 +51,6 @@ import androidx.compose.ui.unit.sp import coil.compose.AsyncImage import coil.request.ImageRequest import com.anytypeio.anytype.core_models.Id -import com.anytypeio.anytype.core_ui.extensions.throttledClick import com.anytypeio.anytype.core_ui.foundation.AlertConfig import com.anytypeio.anytype.core_ui.foundation.BUTTON_SECONDARY import com.anytypeio.anytype.core_ui.foundation.BUTTON_WARNING @@ -80,6 +79,7 @@ fun Bubble( attachments: List = emptyList(), isUserAuthor: Boolean = false, isEdited: Boolean = false, + isMaxReactionCountReached: Boolean = false, reactions: List = emptyList(), onReacted: (String) -> Unit, onDeleteMessage: () -> Unit, @@ -303,7 +303,9 @@ fun Bubble( ReactionList( reactions = reactions, onReacted = onReacted, - onViewReaction = onViewChatReaction + onViewReaction = onViewChatReaction, + onAddNewReaction = onAddReactionClicked, + isMaxReactionCountReached = isMaxReactionCountReached ) } MaterialTheme( diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt index 6cf27cc3ee..7f64c22595 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt @@ -519,6 +519,7 @@ fun Messages( timestamp = msg.timestamp, attachments = msg.attachments, isUserAuthor = msg.isUserAuthor, + isMaxReactionCountReached = msg.isMaxReactionCountReached, isEdited = msg.isEdited, onReacted = { emoji -> onReacted(msg.id, emoji) diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/Reactions.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/Reactions.kt index 33b211cfaa..b658e185c3 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/Reactions.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/Reactions.kt @@ -1,9 +1,10 @@ package com.anytypeio.anytype.feature_chats.ui -import android.content.res.Configuration import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border +import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -11,7 +12,9 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Text import androidx.compose.runtime.Composable @@ -19,8 +22,9 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.colorResource -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp +import com.anytypeio.anytype.core_ui.common.DefaultPreviews import com.anytypeio.anytype.core_ui.views.BodyCalloutMedium import com.anytypeio.anytype.core_ui.views.Caption1Regular import com.anytypeio.anytype.feature_chats.R @@ -31,7 +35,9 @@ import com.anytypeio.anytype.feature_chats.presentation.ChatView fun ReactionList( reactions: List, onReacted: (String) -> Unit, - onViewReaction: (String) -> Unit + onViewReaction: (String) -> Unit, + onAddNewReaction: () -> Unit, + isMaxReactionCountReached: Boolean = false, ) { FlowRow( modifier = Modifier.padding(start = 12.dp, end = 12.dp, bottom = 12.dp, top = 4.dp), @@ -95,11 +101,27 @@ fun ReactionList( ) } } + if (!isMaxReactionCountReached) { + Box( + modifier = Modifier + .padding(end = 8.dp) + .size(28.dp) + .clip(CircleShape) + .clickable { + onAddNewReaction() + } + ) { + Image( + painter = painterResource(R.drawable.ic_add_reaction), + contentDescription = null, + modifier = Modifier.align(Alignment.Center) + ) + } + } } } -@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, name = "Light Mode") -@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO, name = "Dark Mode") +@DefaultPreviews @Composable fun ReactionListPreview() { ReactionList( @@ -136,6 +158,7 @@ fun ReactionListPreview() { ) ), onReacted = {}, - onViewReaction = {} + onViewReaction = {}, + onAddNewReaction = {} ) } \ No newline at end of file diff --git a/feature-chats/src/main/res/drawable/ic_add_reaction.xml b/feature-chats/src/main/res/drawable/ic_add_reaction.xml new file mode 100644 index 0000000000..643bc54fc1 --- /dev/null +++ b/feature-chats/src/main/res/drawable/ic_add_reaction.xml @@ -0,0 +1,10 @@ + + + diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/confgs/Confgs.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/confgs/Confgs.kt index bda7c228af..f97e6e16ef 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/confgs/Confgs.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/confgs/Confgs.kt @@ -4,7 +4,10 @@ import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.presentation.BuildConfig object ChatConfig { + const val MAX_ATTACHMENT_COUNT = 10 + const val MAX_USER_REACTION_COUNT = 3 + const val MAX_REACTION_COUNT = 12 /** * Spaces for beta-testing space-level chats From 20a95fbaeae7ef08cb1aff47a8f8cd8c1b800acb Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Thu, 13 Feb 2025 18:36:45 +0100 Subject: [PATCH 34/78] DROID-2893 Space-level chat | Enhancement | Enforce max character limit for chat message (#2094) --- .../anytype/feature_chats/ui/ChatBox.kt | 74 ++++++++++++++----- .../anytype/presentation/confgs/Confgs.kt | 3 + 2 files changed, 58 insertions(+), 19 deletions(-) diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBox.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBox.kt index f054de2a65..bc80dca5d1 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBox.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBox.kt @@ -11,8 +11,8 @@ import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.border 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.Row @@ -20,16 +20,12 @@ import androidx.compose.foundation.layout.defaultMinSize 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.width -import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.material.DropdownMenu import androidx.compose.material.MaterialTheme import androidx.compose.material.Text -import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.DropdownMenuItem import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -44,7 +40,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource @@ -53,10 +48,9 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp -import coil.compose.rememberAsyncImagePainter -import com.anytypeio.anytype.core_ui.common.DefaultPreviews +import com.anytypeio.anytype.core_ui.common.DEFAULT_DISABLED_ALPHA +import com.anytypeio.anytype.core_ui.common.FULL_ALPHA import com.anytypeio.anytype.core_ui.foundation.Divider -import com.anytypeio.anytype.core_ui.foundation.noRippleClickable import com.anytypeio.anytype.core_ui.views.BodyRegular import com.anytypeio.anytype.core_ui.views.Caption1Medium import com.anytypeio.anytype.core_ui.views.Caption1Regular @@ -64,8 +58,6 @@ import com.anytypeio.anytype.feature_chats.R import com.anytypeio.anytype.feature_chats.presentation.ChatView import com.anytypeio.anytype.feature_chats.presentation.ChatViewModel.ChatBoxMode import com.anytypeio.anytype.presentation.confgs.ChatConfig -import com.anytypeio.anytype.presentation.objects.ObjectIcon -import kotlin.collections.forEach import kotlinx.coroutines.launch import timber.log.Timber @@ -90,6 +82,8 @@ fun ChatBox( onValueChange: (TextFieldValue, List) -> Unit ) { + val length = text.text.length + val uploadMediaLauncher = rememberLauncherForActivityResult( ActivityResultContracts.PickMultipleVisualMedia(maxItems = ChatConfig.MAX_ATTACHMENT_COUNT) ) { @@ -276,15 +270,52 @@ fun ChatBox( } } } - ChatBoxUserInput( - text = text, - spans = spans, - onValueChange = onValueChange, + Box( modifier = Modifier .weight(1f) .align(Alignment.Bottom) - .focusRequester(chatBoxFocusRequester) - ) + ) { + ChatBoxUserInput( + text = text, + spans = spans, + onValueChange = onValueChange, + modifier = Modifier + .fillMaxWidth() + .focusRequester(chatBoxFocusRequester) + ) + if (length >= ChatConfig.MAX_MESSAGE_CHARACTER_OFFSET_LIMIT) { + Box( + modifier = Modifier + .padding(top = 8.dp) + .background( + color = colorResource(R.color.background_secondary), + shape = RoundedCornerShape(100.dp) + ) + .clip(RoundedCornerShape(100.dp)) + .border( + color = colorResource(R.color.shape_tertiary), + width = 1.dp, + shape = RoundedCornerShape(100.dp) + ) + .align(Alignment.TopCenter) + ) { + Text( + text = "${text.text.length} / ${ChatConfig.MAX_MESSAGE_CHARACTER_LIMIT}", + modifier = Modifier.padding( + horizontal = 8.dp, + vertical = 3.dp + ).align( + Alignment.Center + ), + style = Caption1Regular, + color = if (length > ChatConfig.MAX_MESSAGE_CHARACTER_LIMIT) + colorResource(R.color.palette_system_red) + else + colorResource(R.color.text_primary) + ) + } + } + } AnimatedVisibility( visible = attachments.isNotEmpty() || text.text.isNotEmpty(), exit = fadeOut() + scaleOut(), @@ -296,7 +327,7 @@ fun ChatBox( .padding(horizontal = 4.dp, vertical = 8.dp) .clip(CircleShape) .then( - if (mode.isSendingMessageBlocked) { + if (mode.isSendingMessageBlocked || length > ChatConfig.MAX_MESSAGE_CHARACTER_LIMIT) { Modifier } else { Modifier @@ -314,7 +345,12 @@ fun ChatBox( modifier = Modifier .align(Alignment.Center) .padding(horizontal = 4.dp, vertical = 4.dp) - .alpha(if (mode.isSendingMessageBlocked) 0.5f else 1f) + .alpha( + if (mode.isSendingMessageBlocked || length > ChatConfig.MAX_MESSAGE_CHARACTER_LIMIT) + 0.3f + else + FULL_ALPHA + ) ) } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/confgs/Confgs.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/confgs/Confgs.kt index f97e6e16ef..55c9e87952 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/confgs/Confgs.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/confgs/Confgs.kt @@ -9,6 +9,9 @@ object ChatConfig { const val MAX_USER_REACTION_COUNT = 3 const val MAX_REACTION_COUNT = 12 + const val MAX_MESSAGE_CHARACTER_LIMIT = 2000 + const val MAX_MESSAGE_CHARACTER_OFFSET_LIMIT = 1950 + /** * Spaces for beta-testing space-level chats */ From 73cd7aba941b002aca5f0c5bcf69467683cf9771 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Fri, 14 Feb 2025 11:17:02 +0100 Subject: [PATCH 35/78] DROID-3317 Account | Tech | Space store migration + UI (#2074) --- .../anytype/di/common/ComponentManager.kt | 7 - .../anytypeio/anytype/di/feature/SplashDi.kt | 7 + .../login/OnboardingMnemonicLoginDI.kt | 7 + .../di/feature/update/MigrationErrorDI.kt | 48 --- .../anytype/di/main/MainComponent.kt | 9 +- .../anytypeio/anytype/navigation/Navigator.kt | 12 - .../anytype/ui/base/NavigationRouter.kt | 2 - .../ui/onboarding/OnboardingFragment.kt | 15 - .../OnboardingRecoveryPhraseLoginScreen.kt | 37 ++- .../anytype/ui/splash/SplashFragment.kt | 54 ++-- .../ui/update/MigrationErrorFragment.kt | 82 ----- .../anytype/ui/update/MigrationErrorScreen.kt | 304 +++++++----------- app/src/main/res/layout/fragment_splash.xml | 12 +- app/src/main/res/navigation/graph.xml | 6 - .../exceptions/AccountIsDeletedException.kt | 8 +- .../data/auth/repo/AuthCacheDataStore.kt | 12 + .../data/auth/repo/AuthDataRepository.kt | 17 + .../anytype/data/auth/repo/AuthDataStore.kt | 4 + .../anytype/data/auth/repo/AuthRemote.kt | 5 + .../data/auth/repo/AuthRemoteDataStore.kt | 17 + .../auth/interactor/CancelAccountMigration.kt | 34 ++ .../domain/auth/interactor/MigrateAccount.kt | 40 +++ .../domain/auth/repo/AuthRepository.kt | 4 + gradle/libs.versions.toml | 2 +- localization/src/main/res/values/strings.xml | 5 + .../anytype/middleware/auth/AuthMiddleware.kt | 17 + .../middleware/interactor/Middleware.kt | 19 ++ .../middleware/service/MiddlewareService.kt | 6 + .../MiddlewareServiceImplementation.kt | 32 ++ .../auth/account/MigrationHelperDelegate.kt | 52 +++ .../presentation/navigation/AppNavigation.kt | 8 - .../login/OnboardingMnemonicLoginViewModel.kt | 67 +++- .../presentation/splash/SplashViewModel.kt | 85 +++-- .../splash/SplashViewModelFactory.kt | 7 +- .../update/MigrationErrorViewModel.kt | 114 ------- .../splash/SplashViewModelTest.kt | 7 +- protocol/src/main/proto/commands.proto | 49 +++ protocol/src/main/proto/events.proto | 2 +- 38 files changed, 643 insertions(+), 572 deletions(-) delete mode 100644 app/src/main/java/com/anytypeio/anytype/di/feature/update/MigrationErrorDI.kt delete mode 100644 app/src/main/java/com/anytypeio/anytype/ui/update/MigrationErrorFragment.kt create mode 100644 domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/CancelAccountMigration.kt create mode 100644 domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/MigrateAccount.kt create mode 100644 presentation/src/main/java/com/anytypeio/anytype/presentation/auth/account/MigrationHelperDelegate.kt delete mode 100644 presentation/src/main/java/com/anytypeio/anytype/presentation/update/MigrationErrorViewModel.kt diff --git a/app/src/main/java/com/anytypeio/anytype/di/common/ComponentManager.kt b/app/src/main/java/com/anytypeio/anytype/di/common/ComponentManager.kt index 73319f70e3..4ea9d1dcd8 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/common/ComponentManager.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/common/ComponentManager.kt @@ -96,7 +96,6 @@ import com.anytypeio.anytype.di.feature.templates.DaggerTemplateSelectComponent import com.anytypeio.anytype.di.feature.types.DaggerCreateObjectTypeComponent import com.anytypeio.anytype.di.feature.types.DaggerTypeEditComponent import com.anytypeio.anytype.di.feature.types.DaggerTypeIconPickComponent -import com.anytypeio.anytype.di.feature.update.DaggerMigrationErrorComponent import com.anytypeio.anytype.di.feature.vault.DaggerVaultComponent import com.anytypeio.anytype.di.feature.wallpaper.WallpaperSelectModule import com.anytypeio.anytype.di.feature.widgets.DaggerSelectWidgetSourceComponent @@ -834,12 +833,6 @@ class ComponentManager( .create(findComponentDependencies()) } - val migrationErrorComponent = Component { - DaggerMigrationErrorComponent - .factory() - .create(findComponentDependencies()) - } - val onboardingComponent = Component { DaggerOnboardingComponent .factory() diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/SplashDi.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/SplashDi.kt index 62cdf8fffe..784dd274fc 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/SplashDi.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/SplashDi.kt @@ -33,6 +33,7 @@ import com.anytypeio.anytype.domain.subscriptions.GlobalSubscriptionManager import com.anytypeio.anytype.domain.templates.GetTemplates import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate +import com.anytypeio.anytype.presentation.auth.account.MigrationHelperDelegate import com.anytypeio.anytype.presentation.splash.SplashViewModelFactory import com.anytypeio.anytype.ui.splash.SplashFragment import dagger.Binds @@ -178,6 +179,12 @@ object SplashModule { @PerScreen @Binds fun bindViewModelFactory(factory: SplashViewModelFactory): ViewModelProvider.Factory + + @Binds + @PerScreen + fun bindMigrationHelperDelegate( + impl: MigrationHelperDelegate.Impl + ): MigrationHelperDelegate } } diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/login/OnboardingMnemonicLoginDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/login/OnboardingMnemonicLoginDI.kt index 65b6d247a8..a2d8ee263c 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/login/OnboardingMnemonicLoginDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/login/OnboardingMnemonicLoginDI.kt @@ -22,6 +22,7 @@ import com.anytypeio.anytype.domain.platform.InitialParamsProvider import com.anytypeio.anytype.domain.spaces.SpaceDeletedStatusWatcher import com.anytypeio.anytype.domain.subscriptions.GlobalSubscriptionManager import com.anytypeio.anytype.domain.workspace.SpaceManager +import com.anytypeio.anytype.presentation.auth.account.MigrationHelperDelegate import com.anytypeio.anytype.presentation.onboarding.login.OnboardingMnemonicLoginViewModel import com.anytypeio.anytype.presentation.util.downloader.UriFileProvider import com.anytypeio.anytype.providers.DefaultUriFileProvider @@ -74,6 +75,12 @@ object OnboardingMnemonicLoginModule { defaultProvider: DefaultUriFileProvider ): UriFileProvider + @Binds + @PerScreen + fun bindMigrationHelperDelegate( + impl: MigrationHelperDelegate.Impl + ): MigrationHelperDelegate + @Binds @PerScreen fun bindViewModelFactory( diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/update/MigrationErrorDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/update/MigrationErrorDI.kt deleted file mode 100644 index 2757859829..0000000000 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/update/MigrationErrorDI.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.anytypeio.anytype.di.feature.update - -import androidx.lifecycle.ViewModelProvider -import com.anytypeio.anytype.analytics.base.Analytics -import com.anytypeio.anytype.core_utils.di.scope.PerScreen -import com.anytypeio.anytype.di.common.ComponentDependencies -import com.anytypeio.anytype.presentation.update.MigrationErrorViewModel -import com.anytypeio.anytype.ui.update.MigrationErrorFragment -import dagger.Binds -import dagger.Component -import dagger.Module - -@Component( - dependencies = [MigrationErrorDependencies::class], - modules = [ - MigrationErrorModule::class, - MigrationErrorModule.Declarations::class - ] -) -@PerScreen -interface MigrationErrorComponent { - - @Component.Factory - interface Factory { - fun create(dependencies: MigrationErrorDependencies): MigrationErrorComponent - } - - fun inject(fragment: MigrationErrorFragment) -} - -@Module -object MigrationErrorModule { - - @Module - interface Declarations { - - @PerScreen - @Binds - fun bindViewModelFactory( - factory: MigrationErrorViewModel.Factory - ): ViewModelProvider.Factory - - } -} - -interface MigrationErrorDependencies : ComponentDependencies { - fun analytics(): Analytics -} \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/di/main/MainComponent.kt b/app/src/main/java/com/anytypeio/anytype/di/main/MainComponent.kt index d8e7fc314a..6a30bb8046 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/main/MainComponent.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/main/MainComponent.kt @@ -20,9 +20,9 @@ import com.anytypeio.anytype.di.feature.ObjectTypeChangeSubComponent import com.anytypeio.anytype.di.feature.PersonalizationSettingsSubComponent import com.anytypeio.anytype.di.feature.SplashDependencies import com.anytypeio.anytype.di.feature.auth.DeletedAccountDependencies +import com.anytypeio.anytype.di.feature.chats.ChatComponentDependencies import com.anytypeio.anytype.di.feature.chats.ChatReactionDependencies import com.anytypeio.anytype.di.feature.chats.SelectChatReactionDependencies -import com.anytypeio.anytype.di.feature.chats.ChatComponentDependencies import com.anytypeio.anytype.di.feature.gallery.GalleryInstallationComponentDependencies import com.anytypeio.anytype.di.feature.home.HomeScreenDependencies import com.anytypeio.anytype.di.feature.membership.MembershipComponentDependencies @@ -57,7 +57,6 @@ import com.anytypeio.anytype.di.feature.templates.TemplateSelectDependencies import com.anytypeio.anytype.di.feature.types.CreateObjectTypeDependencies import com.anytypeio.anytype.di.feature.types.TypeEditDependencies import com.anytypeio.anytype.di.feature.types.TypeIconPickDependencies -import com.anytypeio.anytype.di.feature.update.MigrationErrorDependencies import com.anytypeio.anytype.di.feature.vault.VaultComponentDependencies import com.anytypeio.anytype.di.feature.wallpaper.WallpaperSelectSubComponent import com.anytypeio.anytype.di.feature.widgets.SelectWidgetSourceDependencies @@ -104,7 +103,6 @@ interface MainComponent : RelationEditDependencies, SplashDependencies, DeletedAccountDependencies, - MigrationErrorDependencies, BacklinkOrAddToObjectDependencies, FilesStorageDependencies, OnboardingDependencies, @@ -218,11 +216,6 @@ abstract class ComponentDependenciesModule { @ComponentDependenciesKey(DeletedAccountDependencies::class) abstract fun provideDeletedAccountDependencies(component: MainComponent): ComponentDependencies - @Binds - @IntoMap - @ComponentDependenciesKey(MigrationErrorDependencies::class) - abstract fun migrationErrorDependencies(component: MainComponent): ComponentDependencies - @Binds @IntoMap @ComponentDependenciesKey(BacklinkOrAddToObjectDependencies::class) diff --git a/app/src/main/java/com/anytypeio/anytype/navigation/Navigator.kt b/app/src/main/java/com/anytypeio/anytype/navigation/Navigator.kt index 055260ce89..0fd5654a97 100644 --- a/app/src/main/java/com/anytypeio/anytype/navigation/Navigator.kt +++ b/app/src/main/java/com/anytypeio/anytype/navigation/Navigator.kt @@ -258,18 +258,6 @@ class Navigator : AppNavigation { navController?.navigate(R.id.actionLogout) } - override fun migrationErrorScreen() { - navController?.navigate(R.id.migrationNeededScreen) - } - - override fun exitFromMigrationScreen() { - navController?.navigate(R.id.onboarding_nav, null, navOptions { - popUpTo(R.id.migrationNeededScreen) { - inclusive = true - } - }) - } - override fun openRemoteFilesManageScreen(subscription: Id, space: Id) { navController?.navigate( resId = R.id.remoteStorageFragment, diff --git a/app/src/main/java/com/anytypeio/anytype/ui/base/NavigationRouter.kt b/app/src/main/java/com/anytypeio/anytype/ui/base/NavigationRouter.kt index 9a1146ec54..3f359498c8 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/base/NavigationRouter.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/base/NavigationRouter.kt @@ -11,7 +11,6 @@ class NavigationRouter( Timber.d("Navigate to $command") try { when (command) { - is AppNavigation.Command.ExitFromMigrationScreen -> navigation.exitFromMigrationScreen() is AppNavigation.Command.OpenSettings -> navigation.openSpaceSettings() is AppNavigation.Command.OpenObject -> navigation.openDocument( target = command.target, @@ -58,7 +57,6 @@ class NavigationRouter( is AppNavigation.Command.OpenTemplates -> navigation.openTemplatesModal( typeId = command.typeId ) - is AppNavigation.Command.MigrationErrorScreen -> navigation.migrationErrorScreen() is AppNavigation.Command.OpenDateObject -> navigation.openDateObject( objectId = command.objectId, space = command.space diff --git a/app/src/main/java/com/anytypeio/anytype/ui/onboarding/OnboardingFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/onboarding/OnboardingFragment.kt index eaf08c12be..e492b42cb3 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/onboarding/OnboardingFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/onboarding/OnboardingFragment.kt @@ -427,21 +427,6 @@ class OnboardingFragment : Fragment() { Timber.e(it, "Error while trying to open vault screen from onboarding") } } - OnboardingMnemonicLoginViewModel.Command.NavigateToMigrationErrorScreen -> { - runCatching { - findNavController().navigate( - R.id.migrationNeededScreen, - null, - navOptions { - popUpTo(R.id.onboarding_nav) { - inclusive = false - } - } - ) - }.onFailure { - Timber.e(it, "Error while trying to open migration screen from onboarding") - } - } is OnboardingMnemonicLoginViewModel.Command.ShareDebugGoroutines -> { try { this@OnboardingFragment.shareFirstFileFromPath(command.path, command.uriFileProvider) diff --git a/app/src/main/java/com/anytypeio/anytype/ui/onboarding/screens/signin/OnboardingRecoveryPhraseLoginScreen.kt b/app/src/main/java/com/anytypeio/anytype/ui/onboarding/screens/signin/OnboardingRecoveryPhraseLoginScreen.kt index 41709c39b3..b98afbd6f4 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/onboarding/screens/signin/OnboardingRecoveryPhraseLoginScreen.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/onboarding/screens/signin/OnboardingRecoveryPhraseLoginScreen.kt @@ -41,6 +41,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.anytypeio.anytype.BuildConfig import com.anytypeio.anytype.R +import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_ui.ColorButtonRegular import com.anytypeio.anytype.core_ui.MnemonicPhrasePaletteColors import com.anytypeio.anytype.core_ui.OnBoardingTextPrimaryColor @@ -54,6 +55,8 @@ import com.anytypeio.anytype.core_utils.ext.toast import com.anytypeio.anytype.presentation.onboarding.login.OnboardingMnemonicLoginViewModel import com.anytypeio.anytype.presentation.onboarding.login.OnboardingMnemonicLoginViewModel.SetupState import com.anytypeio.anytype.ui.onboarding.OnboardingMnemonicInput +import com.anytypeio.anytype.ui.update.MigrationFailedScreen +import com.anytypeio.anytype.ui.update.MigrationInProgressScreen import kotlin.Unit @Composable @@ -67,11 +70,12 @@ fun RecoveryScreenWrapper( onNextClicked = vm::onLoginClicked, onActionDoneClicked = vm::onActionDone, onScanQrClicked = onScanQrClick, - isLoading = vm.state.collectAsState().value is SetupState.InProgress, + state = vm.state.collectAsState().value, onEnterMyVaultClicked = vm::onEnterMyVaultClicked, onDebugAccountTraceClicked = { vm.onAccountThraceButtonClicked() - } + }, + onRetryMigrationClicked = vm::onRetryMigrationClicked ) } @@ -81,9 +85,10 @@ fun RecoveryScreen( onNextClicked: (Mnemonic) -> Unit, onActionDoneClicked: (Mnemonic) -> Unit, onScanQrClicked: () -> Unit, - isLoading: Boolean, + state: SetupState, onEnterMyVaultClicked: () -> Unit, - onDebugAccountTraceClicked: () -> Unit + onDebugAccountTraceClicked: () -> Unit, + onRetryMigrationClicked: (Id) -> Unit ) { val focus = LocalFocusManager.current val context = LocalContext.current @@ -186,7 +191,7 @@ fun RecoveryScreen( modifier = Modifier .fillMaxWidth() .padding(horizontal = 18.dp), - isLoading = isLoading + isLoading = state is SetupState.InProgress ) } item { @@ -207,7 +212,7 @@ fun RecoveryScreen( onClick = { onScanQrClicked.invoke() }, - enabled = !isLoading, + enabled = state !is SetupState.InProgress, disabledBackgroundColor = Color.Transparent, size = ButtonSize.Large, modifier = Modifier @@ -218,6 +223,16 @@ fun RecoveryScreen( } } ) + if (state is SetupState.Migration.InProgress) { + MigrationInProgressScreen() + } else if(state is SetupState.Migration.Failed) { + MigrationFailedScreen( + state = state.state, + onRetryClicked = { + onRetryMigrationClicked(state.account) + } + ) + } } } @@ -267,9 +282,10 @@ fun RecoveryScreenPreview() { onNextClicked = {}, onActionDoneClicked = {}, onScanQrClicked = {}, - isLoading = false, + state = SetupState.Idle, onEnterMyVaultClicked = {}, - onDebugAccountTraceClicked = {} + onDebugAccountTraceClicked = {}, + onRetryMigrationClicked = {} ) } @@ -282,8 +298,9 @@ fun RecoveryScreenLoadingPreview() { onNextClicked = {}, onActionDoneClicked = {}, onScanQrClicked = {}, - isLoading = true, + state = SetupState.InProgress, onEnterMyVaultClicked = {}, - onDebugAccountTraceClicked = {} + onDebugAccountTraceClicked = {}, + onRetryMigrationClicked = {} ) } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/splash/SplashFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/splash/SplashFragment.kt index a5161d767b..5490c29e48 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/splash/SplashFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/splash/SplashFragment.kt @@ -18,7 +18,6 @@ import com.anytypeio.anytype.core_utils.ext.orNull import com.anytypeio.anytype.core_utils.ext.toast import com.anytypeio.anytype.core_utils.ext.visible import com.anytypeio.anytype.core_utils.ui.BaseFragment -import com.anytypeio.anytype.core_utils.ui.ViewState import com.anytypeio.anytype.databinding.FragmentSplashBinding import com.anytypeio.anytype.di.common.componentManager import com.anytypeio.anytype.other.DefaultDeepLinkResolver @@ -31,6 +30,8 @@ import com.anytypeio.anytype.ui.editor.EditorFragment import com.anytypeio.anytype.ui.home.HomeScreenFragment import com.anytypeio.anytype.ui.onboarding.OnboardingFragment import com.anytypeio.anytype.ui.sets.ObjectSetFragment +import com.anytypeio.anytype.ui.update.MigrationFailedScreen +import com.anytypeio.anytype.ui.update.MigrationInProgressScreen import com.anytypeio.anytype.ui.vault.VaultFragment import javax.inject.Inject import kotlinx.coroutines.launch @@ -64,31 +65,37 @@ class SplashFragment : BaseFragment(R.layout.fragment_spl launch { vm.state.collect { state -> when(state) { - is ViewState.Error -> { - binding.error.text = state.error + is SplashViewModel.State.Init -> { + binding.error.gone() + binding.compose.visibility = View.GONE + } + is SplashViewModel.State.Error -> { + binding.error.text = state.msg binding.error.visible() } - else -> { - binding.error.gone() - binding.error.text = "" - } - } - } - } - - launch { - vm.loadingState.collect { isLoading -> - when (isLoading) { - true -> { - binding.loadingContainer.setContent { + is SplashViewModel.State.Loading -> { + binding.compose.setContent { PulsatingCircleScreen() } - binding.logo.visibility = View.GONE - binding.loadingContainer.visibility = View.VISIBLE + binding.compose.visible() } - false -> { - binding.logo.visibility = View.GONE - binding.loadingContainer.visibility = View.GONE + is SplashViewModel.State.Migration -> { + binding.compose.setContent { + if (state is SplashViewModel.State.Migration.InProgress) { + MigrationInProgressScreen() + } else if (state is SplashViewModel.State.Migration.Failed) { + MigrationFailedScreen( + state = state.state, + onRetryClicked = vm::onRetryMigrationClicked + ) + } + } + binding.compose.visible() + } + is SplashViewModel.State.Success -> { + binding.compose.gone() + binding.error.gone() + binding.error.text = "" } } } @@ -271,11 +278,6 @@ class SplashFragment : BaseFragment(R.layout.fragment_spl args = OnboardingFragment.args(deepLink) ) } - is SplashViewModel.Command.NavigateToMigration -> { - findNavController().navigate( - R.id.migrationNeededScreen - ) - } is SplashViewModel.Command.CheckAppStartIntent -> { val intent = activity?.intent if (intent != null && (intent.action == Intent.ACTION_VIEW || intent.action == Intent.ACTION_SEND)) { diff --git a/app/src/main/java/com/anytypeio/anytype/ui/update/MigrationErrorFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/update/MigrationErrorFragment.kt deleted file mode 100644 index d9b06acb8b..0000000000 --- a/app/src/main/java/com/anytypeio/anytype/ui/update/MigrationErrorFragment.kt +++ /dev/null @@ -1,82 +0,0 @@ -package com.anytypeio.anytype.ui.update - -import android.content.Intent -import android.net.Uri -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.compose.material.MaterialTheme -import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.platform.ViewCompositionStrategy -import androidx.fragment.app.viewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import com.anytypeio.anytype.core_utils.ui.BaseComposeFragment -import com.anytypeio.anytype.di.common.componentManager -import com.anytypeio.anytype.presentation.update.MigrationErrorViewModel -import com.anytypeio.anytype.ui.base.navigation -import com.anytypeio.anytype.ui.settings.typography -import javax.inject.Inject -import kotlinx.coroutines.launch -import timber.log.Timber - -class MigrationErrorFragment : BaseComposeFragment() { - - @Inject - lateinit var factory: MigrationErrorViewModel.Factory - - private val vm by viewModels { factory } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = ComposeView( - context = requireContext() - ).apply { - setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) - setContent { - MaterialTheme(typography = typography) { - MigrationErrorScreen(vm::onAction) - } - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { - vm.commands.collect { command -> - when(command) { - is MigrationErrorViewModel.Command.Browse -> { - browseUrl(command) - } - is MigrationErrorViewModel.Command.Exit -> { - navigation().exitFromMigrationScreen() - } - } - } - } - } - } - - private fun browseUrl(command: MigrationErrorViewModel.Command.Browse) { - try { - Intent(Intent.ACTION_VIEW).apply { - data = Uri.parse(command.url) - }.let(::startActivity) - } catch (e: Exception) { - Timber.e(e, "Error while browsing url") - } - } - - override fun injectDependencies() { - componentManager().migrationErrorComponent.get().inject(this) - } - - override fun releaseDependencies() { - componentManager().migrationErrorComponent.release() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/update/MigrationErrorScreen.kt b/app/src/main/java/com/anytypeio/anytype/ui/update/MigrationErrorScreen.kt index 049aa7ef2f..aa3d6d4407 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/update/MigrationErrorScreen.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/update/MigrationErrorScreen.kt @@ -1,219 +1,159 @@ package com.anytypeio.anytype.ui.update -import androidx.activity.compose.BackHandler -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.AnimatedVisibilityScope -import androidx.compose.animation.core.Animatable -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.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.ClickableText -import androidx.compose.material.Card +import androidx.compose.foundation.layout.size +import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -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.rotate +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.SpanStyle -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.withStyle +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.anytypeio.anytype.R -import com.anytypeio.anytype.core_ui.foundation.noRippleClickable -import com.anytypeio.anytype.core_ui.views.BodyCallout -import com.anytypeio.anytype.core_ui.views.BodyRegular +import com.anytypeio.anytype.core_ui.common.DefaultPreviews +import com.anytypeio.anytype.core_ui.foundation.AlertConfig +import com.anytypeio.anytype.core_ui.foundation.AlertIcon +import com.anytypeio.anytype.core_ui.foundation.GRADIENT_TYPE_RED +import com.anytypeio.anytype.core_ui.views.BodyCalloutRegular import com.anytypeio.anytype.core_ui.views.ButtonPrimary import com.anytypeio.anytype.core_ui.views.ButtonSize import com.anytypeio.anytype.core_ui.views.HeadlineHeading -import com.anytypeio.anytype.core_ui.views.HeadlineSubheading -import com.anytypeio.anytype.presentation.update.MigrationErrorViewModel.ViewAction -import kotlinx.coroutines.launch - +import com.anytypeio.anytype.presentation.auth.account.MigrationHelperDelegate @Composable -fun MigrationErrorScreen(onViewAction: (ViewAction) -> Unit) { +fun MigrationInProgressScreen() { + Box( + modifier = Modifier + .fillMaxSize() + .background(color = colorResource(id = R.color.background_primary)), + contentAlignment = Alignment.Center + ) { + Column( + modifier = Modifier.fillMaxWidth() + ) { + CircularProgressIndicator( + modifier = Modifier + .size(96.dp) + .align(Alignment.CenterHorizontally) + , + backgroundColor = colorResource(R.color.shape_secondary), + color = Color(0xFFFFB522), + strokeWidth = 8.dp + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = stringResource(R.string.migration_migration_is_in_progress), + style = HeadlineHeading, + color = colorResource(R.color.text_primary), + textAlign = TextAlign.Center, + modifier = Modifier + .padding(horizontal = 44.dp) + .fillMaxWidth() + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = stringResource(R.string.migration_this_shouldn_t_take_long), + style = BodyCalloutRegular, + color = colorResource(R.color.text_secondary), + textAlign = TextAlign.Center, + modifier = Modifier + .padding(horizontal = 44.dp) + .fillMaxWidth() + ) + } + } +} + +@Composable +fun MigrationFailedScreen( + state: MigrationHelperDelegate.State.Failed, + onRetryClicked: () -> Unit +) { + val description = when(state) { + MigrationHelperDelegate.State.Failed.NotEnoughSpace -> { + stringResource(R.string.migration_error_please_free_up_space_and_run_the_process_again) + } + is MigrationHelperDelegate.State.Failed.UnknownError -> { + state.error.message ?: stringResource(R.string.unknown_error) + } + } Box( modifier = Modifier .fillMaxSize() .background(color = colorResource(id = R.color.background_primary)) ) { - Cards(onViewAction) - CloseButton(closeClicks = { onViewAction(ViewAction.CloseScreen) }) - BackHandler(enabled = true) { onViewAction(ViewAction.CloseScreen) } - } -} - -@Composable -fun Cards(onViewAction: (ViewAction) -> Unit) { - Column(modifier = Modifier.padding(horizontal = 20.dp)) { - Text( - text = stringResource(id = R.string.almost_there), - style = HeadlineHeading, - color = colorResource(id = R.color.text_primary), - modifier = Modifier.padding(top = 56.dp) - ) - Text( - text = stringResource(id = R.string.almost_there_subtitle), - style = BodyRegular, - color = colorResource(id = R.color.text_primary), - modifier = Modifier.padding(top = 12.dp) - ) - InfoCard( - modifier = Modifier.padding(top = 32.dp), - title = stringResource(id = R.string.i_did_not_not_complete_migration), - toggleClick = { onViewAction(ViewAction.ToggleMigrationNotReady) }, - expanded = true, - content = { - val hereText = stringResource(id = R.string.here) - val text = buildAnnotatedString { - append(stringResource(id = R.string.update_steps_first)) - append(" ") - pushStringAnnotation( - tag = ANNOTATION_TAG, - annotation = hereText - ) - withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { - append(hereText) - } - pop() - append(stringResource(R.string.update_steps_last)) - } - ClickableText( - modifier = Modifier.padding(top = 12.dp), - text = text, - style = BodyCallout.copy( - color = colorResource(id = R.color.text_primary) - ), - onClick = { offset -> - text.getStringAnnotations( - tag = ANNOTATION_TAG, - start = offset, - end = offset - ).firstOrNull().let { - if (it?.item == hereText) { - onViewAction(ViewAction.DownloadDesktop) - } - } - }, + Column( + modifier = Modifier.align(Alignment.Center) + ) { + AlertIcon( + icon = AlertConfig.Icon( + gradient = GRADIENT_TYPE_RED, + icon = R.drawable.ic_alert_error ) - }, - ) - InfoCard( - modifier = Modifier.padding(top = 20.dp), - title = stringResource(id = R.string.i_completed_migration), - expanded = false, - toggleClick = { onViewAction(ViewAction.ToggleMigrationReady) }, - content = { - Column { - Text( - modifier = Modifier.padding(top = 12.dp), - text = stringResource(id = R.string.migration_error_msg), - style = BodyCallout, - color = colorResource(id = R.color.text_primary) - ) - ButtonPrimary( - text = stringResource(id = R.string.visit_forum), - modifier = Modifier - .fillMaxWidth() - .padding(top = 16.dp), - onClick = { onViewAction(ViewAction.VisitForum) }, - size = ButtonSize.Large - ) - } - } - ) - } -} - -@Composable -fun InfoCard( - modifier: Modifier = Modifier, - title: String, - expanded: Boolean, - toggleClick: () -> Unit, - content: @Composable AnimatedVisibilityScope.() -> Unit -) { - - val cardOpened = remember { mutableStateOf(expanded) } - - val rotationDegree = remember { - Animatable( - if (expanded) ROTATION_CLOSED else ROTATION_OPENED - ) - } - val coroutineScope = rememberCoroutineScope() - - Card( - modifier = modifier, - backgroundColor = colorResource(id = R.color.shape_transparent), - elevation = 0.dp, - shape = RoundedCornerShape(16.dp) - ) { - Box { - Image( - painter = painterResource(id = R.drawable.icon_migration_card_arrow), - contentDescription = "", - modifier = Modifier - .align(Alignment.TopEnd) - .padding(top = 22.dp, end = 12.dp) - .rotate(rotationDegree.value) - .noRippleClickable { - cardOpened.value = !cardOpened.value - coroutineScope.launch { - if (cardOpened.value) { - toggleClick() - rotationDegree.animateTo(ROTATION_CLOSED) - } else { - rotationDegree.animateTo(ROTATION_OPENED) - } - } - } ) - - Column( - Modifier - .fillMaxWidth() - .padding(20.dp) - ) { + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = stringResource(R.string.migration_migration_failed), + style = HeadlineHeading, + color = colorResource(R.color.text_primary), + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center + ) + if (description.isNotEmpty()) { + Spacer(modifier = Modifier.height(8.dp)) Text( - text = title, - style = HeadlineSubheading, - color = colorResource(id = R.color.text_primary) + text = description, + color = colorResource(R.color.text_secondary), + style = BodyCalloutRegular, + modifier = Modifier.padding(horizontal = 20.dp).fillMaxWidth(), + textAlign = TextAlign.Center ) - AnimatedVisibility(visible = cardOpened.value) { - content() - } } } + ButtonPrimary( + modifier = Modifier + .padding(20.dp) + .align(Alignment.BottomCenter) + .fillMaxWidth(), + text = stringResource(R.string.migration_error_try_again), + size = ButtonSize.Large, + onClick = onRetryClicked + ) } } - -private const val ANNOTATION_TAG = "here_text_tag" -private const val ROTATION_OPENED = 0F -private const val ROTATION_CLOSED = 180F - +@DefaultPreviews @Composable -private fun CloseButton(closeClicks: () -> Unit) { - Box(modifier = Modifier.fillMaxSize()) { - Image(painter = painterResource(id = R.drawable.ic_navigation_close), - contentDescription = "close image", - modifier = Modifier - .align(Alignment.TopEnd) - .padding(top = 12.dp, end = 12.dp) - .noRippleClickable { closeClicks.invoke() } - ) - } +fun MigrationInProgressScreenPreview() { + MigrationInProgressScreen() +} + +@DefaultPreviews +@Composable +fun MigrationFailedScreenPreview() { + MigrationFailedScreen( + state = MigrationHelperDelegate.State.Failed.NotEnoughSpace, + onRetryClicked = {} + ) +} + +@DefaultPreviews +@Composable +fun MigrationFailedGenericScreenPreview() { + MigrationFailedScreen( + state = MigrationHelperDelegate.State.Failed.UnknownError( + Exception(stringResource(R.string.default_text_placeholder)) + ), + onRetryClicked = {} + ) } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_splash.xml b/app/src/main/res/layout/fragment_splash.xml index 1d95f2888f..fabfb3e84e 100644 --- a/app/src/main/res/layout/fragment_splash.xml +++ b/app/src/main/res/layout/fragment_splash.xml @@ -5,13 +5,6 @@ android:layout_height="match_parent" android:background="@color/background_primary"> - - + android:visibility="gone" /> \ No newline at end of file diff --git a/app/src/main/res/navigation/graph.xml b/app/src/main/res/navigation/graph.xml index 61abe8f2f4..1dec7c401f 100644 --- a/app/src/main/res/navigation/graph.xml +++ b/app/src/main/res/navigation/graph.xml @@ -631,12 +631,6 @@ android:label="TemplateSelectScreen" tools:layout="@layout/fragment_template_select" /> - - - diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/exceptions/AccountIsDeletedException.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/exceptions/AccountIsDeletedException.kt index 4e0743340e..a9b78e1f66 100644 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/exceptions/AccountIsDeletedException.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/exceptions/AccountIsDeletedException.kt @@ -1,5 +1,9 @@ package com.anytypeio.anytype.core_models.exceptions class AccountIsDeletedException : Exception() -class MigrationNeededException: Exception() -class NeedToUpdateApplicationException: Exception() \ No newline at end of file +class NeedToUpdateApplicationException: Exception() +class AccountMigrationNeededException: Exception() + +sealed class MigrationFailedException : Exception() { + class NotEnoughSpace : MigrationFailedException() +} \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthCacheDataStore.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthCacheDataStore.kt index 471c53c790..bf5c1eaf56 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthCacheDataStore.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthCacheDataStore.kt @@ -3,6 +3,7 @@ package com.anytypeio.anytype.data.auth.repo import com.anytypeio.anytype.core_models.AccountSetup import com.anytypeio.anytype.core_models.AccountStatus import com.anytypeio.anytype.core_models.Command +import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.NetworkModeConfig import com.anytypeio.anytype.data.auth.model.AccountEntity import com.anytypeio.anytype.data.auth.model.WalletEntity @@ -93,4 +94,15 @@ class AuthCacheDataStore(private val cache: AuthCache) : AuthDataStore { override suspend fun debugExportLogs(dir: String): String { throw UnsupportedOperationException() } + + override suspend fun migrateAccount( + account: Id, + path: String + ) { + throw UnsupportedOperationException() + } + + override suspend fun cancelAccountMigration(account: Id) { + throw UnsupportedOperationException() + } } \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataRepository.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataRepository.kt index 30a41ebe2f..7d363ea2f8 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataRepository.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataRepository.kt @@ -4,6 +4,7 @@ import com.anytypeio.anytype.core_models.Account import com.anytypeio.anytype.core_models.AccountSetup import com.anytypeio.anytype.core_models.AccountStatus import com.anytypeio.anytype.core_models.Command +import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.NetworkModeConfig import com.anytypeio.anytype.data.auth.mapper.toDomain import com.anytypeio.anytype.data.auth.mapper.toEntity @@ -93,6 +94,22 @@ class AuthDataRepository( override suspend fun getVersion(): String = factory.remote.getVersion() + override suspend fun migrateAccount( + account: Id, + path: String + ) { + factory.remote.migrateAccount( + account = account, + path = path + ) + } + + override suspend fun cancelAccountMigration(account: Id) { + factory.remote.cancelAccountMigration( + account = account + ) + } + override suspend fun getNetworkMode(): NetworkModeConfig { return factory.cache.getNetworkMode() } diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataStore.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataStore.kt index 1b9b077ace..fcf7fcd93b 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataStore.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataStore.kt @@ -3,6 +3,7 @@ package com.anytypeio.anytype.data.auth.repo import com.anytypeio.anytype.core_models.AccountSetup import com.anytypeio.anytype.core_models.AccountStatus import com.anytypeio.anytype.core_models.Command +import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.NetworkModeConfig import com.anytypeio.anytype.data.auth.model.AccountEntity import com.anytypeio.anytype.data.auth.model.WalletEntity @@ -16,6 +17,9 @@ interface AuthDataStore { suspend fun deleteAccount() : AccountStatus suspend fun restoreAccount() : AccountStatus + suspend fun migrateAccount(account: Id, path: String) + suspend fun cancelAccountMigration(account: Id) + suspend fun recoverAccount() suspend fun saveAccount(account: AccountEntity) diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemote.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemote.kt index 9ec8e409bf..c7a6233694 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemote.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemote.kt @@ -3,6 +3,7 @@ package com.anytypeio.anytype.data.auth.repo import com.anytypeio.anytype.core_models.AccountSetup import com.anytypeio.anytype.core_models.AccountStatus import com.anytypeio.anytype.core_models.Command +import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.data.auth.model.AccountEntity import com.anytypeio.anytype.data.auth.model.WalletEntity import kotlinx.coroutines.flow.Flow @@ -10,6 +11,10 @@ import kotlinx.coroutines.flow.Flow interface AuthRemote { suspend fun selectAccount(command: Command.AccountSelect): AccountSetup suspend fun createAccount(command: Command.AccountCreate): AccountSetup + + suspend fun migrateAccount(account: Id, path: String) + suspend fun cancelAccountMigration(account: Id) + suspend fun deleteAccount() : AccountStatus suspend fun restoreAccount() : AccountStatus suspend fun recoverAccount() diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemoteDataStore.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemoteDataStore.kt index 6620b8478d..02a436335e 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemoteDataStore.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemoteDataStore.kt @@ -3,6 +3,7 @@ package com.anytypeio.anytype.data.auth.repo import com.anytypeio.anytype.core_models.AccountSetup import com.anytypeio.anytype.core_models.AccountStatus import com.anytypeio.anytype.core_models.Command +import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.NetworkModeConfig import com.anytypeio.anytype.data.auth.model.AccountEntity import com.anytypeio.anytype.data.auth.model.WalletEntity @@ -23,6 +24,22 @@ class AuthRemoteDataStore( override suspend fun restoreAccount(): AccountStatus = authRemote.restoreAccount() + override suspend fun migrateAccount( + account: Id, + path: String + ) { + authRemote.migrateAccount( + account = account, + path = path + ) + } + + override suspend fun cancelAccountMigration(account: Id) { + authRemote.cancelAccountMigration( + account = account + ) + } + override suspend fun recoverAccount() { authRemote.recoverAccount() } diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/CancelAccountMigration.kt b/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/CancelAccountMigration.kt new file mode 100644 index 0000000000..3b45473f3e --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/CancelAccountMigration.kt @@ -0,0 +1,34 @@ +package com.anytypeio.anytype.domain.auth.interactor + +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.domain.auth.repo.AuthRepository +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.base.ResultInteractor +import javax.inject.Inject + +class CancelAccountMigration @Inject constructor( + private val repo: AuthRepository, + dispatchers: AppCoroutineDispatchers +) : ResultInteractor(dispatchers.io) { + + override suspend fun doWork(params: Params) { + when(params) { + is Params.Current -> { + val acc = repo.getCurrentAccount() + repo.cancelAccountMigration( + account = acc.id + ) + } + is Params.Other -> { + repo.cancelAccountMigration( + account = params.acc + ) + } + } + } + + sealed class Params { + data object Current : Params() + data class Other(val acc: Id) : Params() + } +} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/MigrateAccount.kt b/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/MigrateAccount.kt new file mode 100644 index 0000000000..5adecd3b0d --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/MigrateAccount.kt @@ -0,0 +1,40 @@ +package com.anytypeio.anytype.domain.auth.interactor + +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.domain.auth.repo.AuthRepository +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.base.ResultInteractor +import com.anytypeio.anytype.domain.device.PathProvider +import javax.inject.Inject + +class MigrateAccount @Inject constructor( + private val repo: AuthRepository, + private val path: PathProvider, + dispatchers: AppCoroutineDispatchers +) : ResultInteractor(dispatchers.io) { + + override suspend fun doWork(params: Params) { + when(params) { + is Params.Current -> { + val acc = repo.getCurrentAccount() + val path = path.providePath() + repo.migrateAccount( + account = acc.id, + path = path + ) + } + is Params.Other -> { + val path = path.providePath() + repo.migrateAccount( + account = params.acc, + path = path + ) + } + } + } + + sealed class Params { + data object Current : Params() + data class Other(val acc: Id) : Params() + } +} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/auth/repo/AuthRepository.kt b/domain/src/main/java/com/anytypeio/anytype/domain/auth/repo/AuthRepository.kt index a0a40934ed..228eeca2b3 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/auth/repo/AuthRepository.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/auth/repo/AuthRepository.kt @@ -4,6 +4,7 @@ import com.anytypeio.anytype.core_models.Account import com.anytypeio.anytype.core_models.AccountSetup import com.anytypeio.anytype.core_models.AccountStatus import com.anytypeio.anytype.core_models.Command +import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.NetworkModeConfig import com.anytypeio.anytype.domain.auth.model.Wallet import kotlinx.coroutines.flow.Flow @@ -20,6 +21,9 @@ interface AuthRepository { suspend fun selectAccount(command: Command.AccountSelect): AccountSetup suspend fun createAccount(command: Command.AccountCreate): AccountSetup + suspend fun migrateAccount(account: Id, path: String) + suspend fun cancelAccountMigration(account: Id) + suspend fun deleteAccount() : AccountStatus suspend fun restoreAccount() : AccountStatus diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 42043bbff1..9c80ef4c9b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -middlewareVersion = "v0.39.10" +middlewareVersion = "v0.40.0-alpha01" kotlinVersion = '2.0.21' kspVersion = "2.0.21-1.0.25" diff --git a/localization/src/main/res/values/strings.xml b/localization/src/main/res/values/strings.xml index dfa192f112..e5c1547edf 100644 --- a/localization/src/main/res/values/strings.xml +++ b/localization/src/main/res/values/strings.xml @@ -1876,5 +1876,10 @@ Please provide specific details of your needs here. It cannot be restored after confirmation Delete this message? + Migration is in progress + This shouldn’t take long. Thanks for your patience. + Try again + Migration failed + Please free up space and run the process again. \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/auth/AuthMiddleware.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/auth/AuthMiddleware.kt index 78b5ac2d47..9ed98d94b7 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/auth/AuthMiddleware.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/auth/AuthMiddleware.kt @@ -3,6 +3,7 @@ package com.anytypeio.anytype.middleware.auth import com.anytypeio.anytype.core_models.AccountSetup import com.anytypeio.anytype.core_models.AccountStatus import com.anytypeio.anytype.core_models.Command +import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.data.auth.model.WalletEntity import com.anytypeio.anytype.data.auth.repo.AuthRemote import com.anytypeio.anytype.middleware.EventProxy @@ -28,6 +29,22 @@ class AuthMiddleware( command: Command.AccountCreate ) : AccountSetup = middleware.accountCreate(command) + override suspend fun migrateAccount( + account: Id, + path: String + ) { + middleware.accountMigrate( + account = account, + path = path + ) + } + + override suspend fun cancelAccountMigration(account: Id) { + middleware.accountMigrateCancel( + account = account + ) + } + override suspend fun deleteAccount(): AccountStatus = middleware.accountDelete() override suspend fun restoreAccount(): AccountStatus = middleware.accountRestore() diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt index 00fbc09c60..f46a1833ea 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt @@ -129,6 +129,25 @@ class Middleware @Inject constructor( return status.core() } + @Throws(Exception::class) + fun accountMigrate(account: Id, path: String) { + val request = Rpc.Account.Migrate.Request( + id = account, + rootPath = path + ) + logRequestIfDebug(request) + val (response, time) = measureTimedValue { service.accountMigrate(request) } + logResponseIfDebug(response, time) + } + + @Throws(Exception::class) + fun accountMigrateCancel(account: Id) { + val request = Rpc.Account.MigrateCancel.Request(id = account) + logRequestIfDebug(request) + val (response, time) = measureTimedValue { service.accountMigrateCancel(request) } + logResponseIfDebug(response, time) + } + @Throws(Exception::class) fun accountRecover() { val request = Rpc.Account.Recover.Request() diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt index 4a1851178d..bc63e30d52 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt @@ -50,6 +50,12 @@ interface MiddlewareService { @Throws(Exception::class) fun accountStop(request: Rpc.Account.Stop.Request): Rpc.Account.Stop.Response + @Throws(Exception::class) + fun accountMigrate(request: Rpc.Account.Migrate.Request): Rpc.Account.Migrate.Response + + @Throws(Exception::class) + fun accountMigrateCancel(request: Rpc.Account.MigrateCancel.Request): Rpc.Account.MigrateCancel.Response + //endregion //region OBJECT commands diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt index 9ae9436eb2..d0c718d7f0 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt @@ -2,7 +2,9 @@ package com.anytypeio.anytype.middleware.service import anytype.Rpc import com.anytypeio.anytype.core_models.exceptions.AccountIsDeletedException +import com.anytypeio.anytype.core_models.exceptions.AccountMigrationNeededException import com.anytypeio.anytype.core_models.exceptions.LoginException +import com.anytypeio.anytype.core_models.exceptions.MigrationFailedException import com.anytypeio.anytype.core_models.exceptions.NeedToUpdateApplicationException import com.anytypeio.anytype.core_models.multiplayer.MultiplayerError import com.anytypeio.anytype.core_models.multiplayer.SpaceInviteError @@ -80,6 +82,9 @@ class MiddlewareServiceImplementation @Inject constructor( Rpc.Account.Select.Response.Error.Code.ACCOUNT_IS_DELETED -> { throw AccountIsDeletedException() } + Rpc.Account.Select.Response.Error.Code.ACCOUNT_STORE_NOT_MIGRATED -> { + throw AccountMigrationNeededException() + } Rpc.Account.Select.Response.Error.Code.FAILED_TO_FETCH_REMOTE_NODE_HAS_INCOMPATIBLE_PROTO_VERSION -> { throw NeedToUpdateApplicationException() } @@ -104,6 +109,33 @@ class MiddlewareServiceImplementation @Inject constructor( } } + override fun accountMigrate(request: Rpc.Account.Migrate.Request): Rpc.Account.Migrate.Response { + val encoded = Service.accountMigrate(Rpc.Account.Migrate.Request.ADAPTER.encode(request)) + val response = Rpc.Account.Migrate.Response.ADAPTER.decode(encoded) + val error = response.error + if (error != null && error.code != Rpc.Account.Migrate.Response.Error.Code.NULL) { + when(error.code) { + Rpc.Account.Migrate.Response.Error.Code.NOT_ENOUGH_FREE_SPACE -> { + throw MigrationFailedException.NotEnoughSpace() + } + else -> throw Exception(error.description) + } + } else { + return response + } + } + + override fun accountMigrateCancel(request: Rpc.Account.MigrateCancel.Request): Rpc.Account.MigrateCancel.Response { + val encoded = Service.accountMigrateCancel(Rpc.Account.MigrateCancel.Request.ADAPTER.encode(request)) + val response = Rpc.Account.MigrateCancel.Response.ADAPTER.decode(encoded) + val error = response.error + if (error != null && error.code != Rpc.Account.MigrateCancel.Response.Error.Code.NULL) { + throw Exception(error.description) + } else { + return response + } + } + override fun blockBookmarkCreateAndFetch(request: Rpc.BlockBookmark.CreateAndFetch.Request): Rpc.BlockBookmark.CreateAndFetch.Response { val encoded = Service.blockBookmarkCreateAndFetch( Rpc.BlockBookmark.CreateAndFetch.Request.ADAPTER.encode(request) diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/auth/account/MigrationHelperDelegate.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/auth/account/MigrationHelperDelegate.kt new file mode 100644 index 0000000000..56aba42a49 --- /dev/null +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/auth/account/MigrationHelperDelegate.kt @@ -0,0 +1,52 @@ +package com.anytypeio.anytype.presentation.auth.account + +import com.anytypeio.anytype.core_models.exceptions.MigrationFailedException +import com.anytypeio.anytype.domain.auth.interactor.MigrateAccount +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.base.Resultat +import javax.inject.Inject +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map + +interface MigrationHelperDelegate { + + suspend fun proceedWithMigration() : Flow + + class Impl @Inject constructor( + private val migrateAccount: MigrateAccount, + private val dispatchers: AppCoroutineDispatchers + ) : MigrationHelperDelegate { + + override suspend fun proceedWithMigration(): Flow { + return migrateAccount + .stream(MigrateAccount.Params.Current) + .map { result -> + when(result) { + is Resultat.Failure -> { + if (result.exception is MigrationFailedException.NotEnoughSpace) { + State.Failed.NotEnoughSpace + } else { + State.Failed.UnknownError(result.exception) + } + } + is Resultat.Loading -> State.InProgress + is Resultat.Success -> State.Migrated + } + } + .flowOn(dispatchers.io) + } + } + + sealed class State { + data object Init: State() + data object InProgress : State() + sealed class Failed : State() { + data class UnknownError(val error: Throwable) : Failed() + data object NotEnoughSpace : Failed() + } + data object Migrated : State() + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/AppNavigation.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/AppNavigation.kt index cffdf265c0..959fa1fbdc 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/AppNavigation.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/AppNavigation.kt @@ -7,8 +7,6 @@ import com.anytypeio.anytype.presentation.widgets.collection.Subscription interface AppNavigation { - fun exitFromMigrationScreen() - fun openSpaceSettings() fun openObjectSet( @@ -59,8 +57,6 @@ interface AppNavigation { fun logout() - fun migrationErrorScreen() - fun openTemplatesModal(typeId: Id) fun openAllContent(space: Id) @@ -75,9 +71,6 @@ interface AppNavigation { data object ExitToDesktop : Command() data object ExitToVault : Command() data object ExitToSpaceHome : Command() - - data object ExitFromMigrationScreen : Command() - data class OpenObject(val target: Id, val space: Id) : Command() data class OpenChat(val target: Id, val space: Id) : Command() data class LaunchDocument(val target: Id, val space: Id) : Command() @@ -89,7 +82,6 @@ interface AppNavigation { ) : Command() object OpenSettings : Command() - object MigrationErrorScreen: Command() data class OpenShareScreen( val space: SpaceId diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/login/OnboardingMnemonicLoginViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/login/OnboardingMnemonicLoginViewModel.kt index d46df251f9..f7ac50a874 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/login/OnboardingMnemonicLoginViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/login/OnboardingMnemonicLoginViewModel.kt @@ -7,13 +7,15 @@ import com.anytypeio.anytype.CrashReporter import com.anytypeio.anytype.analytics.base.Analytics import com.anytypeio.anytype.analytics.base.EventsDictionary import com.anytypeio.anytype.analytics.base.sendEvent +import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.exceptions.AccountIsDeletedException +import com.anytypeio.anytype.core_models.exceptions.AccountMigrationNeededException import com.anytypeio.anytype.core_models.exceptions.LoginException -import com.anytypeio.anytype.core_models.exceptions.MigrationNeededException import com.anytypeio.anytype.core_models.exceptions.NeedToUpdateApplicationException import com.anytypeio.anytype.core_utils.ext.cancel import com.anytypeio.anytype.domain.auth.interactor.ConvertWallet import com.anytypeio.anytype.domain.auth.interactor.Logout +import com.anytypeio.anytype.domain.auth.interactor.MigrateAccount import com.anytypeio.anytype.domain.auth.interactor.ObserveAccounts import com.anytypeio.anytype.domain.auth.interactor.RecoverWallet import com.anytypeio.anytype.domain.auth.interactor.SaveMnemonic @@ -27,9 +29,10 @@ import com.anytypeio.anytype.domain.debugging.DebugGoroutines import com.anytypeio.anytype.domain.device.PathProvider import com.anytypeio.anytype.domain.misc.LocaleProvider import com.anytypeio.anytype.domain.subscriptions.GlobalSubscriptionManager +import com.anytypeio.anytype.presentation.auth.account.MigrationHelperDelegate import com.anytypeio.anytype.presentation.extension.proceedWithAccountEvent import com.anytypeio.anytype.presentation.extension.sendAnalyticsOnboardingLoginEvent -import com.anytypeio.anytype.presentation.splash.SplashViewModel +import com.anytypeio.anytype.presentation.splash.SplashViewModel.State import com.anytypeio.anytype.presentation.util.downloader.UriFileProvider import javax.inject.Inject import kotlinx.coroutines.Job @@ -55,8 +58,9 @@ class OnboardingMnemonicLoginViewModel @Inject constructor( private val uriFileProvider: UriFileProvider, private val logout: Logout, private val globalSubscriptionManager: GlobalSubscriptionManager, - private val debugAccountSelectTrace: DebugAccountSelectTrace -) : ViewModel() { + private val debugAccountSelectTrace: DebugAccountSelectTrace, + private val migrationDelegate: MigrationHelperDelegate +) : ViewModel(), MigrationHelperDelegate by migrationDelegate { private val jobs = mutableListOf() private var goroutinesJob : Job? = null @@ -67,6 +71,7 @@ class OnboardingMnemonicLoginViewModel @Inject constructor( val command = MutableSharedFlow(replay = 0) private var debugClickCount = 0 + private var migrationRetryCount: Int = 0 private val _fiveClicks = MutableStateFlow(false) init { @@ -177,7 +182,7 @@ class OnboardingMnemonicLoginViewModel @Inject constructor( else -> SideEffect.Error.Unknown("Error while login: ${exception.message}") } sideEffects.emit(error).also { - Timber.e(exception, "Error while selecting account") + Timber.e(exception, "Error while recovering wallet") } } } @@ -266,8 +271,8 @@ class OnboardingMnemonicLoginViewModel @Inject constructor( Timber.e(e, "Error while selecting account with id: $id") state.value = SetupState.Failed when (e) { - is MigrationNeededException -> { - navigateToMigrationErrorScreen() + is AccountMigrationNeededException -> { + proceedWithAccountMigration(id) } is AccountIsDeletedException -> { sideEffects.emit(value = SideEffect.Error.AccountDeletedError) @@ -306,9 +311,27 @@ class OnboardingMnemonicLoginViewModel @Inject constructor( } } - private fun navigateToMigrationErrorScreen() { - viewModelScope.launch { - command.emit(Command.NavigateToMigrationErrorScreen) + private suspend fun proceedWithAccountMigration(id: String) { + proceedWithMigration().collect { migrationState -> + when (migrationState) { + is MigrationHelperDelegate.State.Failed -> { + state.value = SetupState.Migration.Failed( + state = migrationState, + account = id + ) + } + MigrationHelperDelegate.State.InProgress -> { + state.value = SetupState.Migration.InProgress( + account = id + ) + } + MigrationHelperDelegate.State.Migrated -> { + proceedWithSelectingAccount(id) + } + MigrationHelperDelegate.State.Init -> { + // Do nothing. + } + } } } @@ -372,6 +395,15 @@ class OnboardingMnemonicLoginViewModel @Inject constructor( } } + fun onRetryMigrationClicked(account: Id) { + if (state.value !is SetupState.InProgress) { + migrationRetryCount = migrationRetryCount + 1 + viewModelScope.launch { + proceedWithAccountMigration(account) + } + } + } + override fun onCleared() { super.onCleared() goroutinesJob?.cancel() @@ -394,11 +426,18 @@ class OnboardingMnemonicLoginViewModel @Inject constructor( data object InProgress: SetupState() data object Failed: SetupState() data object Abort: SetupState() + sealed class Migration : SetupState() { + abstract val account: Id + data class InProgress(override val account: Id): Migration() + data class Failed( + val state: MigrationHelperDelegate.State.Failed, + override val account: Id + ) : Migration() + } } sealed class Command { data object Exit : Command() - data object NavigateToMigrationErrorScreen : Command() data object NavigateToVaultScreen: Command() data class ShowToast(val message: String) : Command() data class ShareDebugGoroutines(val path: String, val uriFileProvider: UriFileProvider) : Command() @@ -420,7 +459,8 @@ class OnboardingMnemonicLoginViewModel @Inject constructor( private val uriFileProvider: UriFileProvider, private val logout: Logout, private val globalSubscriptionManager: GlobalSubscriptionManager, - private val debugAccountSelectTrace: DebugAccountSelectTrace + private val debugAccountSelectTrace: DebugAccountSelectTrace, + private val migrationHelperDelegate: MigrationHelperDelegate ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { @@ -440,7 +480,8 @@ class OnboardingMnemonicLoginViewModel @Inject constructor( uriFileProvider = uriFileProvider, logout = logout, globalSubscriptionManager = globalSubscriptionManager, - debugAccountSelectTrace = debugAccountSelectTrace + debugAccountSelectTrace = debugAccountSelectTrace, + migrationDelegate = migrationHelperDelegate ) as T } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModel.kt index 2c307b931d..b61020f742 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModel.kt @@ -15,7 +15,9 @@ import com.anytypeio.anytype.core_models.Key import com.anytypeio.anytype.core_models.MarketplaceObjectTypeIds.SET import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.core_models.ObjectTypeIds.COLLECTION -import com.anytypeio.anytype.core_models.exceptions.MigrationNeededException +import com.anytypeio.anytype.core_models.SupportedLayouts +import com.anytypeio.anytype.core_models.exceptions.AccountMigrationNeededException +import com.anytypeio.anytype.core_models.exceptions.MigrationFailedException import com.anytypeio.anytype.core_models.exceptions.NeedToUpdateApplicationException import com.anytypeio.anytype.core_models.primitives.SpaceId import com.anytypeio.anytype.core_models.primitives.TypeKey @@ -28,24 +30,22 @@ import com.anytypeio.anytype.domain.auth.model.AuthStatus import com.anytypeio.anytype.domain.base.BaseUseCase import com.anytypeio.anytype.domain.base.fold import com.anytypeio.anytype.domain.misc.LocaleProvider +import com.anytypeio.anytype.domain.multiplayer.SpaceViewSubscriptionContainer import com.anytypeio.anytype.domain.page.CreateObjectByTypeAndTemplate import com.anytypeio.anytype.domain.spaces.GetLastOpenedSpace import com.anytypeio.anytype.domain.subscriptions.GlobalSubscriptionManager import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.presentation.BuildConfig import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate -import com.anytypeio.anytype.presentation.extension.sendAnalyticsObjectCreateEvent -import com.anytypeio.anytype.core_models.SupportedLayouts -import com.anytypeio.anytype.domain.multiplayer.SpaceViewSubscriptionContainer -import com.anytypeio.anytype.domain.spaces.GetSpaceView +import com.anytypeio.anytype.presentation.auth.account.MigrationHelperDelegate import com.anytypeio.anytype.presentation.confgs.ChatConfig +import com.anytypeio.anytype.presentation.extension.sendAnalyticsObjectCreateEvent import com.anytypeio.anytype.presentation.search.ObjectSearchConstants +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.take -import kotlinx.coroutines.flow.timeout import kotlinx.coroutines.launch import timber.log.Timber @@ -68,13 +68,16 @@ class SplashViewModel( private val getLastOpenedSpace: GetLastOpenedSpace, private val createObjectByTypeAndTemplate: CreateObjectByTypeAndTemplate, private val spaceViews: SpaceViewSubscriptionContainer, -) : ViewModel(), AnalyticSpaceHelperDelegate by analyticSpaceHelperDelegate { - - val state = MutableStateFlow>(ViewState.Init) + private val migration: MigrationHelperDelegate +) : ViewModel(), + AnalyticSpaceHelperDelegate by analyticSpaceHelperDelegate, + MigrationHelperDelegate by migration +{ + val state = MutableStateFlow(State.Init) val commands = MutableSharedFlow(replay = 0) - val loadingState = MutableStateFlow(false) + private var migrationRetryCount: Int = 0 init { Timber.i("SplashViewModel, init") @@ -82,12 +85,41 @@ class SplashViewModel( } fun onErrorClicked() { - if (BuildConfig.DEBUG && state.value is ViewState.Error) { - state.value = ViewState.Loading + if (state.value is State.Error) { proceedWithLaunchingAccount() } } + fun onRetryMigrationClicked() { + viewModelScope.launch { + migrationRetryCount = migrationRetryCount + 1 + proceedWithAccountMigration() + } + } + + private suspend fun proceedWithAccountMigration() { + if (migrationRetryCount <= 1) { + proceedWithMigration().collect { migrationState -> + when (migrationState) { + is MigrationHelperDelegate.State.Failed -> { + state.value = State.Migration.Failed(migrationState) + } + is MigrationHelperDelegate.State.Init -> { + // Do nothing. + } + is MigrationHelperDelegate.State.InProgress -> { + state.value = State.Migration.InProgress + } + is MigrationHelperDelegate.State.Migrated -> { + proceedWithLaunchingAccount() + } + } + } + } else { + Timber.e("Failed to migration account after retry") + } + } + private fun checkAuthorizationStatus() { viewModelScope.launch { checkAuthorizationStatus(Unit).process( @@ -120,7 +152,7 @@ class SplashViewModel( failure = { e -> Timber.e(e, "Error while retrying launching wallet") val msg = "Error while launching account: ${e.message}" - state.value = ViewState.Error(msg) + state.value = State.Error(msg) }, success = { proceedWithLaunchingAccount() @@ -132,10 +164,10 @@ class SplashViewModel( private fun proceedWithLaunchingAccount() { val startTime = System.currentTimeMillis() viewModelScope.launch { - loadingState.value = true + state.value = State.Loading launchAccount(BaseUseCase.None).proceed( success = { analyticsId -> - loadingState.value = false + state.value = State.Loading crashReporter.setUser(analyticsId) updateUserProps(analyticsId) val props = Props.empty() @@ -144,18 +176,17 @@ class SplashViewModel( commands.emit(Command.CheckAppStartIntent) }, failure = { e -> - loadingState.value = false Timber.e(e, "Error while launching account") when (e) { - is MigrationNeededException -> { - commands.emit(Command.NavigateToMigration) + is AccountMigrationNeededException -> { + proceedWithAccountMigration() } is NeedToUpdateApplicationException -> { - state.value = ViewState.Error(ERROR_NEED_UPDATE) + state.value = State.Error(ERROR_NEED_UPDATE) } else -> { val msg = "$ERROR_MESSAGE : ${e.message ?: "Unknown error"}" - state.value = ViewState.Error(msg) + state.value = State.Error(msg) } } } @@ -386,7 +417,6 @@ class SplashViewModel( ) : Command() data class NavigateToVault(val deeplink: String? = null) : Command() data object NavigateToAuthStart : Command() - data object NavigateToMigration: Command() data object CheckAppStartIntent : Command() data class NavigateToObject(val id: Id, val space: Id, val chat: Id?) : Command() data class NavigateToObjectSet(val id: Id, val space: Id, val chat: Id?) : Command() @@ -399,4 +429,15 @@ class SplashViewModel( const val ERROR_NEED_UPDATE = "Unable to retrieve account. Please update Anytype to the latest version." const val ERROR_CREATE_OBJECT = "Error while creating object: object type not found" } + + sealed class State { + data object Init : State() + data object Loading : State() + data object Success: State() + data class Error(val msg: String): State() + sealed class Migration : State() { + data object InProgress: Migration() + data class Failed(val state: MigrationHelperDelegate.State.Failed) : Migration() + } + } } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModelFactory.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModelFactory.kt index fb26c70e04..841b65b836 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModelFactory.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModelFactory.kt @@ -16,6 +16,7 @@ import com.anytypeio.anytype.domain.spaces.GetLastOpenedSpace import com.anytypeio.anytype.domain.subscriptions.GlobalSubscriptionManager import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate +import com.anytypeio.anytype.presentation.auth.account.MigrationHelperDelegate import javax.inject.Inject /** @@ -36,7 +37,8 @@ class SplashViewModelFactory @Inject constructor( private val globalSubscriptionManager: GlobalSubscriptionManager, private val getLastOpenedSpace: GetLastOpenedSpace, private val createObjectByTypeAndTemplate: CreateObjectByTypeAndTemplate, - private val spaceViews: SpaceViewSubscriptionContainer + private val spaceViews: SpaceViewSubscriptionContainer, + private val migration: MigrationHelperDelegate ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") @@ -54,6 +56,7 @@ class SplashViewModelFactory @Inject constructor( globalSubscriptionManager = globalSubscriptionManager, getLastOpenedSpace = getLastOpenedSpace, createObjectByTypeAndTemplate = createObjectByTypeAndTemplate, - spaceViews = spaceViews + spaceViews = spaceViews, + migration = migration ) as T } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/update/MigrationErrorViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/update/MigrationErrorViewModel.kt deleted file mode 100644 index 8e4ce78bd8..0000000000 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/update/MigrationErrorViewModel.kt +++ /dev/null @@ -1,114 +0,0 @@ -package com.anytypeio.anytype.presentation.update - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.viewModelScope -import com.anytypeio.anytype.analytics.base.Analytics -import com.anytypeio.anytype.analytics.base.sendEvent -import com.anytypeio.anytype.analytics.props.Props -import com.anytypeio.anytype.core_models.Url -import javax.inject.Inject -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.launch - -class MigrationErrorViewModel( - val analytics: Analytics -) : ViewModel() { - - val commands = MutableSharedFlow() - private val viewActions = MutableSharedFlow() - - init { - viewModelScope.launch { - viewActions.collect { action -> - when (action) { - ViewAction.CloseScreen -> { - sendAnalyticsEvent(ANALYTICS_TYPE_EXIT) - proceedWithCloseScreen() - } - ViewAction.VisitForum -> { - sendAnalyticsEvent(ANALYTICS_TYPE_CHECK_INSTRUCTIONS) - proceedWithVisitingForum() - } - ViewAction.DownloadDesktop -> { - sendAnalyticsEvent(ANALYTICS_TYPE_DESKTOP_DOWNLOAD) - proceedWithDesktopDownload() - } - ViewAction.ToggleMigrationNotReady -> { - // Do nothing - } - ViewAction.ToggleMigrationReady -> { - sendAnalyticsEvent(ANALYTICS_TYPE_MIGRATION_COMPLETED) - } - } - } - } - } - - fun onAction(action: ViewAction) { - viewModelScope.launch { - viewActions.emit(action) - } - } - - private fun proceedWithCloseScreen() { - viewModelScope.launch { - commands.emit(Command.Exit) - } - } - - private fun proceedWithVisitingForum() { - viewModelScope.launch { - commands.emit(Command.Browse(VISIT_FORUM_URL)) - } - } - - private fun proceedWithDesktopDownload() { - viewModelScope.launch { - commands.emit(Command.Browse(DOWNLOAD_DESKTOP_URL)) - } - } - - private fun sendAnalyticsEvent(type: String) { - viewModelScope.sendEvent( - analytics = analytics, - eventName = ANALYTICS_EVENT_SCREEN, - props = Props(mapOf("type" to type)) - ) - } - - sealed interface Command { - object Exit: Command - data class Browse(val url: Url): Command - } - - sealed interface ViewAction { - object CloseScreen: ViewAction - object ToggleMigrationNotReady: ViewAction - object ToggleMigrationReady: ViewAction - object VisitForum: ViewAction - object DownloadDesktop: ViewAction - } - - class Factory @Inject constructor( - private val analytics: Analytics - ) : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return MigrationErrorViewModel( - analytics = analytics - ) as T - } - } - - companion object { - const val DOWNLOAD_DESKTOP_URL = "https://download.anytype.io/" - const val VISIT_FORUM_URL = "https://community.anytype.io/migration" - } -} - -private const val ANALYTICS_EVENT_SCREEN = "MigrationGoneWrong" -private const val ANALYTICS_TYPE_MIGRATION_COMPLETED = "complete" -private const val ANALYTICS_TYPE_CHECK_INSTRUCTIONS = "instructions" -private const val ANALYTICS_TYPE_DESKTOP_DOWNLOAD = "download" -private const val ANALYTICS_TYPE_EXIT = "exit" \ No newline at end of file diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/splash/SplashViewModelTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/splash/SplashViewModelTest.kt index 253974dd51..6ef9ad6810 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/splash/SplashViewModelTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/splash/SplashViewModelTest.kt @@ -20,6 +20,7 @@ import com.anytypeio.anytype.domain.spaces.GetLastOpenedSpace import com.anytypeio.anytype.domain.subscriptions.GlobalSubscriptionManager import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate +import com.anytypeio.anytype.presentation.auth.account.MigrationHelperDelegate import com.anytypeio.anytype.presentation.util.CoroutinesTestRule import java.util.Locale import kotlinx.coroutines.runBlocking @@ -89,6 +90,9 @@ class SplashViewModelTest { @Mock lateinit var spaceViewSubscriptionContainer: SpaceViewSubscriptionContainer + @Mock + lateinit var migrationHelperDelegate: MigrationHelperDelegate + lateinit var vm: SplashViewModel private val defaultSpaceConfig = StubConfig() @@ -119,7 +123,8 @@ class SplashViewModelTest { globalSubscriptionManager = globalSubscriptionManager, getLastOpenedSpace = getLastOpenedSpace, createObjectByTypeAndTemplate = createObjectByTypeAndTemplate, - spaceViews = spaceViewSubscriptionContainer + spaceViews = spaceViewSubscriptionContainer, + migration = migrationHelperDelegate ) } diff --git a/protocol/src/main/proto/commands.proto b/protocol/src/main/proto/commands.proto index 18f986d9b6..39924e8fb5 100644 --- a/protocol/src/main/proto/commands.proto +++ b/protocol/src/main/proto/commands.proto @@ -812,6 +812,54 @@ message Rpc { } } + message Migrate { + message Request { + option (no_auth) = true; + string id = 1; // Id of a selected account + string rootPath = 2; + } + + message Response { + Error error = 1; + message Error { + Code code = 1; + string description = 2; + + enum Code { + NULL = 0; // No error + UNKNOWN_ERROR = 1; // Any other errors + BAD_INPUT = 2; // Id or root path is wrong + + ACCOUNT_NOT_FOUND = 101; + CANCELED = 102; + NOT_ENOUGH_FREE_SPACE = 103; + // TODO: [storage] Add specific error codes for migration problems + } + } + } + } + + message MigrateCancel { + message Request { + option (no_auth) = true; + string id = 1; // Id of a selected account + } + + message Response { + Error error = 1; + message Error { + Code code = 1; + string description = 2; + + enum Code { + NULL = 0; // No error + UNKNOWN_ERROR = 1; // Any other errors + BAD_INPUT = 2; // Id or root path is wrong + } + } + } + } + message Select { /** * Front end to middleware request-to-launch-a specific account using account id and a root path @@ -854,6 +902,7 @@ message Rpc { FAILED_TO_FETCH_REMOTE_NODE_HAS_INCOMPATIBLE_PROTO_VERSION = 110; ACCOUNT_IS_DELETED = 111; ACCOUNT_LOAD_IS_CANCELED = 112; + ACCOUNT_STORE_NOT_MIGRATED = 113; CONFIG_FILE_NOT_FOUND = 200; CONFIG_FILE_INVALID = 201; diff --git a/protocol/src/main/proto/events.proto b/protocol/src/main/proto/events.proto index d2f121ac90..a5b5820be8 100644 --- a/protocol/src/main/proto/events.proto +++ b/protocol/src/main/proto/events.proto @@ -732,7 +732,7 @@ message Event { FaviconHash faviconHash = 6; Type type = 7; TargetObjectId targetObjectId = 8; - + message Url { string value = 1; From 7fe3f6d3068033c912b23be597baed275c43cea1 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Fri, 14 Feb 2025 12:35:59 +0100 Subject: [PATCH 36/78] DROID-3140 Space-level chat | Fix | Opening bookmark objects in browser (#2100) --- .../anytypeio/anytype/ui/chats/ChatFragment.kt | 11 +++++++++++ .../feature_chats/presentation/ChatViewModel.kt | 15 ++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/anytypeio/anytype/ui/chats/ChatFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/chats/ChatFragment.kt index da01f3b6b1..7ca853a84b 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/chats/ChatFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/chats/ChatFragment.kt @@ -251,6 +251,17 @@ class ChatFragment : BaseComposeFragment() { Timber.e(it, "Error while opening space member card") } } + is ChatViewModel.ViewModelCommand.Browse -> { + runCatching { + proceedWithAction( + SystemAction.OpenUrl( + command.url + ) + ) + }.onFailure { + Timber.e(it, "Error while opening bookmark from chat") + } + } } } } diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt index d2b7511cb7..02c124480c 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt @@ -5,6 +5,7 @@ import com.anytypeio.anytype.core_models.Block import com.anytypeio.anytype.core_models.Command import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.ObjectType +import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_models.chats.Chat import com.anytypeio.anytype.core_models.primitives.Space import com.anytypeio.anytype.core_models.primitives.SpaceId @@ -622,7 +623,18 @@ class ChatViewModel @Inject constructor( is ChatView.Message.Attachment.Link -> { val wrapper = attachment.wrapper if (wrapper != null) { - navigation.emit(wrapper.navigation()) + if (wrapper.layout == ObjectType.Layout.BOOKMARK) { + val bookmark = ObjectWrapper.Bookmark(wrapper.map) + val url = bookmark.source + if (!url.isNullOrEmpty()) { + commands.emit(ViewModelCommand.Browse(url)) + } else { + // If url not found, open bookmark object instead of browsing. + navigation.emit(wrapper.navigation()) + } + } else { + navigation.emit(wrapper.navigation()) + } } else { Timber.w("Wrapper is not found in attachment") } @@ -761,6 +773,7 @@ class ChatViewModel @Inject constructor( data object Exit : ViewModelCommand() data object OpenWidgets : ViewModelCommand() data class MediaPreview(val url: String) : ViewModelCommand() + data class Browse(val url: String) : ViewModelCommand() data class SelectChatReaction(val msg: Id) : ViewModelCommand() data class ViewChatReaction(val msg: Id, val emoji: String) : ViewModelCommand() data class ViewMemberCard(val member: Id, val space: SpaceId) : ViewModelCommand() From c79ecd3331585ab000b31cf722f2f027b5dfe4e7 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Fri, 14 Feb 2025 12:51:24 +0100 Subject: [PATCH 37/78] DROID-3233 App | Tech | Bump version (0.37.0) --- app/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/gradle.properties b/app/gradle.properties index b6e8f5a1a9..71fd3854a2 100644 --- a/app/gradle.properties +++ b/app/gradle.properties @@ -1,4 +1,4 @@ version.versionMajor=0 -version.versionMinor=36 +version.versionMinor=37 version.versionPatch=0 version.useDatedVersionName=false \ No newline at end of file From f9ac843ca27327c3d34b14f033f60459e7f58f7a Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Fri, 14 Feb 2025 14:17:42 +0100 Subject: [PATCH 38/78] DROID-3389 Space-level chat | Enhancement | Parse urls when sending message and include it in message markup (#2102) --- .../anytype/core_models/chats/Chat.kt | 3 +- .../anytype/core_utils/tools/Regex.kt | 4 +++ .../presentation/ChatViewModel.kt | 30 +++++++++++++++++-- 3 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 core-utils/src/main/java/com/anytypeio/anytype/core_utils/tools/Regex.kt diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/chats/Chat.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/chats/Chat.kt index 1c66fbdb22..4e28968cb2 100644 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/chats/Chat.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/chats/Chat.kt @@ -67,6 +67,7 @@ sealed class Chat { id: Id, text: String, attachments: List = emptyList(), + marks: List ) : Message = Message( id = id, createdAt = 0L, @@ -77,7 +78,7 @@ sealed class Chat { replyToMessageId = "", content = Content( text = text, - marks = emptyList(), + marks = marks, style = Block.Content.Text.Style.P ), order = "" diff --git a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/tools/Regex.kt b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/tools/Regex.kt new file mode 100644 index 0000000000..2bbb0bb94d --- /dev/null +++ b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/tools/Regex.kt @@ -0,0 +1,4 @@ +package com.anytypeio.anytype.core_utils.tools + + +const val DEFAULT_URL_REGEX = """(?:https?://|www\.)\S+|\b[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(/\S*)?\b""" \ No newline at end of file diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt index 02c124480c..e6e6a4eb18 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt @@ -11,6 +11,7 @@ import com.anytypeio.anytype.core_models.primitives.Space import com.anytypeio.anytype.core_models.primitives.SpaceId import com.anytypeio.anytype.core_ui.text.splitByMarks import com.anytypeio.anytype.core_utils.common.DefaultFileInfo +import com.anytypeio.anytype.core_utils.tools.DEFAULT_URL_REGEX import com.anytypeio.anytype.domain.auth.interactor.GetAccount import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers import com.anytypeio.anytype.domain.base.onFailure @@ -342,6 +343,28 @@ class ChatViewModel @Inject constructor( Timber.d("DROID-2635 OnMessageSent, markup: $markup}") } viewModelScope.launch { + + val urlRegex = Regex(DEFAULT_URL_REGEX) + val parsedUrls = buildList { + urlRegex.findAll(msg).forEach { match -> + val range = match.range + val url = match.value + + // Check if a LINK markup already exists in the same range + if (markup.none { it.range == range && it.type == Block.Content.Text.Mark.Type.LINK }) { + add( + Block.Content.Text.Mark( + range = range, + type = Block.Content.Text.Mark.Type.LINK, + param = url + ) + ) + } + } + } + + val normalizedMarkup = (markup + parsedUrls).sortedBy { it.range.first } + chatBoxMode.value = chatBoxMode.value.updateIsSendingBlocked(isBlocked = true) val attachments = buildList { val currAttachments = chatBoxAttachments.value @@ -456,7 +479,7 @@ class ChatViewModel @Inject constructor( message = Chat.Message.new( text = msg.trim(), attachments = attachments, - marks = markup + marks = normalizedMarkup ) ) ).onSuccess { (id, payload) -> @@ -479,7 +502,8 @@ class ChatViewModel @Inject constructor( message = Chat.Message.updated( id = mode.msg, text = msg.trim(), - attachments = editedMessage?.attachments.orEmpty() + attachments = editedMessage?.attachments.orEmpty(), + marks = normalizedMarkup ) ) ).onSuccess { @@ -500,7 +524,7 @@ class ChatViewModel @Inject constructor( text = msg.trim(), replyToMessageId = mode.msg, attachments = attachments, - marks = markup + marks = normalizedMarkup ) ) ).onSuccess { (id, payload) -> From d8673e0ac4064b0b96b87120ce6e7e0055db1539 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Fri, 14 Feb 2025 14:28:36 +0100 Subject: [PATCH 39/78] DROID-3381 Widgets | Fix | Automatically apply link layout for widgets where source is image, file, video or space member (#2090) --- .../anytype/presentation/home/HomeScreenViewModel.kt | 4 ++-- .../presentation/widgets/SelectWidgetSourceViewModel.kt | 4 +--- .../anytype/presentation/widgets/WidgetConfig.kt | 9 +++++++++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt index ff73588efb..7a7f58dd80 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt @@ -116,6 +116,7 @@ import com.anytypeio.anytype.presentation.widgets.TreeWidgetContainer import com.anytypeio.anytype.presentation.widgets.ViewId import com.anytypeio.anytype.presentation.widgets.Widget import com.anytypeio.anytype.presentation.widgets.WidgetActiveViewStateHolder +import com.anytypeio.anytype.presentation.widgets.WidgetConfig import com.anytypeio.anytype.presentation.widgets.WidgetContainer import com.anytypeio.anytype.presentation.widgets.WidgetDispatchEvent import com.anytypeio.anytype.presentation.widgets.WidgetId @@ -673,8 +674,7 @@ class HomeScreenViewModel( .withLatestFrom(spaceManager.observe()) { dispatch, config -> when (dispatch) { is WidgetDispatchEvent.SourcePicked.Default -> { - if (dispatch.sourceLayout == ObjectType.Layout.DATE.code || - dispatch.sourceLayout == ObjectType.Layout.PARTICIPANT.code) { + if (WidgetConfig.isLinkOnlyLayout(dispatch.sourceLayout)) { proceedWithCreatingWidget( ctx = config.widgets, source = dispatch.source, diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/SelectWidgetSourceViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/SelectWidgetSourceViewModel.kt index 75b9853fed..6d327f8bf9 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/SelectWidgetSourceViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/SelectWidgetSourceViewModel.kt @@ -208,9 +208,7 @@ class SelectWidgetSourceViewModel( isInEditMode = curr.isInEditMode ) } - if (view.layout == ObjectType.Layout.DATE || - view.layout == ObjectType.Layout.PARTICIPANT - ) { + if (view.layout != null && WidgetConfig.isLinkOnlyLayout(view.layout.code)) { isDismissed.value = true } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/WidgetConfig.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/WidgetConfig.kt index 5037916813..e5f27fb0e4 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/WidgetConfig.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/widgets/WidgetConfig.kt @@ -1,5 +1,6 @@ package com.anytypeio.anytype.presentation.widgets +import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.core_models.ObjectTypeIds import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_models.SupportedLayouts @@ -26,6 +27,14 @@ object WidgetConfig { && SupportedLayouts.isSupportedForWidgets(obj.layout) } + fun isLinkOnlyLayout(code: Int): Boolean { + return code == ObjectType.Layout.DATE.code || + code == ObjectType.Layout.PARTICIPANT.code || + code == ObjectType.Layout.IMAGE.code || + code == ObjectType.Layout.VIDEO.code || + code == ObjectType.Layout.FILE.code + } + fun resolveListWidgetLimit( isCompact: Boolean, isGallery: Boolean = false, From 4dc4cc8e2f4dfc4ff8ec81c200a508e4a22414be Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Fri, 14 Feb 2025 15:09:40 +0100 Subject: [PATCH 40/78] DROID-3389 Space-level chat | Fix | Fix range for URL markup flow --- .../anytype/feature_chats/presentation/ChatViewModel.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt index e6e6a4eb18..6d75ab1897 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt @@ -348,13 +348,15 @@ class ChatViewModel @Inject constructor( val parsedUrls = buildList { urlRegex.findAll(msg).forEach { match -> val range = match.range + // Adjust the range to include the last character (inclusive end range) + val adjustedRange = range.first..range.last + 1 val url = match.value // Check if a LINK markup already exists in the same range - if (markup.none { it.range == range && it.type == Block.Content.Text.Mark.Type.LINK }) { + if (markup.none { it.range == adjustedRange && it.type == Block.Content.Text.Mark.Type.LINK }) { add( Block.Content.Text.Mark( - range = range, + range = adjustedRange, type = Block.Content.Text.Mark.Type.LINK, param = url ) From 88335a10eddbd182a4134c24ecd0e07db443ce9e Mon Sep 17 00:00:00 2001 From: Nadezhda-Gurova <84003815+Nadezhda-Gurova@users.noreply.github.com> Date: Mon, 17 Feb 2025 11:57:30 +0100 Subject: [PATCH 41/78] DROID-3264 Editor | Enhancement | Improved image file layout and features (#2045) --- .../anytype/ui/sets/ObjectSetFragment.kt | 27 +++--- .../core_ui/features/editor/BlockAdapter.kt | 9 +- .../other/CustomImageResizeTransformation.kt | 71 +++++++++++++++ .../features/editor/holders/other/Title.kt | 90 ++++++++++++++----- .../core_ui/widgets/ObjectIconWidget.kt | 2 +- .../src/main/res/layout/item_block_title.xml | 15 ++-- .../main/res/layout/item_block_title_file.xml | 2 + core-ui/src/main/res/values/dimens.xml | 1 + .../presentation/editor/EditorViewModel.kt | 19 ++++ .../editor/editor/listener/ListenerType.kt | 1 + .../editor/render/DefaultBlockViewRenderer.kt | 2 +- 11 files changed, 190 insertions(+), 49 deletions(-) create mode 100644 core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/other/CustomImageResizeTransformation.kt diff --git a/app/src/main/java/com/anytypeio/anytype/ui/sets/ObjectSetFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/sets/ObjectSetFragment.kt index 433da47e82..992a4e076d 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/sets/ObjectSetFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/sets/ObjectSetFragment.kt @@ -824,29 +824,28 @@ open class ObjectSetFragment : .launchIn(lifecycleScope) } - binding.objectHeader.root.findViewById(R.id.imageIcon).apply { - if (header.title.image != null) visible() else gone() + binding.objectHeader.root.findViewById(R.id.imageIcon).apply { jobs += this.clicks() .throttleFirst() .onEach { vm.onObjectIconClicked() } .launchIn(lifecycleScope) + + if (header.title.image != null) { + this.visible() + Glide + .with(this) + .load(header.title.image) + .centerCrop() + .into(this) + } else { + this.gone() + this.setImageDrawable(null) + } } binding.objectHeader.root.findViewById(R.id.emojiIcon) .setEmojiOrNull(header.title.emoji) - if (header.title.image != null) { - binding.objectHeader.root.findViewById(R.id.imageIcon).apply { - Glide - .with(this) - .load(header.title.image) - .centerCrop() - .into(this) - } - } else { - binding.objectHeader.root.findViewById(R.id.imageIcon).setImageDrawable(null) - } - setCover( coverColor = header.title.coverColor, coverGradient = header.title.coverGradient, diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/BlockAdapter.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/BlockAdapter.kt index 68cfeba72b..5390f19240 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/BlockAdapter.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/BlockAdapter.kt @@ -1339,7 +1339,8 @@ class BlockAdapter( bind( item = blocks[position] as BlockView.Title.Basic, onPageIconClicked = onPageIconClicked, - onCoverClicked = onCoverClicked + onCoverClicked = onCoverClicked, + click = onClickListener ) setTextInputClickListener { if (Build.VERSION.SDK_INT == N || Build.VERSION.SDK_INT == N_MR1) { @@ -1356,7 +1357,8 @@ class BlockAdapter( bind( item = blocks[position] as BlockView.Title.Todo, onPageIconClicked = onPageIconClicked, - onCoverClicked = onCoverClicked + onCoverClicked = onCoverClicked, + click = onClickListener ) setTextInputClickListener { if (Build.VERSION.SDK_INT == N || Build.VERSION.SDK_INT == N_MR1) { @@ -1373,7 +1375,8 @@ class BlockAdapter( bind( item = blocks[position] as BlockView.Title.Profile, onProfileIconClicked = onClickListener, - onCoverClicked = onCoverClicked + onCoverClicked = onCoverClicked, + click = onClickListener ) setTextInputClickListener { if (Build.VERSION.SDK_INT == N || Build.VERSION.SDK_INT == N_MR1) { diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/other/CustomImageResizeTransformation.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/other/CustomImageResizeTransformation.kt new file mode 100644 index 0000000000..d59bcdc7c4 --- /dev/null +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/other/CustomImageResizeTransformation.kt @@ -0,0 +1,71 @@ +package com.anytypeio.anytype.core_ui.features.editor.holders.other + +import android.graphics.Bitmap +import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool +import com.bumptech.glide.load.resource.bitmap.BitmapTransformation +import java.security.MessageDigest +import timber.log.Timber + +class CustomImageResizeTransformation( + private val maxWidth: Int, + private val maxHeight: Int +) : BitmapTransformation() { + + override fun transform( + pool: BitmapPool, + toTransform: Bitmap, + outWidth: Int, + outHeight: Int + ): Bitmap { + return try { + val imageWidth = toTransform.width + val imageHeight = toTransform.height + val targetAspectRatio = maxWidth.toFloat() / maxHeight + + when { + imageWidth > maxWidth && imageHeight > maxHeight -> { + val imageAspectRatio = imageWidth.toFloat() / imageHeight + + if (imageAspectRatio > targetAspectRatio) { + val cropWidth = (imageHeight * targetAspectRatio).toInt() + val cropStartX = (imageWidth - cropWidth) / 2 + Bitmap.createBitmap(toTransform, cropStartX, 0, cropWidth, imageHeight) + } else { + val cropHeight = (imageWidth / targetAspectRatio).toInt() + val cropStartY = (imageHeight - cropHeight) / 2 + Bitmap.createBitmap(toTransform, 0, cropStartY, imageWidth, cropHeight) + } + } + imageWidth > maxWidth && imageHeight <= maxHeight -> { + val scaleFactor = maxWidth.toFloat() / imageWidth + val newHeight = (imageHeight * scaleFactor).toInt() + Bitmap.createScaledBitmap(toTransform, maxWidth, newHeight, true) + } + imageHeight > maxHeight && imageWidth <= maxWidth -> { + val cropHeight = (imageWidth / targetAspectRatio).toInt() + val cropStartY = (imageHeight - cropHeight) / 2 + Bitmap.createBitmap(toTransform, 0, cropStartY, imageWidth, cropHeight) + } + else -> toTransform + } + } catch (e: IllegalArgumentException) { + Timber.e( + e, + "Failed to transform bitmap: Invalid dimensions or parameters provided. Width: ${toTransform.width}, Height: ${toTransform.height}, MaxWidth: $maxWidth, MaxHeight: $maxHeight" + ) + toTransform + } catch (e: OutOfMemoryError) { + Timber.e( + e, + "Failed to transform bitmap: Insufficient memory to process the image." + ) + toTransform + } + } + + override fun equals(other: Any?) = other is CustomImageResizeTransformation + override fun hashCode() = "CustomImageResizeTransformation".hashCode() + override fun updateDiskCacheKey(messageDigest: MessageDigest) { + messageDigest.update("CustomImageResizeTransformation".toByteArray()) + } +} \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/other/Title.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/other/Title.kt index 442aa63fb7..12ab093d4f 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/other/Title.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/other/Title.kt @@ -1,6 +1,9 @@ package com.anytypeio.anytype.core_ui.features.editor.holders.other +import android.content.Context +import android.graphics.Bitmap import android.text.Spannable +import android.util.TypedValue import android.view.View import android.view.inputmethod.InputMethodManager import android.widget.FrameLayout.LayoutParams @@ -38,6 +41,11 @@ import com.anytypeio.anytype.presentation.editor.editor.listener.ListenerType import com.anytypeio.anytype.presentation.editor.editor.model.BlockView import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool +import com.bumptech.glide.load.resource.bitmap.BitmapTransformation +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.Target +import java.security.MessageDigest import timber.log.Timber sealed class Title(view: View) : BlockViewHolder(view), TextHolder { @@ -50,7 +58,8 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { fun bind( item: BlockView.Title, - onCoverClicked: () -> Unit + onCoverClicked: () -> Unit, + click: (ListenerType) -> Unit ) { setImage(item) applyTextColor(item) @@ -162,22 +171,34 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { } } } - open fun setImage(item: BlockView.Title) { + Timber.d("Setting image for ${item.id}, image=${item.image}") item.image?.let { url -> image.visible() - Glide - .with(image) - .load(url) - .centerCrop() - .into(image) - } ?: apply { image.setImageDrawable(null) } + loadImageWithCustomResize(image, url) + } ?: run { image.setImageDrawable(null) } } - private fun showKeyboard() { - content.postDelayed(16L) { - imm().showSoftInput(content, InputMethodManager.SHOW_IMPLICIT) - } + private fun loadImageWithCustomResize(imageView: ImageView, url: String) { + val context = imageView.context + val displayMetrics = context.resources.displayMetrics + val screenWidth = displayMetrics.widthPixels + val maxWidth = screenWidth - dpToPx(context, 40) + val maxHeight = dpToPx(context, 443) + + Glide.with(context) + .load(url) + .override(Target.SIZE_ORIGINAL) + .apply(RequestOptions().transform(CustomImageResizeTransformation(maxWidth, maxHeight))) + .into(imageView) + } + + private fun dpToPx(context: Context, dp: Int): Int { + return TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + dp.toFloat(), + context.resources.displayMetrics + ).toInt() } open fun processPayloads( @@ -275,14 +296,25 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { fun bind( item: BlockView.Title.Basic, onPageIconClicked: () -> Unit, - onCoverClicked: () -> Unit + onCoverClicked: () -> Unit, + click: (ListenerType) -> Unit ) { super.bind( item = item, - onCoverClicked = onCoverClicked + onCoverClicked = onCoverClicked, + click = click ) setEmoji(item) applySearchHighlights(item) + + image.setOnClickListener { + click( + ListenerType.Picture.TitleView( + item = item + ) + ) + } + if (item.mode == BlockView.Mode.EDIT) { icon.setOnClickListener { onPageIconClicked() } image.setOnClickListener { onPageIconClicked() } @@ -299,9 +331,11 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { topMargin = dimen(R.dimen.dp_10) } binding.imageIcon.updateLayoutParams { - topMargin = if (!item.hasCover) dimen(R.dimen.dp_51) else dimen(R.dimen.dp_102) + topMargin = + if (!item.hasCover) dimen(R.dimen.dp_51) else dimen(R.dimen.dp_102) } } + item.emoji != null -> { binding.imageIcon.gone() binding.docEmojiIconContainer.visible() @@ -309,9 +343,11 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { topMargin = dimen(R.dimen.dp_12) } binding.docEmojiIconContainer.updateLayoutParams { - topMargin = if (!item.hasCover) dimen(R.dimen.dp_60) else dimen(R.dimen.dp_120) + topMargin = + if (!item.hasCover) dimen(R.dimen.dp_60) else dimen(R.dimen.dp_120) } } + else -> { binding.imageIcon.gone() binding.docEmojiIconContainer.gone() @@ -397,9 +433,10 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { override val content: TextInputWidget = binding.title override val selectionView: View = itemView - private val gradientView : ComposeView get() = binding - .docProfileIconContainer - .findViewById(R.id.gradient) + private val gradientView: ComposeView + get() = binding + .docProfileIconContainer + .findViewById(R.id.gradient) private val iconText = binding.imageText private var hasImage = false @@ -411,11 +448,13 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { fun bind( item: BlockView.Title.Profile, onProfileIconClicked: (ListenerType) -> Unit, - onCoverClicked: () -> Unit + onCoverClicked: () -> Unit, + click: (ListenerType) -> Unit ) { super.bind( item = item, - onCoverClicked = onCoverClicked + onCoverClicked = onCoverClicked, + click = click ) setupMargins(item) applySearchHighlights(item) @@ -512,11 +551,13 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { fun bind( item: BlockView.Title.Todo, onPageIconClicked: () -> Unit, - onCoverClicked: () -> Unit + onCoverClicked: () -> Unit, + click: (ListenerType) -> Unit ) { super.bind( item = item, - onCoverClicked = onCoverClicked + onCoverClicked = onCoverClicked, + click = click ) setLocked(item.mode) checkbox.isSelected = item.isChecked @@ -576,7 +617,8 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { ) { super.bind( item = item, - onCoverClicked = {} + onCoverClicked = {}, + click = {} ) icon.setIcon(item.icon) } diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/ObjectIconWidget.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/ObjectIconWidget.kt index 482afdbf79..e575ab68d7 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/ObjectIconWidget.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/ObjectIconWidget.kt @@ -392,4 +392,4 @@ class ObjectIconWidget @JvmOverloads constructor( this.width = emojiSize } } -} \ No newline at end of file +} diff --git a/core-ui/src/main/res/layout/item_block_title.xml b/core-ui/src/main/res/layout/item_block_title.xml index a4335ced61..8e97a2f12e 100644 --- a/core-ui/src/main/res/layout/item_block_title.xml +++ b/core-ui/src/main/res/layout/item_block_title.xml @@ -52,21 +52,24 @@ + tools:visibility="visible" /> 102dp 120dp 203dp + 443dp 4dp 1dp diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt index 6f006a7c27..86cf589db6 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt @@ -3853,6 +3853,25 @@ class EditorViewModel( else -> Unit } } + is ListenerType.Picture.TitleView -> { + when (mode) { + EditorMode.Edit, EditorMode.Locked, EditorMode.Read -> { + if (!clicked.item.image.isNullOrEmpty()){ + dispatch( + Command.OpenFullScreenImage( + target = "", + url = clicked.item.image + ) + ) + } else { + Timber.e("Can't proceed with opening full screen image") + sendToast("Something went wrong. Couldn't open image") + } + } + EditorMode.Select -> onBlockMultiSelectClicked(clicked.item.id) + else -> Unit + } + } is ListenerType.Picture.View -> { when (mode) { EditorMode.Edit, EditorMode.Locked, EditorMode.Read -> { diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/listener/ListenerType.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/listener/ListenerType.kt index 967121370f..e75cbdd261 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/listener/ListenerType.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/listener/ListenerType.kt @@ -23,6 +23,7 @@ sealed interface ListenerType { } sealed class Picture: ListenerType { + data class TitleView(val item: BlockView.Title.Basic) : Picture() data class View(val target: String) : Picture() data class Placeholder(val target: String) : Picture() data class Upload(val target: String) : Picture() diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/DefaultBlockViewRenderer.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/DefaultBlockViewRenderer.kt index 73e54b6c7c..f69226bb9a 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/DefaultBlockViewRenderer.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/DefaultBlockViewRenderer.kt @@ -1516,7 +1516,7 @@ class DefaultBlockViewRenderer @Inject constructor( text = fieldParser.getObjectName(currentObject), image = currentObject.iconImage?.let { image -> if (image.isNotBlank()) - urlBuilder.thumbnail(image) + urlBuilder.large(image) else null }, From c2ec6479631497689de362ad7e942ada89ba8995 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Mon, 17 Feb 2025 20:35:14 +0100 Subject: [PATCH 42/78] DROID-3391 Migration | Tech | Fix migration error description (#2107) --- localization/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/localization/src/main/res/values/strings.xml b/localization/src/main/res/values/strings.xml index e5c1547edf..7e53172d00 100644 --- a/localization/src/main/res/values/strings.xml +++ b/localization/src/main/res/values/strings.xml @@ -1877,7 +1877,7 @@ Please provide specific details of your needs here. It cannot be restored after confirmation Delete this message? Migration is in progress - This shouldn’t take long. Thanks for your patience. + This may take some time. Please don’t close the app until the process is complete. Try again Migration failed Please free up space and run the process again. From be655252df091c5544058ab1cb0d537d7d75368a Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Mon, 17 Feb 2025 21:17:38 +0100 Subject: [PATCH 43/78] DROID-3364 Objects | Fix | Remove max lines limit for description block in sets and editor (#2109) --- app/src/main/res/layout/layout_object_set_header.xml | 1 - core-ui/src/main/res/values/styles.xml | 1 - 2 files changed, 2 deletions(-) diff --git a/app/src/main/res/layout/layout_object_set_header.xml b/app/src/main/res/layout/layout_object_set_header.xml index bd5f6d006f..df57ab26ac 100644 --- a/app/src/main/res/layout/layout_object_set_header.xml +++ b/app/src/main/res/layout/layout_object_set_header.xml @@ -84,7 +84,6 @@ android:ellipsize="end" android:hint="@string/description" android:inputType="textMultiLine" - android:maxLines="3" android:textColorHint="@color/text_tertiary" tools:text="Description" /> diff --git a/core-ui/src/main/res/values/styles.xml b/core-ui/src/main/res/values/styles.xml index 8aa284b3a6..131c596dba 100644 --- a/core-ui/src/main/res/values/styles.xml +++ b/core-ui/src/main/res/values/styles.xml @@ -446,7 +446,6 @@