diff --git a/analysis_options.yaml b/analysis_options.yaml index e86c8d07b..b176da65b 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -7,6 +7,17 @@ analyzer: linter: rules: + # TODO: convert to true + prefer_const_constructors: false + lines_longer_than_80_chars: false + avoid_redundant_argument_values: false + prefer_typing_uninitialized_variables: false + empty_catches: false + avoid_dynamic_calls: false + # sort_constructors_first: true + # always_put_required_named_parameters_first: true + # always_specify_types: true + # Disabled file_names: false type_annotate_public_apis: false @@ -18,21 +29,9 @@ linter: prefer_conditional_assignment: false sized_box_for_whitespace: false always_use_package_imports: false + require_trailing_commas: false use_build_context_synchronously: false # powers most of the navigation... # Enabled prefer_single_quotes: true - - # sort_constructors_first: true - # prefer_double_quotes: true - # public_member_api_docs: true - # always_specify_types: true - # always_put_required_named_parameters_first: false - - # TODO: - prefer_const_constructors: false - lines_longer_than_80_chars: false - avoid_redundant_argument_values: false - prefer_typing_uninitialized_variables: false - empty_catches: false diff --git a/assets/translations/ar.json b/assets/translations/ar.json index ff661862b..1817c01db 100644 --- a/assets/translations/ar.json +++ b/assets/translations/ar.json @@ -224,7 +224,7 @@ "title-dialog-draft-preview": "معاينة المسودة", "label-color": "اللون", "button-text-go-back": "الرجوع للخلف", - "list-item-user-details-confirm-start-chat": "الدردشة مع {}", + "list-item-user-details-start-chat": "الدردشة مع {}", "label-search-unencrypted": "البحث غير مشفر", "label-chat-settings": "إعدادات الدردشة", "button-gallery": "المعرض", diff --git a/assets/translations/cs.json b/assets/translations/cs.json index 93ea446d2..b69f113f9 100644 --- a/assets/translations/cs.json +++ b/assets/translations/cs.json @@ -248,7 +248,7 @@ "list-item-settings-proxy-password": "Proxy heslo", "list-item-settings-sync-interval": "Interval synchronizace", "list-item-user-details-invite-to-room": "Pozvat do místnosti", - "list-item-user-details-confirm-start-chat": "Psát si s {}", + "list-item-user-details-start-chat": "Psát si s {}", "list-item-user-details-send-message": "Poslat správu", "list-item-advanced-settings-start-background": "Spustit službu na pozadí", "list-item-advanced-settings-test-sync-loop": "Test synchronizace na pozadí", diff --git a/assets/translations/de.json b/assets/translations/de.json index d111994a1..ca5c370e1 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -202,7 +202,7 @@ "list-item-chat-detail-view-key": "Schlüssel anzeigen", "list-item-settings-sync-toggle": "Synchronisation an/aus", "list-item-user-details-invite-to-room": "In Raum einladen", - "list-item-user-details-confirm-start-chat": "Reden mit {}", + "list-item-user-details-start-chat": "Reden mit {}", "list-item-user-details-unblock-user": "Benutzer entsperren", "list-item-user-details-block-user": "Benutzer sperren", "list-item-image-options-photo-select-method": "Fotoauswahlverfahren", diff --git a/assets/translations/en-Shaw.json b/assets/translations/en-Shaw.json index 4738153be..d3da72fb9 100644 --- a/assets/translations/en-Shaw.json +++ b/assets/translations/en-Shaw.json @@ -153,7 +153,7 @@ "list-item-settings-sync-interval": "𐑕𐑦𐑙𐑒 𐑦𐑯𐑑𐑼𐑝𐑩𐑤", "list-item-settings-sync-toggle": "𐑑𐑪𐑜𐑩𐑤 𐑕𐑦𐑙𐑒𐑦𐑙", "list-item-user-details-invite-to-room": "𐑦𐑯𐑝𐑲𐑑 𐑑 𐑮𐑵𐑥", - "list-item-user-details-confirm-start-chat": "𐑗𐑨𐑑 𐑢𐑦𐑞 {}", + "list-item-user-details-start-chat": "𐑗𐑨𐑑 𐑢𐑦𐑞 {}", "list-item-user-details-send-message": "𐑕𐑧𐑯𐑛 𐑩 𐑥𐑧𐑕𐑦𐑡", "list-item-user-details-view-profile": "𐑝𐑿 𐑐𐑮𐑴𐑓𐑲𐑤", "list-item-user-details-unblock-user": "𐑳𐑯𐑚𐑤𐑪𐑒 𐑿𐑟𐑼", diff --git a/assets/translations/en.json b/assets/translations/en.json index 132daf6fd..47d268c21 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -209,7 +209,7 @@ "list-item-settings-manual-sync": "Manual Sync", "list-item-settings-force-full-sync": "Force Full Sync", "list-item-user-details-invite-to-room": "Invite to Room", - "list-item-user-details-confirm-start-chat": "Chat with {}", + "list-item-user-details-start-chat": "Chat with {}", "list-item-user-details-send-message": "Send A Message", "list-item-user-details-view-profile": "View Profile", "list-item-user-details-unblock-user": "Unblock User", @@ -231,7 +231,7 @@ "list-item-received": "Received", "list-item-via": "Platform", "list-item-from": "From", - "list-item-read-by": "Read By", + "list-item-read-by": "Seen By", "alert-restart-app-effect": "You'll need to close and restart the app for this to take effect", "alert-invite-user-unknown": "This user doesn't appear to exist within matrix, but you can attempt to invite them anyway.\n\nMake sure you have the correct name before trying.", "alert-feature-in-progress": "🛠 This feature is coming soon", diff --git a/assets/translations/eu.json b/assets/translations/eu.json index 098f7ca8c..53d60f4f9 100644 --- a/assets/translations/eu.json +++ b/assets/translations/eu.json @@ -99,7 +99,7 @@ "list-item-settings-sync-interval": "Sinkronizazio denbora-tartea", "list-item-settings-sync-toggle": "Piztu edo itzali sinkronizazioa", "list-item-user-details-invite-to-room": "Gonbidatu gelara", - "list-item-user-details-confirm-start-chat": "Txateatu {}(r)ekin", + "list-item-user-details-start-chat": "Txateatu {}(r)ekin", "list-item-user-details-send-message": "Bidali mezua", "list-item-user-details-view-profile": "Ikusi profila", "list-item-user-details-unblock-user": "Desblokeatu erabiltzailea", diff --git a/assets/translations/fa.json b/assets/translations/fa.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/assets/translations/fa.json @@ -0,0 +1 @@ +{} diff --git a/assets/translations/fr.json b/assets/translations/fr.json index 3a02b80b3..e71344cb6 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -192,7 +192,7 @@ "list-item-settings-sync-interval": "Intervalle de synchronisation", "list-item-settings-sync-toggle": "Activer/Désactiver la synchronisation", "list-item-user-details-invite-to-room": "Inviter dans le groupe", - "list-item-user-details-confirm-start-chat": "Discuter avec {}", + "list-item-user-details-start-chat": "Discuter avec {}", "list-item-user-details-view-profile": "Voir le profil", "list-item-user-details-unblock-user": "Débloquer l'utilisateur", "list-item-user-details-block-user": "Bloquer l'utilisateur", @@ -269,5 +269,72 @@ "content-notification-style-type-latest": "Afficher une notification avec le message le plus récent", "content-notification-style-type-itemized": "Une nouvelle notification apparaîtra pour chaque nouveau message", "label-show-attachment-options": "Ouvrir les options de pièce jointe", - "content-support-dialog": "La communauté Syphon est là pour vous aider dans la mesure de ses possibilités.\n\nN'hésitez pas à rejoindre notre discussion d'assistance ou à nous envoyer un courriel. Veuillez noter que la réponse à un courriel peut prendre de 1 à 5 jours ouvrables !" + "content-support-dialog": "La communauté Syphon est là pour vous aider dans la mesure de ses possibilités.\n\nN'hésitez pas à rejoindre notre discussion d'assistance ou à nous envoyer un courriel. Veuillez noter que la réponse à un courriel peut prendre de 1 à 5 jours ouvrables !", + "header-ordering": "Classement", + "header-update-password": "Mettre à jour le mot de passe", + "title-dialog-sync-interval": "Modifier l'intervalle de synchronisation", + "title-dialog-attempt-chat-with-user": "Essayer de discuter avec {}", + "title-blocked-users": "Utilisateurs bloqués", + "title-export-session-keys": "Exporter les clés de session", + "title-dialog-confirm-deactivate-account": "Confirmer la désactivation du compte", + "title-dialog-confirm-deactivate-account-final": "Confirmer la désactivation finale du compte", + "title-dialog-remove-screen-lock": "Supprimer le verrouillage de l'écran", + "title-dialog-enter-screen-lock-pin": "Saisissez votre code de verrouillage actuel", + "title-dialog-verify-new-screen-lock-pin": "Entrez à nouveau votre code pour confirmer", + "subtitle-settings-show-membership-events": "Afficher les changements de membres dans la discussion", + "subtitle-settings-enter-sends": "En appuyant sur la touche Entrée, vous enverrez un message", + "subtitle-settings-dismiss-keyboard": "Désactiver le clavier après avoir envoyé un message", + "subtitle-settings-view-uploaded-media": "Voir toutes les données téléversées, même celles qui ne sont pas accessibles à partir des messages", + "subtitle-theme-settings": "Thème {}, police {}", + "subtitle-privacy-settings": "Verrouillage de l'écran {}, verrouillage de l'enregistrement {}", + "label-seconds": "secondes", + "label-new-password": "Nouveau mot de passe", + "button-text-remove": "Retirer", + "list-item-settings-show-membership-events": "Afficher les évènements d'adhésion", + "list-item-settings-enter-sends": "La touche Entrée envoie", + "list-item-settings-24h-format": "Format de l'heure en 24 heures", + "list-item-settings-dismiss-keyboard": "Désactiver le clavier", + "list-item-settings-group-by": "Regrouper par", + "list-item-settings-view-uploaded-media": "Voir tous les médias téléversés", + "list-item-settings-auto-download": "Téléchargement automatique", + "list-item-settings-when-using-wi-fi": "En Wi-Fi", + "list-item-settings-when-roaming": "En itinérance", + "list-item-settings-manual-sync": "Synchronisation manuelle", + "list-item-settings-force-full-sync": "Forcer la synchronisation complète", + "content-logout-confirm": "Voulez-vous vraiment vous déconnecter ?", + "content-logout-multiaccount-confirm": "\n\nComme vous avez d'autres comptes, la déconnexion vous fera passer à une autre session de compte.", + "content-remove-screen-lock": "Voulez-vous vraiment supprimer le verrouillage de l'écran ? Ceci supprimera également la protection par NIP du cache", + "title-dialog-logout": "Se déconnecter", + "header-media": "Média", + "subtitle-settings-24h-format": "Afficher l'horodatage des messages au format 24 heures", + "header-general": "Général", + "header-media-auto-download": "Téléchargement automatique des médias", + "title-dialog-chat-with-user": "Discuter avec {}", + "title-import-session-keys": "Importer les clés de session", + "title-dialog-backup-session-keys": "Clés de session de sauvegarde", + "title-dialog-enter-new-screen-lock-pin": "Saisissez votre nouveau code de verrouillage", + "label-timestamp": "Horodatage", + "label-current-password": "Mot de passe actuel", + "list-item-settings-language": "Langue", + "subtitle-images-audio-video-files": "Images, audio, vidéo, fichiers", + "subtitle-force-full-sync": "Effectuer une synchronisation complète forcée de toutes les données et de tous les messages de l'utilisateur", + "label-none": "Aucun", + "label-version": "Version", + "label-search-for-user": "Rechercher un utilisateur…", + "button-text-import": "importer", + "subtitle-manual-sync": "Effectuer une synchronisation forcée de Matrix en fonction de l'horodatage de la dernière synchronisation", + "label-stopped": "Arrêté", + "list-item-settings-sort-by": "Trier par", + "label-syncing": "Synchronisation", + "button-text-confirm-delete-keys": "Supprimer les clés", + "label-confirm-new-password": "Confirmer le nouveau mot de passe", + "list-item-settings-when-using-mobile-data": "En utilisant les données mobiles", + "alert-storage-access-required-for-keys": "Saisissez le mot de passe pour cette importation de clé de session.", + "alert-wait-for-full-sync-before-switching": "Attendez la fin de la synchronisation complète avant de changer de compte", + "content-export-session-keys": "Saisissez un mot de passe pour cette sauvegarde de clé de session.\n\nVeuillez noter que l'exportation peut prendre un certain temps.", + "alert-log-out-enable-multiaccount": "Vous devez vous déconnecter de votre\\nsession actuelle pour activer le multicompte", + "content-import-session-keys-enter-password": "Saisissez le mot de passe pour cette importation de clé de session.", + "content-export-session-keys-enter-password": "Saisissez un mot de passe pour crypter vos clés de session.", + "content-import-session-keys": "Saisissez le mot de passe pour cette importation de clé de session.\n\nVeuillez noter que l'importation peut prendre un certain temps.", + "semantics-image-password-update": "Utilisateur imaginant un mot de passe dans un tourbillon de vent" } diff --git a/assets/translations/hu.json b/assets/translations/hu.json index 835b7e873..bb4b6a78b 100644 --- a/assets/translations/hu.json +++ b/assets/translations/hu.json @@ -210,7 +210,7 @@ "list-item-chat-detail-notification-setting": "Értesítési beállítások", "button-chat-details": "beszélgetés részletei", "list-item-chat-detail-vibrate": "Rezgés", - "list-item-user-details-confirm-start-chat": "Beszélgetés {} felhasználóval", + "list-item-user-details-start-chat": "Beszélgetés {} felhasználóval", "list-item-image-options-remove-photo": "Kép eltávolítása", "list-item-advanced-settings-stop-background": "Összes Service megállítása", "content-notification-style-type-itemized": "Új értesítés mutatása minden egyes új üzenethez", diff --git a/assets/translations/id.json b/assets/translations/id.json index 43df187d8..068bc0194 100644 --- a/assets/translations/id.json +++ b/assets/translations/id.json @@ -208,7 +208,7 @@ "list-item-settings-sync-interval": "Interval Sinkronisasi", "list-item-settings-sync-toggle": "Alih Sinkronisasi", "list-item-user-details-invite-to-room": "Undang ke Ruangan", - "list-item-user-details-confirm-start-chat": "Obrol dengan {}", + "list-item-user-details-start-chat": "Obrol dengan {}", "list-item-user-details-send-message": "Kirim Sebuah Pesan", "list-item-user-details-view-profile": "Tampilkan Profil", "list-item-user-details-unblock-user": "Hilangkan Pemblokiran Pengguna", @@ -269,5 +269,72 @@ "list-item-chat-detail-vibrate": "Getaran", "content-notification-style-type-latest": "Tampilkan satu notifikasi dengan pesan terbaru", "label-show-attachment-options": "Buka opsi lampiran", - "content-support-dialog": "Komunitas Syphon ada untuk membantu Anda sebanyak yang kami bisa.\n\nSilakan bergabung ke obrolan Dukungam kami atau kirim kami sebuah email. Diingat bahwa email dapat membutuhkan 1-5+ hari kerja untuk sebuah balasan!" + "content-support-dialog": "Komunitas Syphon ada untuk membantu Anda sebanyak yang kami bisa.\n\nSilakan bergabung ke obrolan Dukungam kami atau kirim kami sebuah email. Diingat bahwa email dapat membutuhkan 1-5+ hari kerja untuk sebuah balasan!", + "label-syncing": "Menyinkron", + "header-ordering": "Urutan", + "header-media-auto-download": "Pengunduhan media otomatis", + "title-dialog-sync-interval": "Atur Interval Penyinkronan", + "title-dialog-chat-with-user": "Obrol dengan {}", + "title-dialog-attempt-chat-with-user": "Coba mengobrol dengan {}", + "title-import-session-keys": "Impor Kunci Sesi", + "title-export-session-keys": "Ekspor Kunci Sesi", + "title-dialog-confirm-deactivate-account": "Konfirmasi Penonaktifan Akun", + "title-dialog-backup-session-keys": "Cadangkan Kunci Sesi", + "title-dialog-enter-screen-lock-pin": "Masukkan pin kunci layar Anda saat ini", + "subtitle-settings-enter-sends": "Menekan tombol enter akan mengirimkan sebuah pesan", + "subtitle-settings-dismiss-keyboard": "Abaikan keyboard setelah mengirimkan sebuah pesan", + "subtitle-settings-view-uploaded-media": "Lihat semua data yang terunggah, bahkan yang tidak dapat diakses dari pesan", + "subtitle-manual-sync": "Lakukan sebuah penyinkronan matrix secara paksa berdasarkan stempel waktu penyinkronan terakhir", + "subtitle-force-full-sync": "Lakukan penyinkronan penuh secara paksa untuk semua data dan pesan-pesan pengguna", + "label-timestamp": "Stempel Waktu", + "label-none": "Tidak Ada", + "label-stopped": "Diberhentikan", + "label-version": "Versi", + "label-search-for-user": "Cari pengguna...", + "label-new-password": "Kata Sandi Baru", + "label-confirm-new-password": "Konfirmasi Kata Sandi Baru", + "list-item-settings-dismiss-keyboard": "Abaikan Keyboard", + "list-item-settings-sort-by": "Urut Berdasarkan", + "list-item-settings-group-by": "Kelompok Berdasarkan", + "list-item-settings-view-uploaded-media": "Lihat semua Media yang terunggah", + "list-item-settings-auto-download": "Pengunduhan Otomatis", + "list-item-settings-when-using-mobile-data": "Ketika menggunakan jaringan seluler", + "list-item-settings-when-using-wi-fi": "Ketika menggunakan Wi-Fi", + "list-item-settings-when-roaming": "Ketika Roaming", + "list-item-settings-force-full-sync": "Paksa Sinkron Penuh", + "title-dialog-logout": "Keluar", + "header-general": "Umum", + "header-media": "Media", + "header-update-password": "Perbarui Kata Sandi", + "title-dialog-remove-screen-lock": "Hilangkan Kunci Layar", + "title-blocked-users": "Pengguna Yang Diblokir", + "title-dialog-confirm-deactivate-account-final": "Konfirmasi Terakhir Penonaktifan Akun", + "button-text-remove": "Hapus", + "title-dialog-verify-new-screen-lock-pin": "Masukkan ulang pin Anda untuk memverifikasi", + "button-text-import": "impor", + "alert-storage-access-required-for-keys": "Masukkan kata sandi untuk pengimporan kunci sesi ini.", + "title-dialog-enter-new-screen-lock-pin": "Masukkan pin kunci layar Anda yang baru", + "subtitle-images-audio-video-files": "Gambar, Audio, Video, File", + "label-current-password": "Kata Sandi Saat Ini", + "list-item-settings-language": "Bahasa", + "list-item-settings-24h-format": "Format Waktu 24 Jam", + "alert-wait-for-full-sync-before-switching": "Tunggu untuk penyinkronan untuk selesai sebelum beralih ke akun lain", + "subtitle-settings-show-membership-events": "Tampilkan perubahan keanggotaan dalam obrolan", + "subtitle-settings-24h-format": "Tampilkan stempel waktu pesan menggunakan format 24 jam", + "subtitle-theme-settings": "Tema {}, Font {}", + "subtitle-privacy-settings": "Layar Kunci {}, Kunci Pendaftaran {}", + "label-seconds": "detik", + "button-text-confirm-delete-keys": "Hapus Kunci", + "list-item-settings-show-membership-events": "Tampilkan Peristiwa Keanggotaan", + "list-item-settings-enter-sends": "Tombol Enter Mengirim", + "list-item-settings-manual-sync": "Penyinkronan Manual", + "content-logout-multiaccount-confirm": "\n\nAnda memiliki beberapa akun lain, mengeluarkan dari akun Anda saat ini akan mengalihkan Anda ke sesi akun yang lain.", + "alert-log-out-enable-multiaccount": "Anda harus mengeluarkan\\nsesi Anda saat ini untuk mengaktifkan multiakun", + "content-import-session-keys-enter-password": "Masukkan kata sandi untuk pengimporan kunci sesi ini.", + "content-export-session-keys": "Masukkan sebuah kata sandi untuk cadangan kunci sesi ini.\n\nDiingat bahwa pengeksporan mungkin membutuhkan beberapa waktu untuk selesai.", + "content-export-session-keys-enter-password": "Masukkan sebuah kata sandi untuk mengenkripsikan kunci sesi.", + "content-import-session-keys": "Masukkan sebuah kata sandi untuk pengimporan kunci sesi ini.\n\nDiingat bahwa pengimporan mungkin membutuhkan beberapa waktu untuk selesai.", + "content-logout-confirm": "Apakah Anda yakin untuk keluar dari akun Anda?", + "content-remove-screen-lock": "Apakah Anda yakin untuk menghapus kunci layar? Ini juga akan menghapus lindungan pin cache", + "semantics-image-password-update": "Pengguna memikirkan sebuah kata sandi dalam putaran angin" } diff --git a/assets/translations/it.json b/assets/translations/it.json index a7e37125e..977c4cfeb 100644 --- a/assets/translations/it.json +++ b/assets/translations/it.json @@ -249,7 +249,7 @@ "tooltip-profile-settings": "Profilo e impostazioni", "tooltip-search-chats": "Cerca discussioni", "tooltip-cancel-reply": "Annulla la risposta", - "list-item-user-details-confirm-start-chat": "Parla con {}", + "list-item-user-details-start-chat": "Parla con {}", "subtitle-proxy-use-basic-authentication": "Fornisci nome utente e password per autenticarsi con un proxy", "content-proxy-password": "La password per l'autenticazione con il tuo proxy", "title-proxy-password": "Modifica la password del proxy", @@ -269,5 +269,52 @@ "list-item-chat-detail-vibrate": "Vibrazione", "content-notification-style-type-inbox": "Raggruppa i nuovi messaggi in una notifica", "label-show-attachment-options": "Apri le opzioni degli allegati", - "content-support-dialog": "La comunità di Syphon è qui per aiutarti come può.\n\nSentiti libero/a di unirti alla nostra chat di supporto o inviarci un'e-mail. Tieni presente che l'e-mail può richiedere da 1 a 5 giorni lavorativi per una risposta!" + "content-support-dialog": "La comunità di Syphon è qui per aiutarti come può.\n\nSentiti libero/a di unirti alla nostra chat di supporto o inviarci un'e-mail. Tieni presente che l'e-mail può richiedere da 1 a 5 giorni lavorativi per una risposta!", + "header-general": "Generale", + "header-ordering": "Ordinamento", + "header-media": "Media", + "header-update-password": "Aggiorna password", + "title-dialog-sync-interval": "Modifica intervallo di sincronizzazione", + "title-dialog-chat-with-user": "Chatta con {}", + "title-dialog-attempt-chat-with-user": "Prova a chattare con {}", + "title-blocked-users": "Utenti bloccati", + "title-import-session-keys": "Importa chiavi di sessione", + "title-export-session-keys": "Esporta chiavi di sessione", + "title-dialog-confirm-deactivate-account": "Conferma la disattivazione dell'account", + "title-dialog-backup-session-keys": "Chiavi di sessione di backup", + "title-dialog-remove-screen-lock": "Rimuovi Blocco schermo", + "title-dialog-enter-screen-lock-pin": "Inserisci il PIN di blocco dello schermo corrente", + "title-dialog-enter-new-screen-lock-pin": "Inserisci il tuo nuovo pin di blocco dello schermo", + "title-dialog-verify-new-screen-lock-pin": "Inserisci di nuovo il tuo PIN per verificare", + "subtitle-settings-enter-sends": "Premendo il tasto Invio verrà inviato un messaggio", + "subtitle-settings-view-uploaded-media": "Visualizza tutti i dati caricati, anche quelli non accessibili dai messaggi", + "subtitle-theme-settings": "Tema {}, Font {}", + "label-version": "Versione", + "label-seconds": "secondi", + "label-search-for-user": "Cerca un utente...", + "label-current-password": "Password attuale", + "label-new-password": "Nuova password", + "label-confirm-new-password": "Conferma nuova password", + "button-text-import": "importa", + "button-text-remove": "Rimuovi", + "list-item-settings-language": "Lingua", + "label-syncing": "Sincronizzazione", + "subtitle-settings-24h-format": "Mostra i timestamp dei messaggi utilizzando il formato 24 ore", + "header-media-auto-download": "Scaricamento automatico dei media", + "title-dialog-logout": "Esci", + "title-dialog-confirm-deactivate-account-final": "Conferma finale disattivazione account", + "subtitle-settings-show-membership-events": "Mostra le modifiche all'interno della chat", + "subtitle-settings-dismiss-keyboard": "Chiudi la tastiera dopo aver inviato un messaggio", + "subtitle-images-audio-video-files": "Immagini, audio, video, file", + "list-item-settings-sort-by": "Ordina per", + "subtitle-privacy-settings": "Blocco schermo {}, Blocco registrazione {}", + "subtitle-manual-sync": "Esegui una sincronizzazione forzata della matrice in base alla marca temporale dell'ultima sincronizzazione", + "subtitle-force-full-sync": "Esegui una sincronizzazione completa forzata di tutti i dati e messaggi dell'utente", + "label-timestamp": "Marca temporale", + "label-none": "Nessuna", + "label-stopped": "Fermato", + "list-item-settings-group-by": "Raggruppa per", + "list-item-settings-auto-download": "Scaricamento automatico", + "list-item-settings-view-uploaded-media": "Visualizza tutti i media caricati", + "list-item-settings-when-using-mobile-data": "Quando si usano i dati mobili" } diff --git a/assets/translations/ru.json b/assets/translations/ru.json index f1502b2a4..219d18a70 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -162,7 +162,7 @@ "list-item-user-details-send-message": "Отправить Сообщение", "list-item-user-details-block-user": "Заблокировать Пользователя", "list-item-chat-detail-notification-setting": "Настройки Уведомлений", - "list-item-user-details-confirm-start-chat": "Чат с {}", + "list-item-user-details-start-chat": "Чат с {}", "list-item-user-details-unblock-user": "Разблокировать Пользователя", "list-item-chat-detail-notifications": "Уведомления", "list-item-chat-detail-vibrate": "Вибрация", diff --git a/assets/translations/uk.json b/assets/translations/uk.json index d99e5b0a6..02cd3106a 100644 --- a/assets/translations/uk.json +++ b/assets/translations/uk.json @@ -194,7 +194,7 @@ "label-default-room-notification": "Типовий (Argon)", "label-search-unencrypted": "Шукати незашифровано", "alert-invite-user-unknown": "Цього користувача немає у matrix, але ви можете спробувати запросити їх.\n\nПерш ніж спробувати, переконайтеся, що у вас є правильне ім'я.", - "list-item-user-details-confirm-start-chat": "Спілкуватися з {}", + "list-item-user-details-start-chat": "Спілкуватися з {}", "list-item-advanced-settings-force-function": "Примусова функція", "alert-restart-app-effect": "Щоб зміни набрати чинності, потрібно закрити та перезапустити застосунок", "alert-feature-in-progress": "🛠 Ця функція з'явиться незабаром", @@ -269,5 +269,72 @@ "semantics-image-signup-username": "Людина, що спирається ліктем на ID-картку", "semantics-image-password-reset": "Людина, що поклала розслаблену руку на лист із прапорцем всередині", "label-show-attachment-options": "Відкрити параметри вкладення", - "content-support-dialog": "Спільнота Syphon тут, щоб допомогти вам.\n\nНе соромтеся приєднатися до нашого чату підтримки або надіслати нам електронний лист. Будь ласка, майте на увазі, що відповідь електронною поштою буде надіслано впродовж 1-5+ робочих днів для відповіді!" + "content-support-dialog": "Спільнота Syphon тут, щоб допомогти вам.\n\nНе соромтеся приєднатися до нашого чату підтримки або надіслати нам електронний лист. Будь ласка, майте на увазі, що відповідь електронною поштою буде надіслано впродовж 1-5+ робочих днів для відповіді!", + "content-export-session-keys-enter-password": "Введіть пароль для шифрування ключів сеансу.", + "content-import-session-keys": "Введіть пароль для імпорту ключа цього сеансу.\n\nЗауважте, що імпорт може тривати деякий час.", + "content-logout-confirm": "Ви впевнені, що хочете вийти?", + "title-dialog-remove-screen-lock": "Вилучити блокування екрана", + "button-text-remove": "Вилучити", + "list-item-settings-language": "Мова", + "title-dialog-enter-screen-lock-pin": "Введіть поточний PIN-код блокування екрана", + "title-dialog-verify-new-screen-lock-pin": "Введіть PIN-код ще раз, щоб підтвердити", + "label-version": "Версія", + "subtitle-settings-dismiss-keyboard": "Ховати клавіатуру після надсилання повідомлення", + "list-item-settings-when-roaming": "У роумінгу", + "subtitle-force-full-sync": "Примусова повна синхронізація всіх даних користувачів та повідомлень", + "label-syncing": "Синхронізація", + "label-stopped": "Зупинено", + "label-seconds": "секунд", + "label-search-for-user": "Шукати користувача...", + "label-current-password": "Поточний пароль", + "button-text-confirm-delete-keys": "Видалити ключі", + "list-item-settings-show-membership-events": "Показувати події участі", + "list-item-settings-auto-download": "Автозавантаження", + "header-ordering": "Упорядкування", + "header-update-password": "Оновити пароль", + "title-dialog-confirm-deactivate-account": "Підтвердити деактивацію облікового запису", + "title-dialog-confirm-deactivate-account-final": "Підтвердити остаточну деактивацію облікового запису", + "title-dialog-backup-session-keys": "Резервні ключі сеансу", + "subtitle-settings-show-membership-events": "Показувати зміни участі у бесіді", + "subtitle-settings-enter-sends": "Натискання клавіші enter надішле повідомлення", + "subtitle-settings-24h-format": "Показувати часові позначки повідомлення у 24-годинному форматі", + "subtitle-settings-view-uploaded-media": "Перегляд усіх вивантажених даних, навіть тих, які неможливо отримати з повідомлень", + "subtitle-images-audio-video-files": "Зображення, аудіо, відео, файли", + "subtitle-theme-settings": "Тема {}, шрифт {}", + "subtitle-privacy-settings": "Блокування екрана {}, блокування реєстрації {}", + "label-none": "Немає", + "label-new-password": "Новий пароль", + "list-item-settings-enter-sends": "Надсилання кнопкою Enter", + "list-item-settings-dismiss-keyboard": "Сховати клавіатуру", + "list-item-settings-sort-by": "Сортувати за", + "list-item-settings-group-by": "Групувати за", + "list-item-settings-when-using-mobile-data": "За використання мобільних даних", + "list-item-settings-when-using-wi-fi": "За використання Wi-Fi", + "list-item-settings-manual-sync": "Синхронізація вручну", + "alert-log-out-enable-multiaccount": "Щоб увімкнути кілька облікових записів, потрібно вийти з\\nпоточного сеансу", + "content-remove-screen-lock": "Ви впевнені, що хочете вилучити блокування екрана? Це також вилучить PIN-захист кешу", + "alert-storage-access-required-for-keys": "Введіть пароль для імпорту ключа сеансу.", + "content-import-session-keys-enter-password": "Введіть пароль для імпорту ключа сеансу.", + "header-media": "Медіа", + "title-dialog-logout": "Вийти", + "header-general": "Загальні", + "header-media-auto-download": "Автоматичне завантаження медіафайлів", + "title-dialog-sync-interval": "Змінити інтервал синхронізації", + "title-import-session-keys": "Імпорт ключів сеансу", + "title-export-session-keys": "Експорт ключів сеансу", + "title-dialog-chat-with-user": "Поспілкуватися з {}", + "title-dialog-attempt-chat-with-user": "Спробувати поспілкуватися з {}", + "title-blocked-users": "Заблоковані користувачі", + "label-timestamp": "Позначка часу", + "label-confirm-new-password": "Підтвердити новий пароль", + "button-text-import": "імпорт", + "list-item-settings-24h-format": "24-годинний формат часу", + "list-item-settings-view-uploaded-media": "Переглянути всі вивантажені медіафайли", + "list-item-settings-force-full-sync": "Примусова повна синхронізація", + "title-dialog-enter-new-screen-lock-pin": "Введіть новий PIN-код блокування екрана", + "subtitle-manual-sync": "Примусова matrix-синхронізація на основі позначки часу останньої синхронізації", + "alert-wait-for-full-sync-before-switching": "Дочекайтеся завершення повної синхронізації перед перемиканням облікових записів", + "content-export-session-keys": "Введіть пароль для резервного копіювання ключа цього сеансу.\n\nЗауважте, що експорт може тривати деякий час.", + "content-logout-multiaccount-confirm": "\n\nОскільки у вас є інші облікові записи, після виходу ви перейдете до іншого сеансу облікового запису.", + "semantics-image-password-update": "Користувач вигадує пароль у вирі вітру" } diff --git a/docs/faq-en.md b/docs/faq-en.md new file mode 100644 index 000000000..9e52b51c6 --- /dev/null +++ b/docs/faq-en.md @@ -0,0 +1,44 @@ +# Syphon FAQ + +## Where is [X] feature? + +Syphon is currently in early development. Whilst we believe the code to be of a standard where we can share with a wider userbase, Syphon does not rely on public Matrix libraries, instead coding against [the Matrix specification](https://spec.matrix.org/latest/). + +This means that Syphon is currently missing features found in other Matrix chat clients as the development team work to implement them. + +[Syphon's feature roadmap can be found here](https://syphon.org/roadmap). + +## [X] feature is acting strange/my settings aren't saved + +Syphon is currently in open alpha, meaning development is currently rapid so each release consists of quite a "jump" in features. + +In the first instance, if you've updated your client and are experiencing "weird" behaviour, please **uninstall** your local version and **clean install** the app with its latest version to ensure any local settings files are recreated at their latest settings. + +If you're still encountering errors or weird behaviour, you've probably encountered a bug so please ask for help! + +## How do I... + +### ...Verify my session? + +The option to manually/non-interactively verify sessions has been "hidden" in recent Element client releases. + +In order to cross-sign/verify your Syphon session, with the Element client: + +1. Open any chat you're in - though to make your life easier the smaller the better +1. Click the ![](images/info.png) button in the top-right to show room information +1. Click ![](images/people.png) +1. Click on your name (if you're in a large room, you may have to search) +1. Click on the untrusted session ![](images/syphon_not_trusted.png) +1. Click "Manually Verify By Text" ![](images/verify.png) + +n.b. Interactive cross-signing is [roadmapped](https://syphon.org/roadmap) as part of the `0.3.0` release. + +### ...Get Notifications? + +Notifications are currently **Android-Only**. + +Like most features in Syphon, notifications are disabled by default. + +### ...Send Read Receipts? + +Like most features in Syphon, Read Receipts are disabled by default and must be enabled in the Settings. \ No newline at end of file diff --git a/docs/images/info.png b/docs/images/info.png new file mode 100644 index 000000000..e419908db Binary files /dev/null and b/docs/images/info.png differ diff --git a/docs/images/people.png b/docs/images/people.png new file mode 100644 index 000000000..4cb51752f Binary files /dev/null and b/docs/images/people.png differ diff --git a/docs/images/syphon_not_trusted.png b/docs/images/syphon_not_trusted.png new file mode 100644 index 000000000..2333bfd1f Binary files /dev/null and b/docs/images/syphon_not_trusted.png differ diff --git a/docs/images/verify.png b/docs/images/verify.png new file mode 100644 index 000000000..e5acfee30 Binary files /dev/null and b/docs/images/verify.png differ diff --git a/docs/various-issue-batch-names.md b/docs/issue-batch-names.md similarity index 100% rename from docs/various-issue-batch-names.md rename to docs/issue-batch-names.md diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 202277476..ac1fac21b 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -355,7 +355,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 2100; + CURRENT_PROJECT_VERSION = 2120; DEVELOPMENT_TEAM = W98LUTKH5G; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -371,7 +371,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 0.2.10; + MARKETING_VERSION = 0.2.12; PRODUCT_BUNDLE_IDENTIFIER = org.tether.tether; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -496,7 +496,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 2100; + CURRENT_PROJECT_VERSION = 2120; DEVELOPMENT_TEAM = W98LUTKH5G; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -512,7 +512,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 0.2.10; + MARKETING_VERSION = 0.2.12; PRODUCT_BUNDLE_IDENTIFIER = org.tether.tether; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -529,7 +529,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 2100; + CURRENT_PROJECT_VERSION = 2120; DEVELOPMENT_TEAM = W98LUTKH5G; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -545,7 +545,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 0.2.10; + MARKETING_VERSION = 0.2.12; PRODUCT_BUNDLE_IDENTIFIER = org.tether.tether; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; diff --git a/lib/cache/codec.dart b/lib/cache/codec.dart index a3586d9f6..3a6460f09 100644 --- a/lib/cache/codec.dart +++ b/lib/cache/codec.dart @@ -48,6 +48,7 @@ class _EncryptDecoder extends Converter { final iv = base64.decode(input.substring(0, IV_LENGTH_BASE_64.round())); // Extract the real input + // ignore: parameter_assignments input = input.substring(IV_LENGTH_BASE_64.round()); // Decode the input @@ -61,7 +62,7 @@ class EncryptCodec extends Codec { late _EncryptDecoder _decoder; EncryptCodec(Key passwordBytes) { - var aes = AES(passwordBytes, mode: AESMode.ctr, padding: null); + final aes = AES(passwordBytes, mode: AESMode.ctr, padding: null); _encoder = _EncryptEncoder(aes); _decoder = _EncryptDecoder(aes); diff --git a/lib/cache/middleware.dart b/lib/cache/middleware.dart index 4451b86c2..f1067d594 100644 --- a/lib/cache/middleware.dart +++ b/lib/cache/middleware.dart @@ -32,7 +32,7 @@ bool cacheMiddleware(Store store, dynamic action) { printInfo('[initStore] persistor saving from ${action.runtimeType}'); return true; case SetSynced: - return (action as SetSynced).synced ?? false; + return ((action as SetSynced).synced ?? false) && !store.state.syncStore.synced; default: return false; } diff --git a/lib/cache/serializer.dart b/lib/cache/serializer.dart index d3a92f897..cc4c83d52 100644 --- a/lib/cache/serializer.dart +++ b/lib/cache/serializer.dart @@ -138,7 +138,6 @@ class CacheSerializer implements StateSerializer { } }); - // TODO: move down after 0.2.9 release final cryptoState = cryptoStore ?? preloaded[StorageKeys.CRYPTO] as CryptoStore? ?? CryptoStore(); @@ -149,10 +148,10 @@ class CacheSerializer implements StateSerializer { loading: false, authStore: authStore ?? preloaded[StorageKeys.AUTH] ?? AuthStore(), cryptoStore: messageSessionsLoaded.isEmpty - ? cryptoState.upgradeSessions_temp() - : cryptoState.upgradeSessions_temp().copyWith( - messageSessionsInbound: preloaded[StorageKeys.MESSAGE_SESSIONS], - ), + ? cryptoState + : cryptoState.copyWith( + messageSessionsInbound: messageSessionsLoaded, + ), settingsStore: preloaded[StorageKeys.SETTINGS] ?? settingsStore ?? SettingsStore(), syncStore: syncStore ?? SyncStore(), mediaStore: mediaStore ?? MediaStore().copyWith(mediaCache: preloaded[StorageKeys.MEDIA]), diff --git a/lib/global/https.dart b/lib/global/https.dart index f1b133441..a08e6c947 100644 --- a/lib/global/https.dart +++ b/lib/global/https.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:http/http.dart' as http; import 'package:http/io_client.dart'; +import 'package:syphon/global/print.dart'; import 'package:syphon/store/settings/proxy-settings/model.dart'; /// This is LetsEncrypt's self-signed trusted root certificate authority @@ -59,9 +60,9 @@ HttpClient customHttpClient({String? cert}) { } } on TlsException catch (e) { if (e.osError?.message != null && e.osError!.message.contains('CERT_ALREADY_IN_HASH_TABLE')) { - print('createHttpClient() - cert already trusted! Skipping.'); + log.info('[customHttpClient] - cert already trusted! Skipping.'); } else { - print('createHttpClient().setTrustedCertificateBytes EXCEPTION: $e'); + log.error('[customHttpClient] setTrustedCertificateBytes EXCEPTION: $e'); rethrow; } } finally {} @@ -82,13 +83,11 @@ http.Client createClient({ProxySettings? proxySettings}) { proxySettings.host, int.parse(proxySettings.port), // port input allows numbers only, so no try/catch 'Basic', // Basic authentication - HttpClientBasicCredentials(proxySettings.username, proxySettings.password) - ); + HttpClientBasicCredentials(proxySettings.username, proxySettings.password)); } return 'PROXY ${proxySettings.host}:${proxySettings.port};'; - } - else { + } else { return 'DIRECT'; } }; diff --git a/lib/global/libs/matrix/encryption.dart b/lib/global/libs/matrix/encryption.dart index 3e7d45ba1..ea933af12 100644 --- a/lib/global/libs/matrix/encryption.dart +++ b/lib/global/libs/matrix/encryption.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:convert'; -import 'package:http/http.dart' as http; import 'package:syphon/global/https.dart'; import 'package:syphon/global/libs/matrix/constants.dart'; import 'package:syphon/global/libs/matrix/index.dart'; diff --git a/lib/global/libs/matrix/notifications.dart b/lib/global/libs/matrix/notifications.dart index 82227a155..4f85f6487 100644 --- a/lib/global/libs/matrix/notifications.dart +++ b/lib/global/libs/matrix/notifications.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:convert'; -import 'package:http/http.dart' as http; import 'package:syphon/global/https.dart'; import 'package:syphon/global/values.dart'; diff --git a/lib/global/noop.dart b/lib/global/noop.dart new file mode 100644 index 000000000..ab3f4c0d4 --- /dev/null +++ b/lib/global/noop.dart @@ -0,0 +1 @@ +noop() {} diff --git a/lib/global/values.dart b/lib/global/values.dart index cc61ba793..77e404c2c 100644 --- a/lib/global/values.dart +++ b/lib/global/values.dart @@ -63,6 +63,7 @@ class Values { // ignore: non_constant_identifier_names const bool DEBUG_MODE = !kReleaseMode; +const bool SHOW_BORDERS = false; class SupportedLanguages { static const defaultLang = 'en'; diff --git a/lib/main.dart b/lib/main.dart index a8d75b16d..addb27951 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; - +import 'package:flutter/rendering.dart'; import 'package:syphon/context/storage.dart'; import 'package:syphon/global/platform.dart'; +import 'package:syphon/global/values.dart'; import 'package:syphon/views/prelock.dart'; // ignore: avoid_void_async @@ -14,6 +15,10 @@ void main() async { // pull current context / nullable final context = await loadContextCurrent(); + if (SHOW_BORDERS && DEBUG_MODE) { + debugPaintSizeEnabled = SHOW_BORDERS; + } + // init app runApp(Prelock( appContext: context, diff --git a/lib/store/alerts/actions.dart b/lib/store/alerts/actions.dart index e900d5b9d..27326c774 100644 --- a/lib/store/alerts/actions.dart +++ b/lib/store/alerts/actions.dart @@ -41,9 +41,11 @@ ThunkAction startAlertsObserver() { throw 'Cannot call startAlertsObserver with an existing instance'; } - store.dispatch(SetAlertsObserver( - alertsObserver: StreamController.broadcast(), - )); + store.dispatch( + SetAlertsObserver( + alertsObserver: StreamController.broadcast(), + ), + ); }; } diff --git a/lib/store/crypto/state.dart b/lib/store/crypto/state.dart index 79e4d9dd5..41c9ed779 100644 --- a/lib/store/crypto/state.dart +++ b/lib/store/crypto/state.dart @@ -1,7 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:olm/olm.dart'; -import 'package:syphon/global/print.dart'; import 'package:syphon/store/crypto/keys/models.dart'; import 'package:syphon/store/crypto/sessions/model.dart'; @@ -21,24 +20,17 @@ class CryptoStore extends Equatable { final bool deviceKeyVerified; final bool oneTimeKeysStable; - // Map + // Map final Map> keySessions; // both olm inbound and outbound key sessions // Map // megolm - messages final Map outboundMessageSessions; // Map // megolm - messages per chat + // NOTE: backed up to sqlite db storage, JsonKey ignore to not cache @JsonKey(ignore: true) final Map>> messageSessionsInbound; - // Map // megolm - index per chat - @Deprecated('switch to using "index" inside MessageSession within inboundMessageSessionsAll') - final Map> messageSessionIndex; - - // Map // megolm - messages per chat - @Deprecated('switch to inboundMessageSessionsAll to include old session for a device') - final Map> inboundMessageSessions; - /// Map deviceKeys final Map> deviceKeys; @@ -57,11 +49,9 @@ class CryptoStore extends Equatable { this.deviceKeysExist = false, this.deviceKeyVerified = false, this.oneTimeKeysStable = true, - this.inboundMessageSessions = const {}, // Megolm Sessions this.messageSessionsInbound = const {}, // Megolm Sessions this.outboundMessageSessions = const {}, // Megolm Sessions this.keySessions = const {}, // Olm sessions - this.messageSessionIndex = const {}, this.deviceKeys = const {}, this.deviceKeysOwned = const {}, this.oneTimeKeysClaimed = const {}, @@ -90,10 +80,8 @@ class CryptoStore extends Equatable { bool? deviceKeysExist, bool? deviceKeyVerified, bool? oneTimeKeysStable, - @Deprecated('only for converting') Map>? messageSessionIndex, - @Deprecated('only for converting') Map>? inboundMessageSessions, - Map>>? messageSessionsInbound, Map? outboundMessageSessions, + Map>>? messageSessionsInbound, Map>? keySessions, Map? deviceKeysOwned, Map>? deviceKeys, @@ -103,8 +91,6 @@ class CryptoStore extends Equatable { CryptoStore( olmAccount: olmAccount ?? this.olmAccount, olmAccountKey: olmAccountKey ?? this.olmAccountKey, - messageSessionIndex: messageSessionIndex ?? this.messageSessionIndex, - inboundMessageSessions: inboundMessageSessions ?? this.inboundMessageSessions, messageSessionsInbound: messageSessionsInbound ?? this.messageSessionsInbound, outboundMessageSessions: outboundMessageSessions ?? this.outboundMessageSessions, keySessions: keySessions ?? this.keySessions, @@ -117,59 +103,6 @@ class CryptoStore extends Equatable { oneTimeKeysCounts: oneTimeKeysCounts ?? this.oneTimeKeysCounts, ); - // TODO: remove after 0.2.9 release - // @Deprecated('only use to migrate keys from < 0.2.8 to 0.2.9') - CryptoStore upgradeSessions_temp() { - if (inboundMessageSessions.isEmpty) { - return this; - } - - log.warn('[upgradeSessions_temp] UPGRADING PREVIOUS KEY SESSIONS'); - - final messageSessionsUpdated = Map>>.from( - messageSessionsInbound, - ); - - for (final roomSessions in inboundMessageSessions.entries) { - final roomId = roomSessions.key; - final sessions = roomSessions.value; - - for (final messsageSessions in sessions.entries) { - final senderKey = messsageSessions.key; - final messageIndex = ((messageSessionIndex[roomId] ?? {})[senderKey]) ?? 0; - final sessionsSerialized = messsageSessions.value; - - final messageSessionNew = MessageSession( - index: messageIndex, - serialized: sessionsSerialized, // already pickled - createdAt: DateTime.now().millisecondsSinceEpoch, - ); - - // new message session updates - messageSessionsUpdated.update( - roomId, - (identitySessions) => identitySessions - ..update( - senderKey, - (sessions) => sessions..insert(0, messageSessionNew), - ifAbsent: () => [messageSessionNew], - ), - ifAbsent: () => { - senderKey: [messageSessionNew], - }, - ); - } - } - - log.warn('[upgradeSessions_temp] COMPLETED, WIPING PREVIOUS KEY SESSIONS'); - - return copyWith( - messageSessionIndex: const {}, - inboundMessageSessions: const {}, - messageSessionsInbound: messageSessionsUpdated, - ); - } - Map toJson() => _$CryptoStoreToJson(this); factory CryptoStore.fromJson(Map json) => _$CryptoStoreFromJson(json); } diff --git a/lib/store/events/messages/actions.dart b/lib/store/events/messages/actions.dart index 88aa2cdcd..49f32a114 100644 --- a/lib/store/events/messages/actions.dart +++ b/lib/store/events/messages/actions.dart @@ -158,6 +158,42 @@ ThunkAction mutateMessagesAll() { }; } +// TODO: need to rework to remove old outbox message +ThunkAction sendMessageExisting({ + required String roomId, + required Message message, + Message? related, + bool edit = false, +}) { + return (Store store) async { + final room = store.state.roomStore.rooms[roomId]!; + + store.dispatch(DeleteOutboxMessage( + message: message, + )); + + if (room.encryptionEnabled) { + return store.dispatch( + sendMessageEncrypted( + roomId: room.id, + message: message, + related: related, + edit: edit, + ), + ); + } + + return store.dispatch( + sendMessage( + roomId: room.id, + message: message, + related: related, + edit: edit, + ), + ); + }; +} + /// Send Message ThunkAction sendMessage({ required String roomId, @@ -169,16 +205,20 @@ ThunkAction sendMessage({ return (Store store) async { final room = store.state.roomStore.rooms[roomId]!; + // if you're incredibly unlucky, and fast, you could have a problem here + final tempId = Random.secure().nextInt(1 << 32).toString(); + + int? sent; + Message? pending; + try { store.dispatch(UpdateRoom(id: room.id, sending: true)); final reply = store.state.roomStore.rooms[room.id]!.reply; final userId = store.state.authStore.user.userId!; - // if you're incredibly unlucky, and fast, you could have a problem here - final tempId = Random.secure().nextInt(1 << 32).toString(); // pending outbox message - Message pending = await formatMessageContent( + pending = await formatMessageContent( tempId: tempId, userId: userId, message: message, @@ -210,6 +250,7 @@ ThunkAction sendMessage({ ); if (data['errcode'] != null) { + // edits will not have outbox messages if (!edit) { store.dispatch(SaveOutboxMessage( tempId: tempId, @@ -225,6 +266,9 @@ ThunkAction sendMessage({ throw data['error']; } + // mark a successfully sent + sent = DateTime.now().millisecondsSinceEpoch; + // Update sent message with event id but needs // to be syncing to remove from outbox if (!edit) { @@ -240,11 +284,27 @@ ThunkAction sendMessage({ return true; } catch (error) { - store.dispatch(addAlert( - error: error, - message: error.toString(), - origin: 'sendMessage', - )); + // dont show error notifications for common networking issues + if (error is! SocketException) { + store.dispatch(addAlert( + error: error, + message: error.toString(), + origin: 'sendMessage', + )); + } + + // if the message has not been successfully sent + if (pending != null && sent == null) { + store.dispatch(SaveOutboxMessage( + tempId: tempId, + pendingMessage: pending.copyWith( + timestamp: DateTime.now().millisecondsSinceEpoch, + pending: false, + syncing: false, + failed: true, + ), + )); + } return false; } finally { store.dispatch(UpdateRoom( diff --git a/lib/store/events/messages/formatters.dart b/lib/store/events/messages/formatters.dart index 8ea555648..54add392c 100644 --- a/lib/store/events/messages/formatters.dart +++ b/lib/store/events/messages/formatters.dart @@ -4,7 +4,6 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:mime/mime.dart'; import 'package:syphon/global/libs/matrix/constants.dart'; -import 'package:syphon/global/print.dart'; import 'package:syphon/store/events/messages/model.dart'; import 'package:syphon/store/media/converters.dart'; import 'package:syphon/store/media/encryption.dart'; diff --git a/lib/store/rooms/actions.dart b/lib/store/rooms/actions.dart index 79ae50f21..7c29e00e8 100644 --- a/lib/store/rooms/actions.dart +++ b/lib/store/rooms/actions.dart @@ -166,7 +166,7 @@ ThunkAction fetchRoom( ThunkAction fetchRooms({bool syncState = false}) { return (Store store) async { try { - printInfo('[fetchSync] *** starting fetch all rooms *** '); + printInfo('[fetchRooms] *** starting fetch all rooms *** '); final data = await MatrixApi.fetchRoomIds( protocol: store.state.authStore.protocol, homeserver: store.state.authStore.user.homeserver, diff --git a/lib/store/rooms/selectors.dart b/lib/store/rooms/selectors.dart index c716238c9..286fb656f 100644 --- a/lib/store/rooms/selectors.dart +++ b/lib/store/rooms/selectors.dart @@ -1,20 +1,35 @@ import 'package:syphon/store/events/messages/model.dart'; import 'package:syphon/store/index.dart'; +import 'package:syphon/store/user/model.dart'; import './room/model.dart'; Room selectRoom({required AppState state, String? id}) { return state.roomStore.rooms[id] ?? Room(id: id ?? ''); } +String selectDirectChatIdExisting({required AppState state, User? user}) { + if (user == null) return ''; + + for (final room in state.roomStore.roomList) { + if (room.direct && room.userIds.contains(user.userId)) { + return room.id; + } + } + + return ''; +} + List filterBlockedRooms(List rooms, List blocked) { final List roomList = rooms; return roomList - ..removeWhere((room) => - room.userIds.length == 2 && - room.userIds.any( - (userId) => blocked.contains(userId), - )) + ..removeWhere( + (room) => + room.userIds.length == 2 && + room.userIds.any( + (userId) => blocked.contains(userId), + ), + ) ..toList(); } diff --git a/lib/store/rooms/state.dart b/lib/store/rooms/state.dart index a3335a645..3dc975343 100644 --- a/lib/store/rooms/state.dart +++ b/lib/store/rooms/state.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -37,6 +35,5 @@ class RoomStore extends Equatable { ); Map toJson() => _$RoomStoreToJson(this); - factory RoomStore.fromJson(Map json) => - _$RoomStoreFromJson(json); + factory RoomStore.fromJson(Map json) => _$RoomStoreFromJson(json); } diff --git a/lib/store/settings/theme-settings/selectors.dart b/lib/store/settings/theme-settings/selectors.dart index 3847143fb..49b8e0424 100644 --- a/lib/store/settings/theme-settings/selectors.dart +++ b/lib/store/settings/theme-settings/selectors.dart @@ -19,6 +19,15 @@ ThemeType themeTypeFromSystem() { return ThemeType.Light; } +ThemeType resolveThemeOverride(ThemeType themeType) { + var themeTypeOverride = themeType; + if (themeTypeOverride == ThemeType.System) { + themeTypeOverride = themeTypeFromSystem(); + } + + return themeTypeOverride; +} + String selectMainFabType(ThemeSettings themeSettings) { return enumToString(themeSettings.mainFabType); } @@ -50,10 +59,6 @@ SystemUiOverlayStyle computeSystemUIColor(BuildContext context, {double ratio = } int selectRowHighlightColor(ThemeType themeType) { - if (themeType == ThemeType.System) { - themeType = themeTypeFromSystem(); - } - switch (themeType) { case ThemeType.Light: return Colours.greyLightest; @@ -64,10 +69,8 @@ int selectRowHighlightColor(ThemeType themeType) { } } -int selectSystemUiColor(ThemeType themeType) { - if (themeType == ThemeType.System) { - themeType = themeTypeFromSystem(); - } +int selectSystemUiColor(ThemeType themeTypeNew) { + final themeType = resolveThemeOverride(themeTypeNew); switch (themeType) { case ThemeType.Light: @@ -79,10 +82,8 @@ int selectSystemUiColor(ThemeType themeType) { } } -Brightness selectSystemUiIconColor(ThemeType themeType) { - if (themeType == ThemeType.System) { - themeType = themeTypeFromSystem(); - } +Brightness selectSystemUiIconColor(ThemeType themeTypeNew) { + final themeType = resolveThemeOverride(themeTypeNew); switch (themeType) { case ThemeType.Light: @@ -92,10 +93,8 @@ Brightness selectSystemUiIconColor(ThemeType themeType) { } } -Color selectIconBackground(ThemeType themeType) { - if (themeType == ThemeType.System) { - themeType = themeTypeFromSystem(); - } +Color selectIconBackground(ThemeType themeTypeNew) { + final themeType = resolveThemeOverride(themeTypeNew); switch (themeType) { case ThemeType.Light: @@ -107,10 +106,8 @@ Color selectIconBackground(ThemeType themeType) { } } -Color selectAvatarBackground(ThemeType themeType) { - if (themeType == ThemeType.System) { - themeType = themeTypeFromSystem(); - } +Color selectAvatarBackground(ThemeType themeTypeNew) { + final themeType = resolveThemeOverride(themeTypeNew); switch (themeType) { case ThemeType.Light: @@ -122,10 +119,8 @@ Color selectAvatarBackground(ThemeType themeType) { } } -Brightness selectThemeBrightness(ThemeType themeType) { - if (themeType == ThemeType.System) { - themeType = themeTypeFromSystem(); - } +Brightness selectThemeBrightness(ThemeType themeTypeNew) { + final themeType = resolveThemeOverride(themeTypeNew); switch (themeType) { case ThemeType.Light: @@ -135,10 +130,8 @@ Brightness selectThemeBrightness(ThemeType themeType) { } } -Color selectIconColor(ThemeType themeType) { - if (themeType == ThemeType.System) { - themeType = themeTypeFromSystem(); - } +Color selectIconColor(ThemeType themeTypeNew) { + final themeType = resolveThemeOverride(themeTypeNew); switch (themeType) { case ThemeType.Light: @@ -148,10 +141,8 @@ Color selectIconColor(ThemeType themeType) { } } -Color? selectModalColor(ThemeType themeType) { - if (themeType == ThemeType.System) { - themeType = themeTypeFromSystem(); - } +Color? selectModalColor(ThemeType themeTypeNew) { + final themeType = resolveThemeOverride(themeTypeNew); switch (themeType) { case ThemeType.Night: @@ -161,10 +152,8 @@ Color? selectModalColor(ThemeType themeType) { } } -int? selectScaffoldBackgroundColor(ThemeType themeType) { - if (themeType == ThemeType.System) { - themeType = themeTypeFromSystem(); - } +int? selectScaffoldBackgroundColor(ThemeType themeTypeNew) { + final themeType = resolveThemeOverride(themeTypeNew); switch (themeType) { case ThemeType.Light: @@ -178,10 +167,8 @@ int? selectScaffoldBackgroundColor(ThemeType themeType) { } } -Color selectInputTextColor(ThemeType themeType) { - if (themeType == ThemeType.System) { - themeType = themeTypeFromSystem(); - } +Color selectInputTextColor(ThemeType themeTypeNew) { + final themeType = resolveThemeOverride(themeTypeNew); switch (themeType) { case ThemeType.Light: @@ -191,10 +178,8 @@ Color selectInputTextColor(ThemeType themeType) { } } -Color selectCursorColor(ThemeType themeType) { - if (themeType == ThemeType.System) { - themeType = themeTypeFromSystem(); - } +Color selectCursorColor(ThemeType themeTypeNew) { + final themeType = resolveThemeOverride(themeTypeNew); switch (themeType) { case ThemeType.Light: @@ -204,10 +189,8 @@ Color selectCursorColor(ThemeType themeType) { } } -Color selectInputBackgroundColor(ThemeType themeType) { - if (themeType == ThemeType.System) { - themeType = themeTypeFromSystem(); - } +Color selectInputBackgroundColor(ThemeType themeTypeNew) { + final themeType = resolveThemeOverride(themeTypeNew); switch (themeType) { case ThemeType.Light: @@ -219,10 +202,8 @@ Color selectInputBackgroundColor(ThemeType themeType) { } } -double? selectAppBarElevation(ThemeType themeType) { - if (themeType == ThemeType.System) { - themeType = themeTypeFromSystem(); - } +double? selectAppBarElevation(ThemeType themeTypeNew) { + final themeType = resolveThemeOverride(themeTypeNew); switch (themeType) { case ThemeType.Darker: diff --git a/lib/store/sync/actions.dart b/lib/store/sync/actions.dart index c60061b33..4ad90b929 100644 --- a/lib/store/sync/actions.dart +++ b/lib/store/sync/actions.dart @@ -260,6 +260,7 @@ ThunkAction fetchSync({String? since, bool forceFull = false}) { final Map toDeviceJson = data['to_device'] ?? {}; final Map oneTimeKeyCount = data['device_one_time_keys_count'] ?? {}; + // Parse and save room / message updates if (roomJson.isNotEmpty) { final Map joinedJson = roomJson['join'] ?? {}; final Map invitesJson = roomJson['invite'] ?? {}; @@ -273,26 +274,23 @@ ThunkAction fetchSync({String? since, bool forceFull = false}) { await store.dispatch(syncRooms(invitesJson)); } } + + // Updates for device specific data (mostly room encryption) if (toDeviceJson.isNotEmpty) { - // Updates for device specific data (mostly room encryption) await store.dispatch(syncDevice(toDeviceJson)); } // Update encryption one time key count - store.dispatch( - updateOneTimeKeyCounts( - Map.from(oneTimeKeyCount), - ), - ); + store.dispatch(updateOneTimeKeyCounts( + Map.from(oneTimeKeyCount), + )); // Update synced to indicate init sync and next batch id (lastSince) - store.dispatch( - SetSynced( - synced: true, - syncing: false, - lastSince: nextBatch, - ), - ); + store.dispatch(SetSynced( + synced: true, + syncing: false, + lastSince: nextBatch, + )); if (isFullSync) { printInfo('[fetchSync] *** full sync completed ***'); diff --git a/lib/views/home/chat/chat-screen.dart b/lib/views/home/chat/chat-screen.dart index 8052d29f6..508fef824 100644 --- a/lib/views/home/chat/chat-screen.dart +++ b/lib/views/home/chat/chat-screen.dart @@ -93,14 +93,16 @@ class ChatScreenState extends State { showDialog( context: context, barrierDismissible: false, - builder: (_) => DialogInvite( + builder: (dialogContext) => DialogInvite( onAccept: props.onAcceptInvite, onReject: () { props.onRejectInvite(); Navigator.popUntil(context, (route) => route.isFirst); + Navigator.pop(dialogContext); }, onCancel: () { Navigator.popUntil(context, (route) => route.isFirst); + Navigator.pop(dialogContext); }, ), ); @@ -665,9 +667,9 @@ class ChatScreenState extends State { child: Stack( children: [ MessageList( + roomId: props.room.id, editing: editing, editorController: editorController, - roomId: props.room.id, showAvatars: props.showAvatars, selectedMessage: selectedMessage, scrollController: messagesController, @@ -862,8 +864,12 @@ class _Props extends Equatable { ), ); }, - onSendMessage: ( - {required String body, String? type, bool edit = false, Message? related}) async { + onSendMessage: ({ + required String body, + String? type, + bool edit = false, + Message? related, + }) async { if (roomId == null || body.isEmpty) return; final room = store.state.roomStore.rooms[roomId]!; diff --git a/lib/views/home/chat/widgets/message-list.dart b/lib/views/home/chat/widgets/message-list.dart index 01cd7d665..e16d5f4e9 100644 --- a/lib/views/home/chat/widgets/message-list.dart +++ b/lib/views/home/chat/widgets/message-list.dart @@ -6,6 +6,7 @@ import 'package:redux/redux.dart'; import 'package:syphon/global/colours.dart'; import 'package:syphon/global/print.dart'; import 'package:syphon/store/events/actions.dart'; +import 'package:syphon/store/events/messages/actions.dart'; import 'package:syphon/store/events/messages/model.dart'; import 'package:syphon/store/events/messages/selectors.dart'; import 'package:syphon/store/events/reactions/actions.dart'; @@ -23,7 +24,7 @@ import 'package:syphon/views/widgets/messages/message.dart'; import 'package:syphon/views/widgets/messages/typing-indicator.dart'; class MessageList extends StatefulWidget { - final String? roomId; + final String roomId; final bool editing; final bool showAvatars; @@ -87,6 +88,18 @@ class MessageListState extends State with Lifecycle { } } + onResendMessage(Message message) { + final store = StoreProvider.of(context); + + final roomId = widget.roomId; + + try { + store.dispatch(sendMessageExisting(roomId: roomId, message: message)); + } catch (error) { + printError(error.toString()); + } + } + onToggleReaction({Message? message, String? emoji}) { final store = StoreProvider.of(context); final roomId = widget.roomId; @@ -219,6 +232,7 @@ class MessageListState extends State with Lifecycle { timeFormat: props.timeFormat, onSendEdit: widget.onSendEdit, onSwipe: onSelectReply, + onResend: onResendMessage, onPressAvatar: () => widget.onViewUserDetails!( message: message, user: user, diff --git a/lib/views/home/search/search-users-screen.dart b/lib/views/home/search/search-users-screen.dart index e80c7314e..bd3e45015 100644 --- a/lib/views/home/search/search-users-screen.dart +++ b/lib/views/home/search/search-users-screen.dart @@ -7,6 +7,7 @@ import 'package:syphon/global/formatters.dart'; import 'package:syphon/global/strings.dart'; import 'package:syphon/store/index.dart'; import 'package:syphon/store/rooms/actions.dart'; +import 'package:syphon/store/rooms/selectors.dart'; import 'package:syphon/store/search/actions.dart'; import 'package:syphon/store/settings/theme-settings/model.dart'; import 'package:syphon/store/user/model.dart'; @@ -73,12 +74,31 @@ class SearchUserState extends State { } @protected - onCreateChat({required BuildContext context, _Props? props, User? user}) async { + onCreateChat({required BuildContext context, required User user, _Props? props}) async { + final store = StoreProvider.of(context); + + final existingChatId = selectDirectChatIdExisting( + state: store.state, + user: user, + ); + + // Navigate to existing DM if one already exists + if (existingChatId.isNotEmpty) { + return Navigator.popAndPushNamed( + context, + Routes.chat, + arguments: ChatScreenArguments( + roomId: existingChatId, + title: user.displayName, + ), + ); + } + return showDialog( context: context, builder: (BuildContext dialogContext) => DialogStartChat( user: user, - title: Strings.titleDialogChatWithUser(formatUsername(user!)), + title: Strings.titleDialogChatWithUser(formatUsername(user)), content: Strings.confirmStartChat, onStartChat: () async { setState(() { @@ -108,7 +128,26 @@ class SearchUserState extends State { /// attempt chating with a user by the name searched /// @protected - onAttemptChat({required User user, required BuildContext context, _Props? props}) async { + onAttemptChat({required BuildContext context, required User user, _Props? props}) async { + final store = StoreProvider.of(context); + + final existingChatId = selectDirectChatIdExisting( + state: store.state, + user: user, + ); + + // Navigate to existing DM if one already exists + if (existingChatId.isNotEmpty) { + return Navigator.popAndPushNamed( + context, + Routes.chat, + arguments: ChatScreenArguments( + roomId: existingChatId, + title: user.displayName, + ), + ); + } + return showDialog( context: context, builder: (BuildContext dialogContext) => DialogStartChat( @@ -292,45 +331,46 @@ class SearchUserState extends State { @override Widget build(BuildContext context) => StoreConnector( - distinct: true, - converter: (Store store) => _Props.mapStateToProps(store), - builder: (context, props) { - return Scaffold( - appBar: AppBarSearch( - title: Strings.titleSearchUsers, - label: Strings.labelSearchForUser, - tooltip: Strings.tooltipSearchUsers, - forceFocus: true, - focusNode: searchInputFocusNode, - onChange: (text) => setState(() { - searchable = text; - }), - onSearch: (text) { - setState(() { + distinct: true, + converter: (Store store) => _Props.mapStateToProps(store), + builder: (context, props) { + return Scaffold( + appBar: AppBarSearch( + title: Strings.titleSearchUsers, + label: Strings.labelSearchForUser, + tooltip: Strings.tooltipSearchUsers, + forceFocus: true, + focusNode: searchInputFocusNode, + onChange: (text) => setState(() { searchable = text; - }); - props.onSearch(text); - }, - ), - body: Stack( - children: [ - Visibility( - visible: searchable.isEmpty, - child: buildPreviewList(context, props), - ), - Visibility( - visible: searchable.isNotEmpty, - child: buildSearchList(context, props), - ), - Positioned( - child: Loader( - loading: props.loading, + }), + onSearch: (text) { + setState(() { + searchable = text; + }); + props.onSearch(text); + }, + ), + body: Stack( + children: [ + Visibility( + visible: searchable.isEmpty, + child: buildPreviewList(context, props), ), - ), - ], - ), - ); - }); + Visibility( + visible: searchable.isNotEmpty, + child: buildSearchList(context, props), + ), + Positioned( + child: Loader( + loading: props.loading, + ), + ), + ], + ), + ); + }, + ); } class _Props extends Equatable { @@ -382,10 +422,12 @@ class _Props extends Equatable { store.dispatch(searchUsers(searchText: text)); }, onCreateChatDirect: ({required User user}) async { - return store.dispatch(createRoom( - isDirect: true, - invites: [user], - )); + return store.dispatch( + createRoom( + isDirect: true, + invites: [user], + ), + ); }, ); } diff --git a/lib/views/widgets/containers/media-card.dart b/lib/views/widgets/containers/media-card.dart index 360915ca1..8fbac7682 100644 --- a/lib/views/widgets/containers/media-card.dart +++ b/lib/views/widgets/containers/media-card.dart @@ -1,16 +1,14 @@ import 'package:flutter/material.dart'; import 'package:syphon/global/colours.dart'; -import 'package:syphon/global/dimensions.dart'; - -_empty() {} +import 'package:syphon/global/noop.dart'; class MediaCard extends StatelessWidget { const MediaCard({ Key? key, this.text, this.icon = Icons.photo, - this.onPress = _empty, + this.onPress = noop, this.disabled = false, }) : super(key: key); diff --git a/lib/views/widgets/image-matrix.dart b/lib/views/widgets/image-matrix.dart index d9e9ef53e..61bb430a4 100644 --- a/lib/views/widgets/image-matrix.dart +++ b/lib/views/widgets/image-matrix.dart @@ -114,31 +114,30 @@ class MatrixImageState extends State with Lifecycle { // allows user option to manually load images on tap if (!widget.autodownload && !props.exists && !localLoading) { return TouchableOpacity( + behavior: HitTestBehavior.translucent, onTap: () => onManualLoad(), child: Container( + padding: EdgeInsets.all(widget.loadingPadding), width: widget.size ?? widget.width, height: widget.size ?? widget.height, - child: Padding( - padding: EdgeInsets.all(widget.loadingPadding), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.photo, - size: Dimensions.avatarSizeLarge, - color: Colors.white, - ), - Padding( - padding: EdgeInsets.only(top: 8), - child: Text( - Strings.labelDownloadImage, - style: TextStyle( - color: Colors.white, - ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.photo, + size: Dimensions.avatarSizeLarge, + color: Colors.white, + ), + Padding( + padding: EdgeInsets.only(top: 8), + child: Text( + Strings.labelDownloadImage, + style: TextStyle( + color: Colors.white, ), ), - ], - ), + ), + ], ), ), ); diff --git a/lib/views/widgets/lists/list-local-images.dart b/lib/views/widgets/lists/list-local-images.dart index fd3528264..7156aaf45 100644 --- a/lib/views/widgets/lists/list-local-images.dart +++ b/lib/views/widgets/lists/list-local-images.dart @@ -8,7 +8,6 @@ import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; import 'package:syphon/global/colours.dart'; import 'package:syphon/global/dimensions.dart'; -import 'package:syphon/global/print.dart'; import 'package:syphon/global/strings.dart'; import 'package:syphon/views/widgets/lifecycle.dart'; diff --git a/lib/views/widgets/messages/message.dart b/lib/views/widgets/messages/message.dart index 711d18c4f..d5596cae5 100644 --- a/lib/views/widgets/messages/message.dart +++ b/lib/views/widgets/messages/message.dart @@ -3,12 +3,12 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_redux/flutter_redux.dart'; -import 'package:photo_view/photo_view.dart'; import 'package:swipeable/swipeable.dart'; import 'package:syphon/global/colours.dart'; import 'package:syphon/global/dimensions.dart'; import 'package:syphon/global/formatters.dart'; import 'package:syphon/global/libs/matrix/constants.dart'; +import 'package:syphon/global/noop.dart'; import 'package:syphon/global/strings.dart'; import 'package:syphon/global/weburl.dart'; import 'package:syphon/store/events/messages/model.dart'; @@ -47,12 +47,13 @@ class MessageWidget extends StatelessWidget { this.timeFormat = TimeFormat.hr12, this.color, this.luminance = 0.0, + this.onSwipe = noop, + this.onResend = noop, this.onSendEdit, this.onLongPress, this.onPressAvatar, this.onInputReaction, this.onToggleReaction, - this.onSwipe, }) : super(key: key); final bool messageOnly; @@ -76,7 +77,8 @@ class MessageWidget extends StatelessWidget { final ThemeType themeType; final TextEditingController? editorController; - final Function? onSwipe; + final Function onSwipe; + final Function onResend; final Function? onSendEdit; final Function? onPressAvatar; final Function? onInputReaction; @@ -203,9 +205,7 @@ class MessageWidget extends StatelessWidget { } onSwipeMessage(Message message) { - if (onSwipe != null) { - onSwipe!(message); - } + onSwipe(message); } onConfirmLink(BuildContext context, String? url) { @@ -415,6 +415,7 @@ class MessageWidget extends StatelessWidget { ), ), child: GestureDetector( + onTap: message.failed ? onResend(message) : null, onLongPress: () { if (onLongPress != null) { HapticFeedback.lightImpact(); diff --git a/lib/views/widgets/modals/modal-user-details.dart b/lib/views/widgets/modals/modal-user-details.dart index fa19255dc..8482784ec 100644 --- a/lib/views/widgets/modals/modal-user-details.dart +++ b/lib/views/widgets/modals/modal-user-details.dart @@ -10,6 +10,7 @@ import 'package:syphon/global/dimensions.dart'; import 'package:syphon/global/strings.dart'; import 'package:syphon/store/index.dart'; import 'package:syphon/store/rooms/actions.dart'; +import 'package:syphon/store/rooms/selectors.dart'; import 'package:syphon/store/user/actions.dart'; import 'package:syphon/store/user/model.dart'; import 'package:syphon/views/home/chat/chat-screen.dart'; @@ -53,43 +54,52 @@ class ModalUserDetails extends StatelessWidget { onMessageUser({required BuildContext context, required _Props props}) async { final user = props.user; - String directChatId = props.directChatId; + final existingChatId = props.existingChatId; - // Asking the user to create new DM if there isn't one already - if (directChatId.isEmpty) { - await showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext dialogContext) => DialogStartChat( - user: user, - title: Strings.listItemUserDetailsStartChat(user.displayName), - content: Strings.confirmStartChat, - onStartChat: () async { - directChatId = await props.onCreateChatDirect(user: user) ?? ''; - Navigator.pop(dialogContext); - - if (nested != null && nested!) { - Navigator.pop(dialogContext); - } - }, - onCancel: () async { - Navigator.pop(dialogContext); - }, - ), - ); - } - - if (directChatId.isNotEmpty) { - Navigator.popAndPushNamed( + // Navigate to existing DM if one already exists + if (existingChatId.isNotEmpty) { + return Navigator.popAndPushNamed( context, Routes.chat, arguments: ChatScreenArguments( - roomId: directChatId, + roomId: existingChatId, title: user.displayName, ), ); } - + + // Asking the user to create new DM if there isn't one already + await showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext dialogContext) => DialogStartChat( + user: user, + title: Strings.listItemUserDetailsStartChat(user.displayName), + content: Strings.confirmStartChat, + onStartChat: () async { + final roomIdNew = await props.onCreateChatDirect(user: user) ?? ''; + Navigator.pop(dialogContext); + + if (nested != null && nested!) { + Navigator.pop(dialogContext); + } + + if (roomIdNew) { + Navigator.popAndPushNamed( + context, + Routes.chat, + arguments: ChatScreenArguments( + roomId: existingChatId, + title: user.displayName, + ), + ); + } + }, + onCancel: () async { + Navigator.pop(dialogContext); + }, + ), + ); } @override @@ -260,19 +270,19 @@ class ModalUserDetails extends StatelessWidget { class _Props extends Equatable { final User user; - final Map users; - final String directChatId; final bool blocked; final bool loading; + final String existingChatId; + final Map users; final Function onBlockUser; final Function onCreateChatDirect; const _Props({ required this.user, required this.users, - required this.directChatId, required this.loading, required this.blocked, + required this.existingChatId, required this.onCreateChatDirect, required this.onBlockUser, }); @@ -281,7 +291,7 @@ class _Props extends Equatable { List get props => [ user, users, - directChatId, + existingChatId, loading, blocked, ]; @@ -306,14 +316,10 @@ class _Props extends Equatable { return users[userId] ?? User(); }(), users: store.state.userStore.users, - directChatId: () { - for (final room in store.state.roomStore.roomList) { - if (room.direct && user != null && room.userIds.contains(user.userId)) { - return room.id; - } - } - return ''; - }(), + existingChatId: selectDirectChatIdExisting( + state: store.state, + user: user ?? User(userId: userId), + ), loading: store.state.userStore.loading, blocked: store.state.userStore.blocked.contains(userId ?? user!.userId), onBlockUser: (User user) async { diff --git a/pubspec.yaml b/pubspec.yaml index 565f778e6..001e62a22 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: syphon description: a privacy focused matrix client -version: 0.2.11+2111 +version: 0.2.12+2122 environment: sdk: ">=2.12.0 <3.0.0"