diff --git a/.gitignore b/.gitignore index e059815..4511167 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ .svn/ lib/generated_plugin_registrant.dart google-services.json +prime # libolm package /assets/js/package/* diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3cea1e6..3c08604 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -158,8 +158,7 @@ upload_to_fdroid_repo: - export UPDATE_VERSION=$(pcregrep -o1 'version:\\s([0-9]*\\.[0-9]*\\.[0-9]*)\\+[0-9]*' pubspec.yaml) && mv app-release.apk "${UPDATE_VERSION}.apk" - rsync -rav -e ssh ./ fluffy@fdroid.nordgedanken.dev:/fdroid/repo - ssh fluffy@fdroid.nordgedanken.dev "cd fdroid && mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc && fdroid update" - dependencies: - - build_android_apk + needs: ["build_android_apk"] only: - tags @@ -174,8 +173,7 @@ pages: - cd build/web/ && bundle install && cd ../../ - cd build/web/ && bundle exec jekyll build -d public && cd ../../ - mv build/web/public ./ - dependencies: - - build_web + needs: ["build_web"] artifacts: paths: - public @@ -215,3 +213,4 @@ snap:publish: - './*.snap' when: on_success expire_in: 1 week + needs: [] diff --git a/README.md b/README.md index 7900aab..5c3a6c5 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,12 @@ An experimental fork of FluffyChat. # Changes from FluffyChat + * Swipe to reply (or forward/edit) * Reworked auth flow * Removed Sentry * Double check of .well-known * Get Jitsi instance from .well-known + * Redesigned settings # Features * Single and group chats @@ -35,8 +37,8 @@ An experimental fork of FluffyChat. 2. Clone the repo: ``` -git clone --recurse-submodules https://gitlab.com/ChristianPauly/fluffychat-flutter -cd fluffychat-flutter +git clone --recurse-submodules https://github.com/innereq/FurryChat.git +cd FurryChat ``` 3. Choose your target platform below and enable support for it. @@ -80,13 +82,6 @@ flutter build windows --release flutter build macos --release ``` - -### Docker - -Don't even ask. - -`docker run -ti --privileged -v /dev/bus/usb:/dev/bus/usb -v ${PWD}:/build -v /home/inex/.pub-cache:/home/inex/.pub-cache -v /home/inex/flutter:/home/inex/flutter -d flutter-fluffy:1.0` - ## How to add translations for your language You can use Weblate to translate the app to your language: diff --git a/l10n.yaml b/l10n.yaml index 19636a3..82662ea 100644 --- a/l10n.yaml +++ b/l10n.yaml @@ -1,4 +1,5 @@ arb-dir: lib/l10n template-arb-file: intl_en.arb output-localization-file: l10n.dart -output-class: L10n \ No newline at end of file +output-class: L10n +preferred-supported-locales: ["en"] \ No newline at end of file diff --git a/lib/components/matrix.dart b/lib/components/matrix.dart index 08b0bf6..27b2219 100644 --- a/lib/components/matrix.dart +++ b/lib/components/matrix.dart @@ -11,7 +11,6 @@ import 'package:furrychat/utils/user_status.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:localstorage/localstorage.dart'; import 'package:universal_html/prefer_universal/html.dart' as html; import 'package:url_launcher/url_launcher.dart'; @@ -72,13 +71,15 @@ class MatrixState extends State { File wallpaper; bool renderHtml = false; + String swipeToEndAction; + String swipeToStartAction = 'reply'; + String jitsiInstance = 'https://meet.jit.si/'; void clean() async { if (!kIsWeb) return; - final storage = LocalStorage('LocalStorage'); - await storage.ready; + final storage = await getLocalStorage(); await storage.deleteItem(widget.clientName); } @@ -285,6 +286,16 @@ class MatrixState extends State { store.getItem('chat.fluffy.renderHtml').then((final render) async { renderHtml = render == '1'; }); + store + .getItem('dev.inex.furrychat.swipeToEndAction') + .then((final action) async { + swipeToEndAction = action ?? swipeToEndAction; + }); + store + .getItem('dev.inex.furrychat.swipeToStartAction') + .then((final action) async { + swipeToStartAction = action ?? swipeToStartAction; + }); } if (kIsWeb) { onFocusSub = html.window.onFocus.listen((_) => webHasFocus = true); diff --git a/lib/l10n/intl_ar.arb b/lib/l10n/intl_ar.arb index e46adbd..f21d888 100644 --- a/lib/l10n/intl_ar.arb +++ b/lib/l10n/intl_ar.arb @@ -189,7 +189,7 @@ "username": {} } }, - "changedTheDisplaynameTo": "{username} غيّر اسمه الى {displayname}", + "changedTheDisplaynameTo": "{username} غيّر اسمه العلني الى {displayname}", "@changedTheDisplaynameTo": { "type": "text", "placeholders": { @@ -375,7 +375,7 @@ "type": "text", "placeholders": {} }, - "couldNotSetDisplayname": "تعذر تعيين الاسم", + "couldNotSetDisplayname": "تعذر تعيين الاسم العلني", "@couldNotSetDisplayname": { "type": "text", "placeholders": {} @@ -489,7 +489,7 @@ "type": "text", "placeholders": {} }, - "displaynameHasBeenChanged": "غُيِّر الاسم", + "displaynameHasBeenChanged": "غُيِّر الاسم العلني", "@displaynameHasBeenChanged": { "type": "text", "placeholders": {} @@ -499,7 +499,7 @@ "type": "text", "placeholders": {} }, - "editDisplayname": "حرر الاسم", + "editDisplayname": "حرر الاسم العلني", "@editDisplayname": { "type": "text", "placeholders": {} @@ -514,7 +514,7 @@ "type": "text", "placeholders": {} }, - "emoteWarnNeedToPick": "اختر صورة ورمزا للانفعالة", + "emoteWarnNeedToPick": "اختر صورة ورمزا للانفعالة!", "@emoteWarnNeedToPick": { "type": "text", "placeholders": {} @@ -1374,7 +1374,7 @@ "type": "text", "placeholders": {} }, - "useAmoledTheme": "", + "useAmoledTheme": "هل تريد استخدم ألوان متوافقة مع Amoled؟", "@useAmoledTheme": { "type": "text", "placeholders": {} @@ -1507,14 +1507,14 @@ "unreadCount": {} } }, - "unreadMessages": "", + "unreadMessages": "{unreadEvents} رسالة غير مقروءة", "@unreadMessages": { "type": "text", "placeholders": { "unreadEvents": {} } }, - "unreadMessagesInChats": "", + "unreadMessagesInChats": "{unreadEvents} رسالة غير مقروءة من {unreadChats} محادثة", "@unreadMessagesInChats": { "type": "text", "placeholders": { @@ -1702,5 +1702,10 @@ "@yourOwnUsername": { "type": "text", "placeholders": {} + }, + "privacy": "الخصوصية", + "@privacy": { + "type": "text", + "placeholders": {} } } diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 588e861..caf9aa8 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -514,6 +514,11 @@ "type": "text", "placeholders": {} }, + "edit": "Edit", + "@edit": { + "type": "text", + "placeholders": {} + }, "editDisplayname": "Edit displayname", "@editDisplayname": { "type": "text", @@ -1439,6 +1444,16 @@ "type": "text", "placeholders": {} }, + "swipeToEndAction": "Swipe to right action", + "@swipeToEndAction": { + "type": "text", + "placeholders": {} + }, + "swipeToStartAction": "Swipe to left action", + "@swipeToStartAction": { + "type": "text", + "placeholders": {} + }, "donate": "Donate", "@donate": { "type": "text", diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index bf5cc93..c5cbc6f 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -194,5 +194,272 @@ "@about": { "type": "text", "placeholders": {} + }, + "deleteMessage": "Cancella messaggio", + "@deleteMessage": { + "type": "text", + "placeholders": {} + }, + "deleteAccount": "Elimina account", + "@deleteAccount": { + "type": "text", + "placeholders": {} + }, + "deactivateAccountWarning": "Disabiliterà il tuo account. Non puoi tornare indietro! Sei sicuro?", + "@deactivateAccountWarning": { + "type": "text", + "placeholders": {} + }, + "delete": "Cancella", + "@delete": { + "type": "text", + "placeholders": {} + }, + "dateWithYear": "{day}-{month}-{year}", + "@dateWithYear": { + "type": "text", + "placeholders": { + "year": {}, + "month": {}, + "day": {} + } + }, + "dateWithoutYear": "{month}-{day}", + "@dateWithoutYear": { + "type": "text", + "placeholders": { + "month": {}, + "day": {} + } + }, + "dateAndTimeOfDay": "{date}, {timeOfDay}", + "@dateAndTimeOfDay": { + "type": "text", + "placeholders": { + "date": {}, + "timeOfDay": {} + } + }, + "currentlyActive": "Attualmente attivo", + "@currentlyActive": { + "type": "text", + "placeholders": {} + }, + "createNewGroup": "Crea un nuovo gruppo", + "@createNewGroup": { + "type": "text", + "placeholders": {} + }, + "createdTheChat": "{username} ha creato la chat", + "@createdTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "createAccountNow": "Crea ora un account", + "@createAccountNow": { + "type": "text", + "placeholders": {} + }, + "create": "Crea", + "@create": { + "type": "text", + "placeholders": {} + }, + "countParticipants": "{count} partecipanti", + "@countParticipants": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "couldNotSetDisplayname": "Impossibile impostare nome", + "@couldNotSetDisplayname": { + "type": "text", + "placeholders": {} + }, + "couldNotSetAvatar": "Impossibile impostare avatar", + "@couldNotSetAvatar": { + "type": "text", + "placeholders": {} + }, + "couldNotDecryptMessage": "Impossibile decriptare messaggio: {error}", + "@couldNotDecryptMessage": { + "type": "text", + "placeholders": { + "error": {} + } + }, + "copy": "Copia", + "@copy": { + "type": "text", + "placeholders": {} + }, + "copiedToClipboard": "Copiato negli Appunti", + "@copiedToClipboard": { + "type": "text", + "placeholders": {} + }, + "contentViewer": "Visualizzatore contenuti", + "@contentViewer": { + "type": "text", + "placeholders": {} + }, + "contactHasBeenInvitedToTheGroup": "Il contatto è stato invitato nel gruppo", + "@contactHasBeenInvitedToTheGroup": { + "type": "text", + "placeholders": {} + }, + "connectionAttemptFailed": "Tentativo di connessione fallito", + "@connectionAttemptFailed": { + "type": "text", + "placeholders": {} + }, + "connect": "Connetti", + "@connect": { + "type": "text", + "placeholders": {} + }, + "confirm": "Conferma", + "@confirm": { + "type": "text", + "placeholders": {} + }, + "compareNumbersMatch": "Confronta e assicurati che le seguenti emoji corrispondano a quelle dell'altro dispositivo:", + "@compareNumbersMatch": { + "type": "text", + "placeholders": {} + }, + "compareEmojiMatch": "Confronta e assicurati che le seguenti emoji corrispondano a quelle dell'altro dispositivo:", + "@compareEmojiMatch": { + "type": "text", + "placeholders": {} + }, + "close": "Chiudi", + "@close": { + "type": "text", + "placeholders": {} + }, + "chooseAUsername": "Scegli un username", + "@chooseAUsername": { + "type": "text", + "placeholders": {} + }, + "chooseAStrongPassword": "Scegli una password complessa", + "@chooseAStrongPassword": { + "type": "text", + "placeholders": {} + }, + "chatDetails": "Dettagli chat", + "@chatDetails": { + "type": "text", + "placeholders": {} + }, + "chat": "Chat", + "@chat": { + "type": "text", + "placeholders": {} + }, + "channelCorruptedDecryptError": "La crittografia è corrotta", + "@channelCorruptedDecryptError": { + "type": "text", + "placeholders": {} + }, + "changeTheServer": "Cambia server", + "@changeTheServer": { + "type": "text", + "placeholders": {} + }, + "changeWallpaper": "Cambia sfondo", + "@changeWallpaper": { + "type": "text", + "placeholders": {} + }, + "changeTheNameOfTheGroup": "Cambia il nome del gruppo", + "@changeTheNameOfTheGroup": { + "type": "text", + "placeholders": {} + }, + "changelog": "Registro cambiamenti", + "@changelog": { + "type": "text", + "placeholders": {} + }, + "changedTheRoomInvitationLink": "{username} ha cambiato il link di invito", + "@changedTheRoomInvitationLink": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheRoomAliases": "{username} ha cambiato il nome delle stanze", + "@changedTheRoomAliases": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheProfileAvatar": "{username} ha cambiato il loro avatar", + "@changedTheProfileAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheJoinRulesTo": "{username} ha cambiato le regole per unirsi in: {joinRules}", + "@changedTheJoinRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "joinRules": {} + } + }, + "changedTheJoinRules": "{username} ha cambiato le regole per unirsi", + "@changedTheJoinRules": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheHistoryVisibilityTo": "{username} ha cambiato la visibilità della cronologia in: {rules}", + "@changedTheHistoryVisibilityTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "changedTheHistoryVisibility": "{username} ha cambiato la visibilità della cronologia", + "@changedTheHistoryVisibility": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheGuestAccessRulesTo": "{username} ha cambiato le regole di accesso per ospiti con: {rules}", + "@changedTheGuestAccessRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "changedTheChatAvatar": "{username} ha cambiato avatar", + "@changedTheChatAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "askSSSSSign": "Per entrare con l'altro utente, per favore inserisci la tua passphrase o recovery key.", + "@askSSSSSign": { + "type": "text", + "placeholders": {} + }, + "askSSSSCache": "Per favore inserisci la tua passphrase o recovery key per la cache delle chiavi.", + "@askSSSSCache": { + "type": "text", + "placeholders": {} } } diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index d8960c1..7c4893f 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -114,6 +114,11 @@ "type": "text", "placeholders": {} }, + "avatar": "Аватар", + "@avatar": { + "type": "text", + "placeholders": {} + }, "avatarHasBeenChanged": "Аватар был изменён", "@avatarHasBeenChanged": { "type": "text", @@ -197,6 +202,11 @@ "displayname": {} } }, + "changeThePassword": "Сменить пароль", + "@changeThePassword": { + "type": "text", + "placeholders": {} + }, "changedTheGuestAccessRules": "{username} изменил(а) правила гостевого доступа", "@changedTheGuestAccessRules": { "type": "text", @@ -509,6 +519,11 @@ "type": "text", "placeholders": {} }, + "edit": "Редактировать", + "@edit": { + "type": "text", + "placeholders": {} + }, "editDisplayname": "Отображаемое имя", "@editDisplayname": { "type": "text", @@ -681,11 +696,21 @@ "type": "text", "placeholders": {} }, + "homeserver": "Сервер Matrix", + "@homeserver": { + "type": "text", + "placeholders": {} + }, "homeserverIsNotCompatible": "Несовместимый сервер Matrix", "@homeserverIsNotCompatible": { "type": "text", "placeholders": {} }, + "homeserverOrMXID": "Сервер или полное имя пользователя", + "@homeserverOrMXID": { + "type": "text", + "placeholders": {} + }, "id": "ID", "@id": { "type": "text", @@ -1372,6 +1397,16 @@ "type": "text", "placeholders": {} }, + "swipeToEndAction": "Действие по жесту вправо", + "@swipeToEndAction": { + "type": "text", + "placeholders": {} + }, + "swipeToStartAction": "Действие по жесту влево", + "@swipeToStartAction": { + "type": "text", + "placeholders": {} + }, "systemTheme": "Системная", "@systemTheme": { "type": "text", diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index 1c851f5..a5295f6 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -92,7 +92,7 @@ "type": "text", "placeholders": {} }, - "askSSSSSign": "", + "askSSSSSign": "请输入您的安全存储的密码短语或恢复密钥,以向对方签名。", "@askSSSSSign": { "type": "text", "placeholders": {} @@ -273,7 +273,7 @@ "type": "text", "placeholders": {} }, - "changeTheme": "", + "changeTheme": "改变风格", "@changeTheme": { "type": "text", "placeholders": {} @@ -323,118 +323,118 @@ "type": "text", "placeholders": {} }, - "compareEmojiMatch": "对比并确认这些表情匹配其他那些设备", + "compareEmojiMatch": "对比并确认这些表情匹配其他那些设备:", "@compareEmojiMatch": { "type": "text", "placeholders": {} }, - "compareNumbersMatch": "", + "compareNumbersMatch": "比较以下数字,确保它们和另一设备上的相同:", "@compareNumbersMatch": { "type": "text", "placeholders": {} }, - "confirm": "", + "confirm": "确认", "@confirm": { "type": "text", "placeholders": {} }, - "connect": "", + "connect": "连接", "@connect": { "type": "text", "placeholders": {} }, - "connectionAttemptFailed": "", + "connectionAttemptFailed": "连接尝试失败", "@connectionAttemptFailed": { "type": "text", "placeholders": {} }, - "contactHasBeenInvitedToTheGroup": "", + "contactHasBeenInvitedToTheGroup": "联系人已被邀请至群组", "@contactHasBeenInvitedToTheGroup": { "type": "text", "placeholders": {} }, - "contentViewer": "", + "contentViewer": "内容查看器", "@contentViewer": { "type": "text", "placeholders": {} }, - "copiedToClipboard": "", + "copiedToClipboard": "已复制到剪贴板", "@copiedToClipboard": { "type": "text", "placeholders": {} }, - "copy": "", + "copy": "复制", "@copy": { "type": "text", "placeholders": {} }, - "couldNotDecryptMessage": "", + "couldNotDecryptMessage": "不能解密消息:{error}", "@couldNotDecryptMessage": { "type": "text", "placeholders": { "error": {} } }, - "couldNotSetAvatar": "", + "couldNotSetAvatar": "不能设定头像", "@couldNotSetAvatar": { "type": "text", "placeholders": {} }, - "couldNotSetDisplayname": "", + "couldNotSetDisplayname": "不能设定显示名称", "@couldNotSetDisplayname": { "type": "text", "placeholders": {} }, - "countParticipants": "", + "countParticipants": "{count} 参与者", "@countParticipants": { "type": "text", "placeholders": { "count": {} } }, - "create": "", + "create": "创建", "@create": { "type": "text", "placeholders": {} }, - "createAccountNow": "", + "createAccountNow": "现在创建账户", "@createAccountNow": { "type": "text", "placeholders": {} }, - "createdTheChat": "", + "createdTheChat": "{username} 创建了聊天", "@createdTheChat": { "type": "text", "placeholders": { "username": {} } }, - "createNewGroup": "", + "createNewGroup": "创建新群组", "@createNewGroup": { "type": "text", "placeholders": {} }, - "crossSigningDisabled": "", + "crossSigningDisabled": "Cross-Signing未启用", "@crossSigningDisabled": { "type": "text", "placeholders": {} }, - "crossSigningEnabled": "", + "crossSigningEnabled": "Cross-Signing已启用", "@crossSigningEnabled": { "type": "text", "placeholders": {} }, - "currentlyActive": "", + "currentlyActive": "目前活跃", "@currentlyActive": { "type": "text", "placeholders": {} }, - "darkTheme": "", + "darkTheme": "深色", "@darkTheme": { "type": "text", "placeholders": {} }, - "dateAndTimeOfDay": "", + "dateAndTimeOfDay": "{date}, {timeOfDay}", "@dateAndTimeOfDay": { "type": "text", "placeholders": { @@ -442,7 +442,7 @@ "timeOfDay": {} } }, - "dateWithoutYear": "", + "dateWithoutYear": "{month}-{day}", "@dateWithoutYear": { "type": "text", "placeholders": { @@ -450,7 +450,7 @@ "day": {} } }, - "dateWithYear": "", + "dateWithYear": "{year}-{month}-{day}", "@dateWithYear": { "type": "text", "placeholders": { @@ -459,211 +459,211 @@ "day": {} } }, - "delete": "", + "delete": "删除", "@delete": { "type": "text", "placeholders": {} }, - "deleteMessage": "", + "deleteMessage": "删除消息", "@deleteMessage": { "type": "text", "placeholders": {} }, - "deny": "", + "deny": "否认", "@deny": { "type": "text", "placeholders": {} }, - "device": "", + "device": "设备", "@device": { "type": "text", "placeholders": {} }, - "devices": "", + "devices": "设备", "@devices": { "type": "text", "placeholders": {} }, - "discardPicture": "", + "discardPicture": "丢弃图片", "@discardPicture": { "type": "text", "placeholders": {} }, - "displaynameHasBeenChanged": "", + "displaynameHasBeenChanged": "显示名称已被改变", "@displaynameHasBeenChanged": { "type": "text", "placeholders": {} }, - "donate": "", + "donate": "捐助", "@donate": { "type": "text", "placeholders": {} }, - "downloadFile": "", + "downloadFile": "下载文件", "@downloadFile": { "type": "text", "placeholders": {} }, - "editDisplayname": "", + "editDisplayname": "编辑显示名称", "@editDisplayname": { "type": "text", "placeholders": {} }, - "editJitsiInstance": "", + "editJitsiInstance": "编辑Jitsi实例", "@editJitsiInstance": { "type": "text", "placeholders": {} }, - "emoteExists": "", + "emoteExists": "表情已存在!", "@emoteExists": { "type": "text", "placeholders": {} }, - "emoteInvalid": "", + "emoteInvalid": "无效的表情快捷码!", "@emoteInvalid": { "type": "text", "placeholders": {} }, - "emoteSettings": "", + "emoteSettings": "表情设置", "@emoteSettings": { "type": "text", "placeholders": {} }, - "emoteShortcode": "", + "emoteShortcode": "表情快捷码", "@emoteShortcode": { "type": "text", "placeholders": {} }, - "emoteWarnNeedToPick": "", + "emoteWarnNeedToPick": "你需要取一个快捷码和一张图片!", "@emoteWarnNeedToPick": { "type": "text", "placeholders": {} }, - "emptyChat": "", + "emptyChat": "空聊天", "@emptyChat": { "type": "text", "placeholders": {} }, - "enableEncryptionWarning": "", + "enableEncryptionWarning": "你将不能再停用加密,确定吗?", "@enableEncryptionWarning": { "type": "text", "placeholders": {} }, - "encryption": "", + "encryption": "加密", "@encryption": { "type": "text", "placeholders": {} }, - "encryptionAlgorithm": "", + "encryptionAlgorithm": "加密算法", "@encryptionAlgorithm": { "type": "text", "placeholders": {} }, - "encryptionNotEnabled": "", + "encryptionNotEnabled": "加密未启用", "@encryptionNotEnabled": { "type": "text", "placeholders": {} }, - "end2endEncryptionSettings": "", + "end2endEncryptionSettings": "端到端加密设置", "@end2endEncryptionSettings": { "type": "text", "placeholders": {} }, - "endedTheCall": "", + "endedTheCall": "{senderName} 结束了通话", "@endedTheCall": { "type": "text", "placeholders": { "senderName": {} } }, - "enterAGroupName": "", + "enterAGroupName": "输入群组名称", "@enterAGroupName": { "type": "text", "placeholders": {} }, - "enterAUsername": "", + "enterAUsername": "输入用户名", "@enterAUsername": { "type": "text", "placeholders": {} }, - "enterYourHomeserver": "", + "enterYourHomeserver": "输入服务器地址", "@enterYourHomeserver": { "type": "text", "placeholders": {} }, - "fileName": "", + "fileName": "文件名", "@fileName": { "type": "text", "placeholders": {} }, - "fileSize": "", + "fileSize": "文件大小", "@fileSize": { "type": "text", "placeholders": {} }, - "fluffychat": "", + "fluffychat": "FluffyChat", "@fluffychat": { "type": "text", "placeholders": {} }, - "forward": "", + "forward": "转发", "@forward": { "type": "text", "placeholders": {} }, - "friday": "", + "friday": "星期五", "@friday": { "type": "text", "placeholders": {} }, - "fromJoining": "", + "fromJoining": "自加入起", "@fromJoining": { "type": "text", "placeholders": {} }, - "fromTheInvitation": "", + "fromTheInvitation": "自邀请起", "@fromTheInvitation": { "type": "text", "placeholders": {} }, - "group": "", + "group": "群组", "@group": { "type": "text", "placeholders": {} }, - "groupDescription": "", + "groupDescription": "群组描述", "@groupDescription": { "type": "text", "placeholders": {} }, - "groupDescriptionHasBeenChanged": "", + "groupDescriptionHasBeenChanged": "群组描述已被更改", "@groupDescriptionHasBeenChanged": { "type": "text", "placeholders": {} }, - "groupIsPublic": "", + "groupIsPublic": "群组是公开的", "@groupIsPublic": { "type": "text", "placeholders": {} }, - "groupWith": "", + "groupWith": "名称为{displayname}的群组", "@groupWith": { "type": "text", "placeholders": { "displayname": {} } }, - "guestsAreForbidden": "", + "guestsAreForbidden": "访客被禁止", "@guestsAreForbidden": { "type": "text", "placeholders": {} }, - "guestsCanJoin": "", + "guestsCanJoin": "访客可以加入", "@guestsCanJoin": { "type": "text", "placeholders": {} }, - "hasWithdrawnTheInvitationFor": "", + "hasWithdrawnTheInvitationFor": "{username} 撤回了对 {targetName} 的邀请", "@hasWithdrawnTheInvitationFor": { "type": "text", "placeholders": { @@ -671,49 +671,49 @@ "targetName": {} } }, - "help": "", + "help": "帮助", "@help": { "type": "text", "placeholders": {} }, - "homeserverIsNotCompatible": "", + "homeserverIsNotCompatible": "服务器不兼容", "@homeserverIsNotCompatible": { "type": "text", "placeholders": {} }, - "id": "", + "id": "ID", "@id": { "type": "text", "placeholders": {} }, - "identity": "", + "identity": "身份", "@identity": { "type": "text", "placeholders": {} }, - "incorrectPassphraseOrKey": "", + "incorrectPassphraseOrKey": "不正确的密码短语或恢复密钥", "@incorrectPassphraseOrKey": { "type": "text", "placeholders": {} }, - "inviteContact": "", + "inviteContact": "邀请联系人", "@inviteContact": { "type": "text", "placeholders": {} }, - "inviteContactToGroup": "", + "inviteContactToGroup": "邀请联系人到 {groupName}", "@inviteContactToGroup": { "type": "text", "placeholders": { "groupName": {} } }, - "invited": "", + "invited": "已邀请", "@invited": { "type": "text", "placeholders": {} }, - "invitedUser": "", + "invitedUser": "{username} 邀请了 {targetName}", "@invitedUser": { "type": "text", "placeholders": { @@ -721,12 +721,12 @@ "targetName": {} } }, - "invitedUsersOnly": "", + "invitedUsersOnly": "仅被邀请用户", "@invitedUsersOnly": { "type": "text", "placeholders": {} }, - "inviteText": "", + "inviteText": "{username} 邀请您到 FluffyChat. \n1. 安装 FluffyChat: https://fluffychat.im \n2. 注册或登录 \n3. 打开该邀请链接: {link}", "@inviteText": { "type": "text", "placeholders": { @@ -734,39 +734,39 @@ "link": {} } }, - "isDeviceKeyCorrect": "", + "isDeviceKeyCorrect": "下列设备密钥是否正确?", "@isDeviceKeyCorrect": { "type": "text", "placeholders": {} }, - "isTyping": "", + "isTyping": "正在打字...", "@isTyping": { "type": "text", "placeholders": {} }, - "joinedTheChat": "", + "joinedTheChat": "{username} 加入了聊天", "@joinedTheChat": { "type": "text", "placeholders": { "username": {} } }, - "joinRoom": "", + "joinRoom": "加入聊天室", "@joinRoom": { "type": "text", "placeholders": {} }, - "keysCached": "", + "keysCached": "密钥已被缓存", "@keysCached": { "type": "text", "placeholders": {} }, - "keysMissing": "", + "keysMissing": "密钥缺失", "@keysMissing": { "type": "text", "placeholders": {} }, - "kicked": "", + "kicked": "{username} 踢了 {targetName}", "@kicked": { "type": "text", "placeholders": { @@ -774,7 +774,7 @@ "targetName": {} } }, - "kickedAndBanned": "", + "kickedAndBanned": "{username} 踢了 {targetName} 并将其封锁", "@kickedAndBanned": { "type": "text", "placeholders": { @@ -782,385 +782,385 @@ "targetName": {} } }, - "kickFromChat": "", + "kickFromChat": "从聊天室移除", "@kickFromChat": { "type": "text", "placeholders": {} }, - "lastActiveAgo": "", + "lastActiveAgo": "上次活跃: {localizedTimeShort}", "@lastActiveAgo": { "type": "text", "placeholders": { "localizedTimeShort": {} } }, - "lastSeenIp": "", + "lastSeenIp": "上次使用的IP", "@lastSeenIp": { "type": "text", "placeholders": {} }, - "lastSeenLongTimeAgo": "", + "lastSeenLongTimeAgo": "很长时间未上线", "@lastSeenLongTimeAgo": { "type": "text", "placeholders": {} }, - "leave": "", + "leave": "离开", "@leave": { "type": "text", "placeholders": {} }, - "leftTheChat": "", + "leftTheChat": "离开了聊天", "@leftTheChat": { "type": "text", "placeholders": {} }, - "license": "", + "license": "许可证", "@license": { "type": "text", "placeholders": {} }, - "lightTheme": "", + "lightTheme": "浅色", "@lightTheme": { "type": "text", "placeholders": {} }, - "loadCountMoreParticipants": "", + "loadCountMoreParticipants": "加载 {count} 个更多的参与者", "@loadCountMoreParticipants": { "type": "text", "placeholders": { "count": {} } }, - "loadingPleaseWait": "", + "loadingPleaseWait": "加载中...请等待", "@loadingPleaseWait": { "type": "text", "placeholders": {} }, - "loadMore": "", + "loadMore": "加载更多...", "@loadMore": { "type": "text", "placeholders": {} }, - "login": "", + "login": "登入", "@login": { "type": "text", "placeholders": {} }, - "logInTo": "", + "logInTo": "登入 {homeserver}", "@logInTo": { "type": "text", "placeholders": { "homeserver": {} } }, - "logout": "", + "logout": "登出", "@logout": { "type": "text", "placeholders": {} }, - "makeAModerator": "", + "makeAModerator": "创建监管者", "@makeAModerator": { "type": "text", "placeholders": {} }, - "makeAnAdmin": "", + "makeAnAdmin": "创建管理员", "@makeAnAdmin": { "type": "text", "placeholders": {} }, - "makeSureTheIdentifierIsValid": "", + "makeSureTheIdentifierIsValid": "确保识别码正确", "@makeSureTheIdentifierIsValid": { "type": "text", "placeholders": {} }, - "messageWillBeRemovedWarning": "", + "messageWillBeRemovedWarning": "消息将对所有参与者移除", "@messageWillBeRemovedWarning": { "type": "text", "placeholders": {} }, - "moderator": "", + "moderator": "监管者", "@moderator": { "type": "text", "placeholders": {} }, - "monday": "", + "monday": "星期一", "@monday": { "type": "text", "placeholders": {} }, - "muteChat": "", + "muteChat": "将该聊天静音", "@muteChat": { "type": "text", "placeholders": {} }, - "needPantalaimonWarning": "", + "needPantalaimonWarning": "请注意当前您需要Pantalaimon以使用端到端加密功能。", "@needPantalaimonWarning": { "type": "text", "placeholders": {} }, - "newMessageInFluffyChat": "", + "newMessageInFluffyChat": "来自 FluffyChat 的新消息", "@newMessageInFluffyChat": { "type": "text", "placeholders": {} }, - "newPrivateChat": "", + "newPrivateChat": "新私密聊天", "@newPrivateChat": { "type": "text", "placeholders": {} }, - "newVerificationRequest": "", + "newVerificationRequest": "新的验证请求!", "@newVerificationRequest": { "type": "text", "placeholders": {} }, - "noCrossSignBootstrap": "", + "noCrossSignBootstrap": "Fluffychat目前不支持启用Cross-Signing. 请在Riot中启用.", "@noCrossSignBootstrap": { "type": "text", "placeholders": {} }, - "noEmotesFound": "", + "noEmotesFound": "未找到表情。😕", "@noEmotesFound": { "type": "text", "placeholders": {} }, - "noGoogleServicesWarning": "", + "noGoogleServicesWarning": "看起来您手机上没有谷歌服务框架。这对您保护隐私而言是个好决定!为收取FluffyChat的推送通知,推荐您使用microG: https://microg.org/", "@noGoogleServicesWarning": { "type": "text", "placeholders": {} }, - "noMegolmBootstrap": "", + "noMegolmBootstrap": "Fluffychat目前不支持启用在线密钥备份. 请在Riot中启用.", "@noMegolmBootstrap": { "type": "text", "placeholders": {} }, - "none": "", + "none": "无", "@none": { "type": "text", "placeholders": {} }, - "noPermission": "", + "noPermission": "没有权限", "@noPermission": { "type": "text", "placeholders": {} }, - "noRoomsFound": "", + "noRoomsFound": "未找到聊天室...", "@noRoomsFound": { "type": "text", "placeholders": {} }, - "notSupportedInWeb": "", + "notSupportedInWeb": "在网页版不支持", "@notSupportedInWeb": { "type": "text", "placeholders": {} }, - "numberSelected": "", + "numberSelected": "{number} 已选择", "@numberSelected": { "type": "text", "placeholders": { "number": {} } }, - "ok": "", + "ok": "ok", "@ok": { "type": "text", "placeholders": {} }, - "onlineKeyBackupDisabled": "", + "onlineKeyBackupDisabled": "在线密钥备份被停用", "@onlineKeyBackupDisabled": { "type": "text", "placeholders": {} }, - "onlineKeyBackupEnabled": "", + "onlineKeyBackupEnabled": "在线密钥备份已启用", "@onlineKeyBackupEnabled": { "type": "text", "placeholders": {} }, - "oopsSomethingWentWrong": "", + "oopsSomethingWentWrong": "哦!出了一些错误...", "@oopsSomethingWentWrong": { "type": "text", "placeholders": {} }, - "openAppToReadMessages": "", + "openAppToReadMessages": "打开应用以查看消息", "@openAppToReadMessages": { "type": "text", "placeholders": {} }, - "openCamera": "", + "openCamera": "打开相机", "@openCamera": { "type": "text", "placeholders": {} }, - "optionalGroupName": "", + "optionalGroupName": "(可选) 群组名称", "@optionalGroupName": { "type": "text", "placeholders": {} }, - "participatingUserDevices": "", + "participatingUserDevices": "参与者的设备", "@participatingUserDevices": { "type": "text", "placeholders": {} }, - "passphraseOrKey": "", + "passphraseOrKey": "密码短语或恢复密钥", "@passphraseOrKey": { "type": "text", "placeholders": {} }, - "password": "", + "password": "密码", "@password": { "type": "text", "placeholders": {} }, - "pickImage": "", + "pickImage": "选择图像", "@pickImage": { "type": "text", "placeholders": {} }, - "pin": "", + "pin": "固定", "@pin": { "type": "text", "placeholders": {} }, - "play": "", + "play": "播放 {fileName}", "@play": { "type": "text", "placeholders": { "fileName": {} } }, - "pleaseChooseAUsername": "", + "pleaseChooseAUsername": "请选择用户名", "@pleaseChooseAUsername": { "type": "text", "placeholders": {} }, - "pleaseEnterAMatrixIdentifier": "", + "pleaseEnterAMatrixIdentifier": "请输入matrix识别码", "@pleaseEnterAMatrixIdentifier": { "type": "text", "placeholders": {} }, - "pleaseEnterYourPassword": "", + "pleaseEnterYourPassword": "请输入您的密码", "@pleaseEnterYourPassword": { "type": "text", "placeholders": {} }, - "pleaseEnterYourUsername": "", + "pleaseEnterYourUsername": "请输入您的用户名", "@pleaseEnterYourUsername": { "type": "text", "placeholders": {} }, - "publicRooms": "", + "publicRooms": "公开聊天室", "@publicRooms": { "type": "text", "placeholders": {} }, - "recording": "", + "recording": "录制", "@recording": { "type": "text", "placeholders": {} }, - "redactedAnEvent": "", + "redactedAnEvent": "{username} 编辑了一个事件", "@redactedAnEvent": { "type": "text", "placeholders": { "username": {} } }, - "reject": "", + "reject": "拒绝", "@reject": { "type": "text", "placeholders": {} }, - "rejectedTheInvitation": "", + "rejectedTheInvitation": "{username} 拒绝了邀请", "@rejectedTheInvitation": { "type": "text", "placeholders": { "username": {} } }, - "rejoin": "", + "rejoin": "重新加入", "@rejoin": { "type": "text", "placeholders": {} }, - "remove": "", + "remove": "移除", "@remove": { "type": "text", "placeholders": {} }, - "removeAllOtherDevices": "", + "removeAllOtherDevices": "移除其他全部设备", "@removeAllOtherDevices": { "type": "text", "placeholders": {} }, - "removedBy": "", + "removedBy": "被{username}移除", "@removedBy": { "type": "text", "placeholders": { "username": {} } }, - "removeDevice": "", + "removeDevice": "移除设备", "@removeDevice": { "type": "text", "placeholders": {} }, - "removeExile": "", + "removeExile": "移除流放", "@removeExile": { "type": "text", "placeholders": {} }, - "removeMessage": "", + "removeMessage": "移除消息", "@removeMessage": { "type": "text", "placeholders": {} }, - "renderRichContent": "", + "renderRichContent": "渲染富文本内容", "@renderRichContent": { "type": "text", "placeholders": {} }, - "reply": "", + "reply": "回复", "@reply": { "type": "text", "placeholders": {} }, - "requestPermission": "", + "requestPermission": "请求权限", "@requestPermission": { "type": "text", "placeholders": {} }, - "requestToReadOlderMessages": "", + "requestToReadOlderMessages": "请求读取旧的消息", "@requestToReadOlderMessages": { "type": "text", "placeholders": {} }, - "revokeAllPermissions": "", + "revokeAllPermissions": "撤销全部权限", "@revokeAllPermissions": { "type": "text", "placeholders": {} }, - "roomHasBeenUpgraded": "", + "roomHasBeenUpgraded": "聊天室已升级", "@roomHasBeenUpgraded": { "type": "text", "placeholders": {} }, - "saturday": "", + "saturday": "星期六", "@saturday": { "type": "text", "placeholders": {} }, - "searchForAChat": "", + "searchForAChat": "搜索聊天室", "@searchForAChat": { "type": "text", "placeholders": {} }, - "seenByUser": "", + "seenByUser": "被 {username} 看见", "@seenByUser": { "type": "text", "placeholders": { "username": {} } }, - "seenByUserAndCountOthers": "", + "seenByUserAndCountOthers": "被 {username} 和 {count} 个其他人看见", "@seenByUserAndCountOthers": { "type": "text", "placeholders": { @@ -1168,7 +1168,7 @@ "count": {} } }, - "seenByUserAndUser": "", + "seenByUserAndUser": "被 {username} 和 {username2} 看见", "@seenByUserAndUser": { "type": "text", "placeholders": { @@ -1176,183 +1176,183 @@ "username2": {} } }, - "send": "", + "send": "发送", "@send": { "type": "text", "placeholders": {} }, - "sendAMessage": "", + "sendAMessage": "发送一条消息", "@sendAMessage": { "type": "text", "placeholders": {} }, - "sendFile": "", + "sendFile": "发送文件", "@sendFile": { "type": "text", "placeholders": {} }, - "sendImage": "", + "sendImage": "发送图像", "@sendImage": { "type": "text", "placeholders": {} }, - "sentAFile": "", + "sentAFile": "{username} 发送了文件", "@sentAFile": { "type": "text", "placeholders": { "username": {} } }, - "sentAnAudio": "", + "sentAnAudio": "{username} 发送了音频", "@sentAnAudio": { "type": "text", "placeholders": { "username": {} } }, - "sentAPicture": "", + "sentAPicture": "{username} 发送了图片", "@sentAPicture": { "type": "text", "placeholders": { "username": {} } }, - "sentASticker": "", + "sentASticker": "{username} 发送了贴纸", "@sentASticker": { "type": "text", "placeholders": { "username": {} } }, - "sentAVideo": "", + "sentAVideo": "{username} 发送了视频", "@sentAVideo": { "type": "text", "placeholders": { "username": {} } }, - "sentCallInformations": "", + "sentCallInformations": "{senderName} 发送了通话信息", "@sentCallInformations": { "type": "text", "placeholders": { "senderName": {} } }, - "sessionVerified": "", + "sessionVerified": "会话已验证", "@sessionVerified": { "type": "text", "placeholders": {} }, - "setAProfilePicture": "", + "setAProfilePicture": "设置个人资料图片", "@setAProfilePicture": { "type": "text", "placeholders": {} }, - "setGroupDescription": "", + "setGroupDescription": "设置群组描述", "@setGroupDescription": { "type": "text", "placeholders": {} }, - "setInvitationLink": "", + "setInvitationLink": "设置邀请链接", "@setInvitationLink": { "type": "text", "placeholders": {} }, - "setStatus": "", + "setStatus": "设置状态", "@setStatus": { "type": "text", "placeholders": {} }, - "settings": "", + "settings": "设置", "@settings": { "type": "text", "placeholders": {} }, - "share": "", + "share": "分享", "@share": { "type": "text", "placeholders": {} }, - "sharedTheLocation": "", + "sharedTheLocation": "{username} 分享了位置", "@sharedTheLocation": { "type": "text", "placeholders": { "username": {} } }, - "signUp": "", + "signUp": "注册", "@signUp": { "type": "text", "placeholders": {} }, - "skip": "", + "skip": "跳过", "@skip": { "type": "text", "placeholders": {} }, - "sourceCode": "", + "sourceCode": "源代码", "@sourceCode": { "type": "text", "placeholders": {} }, - "startedACall": "", + "startedACall": "{senderName} 开始了通话", "@startedACall": { "type": "text", "placeholders": { "senderName": {} } }, - "startYourFirstChat": "", + "startYourFirstChat": "开始你的第一个聊天 :-)", "@startYourFirstChat": { "type": "text", "placeholders": {} }, - "statusExampleMessage": "", + "statusExampleMessage": "你今天怎么样?", "@statusExampleMessage": { "type": "text", "placeholders": {} }, - "submit": "", + "submit": "提交", "@submit": { "type": "text", "placeholders": {} }, - "sunday": "", + "sunday": "星期日", "@sunday": { "type": "text", "placeholders": {} }, - "systemTheme": "", + "systemTheme": "系统", "@systemTheme": { "type": "text", "placeholders": {} }, - "tapToShowMenu": "", + "tapToShowMenu": "点击以显示菜单", "@tapToShowMenu": { "type": "text", "placeholders": {} }, - "theyDontMatch": "", + "theyDontMatch": "它们不匹配", "@theyDontMatch": { "type": "text", "placeholders": {} }, - "theyMatch": "", + "theyMatch": "它们匹配", "@theyMatch": { "type": "text", "placeholders": {} }, - "thisRoomHasBeenArchived": "", + "thisRoomHasBeenArchived": "该聊天室已被归档。", "@thisRoomHasBeenArchived": { "type": "text", "placeholders": {} }, - "thursday": "", + "thursday": "星期四", "@thursday": { "type": "text", "placeholders": {} }, - "timeOfDay": "", + "timeOfDay": "{hours12}:{minutes} {suffix}", "@timeOfDay": { "type": "text", "placeholders": { @@ -1362,23 +1362,23 @@ "suffix": {} } }, - "title": "", + "title": "FluffyChat", "@title": { "description": "Title for the application", "type": "text", "placeholders": {} }, - "tryToSendAgain": "", + "tryToSendAgain": "尝试重新发送", "@tryToSendAgain": { "type": "text", "placeholders": {} }, - "tuesday": "", + "tuesday": "星期二", "@tuesday": { "type": "text", "placeholders": {} }, - "unbannedUser": "", + "unbannedUser": "{username} 解除了 {targetName} 的封锁", "@unbannedUser": { "type": "text", "placeholders": { @@ -1386,58 +1386,58 @@ "targetName": {} } }, - "unblockDevice": "", + "unblockDevice": "解锁设备", "@unblockDevice": { "type": "text", "placeholders": {} }, - "unknownDevice": "", + "unknownDevice": "未知设备", "@unknownDevice": { "type": "text", "placeholders": {} }, - "unknownEncryptionAlgorithm": "", + "unknownEncryptionAlgorithm": "未知加密算法", "@unknownEncryptionAlgorithm": { "type": "text", "placeholders": {} }, - "unknownEvent": "", + "unknownEvent": "未知事件 '{type}'", "@unknownEvent": { "type": "text", "placeholders": { "type": {} } }, - "unknownSessionVerify": "", + "unknownSessionVerify": "未知会话,请验证", "@unknownSessionVerify": { "type": "text", "placeholders": {} }, - "unmuteChat": "", + "unmuteChat": "解除聊天的静音", "@unmuteChat": { "type": "text", "placeholders": {} }, - "unpin": "", + "unpin": "取消固定", "@unpin": { "type": "text", "placeholders": {} }, - "unreadChats": "", + "unreadChats": "{unreadCount} 未读聊天", "@unreadChats": { "type": "text", "placeholders": { "unreadCount": {} } }, - "unreadMessages": "", + "unreadMessages": "{unreadEvents} 未读消息", "@unreadMessages": { "type": "text", "placeholders": { "unreadEvents": {} } }, - "unreadMessagesInChats": "", + "unreadMessagesInChats": "来自 {unreadChats} 聊天的 {unreadEvents} 未读消息", "@unreadMessagesInChats": { "type": "text", "placeholders": { @@ -1445,12 +1445,12 @@ "unreadChats": {} } }, - "useAmoledTheme": "", + "useAmoledTheme": "使用适合Amoled屏的颜色?", "@useAmoledTheme": { "type": "text", "placeholders": {} }, - "userAndOthersAreTyping": "", + "userAndOthersAreTyping": "{username} 和 {count} 其他人正在打字...", "@userAndOthersAreTyping": { "type": "text", "placeholders": { @@ -1458,7 +1458,7 @@ "count": {} } }, - "userAndUserAreTyping": "", + "userAndUserAreTyping": "{username} 和 {username2} 正在打字...", "@userAndUserAreTyping": { "type": "text", "placeholders": { @@ -1466,26 +1466,26 @@ "username2": {} } }, - "userIsTyping": "", + "userIsTyping": "{username} 正在打字...", "@userIsTyping": { "type": "text", "placeholders": { "username": {} } }, - "userLeftTheChat": "", + "userLeftTheChat": "{username} 离开了聊天", "@userLeftTheChat": { "type": "text", "placeholders": { "username": {} } }, - "username": "", + "username": "用户名", "@username": { "type": "text", "placeholders": {} }, - "userSentUnknownEvent": "", + "userSentUnknownEvent": "{username} 发送了一个 {type} 事件", "@userSentUnknownEvent": { "type": "text", "placeholders": { @@ -1493,144 +1493,234 @@ "type": {} } }, - "verifiedSession": "", + "verifiedSession": "成功验证会话!", "@verifiedSession": { "type": "text", "placeholders": {} }, - "verify": "", + "verify": "验证", "@verify": { "type": "text", "placeholders": {} }, - "verifyManual": "", + "verifyManual": "手动验证", "@verifyManual": { "type": "text", "placeholders": {} }, - "verifyStart": "", + "verifyStart": "开始验证", "@verifyStart": { "type": "text", "placeholders": {} }, - "verifySuccess": "", + "verifySuccess": "您已成功验证!", "@verifySuccess": { "type": "text", "placeholders": {} }, - "verifyTitle": "", + "verifyTitle": "验证其他账号", "@verifyTitle": { "type": "text", "placeholders": {} }, - "verifyUser": "", + "verifyUser": "验证用户", "@verifyUser": { "type": "text", "placeholders": {} }, - "videoCall": "", + "videoCall": "视频通话", "@videoCall": { "type": "text", "placeholders": {} }, - "visibilityOfTheChatHistory": "", + "visibilityOfTheChatHistory": "聊天记录的可见性", "@visibilityOfTheChatHistory": { "type": "text", "placeholders": {} }, - "visibleForAllParticipants": "", + "visibleForAllParticipants": "对所有参与者可见", "@visibleForAllParticipants": { "type": "text", "placeholders": {} }, - "visibleForEveryone": "", + "visibleForEveryone": "对所有人可见", "@visibleForEveryone": { "type": "text", "placeholders": {} }, - "voiceMessage": "", + "voiceMessage": "语音消息", "@voiceMessage": { "type": "text", "placeholders": {} }, - "waitingPartnerAcceptRequest": "", + "waitingPartnerAcceptRequest": "等待对方接受请求...", "@waitingPartnerAcceptRequest": { "type": "text", "placeholders": {} }, - "waitingPartnerEmoji": "", + "waitingPartnerEmoji": "等待对方接受emoji...", "@waitingPartnerEmoji": { "type": "text", "placeholders": {} }, - "waitingPartnerNumbers": "", + "waitingPartnerNumbers": "等待对方接受数字...", "@waitingPartnerNumbers": { "type": "text", "placeholders": {} }, - "wallpaper": "", + "wallpaper": "壁纸", "@wallpaper": { "type": "text", "placeholders": {} }, - "warningEncryptionInBeta": "", + "warningEncryptionInBeta": "端到端加密目前在测试阶段!请自行承担风险!", "@warningEncryptionInBeta": { "type": "text", "placeholders": {} }, - "wednesday": "", + "wednesday": "星期三", "@wednesday": { "type": "text", "placeholders": {} }, - "welcomeText": "", + "welcomeText": "欢迎来到matrix网络中最可爱的即时通讯应用。", "@welcomeText": { "type": "text", "placeholders": {} }, - "whoIsAllowedToJoinThisGroup": "", + "whoIsAllowedToJoinThisGroup": "谁被允许加入本群组", "@whoIsAllowedToJoinThisGroup": { "type": "text", "placeholders": {} }, - "writeAMessage": "", + "writeAMessage": "写一条消息...", "@writeAMessage": { "type": "text", "placeholders": {} }, - "yes": "", + "yes": "是", "@yes": { "type": "text", "placeholders": {} }, - "you": "", + "you": "你", "@you": { "type": "text", "placeholders": {} }, - "youAreInvitedToThisChat": "", + "youAreInvitedToThisChat": "你被邀请到该聊天", "@youAreInvitedToThisChat": { "type": "text", "placeholders": {} }, - "youAreNoLongerParticipatingInThisChat": "", + "youAreNoLongerParticipatingInThisChat": "你已不再参与此聊天", "@youAreNoLongerParticipatingInThisChat": { "type": "text", "placeholders": {} }, - "youCannotInviteYourself": "", + "youCannotInviteYourself": "你不能邀请自己", "@youCannotInviteYourself": { "type": "text", "placeholders": {} }, - "youHaveBeenBannedFromThisChat": "", + "youHaveBeenBannedFromThisChat": "你已被该聊天封锁", "@youHaveBeenBannedFromThisChat": { "type": "text", "placeholders": {} }, - "yourOwnUsername": "", + "yourOwnUsername": "你自己的用户名", "@yourOwnUsername": { "type": "text", "placeholders": {} + }, + "warning": "警告!", + "@warning": { + "type": "text", + "placeholders": {} + }, + "sendVideo": "发送视频", + "@sendVideo": { + "type": "text", + "placeholders": {} + }, + "sendOriginal": "发送原创内容", + "@sendOriginal": { + "type": "text", + "placeholders": {} + }, + "sendAudio": "发送音频", + "@sendAudio": { + "type": "text", + "placeholders": {} + }, + "no": "不", + "@no": { + "type": "text", + "placeholders": {} + }, + "changesHaveBeenSaved": "更改已被保存", + "@changesHaveBeenSaved": { + "type": "text", + "placeholders": {} + }, + "sentryInfo": "关于您隐私的信息: https://sentry.io/security/", + "@sentryInfo": { + "type": "text", + "placeholders": {} + }, + "sendBugReports": "允许向sentry.io发送错误报告", + "@sendBugReports": { + "type": "text", + "placeholders": {} + }, + "privacy": "隐私", + "@privacy": { + "type": "text", + "placeholders": {} + }, + "passwordHasBeenChanged": "密码已被更改", + "@passwordHasBeenChanged": { + "type": "text", + "placeholders": {} + }, + "ignoreListDescription": "你可以忽略打扰你的用户。你将不会收到来自忽略列表中用户的任何消息或聊天室邀请。", + "@ignoreListDescription": { + "type": "text", + "placeholders": {} + }, + "ignoreUsername": "忽略用户名", + "@ignoreUsername": { + "type": "text", + "placeholders": {} + }, + "ignoredUsers": "已忽略的用户", + "@ignoredUsers": { + "type": "text", + "placeholders": {} + }, + "enableEmotesGlobally": "在全局启用表情包", + "@enableEmotesGlobally": { + "type": "text", + "placeholders": {} + }, + "emotePacks": "聊天室的表情包", + "@emotePacks": { + "type": "text", + "placeholders": {} + }, + "deleteAccount": "删除账号", + "@deleteAccount": { + "type": "text", + "placeholders": {} + }, + "deactivateAccountWarning": "这将停用您的用户账号。这不能被撤销,您确定吗?", + "@deactivateAccountWarning": { + "type": "text", + "placeholders": {} + }, + "changeDeviceName": "更改设备名称", + "@changeDeviceName": { + "type": "text", + "placeholders": {} } } diff --git a/lib/main.dart b/lib/main.dart index a9975bf..5ee416e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,7 +8,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:localstorage/localstorage.dart'; import 'package:universal_html/prefer_universal/html.dart' as html; import 'components/matrix.dart'; @@ -21,8 +20,6 @@ void main() { runZonedGuarded( () => runApp(App()), (error, stackTrace) async { - final storage = LocalStorage('LocalStorage'); - await storage.ready; debugPrint(error.toString()); debugPrint(stackTrace.toString()); }, diff --git a/lib/utils/famedlysdk_store.dart b/lib/utils/famedlysdk_store.dart index 42aeb84..f7407ce 100644 --- a/lib/utils/famedlysdk_store.dart +++ b/lib/utils/famedlysdk_store.dart @@ -6,12 +6,22 @@ import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:localstorage/localstorage.dart'; +import 'package:path_provider/path_provider.dart'; import 'dart:async'; import 'dart:core'; import './database/shared.dart'; import 'package:olm/olm.dart' as olm; // needed for migration import 'package:random_string/random_string.dart'; +Future getLocalStorage() async { + final directory = PlatformInfos.isBetaDesktop + ? await getApplicationSupportDirectory() + : await getApplicationDocumentsDirectory(); + final localStorage = LocalStorage('LocalStorage', directory.path); + await localStorage.ready; + return localStorage; +} + Future getDatabase(Client client) async { while (_generateDatabaseLock) { await Future.delayed(Duration(milliseconds: 50)); diff --git a/lib/utils/resize_image.dart b/lib/utils/resize_image.dart index 93b328f..102c78b 100644 --- a/lib/utils/resize_image.dart +++ b/lib/utils/resize_image.dart @@ -18,8 +18,8 @@ Future resizeImage(MatrixImageFile file, try { final nativeImg = native.Image(); await nativeImg.loadEncoded(file.bytes); - file.width = nativeImg.width(); - file.height = nativeImg.height(); + file.width = nativeImg.width; + file.height = nativeImg.height; args = _IsolateArgs( width: file.width, height: file.height, bytes: file.bytes, max: max); nativeImg.free(); @@ -96,8 +96,8 @@ Future<_IsolateResponse> _isolateFunction(_IsolateArgs args) async { final ret = _IsolateResponse( blurhash: blurhash, jpegBytes: jpegBytes, - width: nativeImg.width(), - height: nativeImg.height()); + width: nativeImg.width, + height: nativeImg.height); nativeImg.free(); diff --git a/lib/views/chat.dart b/lib/views/chat.dart index 5bb4cbf..f989262 100644 --- a/lib/views/chat.dart +++ b/lib/views/chat.dart @@ -28,6 +28,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:image_picker/image_picker.dart'; import 'package:pedantic/pedantic.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; +import 'package:swipe_to_action/swipe_to_action.dart'; import '../components/dialogs/send_file_dialog.dart'; import '../components/input_bar.dart'; @@ -316,8 +317,10 @@ class _ChatState extends State<_Chat> { return true; } - void forwardEventsAction(BuildContext context) async { - if (selectedEvents.length == 1) { + void forwardEventsAction(BuildContext context, {Event event}) async { + if (event != null) { + Matrix.of(context).shareContent = event.content; + } else if (selectedEvents.length == 1) { Matrix.of(context).shareContent = selectedEvents.first.content; } else { Matrix.of(context).shareContent = { @@ -343,9 +346,9 @@ class _ChatState extends State<_Chat> { setState(() => selectedEvents.clear()); } - void replyAction() { + void replyAction({Event replyTo}) { setState(() { - replyEvent = selectedEvents.first; + replyEvent = replyTo ?? selectedEvents.first; selectedEvents.clear(); }); inputFocus.requestFocus(); @@ -411,6 +414,128 @@ class _ChatState extends State<_Chat> { e.type != 'm.reaction') .toList(); + SwipeDirection _getSwipeDirection(Event event) { + var swipeToEndAction = Matrix.of(context).swipeToEndAction; + var swipeToStartAction = Matrix.of(context).swipeToStartAction; + var client = Matrix.of(context).client; + if (event.senderId != client.userID && swipeToEndAction == 'edit') { + swipeToEndAction = null; + } + if (event.senderId != client.userID && swipeToStartAction == 'edit') { + swipeToStartAction = null; + } + if (swipeToEndAction != null && swipeToStartAction != null) { + return SwipeDirection.horizontal; + } + if (swipeToEndAction != null) { + return SwipeDirection.startToEnd; + } + if (swipeToStartAction != null) { + return SwipeDirection.endToStart; + } + return null; + } + + Widget _getSwipeBackground(Event event, {bool isSecondary = false}) { + var alignToRight, action; + if (_getSwipeDirection(event) == SwipeDirection.horizontal) { + if (isSecondary) { + alignToRight = true; + action = Matrix.of(context).swipeToStartAction; + } else { + alignToRight = false; + action = Matrix.of(context).swipeToEndAction; + } + } else if (isSecondary) { + return null; + } else if (_getSwipeDirection(event) == SwipeDirection.endToStart) { + alignToRight = true; + action = Matrix.of(context).swipeToStartAction; + } else { + alignToRight = false; + action = Matrix.of(context).swipeToStartAction; + } + + switch (action) { + case 'reply': + return Container( + color: Theme.of(context).primaryColor.withAlpha(100), + padding: EdgeInsets.symmetric(horizontal: 12.0), + child: Row( + mainAxisAlignment: + alignToRight ? MainAxisAlignment.end : MainAxisAlignment.start, + children: [ + Icon(Icons.reply_outlined), + SizedBox(width: 2.0), + Text(L10n.of(context).reply) + ], + ), + ); + case 'forward': + return Container( + color: Theme.of(context).primaryColor.withAlpha(100), + padding: EdgeInsets.symmetric(horizontal: 12.0), + child: Row( + mainAxisAlignment: + alignToRight ? MainAxisAlignment.end : MainAxisAlignment.start, + children: [ + Icon(Icons.forward_outlined), + SizedBox(width: 2.0), + Text(L10n.of(context).forward) + ], + ), + ); + case 'edit': + return Container( + color: Theme.of(context).primaryColor.withAlpha(100), + padding: EdgeInsets.symmetric(horizontal: 12.0), + child: Row( + mainAxisAlignment: + alignToRight ? MainAxisAlignment.end : MainAxisAlignment.start, + children: [ + Icon(Icons.edit_outlined), + SizedBox(width: 2.0), + Text(L10n.of(context).edit) + ], + ), + ); + default: + return Container( + color: Theme.of(context).primaryColor.withAlpha(100), + ); + } + } + + void _handleSwipe(SwipeDirection direction, Event event) { + var action; + if (direction == SwipeDirection.endToStart) { + action = Matrix.of(context).swipeToStartAction; + } else { + action = Matrix.of(context).swipeToEndAction; + } + + switch (action) { + case 'reply': + replyAction(replyTo: event); + break; + case 'forward': + forwardEventsAction(context, event: event); + break; + case 'edit': + setState(() { + editEvent = event; + sendController.text = editEvent + .getDisplayEvent(timeline) + .getLocalizedBody(MatrixLocals(L10n.of(context)), + withSenderNamePrefix: false, hideReply: true); + selectedEvents.clear(); + }); + inputFocus.requestFocus(); + break; + default: + } + } + @override Widget build(BuildContext context) { matrix = Matrix.of(context); @@ -668,43 +793,57 @@ class _ChatState extends State<_Chat> { key: ValueKey(i - 1), index: i - 1, controller: _scrollController, - child: Message(filteredEvents[i - 1], - onAvatarTab: (Event event) { - sendController.text += - ' ${event.senderId}'; - }, - onSelect: (Event event) { - if (!event.redacted) { - if (selectedEvents - .contains(event)) { - setState( - () => selectedEvents - .remove(event), - ); - } else { - setState( - () => - selectedEvents.add(event), + child: Swipeable( + key: ValueKey( + filteredEvents[i - 1].eventId), + background: _getSwipeBackground( + filteredEvents[i - 1]), + secondaryBackground: + _getSwipeBackground( + filteredEvents[i - 1], + isSecondary: true), + direction: _getSwipeDirection( + filteredEvents[i - 1]), + onSwipe: (direction) => _handleSwipe( + direction, filteredEvents[i - 1]), + child: Message(filteredEvents[i - 1], + onAvatarTab: (Event event) { + sendController.text += + ' ${event.senderId}'; + }, + onSelect: (Event event) { + if (!event.redacted) { + if (selectedEvents + .contains(event)) { + setState( + () => selectedEvents + .remove(event), + ); + } else { + setState( + () => selectedEvents + .add(event), + ); + } + selectedEvents.sort( + (a, b) => a.originServerTs + .compareTo( + b.originServerTs), ); } - selectedEvents.sort( - (a, b) => a.originServerTs - .compareTo( - b.originServerTs), - ); - } - }, - scrollToEventId: (String eventId) => - _scrollToEventId(eventId, - context: context), - longPressSelect: - selectedEvents.isEmpty, - selected: selectedEvents - .contains(filteredEvents[i - 1]), - timeline: timeline, - nextEvent: i >= 2 - ? filteredEvents[i - 2] - : null), + }, + scrollToEventId: (String eventId) => + _scrollToEventId(eventId, + context: context), + longPressSelect: + selectedEvents.isEmpty, + selected: selectedEvents.contains( + filteredEvents[i - 1]), + timeline: timeline, + nextEvent: i >= 2 + ? filteredEvents[i - 2] + : null), + ), ); }); }, diff --git a/lib/views/login.dart b/lib/views/login.dart index bdd2ec9..4edab6b 100644 --- a/lib/views/login.dart +++ b/lib/views/login.dart @@ -171,7 +171,6 @@ class _LoginState extends State { readOnly: loading, autocorrect: false, autofocus: true, - keyboardType: TextInputType.emailAddress, onChanged: (t) => _checkWellKnownWithCoolDown(t, context), controller: usernameController, decoration: InputDecoration( diff --git a/lib/views/settings/settings_chat.dart b/lib/views/settings/settings_chat.dart index d68d346..43c6268 100644 --- a/lib/views/settings/settings_chat.dart +++ b/lib/views/settings/settings_chat.dart @@ -22,6 +22,74 @@ class ChatSettings extends StatefulWidget { } class _ChatSettingsState extends State { + String _getActionDescription(String action) { + switch (action) { + case 'reply': + return L10n.of(context).reply; + case 'forward': + return L10n.of(context).forward; + case 'edit': + return L10n.of(context).edit; + default: + return L10n.of(context).none; + } + } + + void _changeSwipeAction(bool isToEnd, String action) async { + if (isToEnd) { + Matrix.of(context).swipeToEndAction = action; + await Matrix.of(context) + .store + .setItem('chat.fluffy.swipeToEndAction', action); + setState(() => null); + } else { + Matrix.of(context).swipeToStartAction = action; + await Matrix.of(context) + .store + .setItem('chat.fluffy.swipeToStartAction', action); + setState(() => null); + } + } + + Widget _swipeActionChooser(BuildContext context, bool isToEnd) { + return ListView( + children: [ + ListTile( + title: Text(L10n.of(context).none), + leading: Icon(Icons.clear_outlined), + onTap: () { + _changeSwipeAction(isToEnd, null); + Navigator.of(context).pop(); + }, + ), + ListTile( + title: Text(L10n.of(context).reply), + leading: Icon(Icons.reply_outlined), + onTap: () { + _changeSwipeAction(isToEnd, 'reply'); + Navigator.of(context).pop(); + }, + ), + ListTile( + title: Text(L10n.of(context).forward), + leading: Icon(Icons.forward_outlined), + onTap: () { + _changeSwipeAction(isToEnd, 'forward'); + Navigator.of(context).pop(); + }, + ), + ListTile( + title: Text(L10n.of(context).edit), + leading: Icon(Icons.edit_outlined), + onTap: () { + _changeSwipeAction(isToEnd, 'edit'); + Navigator.of(context).pop(); + }, + ), + ], + ); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -42,6 +110,27 @@ class _ChatSettingsState extends State { }, ), ), + Divider(thickness: 1), + ListTile( + title: Text(L10n.of(context).swipeToEndAction), + onTap: () => showModalBottomSheet( + context: context, + builder: (BuildContext context) => + _swipeActionChooser(context, true), + ), + subtitle: Text( + _getActionDescription(Matrix.of(context).swipeToEndAction)), + ), + ListTile( + title: Text(L10n.of(context).swipeToStartAction), + onTap: () => showModalBottomSheet( + context: context, + builder: (BuildContext context) => + _swipeActionChooser(context, false), + ), + subtitle: Text( + _getActionDescription(Matrix.of(context).swipeToStartAction)), + ), ], ), ); diff --git a/pubspec.lock b/pubspec.lock index bff99ee..c5876b0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -553,7 +553,7 @@ packages: description: path: "." ref: master - resolved-ref: bd24832f96537447174aa34ba78eaed7ff05bb8e + resolved-ref: c8eb59c25c4e3a568bd64e4722108ec45259e157 url: "https://gitlab.com/famedly/libraries/native_imaging.git" source: git version: "0.0.1" @@ -870,6 +870,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0-nullsafety.1" + swipe_to_action: + dependency: "direct main" + description: + name: swipe_to_action + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0" synchronized: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f7f6b5e..a22aeff 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -69,6 +69,7 @@ dependencies: ref: master flutter_blurhash: ^0.5.0 scroll_to_index: ^1.0.6 + swipe_to_action: ^0.1.0 dev_dependencies: flutter_test: diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml new file mode 100644 index 0000000..9e20cee --- /dev/null +++ b/snap/snapcraft.yaml @@ -0,0 +1,59 @@ +name: fluffychat +base: core18 # the base snap is the execution environment for this snap +version: git # just for humans, typically '1.2+git' or '1.3.2' +summary: Open. Nonprofit. Cute ♥ +description: | + FluffyChat - Chat with your friends + + 9 greatest FluffyChat features: + 1. Opensource and open development where everyone can join. + 2. Nonprofit - FluffyChat is donation funded. + 3. Cute design and many theme settings including a dark mode. + 4. Unlimited groups and direct chats. + 5. FluffyChat is made as simple to use as possible. + 6. Free to use for everyone without ads. + 7. FluffyChat can use your addressbook to find your friends or you can use + usernames. + 8. There is no "FluffyChat server" you are forced to use. Use the server + you find trustworthy or host your own. + 9. Compatible with Riot, Fractal, Nekho and all matrix messengers. + + Join the community: fluffychat://+ubports_community:matrix.org + Website: http://fluffy.chat + Microblog: https://metalhead.club/@krille + +grade: devel # must be 'stable' to release into candidate/stable channels +confinement: strict # use 'strict' once you have the right plugs and slots + +parts: + olm: + plugin: cmake + source: https://gitlab.matrix.org/matrix-org/olm.git + source-type: git + source-tag: 3.2.1 + fluffychat: + plugin: flutter + source: . + flutter-target: lib/main.dart + stage-packages: + - libsqlite3-0 + override-prime: | + snapcraftctl prime + ln -sf libsqlite3.so.0 ${SNAPCRAFT_PRIME}/usr/lib/x86_64-linux-gnu/libsqlite3.so + +slots: + dbus-svc: + interface: dbus + bus: session + name: chat.fluffy.fluffychat + +apps: + fluffychat: + command: fluffychat + extensions: + - flutter-dev + plugs: + - network + - home + slots: + - dbus-svc