diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index a0db7d0..9e2c7ca 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -6,6 +6,11 @@ "longPressToRecordVoiceMessage": "Long press to record voice message.", "pause": "Pause", "resume": "Resume", + "newSubSpace": "New sub space", + "moveToDifferentSpace": "Move to different space", + "moveUp": "Move up", + "moveDown": "Move down", + "removeFromSpaceDescription": "The chat will be removed from the space but still appear in your chat list.", "endPoll": "End poll", "anonymousPoll": "Anonymous", "publicPoll": "Public", @@ -16,6 +21,7 @@ "vote": "Vote", "changeVote": "Re-vote", "choicesSelected": "{selected} of {max} selected", + "discuss": "Discuss", "@choicesSelected": { "type": "String", "placeholders": { @@ -893,6 +899,9 @@ }, "chatPermissions": "Chat permissions", "@chatPermissions": {}, + "chatThreads": "Threads", + "@chatThreads": {}, + "chatThreadsDescription": "See all threads in this room", "editDisplayname": "Edit displayname", "@editDisplayname": { "type": "String", @@ -3145,6 +3154,24 @@ }, "restricted": "Restricted", "@restricted": {}, + "spaceMemberOf": "Space member of {spaces}", + "@spaceMemberOf": { + "type": "String", + "placeholders": { + "spaces": { + "type": "String" + } + } + }, + "spaceMemberOfCanKnock": "Space member of {spaces} can knock", + "@spaceMemberOfCanKnock": { + "type": "String", + "placeholders": { + "spaces": { + "type": "String" + } + } + }, "knockRestricted": "Knock restricted", "@knockRestricted": {}, "goToSpace": "Go to space: {space}", diff --git a/assets/l10n/intl_ru.arb b/assets/l10n/intl_ru.arb index 052efcb..57b22f5 100644 --- a/assets/l10n/intl_ru.arb +++ b/assets/l10n/intl_ru.arb @@ -6,6 +6,11 @@ "noMessagesYet": "Нет сообщений", "longPressToRecordVoiceMessage": "Зажмите, чтобы записать голосовое сообщение.", "pause": "Пауза", + "newSubSpace": "Новое подпространство", + "moveToDifferentSpace": "Перенести в другое пространство", + "moveUp": "Вверх", + "moveDown": "Вниз", + "removeFromSpaceDescription": "Эта комната будет удалена из пространства, но останется в Вашем списке чатов.", "resume": "Продолжить", "anonymousPoll": "Анонимный", "publicPoll": "Открытый", @@ -16,6 +21,7 @@ "vote": "Голосовать", "changeVote": "Изменить ответ", "choicesSelected": "Выбрано {selected} из {max}", + "discuss": "Обсудить", "@choicesSelected": { "type": "String", "placeholders": { @@ -41,7 +47,7 @@ "type": "String", "placeholders": {} }, - "cleanExifDescription": "Удалять метаданные EXIF (модель камеры, геолокация, время) из изображений еред отправкой.", + "cleanExifDescription": "Удалять метаданные EXIF (модель камеры, геолокация, время) из изображений перед отправкой.", "@cleanExifDescription": { "type": "String", "placeholders": {} @@ -892,6 +898,9 @@ }, "chatPermissions": "Права в чате", "@chatPermissions": {}, + "chatThreads": "Обсуждения", + "@chatThreads": {}, + "chatThreadsDescription": "Список всех обсуждений в этой комнате", "editDisplayname": "Отображаемое имя", "@editDisplayname": { "type": "String", @@ -936,7 +945,7 @@ "@globalChatId": {}, "accessAndVisibility": "Доступность и видимость", "@accessAndVisibility": {}, - "accessAndVisibilityDescription": "Кто может зайти и как найти этот чат.", + "accessAndVisibilityDescription": "Кто может зайти и как найти этот чат", "@accessAndVisibilityDescription": {}, "calls": "Звонки", "@calls": {}, @@ -3145,6 +3154,25 @@ "space": {} }, "markAsUnread": "Отметить как непрочитанное", + + "spaceMemberOf": "Участники пространства {spaces}", + "@spaceMemberOf": { + "type": "String", + "placeholders": { + "spaces": { + "type": "String" + } + } + }, + "spaceMemberOfCanKnock": "Участники пространства {spaces} (по запросу)", + "@spaceMemberOfCanKnock": { + "type": "String", + "placeholders": { + "spaces": { + "type": "String" + } + } + }, "userLevel": "{level} - Пользователь", "@userLevel": { "type": "String", diff --git a/l10n.yaml b/l10n.yaml index 8cba8e1..2323963 100644 --- a/l10n.yaml +++ b/l10n.yaml @@ -5,5 +5,4 @@ output-class: L10n preferred-supported-locales: ["en"] use-deferred-loading: true nullable-getter: false -synthetic-package: false output-dir: lib/generated/l10n \ No newline at end of file diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index 8704c22..c552835 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -15,6 +15,8 @@ abstract class AppConfig { static bool enableGradient = true; static bool cleanExif = true; + + static String? httpProxy; static String get defaultHomeserver => _defaultHomeserver; static double fontSizeFactor = 1; static const Color chatColor = primaryColor; diff --git a/lib/config/routes.dart b/lib/config/routes.dart index cc987a9..fa32d04 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:extera_next/pages/chat_thread/thread.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; @@ -148,6 +149,27 @@ abstract class AppRoutes { ), ), redirect: loggedOutRedirect, + routes: [ + GoRoute( + path: 'threads', + redirect: loggedOutRedirect, + routes: [ + GoRoute( + path: ':threadroot', + pageBuilder: (context, state) => defaultPageBuilder( + context, + state, + ThreadPage( + roomId: state.pathParameters['roomid']!, + threadRootEventId: + state.pathParameters['threadroot']!, + eventId: state.uri.queryParameters['event'], + ), + ), + ), + ], + ), + ], ), ], redirect: loggedOutRedirect, @@ -347,11 +369,31 @@ abstract class AppRoutes { roomId: state.pathParameters['roomid']!, shareItems: shareItems, eventId: state.uri.queryParameters['event'], + showThreadRoots: state.uri.queryParameters['threads'] == 'true', ), ); }, redirect: loggedOutRedirect, routes: [ + GoRoute( + path: 'threads', + redirect: loggedOutRedirect, + routes: [ + GoRoute( + path: ':threadroot', + pageBuilder: (context, state) => defaultPageBuilder( + context, + state, + ThreadPage( + roomId: state.pathParameters['roomid']!, + threadRootEventId: + state.pathParameters['threadroot']!, + eventId: state.uri.queryParameters['event'], + ), + ), + ), + ], + ), GoRoute( path: 'search', pageBuilder: (context, state) => defaultPageBuilder( diff --git a/lib/config/setting_keys.dart b/lib/config/setting_keys.dart index 1ee36b5..ebeb3b2 100644 --- a/lib/config/setting_keys.dart +++ b/lib/config/setting_keys.dart @@ -1,6 +1,7 @@ import 'package:shared_preferences/shared_preferences.dart'; abstract class SettingKeys { + static const String httpProxy = 'xyz.extera.next.httpProxy'; static const String cleanExif = 'xyz.extera.next.cleanExif'; static const String displayNavigationRail = 'chat.fluffy.displayNavigationRail'; static const String hideAvatarsInInvites = 'xyz.extera.next.hideAvatarsInInvites'; diff --git a/lib/generated/l10n/l10n.dart b/lib/generated/l10n/l10n.dart index 0e28925..338dba3 100644 --- a/lib/generated/l10n/l10n.dart +++ b/lib/generated/l10n/l10n.dart @@ -223,6 +223,36 @@ abstract class L10n { /// **'Resume'** String get resume; + /// No description provided for @newSubSpace. + /// + /// In en, this message translates to: + /// **'New sub space'** + String get newSubSpace; + + /// No description provided for @moveToDifferentSpace. + /// + /// In en, this message translates to: + /// **'Move to different space'** + String get moveToDifferentSpace; + + /// No description provided for @moveUp. + /// + /// In en, this message translates to: + /// **'Move up'** + String get moveUp; + + /// No description provided for @moveDown. + /// + /// In en, this message translates to: + /// **'Move down'** + String get moveDown; + + /// No description provided for @removeFromSpaceDescription. + /// + /// In en, this message translates to: + /// **'The chat will be removed from the space but still appear in your chat list.'** + String get removeFromSpaceDescription; + /// No description provided for @endPoll. /// /// In en, this message translates to: @@ -283,6 +313,12 @@ abstract class L10n { /// **'{selected} of {max} selected'** String choicesSelected(int selected, int max); + /// No description provided for @discuss. + /// + /// In en, this message translates to: + /// **'Discuss'** + String get discuss; + /// No description provided for @pollType. /// /// In en, this message translates to: @@ -1311,6 +1347,18 @@ abstract class L10n { /// **'Chat permissions'** String get chatPermissions; + /// No description provided for @chatThreads. + /// + /// In en, this message translates to: + /// **'Threads'** + String get chatThreads; + + /// No description provided for @chatThreadsDescription. + /// + /// In en, this message translates to: + /// **'See all threads in this room'** + String get chatThreadsDescription; + /// No description provided for @editDisplayname. /// /// In en, this message translates to: @@ -4353,6 +4401,18 @@ abstract class L10n { /// **'Restricted'** String get restricted; + /// No description provided for @spaceMemberOf. + /// + /// In en, this message translates to: + /// **'Space member of {spaces}'** + String spaceMemberOf(String spaces); + + /// No description provided for @spaceMemberOfCanKnock. + /// + /// In en, this message translates to: + /// **'Space member of {spaces} can knock'** + String spaceMemberOfCanKnock(String spaces); + /// No description provided for @knockRestricted. /// /// In en, this message translates to: diff --git a/lib/generated/l10n/l10n_ar.dart b/lib/generated/l10n/l10n_ar.dart index 6080152..0efcb6d 100644 --- a/lib/generated/l10n/l10n_ar.dart +++ b/lib/generated/l10n/l10n_ar.dart @@ -24,6 +24,22 @@ class L10nAr extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nAr extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -656,6 +675,12 @@ class L10nAr extends L10n { @override String get chatPermissions => 'صلاحيات المحادثة'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'حرر الاسم العلني'; @@ -2406,6 +2431,16 @@ class L10nAr extends L10n { @override String get restricted => 'مقيد'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'قيود النقر'; diff --git a/lib/generated/l10n/l10n_be.dart b/lib/generated/l10n/l10n_be.dart index a58e901..9235913 100644 --- a/lib/generated/l10n/l10n_be.dart +++ b/lib/generated/l10n/l10n_be.dart @@ -24,6 +24,22 @@ class L10nBe extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nBe extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -660,6 +679,12 @@ class L10nBe extends L10n { @override String get chatPermissions => 'Chat permissions'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Edit displayname'; @@ -2421,6 +2446,16 @@ class L10nBe extends L10n { @override String get restricted => 'Restricted'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Knock restricted'; diff --git a/lib/generated/l10n/l10n_bn.dart b/lib/generated/l10n/l10n_bn.dart index 28883f5..f5bfc69 100644 --- a/lib/generated/l10n/l10n_bn.dart +++ b/lib/generated/l10n/l10n_bn.dart @@ -24,6 +24,22 @@ class L10nBn extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nBn extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -660,6 +679,12 @@ class L10nBn extends L10n { @override String get chatPermissions => 'Chat permissions'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Edit displayname'; @@ -2421,6 +2446,16 @@ class L10nBn extends L10n { @override String get restricted => 'Restricted'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Knock restricted'; diff --git a/lib/generated/l10n/l10n_bo.dart b/lib/generated/l10n/l10n_bo.dart index df8ca51..badf080 100644 --- a/lib/generated/l10n/l10n_bo.dart +++ b/lib/generated/l10n/l10n_bo.dart @@ -24,6 +24,22 @@ class L10nBo extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nBo extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -660,6 +679,12 @@ class L10nBo extends L10n { @override String get chatPermissions => 'Chat permissions'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Edit displayname'; @@ -2421,6 +2446,16 @@ class L10nBo extends L10n { @override String get restricted => 'Restricted'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Knock restricted'; diff --git a/lib/generated/l10n/l10n_ca.dart b/lib/generated/l10n/l10n_ca.dart index 7a202db..6969bc9 100644 --- a/lib/generated/l10n/l10n_ca.dart +++ b/lib/generated/l10n/l10n_ca.dart @@ -24,6 +24,22 @@ class L10nCa extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nCa extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -665,6 +684,12 @@ class L10nCa extends L10n { @override String get chatPermissions => 'Permisos del xat'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Edita l\'àlies'; @@ -2445,6 +2470,16 @@ class L10nCa extends L10n { @override String get restricted => 'Restringit'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'No es pot picar a la porta'; diff --git a/lib/generated/l10n/l10n_cs.dart b/lib/generated/l10n/l10n_cs.dart index 3fc13e6..68d9c23 100644 --- a/lib/generated/l10n/l10n_cs.dart +++ b/lib/generated/l10n/l10n_cs.dart @@ -24,6 +24,22 @@ class L10nCs extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nCs extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -664,6 +683,12 @@ class L10nCs extends L10n { @override String get chatPermissions => 'Oprávnění konverzace'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Změnit přezdívku'; @@ -2425,6 +2450,16 @@ class L10nCs extends L10n { @override String get restricted => 'Restricted'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Knock restricted'; diff --git a/lib/generated/l10n/l10n_de.dart b/lib/generated/l10n/l10n_de.dart index 0dfe099..5fc9004 100644 --- a/lib/generated/l10n/l10n_de.dart +++ b/lib/generated/l10n/l10n_de.dart @@ -24,6 +24,22 @@ class L10nDe extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nDe extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -666,6 +685,12 @@ class L10nDe extends L10n { @override String get chatPermissions => 'Chatberechtigungen'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Anzeigename ändern'; @@ -2440,6 +2465,16 @@ class L10nDe extends L10n { @override String get restricted => 'Beschränkt'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Anklopfen beschränkt'; diff --git a/lib/generated/l10n/l10n_el.dart b/lib/generated/l10n/l10n_el.dart index e62f767..2c784d1 100644 --- a/lib/generated/l10n/l10n_el.dart +++ b/lib/generated/l10n/l10n_el.dart @@ -24,6 +24,22 @@ class L10nEl extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nEl extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -661,6 +680,12 @@ class L10nEl extends L10n { @override String get chatPermissions => 'Chat permissions'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Edit displayname'; @@ -2422,6 +2447,16 @@ class L10nEl extends L10n { @override String get restricted => 'Restricted'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Knock restricted'; diff --git a/lib/generated/l10n/l10n_en.dart b/lib/generated/l10n/l10n_en.dart index f6b2d1f..b1f3b69 100644 --- a/lib/generated/l10n/l10n_en.dart +++ b/lib/generated/l10n/l10n_en.dart @@ -24,6 +24,22 @@ class L10nEn extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nEn extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -660,6 +679,12 @@ class L10nEn extends L10n { @override String get chatPermissions => 'Chat permissions'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Edit displayname'; @@ -2421,6 +2446,16 @@ class L10nEn extends L10n { @override String get restricted => 'Restricted'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Knock restricted'; diff --git a/lib/generated/l10n/l10n_eo.dart b/lib/generated/l10n/l10n_eo.dart index f9194f8..30939c7 100644 --- a/lib/generated/l10n/l10n_eo.dart +++ b/lib/generated/l10n/l10n_eo.dart @@ -24,6 +24,22 @@ class L10nEo extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nEo extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -663,6 +682,12 @@ class L10nEo extends L10n { @override String get chatPermissions => 'Chat permissions'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Redakti prezentan nomon'; @@ -2428,6 +2453,16 @@ class L10nEo extends L10n { @override String get restricted => 'Restricted'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Knock restricted'; diff --git a/lib/generated/l10n/l10n_es.dart b/lib/generated/l10n/l10n_es.dart index 330336c..9aaa079 100644 --- a/lib/generated/l10n/l10n_es.dart +++ b/lib/generated/l10n/l10n_es.dart @@ -24,6 +24,22 @@ class L10nEs extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nEs extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -664,6 +683,12 @@ class L10nEs extends L10n { @override String get chatPermissions => 'Permisos del chat'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Editar nombre visible'; @@ -2444,6 +2469,16 @@ class L10nEs extends L10n { @override String get restricted => 'Restringido'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Aviso restringido'; diff --git a/lib/generated/l10n/l10n_et.dart b/lib/generated/l10n/l10n_et.dart index 1f69070..3176483 100644 --- a/lib/generated/l10n/l10n_et.dart +++ b/lib/generated/l10n/l10n_et.dart @@ -24,6 +24,22 @@ class L10nEt extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nEt extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -662,6 +681,12 @@ class L10nEt extends L10n { @override String get chatPermissions => 'Vestluse õigused'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Muuda kuvatavat nime'; @@ -2439,6 +2464,16 @@ class L10nEt extends L10n { @override String get restricted => 'Piiratud'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Koputa piiratud ligipääsuga jututoa uksele'; diff --git a/lib/generated/l10n/l10n_eu.dart b/lib/generated/l10n/l10n_eu.dart index 52e8d1d..1f359ea 100644 --- a/lib/generated/l10n/l10n_eu.dart +++ b/lib/generated/l10n/l10n_eu.dart @@ -24,6 +24,22 @@ class L10nEu extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nEu extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -663,6 +682,12 @@ class L10nEu extends L10n { @override String get chatPermissions => 'Txataren baimenak'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Editatu ezizena'; @@ -2431,6 +2456,16 @@ class L10nEu extends L10n { @override String get restricted => 'Mugatuta'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Eskatu baimena sarrera mugatua duen txatean'; diff --git a/lib/generated/l10n/l10n_fa.dart b/lib/generated/l10n/l10n_fa.dart index afb64a9..d8474a0 100644 --- a/lib/generated/l10n/l10n_fa.dart +++ b/lib/generated/l10n/l10n_fa.dart @@ -24,6 +24,22 @@ class L10nFa extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nFa extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -658,6 +677,12 @@ class L10nFa extends L10n { @override String get chatPermissions => 'Chat permissions'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'ویرایش نام نمایشی'; @@ -2424,6 +2449,16 @@ class L10nFa extends L10n { @override String get restricted => 'Restricted'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Knock restricted'; diff --git a/lib/generated/l10n/l10n_fi.dart b/lib/generated/l10n/l10n_fi.dart index c71cbe5..b2e0e15 100644 --- a/lib/generated/l10n/l10n_fi.dart +++ b/lib/generated/l10n/l10n_fi.dart @@ -24,6 +24,22 @@ class L10nFi extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nFi extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -665,6 +684,12 @@ class L10nFi extends L10n { @override String get chatPermissions => 'Keskustelun oikeudet'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Muokkaa näyttönimeä'; @@ -2430,6 +2455,16 @@ class L10nFi extends L10n { @override String get restricted => 'Restricted'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Knock restricted'; diff --git a/lib/generated/l10n/l10n_fil.dart b/lib/generated/l10n/l10n_fil.dart index 1429a9c..04872fd 100644 --- a/lib/generated/l10n/l10n_fil.dart +++ b/lib/generated/l10n/l10n_fil.dart @@ -24,6 +24,22 @@ class L10nFil extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nFil extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -666,6 +685,12 @@ class L10nFil extends L10n { @override String get chatPermissions => 'Mga pahintulot ng chat'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'I-edit ang display name'; @@ -2427,6 +2452,16 @@ class L10nFil extends L10n { @override String get restricted => 'Restricted'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Knock restricted'; diff --git a/lib/generated/l10n/l10n_fr.dart b/lib/generated/l10n/l10n_fr.dart index 5602fbf..32308c7 100644 --- a/lib/generated/l10n/l10n_fr.dart +++ b/lib/generated/l10n/l10n_fr.dart @@ -24,6 +24,22 @@ class L10nFr extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nFr extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -669,6 +688,12 @@ class L10nFr extends L10n { @override String get chatPermissions => 'Permissions du salon'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Changer de nom d\'affichage'; @@ -2465,6 +2490,16 @@ class L10nFr extends L10n { @override String get restricted => 'Limité'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Frapper à la porte limité'; diff --git a/lib/generated/l10n/l10n_ga.dart b/lib/generated/l10n/l10n_ga.dart index 131fcd2..7ed95c9 100644 --- a/lib/generated/l10n/l10n_ga.dart +++ b/lib/generated/l10n/l10n_ga.dart @@ -24,6 +24,22 @@ class L10nGa extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nGa extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -668,6 +687,12 @@ class L10nGa extends L10n { @override String get chatPermissions => 'Ceadanna comhrá'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Cuir ainm taispeána in eagar'; @@ -2450,6 +2475,16 @@ class L10nGa extends L10n { @override String get restricted => 'Srianta'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Cnoc Mhuire srianta'; diff --git a/lib/generated/l10n/l10n_gl.dart b/lib/generated/l10n/l10n_gl.dart index 01ae489..5f5eed9 100644 --- a/lib/generated/l10n/l10n_gl.dart +++ b/lib/generated/l10n/l10n_gl.dart @@ -24,6 +24,22 @@ class L10nGl extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nGl extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -663,6 +682,12 @@ class L10nGl extends L10n { @override String get chatPermissions => 'Permisos da conversa'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Editar nome público'; @@ -2433,6 +2458,16 @@ class L10nGl extends L10n { @override String get restricted => 'Non accesible'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Peta á porta'; diff --git a/lib/generated/l10n/l10n_he.dart b/lib/generated/l10n/l10n_he.dart index 7db68b8..39eb2cb 100644 --- a/lib/generated/l10n/l10n_he.dart +++ b/lib/generated/l10n/l10n_he.dart @@ -24,6 +24,22 @@ class L10nHe extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nHe extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -658,6 +677,12 @@ class L10nHe extends L10n { @override String get chatPermissions => 'Chat permissions'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'ערוך את שם התצוגה'; @@ -2416,6 +2441,16 @@ class L10nHe extends L10n { @override String get restricted => 'Restricted'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Knock restricted'; diff --git a/lib/generated/l10n/l10n_hi.dart b/lib/generated/l10n/l10n_hi.dart index bf68016..50a282c 100644 --- a/lib/generated/l10n/l10n_hi.dart +++ b/lib/generated/l10n/l10n_hi.dart @@ -24,6 +24,22 @@ class L10nHi extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nHi extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -660,6 +679,12 @@ class L10nHi extends L10n { @override String get chatPermissions => 'Chat permissions'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Edit displayname'; @@ -2421,6 +2446,16 @@ class L10nHi extends L10n { @override String get restricted => 'Restricted'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Knock restricted'; diff --git a/lib/generated/l10n/l10n_hr.dart b/lib/generated/l10n/l10n_hr.dart index 1e8145d..6af812d 100644 --- a/lib/generated/l10n/l10n_hr.dart +++ b/lib/generated/l10n/l10n_hr.dart @@ -24,6 +24,22 @@ class L10nHr extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nHr extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -661,6 +680,12 @@ class L10nHr extends L10n { @override String get chatPermissions => 'Dozvole za razgovor'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Uredi prikazano ime'; @@ -2430,6 +2455,16 @@ class L10nHr extends L10n { @override String get restricted => 'Ograničeni'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Pokucaj na ograničene sobe'; diff --git a/lib/generated/l10n/l10n_hu.dart b/lib/generated/l10n/l10n_hu.dart index 410e9e0..b7e15e2 100644 --- a/lib/generated/l10n/l10n_hu.dart +++ b/lib/generated/l10n/l10n_hu.dart @@ -24,6 +24,22 @@ class L10nHu extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nHu extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -663,6 +682,12 @@ class L10nHu extends L10n { @override String get chatPermissions => 'Csevegés engedélyek'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Megjelenítési név szerkesztése'; @@ -2431,6 +2456,16 @@ class L10nHu extends L10n { @override String get restricted => 'Korlátozott'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Kopogás korlátozva'; diff --git a/lib/generated/l10n/l10n_ia.dart b/lib/generated/l10n/l10n_ia.dart index 5be8d93..8fcafa0 100644 --- a/lib/generated/l10n/l10n_ia.dart +++ b/lib/generated/l10n/l10n_ia.dart @@ -24,6 +24,22 @@ class L10nIa extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nIa extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -660,6 +679,12 @@ class L10nIa extends L10n { @override String get chatPermissions => 'Chat permissions'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Edit displayname'; @@ -2421,6 +2446,16 @@ class L10nIa extends L10n { @override String get restricted => 'Restricted'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Knock restricted'; diff --git a/lib/generated/l10n/l10n_id.dart b/lib/generated/l10n/l10n_id.dart index 7075f65..3968737 100644 --- a/lib/generated/l10n/l10n_id.dart +++ b/lib/generated/l10n/l10n_id.dart @@ -24,6 +24,22 @@ class L10nId extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nId extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -667,6 +686,12 @@ class L10nId extends L10n { @override String get chatPermissions => 'Perizinan obrolan'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Edit nama tampilan'; @@ -2438,6 +2463,16 @@ class L10nId extends L10n { @override String get restricted => 'Dibatasi'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Ketukan dibatasi'; diff --git a/lib/generated/l10n/l10n_ie.dart b/lib/generated/l10n/l10n_ie.dart index e455c13..4a06704 100644 --- a/lib/generated/l10n/l10n_ie.dart +++ b/lib/generated/l10n/l10n_ie.dart @@ -24,6 +24,22 @@ class L10nIe extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nIe extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -659,6 +678,12 @@ class L10nIe extends L10n { @override String get chatPermissions => 'Chat permissions'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Redacter li visibil nómine'; @@ -2420,6 +2445,16 @@ class L10nIe extends L10n { @override String get restricted => 'Restricted'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Knock restricted'; diff --git a/lib/generated/l10n/l10n_it.dart b/lib/generated/l10n/l10n_it.dart index 3edf595..9012c46 100644 --- a/lib/generated/l10n/l10n_it.dart +++ b/lib/generated/l10n/l10n_it.dart @@ -24,6 +24,22 @@ class L10nIt extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nIt extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -664,6 +683,12 @@ class L10nIt extends L10n { @override String get chatPermissions => 'Permessi della chat'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Modifica il nominativo'; @@ -2445,6 +2470,16 @@ class L10nIt extends L10n { @override String get restricted => 'Limitato'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Limitato al bussare'; diff --git a/lib/generated/l10n/l10n_ja.dart b/lib/generated/l10n/l10n_ja.dart index 5c0b0b9..82bca14 100644 --- a/lib/generated/l10n/l10n_ja.dart +++ b/lib/generated/l10n/l10n_ja.dart @@ -24,6 +24,22 @@ class L10nJa extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nJa extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -651,6 +670,12 @@ class L10nJa extends L10n { @override String get chatPermissions => 'Chat permissions'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => '表示名を編集'; @@ -2390,6 +2415,16 @@ class L10nJa extends L10n { @override String get restricted => 'Restricted'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Knock restricted'; diff --git a/lib/generated/l10n/l10n_ka.dart b/lib/generated/l10n/l10n_ka.dart index b14cf51..477fe69 100644 --- a/lib/generated/l10n/l10n_ka.dart +++ b/lib/generated/l10n/l10n_ka.dart @@ -24,6 +24,22 @@ class L10nKa extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nKa extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -662,6 +681,12 @@ class L10nKa extends L10n { @override String get chatPermissions => 'ჩატის უფლებები'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'ნაჩვენები სახელის შეცვლა'; @@ -2423,6 +2448,16 @@ class L10nKa extends L10n { @override String get restricted => 'Restricted'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Knock restricted'; diff --git a/lib/generated/l10n/l10n_ko.dart b/lib/generated/l10n/l10n_ko.dart index 987d15b..47bf4a0 100644 --- a/lib/generated/l10n/l10n_ko.dart +++ b/lib/generated/l10n/l10n_ko.dart @@ -24,6 +24,22 @@ class L10nKo extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nKo extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -649,6 +668,12 @@ class L10nKo extends L10n { @override String get chatPermissions => '채팅 권한'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => '표시 이름 수정'; @@ -2372,6 +2397,16 @@ class L10nKo extends L10n { @override String get restricted => '스페이스 멤버로 제한'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => '스페이스 멤버만 참가 요청 가능'; diff --git a/lib/generated/l10n/l10n_lt.dart b/lib/generated/l10n/l10n_lt.dart index ac83bb7..028443b 100644 --- a/lib/generated/l10n/l10n_lt.dart +++ b/lib/generated/l10n/l10n_lt.dart @@ -24,6 +24,22 @@ class L10nLt extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nLt extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -662,6 +681,12 @@ class L10nLt extends L10n { @override String get chatPermissions => 'Chat permissions'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Redaguoti rodomą vardą'; @@ -2427,6 +2452,16 @@ class L10nLt extends L10n { @override String get restricted => 'Restricted'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Knock restricted'; diff --git a/lib/generated/l10n/l10n_lv.dart b/lib/generated/l10n/l10n_lv.dart index 1c846be..c106c8f 100644 --- a/lib/generated/l10n/l10n_lv.dart +++ b/lib/generated/l10n/l10n_lv.dart @@ -24,6 +24,22 @@ class L10nLv extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nLv extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -666,6 +685,12 @@ class L10nLv extends L10n { @override String get chatPermissions => 'Tērzēšanas atļaujas'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Labot attēlojamo vārdu'; @@ -2438,6 +2463,16 @@ class L10nLv extends L10n { @override String get restricted => 'Ierobežots'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Pieklauvēt ierobežotajiem'; diff --git a/lib/generated/l10n/l10n_nb.dart b/lib/generated/l10n/l10n_nb.dart index 46d562b..eb42c63 100644 --- a/lib/generated/l10n/l10n_nb.dart +++ b/lib/generated/l10n/l10n_nb.dart @@ -24,6 +24,22 @@ class L10nNb extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nNb extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -660,6 +679,12 @@ class L10nNb extends L10n { @override String get chatPermissions => 'Chat permissions'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Rediger visningsnavn'; @@ -2424,6 +2449,16 @@ class L10nNb extends L10n { @override String get restricted => 'Restricted'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Knock restricted'; diff --git a/lib/generated/l10n/l10n_nl.dart b/lib/generated/l10n/l10n_nl.dart index 515d851..0162add 100644 --- a/lib/generated/l10n/l10n_nl.dart +++ b/lib/generated/l10n/l10n_nl.dart @@ -24,6 +24,22 @@ class L10nNl extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nNl extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -662,6 +681,12 @@ class L10nNl extends L10n { @override String get chatPermissions => 'Chat toestemmingen'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Naam wijzigen'; @@ -2427,6 +2452,16 @@ class L10nNl extends L10n { @override String get restricted => 'Beperkt'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Kloppen is beperkt'; diff --git a/lib/generated/l10n/l10n_pl.dart b/lib/generated/l10n/l10n_pl.dart index bd7c434..7222ce9 100644 --- a/lib/generated/l10n/l10n_pl.dart +++ b/lib/generated/l10n/l10n_pl.dart @@ -24,6 +24,22 @@ class L10nPl extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nPl extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -663,6 +682,12 @@ class L10nPl extends L10n { @override String get chatPermissions => 'Uprawnienia w czacie'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Edytuj nazwę wyświetlaną'; @@ -2437,6 +2462,16 @@ class L10nPl extends L10n { @override String get restricted => 'Ograniczone'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Pukanie jest ograniczone'; diff --git a/lib/generated/l10n/l10n_pt.dart b/lib/generated/l10n/l10n_pt.dart index dec34b1..87f4ddd 100644 --- a/lib/generated/l10n/l10n_pt.dart +++ b/lib/generated/l10n/l10n_pt.dart @@ -24,6 +24,22 @@ class L10nPt extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nPt extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -660,6 +679,12 @@ class L10nPt extends L10n { @override String get chatPermissions => 'Chat permissions'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Edit displayname'; @@ -2421,6 +2446,16 @@ class L10nPt extends L10n { @override String get restricted => 'Restricted'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Knock restricted'; diff --git a/lib/generated/l10n/l10n_ro.dart b/lib/generated/l10n/l10n_ro.dart index bee47b5..4570862 100644 --- a/lib/generated/l10n/l10n_ro.dart +++ b/lib/generated/l10n/l10n_ro.dart @@ -24,6 +24,22 @@ class L10nRo extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nRo extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -665,6 +684,12 @@ class L10nRo extends L10n { @override String get chatPermissions => 'Chat permissions'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Schimbați displayname'; @@ -2438,6 +2463,16 @@ class L10nRo extends L10n { @override String get restricted => 'Restricted'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Knock restricted'; diff --git a/lib/generated/l10n/l10n_ru.dart b/lib/generated/l10n/l10n_ru.dart index 6185935..f7b4396 100644 --- a/lib/generated/l10n/l10n_ru.dart +++ b/lib/generated/l10n/l10n_ru.dart @@ -24,6 +24,22 @@ class L10nRu extends L10n { @override String get resume => 'Продолжить'; + @override + String get newSubSpace => 'Новое подпространство'; + + @override + String get moveToDifferentSpace => 'Перенести в другое пространство'; + + @override + String get moveUp => 'Вверх'; + + @override + String get moveDown => 'Вниз'; + + @override + String get removeFromSpaceDescription => + 'Эта комната будет удалена из пространства, но останется в Вашем списке чатов.'; + @override String get endPoll => 'Завершить опрос'; @@ -56,6 +72,9 @@ class L10nRu extends L10n { return 'Выбрано $selected из $max'; } + @override + String get discuss => 'Обсудить'; + @override String get pollType => 'Тип опроса'; @@ -88,7 +107,7 @@ class L10nRu extends L10n { @override String get cleanExifDescription => - 'Удалять метаданные EXIF (модель камеры, геолокация, время) из изображений еред отправкой.'; + 'Удалять метаданные EXIF (модель камеры, геолокация, время) из изображений перед отправкой.'; @override String get repeatPassword => 'Повторите пароль'; @@ -662,6 +681,12 @@ class L10nRu extends L10n { @override String get chatPermissions => 'Права в чате'; + @override + String get chatThreads => 'Обсуждения'; + + @override + String get chatThreadsDescription => 'Список всех обсуждений в этой комнате'; + @override String get editDisplayname => 'Отображаемое имя'; @@ -695,7 +720,7 @@ class L10nRu extends L10n { @override String get accessAndVisibilityDescription => - 'Кто может зайти и как найти этот чат.'; + 'Кто может зайти и как найти этот чат'; @override String get calls => 'Звонки'; @@ -2426,6 +2451,16 @@ class L10nRu extends L10n { @override String get restricted => 'Ограничено'; + @override + String spaceMemberOf(String spaces) { + return 'Участники пространства $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Участники пространства $spaces (по запросу)'; + } + @override String get knockRestricted => 'Ограничено + по запросу'; diff --git a/lib/generated/l10n/l10n_sk.dart b/lib/generated/l10n/l10n_sk.dart index ccf70fd..2187884 100644 --- a/lib/generated/l10n/l10n_sk.dart +++ b/lib/generated/l10n/l10n_sk.dart @@ -24,6 +24,22 @@ class L10nSk extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nSk extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -662,6 +681,12 @@ class L10nSk extends L10n { @override String get chatPermissions => 'Chat permissions'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Zmeniť prezývku'; @@ -2422,6 +2447,16 @@ class L10nSk extends L10n { @override String get restricted => 'Restricted'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Knock restricted'; diff --git a/lib/generated/l10n/l10n_sl.dart b/lib/generated/l10n/l10n_sl.dart index 77b0bb6..4566d42 100644 --- a/lib/generated/l10n/l10n_sl.dart +++ b/lib/generated/l10n/l10n_sl.dart @@ -24,6 +24,22 @@ class L10nSl extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nSl extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -664,6 +683,12 @@ class L10nSl extends L10n { @override String get chatPermissions => 'Chat permissions'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Edit displayname'; @@ -2425,6 +2450,16 @@ class L10nSl extends L10n { @override String get restricted => 'Restricted'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Knock restricted'; diff --git a/lib/generated/l10n/l10n_sr.dart b/lib/generated/l10n/l10n_sr.dart index 9dcd3ed..175d670 100644 --- a/lib/generated/l10n/l10n_sr.dart +++ b/lib/generated/l10n/l10n_sr.dart @@ -24,6 +24,22 @@ class L10nSr extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nSr extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -660,6 +679,12 @@ class L10nSr extends L10n { @override String get chatPermissions => 'Chat permissions'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Уреди име за приказ'; @@ -2417,6 +2442,16 @@ class L10nSr extends L10n { @override String get restricted => 'Restricted'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Knock restricted'; diff --git a/lib/generated/l10n/l10n_sv.dart b/lib/generated/l10n/l10n_sv.dart index 114a14c..761f2f1 100644 --- a/lib/generated/l10n/l10n_sv.dart +++ b/lib/generated/l10n/l10n_sv.dart @@ -24,6 +24,22 @@ class L10nSv extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nSv extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -661,6 +680,12 @@ class L10nSv extends L10n { @override String get chatPermissions => 'Chatt-behörigheter'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Ändra visningsnamn'; @@ -2431,6 +2456,16 @@ class L10nSv extends L10n { @override String get restricted => 'Restricted'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Knock restricted'; diff --git a/lib/generated/l10n/l10n_ta.dart b/lib/generated/l10n/l10n_ta.dart index 23b7602..d07856d 100644 --- a/lib/generated/l10n/l10n_ta.dart +++ b/lib/generated/l10n/l10n_ta.dart @@ -24,6 +24,22 @@ class L10nTa extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nTa extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -670,6 +689,12 @@ class L10nTa extends L10n { @override String get chatPermissions => 'அரட்டை அனுமதிகள்'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'காட்சி பெயர் திருத்து'; @@ -2452,6 +2477,16 @@ class L10nTa extends L10n { @override String get restricted => 'தடைசெய்யப்பட்டது'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'நாக் தடை'; diff --git a/lib/generated/l10n/l10n_te.dart b/lib/generated/l10n/l10n_te.dart index 394e118..bbeeaac 100644 --- a/lib/generated/l10n/l10n_te.dart +++ b/lib/generated/l10n/l10n_te.dart @@ -24,6 +24,22 @@ class L10nTe extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nTe extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -660,6 +679,12 @@ class L10nTe extends L10n { @override String get chatPermissions => 'Chat permissions'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Edit displayname'; @@ -2421,6 +2446,16 @@ class L10nTe extends L10n { @override String get restricted => 'Restricted'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Knock restricted'; diff --git a/lib/generated/l10n/l10n_th.dart b/lib/generated/l10n/l10n_th.dart index 60b3fb7..71c7a29 100644 --- a/lib/generated/l10n/l10n_th.dart +++ b/lib/generated/l10n/l10n_th.dart @@ -24,6 +24,22 @@ class L10nTh extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nTh extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -662,6 +681,12 @@ class L10nTh extends L10n { @override String get chatPermissions => 'Chat permissions'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Edit displayname'; @@ -2423,6 +2448,16 @@ class L10nTh extends L10n { @override String get restricted => 'Restricted'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Knock restricted'; diff --git a/lib/generated/l10n/l10n_tr.dart b/lib/generated/l10n/l10n_tr.dart index b3672ae..e503551 100644 --- a/lib/generated/l10n/l10n_tr.dart +++ b/lib/generated/l10n/l10n_tr.dart @@ -24,6 +24,22 @@ class L10nTr extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nTr extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -662,6 +681,12 @@ class L10nTr extends L10n { @override String get chatPermissions => 'Sohbet izinleri'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Görünen adı düzenle'; @@ -2431,6 +2456,16 @@ class L10nTr extends L10n { @override String get restricted => 'Kısıtlı'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Tıklatma kısıtlı'; diff --git a/lib/generated/l10n/l10n_uk.dart b/lib/generated/l10n/l10n_uk.dart index 94f5d9d..9a59c4e 100644 --- a/lib/generated/l10n/l10n_uk.dart +++ b/lib/generated/l10n/l10n_uk.dart @@ -24,6 +24,22 @@ class L10nUk extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nUk extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -664,6 +683,12 @@ class L10nUk extends L10n { @override String get chatPermissions => 'Дозволи бесіди'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Змінити показуване ім\'я'; @@ -2434,6 +2459,16 @@ class L10nUk extends L10n { @override String get restricted => 'Обмежено'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Стук обмежено'; diff --git a/lib/generated/l10n/l10n_vi.dart b/lib/generated/l10n/l10n_vi.dart index be90270..c4058c8 100644 --- a/lib/generated/l10n/l10n_vi.dart +++ b/lib/generated/l10n/l10n_vi.dart @@ -24,6 +24,22 @@ class L10nVi extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nVi extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -660,6 +679,12 @@ class L10nVi extends L10n { @override String get chatPermissions => 'Chat permissions'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => 'Sửa tên hiển thị'; @@ -2420,6 +2445,16 @@ class L10nVi extends L10n { @override String get restricted => 'Bị hạn chế'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => 'Knock restricted'; diff --git a/lib/generated/l10n/l10n_zh.dart b/lib/generated/l10n/l10n_zh.dart index 3bdf92f..87ea9b5 100644 --- a/lib/generated/l10n/l10n_zh.dart +++ b/lib/generated/l10n/l10n_zh.dart @@ -24,6 +24,22 @@ class L10nZh extends L10n { @override String get resume => 'Resume'; + @override + String get newSubSpace => 'New sub space'; + + @override + String get moveToDifferentSpace => 'Move to different space'; + + @override + String get moveUp => 'Move up'; + + @override + String get moveDown => 'Move down'; + + @override + String get removeFromSpaceDescription => + 'The chat will be removed from the space but still appear in your chat list.'; + @override String get endPoll => 'End poll'; @@ -56,6 +72,9 @@ class L10nZh extends L10n { return '$selected of $max selected'; } + @override + String get discuss => 'Discuss'; + @override String get pollType => 'Poll type'; @@ -644,6 +663,12 @@ class L10nZh extends L10n { @override String get chatPermissions => '聊天权限'; + @override + String get chatThreads => 'Threads'; + + @override + String get chatThreadsDescription => 'See all threads in this room'; + @override String get editDisplayname => '编辑昵称'; @@ -2346,6 +2371,16 @@ class L10nZh extends L10n { @override String get restricted => '受限'; + @override + String spaceMemberOf(String spaces) { + return 'Space member of $spaces'; + } + + @override + String spaceMemberOfCanKnock(String spaces) { + return 'Space member of $spaces can knock'; + } + @override String get knockRestricted => '“请求加入”请求受限'; diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 84d63ff..b6b09eb 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -51,12 +51,14 @@ class ChatPage extends StatelessWidget { final String roomId; final List? shareItems; final String? eventId; + final bool? showThreadRoots; const ChatPage({ super.key, required this.roomId, this.eventId, this.shareItems, + this.showThreadRoots, }); @override @@ -79,20 +81,25 @@ class ChatPage extends StatelessWidget { room: room, shareItems: shareItems, eventId: eventId, + showThreadRoots: showThreadRoots, ); } } class ChatPageWithRoom extends StatefulWidget { final Room room; + final Thread? thread; final List? shareItems; final String? eventId; + final bool? showThreadRoots; const ChatPageWithRoom({ super.key, required this.room, + this.thread, this.shareItems, this.eventId, + this.showThreadRoots, }); @override @@ -102,6 +109,10 @@ class ChatPageWithRoom extends StatefulWidget { class ChatController extends State with WidgetsBindingObserver { Room get room => sendingClient.getRoomById(roomId) ?? widget.room; + bool get showThreadRoots => (widget.showThreadRoots ?? false); + Thread? get thread => + sendingClient.getRoomById(roomId)?.threads[threadRootEventId] ?? + widget.room.threads[threadRootEventId]; late Client sendingClient; @@ -110,6 +121,7 @@ class ChatController extends State late final String readMarkerEventId; String get roomId => widget.room.id; + String? get threadRootEventId => widget.thread?.rootEvent.eventId; final AutoScrollController scrollController = AutoScrollController(); @@ -134,6 +146,7 @@ class ChatController extends State builder: (c) => SendFileDialog( files: details.files, room: room, + thread: thread, replyEvent: replyEvent, outerContext: context, ), @@ -274,6 +287,7 @@ class ChatController extends State builder: (c) => SendFileDialog( files: files, room: room, + thread: thread, outerContext: context, replyEvent: replyEvent, ), @@ -317,11 +331,14 @@ class ChatController extends State if (kIsWeb) { onFocusSub = html.window.onFocus.listen((_) => setReadMarker()); } + + _getThreads(); } void _tryLoadTimeline() async { final initialEventId = widget.eventId; loadTimelineFuture = _getTimeline(); + Logs().v("Trying to load timeline..."); try { await loadTimelineFuture; if (initialEventId != null) scrollToEventId(initialEventId); @@ -369,13 +386,32 @@ class ChatController extends State scrollUpBannerEventId = eventId; }); - void updateView() { + Future updateView() async { if (!mounted) return; setReadMarker(); + await updateThreads(); setState(() {}); } + Future updateThreads() async { + if (timeline?.events == null) return; + final lastEvent = timeline?.events[timeline!.events.length - 1]; + + if (lastEvent == null) return; + if (lastEvent.relationshipType == RelationshipTypes.thread && + lastEvent.relationshipEventId != null) { + final thread = await room.client.database + .getThread(room.id, lastEvent.relationshipEventId!, room.client); + if (thread != null) { + setState(() { + threads?[lastEvent.eventId] = thread; + }); + } + } + } + Future? loadTimelineFuture; + Map? threads = {}; int? animateInEventIndex; @@ -384,15 +420,7 @@ class ChatController extends State animateInEventIndex = i; } - Future _getTimeline({ - String? eventContextId, - }) async { - await Matrix.of(context).client.roomsLoading; - await Matrix.of(context).client.accountDataLoading; - if (eventContextId != null && - (!eventContextId.isValidMatrixId || eventContextId.sigil != '\$')) { - eventContextId = null; - } + Future _loadRoomTimeline({String? eventContextId}) async { try { timeline?.cancelSubscriptions(); timeline = await room.getTimeline( @@ -412,12 +440,72 @@ class ChatController extends State _showScrollUpMaterialBanner(eventContextId!); } } + } + + Future _loadThreadTimeline({String? eventContextId}) async { + if (thread == null) { + throw Exception( + "_loadThreadTimeline should not be called, thread == null", + ); + } + try { + timeline?.cancelSubscriptions(); + timeline = await thread!.getTimeline( + onUpdate: updateView, + eventContextId: eventContextId, + onInsert: onInsert, + ); + Logs().v("Thread timeline loaded"); + } catch (e, s) { + Logs().w( + 'Unable to load timeline on event ID $eventContextId (in thread)', + e, + s, + ); + if (!mounted) return; + timeline = await thread!.getTimeline( + onUpdate: updateView, + onInsert: onInsert, + ); + if (!mounted) return; + if (e is TimeoutException || e is IOException) { + _showScrollUpMaterialBanner(eventContextId!); + } + } + if (timeline is ThreadTimeline) { + (timeline as ThreadTimeline).getThreadEvents(); + } + } + + Future _getTimeline({ + String? eventContextId, + }) async { + await Matrix.of(context).client.roomsLoading; + await Matrix.of(context).client.accountDataLoading; + if (eventContextId != null && + (!eventContextId.isValidMatrixId || eventContextId.sigil != '\$')) { + eventContextId = null; + } + if (thread == null) { + await _loadRoomTimeline(eventContextId: eventContextId); + } else { + await _loadThreadTimeline(eventContextId: eventContextId); + } timeline!.requestKeys(onlineKeyBackupOnly: false); if (room.markedUnread) room.markUnread(false); return; } + Future _getThreads() async { + try { + threads = await room.getThreads(); + Logs().w('Thread amount: ${threads?.length}'); + } catch (e, s) { + Logs().w('Unable to load threads in $roomId', e, s); + } + } + String? scrollToEventIdMarker; @override @@ -459,9 +547,13 @@ class ChatController extends State .then((_) { _setReadMarkerFuture = null; }); - if (eventId == null || eventId == timeline.room.lastEvent?.eventId) { - Matrix.of(context).backgroundPush?.cancelNotification(roomId); + + if (timeline is RoomTimeline) { + if (eventId == null || eventId == timeline.room.lastEvent?.eventId) { + Matrix.of(context).backgroundPush?.cancelNotification(roomId); + } } + // TODO same for Threads } @override @@ -533,6 +625,9 @@ class ChatController extends State inReplyTo: replyEvent, editEventId: editEvent?.eventId, parseCommands: parseCommands, + threadRootEventId: thread?.rootEvent.eventId, + threadLastEventId: + thread?.lastEvent?.eventId ?? thread?.rootEvent.eventId, ); sendController.value = TextEditingValue( text: pendingText, @@ -550,8 +645,13 @@ class ChatController extends State void sendPollAction() async { await showAdaptiveDialog( - context: context, - builder: (c) => SendPollDialog(room: room, outerContext: context)); + context: context, + builder: (c) => SendPollDialog( + room: room, + thread: thread, + outerContext: context, + ), + ); replyEvent = null; } @@ -570,6 +670,7 @@ class ChatController extends State builder: (c) => SendFileDialog( files: files, room: room, + thread: thread, outerContext: context, replyEvent: replyEvent, ), @@ -584,6 +685,7 @@ class ChatController extends State builder: (c) => SendFileDialog( files: [XFile.fromData(image)], room: room, + thread: thread, outerContext: context, ), ); @@ -600,6 +702,7 @@ class ChatController extends State builder: (c) => SendFileDialog( files: [file], room: room, + thread: thread, outerContext: context, ), ); @@ -619,6 +722,7 @@ class ChatController extends State builder: (c) => SendFileDialog( files: [file], room: room, + thread: thread, outerContext: context, ), ); @@ -645,7 +749,8 @@ class ChatController extends State name: fileName ?? audioFile.path, ); - await room.sendFileEvent( + await room + .sendFileEvent( file, inReplyTo: replyEvent, extraContent: { @@ -659,7 +764,10 @@ class ChatController extends State 'waveform': waveform, }, }, - ).catchError((e) { + threadLastEventId: thread?.lastEvent?.eventId, + threadRootEventId: thread?.rootEvent.eventId, + ) + .catchError((e) { scaffoldMessenger.showSnackBar( SnackBar( content: Text( @@ -698,7 +806,10 @@ class ChatController extends State void sendLocationAction() async { await showAdaptiveDialog( context: context, - builder: (c) => SendLocationDialog(room: room), + builder: (c) => SendLocationDialog( + room: room, + thread: thread, + ), ); } @@ -744,7 +855,8 @@ class ChatController extends State final reports = await mx.client.getEventReports(); final report = reports.firstWhere( - (rep) => rep['room_id'] == roomId && rep['event_id'] == event.eventId); + (rep) => rep['room_id'] == roomId && rep['event_id'] == event.eventId, + ); final recoveredEvent = await mx.client.getReportedEvent(report['id']); if (recoveredEvent == null) { @@ -754,15 +866,17 @@ class ChatController extends State return; } - Navigator.of(context).push(new MaterialPageRoute( - builder: (BuildContext ctx) { - return RecoveredEventDialog( - event: recoveredEvent, - timeline: timeline!, - ); - }, - fullscreenDialog: true, - )); + Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext ctx) { + return RecoveredEventDialog( + event: recoveredEvent, + timeline: timeline!, + ); + }, + fullscreenDialog: true, + ), + ); } void translateEventAction() async { @@ -774,10 +888,12 @@ class ChatController extends State } final event = selectedEvents.single; var text = event.isRichMessage ? event.formattedText : event.text; - var content = {...event.content}; + final content = {...event.content}; try { text = await Translator.translate( - text, PlatformDispatcher.instance.locale.languageCode); + text, + PlatformDispatcher.instance.locale.languageCode, + ); } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(L10n.of(context).errorTranslatingMessage)), @@ -790,19 +906,24 @@ class ChatController extends State content['body'] = text; } content['xyz.extera.translated'] = true; - Navigator.of(context).push(new MaterialPageRoute( + Navigator.of(context).push( + MaterialPageRoute( builder: (BuildContext ctx) { return TranslatedEventDialog( - event: new Event( - content: content, - type: 'm.room.message', - eventId: event.eventId, - senderId: event.senderId, - originServerTs: event.originServerTs, - room: room), - timeline: timeline!); + event: Event( + content: content, + type: 'm.room.message', + eventId: event.eventId, + senderId: event.senderId, + originServerTs: event.originServerTs, + room: room, + ), + timeline: timeline!, + ); }, - fullscreenDialog: true)); + fullscreenDialog: true, + ), + ); } void reportEventAction() async { @@ -871,20 +992,43 @@ class ChatController extends State } } + void discussAction() async { + final event = selectedEvents.first; + if (!room.threads.containsKey(event.eventId)) { + room.threads[event.eventId] = Thread( + room: room, + rootEvent: event, + client: room.client, + currentUserParticipated: false, + count: 0, + highlightCount: 0, + notificationCount: 0, + ); + } + + context.go('/rooms/$roomId/threads/${event.eventId}'); + selectedEvents.clear(); + } + void endPollAction() async { final event = selectedEvents.first; - if (event == null) return; final client = currentRoomBundle.firstWhere( (cl) => selectedEvents.first.senderId == cl!.userID, orElse: () => null, ); if (client == null) return; - if (event.senderId != client!.userID) return; - await room.sendEvent({ - 'org.matrix.msc1767.text': 'Ended poll', - 'm.relates_to': {'rel_type': 'm.reference', 'event_id': event.eventId}, - 'body': 'Ended poll' - }, type: 'org.matrix.msc3381.poll.end'); + if (event.senderId != client.userID) return; + await room.sendEvent( + { + 'org.matrix.msc1767.text': 'Ended poll', + 'm.relates_to': { + 'rel_type': 'm.reference', + 'event_id': event.eventId, + }, + 'body': 'Ended poll', + }, + type: 'org.matrix.msc3381.poll.end', + ); } void redactEventsAction() async { diff --git a/lib/pages/chat/chat_app_bar_title.dart b/lib/pages/chat/chat_app_bar_title.dart index 1b11bd6..2a02699 100644 --- a/lib/pages/chat/chat_app_bar_title.dart +++ b/lib/pages/chat/chat_app_bar_title.dart @@ -31,22 +31,40 @@ class ChatAppBarTitle extends StatelessWidget { hoverColor: Colors.transparent, splashColor: Colors.transparent, highlightColor: Colors.transparent, - onTap: controller.isArchived - ? null - : () => FluffyThemes.isThreeColumnMode(context) - ? controller.toggleDisplayChatDetailsColumn() - : context.go('/rooms/${room.id}/details'), + onTap: () { + if (controller.thread != null) { + if (context.canPop()) { + context.pop(); + } else { + context.go('/rooms/${room.id}'); + } + return; + } + if (!controller.isArchived) { + if (FluffyThemes.isThreeColumnMode(context)) { + controller.toggleDisplayChatDetailsColumn(); + } else { + context.go('/rooms/${room.id}/details'); + } + } + }, child: Row( children: [ Hero( tag: 'content_banner', - child: Avatar( - mxContent: room.avatar, - name: room.getLocalizedDisplayname( - MatrixLocals(L10n.of(context)), - ), - size: 32, - ), + child: controller.thread == null + ? Avatar( + mxContent: room.avatar, + name: room.getLocalizedDisplayname( + MatrixLocals(L10n.of(context)), + ), + size: 32, + ) + : Icon( + Icons.chat_bubble_outline, + color: Colors.grey[200], + size: 20, + ), ), const SizedBox(width: 12), Expanded( @@ -54,7 +72,10 @@ class ChatAppBarTitle extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - room.getLocalizedDisplayname(MatrixLocals(L10n.of(context))), + controller.thread == null + ? room.getLocalizedDisplayname( + MatrixLocals(L10n.of(context))) + : '${controller.thread!.rootEvent.senderFromMemoryOrFallback.displayName ?? controller.thread!.rootEvent.senderId}: ${controller.thread!.rootEvent.text}', maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index 3fc2937..85b562e 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -15,10 +15,12 @@ import 'package:extera_next/utils/platform_infos.dart'; class ChatEventList extends StatelessWidget { final ChatController controller; - + final bool showThreadRoots; + const ChatEventList({ super.key, required this.controller, + this.showThreadRoots = false, }); @override @@ -37,8 +39,18 @@ class ChatEventList extends StatelessWidget { final horizontalPadding = FluffyThemes.isColumnMode(context) ? 8.0 : 0.0; - final events = timeline.events.filterByVisibleInGui(); + var events = timeline.events; + + if (showThreadRoots) { + events = events.filterThreadRoots(); + } else { + events = events.filterByThreaded(controller.thread != null); + } + + events = events.filterByVisibleInGui(); + final animateInEventIndex = controller.animateInEventIndex; + final threads = controller.room.threads; // create a map of eventId --> index to greatly improve performance of // ListView's findChildIndexCallback @@ -120,6 +132,10 @@ class ChatEventList extends StatelessWidget { final animateIn = animateInEventIndex != null && timeline.events.length > animateInEventIndex && event == timeline.events[animateInEventIndex]; + + final thread = threads.containsKey(event.eventId) + ? threads[event.eventId] + : null; return AutoScrollTag( key: ValueKey(event.eventId), @@ -128,6 +144,7 @@ class ChatEventList extends StatelessWidget { child: Message( event, animateIn: animateIn, + thread: thread, resetAnimateIn: () { controller.animateInEventIndex = null; }, diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index f518e02..f9d7c71 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -48,12 +48,20 @@ class ChatView extends StatelessWidget { tooltip: L10n.of(context).copy, onPressed: controller.copyEventsAction, ), - if (controller.selectedEvents.length == 1 && controller.selectedEvents.single.content['xyz.extera.translated'] == null) - IconButton( - icon: const Icon(Icons.translate_outlined), - tooltip: L10n.of(context).translateMessage, - onPressed: controller.translateEventAction, - ), + if (controller.selectedEvents.length == 1) + IconButton( + onPressed: controller.discussAction, + icon: const Icon(Icons.chat_bubble_outline), + tooltip: L10n.of(context).discuss, + ), + if (controller.selectedEvents.length == 1 && + controller.selectedEvents.single.content['xyz.extera.translated'] == + null) + IconButton( + icon: const Icon(Icons.translate_outlined), + tooltip: L10n.of(context).translateMessage, + onPressed: controller.translateEventAction, + ), if (controller.canSaveSelectedEvent) // Use builder context to correctly position the share dialog on iPad Builder( @@ -118,8 +126,10 @@ class ChatView extends StatelessWidget { Text(L10n.of(context).recoverMessage), ]), ), - if (controller.selectedEvents.single.type == 'org.matrix.msc3381.poll.start' && controller.selectedEvents.single.senderId == Matrix.of(context).client.userID) - + if (controller.selectedEvents.single.type == + 'org.matrix.msc3381.poll.start' && + controller.selectedEvents.single.senderId == + Matrix.of(context).client.userID) PopupMenuItem( value: _EventContextAction.endPoll, child: Row( @@ -318,7 +328,10 @@ class ChatView extends StatelessWidget { Expanded( child: GestureDetector( onTap: controller.clearSingleSelectedEvent, - child: ChatEventList(controller: controller), + child: ChatEventList( + controller: controller, + showThreadRoots: controller.showThreadRoots, + ), ), ), if (controller.showScrollDownButton) @@ -337,7 +350,8 @@ class ChatView extends StatelessWidget { ), ) else if (controller.room.canSendDefaultMessages && - controller.room.membership == Membership.join) + controller.room.membership == Membership.join && + !controller.showThreadRoots) Container( margin: EdgeInsets.all(bottomSheetPadding), constraints: const BoxConstraints( diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index 0f4d26b..06d2c17 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:extera_next/generated/l10n/l10n.dart'; +import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; import 'package:swipe_to_action/swipe_to_action.dart'; @@ -45,6 +46,7 @@ class Message extends StatelessWidget { final List colors; final bool gradient; final bool singleSelected; + final Thread? thread; const Message( this.event, { @@ -54,6 +56,7 @@ class Message extends StatelessWidget { this.longPressSelect = false, this.gradient = false, this.singleSelected = false, + this.thread, required this.onSelect, required this.onInfoTab, required this.scrollToEventId, @@ -688,6 +691,59 @@ class Message extends StatelessWidget { : const SizedBox.shrink(), ), ), + thread != null + ? Align( + alignment: ownMessage + ? Alignment.bottomRight + : Alignment.bottomLeft, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: InkWell( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + (thread?.hasNewMessages ?? false) ? Icons.mark_chat_unread_outlined : Icons.chat_bubble_outline, + color: Colors.grey[200], + size: 20, + ), + const SizedBox(width: 16), + thread!.lastEvent != null + ? FutureBuilder( + future: thread!.lastEvent! + .fetchSenderUser(), + builder: + (context, snapshot) { + final user = snapshot + .data ?? + event + .senderFromMemoryOrFallback; + + return Avatar( + mxContent: + user.avatarUrl, + name: user + .calcDisplayname(), + size: 24, + ); + }, + ) + : const SizedBox.shrink(), + const SizedBox(width: 6), + thread!.lastEvent != null + ? Text( + thread!.lastEvent!.text, + ) + : const Text('Thread'), + ], + ), + onTap: () => context.go( + '/rooms/${event.roomId}/threads/${event.eventId}', + ), + ), + ), + ) + : const SizedBox.shrink(), ], ), ), diff --git a/lib/pages/chat/events/poll_content.dart b/lib/pages/chat/events/poll_content.dart index 579f63e..33fb26b 100644 --- a/lib/pages/chat/events/poll_content.dart +++ b/lib/pages/chat/events/poll_content.dart @@ -58,8 +58,12 @@ class PollWidgetState extends State { final rel = await Matrix.of(context) .client - .getRelatingEventsWithRelTypeAndEventType(room.id, widget.event.eventId, - "m.reference", "org.matrix.msc3381.poll.response"); + .getRelatingEventsWithRelTypeAndEventType( + room.id, + widget.event.eventId, + "m.reference", + "org.matrix.msc3381.poll.response", + ); // Get all poll response events for this poll final responses = rel.chunk; @@ -90,8 +94,12 @@ class PollWidgetState extends State { final rel = await Matrix.of(context) .client - .getRelatingEventsWithRelTypeAndEventType(room.id, pollEventId, - "m.reference", "org.matrix.msc3381.poll.response"); + .getRelatingEventsWithRelTypeAndEventType( + room.id, + pollEventId, + "m.reference", + "org.matrix.msc3381.poll.response", + ); // Get all poll response events for this poll final responses = rel.chunk; @@ -124,15 +132,18 @@ class PollWidgetState extends State { final room = widget.event.room; // Send poll response event - await room.sendEvent({ - 'm.relates_to': { - 'rel_type': 'm.reference', - 'event_id': widget.event.eventId, + await room.sendEvent( + { + 'm.relates_to': { + 'rel_type': 'm.reference', + 'event_id': widget.event.eventId, + }, + 'org.matrix.msc3381.poll.response': { + 'answers': answers, + }, }, - 'org.matrix.msc3381.poll.response': { - 'answers': answers, - }, - }, type: 'org.matrix.msc3381.poll.response'); + type: 'org.matrix.msc3381.poll.response', + ); setState(() { selectedAnswers = answers; @@ -369,9 +380,11 @@ class PollWidgetState extends State { height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) - : Text(hasVoted - ? L10n.of(context).changeVote - : L10n.of(context).vote), + : Text( + hasVoted + ? L10n.of(context).changeVote + : L10n.of(context).vote, + ), ), const Spacer(), diff --git a/lib/pages/chat/events/video_player.dart b/lib/pages/chat/events/video_player.dart index 920db06..d6628e3 100644 --- a/lib/pages/chat/events/video_player.dart +++ b/lib/pages/chat/events/video_player.dart @@ -1,28 +1,17 @@ -import 'dart:io'; - import 'package:extera_next/pages/chat/events/html_message.dart'; import 'package:extera_next/pages/image_viewer/image_viewer.dart'; import 'package:extera_next/widgets/mxc_image.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:chewie/chewie.dart'; -import 'package:extera_next/generated/l10n/l10n.dart'; import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:matrix/matrix.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:universal_html/html.dart' as html; -import 'package:video_player/video_player.dart'; import 'package:extera_next/config/app_config.dart'; -import 'package:extera_next/pages/chat/events/image_bubble.dart'; import 'package:extera_next/utils/file_description.dart'; -import 'package:extera_next/utils/localized_exception_extension.dart'; import 'package:extera_next/utils/matrix_sdk_extensions/event_extension.dart'; import 'package:extera_next/utils/platform_infos.dart'; import 'package:extera_next/utils/url_launcher.dart'; import 'package:extera_next/widgets/blur_hash.dart'; -import '../../../utils/error_reporter.dart'; class EventVideoPlayer extends StatelessWidget { final Event event; @@ -128,8 +117,6 @@ class EventVideoPlayer extends StatelessWidget { ), ), if (fileDescription != null && - textColor != null && - linkColor != null && !event.isRichFileDescription) SizedBox( width: width, @@ -159,8 +146,6 @@ class EventVideoPlayer extends StatelessWidget { ), ), if (fileDescription != null && - textColor != null && - linkColor != null && event.isRichFileDescription) SizedBox( width: width, diff --git a/lib/pages/chat/send_file_dialog.dart b/lib/pages/chat/send_file_dialog.dart index 853ca2a..e00f3b2 100644 --- a/lib/pages/chat/send_file_dialog.dart +++ b/lib/pages/chat/send_file_dialog.dart @@ -23,12 +23,14 @@ import 'package:html_unescape/html_unescape.dart'; class SendFileDialog extends StatefulWidget { final Room room; + final Thread? thread; final List files; final BuildContext outerContext; final Event? replyEvent; const SendFileDialog({ required this.room, + required this.thread, required this.files, required this.outerContext, this.replyEvent, @@ -151,6 +153,8 @@ class SendFileDialogState extends State { thumbnail: thumbnail, shrinkImageMaxDimension: compress ? 1600 : null, extraContent: extraContent, + threadLastEventId: widget.thread?.lastEvent?.eventId ?? widget.thread?.rootEvent.eventId, + threadRootEventId: widget.thread?.rootEvent.eventId ); } on MatrixException catch (e) { final retryAfterMs = e.retryAfterMs; diff --git a/lib/pages/chat/send_location_dialog.dart b/lib/pages/chat/send_location_dialog.dart index 228a482..66515d5 100644 --- a/lib/pages/chat/send_location_dialog.dart +++ b/lib/pages/chat/send_location_dialog.dart @@ -13,9 +13,11 @@ import 'package:extera_next/widgets/future_loading_dialog.dart'; class SendLocationDialog extends StatefulWidget { final Room room; + final Thread? thread; const SendLocationDialog({ required this.room, + required this.thread, super.key, }); @@ -85,7 +87,13 @@ class SendLocationDialogState extends State { 'geo:${position!.latitude},${position!.longitude};u=${position!.accuracy}'; await showFutureLoadingDialog( context: context, - future: () => widget.room.sendLocation(body, uri), + future: () { + if (widget.thread != null) { + return widget.thread!.sendLocation(body, uri); + } else { + return widget.room.sendLocation(body, uri); + } + }, ); Navigator.of(context, rootNavigator: false).pop(); } diff --git a/lib/pages/chat/send_poll_dialog.dart b/lib/pages/chat/send_poll_dialog.dart index 6f81ffd..3142829 100644 --- a/lib/pages/chat/send_poll_dialog.dart +++ b/lib/pages/chat/send_poll_dialog.dart @@ -1,5 +1,4 @@ import 'package:uuid/uuid.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:extera_next/generated/l10n/l10n.dart'; import 'package:matrix/matrix.dart'; @@ -8,9 +7,11 @@ class SendPollDialog extends StatefulWidget { final Room room; final BuildContext outerContext; final Event? replyEvent; + final Thread? thread; const SendPollDialog({ required this.room, + required this.thread, required this.outerContext, this.replyEvent, super.key, @@ -74,11 +75,13 @@ class SendPollDialogState extends State { 'm.text': question, }, 'answers': answers - .map((answer) => { - 'id': const Uuid().v4(), - 'org.matrix.msc1767.text': answer, - 'm.text': answer, - }) + .map( + (answer) => { + 'id': const Uuid().v4(), + 'org.matrix.msc1767.text': answer, + 'm.text': answer, + }, + ) .toList(), 'max_selections': _maxSelections, 'kind': _kind, @@ -86,7 +89,13 @@ class SendPollDialogState extends State { }; try { - await widget.room.sendEvent(pollContent, type: 'org.matrix.msc3381.poll.start'); + await widget.room.sendEvent( + pollContent, + type: 'org.matrix.msc3381.poll.start', + threadLastEventId: widget.thread?.lastEvent?.eventId ?? + widget.thread?.rootEvent.eventId, + threadRootEventId: widget.thread?.rootEvent.eventId, + ); // ignore: use_build_context_synchronously Navigator.of(context).pop(); } catch (e) { @@ -154,7 +163,7 @@ class SendPollDialogState extends State { ), const SizedBox(height: 16), DropdownButtonFormField( - value: _maxSelections, + initialValue: _maxSelections, decoration: InputDecoration( labelText: L10n.of(context).maxSelections, border: const OutlineInputBorder(), @@ -170,7 +179,7 @@ class SendPollDialogState extends State { ), const SizedBox(height: 16), DropdownButtonFormField( - value: _kind, + initialValue: _kind, decoration: InputDecoration( labelText: L10n.of(context).pollType, border: const OutlineInputBorder(), @@ -202,4 +211,4 @@ class SendPollDialogState extends State { ], ); } -} \ No newline at end of file +} diff --git a/lib/pages/chat/translated_event_dialog.dart b/lib/pages/chat/translated_event_dialog.dart index 337701f..7b9f399 100644 --- a/lib/pages/chat/translated_event_dialog.dart +++ b/lib/pages/chat/translated_event_dialog.dart @@ -16,14 +16,13 @@ class TranslatedEventDialog extends StatefulWidget { }); @override - TranslatedEventDialogState createState() => - TranslatedEventDialogState(event, timeline); + TranslatedEventDialogState createState() => TranslatedEventDialogState(); } class TranslatedEventDialogState extends State { - final Event event; - final Timeline timeline; - TranslatedEventDialogState(this.event, this.timeline); + Event get event => widget.event; + Timeline get timeline => widget.timeline; + TranslatedEventDialogState(); @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -33,35 +32,31 @@ class TranslatedEventDialogState extends State { theme.bubbleColor, ]; - final message = Message( - event, - colors: colors, - onInfoTab: (Event ev) => {}, - onMention: () => {}, - onSelect: (Event ev) => {}, - onSwipe: () => {}, - scrollToEventId: (String p0) => {}, - timeline: timeline, - animateIn: false, - displayReadMarker: false, - highlightMarker: false, - longPressSelect: false, - selected: false, - wallpaperMode: false, - gradient: false - ); + final message = Message( + event, + colors: colors, + onInfoTab: (Event ev) => {}, + onMention: () => {}, + onSelect: (Event ev) => {}, + onSwipe: () => {}, + scrollToEventId: (String p0) => {}, + timeline: timeline, + animateIn: false, + displayReadMarker: false, + highlightMarker: false, + longPressSelect: false, + selected: false, + wallpaperMode: false, + gradient: false, + ); return Scaffold( - appBar: AppBar( - title: Text(L10n.of(context).translatedMessage) - ), - body: Container( - child: Column( - children: [ - message, - ], - ), - ), - ); + appBar: AppBar(title: Text(L10n.of(context).translatedMessage)), + body: Column( + children: [ + message, + ], + ), + ); } } diff --git a/lib/pages/chat_access_settings/chat_access_settings_controller.dart b/lib/pages/chat_access_settings/chat_access_settings_controller.dart index 3840ebb..c4b075a 100644 --- a/lib/pages/chat_access_settings/chat_access_settings_controller.dart +++ b/lib/pages/chat_access_settings/chat_access_settings_controller.dart @@ -26,6 +26,16 @@ class ChatAccessSettingsController extends State { bool historyVisibilityLoading = false; bool guestAccessLoading = false; Room get room => Matrix.of(context).client.getRoomById(widget.roomId)!; + Set get knownSpaceParents => { + ...room.client.rooms.where( + (space) => + space.isSpace && + space.spaceChildren.any((child) => child.roomId == room.id), + ), + ...room.spaceParents + .map((parent) => room.client.getRoomById(parent.roomId ?? '')) + .whereType(), + }; String get roomVersion => room @@ -46,9 +56,12 @@ class ChatAccessSettingsController extends State { joinRules.remove(JoinRules.knock); } - // Not yet supported in FluffyChat: - joinRules.remove(JoinRules.restricted); - joinRules.remove(JoinRules.knockRestricted); + if (knownSpaceParents.isEmpty) { + joinRules.remove(JoinRules.restricted); + if (roomVersionInt != null && roomVersionInt <= 6) { + joinRules.remove(JoinRules.knockRestricted); + } + } // If an unsupported join rule is the current join rule, display it: final currentJoinRule = room.joinRules; @@ -64,7 +77,13 @@ class ChatAccessSettingsController extends State { }); try { - await room.setJoinRules(newJoinRules); + await room.setJoinRules( + newJoinRules, + allowConditionRoomId: {JoinRules.restricted, JoinRules.knockRestricted} + .contains(newJoinRules) + ? knownSpaceParents.first.id + : null, + ); } catch (e, s) { Logs().w('Unable to change join rules', e, s); if (mounted) { @@ -297,4 +316,4 @@ class ChatAccessSettingsController extends State { Widget build(BuildContext context) { return ChatAccessSettingsPageView(this); } -} +} \ No newline at end of file diff --git a/lib/pages/chat_access_settings/chat_access_settings_page.dart b/lib/pages/chat_access_settings/chat_access_settings_page.dart index dc11925..d6ad0de 100644 --- a/lib/pages/chat_access_settings/chat_access_settings_page.dart +++ b/lib/pages/chat_access_settings/chat_access_settings_page.dart @@ -45,19 +45,27 @@ class ChatAccessSettingsPageView extends StatelessWidget { ), ), ), - for (final historyVisibility in HistoryVisibility.values) - RadioListTile.adaptive( - title: Text( - historyVisibility - .getLocalizedString(MatrixLocals(L10n.of(context))), - ), - value: historyVisibility, - groupValue: room.historyVisibility, - onChanged: controller.historyVisibilityLoading || - !room.canChangeHistoryVisibility - ? null - : controller.setHistoryVisibility, + RadioGroup( + groupValue: room.historyVisibility, + onChanged: controller.historyVisibilityLoading || + !room.canChangeHistoryVisibility + ? (_) {} + : controller.setHistoryVisibility, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (final historyVisibility in HistoryVisibility.values) + RadioListTile.adaptive( + title: Text( + historyVisibility.getLocalizedString( + MatrixLocals(L10n.of(context)), + ), + ), + value: historyVisibility, + ), + ], ), + ), Divider(color: theme.dividerColor), ListTile( title: Text( @@ -68,19 +76,28 @@ class ChatAccessSettingsPageView extends StatelessWidget { ), ), ), - for (final joinRule in controller.availableJoinRules) - if (joinRule != JoinRules.private) - RadioListTile.adaptive( - title: Text( - joinRule.localizedString(L10n.of(context)), - ), - value: joinRule, - groupValue: room.joinRules, - onChanged: controller.joinRulesLoading || - !room.canChangeJoinRules - ? null - : controller.setJoinRule, - ), + RadioGroup( + groupValue: room.joinRules, + onChanged: controller.setJoinRule, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (final joinRule in controller.availableJoinRules) + if (joinRule != JoinRules.private) + RadioListTile.adaptive( + enabled: !controller.joinRulesLoading && + room.canChangeJoinRules, + title: Text( + joinRule.localizedString( + L10n.of(context), + controller.knownSpaceParents, + ), + ), + value: joinRule, + ), + ], + ), + ), Divider(color: theme.dividerColor), if ({JoinRules.public, JoinRules.knock} .contains(room.joinRules)) ...[ @@ -93,20 +110,26 @@ class ChatAccessSettingsPageView extends StatelessWidget { ), ), ), - for (final guestAccess in GuestAccess.values) - RadioListTile.adaptive( - title: Text( - guestAccess.getLocalizedString( - MatrixLocals(L10n.of(context)), - ), - ), - value: guestAccess, - groupValue: room.guestAccess, - onChanged: controller.guestAccessLoading || - !room.canChangeGuestAccess - ? null - : controller.setGuestAccess, + RadioGroup( + groupValue: room.guestAccess, + onChanged: controller.setGuestAccess, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (final guestAccess in GuestAccess.values) + RadioListTile.adaptive( + enabled: !controller.guestAccessLoading && + room.canChangeGuestAccess, + title: Text( + guestAccess.getLocalizedString( + MatrixLocals(L10n.of(context)), + ), + ), + value: guestAccess, + ), + ], ), + ), Divider(color: theme.dividerColor), ListTile( title: Text( @@ -260,7 +283,7 @@ class _AliasListTile extends StatelessWidget { } extension JoinRulesDisplayString on JoinRules { - String localizedString(L10n l10n) { + String localizedString(L10n l10n, Set spaceParents) { switch (this) { case JoinRules.public: return l10n.anyoneCanJoin; @@ -271,9 +294,17 @@ extension JoinRulesDisplayString on JoinRules { case JoinRules.private: return l10n.noOneCanJoin; case JoinRules.restricted: - return l10n.restricted; + return l10n.spaceMemberOf( + spaceParents + .map((space) => space.getLocalizedDisplayname(MatrixLocals(l10n))) + .join(', '), + ); case JoinRules.knockRestricted: - return l10n.knockRestricted; + return l10n.spaceMemberOfCanKnock( + spaceParents + .map((space) => space.getLocalizedDisplayname(MatrixLocals(l10n))) + .join(', '), + ); } } -} +} \ No newline at end of file diff --git a/lib/pages/chat_details/chat_details_view.dart b/lib/pages/chat_details/chat_details_view.dart index f5f4ccc..21df5fc 100644 --- a/lib/pages/chat_details/chat_details_view.dart +++ b/lib/pages/chat_details/chat_details_view.dart @@ -259,6 +259,21 @@ class ChatDetailsView extends StatelessWidget { const SizedBox(height: 16), ], Divider(color: theme.dividerColor), + ListTile( + leading: CircleAvatar( + backgroundColor: theme.scaffoldBackgroundColor, + foregroundColor: iconColor, + child: const Icon( + Icons.chat_bubble_outline, + ), + ), + title: Text(L10n.of(context).chatThreads), + subtitle: + Text(L10n.of(context).chatThreadsDescription), + onTap: () => + context.push('/rooms/${room.id}?threads=true'), + trailing: const Icon(Icons.chevron_right_outlined), + ), ListTile( leading: CircleAvatar( backgroundColor: theme.scaffoldBackgroundColor, @@ -360,4 +375,4 @@ class ChatDetailsView extends StatelessWidget { }, ); } -} \ No newline at end of file +} diff --git a/lib/pages/chat_list/chat_list_item.dart b/lib/pages/chat_list/chat_list_item.dart index 9ccac97..5c64552 100644 --- a/lib/pages/chat_list/chat_list_item.dart +++ b/lib/pages/chat_list/chat_list_item.dart @@ -1,9 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:extera_next/generated/l10n/l10n.dart'; import 'package:matrix/matrix.dart'; import 'package:extera_next/config/app_config.dart'; +import 'package:extera_next/generated/l10n/l10n.dart'; +import 'package:extera_next/pages/chat_list/unread_bubble.dart'; import 'package:extera_next/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:extera_next/utils/room_status_extension.dart'; import 'package:extera_next/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart'; @@ -35,32 +36,6 @@ class ChatListItem extends StatelessWidget { super.key, }); - Future archiveAction(BuildContext context) async { - { - if ([Membership.leave, Membership.ban].contains(room.membership)) { - final forgetResult = await showFutureLoadingDialog( - context: context, - future: () => room.forget(), - ); - return forgetResult.isValue; - } - final confirmed = await showOkCancelAlertDialog( - context: context, - title: L10n.of(context).areYouSure, - okLabel: L10n.of(context).leave, - cancelLabel: L10n.of(context).cancel, - message: L10n.of(context).archiveRoomDescription, - isDestructive: true, - ); - if (confirmed != OkCancelResult.ok) return false; - final leaveResult = await showFutureLoadingDialog( - context: context, - future: () => room.leave(), - ); - return leaveResult.isValue; - } - } - @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -69,14 +44,9 @@ class ChatListItem extends StatelessWidget { final typingText = room.getLocalizedTypingText(context); final lastEvent = room.lastEvent; final ownMessage = lastEvent?.senderId == room.client.userID; - final unread = room.isUnread || room.membership == Membership.invite; + final unread = room.isUnread; final directChatMatrixId = room.directChatMatrixID; final isDirectChat = directChatMatrixId != null; - final unreadBubbleSize = unread || room.hasNewMessages - ? room.notificationCount > 0 - ? 20.0 - : 14.0 - : 0.0; final hasNotifications = room.notificationCount > 0; final backgroundColor = activeChat ? theme.colorScheme.secondaryContainer : null; @@ -93,8 +63,6 @@ class ChatListItem extends StatelessWidget { : room.getState(EventTypes.RoomMember, lastEvent.senderId) == null; final space = this.space; - final inviterMxId = room.getState(EventTypes.RoomMember, room.client.userID!)?.senderId; - return Padding( padding: const EdgeInsets.symmetric( horizontal: 8, @@ -116,9 +84,7 @@ class ChatListItem extends StatelessWidget { duration: FluffyThemes.animationDuration, curve: FluffyThemes.animationCurve, scale: hovered ? 1.1 : 1.0, - child: - (!AppConfig.hideAvatarsInInvites || room.membership != Membership.invite) - ? SizedBox( + child: SizedBox( width: Avatar.defaultSize, height: Avatar.defaultSize, child: Stack( @@ -195,7 +161,7 @@ class ChatListItem extends StatelessWidget { ), ], ), - ) : null, + ), ), ), title: Row( @@ -232,13 +198,12 @@ class ChatListItem extends StatelessWidget { color: theme.colorScheme.primary, ), ), - if (!room.isSpace && - lastEvent != null && - room.membership != Membership.invite) + if (!room.isSpace && room.membership != Membership.invite) Padding( padding: const EdgeInsets.only(left: 4.0), child: Text( - lastEvent.originServerTs.localizedTimeShort(context), + room.latestEventReceivedTime + .localizedTimeShort(context), style: TextStyle( fontSize: 12, color: theme.colorScheme.outline, @@ -253,7 +218,7 @@ class ChatListItem extends StatelessWidget { children: [ if (typingText.isEmpty && ownMessage && - room.lastEvent!.status.isSending) ...[ + room.lastEvent?.status.isSending == true) ...[ const SizedBox( width: 16, height: 16, @@ -321,7 +286,7 @@ class ChatListItem extends StatelessWidget { ), builder: (context, snapshot) => Text( room.membership == Membership.invite - ? "${room + ? room .getState( EventTypes.RoomMember, room.client.userID!, @@ -331,9 +296,9 @@ class ChatListItem extends StatelessWidget { (isDirectChat ? L10n.of(context).newChatRequest : L10n.of(context) - .inviteGroupChat)}${inviterMxId != null ? " ($inviterMxId)" : ""}" + .inviteGroupChat) : snapshot.data ?? - L10n.of(context).emptyChat, + L10n.of(context).noMessagesYet, softWrap: false, maxLines: room.notificationCount >= 1 ? 2 : 1, overflow: TextOverflow.ellipsis, @@ -349,48 +314,33 @@ class ChatListItem extends StatelessWidget { ), ), const SizedBox(width: 8), - AnimatedContainer( - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - alignment: Alignment.center, - padding: const EdgeInsets.symmetric(horizontal: 7), - height: unreadBubbleSize, - width: !hasNotifications && !unread && !room.hasNewMessages - ? 0 - : (unreadBubbleSize - 9) * - room.notificationCount.toString().length + - 9, - decoration: BoxDecoration( - color: room.highlightCount > 0 || - room.membership == Membership.invite - ? theme.colorScheme.error - : hasNotifications || room.markedUnread - ? theme.colorScheme.primary - : theme.colorScheme.primaryContainer, - borderRadius: BorderRadius.circular(7), - ), - child: hasNotifications - ? Text( - room.notificationCount.toString(), - style: TextStyle( - color: room.highlightCount > 0 || - room.membership == Membership.invite - ? theme.colorScheme.onError - : hasNotifications - ? theme.colorScheme.onPrimary - : theme.colorScheme.onPrimaryContainer, - fontSize: 13, - fontWeight: FontWeight.w500, - ), - textAlign: TextAlign.center, - ) - : const SizedBox.shrink(), - ), + UnreadBubble(room: room), ], ), onTap: onTap, trailing: onForget == null - ? null + ? room.membership == Membership.invite + ? IconButton( + tooltip: L10n.of(context).decline, + icon: const Icon(Icons.delete_forever_outlined), + color: theme.colorScheme.error, + onPressed: () async { + final consent = await showOkCancelAlertDialog( + context: context, + title: L10n.of(context).decline, + message: L10n.of(context).areYouSure, + okLabel: L10n.of(context).yes, + isDestructive: true, + ); + if (consent != OkCancelResult.ok) return; + if (!context.mounted) return; + await showFutureLoadingDialog( + context: context, + future: room.leave, + ); + }, + ) + : null : IconButton( icon: const Icon(Icons.delete_outlined), onPressed: onForget, diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index 10698d5..fc5e330 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -1,27 +1,40 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; -import 'package:extera_next/generated/l10n/l10n.dart'; import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart' as sdk; import 'package:matrix/matrix.dart'; import 'package:extera_next/config/app_config.dart'; import 'package:extera_next/config/themes.dart'; -import 'package:extera_next/pages/chat_list/chat_list_item.dart'; -import 'package:extera_next/pages/chat_list/search_title.dart'; +import 'package:extera_next/generated/l10n/l10n.dart'; +import 'package:extera_next/pages/chat_list/unread_bubble.dart'; import 'package:extera_next/utils/localized_exception_extension.dart'; +import 'package:extera_next/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:extera_next/utils/stream_extension.dart'; +import 'package:extera_next/utils/string_color.dart'; import 'package:extera_next/widgets/adaptive_dialogs/public_room_dialog.dart'; import 'package:extera_next/widgets/adaptive_dialogs/show_modal_action_popup.dart'; import 'package:extera_next/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart'; import 'package:extera_next/widgets/adaptive_dialogs/show_text_input_dialog.dart'; import 'package:extera_next/widgets/avatar.dart'; import 'package:extera_next/widgets/future_loading_dialog.dart'; +import 'package:extera_next/widgets/hover_builder.dart'; import 'package:extera_next/widgets/matrix.dart'; enum AddRoomType { chat, subspace } +enum SpaceChildAction { edit, moveToSpace, removeFromSpace } + +enum SpaceActions { + settings, + invite, + members, + leave, +} + class SpaceView extends StatefulWidget { final String spaceId; final void Function() onBack; @@ -45,7 +58,7 @@ class SpaceView extends StatefulWidget { } class _SpaceViewState extends State { - final List _discoveredChildren = []; + final List _discoveredChildren = []; final TextEditingController _filterController = TextEditingController(); String? _nextBatch; bool _noMoreRooms = false; @@ -58,9 +71,28 @@ class _SpaceViewState extends State { } void _loadHierarchy() async { - final room = Matrix.of(context).client.getRoomById(widget.spaceId); + final matrix = Matrix.of(context); + final room = matrix.client.getRoomById(widget.spaceId); if (room == null) return; + final cacheKey = 'spaces_history_cache${room.id}'; + if (_discoveredChildren.isEmpty) { + final cachedChildren = matrix.store.getStringList(cacheKey); + if (cachedChildren != null) { + try { + _discoveredChildren.addAll( + cachedChildren.map( + (jsonString) => + SpaceRoomsChunk$2.fromJson(jsonDecode(jsonString)), + ), + ); + } catch (e, s) { + Logs().e('Unable to json decode spaces hierarchy cache!', e, s); + matrix.store.remove(cacheKey); + } + } + } + setState(() { _isLoading = true; }); @@ -74,16 +106,25 @@ class _SpaceViewState extends State { ); if (!mounted) return; setState(() { + if (_nextBatch == null) _discoveredChildren.clear(); _nextBatch = hierarchy.nextBatch; if (hierarchy.nextBatch == null) { _noMoreRooms = true; } _discoveredChildren.addAll( - hierarchy.rooms - .where((c) => room.client.getRoomById(c.roomId) == null), + hierarchy.rooms.where((room) => room.roomId != widget.spaceId), ); _isLoading = false; }); + + if (_nextBatch == null) { + matrix.store.setStringList( + cacheKey, + _discoveredChildren + .map((child) => jsonEncode(child.toJson())) + .toList(), + ); + } } catch (e, s) { Logs().w('Unable to load hierarchy', e, s); if (!mounted) return; @@ -95,7 +136,7 @@ class _SpaceViewState extends State { } } - void _joinChildRoom(SpaceRoomsChunk item) async { + void _joinChildRoom(SpaceRoomsChunk$2 item) async { final client = Matrix.of(context).client; final space = client.getRoomById(widget.spaceId); @@ -111,9 +152,7 @@ class _SpaceViewState extends State { ), ); if (mounted && joined == true) { - setState(() { - _discoveredChildren.remove(item); - }); + setState(() {}); } } @@ -129,6 +168,10 @@ class _SpaceViewState extends State { await space?.postLoad(); context.push('/rooms/${widget.spaceId}/invite'); break; + case SpaceActions.members: + await space?.postLoad(); + context.push('/rooms/${widget.spaceId}/details/members'); + break; case SpaceActions.leave: final confirmed = await showOkCancelAlertDialog( context: context, @@ -151,27 +194,11 @@ class _SpaceViewState extends State { } } - void _addChatOrSubspace() async { - final roomType = await showModalActionPopup( - context: context, - title: L10n.of(context).addChatOrSubSpace, - actions: [ - AdaptiveModalAction( - value: AddRoomType.subspace, - label: L10n.of(context).createNewSpace, - ), - AdaptiveModalAction( - value: AddRoomType.chat, - label: L10n.of(context).createGroup, - ), - ], - ); - if (roomType == null) return; - + void _addChatOrSubspace(AddRoomType roomType) async { final names = await showTextInputDialog( context: context, title: roomType == AddRoomType.subspace - ? L10n.of(context).createNewSpace + ? L10n.of(context).newSubSpace : L10n.of(context).createGroup, hintText: roomType == AddRoomType.subspace ? L10n.of(context).spaceName @@ -196,29 +223,169 @@ class _SpaceViewState extends State { late final String roomId; final activeSpace = client.getRoomById(widget.spaceId)!; await activeSpace.postLoad(); + final isPublicSpace = activeSpace.joinRules == JoinRules.public; if (roomType == AddRoomType.subspace) { roomId = await client.createSpace( name: names, - visibility: activeSpace.joinRules == JoinRules.public - ? sdk.Visibility.public - : sdk.Visibility.private, + visibility: + isPublicSpace ? sdk.Visibility.public : sdk.Visibility.private, ); } else { roomId = await client.createGroupChat( + enableEncryption: !isPublicSpace, groupName: names, - preset: activeSpace.joinRules == JoinRules.public + preset: isPublicSpace ? CreateRoomPreset.publicChat : CreateRoomPreset.privateChat, - visibility: activeSpace.joinRules == JoinRules.public - ? sdk.Visibility.public - : sdk.Visibility.private, + visibility: + isPublicSpace ? sdk.Visibility.public : sdk.Visibility.private, + initialState: isPublicSpace + ? null + : [ + StateEvent( + content: { + 'join_rule': 'restricted', + 'allow': [ + { + 'room_id': widget.spaceId, + 'type': 'm.room_membership', + }, + ], + }, + type: EventTypes.RoomJoinRules, + ), + ], ); } await activeSpace.setSpaceChild(roomId); }, ); if (result.error != null) return; + setState(() { + _nextBatch = null; + _discoveredChildren.clear(); + }); + _loadHierarchy(); + } + + void _showSpaceChildEditMenu(BuildContext posContext, String roomId) async { + final overlay = + Overlay.of(posContext).context.findRenderObject() as RenderBox; + + final button = posContext.findRenderObject() as RenderBox; + + final position = RelativeRect.fromRect( + Rect.fromPoints( + button.localToGlobal(const Offset(0, -65), ancestor: overlay), + button.localToGlobal( + button.size.bottomRight(Offset.zero) + const Offset(-50, 0), + ancestor: overlay, + ), + ), + Offset.zero & overlay.size, + ); + + final action = await showMenu( + context: posContext, + position: position, + items: [ + PopupMenuItem( + value: SpaceChildAction.moveToSpace, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.move_down_outlined), + const SizedBox(width: 12), + Text(L10n.of(context).moveToDifferentSpace), + ], + ), + ), + PopupMenuItem( + value: SpaceChildAction.edit, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.edit_outlined), + const SizedBox(width: 12), + Text(L10n.of(context).edit), + ], + ), + ), + PopupMenuItem( + value: SpaceChildAction.removeFromSpace, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.group_remove_outlined), + const SizedBox(width: 12), + Text(L10n.of(context).removeFromSpace), + ], + ), + ), + ], + ); + if (action == null) return; + if (!mounted) return; + final space = Matrix.of(context).client.getRoomById(widget.spaceId); + if (space == null) return; + switch (action) { + case SpaceChildAction.edit: + context.push('/rooms/${widget.spaceId}/details'); + case SpaceChildAction.moveToSpace: + final spacesWithPowerLevels = space.client.rooms + .where( + (room) => + room.isSpace && + room.canChangeStateEvent(EventTypes.SpaceChild) && + room.id != widget.spaceId, + ) + .toList(); + final newSpace = await showModalActionPopup( + context: context, + title: L10n.of(context).space, + actions: spacesWithPowerLevels + .map( + (space) => AdaptiveModalAction( + value: space, + label: space + .getLocalizedDisplayname(MatrixLocals(L10n.of(context))), + ), + ) + .toList(), + ); + if (newSpace == null) return; + final result = await showFutureLoadingDialog( + context: context, + future: () async { + await newSpace.setSpaceChild(newSpace.id); + await space.removeSpaceChild(roomId); + }, + ); + if (result.isError) return; + if (!mounted) return; + _nextBatch = null; + _loadHierarchy(); + return; + + case SpaceChildAction.removeFromSpace: + final consent = await showOkCancelAlertDialog( + context: context, + title: L10n.of(context).removeFromSpace, + message: L10n.of(context).removeFromSpaceDescription, + ); + if (consent != OkCancelResult.ok) return; + if (!mounted) return; + final result = await showFutureLoadingDialog( + context: context, + future: () => space.removeSpaceChild(roomId), + ); + if (result.isError) return; + if (!mounted) return; + _nextBatch = null; + _loadHierarchy(); + return; + } } @override @@ -228,6 +395,11 @@ class _SpaceViewState extends State { final room = Matrix.of(context).client.getRoomById(widget.spaceId); final displayname = room?.getLocalizedDisplayname() ?? L10n.of(context).nothingFound; + const avatarSize = Avatar.defaultSize / 1.5; + final isAdmin = room?.canChangeStateEvent( + EventTypes.SpaceChild, + ) == + true; return Scaffold( appBar: AppBar( leading: FluffyThemes.isColumnMode(context) @@ -242,6 +414,7 @@ class _SpaceViewState extends State { title: ListTile( contentPadding: EdgeInsets.zero, leading: Avatar( + size: avatarSize, mxContent: room?.avatar, name: displayname, border: BorderSide(width: 1, color: theme.dividerColor), @@ -252,19 +425,40 @@ class _SpaceViewState extends State { maxLines: 1, overflow: TextOverflow.ellipsis, ), - subtitle: room == null - ? null - : Text( - L10n.of(context).countChatsAndCountParticipants( - room.spaceChildren.length, - room.summary.mJoinedMemberCount ?? 1, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), ), actions: [ + if (isAdmin) + PopupMenuButton( + icon: const Icon(Icons.add_outlined), + onSelected: _addChatOrSubspace, + tooltip: L10n.of(context).addChatOrSubSpace, + itemBuilder: (context) => [ + PopupMenuItem( + value: AddRoomType.chat, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.group_add_outlined), + const SizedBox(width: 12), + Text(L10n.of(context).newGroup), + ], + ), + ), + PopupMenuItem( + value: AddRoomType.subspace, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.workspaces_outlined), + const SizedBox(width: 12), + Text(L10n.of(context).newSubSpace), + ], + ), + ), + ], + ), PopupMenuButton( + useRootNavigator: true, onSelected: _onSpaceAction, itemBuilder: (context) => [ PopupMenuItem( @@ -289,6 +483,21 @@ class _SpaceViewState extends State { ], ), ), + PopupMenuItem( + value: SpaceActions.members, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.group_outlined), + const SizedBox(width: 12), + Text( + L10n.of(context).countParticipants( + room?.summary.mJoinedMemberCount ?? 1, + ), + ), + ], + ), + ), PopupMenuItem( value: SpaceActions.leave, child: Row( @@ -304,16 +513,6 @@ class _SpaceViewState extends State { ), ], ), - floatingActionButton: room?.canChangeStateEvent( - EventTypes.SpaceChild, - ) == - true - ? FloatingActionButton.extended( - onPressed: _addChatOrSubspace, - label: Text(L10n.of(context).group), - icon: const Icon(Icons.group_add_outlined), - ) - : null, body: room == null ? const Center( child: Icon( @@ -331,9 +530,11 @@ class _SpaceViewState extends State { .whereType() .toSet(); - final joinedRooms = room.client.rooms - .where((room) => childrenIds.remove(room.id)) - .toList(); + final joinedRooms = Map.fromEntries( + room.client.rooms + .where((room) => childrenIds.remove(room.id)) + .map((room) => MapEntry(room.id, room)), + ); final joinedParents = room.spaceParents .map((parent) { @@ -348,7 +549,6 @@ class _SpaceViewState extends State { slivers: [ SliverAppBar( floating: true, - toolbarHeight: 72, scrolledUnderElevation: 0, backgroundColor: Colors.transparent, automaticallyImplyLeading: false, @@ -358,11 +558,6 @@ class _SpaceViewState extends State { textInputAction: TextInputAction.search, decoration: InputDecoration( filled: true, - fillColor: theme.colorScheme.secondaryContainer, - border: OutlineInputBorder( - borderSide: BorderSide.none, - borderRadius: BorderRadius.circular(99), - ), contentPadding: EdgeInsets.zero, hintText: L10n.of(context).search, hintStyle: TextStyle( @@ -422,42 +617,11 @@ class _SpaceViewState extends State { }, ), SliverList.builder( - itemCount: joinedRooms.length, + itemCount: _discoveredChildren.length + 1, itemBuilder: (context, i) { - final joinedRoom = joinedRooms[i]; - return ChatListItem( - joinedRoom, - filter: filter, - onTap: () => widget.onChatTab(joinedRoom), - onLongPress: (context) => widget.onChatContext( - joinedRoom, - context, - ), - activeChat: widget.activeChat == joinedRoom.id, - ); - }, - ), - SliverList.builder( - itemCount: _discoveredChildren.length + 2, - itemBuilder: (context, i) { - if (i == 0) { - return SearchTitle( - title: L10n.of(context).discover, - icon: const Icon(Icons.explore_outlined), - ); - } - i--; if (i == _discoveredChildren.length) { if (_noMoreRooms) { - return Padding( - padding: const EdgeInsets.all(12.0), - child: Center( - child: Text( - L10n.of(context).noMoreChatsFound, - style: const TextStyle(fontSize: 13), - ), - ), - ); + return const SizedBox.shrink(); } return Padding( padding: const EdgeInsets.symmetric( @@ -467,11 +631,7 @@ class _SpaceViewState extends State { child: TextButton( onPressed: _isLoading ? null : _loadHierarchy, child: _isLoading - ? LinearProgressIndicator( - borderRadius: BorderRadius.circular( - AppConfig.borderRadius, - ), - ) + ? const CircularProgressIndicator.adaptive() : Text(L10n.of(context).loadMore), ), ); @@ -483,6 +643,7 @@ class _SpaceViewState extends State { if (!displayname.toLowerCase().contains(filter)) { return const SizedBox.shrink(); } + final joinedRoom = joinedRooms[item.roomId]; return Padding( padding: const EdgeInsets.symmetric( horizontal: 8, @@ -492,51 +653,83 @@ class _SpaceViewState extends State { borderRadius: BorderRadius.circular(AppConfig.borderRadius), clipBehavior: Clip.hardEdge, - child: ListTile( - visualDensity: - const VisualDensity(vertical: -0.5), - contentPadding: - const EdgeInsets.symmetric(horizontal: 8), - onTap: () => _joinChildRoom(item), - leading: Avatar( - mxContent: item.avatarUrl, - name: displayname, - borderRadius: item.roomType == 'm.space' - ? BorderRadius.circular( - AppConfig.borderRadius / 2, - ) + color: joinedRoom != null && + widget.activeChat == joinedRoom.id + ? theme.colorScheme.secondaryContainer + : Colors.transparent, + child: HoverBuilder( + builder: (context, hovered) => ListTile( + visualDensity: + const VisualDensity(vertical: -0.5), + contentPadding: + const EdgeInsets.symmetric(horizontal: 8), + onTap: joinedRoom != null + ? () => widget.onChatTab(joinedRoom) + : () => _joinChildRoom(item), + onLongPress: isAdmin + ? () => _showSpaceChildEditMenu( + context, + item.roomId, + ) : null, - ), - title: Row( - children: [ - Expanded( - child: Text( - displayname, - maxLines: 1, - overflow: TextOverflow.ellipsis, + leading: hovered && isAdmin + ? SizedBox.square( + dimension: avatarSize, + child: IconButton( + splashRadius: avatarSize, + iconSize: 14, + style: IconButton.styleFrom( + foregroundColor: theme.colorScheme + .onTertiaryContainer, + backgroundColor: theme + .colorScheme.tertiaryContainer, + ), + onPressed: () => + _showSpaceChildEditMenu( + context, + item.roomId, + ), + icon: const Icon(Icons.edit_outlined), + ), + ) + : Avatar( + size: avatarSize, + mxContent: item.avatarUrl, + name: '#', + backgroundColor: + theme.colorScheme.surfaceContainer, + textColor: item.name?.darkColor ?? + theme.colorScheme.onSurface, + border: item.roomType == 'm.space' + ? BorderSide( + color: theme.colorScheme + .surfaceContainerHighest, + ) + : null, + borderRadius: item.roomType == 'm.space' + ? BorderRadius.circular( + AppConfig.borderRadius / 4, + ) + : null, + ), + title: Row( + children: [ + Expanded( + child: Opacity( + opacity: joinedRoom == null ? 0.5 : 1, + child: Text( + displayname, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), ), - ), - Text( - item.numJoinedMembers.toString(), - style: TextStyle( - fontSize: 13, - color: theme.textTheme.bodyMedium!.color, - ), - ), - const SizedBox(width: 4), - const Icon( - Icons.people_outlined, - size: 14, - ), - ], - ), - subtitle: Text( - item.topic ?? - L10n.of(context).countParticipants( - item.numJoinedMembers, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, + if (joinedRoom != null) + UnreadBubble(room: joinedRoom) + else + const Icon(Icons.chevron_right_outlined), + ], + ), ), ), ), @@ -550,10 +743,4 @@ class _SpaceViewState extends State { ), ); } -} - -enum SpaceActions { - settings, - invite, - leave, -} +} \ No newline at end of file diff --git a/lib/pages/chat_list/unread_bubble.dart b/lib/pages/chat_list/unread_bubble.dart new file mode 100644 index 0000000..b244e6e --- /dev/null +++ b/lib/pages/chat_list/unread_bubble.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; + +import 'package:matrix/matrix.dart'; + +import 'package:extera_next/config/themes.dart'; + +class UnreadBubble extends StatelessWidget { + final Room room; + const UnreadBubble({required this.room, super.key}); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final unread = room.isUnread; + final hasNotifications = room.notificationCount > 0; + final unreadBubbleSize = unread || room.hasNewMessages + ? room.notificationCount > 0 + ? 20.0 + : 14.0 + : 0.0; + return AnimatedContainer( + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + alignment: Alignment.center, + padding: const EdgeInsets.symmetric(horizontal: 7), + height: unreadBubbleSize, + width: !hasNotifications && !unread && !room.hasNewMessages + ? 0 + : (unreadBubbleSize - 9) * room.notificationCount.toString().length + + 9, + decoration: BoxDecoration( + color: room.highlightCount > 0 + ? theme.colorScheme.error + : hasNotifications || room.markedUnread + ? theme.colorScheme.primary + : theme.colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(7), + ), + child: hasNotifications + ? Text( + room.notificationCount.toString(), + style: TextStyle( + color: room.highlightCount > 0 + ? theme.colorScheme.onError + : hasNotifications + ? theme.colorScheme.onPrimary + : theme.colorScheme.onPrimaryContainer, + fontSize: 13, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ) + : const SizedBox.shrink(), + ); + } +} \ No newline at end of file diff --git a/lib/pages/chat_thread/thread.dart b/lib/pages/chat_thread/thread.dart new file mode 100644 index 0000000..b1f0bf4 --- /dev/null +++ b/lib/pages/chat_thread/thread.dart @@ -0,0 +1,47 @@ +import 'package:extera_next/generated/l10n/l10n.dart'; +import 'package:extera_next/pages/chat/chat.dart'; +import 'package:extera_next/widgets/matrix.dart'; +import 'package:extera_next/widgets/share_scaffold_dialog.dart'; +import 'package:flutter/material.dart'; + +class ThreadPage extends StatelessWidget { + final String roomId; + final List? shareItems; + final String? threadRootEventId; + final String? eventId; + + const ThreadPage({ + super.key, + required this.roomId, + required this.threadRootEventId, + this.eventId, + this.shareItems, + }); + + @override + Widget build(BuildContext context) { + final client = Matrix.of(context).client; + final room = client.getRoomById(roomId); + if (room == null) { + return Scaffold( + appBar: AppBar(title: Text(L10n.of(context).oopsSomethingWentWrong)), + body: Center( + child: Padding( + padding: const EdgeInsets.all(16), + child: Text(L10n.of(context).youAreNoLongerParticipatingInThisChat), + ), + ), + ); + } + + final thread = room.threads[threadRootEventId]; + + return ChatPageWithRoom( + key: Key('chat_page_${roomId}_${threadRootEventId}_$eventId'), + room: room, + thread: thread, + shareItems: shareItems, + eventId: eventId, + ); + } +} \ No newline at end of file diff --git a/lib/pages/image_viewer/image_viewer_view.dart b/lib/pages/image_viewer/image_viewer_view.dart index 9adb2c7..6c70779 100644 --- a/lib/pages/image_viewer/image_viewer_view.dart +++ b/lib/pages/image_viewer/image_viewer_view.dart @@ -88,7 +88,7 @@ class ImageViewerView extends StatelessWidget { child: GestureDetector( // Ignore taps to not go back here: onTap: () {}, - child: EventVideoPlayer(event), + child: EventVideoPlayer(event, controller), ), ), ); diff --git a/lib/pages/image_viewer/video_player.dart b/lib/pages/image_viewer/video_player.dart index 5d91dea..16d3b6e 100644 --- a/lib/pages/image_viewer/video_player.dart +++ b/lib/pages/image_viewer/video_player.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:extera_next/pages/image_viewer/image_viewer.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -18,9 +19,11 @@ import '../../widgets/mxc_image.dart'; class EventVideoPlayer extends StatefulWidget { final Event event; + final ImageViewerController ivController; const EventVideoPlayer( - this.event, { + this.event, + this.ivController, { super.key, }); @@ -69,6 +72,11 @@ class EventVideoPlayerState extends State { await videoPlayerController.initialize(); + if (widget.ivController.currentEvent.eventId != widget.event.eventId) { + dispose(); + return; + } + // Create a ChewieController on top. setState(() { _chewieController = ChewieController( diff --git a/lib/utils/client_manager.dart b/lib/utils/client_manager.dart index f8741f0..8e3afb2 100644 --- a/lib/utils/client_manager.dart +++ b/lib/utils/client_manager.dart @@ -115,7 +115,7 @@ abstract class ClientManager { return Client( clientName, httpClient: - PlatformInfos.isAndroid ? CustomHttpClient.createHTTPClient() : null, + CustomHttpClient.createHTTPClient(), verificationMethods: { KeyVerificationMethod.numbers, if (kIsWeb || PlatformInfos.isMobile || PlatformInfos.isLinux) diff --git a/lib/utils/custom_http_client.dart b/lib/utils/custom_http_client.dart index c310fdd..c6c5060 100644 --- a/lib/utils/custom_http_client.dart +++ b/lib/utils/custom_http_client.dart @@ -1,29 +1,43 @@ import 'dart:convert'; import 'dart:io'; +import 'package:extera_next/config/app_config.dart'; +import 'package:extera_next/utils/platform_infos.dart'; import 'package:http/http.dart' as http; import 'package:http/io_client.dart'; import 'package:extera_next/config/isrg_x1.dart'; class CustomHttpClient { - static HttpClient customHttpClient(String? cert) { + static HttpClient? customHttpClient(String? cert) { + if (PlatformInfos.isWeb) return null; + final context = SecurityContext.defaultContext; - try { - if (cert != null) { - final bytes = utf8.encode(cert); - context.setTrustedCertificatesBytes(bytes); - } - } on TlsException catch (e) { - if (e.osError != null && - e.osError!.message.contains('CERT_ALREADY_IN_HASH_TABLE')) { - } else { - rethrow; + if (PlatformInfos.isAndroid) { + try { + if (cert != null) { + final bytes = utf8.encode(cert); + context.setTrustedCertificatesBytes(bytes); + } + } on TlsException catch (e) { + if (e.osError != null && + e.osError!.message.contains('CERT_ALREADY_IN_HASH_TABLE')) { + } else { + rethrow; + } } } - return HttpClient(context: context); + final client = HttpClient(context: context); + + if (AppConfig.httpProxy != null) { + client.findProxy = (uri) { + return "PROXY ${AppConfig.httpProxy};"; + }; + } + + return client; } static http.Client createHTTPClient() => IOClient(customHttpClient(ISRG_X1)); diff --git a/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart b/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart index 756d23f..dce194e 100644 --- a/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart +++ b/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart @@ -4,6 +4,14 @@ import 'package:matrix/matrix.dart'; import '../../config/app_config.dart'; extension VisibleInGuiExtension on List { + List filterByThreaded(bool threaded) { + return where((e) => e.isThreaded == threaded || e.relationshipType == RelationshipTypes.edit || e.relationshipType == RelationshipTypes.reaction).toList(); + } + + List filterThreadRoots() { + return where((e) => e.room.threads.containsKey(e.eventId)).toList(); + } + List filterByVisibleInGui({String? exceptionEventId}) { final visibleEvents = where((e) => e.isVisibleInGui || e.eventId == exceptionEventId) @@ -46,7 +54,9 @@ extension IsStateExtension on Event { // if we enabled to hide all redacted events, don't show those (!AppConfig.hideRedactedEvents || !redacted) && // if we enabled to hide all unknown events, don't show those - (!AppConfig.hideUnknownEvents || isEventTypeKnown || type == PollEvents.PollStart) && + (!AppConfig.hideUnknownEvents || + isEventTypeKnown || + type == PollEvents.PollStart) && // remove state events that we don't want to render (isState || !AppConfig.hideAllStateEvents) && // hide simple join/leave member events in public rooms @@ -56,5 +66,9 @@ extension IsStateExtension on Event { content.tryGet('membership') == 'ban' || stateKey != senderId); + bool get isThreaded => + relationshipEventId != null && + relationshipType == RelationshipTypes.thread; + bool get isState => this.stateKey != null; } diff --git a/lib/utils/matrix_sdk_extensions/flutter_matrix_dart_sdk_database/builder.dart b/lib/utils/matrix_sdk_extensions/flutter_matrix_dart_sdk_database/builder.dart index f432bbe..351339a 100644 --- a/lib/utils/matrix_sdk_extensions/flutter_matrix_dart_sdk_database/builder.dart +++ b/lib/utils/matrix_sdk_extensions/flutter_matrix_dart_sdk_database/builder.dart @@ -10,7 +10,6 @@ import 'package:sqflite_common_ffi/sqflite_ffi.dart'; import 'package:universal_html/html.dart' as html; import 'package:extera_next/config/app_config.dart'; -import 'package:extera_next/generated/l10n/l10n.dart'; import 'package:extera_next/utils/client_manager.dart'; import 'package:extera_next/utils/platform_infos.dart'; import 'cipher.dart'; @@ -69,7 +68,12 @@ Future _constructDatabase(String clientName) async { Directory? fileStorageLocation; try { - fileStorageLocation = await getTemporaryDirectory(); + final tmpDir = await getTemporaryDirectory(); + final appTmpDir = Directory(join(tmpDir.path, clientName)); + if (!await appTmpDir.exists()) { + await appTmpDir.create(recursive: true); + } + fileStorageLocation = appTmpDir; } on MissingPlatformDirectoryException catch (_) { Logs().w( 'No temporary directory for file cache available on this platform.', @@ -106,11 +110,10 @@ Future _constructDatabase(String clientName) async { final database = await factory.openDatabase( path, options: OpenDatabaseOptions( - version: 1, - // most important : apply encryption when opening the DB - onConfigure: helper?.applyPragmaKey, - singleInstance: false - ), + version: 1, + // most important : apply encryption when opening the DB + onConfigure: helper?.applyPragmaKey, + singleInstance: false), ); return await MatrixSdkDatabase.init( @@ -149,4 +152,4 @@ Future _migrateLegacyLocation( await maybeOldFile.copy(sqlFilePath); await maybeOldFile.delete(); } -} \ No newline at end of file +} diff --git a/lib/utils/matrix_sdk_extensions/synapse_admin_extension.dart b/lib/utils/matrix_sdk_extensions/synapse_admin_extension.dart index 65c5c19..a1de8d4 100644 --- a/lib/utils/matrix_sdk_extensions/synapse_admin_extension.dart +++ b/lib/utils/matrix_sdk_extensions/synapse_admin_extension.dart @@ -6,12 +6,12 @@ import 'package:matrix/matrix.dart' as matrix; extension SynapseAdmin on matrix.Client { Future> getEventReports({int from = 0, int limit = 10}) async { final requestUri = Uri( - path: '/_synapse/admin/v1/event_reports', - query: 'from=$from&limit=$limit&order_by=received_ts&dir=b' - ); + path: '/_synapse/admin/v1/event_reports', + query: 'from=$from&limit=$limit&order_by=received_ts&dir=b', + ); if (baseUri == null) return []; - print(baseUri!.resolveUri(requestUri).toString()); + final request = Request('GET', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer $accessToken'; final response = await httpClient.send(request); @@ -40,14 +40,13 @@ extension SynapseAdmin on matrix.Client { if (room == null) return null; - print('Event content: ${jsonEncode(json['event_json'])}'); - - return new matrix.Event( + return matrix.Event( content: json['event_json']['content'], type: json['event_json']['type'], eventId: json['event_id'], senderId: json['sender'], - originServerTs: DateTime.fromMillisecondsSinceEpoch(json['event_json']['origin_server_ts']), + originServerTs: DateTime.fromMillisecondsSinceEpoch( + json['event_json']['origin_server_ts']), room: room, ); } diff --git a/lib/widgets/adaptive_dialogs/public_room_dialog.dart b/lib/widgets/adaptive_dialogs/public_room_dialog.dart index 2add953..c604248 100644 --- a/lib/widgets/adaptive_dialogs/public_room_dialog.dart +++ b/lib/widgets/adaptive_dialogs/public_room_dialog.dart @@ -30,7 +30,9 @@ class PublicRoomDialog extends StatelessWidget { final result = await showFutureLoadingDialog( context: context, future: () async { - if (chunk != null && client.getRoomById(chunk.roomId) != null) { + if (chunk != null && + client.getRoomById(chunk.roomId) != null && + client.getRoomById(chunk.roomId)?.membership != Membership.leave) { return chunk.roomId; } final roomId = chunk != null && knock diff --git a/lib/widgets/avatar.dart b/lib/widgets/avatar.dart index 293b557..c2a0895 100644 --- a/lib/widgets/avatar.dart +++ b/lib/widgets/avatar.dart @@ -18,6 +18,8 @@ class Avatar extends StatelessWidget { final BorderRadius? borderRadius; final IconData? icon; final BorderSide? border; + final Color? backgroundColor; + final Color? textColor; const Avatar({ this.mxContent, @@ -30,6 +32,8 @@ class Avatar extends StatelessWidget { this.borderRadius, this.border, this.icon, + this.backgroundColor, + this.textColor, super.key, }); @@ -71,14 +75,16 @@ class Avatar extends StatelessWidget { height: size, placeholder: (_) => noPic ? Container( - decoration: BoxDecoration(color: name?.lightColorAvatar), + decoration: BoxDecoration( + color: backgroundColor ?? name?.lightColorAvatar, + ), alignment: Alignment.center, child: Text( fallbackLetters, textAlign: TextAlign.center, style: TextStyle( fontFamily: 'RobotoMono', - color: Colors.white, + color: textColor ?? Colors.white, fontWeight: FontWeight.bold, fontSize: (size / 2.5).roundToDouble(), ), diff --git a/pubspec.lock b/pubspec.lock index feacc4c..20451ab 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1203,10 +1203,10 @@ packages: description: path: "." ref: main - resolved-ref: "58c4cf19d010d9ae193e9df10bd1f8fdf02277b0" + resolved-ref: f678376e3a4825a6e1a00d89585d6bae0843950c url: "https://git.extera.xyz/OfficialDakari/matrix-dart-sdk.git" source: git - version: "2.0.1" + version: "3.0.1" meta: dependency: transitive description: @@ -2173,7 +2173,7 @@ packages: source: hosted version: "3.1.4" uuid: - dependency: transitive + dependency: "direct main" description: name: uuid sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff diff --git a/pubspec.yaml b/pubspec.yaml index 619a8e1..3b37331 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -101,6 +101,7 @@ dependencies: wakelock_plus: ^1.2.2 webrtc_interface: ^1.0.13 dio: ^5.9.0 + uuid: ^4.5.1 dev_dependencies: flutter_lints: ^3.0.0