From f2e609ad13a1c49f04e3f7e4d35a508187c8043a Mon Sep 17 00:00:00 2001
From: Tim Segers
Date: Thu, 8 Oct 2020 11:44:35 +0000
Subject: [PATCH 01/20] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index d1d9449..a19f67b 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
- Open FluffyChat in the browser - Join the community - Follow me on Mastodon - Translate FluffyChat - Translate the website - FAQ - Website - Download latest APK
+ Open FluffyChat in the browser - Join the community - Follow me on Mastodon - Translate FluffyChat - Translate the website - FAQ - Website - Download latest APK - Famedly Matrix SDK
From 060156ce12fa0a36c9bbd4c596c7813f5aa39259 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kate=C5=99ina=20Churanov=C3=A1?=
Date: Sun, 11 Oct 2020 13:25:06 +0200
Subject: [PATCH 02/20] fix: fixed mxid input method, removed code redundancy
---
lib/views/homeserver_picker.dart | 6 ------
lib/views/login.dart | 1 -
2 files changed, 7 deletions(-)
diff --git a/lib/views/homeserver_picker.dart b/lib/views/homeserver_picker.dart
index b1bc842..a7d7b43 100644
--- a/lib/views/homeserver_picker.dart
+++ b/lib/views/homeserver_picker.dart
@@ -30,12 +30,6 @@ class HomeserverPicker extends StatelessWidget {
homeserver = 'https://$homeserver';
}
- // removes trailing spaces and slash from url if present (api errors on it)
- homeserver = homeserver.trim();
- if (homeserver.endsWith('/')) {
- homeserver = homeserver.substring(0, homeserver.length - 1);
- }
-
final success = await SimpleDialogs(context).tryRequestWithLoadingDialog(
Matrix.of(context).client.checkServer(homeserver));
if (success != false) {
diff --git a/lib/views/login.dart b/lib/views/login.dart
index 40903c2..d7d327a 100644
--- a/lib/views/login.dart
+++ b/lib/views/login.dart
@@ -131,7 +131,6 @@ class _LoginState extends State {
readOnly: loading,
autocorrect: false,
autofocus: true,
- keyboardType: TextInputType.emailAddress,
onChanged: (t) => _checkWellKnownWithCoolDown(t, context),
controller: usernameController,
decoration: InputDecoration(
From 39d8b7b09f4f18a170d11b5c5992b90e409ab561 Mon Sep 17 00:00:00 2001
From: abidin toumi
Date: Sat, 10 Oct 2020 20:04:13 +0000
Subject: [PATCH 03/20] Translated using Weblate (Arabic)
Currently translated at 90.7% (284 of 313 strings)
Translation: FluffyChat/Translations-New
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations-new/ar/
---
lib/l10n/intl_ar.arb | 21 +++++++++++++--------
1 file changed, 13 insertions(+), 8 deletions(-)
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": {}
}
}
From 6b70cd1fe93f8c1b2b736201b28fabc0469fc079 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=88=B1=E9=85=B1?=
Date: Sat, 10 Oct 2020 14:14:20 +0000
Subject: [PATCH 04/20] Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (313 of 313 strings)
Translation: FluffyChat/Translations-New
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations-new/zh_Hans/
---
lib/l10n/intl_zh.arb | 574 +++++++++++++++++++++++++------------------
1 file changed, 332 insertions(+), 242 deletions(-)
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": {}
}
}
From 6a6845406a03c05b9e7c52776da521d889403d23 Mon Sep 17 00:00:00 2001
From: lucanomax
Date: Sat, 10 Oct 2020 21:08:20 +0000
Subject: [PATCH 05/20] Translated using Weblate (Italian)
Currently translated at 25.2% (79 of 313 strings)
Translation: FluffyChat/Translations-New
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations-new/it/
---
lib/l10n/intl_it.arb | 267 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 267 insertions(+)
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": {}
}
}
From 4f272efbb5426397fd7edb5b8f0da32cefc6247b Mon Sep 17 00:00:00 2001
From: Marcel
Date: Mon, 12 Oct 2020 06:44:41 +0000
Subject: [PATCH 06/20] fix(l10n): Make en the default fallback language.
---
l10n.yaml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
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
From f18779221f284d3d548290cc2c35694acce1cecd Mon Sep 17 00:00:00 2001
From: Marcel
Date: Mon, 12 Oct 2020 08:09:47 +0000
Subject: [PATCH 07/20] ci: Optimize dependencies
---
.gitlab-ci.yml | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
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: []
From 1b1dfc7c81dbe72cb8b21ab32fdc4a6864f13ba9 Mon Sep 17 00:00:00 2001
From: Lukas Lihotzki
Date: Mon, 12 Oct 2020 11:56:20 +0200
Subject: [PATCH 08/20] Update native_imaging
---
lib/utils/resize_image.dart | 8 ++++----
pubspec.lock | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
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/pubspec.lock b/pubspec.lock
index e9511b5..fea4cbd 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -553,7 +553,7 @@ packages:
description:
path: "."
ref: master
- resolved-ref: bd24832f96537447174aa34ba78eaed7ff05bb8e
+ resolved-ref: "7fef2565e4ab0c3f6a0e0ac19a77c30ea6778e16"
url: "https://gitlab.com/famedly/libraries/native_imaging.git"
source: git
version: "0.0.1"
From 1236062768663081a81a6725ba47e7151f447ed6 Mon Sep 17 00:00:00 2001
From: Christopher James Halse Rogers
Date: Tue, 13 Oct 2020 10:24:47 +1100
Subject: [PATCH 09/20] Initial snapcraft metadata
---
snap/snapcraft.yaml | 59 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 59 insertions(+)
create mode 100644 snap/snapcraft.yaml
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
From 81e32c5ee421474a504dab6bc7f29be0fc3921b6 Mon Sep 17 00:00:00 2001
From: Christian Pauly
Date: Tue, 13 Oct 2020 12:20:13 +0200
Subject: [PATCH 10/20] fix: LocalStorage location on desktop
---
.gitignore | 1 +
lib/components/matrix.dart | 4 +---
lib/main.dart | 5 ++---
lib/utils/famedlysdk_store.dart | 10 ++++++++++
lib/utils/sentry_controller.dart | 8 ++++----
pubspec.lock | 2 +-
6 files changed, 19 insertions(+), 11 deletions(-)
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/lib/components/matrix.dart b/lib/components/matrix.dart
index 8c0cf95..17b1fe4 100644
--- a/lib/components/matrix.dart
+++ b/lib/components/matrix.dart
@@ -11,7 +11,6 @@ import 'package:fluffychat/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';
@@ -78,8 +77,7 @@ class MatrixState extends State {
void clean() async {
if (!kIsWeb) return;
- final storage = LocalStorage('LocalStorage');
- await storage.ready;
+ final storage = await getLocalStorage();
await storage.deleteItem(widget.clientName);
}
diff --git a/lib/main.dart b/lib/main.dart
index 334eb96..a08f001 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -8,12 +8,12 @@ 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:sentry/sentry.dart';
import 'package:universal_html/prefer_universal/html.dart' as html;
import 'components/matrix.dart';
import 'components/theme_switcher.dart';
+import 'utils/famedlysdk_store.dart';
import 'views/chat_list.dart';
final sentry = SentryClient(dsn: '8591d0d863b646feb4f3dda7e5dcab38');
@@ -21,8 +21,7 @@ final sentry = SentryClient(dsn: '8591d0d863b646feb4f3dda7e5dcab38');
void captureException(error, stackTrace) async {
debugPrint(error.toString());
debugPrint(stackTrace.toString());
- final storage = LocalStorage('LocalStorage');
- await storage.ready;
+ final storage = await getLocalStorage();
if (storage.getItem('sentry') == true) {
await sentry.captureException(
exception: error,
diff --git a/lib/utils/famedlysdk_store.dart b/lib/utils/famedlysdk_store.dart
index 52b18fc..d305ade 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/sentry_controller.dart b/lib/utils/sentry_controller.dart
index 1557262..970a419 100644
--- a/lib/utils/sentry_controller.dart
+++ b/lib/utils/sentry_controller.dart
@@ -2,10 +2,10 @@ import 'package:bot_toast/bot_toast.dart';
import 'package:fluffychat/components/dialogs/simple_dialogs.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
-import 'package:localstorage/localstorage.dart';
+
+import 'famedlysdk_store.dart';
abstract class SentryController {
- static LocalStorage storage = LocalStorage('LocalStorage');
static Future toggleSentryAction(BuildContext context) async {
final enableSentry = await SimpleDialogs(context).askConfirmation(
titleText: L10n.of(context).sendBugReports,
@@ -13,14 +13,14 @@ abstract class SentryController {
confirmText: L10n.of(context).ok,
cancelText: L10n.of(context).no,
);
- await storage.ready;
+ final storage = await getLocalStorage();
await storage.setItem('sentry', enableSentry);
BotToast.showText(text: L10n.of(context).changesHaveBeenSaved);
return;
}
static Future getSentryStatus() async {
- await storage.ready;
+ final storage = await getLocalStorage();
return storage.getItem('sentry') as bool;
}
}
diff --git a/pubspec.lock b/pubspec.lock
index fea4cbd..eda8463 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -1074,5 +1074,5 @@ packages:
source: hosted
version: "0.1.2"
sdks:
- dart: ">=2.10.0-110 <=2.11.0-161.0.dev"
+ dart: ">=2.10.0-110 <2.11.0"
flutter: ">=1.20.0 <2.0.0"
From 49cf5846262992434a730115bdbccde35622770c Mon Sep 17 00:00:00 2001
From: Lukas Lihotzki
Date: Tue, 13 Oct 2020 13:10:36 +0200
Subject: [PATCH 11/20] Update native_imaging
---
pubspec.lock | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pubspec.lock b/pubspec.lock
index eda8463..66ae763 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -553,7 +553,7 @@ packages:
description:
path: "."
ref: master
- resolved-ref: "7fef2565e4ab0c3f6a0e0ac19a77c30ea6778e16"
+ resolved-ref: c8eb59c25c4e3a568bd64e4722108ec45259e157
url: "https://gitlab.com/famedly/libraries/native_imaging.git"
source: git
version: "0.0.1"
From b6c35061262045d362ece05f7f8b7c9e61364247 Mon Sep 17 00:00:00 2001
From: Inex Code
Date: Sun, 4 Oct 2020 23:22:41 +0000
Subject: [PATCH 12/20] feat: Swipe to Reply
---
lib/components/swipeable.dart | 459 ++++++++++++++++++++++++++++++++++
lib/views/chat.dart | 58 +++--
2 files changed, 502 insertions(+), 15 deletions(-)
create mode 100644 lib/components/swipeable.dart
diff --git a/lib/components/swipeable.dart b/lib/components/swipeable.dart
new file mode 100644
index 0000000..71543dc
--- /dev/null
+++ b/lib/components/swipeable.dart
@@ -0,0 +1,459 @@
+import 'package:flutter/gestures.dart';
+import 'package:flutter/material.dart';
+
+const double _kMinFlingVelocity = 700.0;
+const double _kMinFlingVelocityDelta = 400.0;
+const double _kFlingVelocityScale = 1.0 / 300.0;
+const double _kDismissThreshold = 0.4;
+
+/// Signature used by [Swipeable] to indicate that it has been swiped in
+/// the given `direction`.
+///
+/// Used by [Swipeable.onSwiped].
+typedef SwipeDirectionCallback = void Function(SwipeDirection direction);
+
+/// Signature used by [Swipeable] to give the application an opportunity to
+/// confirm or veto a swipe gesture.
+///
+/// Used by [Swipeable.confirmSwipe].
+typedef ConfirmSwipeCallback = Future Function(SwipeDirection direction);
+
+/// The direction in which a [Swipeable] can be swiped.
+enum SwipeDirection {
+ /// The [Swipeable] can be swiped by dragging either left or right.
+ horizontal,
+
+ /// The [Swipeable] can be swiped by dragging in the reverse of the
+ /// reading direction (e.g., from right to left in left-to-right languages).
+ endToStart,
+
+ /// The [Swipeable] can be swiped by dragging in the reading direction
+ /// (e.g., from left to right in left-to-right languages).
+ startToEnd,
+}
+
+class Swipeable extends StatefulWidget {
+ /// Creates a widget that calls a function when swiped.
+ ///
+ /// The [key] argument must not be null because [Swipeable]s are commonly
+ /// used in lists and removed from the list when dismissed. Without keys, the
+ /// default behavior is to sync widgets based on their index in the list,
+ /// which means the item after the dismissed item would be synced with the
+ /// state of the dismissed item. Using keys causes the widgets to sync
+ /// according to their keys and avoids this pitfall.
+ const Swipeable({
+ @required Key key,
+ @required this.child,
+ this.background,
+ this.secondaryBackground,
+ this.confirmSwipe,
+ this.onSwiped,
+ this.direction = SwipeDirection.horizontal,
+ this.dismissThresholds = const {},
+ this.maxOffset = 0.4,
+ this.movementDuration = const Duration(milliseconds: 200),
+ this.crossAxisEndOffset = 0.0,
+ this.dragStartBehavior = DragStartBehavior.start,
+ }) : assert(key != null),
+ assert(secondaryBackground == null || background != null),
+ assert(dragStartBehavior != null),
+ super(key: key);
+
+ /// The widget below this widget in the tree.
+ ///
+ /// {@macro flutter.widgets.child}
+ final Widget child;
+
+ /// A widget that is stacked behind the child. If secondaryBackground is also
+ /// specified then this widget only appears when the child has been dragged
+ /// to the right.
+ final Widget background;
+
+ /// A widget that is stacked behind the child and is exposed when the child
+ /// has been dragged to the left. It may only be specified when background
+ /// has also been specified.
+ final Widget secondaryBackground;
+
+ /// Gives the app an opportunity to confirm or veto a pending dismissal.
+ ///
+ /// If the returned Future completes true, then this widget will be
+ /// dismissed, otherwise it will be moved back to its original location.
+ ///
+ /// If the returned Future completes to false or null the [onSwiped]
+ /// callback will not run.
+ final ConfirmSwipeCallback confirmSwipe;
+
+ /// Called when the widget has been dismissed, after finishing resizing.
+ final SwipeDirectionCallback onSwiped;
+
+ /// The direction in which the widget can be dismissed.
+ final SwipeDirection direction;
+
+ /// The offset threshold the item has to be dragged in order to be considered
+ /// dismissed.
+ ///
+ /// Represented as a fraction, e.g. if it is 0.4 (the default), then the item
+ /// has to be dragged at least 40% towards one direction to be considered
+ /// dismissed. Clients can define different thresholds for each dismiss
+ /// direction.
+ ///
+ /// Flinging is treated as being equivalent to dragging almost to 1.0, so
+ /// flinging can dismiss an item past any threshold less than 1.0.
+ ///
+ /// Setting a threshold of 1.0 (or greater) prevents a drag in the given
+ /// [SwipeDirection] even if it would be allowed by the [direction]
+ /// property.
+ ///
+ /// See also:
+ ///
+ /// * [direction], which controls the directions in which the items can
+ /// be dismissed.
+ final Map dismissThresholds;
+
+ /// The maximum horizontal offset the item can move to/
+ ///
+ /// Represented as a fraction, e.g. if it is 0.4 (the default), then the
+ /// item can be moved at maximum 40% of item's width.
+ final double maxOffset;
+
+ /// Defines the duration for card to dismiss or to come back to original position if not dismissed.
+ final Duration movementDuration;
+
+ /// Defines the end offset across the main axis after the card is dismissed.
+ ///
+ /// If non-zero value is given then widget moves in cross direction depending on whether
+ /// it is positive or negative.
+ final double crossAxisEndOffset;
+
+ /// Determines the way that drag start behavior is handled.
+ ///
+ /// If set to [DragStartBehavior.start], the drag gesture used to dismiss a
+ /// dismissible will begin upon the detection of a drag gesture. If set to
+ /// [DragStartBehavior.down] it will begin when a down event is first detected.
+ ///
+ /// In general, setting this to [DragStartBehavior.start] will make drag
+ /// animation smoother and setting it to [DragStartBehavior.down] will make
+ /// drag behavior feel slightly more reactive.
+ ///
+ /// By default, the drag start behavior is [DragStartBehavior.start].
+ ///
+ /// See also:
+ ///
+ /// * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors.
+ final DragStartBehavior dragStartBehavior;
+
+ @override
+ _SwipeableState createState() => _SwipeableState();
+}
+
+class _SwipeableClipper extends CustomClipper {
+ _SwipeableClipper({
+ @required this.moveAnimation,
+ }) : assert(moveAnimation != null),
+ super(reclip: moveAnimation);
+
+ final Animation moveAnimation;
+
+ @override
+ Rect getClip(Size size) {
+ final offset = moveAnimation.value.dx * size.width;
+ if (offset < 0) {
+ return Rect.fromLTRB(size.width + offset, 0.0, size.width, size.height);
+ }
+ return Rect.fromLTRB(0.0, 0.0, offset, size.height);
+ }
+
+ @override
+ Rect getApproximateClipRect(Size size) => getClip(size);
+
+ @override
+ bool shouldReclip(_SwipeableClipper oldClipper) {
+ return oldClipper.moveAnimation.value != moveAnimation.value;
+ }
+}
+
+enum _FlingGestureKind { none, forward, reverse }
+
+class _SwipeableState extends State
+ with TickerProviderStateMixin, AutomaticKeepAliveClientMixin {
+ @override
+ void initState() {
+ super.initState();
+ _moveController =
+ AnimationController(duration: widget.movementDuration, vsync: this)
+ ..addStatusListener(_handleDismissStatusChanged);
+ _updateMoveAnimation();
+ }
+
+ AnimationController _moveController;
+ Animation _moveAnimation;
+
+ double _dragExtent = 0.0;
+ bool _dragUnderway = false;
+ Size _sizePriorToCollapse;
+
+ @override
+ bool get wantKeepAlive => _moveController?.isAnimating == true;
+
+ @override
+ void dispose() {
+ _moveController.dispose();
+ super.dispose();
+ }
+
+ SwipeDirection _extentToDirection(double extent) {
+ if (extent == 0.0) {
+ return null;
+ }
+ switch (Directionality.of(context)) {
+ case TextDirection.rtl:
+ return extent < 0
+ ? SwipeDirection.startToEnd
+ : SwipeDirection.endToStart;
+ case TextDirection.ltr:
+ return extent > 0
+ ? SwipeDirection.startToEnd
+ : SwipeDirection.endToStart;
+ }
+ assert(false);
+ return null;
+ }
+
+ SwipeDirection get _SwipeDirection => _extentToDirection(_dragExtent);
+
+ bool get _isActive {
+ return _dragUnderway || _moveController.isAnimating;
+ }
+
+ double get _overallDragAxisExtent {
+ final size = context.size;
+ return size.width;
+ }
+
+ void _handleDragStart(DragStartDetails details) {
+ _dragUnderway = true;
+ if (_moveController.isAnimating) {
+ _dragExtent =
+ _moveController.value * _overallDragAxisExtent * _dragExtent.sign;
+ _moveController.stop();
+ } else {
+ _dragExtent = 0.0;
+ _moveController.value = 0.0;
+ }
+ setState(() {
+ _updateMoveAnimation();
+ });
+ }
+
+ void _handleDragUpdate(DragUpdateDetails details) {
+ if (!_isActive || _moveController.isAnimating) {
+ return;
+ }
+
+ final delta = details.primaryDelta;
+ final oldDragExtent = _dragExtent;
+ switch (widget.direction) {
+ case SwipeDirection.horizontal:
+ _dragExtent += delta;
+ break;
+
+ case SwipeDirection.endToStart:
+ switch (Directionality.of(context)) {
+ case TextDirection.rtl:
+ if (_dragExtent + delta > 0) {
+ _dragExtent += delta;
+ }
+ break;
+ case TextDirection.ltr:
+ if (_dragExtent + delta < 0) {
+ _dragExtent += delta;
+ }
+ break;
+ }
+ break;
+
+ case SwipeDirection.startToEnd:
+ switch (Directionality.of(context)) {
+ case TextDirection.rtl:
+ if (_dragExtent + delta < 0) {
+ _dragExtent += delta;
+ }
+ break;
+ case TextDirection.ltr:
+ if (_dragExtent + delta > 0) {
+ _dragExtent += delta;
+ }
+ break;
+ }
+ break;
+ }
+ if (oldDragExtent.sign != _dragExtent.sign) {
+ setState(() {
+ _updateMoveAnimation();
+ });
+ }
+ if (!_moveController.isAnimating) {
+ _moveController.value = _dragExtent.abs() / _overallDragAxisExtent;
+ }
+ }
+
+ void _updateMoveAnimation() {
+ final end = _dragExtent.sign;
+ _moveAnimation = _moveController.drive(
+ Tween(
+ begin: Offset.zero,
+ end: Offset(widget.maxOffset * end, widget.crossAxisEndOffset),
+ ),
+ );
+ }
+
+ _FlingGestureKind _describeFlingGesture(Velocity velocity) {
+ assert(widget.direction != null);
+ if (_dragExtent == 0.0) {
+ // If it was a fling, then it was a fling that was let loose at the exact
+ // middle of the range (i.e. when there's no displacement). In that case,
+ // we assume that the user meant to fling it back to the center, as
+ // opposed to having wanted to drag it out one way, then fling it past the
+ // center and into and out the other side.
+ return _FlingGestureKind.none;
+ }
+ final vx = velocity.pixelsPerSecond.dx;
+ final vy = velocity.pixelsPerSecond.dy;
+ SwipeDirection flingDirection;
+ // Verify that the fling is in the generally right direction and fast enough.
+ if (vx.abs() - vy.abs() < _kMinFlingVelocityDelta ||
+ vx.abs() < _kMinFlingVelocity) {
+ return _FlingGestureKind.none;
+ }
+ assert(vx != 0.0);
+ flingDirection = _extentToDirection(vx);
+
+ assert(_SwipeDirection != null);
+ if (flingDirection == _SwipeDirection) {
+ return _FlingGestureKind.forward;
+ }
+ return _FlingGestureKind.reverse;
+ }
+
+ Future _handleDragEnd(DragEndDetails details) async {
+ if (!_isActive || _moveController.isAnimating) {
+ return;
+ }
+ _dragUnderway = false;
+ if (_moveController.isCompleted &&
+ await _confirmStartResizeAnimation() == true) {
+ _startResizeAnimation();
+ return;
+ }
+ final flingVelocity = details.velocity.pixelsPerSecond.dx;
+ switch (_describeFlingGesture(details.velocity)) {
+ case _FlingGestureKind.forward:
+ assert(_dragExtent != 0.0);
+ assert(!_moveController.isDismissed);
+ if ((widget.dismissThresholds[_SwipeDirection] ?? _kDismissThreshold) >=
+ 1.0) {
+ await _moveController.reverse();
+ break;
+ }
+ _dragExtent = flingVelocity.sign;
+ await _moveController.fling(
+ velocity: flingVelocity.abs() * _kFlingVelocityScale);
+ break;
+ case _FlingGestureKind.reverse:
+ assert(_dragExtent != 0.0);
+ assert(!_moveController.isDismissed);
+ _dragExtent = flingVelocity.sign;
+ await _moveController.fling(
+ velocity: -flingVelocity.abs() * _kFlingVelocityScale);
+ break;
+ case _FlingGestureKind.none:
+ if (!_moveController.isDismissed) {
+ // we already know it's not completed, we check that above
+ if (_moveController.value >
+ (widget.dismissThresholds[_SwipeDirection] ??
+ _kDismissThreshold)) {
+ await _moveController.forward();
+ } else {
+ await _moveController.reverse();
+ }
+ }
+ break;
+ }
+ }
+
+ Future _handleDismissStatusChanged(AnimationStatus status) async {
+ if (status == AnimationStatus.completed && !_dragUnderway) {
+ if (await _confirmStartResizeAnimation() == true) {
+ _startResizeAnimation();
+ } else {
+ await _moveController.reverse();
+ }
+ }
+ updateKeepAlive();
+ }
+
+ Future _confirmStartResizeAnimation() async {
+ if (widget.confirmSwipe != null) {
+ final direction = _SwipeDirection;
+ assert(direction != null);
+ return widget.confirmSwipe(direction);
+ }
+ return true;
+ }
+
+ void _startResizeAnimation() {
+ assert(_moveController != null);
+ assert(_moveController.isCompleted);
+ assert(_sizePriorToCollapse == null);
+ _moveController.reverse();
+ if (widget.onSwiped != null) {
+ final direction = _SwipeDirection;
+ assert(direction != null);
+ widget.onSwiped(direction);
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ super.build(context); // See AutomaticKeepAliveClientMixin.
+
+ assert(debugCheckHasDirectionality(context));
+
+ var background = widget.background;
+ if (widget.secondaryBackground != null) {
+ final direction = _SwipeDirection;
+ if (direction == SwipeDirection.endToStart) {
+ background = widget.secondaryBackground;
+ }
+ }
+
+ Widget content = SlideTransition(
+ position: _moveAnimation,
+ child: widget.child,
+ );
+
+ if (background != null) {
+ content = Stack(children: [
+ if (!_moveAnimation.isDismissed)
+ Positioned.fill(
+ child: ClipRect(
+ clipper: _SwipeableClipper(
+ moveAnimation: _moveAnimation,
+ ),
+ child: background,
+ ),
+ ),
+ content,
+ ]);
+ }
+ // We are not swiping but we may be being dragging in widget.direction.
+ return GestureDetector(
+ onHorizontalDragStart: _handleDragStart,
+ onHorizontalDragUpdate: _handleDragUpdate,
+ onHorizontalDragEnd: _handleDragEnd,
+ behavior: HitTestBehavior.opaque,
+ child: content,
+ dragStartBehavior: widget.dragStartBehavior,
+ );
+ }
+}
diff --git a/lib/views/chat.dart b/lib/views/chat.dart
index 5bb4cbf..036ced2 100644
--- a/lib/views/chat.dart
+++ b/lib/views/chat.dart
@@ -351,6 +351,14 @@ class _ChatState extends State<_Chat> {
inputFocus.requestFocus();
}
+ void replyBySwipeAction(Event replyTo) {
+ setState(() {
+ replyEvent = replyTo;
+ selectedEvents.clear();
+ });
+ inputFocus.requestFocus();
+ }
+
void _scrollToEventId(String eventId, {BuildContext context}) async {
var eventIndex =
getFilteredEvents().indexWhere((e) => e.eventId == eventId);
@@ -668,21 +676,40 @@ 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(
+ child: Swipeable(
+ key: ValueKey(i - 1),
+ background: Container(
+ color: Theme.of(context).primaryColor.withAlpha(100),
+ padding: EdgeInsets.symmetric(horizontal: 12.0),
+ alignment: Alignment.centerLeft,
+ child: Row(
+ children: [
+ Icon(Icons.reply),
+ SizedBox(width: 2.0),
+ Text(L10n.of(context).reply)
+ ],
+ ),
+ ),
+ direction: SwipeDirection.startToEnd,
+ onSwiped: (direction) {
+ replyBySwipeAction(
+ 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),
);
@@ -705,6 +732,7 @@ class _ChatState extends State<_Chat> {
nextEvent: i >= 2
? filteredEvents[i - 2]
: null),
+ ),
);
});
},
From 20efc3dbb6469692c1af645a98905245b5ae192f Mon Sep 17 00:00:00 2001
From: Inex Code
Date: Sun, 4 Oct 2020 23:37:32 +0000
Subject: [PATCH 13/20] fix formatting
---
lib/components/swipeable.dart | 2 +-
lib/views/chat.dart | 47 +++++++++++++++++++----------------
2 files changed, 26 insertions(+), 23 deletions(-)
diff --git a/lib/components/swipeable.dart b/lib/components/swipeable.dart
index 71543dc..f876c28 100644
--- a/lib/components/swipeable.dart
+++ b/lib/components/swipeable.dart
@@ -111,7 +111,7 @@ class Swipeable extends StatefulWidget {
final Map dismissThresholds;
/// The maximum horizontal offset the item can move to/
- ///
+ ///
/// Represented as a fraction, e.g. if it is 0.4 (the default), then the
/// item can be moved at maximum 40% of item's width.
final double maxOffset;
diff --git a/lib/views/chat.dart b/lib/views/chat.dart
index 036ced2..f86fb6b 100644
--- a/lib/views/chat.dart
+++ b/lib/views/chat.dart
@@ -679,8 +679,11 @@ class _ChatState extends State<_Chat> {
child: Swipeable(
key: ValueKey(i - 1),
background: Container(
- color: Theme.of(context).primaryColor.withAlpha(100),
- padding: EdgeInsets.symmetric(horizontal: 12.0),
+ color: Theme.of(context)
+ .primaryColor
+ .withAlpha(100),
+ padding: EdgeInsets.symmetric(
+ horizontal: 12.0),
alignment: Alignment.centerLeft,
child: Row(
children: [
@@ -710,28 +713,28 @@ class _ChatState extends State<_Chat> {
);
} else {
setState(
- () =>
- selectedEvents.add(event),
+ () => 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),
),
);
});
From 3ee20eb07c285168d769aca6c9fa8210098a638e Mon Sep 17 00:00:00 2001
From: Inex Code
Date: Mon, 5 Oct 2020 01:28:34 +0000
Subject: [PATCH 14/20] fix replying to wrong message when new message arrives
during the gesture
---
lib/views/chat.dart | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lib/views/chat.dart b/lib/views/chat.dart
index f86fb6b..9cce68f 100644
--- a/lib/views/chat.dart
+++ b/lib/views/chat.dart
@@ -677,7 +677,8 @@ class _ChatState extends State<_Chat> {
index: i - 1,
controller: _scrollController,
child: Swipeable(
- key: ValueKey(i - 1),
+ key: ValueKey(
+ filteredEvents[i - 1].eventId),
background: Container(
color: Theme.of(context)
.primaryColor
From de41700741a62d0d5ef1912e41de5e2bce82ee9a Mon Sep 17 00:00:00 2001
From: Inex Code
Date: Tue, 6 Oct 2020 20:56:29 +0300
Subject: [PATCH 15/20] Add pointer type detection for Swipeable
---
lib/components/swipeable.dart | 36 ++++++++++++++++++++++++++++-------
1 file changed, 29 insertions(+), 7 deletions(-)
diff --git a/lib/components/swipeable.dart b/lib/components/swipeable.dart
index f876c28..fd1a6f5 100644
--- a/lib/components/swipeable.dart
+++ b/lib/components/swipeable.dart
@@ -1,3 +1,4 @@
+import 'dart:ui';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
@@ -54,6 +55,11 @@ class Swipeable extends StatefulWidget {
this.movementDuration = const Duration(milliseconds: 200),
this.crossAxisEndOffset = 0.0,
this.dragStartBehavior = DragStartBehavior.start,
+ this.allowedPointerKinds = const {
+ PointerDeviceKind.invertedStylus,
+ PointerDeviceKind.stylus,
+ PointerDeviceKind.touch
+ },
}) : assert(key != null),
assert(secondaryBackground == null || background != null),
assert(dragStartBehavior != null),
@@ -125,6 +131,11 @@ class Swipeable extends StatefulWidget {
/// it is positive or negative.
final double crossAxisEndOffset;
+ /// Defines pointer types which are allowed to trigger swipe gesture.
+ ///
+ /// Defaults to {PointerDeviceKind.touch, PointerDeviceKind.invertedStylus, PointerDeviceKind.stylus}
+ final Set allowedPointerKinds;
+
/// Determines the way that drag start behavior is handled.
///
/// If set to [DragStartBehavior.start], the drag gesture used to dismiss a
@@ -192,6 +203,8 @@ class _SwipeableState extends State
bool _dragUnderway = false;
Size _sizePriorToCollapse;
+ bool _isTouch = true;
+
@override
bool get wantKeepAlive => _moveController?.isAnimating == true;
@@ -230,6 +243,12 @@ class _SwipeableState extends State
return size.width;
}
+ void _handlePointerDown(PointerDownEvent event) {
+ setState(() {
+ _isTouch = widget.allowedPointerKinds.contains(event.kind);
+ });
+ }
+
void _handleDragStart(DragStartDetails details) {
_dragUnderway = true;
if (_moveController.isAnimating) {
@@ -447,13 +466,16 @@ class _SwipeableState extends State
]);
}
// We are not swiping but we may be being dragging in widget.direction.
- return GestureDetector(
- onHorizontalDragStart: _handleDragStart,
- onHorizontalDragUpdate: _handleDragUpdate,
- onHorizontalDragEnd: _handleDragEnd,
- behavior: HitTestBehavior.opaque,
- child: content,
- dragStartBehavior: widget.dragStartBehavior,
+ return Listener(
+ onPointerDown: _handlePointerDown,
+ child: GestureDetector(
+ onHorizontalDragStart: _isTouch ? _handleDragStart : null,
+ onHorizontalDragUpdate: _isTouch ? _handleDragUpdate : null,
+ onHorizontalDragEnd: _isTouch ? _handleDragEnd : null,
+ behavior: HitTestBehavior.opaque,
+ child: content,
+ dragStartBehavior: widget.dragStartBehavior,
+ ),
);
}
}
From 84a4a2c0c416f0181e2c7a0a16caa363e85aca76 Mon Sep 17 00:00:00 2001
From: Inex Code
Date: Tue, 6 Oct 2020 18:53:15 +0000
Subject: [PATCH 16/20] Code deduplication
---
lib/views/chat.dart | 16 ++++------------
1 file changed, 4 insertions(+), 12 deletions(-)
diff --git a/lib/views/chat.dart b/lib/views/chat.dart
index 9cce68f..cdfd254 100644
--- a/lib/views/chat.dart
+++ b/lib/views/chat.dart
@@ -343,17 +343,9 @@ class _ChatState extends State<_Chat> {
setState(() => selectedEvents.clear());
}
- void replyAction() {
+ void replyAction({Event replyTo}) {
setState(() {
- replyEvent = selectedEvents.first;
- selectedEvents.clear();
- });
- inputFocus.requestFocus();
- }
-
- void replyBySwipeAction(Event replyTo) {
- setState(() {
- replyEvent = replyTo;
+ replyEvent = replyTo ?? selectedEvents.first;
selectedEvents.clear();
});
inputFocus.requestFocus();
@@ -696,8 +688,8 @@ class _ChatState extends State<_Chat> {
),
direction: SwipeDirection.startToEnd,
onSwiped: (direction) {
- replyBySwipeAction(
- filteredEvents[i - 1]);
+ replyAction(
+ replyTo: filteredEvents[i - 1]);
},
child: Message(filteredEvents[i - 1],
onAvatarTab: (Event event) {
From 23936fa7f1fcafba35b7eb578accc01a29df953e Mon Sep 17 00:00:00 2001
From: Inex Code
Date: Wed, 7 Oct 2020 11:20:50 +0000
Subject: [PATCH 17/20] Move Swipeable to separate package
---
lib/components/swipeable.dart | 481 ----------------------------------
lib/views/chat.dart | 3 +-
pubspec.lock | 7 +
pubspec.yaml | 1 +
4 files changed, 10 insertions(+), 482 deletions(-)
delete mode 100644 lib/components/swipeable.dart
diff --git a/lib/components/swipeable.dart b/lib/components/swipeable.dart
deleted file mode 100644
index fd1a6f5..0000000
--- a/lib/components/swipeable.dart
+++ /dev/null
@@ -1,481 +0,0 @@
-import 'dart:ui';
-import 'package:flutter/gestures.dart';
-import 'package:flutter/material.dart';
-
-const double _kMinFlingVelocity = 700.0;
-const double _kMinFlingVelocityDelta = 400.0;
-const double _kFlingVelocityScale = 1.0 / 300.0;
-const double _kDismissThreshold = 0.4;
-
-/// Signature used by [Swipeable] to indicate that it has been swiped in
-/// the given `direction`.
-///
-/// Used by [Swipeable.onSwiped].
-typedef SwipeDirectionCallback = void Function(SwipeDirection direction);
-
-/// Signature used by [Swipeable] to give the application an opportunity to
-/// confirm or veto a swipe gesture.
-///
-/// Used by [Swipeable.confirmSwipe].
-typedef ConfirmSwipeCallback = Future Function(SwipeDirection direction);
-
-/// The direction in which a [Swipeable] can be swiped.
-enum SwipeDirection {
- /// The [Swipeable] can be swiped by dragging either left or right.
- horizontal,
-
- /// The [Swipeable] can be swiped by dragging in the reverse of the
- /// reading direction (e.g., from right to left in left-to-right languages).
- endToStart,
-
- /// The [Swipeable] can be swiped by dragging in the reading direction
- /// (e.g., from left to right in left-to-right languages).
- startToEnd,
-}
-
-class Swipeable extends StatefulWidget {
- /// Creates a widget that calls a function when swiped.
- ///
- /// The [key] argument must not be null because [Swipeable]s are commonly
- /// used in lists and removed from the list when dismissed. Without keys, the
- /// default behavior is to sync widgets based on their index in the list,
- /// which means the item after the dismissed item would be synced with the
- /// state of the dismissed item. Using keys causes the widgets to sync
- /// according to their keys and avoids this pitfall.
- const Swipeable({
- @required Key key,
- @required this.child,
- this.background,
- this.secondaryBackground,
- this.confirmSwipe,
- this.onSwiped,
- this.direction = SwipeDirection.horizontal,
- this.dismissThresholds = const {},
- this.maxOffset = 0.4,
- this.movementDuration = const Duration(milliseconds: 200),
- this.crossAxisEndOffset = 0.0,
- this.dragStartBehavior = DragStartBehavior.start,
- this.allowedPointerKinds = const {
- PointerDeviceKind.invertedStylus,
- PointerDeviceKind.stylus,
- PointerDeviceKind.touch
- },
- }) : assert(key != null),
- assert(secondaryBackground == null || background != null),
- assert(dragStartBehavior != null),
- super(key: key);
-
- /// The widget below this widget in the tree.
- ///
- /// {@macro flutter.widgets.child}
- final Widget child;
-
- /// A widget that is stacked behind the child. If secondaryBackground is also
- /// specified then this widget only appears when the child has been dragged
- /// to the right.
- final Widget background;
-
- /// A widget that is stacked behind the child and is exposed when the child
- /// has been dragged to the left. It may only be specified when background
- /// has also been specified.
- final Widget secondaryBackground;
-
- /// Gives the app an opportunity to confirm or veto a pending dismissal.
- ///
- /// If the returned Future completes true, then this widget will be
- /// dismissed, otherwise it will be moved back to its original location.
- ///
- /// If the returned Future completes to false or null the [onSwiped]
- /// callback will not run.
- final ConfirmSwipeCallback confirmSwipe;
-
- /// Called when the widget has been dismissed, after finishing resizing.
- final SwipeDirectionCallback onSwiped;
-
- /// The direction in which the widget can be dismissed.
- final SwipeDirection direction;
-
- /// The offset threshold the item has to be dragged in order to be considered
- /// dismissed.
- ///
- /// Represented as a fraction, e.g. if it is 0.4 (the default), then the item
- /// has to be dragged at least 40% towards one direction to be considered
- /// dismissed. Clients can define different thresholds for each dismiss
- /// direction.
- ///
- /// Flinging is treated as being equivalent to dragging almost to 1.0, so
- /// flinging can dismiss an item past any threshold less than 1.0.
- ///
- /// Setting a threshold of 1.0 (or greater) prevents a drag in the given
- /// [SwipeDirection] even if it would be allowed by the [direction]
- /// property.
- ///
- /// See also:
- ///
- /// * [direction], which controls the directions in which the items can
- /// be dismissed.
- final Map dismissThresholds;
-
- /// The maximum horizontal offset the item can move to/
- ///
- /// Represented as a fraction, e.g. if it is 0.4 (the default), then the
- /// item can be moved at maximum 40% of item's width.
- final double maxOffset;
-
- /// Defines the duration for card to dismiss or to come back to original position if not dismissed.
- final Duration movementDuration;
-
- /// Defines the end offset across the main axis after the card is dismissed.
- ///
- /// If non-zero value is given then widget moves in cross direction depending on whether
- /// it is positive or negative.
- final double crossAxisEndOffset;
-
- /// Defines pointer types which are allowed to trigger swipe gesture.
- ///
- /// Defaults to {PointerDeviceKind.touch, PointerDeviceKind.invertedStylus, PointerDeviceKind.stylus}
- final Set allowedPointerKinds;
-
- /// Determines the way that drag start behavior is handled.
- ///
- /// If set to [DragStartBehavior.start], the drag gesture used to dismiss a
- /// dismissible will begin upon the detection of a drag gesture. If set to
- /// [DragStartBehavior.down] it will begin when a down event is first detected.
- ///
- /// In general, setting this to [DragStartBehavior.start] will make drag
- /// animation smoother and setting it to [DragStartBehavior.down] will make
- /// drag behavior feel slightly more reactive.
- ///
- /// By default, the drag start behavior is [DragStartBehavior.start].
- ///
- /// See also:
- ///
- /// * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors.
- final DragStartBehavior dragStartBehavior;
-
- @override
- _SwipeableState createState() => _SwipeableState();
-}
-
-class _SwipeableClipper extends CustomClipper {
- _SwipeableClipper({
- @required this.moveAnimation,
- }) : assert(moveAnimation != null),
- super(reclip: moveAnimation);
-
- final Animation moveAnimation;
-
- @override
- Rect getClip(Size size) {
- final offset = moveAnimation.value.dx * size.width;
- if (offset < 0) {
- return Rect.fromLTRB(size.width + offset, 0.0, size.width, size.height);
- }
- return Rect.fromLTRB(0.0, 0.0, offset, size.height);
- }
-
- @override
- Rect getApproximateClipRect(Size size) => getClip(size);
-
- @override
- bool shouldReclip(_SwipeableClipper oldClipper) {
- return oldClipper.moveAnimation.value != moveAnimation.value;
- }
-}
-
-enum _FlingGestureKind { none, forward, reverse }
-
-class _SwipeableState extends State
- with TickerProviderStateMixin, AutomaticKeepAliveClientMixin {
- @override
- void initState() {
- super.initState();
- _moveController =
- AnimationController(duration: widget.movementDuration, vsync: this)
- ..addStatusListener(_handleDismissStatusChanged);
- _updateMoveAnimation();
- }
-
- AnimationController _moveController;
- Animation _moveAnimation;
-
- double _dragExtent = 0.0;
- bool _dragUnderway = false;
- Size _sizePriorToCollapse;
-
- bool _isTouch = true;
-
- @override
- bool get wantKeepAlive => _moveController?.isAnimating == true;
-
- @override
- void dispose() {
- _moveController.dispose();
- super.dispose();
- }
-
- SwipeDirection _extentToDirection(double extent) {
- if (extent == 0.0) {
- return null;
- }
- switch (Directionality.of(context)) {
- case TextDirection.rtl:
- return extent < 0
- ? SwipeDirection.startToEnd
- : SwipeDirection.endToStart;
- case TextDirection.ltr:
- return extent > 0
- ? SwipeDirection.startToEnd
- : SwipeDirection.endToStart;
- }
- assert(false);
- return null;
- }
-
- SwipeDirection get _SwipeDirection => _extentToDirection(_dragExtent);
-
- bool get _isActive {
- return _dragUnderway || _moveController.isAnimating;
- }
-
- double get _overallDragAxisExtent {
- final size = context.size;
- return size.width;
- }
-
- void _handlePointerDown(PointerDownEvent event) {
- setState(() {
- _isTouch = widget.allowedPointerKinds.contains(event.kind);
- });
- }
-
- void _handleDragStart(DragStartDetails details) {
- _dragUnderway = true;
- if (_moveController.isAnimating) {
- _dragExtent =
- _moveController.value * _overallDragAxisExtent * _dragExtent.sign;
- _moveController.stop();
- } else {
- _dragExtent = 0.0;
- _moveController.value = 0.0;
- }
- setState(() {
- _updateMoveAnimation();
- });
- }
-
- void _handleDragUpdate(DragUpdateDetails details) {
- if (!_isActive || _moveController.isAnimating) {
- return;
- }
-
- final delta = details.primaryDelta;
- final oldDragExtent = _dragExtent;
- switch (widget.direction) {
- case SwipeDirection.horizontal:
- _dragExtent += delta;
- break;
-
- case SwipeDirection.endToStart:
- switch (Directionality.of(context)) {
- case TextDirection.rtl:
- if (_dragExtent + delta > 0) {
- _dragExtent += delta;
- }
- break;
- case TextDirection.ltr:
- if (_dragExtent + delta < 0) {
- _dragExtent += delta;
- }
- break;
- }
- break;
-
- case SwipeDirection.startToEnd:
- switch (Directionality.of(context)) {
- case TextDirection.rtl:
- if (_dragExtent + delta < 0) {
- _dragExtent += delta;
- }
- break;
- case TextDirection.ltr:
- if (_dragExtent + delta > 0) {
- _dragExtent += delta;
- }
- break;
- }
- break;
- }
- if (oldDragExtent.sign != _dragExtent.sign) {
- setState(() {
- _updateMoveAnimation();
- });
- }
- if (!_moveController.isAnimating) {
- _moveController.value = _dragExtent.abs() / _overallDragAxisExtent;
- }
- }
-
- void _updateMoveAnimation() {
- final end = _dragExtent.sign;
- _moveAnimation = _moveController.drive(
- Tween(
- begin: Offset.zero,
- end: Offset(widget.maxOffset * end, widget.crossAxisEndOffset),
- ),
- );
- }
-
- _FlingGestureKind _describeFlingGesture(Velocity velocity) {
- assert(widget.direction != null);
- if (_dragExtent == 0.0) {
- // If it was a fling, then it was a fling that was let loose at the exact
- // middle of the range (i.e. when there's no displacement). In that case,
- // we assume that the user meant to fling it back to the center, as
- // opposed to having wanted to drag it out one way, then fling it past the
- // center and into and out the other side.
- return _FlingGestureKind.none;
- }
- final vx = velocity.pixelsPerSecond.dx;
- final vy = velocity.pixelsPerSecond.dy;
- SwipeDirection flingDirection;
- // Verify that the fling is in the generally right direction and fast enough.
- if (vx.abs() - vy.abs() < _kMinFlingVelocityDelta ||
- vx.abs() < _kMinFlingVelocity) {
- return _FlingGestureKind.none;
- }
- assert(vx != 0.0);
- flingDirection = _extentToDirection(vx);
-
- assert(_SwipeDirection != null);
- if (flingDirection == _SwipeDirection) {
- return _FlingGestureKind.forward;
- }
- return _FlingGestureKind.reverse;
- }
-
- Future _handleDragEnd(DragEndDetails details) async {
- if (!_isActive || _moveController.isAnimating) {
- return;
- }
- _dragUnderway = false;
- if (_moveController.isCompleted &&
- await _confirmStartResizeAnimation() == true) {
- _startResizeAnimation();
- return;
- }
- final flingVelocity = details.velocity.pixelsPerSecond.dx;
- switch (_describeFlingGesture(details.velocity)) {
- case _FlingGestureKind.forward:
- assert(_dragExtent != 0.0);
- assert(!_moveController.isDismissed);
- if ((widget.dismissThresholds[_SwipeDirection] ?? _kDismissThreshold) >=
- 1.0) {
- await _moveController.reverse();
- break;
- }
- _dragExtent = flingVelocity.sign;
- await _moveController.fling(
- velocity: flingVelocity.abs() * _kFlingVelocityScale);
- break;
- case _FlingGestureKind.reverse:
- assert(_dragExtent != 0.0);
- assert(!_moveController.isDismissed);
- _dragExtent = flingVelocity.sign;
- await _moveController.fling(
- velocity: -flingVelocity.abs() * _kFlingVelocityScale);
- break;
- case _FlingGestureKind.none:
- if (!_moveController.isDismissed) {
- // we already know it's not completed, we check that above
- if (_moveController.value >
- (widget.dismissThresholds[_SwipeDirection] ??
- _kDismissThreshold)) {
- await _moveController.forward();
- } else {
- await _moveController.reverse();
- }
- }
- break;
- }
- }
-
- Future _handleDismissStatusChanged(AnimationStatus status) async {
- if (status == AnimationStatus.completed && !_dragUnderway) {
- if (await _confirmStartResizeAnimation() == true) {
- _startResizeAnimation();
- } else {
- await _moveController.reverse();
- }
- }
- updateKeepAlive();
- }
-
- Future _confirmStartResizeAnimation() async {
- if (widget.confirmSwipe != null) {
- final direction = _SwipeDirection;
- assert(direction != null);
- return widget.confirmSwipe(direction);
- }
- return true;
- }
-
- void _startResizeAnimation() {
- assert(_moveController != null);
- assert(_moveController.isCompleted);
- assert(_sizePriorToCollapse == null);
- _moveController.reverse();
- if (widget.onSwiped != null) {
- final direction = _SwipeDirection;
- assert(direction != null);
- widget.onSwiped(direction);
- }
- }
-
- @override
- Widget build(BuildContext context) {
- super.build(context); // See AutomaticKeepAliveClientMixin.
-
- assert(debugCheckHasDirectionality(context));
-
- var background = widget.background;
- if (widget.secondaryBackground != null) {
- final direction = _SwipeDirection;
- if (direction == SwipeDirection.endToStart) {
- background = widget.secondaryBackground;
- }
- }
-
- Widget content = SlideTransition(
- position: _moveAnimation,
- child: widget.child,
- );
-
- if (background != null) {
- content = Stack(children: [
- if (!_moveAnimation.isDismissed)
- Positioned.fill(
- child: ClipRect(
- clipper: _SwipeableClipper(
- moveAnimation: _moveAnimation,
- ),
- child: background,
- ),
- ),
- content,
- ]);
- }
- // We are not swiping but we may be being dragging in widget.direction.
- return Listener(
- onPointerDown: _handlePointerDown,
- child: GestureDetector(
- onHorizontalDragStart: _isTouch ? _handleDragStart : null,
- onHorizontalDragUpdate: _isTouch ? _handleDragUpdate : null,
- onHorizontalDragEnd: _isTouch ? _handleDragEnd : null,
- behavior: HitTestBehavior.opaque,
- child: content,
- dragStartBehavior: widget.dragStartBehavior,
- ),
- );
- }
-}
diff --git a/lib/views/chat.dart b/lib/views/chat.dart
index cdfd254..b991841 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';
@@ -687,7 +688,7 @@ class _ChatState extends State<_Chat> {
),
),
direction: SwipeDirection.startToEnd,
- onSwiped: (direction) {
+ onSwipe: (direction) {
replyAction(
replyTo: filteredEvents[i - 1]);
},
diff --git a/pubspec.lock b/pubspec.lock
index eaec670..c5876b0 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -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:
From c97cb326f82c8ce633f452964a5c5bf3ef8f6b3f Mon Sep 17 00:00:00 2001
From: Inex Code
Date: Wed, 14 Oct 2020 06:26:33 +0300
Subject: [PATCH 18/20] Add settings for swipe actions
---
lib/components/matrix.dart | 13 +++
lib/l10n/intl_en.arb | 15 +++
lib/views/chat.dart | 158 ++++++++++++++++++++++----
lib/views/settings/settings_chat.dart | 89 +++++++++++++++
4 files changed, 253 insertions(+), 22 deletions(-)
diff --git a/lib/components/matrix.dart b/lib/components/matrix.dart
index 3a9b8a2..27b2219 100644
--- a/lib/components/matrix.dart
+++ b/lib/components/matrix.dart
@@ -71,6 +71,9 @@ class MatrixState extends State {
File wallpaper;
bool renderHtml = false;
+ String swipeToEndAction;
+ String swipeToStartAction = 'reply';
+
String jitsiInstance = 'https://meet.jit.si/';
void clean() async {
@@ -283,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_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/views/chat.dart b/lib/views/chat.dart
index b991841..f989262 100644
--- a/lib/views/chat.dart
+++ b/lib/views/chat.dart
@@ -317,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 = {
@@ -412,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);
@@ -672,26 +796,16 @@ class _ChatState extends State<_Chat> {
child: Swipeable(
key: ValueKey(
filteredEvents[i - 1].eventId),
- background: Container(
- color: Theme.of(context)
- .primaryColor
- .withAlpha(100),
- padding: EdgeInsets.symmetric(
- horizontal: 12.0),
- alignment: Alignment.centerLeft,
- child: Row(
- children: [
- Icon(Icons.reply),
- SizedBox(width: 2.0),
- Text(L10n.of(context).reply)
- ],
- ),
- ),
- direction: SwipeDirection.startToEnd,
- onSwipe: (direction) {
- replyAction(
- replyTo: filteredEvents[i - 1]);
- },
+ 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 +=
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)),
+ ),
],
),
);
From b061c4471a927388d8a0114a1f30e7d01f9eef40 Mon Sep 17 00:00:00 2001
From: Inex Code
Date: Wed, 14 Oct 2020 03:37:00 +0000
Subject: [PATCH 19/20] Update russian translation
---
lib/l10n/intl_ru.arb | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
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",
From 787add88ae9b6f07319aaf1beedbef6d35f86d2f Mon Sep 17 00:00:00 2001
From: Inex Code
Date: Wed, 14 Oct 2020 03:40:41 +0000
Subject: [PATCH 20/20] Update readme
---
README.md | 12 +++---------
1 file changed, 3 insertions(+), 9 deletions(-)
diff --git a/README.md b/README.md
index d489b0c..5c3a6c5 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,7 @@
An experimental fork of FluffyChat.
# Changes from FluffyChat
+ * Swipe to reply (or forward/edit)
* Reworked auth flow
* Removed Sentry
* Double check of .well-known
@@ -36,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.
@@ -81,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: