From 642881b3d0a3c0131b5b85ef2f03327fabb4695d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 21:29:07 +0000 Subject: [PATCH 001/288] build: (deps): bump docker/build-push-action from 5 to 6 Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5 to 6. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v5...v6) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 1a0e42e8a9..1304de6307 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -222,7 +222,7 @@ jobs: with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - name: Build and push Docker image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . push: true From b89a6c8f46f0c24af2a0079362b809f588bc6ae8 Mon Sep 17 00:00:00 2001 From: xabirequejo Date: Fri, 31 May 2024 19:06:15 +0000 Subject: [PATCH 002/288] Translated using Weblate (Basque) Currently translated at 99.5% (629 of 632 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/ --- assets/l10n/intl_eu.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_eu.arb b/assets/l10n/intl_eu.arb index dd4d350873..2ac172707b 100644 --- a/assets/l10n/intl_eu.arb +++ b/assets/l10n/intl_eu.arb @@ -2702,5 +2702,7 @@ } }, "searchMore": "Bilatu gehiago...", - "@searchMore": {} + "@searchMore": {}, + "restricted": "Mugatuta", + "@restricted": {} } From 0e63958aa0954c3fe2208f75e83c18a9068602d2 Mon Sep 17 00:00:00 2001 From: Zig-Rust-Odin Date: Tue, 4 Jun 2024 03:27:09 +0200 Subject: [PATCH 003/288] Added translation using Weblate (Lojban) --- assets/l10n/intl_jbo.arb | 1 + 1 file changed, 1 insertion(+) create mode 100644 assets/l10n/intl_jbo.arb diff --git a/assets/l10n/intl_jbo.arb b/assets/l10n/intl_jbo.arb new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/assets/l10n/intl_jbo.arb @@ -0,0 +1 @@ +{} From 7d01cc374e193fe62e47d0635d536d5546d3e7e6 Mon Sep 17 00:00:00 2001 From: Nicholas Winterhalter Date: Mon, 3 Jun 2024 19:44:46 +0000 Subject: [PATCH 004/288] Translated using Weblate (Russian) Currently translated at 100.0% (632 of 632 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ru/ --- assets/l10n/intl_ru.arb | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_ru.arb b/assets/l10n/intl_ru.arb index 8e8021dac3..fc8497838d 100644 --- a/assets/l10n/intl_ru.arb +++ b/assets/l10n/intl_ru.arb @@ -2689,5 +2689,22 @@ "@thereAreCountUsersBlocked": { "type": "text", "count": {} - } + }, + "restricted": "Запрещено", + "@restricted": {}, + "knockRestricted": "Стук запрещен", + "@knockRestricted": {}, + "searchIn": "Поиск в чате \"{chat}\"...", + "@searchIn": { + "type": "text", + "placeholders": { + "chat": {} + } + }, + "searchMore": "Найти еще...", + "@searchMore": {}, + "gallery": "Галерея", + "@gallery": {}, + "files": "Файлы", + "@files": {} } From dc3cbd7ad67f1a0da1f16ae98538af8ef56c1394 Mon Sep 17 00:00:00 2001 From: kdh8219 Date: Mon, 3 Jun 2024 08:31:40 +0000 Subject: [PATCH 005/288] Translated using Weblate (Korean) Currently translated at 100.0% (632 of 632 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ko/ --- assets/l10n/intl_ko.arb | 48 ++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/assets/l10n/intl_ko.arb b/assets/l10n/intl_ko.arb index 4e47d233d1..0fe1afe73c 100644 --- a/assets/l10n/intl_ko.arb +++ b/assets/l10n/intl_ko.arb @@ -99,17 +99,17 @@ "type": "text", "placeholders": {} }, - "groups": "그룹", + "groups": "그룹 채팅", "@groups": { "type": "text", "placeholders": {} }, - "groupIsPublic": "그룹 공개됨", + "groupIsPublic": "그룹 채팅 공개됨", "@groupIsPublic": { "type": "text", "placeholders": {} }, - "group": "그룹", + "group": "그룹 채팅", "@group": { "type": "text", "placeholders": {} @@ -379,7 +379,7 @@ "type": "text", "placeholders": {} }, - "contactHasBeenInvitedToTheGroup": "연락처가 그룹에 초대되었습니다", + "contactHasBeenInvitedToTheGroup": "연락처가 채팅에 초대되었습니다", "@contactHasBeenInvitedToTheGroup": { "type": "text", "placeholders": {} @@ -495,7 +495,7 @@ "type": "text", "placeholders": {} }, - "changeTheNameOfTheGroup": "그룹의 이름 바꾸기", + "changeTheNameOfTheGroup": "채팅의 이름 바꾸기", "@changeTheNameOfTheGroup": { "type": "text", "placeholders": {} @@ -813,7 +813,7 @@ "type": "text", "placeholders": {} }, - "groupWith": "{displayname} 과의 그룹", + "groupWith": "{displayname}님과의 그룹", "@groupWith": { "type": "text", "placeholders": { @@ -892,7 +892,7 @@ "type": "text", "placeholders": {} }, - "whoIsAllowedToJoinThisGroup": "누가 이 그룹에 들어오도록 허용할지", + "whoIsAllowedToJoinThisGroup": "누가 이 그룹 채팅에 들어오도록 허용할지", "@whoIsAllowedToJoinThisGroup": { "type": "text", "placeholders": {} @@ -1759,7 +1759,7 @@ "type": "text", "placeholders": {} }, - "inviteContactToGroup": "연락처 {groupName} 에 초대", + "inviteContactToGroup": "연락처를 {groupName}에 초대", "@inviteContactToGroup": { "type": "text", "placeholders": { @@ -1975,7 +1975,7 @@ "type": "text", "description": "Usage hint for the command /clearcache" }, - "commandHint_create": "빈 그룹 채팅을 생성\t\n--no-encryption을 사용해 암호화를 비활성화", + "commandHint_create": "빈 그룹 채팅을 생성\n--no-encryption을 사용해 암호화를 비활성화", "@commandHint_create": { "type": "text", "description": "Usage hint for the command /create" @@ -2041,7 +2041,7 @@ "senderName": {} } }, - "commandHint_markasgroup": "그룹으로 만들기", + "commandHint_markasgroup": "그룹 채팅으로 만들기", "@commandHint_markasgroup": {}, "dehydrate": "세션을 내보내고 기기 초기화 하기", "@dehydrate": {}, @@ -2104,7 +2104,7 @@ "@widgetEtherpad": {}, "removeDevicesDescription": "이 기기에서 로그아웃되며 더 이상 메시지를 받을 수 없습니다.", "@removeDevicesDescription": {}, - "separateChatTypes": "다이렉트 채팅 및 그룹 분리", + "separateChatTypes": "다이렉트 채팅과 그룹 채팅 분리", "@separateChatTypes": { "type": "text", "placeholders": {} @@ -2334,7 +2334,7 @@ "@users": {}, "chatDescriptionHasBeenChanged": "채팅 설명 변경됨", "@chatDescriptionHasBeenChanged": {}, - "newGroup": "새 그룹", + "newGroup": "새 그룹 채팅", "@newGroup": {}, "dehydrateTor": "TOR 사용자: 세션 내보내기", "@dehydrateTor": {}, @@ -2396,11 +2396,11 @@ "@newSpace": {}, "pleaseTryAgainLaterOrChooseDifferentServer": "나중에 다시 시도하거나 다른 서버를 선택하십시오.", "@pleaseTryAgainLaterOrChooseDifferentServer": {}, - "createGroup": "그룹 만들기", + "createGroup": "새 그룹 채팅", "@createGroup": {}, "hydrateTorLong": "지난 TOR 이용에서 세션을 내보내셨나요? 빠르게 불러오고 채팅을 계속하세요.", "@hydrateTorLong": {}, - "custom": "개인화", + "custom": "커스텀", "@custom": {}, "noBackupWarning": "경고! 채팅 백업을 켜지 않을경우, 당신은 암호화된 메시지에대한 접근권한을 잃을것입니다. 로그아웃 하기 전에 채팅을 백업하는것이 강력히 권장됩니다.", "@noBackupWarning": {}, @@ -2424,7 +2424,7 @@ "@block": {}, "blockedUsers": "차단된 유저", "@blockedUsers": {}, - "groupName": "그룹 이름", + "groupName": "그룹 채팅 이름", "@groupName": {}, "commandHint_sendraw": "raw json 전송", "@commandHint_sendraw": {}, @@ -2464,7 +2464,7 @@ "sender": {} } }, - "createGroupAndInviteUsers": "그룹을 생성하고 유저를 초대", + "createGroupAndInviteUsers": "그룹 채팅을 생성하고 유저를 초대", "@createGroupAndInviteUsers": {}, "passwordsDoNotMatch": "비밀번호가 일치하지 않습니다", "@passwordsDoNotMatch": {}, @@ -2484,7 +2484,7 @@ "@hidePresences": {}, "searchChatsRooms": "#chats, @users 검색...", "@searchChatsRooms": {}, - "groupCanBeFoundViaSearch": "검색으로 그룹을 찾을 수 있음", + "groupCanBeFoundViaSearch": "검색으로 그룹 채팅을 찾을 수 있음", "@groupCanBeFoundViaSearch": {}, "restoreSessionBody": "앱이 백업에서 세션을 복원하려 시도중입니다. {url} 에서 개발자에게 오류를 신고하세요. 오류 메시지는 다음과 같습니다: {error}", "@restoreSessionBody": { @@ -2622,13 +2622,13 @@ "@calls": {}, "globalChatId": "글로벌 채팅 ID", "@globalChatId": {}, - "customEmojisAndStickers": "개인 이모지와 스티커", + "customEmojisAndStickers": "커스텀 이모지와 스티커", "@customEmojisAndStickers": {}, - "accessAndVisibilityDescription": "채팅에 참여하거나 찾을 수 있는 사람을 설정합니다.", + "accessAndVisibilityDescription": "채팅에 참여 할 수 있는 사람과 채팅을 볼 수 있는 범위", "@accessAndVisibilityDescription": {}, - "accessAndVisibility": "방 가입과 대화 기록", + "accessAndVisibility": "채팅 가입과 대화 기록", "@accessAndVisibility": {}, - "customEmojisAndStickersBody": "모든 채팅에서 사용할 수있는 개인 이모지와 스티커를 추가하거나 공유합니다.", + "customEmojisAndStickersBody": "모든 채팅에서 사용할 수있는 커스텀 이모지와 스티커를 추가하거나 공유합니다.", "@customEmojisAndStickersBody": {}, "hideRedactedMessages": "삭제된 메시지 숨기기", "@hideRedactedMessages": {}, @@ -2701,5 +2701,9 @@ } }, "gallery": "갤러리", - "@gallery": {} + "@gallery": {}, + "restricted": "스페이스 멤버로 제한", + "@restricted": {}, + "knockRestricted": "스페이스 멤버만 참가 요청 가능", + "@knockRestricted": {} } From cf0ee2f6154ef441722c9bf442bb1dd3c751a84a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=20Tam=C3=A1s?= Date: Wed, 5 Jun 2024 13:18:07 +0000 Subject: [PATCH 006/288] Translated using Weblate (Hungarian) Currently translated at 100.0% (632 of 632 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/hu/ --- assets/l10n/intl_hu.arb | 48 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/assets/l10n/intl_hu.arb b/assets/l10n/intl_hu.arb index daae996ce1..185a048165 100644 --- a/assets/l10n/intl_hu.arb +++ b/assets/l10n/intl_hu.arb @@ -2245,7 +2245,7 @@ }, "addChatDescription": "Chat leírás hozzáadása...", "@addChatDescription": {}, - "hasKnocked": "{user} kopogott", + "hasKnocked": "🚪 {user} bekopogott", "@hasKnocked": { "placeholders": { "user": {} @@ -2419,7 +2419,7 @@ "@screenSharingDetail": {}, "placeCall": "Tér hívás", "@placeCall": {}, - "block": "blokkolás", + "block": "Blokkolás", "@block": {}, "blockedUsers": "Blokkolt felhasználók", "@blockedUsers": {}, @@ -2664,5 +2664,47 @@ "appname": {}, "unread": {} } - } + }, + "searchIn": "Keresés a csevegésben \"{chat}\"...", + "@searchIn": { + "type": "text", + "placeholders": { + "chat": {} + } + }, + "files": "Fájlok", + "@files": {}, + "commandHint_unignore": "Adott matrix ID figyelembe vétele", + "@commandHint_unignore": {}, + "restricted": "Korlátozott", + "@restricted": {}, + "knockRestricted": "Kopogás korlátozva", + "@knockRestricted": {}, + "globalChatId": "Globális csevegő azonosító", + "@globalChatId": {}, + "hideRedactedMessages": "Szerkesztett üzenetek elrejtése", + "@hideRedactedMessages": {}, + "hideRedactedMessagesBody": "Ha valaki szerkeszti az üzenetét, ez az üzenet nem jelenik meg a csevegés során.", + "@hideRedactedMessagesBody": {}, + "hideMemberChangesInPublicChats": "Tag változások elrejtése a publikus csevegésben", + "@hideMemberChangesInPublicChats": {}, + "knocking": "Bekopogás", + "@knocking": {}, + "usersMustKnock": "A felhasználóknak be kell kopogniuk", + "@usersMustKnock": {}, + "knock": "Kopogás", + "@knock": {}, + "minimumPowerLevel": "{level} a minimum szint.", + "@minimumPowerLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "searchMore": "További keresés...", + "@searchMore": {}, + "gallery": "Galéria", + "@gallery": {}, + "commandHint_ignore": "Adott matrix ID figyelmen kívül hagyása", + "@commandHint_ignore": {} } From cd39caf2d386c0613c83ec31940df0e29e3fc4a0 Mon Sep 17 00:00:00 2001 From: Milo Ivir Date: Fri, 7 Jun 2024 18:54:22 +0000 Subject: [PATCH 007/288] Translated using Weblate (Croatian) Currently translated at 95.4% (603 of 632 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/hr/ --- assets/l10n/intl_hr.arb | 136 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 133 insertions(+), 3 deletions(-) diff --git a/assets/l10n/intl_hr.arb b/assets/l10n/intl_hr.arb index dcf927e7ed..5fb22feeae 100644 --- a/assets/l10n/intl_hr.arb +++ b/assets/l10n/intl_hr.arb @@ -914,7 +914,7 @@ "targetName": {} } }, - "kickedAndBanned": "🙅 {username} je izbacio/la i isključio/la {targetName}", + "kickedAndBanned": "🙅 {username} je izbacio/la i blokirao/la {targetName}", "@kickedAndBanned": { "type": "text", "placeholders": { @@ -2029,7 +2029,7 @@ "@recoveryKey": {}, "recoveryKeyLost": "Izgubio/la si ključ za obnavljanje?", "@recoveryKeyLost": {}, - "youKickedAndBanned": "🙅 Izbacio/la si i isključio/la korisnika {user}", + "youKickedAndBanned": "🙅 Izbacio/la si i blokirao/la korisnika {user}", "@youKickedAndBanned": { "placeholders": { "user": {} @@ -2449,5 +2449,135 @@ "discover": "Otkrij", "@discover": {}, "formattedMessagesDescription": "Prikaži formatirani sadržaj poruke poput podebljanog teksta koristeći markdown.", - "@formattedMessagesDescription": {} + "@formattedMessagesDescription": {}, + "nothingFound": "Ništa nije pronađeno...", + "@nothingFound": {}, + "select": "Odaberi", + "@select": {}, + "newPassword": "Nova lozinka", + "@newPassword": {}, + "unbanUserDescription": "Korisnik će se ponovo moći pridružiti razgovoru ako pokuša.", + "@unbanUserDescription": {}, + "publicSpaces": "Javni prostori", + "@publicSpaces": {}, + "subspace": "Podprostori", + "@subspace": {}, + "decline": "Odbij", + "@decline": {}, + "thisDevice": "Ovaj uređaj:", + "@thisDevice": {}, + "presenceStyle": "Prisutnost:", + "@presenceStyle": { + "type": "text", + "placeholders": {} + }, + "presencesToggle": "Prikaži poruke stanja od drugih korisnika", + "@presencesToggle": { + "type": "text", + "placeholders": {} + }, + "noPublicLinkHasBeenCreatedYet": "Još nije stvorena nijedna javna poveznica", + "@noPublicLinkHasBeenCreatedYet": {}, + "hidePresences": "Sakriti popis stanja?", + "@hidePresences": {}, + "pleaseEnterYourCurrentPassword": "Upiši svoju trenutačnu lozinku", + "@pleaseEnterYourCurrentPassword": {}, + "publicLink": "Javna poveznica", + "@publicLink": {}, + "passwordIsWrong": "Tvoja upisana lozinka je kriva", + "@passwordIsWrong": {}, + "initAppError": "Dogodila se greška prilikom inicijaliziranja aplikacije", + "@initAppError": {}, + "hideRedactedMessagesBody": "Ako netko redigira poruku, ta poruka više neće biti vidljiva u razgovoru.", + "@hideRedactedMessagesBody": {}, + "kickUserDescription": "Korisnik je izbačen iz razgovora, ali nije blokiran. U javnim razgovorima se korisnik može ponovo pridružiti u bilo kojem trenutku.", + "@kickUserDescription": {}, + "addChatOrSubSpace": "Dodaj razgovor ili podpodručje", + "@addChatOrSubSpace": {}, + "appLockDescription": "Zaključaj aplikaciju kada je ne koristiš s PIN kodom", + "@appLockDescription": {}, + "globalChatId": "Globalni ID razgovora", + "@globalChatId": {}, + "hideRedactedMessages": "Sakrij redigirane poruke", + "@hideRedactedMessages": {}, + "hideInvalidOrUnknownMessageFormats": "Sakrij nevažeće ili nepoznate formate poruka", + "@hideInvalidOrUnknownMessageFormats": {}, + "overview": "Pregled", + "@overview": {}, + "notifyMeFor": "Obavijesit me za", + "@notifyMeFor": {}, + "passwordRecoverySettings": "Postavke za obnavljanje lozinke", + "@passwordRecoverySettings": {}, + "hideMemberChangesInPublicChats": "Sakrij promjene članova u javnim razgovorima", + "@hideMemberChangesInPublicChats": {}, + "youInvitedToBy": "📩 Pozvan/a si putem poveznice na:\n{alias}", + "@youInvitedToBy": { + "placeholders": { + "alias": {} + } + }, + "usersMustKnock": "Korisnici moraju pokucati", + "@usersMustKnock": {}, + "noOneCanJoin": "Nitko se ne može pridružiti", + "@noOneCanJoin": {}, + "userWouldLikeToChangeTheChat": "{user} se želi pridružiti razgovoru.", + "@userWouldLikeToChangeTheChat": { + "placeholders": { + "user": {} + } + }, + "knock": "Pokucaj", + "@knock": {}, + "knocking": "Kucanje", + "@knocking": {}, + "chatCanBeDiscoveredViaSearchOnServer": "Razgovor se može otkriti pretraživanjem servera {server}", + "@chatCanBeDiscoveredViaSearchOnServer": { + "type": "text", + "placeholders": { + "server": {} + } + }, + "searchForUsers": "Traži @users...", + "@searchForUsers": {}, + "pleaseChooseAStrongPassword": "Odaberi snažnu lozinku", + "@pleaseChooseAStrongPassword": {}, + "joinSpace": "Pridruži se prostoru", + "@joinSpace": {}, + "publicChatAddresses": "Adrese javnih razgovora", + "@publicChatAddresses": {}, + "createNewAddress": "Stvori novu adresu", + "@createNewAddress": {}, + "userRole": "Korisnička uloga", + "@userRole": {}, + "verifyOtherUser": "🔐 Potvrdi drugog korisnika", + "@verifyOtherUser": {}, + "sendTypingNotificationsDescription": "Drugi sudionici u razgovoru mogu vidjeti kada pišeš novu poruku.", + "@sendTypingNotificationsDescription": {}, + "sendReadReceiptsDescription": "Drugi sudionici u raygovoru mogu vidjeti kada pročitaš poruku.", + "@sendReadReceiptsDescription": {}, + "searchIn": "Traži u razgovoru „{chat}”...", + "@searchIn": { + "type": "text", + "placeholders": { + "chat": {} + } + }, + "searchMore": "Traži više...", + "@searchMore": {}, + "gallery": "Galerija", + "@gallery": {}, + "files": "Datoteke", + "@files": {}, + "verifyOtherDevice": "🔐 Potvrdi drugi uređaj", + "@verifyOtherDevice": {}, + "unreadChatsInApp": "{appname}: Nroj nepročitanih razgovora: {unread}", + "@unreadChatsInApp": { + "type": "text", + "placeholders": { + "appname": {}, + "unread": {} + } + }, + "commandHint_ignore": "Zanemari navedeni matrix ID", + "@commandHint_ignore": {} } From 80d81237f65ca28f16a7b98874f0fd2547b18d29 Mon Sep 17 00:00:00 2001 From: lucasmz-dev Date: Thu, 6 Jun 2024 23:04:15 +0000 Subject: [PATCH 008/288] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (632 of 632 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/pt_BR/ --- assets/l10n/intl_pt_BR.arb | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_pt_BR.arb b/assets/l10n/intl_pt_BR.arb index cf700209b1..95ba83675a 100644 --- a/assets/l10n/intl_pt_BR.arb +++ b/assets/l10n/intl_pt_BR.arb @@ -2688,5 +2688,22 @@ "createNewAddress": "Criar um novo endereço", "@createNewAddress": {}, "knock": "Bater na porta", - "@knock": {} + "@knock": {}, + "searchIn": "Pesquisar em {chat}...", + "@searchIn": { + "type": "text", + "placeholders": { + "chat": {} + } + }, + "restricted": "Restrito", + "@restricted": {}, + "knockRestricted": "Bater na porta restrito", + "@knockRestricted": {}, + "searchMore": "Pesquisar mais...", + "@searchMore": {}, + "gallery": "Galeria", + "@gallery": {}, + "files": "Arquivos", + "@files": {} } From f04728192961a33cef3dff2a63f71b66104364a3 Mon Sep 17 00:00:00 2001 From: xabirequejo Date: Sat, 8 Jun 2024 11:02:25 +0000 Subject: [PATCH 009/288] Translated using Weblate (Basque) Currently translated at 99.8% (631 of 632 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/ --- assets/l10n/intl_eu.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/l10n/intl_eu.arb b/assets/l10n/intl_eu.arb index 2ac172707b..bb7b8dfa2b 100644 --- a/assets/l10n/intl_eu.arb +++ b/assets/l10n/intl_eu.arb @@ -2694,7 +2694,7 @@ "@files": {}, "gallery": "Galeria", "@gallery": {}, - "searchIn": "Bilatu {chat}ean...", + "searchIn": "Bilatu {chat} txatean...", "@searchIn": { "type": "text", "placeholders": { From 7e38a8f6776254786173e269d020fcc1b5085d63 Mon Sep 17 00:00:00 2001 From: Milo Ivir Date: Sat, 8 Jun 2024 11:04:27 +0000 Subject: [PATCH 010/288] Translated using Weblate (Croatian) Currently translated at 100.0% (632 of 632 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/hr/ --- assets/l10n/intl_hr.arb | 131 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 2 deletions(-) diff --git a/assets/l10n/intl_hr.arb b/assets/l10n/intl_hr.arb index 5fb22feeae..6d763105e9 100644 --- a/assets/l10n/intl_hr.arb +++ b/assets/l10n/intl_hr.arb @@ -949,7 +949,7 @@ "type": "text", "placeholders": {} }, - "lightTheme": "Svjetla", + "lightTheme": "Svijetla", "@lightTheme": { "type": "text", "placeholders": {} @@ -2579,5 +2579,132 @@ } }, "commandHint_ignore": "Zanemari navedeni matrix ID", - "@commandHint_ignore": {} + "@commandHint_ignore": {}, + "blockListDescription": "Možeš blokirati korisnike koji te ometaju. Nećeš moći primati poruke ili pozivnice za sobe od korisnika koji se nalaze u tvom osobnom popisu blokiranih.", + "@blockListDescription": {}, + "isReadyForKeyVerification": "{sender} je spreman/na za potvrđivanje ključa", + "@isReadyForKeyVerification": { + "type": "text", + "placeholders": { + "sender": {} + } + }, + "banUserDescription": "Korisnik će biti isključen iz razgovora i moći će ponovo prisustvovati razgovoru kad ga se deblokira.", + "@banUserDescription": {}, + "sessionLostBody": "Tvoja je sesija izgubljena. Prijavi ovu grešku programerima na {url}. Poruka o grešci glasi: {error}", + "@sessionLostBody": { + "type": "text", + "placeholders": { + "url": {}, + "error": {} + } + }, + "completedKeyVerification": "{sender} je dovršio/la potvrđivanje ključa", + "@completedKeyVerification": { + "type": "text", + "placeholders": { + "sender": {} + } + }, + "archiveRoomDescription": "Razgovor će se premjestiti u arhivu. Drugi korisnici će moći vidjeti da si napustio/la razgovor.", + "@archiveRoomDescription": {}, + "removeDevicesDescription": "Bit ćeš odjavljen/a s ovog uređaja i više nećeš moći primati poruke.", + "@removeDevicesDescription": {}, + "noUsersFoundWithQuery": "Nažalost nije pronađen nijedan korisnik s „{query}”. Provjeri točnost upisa.", + "@noUsersFoundWithQuery": { + "type": "text", + "placeholders": { + "query": {} + } + }, + "restoreSessionBody": "Aplikacija sada pokušava obnoviti tvoju sesiju iz sigurnosne kopije. Prijavi ovu grešku programerima na {url}. Poruka o grešci glasi: {error}", + "@restoreSessionBody": { + "type": "text", + "placeholders": { + "url": {}, + "error": {} + } + }, + "requestedKeyVerification": "{sender} je zatražio/la potvrđivanje ključa", + "@requestedKeyVerification": { + "type": "text", + "placeholders": { + "sender": {} + } + }, + "restricted": "Ograničeni", + "@restricted": {}, + "roomUpgradeDescription": "Razgovor će se tada ponovo stvoriti s novom verzijom sobe. Svi sudionici će biti obaviješteni da se moraju prebaciti na novi razgovor. Više o verzijama soba možeš saznati na https://spec.matrix.org/latest/rooms/", + "@roomUpgradeDescription": {}, + "noGoogleServicesWarning": "Čini se da Firebase Cloud Messaging nije dostupan na tvom uređaju. Za daljnje primanje push obavijesti, preporučujemo da instaliraš ntfy. S ntfy ili drugim pružateljem usluge Unified Push možeš primati push obavijesti na podatkovno siguran način. Ntfy možeš preuzeti s PlayStorea ili s F-Droida.", + "@noGoogleServicesWarning": { + "type": "text", + "placeholders": {} + }, + "verifyOtherDeviceDescription": "Kada potvrdiš jedan drugi uređaj, ti uređaji mogu razmjenjivati ključeve, povećavajući tvoju ukupnu sigurnost. 💪 Kada pokreneš provjeru, pojavit će se skočni prozor u aplikaciji na oba uređaja. Tamo ćeš tada vidjeti niz emojija ili brojeve koje moraš međusobno usporediti. Najbolje je imati oba uređaja pri ruci prije nego što započneš provjeru. 🤳", + "@verifyOtherDeviceDescription": {}, + "verifyOtherUserDescription": "Ako potvrdiš jednog drugog korisnika, možeš biti siguran/na da znaš kome zapravo pišeš. 💪\n\nKada pokreneš provjeru, vi i drugi korisnik vidjet ćete skočni prozor u aplikaciji. Tamo ćeš tada vidjeti niz emojija ili brojeve koje morate međusobno usporediti.\n\nNajbolji način za to je da se nađete zajedno ili započnete videopoziv. 👭", + "@verifyOtherUserDescription": {}, + "knockRestricted": "Pokucaj na ograničene sobe", + "@knockRestricted": {}, + "hideMemberChangesInPublicChatsBody": "Za bolju čitljivosti, na vremenskoj traci razgovora nemoj prikazivati kad se netko pridruži ili napusti javni razgovor.", + "@hideMemberChangesInPublicChatsBody": {}, + "makeAdminDescription": "Nakon postavljanja ovog korisnika kao administratora, to možda nećeš moći poništiti jer će on tada imati iste dozvole kao i ti.", + "@makeAdminDescription": {}, + "leaveEmptyToClearStatus": "Ostavi prazno za brisanje tvog stanja.", + "@leaveEmptyToClearStatus": {}, + "forwardMessageTo": "Proslijediti poruku u sobu {roomName}?", + "@forwardMessageTo": { + "type": "text", + "placeholders": { + "roomName": {} + } + }, + "minimumPowerLevel": "{level} je najmanja razina prava.", + "@minimumPowerLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "databaseBuildErrorBody": "Nije moguće izgraditi SQlite bazu podataka. Aplikacija za sada pokušava koristiti staru bazu podataka. Prijavi ovu grešku programerima na {url}. Poruka o grešci glasi: {error}", + "@databaseBuildErrorBody": { + "type": "text", + "placeholders": { + "url": {}, + "error": {} + } + }, + "sendReadReceipts": "Šalji potvrde o čitanju", + "@sendReadReceipts": {}, + "acceptedKeyVerification": "{sender} je prihvatio/la potvrđivanje ključa", + "@acceptedKeyVerification": { + "type": "text", + "placeholders": { + "sender": {} + } + }, + "canceledKeyVerification": "{sender} je prekinuo/la potvrđivanje ključa", + "@canceledKeyVerification": { + "type": "text", + "placeholders": { + "sender": {} + } + }, + "startedKeyVerification": "{sender} je pokrenuo/la potvrđivanje ključa", + "@startedKeyVerification": { + "type": "text", + "placeholders": { + "sender": {} + } + }, + "commandHint_unignore": "Poništi zanemarivanje navedenog matrix ID-a", + "@commandHint_unignore": {}, + "noDatabaseEncryption": "Šifriranje baze podataka nije podržano na ovoj platformi", + "@noDatabaseEncryption": {}, + "thereAreCountUsersBlocked": "Broj trenutačno blokiranih korisnika: {count}.", + "@thereAreCountUsersBlocked": { + "type": "text", + "count": {} + } } From 1a8fd974e69f2dc586c6d394a360e8f30278bf62 Mon Sep 17 00:00:00 2001 From: Milo Ivir Date: Sun, 9 Jun 2024 17:09:42 +0000 Subject: [PATCH 011/288] Translated using Weblate (Croatian) Currently translated at 100.0% (633 of 633 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/hr/ --- assets/l10n/intl_hr.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_hr.arb b/assets/l10n/intl_hr.arb index 6d763105e9..a3f981b2dd 100644 --- a/assets/l10n/intl_hr.arb +++ b/assets/l10n/intl_hr.arb @@ -2706,5 +2706,7 @@ "@thereAreCountUsersBlocked": { "type": "text", "count": {} - } + }, + "swipeRightToLeftToReply": "Za odgovaranje povuci prstom zdesna ulijevo", + "@swipeRightToLeftToReply": {} } From e2c5267f7788386e4e5308687c8eb34c4d782892 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Sun, 9 Jun 2024 13:13:56 +0000 Subject: [PATCH 012/288] Translated using Weblate (Turkish) Currently translated at 100.0% (633 of 633 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/tr/ --- assets/l10n/intl_tr.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_tr.arb b/assets/l10n/intl_tr.arb index aead65362a..18b90caaf5 100644 --- a/assets/l10n/intl_tr.arb +++ b/assets/l10n/intl_tr.arb @@ -2706,5 +2706,7 @@ "knockRestricted": "Tıklatma kısıtlı", "@knockRestricted": {}, "restricted": "Kısıtlı", - "@restricted": {} + "@restricted": {}, + "swipeRightToLeftToReply": "Yanıtlamak için sağdan sola kaydır", + "@swipeRightToLeftToReply": {} } From 933bef648381bd060f2a60b81c1286161b0a848f Mon Sep 17 00:00:00 2001 From: kdh8219 Date: Sun, 9 Jun 2024 11:11:29 +0000 Subject: [PATCH 013/288] Translated using Weblate (Korean) Currently translated at 100.0% (633 of 633 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ko/ --- assets/l10n/intl_ko.arb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/assets/l10n/intl_ko.arb b/assets/l10n/intl_ko.arb index 0fe1afe73c..18d59f90a5 100644 --- a/assets/l10n/intl_ko.arb +++ b/assets/l10n/intl_ko.arb @@ -727,7 +727,7 @@ "type": "text", "placeholders": {} }, - "answeredTheCall": "{senderName} 가 전화에 응답했습니다", + "answeredTheCall": "{senderName}님이 전화에 응답했습니다", "@answeredTheCall": { "type": "text", "placeholders": { @@ -2705,5 +2705,7 @@ "restricted": "스페이스 멤버로 제한", "@restricted": {}, "knockRestricted": "스페이스 멤버만 참가 요청 가능", - "@knockRestricted": {} + "@knockRestricted": {}, + "swipeRightToLeftToReply": "오른쪽에서 왼쪽으로 스와이프해서 답장", + "@swipeRightToLeftToReply": {} } From 8a79489fe58a69e9cbe5ee15164ae553967a0afe Mon Sep 17 00:00:00 2001 From: Rex_sa Date: Sun, 9 Jun 2024 19:33:41 +0000 Subject: [PATCH 014/288] Translated using Weblate (Arabic) Currently translated at 100.0% (633 of 633 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ar/ --- assets/l10n/intl_ar.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_ar.arb b/assets/l10n/intl_ar.arb index 8cd5a4080b..53b18d7b1b 100644 --- a/assets/l10n/intl_ar.arb +++ b/assets/l10n/intl_ar.arb @@ -2706,5 +2706,7 @@ "searchMore": "ابحث أكثر...", "@searchMore": {}, "gallery": "المعرض", - "@gallery": {} + "@gallery": {}, + "swipeRightToLeftToReply": "اسحب من اليمين إلى اليسار للرد", + "@swipeRightToLeftToReply": {} } From 76a7e7cff78148bcce5a1830be389d9bf515cf8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Mon, 10 Jun 2024 11:32:40 +0000 Subject: [PATCH 015/288] Translated using Weblate (Estonian) Currently translated at 100.0% (633 of 633 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/ --- assets/l10n/intl_et.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_et.arb b/assets/l10n/intl_et.arb index 649199cc5a..867822dc36 100644 --- a/assets/l10n/intl_et.arb +++ b/assets/l10n/intl_et.arb @@ -2706,5 +2706,7 @@ "gallery": "Galerii", "@gallery": {}, "files": "Failid", - "@files": {} + "@files": {}, + "swipeRightToLeftToReply": "Vastamiseks viipa paremalt vasakule", + "@swipeRightToLeftToReply": {} } From ddbece27bade900a592416f38f6a4f87aaa66d40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jos=C3=A9=20m?= Date: Mon, 10 Jun 2024 06:27:12 +0000 Subject: [PATCH 016/288] Translated using Weblate (Galician) Currently translated at 100.0% (633 of 633 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/gl/ --- assets/l10n/intl_gl.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_gl.arb b/assets/l10n/intl_gl.arb index 919b035c23..2fe2464f56 100644 --- a/assets/l10n/intl_gl.arb +++ b/assets/l10n/intl_gl.arb @@ -2706,5 +2706,7 @@ "knockRestricted": "Peta á porta", "@knockRestricted": {}, "restricted": "Non accesible", - "@restricted": {} + "@restricted": {}, + "swipeRightToLeftToReply": "Despraza hacia a esquerda para responder", + "@swipeRightToLeftToReply": {} } From 61f386cd785960a10ae9d2d91d313b552641d35a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E7=8E=8B=E5=8F=AB=E6=88=91=E6=9D=A5=E5=B7=A1?= =?UTF-8?q?=E5=B1=B1?= Date: Mon, 10 Jun 2024 01:56:24 +0000 Subject: [PATCH 017/288] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (633 of 633 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hans/ --- assets/l10n/intl_zh.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_zh.arb b/assets/l10n/intl_zh.arb index 72961c998a..8ca5e31531 100644 --- a/assets/l10n/intl_zh.arb +++ b/assets/l10n/intl_zh.arb @@ -2706,5 +2706,7 @@ "knockRestricted": "“请求加入”请求受限", "@knockRestricted": {}, "restricted": "受限", - "@restricted": {} + "@restricted": {}, + "swipeRightToLeftToReply": "从右向左滑动进行回复", + "@swipeRightToLeftToReply": {} } From 09ae9dae2cfb3316ec89dadf0036857dea98e0b8 Mon Sep 17 00:00:00 2001 From: Nicholas Winterhalter Date: Wed, 12 Jun 2024 21:44:01 +0000 Subject: [PATCH 018/288] Translated using Weblate (Russian) Currently translated at 100.0% (633 of 633 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ru/ --- assets/l10n/intl_ru.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_ru.arb b/assets/l10n/intl_ru.arb index fc8497838d..7cc58523a8 100644 --- a/assets/l10n/intl_ru.arb +++ b/assets/l10n/intl_ru.arb @@ -2706,5 +2706,7 @@ "gallery": "Галерея", "@gallery": {}, "files": "Файлы", - "@files": {} + "@files": {}, + "swipeRightToLeftToReply": "Для ответа проведите с права на лево", + "@swipeRightToLeftToReply": {} } From dad96bd2c847fa87bbc13662b989c201743061f5 Mon Sep 17 00:00:00 2001 From: Eryk Michalak Date: Sat, 15 Jun 2024 15:08:05 +0000 Subject: [PATCH 019/288] Translated using Weblate (Polish) Currently translated at 85.7% (543 of 633 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/pl/ --- assets/l10n/intl_pl.arb | 55 ++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/assets/l10n/intl_pl.arb b/assets/l10n/intl_pl.arb index b8e6517e2b..506b85f6d6 100644 --- a/assets/l10n/intl_pl.arb +++ b/assets/l10n/intl_pl.arb @@ -2393,23 +2393,44 @@ "@importNow": {}, "invite": "Zaproszenie", "@invite": {}, - "@banUserDescription": {}, - "@removeDevicesDescription": {}, - "@unbanUserDescription": {}, - "@pushNotificationsNotAvailable": {}, - "@makeAdminDescription": {}, - "@archiveRoomDescription": {}, - "@hasKnocked": { - "placeholders": { - "user": {} - } - }, - "@learnMore": {}, - "@roomUpgradeDescription": {}, - "@pleaseEnterANumber": {}, - "@kickUserDescription": {}, - "block": "zablokuj", + "block": "Zablokuj", "@block": {}, "blockedUsers": "Zablokowani użytkownicy", - "@blockedUsers": {} + "@blockedUsers": {}, + "blockUsername": "Ignoruj użytkownika", + "@blockUsername": {}, + "publicLink": "Link publiczny", + "@publicLink": {}, + "transparent": "Przezroczystość", + "@transparent": {}, + "select": "Zaznacz", + "@select": {}, + "calls": "Połączenia", + "@calls": {}, + "overview": "Podsumowanie", + "@overview": {}, + "learnMore": "Dowiedz się więcej", + "@learnMore": {}, + "groupName": "Nazwa grupy", + "@groupName": {}, + "startConversation": "Rozpocznij rozmowę", + "@startConversation": {}, + "newPassword": "Nowe hasło", + "@newPassword": {}, + "thisDevice": "To urządzenie:", + "@thisDevice": {}, + "gallery": "Galeria", + "@gallery": {}, + "files": "Pliki", + "@files": {}, + "discover": "Odkrywaj", + "@discover": {}, + "restricted": "Ograniczone", + "@restricted": {}, + "decline": "Odmów", + "@decline": {}, + "nothingFound": "Nic nie odnaleziono...", + "@nothingFound": {}, + "stickers": "Naklejki", + "@stickers": {} } From 42e1b6d96135fe52499915cd8479bed386b69dce Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 18 Jun 2024 07:12:13 +0200 Subject: [PATCH 020/288] Deleted translation using Weblate (Lojban) --- assets/l10n/intl_jbo.arb | 1 - 1 file changed, 1 deletion(-) delete mode 100644 assets/l10n/intl_jbo.arb diff --git a/assets/l10n/intl_jbo.arb b/assets/l10n/intl_jbo.arb deleted file mode 100644 index 0967ef424b..0000000000 --- a/assets/l10n/intl_jbo.arb +++ /dev/null @@ -1 +0,0 @@ -{} From 520d854e689ed8670b286745aec55bd24e9e2c6b Mon Sep 17 00:00:00 2001 From: Krille Date: Mon, 24 Jun 2024 13:46:41 +0200 Subject: [PATCH 021/288] build: Update matrix dart sdk --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 7d4febdb25..878f03c87a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -64,7 +64,7 @@ dependencies: keyboard_shortcuts: ^0.1.4 latlong2: ^0.9.1 linkify: ^5.0.0 - matrix: ^0.29.12 + matrix: ^0.29.13 native_imaging: ^0.1.1 package_info_plus: ^6.0.0 pasteboard: ^0.2.0 From cdd32e7002926cd59f4980c33d56eee4274af8bd Mon Sep 17 00:00:00 2001 From: Krille Date: Wed, 26 Jun 2024 14:46:51 +0200 Subject: [PATCH 022/288] chore: Update last event after decryption --- lib/pages/chat_list/chat_list_item.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/pages/chat_list/chat_list_item.dart b/lib/pages/chat_list/chat_list_item.dart index 965c73917a..4ac02e62b6 100644 --- a/lib/pages/chat_list/chat_list_item.dart +++ b/lib/pages/chat_list/chat_list_item.dart @@ -232,7 +232,9 @@ class ChatListItem extends StatelessWidget { softWrap: false, ) : FutureBuilder( - key: ValueKey(lastEvent?.eventId), + key: ValueKey( + '${lastEvent?.eventId}_${lastEvent?.type}', + ), future: needLastEventSender ? lastEvent.calcLocalizedBody( MatrixLocals(L10n.of(context)!), From 2b630aca4d1a2d67b3796c50cc8ce0067eb9eef8 Mon Sep 17 00:00:00 2001 From: Krille Date: Thu, 27 Jun 2024 15:26:56 +0200 Subject: [PATCH 023/288] fix: Correctly localize time of date --- lib/utils/date_time_extension.dart | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/lib/utils/date_time_extension.dart b/lib/utils/date_time_extension.dart index 121eaf72e7..f546f9a5a6 100644 --- a/lib/utils/date_time_extension.dart +++ b/lib/utils/date_time_extension.dart @@ -34,14 +34,8 @@ extension DateTimeExtension on DateTime { } /// Returns a simple time String. - /// TODO: Add localization - String localizedTimeOfDay(BuildContext context) { - if (MediaQuery.of(context).alwaysUse24HourFormat) { - return '${_z(hour)}:${_z(minute)}'; - } else { - return '${_z(hour % 12 == 0 ? 12 : hour % 12)}:${_z(minute)} ${hour > 11 ? "pm" : "am"}'; - } - } + String localizedTimeOfDay(BuildContext context) => + DateFormat.Hm(L10n.of(context)!.localeName).format(this); /// Returns [localizedTimeOfDay()] if the ChatTime is today, the name of the week /// day if the ChatTime is this week and a date string else. @@ -91,6 +85,4 @@ extension DateTimeExtension on DateTime { localizedTimeOfDay(context), ); } - - static String _z(int i) => i < 10 ? '0${i.toString()}' : i.toString(); } From 0ca196f4df12c484ad578f9b3c8d62b15489906d Mon Sep 17 00:00:00 2001 From: xabirequejo Date: Thu, 27 Jun 2024 20:12:46 +0000 Subject: [PATCH 024/288] Translated using Weblate (Basque) Currently translated at 99.6% (631 of 633 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/ --- assets/l10n/intl_eu.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/l10n/intl_eu.arb b/assets/l10n/intl_eu.arb index bb7b8dfa2b..73b2e069e8 100644 --- a/assets/l10n/intl_eu.arb +++ b/assets/l10n/intl_eu.arb @@ -2202,7 +2202,7 @@ }, "hideUnimportantStateEvents": "Ezkutatu garrantzirik gabeko gertaerak", "@hideUnimportantStateEvents": {}, - "noKeyForThisMessage": "Mezua gailu honetan saioa hasi baino lehen bidali bazen gertatu daiteke.\n\nBeste aukera bat igorleak zure gailua blokeatu izana da, edo zerbaitek huts egin izana interneteko konexioan.\n\nMezua beste saio batean irakur dezakezu? Hala bada, mezua transferitu dezakezu! Zoaz Ezrpenetara > Gailuak eta baieztatu zure gailuek bata bestea egiaztatu dutela. Gela irakiko duzun hurrengo aldian eta bi saioak aurreko planoan irekita daudenean, gakoak automatikoki partekatuko dira.\n\nEz duzu gakorik galdu nahi saioa amaitu edo gailuak aldatzen dituzunean? Baieztatu ezarpenetan txaten babeskopiak gaituta dituzula.", + "noKeyForThisMessage": "Mezua gailu honetan saioa hasi baino lehen bidali bazen gertatu daiteke.\n\nBeste aukera bat igorleak zure gailua blokeatu izana da, edo zerbaitek huts egin izana interneteko konexioan.\n\nMezua beste saio batean irakur dezakezu? Hala bada, mezua transferitu dezakezu! Zoaz Ezrpenetara > Gailuak eta baieztatu zure gailuek bata bestea egiaztatu dutela. Gela irekiko duzun hurrengo aldian eta bi saioak aurreko planoan irekita daudenean, gakoak automatikoki partekatuko dira.\n\nEz duzu gakorik galdu nahi saioa amaitu edo gailuak aldatzen dituzunean? Baieztatu ezarpenetan txaten babeskopiak gaituta dituzula.", "@noKeyForThisMessage": {}, "supposedMxid": "Hau {mxid} izan behar da", "@supposedMxid": { From 233e7f48894f9afeb80b6c2e8e24738829e75b99 Mon Sep 17 00:00:00 2001 From: kdh8219 Date: Thu, 27 Jun 2024 08:50:10 +0000 Subject: [PATCH 025/288] Translated using Weblate (Korean) Currently translated at 100.0% (633 of 633 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ko/ --- assets/l10n/intl_ko.arb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/assets/l10n/intl_ko.arb b/assets/l10n/intl_ko.arb index 18d59f90a5..6ff3daaea3 100644 --- a/assets/l10n/intl_ko.arb +++ b/assets/l10n/intl_ko.arb @@ -2092,7 +2092,7 @@ "@user": {}, "youAcceptedTheInvitation": "👍 초대를 수락했습니다", "@youAcceptedTheInvitation": {}, - "youInvitedBy": "📩 {user}에 의해 초대되었습니다", + "youInvitedBy": "📩 {user}님에 의해 초대되었습니다", "@youInvitedBy": { "placeholders": { "user": {} @@ -2111,7 +2111,7 @@ }, "tryAgain": "다시 시도하기", "@tryAgain": {}, - "youKickedAndBanned": "🙅 {user}를 영구 추방했습니다", + "youKickedAndBanned": "🙅 {user}님을 영구 추방했습니다", "@youKickedAndBanned": { "placeholders": { "user": {} @@ -2237,7 +2237,7 @@ "reason": {} } }, - "youHaveWithdrawnTheInvitationFor": "{user}에 대한 초대를 철회함", + "youHaveWithdrawnTheInvitationFor": "{user}님에 대한 초대를 철회함", "@youHaveWithdrawnTheInvitationFor": { "placeholders": { "user": {} @@ -2247,7 +2247,7 @@ "@appearOnTopDetails": {}, "enterRoom": "방에 입장", "@enterRoom": {}, - "youInvitedUser": "📩 {user}를 초대했습니다", + "youInvitedUser": "📩 {user}님을 초대했습니다", "@youInvitedUser": { "placeholders": { "user": {} @@ -2278,7 +2278,7 @@ "@screenSharingTitle": {}, "widgetCustom": "사용자 정의", "@widgetCustom": {}, - "youBannedUser": "{user}을 영구 추방함", + "youBannedUser": "{user}님을 영구 추방함", "@youBannedUser": { "placeholders": { "user": {} @@ -2286,7 +2286,7 @@ }, "addChatDescription": "채팅 설명 추가하기...", "@addChatDescription": {}, - "hasKnocked": "🚪 {user}가 참가를 요청했습니다", + "hasKnocked": "🚪 {user}님이 참가를 요청했습니다", "@hasKnocked": { "placeholders": { "user": {} @@ -2342,7 +2342,7 @@ "@roomUpgradeDescription": {}, "pleaseEnterANumber": "0보다 큰 숫자를 입력하세요", "@pleaseEnterANumber": {}, - "youKicked": "👞 {user}를 추방했습니다", + "youKicked": "👞 {user}님을 추방했습니다", "@youKicked": { "placeholders": { "user": {} @@ -2386,7 +2386,7 @@ "@commandHint_hug": {}, "replace": "대체", "@replace": {}, - "youUnbannedUser": "{user}의 영구 추방을 해제했습니다", + "youUnbannedUser": "{user}님의 영구 추방을 해제했습니다", "@youUnbannedUser": { "placeholders": { "user": {} @@ -2644,7 +2644,7 @@ "@passwordRecoverySettings": {}, "hideMemberChangesInPublicChats": "공개 채팅에서의 참가자 변화 숨김", "@hideMemberChangesInPublicChats": {}, - "userWouldLikeToChangeTheChat": "{user}가 참가를 희망합니다.", + "userWouldLikeToChangeTheChat": "{user}님이 참가를 희망합니다.", "@userWouldLikeToChangeTheChat": { "placeholders": { "user": {} @@ -2667,7 +2667,7 @@ }, "noOneCanJoin": "아무도 들어올 수 없음", "@noOneCanJoin": {}, - "thereAreCountUsersBlocked": "여기 {count}명의 차단된 유저가 있습니다.", + "thereAreCountUsersBlocked": "{count}명의 차단된 유저가 있습니다.", "@thereAreCountUsersBlocked": { "type": "text", "count": {} From 705723e741c00cbd4f62f6d75ce092d24f17503d Mon Sep 17 00:00:00 2001 From: Edgars Andersons Date: Thu, 27 Jun 2024 06:33:22 +0000 Subject: [PATCH 026/288] Translated using Weblate (Latvian) Currently translated at 100.0% (633 of 633 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/lv/ --- assets/l10n/intl_lv.arb | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/assets/l10n/intl_lv.arb b/assets/l10n/intl_lv.arb index 03543548de..5c11ff97f5 100644 --- a/assets/l10n/intl_lv.arb +++ b/assets/l10n/intl_lv.arb @@ -740,10 +740,6 @@ }, "signInWithPassword": "Pieteikties ar paroli", "@signInWithPassword": {}, - "@ignoredUsers": { - "type": "text", - "placeholders": {} - }, "lastActiveAgo": "Pēdējoreiz redzēts: {localizedTimeShort}", "@lastActiveAgo": { "type": "text", @@ -840,10 +836,6 @@ }, "editBundlesForAccount": "Labot šī konta komplektus", "@editBundlesForAccount": {}, - "@renderRichContent": { - "type": "text", - "placeholders": {} - }, "enableEncryption": "Iespējot šifrēšanu", "@enableEncryption": { "type": "text", @@ -1114,10 +1106,6 @@ "joinRules": {} } }, - "@ignore": { - "type": "text", - "placeholders": {} - }, "recording": "Ieraksta", "@recording": { "type": "text", @@ -1537,10 +1525,6 @@ "type": "text", "placeholders": {} }, - "@hideUnknownEvents": { - "type": "text", - "placeholders": {} - }, "dehydrateTorLong": "TOR lietotājiem ir ieteicams izgūt sesiju pirms loga aizvēršanas.", "@dehydrateTorLong": {}, "yourPublicKey": "Tava publiskā atslēga", @@ -2683,5 +2667,24 @@ "count": {} }, "verifyOtherDeviceDescription": "Kad apliecini citu ierīci, šīs ierīces var apmainīt atslēgas, palielinot vispārējo drošību. 💪 Kad uzsāc apliecināšanu, abās ierīcēs lietotnē parādīsies uznirstošais logs. Tajā būs redzamas dažādas emocijzīmes vai skaitļi, kas jāsalīdzina abās ierīcēs. Vislabāk, ja abas ierīces ir pieejams, pirms tiek uzsākta apliecināšana. 🤳", - "@verifyOtherDeviceDescription": {} + "@verifyOtherDeviceDescription": {}, + "swipeRightToLeftToReply": "Pavilkt pa labi, lai atbildētu", + "@swipeRightToLeftToReply": {}, + "searchIn": "Meklēt tērzēšanā \"{chat}\"...", + "@searchIn": { + "type": "text", + "placeholders": { + "chat": {} + } + }, + "searchMore": "Meklēt vairāk...", + "@searchMore": {}, + "gallery": "Galerija", + "@gallery": {}, + "files": "Datnes", + "@files": {}, + "restricted": "Ierobežots", + "@restricted": {}, + "knockRestricted": "Pieklauvēt ierobežotajiem", + "@knockRestricted": {} } From 3e0b73434e4b8cd1118170f958f9c7582c0d6486 Mon Sep 17 00:00:00 2001 From: kdh8219 Date: Fri, 28 Jun 2024 14:41:41 +0000 Subject: [PATCH 027/288] Translated using Weblate (Korean) Currently translated at 100.0% (633 of 633 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ko/ --- assets/l10n/intl_ko.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/l10n/intl_ko.arb b/assets/l10n/intl_ko.arb index 6ff3daaea3..d144eb7a07 100644 --- a/assets/l10n/intl_ko.arb +++ b/assets/l10n/intl_ko.arb @@ -808,7 +808,7 @@ "targetName": {} } }, - "guestsAreForbidden": "게스트는 금지되어 있습니다", + "guestsAreForbidden": "게스트가 들어올 수 없음", "@guestsAreForbidden": { "type": "text", "placeholders": {} From 65ad968d8a51f56aa3d828f87f94f61c512c43e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Sat, 29 Jun 2024 16:13:24 +0000 Subject: [PATCH 028/288] Translated using Weblate (Estonian) Currently translated at 100.0% (633 of 633 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/ --- assets/l10n/intl_et.arb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/l10n/intl_et.arb b/assets/l10n/intl_et.arb index 867822dc36..be3e5503c0 100644 --- a/assets/l10n/intl_et.arb +++ b/assets/l10n/intl_et.arb @@ -1085,7 +1085,7 @@ "type": "text", "placeholders": {} }, - "noGoogleServicesWarning": "Tundub, et sinu nutiseadmes pole Firebase Cloud Messaging teenuseid. Sinu privaatsuse mõttes on see kindlasti hea otsus! Kui sa soovid FluffyChat'is näha tõuketeavitusi, siis soovitame, et selle jaoks kasutad ntfy liidestust. Kasutades ntfy'd või mõnda muud Unified Push standardil põhinevat liidestust saad tõuketeavitusi turvalisel moel. Ntfy rakendus on saadaval nii PlayStore kui F-Droid'i rakendusepoodides.", + "noGoogleServicesWarning": "Tundub, et sinu nutiseadmes pole Firebase Cloud Messaging teenuseid. Sinu privaatsuse mõttes on see kindlasti hea otsus! Kui sa soovid FluffyChatis näha tõuketeavitusi, siis soovitame, et selle jaoks kasutad ntfy liidestust. Kasutades ntfyd või mõnda muud Unified Push standardil põhinevat liidestust saad tõuketeavitusi turvalisel moel. Ntfy rakendus on saadaval nii PlayStore kui F-Droidi rakendusepoodides.", "@noGoogleServicesWarning": { "type": "text", "placeholders": {} @@ -1150,7 +1150,7 @@ "type": "text", "placeholders": {} }, - "ok": "sobib", + "ok": "Sobib", "@ok": { "type": "text", "placeholders": {} @@ -2020,7 +2020,7 @@ "@emailOrUsername": {}, "experimentalVideoCalls": "Katselised videokõned", "@experimentalVideoCalls": {}, - "unsupportedAndroidVersionLong": "See funktsionaalsus eeldab uuemat Androidi versiooni. Palun kontrolli, kas sinu nutiseadmele leidub süsteemiuuendusi või saaks seal Lineage OS'i kasutada.", + "unsupportedAndroidVersionLong": "See funktsionaalsus eeldab uuemat Androidi versiooni. Palun kontrolli, kas sinu nutiseadmele leidub süsteemiuuendusi või saaks seal Lineage OSi kasutada.", "@unsupportedAndroidVersionLong": {}, "nextAccount": "Järgmine kasutajakonto", "@nextAccount": {}, @@ -2206,7 +2206,7 @@ "@foregroundServiceRunning": {}, "allSpaces": "Kõik kogukonnad", "@allSpaces": {}, - "screenSharingDetail": "Sa jagad oma ekraani FuffyChat'i vahendusel", + "screenSharingDetail": "Sa jagad oma ekraani FuffyChati vahendusel", "@screenSharingDetail": {}, "numChats": "{number} vestlust", "@numChats": { From 03af75f0830c4e3816505e0a6d6e898847235c4f Mon Sep 17 00:00:00 2001 From: xabirequejo Date: Sun, 30 Jun 2024 17:57:46 +0000 Subject: [PATCH 029/288] Translated using Weblate (Basque) Currently translated at 100.0% (633 of 633 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/ --- assets/l10n/intl_eu.arb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/assets/l10n/intl_eu.arb b/assets/l10n/intl_eu.arb index 73b2e069e8..afe40532f7 100644 --- a/assets/l10n/intl_eu.arb +++ b/assets/l10n/intl_eu.arb @@ -2161,7 +2161,7 @@ "@hydrateTor": {}, "saveKeyManuallyDescription": "Gorde eskuz gako hau gailuko partekatze-menua edo arbela erabiliz.", "@saveKeyManuallyDescription": {}, - "indexedDbErrorTitle": "Arazoak modu pribatuan", + "indexedDbErrorTitle": "Arazoak modu pribatuarekin", "@indexedDbErrorTitle": {}, "confirmMatrixId": "Baieztatu zure Matrix IDa kontua ezabatu ahal izateko.", "@confirmMatrixId": {}, @@ -2704,5 +2704,9 @@ "searchMore": "Bilatu gehiago...", "@searchMore": {}, "restricted": "Mugatuta", - "@restricted": {} + "@restricted": {}, + "knockRestricted": "Eskatu baimena sarrera mugatua duen txatean", + "@knockRestricted": {}, + "swipeRightToLeftToReply": "Herrestatu eskuin-ezker erantzuteko", + "@swipeRightToLeftToReply": {} } From d74a3e28f697a0e2130498d63a69adf88a06456b Mon Sep 17 00:00:00 2001 From: kdh8219 Date: Tue, 2 Jul 2024 11:07:12 +0000 Subject: [PATCH 030/288] Translated using Weblate (Korean) Currently translated at 100.0% (633 of 633 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ko/ --- assets/l10n/intl_ko.arb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/l10n/intl_ko.arb b/assets/l10n/intl_ko.arb index d144eb7a07..0e049112e0 100644 --- a/assets/l10n/intl_ko.arb +++ b/assets/l10n/intl_ko.arb @@ -369,12 +369,12 @@ "type": "text", "placeholders": {} }, - "containsUserName": "유저 이름 포함", + "containsUserName": "내 아이디 포함", "@containsUserName": { "type": "text", "placeholders": {} }, - "containsDisplayName": "표시 이름 포함", + "containsDisplayName": "내 닉네임 포함", "@containsDisplayName": { "type": "text", "placeholders": {} @@ -1723,7 +1723,7 @@ "username": {} } }, - "isTyping": "가 입력 중…", + "isTyping": "입력 중…", "@isTyping": { "type": "text", "placeholders": {} @@ -1736,7 +1736,7 @@ "link": {} } }, - "inviteForMe": "나를 위해 초대", + "inviteForMe": "초대됨", "@inviteForMe": { "type": "text", "placeholders": {} From bc52c9f677cea96cad1627008311e8dfa92fc71d Mon Sep 17 00:00:00 2001 From: Krille Date: Wed, 3 Jul 2024 15:13:04 +0200 Subject: [PATCH 031/288] build: Update matrix dart sdk --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 97aad333e0..ccc7006c2a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1210,10 +1210,10 @@ packages: dependency: "direct main" description: name: matrix - sha256: bb6de59d0f69e10bb6893130a967f1ffcbfa3d3ffed3864f0736ce3d968e669c + sha256: a27c2f73d28ea292e0f67f3d36396fb8acd7cfc97a07901dc7b22f46e082c3d6 url: "https://pub.dev" source: hosted - version: "0.29.12" + version: "0.30.0" meta: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 878f03c87a..e4a6ec8d6c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -64,7 +64,7 @@ dependencies: keyboard_shortcuts: ^0.1.4 latlong2: ^0.9.1 linkify: ^5.0.0 - matrix: ^0.29.13 + matrix: ^0.30.0 native_imaging: ^0.1.1 package_info_plus: ^6.0.0 pasteboard: ^0.2.0 From 873362428c2ad8bf2db122c2c8c7dd49a44174af Mon Sep 17 00:00:00 2001 From: Krille Date: Wed, 3 Jul 2024 15:14:10 +0200 Subject: [PATCH 032/288] fix: Follow up matrix sdk update --- lib/pages/chat/events/video_player.dart | 2 +- lib/utils/localized_exception_extension.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/chat/events/video_player.dart b/lib/pages/chat/events/video_player.dart index 1b0983bd40..5ccd560f97 100644 --- a/lib/pages/chat/events/video_player.dart +++ b/lib/pages/chat/events/video_player.dart @@ -71,7 +71,7 @@ class EventVideoPlayerState extends State { autoInitialize: true, ); } - } on MatrixConnectionException catch (e) { + } on IOException catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(e.toLocalizedString(context)), diff --git a/lib/utils/localized_exception_extension.dart b/lib/utils/localized_exception_extension.dart index 65be02a1f7..f9473c3f55 100644 --- a/lib/utils/localized_exception_extension.dart +++ b/lib/utils/localized_exception_extension.dart @@ -67,7 +67,7 @@ extension LocalizedExceptionExtension on Object { supportedVersions, ); } - if (this is MatrixConnectionException || + if (this is IOException || this is SocketException || this is SyncConnectionException) { return L10n.of(context)!.noConnectionToTheServer; From fc867b33a14db0dbe16d81eed098a7f296ca701a Mon Sep 17 00:00:00 2001 From: Krille Date: Thu, 4 Jul 2024 10:59:52 +0200 Subject: [PATCH 033/288] chore: Follow up time of day format --- lib/utils/date_time_extension.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/utils/date_time_extension.dart b/lib/utils/date_time_extension.dart index f546f9a5a6..d02f453345 100644 --- a/lib/utils/date_time_extension.dart +++ b/lib/utils/date_time_extension.dart @@ -35,7 +35,9 @@ extension DateTimeExtension on DateTime { /// Returns a simple time String. String localizedTimeOfDay(BuildContext context) => - DateFormat.Hm(L10n.of(context)!.localeName).format(this); + MediaQuery.of(context).alwaysUse24HourFormat + ? DateFormat('HH:mm', L10n.of(context)!.localeName).format(this) + : DateFormat('h:mm a', L10n.of(context)!.localeName).format(this); /// Returns [localizedTimeOfDay()] if the ChatTime is today, the name of the week /// day if the ChatTime is this week and a date string else. From e88afdd357ff1af52287644bc2157b42a5fbfc1b Mon Sep 17 00:00:00 2001 From: Krille Date: Thu, 4 Jul 2024 15:37:28 +0200 Subject: [PATCH 034/288] chore: Follow up use 24 hour format --- assets/l10n/intl_de.arb | 4 ++++ assets/l10n/intl_en.arb | 4 ++++ lib/utils/date_time_extension.dart | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_de.arb b/assets/l10n/intl_de.arb index adf2d99d07..07dea5ae80 100644 --- a/assets/l10n/intl_de.arb +++ b/assets/l10n/intl_de.arb @@ -1,6 +1,10 @@ { "@@locale": "de", "@@last_modified": "2021-08-14 12:41:10.119255", + "alwaysUse24HourFormat": "true", + "@alwaysUse24HourFormat": { + "description": "Set to true to always display time of day in 24 hour format." + }, "about": "Über", "@about": { "type": "text", diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index b687dc863e..cfd26a7c4c 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -1,6 +1,10 @@ { "@@locale": "en", "@@last_modified": "2021-08-14 12:38:37.885451", + "alwaysUse24HourFormat": "false", + "@alwaysUse24HourFormat": { + "description": "Set to true to always display time of day in 24 hour format." + }, "repeatPassword": "Repeat password", "@repeatPassword": {}, "notAnImage": "Not an image file.", diff --git a/lib/utils/date_time_extension.dart b/lib/utils/date_time_extension.dart index d02f453345..13bae4efab 100644 --- a/lib/utils/date_time_extension.dart +++ b/lib/utils/date_time_extension.dart @@ -35,7 +35,7 @@ extension DateTimeExtension on DateTime { /// Returns a simple time String. String localizedTimeOfDay(BuildContext context) => - MediaQuery.of(context).alwaysUse24HourFormat + L10n.of(context)!.alwaysUse24HourFormat == 'true' ? DateFormat('HH:mm', L10n.of(context)!.localeName).format(this) : DateFormat('h:mm a', L10n.of(context)!.localeName).format(this); From ef5ea57c5832966c946e24973274b5e975597875 Mon Sep 17 00:00:00 2001 From: Krille Date: Thu, 4 Jul 2024 15:42:00 +0200 Subject: [PATCH 035/288] refactor: Omit local types --- analysis_options.yaml | 1 + lib/pages/chat/chat_emoji_picker.dart | 2 +- lib/pages/chat/event_info_dialog.dart | 4 ++-- lib/pages/chat/input_bar.dart | 2 +- lib/pages/chat/send_file_dialog.dart | 2 +- lib/pages/chat_list/client_chooser_button.dart | 6 +++--- .../flutter_hive_collections_database.dart | 2 +- 7 files changed, 10 insertions(+), 9 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 34a01078b4..d74b363557 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -9,6 +9,7 @@ linter: - prefer_final_in_for_each - sort_pub_dependencies - require_trailing_commas + - omit_local_variable_types analyzer: errors: diff --git a/lib/pages/chat/chat_emoji_picker.dart b/lib/pages/chat/chat_emoji_picker.dart index 153abad67d..0225e7350c 100644 --- a/lib/pages/chat/chat_emoji_picker.dart +++ b/lib/pages/chat/chat_emoji_picker.dart @@ -14,7 +14,7 @@ class ChatEmojiPicker extends StatelessWidget { @override Widget build(BuildContext context) { - final ThemeData theme = Theme.of(context); + final theme = Theme.of(context); return AnimatedContainer( duration: FluffyThemes.animationDuration, curve: FluffyThemes.animationCurve, diff --git a/lib/pages/chat/event_info_dialog.dart b/lib/pages/chat/event_info_dialog.dart index 38acdc84c4..3b35035933 100644 --- a/lib/pages/chat/event_info_dialog.dart +++ b/lib/pages/chat/event_info_dialog.dart @@ -28,8 +28,8 @@ class EventInfoDialog extends StatelessWidget { }); String get prettyJson { - const JsonDecoder decoder = JsonDecoder(); - const JsonEncoder encoder = JsonEncoder.withIndent(' '); + const decoder = JsonDecoder(); + const encoder = JsonEncoder.withIndent(' '); final object = decoder.convert(jsonEncode(event.toJson())); return encoder.convert(object); } diff --git a/lib/pages/chat/input_bar.dart b/lib/pages/chat/input_bar.dart index 5cd801e042..d4917c1dc9 100644 --- a/lib/pages/chat/input_bar.dart +++ b/lib/pages/chat/input_bar.dart @@ -55,7 +55,7 @@ class InputBar extends StatelessWidget { } final searchText = controller!.text.substring(0, controller!.selection.baseOffset); - final List> ret = >[]; + final ret = >[]; const maxResults = 30; final commandMatch = RegExp(r'^/(\w*)$').firstMatch(searchText); diff --git a/lib/pages/chat/send_file_dialog.dart b/lib/pages/chat/send_file_dialog.dart index b2885a635a..b50a7d19ee 100644 --- a/lib/pages/chat/send_file_dialog.dart +++ b/lib/pages/chat/send_file_dialog.dart @@ -64,7 +64,7 @@ class SendFileDialogState extends State { @override Widget build(BuildContext context) { var sendStr = L10n.of(context)!.sendFile; - final bool allFilesAreImages = + final allFilesAreImages = widget.files.every((file) => file is MatrixImageFile); final sizeString = widget.files .fold(0, (p, file) => p + file.bytes.length) diff --git a/lib/pages/chat_list/client_chooser_button.dart b/lib/pages/chat_list/client_chooser_button.dart index a37901b2f5..d937b7cb16 100644 --- a/lib/pages/chat_list/client_chooser_button.dart +++ b/lib/pages/chat_list/client_chooser_button.dart @@ -165,7 +165,7 @@ class ClientChooserButton extends StatelessWidget { Widget build(BuildContext context) { final matrix = Matrix.of(context); - int clientCount = 0; + var clientCount = 0; matrix.accountBundles.forEach((key, value) => clientCount += value.length); return FutureBuilder( future: matrix.client.fetchOwnProfile(), @@ -292,7 +292,7 @@ class ClientChooserButton extends StatelessWidget { ); // beginning from end if negative if (index < 0) { - int clientCount = 0; + var clientCount = 0; matrix.accountBundles .forEach((key, value) => clientCount += value.length); _handleKeyboardShortcut(matrix, clientCount, context); @@ -312,7 +312,7 @@ class ClientChooserButton extends StatelessWidget { } int? _shortcutIndexOfClient(MatrixState matrix, Client client) { - int index = 0; + var index = 0; final bundles = matrix.accountBundles.keys.toList() ..sort( diff --git a/lib/utils/matrix_sdk_extensions/flutter_hive_collections_database.dart b/lib/utils/matrix_sdk_extensions/flutter_hive_collections_database.dart index 1fb9a4e4c8..d10a09bd3a 100644 --- a/lib/utils/matrix_sdk_extensions/flutter_hive_collections_database.dart +++ b/lib/utils/matrix_sdk_extensions/flutter_hive_collections_database.dart @@ -82,7 +82,7 @@ class FlutterHiveCollectionsDatabase extends HiveCollectionsDatabase { } static Future findDatabasePath(Client client) async { - String path = client.clientName; + var path = client.clientName; if (!kIsWeb) { Directory directory; try { From 3018a5213fecf6cdab1a3f7b7a2c4b08d7859f70 Mon Sep 17 00:00:00 2001 From: Krille Date: Sun, 7 Jul 2024 11:55:12 +0200 Subject: [PATCH 036/288] build: Bump version to v1.21.2 --- CHANGELOG.md | 22 ++++++++++++++++++++++ pubspec.yaml | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83968b329f..627272439a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ +## v1.21.2 +Updates the Matrix Dart SDK to fix some minor bugs. + +- Added translation using Weblate (Lojban) (Zig-Rust-Odin) +- build: Update matrix dart sdk (Krille) +- chore: Update last event after decryption (Krille) +- fix: Correctly localize time of date (Krille) +- refactor: Omit local types (Krille) +- Translated using Weblate (Arabic) (Rex_sa) +- Translated using Weblate (Basque) (xabirequejo) +- Translated using Weblate (Chinese (Simplified)) (大王叫我来巡山) +- Translated using Weblate (Croatian) (Milo Ivir) +- Translated using Weblate (Estonian) (Priit Jõerüüt) +- Translated using Weblate (Galician) (josé m) +- Translated using Weblate (Hungarian) (H Tamás) +- Translated using Weblate (Korean) (kdh8219) +- Translated using Weblate (Latvian) (Edgars Andersons) +- Translated using Weblate (Polish) (Eryk Michalak) +- Translated using Weblate (Portuguese (Brazil)) (lucasmz-dev) +- Translated using Weblate (Russian) (Nicholas Winterhalter) +- Translated using Weblate (Turkish) (Oğuz Ersen) + ## v1.21.1 - build: Update Matrix Dart SDK (Krille) - build: Update to Flutter 3.22.2 (krille-chan) diff --git a/pubspec.yaml b/pubspec.yaml index e4a6ec8d6c..16e1ffafc5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: fluffychat description: Chat with your friends. publish_to: none # On version bump also increase the build number for F-Droid -version: 1.21.1+3533 +version: 1.21.2+3534 environment: sdk: ">=3.0.0 <4.0.0" From 6665e74bdcd0549a4e808173c8b7cf4c5aa07b20 Mon Sep 17 00:00:00 2001 From: Krille Date: Thu, 11 Jul 2024 09:44:28 +0200 Subject: [PATCH 037/288] chore: Update mastodon link --- docs/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.html b/docs/index.html index 0d82ae5187..b0205811cc 100644 --- a/docs/index.html +++ b/docs/index.html @@ -38,7 +38,7 @@ Buy Me a Coffee at ko-fi.com - From ae2f4e432ebaeeeda9047f40bc2d8a59ea57c86d Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Mon, 8 Jul 2024 19:47:10 +0000 Subject: [PATCH 038/288] Translated using Weblate (Ukrainian) Currently translated at 100.0% (633 of 633 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/uk/ --- assets/l10n/intl_uk.arb | 110 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 107 insertions(+), 3 deletions(-) diff --git a/assets/l10n/intl_uk.arb b/assets/l10n/intl_uk.arb index 62c59b73e2..9af791b258 100644 --- a/assets/l10n/intl_uk.arb +++ b/assets/l10n/intl_uk.arb @@ -2405,7 +2405,7 @@ "@makeAdminDescription": {}, "archiveRoomDescription": "Бесіду буде переміщено до архіву. Інші користувачі зможуть побачити, що ви вийшли з неї.", "@archiveRoomDescription": {}, - "hasKnocked": "{user} стукає до вас", + "hasKnocked": "🚪{user} стукає до вас", "@hasKnocked": { "placeholders": { "user": {} @@ -2436,7 +2436,7 @@ "query": {} } }, - "block": "блокування", + "block": "Заблокувати", "@block": {}, "yourGlobalUserIdIs": "Ваш глобальний ID користувача: ", "@yourGlobalUserIdIs": {}, @@ -2604,5 +2604,109 @@ "discover": "Огляд", "@discover": {}, "stickers": "Наліпки", - "@stickers": {} + "@stickers": {}, + "searchIn": "Пошук у бесіді \"{chat}\"...", + "@searchIn": { + "type": "text", + "placeholders": { + "chat": {} + } + }, + "commandHint_ignore": "Ігнорувати цей Matrix ID", + "@commandHint_ignore": {}, + "restricted": "Обмежено", + "@restricted": {}, + "swipeRightToLeftToReply": "Посунути праворуч або ліворуч, щоб відповісти", + "@swipeRightToLeftToReply": {}, + "globalChatId": "Глобальний ID бесіди", + "@globalChatId": {}, + "accessAndVisibility": "Доступ і видимість", + "@accessAndVisibility": {}, + "accessAndVisibilityDescription": "Хто може приєднатися до цієї бесіди і як її можна знайти.", + "@accessAndVisibilityDescription": {}, + "calls": "Виклики", + "@calls": {}, + "customEmojisAndStickers": "Власні емоджі та наліпки", + "@customEmojisAndStickers": {}, + "customEmojisAndStickersBody": "Додавайте або діліться власними емоджі або наліпками, які можна використовувати в будь-якій бесіді.", + "@customEmojisAndStickersBody": {}, + "createNewAddress": "Створити нову адресу", + "@createNewAddress": {}, + "userRole": "Роль користувача", + "@userRole": {}, + "minimumPowerLevel": "{level} — це найнижчий рівень повноважень.", + "@minimumPowerLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "commandHint_unignore": "Не ігнорувати цей Matrix ID", + "@commandHint_unignore": {}, + "knockRestricted": "Стук обмежено", + "@knockRestricted": {}, + "appLockDescription": "Блокувати застосунок, коли не використовується ПІН-код", + "@appLockDescription": {}, + "hideRedactedMessages": "Сховати змінені повідомлення", + "@hideRedactedMessages": {}, + "hideRedactedMessagesBody": "Якщо хтось змінить повідомлення, його більше не буде видно в бесіді.", + "@hideRedactedMessagesBody": {}, + "hideInvalidOrUnknownMessageFormats": "Сховати недійсні або невідомі формати повідомлень", + "@hideInvalidOrUnknownMessageFormats": {}, + "hideMemberChangesInPublicChats": "Сховати зміни користувачів у загальнодоступних бесідах", + "@hideMemberChangesInPublicChats": {}, + "hideMemberChangesInPublicChatsBody": "Не показувати в хронології бесіди, якщо хтось приєднується до загальнодоступної бесіди або виходить з неї, щоб покращити її читабельність.", + "@hideMemberChangesInPublicChatsBody": {}, + "overview": "Огляд", + "@overview": {}, + "notifyMeFor": "Сповіщати мене про", + "@notifyMeFor": {}, + "passwordRecoverySettings": "Налаштування відновлення пароля", + "@passwordRecoverySettings": {}, + "userWouldLikeToChangeTheChat": "{user} хоче приєднатися до бесіди.", + "@userWouldLikeToChangeTheChat": { + "placeholders": { + "user": {} + } + }, + "noPublicLinkHasBeenCreatedYet": "Загальнодоступне посилання ще не створено", + "@noPublicLinkHasBeenCreatedYet": {}, + "knock": "Постукатись", + "@knock": {}, + "knocking": "Стукаються", + "@knocking": {}, + "noDatabaseEncryption": "Шифрування бази даних не підтримується на цій платформі", + "@noDatabaseEncryption": {}, + "usersMustKnock": "Користувачі повинні постукатись", + "@usersMustKnock": {}, + "noOneCanJoin": "Ніхто не може приєднатись", + "@noOneCanJoin": {}, + "chatCanBeDiscoveredViaSearchOnServer": "Бесіду можна знайти за допомогою пошуку на {server}", + "@chatCanBeDiscoveredViaSearchOnServer": { + "type": "text", + "placeholders": { + "server": {} + } + }, + "publicChatAddresses": "Адреси загальнодоступної бесіди", + "@publicChatAddresses": {}, + "searchMore": "Шукати ще...", + "@searchMore": {}, + "gallery": "Галерея", + "@gallery": {}, + "files": "Файли", + "@files": {}, + "unreadChatsInApp": "{appname}: {unread} непрочитаних бесід", + "@unreadChatsInApp": { + "type": "text", + "placeholders": { + "appname": {}, + "unread": {} + } + }, + "thereAreCountUsersBlocked": "Наразі заблоковано {count} користувачів.", + "@thereAreCountUsersBlocked": { + "type": "text", + "count": {} + } } From df41722f1c821c0ecaefa889a36e7e82047376fe Mon Sep 17 00:00:00 2001 From: Ricky From Hong Kong Date: Mon, 8 Jul 2024 07:56:58 +0000 Subject: [PATCH 039/288] Translated using Weblate (Chinese (Traditional)) Currently translated at 52.4% (332 of 633 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hant/ --- assets/l10n/intl_zh_Hant.arb | 381 +---------------------------------- 1 file changed, 2 insertions(+), 379 deletions(-) diff --git a/assets/l10n/intl_zh_Hant.arb b/assets/l10n/intl_zh_Hant.arb index 8758122cfc..26a6b0fbcb 100644 --- a/assets/l10n/intl_zh_Hant.arb +++ b/assets/l10n/intl_zh_Hant.arb @@ -164,7 +164,7 @@ "username": {} } }, - "changedTheChatDescriptionTo": "{username}變更了對話介紹為:「{description}」", + "changedTheChatDescriptionTo": "{username}變更了聊天介紹為:「{description}」", "@changedTheChatDescriptionTo": { "type": "text", "placeholders": { @@ -172,7 +172,7 @@ "description": {} } }, - "changedTheChatNameTo": "{username}變更了暱稱為:「{chatname}」", + "changedTheChatNameTo": "{username}變更了聊天名稱為:「{chatname}」", "@changedTheChatNameTo": { "type": "text", "placeholders": { @@ -1791,43 +1791,8 @@ "senderName": {} } }, - "@jumpToLastReadMessage": {}, - "@allRooms": { - "type": "text", - "placeholders": {} - }, - "@obtainingLocation": { - "type": "text", - "placeholders": {} - }, "commandHint_cuddle": "發送一個摟抱表情", "@commandHint_cuddle": {}, - "@widgetVideo": {}, - "@dismiss": {}, - "@reportErrorDescription": {}, - "@addAccount": {}, - "@removeYourAvatar": { - "type": "text", - "placeholders": {} - }, - "@unsupportedAndroidVersion": {}, - "@commandHint_html": { - "type": "text", - "description": "Usage hint for the command /html" - }, - "@widgetJitsi": {}, - "@messageType": {}, - "@indexedDbErrorLong": {}, - "@oneClientLoggedOut": {}, - "@startFirstChat": {}, - "@callingAccount": {}, - "@setColorTheme": {}, - "@nextAccount": {}, - "@singlesignon": { - "type": "text", - "placeholders": {} - }, - "@allSpaces": {}, "supposedMxid": "此處應爲{mxid}", "@supposedMxid": { "type": "text", @@ -1835,213 +1800,18 @@ "mxid": {} } }, - "@user": {}, - "@youAcceptedTheInvitation": {}, - "@noMatrixServer": { - "type": "text", - "placeholders": { - "server1": {}, - "server2": {} - } - }, - "@youInvitedBy": { - "placeholders": { - "user": {} - } - }, - "@banUserDescription": {}, - "@widgetEtherpad": {}, - "@removeDevicesDescription": {}, - "@separateChatTypes": { - "type": "text", - "placeholders": {} - }, - "@tryAgain": {}, - "@youKickedAndBanned": { - "placeholders": { - "user": {} - } - }, - "@unbanUserDescription": {}, - "@saveFile": { - "type": "text", - "placeholders": {} - }, - "@youRejectedTheInvitation": {}, - "@otherCallingPermissions": {}, - "@messagesStyle": {}, - "@link": {}, - "@widgetUrlError": {}, - "@emailOrUsername": {}, - "@newSpaceDescription": {}, - "@chatDescription": {}, - "@callingAccountDetails": {}, - "@enterSpace": {}, - "@encryptThisChat": {}, - "@previousAccount": {}, - "@reopenChat": {}, - "@pleaseEnterRecoveryKey": {}, - "@widgetNameError": {}, - "@addToBundle": {}, - "@spaceIsPublic": { - "type": "text", - "placeholders": {} - }, - "@addWidget": {}, - "@countFiles": { - "placeholders": { - "count": {} - } - }, - "@noKeyForThisMessage": {}, - "@shareLocation": { - "type": "text", - "placeholders": {} - }, - "@commandHint_markasgroup": {}, - "@errorObtainingLocation": { - "type": "text", - "placeholders": { - "error": {} - } - }, - "@hydrateTor": {}, - "@pushNotificationsNotAvailable": {}, - "@storeInAppleKeyChain": {}, - "@hydrate": {}, "invalidServerName": "伺服器名稱錯誤", "@invalidServerName": {}, - "@chatPermissions": {}, - "@sender": {}, - "@storeInAndroidKeystore": {}, - "@signInWithPassword": {}, - "@makeAdminDescription": {}, - "@synchronizingPleaseWait": { - "type": "text", - "placeholders": {} - }, - "@saveKeyManuallyDescription": {}, - "@editBundlesForAccount": {}, - "@whyIsThisMessageEncrypted": {}, - "@setChatDescription": {}, - "@spaceName": { - "type": "text", - "placeholders": {} - }, "importFromZipFile": "從 zip 檔案匯入", "@importFromZipFile": {}, - "@or": { - "type": "text", - "placeholders": {} - }, - "@dehydrateWarning": {}, - "@noOtherDevicesFound": {}, - "@redactedBy": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@videoCallsBetaWarning": {}, - "@signInWith": { - "type": "text", - "placeholders": { - "provider": {} - } - }, - "@fileIsTooBigForServer": {}, "homeserver": "伺服器", "@homeserver": {}, - "@callingPermissions": {}, - "@readUpToHere": {}, - "@start": {}, - "@register": { - "type": "text", - "placeholders": {} - }, - "@unlockOldMessages": {}, - "@numChats": { - "type": "number", - "placeholders": { - "number": {} - } - }, - "@optionalRedactReason": {}, - "@dehydrate": {}, - "@locationPermissionDeniedNotice": { - "type": "text", - "placeholders": {} - }, - "@sendAsText": { - "type": "text" - }, - "@archiveRoomDescription": {}, "exportEmotePack": "將表情包匯出成 zip 檔案", "@exportEmotePack": {}, - "@sendSticker": { - "type": "text", - "placeholders": {} - }, - "@switchToAccount": { - "type": "number", - "placeholders": { - "number": {} - } - }, "commandInvalid": "命令無效", "@commandInvalid": { "type": "text" }, - "@setAsCanonicalAlias": { - "type": "text", - "placeholders": {} - }, - "@locationDisabledNotice": { - "type": "text", - "placeholders": {} - }, - "@commandHint_plain": { - "type": "text", - "description": "Usage hint for the command /plain" - }, - "@experimentalVideoCalls": {}, - "@pleaseEnterRecoveryKeyDescription": {}, - "@openInMaps": { - "type": "text", - "placeholders": {} - }, - "@inviteContactToGroupQuestion": {}, - "@redactedByBecause": { - "type": "text", - "placeholders": { - "username": {}, - "reason": {} - } - }, - "@youHaveWithdrawnTheInvitationFor": { - "placeholders": { - "user": {} - } - }, - "@appearOnTopDetails": {}, - "@enterRoom": {}, - "@reportUser": {}, - "@commandHint_send": { - "type": "text", - "description": "Usage hint for the command /send" - }, - "@confirmEventUnpin": {}, - "@youInvitedUser": { - "placeholders": { - "user": {} - } - }, - "@fileHasBeenSavedAt": { - "type": "text", - "placeholders": { - "path": {} - } - }, "commandMissing": "{command}不是正確的命令。", "@commandMissing": { "type": "text", @@ -2050,18 +1820,6 @@ }, "description": "State that {command} is not a valid /command." }, - "@redactMessageDescription": {}, - "@recoveryKey": {}, - "@invalidInput": {}, - "@dehydrateTorLong": {}, - "@doNotShowAgain": {}, - "@report": {}, - "@unverified": {}, - "@serverRequiresEmail": {}, - "@hideUnimportantStateEvents": {}, - "@screenSharingTitle": {}, - "@widgetCustom": {}, - "@addToSpaceDescription": {}, "googlyEyesContent": "{senderName}向您發送了瞪眼表情", "@googlyEyesContent": { "type": "text", @@ -2069,101 +1827,16 @@ "senderName": {} } }, - "@youBannedUser": { - "placeholders": { - "user": {} - } - }, "addChatDescription": "新增聊天說明...", "@addChatDescription": {}, - "@hasKnocked": { - "placeholders": { - "user": {} - } - }, - "@publish": {}, - "@openLinkInBrowser": {}, - "@commandHint_react": { - "type": "text", - "description": "Usage hint for the command /react" - }, - "@commandHint_me": { - "type": "text", - "description": "Usage hint for the command /me" - }, - "@messageInfo": {}, - "@disableEncryptionWarning": {}, - "@directChat": {}, - "@wrongPinEntered": { - "type": "text", - "placeholders": { - "seconds": {} - } - }, "sendTypingNotifications": "傳送「輸入中」通知", "@sendTypingNotifications": {}, - "@inviteGroupChat": {}, - "@appearOnTop": {}, - "@invitePrivateChat": {}, - "@foregroundServiceRunning": {}, - "@voiceCall": {}, - "@createNewSpace": { - "type": "text", - "placeholders": {} - }, "importEmojis": "匯入表情包", "@importEmojis": {}, - "@wasDirectChatDisplayName": { - "type": "text", - "placeholders": { - "oldDisplayName": {} - } - }, - "@noChatDescriptionYet": {}, - "@removeFromBundle": {}, "confirmMatrixId": "如需要刪除你的帳戶,請確認你的Matrix ID。", "@confirmMatrixId": {}, - "@learnMore": {}, "notAnImage": "不是圖片檔案。", "@notAnImage": {}, - "@users": {}, - "@openGallery": {}, - "@chatDescriptionHasBeenChanged": {}, - "@newGroup": {}, - "@bundleName": {}, - "@dehydrateTor": {}, - "@removeFromSpace": {}, - "@commandHint_op": { - "type": "text", - "description": "Usage hint for the command /op" - }, - "@roomUpgradeDescription": {}, - "@scanQrCode": {}, - "@pleaseEnterANumber": {}, - "@youKicked": { - "placeholders": { - "user": {} - } - }, - "@profileNotFound": {}, - "@jump": {}, - "@reactedWith": { - "type": "text", - "placeholders": { - "sender": {}, - "reaction": {} - } - }, - "@sorryThatsNotPossible": {}, - "@videoWithSize": { - "type": "text", - "placeholders": { - "size": {} - } - }, - "@shareInviteLink": {}, - "@commandHint_markasdm": {}, - "@recoveryKeyLost": {}, "cuddleContent": "{senderName}摟抱您", "@cuddleContent": { "type": "text", @@ -2171,64 +1844,14 @@ "senderName": {} } }, - "@deviceKeys": {}, - "@emoteKeyboardNoRecents": { - "type": "text", - "placeholders": {} - }, - "@setTheme": {}, - "@youJoinedTheChat": {}, - "@openVideoCamera": { - "type": "text", - "placeholders": {} - }, - "@markAsRead": {}, - "@widgetName": {}, - "@errorAddingWidget": {}, "commandHint_hug": "發送一個擁抱表情", "@commandHint_hug": {}, "replace": "取代", "@replace": {}, - "@oopsPushError": { - "type": "text", - "placeholders": {} - }, - "@youUnbannedUser": { - "placeholders": { - "user": {} - } - }, - "@newSpace": {}, - "@emojis": {}, - "@pleaseEnterYourPin": { - "type": "text", - "placeholders": {} - }, - "@pleaseChoose": { - "type": "text", - "placeholders": {} - }, "commandHint_googly": "發送一些瞪眼表情", "@commandHint_googly": {}, - "@pleaseTryAgainLaterOrChooseDifferentServer": {}, - "@createGroup": {}, - "@hydrateTorLong": {}, - "@time": {}, - "@custom": {}, - "@noBackupWarning": {}, - "@storeInSecureStorageDescription": {}, - "@openChat": {}, - "@kickUserDescription": {}, "importNow": "立即匯入", "@importNow": {}, - "@pinMessage": {}, - "@invite": {}, - "@enableMultiAccounts": {}, - "@indexedDbErrorTitle": {}, - "@unsupportedAndroidVersionLong": {}, - "@storeSecurlyOnThisDevice": {}, - "@screenSharingDetail": {}, - "@placeCall": {}, "blockListDescription": "你可以封鎖打擾你的使用者。你不會再收到任何從已封鎖使用者發來的訊息或聊天室邀請。", "@blockListDescription": {}, "blockedUsers": "已封鎖的使用者", From b8120991f01da9d89df567a097303f19e7ab4289 Mon Sep 17 00:00:00 2001 From: Rex_sa Date: Wed, 10 Jul 2024 15:22:20 +0000 Subject: [PATCH 040/288] Translated using Weblate (Arabic) Currently translated at 100.0% (634 of 634 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ar/ --- assets/l10n/intl_ar.arb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_ar.arb b/assets/l10n/intl_ar.arb index 53b18d7b1b..eff3e2516b 100644 --- a/assets/l10n/intl_ar.arb +++ b/assets/l10n/intl_ar.arb @@ -2708,5 +2708,9 @@ "gallery": "المعرض", "@gallery": {}, "swipeRightToLeftToReply": "اسحب من اليمين إلى اليسار للرد", - "@swipeRightToLeftToReply": {} + "@swipeRightToLeftToReply": {}, + "alwaysUse24HourFormat": "خاطئ", + "@alwaysUse24HourFormat": { + "description": "Set to true to always display time of day in 24 hour format." + } } From fa133c11671f7b476b90f9899ada5b758f6c504a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Tue, 9 Jul 2024 19:00:29 +0000 Subject: [PATCH 041/288] Translated using Weblate (Estonian) Currently translated at 100.0% (634 of 634 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/ --- assets/l10n/intl_et.arb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_et.arb b/assets/l10n/intl_et.arb index be3e5503c0..b53675eda0 100644 --- a/assets/l10n/intl_et.arb +++ b/assets/l10n/intl_et.arb @@ -2708,5 +2708,9 @@ "files": "Failid", "@files": {}, "swipeRightToLeftToReply": "Vastamiseks viipa paremalt vasakule", - "@swipeRightToLeftToReply": {} + "@swipeRightToLeftToReply": {}, + "alwaysUse24HourFormat": "vale", + "@alwaysUse24HourFormat": { + "description": "Set to true to always display time of day in 24 hour format." + } } From a1b2d8862282b043d98cf6551b761c2d34ef8cdc Mon Sep 17 00:00:00 2001 From: xabirequejo Date: Wed, 10 Jul 2024 09:53:06 +0000 Subject: [PATCH 042/288] Translated using Weblate (Basque) Currently translated at 100.0% (634 of 634 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/ --- assets/l10n/intl_eu.arb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_eu.arb b/assets/l10n/intl_eu.arb index afe40532f7..12ad1c53d8 100644 --- a/assets/l10n/intl_eu.arb +++ b/assets/l10n/intl_eu.arb @@ -2708,5 +2708,9 @@ "knockRestricted": "Eskatu baimena sarrera mugatua duen txatean", "@knockRestricted": {}, "swipeRightToLeftToReply": "Herrestatu eskuin-ezker erantzuteko", - "@swipeRightToLeftToReply": {} + "@swipeRightToLeftToReply": {}, + "alwaysUse24HourFormat": "ez", + "@alwaysUse24HourFormat": { + "description": "Set to true to always display time of day in 24 hour format." + } } From f15b837dcd70a3a0acaa7121c683f907e65def64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Tue, 9 Jul 2024 17:11:04 +0000 Subject: [PATCH 043/288] Translated using Weblate (Turkish) Currently translated at 100.0% (634 of 634 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/tr/ --- assets/l10n/intl_tr.arb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_tr.arb b/assets/l10n/intl_tr.arb index 18b90caaf5..5dccad6460 100644 --- a/assets/l10n/intl_tr.arb +++ b/assets/l10n/intl_tr.arb @@ -2708,5 +2708,9 @@ "restricted": "Kısıtlı", "@restricted": {}, "swipeRightToLeftToReply": "Yanıtlamak için sağdan sola kaydır", - "@swipeRightToLeftToReply": {} + "@swipeRightToLeftToReply": {}, + "alwaysUse24HourFormat": "yanlış", + "@alwaysUse24HourFormat": { + "description": "Set to true to always display time of day in 24 hour format." + } } From e4c77791db94a8fa1f51dc2cfbfa65d038b1af0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E7=8E=8B=E5=8F=AB=E6=88=91=E6=9D=A5=E5=B7=A1?= =?UTF-8?q?=E5=B1=B1?= Date: Wed, 10 Jul 2024 08:51:51 +0000 Subject: [PATCH 044/288] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (634 of 634 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hans/ --- assets/l10n/intl_zh.arb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_zh.arb b/assets/l10n/intl_zh.arb index 8ca5e31531..70451b9587 100644 --- a/assets/l10n/intl_zh.arb +++ b/assets/l10n/intl_zh.arb @@ -2708,5 +2708,9 @@ "restricted": "受限", "@restricted": {}, "swipeRightToLeftToReply": "从右向左滑动进行回复", - "@swipeRightToLeftToReply": {} + "@swipeRightToLeftToReply": {}, + "alwaysUse24HourFormat": "否", + "@alwaysUse24HourFormat": { + "description": "Set to true to always display time of day in 24 hour format." + } } From fbf2a069ed323a59e15018b1c10b63f07abe7bf9 Mon Sep 17 00:00:00 2001 From: Linerly Date: Thu, 11 Jul 2024 11:37:10 +0000 Subject: [PATCH 045/288] Translated using Weblate (Indonesian) Currently translated at 100.0% (634 of 634 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/id/ --- assets/l10n/intl_id.arb | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/assets/l10n/intl_id.arb b/assets/l10n/intl_id.arb index 34f821c5e7..aa4879c95c 100644 --- a/assets/l10n/intl_id.arb +++ b/assets/l10n/intl_id.arb @@ -324,7 +324,7 @@ "type": "text", "placeholders": {} }, - "edit": "Edit", + "edit": "Sunting", "@edit": { "type": "text", "placeholders": {} @@ -2323,7 +2323,7 @@ "@sendTypingNotifications": {}, "createGroup": "Buat grup", "@createGroup": {}, - "inviteContactToGroupQuestion": "Apakah kamu ingin mengundang {contact} ke chat \"{groupName}\"?", + "inviteContactToGroupQuestion": "Apakah kamu ingin mengundang {contact} ke obrolan \"{groupName}\"?", "@inviteContactToGroupQuestion": {}, "tryAgain": "Coba ulang", "@tryAgain": {}, @@ -2335,15 +2335,15 @@ "@setTheme": {}, "invalidServerName": "Nama server tidak valid", "@invalidServerName": {}, - "addChatDescription": "Tambahkan deskripsi chat...", + "addChatDescription": "Tambahkan deskripsi obrolan...", "@addChatDescription": {}, - "chatPermissions": "Perizinan chat", + "chatPermissions": "Perizinan obrolan", "@chatPermissions": {}, - "chatDescription": "Deskripsi chat", + "chatDescription": "Deskripsi obrolan", "@chatDescription": {}, - "chatDescriptionHasBeenChanged": "Deskripsi chat diubah", + "chatDescriptionHasBeenChanged": "Deskripsi obrolan diubah", "@chatDescriptionHasBeenChanged": {}, - "noChatDescriptionYet": "Deskripsi chat belum dibuat.", + "noChatDescriptionYet": "Deskripsi obrolan belum dibuat.", "@noChatDescriptionYet": {}, "redactMessageDescription": "Pesan akan dihilangkan untuk semua anggota dalam percakapan ini. Ini tidak dapat diurungkan.", "@redactMessageDescription": {}, @@ -2366,7 +2366,7 @@ "reason": {} } }, - "setChatDescription": "Lihat deskripsi chat", + "setChatDescription": "Lihat deskripsi obrolan", "@setChatDescription": {}, "profileNotFound": "Pengguna ini tidak dapat ditemukan di server. Mungkin ada masalah koneksi atau penggunanya tidak ada.", "@profileNotFound": {}, @@ -2688,5 +2688,28 @@ "placeholders": { "level": {} } + }, + "swipeRightToLeftToReply": "Usap dari kanan ke kiri untuk membalas", + "@swipeRightToLeftToReply": {}, + "searchMore": "Cari lebih banyak...", + "@searchMore": {}, + "gallery": "Galeri", + "@gallery": {}, + "searchIn": "Cari dalam obrolan \"{chat}\"...", + "@searchIn": { + "type": "text", + "placeholders": { + "chat": {} + } + }, + "files": "Berkas", + "@files": {}, + "restricted": "Dibatasi", + "@restricted": {}, + "knockRestricted": "Ketukan dibatasi", + "@knockRestricted": {}, + "alwaysUse24HourFormat": "tidak", + "@alwaysUse24HourFormat": { + "description": "Set to true to always display time of day in 24 hour format." } } From e7f446813331d6a73aa63e8cff7818753e474eec Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sat, 13 Jul 2024 17:12:07 +0200 Subject: [PATCH 046/288] chore: Move default PR template to correct dir --- .github/{PULL_REQUEST_TEMPLATE => }/pull_request_template.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{PULL_REQUEST_TEMPLATE => }/pull_request_template.md (100%) diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/pull_request_template.md similarity index 100% rename from .github/PULL_REQUEST_TEMPLATE/pull_request_template.md rename to .github/pull_request_template.md From 82a7f5de51e4692bdcab79d091a6ebc7096001e8 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sat, 13 Jul 2024 17:16:30 +0200 Subject: [PATCH 047/288] build: Update dependencies after release --- pubspec.lock | 280 +++++++++---------- pubspec.yaml | 8 +- scripts/enable-android-google-services.patch | 2 +- 3 files changed, 141 insertions(+), 149 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index ccc7006c2a..0ff035c1b5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -53,10 +53,10 @@ packages: dependency: "direct main" description: name: archive - sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" + sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d url: "https://pub.dev" source: hosted - version: "3.4.10" + version: "3.6.1" args: dependency: transitive description: @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: audio_session - sha256: a49af9981eec5d7cd73b37bacb6ee73f8143a6a9f9bd5b6021e6c346b9b6cf4e + sha256: "4012d798e25a0ebfd4ad7203fefe7e386fdfedd2243afc52fa700b8bde6a3a4f" url: "https://pub.dev" source: hosted - version: "0.1.19" + version: "0.1.20" badges: dependency: "direct main" description: @@ -229,10 +229,10 @@ packages: dependency: transitive description: name: coverage - sha256: "8acabb8306b57a409bf4c83522065672ee13179297a6bb0cb9ead73948df7c76" + sha256: "3945034e86ea203af7a056d98e98e42a5518fff200d6e8e6647e1886b07e936e" url: "https://pub.dev" source: hosted - version: "1.7.2" + version: "1.8.0" cross_file: dependency: transitive description: @@ -269,10 +269,10 @@ packages: dependency: transitive description: name: dart_webrtc - sha256: b3a4f109c551a10170ece8fc79b5ca1b98223f24bcebc0f971d7fe35daad7a3b + sha256: dc97a87e8d8c168fc5401ef18eaa0e06e1ac8fc099812de27a002860d021e227 url: "https://pub.dev" source: hosted - version: "1.4.4" + version: "1.4.7" dbus: dependency: transitive description: @@ -397,10 +397,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: "45c70b43df893027e441a6fa0aacc8f484fb9f9c60c746dc8f1dc4f774cf55cd" + sha256: "824f5b9f389bfc4dddac3dea76cd70c51092d9dff0b2ece7ef4f53db8547d258" url: "https://pub.dev" source: hosted - version: "8.0.2" + version: "8.0.6" file_selector_linux: dependency: transitive description: @@ -413,10 +413,10 @@ packages: dependency: transitive description: name: file_selector_macos - sha256: b15c3da8bd4908b9918111fa486903f5808e388b8d1c559949f584725a6594d6 + sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 url: "https://pub.dev" source: hosted - version: "0.9.3+3" + version: "0.9.4" file_selector_platform_interface: dependency: transitive description: @@ -471,10 +471,10 @@ packages: dependency: "direct main" description: name: flutter_foreground_task - sha256: d40a1ddd5f275450d2e32055e7f884796c028a02ac26c751c20916576f79e132 + sha256: "6cf10a27f5e344cd2ecad0752d3a5f4ec32846d82fda8753b3fe2480ebb832a3" url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.5.0" flutter_highlighter: dependency: "direct main" description: @@ -551,10 +551,10 @@ packages: dependency: transitive description: name: flutter_layout_grid - sha256: "962a7ec8c7ea46c3b10606dac9c964f9143d10daa5ca28e40f4ce14eeef85b2a" + sha256: "88b4f8484a0874962e27c47733ad256aeb26acc694a9f029edbef771d301885a" url: "https://pub.dev" source: hosted - version: "2.0.6" + version: "2.0.7" flutter_linkify: dependency: "direct main" description: @@ -575,10 +575,10 @@ packages: dependency: "direct main" description: name: flutter_local_notifications - sha256: "8cdc719114ab1c86c64bb7a86d3a679674c3637edd229e3a994797d4a1504ce4" + sha256: "0a9068149f0225e81642b03562e99776106edbd967816ee68bc16310d457c60e" url: "https://pub.dev" source: hosted - version: "17.1.0" + version: "17.2.1+1" flutter_local_notifications_linux: dependency: transitive description: @@ -591,10 +591,10 @@ packages: dependency: transitive description: name: flutter_local_notifications_platform_interface - sha256: "340abf67df238f7f0ef58f4a26d2a83e1ab74c77ab03cd2b2d5018ac64db30b7" + sha256: "85f8d07fe708c1bdcf45037f2c0109753b26ae077e9d9e899d55971711a4ea66" url: "https://pub.dev" source: hosted - version: "7.1.0" + version: "7.2.0" flutter_localizations: dependency: "direct main" description: flutter @@ -604,10 +604,10 @@ packages: dependency: "direct main" description: name: flutter_map - sha256: cda8d72135b697f519287258b5294a57ce2f2a5ebf234f0e406aad4dc14c9399 + sha256: "87cc8349b8fa5dccda5af50018c7374b6645334a0d680931c1fe11bce88fa5bb" url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "6.2.1" flutter_math_fork: dependency: "direct main" description: @@ -620,10 +620,10 @@ packages: dependency: "direct dev" description: name: flutter_native_splash - sha256: edf39bcf4d74aca1eb2c1e43c3e445fd9f494013df7f0da752fefe72020eedc0 + sha256: aa06fec78de2190f3db4319dd60fdc8d12b2626e93ef9828633928c2dcaea840 url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" flutter_olm: dependency: "direct main" description: @@ -644,26 +644,26 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f" + sha256: c6b0b4c05c458e1c01ad9bcc14041dd7b1f6783d487be4386f793f47a8a4d03e url: "https://pub.dev" source: hosted - version: "2.0.19" + version: "2.0.20" flutter_ringtone_player: dependency: "direct main" description: name: flutter_ringtone_player - sha256: bdbf0ba551fd81cf02fab5c45800dc0006fc51167a1ed252321046fd8ac2bce3 + sha256: d0277a04e629a6582d776f5dcc2a879a733f7326ba073b872a9ccfbff9d9b51f url: "https://pub.dev" source: hosted - version: "4.0.0+2" + version: "4.0.0+3" flutter_secure_storage: dependency: "direct main" description: name: flutter_secure_storage - sha256: ffdbb60130e4665d2af814a0267c481bcf522c41ae2e43caf69fa0146876d685 + sha256: "165164745e6afb5c0e3e3fcc72a012fb9e58496fb26ffb92cf22e16a821e85d0" url: "https://pub.dev" source: hosted - version: "9.0.0" + version: "9.2.2" flutter_secure_storage_linux: dependency: "direct overridden" description: @@ -676,34 +676,34 @@ packages: dependency: transitive description: name: flutter_secure_storage_macos - sha256: bd33935b4b628abd0b86c8ca20655c5b36275c3a3f5194769a7b3f37c905369c + sha256: "1693ab11121a5f925bbea0be725abfcfbbcf36c1e29e571f84a0c0f436147a81" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.1.2" flutter_secure_storage_platform_interface: dependency: transitive description: name: flutter_secure_storage_platform_interface - sha256: "0d4d3a5dd4db28c96ae414d7ba3b8422fd735a8255642774803b2532c9a61d7e" + sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.1.2" flutter_secure_storage_web: dependency: transitive description: name: flutter_secure_storage_web - sha256: "30f84f102df9dcdaa2241866a958c2ec976902ebdaa8883fbfe525f1f2f3cf20" + sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.2.1" flutter_secure_storage_windows: dependency: transitive description: name: flutter_secure_storage_windows - sha256: "5809c66f9dd3b4b93b0a6e2e8561539405322ee767ac2f64d084e2ab5429d108" + sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.1.2" flutter_shortcuts: dependency: "direct main" description: @@ -738,10 +738,10 @@ packages: dependency: "direct main" description: name: flutter_web_auth_2 - sha256: "3ea3a0cc539ca74319f4f2f7484f62742fe5b2ff9a0fca37575426d6e6f07901" + sha256: "4d3d2fd3d26bf1a26b3beafd4b4b899c0ffe10dc99af25abc58ffe24e991133c" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" flutter_web_auth_2_platform_interface: dependency: transitive description: @@ -759,18 +759,18 @@ packages: dependency: "direct main" description: name: flutter_webrtc - sha256: "20eac28848a2dffb26cc2b2870a5164613904511a0b7e8f4825e31a2768175d2" + sha256: fd5f115a08dcdc00b988bea3003c956f1b60a78a61d899cbddfb44f5d0e44d4a url: "https://pub.dev" source: hosted - version: "0.10.3" + version: "0.10.8" frontend_server_client: dependency: transitive description: name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.0.0" fuchsia_remote_debug_protocol: dependency: transitive description: flutter @@ -844,18 +844,18 @@ packages: dependency: "direct main" description: name: go_router - sha256: "466425a64508ca00983882f523400d9169365cb9b464e2e2419f3b6545ff9c51" + sha256: "39dd52168d6c59984454183148dc3a5776960c61083adfc708cc79a7b3ce1ba8" url: "https://pub.dev" source: hosted - version: "14.0.1" + version: "14.2.1" gradient_borders: dependency: transitive description: name: gradient_borders - sha256: "69eeaff519d145a4c6c213ada1abae386bcc8981a4970d923e478ce7ba19e309" + sha256: b1cd969552c83f458ff755aa68e13a0327d09f06c3f42f471b423b01427f21f8 url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.1" highlighter: dependency: transitive description: @@ -924,26 +924,26 @@ packages: dependency: "direct main" description: name: image - sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e" + sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8" url: "https://pub.dev" source: hosted - version: "4.1.7" + version: "4.2.0" image_picker: dependency: "direct main" description: name: image_picker - sha256: fe9ee64ccb8d599a5dfb0e21cc6652232c610bcf667af4e79b9eb175cc30a7a5 + sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.2" image_picker_android: dependency: transitive description: name: image_picker_android - sha256: "8e75431a62b7feb4fd55cb4a5c6f0ac4564460ec5dc09f9c4a0d50a5ce7c4cb9" + sha256: ff39a10ab4f48f4ac70776d0494a97bf073cd2570892cd46bc8a5cac162c25db url: "https://pub.dev" source: hosted - version: "0.8.10" + version: "0.8.12+4" image_picker_for_web: dependency: transitive description: @@ -956,10 +956,10 @@ packages: dependency: transitive description: name: image_picker_ios - sha256: f74064bc548b5164a033ec05638e23c91be1a249c255e0f56319dddffd759794 + sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447" url: "https://pub.dev" source: hosted - version: "0.8.10+1" + version: "0.8.12" image_picker_linux: dependency: transitive description: @@ -1049,26 +1049,26 @@ packages: dependency: "direct main" description: name: just_audio - sha256: b7cb6bbf3750caa924d03f432ba401ec300fd90936b3f73a9b33d58b1e96286b + sha256: ee50602364ba83fa6308f5512dd560c713ec3e1f2bc75f0db43618f0d82ef71a url: "https://pub.dev" source: hosted - version: "0.9.37" + version: "0.9.39" just_audio_platform_interface: dependency: transitive description: name: just_audio_platform_interface - sha256: c3dee0014248c97c91fe6299edb73dc4d6c6930a2f4f713579cd692d9e47f4a1 + sha256: "0243828cce503c8366cc2090cefb2b3c871aa8ed2f520670d76fd47aa1ab2790" url: "https://pub.dev" source: hosted - version: "4.2.2" + version: "4.3.0" just_audio_web: dependency: transitive description: name: just_audio_web - sha256: "134356b0fe3d898293102b33b5fd618831ffdc72bb7a1b726140abdf22772b70" + sha256: "0edb481ad4aa1ff38f8c40f1a3576013c3420bf6669b686fe661627d49bc606c" url: "https://pub.dev" source: hosted - version: "0.4.9" + version: "0.4.11" keyboard_shortcuts: dependency: "direct main" description: @@ -1154,10 +1154,10 @@ packages: dependency: transitive description: name: logger - sha256: "8c94b8c219e7e50194efc8771cd0e9f10807d8d3e219af473d89b06cc2ee4e04" + sha256: af05cc8714f356fd1f3888fb6741cbe9fbe25cdb6eedbab80e1a6db21047d4a4 url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.0" logging: dependency: transitive description: @@ -1170,10 +1170,10 @@ packages: dependency: transitive description: name: macos_ui - sha256: d351f0bada7e5b0cee8cf394299878a6c04e5cfcd784fa1d40e44299501124d8 + sha256: "91c7f3427f763fd96b65831342b896b18751140e6bf55f8572fcb41f7b30bcab" url: "https://pub.dev" source: hosted - version: "2.0.5" + version: "2.0.7" macos_window_utils: dependency: transitive description: @@ -1346,18 +1346,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d + sha256: "30c5aa827a6ae95ce2853cdc5fe3971daaac00f6f081c419c013f7f57bff2f5e" url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.2.7" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -1378,10 +1378,10 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.0" permission_handler: dependency: "direct main" description: @@ -1394,18 +1394,18 @@ packages: dependency: transitive description: name: permission_handler_android - sha256: "1acac6bae58144b442f11e66621c062aead9c99841093c38f5bcdcc24c1c3474" + sha256: b29a799ca03be9f999aa6c39f7de5209482d638e6f857f6b93b0875c618b7e54 url: "https://pub.dev" source: hosted - version: "12.0.5" + version: "12.0.7" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: e9ad66020b89ff1b63908f247c2c6f931c6e62699b756ef8b3c4569350cd8662 + sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0 url: "https://pub.dev" source: hosted - version: "9.4.4" + version: "9.4.5" permission_handler_html: dependency: transitive description: @@ -1450,10 +1450,10 @@ packages: dependency: transitive description: name: platform_detect - sha256: "08f4ee79c0e1c4858d37e06b22352a3ebdef5466b613749a3adb03e703d4f5b0" + sha256: a62f99417fc4fa2d099ce0ccdbb1bd3977920f2a64292c326271f049d4bc3a4f url: "https://pub.dev" source: hosted - version: "2.0.11" + version: "2.1.0" plugin_platform_interface: dependency: transitive description: @@ -1466,18 +1466,18 @@ packages: dependency: transitive description: name: pointer_interceptor - sha256: bd18321519718678d5fa98ad3a3359cbc7a31f018554eab80b73d08a7f0c165a + sha256: d0a8e660d1204eaec5bd34b34cc92174690e076d2e4f893d9d68c486a13b07c4 url: "https://pub.dev" source: hosted - version: "0.10.1" + version: "0.10.1+1" pointer_interceptor_ios: dependency: transitive description: name: pointer_interceptor_ios - sha256: "2e73c39452830adc4695757130676a39412a3b7f3c34e3f752791b5384770877" + sha256: a6906772b3205b42c44614fcea28f818b1e5fdad73a4ca742a7bd49818d9c917 url: "https://pub.dev" source: hosted - version: "0.10.0+2" + version: "0.10.1" pointer_interceptor_platform_interface: dependency: transitive description: @@ -1494,14 +1494,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.10.2" - pointycastle: - dependency: transitive - description: - name: pointycastle - sha256: "79fbafed02cfdbe85ef3fd06c7f4bc2cbcba0177e61b765264853d4253b21744" - url: "https://pub.dev" - source: hosted - version: "3.9.0" polylabel: dependency: transitive description: @@ -1562,10 +1554,10 @@ packages: dependency: transitive description: name: pubspec_parse - sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.3.0" punycode: dependency: "direct main" description: @@ -1618,58 +1610,58 @@ packages: dependency: "direct main" description: name: record - sha256: "113b368168c49c78902ab37c2b354dea30a0aec5bdeca434073826b6ea73eca1" + sha256: "4a5cf4d083d1ee49e0878823c4397d073f8eb0a775f31215d388e2bc47a9e867" url: "https://pub.dev" source: hosted - version: "5.0.5" + version: "5.1.2" record_android: dependency: transitive description: name: record_android - sha256: "0df98e05873b22b443309e289bf1eb3b5b9a60e7779134334e2073eb0763a992" + sha256: "9ccf6a206dc72b486cf37893690e70c17610e8f05dba8da1a808e73dc2f49a04" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.4" record_darwin: dependency: transitive description: name: record_darwin - sha256: ee8cb1bb1712d7ce38140ecabe70e5c286c02f05296d66043bee865ace7eb1b9 + sha256: b038c26d1066eb81f4e7433bfb85f0d450ca3fac0002a7216b83a21b775ecf21 url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.1.1" record_linux: dependency: transitive description: name: record_linux - sha256: "7d0e70cd51635128fe9d37d89bafd6011d7cbba9af8dc323079ae60f23546aef" + sha256: "74d41a9ebb1eb498a38e9a813dd524e8f0b4fdd627270bda9756f437b110a3e3" url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "0.7.2" record_platform_interface: dependency: transitive description: name: record_platform_interface - sha256: "3a4b56e94ecd2a0b2b43eb1fa6f94c5b8484334f5d38ef43959c4bf97fb374cf" + sha256: "11f8b03ea8a0e279b0e306571dbe0db0202c0b8e866495c9fa1ad2281d5e4c15" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.1.0" record_web: dependency: transitive description: name: record_web - sha256: "24847cdbcf999f7a5762170792f622ac844858766becd0f2370ec8ae22f7526e" + sha256: "703adb626d31e2dd86a8f6b34e306e03cd323e0c5e16e11bbc0385b07a8df97e" url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "1.1.1" record_windows: dependency: transitive description: name: record_windows - sha256: "39998b3ea7d8d28b04159d82220e6e5e32a7c357c6fb2794f5736beea272f6c3" + sha256: e653555aa3fda168aded7c34e11bd82baf0c6ac84e7624553def3c77ffefd36f url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.0.3" remove_emoji: dependency: transitive description: @@ -1754,18 +1746,18 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2" + sha256: "93d0ec9dd902d85f326068e6a899487d1f65ffcd5798721a95330b26c8131577" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" + sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7" url: "https://pub.dev" source: hosted - version: "2.3.5" + version: "2.4.0" shared_preferences_linux: dependency: transitive description: @@ -1879,10 +1871,10 @@ packages: dependency: transitive description: name: sqflite - sha256: "5ce2e1a15e822c3b4bfb5400455775e421da7098eed8adc8f26298ada7c9308c" + sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.3.3+1" sqflite_common: dependency: transitive description: @@ -1903,18 +1895,18 @@ packages: dependency: "direct main" description: name: sqlcipher_flutter_libs - sha256: "60fe3444ff5b1b298a9ca3003c6c7f1f7ee4c90aa6035a8647f3aeaf05a073e2" + sha256: "672e7f9d8a19896c3bfc44ca5f6fd8ee978970c5946817eeedf5e59c176aacf1" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.6.3" sqlite3: dependency: transitive description: name: sqlite3 - sha256: "1abbeb84bf2b1a10e5e1138c913123c8aa9d83cd64e5f9a0dd847b3c83063202" + sha256: "6d17989c0b06a5870b2190d391925186f944cb943e5262d0d3f778fcfca3bc6e" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.4" stack_trace: dependency: transitive description: @@ -1975,10 +1967,10 @@ packages: dependency: transitive description: name: tar - sha256: aca91e93ff9ff2dba4462c6eea6bc260b72f0d7010e748e3397c32190529bd6e + sha256: "22f67e2d77b51050436620b2a5de521c58ca6f0b75af1d9ab3c8cae2eae58fcd" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" term_glyph: dependency: transitive description: @@ -2015,10 +2007,10 @@ packages: dependency: transitive description: name: timezone - sha256: a6ccda4a69a442098b602c44e61a1e2b4bf6f5516e875bbf0f427d5df14745d5 + sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d" url: "https://pub.dev" source: hosted - version: "0.9.3" + version: "0.9.4" tint: dependency: transitive description: @@ -2143,26 +2135,26 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e" + sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" url: "https://pub.dev" source: hosted - version: "6.2.6" + version: "6.3.0" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "360a6ed2027f18b73c8d98e159dda67a61b7f2e0f6ec26e86c3ada33b0621775" + sha256: ceb2625f0c24ade6ef6778d1de0b2e44f2db71fded235eb52295247feba8c5cf url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.3.3" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5" + sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e url: "https://pub.dev" source: hosted - version: "6.2.5" + version: "6.3.1" url_launcher_linux: dependency: transitive description: @@ -2175,10 +2167,10 @@ packages: dependency: transitive description: name: url_launcher_macos - sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 + sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.2.0" url_launcher_platform_interface: dependency: transitive description: @@ -2207,10 +2199,10 @@ packages: dependency: transitive description: name: uuid - sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8" + sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90" url: "https://pub.dev" source: hosted - version: "4.4.0" + version: "4.4.2" vector_graphics: dependency: transitive description: @@ -2255,26 +2247,26 @@ packages: dependency: "direct main" description: name: video_player - sha256: db6a72d8f4fd155d0189845678f55ad2fd54b02c10dcafd11c068dbb631286c0 + sha256: e30df0d226c4ef82e2c150ebf6834b3522cf3f654d8e2f9419d376cdc071425d url: "https://pub.dev" source: hosted - version: "2.8.6" + version: "2.9.1" video_player_android: dependency: transitive description: name: video_player_android - sha256: "134e1ad410d67e18a19486ed9512c72dfc6d8ffb284d0e8f2e99e903d1ba8fa3" + sha256: fdc0331ce9f808cc2714014cb8126bd6369943affefd54f8fdab0ea0bb617b7f url: "https://pub.dev" source: hosted - version: "2.4.14" + version: "2.5.2" video_player_avfoundation: dependency: transitive description: name: video_player_avfoundation - sha256: "00c49b1d68071341397cf760b982c1e26ed9232464c8506ee08378a5cca5070d" + sha256: d1e9a824f2b324000dc8fb2dcb2a3285b6c1c7c487521c63306cc5b394f68a7c url: "https://pub.dev" source: hosted - version: "2.5.7" + version: "2.6.1" video_player_platform_interface: dependency: transitive description: @@ -2287,10 +2279,10 @@ packages: dependency: transitive description: name: video_player_web - sha256: "41245cef5ef29c4585dbabcbcbe9b209e34376642c7576cabf11b4ad9289d6e4" + sha256: ff4d69a6614b03f055397c27a71c9d3ddea2b2a23d71b2ba0164f59ca32b8fe2 url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.3.1" visibility_detector: dependency: transitive description: @@ -2311,10 +2303,10 @@ packages: dependency: "direct main" description: name: wakelock_plus - sha256: c8b7cc80f045533b40a0e6c9109905494e3cf32c0fbd5c62616998e0de44003f + sha256: "14758533319a462ffb5aa3b7ddb198e59b29ac3b02da14173a1715d65d4e6e68" url: "https://pub.dev" source: hosted - version: "1.2.4" + version: "1.2.5" wakelock_plus_platform_interface: dependency: transitive description: @@ -2375,10 +2367,10 @@ packages: dependency: transitive description: name: win32 - sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" + sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4 url: "https://pub.dev" source: hosted - version: "5.5.0" + version: "5.5.1" win32_registry: dependency: transitive description: @@ -2428,5 +2420,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.19.3" + dart: ">=3.4.0 <4.0.0" + flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index 16e1ffafc5..36a37e8c4a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -26,7 +26,7 @@ dependencies: emoji_proposal: ^0.0.1 emojis: ^0.9.9 #fcm_shared_isolate: ^0.1.0 - file_picker: ^8.0.1 + file_picker: ^8.0.6 flutter: sdk: flutter flutter_app_badger: ^1.5.0 @@ -36,7 +36,7 @@ dependencies: flutter_html: ^3.0.0-beta.2 flutter_html_table: ^3.0.0-beta.2 flutter_linkify: ^6.0.0 - flutter_local_notifications: ^17.0.0 + flutter_local_notifications: ^17.2.1+1 flutter_localizations: sdk: flutter flutter_map: ^6.1.0 @@ -60,7 +60,7 @@ dependencies: image: ^4.1.7 image_picker: ^1.1.0 intl: any - just_audio: ^0.9.37 + just_audio: ^0.9.39 keyboard_shortcuts: ^0.1.4 latlong2: ^0.9.1 linkify: ^5.0.0 @@ -76,7 +76,7 @@ dependencies: punycode: ^1.0.0 qr_code_scanner: ^1.0.1 receive_sharing_intent: 1.4.5 # Update needs more work - record: ^5.0.5 + record: ^5.1.2 scroll_to_index: ^3.0.1 share_plus: ^9.0.0 shared_preferences: ^2.2.0 # Pinned because https://github.com/flutter/flutter/issues/118401 diff --git a/scripts/enable-android-google-services.patch b/scripts/enable-android-google-services.patch index 8353553d39..88bfb5ce8f 100644 --- a/scripts/enable-android-google-services.patch +++ b/scripts/enable-android-google-services.patch @@ -156,6 +156,6 @@ index 193e6ed6..f70e48d4 100644 emojis: ^0.9.9 - #fcm_shared_isolate: ^0.1.0 + fcm_shared_isolate: ^0.1.0 - file_picker: ^8.0.1 + file_picker: ^8.0.6 flutter: sdk: flutter From 13a39498a39f0bb21f035d2ca71831e04487677e Mon Sep 17 00:00:00 2001 From: dlyrsk <129295755+dlyrsk@users.noreply.github.com> Date: Sun, 14 Jul 2024 21:27:52 +0930 Subject: [PATCH 048/288] Fix web base url and privacy url configuration processing --- lib/config/app_config.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index 841d810ec6..a00554c12a 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -94,10 +94,10 @@ abstract class AppConfig { _defaultHomeserver = json['default_homeserver']; } if (json['privacy_url'] is String) { - _webBaseUrl = json['privacy_url']; + _privacyUrl = json['privacy_url']; } if (json['web_base_url'] is String) { - _privacyUrl = json['web_base_url']; + _webBaseUrl = json['web_base_url']; } if (json['render_html'] is bool) { renderHtml = json['render_html']; From 5c23453e665ed8804b445b5f792dc6efc3e14f08 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sun, 14 Jul 2024 16:49:46 +0200 Subject: [PATCH 049/288] feat: New spaces and chat list design --- assets/l10n/intl_en.arb | 13 + lib/config/app_config.dart | 1 - lib/config/routes.dart | 5 - lib/config/setting_keys.dart | 1 - lib/pages/chat_list/chat_list.dart | 519 +++++----- lib/pages/chat_list/chat_list_body.dart | 458 +++++---- lib/pages/chat_list/chat_list_header.dart | 192 ++-- lib/pages/chat_list/chat_list_item.dart | 176 ++-- lib/pages/chat_list/chat_list_view.dart | 206 +--- lib/pages/chat_list/space_view.dart | 886 ++++++++---------- lib/pages/chat_list/start_chat_fab.dart | 88 -- lib/pages/chat_list/utils/on_chat_tap.dart | 127 --- .../settings_style/settings_style_view.dart | 6 - lib/widgets/avatar.dart | 98 +- lib/widgets/layouts/two_column_layout.dart | 4 +- lib/widgets/matrix.dart | 4 - linux/my_application.cc | 2 +- 17 files changed, 1237 insertions(+), 1549 deletions(-) delete mode 100644 lib/pages/chat_list/start_chat_fab.dart delete mode 100644 lib/pages/chat_list/utils/on_chat_tap.dart diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index cfd26a7c4c..ab0ba910df 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -196,6 +196,19 @@ "supportedVersions": {} } }, + "countChatsAndCountParticipants": "{chats} chats and {participants} participants", + "@countChatsAndCountParticipants": { + "type": "text", + "placeholders": { + "chats": {}, + "participants": {} + } + }, + "noMoreChatsFound": "No more chats found...", + "joinedChats": "Joined chats", + "unread": "Unread", + "space": "Space", + "spaces": "Spaces", "banFromChat": "Ban from chat", "@banFromChat": { "type": "text", diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index 841d810ec6..6ee20d3372 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -44,7 +44,6 @@ abstract class AppConfig { static bool hideRedactedEvents = false; static bool hideUnknownEvents = true; static bool hideUnimportantStateEvents = true; - static bool separateChatTypes = false; static bool autoplayImages = true; static bool sendTypingNotifications = true; static bool sendPublicReadReceipts = true; diff --git a/lib/config/routes.dart b/lib/config/routes.dart index d11cd56db6..4aaf63a0b0 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -92,12 +92,8 @@ abstract class AppRoutes { FluffyThemes.isColumnMode(context) && state.fullPath?.startsWith('/rooms/settings') == false ? TwoColumnLayout( - displayNavigationRail: - state.path?.startsWith('/rooms/settings') != true, mainView: ChatList( activeChat: state.pathParameters['roomid'], - displayNavigationRail: - state.path?.startsWith('/rooms/settings') != true, ), sideView: child, ) @@ -175,7 +171,6 @@ abstract class AppRoutes { ? TwoColumnLayout( mainView: const Settings(), sideView: child, - displayNavigationRail: false, ) : child, ), diff --git a/lib/config/setting_keys.dart b/lib/config/setting_keys.dart index 7c0e50df81..5b795b08e0 100644 --- a/lib/config/setting_keys.dart +++ b/lib/config/setting_keys.dart @@ -4,7 +4,6 @@ abstract class SettingKeys { static const String hideUnknownEvents = 'chat.fluffy.hideUnknownEvents'; static const String hideUnimportantStateEvents = 'chat.fluffy.hideUnimportantStateEvents'; - static const String separateChatTypes = 'chat.fluffy.separateChatTypes'; static const String sentry = 'sentry'; static const String theme = 'theme'; static const String amoledEnabled = 'amoled_enabled'; diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 93f8d4ad27..9181b1dff2 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -10,12 +10,13 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_shortcuts/flutter_shortcuts.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:go_router/go_router.dart'; +import 'package:matrix/matrix.dart' as sdk; import 'package:matrix/matrix.dart'; import 'package:receive_sharing_intent/receive_sharing_intent.dart'; import 'package:uni_links/uni_links.dart'; import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/pages/chat/send_file_dialog.dart'; import 'package:fluffychat/pages/chat_list/chat_list_view.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; @@ -35,7 +36,6 @@ import 'package:fluffychat/utils/tor_stub.dart' enum SelectMode { normal, share, - select, } enum PopupMenuAction { @@ -49,19 +49,32 @@ enum PopupMenuAction { enum ActiveFilter { allChats, + unread, groups, - messages, spaces, } +extension LocalizedActiveFilter on ActiveFilter { + String toLocalizedString(BuildContext context) { + switch (this) { + case ActiveFilter.allChats: + return L10n.of(context)!.all; + case ActiveFilter.unread: + return L10n.of(context)!.unread; + case ActiveFilter.groups: + return L10n.of(context)!.groups; + case ActiveFilter.spaces: + return L10n.of(context)!.spaces; + } + } +} + class ChatList extends StatefulWidget { static BuildContext? contextForVoip; - final bool displayNavigationRail; final String? activeChat; const ChatList({ super.key, - this.displayNavigationRail = false, required this.activeChat, }); @@ -77,85 +90,240 @@ class ChatListController extends State StreamSubscription? _intentUriStreamSubscription; - bool get displayNavigationBar => - !FluffyThemes.isColumnMode(context) && - (spaces.isNotEmpty || AppConfig.separateChatTypes); + void createNewSpace() { + context.push('/rooms/newspace'); + } - String? activeSpaceId; + ActiveFilter activeFilter = ActiveFilter.allChats; - void resetActiveSpaceId() { - setState(() { - selectedRoomIds.clear(); - activeSpaceId = null; - }); - } + String? _activeSpaceId; + String? get activeSpaceId => _activeSpaceId; - void setActiveSpace(String? spaceId) { - setState(() { - selectedRoomIds.clear(); - activeSpaceId = spaceId; - activeFilter = ActiveFilter.spaces; - }); - } + void setActiveSpace(String spaceId) => setState(() { + _activeSpaceId = spaceId; + }); + void clearActiveSpace() => setState(() { + _activeSpaceId = null; + }); - void createNewSpace() async { - final spaceId = await context.push('/rooms/newspace'); - if (spaceId != null) { - setActiveSpace(spaceId); + void addChatAction() async { + if (activeSpaceId == null) { + context.go('/rooms/newprivatechat'); + return; } + + final roomType = await showConfirmationDialog( + context: context, + title: L10n.of(context)!.addChatOrSubSpace, + actions: [ + AlertDialogAction( + key: AddRoomType.subspace, + label: L10n.of(context)!.createNewSpace, + ), + AlertDialogAction( + key: AddRoomType.chat, + label: L10n.of(context)!.createGroup, + ), + ], + ); + if (roomType == null) return; + + final names = await showTextInputDialog( + context: context, + title: roomType == AddRoomType.subspace + ? L10n.of(context)!.createNewSpace + : L10n.of(context)!.createGroup, + textFields: [ + DialogTextField( + hintText: roomType == AddRoomType.subspace + ? L10n.of(context)!.spaceName + : L10n.of(context)!.groupName, + minLines: 1, + maxLines: 1, + maxLength: 64, + validator: (text) { + if (text == null || text.isEmpty) { + return L10n.of(context)!.pleaseChoose; + } + return null; + }, + ), + DialogTextField( + hintText: L10n.of(context)!.chatDescription, + minLines: 4, + maxLines: 8, + maxLength: 255, + ), + ], + okLabel: L10n.of(context)!.create, + cancelLabel: L10n.of(context)!.cancel, + ); + if (names == null) return; + final client = Matrix.of(context).client; + final result = await showFutureLoadingDialog( + context: context, + future: () async { + late final String roomId; + final activeSpace = client.getRoomById(activeSpaceId!)!; + await activeSpace.postLoad(); + + if (roomType == AddRoomType.subspace) { + roomId = await client.createSpace( + name: names.first, + topic: names.last.isEmpty ? null : names.last, + visibility: activeSpace.joinRules == JoinRules.public + ? sdk.Visibility.public + : sdk.Visibility.private, + ); + } else { + roomId = await client.createGroupChat( + groupName: names.first, + preset: activeSpace.joinRules == JoinRules.public + ? CreateRoomPreset.publicChat + : CreateRoomPreset.privateChat, + visibility: activeSpace.joinRules == JoinRules.public + ? sdk.Visibility.public + : sdk.Visibility.private, + initialState: names.length > 1 && names.last.isNotEmpty + ? [ + sdk.StateEvent( + type: sdk.EventTypes.RoomTopic, + content: {'topic': names.last}, + ), + ] + : null, + ); + } + await activeSpace.setSpaceChild(roomId); + }, + ); + if (result.error != null) return; } - int get selectedIndex { - switch (activeFilter) { - case ActiveFilter.allChats: - case ActiveFilter.messages: - return 0; - case ActiveFilter.groups: - return 1; - case ActiveFilter.spaces: - return AppConfig.separateChatTypes ? 2 : 1; + void onChatTap(Room room, BuildContext context) async { + if (room.isSpace) { + setActiveSpace(room.id); + return; + } + if (room.membership == Membership.invite) { + final inviterId = + room.getState(EventTypes.RoomMember, room.client.userID!)?.senderId; + final inviteAction = await showModalActionSheet( + context: context, + message: room.isDirectChat + ? L10n.of(context)!.invitePrivateChat + : L10n.of(context)!.inviteGroupChat, + title: room.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)), + actions: [ + SheetAction( + key: InviteActions.accept, + label: L10n.of(context)!.accept, + icon: Icons.check_outlined, + isDefaultAction: true, + ), + SheetAction( + key: InviteActions.decline, + label: L10n.of(context)!.decline, + icon: Icons.close_outlined, + isDestructiveAction: true, + ), + SheetAction( + key: InviteActions.block, + label: L10n.of(context)!.block, + icon: Icons.block_outlined, + isDestructiveAction: true, + ), + ], + ); + if (inviteAction == null) return; + if (inviteAction == InviteActions.block) { + context.go('/rooms/settings/security/ignorelist', extra: inviterId); + return; + } + if (inviteAction == InviteActions.decline) { + await showFutureLoadingDialog( + context: context, + future: room.leave, + ); + return; + } + final joinResult = await showFutureLoadingDialog( + context: context, + future: () async { + final waitForRoom = room.client.waitForRoomInSync( + room.id, + join: true, + ); + await room.join(); + await waitForRoom; + }, + ); + if (joinResult.error != null) return; } - } - ActiveFilter getActiveFilterByDestination(int? i) { - switch (i) { - case 1: - if (AppConfig.separateChatTypes) { - return ActiveFilter.groups; + if (room.membership == Membership.ban) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(L10n.of(context)!.youHaveBeenBannedFromThisChat), + ), + ); + return; + } + + if (room.membership == Membership.leave) { + context.go('/rooms/archive/${room.id}'); + return; + } + + // Share content into this room + final shareContent = Matrix.of(context).shareContent; + if (shareContent != null) { + final shareFile = shareContent.tryGet('file'); + if (shareContent.tryGet('msgtype') == 'chat.fluffy.shared_file' && + shareFile != null) { + await showDialog( + context: context, + useRootNavigator: false, + builder: (c) => SendFileDialog( + files: [shareFile], + room: room, + ), + ); + Matrix.of(context).shareContent = null; + } else { + final consent = await showOkCancelAlertDialog( + context: context, + title: L10n.of(context)!.forward, + message: L10n.of(context)!.forwardMessageTo( + room.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)), + ), + okLabel: L10n.of(context)!.forward, + cancelLabel: L10n.of(context)!.cancel, + ); + if (consent == OkCancelResult.cancel) { + Matrix.of(context).shareContent = null; + return; } - return ActiveFilter.spaces; - case 2: - return ActiveFilter.spaces; - case 0: - default: - if (AppConfig.separateChatTypes) { - return ActiveFilter.messages; + if (consent == OkCancelResult.ok) { + room.sendEvent(shareContent); + Matrix.of(context).shareContent = null; } - return ActiveFilter.allChats; + } } - } - void onDestinationSelected(int? i) { - setState(() { - selectedRoomIds.clear(); - activeFilter = getActiveFilterByDestination(i); - }); + context.go('/rooms/${room.id}'); } - ActiveFilter activeFilter = AppConfig.separateChatTypes - ? ActiveFilter.messages - : ActiveFilter.allChats; - bool Function(Room) getRoomFilterByActiveFilter(ActiveFilter activeFilter) { switch (activeFilter) { case ActiveFilter.allChats: - return (room) => !room.isSpace; + return (room) => true; case ActiveFilter.groups: return (room) => !room.isSpace && !room.isDirectChat; - case ActiveFilter.messages: - return (room) => !room.isSpace && room.isDirectChat; + case ActiveFilter.unread: + return (room) => room.isUnreadOrInvited; case ActiveFilter.spaces: - return (r) => r.isSpace; + return (room) => room.isSpace; } } @@ -331,15 +499,11 @@ class ChatListController extends State List get spaces => Matrix.of(context).client.rooms.where((r) => r.isSpace).toList(); - final selectedRoomIds = {}; - String? get activeChat => widget.activeChat; SelectMode get selectMode => Matrix.of(context).shareContent != null ? SelectMode.share - : selectedRoomIds.isEmpty - ? SelectMode.normal - : SelectMode.select; + : SelectMode.normal; void _processIncomingSharedFiles(List files) { if (files.isEmpty) return; @@ -448,80 +612,67 @@ class ChatListController extends State super.dispose(); } - void toggleSelection(String roomId) { - setState( - () => selectedRoomIds.contains(roomId) - ? selectedRoomIds.remove(roomId) - : selectedRoomIds.add(roomId), - ); - } - - Future toggleUnread() async { - await showFutureLoadingDialog( + void chatContextAction(Room room) async { + final action = await showModalActionSheet( context: context, - future: () async { - final markUnread = anySelectedRoomNotMarkedUnread; - final client = Matrix.of(context).client; - for (final roomId in selectedRoomIds) { - final room = client.getRoomById(roomId)!; - if (room.markedUnread == markUnread) continue; - await client.getRoomById(roomId)!.markUnread(markUnread); - } - }, + title: room.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)), + actions: [ + SheetAction( + key: ChatContextAction.markUnread, + icon: room.markedUnread + ? Icons.mark_as_unread + : Icons.mark_as_unread_outlined, + label: room.markedUnread + ? L10n.of(context)!.markAsRead + : L10n.of(context)!.unread, + ), + SheetAction( + key: ChatContextAction.favorite, + icon: room.isFavourite ? Icons.pin : Icons.pin_outlined, + label: room.isFavourite + ? L10n.of(context)!.unpin + : L10n.of(context)!.pin, + ), + SheetAction( + key: ChatContextAction.mute, + icon: room.pushRuleState == PushRuleState.notify + ? Icons.notifications_off_outlined + : Icons.notifications, + label: room.pushRuleState == PushRuleState.notify + ? L10n.of(context)!.muteChat + : L10n.of(context)!.unmuteChat, + ), + SheetAction( + isDestructiveAction: true, + key: ChatContextAction.leave, + icon: Icons.delete_outlined, + label: L10n.of(context)!.leave, + ), + ], ); - cancelAction(); - } - Future toggleFavouriteRoom() async { - await showFutureLoadingDialog( - context: context, - future: () async { - final makeFavorite = anySelectedRoomNotFavorite; - final client = Matrix.of(context).client; - for (final roomId in selectedRoomIds) { - final room = client.getRoomById(roomId)!; - if (room.isFavourite == makeFavorite) continue; - await client.getRoomById(roomId)!.setFavourite(makeFavorite); - } - }, - ); - cancelAction(); - } + if (action == null) return; + if (!mounted) return; - Future toggleMuted() async { await showFutureLoadingDialog( context: context, - future: () async { - final newState = anySelectedRoomNotMuted - ? PushRuleState.mentionsOnly - : PushRuleState.notify; - final client = Matrix.of(context).client; - for (final roomId in selectedRoomIds) { - final room = client.getRoomById(roomId)!; - if (room.pushRuleState == newState) continue; - await client.getRoomById(roomId)!.setPushRuleState(newState); + future: () { + switch (action) { + case ChatContextAction.favorite: + return room.setFavourite(!room.isFavourite); + case ChatContextAction.markUnread: + return room.markUnread(!room.markedUnread); + case ChatContextAction.mute: + return room.setPushRuleState( + room.pushRuleState == PushRuleState.notify + ? PushRuleState.mentionsOnly + : PushRuleState.notify, + ); + case ChatContextAction.leave: + return room.leave(); } }, ); - cancelAction(); - } - - Future archiveAction() async { - final confirmed = await showOkCancelAlertDialog( - useRootNavigator: false, - context: context, - title: L10n.of(context)!.areYouSure, - okLabel: L10n.of(context)!.yes, - cancelLabel: L10n.of(context)!.cancel, - message: L10n.of(context)!.archiveRoomDescription, - ) == - OkCancelResult.ok; - if (!confirmed) return; - await showFutureLoadingDialog( - context: context, - future: () => _archiveSelectedRooms(), - ); - setState(() {}); } void dismissStatusList() async { @@ -568,76 +719,6 @@ class ChatListController extends State ); } - Future _archiveSelectedRooms() async { - final client = Matrix.of(context).client; - while (selectedRoomIds.isNotEmpty) { - final roomId = selectedRoomIds.first; - try { - await client.getRoomById(roomId)!.leave(); - } finally { - toggleSelection(roomId); - } - } - } - - Future addToSpace() async { - final selectedSpace = await showConfirmationDialog( - context: context, - title: L10n.of(context)!.addToSpace, - message: L10n.of(context)!.addToSpaceDescription, - fullyCapitalizedForMaterial: false, - actions: Matrix.of(context) - .client - .rooms - .where((r) => r.isSpace) - .map( - (space) => AlertDialogAction( - key: space.id, - label: space - .getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)), - ), - ) - .toList(), - ); - if (selectedSpace == null) return; - final result = await showFutureLoadingDialog( - context: context, - future: () async { - final space = Matrix.of(context).client.getRoomById(selectedSpace)!; - if (space.canSendDefaultStates) { - for (final roomId in selectedRoomIds) { - await space.setSpaceChild(roomId); - } - } - }, - ); - if (result.error == null) { - if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(L10n.of(context)!.chatHasBeenAddedToThisSpace), - ), - ); - } - - setState(() => selectedRoomIds.clear()); - } - - bool get anySelectedRoomNotMarkedUnread => selectedRoomIds.any( - (roomId) => - !Matrix.of(context).client.getRoomById(roomId)!.markedUnread, - ); - - bool get anySelectedRoomNotFavorite => selectedRoomIds.any( - (roomId) => !Matrix.of(context).client.getRoomById(roomId)!.isFavourite, - ); - - bool get anySelectedRoomNotMuted => selectedRoomIds.any( - (roomId) => - Matrix.of(context).client.getRoomById(roomId)!.pushRuleState == - PushRuleState.notify, - ); - bool waitForFirstSync = false; Future _waitForFirstSync() async { @@ -666,19 +747,20 @@ class ChatListController extends State void cancelAction() { if (selectMode == SelectMode.share) { setState(() => Matrix.of(context).shareContent = null); - } else { - setState(() => selectedRoomIds.clear()); } } + void setActiveFilter(ActiveFilter filter) { + setState(() { + activeFilter = filter; + }); + } + void setActiveClient(Client client) { context.go('/rooms'); setState(() { - activeFilter = AppConfig.separateChatTypes - ? ActiveFilter.messages - : ActiveFilter.allChats; - activeSpaceId = null; - selectedRoomIds.clear(); + activeFilter = ActiveFilter.allChats; + _activeSpaceId = null; Matrix.of(context).setActiveClient(client); }); _clientStream.add(client); @@ -687,7 +769,7 @@ class ChatListController extends State void setActiveBundle(String bundle) { context.go('/rooms'); setState(() { - selectedRoomIds.clear(); + _activeSpaceId = null; Matrix.of(context).activeBundle = bundle; if (!Matrix.of(context) .currentBundle! @@ -780,3 +862,18 @@ class ChatListController extends State } enum EditBundleAction { addToBundle, removeFromBundle } + +enum InviteActions { + accept, + decline, + block, +} + +enum AddRoomType { chat, subspace } + +enum ChatContextAction { + favorite, + markUnread, + mute, + leave, +} diff --git a/lib/pages/chat_list/chat_list_body.dart b/lib/pages/chat_list/chat_list_body.dart index eeb6bb656a..a6526989bc 100644 --- a/lib/pages/chat_list/chat_list_body.dart +++ b/lib/pages/chat_list/chat_list_body.dart @@ -1,7 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:animations/animations.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; @@ -11,7 +10,6 @@ import 'package:fluffychat/pages/chat_list/chat_list_item.dart'; import 'package:fluffychat/pages/chat_list/search_title.dart'; import 'package:fluffychat/pages/chat_list/space_view.dart'; import 'package:fluffychat/pages/chat_list/status_msg_list.dart'; -import 'package:fluffychat/pages/chat_list/utils/on_chat_tap.dart'; import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart'; import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; import 'package:fluffychat/utils/stream_extension.dart'; @@ -29,6 +27,17 @@ class ChatListViewBody extends StatelessWidget { @override Widget build(BuildContext context) { + final activeSpace = controller.activeSpaceId; + if (activeSpace != null) { + return SpaceView( + spaceId: activeSpace, + onBack: controller.clearActiveSpace, + onChatTab: (room) => controller.onChatTap(room, context), + onChatContext: (room) => controller.chatContextAction(room), + activeChat: controller.activeChat, + toParentSpace: controller.setActiveSpace, + ); + } final publicRooms = controller.roomSearchResult?.chunk .where((room) => room.roomType != 'm.space') .toList(); @@ -43,224 +52,281 @@ class ChatListViewBody extends StatelessWidget { final subtitleColor = Theme.of(context).textTheme.bodyLarge!.color!.withAlpha(50); final filter = controller.searchController.text.toLowerCase(); - return PageTransitionSwitcher( - transitionBuilder: ( - Widget child, - Animation primaryAnimation, - Animation secondaryAnimation, - ) { - return SharedAxisTransition( - animation: primaryAnimation, - secondaryAnimation: secondaryAnimation, - transitionType: SharedAxisTransitionType.vertical, - fillColor: Theme.of(context).scaffoldBackgroundColor, - child: child, - ); - }, - child: StreamBuilder( - key: ValueKey( - client.userID.toString() + - controller.activeFilter.toString() + - controller.activeSpaceId.toString(), - ), - stream: client.onSync.stream - .where((s) => s.hasRoomUpdate) - .rateLimit(const Duration(seconds: 1)), - builder: (context, _) { - if (controller.activeFilter == ActiveFilter.spaces) { - return SpaceView( - controller, - scrollController: controller.scrollController, - key: Key(controller.activeSpaceId ?? 'Spaces'), - ); + return StreamBuilder( + key: ValueKey( + client.userID.toString(), + ), + stream: client.onSync.stream + .where((s) => s.hasRoomUpdate) + .rateLimit(const Duration(seconds: 1)), + builder: (context, _) { + final rooms = controller.filteredRooms; + + final spaces = rooms.where((r) => r.isSpace); + final spaceDelegateCandidates = {}; + for (final space in spaces) { + spaceDelegateCandidates[space.id] = space; + for (final spaceChild in space.spaceChildren) { + final roomId = spaceChild.roomId; + if (roomId == null) continue; + spaceDelegateCandidates[roomId] = space; } - final rooms = controller.filteredRooms; - return SafeArea( - child: CustomScrollView( - controller: controller.scrollController, - slivers: [ - ChatListHeader(controller: controller), - SliverList( - delegate: SliverChildListDelegate( - [ - if (controller.isSearchMode) ...[ - SearchTitle( - title: L10n.of(context)!.publicRooms, - icon: const Icon(Icons.explore_outlined), - ), - PublicRoomsHorizontalList(publicRooms: publicRooms), - SearchTitle( - title: L10n.of(context)!.publicSpaces, - icon: const Icon(Icons.workspaces_outlined), - ), - PublicRoomsHorizontalList(publicRooms: publicSpaces), - SearchTitle( - title: L10n.of(context)!.users, - icon: const Icon(Icons.group_outlined), - ), - AnimatedContainer( - clipBehavior: Clip.hardEdge, - decoration: const BoxDecoration(), - height: userSearchResult == null || - userSearchResult.results.isEmpty - ? 0 - : 106, - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - child: userSearchResult == null - ? null - : ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: userSearchResult.results.length, - itemBuilder: (context, i) => _SearchItem( - title: userSearchResult - .results[i].displayName ?? - userSearchResult - .results[i].userId.localpart ?? - L10n.of(context)!.unknownDevice, - avatar: - userSearchResult.results[i].avatarUrl, - onPressed: () => showAdaptiveBottomSheet( - context: context, - builder: (c) => UserBottomSheet( - profile: userSearchResult.results[i], - outerContext: context, - ), + } + final spaceDelegates = {}; + + return SafeArea( + child: CustomScrollView( + controller: controller.scrollController, + slivers: [ + ChatListHeader(controller: controller), + SliverList( + delegate: SliverChildListDelegate( + [ + if (controller.isSearchMode) ...[ + SearchTitle( + title: L10n.of(context)!.publicRooms, + icon: const Icon(Icons.explore_outlined), + ), + PublicRoomsHorizontalList(publicRooms: publicRooms), + SearchTitle( + title: L10n.of(context)!.publicSpaces, + icon: const Icon(Icons.workspaces_outlined), + ), + PublicRoomsHorizontalList(publicRooms: publicSpaces), + SearchTitle( + title: L10n.of(context)!.users, + icon: const Icon(Icons.group_outlined), + ), + AnimatedContainer( + clipBehavior: Clip.hardEdge, + decoration: const BoxDecoration(), + height: userSearchResult == null || + userSearchResult.results.isEmpty + ? 0 + : 106, + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + child: userSearchResult == null + ? null + : ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: userSearchResult.results.length, + itemBuilder: (context, i) => _SearchItem( + title: + userSearchResult.results[i].displayName ?? + userSearchResult + .results[i].userId.localpart ?? + L10n.of(context)!.unknownDevice, + avatar: userSearchResult.results[i].avatarUrl, + onPressed: () => showAdaptiveBottomSheet( + context: context, + builder: (c) => UserBottomSheet( + profile: userSearchResult.results[i], + outerContext: context, ), ), ), + ), + ), + ], + if (!controller.isSearchMode && AppConfig.showPresences) + GestureDetector( + onLongPress: () => controller.dismissStatusList(), + child: StatusMessageList( + onStatusEdit: controller.setStatus, ), - ], - if (!controller.isSearchMode && - controller.activeFilter != ActiveFilter.groups && - AppConfig.showPresences) - GestureDetector( - onLongPress: () => controller.dismissStatusList(), - child: StatusMessageList( - onStatusEdit: controller.setStatus, - ), + ), + const ConnectionStatusHeader(), + AnimatedContainer( + height: controller.isTorBrowser ? 64 : 0, + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + clipBehavior: Clip.hardEdge, + decoration: const BoxDecoration(), + child: Material( + color: Theme.of(context).colorScheme.surface, + child: ListTile( + leading: const Icon(Icons.vpn_key), + title: Text(L10n.of(context)!.dehydrateTor), + subtitle: Text(L10n.of(context)!.dehydrateTorLong), + trailing: const Icon(Icons.chevron_right_outlined), + onTap: controller.dehydrate, ), - const ConnectionStatusHeader(), - AnimatedContainer( - height: controller.isTorBrowser ? 64 : 0, - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - clipBehavior: Clip.hardEdge, - decoration: const BoxDecoration(), - child: Material( - color: Theme.of(context).colorScheme.surface, - child: ListTile( - leading: const Icon(Icons.vpn_key), - title: Text(L10n.of(context)!.dehydrateTor), - subtitle: Text(L10n.of(context)!.dehydrateTorLong), - trailing: const Icon(Icons.chevron_right_outlined), - onTap: controller.dehydrate, + ), + ), + if (client.rooms.isNotEmpty && !controller.isSearchMode) + SizedBox( + height: 44, + child: ListView( + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 6, ), + shrinkWrap: true, + scrollDirection: Axis.horizontal, + children: ActiveFilter.values + .map( + (filter) => Padding( + padding: + const EdgeInsets.symmetric(horizontal: 4), + child: InkWell( + borderRadius: BorderRadius.circular( + AppConfig.borderRadius, + ), + onTap: () => + controller.setActiveFilter(filter), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: filter == controller.activeFilter + ? Theme.of(context) + .colorScheme + .primary + : Theme.of(context) + .colorScheme + .secondaryContainer, + borderRadius: BorderRadius.circular( + AppConfig.borderRadius, + ), + ), + alignment: Alignment.center, + child: Text( + filter.toLocalizedString(context), + style: TextStyle( + fontWeight: + filter == controller.activeFilter + ? FontWeight.bold + : FontWeight.normal, + color: + filter == controller.activeFilter + ? Theme.of(context) + .colorScheme + .onPrimary + : Theme.of(context) + .colorScheme + .onSecondaryContainer, + ), + ), + ), + ), + ), + ) + .toList(), ), ), - if (controller.isSearchMode) - SearchTitle( - title: L10n.of(context)!.chats, - icon: const Icon(Icons.forum_outlined), - ), - if (client.prevBatch != null && - rooms.isEmpty && - !controller.isSearchMode) ...[ - Padding( - padding: const EdgeInsets.all(32.0), - child: Icon( - CupertinoIcons.chat_bubble_2, - size: 128, - color: - Theme.of(context).colorScheme.onInverseSurface, - ), + if (controller.isSearchMode) + SearchTitle( + title: L10n.of(context)!.chats, + icon: const Icon(Icons.forum_outlined), + ), + if (client.prevBatch != null && + rooms.isEmpty && + !controller.isSearchMode) ...[ + Padding( + padding: const EdgeInsets.all(32.0), + child: Icon( + CupertinoIcons.chat_bubble_2, + size: 128, + color: Theme.of(context).colorScheme.secondary, ), - ], + ), ], - ), + ], ), - if (client.prevBatch == null) - SliverList( - delegate: SliverChildBuilderDelegate( - (context, i) => Opacity( - opacity: (dummyChatCount - i) / dummyChatCount, - child: ListTile( - leading: CircleAvatar( - backgroundColor: titleColor, - child: CircularProgressIndicator( - strokeWidth: 1, - color: - Theme.of(context).textTheme.bodyLarge!.color, - ), + ), + if (client.prevBatch == null) + SliverList( + delegate: SliverChildBuilderDelegate( + (context, i) => Opacity( + opacity: (dummyChatCount - i) / dummyChatCount, + child: ListTile( + leading: CircleAvatar( + backgroundColor: titleColor, + child: CircularProgressIndicator( + strokeWidth: 1, + color: Theme.of(context).textTheme.bodyLarge!.color, ), - title: Row( - children: [ - Expanded( - child: Container( - height: 14, - decoration: BoxDecoration( - color: titleColor, - borderRadius: BorderRadius.circular(3), - ), - ), - ), - const SizedBox(width: 36), - Container( + ), + title: Row( + children: [ + Expanded( + child: Container( height: 14, - width: 14, decoration: BoxDecoration( - color: subtitleColor, - borderRadius: BorderRadius.circular(14), + color: titleColor, + borderRadius: BorderRadius.circular(3), ), ), - const SizedBox(width: 12), - Container( - height: 14, - width: 14, - decoration: BoxDecoration( - color: subtitleColor, - borderRadius: BorderRadius.circular(14), - ), + ), + const SizedBox(width: 36), + Container( + height: 14, + width: 14, + decoration: BoxDecoration( + color: subtitleColor, + borderRadius: BorderRadius.circular(14), ), - ], - ), - subtitle: Container( - decoration: BoxDecoration( - color: subtitleColor, - borderRadius: BorderRadius.circular(3), ), - height: 12, - margin: const EdgeInsets.only(right: 22), + const SizedBox(width: 12), + Container( + height: 14, + width: 14, + decoration: BoxDecoration( + color: subtitleColor, + borderRadius: BorderRadius.circular(14), + ), + ), + ], + ), + subtitle: Container( + decoration: BoxDecoration( + color: subtitleColor, + borderRadius: BorderRadius.circular(3), ), + height: 12, + margin: const EdgeInsets.only(right: 22), ), ), - childCount: dummyChatCount, ), + childCount: dummyChatCount, ), - if (client.prevBatch != null) - SliverList.builder( - itemCount: rooms.length, - itemBuilder: (BuildContext context, int i) { - return ChatListItem( - rooms[i], - key: Key('chat_list_item_${rooms[i].id}'), - filter: filter, - selected: - controller.selectedRoomIds.contains(rooms[i].id), - onTap: controller.selectMode == SelectMode.select - ? () => controller.toggleSelection(rooms[i].id) - : () => onChatTap(rooms[i], context), - onLongPress: () => - controller.toggleSelection(rooms[i].id), - activeChat: controller.activeChat == rooms[i].id, - ); - }, - ), - ], - ), - ); - }, - ), + ), + if (client.prevBatch != null) + SliverList.builder( + itemCount: rooms.length, + itemBuilder: (BuildContext context, int i) { + var room = rooms[i]; + if (controller.activeFilter != ActiveFilter.groups) { + final parent = room.isSpace + ? room + : spaceDelegateCandidates[room.id]; + if (parent != null) { + if (spaceDelegates.contains(parent.id)) { + return const SizedBox.shrink(); + } + spaceDelegates.add(parent.id); + room = parent; + } + } + + return ChatListItem( + room, + lastEventRoom: rooms[i], + key: Key('chat_list_item_${room.id}'), + filter: filter, + onTap: () => controller.onChatTap(room, context), + onLongPress: () => controller.chatContextAction(room), + activeChat: controller.activeChat == room.id, + ); + }, + ), + ], + ), + ); + }, ); } } diff --git a/lib/pages/chat_list/chat_list_header.dart b/lib/pages/chat_list/chat_list_header.dart index 5e76e780f7..d60cc9ba2d 100644 --- a/lib/pages/chat_list/chat_list_header.dart +++ b/lib/pages/chat_list/chat_list_header.dart @@ -43,88 +43,77 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget { L10n.of(context)!.share, key: const ValueKey(SelectMode.share), ) - : selectMode == SelectMode.select - ? Text( - controller.selectedRoomIds.length.toString(), - key: const ValueKey(SelectMode.select), - ) - : TextField( - controller: controller.searchController, - focusNode: controller.searchFocusNode, - textInputAction: TextInputAction.search, - onChanged: (text) => controller.onSearchEnter( - text, - globalSearch: globalSearch, - ), - decoration: InputDecoration( - fillColor: Theme.of(context).colorScheme.secondaryContainer, - border: OutlineInputBorder( - borderSide: BorderSide.none, - borderRadius: BorderRadius.circular(99), - ), - contentPadding: EdgeInsets.zero, - hintText: L10n.of(context)!.searchChatsRooms, - hintStyle: TextStyle( - color: Theme.of(context).colorScheme.onPrimaryContainer, - fontWeight: FontWeight.normal, - ), - floatingLabelBehavior: FloatingLabelBehavior.never, - prefixIcon: controller.isSearchMode - ? IconButton( - tooltip: L10n.of(context)!.cancel, - icon: const Icon(Icons.close_outlined), - onPressed: controller.cancelSearch, - color: Theme.of(context) - .colorScheme - .onPrimaryContainer, + : TextField( + controller: controller.searchController, + focusNode: controller.searchFocusNode, + textInputAction: TextInputAction.search, + onChanged: (text) => controller.onSearchEnter( + text, + globalSearch: globalSearch, + ), + decoration: InputDecoration( + fillColor: Theme.of(context).colorScheme.secondaryContainer, + border: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(99), + ), + contentPadding: EdgeInsets.zero, + hintText: L10n.of(context)!.searchChatsRooms, + hintStyle: TextStyle( + color: Theme.of(context).colorScheme.onPrimaryContainer, + fontWeight: FontWeight.normal, + ), + floatingLabelBehavior: FloatingLabelBehavior.never, + prefixIcon: controller.isSearchMode + ? IconButton( + tooltip: L10n.of(context)!.cancel, + icon: const Icon(Icons.close_outlined), + onPressed: controller.cancelSearch, + color: Theme.of(context).colorScheme.onPrimaryContainer, + ) + : IconButton( + onPressed: controller.startSearch, + icon: Icon( + Icons.search_outlined, + color: + Theme.of(context).colorScheme.onPrimaryContainer, + ), + ), + suffixIcon: controller.isSearchMode && globalSearch + ? controller.isSearching + ? const Padding( + padding: EdgeInsets.symmetric( + vertical: 10.0, + horizontal: 12, + ), + child: SizedBox.square( + dimension: 24, + child: CircularProgressIndicator.adaptive( + strokeWidth: 2, + ), + ), ) - : IconButton( - onPressed: controller.startSearch, - icon: Icon( - Icons.search_outlined, - color: Theme.of(context) - .colorScheme - .onPrimaryContainer, + : TextButton.icon( + onPressed: controller.setServer, + style: TextButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(99), + ), + textStyle: const TextStyle(fontSize: 12), ), - ), - suffixIcon: controller.isSearchMode && globalSearch - ? controller.isSearching - ? const Padding( - padding: EdgeInsets.symmetric( - vertical: 10.0, - horizontal: 12, - ), - child: SizedBox.square( - dimension: 24, - child: CircularProgressIndicator.adaptive( - strokeWidth: 2, - ), - ), - ) - : TextButton.icon( - onPressed: controller.setServer, - style: TextButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(99), - ), - textStyle: const TextStyle(fontSize: 12), - ), - icon: const Icon(Icons.edit_outlined, size: 16), - label: Text( - controller.searchServer ?? - Matrix.of(context) - .client - .homeserver! - .host, - maxLines: 2, - ), - ) - : SizedBox( - width: 0, - child: ClientChooserButton(controller), - ), - ), - ), + icon: const Icon(Icons.edit_outlined, size: 16), + label: Text( + controller.searchServer ?? + Matrix.of(context).client.homeserver!.host, + maxLines: 2, + ), + ) + : SizedBox( + width: 0, + child: ClientChooserButton(controller), + ), + ), + ), actions: selectMode == SelectMode.share ? [ Padding( @@ -135,48 +124,7 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget { child: ClientChooserButton(controller), ), ] - : selectMode == SelectMode.select - ? [ - if (controller.spaces.isNotEmpty) - IconButton( - tooltip: L10n.of(context)!.addToSpace, - icon: const Icon(Icons.workspaces_outlined), - onPressed: controller.addToSpace, - ), - IconButton( - tooltip: L10n.of(context)!.toggleUnread, - icon: Icon( - controller.anySelectedRoomNotMarkedUnread - ? Icons.mark_chat_unread_outlined - : Icons.mark_chat_read_outlined, - ), - onPressed: controller.toggleUnread, - ), - IconButton( - tooltip: L10n.of(context)!.toggleFavorite, - icon: Icon( - controller.anySelectedRoomNotFavorite - ? Icons.push_pin - : Icons.push_pin_outlined, - ), - onPressed: controller.toggleFavouriteRoom, - ), - IconButton( - icon: Icon( - controller.anySelectedRoomNotMuted - ? Icons.notifications_off_outlined - : Icons.notifications_outlined, - ), - tooltip: L10n.of(context)!.toggleMuted, - onPressed: controller.toggleMuted, - ), - IconButton( - icon: const Icon(Icons.delete_outlined), - tooltip: L10n.of(context)!.archive, - onPressed: controller.archiveAction, - ), - ] - : null, + : null, ); } diff --git a/lib/pages/chat_list/chat_list_item.dart b/lib/pages/chat_list/chat_list_item.dart index 4ac02e62b6..7bf5f6b520 100644 --- a/lib/pages/chat_list/chat_list_item.dart +++ b/lib/pages/chat_list/chat_list_item.dart @@ -17,8 +17,8 @@ enum ArchivedRoomAction { delete, rejoin } class ChatListItem extends StatelessWidget { final Room room; + final Room? lastEventRoom; final bool activeChat; - final bool selected; final void Function()? onLongPress; final void Function()? onForget; final void Function() onTap; @@ -27,11 +27,11 @@ class ChatListItem extends StatelessWidget { const ChatListItem( this.room, { this.activeChat = false, - this.selected = false, required this.onTap, this.onLongPress, this.onForget, this.filter, + this.lastEventRoom, super.key, }); @@ -64,24 +64,23 @@ class ChatListItem extends StatelessWidget { @override Widget build(BuildContext context) { final isMuted = room.pushRuleState != PushRuleState.notify; - final typingText = room.getLocalizedTypingText(context); - final lastEvent = room.lastEvent; + final lastEventRoom = this.lastEventRoom ?? room; + final typingText = lastEventRoom.getLocalizedTypingText(context); + final lastEvent = lastEventRoom.lastEvent; final ownMessage = lastEvent?.senderId == room.client.userID; - final unread = room.isUnread || room.membership == Membership.invite; + final unread = + lastEventRoom.isUnread || lastEventRoom.membership == Membership.invite; final theme = Theme.of(context); final directChatMatrixId = room.directChatMatrixID; final isDirectChat = directChatMatrixId != null; - final unreadBubbleSize = unread || room.hasNewMessages - ? room.notificationCount > 0 + final unreadBubbleSize = unread || lastEventRoom.hasNewMessages + ? lastEventRoom.notificationCount > 0 ? 20.0 : 14.0 : 0.0; - final hasNotifications = room.notificationCount > 0; - final backgroundColor = selected - ? theme.colorScheme.primaryContainer - : activeChat - ? theme.colorScheme.secondaryContainer - : null; + final hasNotifications = lastEventRoom.notificationCount > 0; + final backgroundColor = + activeChat ? theme.colorScheme.secondaryContainer : null; final displayname = room.getLocalizedDisplayname( MatrixLocals(L10n.of(context)!), ); @@ -119,6 +118,9 @@ class ChatListItem extends StatelessWidget { curve: FluffyThemes.animationCurve, scale: hovered ? 1.1 : 1.0, child: Avatar( + borderRadius: room.isSpace + ? BorderRadius.circular(AppConfig.borderRadius / 3) + : null, mxContent: room.avatar, name: displayname, presenceUserId: directChatMatrixId, @@ -133,14 +135,12 @@ class ChatListItem extends StatelessWidget { child: AnimatedScale( duration: FluffyThemes.animationDuration, curve: FluffyThemes.animationCurve, - scale: (hovered || selected) ? 1.0 : 0.0, + scale: (hovered) ? 1.0 : 0.0, child: Material( color: backgroundColor, borderRadius: BorderRadius.circular(16), - child: Icon( - selected - ? Icons.check_circle - : Icons.check_circle_outlined, + child: const Icon( + Icons.check_circle_outlined, size: 18, ), ), @@ -180,7 +180,9 @@ class ChatListItem extends StatelessWidget { color: theme.colorScheme.primary, ), ), - if (lastEvent != null && room.membership != Membership.invite) + if (!room.isSpace && + lastEvent != null && + room.membership != Membership.invite) Padding( padding: const EdgeInsets.only(left: 4.0), child: Text( @@ -193,11 +195,30 @@ class ChatListItem extends StatelessWidget { ), ), ), + if (room.isSpace) + const Icon( + Icons.arrow_circle_right_outlined, + size: 18, + ), ], ), subtitle: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ + if (room.isSpace) ...[ + room.id != lastEventRoom.id && + lastEventRoom.isUnreadOrInvited + ? Avatar( + mxContent: lastEventRoom.avatar, + name: lastEventRoom.name, + size: 18, + ) + : const Icon( + Icons.workspaces_outlined, + size: 18, + ), + const SizedBox(width: 4), + ], if (typingText.isEmpty && ownMessage && room.lastEvent!.status.isSending) ...[ @@ -222,62 +243,71 @@ class ChatListItem extends StatelessWidget { ), ), Expanded( - child: typingText.isNotEmpty + child: room.isSpace && !lastEventRoom.isUnreadOrInvited ? Text( - typingText, - style: TextStyle( - color: theme.colorScheme.primary, + L10n.of(context)!.countChatsAndCountParticipants( + room.spaceChildren.length.toString(), + (room.summary.mJoinedMemberCount ?? 1).toString(), ), - maxLines: 1, - softWrap: false, ) - : FutureBuilder( - key: ValueKey( - '${lastEvent?.eventId}_${lastEvent?.type}', - ), - future: needLastEventSender - ? lastEvent.calcLocalizedBody( - MatrixLocals(L10n.of(context)!), - hideReply: true, - hideEdit: true, - plaintextBody: true, - removeMarkdown: true, - withSenderNamePrefix: !isDirectChat || - directChatMatrixId != - room.lastEvent?.senderId, - ) - : null, - initialData: lastEvent?.calcLocalizedBodyFallback( - MatrixLocals(L10n.of(context)!), - hideReply: true, - hideEdit: true, - plaintextBody: true, - removeMarkdown: true, - withSenderNamePrefix: !isDirectChat || - directChatMatrixId != - room.lastEvent?.senderId, - ), - builder: (context, snapshot) => Text( - room.membership == Membership.invite - ? isDirectChat - ? L10n.of(context)!.invitePrivateChat - : L10n.of(context)!.inviteGroupChat - : snapshot.data ?? - L10n.of(context)!.emptyChat, - softWrap: false, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontWeight: unread || room.hasNewMessages - ? FontWeight.bold - : null, - color: theme.colorScheme.onSurfaceVariant, - decoration: room.lastEvent?.redacted == true - ? TextDecoration.lineThrough + : typingText.isNotEmpty + ? Text( + typingText, + style: TextStyle( + color: theme.colorScheme.primary, + ), + maxLines: 1, + softWrap: false, + ) + : FutureBuilder( + key: ValueKey( + '${lastEvent?.eventId}_${lastEvent?.type}', + ), + future: needLastEventSender + ? lastEvent.calcLocalizedBody( + MatrixLocals(L10n.of(context)!), + hideReply: true, + hideEdit: true, + plaintextBody: true, + removeMarkdown: true, + withSenderNamePrefix: (!isDirectChat || + directChatMatrixId != + room.lastEvent?.senderId), + ) : null, + initialData: + lastEvent?.calcLocalizedBodyFallback( + MatrixLocals(L10n.of(context)!), + hideReply: true, + hideEdit: true, + plaintextBody: true, + removeMarkdown: true, + withSenderNamePrefix: (!isDirectChat || + directChatMatrixId != + room.lastEvent?.senderId), + ), + builder: (context, snapshot) => Text( + room.membership == Membership.invite + ? isDirectChat + ? L10n.of(context)!.invitePrivateChat + : L10n.of(context)!.inviteGroupChat + : snapshot.data ?? + L10n.of(context)!.emptyChat, + softWrap: false, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontWeight: + unread || lastEventRoom.hasNewMessages + ? FontWeight.bold + : null, + color: theme.colorScheme.onSurfaceVariant, + decoration: room.lastEvent?.redacted == true + ? TextDecoration.lineThrough + : null, + ), + ), ), - ), - ), ), const SizedBox(width: 8), AnimatedContainer( @@ -288,7 +318,9 @@ class ChatListItem extends StatelessWidget { width: !hasNotifications && !unread && !room.hasNewMessages ? 0 : (unreadBubbleSize - 9) * - room.notificationCount.toString().length + + lastEventRoom.notificationCount + .toString() + .length + 9, decoration: BoxDecoration( color: room.highlightCount > 0 || @@ -303,7 +335,7 @@ class ChatListItem extends StatelessWidget { child: Center( child: hasNotifications ? Text( - room.notificationCount.toString(), + lastEventRoom.notificationCount.toString(), style: TextStyle( color: room.highlightCount > 0 ? Colors.white diff --git a/lib/pages/chat_list/chat_list_view.dart b/lib/pages/chat_list/chat_list_view.dart index 160db9b86b..7984646931 100644 --- a/lib/pages/chat_list/chat_list_view.dart +++ b/lib/pages/chat_list/chat_list_view.dart @@ -1,87 +1,21 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:badges/badges.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:go_router/go_router.dart'; import 'package:keyboard_shortcuts/keyboard_shortcuts.dart'; -import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat_list/chat_list.dart'; -import 'package:fluffychat/pages/chat_list/navi_rail_item.dart'; -import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; -import 'package:fluffychat/widgets/avatar.dart'; -import 'package:fluffychat/widgets/unread_rooms_badge.dart'; import '../../widgets/matrix.dart'; import 'chat_list_body.dart'; -import 'start_chat_fab.dart'; class ChatListView extends StatelessWidget { final ChatListController controller; const ChatListView(this.controller, {super.key}); - List getNavigationDestinations(BuildContext context) { - final badgePosition = BadgePosition.topEnd(top: -12, end: -8); - return [ - if (AppConfig.separateChatTypes) ...[ - NavigationDestination( - icon: UnreadRoomsBadge( - badgePosition: badgePosition, - filter: - controller.getRoomFilterByActiveFilter(ActiveFilter.messages), - child: const Icon(Icons.chat_outlined), - ), - selectedIcon: UnreadRoomsBadge( - badgePosition: badgePosition, - filter: - controller.getRoomFilterByActiveFilter(ActiveFilter.messages), - child: const Icon(Icons.chat), - ), - label: L10n.of(context)!.messages, - ), - NavigationDestination( - icon: UnreadRoomsBadge( - badgePosition: badgePosition, - filter: controller.getRoomFilterByActiveFilter(ActiveFilter.groups), - child: const Icon(Icons.group_outlined), - ), - selectedIcon: UnreadRoomsBadge( - badgePosition: badgePosition, - filter: controller.getRoomFilterByActiveFilter(ActiveFilter.groups), - child: const Icon(Icons.group), - ), - label: L10n.of(context)!.groups, - ), - ] else - NavigationDestination( - icon: UnreadRoomsBadge( - badgePosition: badgePosition, - filter: - controller.getRoomFilterByActiveFilter(ActiveFilter.allChats), - child: const Icon(Icons.chat_outlined), - ), - selectedIcon: UnreadRoomsBadge( - badgePosition: badgePosition, - filter: - controller.getRoomFilterByActiveFilter(ActiveFilter.allChats), - child: const Icon(Icons.chat), - ), - label: L10n.of(context)!.chats, - ), - if (controller.spaces.isNotEmpty) - const NavigationDestination( - icon: Icon(Icons.workspaces_outlined), - selectedIcon: Icon(Icons.workspaces), - label: 'Spaces', - ), - ]; - } - @override Widget build(BuildContext context) { - final client = Matrix.of(context).client; return StreamBuilder( stream: Matrix.of(context).onShareContentChanged.stream, builder: (_, __) { @@ -89,10 +23,7 @@ class ChatListView extends StatelessWidget { return PopScope( canPop: controller.selectMode == SelectMode.normal && !controller.isSearchMode && - controller.activeFilter == - (AppConfig.separateChatTypes - ? ActiveFilter.messages - : ActiveFilter.allChats), + controller.activeFilter == ActiveFilter.allChats, onPopInvoked: (pop) async { if (pop) return; final selMode = controller.selectMode; @@ -104,122 +35,33 @@ class ChatListView extends StatelessWidget { controller.cancelAction(); return; } - if (controller.activeFilter != - (AppConfig.separateChatTypes - ? ActiveFilter.messages - : ActiveFilter.allChats)) { - controller - .onDestinationSelected(AppConfig.separateChatTypes ? 1 : 0); - return; - } }, - child: Row( - children: [ - if (FluffyThemes.isColumnMode(context) && - controller.widget.displayNavigationRail) ...[ - Builder( - builder: (context) { - final allSpaces = - client.rooms.where((room) => room.isSpace); - final rootSpaces = allSpaces - .where( - (space) => !allSpaces.any( - (parentSpace) => parentSpace.spaceChildren - .any((child) => child.roomId == space.id), - ), - ) - .toList(); - final destinations = getNavigationDestinations(context); - - return SizedBox( - width: FluffyThemes.navRailWidth, - child: ListView.builder( - scrollDirection: Axis.vertical, - itemCount: rootSpaces.length + destinations.length, - itemBuilder: (context, i) { - if (i < destinations.length) { - return NaviRailItem( - isSelected: i == controller.selectedIndex, - onTap: () => controller.onDestinationSelected(i), - icon: destinations[i].icon, - selectedIcon: destinations[i].selectedIcon, - toolTip: destinations[i].label, - ); - } - i -= destinations.length; - final isSelected = - controller.activeFilter == ActiveFilter.spaces && - rootSpaces[i].id == controller.activeSpaceId; - return NaviRailItem( - toolTip: rootSpaces[i].getLocalizedDisplayname( - MatrixLocals(L10n.of(context)!), - ), - isSelected: isSelected, - onTap: () => - controller.setActiveSpace(rootSpaces[i].id), - icon: Avatar( - mxContent: rootSpaces[i].avatar, - name: rootSpaces[i].getLocalizedDisplayname( - MatrixLocals(L10n.of(context)!), - ), - size: 32, + child: GestureDetector( + onTap: FocusManager.instance.primaryFocus?.unfocus, + excludeFromSemantics: true, + behavior: HitTestBehavior.translucent, + child: Scaffold( + body: ChatListViewBody(controller), + floatingActionButton: KeyBoardShortcuts( + keysToPress: { + LogicalKeyboardKey.controlLeft, + LogicalKeyboardKey.keyN, + }, + onKeysPressed: () => context.go('/rooms/newprivatechat'), + helpLabel: L10n.of(context)!.newChat, + child: + selectMode == SelectMode.normal && !controller.isSearchMode + ? FloatingActionButton.extended( + onPressed: controller.addChatAction, + icon: const Icon(Icons.add_outlined), + label: Text( + L10n.of(context)!.chat, + overflow: TextOverflow.fade, ), - ); - }, - ), - ); - }, - ), - Container( - color: Theme.of(context).dividerColor, - width: 1, - ), - ], - Expanded( - child: GestureDetector( - onTap: FocusManager.instance.primaryFocus?.unfocus, - excludeFromSemantics: true, - behavior: HitTestBehavior.translucent, - child: Scaffold( - body: ChatListViewBody(controller), - bottomNavigationBar: controller.displayNavigationBar - ? NavigationBar( - elevation: 4, - labelBehavior: - NavigationDestinationLabelBehavior.alwaysShow, - shadowColor: - Theme.of(context).colorScheme.onSurface, - backgroundColor: - Theme.of(context).colorScheme.surface, - surfaceTintColor: - Theme.of(context).colorScheme.surface, - selectedIndex: controller.selectedIndex, - onDestinationSelected: - controller.onDestinationSelected, - destinations: getNavigationDestinations(context), ) - : null, - floatingActionButton: KeyBoardShortcuts( - keysToPress: { - LogicalKeyboardKey.controlLeft, - LogicalKeyboardKey.keyN, - }, - onKeysPressed: () => context.go('/rooms/newprivatechat'), - helpLabel: L10n.of(context)!.newChat, - child: selectMode == SelectMode.normal && - !controller.isSearchMode - ? StartChatFloatingActionButton( - activeFilter: controller.activeFilter, - roomsIsEmpty: false, - scrolledToTop: controller.scrolledToTop, - createNewSpace: controller.createNewSpace, - ) - : const SizedBox.shrink(), - ), - ), - ), + : const SizedBox.shrink(), ), - ], + ), ), ); }, diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index a984b43779..6459de1e8c 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -5,26 +5,32 @@ import 'package:collection/collection.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:go_router/go_router.dart'; -import 'package:matrix/matrix.dart' as sdk; import 'package:matrix/matrix.dart'; -import 'package:fluffychat/pages/chat_list/chat_list.dart'; +import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/chat_list/chat_list_item.dart'; import 'package:fluffychat/pages/chat_list/search_title.dart'; -import 'package:fluffychat/pages/chat_list/utils/on_chat_tap.dart'; -import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; +import 'package:fluffychat/utils/localized_exception_extension.dart'; +import 'package:fluffychat/utils/stream_extension.dart'; import 'package:fluffychat/widgets/avatar.dart'; -import '../../utils/localized_exception_extension.dart'; -import '../../widgets/matrix.dart'; -import 'chat_list_header.dart'; +import 'package:fluffychat/widgets/matrix.dart'; class SpaceView extends StatefulWidget { - final ChatListController controller; - final ScrollController scrollController; - const SpaceView( - this.controller, { + final String spaceId; + final void Function() onBack; + final void Function(String spaceId) toParentSpace; + final void Function(Room room) onChatTab; + final void Function(Room room) onChatContext; + final String? activeChat; + + const SpaceView({ + required this.spaceId, + required this.onBack, + required this.onChatTab, + required this.activeChat, + required this.toParentSpace, + required this.onChatContext, super.key, - required this.scrollController, }); @override @@ -32,543 +38,449 @@ class SpaceView extends StatefulWidget { } class _SpaceViewState extends State { - static final Map _lastResponse = {}; - - String? prevBatch; - Object? error; - bool loading = false; + final List _discoveredChildren = []; + final TextEditingController _filterController = TextEditingController(); + String? _nextBatch; + bool _noMoreRooms = false; + bool _isLoading = false; @override void initState() { - loadHierarchy(); + _loadHierarchy(); super.initState(); } - void _refresh() { - _lastResponse.remove(widget.controller.activeSpaceId); - loadHierarchy(); - } - - Future loadHierarchy([String? prevBatch]) async { - final activeSpaceId = widget.controller.activeSpaceId; - if (activeSpaceId == null) return null; - final client = Matrix.of(context).client; - - final activeSpace = client.getRoomById(activeSpaceId); - await activeSpace?.postLoad(); + void _loadHierarchy() async { + final room = Matrix.of(context).client.getRoomById(widget.spaceId); + if (room == null) return; setState(() { - error = null; - loading = true; + _isLoading = true; }); try { - final response = await client.getSpaceHierarchy( - activeSpaceId, - maxDepth: 1, - from: prevBatch, + final hierarchy = await room.client.getSpaceHierarchy( + widget.spaceId, + suggestedOnly: false, + maxDepth: 2, + from: _nextBatch, ); - - if (prevBatch != null) { - response.rooms.insertAll(0, _lastResponse[activeSpaceId]?.rooms ?? []); - } - setState(() { - _lastResponse[activeSpaceId] = response; - }); - return _lastResponse[activeSpaceId]!; - } catch (e) { + if (!mounted) return; setState(() { - error = e; + _nextBatch = hierarchy.nextBatch; + if (hierarchy.nextBatch == null) { + _noMoreRooms = true; + } + _discoveredChildren.addAll( + hierarchy.rooms + .where((c) => room.client.getRoomById(c.roomId) == null), + ); + _isLoading = false; }); - rethrow; - } finally { + } catch (e, s) { + Logs().w('Unable to load hierarchy', e, s); + if (!mounted) return; + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text(e.toLocalizedString(context)))); setState(() { - loading = false; + _isLoading = false; }); } } - void _onJoinSpaceChild(SpaceRoomsChunk spaceChild) async { + void _joinChildRoom(SpaceRoomsChunk item) async { final client = Matrix.of(context).client; - final space = client.getRoomById(widget.controller.activeSpaceId!); - if (client.getRoomById(spaceChild.roomId) == null) { - final result = await showFutureLoadingDialog( - context: context, - future: () async { - await client.joinRoom( - spaceChild.roomId, - serverName: space?.spaceChildren - .firstWhereOrNull( - (child) => child.roomId == spaceChild.roomId, - ) - ?.via, - ); - if (client.getRoomById(spaceChild.roomId) == null) { - // Wait for room actually appears in sync - await client.waitForRoomInSync(spaceChild.roomId, join: true); - } - }, - ); - if (result.error != null) return; - _refresh(); - } - if (spaceChild.roomType == 'm.space') { - if (spaceChild.roomId == widget.controller.activeSpaceId) { - context.go('/rooms/${spaceChild.roomId}'); - } else { - widget.controller.setActiveSpace(spaceChild.roomId); - } - return; - } - context.go('/rooms/${spaceChild.roomId}'); - } + final space = client.getRoomById(widget.spaceId); - void _onSpaceChildContextMenu([ - SpaceRoomsChunk? spaceChild, - Room? room, - ]) async { - final client = Matrix.of(context).client; - final activeSpaceId = widget.controller.activeSpaceId; - final activeSpace = - activeSpaceId == null ? null : client.getRoomById(activeSpaceId); - final action = await showModalActionSheet( + final consent = await showOkCancelAlertDialog( context: context, - title: spaceChild?.name ?? - room?.getLocalizedDisplayname( - MatrixLocals(L10n.of(context)!), - ), - message: spaceChild?.topic ?? room?.topic, - actions: [ - if (room == null) - SheetAction( - key: SpaceChildContextAction.join, - label: L10n.of(context)!.joinRoom, - icon: Icons.send_outlined, - ), - if (spaceChild != null && - (activeSpace?.canChangeStateEvent(EventTypes.SpaceChild) ?? false)) - SheetAction( - key: SpaceChildContextAction.removeFromSpace, - label: L10n.of(context)!.removeFromSpace, - icon: Icons.delete_sweep_outlined, - ), - if (room != null) - SheetAction( - key: SpaceChildContextAction.leave, - label: L10n.of(context)!.leave, - icon: Icons.delete_outlined, - isDestructiveAction: true, - ), - ], + title: item.name ?? item.canonicalAlias ?? L10n.of(context)!.emptyChat, + message: item.topic, + okLabel: L10n.of(context)!.joinRoom, + cancelLabel: L10n.of(context)!.cancel, + ); + if (consent != OkCancelResult.ok) return; + if (!mounted) return; + + await showFutureLoadingDialog( + context: context, + future: () async { + await client.joinRoom( + item.roomId, + serverName: space?.spaceChildren + .firstWhereOrNull( + (child) => child.roomId == item.roomId, + ) + ?.via, + ); + if (client.getRoomById(item.roomId) == null) { + // Wait for room actually appears in sync + await client.waitForRoomInSync(item.roomId, join: true); + } + }, ); - if (action == null) return; + if (!mounted) return; + + setState(() { + _discoveredChildren.remove(item); + }); + } + + void _onSpaceAction(SpaceActions action) async { + final space = Matrix.of(context).client.getRoomById(widget.spaceId); switch (action) { - case SpaceChildContextAction.join: - _onJoinSpaceChild(spaceChild!); + case SpaceActions.settings: + await space?.postLoad(); + context.push('/rooms/${widget.spaceId}/details'); break; - case SpaceChildContextAction.leave: - await showFutureLoadingDialog( + case SpaceActions.invite: + await space?.postLoad(); + context.push('/rooms/${widget.spaceId}/invite'); + break; + case SpaceActions.leave: + final confirmed = await showOkCancelAlertDialog( + useRootNavigator: false, context: context, - future: room!.leave, + title: L10n.of(context)!.areYouSure, + okLabel: L10n.of(context)!.ok, + cancelLabel: L10n.of(context)!.cancel, + message: L10n.of(context)!.archiveRoomDescription, ); - break; - case SpaceChildContextAction.removeFromSpace: - await showFutureLoadingDialog( + if (!mounted) return; + if (confirmed != OkCancelResult.ok) return; + + final success = await showFutureLoadingDialog( context: context, - future: () => activeSpace!.removeSpaceChild(spaceChild!.roomId), + future: () async => await space?.leave(), ); - break; + if (!mounted) return; + if (success.error != null) return; + widget.onBack(); } } - void _addChatOrSubSpace() async { - final roomType = await showConfirmationDialog( - context: context, - title: L10n.of(context)!.addChatOrSubSpace, - actions: [ - AlertDialogAction( - key: AddRoomType.subspace, - label: L10n.of(context)!.createNewSpace, - ), - AlertDialogAction( - key: AddRoomType.chat, - label: L10n.of(context)!.createGroup, - ), - ], - ); - if (roomType == null) return; - - final names = await showTextInputDialog( - context: context, - title: roomType == AddRoomType.subspace - ? L10n.of(context)!.createNewSpace - : L10n.of(context)!.createGroup, - textFields: [ - DialogTextField( - hintText: roomType == AddRoomType.subspace - ? L10n.of(context)!.spaceName - : L10n.of(context)!.groupName, - minLines: 1, - maxLines: 1, - maxLength: 64, - validator: (text) { - if (text == null || text.isEmpty) { - return L10n.of(context)!.pleaseChoose; - } - return null; - }, - ), - DialogTextField( - hintText: L10n.of(context)!.chatDescription, - minLines: 4, - maxLines: 8, - maxLength: 255, - ), - ], - okLabel: L10n.of(context)!.create, - cancelLabel: L10n.of(context)!.cancel, - ); - if (names == null) return; - final client = Matrix.of(context).client; - final result = await showFutureLoadingDialog( - context: context, - future: () async { - late final String roomId; - final activeSpace = client.getRoomById( - widget.controller.activeSpaceId!, - )!; - - if (roomType == AddRoomType.subspace) { - roomId = await client.createSpace( - name: names.first, - topic: names.last.isEmpty ? null : names.last, - visibility: activeSpace.joinRules == JoinRules.public - ? sdk.Visibility.public - : sdk.Visibility.private, - ); - } else { - roomId = await client.createGroupChat( - groupName: names.first, - initialState: names.length > 1 && names.last.isNotEmpty - ? [ - sdk.StateEvent( - type: sdk.EventTypes.RoomTopic, - content: {'topic': names.last}, - ), - ] - : null, - ); - } - await activeSpace.setSpaceChild(roomId); - }, - ); - if (result.error != null) return; - _refresh(); - } - @override Widget build(BuildContext context) { - final client = Matrix.of(context).client; - final activeSpaceId = widget.controller.activeSpaceId; - final activeSpace = activeSpaceId == null - ? null - : client.getRoomById( - activeSpaceId, - ); - final allSpaces = client.rooms.where((room) => room.isSpace); - if (activeSpaceId == null) { - final rootSpaces = allSpaces - .where( - (space) => - !allSpaces.any( - (parentSpace) => parentSpace.spaceChildren - .any((child) => child.roomId == space.id), - ) && - space - .getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)) - .toLowerCase() - .contains( - widget.controller.searchController.text.toLowerCase(), - ), - ) - .toList(); - - return SafeArea( - child: CustomScrollView( - controller: widget.scrollController, - slivers: [ - ChatListHeader(controller: widget.controller), - SliverList( - delegate: SliverChildBuilderDelegate( - (context, i) { - final rootSpace = rootSpaces[i]; - final displayname = rootSpace.getLocalizedDisplayname( - MatrixLocals(L10n.of(context)!), - ); - return Material( - color: Theme.of(context).colorScheme.surface, - child: ListTile( - leading: Avatar( - mxContent: rootSpace.avatar, - name: displayname, - ), - title: Text( - displayname, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - subtitle: Text( - L10n.of(context)!.numChats( - rootSpace.spaceChildren.length.toString(), - ), - ), - onTap: () => - widget.controller.setActiveSpace(rootSpace.id), - onLongPress: () => - _onSpaceChildContextMenu(null, rootSpace), - trailing: const Icon(Icons.chevron_right_outlined), - ), - ); - }, - childCount: rootSpaces.length, - ), - ), - ], + final room = Matrix.of(context).client.getRoomById(widget.spaceId); + final displayname = + room?.getLocalizedDisplayname() ?? L10n.of(context)!.nothingFound; + return Scaffold( + appBar: AppBar( + leading: Center( + child: CloseButton( + onPressed: widget.onBack, + ), ), - ); - } - - final parentSpace = allSpaces.firstWhereOrNull( - (space) => - space.spaceChildren.any((child) => child.roomId == activeSpaceId), - ); - return PopScope( - canPop: parentSpace == null, - onPopInvoked: (pop) async { - if (pop) return; - if (parentSpace != null) { - widget.controller.setActiveSpace(parentSpace.id); - } - }, - child: SafeArea( - child: CustomScrollView( - controller: widget.scrollController, - slivers: [ - ChatListHeader(controller: widget.controller, globalSearch: false), - SliverAppBar( - automaticallyImplyLeading: false, - primary: false, - titleSpacing: 0, - title: ListTile( - leading: BackButton( - onPressed: () => - widget.controller.setActiveSpace(parentSpace?.id), + titleSpacing: 0, + title: ListTile( + contentPadding: EdgeInsets.zero, + leading: Avatar( + mxContent: room?.avatar, + name: displayname, + borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), + ), + title: Text( + displayname, + 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, ), - title: Text( - parentSpace == null - ? L10n.of(context)!.allSpaces - : parentSpace.getLocalizedDisplayname( - MatrixLocals(L10n.of(context)!), - ), + ), + actions: [ + PopupMenuButton( + onSelected: _onSpaceAction, + itemBuilder: (context) => [ + PopupMenuItem( + value: SpaceActions.settings, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.settings_outlined), + const SizedBox(width: 12), + Text(L10n.of(context)!.settings), + ], ), - trailing: IconButton( - icon: loading - ? const CircularProgressIndicator.adaptive(strokeWidth: 2) - : const Icon(Icons.refresh_outlined), - onPressed: loading ? null : _refresh, + ), + PopupMenuItem( + value: SpaceActions.invite, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.person_add_outlined), + const SizedBox(width: 12), + Text(L10n.of(context)!.invite), + ], ), ), - ), - Builder( - builder: (context) { - final response = _lastResponse[activeSpaceId]; - final error = this.error; - if (error != null) { - return SliverFillRemaining( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.all(16.0), - child: Text(error.toLocalizedString(context)), - ), - IconButton( - onPressed: _refresh, - icon: const Icon(Icons.refresh_outlined), + PopupMenuItem( + value: SpaceActions.leave, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.delete_outlined), + const SizedBox(width: 12), + Text(L10n.of(context)!.leave), + ], + ), + ), + ], + ), + ], + ), + body: room == null + ? const Center( + child: Icon( + Icons.search_outlined, + size: 80, + ), + ) + : StreamBuilder( + stream: room.client.onSync.stream + .where((s) => s.hasRoomUpdate) + .rateLimit(const Duration(seconds: 1)), + builder: (context, snapshot) { + final joinedRooms = room.spaceChildren + .map((child) { + final roomId = child.roomId; + if (roomId == null) return null; + return room.client.getRoomById(roomId); + }) + .whereType() + .where((room) => room.membership != Membership.leave) + .toList(); + + // Sort rooms by last activity + joinedRooms.sort( + (b, a) => (a.lastEvent?.originServerTs ?? + DateTime.fromMillisecondsSinceEpoch(0)) + .compareTo( + b.lastEvent?.originServerTs ?? + DateTime.fromMillisecondsSinceEpoch(0), + ), + ); + + final joinedParents = room.spaceParents + .map((parent) { + final roomId = parent.roomId; + if (roomId == null) return null; + return room.client.getRoomById(roomId); + }) + .whereType() + .toList(); + final filter = _filterController.text.trim().toLowerCase(); + return CustomScrollView( + slivers: [ + SliverAppBar( + floating: true, + toolbarHeight: 72, + scrolledUnderElevation: 0, + backgroundColor: Colors.transparent, + automaticallyImplyLeading: false, + title: TextField( + controller: _filterController, + onChanged: (_) => setState(() {}), + textInputAction: TextInputAction.search, + decoration: InputDecoration( + fillColor: + Theme.of(context).colorScheme.secondaryContainer, + border: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(99), + ), + contentPadding: EdgeInsets.zero, + hintText: L10n.of(context)!.search, + hintStyle: TextStyle( + color: Theme.of(context) + .colorScheme + .onPrimaryContainer, + fontWeight: FontWeight.normal, + ), + floatingLabelBehavior: FloatingLabelBehavior.never, + prefixIcon: IconButton( + onPressed: () {}, + icon: Icon( + Icons.search_outlined, + color: Theme.of(context) + .colorScheme + .onPrimaryContainer, + ), + ), ), - ], - ), - ); - } - if (response == null) { - return SliverFillRemaining( - child: Center( - child: Text(L10n.of(context)!.loadingPleaseWait), + ), ), - ); - } - final spaceChildren = response.rooms; - final canLoadMore = response.nextBatch != null; - return SliverList( - delegate: SliverChildBuilderDelegate( - (context, i) { - if (canLoadMore && i == spaceChildren.length) { + SliverList.builder( + itemCount: joinedParents.length, + itemBuilder: (context, i) { + final displayname = + joinedParents[i].getLocalizedDisplayname(); return Padding( - padding: const EdgeInsets.all(16.0), - child: OutlinedButton.icon( - label: loading - ? const LinearProgressIndicator() - : Text(L10n.of(context)!.loadMore), - icon: const Icon(Icons.chevron_right_outlined), - onPressed: loading - ? null - : () { - loadHierarchy(response.nextBatch); - }, + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 1, + ), + child: Material( + borderRadius: + BorderRadius.circular(AppConfig.borderRadius), + clipBehavior: Clip.hardEdge, + child: ListTile( + minVerticalPadding: 0, + leading: Icon( + Icons.adaptive.arrow_back_outlined, + size: 16, + ), + title: Row( + children: [ + Avatar( + mxContent: joinedParents[i].avatar, + name: displayname, + size: Avatar.defaultSize / 2, + borderRadius: BorderRadius.circular( + AppConfig.borderRadius / 4, + ), + ), + const SizedBox(width: 8), + Expanded(child: Text(displayname)), + ], + ), + onTap: () => + widget.toParentSpace(joinedParents[i].id), + ), ), ); - } - final spaceChild = spaceChildren[i]; - final room = client.getRoomById(spaceChild.roomId); - if (room != null && !room.isSpace) { + }, + ), + SliverList.builder( + itemCount: joinedRooms.length + 1, + itemBuilder: (context, i) { + if (i == 0) { + return SearchTitle( + title: L10n.of(context)!.joinedChats, + icon: const Icon(Icons.chat_outlined), + ); + } + i--; + final room = joinedRooms[i]; return ChatListItem( room, - onLongPress: () => - _onSpaceChildContextMenu(spaceChild, room), - activeChat: widget.controller.activeChat == room.id, - onTap: () => onChatTap(room, context), + filter: filter, + onTap: () => widget.onChatTab(room), + onLongPress: () => widget.onChatContext(room), + activeChat: widget.activeChat == room.id, ); - } - final isSpace = spaceChild.roomType == 'm.space'; - final topic = spaceChild.topic?.isEmpty ?? true - ? null - : spaceChild.topic; - if (spaceChild.roomId == activeSpaceId) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - SearchTitle( - title: spaceChild.name ?? - spaceChild.canonicalAlias ?? - 'Space', - icon: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10.0, - ), - child: Avatar( - size: 24, - mxContent: spaceChild.avatarUrl, - name: spaceChild.name, + }, + ), + 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), ), ), - color: Theme.of(context) - .colorScheme - .secondaryContainer - .withAlpha(128), - trailing: const Padding( - padding: EdgeInsets.symmetric(horizontal: 16.0), - child: Icon(Icons.edit_outlined), - ), - onTap: () => _onJoinSpaceChild(spaceChild), + ); + } + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 2.0, ), - if (activeSpace?.canChangeStateEvent( - EventTypes.SpaceChild, - ) == - true) - Material( - child: ListTile( - leading: const CircleAvatar( - child: Icon(Icons.group_add_outlined), - ), - title: - Text(L10n.of(context)!.addChatOrSubSpace), - trailing: - const Icon(Icons.chevron_right_outlined), - onTap: _addChatOrSubSpace, - ), - ), - ], - ); - } - final name = spaceChild.name ?? - spaceChild.canonicalAlias ?? - L10n.of(context)!.chat; - if (widget.controller.isSearchMode && - !name.toLowerCase().contains( - widget.controller.searchController.text - .toLowerCase(), - )) { - return const SizedBox.shrink(); - } - return Material( - child: ListTile( - leading: Avatar( - mxContent: spaceChild.avatarUrl, - name: spaceChild.name, + child: TextButton( + onPressed: _isLoading ? null : _loadHierarchy, + child: _isLoading + ? LinearProgressIndicator( + borderRadius: BorderRadius.circular( + AppConfig.borderRadius, + ), + ) + : Text(L10n.of(context)!.loadMore), + ), + ); + } + final item = _discoveredChildren[i]; + final displayname = item.name ?? + item.canonicalAlias ?? + L10n.of(context)!.emptyChat; + if (!displayname.toLowerCase().contains(filter)) { + return const SizedBox.shrink(); + } + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 1, ), - title: Row( - children: [ - Expanded( - child: Text( - name, - maxLines: 1, - style: const TextStyle( - fontWeight: FontWeight.bold, + child: Material( + borderRadius: + BorderRadius.circular(AppConfig.borderRadius), + clipBehavior: Clip.hardEdge, + child: ListTile( + onTap: () => _joinChildRoom(item), + leading: Avatar( + mxContent: item.avatarUrl, + name: displayname, + borderRadius: item.roomType == 'm.space' + ? BorderRadius.circular( + AppConfig.borderRadius / 2, + ) + : null, + ), + title: Row( + children: [ + Expanded( + child: Text( + displayname, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), ), - ), + const SizedBox(width: 8), + const Icon(Icons.add_circle_outline_outlined), + ], + ), + subtitle: Text( + item.topic ?? + L10n.of(context)!.countParticipants( + item.numJoinedMembers, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - if (!isSpace) ...[ - const Icon( - Icons.people_outline, - size: 16, - ), - const SizedBox(width: 4), - Text( - spaceChild.numJoinedMembers.toString(), - style: const TextStyle(fontSize: 14), - ), - ], - ], - ), - onTap: () => room?.isSpace == true - ? widget.controller.setActiveSpace(room!.id) - : _onSpaceChildContextMenu(spaceChild, room), - onLongPress: () => - _onSpaceChildContextMenu(spaceChild, room), - subtitle: Text( - topic ?? - (isSpace - ? L10n.of(context)!.enterSpace - : L10n.of(context)!.enterRoom), - maxLines: 1, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface, ), ), - trailing: isSpace - ? const Icon(Icons.chevron_right_outlined) - : null, - ), - ); - }, - childCount: spaceChildren.length + (canLoadMore ? 1 : 0), - ), + ); + }, + ), + ], ); }, ), - ], - ), - ), ); } } -enum SpaceChildContextAction { - join, +enum SpaceActions { + settings, + invite, leave, - removeFromSpace, } - -enum AddRoomType { chat, subspace } diff --git a/lib/pages/chat_list/start_chat_fab.dart b/lib/pages/chat_list/start_chat_fab.dart deleted file mode 100644 index c6a74c0fd7..0000000000 --- a/lib/pages/chat_list/start_chat_fab.dart +++ /dev/null @@ -1,88 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:go_router/go_router.dart'; - -import '../../config/themes.dart'; -import 'chat_list.dart'; - -class StartChatFloatingActionButton extends StatelessWidget { - final ActiveFilter activeFilter; - final ValueNotifier scrolledToTop; - final bool roomsIsEmpty; - final void Function() createNewSpace; - - const StartChatFloatingActionButton({ - super.key, - required this.activeFilter, - required this.scrolledToTop, - required this.roomsIsEmpty, - required this.createNewSpace, - }); - - void _onPressed(BuildContext context) async { - switch (activeFilter) { - case ActiveFilter.allChats: - case ActiveFilter.messages: - context.go('/rooms/newprivatechat'); - break; - case ActiveFilter.groups: - context.go('/rooms/newgroup'); - break; - case ActiveFilter.spaces: - createNewSpace(); - break; - } - } - - IconData get icon { - switch (activeFilter) { - case ActiveFilter.allChats: - case ActiveFilter.messages: - return Icons.add_outlined; - case ActiveFilter.groups: - return Icons.group_add_outlined; - case ActiveFilter.spaces: - return Icons.workspaces_outlined; - } - } - - String getLabel(BuildContext context) { - switch (activeFilter) { - case ActiveFilter.allChats: - case ActiveFilter.messages: - return roomsIsEmpty - ? L10n.of(context)!.startFirstChat - : L10n.of(context)!.newChat; - case ActiveFilter.groups: - return L10n.of(context)!.newGroup; - case ActiveFilter.spaces: - return L10n.of(context)!.newSpace; - } - } - - @override - Widget build(BuildContext context) { - return ValueListenableBuilder( - valueListenable: scrolledToTop, - builder: (context, scrolledToTop, _) => AnimatedSize( - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - clipBehavior: Clip.none, - child: scrolledToTop - ? FloatingActionButton.extended( - onPressed: () => _onPressed(context), - icon: Icon(icon), - label: Text( - getLabel(context), - overflow: TextOverflow.fade, - ), - ) - : FloatingActionButton( - onPressed: () => _onPressed(context), - child: Icon(icon), - ), - ), - ); - } -} diff --git a/lib/pages/chat_list/utils/on_chat_tap.dart b/lib/pages/chat_list/utils/on_chat_tap.dart deleted file mode 100644 index d24af1fb42..0000000000 --- a/lib/pages/chat_list/utils/on_chat_tap.dart +++ /dev/null @@ -1,127 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:adaptive_dialog/adaptive_dialog.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:future_loading_dialog/future_loading_dialog.dart'; -import 'package:go_router/go_router.dart'; -import 'package:matrix/matrix.dart'; - -import 'package:fluffychat/pages/chat/send_file_dialog.dart'; -import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; -import 'package:fluffychat/widgets/matrix.dart'; - -void onChatTap(Room room, BuildContext context) async { - if (room.membership == Membership.invite) { - final inviterId = - room.getState(EventTypes.RoomMember, room.client.userID!)?.senderId; - final inviteAction = await showModalActionSheet( - context: context, - message: room.isDirectChat - ? L10n.of(context)!.invitePrivateChat - : L10n.of(context)!.inviteGroupChat, - title: room.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)), - actions: [ - SheetAction( - key: InviteActions.accept, - label: L10n.of(context)!.accept, - icon: Icons.check_outlined, - isDefaultAction: true, - ), - SheetAction( - key: InviteActions.decline, - label: L10n.of(context)!.decline, - icon: Icons.close_outlined, - isDestructiveAction: true, - ), - SheetAction( - key: InviteActions.block, - label: L10n.of(context)!.block, - icon: Icons.block_outlined, - isDestructiveAction: true, - ), - ], - ); - if (inviteAction == null) return; - if (inviteAction == InviteActions.block) { - context.go('/rooms/settings/security/ignorelist', extra: inviterId); - return; - } - if (inviteAction == InviteActions.decline) { - await showFutureLoadingDialog( - context: context, - future: room.leave, - ); - return; - } - final joinResult = await showFutureLoadingDialog( - context: context, - future: () async { - final waitForRoom = room.client.waitForRoomInSync( - room.id, - join: true, - ); - await room.join(); - await waitForRoom; - }, - ); - if (joinResult.error != null) return; - } - - if (room.membership == Membership.ban) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(L10n.of(context)!.youHaveBeenBannedFromThisChat), - ), - ); - return; - } - - if (room.membership == Membership.leave) { - context.go('/rooms/archive/${room.id}'); - return; - } - - // Share content into this room - final shareContent = Matrix.of(context).shareContent; - if (shareContent != null) { - final shareFile = shareContent.tryGet('file'); - if (shareContent.tryGet('msgtype') == 'chat.fluffy.shared_file' && - shareFile != null) { - await showDialog( - context: context, - useRootNavigator: false, - builder: (c) => SendFileDialog( - files: [shareFile], - room: room, - ), - ); - Matrix.of(context).shareContent = null; - } else { - final consent = await showOkCancelAlertDialog( - context: context, - title: L10n.of(context)!.forward, - message: L10n.of(context)!.forwardMessageTo( - room.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)), - ), - okLabel: L10n.of(context)!.forward, - cancelLabel: L10n.of(context)!.cancel, - ); - if (consent == OkCancelResult.cancel) { - Matrix.of(context).shareContent = null; - return; - } - if (consent == OkCancelResult.ok) { - room.sendEvent(shareContent); - Matrix.of(context).shareContent = null; - } - } - } - - context.go('/rooms/${room.id}'); -} - -enum InviteActions { - accept, - decline, - block, -} diff --git a/lib/pages/settings_style/settings_style_view.dart b/lib/pages/settings_style/settings_style_view.dart index 86f48fe879..0b505d5978 100644 --- a/lib/pages/settings_style/settings_style_view.dart +++ b/lib/pages/settings_style/settings_style_view.dart @@ -185,12 +185,6 @@ class SettingsStyleView extends StatelessWidget { storeKey: SettingKeys.showPresences, defaultValue: AppConfig.showPresences, ), - SettingsSwitchListTile.adaptive( - title: L10n.of(context)!.separateChatTypes, - onChanged: (b) => AppConfig.separateChatTypes = b, - storeKey: SettingKeys.separateChatTypes, - defaultValue: AppConfig.separateChatTypes, - ), Divider( height: 1, color: Theme.of(context).dividerColor, diff --git a/lib/widgets/avatar.dart b/lib/widgets/avatar.dart index 2066536dcf..180f3437a0 100644 --- a/lib/widgets/avatar.dart +++ b/lib/widgets/avatar.dart @@ -15,6 +15,8 @@ class Avatar extends StatelessWidget { final Client? client; final String? presenceUserId; final Color? presenceBackgroundColor; + final BorderRadius? borderRadius; + final IconData? icon; const Avatar({ this.mxContent, @@ -24,6 +26,8 @@ class Avatar extends StatelessWidget { this.client, this.presenceUserId, this.presenceBackgroundColor, + this.borderRadius, + this.icon, super.key, }); @@ -50,18 +54,25 @@ class Avatar extends StatelessWidget { ), ), ); - final borderRadius = BorderRadius.circular(size / 2); + final borderRadius = this.borderRadius ?? BorderRadius.circular(size / 2); final presenceUserId = this.presenceUserId; final color = noPic ? name?.lightColorAvatar : Theme.of(context).secondaryHeaderColor; final container = Stack( children: [ - ClipRRect( - borderRadius: borderRadius, - child: Container( - width: size, - height: size, + SizedBox( + width: size, + height: size, + child: Material( color: color, + shape: RoundedRectangleBorder( + borderRadius: borderRadius, + side: BorderSide( + width: 0, + color: Theme.of(context).dividerColor, + ), + ), + clipBehavior: Clip.hardEdge, child: noPic ? textWidget : MxcImage( @@ -75,48 +86,49 @@ class Avatar extends StatelessWidget { ), ), ), - PresenceBuilder( - client: client, - userId: presenceUserId, - builder: (context, presence) { - if (presence == null || - (presence.presence == PresenceType.offline && - presence.lastActiveTimestamp == null)) { - return const SizedBox.shrink(); - } - final dotColor = presence.presence.isOnline - ? Colors.green - : presence.presence.isUnavailable - ? Colors.orange - : Colors.grey; - return Positioned( - bottom: -3, - right: -3, - child: Container( - width: 16, - height: 16, - decoration: BoxDecoration( - color: presenceBackgroundColor ?? - Theme.of(context).colorScheme.surface, - borderRadius: BorderRadius.circular(32), - ), - alignment: Alignment.center, + if (presenceUserId != null) + PresenceBuilder( + client: client, + userId: presenceUserId, + builder: (context, presence) { + if (presence == null || + (presence.presence == PresenceType.offline && + presence.lastActiveTimestamp == null)) { + return const SizedBox.shrink(); + } + final dotColor = presence.presence.isOnline + ? Colors.green + : presence.presence.isUnavailable + ? Colors.orange + : Colors.grey; + return Positioned( + bottom: -3, + right: -3, child: Container( - width: 10, - height: 10, + width: 16, + height: 16, decoration: BoxDecoration( - color: dotColor, - borderRadius: BorderRadius.circular(16), - border: Border.all( - width: 1, - color: Theme.of(context).colorScheme.surface, + color: presenceBackgroundColor ?? + Theme.of(context).colorScheme.surface, + borderRadius: BorderRadius.circular(32), + ), + alignment: Alignment.center, + child: Container( + width: 10, + height: 10, + decoration: BoxDecoration( + color: dotColor, + borderRadius: BorderRadius.circular(16), + border: Border.all( + width: 1, + color: Theme.of(context).colorScheme.surface, + ), ), ), ), - ), - ); - }, - ), + ); + }, + ), ], ); if (onTap == null) return container; diff --git a/lib/widgets/layouts/two_column_layout.dart b/lib/widgets/layouts/two_column_layout.dart index a6f4c8bdf6..c270f120a0 100644 --- a/lib/widgets/layouts/two_column_layout.dart +++ b/lib/widgets/layouts/two_column_layout.dart @@ -3,13 +3,11 @@ import 'package:flutter/material.dart'; class TwoColumnLayout extends StatelessWidget { final Widget mainView; final Widget sideView; - final bool displayNavigationRail; const TwoColumnLayout({ super.key, required this.mainView, required this.sideView, - required this.displayNavigationRail, }); @override Widget build(BuildContext context) { @@ -20,7 +18,7 @@ class TwoColumnLayout extends StatelessWidget { Container( clipBehavior: Clip.antiAlias, decoration: const BoxDecoration(), - width: 360.0 + (displayNavigationRail ? 64 : 0), + width: 384.0, child: mainView, ), Container( diff --git a/lib/widgets/matrix.dart b/lib/widgets/matrix.dart index 4de23a2f59..581e3ccff4 100644 --- a/lib/widgets/matrix.dart +++ b/lib/widgets/matrix.dart @@ -433,10 +433,6 @@ class MatrixState extends State with WidgetsBindingObserver { store.getBool(SettingKeys.hideUnimportantStateEvents) ?? AppConfig.hideUnimportantStateEvents; - AppConfig.separateChatTypes = - store.getBool(SettingKeys.separateChatTypes) ?? - AppConfig.separateChatTypes; - AppConfig.autoplayImages = store.getBool(SettingKeys.autoplayImages) ?? AppConfig.autoplayImages; diff --git a/linux/my_application.cc b/linux/my_application.cc index c185bcd785..0abe77c60c 100644 --- a/linux/my_application.cc +++ b/linux/my_application.cc @@ -60,7 +60,7 @@ static void my_application_activate(GApplication* application) { gtk_window_set_title(window, "FluffyChat"); } - gtk_window_set_default_size(window, 864, 680); + gtk_window_set_default_size(window, 800, 600); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); From 254f21ce00251be883c7aae1fdfc1aafa868252b Mon Sep 17 00:00:00 2001 From: Krille Date: Mon, 15 Jul 2024 13:18:15 +0200 Subject: [PATCH 050/288] chore: Follow up new spaces design --- lib/pages/chat_list/chat_list.dart | 2 +- lib/pages/chat_list/chat_list_body.dart | 26 ++---- lib/pages/chat_list/chat_list_item.dart | 118 +++++++++++++++--------- lib/pages/chat_list/chat_list_view.dart | 4 + lib/widgets/avatar.dart | 7 +- 5 files changed, 90 insertions(+), 67 deletions(-) diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 9181b1dff2..28e78dfe6b 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -200,7 +200,7 @@ class ChatListController extends State if (result.error != null) return; } - void onChatTap(Room room, BuildContext context) async { + void onChatTap(Room room) async { if (room.isSpace) { setActiveSpace(room.id); return; diff --git a/lib/pages/chat_list/chat_list_body.dart b/lib/pages/chat_list/chat_list_body.dart index a6526989bc..fd063ddbac 100644 --- a/lib/pages/chat_list/chat_list_body.dart +++ b/lib/pages/chat_list/chat_list_body.dart @@ -32,7 +32,7 @@ class ChatListViewBody extends StatelessWidget { return SpaceView( spaceId: activeSpace, onBack: controller.clearActiveSpace, - onChatTab: (room) => controller.onChatTap(room, context), + onChatTab: (room) => controller.onChatTap(room), onChatContext: (room) => controller.chatContextAction(room), activeChat: controller.activeChat, toParentSpace: controller.setActiveSpace, @@ -62,17 +62,15 @@ class ChatListViewBody extends StatelessWidget { builder: (context, _) { final rooms = controller.filteredRooms; - final spaces = rooms.where((r) => r.isSpace); + final spaces = client.rooms.where((r) => r.isSpace); final spaceDelegateCandidates = {}; for (final space in spaces) { - spaceDelegateCandidates[space.id] = space; for (final spaceChild in space.spaceChildren) { final roomId = spaceChild.roomId; if (roomId == null) continue; spaceDelegateCandidates[roomId] = space; } } - final spaceDelegates = {}; return SafeArea( child: CustomScrollView( @@ -298,26 +296,14 @@ class ChatListViewBody extends StatelessWidget { SliverList.builder( itemCount: rooms.length, itemBuilder: (BuildContext context, int i) { - var room = rooms[i]; - if (controller.activeFilter != ActiveFilter.groups) { - final parent = room.isSpace - ? room - : spaceDelegateCandidates[room.id]; - if (parent != null) { - if (spaceDelegates.contains(parent.id)) { - return const SizedBox.shrink(); - } - spaceDelegates.add(parent.id); - room = parent; - } - } - + final room = rooms[i]; + final space = spaceDelegateCandidates[room.id]; return ChatListItem( room, - lastEventRoom: rooms[i], + space: space, key: Key('chat_list_item_${room.id}'), filter: filter, - onTap: () => controller.onChatTap(room, context), + onTap: () => controller.onChatTap(room), onLongPress: () => controller.chatContextAction(room), activeChat: controller.activeChat == room.id, ); diff --git a/lib/pages/chat_list/chat_list_item.dart b/lib/pages/chat_list/chat_list_item.dart index 7bf5f6b520..2fcce18390 100644 --- a/lib/pages/chat_list/chat_list_item.dart +++ b/lib/pages/chat_list/chat_list_item.dart @@ -17,7 +17,7 @@ enum ArchivedRoomAction { delete, rejoin } class ChatListItem extends StatelessWidget { final Room room; - final Room? lastEventRoom; + final Room? space; final bool activeChat; final void Function()? onLongPress; final void Function()? onForget; @@ -31,7 +31,7 @@ class ChatListItem extends StatelessWidget { this.onLongPress, this.onForget, this.filter, - this.lastEventRoom, + this.space, super.key, }); @@ -64,21 +64,19 @@ class ChatListItem extends StatelessWidget { @override Widget build(BuildContext context) { final isMuted = room.pushRuleState != PushRuleState.notify; - final lastEventRoom = this.lastEventRoom ?? room; - final typingText = lastEventRoom.getLocalizedTypingText(context); - final lastEvent = lastEventRoom.lastEvent; + final typingText = room.getLocalizedTypingText(context); + final lastEvent = room.lastEvent; final ownMessage = lastEvent?.senderId == room.client.userID; - final unread = - lastEventRoom.isUnread || lastEventRoom.membership == Membership.invite; + final unread = room.isUnread || room.membership == Membership.invite; final theme = Theme.of(context); final directChatMatrixId = room.directChatMatrixID; final isDirectChat = directChatMatrixId != null; - final unreadBubbleSize = unread || lastEventRoom.hasNewMessages - ? lastEventRoom.notificationCount > 0 + final unreadBubbleSize = unread || room.hasNewMessages + ? room.notificationCount > 0 ? 20.0 : 14.0 : 0.0; - final hasNotifications = lastEventRoom.notificationCount > 0; + final hasNotifications = room.notificationCount > 0; final backgroundColor = activeChat ? theme.colorScheme.secondaryContainer : null; final displayname = room.getLocalizedDisplayname( @@ -92,6 +90,7 @@ class ChatListItem extends StatelessWidget { final needLastEventSender = lastEvent == null ? false : room.getState(EventTypes.RoomMember, lastEvent.senderId) == null; + final space = this.space; return Padding( padding: const EdgeInsets.symmetric( @@ -117,15 +116,67 @@ class ChatListItem extends StatelessWidget { duration: FluffyThemes.animationDuration, curve: FluffyThemes.animationCurve, scale: hovered ? 1.1 : 1.0, - child: Avatar( - borderRadius: room.isSpace - ? BorderRadius.circular(AppConfig.borderRadius / 3) - : null, - mxContent: room.avatar, - name: displayname, - presenceUserId: directChatMatrixId, - presenceBackgroundColor: backgroundColor, - onTap: onLongPress, + child: SizedBox( + width: Avatar.defaultSize, + height: Avatar.defaultSize, + child: Stack( + children: [ + if (space != null) + Positioned( + top: 0, + left: 0, + child: Avatar( + border: BorderSide( + width: 2, + color: backgroundColor ?? + Theme.of(context).colorScheme.surface, + ), + borderRadius: BorderRadius.circular( + AppConfig.borderRadius / 4, + ), + mxContent: space.avatar, + size: Avatar.defaultSize * 0.75, + name: space.getLocalizedDisplayname(), + onTap: onLongPress, + ), + ), + Positioned( + bottom: 0, + right: 0, + child: Avatar( + border: space == null + ? room.isSpace + ? BorderSide( + width: 0, + color: Theme.of(context) + .colorScheme + .outline, + ) + : null + : BorderSide( + width: 2, + color: backgroundColor ?? + Theme.of(context) + .colorScheme + .surface, + ), + borderRadius: room.isSpace + ? BorderRadius.circular( + AppConfig.borderRadius / 4, + ) + : null, + mxContent: room.avatar, + size: space != null + ? Avatar.defaultSize * 0.75 + : Avatar.defaultSize, + name: displayname, + presenceUserId: directChatMatrixId, + presenceBackgroundColor: backgroundColor, + onTap: onLongPress, + ), + ), + ], + ), ), ), ), @@ -205,20 +256,6 @@ class ChatListItem extends StatelessWidget { subtitle: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - if (room.isSpace) ...[ - room.id != lastEventRoom.id && - lastEventRoom.isUnreadOrInvited - ? Avatar( - mxContent: lastEventRoom.avatar, - name: lastEventRoom.name, - size: 18, - ) - : const Icon( - Icons.workspaces_outlined, - size: 18, - ), - const SizedBox(width: 4), - ], if (typingText.isEmpty && ownMessage && room.lastEvent!.status.isSending) ...[ @@ -243,7 +280,7 @@ class ChatListItem extends StatelessWidget { ), ), Expanded( - child: room.isSpace && !lastEventRoom.isUnreadOrInvited + child: room.isSpace && room.membership == Membership.join ? Text( L10n.of(context)!.countChatsAndCountParticipants( room.spaceChildren.length.toString(), @@ -297,10 +334,9 @@ class ChatListItem extends StatelessWidget { maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( - fontWeight: - unread || lastEventRoom.hasNewMessages - ? FontWeight.bold - : null, + fontWeight: unread || room.hasNewMessages + ? FontWeight.bold + : null, color: theme.colorScheme.onSurfaceVariant, decoration: room.lastEvent?.redacted == true ? TextDecoration.lineThrough @@ -318,9 +354,7 @@ class ChatListItem extends StatelessWidget { width: !hasNotifications && !unread && !room.hasNewMessages ? 0 : (unreadBubbleSize - 9) * - lastEventRoom.notificationCount - .toString() - .length + + room.notificationCount.toString().length + 9, decoration: BoxDecoration( color: room.highlightCount > 0 || @@ -335,7 +369,7 @@ class ChatListItem extends StatelessWidget { child: Center( child: hasNotifications ? Text( - lastEventRoom.notificationCount.toString(), + room.notificationCount.toString(), style: TextStyle( color: room.highlightCount > 0 ? Colors.white diff --git a/lib/pages/chat_list/chat_list_view.dart b/lib/pages/chat_list/chat_list_view.dart index 7984646931..81f765125c 100644 --- a/lib/pages/chat_list/chat_list_view.dart +++ b/lib/pages/chat_list/chat_list_view.dart @@ -26,6 +26,10 @@ class ChatListView extends StatelessWidget { controller.activeFilter == ActiveFilter.allChats, onPopInvoked: (pop) async { if (pop) return; + if (controller.activeSpaceId != null) { + controller.clearActiveSpace(); + return; + } final selMode = controller.selectMode; if (controller.isSearchMode) { controller.cancelSearch(); diff --git a/lib/widgets/avatar.dart b/lib/widgets/avatar.dart index 180f3437a0..0c280a88be 100644 --- a/lib/widgets/avatar.dart +++ b/lib/widgets/avatar.dart @@ -17,6 +17,7 @@ class Avatar extends StatelessWidget { final Color? presenceBackgroundColor; final BorderRadius? borderRadius; final IconData? icon; + final BorderSide? border; const Avatar({ this.mxContent, @@ -27,6 +28,7 @@ class Avatar extends StatelessWidget { this.presenceUserId, this.presenceBackgroundColor, this.borderRadius, + this.border, this.icon, super.key, }); @@ -67,10 +69,7 @@ class Avatar extends StatelessWidget { color: color, shape: RoundedRectangleBorder( borderRadius: borderRadius, - side: BorderSide( - width: 0, - color: Theme.of(context).dividerColor, - ), + side: border ?? BorderSide.none, ), clipBehavior: Clip.hardEdge, child: noPic From 64c56f889b870e7ef27f0d51204a004da7d72804 Mon Sep 17 00:00:00 2001 From: Krille Date: Mon, 15 Jul 2024 13:21:35 +0200 Subject: [PATCH 051/288] chore: Follow up join space invites --- lib/pages/chat_list/chat_list.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 28e78dfe6b..d057cf1811 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -201,10 +201,6 @@ class ChatListController extends State } void onChatTap(Room room) async { - if (room.isSpace) { - setActiveSpace(room.id); - return; - } if (room.membership == Membership.invite) { final inviterId = room.getState(EventTypes.RoomMember, room.client.userID!)?.senderId; @@ -275,6 +271,10 @@ class ChatListController extends State return; } + if (room.isSpace) { + setActiveSpace(room.id); + return; + } // Share content into this room final shareContent = Matrix.of(context).shareContent; if (shareContent != null) { From 7b0e0404c06faa5f78dfae6e65a36c1dd3deed39 Mon Sep 17 00:00:00 2001 From: Krille Date: Mon, 15 Jul 2024 13:39:12 +0200 Subject: [PATCH 052/288] chore: Follow up select chats --- assets/l10n/intl_en.arb | 7 ++++++- lib/pages/chat_list/chat_list.dart | 19 +++++++++++++------ lib/pages/chat_list/chat_list_body.dart | 8 +++++--- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index ab0ba910df..904b06c9c9 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -2705,5 +2705,10 @@ "restricted": "Restricted", "@restricted": {}, "knockRestricted": "Knock restricted", - "@knockRestricted": {} + "@knockRestricted": {}, + "goToSpace": "Go to space: {space}", + "@goToSpace": { + "type": "text", + "space": {} + } } diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index d057cf1811..7084a082d0 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -612,19 +612,22 @@ class ChatListController extends State super.dispose(); } - void chatContextAction(Room room) async { + void chatContextAction(Room room, [Room? space]) async { final action = await showModalActionSheet( context: context, - title: room.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)), actions: [ + if (space != null) + SheetAction( + key: ChatContextAction.goToSpace, + icon: Icons.workspaces_outlined, + label: L10n.of(context)!.goToSpace(space.getLocalizedDisplayname()), + ), SheetAction( key: ChatContextAction.markUnread, icon: room.markedUnread ? Icons.mark_as_unread : Icons.mark_as_unread_outlined, - label: room.markedUnread - ? L10n.of(context)!.markAsRead - : L10n.of(context)!.unread, + label: L10n.of(context)!.toggleUnread, ), SheetAction( key: ChatContextAction.favorite, @@ -656,8 +659,11 @@ class ChatListController extends State await showFutureLoadingDialog( context: context, - future: () { + future: () async { switch (action) { + case ChatContextAction.goToSpace: + setActiveSpace(space!.id); + return; case ChatContextAction.favorite: return room.setFavourite(!room.isFavourite); case ChatContextAction.markUnread: @@ -872,6 +878,7 @@ enum InviteActions { enum AddRoomType { chat, subspace } enum ChatContextAction { + goToSpace, favorite, markUnread, mute, diff --git a/lib/pages/chat_list/chat_list_body.dart b/lib/pages/chat_list/chat_list_body.dart index fd063ddbac..0b9c00889d 100644 --- a/lib/pages/chat_list/chat_list_body.dart +++ b/lib/pages/chat_list/chat_list_body.dart @@ -27,13 +27,15 @@ class ChatListViewBody extends StatelessWidget { @override Widget build(BuildContext context) { + final client = Matrix.of(context).client; final activeSpace = controller.activeSpaceId; if (activeSpace != null) { return SpaceView( spaceId: activeSpace, onBack: controller.clearActiveSpace, onChatTab: (room) => controller.onChatTap(room), - onChatContext: (room) => controller.chatContextAction(room), + onChatContext: (room) => + controller.chatContextAction(room, client.getRoomById(activeSpace)), activeChat: controller.activeChat, toParentSpace: controller.setActiveSpace, ); @@ -45,7 +47,6 @@ class ChatListViewBody extends StatelessWidget { .where((room) => room.roomType == 'm.space') .toList(); final userSearchResult = controller.userSearchResult; - final client = Matrix.of(context).client; const dummyChatCount = 4; final titleColor = Theme.of(context).textTheme.bodyLarge!.color!.withAlpha(100); @@ -304,7 +305,8 @@ class ChatListViewBody extends StatelessWidget { key: Key('chat_list_item_${room.id}'), filter: filter, onTap: () => controller.onChatTap(room), - onLongPress: () => controller.chatContextAction(room), + onLongPress: () => + controller.chatContextAction(room, space), activeChat: controller.activeChat == room.id, ); }, From 8a5cd9bf160299eb28df346e2e137e4ad5ca3fdc Mon Sep 17 00:00:00 2001 From: Krille Date: Mon, 15 Jul 2024 13:45:02 +0200 Subject: [PATCH 053/288] chore: Follow up spaces ui --- lib/pages/chat_list/chat_list_body.dart | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/pages/chat_list/chat_list_body.dart b/lib/pages/chat_list/chat_list_body.dart index 0b9c00889d..1d1705ca10 100644 --- a/lib/pages/chat_list/chat_list_body.dart +++ b/lib/pages/chat_list/chat_list_body.dart @@ -163,7 +163,13 @@ class ChatListViewBody extends StatelessWidget { ), shrinkWrap: true, scrollDirection: Axis.horizontal, - children: ActiveFilter.values + children: [ + ActiveFilter.allChats, + ActiveFilter.unread, + ActiveFilter.groups, + if (spaceDelegateCandidates.isNotEmpty) + ActiveFilter.spaces, + ] .map( (filter) => Padding( padding: From 942970e04970fac768e351722f97e514603ef870 Mon Sep 17 00:00:00 2001 From: Krille Date: Mon, 15 Jul 2024 13:47:01 +0200 Subject: [PATCH 054/288] chore: Follow up spaces design --- lib/pages/chat_list/chat_list_item.dart | 140 ++++++++++-------------- 1 file changed, 58 insertions(+), 82 deletions(-) diff --git a/lib/pages/chat_list/chat_list_item.dart b/lib/pages/chat_list/chat_list_item.dart index 2fcce18390..3df2e01c45 100644 --- a/lib/pages/chat_list/chat_list_item.dart +++ b/lib/pages/chat_list/chat_list_item.dart @@ -108,96 +108,72 @@ class ChatListItem extends StatelessWidget { visualDensity: const VisualDensity(vertical: -0.5), contentPadding: const EdgeInsets.symmetric(horizontal: 8), onLongPress: onLongPress, - leading: Stack( - clipBehavior: Clip.none, - children: [ - HoverBuilder( - builder: (context, hovered) => AnimatedScale( - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - scale: hovered ? 1.1 : 1.0, - child: SizedBox( - width: Avatar.defaultSize, - height: Avatar.defaultSize, - child: Stack( - children: [ - if (space != null) - Positioned( - top: 0, - left: 0, - child: Avatar( - border: BorderSide( + leading: HoverBuilder( + builder: (context, hovered) => AnimatedScale( + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + scale: hovered ? 1.1 : 1.0, + child: SizedBox( + width: Avatar.defaultSize, + height: Avatar.defaultSize, + child: Stack( + children: [ + if (space != null) + Positioned( + top: 0, + left: 0, + child: Avatar( + border: BorderSide( + width: 2, + color: backgroundColor ?? + Theme.of(context).colorScheme.surface, + ), + borderRadius: BorderRadius.circular( + AppConfig.borderRadius / 4, + ), + mxContent: space.avatar, + size: Avatar.defaultSize * 0.75, + name: space.getLocalizedDisplayname(), + onTap: onLongPress, + ), + ), + Positioned( + bottom: 0, + right: 0, + child: Avatar( + border: space == null + ? room.isSpace + ? BorderSide( + width: 0, + color: Theme.of(context) + .colorScheme + .outline, + ) + : null + : BorderSide( width: 2, color: backgroundColor ?? Theme.of(context).colorScheme.surface, ), - borderRadius: BorderRadius.circular( + borderRadius: room.isSpace + ? BorderRadius.circular( AppConfig.borderRadius / 4, - ), - mxContent: space.avatar, - size: Avatar.defaultSize * 0.75, - name: space.getLocalizedDisplayname(), - onTap: onLongPress, - ), - ), - Positioned( - bottom: 0, - right: 0, - child: Avatar( - border: space == null - ? room.isSpace - ? BorderSide( - width: 0, - color: Theme.of(context) - .colorScheme - .outline, - ) - : null - : BorderSide( - width: 2, - color: backgroundColor ?? - Theme.of(context) - .colorScheme - .surface, - ), - borderRadius: room.isSpace - ? BorderRadius.circular( - AppConfig.borderRadius / 4, - ) - : null, - mxContent: room.avatar, - size: space != null - ? Avatar.defaultSize * 0.75 - : Avatar.defaultSize, - name: displayname, - presenceUserId: directChatMatrixId, - presenceBackgroundColor: backgroundColor, - onTap: onLongPress, - ), - ), - ], - ), - ), - ), - ), - Positioned( - bottom: -2, - right: -2, - child: AnimatedScale( - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - scale: (hovered) ? 1.0 : 0.0, - child: Material( - color: backgroundColor, - borderRadius: BorderRadius.circular(16), - child: const Icon( - Icons.check_circle_outlined, - size: 18, + ) + : null, + mxContent: room.avatar, + size: space != null + ? Avatar.defaultSize * 0.75 + : Avatar.defaultSize, + name: displayname, + presenceUserId: directChatMatrixId, + presenceBackgroundColor: backgroundColor, + onTap: onLongPress, + ), ), - ), + ], ), ), - ], + ), ), title: Row( children: [ From 3bd7257249ec035d5b8f12746ebba329b3848014 Mon Sep 17 00:00:00 2001 From: Krille Date: Mon, 15 Jul 2024 13:57:29 +0200 Subject: [PATCH 055/288] chore: Follow up spaces ui --- lib/pages/chat_list/chat_list_body.dart | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/pages/chat_list/chat_list_body.dart b/lib/pages/chat_list/chat_list_body.dart index 1d1705ca10..d2f90fc300 100644 --- a/lib/pages/chat_list/chat_list_body.dart +++ b/lib/pages/chat_list/chat_list_body.dart @@ -40,6 +40,16 @@ class ChatListViewBody extends StatelessWidget { toParentSpace: controller.setActiveSpace, ); } + final spaces = client.rooms.where((r) => r.isSpace); + final spaceDelegateCandidates = {}; + for (final space in spaces) { + for (final spaceChild in space.spaceChildren) { + final roomId = spaceChild.roomId; + if (roomId == null) continue; + spaceDelegateCandidates[roomId] = space; + } + } + final publicRooms = controller.roomSearchResult?.chunk .where((room) => room.roomType != 'm.space') .toList(); @@ -63,16 +73,6 @@ class ChatListViewBody extends StatelessWidget { builder: (context, _) { final rooms = controller.filteredRooms; - final spaces = client.rooms.where((r) => r.isSpace); - final spaceDelegateCandidates = {}; - for (final space in spaces) { - for (final spaceChild in space.spaceChildren) { - final roomId = spaceChild.roomId; - if (roomId == null) continue; - spaceDelegateCandidates[roomId] = space; - } - } - return SafeArea( child: CustomScrollView( controller: controller.scrollController, From 650f87b1d29fb7e85592aba08291390faf6a47cd Mon Sep 17 00:00:00 2001 From: Krille Date: Mon, 15 Jul 2024 15:19:50 +0200 Subject: [PATCH 056/288] chore: Follow up spaces ui --- assets/l10n/intl_en.arb | 3 +- lib/pages/chat_list/chat_list.dart | 23 +- lib/pages/chat_list/chat_list_view.dart | 9 +- lib/pages/chat_list/space_view.dart | 548 ++++++++++++------------ 4 files changed, 295 insertions(+), 288 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 904b06c9c9..6c6da498a1 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -2710,5 +2710,6 @@ "@goToSpace": { "type": "text", "space": {} - } + }, + "markAsUnread": "Mark as unread" } diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 7084a082d0..6c8ebc7552 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -619,7 +619,7 @@ class ChatListController extends State if (space != null) SheetAction( key: ChatContextAction.goToSpace, - icon: Icons.workspaces_outlined, + icon: Icons.chevron_right_outlined, label: L10n.of(context)!.goToSpace(space.getLocalizedDisplayname()), ), SheetAction( @@ -627,11 +627,13 @@ class ChatListController extends State icon: room.markedUnread ? Icons.mark_as_unread : Icons.mark_as_unread_outlined, - label: L10n.of(context)!.toggleUnread, + label: room.markedUnread + ? L10n.of(context)!.markAsRead + : L10n.of(context)!.markAsUnread, ), SheetAction( key: ChatContextAction.favorite, - icon: room.isFavourite ? Icons.pin : Icons.pin_outlined, + icon: room.isFavourite ? Icons.push_pin : Icons.push_pin_outlined, label: room.isFavourite ? L10n.of(context)!.unpin : L10n.of(context)!.pin, @@ -640,7 +642,7 @@ class ChatListController extends State key: ChatContextAction.mute, icon: room.pushRuleState == PushRuleState.notify ? Icons.notifications_off_outlined - : Icons.notifications, + : Icons.notifications_outlined, label: room.pushRuleState == PushRuleState.notify ? L10n.of(context)!.muteChat : L10n.of(context)!.unmuteChat, @@ -657,6 +659,19 @@ class ChatListController extends State if (action == null) return; if (!mounted) return; + if (action == ChatContextAction.leave) { + final confirmed = await showOkCancelAlertDialog( + useRootNavigator: false, + context: context, + title: L10n.of(context)!.areYouSure, + okLabel: L10n.of(context)!.yes, + cancelLabel: L10n.of(context)!.no, + message: L10n.of(context)!.archiveRoomDescription, + ); + if (confirmed == OkCancelResult.cancel) return; + } + if (!mounted) return; + await showFutureLoadingDialog( context: context, future: () async { diff --git a/lib/pages/chat_list/chat_list_view.dart b/lib/pages/chat_list/chat_list_view.dart index 81f765125c..93fa8575b2 100644 --- a/lib/pages/chat_list/chat_list_view.dart +++ b/lib/pages/chat_list/chat_list_view.dart @@ -22,14 +22,9 @@ class ChatListView extends StatelessWidget { final selectMode = controller.selectMode; return PopScope( canPop: controller.selectMode == SelectMode.normal && - !controller.isSearchMode && - controller.activeFilter == ActiveFilter.allChats, - onPopInvoked: (pop) async { + !controller.isSearchMode, + onPopInvoked: (pop) { if (pop) return; - if (controller.activeSpaceId != null) { - controller.clearActiveSpace(); - return; - } final selMode = controller.selectMode; if (controller.isSearchMode) { controller.cancelSearch(); diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index 6459de1e8c..8fe443bf10 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -165,316 +165,312 @@ class _SpaceViewState extends State { final room = Matrix.of(context).client.getRoomById(widget.spaceId); final displayname = room?.getLocalizedDisplayname() ?? L10n.of(context)!.nothingFound; - return Scaffold( - appBar: AppBar( - leading: Center( - child: CloseButton( - onPressed: widget.onBack, - ), - ), - titleSpacing: 0, - title: ListTile( - contentPadding: EdgeInsets.zero, - leading: Avatar( - mxContent: room?.avatar, - name: displayname, - borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), + return PopScope( + canPop: false, + onPopInvoked: (_) => widget.onBack(), + child: Scaffold( + appBar: AppBar( + leading: Center( + child: CloseButton( + onPressed: widget.onBack, + ), ), - title: Text( - displayname, - maxLines: 1, - overflow: TextOverflow.ellipsis, + titleSpacing: 0, + title: ListTile( + contentPadding: EdgeInsets.zero, + leading: Avatar( + mxContent: room?.avatar, + name: displayname, + borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), + ), + title: Text( + displayname, + 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, + ), ), - subtitle: room == null - ? null - : Text( - L10n.of(context)!.countChatsAndCountParticipants( - room.spaceChildren.length, - room.summary.mJoinedMemberCount ?? 1, + actions: [ + PopupMenuButton( + onSelected: _onSpaceAction, + itemBuilder: (context) => [ + PopupMenuItem( + value: SpaceActions.settings, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.settings_outlined), + const SizedBox(width: 12), + Text(L10n.of(context)!.settings), + ], ), - maxLines: 1, - overflow: TextOverflow.ellipsis, ), - ), - actions: [ - PopupMenuButton( - onSelected: _onSpaceAction, - itemBuilder: (context) => [ - PopupMenuItem( - value: SpaceActions.settings, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.settings_outlined), - const SizedBox(width: 12), - Text(L10n.of(context)!.settings), - ], + PopupMenuItem( + value: SpaceActions.invite, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.person_add_outlined), + const SizedBox(width: 12), + Text(L10n.of(context)!.invite), + ], + ), ), - ), - PopupMenuItem( - value: SpaceActions.invite, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.person_add_outlined), - const SizedBox(width: 12), - Text(L10n.of(context)!.invite), - ], + PopupMenuItem( + value: SpaceActions.leave, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.delete_outlined), + const SizedBox(width: 12), + Text(L10n.of(context)!.leave), + ], + ), ), - ), - PopupMenuItem( - value: SpaceActions.leave, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.delete_outlined), - const SizedBox(width: 12), - Text(L10n.of(context)!.leave), - ], + ], + ), + ], + ), + body: room == null + ? const Center( + child: Icon( + Icons.search_outlined, + size: 80, ), - ), - ], - ), - ], - ), - body: room == null - ? const Center( - child: Icon( - Icons.search_outlined, - size: 80, - ), - ) - : StreamBuilder( - stream: room.client.onSync.stream - .where((s) => s.hasRoomUpdate) - .rateLimit(const Duration(seconds: 1)), - builder: (context, snapshot) { - final joinedRooms = room.spaceChildren - .map((child) { - final roomId = child.roomId; - if (roomId == null) return null; - return room.client.getRoomById(roomId); - }) - .whereType() - .where((room) => room.membership != Membership.leave) - .toList(); + ) + : StreamBuilder( + stream: room.client.onSync.stream + .where((s) => s.hasRoomUpdate) + .rateLimit(const Duration(seconds: 1)), + builder: (context, snapshot) { + final childrenIds = room.spaceChildren + .map((c) => c.roomId) + .whereType() + .toSet(); - // Sort rooms by last activity - joinedRooms.sort( - (b, a) => (a.lastEvent?.originServerTs ?? - DateTime.fromMillisecondsSinceEpoch(0)) - .compareTo( - b.lastEvent?.originServerTs ?? - DateTime.fromMillisecondsSinceEpoch(0), - ), - ); + final joinedRooms = room.client.rooms + .where((room) => childrenIds.remove(room.id)) + .toList(); - final joinedParents = room.spaceParents - .map((parent) { - final roomId = parent.roomId; - if (roomId == null) return null; - return room.client.getRoomById(roomId); - }) - .whereType() - .toList(); - final filter = _filterController.text.trim().toLowerCase(); - return CustomScrollView( - slivers: [ - SliverAppBar( - floating: true, - toolbarHeight: 72, - scrolledUnderElevation: 0, - backgroundColor: Colors.transparent, - automaticallyImplyLeading: false, - title: TextField( - controller: _filterController, - onChanged: (_) => setState(() {}), - textInputAction: TextInputAction.search, - decoration: InputDecoration( - fillColor: - Theme.of(context).colorScheme.secondaryContainer, - border: OutlineInputBorder( - borderSide: BorderSide.none, - borderRadius: BorderRadius.circular(99), - ), - contentPadding: EdgeInsets.zero, - hintText: L10n.of(context)!.search, - hintStyle: TextStyle( - color: Theme.of(context) + final joinedParents = room.spaceParents + .map((parent) { + final roomId = parent.roomId; + if (roomId == null) return null; + return room.client.getRoomById(roomId); + }) + .whereType() + .toList(); + final filter = _filterController.text.trim().toLowerCase(); + return CustomScrollView( + slivers: [ + SliverAppBar( + floating: true, + toolbarHeight: 72, + scrolledUnderElevation: 0, + backgroundColor: Colors.transparent, + automaticallyImplyLeading: false, + title: TextField( + controller: _filterController, + onChanged: (_) => setState(() {}), + textInputAction: TextInputAction.search, + decoration: InputDecoration( + fillColor: Theme.of(context) .colorScheme - .onPrimaryContainer, - fontWeight: FontWeight.normal, - ), - floatingLabelBehavior: FloatingLabelBehavior.never, - prefixIcon: IconButton( - onPressed: () {}, - icon: Icon( - Icons.search_outlined, + .secondaryContainer, + border: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(99), + ), + contentPadding: EdgeInsets.zero, + hintText: L10n.of(context)!.search, + hintStyle: TextStyle( color: Theme.of(context) .colorScheme .onPrimaryContainer, + fontWeight: FontWeight.normal, + ), + floatingLabelBehavior: FloatingLabelBehavior.never, + prefixIcon: IconButton( + onPressed: () {}, + icon: Icon( + Icons.search_outlined, + color: Theme.of(context) + .colorScheme + .onPrimaryContainer, + ), ), ), ), ), - ), - SliverList.builder( - itemCount: joinedParents.length, - itemBuilder: (context, i) { - final displayname = - joinedParents[i].getLocalizedDisplayname(); - return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 1, - ), - child: Material( - borderRadius: - BorderRadius.circular(AppConfig.borderRadius), - clipBehavior: Clip.hardEdge, - child: ListTile( - minVerticalPadding: 0, - leading: Icon( - Icons.adaptive.arrow_back_outlined, - size: 16, - ), - title: Row( - children: [ - Avatar( - mxContent: joinedParents[i].avatar, - name: displayname, - size: Avatar.defaultSize / 2, - borderRadius: BorderRadius.circular( - AppConfig.borderRadius / 4, + SliverList.builder( + itemCount: joinedParents.length, + itemBuilder: (context, i) { + final displayname = + joinedParents[i].getLocalizedDisplayname(); + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 1, + ), + child: Material( + borderRadius: + BorderRadius.circular(AppConfig.borderRadius), + clipBehavior: Clip.hardEdge, + child: ListTile( + minVerticalPadding: 0, + leading: Icon( + Icons.adaptive.arrow_back_outlined, + size: 16, + ), + title: Row( + children: [ + Avatar( + mxContent: joinedParents[i].avatar, + name: displayname, + size: Avatar.defaultSize / 2, + borderRadius: BorderRadius.circular( + AppConfig.borderRadius / 4, + ), ), - ), - const SizedBox(width: 8), - Expanded(child: Text(displayname)), - ], + const SizedBox(width: 8), + Expanded(child: Text(displayname)), + ], + ), + onTap: () => + widget.toParentSpace(joinedParents[i].id), ), - onTap: () => - widget.toParentSpace(joinedParents[i].id), ), - ), - ); - }, - ), - SliverList.builder( - itemCount: joinedRooms.length + 1, - itemBuilder: (context, i) { - if (i == 0) { - return SearchTitle( - title: L10n.of(context)!.joinedChats, - icon: const Icon(Icons.chat_outlined), ); - } - i--; - final room = joinedRooms[i]; - return ChatListItem( - room, - filter: filter, - onTap: () => widget.onChatTab(room), - onLongPress: () => widget.onChatContext(room), - activeChat: widget.activeChat == room.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), + }, + ), + SliverList.builder( + itemCount: joinedRooms.length + 1, + itemBuilder: (context, i) { + if (i == 0) { + return SearchTitle( + title: L10n.of(context)!.joinedChats, + icon: const Icon(Icons.chat_outlined), + ); + } + i--; + final room = joinedRooms[i]; + return ChatListItem( + room, + filter: filter, + onTap: () => widget.onChatTab(room), + onLongPress: () => widget.onChatContext(room), + activeChat: widget.activeChat == room.id, ); - } - 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), + }, + ), + 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 Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 2.0, + ), + child: TextButton( + onPressed: _isLoading ? null : _loadHierarchy, + child: _isLoading + ? LinearProgressIndicator( + borderRadius: BorderRadius.circular( + AppConfig.borderRadius, + ), + ) + : Text(L10n.of(context)!.loadMore), ), ); } + final item = _discoveredChildren[i]; + final displayname = item.name ?? + item.canonicalAlias ?? + L10n.of(context)!.emptyChat; + if (!displayname.toLowerCase().contains(filter)) { + return const SizedBox.shrink(); + } return Padding( padding: const EdgeInsets.symmetric( - horizontal: 12.0, - vertical: 2.0, + horizontal: 8, + vertical: 1, ), - child: TextButton( - onPressed: _isLoading ? null : _loadHierarchy, - child: _isLoading - ? LinearProgressIndicator( - borderRadius: BorderRadius.circular( - AppConfig.borderRadius, + child: Material( + borderRadius: + BorderRadius.circular(AppConfig.borderRadius), + clipBehavior: Clip.hardEdge, + child: ListTile( + onTap: () => _joinChildRoom(item), + leading: Avatar( + mxContent: item.avatarUrl, + name: displayname, + borderRadius: item.roomType == 'm.space' + ? BorderRadius.circular( + AppConfig.borderRadius / 2, + ) + : null, + ), + title: Row( + children: [ + Expanded( + child: Text( + displayname, + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - ) - : Text(L10n.of(context)!.loadMore), - ), - ); - } - final item = _discoveredChildren[i]; - final displayname = item.name ?? - item.canonicalAlias ?? - L10n.of(context)!.emptyChat; - if (!displayname.toLowerCase().contains(filter)) { - return const SizedBox.shrink(); - } - return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 1, - ), - child: Material( - borderRadius: - BorderRadius.circular(AppConfig.borderRadius), - clipBehavior: Clip.hardEdge, - child: ListTile( - onTap: () => _joinChildRoom(item), - leading: Avatar( - mxContent: item.avatarUrl, - name: displayname, - borderRadius: item.roomType == 'm.space' - ? BorderRadius.circular( - AppConfig.borderRadius / 2, - ) - : null, - ), - title: Row( - children: [ - Expanded( - child: Text( - displayname, - maxLines: 1, - overflow: TextOverflow.ellipsis, ), - ), - const SizedBox(width: 8), - const Icon(Icons.add_circle_outline_outlined), - ], - ), - subtitle: Text( - item.topic ?? - L10n.of(context)!.countParticipants( - item.numJoinedMembers, + const SizedBox(width: 8), + const Icon( + Icons.add_circle_outline_outlined, ), - maxLines: 1, - overflow: TextOverflow.ellipsis, + ], + ), + subtitle: Text( + item.topic ?? + L10n.of(context)!.countParticipants( + item.numJoinedMembers, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), ), ), - ), - ); - }, - ), - ], - ); - }, - ), + ); + }, + ), + ], + ); + }, + ), + ), ); } } From cfdb86b7e97ef7a52d51c561305a81c99a726f2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jos=C3=A9=20m?= Date: Fri, 12 Jul 2024 15:55:04 +0000 Subject: [PATCH 057/288] Translated using Weblate (Galician) Currently translated at 100.0% (634 of 634 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/gl/ --- assets/l10n/intl_gl.arb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_gl.arb b/assets/l10n/intl_gl.arb index 2fe2464f56..8a7ef79225 100644 --- a/assets/l10n/intl_gl.arb +++ b/assets/l10n/intl_gl.arb @@ -2708,5 +2708,9 @@ "restricted": "Non accesible", "@restricted": {}, "swipeRightToLeftToReply": "Despraza hacia a esquerda para responder", - "@swipeRightToLeftToReply": {} + "@swipeRightToLeftToReply": {}, + "alwaysUse24HourFormat": "falso", + "@alwaysUse24HourFormat": { + "description": "Set to true to always display time of day in 24 hour format." + } } From d436aa7ff54d9a3efd07ad7e8f5ea11063c5aaf9 Mon Sep 17 00:00:00 2001 From: Milo Ivir Date: Sun, 14 Jul 2024 21:12:44 +0000 Subject: [PATCH 058/288] Translated using Weblate (Croatian) Currently translated at 100.0% (634 of 634 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/hr/ --- assets/l10n/intl_hr.arb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_hr.arb b/assets/l10n/intl_hr.arb index a3f981b2dd..3d6c84730e 100644 --- a/assets/l10n/intl_hr.arb +++ b/assets/l10n/intl_hr.arb @@ -2708,5 +2708,9 @@ "count": {} }, "swipeRightToLeftToReply": "Za odgovaranje povuci prstom zdesna ulijevo", - "@swipeRightToLeftToReply": {} + "@swipeRightToLeftToReply": {}, + "alwaysUse24HourFormat": "true", + "@alwaysUse24HourFormat": { + "description": "Set to true to always display time of day in 24 hour format." + } } From eb0ae28adc767460a1584791d8b05b50714a6951 Mon Sep 17 00:00:00 2001 From: Christian Date: Mon, 15 Jul 2024 13:26:06 +0000 Subject: [PATCH 059/288] Translated using Weblate (German) Currently translated at 99.6% (640 of 642 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/de/ --- assets/l10n/intl_de.arb | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/assets/l10n/intl_de.arb b/assets/l10n/intl_de.arb index 07dea5ae80..cb0c713d9a 100644 --- a/assets/l10n/intl_de.arb +++ b/assets/l10n/intl_de.arb @@ -2,8 +2,8 @@ "@@locale": "de", "@@last_modified": "2021-08-14 12:41:10.119255", "alwaysUse24HourFormat": "true", - "@alwaysUse24HourFormat": { - "description": "Set to true to always display time of day in 24 hour format." + "@alwaysUse24HourFormat": { + "description": "Set to true to always display time of day in 24 hour format." }, "about": "Über", "@about": { @@ -2710,5 +2710,22 @@ } }, "searchMore": "Weiter suchen ...", - "@searchMore": {} + "@searchMore": {}, + "unread": "Ungelesen", + "@unread": {}, + "noMoreChatsFound": "Keine weiteren Chats gefunden ...", + "@noMoreChatsFound": {}, + "joinedChats": "Beigetretene Chats", + "@joinedChats": {}, + "space": "Space", + "@space": {}, + "spaces": "Spaces", + "@spaces": {}, + "goToSpace": "Geh zum Space: {space}", + "@goToSpace": { + "type": "text", + "space": {} + }, + "markAsUnread": "Als ungelesen markieren", + "@markAsUnread": {} } From 47d1165b45801ab9af612fb3a73bbcfe7e3a81ed Mon Sep 17 00:00:00 2001 From: Krille Date: Mon, 15 Jul 2024 16:34:48 +0200 Subject: [PATCH 060/288] chore: Follow up chat list context menu --- lib/pages/chat_list/chat_list.dart | 164 ++++++-- lib/pages/chat_list/chat_list_body.dart | 74 ++-- lib/pages/chat_list/chat_list_item.dart | 485 ++++++++++++------------ lib/pages/chat_list/space_view.dart | 7 +- 4 files changed, 414 insertions(+), 316 deletions(-) diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 6c8ebc7552..06cb532d43 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -21,6 +21,7 @@ import 'package:fluffychat/pages/chat_list/chat_list_view.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:fluffychat/widgets/avatar.dart'; import '../../../utils/account_bundles.dart'; import '../../config/setting_keys.dart'; import '../../utils/matrix_sdk_extensions/matrix_file_extension.dart'; @@ -612,46 +613,130 @@ class ChatListController extends State super.dispose(); } - void chatContextAction(Room room, [Room? space]) async { - final action = await showModalActionSheet( - context: context, - actions: [ + void chatContextAction( + Room room, + BuildContext posContext, [ + Room? space, + ]) 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 displayname = + room.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)); + + final action = await showMenu( + context: posContext, + position: position, + items: [ + PopupMenuItem( + enabled: false, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Avatar( + mxContent: room.avatar, + size: Avatar.defaultSize / 2, + name: displayname, + ), + const SizedBox(width: 12), + Text(displayname), + ], + ), + ), + const PopupMenuDivider(), if (space != null) - SheetAction( - key: ChatContextAction.goToSpace, - icon: Icons.chevron_right_outlined, - label: L10n.of(context)!.goToSpace(space.getLocalizedDisplayname()), + PopupMenuItem( + value: ChatContextAction.goToSpace, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.navigate_next_outlined), + const SizedBox(width: 12), + Expanded( + child: Text( + L10n.of(context)! + .goToSpace(space.getLocalizedDisplayname()), + ), + ), + ], + ), + ), + PopupMenuItem( + value: ChatContextAction.mute, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + room.pushRuleState == PushRuleState.notify + ? Icons.notifications_off_outlined + : Icons.notifications_off, + ), + const SizedBox(width: 12), + Text( + room.pushRuleState == PushRuleState.notify + ? L10n.of(context)!.muteChat + : L10n.of(context)!.unmuteChat, + ), + ], ), - SheetAction( - key: ChatContextAction.markUnread, - icon: room.markedUnread - ? Icons.mark_as_unread - : Icons.mark_as_unread_outlined, - label: room.markedUnread - ? L10n.of(context)!.markAsRead - : L10n.of(context)!.markAsUnread, ), - SheetAction( - key: ChatContextAction.favorite, - icon: room.isFavourite ? Icons.push_pin : Icons.push_pin_outlined, - label: room.isFavourite - ? L10n.of(context)!.unpin - : L10n.of(context)!.pin, + PopupMenuItem( + value: ChatContextAction.markUnread, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + room.markedUnread + ? Icons.mark_as_unread + : Icons.mark_as_unread_outlined, + ), + const SizedBox(width: 12), + Text( + room.markedUnread + ? L10n.of(context)!.markAsRead + : L10n.of(context)!.markAsUnread, + ), + ], + ), ), - SheetAction( - key: ChatContextAction.mute, - icon: room.pushRuleState == PushRuleState.notify - ? Icons.notifications_off_outlined - : Icons.notifications_outlined, - label: room.pushRuleState == PushRuleState.notify - ? L10n.of(context)!.muteChat - : L10n.of(context)!.unmuteChat, + PopupMenuItem( + value: ChatContextAction.favorite, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(room.isFavourite ? Icons.push_pin : Icons.push_pin_outlined), + const SizedBox(width: 12), + Text( + room.isFavourite + ? L10n.of(context)!.unpin + : L10n.of(context)!.pin, + ), + ], + ), ), - SheetAction( - isDestructiveAction: true, - key: ChatContextAction.leave, - icon: Icons.delete_outlined, - label: L10n.of(context)!.leave, + PopupMenuItem( + value: ChatContextAction.leave, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.delete_outlined), + const SizedBox(width: 12), + Text(L10n.of(context)!.leave), + ], + ), ), ], ); @@ -659,14 +744,20 @@ class ChatListController extends State if (action == null) return; if (!mounted) return; + if (action == ChatContextAction.goToSpace) { + setActiveSpace(space!.id); + return; + } + if (action == ChatContextAction.leave) { final confirmed = await showOkCancelAlertDialog( useRootNavigator: false, context: context, title: L10n.of(context)!.areYouSure, - okLabel: L10n.of(context)!.yes, + okLabel: L10n.of(context)!.leave, cancelLabel: L10n.of(context)!.no, message: L10n.of(context)!.archiveRoomDescription, + isDestructiveAction: true, ); if (confirmed == OkCancelResult.cancel) return; } @@ -677,7 +768,6 @@ class ChatListController extends State future: () async { switch (action) { case ChatContextAction.goToSpace: - setActiveSpace(space!.id); return; case ChatContextAction.favorite: return room.setFavourite(!room.isFavourite); diff --git a/lib/pages/chat_list/chat_list_body.dart b/lib/pages/chat_list/chat_list_body.dart index d2f90fc300..4b17ad6b51 100644 --- a/lib/pages/chat_list/chat_list_body.dart +++ b/lib/pages/chat_list/chat_list_body.dart @@ -14,6 +14,7 @@ import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart'; import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; import 'package:fluffychat/utils/stream_extension.dart'; import 'package:fluffychat/widgets/avatar.dart'; +import 'package:fluffychat/widgets/hover_builder.dart'; import 'package:fluffychat/widgets/public_room_bottom_sheet.dart'; import '../../config/themes.dart'; import '../../widgets/connection_status_header.dart'; @@ -34,8 +35,8 @@ class ChatListViewBody extends StatelessWidget { spaceId: activeSpace, onBack: controller.clearActiveSpace, onChatTab: (room) => controller.onChatTap(room), - onChatContext: (room) => - controller.chatContextAction(room, client.getRoomById(activeSpace)), + onChatContext: (room, context) => + controller.chatContextAction(room, context), activeChat: controller.activeChat, toParentSpace: controller.setActiveSpace, ); @@ -174,45 +175,54 @@ class ChatListViewBody extends StatelessWidget { (filter) => Padding( padding: const EdgeInsets.symmetric(horizontal: 4), - child: InkWell( - borderRadius: BorderRadius.circular( - AppConfig.borderRadius, - ), - onTap: () => - controller.setActiveFilter(filter), - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 6, - ), - decoration: BoxDecoration( - color: filter == controller.activeFilter - ? Theme.of(context) - .colorScheme - .primary - : Theme.of(context) - .colorScheme - .secondaryContainer, + child: HoverBuilder( + builder: (context, hovered) => + AnimatedScale( + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + scale: hovered ? 1.1 : 1.0, + child: InkWell( borderRadius: BorderRadius.circular( AppConfig.borderRadius, ), - ), - alignment: Alignment.center, - child: Text( - filter.toLocalizedString(context), - style: TextStyle( - fontWeight: - filter == controller.activeFilter + onTap: () => + controller.setActiveFilter(filter), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: filter == + controller.activeFilter + ? Theme.of(context) + .colorScheme + .primary + : Theme.of(context) + .colorScheme + .secondaryContainer, + borderRadius: BorderRadius.circular( + AppConfig.borderRadius, + ), + ), + alignment: Alignment.center, + child: Text( + filter.toLocalizedString(context), + style: TextStyle( + fontWeight: filter == + controller.activeFilter ? FontWeight.bold : FontWeight.normal, - color: - filter == controller.activeFilter + color: filter == + controller.activeFilter ? Theme.of(context) .colorScheme .onPrimary : Theme.of(context) .colorScheme .onSecondaryContainer, + ), + ), ), ), ), @@ -311,8 +321,8 @@ class ChatListViewBody extends StatelessWidget { key: Key('chat_list_item_${room.id}'), filter: filter, onTap: () => controller.onChatTap(room), - onLongPress: () => - controller.chatContextAction(room, space), + onLongPress: (context) => + controller.chatContextAction(room, context, space), activeChat: controller.activeChat == room.id, ); }, diff --git a/lib/pages/chat_list/chat_list_item.dart b/lib/pages/chat_list/chat_list_item.dart index 3df2e01c45..ed46b03f7b 100644 --- a/lib/pages/chat_list/chat_list_item.dart +++ b/lib/pages/chat_list/chat_list_item.dart @@ -19,7 +19,7 @@ class ChatListItem extends StatelessWidget { final Room room; final Room? space; final bool activeChat; - final void Function()? onLongPress; + final void Function(BuildContext context)? onLongPress; final void Function()? onForget; final void Function() onTap; final String? filter; @@ -103,271 +103,266 @@ class ChatListItem extends StatelessWidget { color: backgroundColor, child: FutureBuilder( future: room.loadHeroUsers(), - builder: (context, snapshot) => HoverBuilder( - builder: (context, hovered) => ListTile( - visualDensity: const VisualDensity(vertical: -0.5), - contentPadding: const EdgeInsets.symmetric(horizontal: 8), - onLongPress: onLongPress, - leading: HoverBuilder( - builder: (context, hovered) => AnimatedScale( - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - scale: hovered ? 1.1 : 1.0, - child: SizedBox( - width: Avatar.defaultSize, - height: Avatar.defaultSize, - child: Stack( - children: [ - if (space != null) - Positioned( - top: 0, - left: 0, - child: Avatar( - border: BorderSide( - width: 2, - color: backgroundColor ?? - Theme.of(context).colorScheme.surface, - ), - borderRadius: BorderRadius.circular( - AppConfig.borderRadius / 4, - ), - mxContent: space.avatar, - size: Avatar.defaultSize * 0.75, - name: space.getLocalizedDisplayname(), - onTap: onLongPress, - ), - ), + builder: (context, snapshot) => ListTile( + visualDensity: const VisualDensity(vertical: -0.5), + contentPadding: const EdgeInsets.symmetric(horizontal: 8), + onLongPress: () => onLongPress?.call(context), + leading: HoverBuilder( + builder: (context, hovered) => AnimatedScale( + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + scale: hovered ? 1.1 : 1.0, + child: SizedBox( + width: Avatar.defaultSize, + height: Avatar.defaultSize, + child: Stack( + children: [ + if (space != null) Positioned( - bottom: 0, - right: 0, + top: 0, + left: 0, child: Avatar( - border: space == null - ? room.isSpace - ? BorderSide( - width: 0, - color: Theme.of(context) - .colorScheme - .outline, - ) - : null - : BorderSide( - width: 2, - color: backgroundColor ?? - Theme.of(context).colorScheme.surface, - ), - borderRadius: room.isSpace - ? BorderRadius.circular( - AppConfig.borderRadius / 4, - ) - : null, - mxContent: room.avatar, - size: space != null - ? Avatar.defaultSize * 0.75 - : Avatar.defaultSize, - name: displayname, - presenceUserId: directChatMatrixId, - presenceBackgroundColor: backgroundColor, - onTap: onLongPress, + border: BorderSide( + width: 2, + color: backgroundColor ?? + Theme.of(context).colorScheme.surface, + ), + borderRadius: BorderRadius.circular( + AppConfig.borderRadius / 4, + ), + mxContent: space.avatar, + size: Avatar.defaultSize * 0.75, + name: space.getLocalizedDisplayname(), + onTap: () => onLongPress?.call(context), ), ), - ], - ), + Positioned( + bottom: 0, + right: 0, + child: Avatar( + border: space == null + ? room.isSpace + ? BorderSide( + width: 0, + color: + Theme.of(context).colorScheme.outline, + ) + : null + : BorderSide( + width: 2, + color: backgroundColor ?? + Theme.of(context).colorScheme.surface, + ), + borderRadius: room.isSpace + ? BorderRadius.circular( + AppConfig.borderRadius / 4, + ) + : null, + mxContent: room.avatar, + size: space != null + ? Avatar.defaultSize * 0.75 + : Avatar.defaultSize, + name: displayname, + presenceUserId: directChatMatrixId, + presenceBackgroundColor: backgroundColor, + onTap: () => onLongPress?.call(context), + ), + ), + ], ), ), ), - title: Row( - children: [ - Expanded( - child: Text( - displayname, - maxLines: 1, - overflow: TextOverflow.ellipsis, - softWrap: false, - style: unread || room.hasNewMessages - ? const TextStyle(fontWeight: FontWeight.bold) - : null, + ), + title: Row( + children: [ + Expanded( + child: Text( + displayname, + maxLines: 1, + overflow: TextOverflow.ellipsis, + softWrap: false, + style: unread || room.hasNewMessages + ? const TextStyle(fontWeight: FontWeight.bold) + : null, + ), + ), + if (isMuted) + const Padding( + padding: EdgeInsets.only(left: 4.0), + child: Icon( + Icons.notifications_off_outlined, + size: 16, ), ), - if (isMuted) - const Padding( - padding: EdgeInsets.only(left: 4.0), - child: Icon( - Icons.notifications_off_outlined, - size: 16, - ), + if (room.isFavourite || room.membership == Membership.invite) + Padding( + padding: EdgeInsets.only( + right: hasNotifications ? 4.0 : 0.0, ), - if (room.isFavourite || room.membership == Membership.invite) - Padding( - padding: EdgeInsets.only( - right: hasNotifications ? 4.0 : 0.0, - ), - child: Icon( - Icons.push_pin, - size: 16, - color: theme.colorScheme.primary, - ), + child: Icon( + Icons.push_pin, + size: 16, + color: theme.colorScheme.primary, ), - if (!room.isSpace && - lastEvent != null && - room.membership != Membership.invite) - Padding( - padding: const EdgeInsets.only(left: 4.0), - child: Text( - lastEvent.originServerTs.localizedTimeShort(context), - style: TextStyle( - fontSize: 13, - color: unread - ? theme.colorScheme.secondary - : theme.textTheme.bodyMedium!.color, - ), + ), + if (!room.isSpace && + lastEvent != null && + room.membership != Membership.invite) + Padding( + padding: const EdgeInsets.only(left: 4.0), + child: Text( + lastEvent.originServerTs.localizedTimeShort(context), + style: TextStyle( + fontSize: 13, + color: unread + ? theme.colorScheme.secondary + : theme.textTheme.bodyMedium!.color, ), ), - if (room.isSpace) - const Icon( - Icons.arrow_circle_right_outlined, - size: 18, - ), - ], - ), - subtitle: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (typingText.isEmpty && - ownMessage && - room.lastEvent!.status.isSending) ...[ - const SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator.adaptive(strokeWidth: 2), - ), - const SizedBox(width: 4), - ], - AnimatedContainer( - width: typingText.isEmpty ? 0 : 18, - clipBehavior: Clip.hardEdge, - decoration: const BoxDecoration(), - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - padding: const EdgeInsets.only(right: 4), - child: Icon( - Icons.edit_outlined, - color: theme.colorScheme.secondary, - size: 14, - ), ), - Expanded( - child: room.isSpace && room.membership == Membership.join - ? Text( - L10n.of(context)!.countChatsAndCountParticipants( - room.spaceChildren.length.toString(), - (room.summary.mJoinedMemberCount ?? 1).toString(), - ), - ) - : typingText.isNotEmpty - ? Text( - typingText, - style: TextStyle( - color: theme.colorScheme.primary, - ), - maxLines: 1, - softWrap: false, - ) - : FutureBuilder( - key: ValueKey( - '${lastEvent?.eventId}_${lastEvent?.type}', - ), - future: needLastEventSender - ? lastEvent.calcLocalizedBody( - MatrixLocals(L10n.of(context)!), - hideReply: true, - hideEdit: true, - plaintextBody: true, - removeMarkdown: true, - withSenderNamePrefix: (!isDirectChat || - directChatMatrixId != - room.lastEvent?.senderId), - ) - : null, - initialData: - lastEvent?.calcLocalizedBodyFallback( - MatrixLocals(L10n.of(context)!), - hideReply: true, - hideEdit: true, - plaintextBody: true, - removeMarkdown: true, - withSenderNamePrefix: (!isDirectChat || - directChatMatrixId != - room.lastEvent?.senderId), - ), - builder: (context, snapshot) => Text( - room.membership == Membership.invite - ? isDirectChat - ? L10n.of(context)!.invitePrivateChat - : L10n.of(context)!.inviteGroupChat - : snapshot.data ?? - L10n.of(context)!.emptyChat, - softWrap: false, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontWeight: unread || room.hasNewMessages - ? FontWeight.bold - : null, - color: theme.colorScheme.onSurfaceVariant, - decoration: room.lastEvent?.redacted == true - ? TextDecoration.lineThrough - : null, - ), - ), - ), + if (room.isSpace) + const Icon( + Icons.arrow_circle_right_outlined, + size: 18, ), - const SizedBox(width: 8), - AnimatedContainer( - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - 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 - ? Colors.red - : hasNotifications || room.markedUnread - ? theme.colorScheme.primary - : theme.colorScheme.primaryContainer, - borderRadius: - BorderRadius.circular(AppConfig.borderRadius), - ), - child: Center( - child: hasNotifications + ], + ), + subtitle: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (typingText.isEmpty && + ownMessage && + room.lastEvent!.status.isSending) ...[ + const SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator.adaptive(strokeWidth: 2), + ), + const SizedBox(width: 4), + ], + AnimatedContainer( + width: typingText.isEmpty ? 0 : 18, + clipBehavior: Clip.hardEdge, + decoration: const BoxDecoration(), + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + padding: const EdgeInsets.only(right: 4), + child: Icon( + Icons.edit_outlined, + color: theme.colorScheme.secondary, + size: 14, + ), + ), + Expanded( + child: room.isSpace && room.membership == Membership.join + ? Text( + L10n.of(context)!.countChatsAndCountParticipants( + room.spaceChildren.length.toString(), + (room.summary.mJoinedMemberCount ?? 1).toString(), + ), + ) + : typingText.isNotEmpty ? Text( - room.notificationCount.toString(), + typingText, style: TextStyle( - color: room.highlightCount > 0 - ? Colors.white - : hasNotifications - ? theme.colorScheme.onPrimary - : theme.colorScheme.onPrimaryContainer, - fontSize: 13, + color: theme.colorScheme.primary, ), + maxLines: 1, + softWrap: false, ) - : const SizedBox.shrink(), - ), + : FutureBuilder( + key: ValueKey( + '${lastEvent?.eventId}_${lastEvent?.type}', + ), + future: needLastEventSender + ? lastEvent.calcLocalizedBody( + MatrixLocals(L10n.of(context)!), + hideReply: true, + hideEdit: true, + plaintextBody: true, + removeMarkdown: true, + withSenderNamePrefix: (!isDirectChat || + directChatMatrixId != + room.lastEvent?.senderId), + ) + : null, + initialData: lastEvent?.calcLocalizedBodyFallback( + MatrixLocals(L10n.of(context)!), + hideReply: true, + hideEdit: true, + plaintextBody: true, + removeMarkdown: true, + withSenderNamePrefix: (!isDirectChat || + directChatMatrixId != + room.lastEvent?.senderId), + ), + builder: (context, snapshot) => Text( + room.membership == Membership.invite + ? isDirectChat + ? L10n.of(context)!.invitePrivateChat + : L10n.of(context)!.inviteGroupChat + : snapshot.data ?? + L10n.of(context)!.emptyChat, + softWrap: false, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontWeight: unread || room.hasNewMessages + ? FontWeight.bold + : null, + color: theme.colorScheme.onSurfaceVariant, + decoration: room.lastEvent?.redacted == true + ? TextDecoration.lineThrough + : null, + ), + ), + ), + ), + const SizedBox(width: 8), + AnimatedContainer( + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + 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 + ? Colors.red + : hasNotifications || room.markedUnread + ? theme.colorScheme.primary + : theme.colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(AppConfig.borderRadius), ), - ], - ), - onTap: onTap, - trailing: onForget == null - ? null - : IconButton( - icon: const Icon(Icons.delete_outlined), - onPressed: onForget, - ), + child: Center( + child: hasNotifications + ? Text( + room.notificationCount.toString(), + style: TextStyle( + color: room.highlightCount > 0 + ? Colors.white + : hasNotifications + ? theme.colorScheme.onPrimary + : theme.colorScheme.onPrimaryContainer, + fontSize: 13, + ), + ) + : const SizedBox.shrink(), + ), + ), + ], ), + onTap: onTap, + trailing: onForget == null + ? 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 8fe443bf10..3a7fdcad0f 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -20,7 +20,7 @@ class SpaceView extends StatefulWidget { final void Function() onBack; final void Function(String spaceId) toParentSpace; final void Function(Room room) onChatTab; - final void Function(Room room) onChatContext; + final void Function(Room room, BuildContext context) onChatContext; final String? activeChat; const SpaceView({ @@ -367,7 +367,10 @@ class _SpaceViewState extends State { room, filter: filter, onTap: () => widget.onChatTab(room), - onLongPress: () => widget.onChatContext(room), + onLongPress: (context) => widget.onChatContext( + room, + context, + ), activeChat: widget.activeChat == room.id, ); }, From 1b95694a5848c8b1503f963550dced896cc358a6 Mon Sep 17 00:00:00 2001 From: Krille Date: Mon, 15 Jul 2024 16:53:09 +0200 Subject: [PATCH 061/288] chore: Follow up spaces design --- lib/pages/chat_list/chat_list.dart | 6 +++++- lib/pages/chat_list/chat_list_item.dart | 13 +++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 06cb532d43..8ee1870b52 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -652,7 +652,11 @@ class ChatListController extends State name: displayname, ), const SizedBox(width: 12), - Text(displayname), + Text( + displayname, + style: + TextStyle(color: Theme.of(context).colorScheme.onSurface), + ), ], ), ), diff --git a/lib/pages/chat_list/chat_list_item.dart b/lib/pages/chat_list/chat_list_item.dart index ed46b03f7b..0217e0c970 100644 --- a/lib/pages/chat_list/chat_list_item.dart +++ b/lib/pages/chat_list/chat_list_item.dart @@ -168,6 +168,19 @@ class ChatListItem extends StatelessWidget { onTap: () => onLongPress?.call(context), ), ), + if (hovered) + Positioned( + top: -2, + right: -2, + child: Material( + color: backgroundColor, + borderRadius: BorderRadius.circular(16), + child: const Icon( + Icons.arrow_drop_down_circle_outlined, + size: 18, + ), + ), + ), ], ), ), From 54ba4544af791e3ab4dbb603277e3dfffe4978c1 Mon Sep 17 00:00:00 2001 From: Krille Date: Mon, 15 Jul 2024 16:57:19 +0200 Subject: [PATCH 062/288] chore: Follow up listtilehover --- lib/pages/chat_list/chat_list_item.dart | 503 ++++++++++++------------ 1 file changed, 254 insertions(+), 249 deletions(-) diff --git a/lib/pages/chat_list/chat_list_item.dart b/lib/pages/chat_list/chat_list_item.dart index 0217e0c970..8036aee620 100644 --- a/lib/pages/chat_list/chat_list_item.dart +++ b/lib/pages/chat_list/chat_list_item.dart @@ -103,279 +103,284 @@ class ChatListItem extends StatelessWidget { color: backgroundColor, child: FutureBuilder( future: room.loadHeroUsers(), - builder: (context, snapshot) => ListTile( - visualDensity: const VisualDensity(vertical: -0.5), - contentPadding: const EdgeInsets.symmetric(horizontal: 8), - onLongPress: () => onLongPress?.call(context), - leading: HoverBuilder( - builder: (context, hovered) => AnimatedScale( - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - scale: hovered ? 1.1 : 1.0, - child: SizedBox( - width: Avatar.defaultSize, - height: Avatar.defaultSize, - child: Stack( - children: [ - if (space != null) + builder: (context, snapshot) => HoverBuilder( + builder: (context, listTileHovered) => ListTile( + visualDensity: const VisualDensity(vertical: -0.5), + contentPadding: const EdgeInsets.symmetric(horizontal: 8), + onLongPress: () => onLongPress?.call(context), + leading: HoverBuilder( + builder: (context, hovered) => AnimatedScale( + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + scale: hovered ? 1.1 : 1.0, + child: SizedBox( + width: Avatar.defaultSize, + height: Avatar.defaultSize, + child: Stack( + children: [ + if (space != null) + Positioned( + top: 0, + left: 0, + child: Avatar( + border: BorderSide( + width: 2, + color: backgroundColor ?? + Theme.of(context).colorScheme.surface, + ), + borderRadius: BorderRadius.circular( + AppConfig.borderRadius / 4, + ), + mxContent: space.avatar, + size: Avatar.defaultSize * 0.75, + name: space.getLocalizedDisplayname(), + onTap: () => onLongPress?.call(context), + ), + ), Positioned( - top: 0, - left: 0, + bottom: 0, + right: 0, child: Avatar( - border: BorderSide( - width: 2, - color: backgroundColor ?? - Theme.of(context).colorScheme.surface, - ), - borderRadius: BorderRadius.circular( - AppConfig.borderRadius / 4, - ), - mxContent: space.avatar, - size: Avatar.defaultSize * 0.75, - name: space.getLocalizedDisplayname(), + border: space == null + ? room.isSpace + ? BorderSide( + width: 0, + color: Theme.of(context) + .colorScheme + .outline, + ) + : null + : BorderSide( + width: 2, + color: backgroundColor ?? + Theme.of(context).colorScheme.surface, + ), + borderRadius: room.isSpace + ? BorderRadius.circular( + AppConfig.borderRadius / 4, + ) + : null, + mxContent: room.avatar, + size: space != null + ? Avatar.defaultSize * 0.75 + : Avatar.defaultSize, + name: displayname, + presenceUserId: directChatMatrixId, + presenceBackgroundColor: backgroundColor, onTap: () => onLongPress?.call(context), ), ), - Positioned( - bottom: 0, - right: 0, - child: Avatar( - border: space == null - ? room.isSpace - ? BorderSide( - width: 0, - color: - Theme.of(context).colorScheme.outline, - ) - : null - : BorderSide( - width: 2, - color: backgroundColor ?? - Theme.of(context).colorScheme.surface, - ), - borderRadius: room.isSpace - ? BorderRadius.circular( - AppConfig.borderRadius / 4, - ) - : null, - mxContent: room.avatar, - size: space != null - ? Avatar.defaultSize * 0.75 - : Avatar.defaultSize, - name: displayname, - presenceUserId: directChatMatrixId, - presenceBackgroundColor: backgroundColor, - onTap: () => onLongPress?.call(context), - ), - ), - if (hovered) - Positioned( - top: -2, - right: -2, - child: Material( - color: backgroundColor, - borderRadius: BorderRadius.circular(16), - child: const Icon( - Icons.arrow_drop_down_circle_outlined, - size: 18, + if (listTileHovered) + Positioned( + top: -2, + right: -2, + child: Material( + color: backgroundColor, + borderRadius: BorderRadius.circular(16), + child: const Icon( + Icons.arrow_drop_down_circle_outlined, + size: 18, + ), ), ), - ), - ], + ], + ), ), ), ), - ), - title: Row( - children: [ - Expanded( - child: Text( - displayname, - maxLines: 1, - overflow: TextOverflow.ellipsis, - softWrap: false, - style: unread || room.hasNewMessages - ? const TextStyle(fontWeight: FontWeight.bold) - : null, - ), - ), - if (isMuted) - const Padding( - padding: EdgeInsets.only(left: 4.0), - child: Icon( - Icons.notifications_off_outlined, - size: 16, + title: Row( + children: [ + Expanded( + child: Text( + displayname, + maxLines: 1, + overflow: TextOverflow.ellipsis, + softWrap: false, + style: unread || room.hasNewMessages + ? const TextStyle(fontWeight: FontWeight.bold) + : null, ), ), - if (room.isFavourite || room.membership == Membership.invite) - Padding( - padding: EdgeInsets.only( - right: hasNotifications ? 4.0 : 0.0, + if (isMuted) + const Padding( + padding: EdgeInsets.only(left: 4.0), + child: Icon( + Icons.notifications_off_outlined, + size: 16, + ), ), - child: Icon( - Icons.push_pin, - size: 16, - color: theme.colorScheme.primary, + if (room.isFavourite || room.membership == Membership.invite) + Padding( + padding: EdgeInsets.only( + right: hasNotifications ? 4.0 : 0.0, + ), + child: Icon( + Icons.push_pin, + size: 16, + color: theme.colorScheme.primary, + ), ), - ), - if (!room.isSpace && - lastEvent != null && - room.membership != Membership.invite) - Padding( - padding: const EdgeInsets.only(left: 4.0), - child: Text( - lastEvent.originServerTs.localizedTimeShort(context), - style: TextStyle( - fontSize: 13, - color: unread - ? theme.colorScheme.secondary - : theme.textTheme.bodyMedium!.color, + if (!room.isSpace && + lastEvent != null && + room.membership != Membership.invite) + Padding( + padding: const EdgeInsets.only(left: 4.0), + child: Text( + lastEvent.originServerTs.localizedTimeShort(context), + style: TextStyle( + fontSize: 13, + color: unread + ? theme.colorScheme.secondary + : theme.textTheme.bodyMedium!.color, + ), ), ), - ), - if (room.isSpace) - const Icon( - Icons.arrow_circle_right_outlined, - size: 18, - ), - ], - ), - subtitle: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (typingText.isEmpty && - ownMessage && - room.lastEvent!.status.isSending) ...[ - const SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator.adaptive(strokeWidth: 2), - ), - const SizedBox(width: 4), + if (room.isSpace) + const Icon( + Icons.arrow_circle_right_outlined, + size: 18, + ), ], - AnimatedContainer( - width: typingText.isEmpty ? 0 : 18, - clipBehavior: Clip.hardEdge, - decoration: const BoxDecoration(), - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - padding: const EdgeInsets.only(right: 4), - child: Icon( - Icons.edit_outlined, - color: theme.colorScheme.secondary, - size: 14, + ), + subtitle: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (typingText.isEmpty && + ownMessage && + room.lastEvent!.status.isSending) ...[ + const SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator.adaptive(strokeWidth: 2), + ), + const SizedBox(width: 4), + ], + AnimatedContainer( + width: typingText.isEmpty ? 0 : 18, + clipBehavior: Clip.hardEdge, + decoration: const BoxDecoration(), + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + padding: const EdgeInsets.only(right: 4), + child: Icon( + Icons.edit_outlined, + color: theme.colorScheme.secondary, + size: 14, + ), ), - ), - Expanded( - child: room.isSpace && room.membership == Membership.join - ? Text( - L10n.of(context)!.countChatsAndCountParticipants( - room.spaceChildren.length.toString(), - (room.summary.mJoinedMemberCount ?? 1).toString(), - ), - ) - : typingText.isNotEmpty - ? Text( - typingText, - style: TextStyle( - color: theme.colorScheme.primary, - ), - maxLines: 1, - softWrap: false, - ) - : FutureBuilder( - key: ValueKey( - '${lastEvent?.eventId}_${lastEvent?.type}', - ), - future: needLastEventSender - ? lastEvent.calcLocalizedBody( - MatrixLocals(L10n.of(context)!), - hideReply: true, - hideEdit: true, - plaintextBody: true, - removeMarkdown: true, - withSenderNamePrefix: (!isDirectChat || - directChatMatrixId != - room.lastEvent?.senderId), - ) - : null, - initialData: lastEvent?.calcLocalizedBodyFallback( - MatrixLocals(L10n.of(context)!), - hideReply: true, - hideEdit: true, - plaintextBody: true, - removeMarkdown: true, - withSenderNamePrefix: (!isDirectChat || - directChatMatrixId != - room.lastEvent?.senderId), - ), - builder: (context, snapshot) => Text( - room.membership == Membership.invite - ? isDirectChat - ? L10n.of(context)!.invitePrivateChat - : L10n.of(context)!.inviteGroupChat - : snapshot.data ?? - L10n.of(context)!.emptyChat, - softWrap: false, - maxLines: 1, - overflow: TextOverflow.ellipsis, + Expanded( + child: room.isSpace && room.membership == Membership.join + ? Text( + L10n.of(context)!.countChatsAndCountParticipants( + room.spaceChildren.length.toString(), + (room.summary.mJoinedMemberCount ?? 1).toString(), + ), + ) + : typingText.isNotEmpty + ? Text( + typingText, style: TextStyle( - fontWeight: unread || room.hasNewMessages - ? FontWeight.bold - : null, - color: theme.colorScheme.onSurfaceVariant, - decoration: room.lastEvent?.redacted == true - ? TextDecoration.lineThrough - : null, + color: theme.colorScheme.primary, + ), + maxLines: 1, + softWrap: false, + ) + : FutureBuilder( + key: ValueKey( + '${lastEvent?.eventId}_${lastEvent?.type}', + ), + future: needLastEventSender + ? lastEvent.calcLocalizedBody( + MatrixLocals(L10n.of(context)!), + hideReply: true, + hideEdit: true, + plaintextBody: true, + removeMarkdown: true, + withSenderNamePrefix: (!isDirectChat || + directChatMatrixId != + room.lastEvent?.senderId), + ) + : null, + initialData: + lastEvent?.calcLocalizedBodyFallback( + MatrixLocals(L10n.of(context)!), + hideReply: true, + hideEdit: true, + plaintextBody: true, + removeMarkdown: true, + withSenderNamePrefix: (!isDirectChat || + directChatMatrixId != + room.lastEvent?.senderId), + ), + builder: (context, snapshot) => Text( + room.membership == Membership.invite + ? isDirectChat + ? L10n.of(context)!.invitePrivateChat + : L10n.of(context)!.inviteGroupChat + : snapshot.data ?? + L10n.of(context)!.emptyChat, + softWrap: false, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontWeight: unread || room.hasNewMessages + ? FontWeight.bold + : null, + color: theme.colorScheme.onSurfaceVariant, + decoration: room.lastEvent?.redacted == true + ? TextDecoration.lineThrough + : null, + ), ), ), - ), - ), - const SizedBox(width: 8), - AnimatedContainer( - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - 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 - ? Colors.red - : hasNotifications || room.markedUnread - ? theme.colorScheme.primary - : theme.colorScheme.primaryContainer, - borderRadius: BorderRadius.circular(AppConfig.borderRadius), ), - child: Center( - child: hasNotifications - ? Text( - room.notificationCount.toString(), - style: TextStyle( - color: room.highlightCount > 0 - ? Colors.white - : hasNotifications - ? theme.colorScheme.onPrimary - : theme.colorScheme.onPrimaryContainer, - fontSize: 13, - ), - ) - : const SizedBox.shrink(), + const SizedBox(width: 8), + AnimatedContainer( + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + 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 + ? Colors.red + : hasNotifications || room.markedUnread + ? theme.colorScheme.primary + : theme.colorScheme.primaryContainer, + borderRadius: + BorderRadius.circular(AppConfig.borderRadius), + ), + child: Center( + child: hasNotifications + ? Text( + room.notificationCount.toString(), + style: TextStyle( + color: room.highlightCount > 0 + ? Colors.white + : hasNotifications + ? theme.colorScheme.onPrimary + : theme.colorScheme.onPrimaryContainer, + fontSize: 13, + ), + ) + : const SizedBox.shrink(), + ), ), - ), - ], + ], + ), + onTap: onTap, + trailing: onForget == null + ? null + : IconButton( + icon: const Icon(Icons.delete_outlined), + onPressed: onForget, + ), ), - onTap: onTap, - trailing: onForget == null - ? null - : IconButton( - icon: const Icon(Icons.delete_outlined), - onPressed: onForget, - ), ), ), ), From 282188f574e419fe6837a3fa6d96999b76534522 Mon Sep 17 00:00:00 2001 From: Krille Date: Mon, 15 Jul 2024 16:57:56 +0200 Subject: [PATCH 063/288] chore: Follow up listtilehovered --- lib/pages/chat_list/chat_list_item.dart | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/pages/chat_list/chat_list_item.dart b/lib/pages/chat_list/chat_list_item.dart index 8036aee620..a146a076fb 100644 --- a/lib/pages/chat_list/chat_list_item.dart +++ b/lib/pages/chat_list/chat_list_item.dart @@ -170,10 +170,13 @@ class ChatListItem extends StatelessWidget { onTap: () => onLongPress?.call(context), ), ), - if (listTileHovered) - Positioned( - top: -2, - right: -2, + Positioned( + top: -2, + right: -2, + child: AnimatedScale( + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + scale: listTileHovered ? 1.1 : 1.0, child: Material( color: backgroundColor, borderRadius: BorderRadius.circular(16), @@ -183,6 +186,7 @@ class ChatListItem extends StatelessWidget { ), ), ), + ), ], ), ), From b05eb891a66816fb71277b94304d594389997a2d Mon Sep 17 00:00:00 2001 From: krille-chan Date: Mon, 15 Jul 2024 21:14:49 +0200 Subject: [PATCH 064/288] chore: Bring back navrail --- lib/config/routes.dart | 5 + lib/pages/chat/chat_view.dart | 15 ++- lib/pages/chat_list/chat_list.dart | 8 +- lib/pages/chat_list/chat_list_body.dart | 3 +- lib/pages/chat_list/chat_list_item.dart | 6 +- lib/pages/chat_list/chat_list_view.dart | 134 +++++++++++++++++---- lib/pages/chat_list/nav_rail_item.dart | 95 +++++++++++++++ lib/pages/chat_list/navi_rail_item.dart | 126 +++++++++---------- lib/widgets/layouts/two_column_layout.dart | 4 +- lib/widgets/unread_rooms_badge.dart | 61 ++++------ linux/my_application.cc | 2 +- 11 files changed, 328 insertions(+), 131 deletions(-) create mode 100644 lib/pages/chat_list/nav_rail_item.dart diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 4aaf63a0b0..d11cd56db6 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -92,8 +92,12 @@ abstract class AppRoutes { FluffyThemes.isColumnMode(context) && state.fullPath?.startsWith('/rooms/settings') == false ? TwoColumnLayout( + displayNavigationRail: + state.path?.startsWith('/rooms/settings') != true, mainView: ChatList( activeChat: state.pathParameters['roomid'], + displayNavigationRail: + state.path?.startsWith('/rooms/settings') != true, ), sideView: child, ) @@ -171,6 +175,7 @@ abstract class AppRoutes { ? TwoColumnLayout( mainView: const Settings(), sideView: child, + displayNavigationRail: false, ) : child, ), diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 9974a9e702..80a84fa41e 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -182,10 +182,17 @@ class ChatView extends StatelessWidget { tooltip: L10n.of(context)!.close, color: Theme.of(context).colorScheme.primary, ) - : UnreadRoomsBadge( - filter: (r) => r.id != controller.roomId, - badgePosition: BadgePosition.topEnd(end: 8, top: 4), - child: const Center(child: BackButton()), + : StreamBuilder( + stream: Matrix.of(context) + .client + .onSync + .stream + .where((syncUpdate) => syncUpdate.hasRoomUpdate), + builder: (context, _) => UnreadRoomsBadge( + filter: (r) => r.id != controller.roomId, + badgePosition: BadgePosition.topEnd(end: 8, top: 4), + child: const Center(child: BackButton()), + ), ), titleSpacing: 0, title: ChatAppBarTitle(controller), diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 8ee1870b52..d4f6067edc 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -73,10 +73,12 @@ extension LocalizedActiveFilter on ActiveFilter { class ChatList extends StatefulWidget { static BuildContext? contextForVoip; final String? activeChat; + final bool displayNavigationRail; const ChatList({ super.key, required this.activeChat, + this.displayNavigationRail = false, }); @override @@ -667,7 +669,11 @@ class ChatListController extends State child: Row( mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.navigate_next_outlined), + Avatar( + mxContent: space.avatar, + size: Avatar.defaultSize / 2, + name: space.getLocalizedDisplayname(), + ), const SizedBox(width: 12), Expanded( child: Text( diff --git a/lib/pages/chat_list/chat_list_body.dart b/lib/pages/chat_list/chat_list_body.dart index 4b17ad6b51..e596440b37 100644 --- a/lib/pages/chat_list/chat_list_body.dart +++ b/lib/pages/chat_list/chat_list_body.dart @@ -168,7 +168,8 @@ class ChatListViewBody extends StatelessWidget { ActiveFilter.allChats, ActiveFilter.unread, ActiveFilter.groups, - if (spaceDelegateCandidates.isNotEmpty) + if (spaceDelegateCandidates.isNotEmpty && + !controller.widget.displayNavigationRail) ActiveFilter.spaces, ] .map( diff --git a/lib/pages/chat_list/chat_list_item.dart b/lib/pages/chat_list/chat_list_item.dart index a146a076fb..f704c41bf7 100644 --- a/lib/pages/chat_list/chat_list_item.dart +++ b/lib/pages/chat_list/chat_list_item.dart @@ -171,12 +171,12 @@ class ChatListItem extends StatelessWidget { ), ), Positioned( - top: -2, - right: -2, + top: 0, + right: 0, child: AnimatedScale( duration: FluffyThemes.animationDuration, curve: FluffyThemes.animationCurve, - scale: listTileHovered ? 1.1 : 1.0, + scale: listTileHovered ? 1.0 : 0.0, child: Material( color: backgroundColor, borderRadius: BorderRadius.circular(16), diff --git a/lib/pages/chat_list/chat_list_view.dart b/lib/pages/chat_list/chat_list_view.dart index 93fa8575b2..5bb6687a05 100644 --- a/lib/pages/chat_list/chat_list_view.dart +++ b/lib/pages/chat_list/chat_list_view.dart @@ -5,7 +5,12 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:go_router/go_router.dart'; import 'package:keyboard_shortcuts/keyboard_shortcuts.dart'; +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat_list/chat_list.dart'; +import 'package:fluffychat/pages/chat_list/navi_rail_item.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; +import 'package:fluffychat/widgets/avatar.dart'; import '../../widgets/matrix.dart'; import 'chat_list_body.dart'; @@ -35,32 +40,113 @@ class ChatListView extends StatelessWidget { return; } }, - child: GestureDetector( - onTap: FocusManager.instance.primaryFocus?.unfocus, - excludeFromSemantics: true, - behavior: HitTestBehavior.translucent, - child: Scaffold( - body: ChatListViewBody(controller), - floatingActionButton: KeyBoardShortcuts( - keysToPress: { - LogicalKeyboardKey.controlLeft, - LogicalKeyboardKey.keyN, - }, - onKeysPressed: () => context.go('/rooms/newprivatechat'), - helpLabel: L10n.of(context)!.newChat, - child: - selectMode == SelectMode.normal && !controller.isSearchMode - ? FloatingActionButton.extended( - onPressed: controller.addChatAction, - icon: const Icon(Icons.add_outlined), - label: Text( - L10n.of(context)!.chat, - overflow: TextOverflow.fade, + child: Row( + children: [ + if (FluffyThemes.isColumnMode(context) && + controller.widget.displayNavigationRail) ...[ + Builder( + builder: (context) { + final allSpaces = Matrix.of(context) + .client + .rooms + .where((room) => room.isSpace); + final rootSpaces = allSpaces + .where( + (space) => !allSpaces.any( + (parentSpace) => parentSpace.spaceChildren + .any((child) => child.roomId == space.id), + ), + ) + .toList(); + + return SizedBox( + width: FluffyThemes.navRailWidth, + child: ListView.builder( + scrollDirection: Axis.vertical, + itemCount: rootSpaces.length + 2, + itemBuilder: (context, i) { + if (i == 0) { + return NaviRailItem( + isSelected: controller.activeSpaceId == null, + onTap: controller.clearActiveSpace, + icon: const Icon(Icons.forum_outlined), + selectedIcon: const Icon(Icons.forum), + toolTip: L10n.of(context)!.chats, + unreadBadgeFilter: (room) => true, + ); + } + i--; + if (i == rootSpaces.length) { + return NaviRailItem( + isSelected: false, + onTap: () => context.go('/rooms/newspace'), + icon: const Icon(Icons.add), + toolTip: L10n.of(context)!.createNewSpace, + ); + } + final space = rootSpaces[i]; + final displayname = + rootSpaces[i].getLocalizedDisplayname( + MatrixLocals(L10n.of(context)!), + ); + final spaceChildrenIds = + space.spaceChildren.map((c) => c.roomId).toSet(); + return NaviRailItem( + toolTip: displayname, + isSelected: controller.activeSpaceId == space.id, + onTap: () => + controller.setActiveSpace(rootSpaces[i].id), + unreadBadgeFilter: (room) => + spaceChildrenIds.contains(room.id), + icon: Avatar( + mxContent: rootSpaces[i].avatar, + name: displayname, + size: 32, + borderRadius: BorderRadius.circular( + AppConfig.borderRadius / 4, + ), ), - ) - : const SizedBox.shrink(), + ); + }, + ), + ); + }, + ), + Container( + color: Theme.of(context).dividerColor, + width: 1, + ), + ], + Expanded( + child: GestureDetector( + onTap: FocusManager.instance.primaryFocus?.unfocus, + excludeFromSemantics: true, + behavior: HitTestBehavior.translucent, + child: Scaffold( + body: ChatListViewBody(controller), + floatingActionButton: KeyBoardShortcuts( + keysToPress: { + LogicalKeyboardKey.controlLeft, + LogicalKeyboardKey.keyN, + }, + onKeysPressed: () => context.go('/rooms/newprivatechat'), + helpLabel: L10n.of(context)!.newChat, + child: selectMode == SelectMode.normal && + !controller.isSearchMode + ? FloatingActionButton.extended( + onPressed: controller.addChatAction, + icon: const Icon(Icons.add_outlined), + label: Text( + L10n.of(context)!.chat, + overflow: TextOverflow.fade, + ), + ) + : const SizedBox.shrink(), + ), + ), + ), ), - ), + ], ), ); }, diff --git a/lib/pages/chat_list/nav_rail_item.dart b/lib/pages/chat_list/nav_rail_item.dart new file mode 100644 index 0000000000..d09659f886 --- /dev/null +++ b/lib/pages/chat_list/nav_rail_item.dart @@ -0,0 +1,95 @@ +import 'package:flutter/material.dart'; + +import 'package:fluffychat/config/app_config.dart'; +import '../../config/themes.dart'; + +class NaviRailItem extends StatefulWidget { + final String toolTip; + final bool isSelected; + final void Function() onTap; + final Widget icon; + final Widget? selectedIcon; + + const NaviRailItem({ + required this.toolTip, + required this.isSelected, + required this.onTap, + required this.icon, + this.selectedIcon, + super.key, + }); + + @override + State createState() => _NaviRailItemState(); +} + +class _NaviRailItemState extends State { + bool _hovered = false; + + void _onHover(bool hover) { + if (hover == _hovered) return; + setState(() { + _hovered = hover; + }); + } + + @override + Widget build(BuildContext context) { + final borderRadius = BorderRadius.circular(AppConfig.borderRadius); + return SizedBox( + height: 64, + width: 64, + child: Stack( + children: [ + Positioned( + top: 16, + bottom: 16, + left: 0, + child: AnimatedContainer( + width: widget.isSelected ? 4 : 0, + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary, + borderRadius: const BorderRadius.only( + topRight: Radius.circular(90), + bottomRight: Radius.circular(90), + ), + ), + ), + ), + Center( + child: AnimatedScale( + scale: _hovered ? 1.2 : 1.0, + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + child: Material( + borderRadius: borderRadius, + color: widget.isSelected + ? Theme.of(context).colorScheme.primaryContainer + : Theme.of(context).colorScheme.surface, + child: Tooltip( + message: widget.toolTip, + child: InkWell( + borderRadius: borderRadius, + onTap: widget.onTap, + onHover: _onHover, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 8.0, + ), + child: widget.isSelected + ? widget.selectedIcon ?? widget.icon + : widget.icon, + ), + ), + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/chat_list/navi_rail_item.dart b/lib/pages/chat_list/navi_rail_item.dart index d09659f886..66ad7c0418 100644 --- a/lib/pages/chat_list/navi_rail_item.dart +++ b/lib/pages/chat_list/navi_rail_item.dart @@ -1,14 +1,20 @@ import 'package:flutter/material.dart'; +import 'package:badges/badges.dart'; +import 'package:matrix/matrix.dart'; + import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/widgets/hover_builder.dart'; +import 'package:fluffychat/widgets/unread_rooms_badge.dart'; import '../../config/themes.dart'; -class NaviRailItem extends StatefulWidget { +class NaviRailItem extends StatelessWidget { final String toolTip; final bool isSelected; final void Function() onTap; final Widget icon; final Widget? selectedIcon; + final bool Function(Room)? unreadBadgeFilter; const NaviRailItem({ required this.toolTip, @@ -16,80 +22,78 @@ class NaviRailItem extends StatefulWidget { required this.onTap, required this.icon, this.selectedIcon, + this.unreadBadgeFilter, super.key, }); - - @override - State createState() => _NaviRailItemState(); -} - -class _NaviRailItemState extends State { - bool _hovered = false; - - void _onHover(bool hover) { - if (hover == _hovered) return; - setState(() { - _hovered = hover; - }); - } - @override Widget build(BuildContext context) { final borderRadius = BorderRadius.circular(AppConfig.borderRadius); - return SizedBox( - height: 64, - width: 64, - child: Stack( - children: [ - Positioned( - top: 16, - bottom: 16, - left: 0, - child: AnimatedContainer( - width: widget.isSelected ? 4 : 0, - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary, - borderRadius: const BorderRadius.only( - topRight: Radius.circular(90), - bottomRight: Radius.circular(90), + final icon = isSelected ? selectedIcon ?? this.icon : this.icon; + final unreadBadgeFilter = this.unreadBadgeFilter; + return HoverBuilder( + builder: (context, hovered) { + return SizedBox( + height: 64, + width: 64, + child: Stack( + children: [ + Positioned( + top: 16, + bottom: 16, + left: 0, + child: AnimatedContainer( + width: isSelected ? 4 : 0, + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary, + borderRadius: const BorderRadius.only( + topRight: Radius.circular(90), + bottomRight: Radius.circular(90), + ), + ), ), ), - ), - ), - Center( - child: AnimatedScale( - scale: _hovered ? 1.2 : 1.0, - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - child: Material( - borderRadius: borderRadius, - color: widget.isSelected - ? Theme.of(context).colorScheme.primaryContainer - : Theme.of(context).colorScheme.surface, - child: Tooltip( - message: widget.toolTip, - child: InkWell( + Center( + child: AnimatedScale( + scale: hovered ? 1.2 : 1.0, + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + child: Material( borderRadius: borderRadius, - onTap: widget.onTap, - onHover: _onHover, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, - vertical: 8.0, + color: isSelected + ? Theme.of(context).colorScheme.primaryContainer + : Theme.of(context).colorScheme.surface, + child: Tooltip( + message: toolTip, + child: InkWell( + borderRadius: borderRadius, + onTap: onTap, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 8.0, + ), + child: unreadBadgeFilter == null + ? icon + : UnreadRoomsBadge( + filter: unreadBadgeFilter, + badgePosition: BadgePosition.topEnd( + top: -12, + end: -8, + ), + child: icon, + ), + ), ), - child: widget.isSelected - ? widget.selectedIcon ?? widget.icon - : widget.icon, ), ), ), ), - ), + ], ), - ], - ), + ); + }, ); } } diff --git a/lib/widgets/layouts/two_column_layout.dart b/lib/widgets/layouts/two_column_layout.dart index c270f120a0..a6f4c8bdf6 100644 --- a/lib/widgets/layouts/two_column_layout.dart +++ b/lib/widgets/layouts/two_column_layout.dart @@ -3,11 +3,13 @@ import 'package:flutter/material.dart'; class TwoColumnLayout extends StatelessWidget { final Widget mainView; final Widget sideView; + final bool displayNavigationRail; const TwoColumnLayout({ super.key, required this.mainView, required this.sideView, + required this.displayNavigationRail, }); @override Widget build(BuildContext context) { @@ -18,7 +20,7 @@ class TwoColumnLayout extends StatelessWidget { Container( clipBehavior: Clip.antiAlias, decoration: const BoxDecoration(), - width: 384.0, + width: 360.0 + (displayNavigationRail ? 64 : 0), child: mainView, ), Container( diff --git a/lib/widgets/unread_rooms_badge.dart b/lib/widgets/unread_rooms_badge.dart index 0066917697..5270c0db35 100644 --- a/lib/widgets/unread_rooms_badge.dart +++ b/lib/widgets/unread_rooms_badge.dart @@ -19,41 +19,32 @@ class UnreadRoomsBadge extends StatelessWidget { @override Widget build(BuildContext context) { - return StreamBuilder( - stream: Matrix.of(context) - .client - .onSync - .stream - .where((syncUpdate) => syncUpdate.hasRoomUpdate), - builder: (context, _) { - final unreadCount = Matrix.of(context) - .client - .rooms - .where(filter) - .where((r) => (r.isUnread || r.membership == Membership.invite)) - .length; - return b.Badge( - badgeStyle: b.BadgeStyle( - badgeColor: Theme.of(context).colorScheme.primary, - elevation: 4, - borderSide: BorderSide( - color: Theme.of(context).colorScheme.surface, - width: 2, - ), - ), - badgeContent: Text( - unreadCount.toString(), - style: TextStyle( - color: Theme.of(context).colorScheme.onPrimary, - fontSize: 12, - ), - ), - showBadge: unreadCount != 0, - badgeAnimation: const b.BadgeAnimation.scale(), - position: badgePosition ?? b.BadgePosition.bottomEnd(), - child: child, - ); - }, + final unreadCount = Matrix.of(context) + .client + .rooms + .where(filter) + .where((r) => (r.isUnread || r.membership == Membership.invite)) + .length; + return b.Badge( + badgeStyle: b.BadgeStyle( + badgeColor: Theme.of(context).colorScheme.primary, + elevation: 4, + borderSide: BorderSide( + color: Theme.of(context).colorScheme.surface, + width: 2, + ), + ), + badgeContent: Text( + unreadCount.toString(), + style: TextStyle( + color: Theme.of(context).colorScheme.onPrimary, + fontSize: 12, + ), + ), + showBadge: unreadCount != 0, + badgeAnimation: const b.BadgeAnimation.scale(), + position: badgePosition ?? b.BadgePosition.bottomEnd(), + child: child, ); } } diff --git a/linux/my_application.cc b/linux/my_application.cc index 0abe77c60c..c185bcd785 100644 --- a/linux/my_application.cc +++ b/linux/my_application.cc @@ -60,7 +60,7 @@ static void my_application_activate(GApplication* application) { gtk_window_set_title(window, "FluffyChat"); } - gtk_window_set_default_size(window, 800, 600); + gtk_window_set_default_size(window, 864, 680); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); From 467d103c2f540ece1ed13c7d912cd5e73a9ac490 Mon Sep 17 00:00:00 2001 From: Krille Date: Tue, 16 Jul 2024 07:31:43 +0200 Subject: [PATCH 065/288] chore: Follow up chat context menu --- lib/pages/chat_list/chat_list.dart | 90 +++++++++++++++++------------- 1 file changed, 50 insertions(+), 40 deletions(-) diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index d4f6067edc..50237a569c 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -620,6 +620,10 @@ class ChatListController extends State BuildContext posContext, [ Room? space, ]) async { + if (room.membership == Membership.invite) { + return onChatTap(room); + } + final overlay = Overlay.of(posContext).context.findRenderObject() as RenderBox; @@ -644,7 +648,7 @@ class ChatListController extends State position: position, items: [ PopupMenuItem( - enabled: false, + value: ChatContextAction.open, child: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -754,46 +758,51 @@ class ChatListController extends State if (action == null) return; if (!mounted) return; - if (action == ChatContextAction.goToSpace) { - setActiveSpace(space!.id); - return; - } - - if (action == ChatContextAction.leave) { - final confirmed = await showOkCancelAlertDialog( - useRootNavigator: false, - context: context, - title: L10n.of(context)!.areYouSure, - okLabel: L10n.of(context)!.leave, - cancelLabel: L10n.of(context)!.no, - message: L10n.of(context)!.archiveRoomDescription, - isDestructiveAction: true, - ); - if (confirmed == OkCancelResult.cancel) return; - } - if (!mounted) return; - - await showFutureLoadingDialog( - context: context, - future: () async { - switch (action) { - case ChatContextAction.goToSpace: - return; - case ChatContextAction.favorite: - return room.setFavourite(!room.isFavourite); - case ChatContextAction.markUnread: - return room.markUnread(!room.markedUnread); - case ChatContextAction.mute: - return room.setPushRuleState( - room.pushRuleState == PushRuleState.notify - ? PushRuleState.mentionsOnly - : PushRuleState.notify, - ); - case ChatContextAction.leave: - return room.leave(); + switch (action) { + case ChatContextAction.open: + onChatTap(room); + return; + case ChatContextAction.goToSpace: + setActiveSpace(space!.id); + return; + case ChatContextAction.favorite: + await showFutureLoadingDialog( + context: context, + future: () => room.setFavourite(!room.isFavourite), + ); + return; + case ChatContextAction.markUnread: + await showFutureLoadingDialog( + context: context, + future: () => room.markUnread(!room.markedUnread), + ); + return; + case ChatContextAction.mute: + await showFutureLoadingDialog( + context: context, + future: () => room.setPushRuleState( + room.pushRuleState == PushRuleState.notify + ? PushRuleState.mentionsOnly + : PushRuleState.notify, + ), + ); + return; + case ChatContextAction.leave: + final confirmed = await showOkCancelAlertDialog( + useRootNavigator: false, + context: context, + title: L10n.of(context)!.areYouSure, + okLabel: L10n.of(context)!.leave, + cancelLabel: L10n.of(context)!.no, + message: L10n.of(context)!.archiveRoomDescription, + isDestructiveAction: true, + ); + if (confirmed == OkCancelResult.cancel) return; + if (!mounted) { + await showFutureLoadingDialog(context: context, future: room.leave); } - }, - ); + return; + } } void dismissStatusList() async { @@ -993,6 +1002,7 @@ enum InviteActions { enum AddRoomType { chat, subspace } enum ChatContextAction { + open, goToSpace, favorite, markUnread, From 0bf387da14c562eae21b36897a53a300ea5b6717 Mon Sep 17 00:00:00 2001 From: Krille Date: Tue, 16 Jul 2024 08:48:45 +0200 Subject: [PATCH 066/288] build: Update android target sdk to 34 --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index aa8b764942..fba1b51896 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -45,7 +45,7 @@ android { defaultConfig { applicationId "chat.fluffy.fluffychat" minSdkVersion 21 - targetSdkVersion 33 + targetSdkVersion 34 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" From 6ccebbcf121a39959b3b97640be87a6d67d39999 Mon Sep 17 00:00:00 2001 From: Rex_sa Date: Mon, 15 Jul 2024 21:08:30 +0000 Subject: [PATCH 067/288] Translated using Weblate (Arabic) Currently translated at 100.0% (642 of 642 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ar/ --- assets/l10n/intl_ar.arb | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/assets/l10n/intl_ar.arb b/assets/l10n/intl_ar.arb index eff3e2516b..126dd8b5c9 100644 --- a/assets/l10n/intl_ar.arb +++ b/assets/l10n/intl_ar.arb @@ -736,7 +736,7 @@ "type": "text", "placeholders": {} }, - "oopsSomethingWentWrong": "عذراً، هناك خطأ ما…", + "oopsSomethingWentWrong": "عفوًا، حدث خطأ ما…", "@oopsSomethingWentWrong": { "type": "text", "placeholders": {} @@ -2260,7 +2260,7 @@ }, "jump": "قفز", "@jump": {}, - "report": "التقرير", + "report": "تقرير", "@report": {}, "noKeyForThisMessage": "يمكن أن يحدث هذا إذا تم إرسال الرسالة قبل تسجيل الدخول إلى حسابك على هذا الجهاز.\n\nمن الممكن أيضا أن يكون المرسل قد حظر جهازك أو حدث خطأ ما في الاتصال بالإنترنت.\n\nهل يمكنك قراءة الرسالة في جلسة أخرى؟ ثم يمكنك نقل الرسالة منه! انتقل إلى الإعدادات > الأجهزة وتأكد من أن أجهزتك قد تحققت من بعضها البعض. عندما تفتح الغرفة في المرة التالية وتكون كلتا الجلستين في المقدمة ، سيتم إرسال المفاتيح تلقائيا.\n\nألا تريد أن تفقد المفاتيح عند تسجيل الخروج أو تبديل الأجهزة؟ تأكد من تمكين النسخ الاحتياطي للدردشة في الإعدادات.", "@noKeyForThisMessage": {}, @@ -2712,5 +2712,30 @@ "alwaysUse24HourFormat": "خاطئ", "@alwaysUse24HourFormat": { "description": "Set to true to always display time of day in 24 hour format." + }, + "countChatsAndCountParticipants": "{chats} دردشات و {participants} مشاركين", + "@countChatsAndCountParticipants": { + "type": "text", + "placeholders": { + "chats": {}, + "participants": {} + } + }, + "noMoreChatsFound": "لم يتم العثور على دردشات...", + "@noMoreChatsFound": {}, + "joinedChats": "انضم إلى الدردشة", + "@joinedChats": {}, + "unread": "غير المقروءة", + "@unread": {}, + "space": "المساحة", + "@space": {}, + "spaces": "المساحات", + "@spaces": {}, + "markAsUnread": "تحديد كغير مقروء", + "@markAsUnread": {}, + "goToSpace": "انتقل إلى المساحة: {space}", + "@goToSpace": { + "type": "text", + "space": {} } } From 146be1bcdcef6ac9e1ae3b66fb7d2ebc187bc9ca Mon Sep 17 00:00:00 2001 From: Christian Date: Mon, 15 Jul 2024 13:39:33 +0000 Subject: [PATCH 068/288] Translated using Weblate (German) Currently translated at 99.6% (640 of 642 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/de/ --- assets/l10n/intl_de.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/l10n/intl_de.arb b/assets/l10n/intl_de.arb index cb0c713d9a..9e8d2fc677 100644 --- a/assets/l10n/intl_de.arb +++ b/assets/l10n/intl_de.arb @@ -1712,7 +1712,7 @@ "type": "text", "placeholders": {} }, - "unpin": "Abpinnen", + "unpin": "Nicht mehr anpinnen", "@unpin": { "type": "text", "placeholders": {} From 92e740bdc502b264254844203c1816cec421252e Mon Sep 17 00:00:00 2001 From: xabirequejo Date: Tue, 16 Jul 2024 06:16:30 +0000 Subject: [PATCH 069/288] Translated using Weblate (Basque) Currently translated at 100.0% (642 of 642 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/ --- assets/l10n/intl_eu.arb | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/assets/l10n/intl_eu.arb b/assets/l10n/intl_eu.arb index 12ad1c53d8..cefc59b531 100644 --- a/assets/l10n/intl_eu.arb +++ b/assets/l10n/intl_eu.arb @@ -2547,7 +2547,7 @@ "sender": {} } }, - "sendReadReceiptsDescription": "Txateko beste kideek mezu bat irakurri duzula ikus dezakete.", + "sendReadReceiptsDescription": "Txateko beste partaideek mezu bat irakurri duzula ikus dezakete.", "@sendReadReceiptsDescription": {}, "forwardMessageTo": "Birbidali mezua {roomName}(e)ra?", "@forwardMessageTo": { @@ -2576,7 +2576,7 @@ "@verifyOtherUserDescription": {}, "formattedMessagesDescription": "Erakutsi mezu aberatsen edukia markdown erabiliz, testu lodia esaterako.", "@formattedMessagesDescription": {}, - "sendTypingNotificationsDescription": "Txateko beste kideek mezu berri bat idazten ari zarela ikus dezakete.", + "sendTypingNotificationsDescription": "Txateko beste partaideek mezu berri bat idazten ari zarela ikus dezakete.", "@sendTypingNotificationsDescription": {}, "verifyOtherUser": "🔐 Egiaztatu beste erabiltzaile bat", "@verifyOtherUser": {}, @@ -2712,5 +2712,30 @@ "alwaysUse24HourFormat": "ez", "@alwaysUse24HourFormat": { "description": "Set to true to always display time of day in 24 hour format." - } + }, + "noMoreChatsFound": "Ez da beste txatik aurkitu...", + "@noMoreChatsFound": {}, + "unread": "Irakurri gabe", + "@unread": {}, + "space": "Gunea", + "@space": {}, + "joinedChats": "Batu zaren txatak", + "@joinedChats": {}, + "goToSpace": "Joan {space} gunera", + "@goToSpace": { + "type": "text", + "space": {} + }, + "markAsUnread": "Markatu irakurri gabetzat", + "@markAsUnread": {}, + "countChatsAndCountParticipants": "{chats} txat eta {participants} partaide", + "@countChatsAndCountParticipants": { + "type": "text", + "placeholders": { + "chats": {}, + "participants": {} + } + }, + "spaces": "Guneak", + "@spaces": {} } From b1cfa00d83c3cb927277fb171ac3d9598d9af4ad Mon Sep 17 00:00:00 2001 From: Sovkipyk Date: Mon, 15 Jul 2024 19:57:57 +0000 Subject: [PATCH 070/288] Translated using Weblate (French) Currently translated at 99.8% (641 of 642 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/fr/ --- assets/l10n/intl_fr.arb | 5118 +++++++++++++++++++++------------------ 1 file changed, 2740 insertions(+), 2378 deletions(-) diff --git a/assets/l10n/intl_fr.arb b/assets/l10n/intl_fr.arb index ba0a826348..7c8c317e2e 100644 --- a/assets/l10n/intl_fr.arb +++ b/assets/l10n/intl_fr.arb @@ -1,2379 +1,2741 @@ { - "@@locale": "fr", - "@@last_modified": "2021-08-14 12:41:10.051787", - "about": "À propos", - "@about": { - "type": "text", - "placeholders": {} - }, - "accept": "Accepter", - "@accept": { - "type": "text", - "placeholders": {} - }, - "acceptedTheInvitation": "👍 {username} a accepté l'invitation", - "@acceptedTheInvitation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "account": "Compte", - "@account": { - "type": "text", - "placeholders": {} - }, - "activatedEndToEndEncryption": "🔐 {username} a activé le chiffrement de bout en bout", - "@activatedEndToEndEncryption": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "addEmail": "Ajouter un courriel", - "@addEmail": { - "type": "text", - "placeholders": {} - }, - "addToSpace": "Ajouter à l'espace", - "@addToSpace": {}, - "admin": "Administrateur", - "@admin": { - "type": "text", - "placeholders": {} - }, - "alias": "adresse", - "@alias": { - "type": "text", - "placeholders": {} - }, - "all": "Tout", - "@all": { - "type": "text", - "placeholders": {} - }, - "allChats": "Toutes les discussions", - "@allChats": { - "type": "text", - "placeholders": {} - }, - "answeredTheCall": "{senderName} a répondu à l'appel", - "@answeredTheCall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "anyoneCanJoin": "Tout le monde peut rejoindre", - "@anyoneCanJoin": { - "type": "text", - "placeholders": {} - }, - "appLock": "Verrouillage de l’application", - "@appLock": { - "type": "text", - "placeholders": {} - }, - "archive": "Archiver", - "@archive": { - "type": "text", - "placeholders": {} - }, - "areGuestsAllowedToJoin": "Les invités peuvent-i·e·ls rejoindre", - "@areGuestsAllowedToJoin": { - "type": "text", - "placeholders": {} - }, - "areYouSure": "Êtes-vous sûr·e ?", - "@areYouSure": { - "type": "text", - "placeholders": {} - }, - "areYouSureYouWantToLogout": "Voulez-vous vraiment vous déconnecter ?", - "@areYouSureYouWantToLogout": { - "type": "text", - "placeholders": {} - }, - "askSSSSSign": "Pour pouvoir faire signer l'autre personne, veuillez entrer la phrase de passe de votre trousseau sécurisé ou votre clé de récupération.", - "@askSSSSSign": { - "type": "text", - "placeholders": {} - }, - "askVerificationRequest": "Accepter cette demande de vérification de la part de {username} ?", - "@askVerificationRequest": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "autoplayImages": "Lire automatiquement les autocollants et les émojis animés", - "@autoplayImages": { - "type": "text", - "placeholder": {} - }, - "badServerLoginTypesException": "Le serveur d'accueil prend en charge les types de connexion :\n{serverVersions}\nMais cette application ne prend en charge que :\n{supportedVersions}", - "@badServerLoginTypesException": { - "type": "text", - "placeholders": { - "serverVersions": {}, - "supportedVersions": {} - } - }, - "badServerVersionsException": "Le serveur d'accueil prend en charge les versions des spécifications :\n{serverVersions}\nMais cette application ne prend en charge que {supportedVersions}", - "@badServerVersionsException": { - "type": "text", - "placeholders": { - "serverVersions": {}, - "supportedVersions": {} - } - }, - "banFromChat": "Bannir de la discussion", - "@banFromChat": { - "type": "text", - "placeholders": {} - }, - "banned": "Banni", - "@banned": { - "type": "text", - "placeholders": {} - }, - "bannedUser": "{username} a banni {targetName}", - "@bannedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "blockDevice": "Bloquer l'appareil", - "@blockDevice": { - "type": "text", - "placeholders": {} - }, - "blocked": "Bloqué", - "@blocked": { - "type": "text", - "placeholders": {} - }, - "botMessages": "Messages de bot", - "@botMessages": { - "type": "text", - "placeholders": {} - }, - "cancel": "Annuler", - "@cancel": { - "type": "text", - "placeholders": {} - }, - "cantOpenUri": "Impossible d'ouvrir l'URI {uri}", - "@cantOpenUri": { - "type": "text", - "placeholders": { - "uri": {} - } - }, - "changeDeviceName": "Modifier le nom de l'appareil", - "@changeDeviceName": { - "type": "text", - "placeholders": {} - }, - "changedTheChatAvatar": "{username} a changé l'image de la discussion", - "@changedTheChatAvatar": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheChatDescriptionTo": "{username} a changé la description de la discussion en : '{description}'", - "@changedTheChatDescriptionTo": { - "type": "text", - "placeholders": { - "username": {}, - "description": {} - } - }, - "changedTheChatNameTo": "{username} a renommé la discussion en : '{chatname}'", - "@changedTheChatNameTo": { - "type": "text", - "placeholders": { - "username": {}, - "chatname": {} - } - }, - "changedTheChatPermissions": "{username} a changé les permissions de la discussion", - "@changedTheChatPermissions": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheDisplaynameTo": "{username} a changé son nom en : '{displayname}'", - "@changedTheDisplaynameTo": { - "type": "text", - "placeholders": { - "username": {}, - "displayname": {} - } - }, - "changedTheGuestAccessRules": "{username} a changé les règles d'accès à la discussion pour les invités", - "@changedTheGuestAccessRules": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheGuestAccessRulesTo": "{username} a changé les règles d'accès à la discussion pour les invités en : {rules}", - "@changedTheGuestAccessRulesTo": { - "type": "text", - "placeholders": { - "username": {}, - "rules": {} - } - }, - "changedTheHistoryVisibility": "{username} a changé la visibilité de l'historique de la discussion", - "@changedTheHistoryVisibility": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheHistoryVisibilityTo": "{username} a changé la visibilité de l'historique de la discussion en : {rules}", - "@changedTheHistoryVisibilityTo": { - "type": "text", - "placeholders": { - "username": {}, - "rules": {} - } - }, - "changedTheJoinRules": "{username} a changé les règles d'accès à la discussion", - "@changedTheJoinRules": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheJoinRulesTo": "{username} a changé les règles d'accès à la discussion en : {joinRules}", - "@changedTheJoinRulesTo": { - "type": "text", - "placeholders": { - "username": {}, - "joinRules": {} - } - }, - "changedTheProfileAvatar": "{username} a changé son avatar", - "@changedTheProfileAvatar": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheRoomAliases": "{username} a changé les adresses du salon", - "@changedTheRoomAliases": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheRoomInvitationLink": "{username} a changé le lien d'invitation", - "@changedTheRoomInvitationLink": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changePassword": "Changer de mot de passe", - "@changePassword": { - "type": "text", - "placeholders": {} - }, - "changeTheHomeserver": "Changer le serveur d'accueil", - "@changeTheHomeserver": { - "type": "text", - "placeholders": {} - }, - "changeTheme": "Changez votre style", - "@changeTheme": { - "type": "text", - "placeholders": {} - }, - "changeTheNameOfTheGroup": "Changer le nom du groupe", - "@changeTheNameOfTheGroup": { - "type": "text", - "placeholders": {} - }, - "changeYourAvatar": "Changer votre avatar", - "@changeYourAvatar": { - "type": "text", - "placeholders": {} - }, - "channelCorruptedDecryptError": "Le chiffrement a été corrompu", - "@channelCorruptedDecryptError": { - "type": "text", - "placeholders": {} - }, - "chat": "Discussion", - "@chat": { - "type": "text", - "placeholders": {} - }, - "chatBackup": "Sauvegarde des discussions", - "@chatBackup": { - "type": "text", - "placeholders": {} - }, - "chatBackupDescription": "Vos anciens messages sont sécurisés par une clé de récupération. Veillez à ne pas la perdre.", - "@chatBackupDescription": { - "type": "text", - "placeholders": {} - }, - "chatDetails": "Détails de la discussion", - "@chatDetails": { - "type": "text", - "placeholders": {} - }, - "chatHasBeenAddedToThisSpace": "La discussion a été ajoutée à cet espace", - "@chatHasBeenAddedToThisSpace": {}, - "chats": "Discussions", - "@chats": { - "type": "text", - "placeholders": {} - }, - "chooseAStrongPassword": "Choisissez un mot de passe fort", - "@chooseAStrongPassword": { - "type": "text", - "placeholders": {} - }, - "clearArchive": "Effacer les archives", - "@clearArchive": {}, - "close": "Fermer", - "@close": { - "type": "text", - "placeholders": {} - }, - "commandHint_ban": "Bannir l'utilisateur/trice donné(e) de ce salon", - "@commandHint_ban": { - "type": "text", - "description": "Usage hint for the command /ban" - }, - "commandHint_html": "Envoyer du texte au format HTML", - "@commandHint_html": { - "type": "text", - "description": "Usage hint for the command /html" - }, - "commandHint_invite": "Inviter l'utilisateur/trice donné(e) dans ce salon", - "@commandHint_invite": { - "type": "text", - "description": "Usage hint for the command /invite" - }, - "commandHint_join": "Rejoindre le salon donné", - "@commandHint_join": { - "type": "text", - "description": "Usage hint for the command /join" - }, - "commandHint_kick": "Supprime l'utilisateur/trice donné(e) de ce salon", - "@commandHint_kick": { - "type": "text", - "description": "Usage hint for the command /kick" - }, - "commandHint_leave": "Quitter ce salon", - "@commandHint_leave": { - "type": "text", - "description": "Usage hint for the command /leave" - }, - "commandHint_me": "Décrivez-vous", - "@commandHint_me": { - "type": "text", - "description": "Usage hint for the command /me" - }, - "commandHint_myroomavatar": "Définir votre image pour ce salon (par mxc-uri)", - "@commandHint_myroomavatar": { - "type": "text", - "description": "Usage hint for the command /myroomavatar" - }, - "commandHint_myroomnick": "Définir votre nom d'affichage pour ce salon", - "@commandHint_myroomnick": { - "type": "text", - "description": "Usage hint for the command /myroomnick" - }, - "commandHint_op": "Définir le niveau de puissance de l'utilisateur/trice donné(e) (par défaut : 50)", - "@commandHint_op": { - "type": "text", - "description": "Usage hint for the command /op" - }, - "commandHint_plain": "Envoyer du texte non formaté", - "@commandHint_plain": { - "type": "text", - "description": "Usage hint for the command /plain" - }, - "commandHint_react": "Envoyer une réponse en tant que réaction", - "@commandHint_react": { - "type": "text", - "description": "Usage hint for the command /react" - }, - "commandHint_send": "Envoyer du texte", - "@commandHint_send": { - "type": "text", - "description": "Usage hint for the command /send" - }, - "commandHint_unban": "Débannir l'utilisateur/trice donné(e) de ce salon", - "@commandHint_unban": { - "type": "text", - "description": "Usage hint for the command /unban" - }, - "commandInvalid": "Commande invalide", - "@commandInvalid": { - "type": "text" - }, - "commandMissing": "{command} n'est pas une commande.", - "@commandMissing": { - "type": "text", - "placeholders": { - "command": {} - }, - "description": "State that {command} is not a valid /command." - }, - "compareEmojiMatch": "Veuillez comparer les émojis", - "@compareEmojiMatch": { - "type": "text", - "placeholders": {} - }, - "compareNumbersMatch": "Veuillez comparer les chiffres", - "@compareNumbersMatch": { - "type": "text", - "placeholders": {} - }, - "configureChat": "Configurer la discussion", - "@configureChat": { - "type": "text", - "placeholders": {} - }, - "confirm": "Confirmer", - "@confirm": { - "type": "text", - "placeholders": {} - }, - "connect": "Se connecter", - "@connect": { - "type": "text", - "placeholders": {} - }, - "contactHasBeenInvitedToTheGroup": "Le contact a été invité au groupe", - "@contactHasBeenInvitedToTheGroup": { - "type": "text", - "placeholders": {} - }, - "containsDisplayName": "Contient un nom d'affichage", - "@containsDisplayName": { - "type": "text", - "placeholders": {} - }, - "containsUserName": "Contient un nom d'utilisateur·ice", - "@containsUserName": { - "type": "text", - "placeholders": {} - }, - "contentHasBeenReported": "Le contenu a été signalé aux administrateurs du serveur", - "@contentHasBeenReported": { - "type": "text", - "placeholders": {} - }, - "copiedToClipboard": "Copié dans le presse-papier", - "@copiedToClipboard": { - "type": "text", - "placeholders": {} - }, - "copy": "Copier", - "@copy": { - "type": "text", - "placeholders": {} - }, - "copyToClipboard": "Copier dans le presse-papiers", - "@copyToClipboard": { - "type": "text", - "placeholders": {} - }, - "couldNotDecryptMessage": "Impossible de déchiffrer le message : {error}", - "@couldNotDecryptMessage": { - "type": "text", - "placeholders": { - "error": {} - } - }, - "countParticipants": "{count} participant(s)", - "@countParticipants": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "create": "Créer", - "@create": { - "type": "text", - "placeholders": {} - }, - "createdTheChat": "💬 {username} a créé la discussion", - "@createdTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "createNewSpace": "Nouvel espace", - "@createNewSpace": { - "type": "text", - "placeholders": {} - }, - "currentlyActive": "Actif en ce moment", - "@currentlyActive": { - "type": "text", - "placeholders": {} - }, - "darkTheme": "Sombre", - "@darkTheme": { - "type": "text", - "placeholders": {} - }, - "dateAndTimeOfDay": "{date}, {timeOfDay}", - "@dateAndTimeOfDay": { - "type": "text", - "placeholders": { - "date": {}, - "timeOfDay": {} - } - }, - "dateWithoutYear": "{day}/{month}", - "@dateWithoutYear": { - "type": "text", - "placeholders": { - "month": {}, - "day": {} - } - }, - "dateWithYear": "{day}/{month}/{year}", - "@dateWithYear": { - "type": "text", - "placeholders": { - "year": {}, - "month": {}, - "day": {} - } - }, - "deactivateAccountWarning": "Cette opération va désactiver votre compte. Une fois cette action effectuée, aucun retour en arrière n'est possible ! Êtes-vous sûr·e ?", - "@deactivateAccountWarning": { - "type": "text", - "placeholders": {} - }, - "defaultPermissionLevel": "Niveau d'autorisation par défaut", - "@defaultPermissionLevel": { - "type": "text", - "placeholders": {} - }, - "delete": "Supprimer", - "@delete": { - "type": "text", - "placeholders": {} - }, - "deleteAccount": "Supprimer le compte", - "@deleteAccount": { - "type": "text", - "placeholders": {} - }, - "deleteMessage": "Supprimer le message", - "@deleteMessage": { - "type": "text", - "placeholders": {} - }, - "device": "Appareil", - "@device": { - "type": "text", - "placeholders": {} - }, - "deviceId": "Identifiant de l'appareil", - "@deviceId": { - "type": "text", - "placeholders": {} - }, - "devices": "Appareils", - "@devices": { - "type": "text", - "placeholders": {} - }, - "directChats": "Discussions directes", - "@directChats": { - "type": "text", - "placeholders": {} - }, - "displaynameHasBeenChanged": "Renommage effectué", - "@displaynameHasBeenChanged": { - "type": "text", - "placeholders": {} - }, - "downloadFile": "Télécharger le fichier", - "@downloadFile": { - "type": "text", - "placeholders": {} - }, - "edit": "Modifier", - "@edit": { - "type": "text", - "placeholders": {} - }, - "editBlockedServers": "Modifier les serveurs bloqués", - "@editBlockedServers": { - "type": "text", - "placeholders": {} - }, - "editDisplayname": "Changer de nom d'affichage", - "@editDisplayname": { - "type": "text", - "placeholders": {} - }, - "editRoomAliases": "Modifier les adresses du salon", - "@editRoomAliases": { - "type": "text", - "placeholders": {} - }, - "editRoomAvatar": "Modifier l'avatar du salon", - "@editRoomAvatar": { - "type": "text", - "placeholders": {} - }, - "emoteExists": "Cette émoticône existe déjà !", - "@emoteExists": { - "type": "text", - "placeholders": {} - }, - "emoteInvalid": "Raccourci d'émoticône invalide !", - "@emoteInvalid": { - "type": "text", - "placeholders": {} - }, - "emotePacks": "Packs d'émoticônes pour le salon", - "@emotePacks": { - "type": "text", - "placeholders": {} - }, - "emoteSettings": "Paramètre des émoticônes", - "@emoteSettings": { - "type": "text", - "placeholders": {} - }, - "emoteShortcode": "Raccourci de l'émoticône", - "@emoteShortcode": { - "type": "text", - "placeholders": {} - }, - "emoteWarnNeedToPick": "Vous devez sélectionner un raccourci d'émoticône et une image !", - "@emoteWarnNeedToPick": { - "type": "text", - "placeholders": {} - }, - "emptyChat": "Discussion vide", - "@emptyChat": { - "type": "text", - "placeholders": {} - }, - "enableEmotesGlobally": "Activer globalement le pack d'émoticônes", - "@enableEmotesGlobally": { - "type": "text", - "placeholders": {} - }, - "enableEncryption": "Activer le chiffrement", - "@enableEncryption": { - "type": "text", - "placeholders": {} - }, - "enableEncryptionWarning": "Vous ne pourrez plus désactiver le chiffrement. Êtes-vous sûr(e) ?", - "@enableEncryptionWarning": { - "type": "text", - "placeholders": {} - }, - "encrypted": "Chiffré", - "@encrypted": { - "type": "text", - "placeholders": {} - }, - "encryption": "Chiffrement", - "@encryption": { - "type": "text", - "placeholders": {} - }, - "encryptionNotEnabled": "Le chiffrement n'est pas activé", - "@encryptionNotEnabled": { - "type": "text", - "placeholders": {} - }, - "endedTheCall": "{senderName} a mis fin à l'appel", - "@endedTheCall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "enterAnEmailAddress": "Saisissez une adresse de courriel", - "@enterAnEmailAddress": { - "type": "text", - "placeholders": {} - }, - "enterYourHomeserver": "Renseignez votre serveur d'accueil", - "@enterYourHomeserver": { - "type": "text", - "placeholders": {} - }, - "errorObtainingLocation": "Erreur lors de l'obtention de la localisation : {error}", - "@errorObtainingLocation": { - "type": "text", - "placeholders": { - "error": {} - } - }, - "everythingReady": "Tout est prêt !", - "@everythingReady": { - "type": "text", - "placeholders": {} - }, - "extremeOffensive": "Extrêmement offensant", - "@extremeOffensive": { - "type": "text", - "placeholders": {} - }, - "fileName": "Nom du ficher", - "@fileName": { - "type": "text", - "placeholders": {} - }, - "fluffychat": "FluffyChat", - "@fluffychat": { - "type": "text", - "placeholders": {} - }, - "fontSize": "Taille de la police", - "@fontSize": { - "type": "text", - "placeholders": {} - }, - "forward": "Transférer", - "@forward": { - "type": "text", - "placeholders": {} - }, - "fromJoining": "À partir de l'entrée dans le salon", - "@fromJoining": { - "type": "text", - "placeholders": {} - }, - "fromTheInvitation": "À partir de l'invitation", - "@fromTheInvitation": { - "type": "text", - "placeholders": {} - }, - "goToTheNewRoom": "Aller dans le nouveau salon", - "@goToTheNewRoom": { - "type": "text", - "placeholders": {} - }, - "group": "Groupe", - "@group": { - "type": "text", - "placeholders": {} - }, - "groupIsPublic": "Le groupe est public", - "@groupIsPublic": { - "type": "text", - "placeholders": {} - }, - "groups": "Groupes", - "@groups": { - "type": "text", - "placeholders": {} - }, - "groupWith": "Groupe avec {displayname}", - "@groupWith": { - "type": "text", - "placeholders": { - "displayname": {} - } - }, - "guestsAreForbidden": "Les invités ne peuvent pas rejoindre", - "@guestsAreForbidden": { - "type": "text", - "placeholders": {} - }, - "guestsCanJoin": "Les invités peuvent rejoindre", - "@guestsCanJoin": { - "type": "text", - "placeholders": {} - }, - "hasWithdrawnTheInvitationFor": "{username} a annulé l'invitation de {targetName}", - "@hasWithdrawnTheInvitationFor": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "help": "Aide", - "@help": { - "type": "text", - "placeholders": {} - }, - "hideRedactedEvents": "Cacher les évènements supprimés", - "@hideRedactedEvents": { - "type": "text", - "placeholders": {} - }, - "hideUnknownEvents": "Cacher les évènements inconnus", - "@hideUnknownEvents": { - "type": "text", - "placeholders": {} - }, - "howOffensiveIsThisContent": "À quel point ce contenu est-il offensant ?", - "@howOffensiveIsThisContent": { - "type": "text", - "placeholders": {} - }, - "id": "Identifiant", - "@id": { - "type": "text", - "placeholders": {} - }, - "identity": "Identité", - "@identity": { - "type": "text", - "placeholders": {} - }, - "ignore": "Ignorer", - "@ignore": { - "type": "text", - "placeholders": {} - }, - "ignoredUsers": "Utilisateur·ices ignoré·es", - "@ignoredUsers": { - "type": "text", - "placeholders": {} - }, - "iHaveClickedOnLink": "J'ai cliqué sur le lien", - "@iHaveClickedOnLink": { - "type": "text", - "placeholders": {} - }, - "incorrectPassphraseOrKey": "Phrase de passe ou clé de récupération incorrecte", - "@incorrectPassphraseOrKey": { - "type": "text", - "placeholders": {} - }, - "inoffensive": "Non offensant", - "@inoffensive": { - "type": "text", - "placeholders": {} - }, - "inviteContact": "Inviter un contact", - "@inviteContact": { - "type": "text", - "placeholders": {} - }, - "inviteContactToGroup": "Inviter un contact dans {groupName}", - "@inviteContactToGroup": { - "type": "text", - "placeholders": { - "groupName": {} - } - }, - "invited": "Invité·e", - "@invited": { - "type": "text", - "placeholders": {} - }, - "invitedUser": "📩 {username} a invité {targetName}", - "@invitedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "invitedUsersOnly": "Uniquement les utilisateur·ices invité·es", - "@invitedUsersOnly": { - "type": "text", - "placeholders": {} - }, - "inviteForMe": "Inviter pour moi", - "@inviteForMe": { - "type": "text", - "placeholders": {} - }, - "inviteText": "{username} vous a invité·e sur FluffyChat. \n1. Installez FluffyChat : https://fluffychat.im \n2. Inscrivez-vous ou connectez-vous \n3. Ouvrez le lien d'invitation : {link}", - "@inviteText": { - "type": "text", - "placeholders": { - "username": {}, - "link": {} - } - }, - "isTyping": "est en train d'écrire…", - "@isTyping": { - "type": "text", - "placeholders": {} - }, - "joinedTheChat": "👋 {username} a rejoint la discussion", - "@joinedTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "joinRoom": "Rejoindre le salon", - "@joinRoom": { - "type": "text", - "placeholders": {} - }, - "kicked": "👞 {username} a expulsé {targetName}", - "@kicked": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "kickedAndBanned": "🙅 {username} a expulsé et banni {targetName}", - "@kickedAndBanned": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "kickFromChat": "Expulser de la discussion", - "@kickFromChat": { - "type": "text", - "placeholders": {} - }, - "lastActiveAgo": "Vu·e pour la dernière fois : {localizedTimeShort}", - "@lastActiveAgo": { - "type": "text", - "placeholders": { - "localizedTimeShort": {} - } - }, - "leave": "Partir", - "@leave": { - "type": "text", - "placeholders": {} - }, - "leftTheChat": "A quitté la discussion", - "@leftTheChat": { - "type": "text", - "placeholders": {} - }, - "license": "Licence", - "@license": { - "type": "text", - "placeholders": {} - }, - "lightTheme": "Clair", - "@lightTheme": { - "type": "text", - "placeholders": {} - }, - "loadCountMoreParticipants": "Charger {count} participant·es de plus", - "@loadCountMoreParticipants": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "loadingPleaseWait": "Chargement… Veuillez patienter.", - "@loadingPleaseWait": { - "type": "text", - "placeholders": {} - }, - "loadMore": "Charger plus…", - "@loadMore": { - "type": "text", - "placeholders": {} - }, - "locationDisabledNotice": "Les services de localisation sont désactivés. Il est nécessaire de les activer avant de pouvoir partager votre localisation.", - "@locationDisabledNotice": { - "type": "text", - "placeholders": {} - }, - "locationPermissionDeniedNotice": "L'application n'a pas la permission d'accéder à votre localisation. Merci de l'accorder afin de pouvoir partager votre localisation.", - "@locationPermissionDeniedNotice": { - "type": "text", - "placeholders": {} - }, - "login": "Se connecter", - "@login": { - "type": "text", - "placeholders": {} - }, - "logInTo": "Se connecter à {homeserver}", - "@logInTo": { - "type": "text", - "placeholders": { - "homeserver": {} - } - }, - "logout": "Se déconnecter", - "@logout": { - "type": "text", - "placeholders": {} - }, - "memberChanges": "Changements de membres", - "@memberChanges": { - "type": "text", - "placeholders": {} - }, - "mention": "Mentionner", - "@mention": { - "type": "text", - "placeholders": {} - }, - "messages": "Messages", - "@messages": { - "type": "text", - "placeholders": {} - }, - "moderator": "Modérateur·rice", - "@moderator": { - "type": "text", - "placeholders": {} - }, - "muteChat": "Mettre la discussion en sourdine", - "@muteChat": { - "type": "text", - "placeholders": {} - }, - "needPantalaimonWarning": "Pour l'instant, vous avez besoin de Pantalaimon pour utiliser le chiffrement de bout en bout.", - "@needPantalaimonWarning": { - "type": "text", - "placeholders": {} - }, - "newChat": "Nouvelle discussion", - "@newChat": { - "type": "text", - "placeholders": {} - }, - "newMessageInFluffyChat": "💬 Nouveau message dans FluffyChat", - "@newMessageInFluffyChat": { - "type": "text", - "placeholders": {} - }, - "newVerificationRequest": "Nouvelle demande de vérification !", - "@newVerificationRequest": { - "type": "text", - "placeholders": {} - }, - "next": "Suivant", - "@next": { - "type": "text", - "placeholders": {} - }, - "no": "Non", - "@no": { - "type": "text", - "placeholders": {} - }, - "noConnectionToTheServer": "Aucune connexion au serveur", - "@noConnectionToTheServer": { - "type": "text", - "placeholders": {} - }, - "noEmotesFound": "Aucune émoticône trouvée. 😕", - "@noEmotesFound": { - "type": "text", - "placeholders": {} - }, - "noEncryptionForPublicRooms": "Vous pouvez activer le chiffrement seulement quand le salon n'est plus accessible au public.", - "@noEncryptionForPublicRooms": { - "type": "text", - "placeholders": {} - }, - "noGoogleServicesWarning": "Il semble que vous n'ayez aucun service Google sur votre téléphone. C'est une bonne décision pour votre vie privée ! Pour recevoir des notifications dans FluffyChat, nous vous recommandons d'utiliser https://microg.org/ ou https://unifiedpush.org/.", - "@noGoogleServicesWarning": { - "type": "text", - "placeholders": {} - }, - "noMatrixServer": "{server1} n'est pas un serveur Matrix, souhaitez-vous utiliser {server2} à la place ?", - "@noMatrixServer": { - "type": "text", - "placeholders": { - "server1": {}, - "server2": {} - } - }, - "none": "Aucun", - "@none": { - "type": "text", - "placeholders": {} - }, - "noPasswordRecoveryDescription": "Vous n'avez pas encore ajouté de moyen pour récupérer votre mot de passe.", - "@noPasswordRecoveryDescription": { - "type": "text", - "placeholders": {} - }, - "noPermission": "Aucune permission", - "@noPermission": { - "type": "text", - "placeholders": {} - }, - "noRoomsFound": "Aucun salon trouvé…", - "@noRoomsFound": { - "type": "text", - "placeholders": {} - }, - "notifications": "Notifications", - "@notifications": { - "type": "text", - "placeholders": {} - }, - "notificationsEnabledForThisAccount": "Notifications activées pour ce compte", - "@notificationsEnabledForThisAccount": { - "type": "text", - "placeholders": {} - }, - "numUsersTyping": "{count} utilisateur·ices écrivent…", - "@numUsersTyping": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "obtainingLocation": "Obtention de la localisation…", - "@obtainingLocation": { - "type": "text", - "placeholders": {} - }, - "offensive": "Offensant", - "@offensive": { - "type": "text", - "placeholders": {} - }, - "offline": "Hors ligne", - "@offline": { - "type": "text", - "placeholders": {} - }, - "ok": "Valider", - "@ok": { - "type": "text", - "placeholders": {} - }, - "online": "En ligne", - "@online": { - "type": "text", - "placeholders": {} - }, - "onlineKeyBackupEnabled": "La sauvegarde en ligne des clés est activée", - "@onlineKeyBackupEnabled": { - "type": "text", - "placeholders": {} - }, - "oopsPushError": "Oups ! Une erreur s'est malheureusement produite lors du réglage des notifications.", - "@oopsPushError": { - "type": "text", - "placeholders": {} - }, - "oopsSomethingWentWrong": "Oups, un problème est survenu…", - "@oopsSomethingWentWrong": { - "type": "text", - "placeholders": {} - }, - "openAppToReadMessages": "Ouvrez l'application pour lire le message", - "@openAppToReadMessages": { - "type": "text", - "placeholders": {} - }, - "openCamera": "Ouvrir l'appareil photo", - "@openCamera": { - "type": "text", - "placeholders": {} - }, - "openInMaps": "Ouvrir dans maps", - "@openInMaps": { - "type": "text", - "placeholders": {} - }, - "or": "Ou", - "@or": { - "type": "text", - "placeholders": {} - }, - "participant": "Participant(e)", - "@participant": { - "type": "text", - "placeholders": {} - }, - "passphraseOrKey": "Phrase de passe ou clé de récupération", - "@passphraseOrKey": { - "type": "text", - "placeholders": {} - }, - "password": "Mot de passe", - "@password": { - "type": "text", - "placeholders": {} - }, - "passwordForgotten": "Mot de passe oublié", - "@passwordForgotten": { - "type": "text", - "placeholders": {} - }, - "passwordHasBeenChanged": "Le mot de passe a été modifié", - "@passwordHasBeenChanged": { - "type": "text", - "placeholders": {} - }, - "passwordRecovery": "Récupération du mot de passe", - "@passwordRecovery": { - "type": "text", - "placeholders": {} - }, - "people": "Personnes", - "@people": { - "type": "text", - "placeholders": {} - }, - "pickImage": "Choisir une image", - "@pickImage": { - "type": "text", - "placeholders": {} - }, - "pin": "Épingler", - "@pin": { - "type": "text", - "placeholders": {} - }, - "play": "Lire {fileName}", - "@play": { - "type": "text", - "placeholders": { - "fileName": {} - } - }, - "pleaseChoose": "Veuillez choisir", - "@pleaseChoose": { - "type": "text", - "placeholders": {} - }, - "pleaseChooseAPasscode": "Veuillez choisir un code d’accès", - "@pleaseChooseAPasscode": { - "type": "text", - "placeholders": {} - }, - "pleaseClickOnLink": "Veuillez cliquer sur le lien contenu dans le courriel puis continuez.", - "@pleaseClickOnLink": { - "type": "text", - "placeholders": {} - }, - "pleaseEnter4Digits": "Veuillez saisir 4 chiffres ou laisser vide pour désactiver le verrouillage de l’application.", - "@pleaseEnter4Digits": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourPassword": "Renseignez votre mot de passe", - "@pleaseEnterYourPassword": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourPin": "Veuillez saisir votre code PIN", - "@pleaseEnterYourPin": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourUsername": "Renseignez votre nom d'utilisateur·ice", - "@pleaseEnterYourUsername": { - "type": "text", - "placeholders": {} - }, - "pleaseFollowInstructionsOnWeb": "Veuillez suivre les instructions sur le site et appuyer sur Suivant.", - "@pleaseFollowInstructionsOnWeb": { - "type": "text", - "placeholders": {} - }, - "privacy": "Vie privée", - "@privacy": { - "type": "text", - "placeholders": {} - }, - "publicRooms": "Salons publics", - "@publicRooms": { - "type": "text", - "placeholders": {} - }, - "pushRules": "Règles de notifications", - "@pushRules": { - "type": "text", - "placeholders": {} - }, - "reason": "Motif", - "@reason": { - "type": "text", - "placeholders": {} - }, - "recording": "Enregistrement", - "@recording": { - "type": "text", - "placeholders": {} - }, - "redactedAnEvent": "{username} a supprimé un évènement", - "@redactedAnEvent": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "redactMessage": "Supprimer un message", - "@redactMessage": { - "type": "text", - "placeholders": {} - }, - "register": "S'inscrire", - "@register": { - "type": "text", - "placeholders": {} - }, - "reject": "Refuser", - "@reject": { - "type": "text", - "placeholders": {} - }, - "rejectedTheInvitation": "{username} a refusé l'invitation", - "@rejectedTheInvitation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "rejoin": "Rejoindre de nouveau", - "@rejoin": { - "type": "text", - "placeholders": {} - }, - "remove": "Supprimer", - "@remove": { - "type": "text", - "placeholders": {} - }, - "removeAllOtherDevices": "Supprimer tous les autres appareils", - "@removeAllOtherDevices": { - "type": "text", - "placeholders": {} - }, - "removedBy": "Supprimé par {username}", - "@removedBy": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "removeDevice": "Supprimer l'appareil", - "@removeDevice": { - "type": "text", - "placeholders": {} - }, - "unbanFromChat": "Débannissement de la discussion", - "@unbanFromChat": { - "type": "text", - "placeholders": {} - }, - "removeYourAvatar": "Supprimer votre avatar", - "@removeYourAvatar": { - "type": "text", - "placeholders": {} - }, - "renderRichContent": "Afficher les contenus riches des messages", - "@renderRichContent": { - "type": "text", - "placeholders": {} - }, - "replaceRoomWithNewerVersion": "Remplacer le salon par une nouvelle version", - "@replaceRoomWithNewerVersion": { - "type": "text", - "placeholders": {} - }, - "reply": "Répondre", - "@reply": { - "type": "text", - "placeholders": {} - }, - "reportMessage": "Signaler un message", - "@reportMessage": { - "type": "text", - "placeholders": {} - }, - "requestPermission": "Demander la permission", - "@requestPermission": { - "type": "text", - "placeholders": {} - }, - "roomHasBeenUpgraded": "Le salon a été mis à niveau", - "@roomHasBeenUpgraded": { - "type": "text", - "placeholders": {} - }, - "roomVersion": "Version du salon", - "@roomVersion": { - "type": "text", - "placeholders": {} - }, - "saveFile": "Enregistrer le fichier", - "@saveFile": { - "type": "text", - "placeholders": {} - }, - "search": "Rechercher", - "@search": { - "type": "text", - "placeholders": {} - }, - "security": "Sécurité", - "@security": { - "type": "text", - "placeholders": {} - }, - "seenByUser": "Vu par {username}", - "@seenByUser": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "send": "Envoyer", - "@send": { - "type": "text", - "placeholders": {} - }, - "sendAMessage": "Envoyer un message", - "@sendAMessage": { - "type": "text", - "placeholders": {} - }, - "sendAsText": "Envoyer un texte", - "@sendAsText": { - "type": "text" - }, - "sendAudio": "Envoyer un fichier audio", - "@sendAudio": { - "type": "text", - "placeholders": {} - }, - "sendFile": "Envoyer un fichier", - "@sendFile": { - "type": "text", - "placeholders": {} - }, - "sendImage": "Envoyer une image", - "@sendImage": { - "type": "text", - "placeholders": {} - }, - "sendMessages": "Envoyer des messages", - "@sendMessages": { - "type": "text", - "placeholders": {} - }, - "sendOriginal": "Envoyer le fichier original", - "@sendOriginal": { - "type": "text", - "placeholders": {} - }, - "sendSticker": "Envoyer un autocollant", - "@sendSticker": { - "type": "text", - "placeholders": {} - }, - "sendVideo": "Envoyer une vidéo", - "@sendVideo": { - "type": "text", - "placeholders": {} - }, - "sentAFile": "📁 {username} a envoyé un fichier", - "@sentAFile": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentAnAudio": "🎤 {username} a envoyé un fichier audio", - "@sentAnAudio": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentAPicture": "🖼️ {username} a envoyé une image", - "@sentAPicture": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentASticker": "😊 {username} a envoyé un autocollant", - "@sentASticker": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentAVideo": "🎥 {username} a envoyé une vidéo", - "@sentAVideo": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentCallInformations": "{senderName} a envoyé des informations sur l'appel", - "@sentCallInformations": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "setAsCanonicalAlias": "Définir comme adresse principale", - "@setAsCanonicalAlias": { - "type": "text", - "placeholders": {} - }, - "setCustomEmotes": "Définir des émoticônes personnalisées", - "@setCustomEmotes": { - "type": "text", - "placeholders": {} - }, - "setInvitationLink": "Créer un lien d'invitation", - "@setInvitationLink": { - "type": "text", - "placeholders": {} - }, - "setPermissionsLevel": "Définir le niveau de permissions", - "@setPermissionsLevel": { - "type": "text", - "placeholders": {} - }, - "setStatus": "Définir le statut", - "@setStatus": { - "type": "text", - "placeholders": {} - }, - "settings": "Paramètres", - "@settings": { - "type": "text", - "placeholders": {} - }, - "share": "Partager", - "@share": { - "type": "text", - "placeholders": {} - }, - "sharedTheLocation": "{username} a partagé sa position", - "@sharedTheLocation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "shareLocation": "Partager la localisation", - "@shareLocation": { - "type": "text", - "placeholders": {} - }, - "showPassword": "Afficher le mot de passe", - "@showPassword": { - "type": "text", - "placeholders": {} - }, - "singlesignon": "Authentification unique", - "@singlesignon": { - "type": "text", - "placeholders": {} - }, - "skip": "Ignorer", - "@skip": { - "type": "text", - "placeholders": {} - }, - "sourceCode": "Code source", - "@sourceCode": { - "type": "text", - "placeholders": {} - }, - "spaceIsPublic": "L'espace est public", - "@spaceIsPublic": { - "type": "text", - "placeholders": {} - }, - "spaceName": "Nom de l'espace", - "@spaceName": { - "type": "text", - "placeholders": {} - }, - "startedACall": "{senderName} a démarré un appel", - "@startedACall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "status": "Statut", - "@status": { - "type": "text", - "placeholders": {} - }, - "statusExampleMessage": "Comment allez-vous aujourd'hui ?", - "@statusExampleMessage": { - "type": "text", - "placeholders": {} - }, - "submit": "Soumettre", - "@submit": { - "type": "text", - "placeholders": {} - }, - "synchronizingPleaseWait": "Synchronisation... Veuillez patienter.", - "@synchronizingPleaseWait": { - "type": "text", - "placeholders": {} - }, - "systemTheme": "Système", - "@systemTheme": { - "type": "text", - "placeholders": {} - }, - "theyDontMatch": "Elles ne correspondent pas", - "@theyDontMatch": { - "type": "text", - "placeholders": {} - }, - "theyMatch": "Elles correspondent", - "@theyMatch": { - "type": "text", - "placeholders": {} - }, - "title": "FluffyChat", - "@title": { - "description": "Title for the application", - "type": "text", - "placeholders": {} - }, - "toggleFavorite": "Activer/désactiver en favori", - "@toggleFavorite": { - "type": "text", - "placeholders": {} - }, - "toggleMuted": "Activer/désactiver la sourdine", - "@toggleMuted": { - "type": "text", - "placeholders": {} - }, - "toggleUnread": "Marquer comme lu / non lu", - "@toggleUnread": { - "type": "text", - "placeholders": {} - }, - "tooManyRequestsWarning": "Trop de requêtes. Veuillez réessayer plus tard !", - "@tooManyRequestsWarning": { - "type": "text", - "placeholders": {} - }, - "transferFromAnotherDevice": "Transfert à partir d'un autre appareil", - "@transferFromAnotherDevice": { - "type": "text", - "placeholders": {} - }, - "tryToSendAgain": "Retenter l'envoi", - "@tryToSendAgain": { - "type": "text", - "placeholders": {} - }, - "unavailable": "Indisponible", - "@unavailable": { - "type": "text", - "placeholders": {} - }, - "unbannedUser": "{username} a annulé le bannissement de {targetName}", - "@unbannedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "unblockDevice": "Retirer le blocage sur l'appareil", - "@unblockDevice": { - "type": "text", - "placeholders": {} - }, - "unknownDevice": "Appareil inconnu", - "@unknownDevice": { - "type": "text", - "placeholders": {} - }, - "unknownEncryptionAlgorithm": "Algorithme de chiffrement inconnu", - "@unknownEncryptionAlgorithm": { - "type": "text", - "placeholders": {} - }, - "unknownEvent": "Événement de type inconnu : '{type}'", - "@unknownEvent": { - "type": "text", - "placeholders": { - "type": {} - } - }, - "unmuteChat": "Retirer la sourdine de la discussion", - "@unmuteChat": { - "type": "text", - "placeholders": {} - }, - "unpin": "Désépingler", - "@unpin": { - "type": "text", - "placeholders": {} - }, - "unreadChats": "{unreadCount, plural, =1{1 discussion non lue} other{{unreadCount} discussions non lues}}", - "@unreadChats": { - "type": "text", - "placeholders": { - "unreadCount": {} - } - }, - "userAndOthersAreTyping": "{username} et {count} autres sont en train d'écrire…", - "@userAndOthersAreTyping": { - "type": "text", - "placeholders": { - "username": {}, - "count": {} - } - }, - "userAndUserAreTyping": "{username} et {username2} sont en train d'écrire…", - "@userAndUserAreTyping": { - "type": "text", - "placeholders": { - "username": {}, - "username2": {} - } - }, - "userIsTyping": "{username} est en train d'écrire…", - "@userIsTyping": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "userLeftTheChat": "🚪 {username} a quitté la discussion", - "@userLeftTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "username": "Nom d'utilisateur·ice", - "@username": { - "type": "text", - "placeholders": {} - }, - "userSentUnknownEvent": "{username} a envoyé un évènement de type {type}", - "@userSentUnknownEvent": { - "type": "text", - "placeholders": { - "username": {}, - "type": {} - } - }, - "verified": "Vérifié", - "@verified": { - "type": "text", - "placeholders": {} - }, - "verify": "Vérifier", - "@verify": { - "type": "text", - "placeholders": {} - }, - "verifyStart": "Commencer la vérification", - "@verifyStart": { - "type": "text", - "placeholders": {} - }, - "verifySuccess": "La vérification a été effectuée avec succès !", - "@verifySuccess": { - "type": "text", - "placeholders": {} - }, - "verifyTitle": "Vérification de l'autre compte", - "@verifyTitle": { - "type": "text", - "placeholders": {} - }, - "videoCall": "Appel vidéo", - "@videoCall": { - "type": "text", - "placeholders": {} - }, - "visibilityOfTheChatHistory": "Visibilité de l'historique de la discussion", - "@visibilityOfTheChatHistory": { - "type": "text", - "placeholders": {} - }, - "visibleForAllParticipants": "Visible pour tous les participant·es", - "@visibleForAllParticipants": { - "type": "text", - "placeholders": {} - }, - "visibleForEveryone": "Visible pour tout le monde", - "@visibleForEveryone": { - "type": "text", - "placeholders": {} - }, - "voiceMessage": "Message vocal", - "@voiceMessage": { - "type": "text", - "placeholders": {} - }, - "waitingPartnerAcceptRequest": "En attente de l'acceptation de la demande par le partenaire…", - "@waitingPartnerAcceptRequest": { - "type": "text", - "placeholders": {} - }, - "waitingPartnerEmoji": "En attente de l'acceptation de l'émoji par le partenaire…", - "@waitingPartnerEmoji": { - "type": "text", - "placeholders": {} - }, - "waitingPartnerNumbers": "En attente de l'acceptation des nombres par le partenaire…", - "@waitingPartnerNumbers": { - "type": "text", - "placeholders": {} - }, - "wallpaper": "Image de fond", - "@wallpaper": { - "type": "text", - "placeholders": {} - }, - "warning": "Attention !", - "@warning": { - "type": "text", - "placeholders": {} - }, - "weSentYouAnEmail": "Nous vous avons envoyé un courriel", - "@weSentYouAnEmail": { - "type": "text", - "placeholders": {} - }, - "whoCanPerformWhichAction": "Qui peut faire quelle action", - "@whoCanPerformWhichAction": { - "type": "text", - "placeholders": {} - }, - "whoIsAllowedToJoinThisGroup": "Qui est autorisé·e à rejoindre ce groupe", - "@whoIsAllowedToJoinThisGroup": { - "type": "text", - "placeholders": {} - }, - "whyDoYouWantToReportThis": "Pourquoi voulez-vous le signaler ?", - "@whyDoYouWantToReportThis": { - "type": "text", - "placeholders": {} - }, - "wipeChatBackup": "Effacer la sauvegarde de votre discussion pour créer une nouvelle clé de récupération ?", - "@wipeChatBackup": { - "type": "text", - "placeholders": {} - }, - "withTheseAddressesRecoveryDescription": "Grâce à ces adresses, vous pouvez récupérer votre mot de passe si vous en avez besoin.", - "@withTheseAddressesRecoveryDescription": { - "type": "text", - "placeholders": {} - }, - "writeAMessage": "Écrivez un message…", - "@writeAMessage": { - "type": "text", - "placeholders": {} - }, - "yes": "Oui", - "@yes": { - "type": "text", - "placeholders": {} - }, - "you": "Vous", - "@you": { - "type": "text", - "placeholders": {} - }, - "youAreNoLongerParticipatingInThisChat": "Vous ne participez plus à cette discussion", - "@youAreNoLongerParticipatingInThisChat": { - "type": "text", - "placeholders": {} - }, - "youHaveBeenBannedFromThisChat": "Vous avez été banni·e de cette discussion", - "@youHaveBeenBannedFromThisChat": { - "type": "text", - "placeholders": {} - }, - "yourPublicKey": "Votre clé publique", - "@yourPublicKey": { - "type": "text", - "placeholders": {} - }, - "scanQrCode": "Scanner un code QR", - "@scanQrCode": {}, - "sendOnEnter": "Envoyer avec Entrée", - "@sendOnEnter": {}, - "homeserver": "Serveur d'accueil", - "@homeserver": {}, - "serverRequiresEmail": "Ce serveur doit valider votre adresse électronique pour l'inscription.", - "@serverRequiresEmail": {}, - "enableMultiAccounts": "(BETA) Activer les comptes multiples sur cet appareil", - "@enableMultiAccounts": {}, - "bundleName": "Nom du groupe", - "@bundleName": {}, - "removeFromBundle": "Retirer de ce groupe", - "@removeFromBundle": {}, - "addToBundle": "Ajouter au groupe", - "@addToBundle": {}, - "editBundlesForAccount": "Modifier les groupes pour ce compte", - "@editBundlesForAccount": {}, - "addAccount": "Ajouter un compte", - "@addAccount": {}, - "oneClientLoggedOut": "Un de vos clients a été déconnecté", - "@oneClientLoggedOut": {}, - "link": "Lien", - "@link": {}, - "yourChatBackupHasBeenSetUp": "Votre sauvegarde de la discussion a été mise en place.", - "@yourChatBackupHasBeenSetUp": {}, - "unverified": "Non vérifié", - "@unverified": {}, - "repeatPassword": "Répétez le mot de passe", - "@repeatPassword": {}, - "messageType": "Type de message", - "@messageType": {}, - "openGallery": "Ouvrir dans la Galerie", - "@openGallery": {}, - "time": "Heure", - "@time": {}, - "sender": "Expéditeur/trice", - "@sender": {}, - "messageInfo": "Informations sur le message", - "@messageInfo": {}, - "removeFromSpace": "Supprimer de l’espace", - "@removeFromSpace": {}, - "addToSpaceDescription": "Sélectionnez un espace pour y ajouter cette discussion.", - "@addToSpaceDescription": {}, - "start": "Commencer", - "@start": {}, - "commandHint_create": "Créer un groupe de discussion vide\nUtilisez --no-encryption pour désactiver le chiffrement", - "@commandHint_create": { - "type": "text", - "description": "Usage hint for the command /create" - }, - "commandHint_discardsession": "Abandonner la session", - "@commandHint_discardsession": { - "type": "text", - "description": "Usage hint for the command /discardsession" - }, - "commandHint_clearcache": "Vider le cache", - "@commandHint_clearcache": { - "type": "text", - "description": "Usage hint for the command /clearcache" - }, - "commandHint_dm": "Commencer une discussion directe\nUtilisez --no-encryption pour désactiver le chiffrement", - "@commandHint_dm": { - "type": "text", - "description": "Usage hint for the command /dm" - }, - "openVideoCamera": "Ouvrir la caméra pour une vidéo", - "@openVideoCamera": { - "type": "text", - "placeholders": {} - }, - "publish": "Publier", - "@publish": {}, - "videoWithSize": "Vidéo ({size})", - "@videoWithSize": { - "type": "text", - "placeholders": { - "size": {} - } - }, - "dismiss": "Rejeter", - "@dismiss": {}, - "markAsRead": "Marquer comme lu", - "@markAsRead": {}, - "reportUser": "Signaler l'utilisateur/trice", - "@reportUser": {}, - "openChat": "Ouvrir la discussion", - "@openChat": {}, - "reactedWith": "{sender} a réagi avec {reaction}", - "@reactedWith": { - "type": "text", - "placeholders": { - "sender": {}, - "reaction": {} - } - }, - "emojis": "Émojis", - "@emojis": {}, - "placeCall": "Passer un appel", - "@placeCall": {}, - "voiceCall": "Appel vocal", - "@voiceCall": {}, - "unsupportedAndroidVersion": "Version d'Android non prise en charge", - "@unsupportedAndroidVersion": {}, - "unsupportedAndroidVersionLong": "Cette fonctionnalité nécessite une nouvelle version d'Android. Veuillez vérifier les mises à jour ou la prise en charge de Lineage OS.", - "@unsupportedAndroidVersionLong": {}, - "pinMessage": "Épingler au salon", - "@pinMessage": {}, - "confirmEventUnpin": "Voulez-vous vraiment désépingler définitivement l'événement ?", - "@confirmEventUnpin": {}, - "videoCallsBetaWarning": "Veuillez noter que les appels vidéo sont actuellement en version bêta. Ils peuvent ne pas fonctionner comme prévu ou ne oas fonctionner du tout sur toutes les plateformes.", - "@videoCallsBetaWarning": {}, - "experimentalVideoCalls": "Appels vidéo expérimentaux", - "@experimentalVideoCalls": {}, - "emailOrUsername": "Courriel ou identifiant", - "@emailOrUsername": {}, - "switchToAccount": "Passer au compte {number}", - "@switchToAccount": { - "type": "number", - "placeholders": { - "number": {} - } - }, - "nextAccount": "Compte suivant", - "@nextAccount": {}, - "previousAccount": "Compte précédent", - "@previousAccount": {}, - "widgetJitsi": "Jitsi Meet", - "@widgetJitsi": {}, - "widgetCustom": "Personnalisé", - "@widgetCustom": {}, - "widgetUrlError": "Ceci n'est pas un lien valide.", - "@widgetUrlError": {}, - "widgetNameError": "Veuillez fournir un nom d'affichage.", - "@widgetNameError": {}, - "errorAddingWidget": "Erreur lors de l'ajout du widget.", - "@errorAddingWidget": {}, - "widgetEtherpad": "Note textuelle", - "@widgetEtherpad": {}, - "addWidget": "Ajouter un widget", - "@addWidget": {}, - "widgetName": "Nom", - "@widgetName": {}, - "widgetVideo": "Vidéo", - "@widgetVideo": {}, - "youRejectedTheInvitation": "Vous avez rejeté l'invitation", - "@youRejectedTheInvitation": {}, - "youJoinedTheChat": "Vous avez rejoint la discussion", - "@youJoinedTheChat": {}, - "youHaveWithdrawnTheInvitationFor": "Vous avez retiré l'invitation pour {user}", - "@youHaveWithdrawnTheInvitationFor": { - "placeholders": { - "user": {} - } - }, - "youAcceptedTheInvitation": "👍 Vous avez accepté l'invitation", - "@youAcceptedTheInvitation": {}, - "youBannedUser": "Vous avez banni {user}", - "@youBannedUser": { - "placeholders": { - "user": {} - } - }, - "youInvitedBy": "📩 Vous avez été invité par {user}", - "@youInvitedBy": { - "placeholders": { - "user": {} - } - }, - "youInvitedUser": "📩 Vous avez invité {user}", - "@youInvitedUser": { - "placeholders": { - "user": {} - } - }, - "youKicked": "👞 Vous avez dégagé {user}", - "@youKicked": { - "placeholders": { - "user": {} - } - }, - "youKickedAndBanned": "🙅 Vous avez dégagé et banni {user}", - "@youKickedAndBanned": { - "placeholders": { - "user": {} - } - }, - "youUnbannedUser": "Vous avez débanni {user}", - "@youUnbannedUser": { - "placeholders": { - "user": {} - } - }, - "separateChatTypes": "Séparer les discussions directes et les groupes", - "@separateChatTypes": { - "type": "text", - "placeholders": {} - }, - "users": "Utilisateurs/trices", - "@users": {}, - "storeInAndroidKeystore": "Stocker dans Android KeyStore", - "@storeInAndroidKeystore": {}, - "storeInAppleKeyChain": "Stocker dans Apple KeyChain", - "@storeInAppleKeyChain": {}, - "user": "Utilisateur/trice", - "@user": {}, - "custom": "Personnalisé", - "@custom": {}, - "hydrate": "Restaurer à partir du fichier de sauvegarde", - "@hydrate": {}, - "dehydrateWarning": "Cette action ne peut pas être annulée. Assurez-vous d'enregistrer convenablement le fichier de sauvegarde.", - "@dehydrateWarning": {}, - "dehydrateTorLong": "Pour les utilisateurs/trices de TOR, il est recommandé d'exporter la session avant de fermer la fenêtre.", - "@dehydrateTorLong": {}, - "recoveryKey": "Clé de récupération", - "@recoveryKey": {}, - "recoveryKeyLost": "Clé de récupération perdue ?", - "@recoveryKeyLost": {}, - "indexedDbErrorLong": "Le stockage des messages n'est malheureusement pas activé par défaut en mode privé.\nVeuillez consulter :\n - about:config\n - Définir dom.indexedDB.privateBrowsing.enabled à « vrai ».\nSinon, il n'est pas possible d'exécuter FluffyChat.", - "@indexedDbErrorLong": {}, - "saveKeyManuallyDescription": "Enregistrer cette clé manuellement en déclenchant la boîte de dialogue de partage du système ou le presse-papiers.", - "@saveKeyManuallyDescription": {}, - "storeInSecureStorageDescription": "Stocker la clé de récupération dans un espace sécurisé de cet appareil.", - "@storeInSecureStorageDescription": {}, - "indexedDbErrorTitle": "Problèmes relatifs au mode privé", - "@indexedDbErrorTitle": {}, - "dehydrate": "Exporter la session et effacer l'appareil", - "@dehydrate": {}, - "dehydrateTor": "Utilisateurs/trices de TOR : Exporter la session", - "@dehydrateTor": {}, - "hydrateTor": "Utilisateurs/trices de TOR : Importer une session exportée", - "@hydrateTor": {}, - "hydrateTorLong": "Vous avez exporté votre session la dernière fois sur TOR ? Importez-la rapidement et continuez à discuter.", - "@hydrateTorLong": {}, - "pleaseEnterRecoveryKey": "Veuillez saisir votre clé de récupération :", - "@pleaseEnterRecoveryKey": {}, - "pleaseEnterRecoveryKeyDescription": "Pour déverrouiller vos anciens messages, veuillez entrer votre clé de récupération qui a été générée lors d'une session précédente. Votre clé de récupération n'est PAS votre mot de passe.", - "@pleaseEnterRecoveryKeyDescription": {}, - "unlockOldMessages": "Déverrouiller les anciens messages", - "@unlockOldMessages": {}, - "storeSecurlyOnThisDevice": "Stocker de manière sécurisé sur cet appareil", - "@storeSecurlyOnThisDevice": {}, - "countFiles": "{count} fichiers", - "@countFiles": { - "placeholders": { - "count": {} - } - }, - "noKeyForThisMessage": "Cela peut se produire si le message a été envoyé avant que vous ne vous soyez connecté à votre compte sur cet appareil.\n\nIl est également possible que l'expéditeur ait bloqué votre appareil ou qu'un problème de connexion Internet se soit produit.\n\nÊtes-vous capable de lire le message sur une autre session ? Vous pouvez alors transférer le message à partir de celle-ci ! Allez dans Paramètres > Appareils et assurez-vous que vos appareils se sont vérifiés mutuellement. Lorsque vous ouvrirez le salon la fois suivante et que les deux sessions seront au premier plan, les clés seront transmises automatiquement.\n\nVous ne voulez pas perdre les clés en vous déconnectant ou en changeant d'appareil ? Assurez-vous que vous avez activé la sauvegarde de la discussion dans les paramètres.", - "@noKeyForThisMessage": {}, - "enterRoom": "Entrer dans le salon", - "@enterRoom": {}, - "allSpaces": "Tous les espaces", - "@allSpaces": {}, - "commandHint_markasdm": "Marquer comme salon de messagerie directe", - "@commandHint_markasdm": {}, - "commandHint_markasgroup": "Marquer comme groupe", - "@commandHint_markasgroup": {}, - "confirmMatrixId": "Veuillez confirmer votre identifiant Matrix afin de supprimer votre compte.", - "@confirmMatrixId": {}, - "supposedMxid": "Cela devrait être {mxid}", - "@supposedMxid": { - "type": "text", - "placeholders": { - "mxid": {} - } - }, - "whyIsThisMessageEncrypted": "Pourquoi ce message est-il illisible ?", - "@whyIsThisMessageEncrypted": {}, - "foregroundServiceRunning": "Cette notification s’affiche lorsque le service au premier plan est en cours d’exécution.", - "@foregroundServiceRunning": {}, - "screenSharingTitle": "Partage d'écran", - "@screenSharingTitle": {}, - "screenSharingDetail": "Vous partagez votre écran dans FuffyChat", - "@screenSharingDetail": {}, - "callingPermissions": "Permissions d'appel", - "@callingPermissions": {}, - "callingAccount": "Compte d'appel", - "@callingAccount": {}, - "callingAccountDetails": "Permet à FluffyChat d'utiliser l'application de numérotation native d'Android.", - "@callingAccountDetails": {}, - "appearOnTop": "Apparaître en haut", - "@appearOnTop": {}, - "appearOnTopDetails": "Permet à l'application d'apparaître en haut de l'écran (non nécessaire si vous avez déjà configuré Fluffychat comme compte d'appel)", - "@appearOnTopDetails": {}, - "otherCallingPermissions": "Microphone, caméra et autres autorisations de FluffyChat", - "@otherCallingPermissions": {}, - "newGroup": "Nouveau groupe", - "@newGroup": {}, - "newSpace": "Nouvel espace", - "@newSpace": {}, - "enterSpace": "Entrer dans l’espace", - "@enterSpace": {}, - "numChats": "{number} discussions", - "@numChats": { - "type": "number", - "placeholders": { - "number": {} - } - }, - "hideUnimportantStateEvents": "Masquer les événements d'état sans importance", - "@hideUnimportantStateEvents": {}, - "doNotShowAgain": "Ne plus afficher", - "@doNotShowAgain": {}, - "commandHint_googly": "Envoyer des yeux écarquillés", - "@commandHint_googly": {}, - "commandHint_cuddle": "Envoyer un câlin", - "@commandHint_cuddle": {}, - "commandHint_hug": "Envoyer une accolade", - "@commandHint_hug": {}, - "googlyEyesContent": "{senderName} vous envoie des yeux écarquillés", - "@googlyEyesContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "cuddleContent": "{senderName} vous fait un câlin", - "@cuddleContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "hugContent": "{senderName} vous fait une accolade", - "@hugContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "wasDirectChatDisplayName": "Discussion vide (était {oldDisplayName})", - "@wasDirectChatDisplayName": { - "type": "text", - "placeholders": { - "oldDisplayName": {} - } - }, - "encryptThisChat": "Chiffrer cette discussion", - "@encryptThisChat": {}, - "sorryThatsNotPossible": "Désolé, ce n'est pas possible", - "@sorryThatsNotPossible": {}, - "deviceKeys": "Clés de l’appareil :", - "@deviceKeys": {}, - "startFirstChat": "Commencez votre première discussion", - "@startFirstChat": {}, - "newSpaceDescription": "Les espaces vous permettent de consolider vos conversations et de construire des communautés privées ou publiques.", - "@newSpaceDescription": {}, - "disableEncryptionWarning": "Pour des raisons de sécurité, vous ne pouvez pas désactiver le chiffrement dans une discussion s'il a été activé avant.", - "@disableEncryptionWarning": {}, - "reopenChat": "Rouvrir la discussion", - "@reopenChat": {}, - "noOtherDevicesFound": "Aucun autre appareil trouvé", - "@noOtherDevicesFound": {}, - "noBackupWarning": "Attention ! Sans l'activation de la sauvegarde de la discussion, vous perdrez l'accès à vos messages chiffrés. Il est fortement recommandé d'activer la sauvegarde de la discussion avant de se déconnecter.", - "@noBackupWarning": {}, - "fileHasBeenSavedAt": "Le fichier a été enregistré dans {path}", - "@fileHasBeenSavedAt": { - "type": "text", - "placeholders": { - "path": {} - } - }, - "fileIsTooBigForServer": "Le serveur signale que le fichier est trop volumineux pour être envoyé.", - "@fileIsTooBigForServer": {}, - "jumpToLastReadMessage": "Aller au dernier message lu", - "@jumpToLastReadMessage": {}, - "readUpToHere": "Lisez jusqu’ici", - "@readUpToHere": {}, - "allRooms": "Toutes les conversations groupées", - "@allRooms": { - "type": "text", - "placeholders": {} - }, - "chatPermissions": "Permissions du salon", - "@chatPermissions": {}, - "importFromZipFile": "Importer depuis un fichier .zip", - "@importFromZipFile": {}, - "inviteContactToGroupQuestion": "Voulez-vous inviter {contact} au salon \"{groupName}\" ?", - "@inviteContactToGroupQuestion": {}, - "importEmojis": "Importer des Emojis", - "@importEmojis": {}, - "notAnImage": "Pas un fichier image", - "@notAnImage": {}, - "chatDescriptionHasBeenChanged": "La description du salon a changé", - "@chatDescriptionHasBeenChanged": {}, - "createGroup": "Créer un groupe", - "@createGroup": {}, - "importNow": "Importer maintenant", - "@importNow": {}, - "@reportErrorDescription": {}, - "@setColorTheme": {}, - "@banUserDescription": {}, - "@removeDevicesDescription": {}, - "@tryAgain": {}, - "@unbanUserDescription": {}, - "@messagesStyle": {}, - "@chatDescription": {}, - "@pushNotificationsNotAvailable": {}, - "@invalidServerName": {}, - "@signInWithPassword": {}, - "@makeAdminDescription": {}, - "@setChatDescription": {}, - "@redactedBy": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@signInWith": { - "type": "text", - "placeholders": { - "provider": {} - } - }, - "@optionalRedactReason": {}, - "@archiveRoomDescription": {}, - "@exportEmotePack": {}, - "@redactedByBecause": { - "type": "text", - "placeholders": { - "username": {}, - "reason": {} - } - }, - "@redactMessageDescription": {}, - "@invalidInput": {}, - "@report": {}, - "@addChatDescription": {}, - "@hasKnocked": { - "placeholders": { - "user": {} - } - }, - "@directChat": {}, - "@wrongPinEntered": { - "type": "text", - "placeholders": { - "seconds": {} - } - }, - "@sendTypingNotifications": {}, - "@inviteGroupChat": {}, - "@invitePrivateChat": {}, - "@noChatDescriptionYet": {}, - "@learnMore": {}, - "@roomUpgradeDescription": {}, - "@pleaseEnterANumber": {}, - "@profileNotFound": {}, - "@jump": {}, - "@shareInviteLink": {}, - "@emoteKeyboardNoRecents": { - "type": "text", - "placeholders": {} - }, - "@setTheme": {}, - "@replace": {}, - "@pleaseTryAgainLaterOrChooseDifferentServer": {}, - "@kickUserDescription": {}, - "@invite": {}, - "@openLinkInBrowser": {} -} \ No newline at end of file + "@@locale": "fr", + "@@last_modified": "2021-08-14 12:41:10.051787", + "about": "À propos", + "@about": { + "type": "text", + "placeholders": {} + }, + "accept": "Accepter", + "@accept": { + "type": "text", + "placeholders": {} + }, + "acceptedTheInvitation": "👍 {username} a accepté l'invitation", + "@acceptedTheInvitation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "account": "Compte", + "@account": { + "type": "text", + "placeholders": {} + }, + "activatedEndToEndEncryption": "🔐 {username} a activé le chiffrement de bout en bout", + "@activatedEndToEndEncryption": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "addEmail": "Ajouter un courriel", + "@addEmail": { + "type": "text", + "placeholders": {} + }, + "addToSpace": "Ajouter à l'espace", + "@addToSpace": {}, + "admin": "Administrateur", + "@admin": { + "type": "text", + "placeholders": {} + }, + "alias": "adresse", + "@alias": { + "type": "text", + "placeholders": {} + }, + "all": "Tout", + "@all": { + "type": "text", + "placeholders": {} + }, + "allChats": "Toutes les discussions", + "@allChats": { + "type": "text", + "placeholders": {} + }, + "answeredTheCall": "{senderName} a répondu à l'appel", + "@answeredTheCall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "anyoneCanJoin": "Tout le monde peut rejoindre", + "@anyoneCanJoin": { + "type": "text", + "placeholders": {} + }, + "appLock": "Verrouillage de l’application", + "@appLock": { + "type": "text", + "placeholders": {} + }, + "archive": "Archiver", + "@archive": { + "type": "text", + "placeholders": {} + }, + "areGuestsAllowedToJoin": "Les invités peuvent-i·e·ls rejoindre", + "@areGuestsAllowedToJoin": { + "type": "text", + "placeholders": {} + }, + "areYouSure": "Êtes-vous sûr·e ?", + "@areYouSure": { + "type": "text", + "placeholders": {} + }, + "areYouSureYouWantToLogout": "Voulez-vous vraiment vous déconnecter ?", + "@areYouSureYouWantToLogout": { + "type": "text", + "placeholders": {} + }, + "askSSSSSign": "Pour pouvoir faire signer l'autre personne, veuillez entrer la phrase de passe de votre trousseau sécurisé ou votre clé de récupération.", + "@askSSSSSign": { + "type": "text", + "placeholders": {} + }, + "askVerificationRequest": "Accepter cette demande de vérification de la part de {username} ?", + "@askVerificationRequest": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "autoplayImages": "Lire automatiquement les autocollants et les émojis animés", + "@autoplayImages": { + "type": "text", + "placeholder": {} + }, + "badServerLoginTypesException": "Le serveur d'accueil prend en charge les types de connexion :\n{serverVersions}\nMais cette application ne prend en charge que :\n{supportedVersions}", + "@badServerLoginTypesException": { + "type": "text", + "placeholders": { + "serverVersions": {}, + "supportedVersions": {} + } + }, + "badServerVersionsException": "Le serveur d'accueil prend en charge les versions des spécifications :\n{serverVersions}\nMais cette application ne prend en charge que {supportedVersions}", + "@badServerVersionsException": { + "type": "text", + "placeholders": { + "serverVersions": {}, + "supportedVersions": {} + } + }, + "banFromChat": "Bannir de la discussion", + "@banFromChat": { + "type": "text", + "placeholders": {} + }, + "banned": "Banni", + "@banned": { + "type": "text", + "placeholders": {} + }, + "bannedUser": "{username} a banni {targetName}", + "@bannedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "blockDevice": "Bloquer l'appareil", + "@blockDevice": { + "type": "text", + "placeholders": {} + }, + "blocked": "Bloqué", + "@blocked": { + "type": "text", + "placeholders": {} + }, + "botMessages": "Messages de bot", + "@botMessages": { + "type": "text", + "placeholders": {} + }, + "cancel": "Annuler", + "@cancel": { + "type": "text", + "placeholders": {} + }, + "cantOpenUri": "Impossible d'ouvrir l'URI {uri}", + "@cantOpenUri": { + "type": "text", + "placeholders": { + "uri": {} + } + }, + "changeDeviceName": "Modifier le nom de l'appareil", + "@changeDeviceName": { + "type": "text", + "placeholders": {} + }, + "changedTheChatAvatar": "{username} a changé l'image de la discussion", + "@changedTheChatAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheChatDescriptionTo": "{username} a changé la description de la discussion en : '{description}'", + "@changedTheChatDescriptionTo": { + "type": "text", + "placeholders": { + "username": {}, + "description": {} + } + }, + "changedTheChatNameTo": "{username} a renommé la discussion en : '{chatname}'", + "@changedTheChatNameTo": { + "type": "text", + "placeholders": { + "username": {}, + "chatname": {} + } + }, + "changedTheChatPermissions": "{username} a changé les permissions de la discussion", + "@changedTheChatPermissions": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheDisplaynameTo": "{username} a changé son nom en : '{displayname}'", + "@changedTheDisplaynameTo": { + "type": "text", + "placeholders": { + "username": {}, + "displayname": {} + } + }, + "changedTheGuestAccessRules": "{username} a changé les règles d'accès à la discussion pour les invités", + "@changedTheGuestAccessRules": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheGuestAccessRulesTo": "{username} a changé les règles d'accès à la discussion pour les invités en : {rules}", + "@changedTheGuestAccessRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "changedTheHistoryVisibility": "{username} a changé la visibilité de l'historique de la discussion", + "@changedTheHistoryVisibility": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheHistoryVisibilityTo": "{username} a changé la visibilité de l'historique de la discussion en : {rules}", + "@changedTheHistoryVisibilityTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "changedTheJoinRules": "{username} a changé les règles d'accès à la discussion", + "@changedTheJoinRules": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheJoinRulesTo": "{username} a changé les règles d'accès à la discussion en : {joinRules}", + "@changedTheJoinRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "joinRules": {} + } + }, + "changedTheProfileAvatar": "{username} a changé son avatar", + "@changedTheProfileAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheRoomAliases": "{username} a changé les adresses du salon", + "@changedTheRoomAliases": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheRoomInvitationLink": "{username} a changé le lien d'invitation", + "@changedTheRoomInvitationLink": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changePassword": "Changer de mot de passe", + "@changePassword": { + "type": "text", + "placeholders": {} + }, + "changeTheHomeserver": "Changer le serveur d'accueil", + "@changeTheHomeserver": { + "type": "text", + "placeholders": {} + }, + "changeTheme": "Changez votre style", + "@changeTheme": { + "type": "text", + "placeholders": {} + }, + "changeTheNameOfTheGroup": "Changer le nom du groupe", + "@changeTheNameOfTheGroup": { + "type": "text", + "placeholders": {} + }, + "changeYourAvatar": "Changer votre avatar", + "@changeYourAvatar": { + "type": "text", + "placeholders": {} + }, + "channelCorruptedDecryptError": "Le chiffrement a été corrompu", + "@channelCorruptedDecryptError": { + "type": "text", + "placeholders": {} + }, + "chat": "Discussion", + "@chat": { + "type": "text", + "placeholders": {} + }, + "chatBackup": "Sauvegarde des discussions", + "@chatBackup": { + "type": "text", + "placeholders": {} + }, + "chatBackupDescription": "Vos anciens messages sont sécurisés par une clé de récupération. Veillez à ne pas la perdre.", + "@chatBackupDescription": { + "type": "text", + "placeholders": {} + }, + "chatDetails": "Détails de la discussion", + "@chatDetails": { + "type": "text", + "placeholders": {} + }, + "chatHasBeenAddedToThisSpace": "La discussion a été ajoutée à cet espace", + "@chatHasBeenAddedToThisSpace": {}, + "chats": "Discussions", + "@chats": { + "type": "text", + "placeholders": {} + }, + "chooseAStrongPassword": "Choisissez un mot de passe fort", + "@chooseAStrongPassword": { + "type": "text", + "placeholders": {} + }, + "clearArchive": "Effacer les archives", + "@clearArchive": {}, + "close": "Fermer", + "@close": { + "type": "text", + "placeholders": {} + }, + "commandHint_ban": "Bannir l'utilisateur/trice donné(e) de ce salon", + "@commandHint_ban": { + "type": "text", + "description": "Usage hint for the command /ban" + }, + "commandHint_html": "Envoyer du texte au format HTML", + "@commandHint_html": { + "type": "text", + "description": "Usage hint for the command /html" + }, + "commandHint_invite": "Inviter l'utilisateur/trice donné(e) dans ce salon", + "@commandHint_invite": { + "type": "text", + "description": "Usage hint for the command /invite" + }, + "commandHint_join": "Rejoindre le salon donné", + "@commandHint_join": { + "type": "text", + "description": "Usage hint for the command /join" + }, + "commandHint_kick": "Supprime l'utilisateur/trice donné(e) de ce salon", + "@commandHint_kick": { + "type": "text", + "description": "Usage hint for the command /kick" + }, + "commandHint_leave": "Quitter ce salon", + "@commandHint_leave": { + "type": "text", + "description": "Usage hint for the command /leave" + }, + "commandHint_me": "Décrivez-vous", + "@commandHint_me": { + "type": "text", + "description": "Usage hint for the command /me" + }, + "commandHint_myroomavatar": "Définir votre image pour ce salon (par mxc-uri)", + "@commandHint_myroomavatar": { + "type": "text", + "description": "Usage hint for the command /myroomavatar" + }, + "commandHint_myroomnick": "Définir votre nom d'affichage pour ce salon", + "@commandHint_myroomnick": { + "type": "text", + "description": "Usage hint for the command /myroomnick" + }, + "commandHint_op": "Définir le niveau de puissance de l'utilisateur/trice donné(e) (par défaut : 50)", + "@commandHint_op": { + "type": "text", + "description": "Usage hint for the command /op" + }, + "commandHint_plain": "Envoyer du texte non formaté", + "@commandHint_plain": { + "type": "text", + "description": "Usage hint for the command /plain" + }, + "commandHint_react": "Envoyer une réponse en tant que réaction", + "@commandHint_react": { + "type": "text", + "description": "Usage hint for the command /react" + }, + "commandHint_send": "Envoyer du texte", + "@commandHint_send": { + "type": "text", + "description": "Usage hint for the command /send" + }, + "commandHint_unban": "Débannir l'utilisateur/trice donné(e) de ce salon", + "@commandHint_unban": { + "type": "text", + "description": "Usage hint for the command /unban" + }, + "commandInvalid": "Commande invalide", + "@commandInvalid": { + "type": "text" + }, + "commandMissing": "{command} n'est pas une commande.", + "@commandMissing": { + "type": "text", + "placeholders": { + "command": {} + }, + "description": "State that {command} is not a valid /command." + }, + "compareEmojiMatch": "Veuillez comparer les émojis", + "@compareEmojiMatch": { + "type": "text", + "placeholders": {} + }, + "compareNumbersMatch": "Veuillez comparer les chiffres", + "@compareNumbersMatch": { + "type": "text", + "placeholders": {} + }, + "configureChat": "Configurer la discussion", + "@configureChat": { + "type": "text", + "placeholders": {} + }, + "confirm": "Confirmer", + "@confirm": { + "type": "text", + "placeholders": {} + }, + "connect": "Se connecter", + "@connect": { + "type": "text", + "placeholders": {} + }, + "contactHasBeenInvitedToTheGroup": "Le contact a été invité au groupe", + "@contactHasBeenInvitedToTheGroup": { + "type": "text", + "placeholders": {} + }, + "containsDisplayName": "Contient un nom d'affichage", + "@containsDisplayName": { + "type": "text", + "placeholders": {} + }, + "containsUserName": "Contient un nom d'utilisateur·ice", + "@containsUserName": { + "type": "text", + "placeholders": {} + }, + "contentHasBeenReported": "Le contenu a été signalé aux administrateurs du serveur", + "@contentHasBeenReported": { + "type": "text", + "placeholders": {} + }, + "copiedToClipboard": "Copié dans le presse-papier", + "@copiedToClipboard": { + "type": "text", + "placeholders": {} + }, + "copy": "Copier", + "@copy": { + "type": "text", + "placeholders": {} + }, + "copyToClipboard": "Copier dans le presse-papiers", + "@copyToClipboard": { + "type": "text", + "placeholders": {} + }, + "couldNotDecryptMessage": "Impossible de déchiffrer le message : {error}", + "@couldNotDecryptMessage": { + "type": "text", + "placeholders": { + "error": {} + } + }, + "countParticipants": "{count} participant(s)", + "@countParticipants": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "create": "Créer", + "@create": { + "type": "text", + "placeholders": {} + }, + "createdTheChat": "💬 {username} a créé la discussion", + "@createdTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "createNewSpace": "Nouvel espace", + "@createNewSpace": { + "type": "text", + "placeholders": {} + }, + "currentlyActive": "Actif en ce moment", + "@currentlyActive": { + "type": "text", + "placeholders": {} + }, + "darkTheme": "Sombre", + "@darkTheme": { + "type": "text", + "placeholders": {} + }, + "dateAndTimeOfDay": "{date}, {timeOfDay}", + "@dateAndTimeOfDay": { + "type": "text", + "placeholders": { + "date": {}, + "timeOfDay": {} + } + }, + "dateWithoutYear": "{day}/{month}", + "@dateWithoutYear": { + "type": "text", + "placeholders": { + "month": {}, + "day": {} + } + }, + "dateWithYear": "{day}/{month}/{year}", + "@dateWithYear": { + "type": "text", + "placeholders": { + "year": {}, + "month": {}, + "day": {} + } + }, + "deactivateAccountWarning": "Cette opération va désactiver votre compte. Une fois cette action effectuée, aucun retour en arrière n'est possible ! Êtes-vous sûr·e ?", + "@deactivateAccountWarning": { + "type": "text", + "placeholders": {} + }, + "defaultPermissionLevel": "Niveau d'autorisation par défaut", + "@defaultPermissionLevel": { + "type": "text", + "placeholders": {} + }, + "delete": "Supprimer", + "@delete": { + "type": "text", + "placeholders": {} + }, + "deleteAccount": "Supprimer le compte", + "@deleteAccount": { + "type": "text", + "placeholders": {} + }, + "deleteMessage": "Supprimer le message", + "@deleteMessage": { + "type": "text", + "placeholders": {} + }, + "device": "Appareil", + "@device": { + "type": "text", + "placeholders": {} + }, + "deviceId": "Identifiant de l'appareil", + "@deviceId": { + "type": "text", + "placeholders": {} + }, + "devices": "Appareils", + "@devices": { + "type": "text", + "placeholders": {} + }, + "directChats": "Discussions directes", + "@directChats": { + "type": "text", + "placeholders": {} + }, + "displaynameHasBeenChanged": "Renommage effectué", + "@displaynameHasBeenChanged": { + "type": "text", + "placeholders": {} + }, + "downloadFile": "Télécharger le fichier", + "@downloadFile": { + "type": "text", + "placeholders": {} + }, + "edit": "Modifier", + "@edit": { + "type": "text", + "placeholders": {} + }, + "editBlockedServers": "Modifier les serveurs bloqués", + "@editBlockedServers": { + "type": "text", + "placeholders": {} + }, + "editDisplayname": "Changer de nom d'affichage", + "@editDisplayname": { + "type": "text", + "placeholders": {} + }, + "editRoomAliases": "Modifier les adresses du salon", + "@editRoomAliases": { + "type": "text", + "placeholders": {} + }, + "editRoomAvatar": "Modifier l'avatar du salon", + "@editRoomAvatar": { + "type": "text", + "placeholders": {} + }, + "emoteExists": "Cette émoticône existe déjà !", + "@emoteExists": { + "type": "text", + "placeholders": {} + }, + "emoteInvalid": "Raccourci d'émoticône invalide !", + "@emoteInvalid": { + "type": "text", + "placeholders": {} + }, + "emotePacks": "Packs d'émoticônes pour le salon", + "@emotePacks": { + "type": "text", + "placeholders": {} + }, + "emoteSettings": "Paramètre des émoticônes", + "@emoteSettings": { + "type": "text", + "placeholders": {} + }, + "emoteShortcode": "Raccourci de l'émoticône", + "@emoteShortcode": { + "type": "text", + "placeholders": {} + }, + "emoteWarnNeedToPick": "Vous devez sélectionner un raccourci d'émoticône et une image !", + "@emoteWarnNeedToPick": { + "type": "text", + "placeholders": {} + }, + "emptyChat": "Discussion vide", + "@emptyChat": { + "type": "text", + "placeholders": {} + }, + "enableEmotesGlobally": "Activer globalement le pack d'émoticônes", + "@enableEmotesGlobally": { + "type": "text", + "placeholders": {} + }, + "enableEncryption": "Activer le chiffrement", + "@enableEncryption": { + "type": "text", + "placeholders": {} + }, + "enableEncryptionWarning": "Vous ne pourrez plus désactiver le chiffrement. Êtes-vous sûr(e) ?", + "@enableEncryptionWarning": { + "type": "text", + "placeholders": {} + }, + "encrypted": "Chiffré", + "@encrypted": { + "type": "text", + "placeholders": {} + }, + "encryption": "Chiffrement", + "@encryption": { + "type": "text", + "placeholders": {} + }, + "encryptionNotEnabled": "Le chiffrement n'est pas activé", + "@encryptionNotEnabled": { + "type": "text", + "placeholders": {} + }, + "endedTheCall": "{senderName} a mis fin à l'appel", + "@endedTheCall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "enterAnEmailAddress": "Saisissez une adresse de courriel", + "@enterAnEmailAddress": { + "type": "text", + "placeholders": {} + }, + "enterYourHomeserver": "Renseignez votre serveur d'accueil", + "@enterYourHomeserver": { + "type": "text", + "placeholders": {} + }, + "errorObtainingLocation": "Erreur lors de l'obtention de la localisation : {error}", + "@errorObtainingLocation": { + "type": "text", + "placeholders": { + "error": {} + } + }, + "everythingReady": "Tout est prêt !", + "@everythingReady": { + "type": "text", + "placeholders": {} + }, + "extremeOffensive": "Extrêmement offensant", + "@extremeOffensive": { + "type": "text", + "placeholders": {} + }, + "fileName": "Nom du ficher", + "@fileName": { + "type": "text", + "placeholders": {} + }, + "fluffychat": "FluffyChat", + "@fluffychat": { + "type": "text", + "placeholders": {} + }, + "fontSize": "Taille de la police", + "@fontSize": { + "type": "text", + "placeholders": {} + }, + "forward": "Transférer", + "@forward": { + "type": "text", + "placeholders": {} + }, + "fromJoining": "À partir de l'entrée dans le salon", + "@fromJoining": { + "type": "text", + "placeholders": {} + }, + "fromTheInvitation": "À partir de l'invitation", + "@fromTheInvitation": { + "type": "text", + "placeholders": {} + }, + "goToTheNewRoom": "Aller dans le nouveau salon", + "@goToTheNewRoom": { + "type": "text", + "placeholders": {} + }, + "group": "Groupe", + "@group": { + "type": "text", + "placeholders": {} + }, + "groupIsPublic": "Le groupe est public", + "@groupIsPublic": { + "type": "text", + "placeholders": {} + }, + "groups": "Groupes", + "@groups": { + "type": "text", + "placeholders": {} + }, + "groupWith": "Groupe avec {displayname}", + "@groupWith": { + "type": "text", + "placeholders": { + "displayname": {} + } + }, + "guestsAreForbidden": "Les invités ne peuvent pas rejoindre", + "@guestsAreForbidden": { + "type": "text", + "placeholders": {} + }, + "guestsCanJoin": "Les invités peuvent rejoindre", + "@guestsCanJoin": { + "type": "text", + "placeholders": {} + }, + "hasWithdrawnTheInvitationFor": "{username} a annulé l'invitation de {targetName}", + "@hasWithdrawnTheInvitationFor": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "help": "Aide", + "@help": { + "type": "text", + "placeholders": {} + }, + "hideRedactedEvents": "Cacher les évènements supprimés", + "@hideRedactedEvents": { + "type": "text", + "placeholders": {} + }, + "hideUnknownEvents": "Cacher les évènements inconnus", + "@hideUnknownEvents": { + "type": "text", + "placeholders": {} + }, + "howOffensiveIsThisContent": "À quel point ce contenu est-il offensant ?", + "@howOffensiveIsThisContent": { + "type": "text", + "placeholders": {} + }, + "id": "Identifiant", + "@id": { + "type": "text", + "placeholders": {} + }, + "identity": "Identité", + "@identity": { + "type": "text", + "placeholders": {} + }, + "ignore": "Ignorer", + "@ignore": { + "type": "text", + "placeholders": {} + }, + "ignoredUsers": "Utilisateur·ices ignoré·es", + "@ignoredUsers": { + "type": "text", + "placeholders": {} + }, + "iHaveClickedOnLink": "J'ai cliqué sur le lien", + "@iHaveClickedOnLink": { + "type": "text", + "placeholders": {} + }, + "incorrectPassphraseOrKey": "Phrase de passe ou clé de récupération incorrecte", + "@incorrectPassphraseOrKey": { + "type": "text", + "placeholders": {} + }, + "inoffensive": "Non offensant", + "@inoffensive": { + "type": "text", + "placeholders": {} + }, + "inviteContact": "Inviter un contact", + "@inviteContact": { + "type": "text", + "placeholders": {} + }, + "inviteContactToGroup": "Inviter un contact dans {groupName}", + "@inviteContactToGroup": { + "type": "text", + "placeholders": { + "groupName": {} + } + }, + "invited": "Invité·e", + "@invited": { + "type": "text", + "placeholders": {} + }, + "invitedUser": "📩 {username} a invité {targetName}", + "@invitedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "invitedUsersOnly": "Uniquement les utilisateur·ices invité·es", + "@invitedUsersOnly": { + "type": "text", + "placeholders": {} + }, + "inviteForMe": "Inviter pour moi", + "@inviteForMe": { + "type": "text", + "placeholders": {} + }, + "inviteText": "{username} vous a invité·e sur FluffyChat.\n1. Visiter fluffychat.im et installer l'application\n2. Inscrivez-vous ou connectez-vous\n3. Ouvrez le lien d'invitation :\n{link}", + "@inviteText": { + "type": "text", + "placeholders": { + "username": {}, + "link": {} + } + }, + "isTyping": "est en train d'écrire…", + "@isTyping": { + "type": "text", + "placeholders": {} + }, + "joinedTheChat": "👋 {username} a rejoint la discussion", + "@joinedTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "joinRoom": "Rejoindre le salon", + "@joinRoom": { + "type": "text", + "placeholders": {} + }, + "kicked": "👞 {username} a expulsé {targetName}", + "@kicked": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "kickedAndBanned": "🙅 {username} a expulsé et banni {targetName}", + "@kickedAndBanned": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "kickFromChat": "Expulser de la discussion", + "@kickFromChat": { + "type": "text", + "placeholders": {} + }, + "lastActiveAgo": "Vu·e pour la dernière fois : {localizedTimeShort}", + "@lastActiveAgo": { + "type": "text", + "placeholders": { + "localizedTimeShort": {} + } + }, + "leave": "Partir", + "@leave": { + "type": "text", + "placeholders": {} + }, + "leftTheChat": "A quitté la discussion", + "@leftTheChat": { + "type": "text", + "placeholders": {} + }, + "license": "Licence", + "@license": { + "type": "text", + "placeholders": {} + }, + "lightTheme": "Clair", + "@lightTheme": { + "type": "text", + "placeholders": {} + }, + "loadCountMoreParticipants": "Charger {count} participant·es de plus", + "@loadCountMoreParticipants": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "loadingPleaseWait": "Chargement… Veuillez patienter.", + "@loadingPleaseWait": { + "type": "text", + "placeholders": {} + }, + "loadMore": "Charger plus…", + "@loadMore": { + "type": "text", + "placeholders": {} + }, + "locationDisabledNotice": "Les services de localisation sont désactivés. Il est nécessaire de les activer avant de pouvoir partager votre localisation.", + "@locationDisabledNotice": { + "type": "text", + "placeholders": {} + }, + "locationPermissionDeniedNotice": "L'application n'a pas la permission d'accéder à votre localisation. Merci de l'accorder afin de pouvoir partager votre localisation.", + "@locationPermissionDeniedNotice": { + "type": "text", + "placeholders": {} + }, + "login": "Se connecter", + "@login": { + "type": "text", + "placeholders": {} + }, + "logInTo": "Se connecter à {homeserver}", + "@logInTo": { + "type": "text", + "placeholders": { + "homeserver": {} + } + }, + "logout": "Se déconnecter", + "@logout": { + "type": "text", + "placeholders": {} + }, + "memberChanges": "Changements de membres", + "@memberChanges": { + "type": "text", + "placeholders": {} + }, + "mention": "Mentionner", + "@mention": { + "type": "text", + "placeholders": {} + }, + "messages": "Messages", + "@messages": { + "type": "text", + "placeholders": {} + }, + "moderator": "Modérateur·rice", + "@moderator": { + "type": "text", + "placeholders": {} + }, + "muteChat": "Mettre la discussion en sourdine", + "@muteChat": { + "type": "text", + "placeholders": {} + }, + "needPantalaimonWarning": "Pour l'instant, vous avez besoin de Pantalaimon pour utiliser le chiffrement de bout en bout.", + "@needPantalaimonWarning": { + "type": "text", + "placeholders": {} + }, + "newChat": "Nouvelle discussion", + "@newChat": { + "type": "text", + "placeholders": {} + }, + "newMessageInFluffyChat": "💬 Nouveau message dans FluffyChat", + "@newMessageInFluffyChat": { + "type": "text", + "placeholders": {} + }, + "newVerificationRequest": "Nouvelle demande de vérification !", + "@newVerificationRequest": { + "type": "text", + "placeholders": {} + }, + "next": "Suivant", + "@next": { + "type": "text", + "placeholders": {} + }, + "no": "Non", + "@no": { + "type": "text", + "placeholders": {} + }, + "noConnectionToTheServer": "Aucune connexion au serveur", + "@noConnectionToTheServer": { + "type": "text", + "placeholders": {} + }, + "noEmotesFound": "Aucune émoticône trouvée. 😕", + "@noEmotesFound": { + "type": "text", + "placeholders": {} + }, + "noEncryptionForPublicRooms": "Vous pouvez activer le chiffrement seulement quand le salon n'est plus accessible au public.", + "@noEncryptionForPublicRooms": { + "type": "text", + "placeholders": {} + }, + "noGoogleServicesWarning": "Firebase Cloud Messaging ne semble pas être disponible sur votre appareil. Pour continuer à recevoir des notifications poussées, nous vous recommandons d'installer ntfy. Avec ntfy ou un autre fournisseur Unified Push, vous pouvez recevoir des notifications poussées de manière sécurisée. Vous pouvez télécharger ntfy sur le PlayStore ou sur F-Droid.", + "@noGoogleServicesWarning": { + "type": "text", + "placeholders": {} + }, + "noMatrixServer": "{server1} n'est pas un serveur Matrix, souhaitez-vous utiliser {server2} à la place ?", + "@noMatrixServer": { + "type": "text", + "placeholders": { + "server1": {}, + "server2": {} + } + }, + "none": "Aucun", + "@none": { + "type": "text", + "placeholders": {} + }, + "noPasswordRecoveryDescription": "Vous n'avez pas encore ajouté de moyen pour récupérer votre mot de passe.", + "@noPasswordRecoveryDescription": { + "type": "text", + "placeholders": {} + }, + "noPermission": "Aucune permission", + "@noPermission": { + "type": "text", + "placeholders": {} + }, + "noRoomsFound": "Aucun salon trouvé…", + "@noRoomsFound": { + "type": "text", + "placeholders": {} + }, + "notifications": "Notifications", + "@notifications": { + "type": "text", + "placeholders": {} + }, + "notificationsEnabledForThisAccount": "Notifications activées pour ce compte", + "@notificationsEnabledForThisAccount": { + "type": "text", + "placeholders": {} + }, + "numUsersTyping": "{count} utilisateur·ices écrivent…", + "@numUsersTyping": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "obtainingLocation": "Obtention de la localisation…", + "@obtainingLocation": { + "type": "text", + "placeholders": {} + }, + "offensive": "Offensant", + "@offensive": { + "type": "text", + "placeholders": {} + }, + "offline": "Hors ligne", + "@offline": { + "type": "text", + "placeholders": {} + }, + "ok": "Valider", + "@ok": { + "type": "text", + "placeholders": {} + }, + "online": "En ligne", + "@online": { + "type": "text", + "placeholders": {} + }, + "onlineKeyBackupEnabled": "La sauvegarde en ligne des clés est activée", + "@onlineKeyBackupEnabled": { + "type": "text", + "placeholders": {} + }, + "oopsPushError": "Oups ! Une erreur s'est malheureusement produite lors du réglage des notifications.", + "@oopsPushError": { + "type": "text", + "placeholders": {} + }, + "oopsSomethingWentWrong": "Oups, un problème est survenu…", + "@oopsSomethingWentWrong": { + "type": "text", + "placeholders": {} + }, + "openAppToReadMessages": "Ouvrez l'application pour lire le message", + "@openAppToReadMessages": { + "type": "text", + "placeholders": {} + }, + "openCamera": "Ouvrir l'appareil photo", + "@openCamera": { + "type": "text", + "placeholders": {} + }, + "openInMaps": "Ouvrir dans maps", + "@openInMaps": { + "type": "text", + "placeholders": {} + }, + "or": "Ou", + "@or": { + "type": "text", + "placeholders": {} + }, + "participant": "Participant(e)", + "@participant": { + "type": "text", + "placeholders": {} + }, + "passphraseOrKey": "Phrase de passe ou clé de récupération", + "@passphraseOrKey": { + "type": "text", + "placeholders": {} + }, + "password": "Mot de passe", + "@password": { + "type": "text", + "placeholders": {} + }, + "passwordForgotten": "Mot de passe oublié", + "@passwordForgotten": { + "type": "text", + "placeholders": {} + }, + "passwordHasBeenChanged": "Le mot de passe a été modifié", + "@passwordHasBeenChanged": { + "type": "text", + "placeholders": {} + }, + "passwordRecovery": "Récupération du mot de passe", + "@passwordRecovery": { + "type": "text", + "placeholders": {} + }, + "people": "Personnes", + "@people": { + "type": "text", + "placeholders": {} + }, + "pickImage": "Choisir une image", + "@pickImage": { + "type": "text", + "placeholders": {} + }, + "pin": "Épingler", + "@pin": { + "type": "text", + "placeholders": {} + }, + "play": "Lire {fileName}", + "@play": { + "type": "text", + "placeholders": { + "fileName": {} + } + }, + "pleaseChoose": "Veuillez choisir", + "@pleaseChoose": { + "type": "text", + "placeholders": {} + }, + "pleaseChooseAPasscode": "Veuillez choisir un code d’accès", + "@pleaseChooseAPasscode": { + "type": "text", + "placeholders": {} + }, + "pleaseClickOnLink": "Veuillez cliquer sur le lien contenu dans le courriel puis continuez.", + "@pleaseClickOnLink": { + "type": "text", + "placeholders": {} + }, + "pleaseEnter4Digits": "Veuillez saisir 4 chiffres ou laisser vide pour désactiver le verrouillage de l’application.", + "@pleaseEnter4Digits": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourPassword": "Renseignez votre mot de passe", + "@pleaseEnterYourPassword": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourPin": "Veuillez saisir votre code PIN", + "@pleaseEnterYourPin": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourUsername": "Renseignez votre nom d'utilisateur·ice", + "@pleaseEnterYourUsername": { + "type": "text", + "placeholders": {} + }, + "pleaseFollowInstructionsOnWeb": "Veuillez suivre les instructions sur le site et appuyer sur Suivant.", + "@pleaseFollowInstructionsOnWeb": { + "type": "text", + "placeholders": {} + }, + "privacy": "Vie privée", + "@privacy": { + "type": "text", + "placeholders": {} + }, + "publicRooms": "Salons publics", + "@publicRooms": { + "type": "text", + "placeholders": {} + }, + "pushRules": "Règles de notifications", + "@pushRules": { + "type": "text", + "placeholders": {} + }, + "reason": "Motif", + "@reason": { + "type": "text", + "placeholders": {} + }, + "recording": "Enregistrement", + "@recording": { + "type": "text", + "placeholders": {} + }, + "redactedAnEvent": "{username} a supprimé un évènement", + "@redactedAnEvent": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "redactMessage": "Supprimer un message", + "@redactMessage": { + "type": "text", + "placeholders": {} + }, + "register": "S'inscrire", + "@register": { + "type": "text", + "placeholders": {} + }, + "reject": "Refuser", + "@reject": { + "type": "text", + "placeholders": {} + }, + "rejectedTheInvitation": "{username} a refusé l'invitation", + "@rejectedTheInvitation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "rejoin": "Rejoindre de nouveau", + "@rejoin": { + "type": "text", + "placeholders": {} + }, + "remove": "Supprimer", + "@remove": { + "type": "text", + "placeholders": {} + }, + "removeAllOtherDevices": "Supprimer tous les autres appareils", + "@removeAllOtherDevices": { + "type": "text", + "placeholders": {} + }, + "removedBy": "Supprimé par {username}", + "@removedBy": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "removeDevice": "Supprimer l'appareil", + "@removeDevice": { + "type": "text", + "placeholders": {} + }, + "unbanFromChat": "Débannissement de la discussion", + "@unbanFromChat": { + "type": "text", + "placeholders": {} + }, + "removeYourAvatar": "Supprimer votre avatar", + "@removeYourAvatar": { + "type": "text", + "placeholders": {} + }, + "renderRichContent": "Afficher les contenus riches des messages", + "@renderRichContent": { + "type": "text", + "placeholders": {} + }, + "replaceRoomWithNewerVersion": "Remplacer le salon par une nouvelle version", + "@replaceRoomWithNewerVersion": { + "type": "text", + "placeholders": {} + }, + "reply": "Répondre", + "@reply": { + "type": "text", + "placeholders": {} + }, + "reportMessage": "Signaler un message", + "@reportMessage": { + "type": "text", + "placeholders": {} + }, + "requestPermission": "Demander la permission", + "@requestPermission": { + "type": "text", + "placeholders": {} + }, + "roomHasBeenUpgraded": "Le salon a été mis à niveau", + "@roomHasBeenUpgraded": { + "type": "text", + "placeholders": {} + }, + "roomVersion": "Version du salon", + "@roomVersion": { + "type": "text", + "placeholders": {} + }, + "saveFile": "Enregistrer le fichier", + "@saveFile": { + "type": "text", + "placeholders": {} + }, + "search": "Rechercher", + "@search": { + "type": "text", + "placeholders": {} + }, + "security": "Sécurité", + "@security": { + "type": "text", + "placeholders": {} + }, + "seenByUser": "Vu par {username}", + "@seenByUser": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "send": "Envoyer", + "@send": { + "type": "text", + "placeholders": {} + }, + "sendAMessage": "Envoyer un message", + "@sendAMessage": { + "type": "text", + "placeholders": {} + }, + "sendAsText": "Envoyer un texte", + "@sendAsText": { + "type": "text" + }, + "sendAudio": "Envoyer un fichier audio", + "@sendAudio": { + "type": "text", + "placeholders": {} + }, + "sendFile": "Envoyer un fichier", + "@sendFile": { + "type": "text", + "placeholders": {} + }, + "sendImage": "Envoyer une image", + "@sendImage": { + "type": "text", + "placeholders": {} + }, + "sendMessages": "Envoyer des messages", + "@sendMessages": { + "type": "text", + "placeholders": {} + }, + "sendOriginal": "Envoyer le fichier original", + "@sendOriginal": { + "type": "text", + "placeholders": {} + }, + "sendSticker": "Envoyer un autocollant", + "@sendSticker": { + "type": "text", + "placeholders": {} + }, + "sendVideo": "Envoyer une vidéo", + "@sendVideo": { + "type": "text", + "placeholders": {} + }, + "sentAFile": "📁 {username} a envoyé un fichier", + "@sentAFile": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAnAudio": "🎤 {username} a envoyé un fichier audio", + "@sentAnAudio": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAPicture": "🖼️ {username} a envoyé une image", + "@sentAPicture": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentASticker": "😊 {username} a envoyé un autocollant", + "@sentASticker": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAVideo": "🎥 {username} a envoyé une vidéo", + "@sentAVideo": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentCallInformations": "{senderName} a envoyé des informations sur l'appel", + "@sentCallInformations": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "setAsCanonicalAlias": "Définir comme adresse principale", + "@setAsCanonicalAlias": { + "type": "text", + "placeholders": {} + }, + "setCustomEmotes": "Définir des émoticônes personnalisées", + "@setCustomEmotes": { + "type": "text", + "placeholders": {} + }, + "setInvitationLink": "Créer un lien d'invitation", + "@setInvitationLink": { + "type": "text", + "placeholders": {} + }, + "setPermissionsLevel": "Définir le niveau de permissions", + "@setPermissionsLevel": { + "type": "text", + "placeholders": {} + }, + "setStatus": "Définir le statut", + "@setStatus": { + "type": "text", + "placeholders": {} + }, + "settings": "Paramètres", + "@settings": { + "type": "text", + "placeholders": {} + }, + "share": "Partager", + "@share": { + "type": "text", + "placeholders": {} + }, + "sharedTheLocation": "{username} a partagé sa position", + "@sharedTheLocation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "shareLocation": "Partager la localisation", + "@shareLocation": { + "type": "text", + "placeholders": {} + }, + "showPassword": "Afficher le mot de passe", + "@showPassword": { + "type": "text", + "placeholders": {} + }, + "singlesignon": "Authentification unique", + "@singlesignon": { + "type": "text", + "placeholders": {} + }, + "skip": "Ignorer", + "@skip": { + "type": "text", + "placeholders": {} + }, + "sourceCode": "Code source", + "@sourceCode": { + "type": "text", + "placeholders": {} + }, + "spaceIsPublic": "L'espace est public", + "@spaceIsPublic": { + "type": "text", + "placeholders": {} + }, + "spaceName": "Nom de l'espace", + "@spaceName": { + "type": "text", + "placeholders": {} + }, + "startedACall": "{senderName} a démarré un appel", + "@startedACall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "status": "Statut", + "@status": { + "type": "text", + "placeholders": {} + }, + "statusExampleMessage": "Comment allez-vous aujourd'hui ?", + "@statusExampleMessage": { + "type": "text", + "placeholders": {} + }, + "submit": "Soumettre", + "@submit": { + "type": "text", + "placeholders": {} + }, + "synchronizingPleaseWait": "Synchronisation... Veuillez patienter.", + "@synchronizingPleaseWait": { + "type": "text", + "placeholders": {} + }, + "systemTheme": "Système", + "@systemTheme": { + "type": "text", + "placeholders": {} + }, + "theyDontMatch": "Elles ne correspondent pas", + "@theyDontMatch": { + "type": "text", + "placeholders": {} + }, + "theyMatch": "Elles correspondent", + "@theyMatch": { + "type": "text", + "placeholders": {} + }, + "title": "FluffyChat", + "@title": { + "description": "Title for the application", + "type": "text", + "placeholders": {} + }, + "toggleFavorite": "Activer/désactiver en favori", + "@toggleFavorite": { + "type": "text", + "placeholders": {} + }, + "toggleMuted": "Activer/désactiver la sourdine", + "@toggleMuted": { + "type": "text", + "placeholders": {} + }, + "toggleUnread": "Marquer comme lu / non lu", + "@toggleUnread": { + "type": "text", + "placeholders": {} + }, + "tooManyRequestsWarning": "Trop de requêtes. Veuillez réessayer plus tard !", + "@tooManyRequestsWarning": { + "type": "text", + "placeholders": {} + }, + "transferFromAnotherDevice": "Transfert à partir d'un autre appareil", + "@transferFromAnotherDevice": { + "type": "text", + "placeholders": {} + }, + "tryToSendAgain": "Retenter l'envoi", + "@tryToSendAgain": { + "type": "text", + "placeholders": {} + }, + "unavailable": "Indisponible", + "@unavailable": { + "type": "text", + "placeholders": {} + }, + "unbannedUser": "{username} a annulé le bannissement de {targetName}", + "@unbannedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "unblockDevice": "Retirer le blocage sur l'appareil", + "@unblockDevice": { + "type": "text", + "placeholders": {} + }, + "unknownDevice": "Appareil inconnu", + "@unknownDevice": { + "type": "text", + "placeholders": {} + }, + "unknownEncryptionAlgorithm": "Algorithme de chiffrement inconnu", + "@unknownEncryptionAlgorithm": { + "type": "text", + "placeholders": {} + }, + "unknownEvent": "Événement de type inconnu : '{type}'", + "@unknownEvent": { + "type": "text", + "placeholders": { + "type": {} + } + }, + "unmuteChat": "Retirer la sourdine de la discussion", + "@unmuteChat": { + "type": "text", + "placeholders": {} + }, + "unpin": "Désépingler", + "@unpin": { + "type": "text", + "placeholders": {} + }, + "unreadChats": "{unreadCount, plural, =1{1 discussion non lue} other{{unreadCount} discussions non lues}}", + "@unreadChats": { + "type": "text", + "placeholders": { + "unreadCount": {} + } + }, + "userAndOthersAreTyping": "{username} et {count} autres sont en train d'écrire…", + "@userAndOthersAreTyping": { + "type": "text", + "placeholders": { + "username": {}, + "count": {} + } + }, + "userAndUserAreTyping": "{username} et {username2} sont en train d'écrire…", + "@userAndUserAreTyping": { + "type": "text", + "placeholders": { + "username": {}, + "username2": {} + } + }, + "userIsTyping": "{username} est en train d'écrire…", + "@userIsTyping": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "userLeftTheChat": "🚪 {username} a quitté la discussion", + "@userLeftTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "username": "Nom d'utilisateur·ice", + "@username": { + "type": "text", + "placeholders": {} + }, + "userSentUnknownEvent": "{username} a envoyé un évènement de type {type}", + "@userSentUnknownEvent": { + "type": "text", + "placeholders": { + "username": {}, + "type": {} + } + }, + "verified": "Vérifié", + "@verified": { + "type": "text", + "placeholders": {} + }, + "verify": "Vérifier", + "@verify": { + "type": "text", + "placeholders": {} + }, + "verifyStart": "Commencer la vérification", + "@verifyStart": { + "type": "text", + "placeholders": {} + }, + "verifySuccess": "La vérification a été effectuée avec succès !", + "@verifySuccess": { + "type": "text", + "placeholders": {} + }, + "verifyTitle": "Vérification de l'autre compte", + "@verifyTitle": { + "type": "text", + "placeholders": {} + }, + "videoCall": "Appel vidéo", + "@videoCall": { + "type": "text", + "placeholders": {} + }, + "visibilityOfTheChatHistory": "Visibilité de l'historique de la discussion", + "@visibilityOfTheChatHistory": { + "type": "text", + "placeholders": {} + }, + "visibleForAllParticipants": "Visible pour tous les participant·es", + "@visibleForAllParticipants": { + "type": "text", + "placeholders": {} + }, + "visibleForEveryone": "Visible pour tout le monde", + "@visibleForEveryone": { + "type": "text", + "placeholders": {} + }, + "voiceMessage": "Message vocal", + "@voiceMessage": { + "type": "text", + "placeholders": {} + }, + "waitingPartnerAcceptRequest": "En attente de l'acceptation de la demande par le partenaire…", + "@waitingPartnerAcceptRequest": { + "type": "text", + "placeholders": {} + }, + "waitingPartnerEmoji": "En attente de l'acceptation de l'émoji par le partenaire…", + "@waitingPartnerEmoji": { + "type": "text", + "placeholders": {} + }, + "waitingPartnerNumbers": "En attente de l'acceptation des nombres par le partenaire…", + "@waitingPartnerNumbers": { + "type": "text", + "placeholders": {} + }, + "wallpaper": "Image de fond :", + "@wallpaper": { + "type": "text", + "placeholders": {} + }, + "warning": "Attention !", + "@warning": { + "type": "text", + "placeholders": {} + }, + "weSentYouAnEmail": "Nous vous avons envoyé un courriel", + "@weSentYouAnEmail": { + "type": "text", + "placeholders": {} + }, + "whoCanPerformWhichAction": "Qui peut faire quelle action", + "@whoCanPerformWhichAction": { + "type": "text", + "placeholders": {} + }, + "whoIsAllowedToJoinThisGroup": "Qui est autorisé·e à rejoindre ce groupe", + "@whoIsAllowedToJoinThisGroup": { + "type": "text", + "placeholders": {} + }, + "whyDoYouWantToReportThis": "Pourquoi voulez-vous le signaler ?", + "@whyDoYouWantToReportThis": { + "type": "text", + "placeholders": {} + }, + "wipeChatBackup": "Effacer la sauvegarde de votre discussion pour créer une nouvelle clé de récupération ?", + "@wipeChatBackup": { + "type": "text", + "placeholders": {} + }, + "withTheseAddressesRecoveryDescription": "Grâce à ces adresses, vous pouvez récupérer votre mot de passe si vous en avez besoin.", + "@withTheseAddressesRecoveryDescription": { + "type": "text", + "placeholders": {} + }, + "writeAMessage": "Écrivez un message…", + "@writeAMessage": { + "type": "text", + "placeholders": {} + }, + "yes": "Oui", + "@yes": { + "type": "text", + "placeholders": {} + }, + "you": "Vous", + "@you": { + "type": "text", + "placeholders": {} + }, + "youAreNoLongerParticipatingInThisChat": "Vous ne participez plus à cette discussion", + "@youAreNoLongerParticipatingInThisChat": { + "type": "text", + "placeholders": {} + }, + "youHaveBeenBannedFromThisChat": "Vous avez été banni·e de cette discussion", + "@youHaveBeenBannedFromThisChat": { + "type": "text", + "placeholders": {} + }, + "yourPublicKey": "Votre clé publique", + "@yourPublicKey": { + "type": "text", + "placeholders": {} + }, + "scanQrCode": "Scanner un code QR", + "@scanQrCode": {}, + "sendOnEnter": "Envoyer avec Entrée", + "@sendOnEnter": {}, + "homeserver": "Serveur d'accueil", + "@homeserver": {}, + "serverRequiresEmail": "Ce serveur doit valider votre adresse électronique pour l'inscription.", + "@serverRequiresEmail": {}, + "enableMultiAccounts": "(BETA) Activer les comptes multiples sur cet appareil", + "@enableMultiAccounts": {}, + "bundleName": "Nom du groupe", + "@bundleName": {}, + "removeFromBundle": "Retirer de ce groupe", + "@removeFromBundle": {}, + "addToBundle": "Ajouter au groupe", + "@addToBundle": {}, + "editBundlesForAccount": "Modifier les groupes pour ce compte", + "@editBundlesForAccount": {}, + "addAccount": "Ajouter un compte", + "@addAccount": {}, + "oneClientLoggedOut": "Un de vos clients a été déconnecté", + "@oneClientLoggedOut": {}, + "link": "Lien", + "@link": {}, + "yourChatBackupHasBeenSetUp": "Votre sauvegarde de la discussion a été mise en place.", + "@yourChatBackupHasBeenSetUp": {}, + "unverified": "Non vérifié", + "@unverified": {}, + "repeatPassword": "Répétez le mot de passe", + "@repeatPassword": {}, + "messageType": "Type de message", + "@messageType": {}, + "openGallery": "Ouvrir dans la Galerie", + "@openGallery": {}, + "time": "Heure", + "@time": {}, + "sender": "Expéditeur/trice", + "@sender": {}, + "messageInfo": "Informations sur le message", + "@messageInfo": {}, + "removeFromSpace": "Supprimer de l’espace", + "@removeFromSpace": {}, + "addToSpaceDescription": "Sélectionnez un espace pour y ajouter cette discussion.", + "@addToSpaceDescription": {}, + "start": "Commencer", + "@start": {}, + "commandHint_create": "Créer un groupe de discussion vide\nUtilisez --no-encryption pour désactiver le chiffrement", + "@commandHint_create": { + "type": "text", + "description": "Usage hint for the command /create" + }, + "commandHint_discardsession": "Abandonner la session", + "@commandHint_discardsession": { + "type": "text", + "description": "Usage hint for the command /discardsession" + }, + "commandHint_clearcache": "Vider le cache", + "@commandHint_clearcache": { + "type": "text", + "description": "Usage hint for the command /clearcache" + }, + "commandHint_dm": "Commencer une discussion directe\nUtilisez --no-encryption pour désactiver le chiffrement", + "@commandHint_dm": { + "type": "text", + "description": "Usage hint for the command /dm" + }, + "openVideoCamera": "Ouvrir la caméra pour une vidéo", + "@openVideoCamera": { + "type": "text", + "placeholders": {} + }, + "publish": "Publier", + "@publish": {}, + "videoWithSize": "Vidéo ({size})", + "@videoWithSize": { + "type": "text", + "placeholders": { + "size": {} + } + }, + "dismiss": "Rejeter", + "@dismiss": {}, + "markAsRead": "Marquer comme lu", + "@markAsRead": {}, + "reportUser": "Signaler l'utilisateur/trice", + "@reportUser": {}, + "openChat": "Ouvrir la discussion", + "@openChat": {}, + "reactedWith": "{sender} a réagi avec {reaction}", + "@reactedWith": { + "type": "text", + "placeholders": { + "sender": {}, + "reaction": {} + } + }, + "emojis": "Émojis", + "@emojis": {}, + "placeCall": "Passer un appel", + "@placeCall": {}, + "voiceCall": "Appel vocal", + "@voiceCall": {}, + "unsupportedAndroidVersion": "Version d'Android non prise en charge", + "@unsupportedAndroidVersion": {}, + "unsupportedAndroidVersionLong": "Cette fonctionnalité nécessite une nouvelle version d'Android. Veuillez vérifier les mises à jour ou la prise en charge de Lineage OS.", + "@unsupportedAndroidVersionLong": {}, + "pinMessage": "Épingler au salon", + "@pinMessage": {}, + "confirmEventUnpin": "Voulez-vous vraiment désépingler définitivement l'événement ?", + "@confirmEventUnpin": {}, + "videoCallsBetaWarning": "Veuillez noter que les appels vidéo sont actuellement en version bêta. Ils peuvent ne pas fonctionner comme prévu ou ne oas fonctionner du tout sur toutes les plateformes.", + "@videoCallsBetaWarning": {}, + "experimentalVideoCalls": "Appels vidéo expérimentaux", + "@experimentalVideoCalls": {}, + "emailOrUsername": "Courriel ou identifiant", + "@emailOrUsername": {}, + "switchToAccount": "Passer au compte {number}", + "@switchToAccount": { + "type": "number", + "placeholders": { + "number": {} + } + }, + "nextAccount": "Compte suivant", + "@nextAccount": {}, + "previousAccount": "Compte précédent", + "@previousAccount": {}, + "widgetJitsi": "Jitsi Meet", + "@widgetJitsi": {}, + "widgetCustom": "Personnalisé", + "@widgetCustom": {}, + "widgetUrlError": "Ceci n'est pas un lien valide.", + "@widgetUrlError": {}, + "widgetNameError": "Veuillez fournir un nom d'affichage.", + "@widgetNameError": {}, + "errorAddingWidget": "Erreur lors de l'ajout du widget.", + "@errorAddingWidget": {}, + "widgetEtherpad": "Note textuelle", + "@widgetEtherpad": {}, + "addWidget": "Ajouter un widget", + "@addWidget": {}, + "widgetName": "Nom", + "@widgetName": {}, + "widgetVideo": "Vidéo", + "@widgetVideo": {}, + "youRejectedTheInvitation": "Vous avez rejeté l'invitation", + "@youRejectedTheInvitation": {}, + "youJoinedTheChat": "Vous avez rejoint la discussion", + "@youJoinedTheChat": {}, + "youHaveWithdrawnTheInvitationFor": "Vous avez retiré l'invitation pour {user}", + "@youHaveWithdrawnTheInvitationFor": { + "placeholders": { + "user": {} + } + }, + "youAcceptedTheInvitation": "👍 Vous avez accepté l'invitation", + "@youAcceptedTheInvitation": {}, + "youBannedUser": "Vous avez banni {user}", + "@youBannedUser": { + "placeholders": { + "user": {} + } + }, + "youInvitedBy": "📩 Vous avez été invité par {user}", + "@youInvitedBy": { + "placeholders": { + "user": {} + } + }, + "youInvitedUser": "📩 Vous avez invité {user}", + "@youInvitedUser": { + "placeholders": { + "user": {} + } + }, + "youKicked": "👞 Vous avez dégagé {user}", + "@youKicked": { + "placeholders": { + "user": {} + } + }, + "youKickedAndBanned": "🙅 Vous avez dégagé et banni {user}", + "@youKickedAndBanned": { + "placeholders": { + "user": {} + } + }, + "youUnbannedUser": "Vous avez débanni {user}", + "@youUnbannedUser": { + "placeholders": { + "user": {} + } + }, + "separateChatTypes": "Séparer les discussions directes et les groupes", + "@separateChatTypes": { + "type": "text", + "placeholders": {} + }, + "users": "Utilisateurs/trices", + "@users": {}, + "storeInAndroidKeystore": "Stocker dans Android KeyStore", + "@storeInAndroidKeystore": {}, + "storeInAppleKeyChain": "Stocker dans Apple KeyChain", + "@storeInAppleKeyChain": {}, + "user": "Utilisateur/trice", + "@user": {}, + "custom": "Personnalisé", + "@custom": {}, + "hydrate": "Restaurer à partir du fichier de sauvegarde", + "@hydrate": {}, + "dehydrateWarning": "Cette action ne peut pas être annulée. Assurez-vous d'enregistrer convenablement le fichier de sauvegarde.", + "@dehydrateWarning": {}, + "dehydrateTorLong": "Pour les utilisateurs/trices de TOR, il est recommandé d'exporter la session avant de fermer la fenêtre.", + "@dehydrateTorLong": {}, + "recoveryKey": "Clé de récupération", + "@recoveryKey": {}, + "recoveryKeyLost": "Clé de récupération perdue ?", + "@recoveryKeyLost": {}, + "indexedDbErrorLong": "Le stockage des messages n'est malheureusement pas activé par défaut en mode privé.\nVeuillez consulter :\n - about:config\n - Définir dom.indexedDB.privateBrowsing.enabled à « vrai ».\nSinon, il n'est pas possible d'exécuter FluffyChat.", + "@indexedDbErrorLong": {}, + "saveKeyManuallyDescription": "Enregistrer cette clé manuellement en déclenchant la boîte de dialogue de partage du système ou le presse-papiers.", + "@saveKeyManuallyDescription": {}, + "storeInSecureStorageDescription": "Stocker la clé de récupération dans un espace sécurisé de cet appareil.", + "@storeInSecureStorageDescription": {}, + "indexedDbErrorTitle": "Problèmes relatifs au mode privé", + "@indexedDbErrorTitle": {}, + "dehydrate": "Exporter la session et effacer l'appareil", + "@dehydrate": {}, + "dehydrateTor": "Utilisateurs/trices de TOR : Exporter la session", + "@dehydrateTor": {}, + "hydrateTor": "Utilisateurs/trices de TOR : Importer une session exportée", + "@hydrateTor": {}, + "hydrateTorLong": "Vous avez exporté votre session la dernière fois sur TOR ? Importez-la rapidement et continuez à discuter.", + "@hydrateTorLong": {}, + "pleaseEnterRecoveryKey": "Veuillez saisir votre clé de récupération :", + "@pleaseEnterRecoveryKey": {}, + "pleaseEnterRecoveryKeyDescription": "Pour déverrouiller vos anciens messages, veuillez entrer votre clé de récupération qui a été générée lors d'une session précédente. Votre clé de récupération n'est PAS votre mot de passe.", + "@pleaseEnterRecoveryKeyDescription": {}, + "unlockOldMessages": "Déverrouiller les anciens messages", + "@unlockOldMessages": {}, + "storeSecurlyOnThisDevice": "Stocker de manière sécurisé sur cet appareil", + "@storeSecurlyOnThisDevice": {}, + "countFiles": "{count} fichiers", + "@countFiles": { + "placeholders": { + "count": {} + } + }, + "noKeyForThisMessage": "Cela peut se produire si le message a été envoyé avant que vous ne vous soyez connecté à votre compte sur cet appareil.\n\nIl est également possible que l'expéditeur ait bloqué votre appareil ou qu'un problème de connexion Internet se soit produit.\n\nÊtes-vous capable de lire le message sur une autre session ? Vous pouvez alors transférer le message à partir de celle-ci ! Allez dans Paramètres > Appareils et assurez-vous que vos appareils se sont vérifiés mutuellement. Lorsque vous ouvrirez le salon la fois suivante et que les deux sessions seront au premier plan, les clés seront transmises automatiquement.\n\nVous ne voulez pas perdre les clés en vous déconnectant ou en changeant d'appareil ? Assurez-vous que vous avez activé la sauvegarde de la discussion dans les paramètres.", + "@noKeyForThisMessage": {}, + "enterRoom": "Entrer dans le salon", + "@enterRoom": {}, + "allSpaces": "Tous les espaces", + "@allSpaces": {}, + "commandHint_markasdm": "Marquer comme salon de messages directs pour l'identifiant Matrix indiqué", + "@commandHint_markasdm": {}, + "commandHint_markasgroup": "Marquer comme groupe", + "@commandHint_markasgroup": {}, + "confirmMatrixId": "Veuillez confirmer votre identifiant Matrix afin de supprimer votre compte.", + "@confirmMatrixId": {}, + "supposedMxid": "Cela devrait être {mxid}", + "@supposedMxid": { + "type": "text", + "placeholders": { + "mxid": {} + } + }, + "whyIsThisMessageEncrypted": "Pourquoi ce message est-il illisible ?", + "@whyIsThisMessageEncrypted": {}, + "foregroundServiceRunning": "Cette notification s’affiche lorsque le service au premier plan est en cours d’exécution.", + "@foregroundServiceRunning": {}, + "screenSharingTitle": "Partage d'écran", + "@screenSharingTitle": {}, + "screenSharingDetail": "Vous partagez votre écran dans FuffyChat", + "@screenSharingDetail": {}, + "callingPermissions": "Permissions d'appel", + "@callingPermissions": {}, + "callingAccount": "Compte d'appel", + "@callingAccount": {}, + "callingAccountDetails": "Permet à FluffyChat d'utiliser l'application de numérotation native d'Android.", + "@callingAccountDetails": {}, + "appearOnTop": "Apparaître en haut", + "@appearOnTop": {}, + "appearOnTopDetails": "Permet à l'application d'apparaître en haut de l'écran (non nécessaire si vous avez déjà configuré Fluffychat comme compte d'appel)", + "@appearOnTopDetails": {}, + "otherCallingPermissions": "Microphone, caméra et autres autorisations de FluffyChat", + "@otherCallingPermissions": {}, + "newGroup": "Nouveau groupe", + "@newGroup": {}, + "newSpace": "Nouvel espace", + "@newSpace": {}, + "enterSpace": "Entrer dans l’espace", + "@enterSpace": {}, + "numChats": "{number} discussions", + "@numChats": { + "type": "number", + "placeholders": { + "number": {} + } + }, + "hideUnimportantStateEvents": "Masquer les événements d'état sans importance", + "@hideUnimportantStateEvents": {}, + "doNotShowAgain": "Ne plus afficher", + "@doNotShowAgain": {}, + "commandHint_googly": "Envoyer des yeux écarquillés", + "@commandHint_googly": {}, + "commandHint_cuddle": "Envoyer un câlin", + "@commandHint_cuddle": {}, + "commandHint_hug": "Envoyer une accolade", + "@commandHint_hug": {}, + "googlyEyesContent": "{senderName} vous envoie des yeux écarquillés", + "@googlyEyesContent": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "cuddleContent": "{senderName} vous fait un câlin", + "@cuddleContent": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "hugContent": "{senderName} vous fait une accolade", + "@hugContent": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "wasDirectChatDisplayName": "Discussion vide (était {oldDisplayName})", + "@wasDirectChatDisplayName": { + "type": "text", + "placeholders": { + "oldDisplayName": {} + } + }, + "encryptThisChat": "Chiffrer cette discussion", + "@encryptThisChat": {}, + "sorryThatsNotPossible": "Désolé, ce n'est pas possible", + "@sorryThatsNotPossible": {}, + "deviceKeys": "Clés de l’appareil :", + "@deviceKeys": {}, + "startFirstChat": "Commencez votre première discussion", + "@startFirstChat": {}, + "newSpaceDescription": "Les espaces vous permettent de consolider vos conversations et de construire des communautés privées ou publiques.", + "@newSpaceDescription": {}, + "disableEncryptionWarning": "Pour des raisons de sécurité, vous ne pouvez pas désactiver le chiffrement dans une discussion s'il a été activé avant.", + "@disableEncryptionWarning": {}, + "reopenChat": "Rouvrir la discussion", + "@reopenChat": {}, + "noOtherDevicesFound": "Aucun autre appareil trouvé", + "@noOtherDevicesFound": {}, + "noBackupWarning": "Attention ! Sans l'activation de la sauvegarde de la discussion, vous perdrez l'accès à vos messages chiffrés. Il est fortement recommandé d'activer la sauvegarde de la discussion avant de se déconnecter.", + "@noBackupWarning": {}, + "fileHasBeenSavedAt": "Le fichier a été enregistré dans {path}", + "@fileHasBeenSavedAt": { + "type": "text", + "placeholders": { + "path": {} + } + }, + "fileIsTooBigForServer": "Le serveur signale que le fichier est trop volumineux pour être envoyé.", + "@fileIsTooBigForServer": {}, + "jumpToLastReadMessage": "Aller au dernier message lu", + "@jumpToLastReadMessage": {}, + "readUpToHere": "Lisez jusqu’ici", + "@readUpToHere": {}, + "allRooms": "Tous les groupes de discussion", + "@allRooms": { + "type": "text", + "placeholders": {} + }, + "chatPermissions": "Permissions du salon", + "@chatPermissions": {}, + "importFromZipFile": "Importer depuis un fichier .zip", + "@importFromZipFile": {}, + "inviteContactToGroupQuestion": "Voulez-vous inviter {contact} au salon \"{groupName}\" ?", + "@inviteContactToGroupQuestion": {}, + "importEmojis": "Importer des Emojis", + "@importEmojis": {}, + "notAnImage": "Pas un fichier image.", + "@notAnImage": {}, + "chatDescriptionHasBeenChanged": "La description du salon a changé", + "@chatDescriptionHasBeenChanged": {}, + "createGroup": "Créer un groupe", + "@createGroup": {}, + "importNow": "Importer maintenant", + "@importNow": {}, + "tryAgain": "Nouvelle tentative", + "@tryAgain": {}, + "blockedUsers": "Utilisateurs/trices bloqués", + "@blockedUsers": {}, + "redactMessageDescription": "Le message sera modifié pour tous les participants de cette conversation. Il n'est pas possible de revenir en arrière.", + "@redactMessageDescription": {}, + "redactedBy": "Modifié par {username}", + "@redactedBy": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "directChat": "Discussion directe", + "@directChat": {}, + "optionalRedactReason": "(Facultatif) Raison de la modification de ce message...", + "@optionalRedactReason": {}, + "subspace": "Sous-espace", + "@subspace": {}, + "sendTypingNotifications": "Envoyer des notifications de frappe", + "@sendTypingNotifications": {}, + "replace": "Remplacer", + "@replace": {}, + "emoteKeyboardNoRecents": "Les émoticônes récemment utilisées apparaîtront ici...", + "@emoteKeyboardNoRecents": { + "type": "text", + "placeholders": {} + }, + "nothingFound": "Rien n'a été trouvé...", + "@nothingFound": {}, + "chatDescription": "Description de la discussion", + "@chatDescription": {}, + "invalidServerName": "Nom de serveur invalide", + "@invalidServerName": {}, + "addChatDescription": "Ajouter une description à la discussion...", + "@addChatDescription": {}, + "shareInviteLink": "Partager un lien d'invitation", + "@shareInviteLink": {}, + "openLinkInBrowser": "Ouvrir le lien dans le navigateur", + "@openLinkInBrowser": {}, + "setTheme": "Définir le thème :", + "@setTheme": {}, + "setColorTheme": "Définir la couleur du thème :", + "@setColorTheme": {}, + "databaseMigrationBody": "Veuillez patienter. Cela peut prendre un moment.", + "@databaseMigrationBody": {}, + "searchForUsers": "Rechercher des @utilisateurs/trices...", + "@searchForUsers": {}, + "passwordsDoNotMatch": "Les mots de passe ne correspondent pas", + "@passwordsDoNotMatch": {}, + "passwordIsWrong": "Votre mot de passe saisi est erroné", + "@passwordIsWrong": {}, + "publicLink": "Lien public", + "@publicLink": {}, + "joinSpace": "Rejoindre l'espace", + "@joinSpace": {}, + "publicSpaces": "Espaces publics", + "@publicSpaces": {}, + "addChatOrSubSpace": "Ajouter une discussion ou un sous-espace", + "@addChatOrSubSpace": {}, + "thisDevice": "Cet appareil :", + "@thisDevice": {}, + "sendReadReceipts": "Envoyer des accusés de réception", + "@sendReadReceipts": {}, + "sendTypingNotificationsDescription": "Les autres participants à une discussion peuvent voir que vous êtes en train de taper un nouveau message.", + "@sendTypingNotificationsDescription": {}, + "verifyOtherDevice": "🔐 Vérifier l'autre appareil", + "@verifyOtherDevice": {}, + "databaseBuildErrorBody": "La base de données SQlite ne peut pas être créée. L'application essaie d'utiliser la base de données existante pour le moment. Veuillez signaler cette erreur aux développeurs à {url}. Le message d'erreur est le suivant : {error}", + "@databaseBuildErrorBody": { + "type": "text", + "placeholders": { + "url": {}, + "error": {} + } + }, + "startConversation": "Démarrer la conversation", + "@startConversation": {}, + "formattedMessagesDescription": "Affichez le contenu formaté des messages comme du texte en gras à l'aide de markdown.", + "@formattedMessagesDescription": {}, + "incomingMessages": "Messages entrants", + "@incomingMessages": {}, + "acceptedKeyVerification": "{sender} a accepté la vérification de clé", + "@acceptedKeyVerification": { + "type": "text", + "placeholders": { + "sender": {} + } + }, + "pleaseTryAgainLaterOrChooseDifferentServer": "Veuillez réessayer plus tard ou choisir un autre serveur.", + "@pleaseTryAgainLaterOrChooseDifferentServer": {}, + "inviteGroupChat": "📨 Inviter à une discussion de groupe", + "@inviteGroupChat": {}, + "invitePrivateChat": "📨 Inviter à une discussion privée", + "@invitePrivateChat": {}, + "jump": "Sauter", + "@jump": {}, + "signInWithPassword": "Se connecter avec mot de passe", + "@signInWithPassword": {}, + "hideMemberChangesInPublicChats": "Masquer les modifications de membres dans les discussions publiques", + "@hideMemberChangesInPublicChats": {}, + "hideMemberChangesInPublicChatsBody": "Ne pas afficher dans la chronologie de la discussion si quelqu'un rejoint ou quitte une discussion publique afin d'améliorer la lisibilité.", + "@hideMemberChangesInPublicChatsBody": {}, + "overview": "Aperçu", + "@overview": {}, + "notifyMeFor": "Me notifier pour", + "@notifyMeFor": {}, + "passwordRecoverySettings": "Paramètres de récupération de mot de passe", + "@passwordRecoverySettings": {}, + "hasKnocked": "🚪 {user} a frappé", + "@hasKnocked": { + "placeholders": { + "user": {} + } + }, + "canceledKeyVerification": "{sender} a annulé la vérification de clé", + "@canceledKeyVerification": { + "type": "text", + "placeholders": { + "sender": {} + } + }, + "unreadChatsInApp": "{appname} : {unread} discussions non lus", + "@unreadChatsInApp": { + "type": "text", + "placeholders": { + "appname": {}, + "unread": {} + } + }, + "requestedKeyVerification": "{sender} a demandé une vérification de clé", + "@requestedKeyVerification": { + "type": "text", + "placeholders": { + "sender": {} + } + }, + "startedKeyVerification": "{sender} a lancé la vérification de clé", + "@startedKeyVerification": { + "type": "text", + "placeholders": { + "sender": {} + } + }, + "discover": "Découvrir", + "@discover": {}, + "usersMustKnock": "Les utilisateurs/trices doivent frapper", + "@usersMustKnock": {}, + "noOneCanJoin": "Personne ne peut rejoindre", + "@noOneCanJoin": {}, + "knock": "Frapper à la porte", + "@knock": {}, + "hidePresences": "Cacher la liste des statuts ?", + "@hidePresences": {}, + "appLockDescription": "Verrouiller l'application avec un code PIN lorsqu'elle n'est pas utilisée", + "@appLockDescription": {}, + "globalChatId": "Identifiant global de la discussion", + "@globalChatId": {}, + "accessAndVisibility": "Accès et visibilité", + "@accessAndVisibility": {}, + "accessAndVisibilityDescription": "Qui est autorisé à rejoindre cette discussion et comment la discussion peut être découverte.", + "@accessAndVisibilityDescription": {}, + "calls": "Appels", + "@calls": {}, + "customEmojisAndStickers": "Émoticônes et autocollants personnalisés", + "@customEmojisAndStickers": {}, + "hideRedactedMessages": "Cacher les messages édités", + "@hideRedactedMessages": {}, + "pleaseEnterYourCurrentPassword": "Veuillez saisir votre mot de passe actuel", + "@pleaseEnterYourCurrentPassword": {}, + "swipeRightToLeftToReply": "Glisser de droite à gauche pour répondre", + "@swipeRightToLeftToReply": {}, + "alwaysUse24HourFormat": "true", + "@alwaysUse24HourFormat": { + "description": "Set to true to always display time of day in 24 hour format." + }, + "hideRedactedMessagesBody": "Si quelqu'un modifie un message, celui-ci ne sera plus visible dans la discussion.", + "@hideRedactedMessagesBody": {}, + "customEmojisAndStickersBody": "Ajoutez ou partagez des émoticônes ou autocollants personnalisés qui peuvent être utilisés dans n'importe quelle discussion.", + "@customEmojisAndStickersBody": {}, + "blockListDescription": "Vous pouvez bloquer des utilisateurs/trices qui vous dérangent. Vous ne pourrez plus recevoir aucun message ou invitation à un salon d'utilisateurs/trices figurant sur votre liste de blocage personnelle.", + "@blockListDescription": {}, + "blockUsername": "Ignorer le nom d'utilisateur/trice", + "@blockUsername": {}, + "hideInvalidOrUnknownMessageFormats": "Masquer les formats de message invalides ou inconnus", + "@hideInvalidOrUnknownMessageFormats": {}, + "messagesStyle": "Messages :", + "@messagesStyle": {}, + "redactedByBecause": "Modifié par {username} car : \"{reason}\"", + "@redactedByBecause": { + "type": "text", + "placeholders": { + "username": {}, + "reason": {} + } + }, + "setChatDescription": "Définir la description de la discussion", + "@setChatDescription": {}, + "presenceStyle": "Statut :", + "@presenceStyle": { + "type": "text", + "placeholders": {} + }, + "presencesToggle": "Afficher les messages de statut des autres utilisateurs/trices", + "@presencesToggle": { + "type": "text", + "placeholders": {} + }, + "youInvitedToBy": "📩 Vous avez été invité par lien à :\n{alias}", + "@youInvitedToBy": { + "placeholders": { + "alias": {} + } + }, + "userWouldLikeToChangeTheChat": "{user} souhaite rejoindre la discussion.", + "@userWouldLikeToChangeTheChat": { + "placeholders": { + "user": {} + } + }, + "noPublicLinkHasBeenCreatedYet": "Aucun lien public n'a encore été crée", + "@noPublicLinkHasBeenCreatedYet": {}, + "gallery": "Galerie", + "@gallery": {}, + "files": "Fichiers", + "@files": {}, + "sessionLostBody": "Votre session est perdue. Veuillez signaler cette erreur aux développeurs à {url}. Le message d'erreur est le suivant : {error}", + "@sessionLostBody": { + "type": "text", + "placeholders": { + "url": {}, + "error": {} + } + }, + "searchIn": "Rechercher dans la discussion \"{chat}\"...", + "@searchIn": { + "type": "text", + "placeholders": { + "chat": {} + } + }, + "forwardMessageTo": "Transférer le message à {roomName} ?", + "@forwardMessageTo": { + "type": "text", + "placeholders": { + "roomName": {} + } + }, + "sendReadReceiptsDescription": "Les autres participants à une discussion peuvent voir si vous avez lu un message.", + "@sendReadReceiptsDescription": {}, + "formattedMessages": "Messages formatés", + "@formattedMessages": {}, + "verifyOtherUser": "🔐 Vérifier l'autre utilisateur/trice", + "@verifyOtherUser": {}, + "searchMore": "Rechercher davantage...", + "@searchMore": {}, + "verifyOtherUserDescription": "Si vous vérifiez un autre utilisateur/trice, vous pouvez être sûr de savoir à qui vous écrivez réellement. 💪\n\nLorsque vous lancez une vérification, vous et l'autre utilisateur/trice verrez une fenêtre contextuelle dans l'application. Vous y verrez alors une série d'émoticônes ou de chiffres que vous devrez comparer.\n\nLa meilleure façon de procéder est de se rencontrer ou de lancer un appel vidéo. 👭", + "@verifyOtherUserDescription": {}, + "verifyOtherDeviceDescription": "Lorsque vous vérifiez un autre appareil, ces appareils peuvent échanger des clés, ce qui augmente votre sécurité globale. 💪 Lorsque vous lancez une vérification, une fenêtre contextuelle s'affiche dans l'application sur les deux appareils. Vous y verrez alors une série d'émoticônes ou de chiffres que vous devrez comparer. Il est préférable d'avoir les deux appareils à portée de main avant de lancer la vérification. 🤳", + "@verifyOtherDeviceDescription": {}, + "completedKeyVerification": "{sender} a terminé la vérification de clé", + "@completedKeyVerification": { + "type": "text", + "placeholders": { + "sender": {} + } + }, + "isReadyForKeyVerification": "{sender} est prêt pour la vérification de clé", + "@isReadyForKeyVerification": { + "type": "text", + "placeholders": { + "sender": {} + } + }, + "transparent": "Transparent", + "@transparent": {}, + "stickers": "Autocollants", + "@stickers": {}, + "noDatabaseEncryption": "Le chiffrement de la base de données n'est pas supporté sur cette plateforme", + "@noDatabaseEncryption": {}, + "commandHint_ignore": "Ignorer l'identifiant Matrix indiqué", + "@commandHint_ignore": {}, + "commandHint_unignore": "Ne plus ignorer l'identifiant Matrix indiqué", + "@commandHint_unignore": {}, + "thereAreCountUsersBlocked": "Actuellement, il y a {count} utilisateurs/trices bloqués.", + "@thereAreCountUsersBlocked": { + "type": "text", + "count": {} + }, + "restricted": "Limité", + "@restricted": {}, + "knockRestricted": "Frapper à la porte limité", + "@knockRestricted": {}, + "signInWith": "Se connecter avec {provider}", + "@signInWith": { + "type": "text", + "placeholders": { + "provider": {} + } + }, + "groupCanBeFoundViaSearch": "Le groupe peut être trouvé via la recherche", + "@groupCanBeFoundViaSearch": {}, + "groupName": "Nom du groupe", + "@groupName": {}, + "invalidInput": "Entrée invalide !", + "@invalidInput": {}, + "block": "Bloquer", + "@block": {}, + "removeDevicesDescription": "Vous serez déconnecté de cet appareil et ne pourrez plus recevoir de messages.", + "@removeDevicesDescription": {}, + "userRole": "Rôle de l'utilisateur/trice", + "@userRole": {}, + "createNewAddress": "Créer une nouvelle adresse", + "@createNewAddress": {}, + "publicChatAddresses": "Addresses de discussion publiques", + "@publicChatAddresses": {}, + "countChatsAndCountParticipants": "{chats} discussions et {participants} participants", + "@countChatsAndCountParticipants": { + "type": "text", + "placeholders": { + "chats": {}, + "participants": {} + } + }, + "space": "Espace", + "@space": {}, + "spaces": "Espaces", + "@spaces": {}, + "noMoreChatsFound": "Aucune autre discussion trouvée...", + "@noMoreChatsFound": {}, + "unread": "Non lu", + "@unread": {}, + "joinedChats": "Discussions rejointes", + "@joinedChats": {}, + "commandHint_sendraw": "Envoyer du JSON brut", + "@commandHint_sendraw": {}, + "databaseMigrationTitle": "La base de données est optimisée", + "@databaseMigrationTitle": {}, + "leaveEmptyToClearStatus": "Laisser vide pour effacer votre statut.", + "@leaveEmptyToClearStatus": {}, + "select": "Sélectionner", + "@select": {}, + "reportErrorDescription": "😭 Oh non. Quelque chose s'est mal passé. Si vous le souhaitez, vous pouvez signaler ce bogue aux développeurs.", + "@reportErrorDescription": {}, + "report": "signaler", + "@report": {}, + "wrongPinEntered": "Mauvais code PIN saisi ! Veuillez réessayer dans {seconds} secondes...", + "@wrongPinEntered": { + "type": "text", + "placeholders": { + "seconds": {} + } + }, + "pushNotificationsNotAvailable": "Notifications poussées indisponibles", + "@pushNotificationsNotAvailable": {}, + "yourGlobalUserIdIs": "Votre identifiant utilisateur global est : ", + "@yourGlobalUserIdIs": {}, + "chatCanBeDiscoveredViaSearchOnServer": "La discussion peut être découverte via la recherche sur {server}", + "@chatCanBeDiscoveredViaSearchOnServer": { + "type": "text", + "placeholders": { + "server": {} + } + }, + "knocking": "Frapper", + "@knocking": {}, + "banUserDescription": "L'utilisateur/trice sera banni de la discussion et ne pourra plus y accéder jusqu'à ce qu'il/elle soit débanni.", + "@banUserDescription": {}, + "unbanUserDescription": "L'utilisateur/trice pourra entrer à nouveau dans la discussion si il/elle le souhaite.", + "@unbanUserDescription": {}, + "kickUserDescription": "L'utilisateur/trice est expulsé de la discussion mais n'est pas banni. Dans les discussions publiques, l'utilisateur/trice peut revenir à tout moment.", + "@kickUserDescription": {}, + "makeAdminDescription": "Une fois que vous aurez nommé cet utilisateur/trice administrateur, vous ne pourrez peut-être plus annuler cette opération, car il disposera alors des mêmes autorisations que vous.", + "@makeAdminDescription": {}, + "newPassword": "Nouveau mot de passe", + "@newPassword": {}, + "pleaseChooseAStrongPassword": "Veuillez choisir un mot de passe fort", + "@pleaseChooseAStrongPassword": {}, + "decline": "Refuser", + "@decline": {}, + "initAppError": "Une erreur est survenue pendant l'initialisation de l'application", + "@initAppError": {}, + "markAsUnread": "Marquer comme non lu", + "@markAsUnread": {}, + "wrongRecoveryKey": "Désolé... il ne semble pas s'agir de la bonne clé de récupération.", + "@wrongRecoveryKey": {}, + "searchChatsRooms": "Rechercher des #discussions, @utilisateurs/trices...", + "@searchChatsRooms": {}, + "createGroupAndInviteUsers": "Créer un groupe et inviter des utilisateurs/trices", + "@createGroupAndInviteUsers": {}, + "goToSpace": "Aller dans l'espace : {space}", + "@goToSpace": { + "type": "text", + "space": {} + }, + "exportEmotePack": "Exporter le pack d'émoticônes au format .zip", + "@exportEmotePack": {}, + "noChatDescriptionYet": "Aucune description de discussion n'a encore été créée.", + "@noChatDescriptionYet": {}, + "invite": "Inviter", + "@invite": {}, + "pleaseEnterANumber": "Veuillez saisir un nombre supérieur à 0", + "@pleaseEnterANumber": {}, + "roomUpgradeDescription": "La discussion sera alors recréé avec la nouvelle version de salon. Tous les participants seront informés qu'ils doivent passer à la nouvelle discussion. Pour en savoir plus sur les versions des salons, consultez le site https://spec.matrix.org/latest/rooms/", + "@roomUpgradeDescription": {}, + "learnMore": "En savoir plus", + "@learnMore": {}, + "minimumPowerLevel": "{level} est le niveau minimum de droits.", + "@minimumPowerLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "restoreSessionBody": "L'application tente maintenant de restaurer votre session depuis la sauvegarde. Veuillez signaler cette erreur aux développeurs à {url}. Le message d'erreur est le suivant : {error}", + "@restoreSessionBody": { + "type": "text", + "placeholders": { + "url": {}, + "error": {} + } + }, + "profileNotFound": "Cet utilisateur/trice n'a pu être trouvé sur le serveur. Peut-être est-ce un problème de connexion ou l'utilisateur/trice n'existe pas.", + "@profileNotFound": {}, + "archiveRoomDescription": "La discussion sera déplacée dans les archives. Les autres utilisateurs/trices pourront voir que vous avez quitté la discussion.", + "@archiveRoomDescription": {}, + "noUsersFoundWithQuery": "Malheureusement, aucun utilisateur/trice n'a pu être trouvé avec \"{query}\". Veuillez vérifier si vous n'avez pas fait de faute de frappe.", + "@noUsersFoundWithQuery": { + "type": "text", + "placeholders": { + "query": {} + } + } +} From c8257f12553b3e920a3a7ee469aa0ff0fdf2694a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Mon, 15 Jul 2024 14:48:37 +0000 Subject: [PATCH 071/288] Translated using Weblate (Turkish) Currently translated at 100.0% (642 of 642 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/tr/ --- assets/l10n/intl_tr.arb | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_tr.arb b/assets/l10n/intl_tr.arb index 5dccad6460..0fa7a18f24 100644 --- a/assets/l10n/intl_tr.arb +++ b/assets/l10n/intl_tr.arb @@ -2712,5 +2712,30 @@ "alwaysUse24HourFormat": "yanlış", "@alwaysUse24HourFormat": { "description": "Set to true to always display time of day in 24 hour format." - } + }, + "countChatsAndCountParticipants": "{chats} sohbet ve {participants} katılımcı", + "@countChatsAndCountParticipants": { + "type": "text", + "placeholders": { + "chats": {}, + "participants": {} + } + }, + "noMoreChatsFound": "Başka sohbet bulunamadı...", + "@noMoreChatsFound": {}, + "goToSpace": "Alana git: {space}", + "@goToSpace": { + "type": "text", + "space": {} + }, + "joinedChats": "Katılınan sohbetler", + "@joinedChats": {}, + "unread": "Okunmadı", + "@unread": {}, + "markAsUnread": "Okunmadı olarak işaretle", + "@markAsUnread": {}, + "space": "Alan", + "@space": {}, + "spaces": "Alanlar", + "@spaces": {} } From 91dc8816002fad854c121f4157372190873ac379 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E7=8E=8B=E5=8F=AB=E6=88=91=E6=9D=A5=E5=B7=A1?= =?UTF-8?q?=E5=B1=B1?= Date: Tue, 16 Jul 2024 08:57:11 +0000 Subject: [PATCH 072/288] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (642 of 642 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hans/ --- assets/l10n/intl_zh.arb | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_zh.arb b/assets/l10n/intl_zh.arb index 70451b9587..1e31fdd78c 100644 --- a/assets/l10n/intl_zh.arb +++ b/assets/l10n/intl_zh.arb @@ -2712,5 +2712,30 @@ "alwaysUse24HourFormat": "否", "@alwaysUse24HourFormat": { "description": "Set to true to always display time of day in 24 hour format." - } + }, + "noMoreChatsFound": "找不到更多聊天…", + "@noMoreChatsFound": {}, + "joinedChats": "已加入的聊天", + "@joinedChats": {}, + "space": "空间", + "@space": {}, + "spaces": "空间", + "@spaces": {}, + "goToSpace": "转到空间:{space}", + "@goToSpace": { + "type": "text", + "space": {} + }, + "markAsUnread": "标为未读", + "@markAsUnread": {}, + "countChatsAndCountParticipants": "{chats} 个聊天和 {participants} 名参与者", + "@countChatsAndCountParticipants": { + "type": "text", + "placeholders": { + "chats": {}, + "participants": {} + } + }, + "unread": "未读", + "@unread": {} } From e865bae01f0ab7d8a2ec81c50817450ef92a119e Mon Sep 17 00:00:00 2001 From: Lukas Date: Tue, 16 Jul 2024 16:03:10 +0000 Subject: [PATCH 073/288] Translated using Weblate (Chinese (Traditional)) Currently translated at 52.1% (335 of 642 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hant/ --- assets/l10n/intl_zh_Hant.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/l10n/intl_zh_Hant.arb b/assets/l10n/intl_zh_Hant.arb index 26a6b0fbcb..2cd581986f 100644 --- a/assets/l10n/intl_zh_Hant.arb +++ b/assets/l10n/intl_zh_Hant.arb @@ -316,7 +316,7 @@ "type": "text", "placeholders": {} }, - "compareEmojiMatch": "對比並確認這些表情符合其他那些裝置:", + "compareEmojiMatch": "請你對比這些表情", "@compareEmojiMatch": { "type": "text", "placeholders": {} From fcf76be3f6f0e59f0847d919b1cde50300f69caf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=8D=E7=9F=A5=E7=81=AB=20Shiranui?= Date: Tue, 16 Jul 2024 16:02:09 +0000 Subject: [PATCH 074/288] Translated using Weblate (Chinese (Traditional)) Currently translated at 52.1% (335 of 642 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hant/ --- assets/l10n/intl_zh_Hant.arb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/assets/l10n/intl_zh_Hant.arb b/assets/l10n/intl_zh_Hant.arb index 2cd581986f..319fff9750 100644 --- a/assets/l10n/intl_zh_Hant.arb +++ b/assets/l10n/intl_zh_Hant.arb @@ -321,7 +321,7 @@ "type": "text", "placeholders": {} }, - "compareNumbersMatch": "比較以下數字,確保它們和另一個裝置上的相同:", + "compareNumbersMatch": "比較以下數字:", "@compareNumbersMatch": { "type": "text", "placeholders": {} @@ -1859,5 +1859,9 @@ "block": "封鎖", "@block": {}, "discover": "探索", - "@discover": {} + "@discover": {}, + "alwaysUse24HourFormat": "否", + "@alwaysUse24HourFormat": { + "description": "Set to true to always display time of day in 24 hour format." + } } From 847f34fa3fcec8e395cc2cc7786d74ce316dc3cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=8D=E7=9F=A5=E7=81=AB=20Shiranui?= Date: Tue, 16 Jul 2024 16:04:12 +0000 Subject: [PATCH 075/288] Translated using Weblate (Chinese (Traditional)) Currently translated at 52.3% (336 of 642 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hant/ --- assets/l10n/intl_zh_Hant.arb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/l10n/intl_zh_Hant.arb b/assets/l10n/intl_zh_Hant.arb index 319fff9750..4a396f398b 100644 --- a/assets/l10n/intl_zh_Hant.arb +++ b/assets/l10n/intl_zh_Hant.arb @@ -316,12 +316,12 @@ "type": "text", "placeholders": {} }, - "compareEmojiMatch": "請你對比這些表情", + "compareEmojiMatch": "請對比這些表情", "@compareEmojiMatch": { "type": "text", "placeholders": {} }, - "compareNumbersMatch": "比較以下數字:", + "compareNumbersMatch": "請對比這些數字", "@compareNumbersMatch": { "type": "text", "placeholders": {} @@ -1595,7 +1595,7 @@ "type": "text", "placeholders": {} }, - "wallpaper": "桌布", + "wallpaper": "桌布:", "@wallpaper": { "type": "text", "placeholders": {} From e48ff69ca7361e0d4b139a93f076d3aaa29cf6c9 Mon Sep 17 00:00:00 2001 From: Lukas Date: Tue, 16 Jul 2024 16:06:41 +0000 Subject: [PATCH 076/288] Translated using Weblate (Chinese (Traditional)) Currently translated at 52.3% (336 of 642 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hant/ --- assets/l10n/intl_zh_Hant.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/l10n/intl_zh_Hant.arb b/assets/l10n/intl_zh_Hant.arb index 4a396f398b..5d932f4e05 100644 --- a/assets/l10n/intl_zh_Hant.arb +++ b/assets/l10n/intl_zh_Hant.arb @@ -5,7 +5,7 @@ "type": "text", "placeholders": {} }, - "accept": "接受", + "accept": "同意", "@accept": { "type": "text", "placeholders": {} From 3e0d103594899f14678a65e4cdf304d85bb68469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=8D=E7=9F=A5=E7=81=AB=20Shiranui?= Date: Tue, 16 Jul 2024 16:52:27 +0000 Subject: [PATCH 077/288] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (642 of 642 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hant/ --- assets/l10n/intl_zh_Hant.arb | 1051 +++++++++++++++++++++++++++++++--- 1 file changed, 962 insertions(+), 89 deletions(-) diff --git a/assets/l10n/intl_zh_Hant.arb b/assets/l10n/intl_zh_Hant.arb index 5d932f4e05..45740dd52c 100644 --- a/assets/l10n/intl_zh_Hant.arb +++ b/assets/l10n/intl_zh_Hant.arb @@ -10,7 +10,7 @@ "type": "text", "placeholders": {} }, - "acceptedTheInvitation": "👍{username}已接受邀請", + "acceptedTheInvitation": "👍 {username} 已接受邀請", "@acceptedTheInvitation": { "type": "text", "placeholders": { @@ -22,7 +22,7 @@ "type": "text", "placeholders": {} }, - "activatedEndToEndEncryption": "🔐{username}已啟用點對點加密", + "activatedEndToEndEncryption": "🔐 {username} 已啟用點對點加密", "@activatedEndToEndEncryption": { "type": "text", "placeholders": { @@ -49,7 +49,7 @@ "type": "text", "placeholders": {} }, - "answeredTheCall": "已開始與{senderName}通話", + "answeredTheCall": "已開始與 {senderName} 通話", "@answeredTheCall": { "type": "text", "placeholders": { @@ -91,14 +91,14 @@ "type": "text", "placeholders": {} }, - "askVerificationRequest": "是否接受來自{username}的驗證申請?", + "askVerificationRequest": "是否接受來自 {username} 的驗證申請?", "@askVerificationRequest": { "type": "text", "placeholders": { "username": {} } }, - "badServerLoginTypesException": "目前伺服器支援的登入類型:\n{serverVersions}\n但本應用程式僅支援:\n{supportedVersions}", + "badServerLoginTypesException": "目前伺服器支援的登入類型:\n {serverVersions} \n但本應用程式僅支援:\n {supportedVersions}", "@badServerLoginTypesException": { "type": "text", "placeholders": { @@ -106,7 +106,7 @@ "supportedVersions": {} } }, - "badServerVersionsException": "目前伺服器支援的Spec版本:\n{serverVersions}\n但本應用程式僅支援{supportedVersions}", + "badServerVersionsException": "目前伺服器支援的Spec版本:\n {serverVersions} \n但本應用程式僅支援 {supportedVersions}", "@badServerVersionsException": { "type": "text", "placeholders": { @@ -114,17 +114,17 @@ "supportedVersions": {} } }, - "banFromChat": "已從聊天室中封禁", + "banFromChat": "已從聊天室中封鎖", "@banFromChat": { "type": "text", "placeholders": {} }, - "banned": "已被封禁", + "banned": "已被封鎖", "@banned": { "type": "text", "placeholders": {} }, - "bannedUser": "{username}封禁了{targetName}", + "bannedUser": "{username} 封鎖了 {targetName}", "@bannedUser": { "type": "text", "placeholders": { @@ -157,14 +157,14 @@ "type": "text", "placeholders": {} }, - "changedTheChatAvatar": "{username}變更了對話頭貼", + "changedTheChatAvatar": "{username} 變更了對話頭貼", "@changedTheChatAvatar": { "type": "text", "placeholders": { "username": {} } }, - "changedTheChatDescriptionTo": "{username}變更了聊天介紹為:「{description}」", + "changedTheChatDescriptionTo": "{username} 變更了聊天室介紹為:「 {description} 」", "@changedTheChatDescriptionTo": { "type": "text", "placeholders": { @@ -172,7 +172,7 @@ "description": {} } }, - "changedTheChatNameTo": "{username}變更了聊天名稱為:「{chatname}」", + "changedTheChatNameTo": "{username} 變更了聊天室名稱為:「 {chatname} 」", "@changedTheChatNameTo": { "type": "text", "placeholders": { @@ -180,14 +180,14 @@ "chatname": {} } }, - "changedTheChatPermissions": "{username}變更了對話權限", + "changedTheChatPermissions": "{username} 變更了對話權限", "@changedTheChatPermissions": { "type": "text", "placeholders": { "username": {} } }, - "changedTheDisplaynameTo": "{username} 變更了顯示名稱為:「{displayname}」", + "changedTheDisplaynameTo": "{username} 變更了顯示名稱為:「 {displayname} 」", "@changedTheDisplaynameTo": { "type": "text", "placeholders": { @@ -195,14 +195,14 @@ "displayname": {} } }, - "changedTheGuestAccessRules": "{username}變更了訪客訪問規則", + "changedTheGuestAccessRules": "{username} 變更了訪客訪問規則", "@changedTheGuestAccessRules": { "type": "text", "placeholders": { "username": {} } }, - "changedTheGuestAccessRulesTo": "{username}變更了訪客訪問規則為:{rules}", + "changedTheGuestAccessRulesTo": "{username} 變更了訪客訪問規則為:{rules}", "@changedTheGuestAccessRulesTo": { "type": "text", "placeholders": { @@ -210,14 +210,14 @@ "rules": {} } }, - "changedTheHistoryVisibility": "{username}變更了歷史記錄觀察狀態", + "changedTheHistoryVisibility": "{username} 變更了歷史記錄觀察狀態", "@changedTheHistoryVisibility": { "type": "text", "placeholders": { "username": {} } }, - "changedTheHistoryVisibilityTo": "{username}變更了歷史紀錄觀察狀態到:{rules}", + "changedTheHistoryVisibilityTo": "{username} 變更了歷史紀錄觀察狀態到:{rules}", "@changedTheHistoryVisibilityTo": { "type": "text", "placeholders": { @@ -225,14 +225,14 @@ "rules": {} } }, - "changedTheJoinRules": "{username}變更了加入的規則", + "changedTheJoinRules": "{username} 變更了加入的規則", "@changedTheJoinRules": { "type": "text", "placeholders": { "username": {} } }, - "changedTheJoinRulesTo": "{username}變更了加入的規則為:{joinRules}", + "changedTheJoinRulesTo": "{username} 變更了加入的規則為:{joinRules}", "@changedTheJoinRulesTo": { "type": "text", "placeholders": { @@ -240,21 +240,21 @@ "joinRules": {} } }, - "changedTheProfileAvatar": "{username}變更了頭貼", + "changedTheProfileAvatar": "{username} 變更了頭貼", "@changedTheProfileAvatar": { "type": "text", "placeholders": { "username": {} } }, - "changedTheRoomAliases": "{username}變更了聊天室名", + "changedTheRoomAliases": "{username} 變更了聊天室名", "@changedTheRoomAliases": { "type": "text", "placeholders": { "username": {} } }, - "changedTheRoomInvitationLink": "{username}變更了邀請連結", + "changedTheRoomInvitationLink": "{username} 變更了邀請連結", "@changedTheRoomInvitationLink": { "type": "text", "placeholders": { @@ -286,7 +286,7 @@ "type": "text", "placeholders": {} }, - "chat": "聊天", + "chat": "聊天室", "@chat": { "type": "text", "placeholders": {} @@ -296,7 +296,7 @@ "type": "text", "placeholders": {} }, - "chatBackupDescription": "您的過往聊天記錄已被恢復金鑰加密。請您確保不會弄丟它。", + "chatBackupDescription": "您的過往聊天室記錄已被恢復金鑰加密。請您確保不會弄丟它。", "@chatBackupDescription": { "type": "text", "placeholders": {} @@ -383,7 +383,7 @@ "error": {} } }, - "countParticipants": "{count}個參與者", + "countParticipants": "{count} 個參與者", "@countParticipants": { "type": "text", "placeholders": { @@ -395,7 +395,7 @@ "type": "text", "placeholders": {} }, - "createdTheChat": "{username}建立了聊天室", + "createdTheChat": "💬 {username} 建立了聊天室", "@createdTheChat": { "type": "text", "placeholders": { @@ -412,7 +412,7 @@ "type": "text", "placeholders": {} }, - "dateAndTimeOfDay": "{date}, {timeOfDay}", + "dateAndTimeOfDay": "{date} , {timeOfDay}", "@dateAndTimeOfDay": { "type": "text", "placeholders": { @@ -420,7 +420,7 @@ "timeOfDay": {} } }, - "dateWithoutYear": "{month}-{day}", + "dateWithoutYear": "{month} - {day}", "@dateWithoutYear": { "type": "text", "placeholders": { @@ -428,7 +428,7 @@ "day": {} } }, - "dateWithYear": "{year}-{month}-{day}", + "dateWithYear": "{year} - {month} - {day}", "@dateWithYear": { "type": "text", "placeholders": { @@ -577,7 +577,7 @@ "type": "text", "placeholders": {} }, - "endedTheCall": "{senderName}結束了通話", + "endedTheCall": "{senderName} 結束了通話", "@endedTheCall": { "type": "text", "placeholders": { @@ -649,7 +649,7 @@ "type": "text", "placeholders": {} }, - "groupWith": "名稱為{displayname}的群組", + "groupWith": "名稱為 {displayname} 的群組", "@groupWith": { "type": "text", "placeholders": { @@ -666,7 +666,7 @@ "type": "text", "placeholders": {} }, - "hasWithdrawnTheInvitationFor": "{username}收回了對{targetName}的邀請", + "hasWithdrawnTheInvitationFor": "{username} 收回了對 {targetName} 的邀請", "@hasWithdrawnTheInvitationFor": { "type": "text", "placeholders": { @@ -734,7 +734,7 @@ "type": "text", "placeholders": {} }, - "inviteContactToGroup": "邀請聯絡人到{groupName}", + "inviteContactToGroup": "邀請聯絡人到 {groupName}", "@inviteContactToGroup": { "type": "text", "placeholders": { @@ -746,7 +746,7 @@ "type": "text", "placeholders": {} }, - "invitedUser": "{username}邀請了{targetName}", + "invitedUser": "📩 {username} 邀請了 {targetName}", "@invitedUser": { "type": "text", "placeholders": { @@ -764,7 +764,7 @@ "type": "text", "placeholders": {} }, - "inviteText": "{username}邀請您使用FluffyChat\n1. 安裝FluffyChat:https://fluffychat.im\n2. 登入或註冊\n3. 打開該邀請網址:{link}", + "inviteText": "{username} 邀請您使用 FluffyChat\n1. 安裝 FluffyChat:https://fluffychat.im\n2. 登入或註冊\n3. 打開該邀請網址:\n {link}", "@inviteText": { "type": "text", "placeholders": { @@ -777,7 +777,7 @@ "type": "text", "placeholders": {} }, - "joinedTheChat": "{username}加入了聊天室", + "joinedTheChat": "👋 {username} 加入了聊天室", "@joinedTheChat": { "type": "text", "placeholders": { @@ -789,7 +789,7 @@ "type": "text", "placeholders": {} }, - "kicked": "{username}踢了{targetName}", + "kicked": "👞 {username} 踢了 {targetName}", "@kicked": { "type": "text", "placeholders": { @@ -797,7 +797,7 @@ "targetName": {} } }, - "kickedAndBanned": "{username}踢了{targetName}並將其封禁", + "kickedAndBanned": "🙅 {username} 踢了 {targetName} 並將其封鎖", "@kickedAndBanned": { "type": "text", "placeholders": { @@ -837,19 +837,19 @@ "type": "text", "placeholders": {} }, - "loadCountMoreParticipants": "載入{count}個更多的參與者", + "loadCountMoreParticipants": "載入 {count} 個更多的參與者", "@loadCountMoreParticipants": { "type": "text", "placeholders": { "count": {} } }, - "loadingPleaseWait": "載入中… 請稍候。", + "loadingPleaseWait": "載入中...... 請稍候。", "@loadingPleaseWait": { "type": "text", "placeholders": {} }, - "loadMore": "載入更多…", + "loadMore": "載入更多...…", "@loadMore": { "type": "text", "placeholders": {} @@ -859,7 +859,7 @@ "type": "text", "placeholders": {} }, - "logInTo": "登入{homeserver}", + "logInTo": "登入 {homeserver}", "@logInTo": { "type": "text", "placeholders": { @@ -906,7 +906,7 @@ "type": "text", "placeholders": {} }, - "newMessageInFluffyChat": "來自 FluffyChat 的新訊息", + "newMessageInFluffyChat": "💬 來自 FluffyChat 的新訊息", "@newMessageInFluffyChat": { "type": "text", "placeholders": {} @@ -941,7 +941,7 @@ "type": "text", "placeholders": {} }, - "noGoogleServicesWarning": "看起來您手機上沒有Google服務框架。這對於保護您的隱私而言是個好決定!但為了收到FluffyChat的推播通知,我們推薦您使用 https://microg.org/ 或 https://unifiedpush.org/。", + "noGoogleServicesWarning": "看起來您手機上沒有Google服務框架。這對於保護您的隱私而言是個好決定!但為了收到 FluffyChat 的推播通知,我們推薦您使用 https://microg.org/ 或 https://unifiedpush.org/。", "@noGoogleServicesWarning": { "type": "text", "placeholders": {} @@ -961,7 +961,7 @@ "type": "text", "placeholders": {} }, - "noRoomsFound": "找不到聊天室…", + "noRoomsFound": "找不到聊天室...…", "@noRoomsFound": { "type": "text", "placeholders": {} @@ -976,7 +976,7 @@ "type": "text", "placeholders": {} }, - "numUsersTyping": "{count}個人正在輸入…", + "numUsersTyping": "{count} 個人正在輸入...…", "@numUsersTyping": { "type": "text", "placeholders": { @@ -1008,7 +1008,7 @@ "type": "text", "placeholders": {} }, - "oopsSomethingWentWrong": "哎呀!出了一點差錯…", + "oopsSomethingWentWrong": "哎呀!出了一點差錯...…", "@oopsSomethingWentWrong": { "type": "text", "placeholders": {} @@ -1063,7 +1063,7 @@ "type": "text", "placeholders": {} }, - "play": "播放{fileName}", + "play": "播放 {fileName}", "@play": { "type": "text", "placeholders": { @@ -1125,7 +1125,7 @@ "type": "text", "placeholders": {} }, - "redactedAnEvent": "{username}編輯了一個事件", + "redactedAnEvent": "{username} 編輯了一個事件", "@redactedAnEvent": { "type": "text", "placeholders": { @@ -1142,7 +1142,7 @@ "type": "text", "placeholders": {} }, - "rejectedTheInvitation": "{username}拒絕了邀請", + "rejectedTheInvitation": "{username} 拒絕了邀請", "@rejectedTheInvitation": { "type": "text", "placeholders": { @@ -1164,7 +1164,7 @@ "type": "text", "placeholders": {} }, - "removedBy": "被{username}移除", + "removedBy": "被 {username} 移除", "@removedBy": { "type": "text", "placeholders": { @@ -1176,7 +1176,7 @@ "type": "text", "placeholders": {} }, - "unbanFromChat": "解禁聊天", + "unbanFromChat": "解禁聊天室", "@unbanFromChat": { "type": "text", "placeholders": {} @@ -1221,7 +1221,7 @@ "type": "text", "placeholders": {} }, - "seenByUser": "{username}已讀", + "seenByUser": "{username} 已讀", "@seenByUser": { "type": "text", "placeholders": { @@ -1268,21 +1268,21 @@ "type": "text", "placeholders": {} }, - "sentAFile": "{username}傳送了一個文件", + "sentAFile": "{username} 傳送了一個文件", "@sentAFile": { "type": "text", "placeholders": { "username": {} } }, - "sentAnAudio": "{username}傳送了一個音訊", + "sentAnAudio": "{username} 傳送了一個音訊", "@sentAnAudio": { "type": "text", "placeholders": { "username": {} } }, - "sentAPicture": "{username}傳送了一張圖片", + "sentAPicture": "{username} 傳送了一張圖片", "@sentAPicture": { "type": "text", "placeholders": { @@ -1362,7 +1362,7 @@ "type": "text", "placeholders": {} }, - "startedACall": "{senderName}開始了通話", + "startedACall": "{senderName} 開始了通話", "@startedACall": { "type": "text", "placeholders": { @@ -1440,7 +1440,7 @@ "type": "text", "placeholders": {} }, - "unbannedUser": "{username}解除封禁了{targetName}", + "unbannedUser": "{username} 解除封鎖了 {targetName}", "@unbannedUser": { "type": "text", "placeholders": { @@ -1463,7 +1463,7 @@ "type": "text", "placeholders": {} }, - "unknownEvent": "未知事件「{type}」", + "unknownEvent": "未知事件「 {type} 」", "@unknownEvent": { "type": "text", "placeholders": { @@ -1480,14 +1480,14 @@ "type": "text", "placeholders": {} }, - "unreadChats": "{unreadCount, plural, =1{1 unread chat} other{{unreadCount} 個未讀聊天室}}", + "unreadChats": "{unreadCount, plural, =1 {1 unread chat} other { {unreadCount} 個未讀聊天室} }", "@unreadChats": { "type": "text", "placeholders": { "unreadCount": {} } }, - "userAndOthersAreTyping": "{username}和其他{count}個人正在輸入…", + "userAndOthersAreTyping": "{username} 和其他 {count} 個人正在輸入...…", "@userAndOthersAreTyping": { "type": "text", "placeholders": { @@ -1495,7 +1495,7 @@ "count": {} } }, - "userAndUserAreTyping": "{username}和{username2}正在輸入…", + "userAndUserAreTyping": "{username} 和 {username2} 正在輸入...…", "@userAndUserAreTyping": { "type": "text", "placeholders": { @@ -1503,14 +1503,14 @@ "username2": {} } }, - "userIsTyping": "{username}正在輸入…", + "userIsTyping": "{username} 正在輸入...…", "@userIsTyping": { "type": "text", "placeholders": { "username": {} } }, - "userLeftTheChat": "{username}離開了聊天室", + "userLeftTheChat": "{username} 離開了聊天室", "@userLeftTheChat": { "type": "text", "placeholders": { @@ -1522,7 +1522,7 @@ "type": "text", "placeholders": {} }, - "userSentUnknownEvent": "{username}傳送了一個{type}事件", + "userSentUnknownEvent": "{username} 傳送了一個 {type} 事件", "@userSentUnknownEvent": { "type": "text", "placeholders": { @@ -1560,7 +1560,7 @@ "type": "text", "placeholders": {} }, - "visibilityOfTheChatHistory": "聊天記錄的可見性", + "visibilityOfTheChatHistory": "聊天室記錄的可見性", "@visibilityOfTheChatHistory": { "type": "text", "placeholders": {} @@ -1580,17 +1580,17 @@ "type": "text", "placeholders": {} }, - "waitingPartnerAcceptRequest": "正在等待夥伴接受請求…", + "waitingPartnerAcceptRequest": "正在等待夥伴接受請求...…", "@waitingPartnerAcceptRequest": { "type": "text", "placeholders": {} }, - "waitingPartnerEmoji": "正在等待夥伴接受表情符號…", + "waitingPartnerEmoji": "正在等待夥伴接受表情符號...…", "@waitingPartnerEmoji": { "type": "text", "placeholders": {} }, - "waitingPartnerNumbers": "正在等待夥伴接受數字…", + "waitingPartnerNumbers": "正在等待夥伴接受數字...…", "@waitingPartnerNumbers": { "type": "text", "placeholders": {} @@ -1625,7 +1625,7 @@ "type": "text", "placeholders": {} }, - "wipeChatBackup": "要清除您的聊天記錄備份以建立新的安全金鑰嗎?", + "wipeChatBackup": "要清除您的聊天室記錄備份以建立新的安全金鑰嗎?", "@wipeChatBackup": { "type": "text", "placeholders": {} @@ -1635,7 +1635,7 @@ "type": "text", "placeholders": {} }, - "writeAMessage": "輸入訊息…", + "writeAMessage": "輸入訊息...…", "@writeAMessage": { "type": "text", "placeholders": {} @@ -1655,7 +1655,7 @@ "type": "text", "placeholders": {} }, - "youHaveBeenBannedFromThisChat": "您已經被這個聊天室封禁", + "youHaveBeenBannedFromThisChat": "您已經被這個聊天室封鎖", "@youHaveBeenBannedFromThisChat": { "type": "text", "placeholders": {} @@ -1680,7 +1680,7 @@ "type": "text", "placeholders": {} }, - "commandHint_ban": "在此聊天室封禁該使用者", + "commandHint_ban": "在此聊天室封鎖該使用者", "@commandHint_ban": { "type": "text", "description": "Usage hint for the command /ban" @@ -1690,7 +1690,7 @@ "type": "text", "description": "Usage hint for the command /clearcache" }, - "commandHint_create": "建立一個空的群聊\n使用 --no-encryption 選項來禁用加密", + "commandHint_create": "建立一個空的群聊\n使用 --no-encryption 選項來停用加密", "@commandHint_create": { "type": "text", "description": "Usage hint for the command /create" @@ -1700,7 +1700,7 @@ "type": "text", "description": "Usage hint for the command /discardsession" }, - "commandHint_dm": "啟動一對一聊天\n使用 --no-encryption 選項來禁用加密", + "commandHint_dm": "啟動一對一聊天室\n使用 --no-encryption 選項來停用加密", "@commandHint_dm": { "type": "text", "description": "Usage hint for the command /dm" @@ -1751,7 +1751,7 @@ }, "repeatPassword": "再次輸入密碼", "@repeatPassword": {}, - "yourChatBackupHasBeenSetUp": "您的聊天記錄備份已設定。", + "yourChatBackupHasBeenSetUp": "您的聊天室記錄備份已設定。", "@yourChatBackupHasBeenSetUp": {}, "goToTheNewRoom": "前往新聊天室", "@goToTheNewRoom": { @@ -1763,7 +1763,7 @@ "type": "text", "description": "Usage hint for the command /myroomavatar" }, - "commandHint_unban": "在此聊天室解封該使用者", + "commandHint_unban": "在此聊天室解除封鎖該使用者", "@commandHint_unban": { "type": "text", "description": "Usage hint for the command /unban" @@ -1780,11 +1780,11 @@ "type": "text", "placeholders": {} }, - "chatHasBeenAddedToThisSpace": "聊天室已添加到此空間", + "chatHasBeenAddedToThisSpace": "聊天室已新增到此空間", "@chatHasBeenAddedToThisSpace": {}, "clearArchive": "清除存檔", "@clearArchive": {}, - "hugContent": "{senderName}擁抱您", + "hugContent": "{senderName} 擁抱您", "@hugContent": { "type": "text", "placeholders": { @@ -1793,7 +1793,7 @@ }, "commandHint_cuddle": "發送一個摟抱表情", "@commandHint_cuddle": {}, - "supposedMxid": "此處應爲{mxid}", + "supposedMxid": "此處應爲 {mxid}", "@supposedMxid": { "type": "text", "placeholders": { @@ -1802,17 +1802,17 @@ }, "invalidServerName": "伺服器名稱錯誤", "@invalidServerName": {}, - "importFromZipFile": "從 zip 檔案匯入", + "importFromZipFile": "從 .zip 檔案匯入", "@importFromZipFile": {}, "homeserver": "伺服器", "@homeserver": {}, - "exportEmotePack": "將表情包匯出成 zip 檔案", + "exportEmotePack": "將表情包匯出成 .zip 檔案", "@exportEmotePack": {}, "commandInvalid": "命令無效", "@commandInvalid": { "type": "text" }, - "commandMissing": "{command}不是正確的命令。", + "commandMissing": "{command} 不是正確的命令。", "@commandMissing": { "type": "text", "placeholders": { @@ -1820,24 +1820,24 @@ }, "description": "State that {command} is not a valid /command." }, - "googlyEyesContent": "{senderName}向您發送了瞪眼表情", + "googlyEyesContent": "{senderName} 向您發送了瞪眼表情", "@googlyEyesContent": { "type": "text", "placeholders": { "senderName": {} } }, - "addChatDescription": "新增聊天說明...", + "addChatDescription": "新增聊天室描述......", "@addChatDescription": {}, "sendTypingNotifications": "傳送「輸入中」通知", "@sendTypingNotifications": {}, "importEmojis": "匯入表情包", "@importEmojis": {}, - "confirmMatrixId": "如需要刪除你的帳戶,請確認你的Matrix ID。", + "confirmMatrixId": "如需要刪除你的帳戶,請確認你的 Matrix ID。", "@confirmMatrixId": {}, "notAnImage": "不是圖片檔案。", "@notAnImage": {}, - "cuddleContent": "{senderName}摟抱您", + "cuddleContent": "{senderName} 摟抱您", "@cuddleContent": { "type": "text", "placeholders": { @@ -1858,10 +1858,883 @@ "@blockedUsers": {}, "block": "封鎖", "@block": {}, - "discover": "探索", + "discover": "發現", "@discover": {}, "alwaysUse24HourFormat": "否", "@alwaysUse24HourFormat": { "description": "Set to true to always display time of day in 24 hour format." + }, + "sender": "發送者", + "@sender": {}, + "voiceCall": "語音通話", + "@voiceCall": {}, + "blockUsername": "忽略使用者名稱", + "@blockUsername": {}, + "noBackupWarning": "警告!如果不啟用聊天室備份,您將失去對加密訊息的訪問。強烈建議在登出前先啟用聊天室備份。", + "@noBackupWarning": {}, + "addChatOrSubSpace": "新增聊天室或子空間", + "@addChatOrSubSpace": {}, + "thisDevice": "這個設備:", + "@thisDevice": {}, + "separateChatTypes": "分開私訊和群組", + "@separateChatTypes": { + "type": "text", + "placeholders": {} + }, + "commandHint_markasdm": "將給定的 Matrix ID 標記為直接訊息房間", + "@commandHint_markasdm": {}, + "commandHint_html": "發送 HTML 格式的文字", + "@commandHint_html": { + "type": "text", + "description": "Usage hint for the command /html" + }, + "commandHint_send": "發送文字", + "@commandHint_send": { + "type": "text", + "description": "Usage hint for the command /send" + }, + "emoteKeyboardNoRecents": "最近使用的表情將顯示在這裡...", + "@emoteKeyboardNoRecents": { + "type": "text", + "placeholders": {} + }, + "noChatDescriptionYet": "尚未建立聊天室描述。", + "@noChatDescriptionYet": {}, + "optionalRedactReason": "(非必填)收回此訊息的原因...", + "@optionalRedactReason": {}, + "dehydrateWarning": "此操作不能反悔。請確保安全地存儲備份文件。", + "@dehydrateWarning": {}, + "hydrateTorLong": "上次在 TOR 上匯出會話了嗎?快速匯入它並繼續聊天室。", + "@hydrateTorLong": {}, + "hydrate": "從備份文件恢復", + "@hydrate": {}, + "locationDisabledNotice": "位置服務被停用。請啟用它們以能夠分享您的位置。", + "@locationDisabledNotice": { + "type": "text", + "placeholders": {} + }, + "noMatrixServer": "{server1} 不是 Matrix 服務器,改用 {server2} 嗎?", + "@noMatrixServer": { + "type": "text", + "placeholders": { + "server1": {}, + "server2": {} + } + }, + "addToBundle": "新增到套組", + "@addToBundle": {}, + "bundleName": "套組名稱", + "@bundleName": {}, + "pleaseEnterYourPin": "請輸入您的密碼", + "@pleaseEnterYourPin": { + "type": "text", + "placeholders": {} + }, + "redactedByBecause": "由 {username} 編輯,原因:\"{reason}\"", + "@redactedByBecause": { + "type": "text", + "placeholders": { + "username": {}, + "reason": {} + } + }, + "saveFile": "儲存檔案", + "@saveFile": { + "type": "text", + "placeholders": {} + }, + "publish": "發布", + "@publish": {}, + "hasKnocked": "🚪 {user} 敲門了", + "@hasKnocked": { + "placeholders": { + "user": {} + } + }, + "unlockOldMessages": "解鎖舊消息", + "@unlockOldMessages": {}, + "callingAccountDetails": "允許 FluffyChat 使用原生 Android 撥號應用程式。", + "@callingAccountDetails": {}, + "noOtherDevicesFound": "未找到其他設備", + "@noOtherDevicesFound": {}, + "noUsersFoundWithQuery": "很遺憾,找不到與「 {query} 」相符的使用者。請檢查是否有打錯字。", + "@noUsersFoundWithQuery": { + "type": "text", + "placeholders": { + "query": {} + } + }, + "publicLink": "公開網址", + "@publicLink": {}, + "dehydrate": "匯出會話並清除裝置", + "@dehydrate": {}, + "dehydrateTor": "TOR 使用者:匯出會話", + "@dehydrateTor": {}, + "reopenChat": "重新開啟聊天室", + "@reopenChat": {}, + "widgetNameError": "請提供一個顯示名稱。", + "@widgetNameError": {}, + "yourGlobalUserIdIs": "您的全域使用者ID是: ", + "@yourGlobalUserIdIs": {}, + "startFirstChat": "開始您的第一次聊天室", + "@startFirstChat": {}, + "experimentalVideoCalls": "實驗性視訊通話", + "@experimentalVideoCalls": {}, + "youAcceptedTheInvitation": "👍 您接受了邀請", + "@youAcceptedTheInvitation": {}, + "storeSecurlyOnThisDevice": "在此設備上安全存儲", + "@storeSecurlyOnThisDevice": {}, + "countFiles": "{count} 個文件", + "@countFiles": { + "placeholders": { + "count": {} + } + }, + "screenSharingDetail": "您正在 FuffyChat 中分享您的螢幕", + "@screenSharingDetail": {}, + "wrongPinEntered": "輸入的密碼錯誤! {seconds} 秒後再試一次......", + "@wrongPinEntered": { + "type": "text", + "placeholders": { + "seconds": {} + } + }, + "archiveRoomDescription": "聊天室將被移動到存檔中。其他使用者將能看到您已離開聊天室。", + "@archiveRoomDescription": {}, + "banUserDescription": "該使用者將被禁止進入聊天室,直到他們被解禁之前都無法再次進入聊天室。", + "@banUserDescription": {}, + "searchChatsRooms": "搜尋 #chats, @users...", + "@searchChatsRooms": {}, + "decline": "拒絕", + "@decline": {}, + "sendReadReceipts": "發送已讀回條", + "@sendReadReceipts": {}, + "formattedMessagesDescription": "使用 markdown 顯示豐富的訊息內容,如粗體文字。", + "@formattedMessagesDescription": {}, + "verifyOtherDevice": "🔐 驗證其他裝置", + "@verifyOtherDevice": {}, + "youInvitedUser": "📩 您邀請了 {user}", + "@youInvitedUser": { + "placeholders": { + "user": {} + } + }, + "pinMessage": "釘選到房間", + "@pinMessage": {}, + "youKicked": "👞 您踢出了 {user}", + "@youKicked": { + "placeholders": { + "user": {} + } + }, + "users": "使用者", + "@users": {}, + "pleaseChoose": "請選擇", + "@pleaseChoose": { + "type": "text", + "placeholders": {} + }, + "youRejectedTheInvitation": "您拒絕了邀請", + "@youRejectedTheInvitation": {}, + "enterRoom": "進入房間", + "@enterRoom": {}, + "allSpaces": "所有空間", + "@allSpaces": {}, + "indexedDbErrorLong": "預設情況下,私密模式不啟用消息存儲。\n請訪問\n - about:config\n - 將 dom.indexedDB.privateBrowsing.enabled 設置為 true\n否則,無法運行 FluffyChat。", + "@indexedDbErrorLong": {}, + "youKickedAndBanned": "🙅 您踢出並封鎖了 {user}", + "@youKickedAndBanned": { + "placeholders": { + "user": {} + } + }, + "user": "使用者", + "@user": {}, + "custom": "自訂", + "@custom": {}, + "hidePresences": "隱藏狀態列表?", + "@hidePresences": {}, + "signInWithPassword": "使用密碼登入", + "@signInWithPassword": {}, + "setColorTheme": "設定顏色主題:", + "@setColorTheme": {}, + "makeAdminDescription": "一旦您讓這個使用者成為管理員,您可能無法撤銷此操作,因為他們將擁有與您相同的權限。", + "@makeAdminDescription": {}, + "createGroupAndInviteUsers": "建立群組並邀請使用者", + "@createGroupAndInviteUsers": {}, + "groupCanBeFoundViaSearch": "可以透過搜尋找到群組", + "@groupCanBeFoundViaSearch": {}, + "pleaseEnterYourCurrentPassword": "請輸入您當前的密碼", + "@pleaseEnterYourCurrentPassword": {}, + "widgetCustom": "自訂", + "@widgetCustom": {}, + "createGroup": "建立群組", + "@createGroup": {}, + "enterSpace": "進入空間", + "@enterSpace": {}, + "shareLocation": "分享位置", + "@shareLocation": { + "type": "text", + "placeholders": {} + }, + "widgetVideo": "影片", + "@widgetVideo": {}, + "redactMessageDescription": "該訊息將對此對話中的所有參與者收回。這不能被反悔。", + "@redactMessageDescription": {}, + "removeFromBundle": "從此套組中移除", + "@removeFromBundle": {}, + "widgetName": "名稱", + "@widgetName": {}, + "jump": "跳轉", + "@jump": {}, + "commandHint_unignore": "取消忽略已提供的 Matrix ID", + "@commandHint_unignore": {}, + "commandHint_markasgroup": "標記為群組", + "@commandHint_markasgroup": {}, + "commandHint_me": "描述自己", + "@commandHint_me": { + "type": "text", + "description": "Usage hint for the command /me" + }, + "commandHint_plain": "發送未格式化的文字", + "@commandHint_plain": { + "type": "text", + "description": "Usage hint for the command /plain" + }, + "commandHint_react": "以反應的形式發送回覆", + "@commandHint_react": { + "type": "text", + "description": "Usage hint for the command /react" + }, + "createNewSpace": "新建空間", + "@createNewSpace": { + "type": "text", + "placeholders": {} + }, + "allRooms": "所有群組聊天室", + "@allRooms": { + "type": "text", + "placeholders": {} + }, + "chatPermissions": "聊天室權限", + "@chatPermissions": {}, + "customEmojisAndStickersBody": "新增或分享可在任何聊天室中使用的自訂表情符號或貼圖。", + "@customEmojisAndStickersBody": {}, + "errorObtainingLocation": "取得位置錯誤:{error}", + "@errorObtainingLocation": { + "type": "text", + "placeholders": { + "error": {} + } + }, + "hideRedactedMessages": "隱藏被刪除的訊息", + "@hideRedactedMessages": {}, + "hideInvalidOrUnknownMessageFormats": "隱藏無效或未知的訊息格式", + "@hideInvalidOrUnknownMessageFormats": {}, + "dehydrateTorLong": "對 TOR 使用者,建議在關閉窗口前匯出會話。", + "@dehydrateTorLong": {}, + "hydrateTor": "TOR 使用者:匯入會話匯出", + "@hydrateTor": {}, + "messagesStyle": "訊息樣式:", + "@messagesStyle": {}, + "shareInviteLink": "分享邀請網址", + "@shareInviteLink": {}, + "scanQrCode": "掃描 QR 碼", + "@scanQrCode": {}, + "openVideoCamera": "打開錄影", + "@openVideoCamera": { + "type": "text", + "placeholders": {} + }, + "oneClientLoggedOut": "您的一個客戶端已登出", + "@oneClientLoggedOut": {}, + "addAccount": "新增帳號", + "@addAccount": {}, + "editBundlesForAccount": "為此帳號編輯套組", + "@editBundlesForAccount": {}, + "openInMaps": "在地圖中打開", + "@openInMaps": { + "type": "text", + "placeholders": {} + }, + "serverRequiresEmail": "該伺服器需要驗證您的註冊電子郵件地址。", + "@serverRequiresEmail": {}, + "or": "或", + "@or": { + "type": "text", + "placeholders": {} + }, + "hideMemberChangesInPublicChatsBody": "若有人加入或離開公開聊天室,將不在聊天室時間軸顯示,以提升資訊可讀性。", + "@hideMemberChangesInPublicChatsBody": {}, + "overview": "概觀", + "@overview": {}, + "notifyMeFor": "通知我", + "@notifyMeFor": {}, + "passwordRecoverySettings": "恢復密碼設定", + "@passwordRecoverySettings": {}, + "redactedBy": "由 {username} 編輯", + "@redactedBy": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "recoveryKey": "恢復金鑰", + "@recoveryKey": {}, + "spaceName": "空間名稱", + "@spaceName": { + "type": "text", + "placeholders": {} + }, + "synchronizingPleaseWait": "正在同步... 請稍候。", + "@synchronizingPleaseWait": { + "type": "text", + "placeholders": {} + }, + "messageInfo": "訊息資訊", + "@messageInfo": {}, + "removeFromSpace": "從空間中移除", + "@removeFromSpace": {}, + "addToSpaceDescription": "選擇一個空間將此聊天室加入。", + "@addToSpaceDescription": {}, + "pleaseEnterRecoveryKeyDescription": "要解鎖您的舊訊息,請輸入在之前的會話中生成的恢復密鑰。您的恢復密鑰不是您的密碼。", + "@pleaseEnterRecoveryKeyDescription": {}, + "videoWithSize": "影片({size})", + "@videoWithSize": { + "type": "text", + "placeholders": { + "size": {} + } + }, + "emojis": "表情符號", + "@emojis": {}, + "placeCall": "發起通話", + "@placeCall": {}, + "unsupportedAndroidVersion": "不支持的Android版本", + "@unsupportedAndroidVersion": {}, + "videoCallsBetaWarning": "請注意,視訊通話目前處於測試階段。它們可能不會按預期工作,或者在所有平台上都不工作。", + "@videoCallsBetaWarning": {}, + "widgetUrlError": "這不是一個有效的URL。", + "@widgetUrlError": {}, + "nextAccount": "下一個帳戶", + "@nextAccount": {}, + "previousAccount": "上一個帳戶", + "@previousAccount": {}, + "addWidget": "新增小工具", + "@addWidget": {}, + "errorAddingWidget": "新增小工具時發生錯誤。", + "@errorAddingWidget": {}, + "youJoinedTheChat": "您加入了聊天室", + "@youJoinedTheChat": {}, + "youBannedUser": "您封鎖了 {user}", + "@youBannedUser": { + "placeholders": { + "user": {} + } + }, + "youHaveWithdrawnTheInvitationFor": "您已收回對 {user} 的邀請", + "@youHaveWithdrawnTheInvitationFor": { + "placeholders": { + "user": {} + } + }, + "youInvitedBy": "📩 您被 {user} 邀請", + "@youInvitedBy": { + "placeholders": { + "user": {} + } + }, + "youUnbannedUser": "您解除封鎖了 {user}", + "@youUnbannedUser": { + "placeholders": { + "user": {} + } + }, + "youInvitedToBy": "📩 您通過網址被邀請至:\n {alias}", + "@youInvitedToBy": { + "placeholders": { + "alias": {} + } + }, + "callingPermissions": "通話權限", + "@callingPermissions": {}, + "callingAccount": "通話帳戶", + "@callingAccount": {}, + "appearOnTop": "顯示在最上層", + "@appearOnTop": {}, + "newGroup": "新群組", + "@newGroup": {}, + "newSpace": "新空間", + "@newSpace": {}, + "numChats": "{number} 個聊天室", + "@numChats": { + "type": "number", + "placeholders": { + "number": {} + } + }, + "hideUnimportantStateEvents": "隱藏不重要的狀態事件", + "@hideUnimportantStateEvents": {}, + "doNotShowAgain": "不再顯示", + "@doNotShowAgain": {}, + "encryptThisChat": "加密此聊天室", + "@encryptThisChat": {}, + "sorryThatsNotPossible": "抱歉......這是不可能的", + "@sorryThatsNotPossible": {}, + "profileNotFound": "在伺服器上找不到該使用者。可能是連接問題或該使用者不存在。", + "@profileNotFound": {}, + "invite": "邀請", + "@invite": {}, + "invitePrivateChat": "📨 邀請私人聊天室", + "@invitePrivateChat": {}, + "removeDevicesDescription": "您將從這個設備登出,並將不再能夠接收消息。", + "@removeDevicesDescription": {}, + "unbanUserDescription": "如果該使用者嘗試,他們將能夠再次進入聊天室。", + "@unbanUserDescription": {}, + "kickUserDescription": "該使用者被踢出聊天室,但未被禁止。在公開聊天室中,該使用者可以隨時重新加入。", + "@kickUserDescription": {}, + "pushNotificationsNotAvailable": "推送通知不可用", + "@pushNotificationsNotAvailable": {}, + "learnMore": "了解更多", + "@learnMore": {}, + "nothingFound": "什麼都沒找到......", + "@nothingFound": {}, + "startConversation": "開始對話", + "@startConversation": {}, + "databaseMigrationBody": "請稍候。這可能需要一點時間。", + "@databaseMigrationBody": {}, + "pleaseChooseAStrongPassword": "請選擇一個強密碼", + "@pleaseChooseAStrongPassword": {}, + "passwordIsWrong": "您輸入的密碼錯誤", + "@passwordIsWrong": {}, + "publicChatAddresses": "公開聊天室地址", + "@publicChatAddresses": {}, + "userRole": "使用者角色", + "@userRole": {}, + "minimumPowerLevel": "{level} 是最低權限等級。", + "@minimumPowerLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "leaveEmptyToClearStatus": "留空以清除您的狀態。", + "@leaveEmptyToClearStatus": {}, + "select": "選擇", + "@select": {}, + "files": "文件", + "@files": {}, + "forwardMessageTo": "將訊息轉發至 {roomName} ?", + "@forwardMessageTo": { + "type": "text", + "placeholders": { + "roomName": {} + } + }, + "sendTypingNotificationsDescription": "聊天室中的其他參與者可以看到您正在輸入新訊息。", + "@sendTypingNotificationsDescription": {}, + "verifyOtherDeviceDescription": "當您驗證另一個裝置時,這些裝置可以交換密鑰,提升您的整體安全性。💪 當您開始驗證時,一個彈出視窗將在兩個裝置上的應用程式中出現。在那裡,您將看到一系列的表情符號或數字,您需要相互比較。在開始驗證之前最好有兩個裝置在手邊。🤳", + "@verifyOtherDeviceDescription": {}, + "acceptedKeyVerification": "{sender} 接受了密鑰驗證", + "@acceptedKeyVerification": { + "type": "text", + "placeholders": { + "sender": {} + } + }, + "completedKeyVerification": "{sender} 完成了密鑰驗證", + "@completedKeyVerification": { + "type": "text", + "placeholders": { + "sender": {} + } + }, + "isReadyForKeyVerification": "{sender} 已準備好進行密鑰驗證", + "@isReadyForKeyVerification": { + "type": "text", + "placeholders": { + "sender": {} + } + }, + "thereAreCountUsersBlocked": "目前有 {count} 名使用者被封鎖。", + "@thereAreCountUsersBlocked": { + "type": "text", + "count": {} + }, + "knockRestricted": "敲門受限", + "@knockRestricted": {}, + "appLockDescription": "未使用時以密碼鎖定應用程式", + "@appLockDescription": {}, + "globalChatId": "全球聊天室 ID", + "@globalChatId": {}, + "accessAndVisibility": "訪問權限和可見性", + "@accessAndVisibility": {}, + "accessAndVisibilityDescription": "誰被允許加入此聊天室以及如何發現聊天室。", + "@accessAndVisibilityDescription": {}, + "calls": "通話", + "@calls": {}, + "chatDescription": "聊天室描述", + "@chatDescription": {}, + "chatDescriptionHasBeenChanged": "聊天室描述已變更", + "@chatDescriptionHasBeenChanged": {}, + "tryAgain": "再試一次", + "@tryAgain": {}, + "pleaseEnterRecoveryKey": "請輸入您的恢復金鑰:", + "@pleaseEnterRecoveryKey": {}, + "directChat": "私訊", + "@directChat": {}, + "register": "註冊", + "@register": { + "type": "text", + "placeholders": {} + }, + "setAsCanonicalAlias": "設為主要別名", + "@setAsCanonicalAlias": { + "type": "text", + "placeholders": {} + }, + "setChatDescription": "設定聊天室描述", + "@setChatDescription": {}, + "groupName": "群組名稱", + "@groupName": {}, + "searchForUsers": "搜尋 @users...", + "@searchForUsers": {}, + "inviteGroupChat": "📨 邀請群組聊天室", + "@inviteGroupChat": {}, + "setTheme": "設定主題:", + "@setTheme": {}, + "knocking": "敲門", + "@knocking": {}, + "sessionLostBody": "您的會話已丟失。請將此錯誤報告給開發人員,網址為 {url} 。錯誤訊息為:{error}", + "@sessionLostBody": { + "type": "text", + "placeholders": { + "url": {}, + "error": {} + } + }, + "swipeRightToLeftToReply": "向右滑至左以回覆", + "@swipeRightToLeftToReply": {}, + "hideRedactedMessagesBody": "如果有人收回一條訊息,該訊息將不再在聊天室中顯示。", + "@hideRedactedMessagesBody": {}, + "link": "網址", + "@link": {}, + "obtainingLocation": "正在取得位置…", + "@obtainingLocation": { + "type": "text", + "placeholders": {} + }, + "oopsPushError": "哎呀!設定推送通知時不幸發生錯誤。", + "@oopsPushError": { + "type": "text", + "placeholders": {} + }, + "removeYourAvatar": "移除您的頭像", + "@removeYourAvatar": { + "type": "text", + "placeholders": {} + }, + "singlesignon": "單一登入", + "@singlesignon": { + "type": "text", + "placeholders": {} + }, + "presenceStyle": "目前狀態:", + "@presenceStyle": { + "type": "text", + "placeholders": {} + }, + "presencesToggle": "顯示其他使用者的狀態訊息", + "@presencesToggle": { + "type": "text", + "placeholders": {} + }, + "spaceIsPublic": "空間是公開的", + "@spaceIsPublic": { + "type": "text", + "placeholders": {} + }, + "dismiss": "解散", + "@dismiss": {}, + "reactedWith": "{sender} 以 {reaction} 回應", + "@reactedWith": { + "type": "text", + "placeholders": { + "sender": {}, + "reaction": {} + } + }, + "confirmEventUnpin": "您確定要永久取消釘選該事件嗎?", + "@confirmEventUnpin": {}, + "switchToAccount": "切換到帳戶 {number}", + "@switchToAccount": { + "type": "number", + "placeholders": { + "number": {} + } + }, + "widgetEtherpad": "文字筆記", + "@widgetEtherpad": {}, + "noOneCanJoin": "沒有人可以加入", + "@noOneCanJoin": {}, + "userWouldLikeToChangeTheChat": "{user} 想要加入聊天室。", + "@userWouldLikeToChangeTheChat": { + "placeholders": { + "user": {} + } + }, + "noPublicLinkHasBeenCreatedYet": "尚未建立公開網址", + "@noPublicLinkHasBeenCreatedYet": {}, + "saveKeyManuallyDescription": "通過觸發系統分享對話框或剪貼板手動保存此密鑰。", + "@saveKeyManuallyDescription": {}, + "storeInAndroidKeystore": "存儲在 Android KeyStore", + "@storeInAndroidKeystore": {}, + "storeInAppleKeyChain": "存儲在 Apple KeyChain", + "@storeInAppleKeyChain": {}, + "foregroundServiceRunning": "當前景服務正在運行時會顯示此通知。", + "@foregroundServiceRunning": {}, + "screenSharingTitle": "螢幕分享", + "@screenSharingTitle": {}, + "wasDirectChatDisplayName": "空的聊天室(原名稱為 {oldDisplayName} )", + "@wasDirectChatDisplayName": { + "type": "text", + "placeholders": { + "oldDisplayName": {} + } + }, + "otherCallingPermissions": "麥克風、相機和其他 FluffyChat 權限", + "@otherCallingPermissions": {}, + "disableEncryptionWarning": "出於安全原因,您不能在之前已啟用加密的聊天室中停用加密。", + "@disableEncryptionWarning": {}, + "deviceKeys": "設備密鑰:", + "@deviceKeys": {}, + "fileIsTooBigForServer": "伺服器報告該文件太大,無法發送。", + "@fileIsTooBigForServer": {}, + "fileHasBeenSavedAt": "文件已保存在 {path}", + "@fileHasBeenSavedAt": { + "type": "text", + "placeholders": { + "path": {} + } + }, + "jumpToLastReadMessage": "跳至最後讀取的訊息", + "@jumpToLastReadMessage": {}, + "openLinkInBrowser": "在瀏覽器中開啟連結", + "@openLinkInBrowser": {}, + "reportErrorDescription": "😭 哦不。出了些問題。如果您願意,可以將此錯誤報告給開發者。", + "@reportErrorDescription": {}, + "readUpToHere": "讀到這裡", + "@readUpToHere": {}, + "report": "報告", + "@report": {}, + "pleaseEnterANumber": "請輸入大於 0 的數字", + "@pleaseEnterANumber": {}, + "roomUpgradeDescription": "然後,聊天室將使用新的房間版本重新建立。所有參與者將被通知他們需要切換到新的聊天室。您可以在 https://spec.matrix.org/latest/rooms/ 了解更多關於房間版本的信息。", + "@roomUpgradeDescription": {}, + "wrongRecoveryKey": "抱歉......這似乎不是正確的恢復密鑰。", + "@wrongRecoveryKey": {}, + "passwordsDoNotMatch": "密碼不匹配", + "@passwordsDoNotMatch": {}, + "publicSpaces": "公共空間", + "@publicSpaces": {}, + "subspace": "子空間", + "@subspace": {}, + "initAppError": "初始化應用時發生錯誤", + "@initAppError": {}, + "canceledKeyVerification": "{sender} 取消了密鑰驗證", + "@canceledKeyVerification": { + "type": "text", + "placeholders": { + "sender": {} + } + }, + "startedKeyVerification": "{sender} 開始了密鑰驗證", + "@startedKeyVerification": { + "type": "text", + "placeholders": { + "sender": {} + } + }, + "transparent": "透明", + "@transparent": {}, + "incomingMessages": "收到的訊息", + "@incomingMessages": {}, + "databaseMigrationTitle": "資料庫已最佳化", + "@databaseMigrationTitle": {}, + "restoreSessionBody": "應用程式現在嘗試從備份中恢復您的會話。請將此錯誤報告給開發人員,網址為 {url} 。錯誤訊息為:{error}", + "@restoreSessionBody": { + "type": "text", + "placeholders": { + "url": {}, + "error": {} + } + }, + "stickers": "貼圖", + "@stickers": {}, + "joinSpace": "加入空間", + "@joinSpace": {}, + "noMoreChatsFound": "沒有更多聊天室了...", + "@noMoreChatsFound": {}, + "commandHint_op": "設定給定使用者的權限等級(預設:50)", + "@commandHint_op": { + "type": "text", + "description": "Usage hint for the command /op" + }, + "customEmojisAndStickers": "自訂表情符號和貼圖", + "@customEmojisAndStickers": {}, + "locationPermissionDeniedNotice": "位置權限被拒絕。請授予它們以能夠分享您的位置。", + "@locationPermissionDeniedNotice": { + "type": "text", + "placeholders": {} + }, + "inviteContactToGroupQuestion": "您想邀請 {contact} 加入 \"{groupName}\" 聊天室嗎?", + "@inviteContactToGroupQuestion": {}, + "enableMultiAccounts": "(實驗性功能)在此裝置上啟用多個帳號", + "@enableMultiAccounts": {}, + "hideMemberChangesInPublicChats": "在公開聊天室中隱藏成員變動", + "@hideMemberChangesInPublicChats": {}, + "recoveryKeyLost": "遺失恢復金鑰?", + "@recoveryKeyLost": {}, + "sendAsText": "以文字發送", + "@sendAsText": { + "type": "text" + }, + "sendSticker": "發送貼圖", + "@sendSticker": { + "type": "text", + "placeholders": {} + }, + "unverified": "尚未驗證", + "@unverified": {}, + "time": "時間", + "@time": {}, + "chatCanBeDiscoveredViaSearchOnServer": "可以透過在 {server} 上的搜尋發現聊天室", + "@chatCanBeDiscoveredViaSearchOnServer": { + "type": "text", + "placeholders": { + "server": {} + } + }, + "commandHint_sendraw": "發送原始 json", + "@commandHint_sendraw": {}, + "newPassword": "新密碼", + "@newPassword": {}, + "createNewAddress": "建立新地址", + "@createNewAddress": {}, + "searchIn": "在聊天室「 {chat} 」中搜尋......", + "@searchIn": { + "type": "text", + "placeholders": { + "chat": {} + } + }, + "searchMore": "搜尋更多......", + "@searchMore": {}, + "gallery": "畫廊", + "@gallery": {}, + "databaseBuildErrorBody": "無法建立 SQLite 資料庫。應用程式目前嘗試使用遺留資料庫。請將此錯誤報告給開發人員,網址為 {url} 。錯誤訊息為:{error}", + "@databaseBuildErrorBody": { + "type": "text", + "placeholders": { + "url": {}, + "error": {} + } + }, + "sendReadReceiptsDescription": "聊天室中的其他參與者可以看到您已讀取一條訊息。", + "@sendReadReceiptsDescription": {}, + "formattedMessages": "格式化訊息", + "@formattedMessages": {}, + "restricted": "受限", + "@restricted": {}, + "goToSpace": "前往空間:{space}", + "@goToSpace": { + "type": "text", + "space": {} + }, + "markAsUnread": "標記為未讀", + "@markAsUnread": {}, + "noDatabaseEncryption": "此平台不支援資料庫加密", + "@noDatabaseEncryption": {}, + "messageType": "訊息類型", + "@messageType": {}, + "openGallery": "開啟畫廊", + "@openGallery": {}, + "markAsRead": "標記為已讀", + "@markAsRead": {}, + "reportUser": "舉報使用者", + "@reportUser": {}, + "unsupportedAndroidVersionLong": "此功能需要較新的Android版本。請檢查更新或Lineage OS支持。", + "@unsupportedAndroidVersionLong": {}, + "emailOrUsername": "電子郵件或使用者名", + "@emailOrUsername": {}, + "indexedDbErrorTitle": "私密模式問題", + "@indexedDbErrorTitle": {}, + "widgetJitsi": "Jitsi Meet", + "@widgetJitsi": {}, + "usersMustKnock": "使用者必須敲門", + "@usersMustKnock": {}, + "knock": "敲門", + "@knock": {}, + "storeInSecureStorageDescription": "將恢復密鑰存儲在此設備的安全存儲中。", + "@storeInSecureStorageDescription": {}, + "appearOnTopDetails": "允許應用程式顯示在最上層(如果您已將 Fluffychat 設定為通話帳戶則不需要)", + "@appearOnTopDetails": {}, + "whyIsThisMessageEncrypted": "為什麼這條訊息無法讀取?", + "@whyIsThisMessageEncrypted": {}, + "noKeyForThisMessage": "如果訊息是在您登入此設備之前發送的,就可能會發生這種情況。\n\n也有可能是發送者已經封鎖了您的設備,或者網絡連接出了問題。\n\n如果您能在另一個會話中讀取該訊息,那麼您可以從中轉移訊息!前往設定 > 設備,並確保您的設備已相互驗證。當您下次打開房間且兩個會話都在前景時,密鑰將自動傳輸。\n\n不想在登出或切換設備時丟失密鑰?請確保您已在設定中啟用了聊天室備份。", + "@noKeyForThisMessage": {}, + "newSpaceDescription": "空間允許您整合您的聊天室並建立私人或公開社群。", + "@newSpaceDescription": {}, + "pleaseTryAgainLaterOrChooseDifferentServer": "請稍後再試,或選擇不同的伺服器。", + "@pleaseTryAgainLaterOrChooseDifferentServer": {}, + "signInWith": "使用 {provider} 登入", + "@signInWith": { + "type": "text", + "placeholders": { + "provider": {} + } + }, + "invalidInput": "無效的輸入!", + "@invalidInput": {}, + "verifyOtherUser": "🔐 驗證其他使用者", + "@verifyOtherUser": {}, + "verifyOtherUserDescription": "如果您驗證了另一個使用者,您可以確定您真正與誰通信。💪\n\n當您開始驗證時,您和另一個使用者將在應用程式中看到一個彈出視窗。在那裡,您將看到一系列的表情符號或數字,您需要相互比較。\n\n最好的方式是見面或開始視訊通話。👭", + "@verifyOtherUserDescription": {}, + "requestedKeyVerification": "{sender} 請求了密鑰驗證", + "@requestedKeyVerification": { + "type": "text", + "placeholders": { + "sender": {} + } + }, + "commandHint_ignore": "忽略已提供的 Matrix ID", + "@commandHint_ignore": {}, + "countChatsAndCountParticipants": "{chats} 個聊天室和 {participants} 位參與者", + "@countChatsAndCountParticipants": { + "type": "text", + "placeholders": { + "chats": {}, + "participants": {} + } + }, + "joinedChats": "已加入的聊天室", + "@joinedChats": {}, + "unread": "未讀", + "@unread": {}, + "space": "空間", + "@space": {}, + "spaces": "空間", + "@spaces": {}, + "start": "開始", + "@start": {}, + "openChat": "開啟聊天室", + "@openChat": {}, + "unreadChatsInApp": "{appname}:{unread} 未讀聊天室", + "@unreadChatsInApp": { + "type": "text", + "placeholders": { + "appname": {}, + "unread": {} + } } } From 87d3d0feed614c69e63856de6d0c4aef05a7b8b2 Mon Sep 17 00:00:00 2001 From: Krille Date: Wed, 17 Jul 2024 08:59:08 +0200 Subject: [PATCH 078/288] chore: Follow up list item click behavior --- lib/pages/chat_list/chat_list_item.dart | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/pages/chat_list/chat_list_item.dart b/lib/pages/chat_list/chat_list_item.dart index f704c41bf7..40ca6631a0 100644 --- a/lib/pages/chat_list/chat_list_item.dart +++ b/lib/pages/chat_list/chat_list_item.dart @@ -173,16 +173,19 @@ class ChatListItem extends StatelessWidget { Positioned( top: 0, right: 0, - child: AnimatedScale( - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - scale: listTileHovered ? 1.0 : 0.0, - child: Material( - color: backgroundColor, - borderRadius: BorderRadius.circular(16), - child: const Icon( - Icons.arrow_drop_down_circle_outlined, - size: 18, + child: GestureDetector( + onTap: () => onLongPress?.call(context), + child: AnimatedScale( + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + scale: listTileHovered ? 1.0 : 0.0, + child: Material( + color: backgroundColor, + borderRadius: BorderRadius.circular(16), + child: const Icon( + Icons.arrow_drop_down_circle_outlined, + size: 18, + ), ), ), ), From 86b1314c61f12f0ce102d98df7941d927de2653d Mon Sep 17 00:00:00 2001 From: Krille Date: Wed, 17 Jul 2024 10:27:56 +0200 Subject: [PATCH 079/288] refactor: Use cached network image for mxc image uris --- lib/pages/chat/events/html_message.dart | 1 - lib/widgets/avatar.dart | 16 ++-- lib/widgets/mxc_image.dart | 108 ++++++++---------------- pubspec.lock | 32 +++++++ pubspec.yaml | 1 + 5 files changed, 75 insertions(+), 83 deletions(-) diff --git a/lib/pages/chat/events/html_message.dart b/lib/pages/chat/events/html_message.dart index d6e6cbc44c..4d5bcdc8c3 100644 --- a/lib/pages/chat/events/html_message.dart +++ b/lib/pages/chat/events/html_message.dart @@ -280,7 +280,6 @@ class ImageExtension extends HtmlExtension { uri: mxcUrl, width: width ?? height ?? defaultDimension, height: height ?? width ?? defaultDimension, - cacheKey: mxcUrl.toString(), ), ), ); diff --git a/lib/widgets/avatar.dart b/lib/widgets/avatar.dart index 0c280a88be..cdb7b2e7aa 100644 --- a/lib/widgets/avatar.dart +++ b/lib/widgets/avatar.dart @@ -47,29 +47,32 @@ class Avatar extends StatelessWidget { final noPic = mxContent == null || mxContent.toString().isEmpty || mxContent.toString() == 'null'; - final textWidget = Center( + final textWidget = Container( + color: name?.lightColorAvatar, + alignment: Alignment.center, child: Text( fallbackLetters, style: TextStyle( - color: noPic ? Colors.white : null, + color: Colors.white, fontSize: (size / 2.5).roundToDouble(), ), ), ); final borderRadius = this.borderRadius ?? BorderRadius.circular(size / 2); final presenceUserId = this.presenceUserId; - final color = - noPic ? name?.lightColorAvatar : Theme.of(context).secondaryHeaderColor; final container = Stack( children: [ SizedBox( width: size, height: size, child: Material( - color: color, shape: RoundedRectangleBorder( borderRadius: borderRadius, - side: border ?? BorderSide.none, + side: border ?? + BorderSide( + color: Theme.of(context).dividerColor, + width: 1, + ), ), clipBehavior: Clip.hardEdge, child: noPic @@ -81,7 +84,6 @@ class Avatar extends StatelessWidget { width: size, height: size, placeholder: (_) => textWidget, - cacheKey: mxContent.toString(), ), ), ), diff --git a/lib/widgets/mxc_image.dart b/lib/widgets/mxc_image.dart index 9290156bf7..b12c9fc993 100644 --- a/lib/widgets/mxc_image.dart +++ b/lib/widgets/mxc_image.dart @@ -2,7 +2,7 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; -import 'package:http/http.dart' as http; +import 'package:cached_network_image/cached_network_image.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/themes.dart'; @@ -18,11 +18,8 @@ class MxcImage extends StatefulWidget { final bool isThumbnail; final bool animated; final Duration retryDuration; - final Duration animationDuration; - final Curve animationCurve; final ThumbnailMethod thumbnailMethod; final Widget Function(BuildContext context)? placeholder; - final String? cacheKey; const MxcImage({ this.uri, @@ -33,11 +30,8 @@ class MxcImage extends StatefulWidget { this.placeholder, this.isThumbnail = true, this.animated = false, - this.animationDuration = FluffyThemes.animationDuration, this.retryDuration = const Duration(seconds: 2), - this.animationCurve = FluffyThemes.animationCurve, this.thumbnailMethod = ThumbnailMethod.scale, - this.cacheKey, super.key, }); @@ -46,76 +40,11 @@ class MxcImage extends StatefulWidget { } class _MxcImageState extends State { - static final Map _imageDataCache = {}; - Uint8List? _imageDataNoCache; - Uint8List? get _imageData { - final cacheKey = widget.cacheKey; - return cacheKey == null ? _imageDataNoCache : _imageDataCache[cacheKey]; - } - - set _imageData(Uint8List? data) { - if (data == null) return; - final cacheKey = widget.cacheKey; - cacheKey == null - ? _imageDataNoCache = data - : _imageDataCache[cacheKey] = data; - } - - bool? _isCached; + Uint8List? _imageData; Future _load() async { - final client = Matrix.of(context).client; - final uri = widget.uri; final event = widget.event; - if (uri != null) { - final devicePixelRatio = MediaQuery.of(context).devicePixelRatio; - final width = widget.width; - final realWidth = width == null ? null : width * devicePixelRatio; - final height = widget.height; - final realHeight = height == null ? null : height * devicePixelRatio; - - final httpUri = widget.isThumbnail - ? uri.getThumbnail( - client, - width: realWidth, - height: realHeight, - animated: widget.animated, - method: widget.thumbnailMethod, - ) - : uri.getDownloadLink(client); - - final storeKey = widget.isThumbnail ? httpUri : uri; - - if (_isCached == null) { - final cachedData = await client.database?.getFile(storeKey); - if (cachedData != null) { - if (!mounted) return; - setState(() { - _imageData = cachedData; - _isCached = true; - }); - return; - } - _isCached = false; - } - - final response = await http.get(httpUri); - if (response.statusCode != 200) { - if (response.statusCode == 404) { - return; - } - throw Exception(); - } - final remoteData = response.bodyBytes; - - if (!mounted) return; - setState(() { - _imageData = remoteData; - }); - await client.database?.storeFile(storeKey, remoteData, 0); - } - if (event != null) { final data = await event.downloadAndDecryptAttachment( getThumbnail: widget.isThumbnail, @@ -131,7 +60,7 @@ class _MxcImageState extends State { } void _tryLoad(_) async { - if (_imageData != null) { + if (_imageData != null || widget.event == null) { return; } try { @@ -160,6 +89,36 @@ class _MxcImageState extends State { @override Widget build(BuildContext context) { + final uri = widget.uri; + + if (uri != null) { + final client = Matrix.of(context).client; + final devicePixelRatio = MediaQuery.of(context).devicePixelRatio; + final width = widget.width; + final realWidth = width == null ? null : width * devicePixelRatio; + final height = widget.height; + final realHeight = height == null ? null : height * devicePixelRatio; + + final httpUri = widget.isThumbnail + ? uri.getThumbnail( + client, + width: realWidth, + height: realHeight, + animated: widget.animated, + method: widget.thumbnailMethod, + ) + : uri.getDownloadLink(client); + + return CachedNetworkImage( + imageUrl: httpUri.toString(), + width: width, + height: height, + fit: widget.fit, + placeholder: (context, _) => placeholder(context), + errorWidget: (context, _, __) => placeholder(context), + ); + } + final data = _imageData; final hasData = data != null && data.isNotEmpty; @@ -177,7 +136,6 @@ class _MxcImageState extends State { filterQuality: widget.isThumbnail ? FilterQuality.low : FilterQuality.medium, errorBuilder: (context, __, ___) { - _isCached = false; _imageData = null; WidgetsBinding.instance.addPostFrameCallback(_tryLoad); return placeholder(context); diff --git a/pubspec.lock b/pubspec.lock index 0ff035c1b5..18e927b9f5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -129,6 +129,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + sha256: "28ea9690a8207179c319965c13cd8df184d5ee721ae2ce60f398ced1219cea1f" + url: "https://pub.dev" + source: hosted + version: "3.3.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "9e90e78ae72caa874a323d78fa6301b3fb8fa7ea76a8f96dc5b5bf79f283bf2f" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "205d6a9f1862de34b93184f22b9d2d94586b2f05c581d546695e3d8f6a805cd7" + url: "https://pub.dev" + source: hosted + version: "1.2.0" callkeep: dependency: "direct main" description: @@ -1270,6 +1294,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "45b40f99622f11901238e18d48f5f12ea36426d8eced9f4cbf58479c7aa2430d" + url: "https://pub.dev" + source: hosted + version: "2.0.0" olm: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 36a37e8c4a..4d9d028d2f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,6 +14,7 @@ dependencies: async: ^2.11.0 badges: ^3.1.2 blurhash_dart: ^1.2.1 + cached_network_image: ^3.3.1 callkeep: ^0.3.2 chewie: ^1.8.1 collection: ^1.18.0 From 998868dd566b3b479566963417748c6c2db1a332 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Wed, 17 Jul 2024 18:00:26 +0200 Subject: [PATCH 080/288] build: Try out flutter 3.22.2 in --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index a32ad5704c..046b1ad66e 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -66,7 +66,7 @@ parts: flutter-git: source: https://github.com/flutter/flutter.git - source-tag: 3.19.6 + source-tag: 3.22.2 source-depth: 1 plugin: nil override-build: | From 69fcb01988a58f030a1e5df5ded3c5f7fe93cab2 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Thu, 18 Jul 2024 07:33:55 +0200 Subject: [PATCH 081/288] build: Fix build snap --- pubspec.lock | 6 +++--- pubspec.yaml | 1 + snap/snapcraft.yaml | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 18e927b9f5..afec622d59 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2396,13 +2396,13 @@ packages: source: hosted version: "1.2.0" win32: - dependency: transitive + dependency: "direct overridden" description: name: win32 - sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4 + sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" url: "https://pub.dev" source: hosted - version: "5.5.1" + version: "5.5.0" win32_registry: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 4d9d028d2f..d89ef7d802 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -161,3 +161,4 @@ dependency_overrides: git: url: https://github.com/TheOneWithTheBraid/keyboard_shortcuts.git ref: null-safety + win32: 5.5.0 \ No newline at end of file diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 046b1ad66e..a32ad5704c 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -66,7 +66,7 @@ parts: flutter-git: source: https://github.com/flutter/flutter.git - source-tag: 3.22.2 + source-tag: 3.19.6 source-depth: 1 plugin: nil override-build: | From 659174b828fa5e2729456466e8b1b4e50f310075 Mon Sep 17 00:00:00 2001 From: Krille Date: Thu, 18 Jul 2024 15:26:59 +0200 Subject: [PATCH 082/288] build: Remove permissions for screensharing until it is fixed --- android/app/src/main/AndroidManifest.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index c0595780ae..149424e6ab 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -18,14 +18,12 @@ - - Date: Thu, 18 Jul 2024 16:45:10 +0200 Subject: [PATCH 083/288] chore: Follow up navrail --- lib/pages/chat_list/chat_list_item.dart | 9 +-------- lib/pages/chat_list/chat_list_view.dart | 13 +++++++++++-- lib/widgets/avatar.dart | 7 ++----- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/lib/pages/chat_list/chat_list_item.dart b/lib/pages/chat_list/chat_list_item.dart index 40ca6631a0..1ba515e29b 100644 --- a/lib/pages/chat_list/chat_list_item.dart +++ b/lib/pages/chat_list/chat_list_item.dart @@ -142,14 +142,7 @@ class ChatListItem extends StatelessWidget { right: 0, child: Avatar( border: space == null - ? room.isSpace - ? BorderSide( - width: 0, - color: Theme.of(context) - .colorScheme - .outline, - ) - : null + ? null : BorderSide( width: 2, color: backgroundColor ?? diff --git a/lib/pages/chat_list/chat_list_view.dart b/lib/pages/chat_list/chat_list_view.dart index 5bb6687a05..4391e83b0f 100644 --- a/lib/pages/chat_list/chat_list_view.dart +++ b/lib/pages/chat_list/chat_list_view.dart @@ -4,12 +4,14 @@ import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:go_router/go_router.dart'; import 'package:keyboard_shortcuts/keyboard_shortcuts.dart'; +import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat_list/chat_list.dart'; import 'package:fluffychat/pages/chat_list/navi_rail_item.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; +import 'package:fluffychat/utils/stream_extension.dart'; import 'package:fluffychat/widgets/avatar.dart'; import '../../widgets/matrix.dart'; import 'chat_list_body.dart'; @@ -21,6 +23,7 @@ class ChatListView extends StatelessWidget { @override Widget build(BuildContext context) { + final client = Matrix.of(context).client; return StreamBuilder( stream: Matrix.of(context).onShareContentChanged.stream, builder: (_, __) { @@ -44,8 +47,14 @@ class ChatListView extends StatelessWidget { children: [ if (FluffyThemes.isColumnMode(context) && controller.widget.displayNavigationRail) ...[ - Builder( - builder: (context) { + StreamBuilder( + key: ValueKey( + client.userID.toString(), + ), + stream: client.onSync.stream + .where((s) => s.hasRoomUpdate) + .rateLimit(const Duration(seconds: 1)), + builder: (context, _) { final allSpaces = Matrix.of(context) .client .rooms diff --git a/lib/widgets/avatar.dart b/lib/widgets/avatar.dart index cdb7b2e7aa..96aed1912f 100644 --- a/lib/widgets/avatar.dart +++ b/lib/widgets/avatar.dart @@ -66,13 +66,10 @@ class Avatar extends StatelessWidget { width: size, height: size, child: Material( + color: Theme.of(context).colorScheme.surfaceContainerLowest, shape: RoundedRectangleBorder( borderRadius: borderRadius, - side: border ?? - BorderSide( - color: Theme.of(context).dividerColor, - width: 1, - ), + side: border ?? BorderSide.none, ), clipBehavior: Clip.hardEdge, child: noPic From 754870e3fd0d60e2cfbac3e9546ceea74a29fb7d Mon Sep 17 00:00:00 2001 From: krille-chan Date: Thu, 18 Jul 2024 16:47:58 +0200 Subject: [PATCH 084/288] chore: Follow up pop space view --- lib/pages/chat_list/chat_list_view.dart | 7 +- lib/pages/chat_list/space_view.dart | 543 ++++++++++++------------ 2 files changed, 275 insertions(+), 275 deletions(-) diff --git a/lib/pages/chat_list/chat_list_view.dart b/lib/pages/chat_list/chat_list_view.dart index 4391e83b0f..510c6c46d7 100644 --- a/lib/pages/chat_list/chat_list_view.dart +++ b/lib/pages/chat_list/chat_list_view.dart @@ -30,9 +30,14 @@ class ChatListView extends StatelessWidget { final selectMode = controller.selectMode; return PopScope( canPop: controller.selectMode == SelectMode.normal && - !controller.isSearchMode, + !controller.isSearchMode && + controller.activeSpaceId == null, onPopInvoked: (pop) { if (pop) return; + if (controller.activeSpaceId != null) { + controller.clearActiveSpace(); + return; + } final selMode = controller.selectMode; if (controller.isSearchMode) { controller.cancelSearch(); diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index 3a7fdcad0f..3401aa1f1e 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -165,315 +165,310 @@ class _SpaceViewState extends State { final room = Matrix.of(context).client.getRoomById(widget.spaceId); final displayname = room?.getLocalizedDisplayname() ?? L10n.of(context)!.nothingFound; - return PopScope( - canPop: false, - onPopInvoked: (_) => widget.onBack(), - child: Scaffold( - appBar: AppBar( - leading: Center( - child: CloseButton( - onPressed: widget.onBack, - ), + return Scaffold( + appBar: AppBar( + leading: Center( + child: CloseButton( + onPressed: widget.onBack, ), - titleSpacing: 0, - title: ListTile( - contentPadding: EdgeInsets.zero, - leading: Avatar( - mxContent: room?.avatar, - name: displayname, - borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), - ), - title: Text( - displayname, - 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, - ), + ), + titleSpacing: 0, + title: ListTile( + contentPadding: EdgeInsets.zero, + leading: Avatar( + mxContent: room?.avatar, + name: displayname, + borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), + ), + title: Text( + displayname, + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - actions: [ - PopupMenuButton( - onSelected: _onSpaceAction, - itemBuilder: (context) => [ - PopupMenuItem( - value: SpaceActions.settings, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.settings_outlined), - const SizedBox(width: 12), - Text(L10n.of(context)!.settings), - ], + subtitle: room == null + ? null + : Text( + L10n.of(context)!.countChatsAndCountParticipants( + room.spaceChildren.length, + room.summary.mJoinedMemberCount ?? 1, ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - PopupMenuItem( - value: SpaceActions.invite, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.person_add_outlined), - const SizedBox(width: 12), - Text(L10n.of(context)!.invite), - ], - ), + ), + actions: [ + PopupMenuButton( + onSelected: _onSpaceAction, + itemBuilder: (context) => [ + PopupMenuItem( + value: SpaceActions.settings, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.settings_outlined), + const SizedBox(width: 12), + Text(L10n.of(context)!.settings), + ], ), - PopupMenuItem( - value: SpaceActions.leave, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.delete_outlined), - const SizedBox(width: 12), - Text(L10n.of(context)!.leave), - ], - ), + ), + PopupMenuItem( + value: SpaceActions.invite, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.person_add_outlined), + const SizedBox(width: 12), + Text(L10n.of(context)!.invite), + ], ), - ], - ), - ], - ), - body: room == null - ? const Center( - child: Icon( - Icons.search_outlined, - size: 80, + ), + PopupMenuItem( + value: SpaceActions.leave, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.delete_outlined), + const SizedBox(width: 12), + Text(L10n.of(context)!.leave), + ], ), - ) - : StreamBuilder( - stream: room.client.onSync.stream - .where((s) => s.hasRoomUpdate) - .rateLimit(const Duration(seconds: 1)), - builder: (context, snapshot) { - final childrenIds = room.spaceChildren - .map((c) => c.roomId) - .whereType() - .toSet(); + ), + ], + ), + ], + ), + body: room == null + ? const Center( + child: Icon( + Icons.search_outlined, + size: 80, + ), + ) + : StreamBuilder( + stream: room.client.onSync.stream + .where((s) => s.hasRoomUpdate) + .rateLimit(const Duration(seconds: 1)), + builder: (context, snapshot) { + final childrenIds = room.spaceChildren + .map((c) => c.roomId) + .whereType() + .toSet(); - final joinedRooms = room.client.rooms - .where((room) => childrenIds.remove(room.id)) - .toList(); + final joinedRooms = room.client.rooms + .where((room) => childrenIds.remove(room.id)) + .toList(); - final joinedParents = room.spaceParents - .map((parent) { - final roomId = parent.roomId; - if (roomId == null) return null; - return room.client.getRoomById(roomId); - }) - .whereType() - .toList(); - final filter = _filterController.text.trim().toLowerCase(); - return CustomScrollView( - slivers: [ - SliverAppBar( - floating: true, - toolbarHeight: 72, - scrolledUnderElevation: 0, - backgroundColor: Colors.transparent, - automaticallyImplyLeading: false, - title: TextField( - controller: _filterController, - onChanged: (_) => setState(() {}), - textInputAction: TextInputAction.search, - decoration: InputDecoration( - fillColor: Theme.of(context) + final joinedParents = room.spaceParents + .map((parent) { + final roomId = parent.roomId; + if (roomId == null) return null; + return room.client.getRoomById(roomId); + }) + .whereType() + .toList(); + final filter = _filterController.text.trim().toLowerCase(); + return CustomScrollView( + slivers: [ + SliverAppBar( + floating: true, + toolbarHeight: 72, + scrolledUnderElevation: 0, + backgroundColor: Colors.transparent, + automaticallyImplyLeading: false, + title: TextField( + controller: _filterController, + onChanged: (_) => setState(() {}), + textInputAction: TextInputAction.search, + decoration: InputDecoration( + fillColor: + Theme.of(context).colorScheme.secondaryContainer, + border: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(99), + ), + contentPadding: EdgeInsets.zero, + hintText: L10n.of(context)!.search, + hintStyle: TextStyle( + color: Theme.of(context) .colorScheme - .secondaryContainer, - border: OutlineInputBorder( - borderSide: BorderSide.none, - borderRadius: BorderRadius.circular(99), - ), - contentPadding: EdgeInsets.zero, - hintText: L10n.of(context)!.search, - hintStyle: TextStyle( + .onPrimaryContainer, + fontWeight: FontWeight.normal, + ), + floatingLabelBehavior: FloatingLabelBehavior.never, + prefixIcon: IconButton( + onPressed: () {}, + icon: Icon( + Icons.search_outlined, color: Theme.of(context) .colorScheme .onPrimaryContainer, - fontWeight: FontWeight.normal, - ), - floatingLabelBehavior: FloatingLabelBehavior.never, - prefixIcon: IconButton( - onPressed: () {}, - icon: Icon( - Icons.search_outlined, - color: Theme.of(context) - .colorScheme - .onPrimaryContainer, - ), ), ), ), ), - SliverList.builder( - itemCount: joinedParents.length, - itemBuilder: (context, i) { - final displayname = - joinedParents[i].getLocalizedDisplayname(); - return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 1, - ), - child: Material( - borderRadius: - BorderRadius.circular(AppConfig.borderRadius), - clipBehavior: Clip.hardEdge, - child: ListTile( - minVerticalPadding: 0, - leading: Icon( - Icons.adaptive.arrow_back_outlined, - size: 16, - ), - title: Row( - children: [ - Avatar( - mxContent: joinedParents[i].avatar, - name: displayname, - size: Avatar.defaultSize / 2, - borderRadius: BorderRadius.circular( - AppConfig.borderRadius / 4, - ), + ), + SliverList.builder( + itemCount: joinedParents.length, + itemBuilder: (context, i) { + final displayname = + joinedParents[i].getLocalizedDisplayname(); + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 1, + ), + child: Material( + borderRadius: + BorderRadius.circular(AppConfig.borderRadius), + clipBehavior: Clip.hardEdge, + child: ListTile( + minVerticalPadding: 0, + leading: Icon( + Icons.adaptive.arrow_back_outlined, + size: 16, + ), + title: Row( + children: [ + Avatar( + mxContent: joinedParents[i].avatar, + name: displayname, + size: Avatar.defaultSize / 2, + borderRadius: BorderRadius.circular( + AppConfig.borderRadius / 4, ), - const SizedBox(width: 8), - Expanded(child: Text(displayname)), - ], - ), - onTap: () => - widget.toParentSpace(joinedParents[i].id), + ), + const SizedBox(width: 8), + Expanded(child: Text(displayname)), + ], ), + onTap: () => + widget.toParentSpace(joinedParents[i].id), ), + ), + ); + }, + ), + SliverList.builder( + itemCount: joinedRooms.length + 1, + itemBuilder: (context, i) { + if (i == 0) { + return SearchTitle( + title: L10n.of(context)!.joinedChats, + icon: const Icon(Icons.chat_outlined), ); - }, - ), - SliverList.builder( - itemCount: joinedRooms.length + 1, - itemBuilder: (context, i) { - if (i == 0) { - return SearchTitle( - title: L10n.of(context)!.joinedChats, - icon: const Icon(Icons.chat_outlined), - ); - } - i--; - final room = joinedRooms[i]; - return ChatListItem( + } + i--; + final room = joinedRooms[i]; + return ChatListItem( + room, + filter: filter, + onTap: () => widget.onChatTab(room), + onLongPress: (context) => widget.onChatContext( room, - filter: filter, - onTap: () => widget.onChatTab(room), - onLongPress: (context) => widget.onChatContext( - room, - context, - ), - activeChat: widget.activeChat == room.id, + context, + ), + activeChat: widget.activeChat == room.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), ); - }, - ), - 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), - ), - ), - ); - } + } + i--; + if (i == _discoveredChildren.length) { + if (_noMoreRooms) { return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12.0, - vertical: 2.0, - ), - child: TextButton( - onPressed: _isLoading ? null : _loadHierarchy, - child: _isLoading - ? LinearProgressIndicator( - borderRadius: BorderRadius.circular( - AppConfig.borderRadius, - ), - ) - : Text(L10n.of(context)!.loadMore), + padding: const EdgeInsets.all(12.0), + child: Center( + child: Text( + L10n.of(context)!.noMoreChatsFound, + style: const TextStyle(fontSize: 13), + ), ), ); } - final item = _discoveredChildren[i]; - final displayname = item.name ?? - item.canonicalAlias ?? - L10n.of(context)!.emptyChat; - if (!displayname.toLowerCase().contains(filter)) { - return const SizedBox.shrink(); - } return Padding( padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 1, + horizontal: 12.0, + vertical: 2.0, ), - child: Material( - borderRadius: - BorderRadius.circular(AppConfig.borderRadius), - clipBehavior: Clip.hardEdge, - child: ListTile( - onTap: () => _joinChildRoom(item), - leading: Avatar( - mxContent: item.avatarUrl, - name: displayname, - borderRadius: item.roomType == 'm.space' - ? BorderRadius.circular( - AppConfig.borderRadius / 2, - ) - : null, - ), - title: Row( - children: [ - Expanded( - child: Text( - displayname, - maxLines: 1, - overflow: TextOverflow.ellipsis, + child: TextButton( + onPressed: _isLoading ? null : _loadHierarchy, + child: _isLoading + ? LinearProgressIndicator( + borderRadius: BorderRadius.circular( + AppConfig.borderRadius, ), + ) + : Text(L10n.of(context)!.loadMore), + ), + ); + } + final item = _discoveredChildren[i]; + final displayname = item.name ?? + item.canonicalAlias ?? + L10n.of(context)!.emptyChat; + if (!displayname.toLowerCase().contains(filter)) { + return const SizedBox.shrink(); + } + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 1, + ), + child: Material( + borderRadius: + BorderRadius.circular(AppConfig.borderRadius), + clipBehavior: Clip.hardEdge, + child: ListTile( + onTap: () => _joinChildRoom(item), + leading: Avatar( + mxContent: item.avatarUrl, + name: displayname, + borderRadius: item.roomType == 'm.space' + ? BorderRadius.circular( + AppConfig.borderRadius / 2, + ) + : null, + ), + title: Row( + children: [ + Expanded( + child: Text( + displayname, + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - const SizedBox(width: 8), - const Icon( - Icons.add_circle_outline_outlined, + ), + const SizedBox(width: 8), + const Icon( + Icons.add_circle_outline_outlined, + ), + ], + ), + subtitle: Text( + item.topic ?? + L10n.of(context)!.countParticipants( + item.numJoinedMembers, ), - ], - ), - subtitle: Text( - item.topic ?? - L10n.of(context)!.countParticipants( - item.numJoinedMembers, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), ), - ); - }, - ), - ], - ); - }, - ), - ), + ), + ); + }, + ), + ], + ); + }, + ), ); } } From b8345e2ca6d9ceef2136cb6ccaf4f8384801936b Mon Sep 17 00:00:00 2001 From: krille-chan Date: Thu, 18 Jul 2024 17:41:45 +0200 Subject: [PATCH 085/288] chore: Follow up avatars --- lib/widgets/avatar.dart | 3 +- lib/widgets/mxc_image.dart | 108 +++++++++++++++++++++++++------------ pubspec.lock | 32 ----------- pubspec.yaml | 1 - 4 files changed, 77 insertions(+), 67 deletions(-) diff --git a/lib/widgets/avatar.dart b/lib/widgets/avatar.dart index 96aed1912f..28524d917f 100644 --- a/lib/widgets/avatar.dart +++ b/lib/widgets/avatar.dart @@ -75,7 +75,8 @@ class Avatar extends StatelessWidget { child: noPic ? textWidget : MxcImage( - key: Key(mxContent.toString()), + key: ValueKey(mxContent.toString()), + cacheKey: '${mxContent}_$size', uri: mxContent, fit: BoxFit.cover, width: size, diff --git a/lib/widgets/mxc_image.dart b/lib/widgets/mxc_image.dart index b12c9fc993..34c53cb577 100644 --- a/lib/widgets/mxc_image.dart +++ b/lib/widgets/mxc_image.dart @@ -2,7 +2,7 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; -import 'package:cached_network_image/cached_network_image.dart'; +import 'package:http/http.dart' as http; import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/themes.dart'; @@ -18,8 +18,11 @@ class MxcImage extends StatefulWidget { final bool isThumbnail; final bool animated; final Duration retryDuration; + final Duration animationDuration; + final Curve animationCurve; final ThumbnailMethod thumbnailMethod; final Widget Function(BuildContext context)? placeholder; + final String? cacheKey; const MxcImage({ this.uri, @@ -30,8 +33,11 @@ class MxcImage extends StatefulWidget { this.placeholder, this.isThumbnail = true, this.animated = false, + this.animationDuration = FluffyThemes.animationDuration, this.retryDuration = const Duration(seconds: 2), + this.animationCurve = FluffyThemes.animationCurve, this.thumbnailMethod = ThumbnailMethod.scale, + this.cacheKey, super.key, }); @@ -40,11 +46,76 @@ class MxcImage extends StatefulWidget { } class _MxcImageState extends State { - Uint8List? _imageData; + static final Map _imageDataCache = {}; + Uint8List? _imageDataNoCache; + + Uint8List? get _imageData => widget.cacheKey == null + ? _imageDataNoCache + : _imageDataCache[widget.cacheKey]; + + set _imageData(Uint8List? data) { + if (data == null) return; + final cacheKey = widget.cacheKey; + cacheKey == null + ? _imageDataNoCache = data + : _imageDataCache[cacheKey] = data; + } + + bool? _isCached; Future _load() async { + final client = Matrix.of(context).client; + final uri = widget.uri; final event = widget.event; + if (uri != null) { + final devicePixelRatio = MediaQuery.of(context).devicePixelRatio; + final width = widget.width; + final realWidth = width == null ? null : width * devicePixelRatio; + final height = widget.height; + final realHeight = height == null ? null : height * devicePixelRatio; + + final httpUri = widget.isThumbnail + ? uri.getThumbnail( + client, + width: realWidth, + height: realHeight, + animated: widget.animated, + method: widget.thumbnailMethod, + ) + : uri.getDownloadLink(client); + + final storeKey = widget.isThumbnail ? httpUri : uri; + + if (_isCached == null) { + final cachedData = await client.database?.getFile(storeKey); + if (cachedData != null) { + if (!mounted) return; + setState(() { + _imageData = cachedData; + _isCached = true; + }); + return; + } + _isCached = false; + } + + final response = await http.get(httpUri); + if (response.statusCode != 200) { + if (response.statusCode == 404) { + return; + } + throw Exception(); + } + final remoteData = response.bodyBytes; + + if (!mounted) return; + setState(() { + _imageData = remoteData; + }); + await client.database?.storeFile(storeKey, remoteData, 0); + } + if (event != null) { final data = await event.downloadAndDecryptAttachment( getThumbnail: widget.isThumbnail, @@ -60,7 +131,7 @@ class _MxcImageState extends State { } void _tryLoad(_) async { - if (_imageData != null || widget.event == null) { + if (_imageData != null) { return; } try { @@ -89,36 +160,6 @@ class _MxcImageState extends State { @override Widget build(BuildContext context) { - final uri = widget.uri; - - if (uri != null) { - final client = Matrix.of(context).client; - final devicePixelRatio = MediaQuery.of(context).devicePixelRatio; - final width = widget.width; - final realWidth = width == null ? null : width * devicePixelRatio; - final height = widget.height; - final realHeight = height == null ? null : height * devicePixelRatio; - - final httpUri = widget.isThumbnail - ? uri.getThumbnail( - client, - width: realWidth, - height: realHeight, - animated: widget.animated, - method: widget.thumbnailMethod, - ) - : uri.getDownloadLink(client); - - return CachedNetworkImage( - imageUrl: httpUri.toString(), - width: width, - height: height, - fit: widget.fit, - placeholder: (context, _) => placeholder(context), - errorWidget: (context, _, __) => placeholder(context), - ); - } - final data = _imageData; final hasData = data != null && data.isNotEmpty; @@ -136,6 +177,7 @@ class _MxcImageState extends State { filterQuality: widget.isThumbnail ? FilterQuality.low : FilterQuality.medium, errorBuilder: (context, __, ___) { + _isCached = false; _imageData = null; WidgetsBinding.instance.addPostFrameCallback(_tryLoad); return placeholder(context); diff --git a/pubspec.lock b/pubspec.lock index afec622d59..7e3909e546 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -129,30 +129,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" - cached_network_image: - dependency: "direct main" - description: - name: cached_network_image - sha256: "28ea9690a8207179c319965c13cd8df184d5ee721ae2ce60f398ced1219cea1f" - url: "https://pub.dev" - source: hosted - version: "3.3.1" - cached_network_image_platform_interface: - dependency: transitive - description: - name: cached_network_image_platform_interface - sha256: "9e90e78ae72caa874a323d78fa6301b3fb8fa7ea76a8f96dc5b5bf79f283bf2f" - url: "https://pub.dev" - source: hosted - version: "4.0.0" - cached_network_image_web: - dependency: transitive - description: - name: cached_network_image_web - sha256: "205d6a9f1862de34b93184f22b9d2d94586b2f05c581d546695e3d8f6a805cd7" - url: "https://pub.dev" - source: hosted - version: "1.2.0" callkeep: dependency: "direct main" description: @@ -1294,14 +1270,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" - octo_image: - dependency: transitive - description: - name: octo_image - sha256: "45b40f99622f11901238e18d48f5f12ea36426d8eced9f4cbf58479c7aa2430d" - url: "https://pub.dev" - source: hosted - version: "2.0.0" olm: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d89ef7d802..a013aeea32 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,7 +14,6 @@ dependencies: async: ^2.11.0 badges: ^3.1.2 blurhash_dart: ^1.2.1 - cached_network_image: ^3.3.1 callkeep: ^0.3.2 chewie: ^1.8.1 collection: ^1.18.0 From cf59a43511ee7e7f8fa0a704ee5beccffaae108b Mon Sep 17 00:00:00 2001 From: Krille Date: Fri, 19 Jul 2024 14:50:17 +0200 Subject: [PATCH 086/288] chore: Follow up avatar default image --- lib/widgets/avatar.dart | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/widgets/avatar.dart b/lib/widgets/avatar.dart index 28524d917f..c95444c746 100644 --- a/lib/widgets/avatar.dart +++ b/lib/widgets/avatar.dart @@ -47,8 +47,9 @@ class Avatar extends StatelessWidget { final noPic = mxContent == null || mxContent.toString().isEmpty || mxContent.toString() == 'null'; + final textColor = name?.lightColorAvatar; final textWidget = Container( - color: name?.lightColorAvatar, + color: textColor, alignment: Alignment.center, child: Text( fallbackLetters, @@ -81,7 +82,13 @@ class Avatar extends StatelessWidget { fit: BoxFit.cover, width: size, height: size, - placeholder: (_) => textWidget, + placeholder: (_) => Center( + child: Icon( + Icons.person_2, + color: textColor, + size: size / 1.5, + ), + ), ), ), ), From 152fcc0d95bcba4acc8c3920fba358016cd547ee Mon Sep 17 00:00:00 2001 From: Krille Date: Fri, 19 Jul 2024 15:01:51 +0200 Subject: [PATCH 087/288] chore: Improved create group and space design --- lib/pages/new_group/new_group.dart | 7 --- lib/pages/new_group/new_group_view.dart | 61 +++++++------------ lib/pages/new_space/new_space_view.dart | 79 +++++++++---------------- 3 files changed, 50 insertions(+), 97 deletions(-) diff --git a/lib/pages/new_group/new_group.dart b/lib/pages/new_group/new_group.dart index 584a3f8853..83a8b478e0 100644 --- a/lib/pages/new_group/new_group.dart +++ b/lib/pages/new_group/new_group.dart @@ -19,8 +19,6 @@ class NewGroup extends StatefulWidget { class NewGroupController extends State { TextEditingController nameController = TextEditingController(); - TextEditingController topicController = TextEditingController(); - bool publicGroup = false; bool groupCanBeFound = true; @@ -71,11 +69,6 @@ class NewGroupController extends State { : sdk.CreateRoomPreset.privateChat, groupName: nameController.text.isNotEmpty ? nameController.text : null, initialState: [ - if (topicController.text.isNotEmpty) - sdk.StateEvent( - type: sdk.EventTypes.RoomTopic, - content: {'topic': topicController.text}, - ), if (avatar != null) sdk.StateEvent( type: sdk.EventTypes.RoomAvatar, diff --git a/lib/pages/new_group/new_group_view.dart b/lib/pages/new_group/new_group_view.dart index a00ba37545..a83497d7b9 100644 --- a/lib/pages/new_group/new_group_view.dart +++ b/lib/pages/new_group/new_group_view.dart @@ -31,54 +31,35 @@ class NewGroupView extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ const SizedBox(height: 16), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Row( - children: [ - InkWell( - borderRadius: BorderRadius.circular(90), - onTap: controller.loading ? null : controller.selectPhoto, - child: CircleAvatar( - radius: Avatar.defaultSize / 2, - child: avatar == null - ? const Icon(Icons.camera_alt_outlined) - : ClipRRect( - borderRadius: BorderRadius.circular(90), - child: Image.memory( - avatar, - width: Avatar.defaultSize, - height: Avatar.defaultSize, - fit: BoxFit.cover, - ), - ), - ), - ), - const SizedBox(width: 16), - Expanded( - child: TextField( - controller: controller.nameController, - autocorrect: false, - readOnly: controller.loading, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.people_outlined), - hintText: L10n.of(context)!.groupName, + InkWell( + borderRadius: BorderRadius.circular(90), + onTap: controller.loading ? null : controller.selectPhoto, + child: CircleAvatar( + radius: Avatar.defaultSize, + child: avatar == null + ? const Icon(Icons.add_a_photo_outlined) + : ClipRRect( + borderRadius: BorderRadius.circular(90), + child: Image.memory( + avatar, + width: Avatar.defaultSize, + height: Avatar.defaultSize, + fit: BoxFit.cover, + ), ), - ), - ), - ], ), ), - const SizedBox(height: 16), + const SizedBox(height: 32), Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: TextField( - controller: controller.topicController, - minLines: 4, - maxLines: 4, - maxLength: 255, + autofocus: true, + controller: controller.nameController, + autocorrect: false, readOnly: controller.loading, decoration: InputDecoration( - hintText: L10n.of(context)!.addChatDescription, + prefixIcon: const Icon(Icons.people_outlined), + hintText: L10n.of(context)!.groupName, ), ), ), diff --git a/lib/pages/new_space/new_space_view.dart b/lib/pages/new_space/new_space_view.dart index eb464855e1..e90c7a84e6 100644 --- a/lib/pages/new_space/new_space_view.dart +++ b/lib/pages/new_space/new_space_view.dart @@ -22,65 +22,37 @@ class NewSpaceView extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - ListTile( - trailing: const Padding( - padding: EdgeInsets.symmetric(horizontal: 16.0), - child: Icon(Icons.info_outlined), - ), - subtitle: Text(L10n.of(context)!.newSpaceDescription), - ), const SizedBox(height: 16), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - InkWell( - borderRadius: BorderRadius.circular(90), - onTap: controller.loading ? null : controller.selectPhoto, - child: CircleAvatar( - radius: Avatar.defaultSize / 2, - child: avatar == null - ? const Icon(Icons.camera_alt_outlined) - : ClipRRect( - borderRadius: BorderRadius.circular(90), - child: Image.memory( - avatar, - width: Avatar.defaultSize, - height: Avatar.defaultSize, - fit: BoxFit.cover, - ), - ), - ), - ), - const SizedBox(width: 16), - Expanded( - child: TextField( - controller: controller.nameController, - autocorrect: false, - readOnly: controller.loading, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.people_outlined), - hintText: L10n.of(context)!.spaceName, - errorText: controller.nameError, + InkWell( + borderRadius: BorderRadius.circular(90), + onTap: controller.loading ? null : controller.selectPhoto, + child: CircleAvatar( + radius: Avatar.defaultSize, + child: avatar == null + ? const Icon(Icons.add_a_photo_outlined) + : ClipRRect( + borderRadius: BorderRadius.circular(90), + child: Image.memory( + avatar, + width: Avatar.defaultSize, + height: Avatar.defaultSize, + fit: BoxFit.cover, + ), ), - ), - ), - ], ), ), - const SizedBox(height: 16), + const SizedBox(height: 32), Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: TextField( - controller: controller.topicController, - minLines: 4, - maxLines: 4, - maxLength: 255, + autofocus: true, + controller: controller.nameController, + autocorrect: false, readOnly: controller.loading, decoration: InputDecoration( - hintText: L10n.of(context)!.addChatDescription, - errorText: controller.topicError, + prefixIcon: const Icon(Icons.people_outlined), + hintText: L10n.of(context)!.spaceName, + errorText: controller.nameError, ), ), ), @@ -90,6 +62,13 @@ class NewSpaceView extends StatelessWidget { value: controller.publicGroup, onChanged: controller.setPublicGroup, ), + ListTile( + trailing: const Padding( + padding: EdgeInsets.symmetric(horizontal: 16.0), + child: Icon(Icons.info_outlined), + ), + subtitle: Text(L10n.of(context)!.newSpaceDescription), + ), Padding( padding: const EdgeInsets.all(16.0), child: SizedBox( From 7fef3a69d80f0bd27ad32d5434bdb44bcbf1e10d Mon Sep 17 00:00:00 2001 From: Krille Date: Fri, 19 Jul 2024 15:30:04 +0200 Subject: [PATCH 088/288] chore: Follow up avatars --- lib/widgets/avatar.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/widgets/avatar.dart b/lib/widgets/avatar.dart index c95444c746..a9d12f6a57 100644 --- a/lib/widgets/avatar.dart +++ b/lib/widgets/avatar.dart @@ -55,7 +55,8 @@ class Avatar extends StatelessWidget { fallbackLetters, style: TextStyle( color: Colors.white, - fontSize: (size / 2.5).roundToDouble(), + fontWeight: FontWeight.bold, + fontSize: (size / 3).roundToDouble(), ), ), ); @@ -85,7 +86,7 @@ class Avatar extends StatelessWidget { placeholder: (_) => Center( child: Icon( Icons.person_2, - color: textColor, + color: Theme.of(context).colorScheme.tertiary, size: size / 1.5, ), ), From e178ab44166b7ef17ffa1728c39dc49fe565b02d Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sat, 20 Jul 2024 08:01:06 +0200 Subject: [PATCH 089/288] chore: Follow up avatar background --- lib/widgets/avatar.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/widgets/avatar.dart b/lib/widgets/avatar.dart index a9d12f6a57..c3e24fe169 100644 --- a/lib/widgets/avatar.dart +++ b/lib/widgets/avatar.dart @@ -68,7 +68,9 @@ class Avatar extends StatelessWidget { width: size, height: size, child: Material( - color: Theme.of(context).colorScheme.surfaceContainerLowest, + color: Theme.of(context).brightness == Brightness.light + ? Colors.white + : Colors.black, shape: RoundedRectangleBorder( borderRadius: borderRadius, side: border ?? BorderSide.none, From 0760acaa40a4e30d85f8c0eed35c6df7a7e7e660 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Mon, 22 Jul 2024 19:42:27 +0200 Subject: [PATCH 090/288] chore: Bring back separate chat types --- lib/config/app_config.dart | 1 + lib/config/setting_keys.dart | 1 + lib/pages/chat_list/chat_list.dart | 7 ++++++- lib/pages/chat_list/chat_list_body.dart | 7 +++++-- lib/pages/settings_style/settings_style_view.dart | 6 ++++++ lib/widgets/matrix.dart | 4 ++++ 6 files changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index 6ee20d3372..841d810ec6 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -44,6 +44,7 @@ abstract class AppConfig { static bool hideRedactedEvents = false; static bool hideUnknownEvents = true; static bool hideUnimportantStateEvents = true; + static bool separateChatTypes = false; static bool autoplayImages = true; static bool sendTypingNotifications = true; static bool sendPublicReadReceipts = true; diff --git a/lib/config/setting_keys.dart b/lib/config/setting_keys.dart index 5b795b08e0..7c0e50df81 100644 --- a/lib/config/setting_keys.dart +++ b/lib/config/setting_keys.dart @@ -4,6 +4,7 @@ abstract class SettingKeys { static const String hideUnknownEvents = 'chat.fluffy.hideUnknownEvents'; static const String hideUnimportantStateEvents = 'chat.fluffy.hideUnimportantStateEvents'; + static const String separateChatTypes = 'chat.fluffy.separateChatTypes'; static const String sentry = 'sentry'; static const String theme = 'theme'; static const String amoledEnabled = 'amoled_enabled'; diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 50237a569c..13ce540534 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -50,8 +50,9 @@ enum PopupMenuAction { enum ActiveFilter { allChats, - unread, + messages, groups, + unread, spaces, } @@ -60,6 +61,8 @@ extension LocalizedActiveFilter on ActiveFilter { switch (this) { case ActiveFilter.allChats: return L10n.of(context)!.all; + case ActiveFilter.messages: + return L10n.of(context)!.messages; case ActiveFilter.unread: return L10n.of(context)!.unread; case ActiveFilter.groups: @@ -321,6 +324,8 @@ class ChatListController extends State switch (activeFilter) { case ActiveFilter.allChats: return (room) => true; + case ActiveFilter.messages: + return (room) => !room.isSpace && room.isDirectChat; case ActiveFilter.groups: return (room) => !room.isSpace && !room.isDirectChat; case ActiveFilter.unread: diff --git a/lib/pages/chat_list/chat_list_body.dart b/lib/pages/chat_list/chat_list_body.dart index e596440b37..d78a88e34a 100644 --- a/lib/pages/chat_list/chat_list_body.dart +++ b/lib/pages/chat_list/chat_list_body.dart @@ -165,9 +165,12 @@ class ChatListViewBody extends StatelessWidget { shrinkWrap: true, scrollDirection: Axis.horizontal, children: [ - ActiveFilter.allChats, - ActiveFilter.unread, + if (AppConfig.separateChatTypes) + ActiveFilter.messages + else + ActiveFilter.allChats, ActiveFilter.groups, + ActiveFilter.unread, if (spaceDelegateCandidates.isNotEmpty && !controller.widget.displayNavigationRail) ActiveFilter.spaces, diff --git a/lib/pages/settings_style/settings_style_view.dart b/lib/pages/settings_style/settings_style_view.dart index 0b505d5978..86f48fe879 100644 --- a/lib/pages/settings_style/settings_style_view.dart +++ b/lib/pages/settings_style/settings_style_view.dart @@ -185,6 +185,12 @@ class SettingsStyleView extends StatelessWidget { storeKey: SettingKeys.showPresences, defaultValue: AppConfig.showPresences, ), + SettingsSwitchListTile.adaptive( + title: L10n.of(context)!.separateChatTypes, + onChanged: (b) => AppConfig.separateChatTypes = b, + storeKey: SettingKeys.separateChatTypes, + defaultValue: AppConfig.separateChatTypes, + ), Divider( height: 1, color: Theme.of(context).dividerColor, diff --git a/lib/widgets/matrix.dart b/lib/widgets/matrix.dart index 581e3ccff4..4de23a2f59 100644 --- a/lib/widgets/matrix.dart +++ b/lib/widgets/matrix.dart @@ -433,6 +433,10 @@ class MatrixState extends State with WidgetsBindingObserver { store.getBool(SettingKeys.hideUnimportantStateEvents) ?? AppConfig.hideUnimportantStateEvents; + AppConfig.separateChatTypes = + store.getBool(SettingKeys.separateChatTypes) ?? + AppConfig.separateChatTypes; + AppConfig.autoplayImages = store.getBool(SettingKeys.autoplayImages) ?? AppConfig.autoplayImages; From a500a91b6bba6ec5ae411de473b76c75829baa2a Mon Sep 17 00:00:00 2001 From: krille-chan Date: Mon, 22 Jul 2024 20:06:07 +0200 Subject: [PATCH 091/288] chore: Follow up add chat or subspace UX --- lib/pages/chat_list/chat_list.dart | 107 ++---------------- lib/pages/chat_list/chat_list_view.dart | 6 +- lib/pages/chat_list/space_view.dart | 141 ++++++++++++++++++++++-- 3 files changed, 145 insertions(+), 109 deletions(-) diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 13ce540534..befba299a5 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -105,107 +105,18 @@ class ChatListController extends State String? _activeSpaceId; String? get activeSpaceId => _activeSpaceId; - void setActiveSpace(String spaceId) => setState(() { - _activeSpaceId = spaceId; - }); + void setActiveSpace(String spaceId) async { + await Matrix.of(context).client.getRoomById(spaceId)!.postLoad(); + + setState(() { + _activeSpaceId = spaceId; + }); + } + void clearActiveSpace() => setState(() { _activeSpaceId = null; }); - void addChatAction() async { - if (activeSpaceId == null) { - context.go('/rooms/newprivatechat'); - return; - } - - final roomType = await showConfirmationDialog( - context: context, - title: L10n.of(context)!.addChatOrSubSpace, - actions: [ - AlertDialogAction( - key: AddRoomType.subspace, - label: L10n.of(context)!.createNewSpace, - ), - AlertDialogAction( - key: AddRoomType.chat, - label: L10n.of(context)!.createGroup, - ), - ], - ); - if (roomType == null) return; - - final names = await showTextInputDialog( - context: context, - title: roomType == AddRoomType.subspace - ? L10n.of(context)!.createNewSpace - : L10n.of(context)!.createGroup, - textFields: [ - DialogTextField( - hintText: roomType == AddRoomType.subspace - ? L10n.of(context)!.spaceName - : L10n.of(context)!.groupName, - minLines: 1, - maxLines: 1, - maxLength: 64, - validator: (text) { - if (text == null || text.isEmpty) { - return L10n.of(context)!.pleaseChoose; - } - return null; - }, - ), - DialogTextField( - hintText: L10n.of(context)!.chatDescription, - minLines: 4, - maxLines: 8, - maxLength: 255, - ), - ], - okLabel: L10n.of(context)!.create, - cancelLabel: L10n.of(context)!.cancel, - ); - if (names == null) return; - final client = Matrix.of(context).client; - final result = await showFutureLoadingDialog( - context: context, - future: () async { - late final String roomId; - final activeSpace = client.getRoomById(activeSpaceId!)!; - await activeSpace.postLoad(); - - if (roomType == AddRoomType.subspace) { - roomId = await client.createSpace( - name: names.first, - topic: names.last.isEmpty ? null : names.last, - visibility: activeSpace.joinRules == JoinRules.public - ? sdk.Visibility.public - : sdk.Visibility.private, - ); - } else { - roomId = await client.createGroupChat( - groupName: names.first, - preset: activeSpace.joinRules == JoinRules.public - ? CreateRoomPreset.publicChat - : CreateRoomPreset.privateChat, - visibility: activeSpace.joinRules == JoinRules.public - ? sdk.Visibility.public - : sdk.Visibility.private, - initialState: names.length > 1 && names.last.isNotEmpty - ? [ - sdk.StateEvent( - type: sdk.EventTypes.RoomTopic, - content: {'topic': names.last}, - ), - ] - : null, - ); - } - await activeSpace.setSpaceChild(roomId); - }, - ); - if (result.error != null) return; - } - void onChatTap(Room room) async { if (room.membership == Membership.invite) { final inviterId = @@ -1004,8 +915,6 @@ enum InviteActions { block, } -enum AddRoomType { chat, subspace } - enum ChatContextAction { open, goToSpace, diff --git a/lib/pages/chat_list/chat_list_view.dart b/lib/pages/chat_list/chat_list_view.dart index 510c6c46d7..a106e5dd7a 100644 --- a/lib/pages/chat_list/chat_list_view.dart +++ b/lib/pages/chat_list/chat_list_view.dart @@ -146,9 +146,11 @@ class ChatListView extends StatelessWidget { onKeysPressed: () => context.go('/rooms/newprivatechat'), helpLabel: L10n.of(context)!.newChat, child: selectMode == SelectMode.normal && - !controller.isSearchMode + !controller.isSearchMode && + controller.activeSpaceId == null ? FloatingActionButton.extended( - onPressed: controller.addChatAction, + onPressed: () => + context.go('/rooms/newprivatechat'), icon: const Icon(Icons.add_outlined), label: Text( L10n.of(context)!.chat, diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index 3401aa1f1e..5ad99faa88 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -5,6 +5,7 @@ import 'package:collection/collection.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:go_router/go_router.dart'; +import 'package:matrix/matrix.dart' as sdk; import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; @@ -15,6 +16,8 @@ import 'package:fluffychat/utils/stream_extension.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/matrix.dart'; +enum AddRoomType { chat, subspace } + class SpaceView extends StatefulWidget { final String spaceId; final void Function() onBack; @@ -160,6 +163,95 @@ class _SpaceViewState extends State { } } + void _addChatOrSubspace() async { + final roomType = await showConfirmationDialog( + context: context, + title: L10n.of(context)!.addChatOrSubSpace, + actions: [ + AlertDialogAction( + key: AddRoomType.subspace, + label: L10n.of(context)!.createNewSpace, + ), + AlertDialogAction( + key: AddRoomType.chat, + label: L10n.of(context)!.createGroup, + ), + ], + ); + if (roomType == null) return; + + final names = await showTextInputDialog( + context: context, + title: roomType == AddRoomType.subspace + ? L10n.of(context)!.createNewSpace + : L10n.of(context)!.createGroup, + textFields: [ + DialogTextField( + hintText: roomType == AddRoomType.subspace + ? L10n.of(context)!.spaceName + : L10n.of(context)!.groupName, + minLines: 1, + maxLines: 1, + maxLength: 64, + validator: (text) { + if (text == null || text.isEmpty) { + return L10n.of(context)!.pleaseChoose; + } + return null; + }, + ), + DialogTextField( + hintText: L10n.of(context)!.chatDescription, + minLines: 4, + maxLines: 8, + maxLength: 255, + ), + ], + okLabel: L10n.of(context)!.create, + cancelLabel: L10n.of(context)!.cancel, + ); + if (names == null) return; + final client = Matrix.of(context).client; + final result = await showFutureLoadingDialog( + context: context, + future: () async { + late final String roomId; + final activeSpace = client.getRoomById(widget.spaceId)!; + await activeSpace.postLoad(); + + if (roomType == AddRoomType.subspace) { + roomId = await client.createSpace( + name: names.first, + topic: names.last.isEmpty ? null : names.last, + visibility: activeSpace.joinRules == JoinRules.public + ? sdk.Visibility.public + : sdk.Visibility.private, + ); + } else { + roomId = await client.createGroupChat( + groupName: names.first, + preset: activeSpace.joinRules == JoinRules.public + ? CreateRoomPreset.publicChat + : CreateRoomPreset.privateChat, + visibility: activeSpace.joinRules == JoinRules.public + ? sdk.Visibility.public + : sdk.Visibility.private, + initialState: names.length > 1 && names.last.isNotEmpty + ? [ + StateEvent( + type: EventTypes.RoomTopic, + content: {'topic': names.last}, + ), + ] + : null, + ); + } + await activeSpace.setSpaceChild(roomId); + }, + ); + if (result.error != null) return; + } + @override Widget build(BuildContext context) { final room = Matrix.of(context).client.getRoomById(widget.spaceId); @@ -352,22 +444,55 @@ class _SpaceViewState extends State { itemCount: joinedRooms.length + 1, itemBuilder: (context, i) { if (i == 0) { - return SearchTitle( - title: L10n.of(context)!.joinedChats, - icon: const Icon(Icons.chat_outlined), + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (room.canChangeStateEvent( + EventTypes.SpaceChild, + ) && + filter.isEmpty) ...[ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 1, + ), + child: Material( + borderRadius: BorderRadius.circular( + AppConfig.borderRadius, + ), + clipBehavior: Clip.hardEdge, + child: ListTile( + onTap: _addChatOrSubspace, + leading: const CircleAvatar( + radius: Avatar.defaultSize / 2, + child: Icon(Icons.add_outlined), + ), + title: Text( + L10n.of(context)!.addChatOrSubSpace, + style: const TextStyle(fontSize: 14), + ), + ), + ), + ), + ], + SearchTitle( + title: L10n.of(context)!.joinedChats, + icon: const Icon(Icons.chat_outlined), + ), + ], ); } i--; - final room = joinedRooms[i]; + final joinedRoom = joinedRooms[i]; return ChatListItem( - room, + joinedRoom, filter: filter, - onTap: () => widget.onChatTab(room), + onTap: () => widget.onChatTab(joinedRoom), onLongPress: (context) => widget.onChatContext( - room, + joinedRoom, context, ), - activeChat: widget.activeChat == room.id, + activeChat: widget.activeChat == joinedRoom.id, ); }, ), From 9e737276b629fc16455ad4807508d29f36b26a89 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Mon, 22 Jul 2024 20:06:55 +0200 Subject: [PATCH 092/288] chore: Follow up active filter --- lib/pages/chat_list/chat_list.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index befba299a5..63cb35571e 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -100,7 +100,9 @@ class ChatListController extends State context.push('/rooms/newspace'); } - ActiveFilter activeFilter = ActiveFilter.allChats; + ActiveFilter activeFilter = AppConfig.separateChatTypes + ? ActiveFilter.messages + : ActiveFilter.allChats; String? _activeSpaceId; String? get activeSpaceId => _activeSpaceId; From 020b6768ebe0d65e0d228e2f133d6293c6faf364 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Wed, 24 Jul 2024 06:48:52 +0200 Subject: [PATCH 093/288] chore: Sligthly improve chat permissions page design --- assets/l10n/intl_en.arb | 32 ++++++++++++- .../permission_list_tile.dart | 46 +++++++++++-------- 2 files changed, 58 insertions(+), 20 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 6c6da498a1..f7016107be 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -673,7 +673,7 @@ "type": "text", "placeholders": {} }, - "defaultPermissionLevel": "Default permission level", + "defaultPermissionLevel": "Default permission level for new users", "@defaultPermissionLevel": { "type": "text", "placeholders": {} @@ -2711,5 +2711,33 @@ "type": "text", "space": {} }, - "markAsUnread": "Mark as unread" + "markAsUnread": "Mark as unread", + "userLevel": "{level} - User", + "@userLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "moderatorLevel": "{level} - Moderator", + "@moderatorLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "adminLevel": "{level} - Admin", + "@adminLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "changeGeneralChatSettings": "Change general chat settings", + "inviteOtherUsers": "Invite other users to this chat", + "changeTheChatPermissions": "Change the chat permissions", + "changeTheVisibilityOfChatHistory": "Change the visibility of the chat history", + "changeTheCanonicalRoomAlias": "Change the main public chat address", + "sendRoomNotifications": "Send a @room notifications", + "changeTheDescriptionOfTheGroup": "Change the description of the chat" } diff --git a/lib/pages/chat_permissions_settings/permission_list_tile.dart b/lib/pages/chat_permissions_settings/permission_list_tile.dart index af447cbec8..99f9e553b3 100644 --- a/lib/pages/chat_permissions_settings/permission_list_tile.dart +++ b/lib/pages/chat_permissions_settings/permission_list_tile.dart @@ -29,7 +29,7 @@ class PermissionsListTile extends StatelessWidget { case 'events_default': return L10n.of(context)!.sendMessages; case 'state_default': - return L10n.of(context)!.configureChat; + return L10n.of(context)!.changeGeneralChatSettings; case 'ban': return L10n.of(context)!.banFromChat; case 'kick': @@ -37,23 +37,25 @@ class PermissionsListTile extends StatelessWidget { case 'redact': return L10n.of(context)!.deleteMessage; case 'invite': - return L10n.of(context)!.inviteContact; + return L10n.of(context)!.inviteOtherUsers; } } else if (category == 'notifications') { switch (permissionKey) { case 'rooms': - return L10n.of(context)!.notifications; + return L10n.of(context)!.sendRoomNotifications; } } else if (category == 'events') { switch (permissionKey) { case EventTypes.RoomName: return L10n.of(context)!.changeTheNameOfTheGroup; + case EventTypes.RoomTopic: + return L10n.of(context)!.changeTheDescriptionOfTheGroup; case EventTypes.RoomPowerLevels: - return L10n.of(context)!.chatPermissions; + return L10n.of(context)!.changeTheChatPermissions; case EventTypes.HistoryVisibility: - return L10n.of(context)!.visibilityOfTheChatHistory; + return L10n.of(context)!.changeTheVisibilityOfChatHistory; case EventTypes.RoomCanonicalAlias: - return L10n.of(context)!.setInvitationLink; + return L10n.of(context)!.changeTheCanonicalRoomAlias; case EventTypes.RoomAvatar: return L10n.of(context)!.editRoomAvatar; case EventTypes.RoomTombstone: @@ -69,32 +71,40 @@ class PermissionsListTile extends StatelessWidget { @override Widget build(BuildContext context) { + final color = permission >= 100 + ? Colors.orangeAccent + : permission >= 50 + ? Colors.blueAccent + : Colors.greenAccent; return ListTile( - title: Text(getLocalizedPowerLevelString(context)), - subtitle: Text( - L10n.of(context)!.minimumPowerLevel(permission.toString()), + title: Text( + getLocalizedPowerLevelString(context), + style: Theme.of(context).textTheme.titleSmall, ), trailing: Material( - borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), - color: Theme.of(context).colorScheme.onInverseSurface, + color: color.withAlpha(64), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), + side: BorderSide(color: color), + ), child: DropdownButton( padding: const EdgeInsets.symmetric(horizontal: 8.0), borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), underline: const SizedBox.shrink(), onChanged: canEdit ? onChanged : null, - value: {0, 50, 100}.contains(permission) ? permission : null, + value: permission, items: [ DropdownMenuItem( - value: 0, - child: Text(L10n.of(context)!.user), + value: permission < 50 ? permission : 0, + child: Text(L10n.of(context)!.userLevel(permission)), ), DropdownMenuItem( - value: 50, - child: Text(L10n.of(context)!.moderator), + value: permission < 100 && permission >= 50 ? permission : 50, + child: Text(L10n.of(context)!.moderatorLevel(permission)), ), DropdownMenuItem( - value: 100, - child: Text(L10n.of(context)!.admin), + value: permission >= 100 ? permission : 100, + child: Text(L10n.of(context)!.adminLevel(permission)), ), DropdownMenuItem( value: null, From fcd3227ef568ddf5a6d97838cb39c4576c548356 Mon Sep 17 00:00:00 2001 From: Krille Date: Wed, 24 Jul 2024 08:37:16 +0200 Subject: [PATCH 094/288] fix: Display only available join rules --- .../chat_access_settings_controller.dart | 30 +++++++++++++++++++ .../chat_access_settings_page.dart | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) 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 c0ab1fa7a8..fd76247906 100644 --- a/lib/pages/chat_access_settings/chat_access_settings_controller.dart +++ b/lib/pages/chat_access_settings/chat_access_settings_controller.dart @@ -24,6 +24,36 @@ class ChatAccessSettingsController extends State { bool guestAccessLoading = false; Room get room => Matrix.of(context).client.getRoomById(widget.roomId)!; + String get roomVersion => + room + .getState(EventTypes.RoomCreate)! + .content + .tryGet('room_version') ?? + 'Unknown'; + + /// Calculates which join rules are available based on the information on + /// https://spec.matrix.org/v1.11/rooms/#feature-matrix + List get availableJoinRules { + final joinRules = Set.from(JoinRules.values); + + final roomVersionInt = int.tryParse(roomVersion); + + // Knock is only supported for rooms up from version 7: + if (roomVersionInt != null && roomVersionInt <= 6) { + joinRules.remove(JoinRules.knock); + } + + // Not yet supported in FluffyChat: + joinRules.remove(JoinRules.restricted); + joinRules.remove(JoinRules.knockRestricted); + + // If an unsupported join rule is the current join rule, display it: + final currentJoinRule = room.joinRules; + if (currentJoinRule != null) joinRules.add(currentJoinRule); + + return joinRules.toList(); + } + void setJoinRule(JoinRules? newJoinRules) async { if (newJoinRules == null) return; setState(() { 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 7de6f37932..c23d9dc6b5 100644 --- a/lib/pages/chat_access_settings/chat_access_settings_page.dart +++ b/lib/pages/chat_access_settings/chat_access_settings_page.dart @@ -66,7 +66,7 @@ class ChatAccessSettingsPageView extends StatelessWidget { ), ), ), - for (final joinRule in JoinRules.values) + for (final joinRule in controller.availableJoinRules) if (joinRule != JoinRules.private) RadioListTile.adaptive( title: Text( From 9d0cefce1837b9b5c69d15a828440dd535d36510 Mon Sep 17 00:00:00 2001 From: Krille Date: Wed, 24 Jul 2024 08:37:33 +0200 Subject: [PATCH 095/288] chore: Do not hide error on file sending --- lib/pages/chat/send_file_dialog.dart | 29 +++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/pages/chat/send_file_dialog.dart b/lib/pages/chat/send_file_dialog.dart index b50a7d19ee..f97a4f2c62 100644 --- a/lib/pages/chat/send_file_dialog.dart +++ b/lib/pages/chat/send_file_dialog.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -6,7 +8,7 @@ import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/utils/localized_exception_extension.dart'; +import 'package:fluffychat/utils/error_reporter.dart'; import 'package:fluffychat/utils/size_string.dart'; import '../../utils/resize_image.dart'; @@ -42,19 +44,20 @@ class SendFileDialogState extends State { }, ); } - final scaffoldMessenger = ScaffoldMessenger.of(context); - widget.room - .sendFileEvent( - file, - thumbnail: thumbnail, - shrinkImageMaxDimension: origImage ? null : 1600, - ) - .catchError((e) { - scaffoldMessenger.showSnackBar( - SnackBar(content: Text((e as Object).toLocalizedString(context))), + try { + await widget.room.sendFileEvent( + file, + thumbnail: thumbnail, + shrinkImageMaxDimension: origImage ? null : 1600, ); - return null; - }); + } on IOException catch (_) { + } on FileTooBigMatrixException catch (_) { + } catch (e, s) { + if (mounted) { + ErrorReporter(context, 'Unable to send file').onErrorCallback(e, s); + } + rethrow; + } } Navigator.of(context, rootNavigator: false).pop(); From 76bc414293d707d437acddaa51f0fc58245e3d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=8D=E7=9F=A5=E7=81=AB=20Shiranui?= Date: Tue, 16 Jul 2024 17:35:19 +0000 Subject: [PATCH 096/288] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (642 of 642 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hant/ --- assets/l10n/intl_zh_Hant.arb | 126 +++++++++++++++++------------------ 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/assets/l10n/intl_zh_Hant.arb b/assets/l10n/intl_zh_Hant.arb index 45740dd52c..f7837bd4ee 100644 --- a/assets/l10n/intl_zh_Hant.arb +++ b/assets/l10n/intl_zh_Hant.arb @@ -98,7 +98,7 @@ "username": {} } }, - "badServerLoginTypesException": "目前伺服器支援的登入類型:\n {serverVersions} \n但本應用程式僅支援:\n {supportedVersions}", + "badServerLoginTypesException": "目前伺服器支援的登入類型:\n{serverVersions}\n但本應用程式僅支援:\n{supportedVersions}", "@badServerLoginTypesException": { "type": "text", "placeholders": { @@ -106,7 +106,7 @@ "supportedVersions": {} } }, - "badServerVersionsException": "目前伺服器支援的Spec版本:\n {serverVersions} \n但本應用程式僅支援 {supportedVersions}", + "badServerVersionsException": "目前伺服器支援的協議版本:\n{serverVersions}\n但本應用程式僅支援 {supportedVersions}", "@badServerVersionsException": { "type": "text", "placeholders": { @@ -164,7 +164,7 @@ "username": {} } }, - "changedTheChatDescriptionTo": "{username} 變更了聊天室介紹為:「 {description} 」", + "changedTheChatDescriptionTo": "{username} 變更了聊天室介紹為:「{description}」", "@changedTheChatDescriptionTo": { "type": "text", "placeholders": { @@ -172,7 +172,7 @@ "description": {} } }, - "changedTheChatNameTo": "{username} 變更了聊天室名稱為:「 {chatname} 」", + "changedTheChatNameTo": "{username} 變更了聊天室名稱為:「{chatname}」", "@changedTheChatNameTo": { "type": "text", "placeholders": { @@ -187,7 +187,7 @@ "username": {} } }, - "changedTheDisplaynameTo": "{username} 變更了顯示名稱為:「 {displayname} 」", + "changedTheDisplaynameTo": "{username} 變更了顯示名稱為:「{displayname}」", "@changedTheDisplaynameTo": { "type": "text", "placeholders": { @@ -562,7 +562,7 @@ "type": "text", "placeholders": {} }, - "encrypted": "加密的", + "encrypted": "已加密的", "@encrypted": { "type": "text", "placeholders": {} @@ -764,7 +764,7 @@ "type": "text", "placeholders": {} }, - "inviteText": "{username} 邀請您使用 FluffyChat\n1. 安裝 FluffyChat:https://fluffychat.im\n2. 登入或註冊\n3. 打開該邀請網址:\n {link}", + "inviteText": "{username} 邀請您使用 FluffyChat\n1. 安裝 FluffyChat:https://fluffychat.im\n2. 登入或註冊\n3. 打開該邀請網址:\n{link}", "@inviteText": { "type": "text", "placeholders": { @@ -896,12 +896,12 @@ "type": "text", "placeholders": {} }, - "needPantalaimonWarning": "請注意您需要Pantalaimon才能使用點對點加密功能。", + "needPantalaimonWarning": "請注意您需要 Pantalaimon 才能使用點對點加密功能。", "@needPantalaimonWarning": { "type": "text", "placeholders": {} }, - "newChat": "新聊天室", + "newChat": "新聊天", "@newChat": { "type": "text", "placeholders": {} @@ -941,7 +941,7 @@ "type": "text", "placeholders": {} }, - "noGoogleServicesWarning": "看起來您手機上沒有Google服務框架。這對於保護您的隱私而言是個好決定!但為了收到 FluffyChat 的推播通知,我們推薦您使用 https://microg.org/ 或 https://unifiedpush.org/。", + "noGoogleServicesWarning": "您手機上沒有安裝 Google 服務框架。這或許對於保護您的隱私而言是個好事!但為了收到 FluffyChat 的推播通知,我們建議您使用 https://microg.org 或 https://unifiedpush.org。", "@noGoogleServicesWarning": { "type": "text", "placeholders": {} @@ -1176,7 +1176,7 @@ "type": "text", "placeholders": {} }, - "unbanFromChat": "解禁聊天室", + "unbanFromChat": "解封聊天室", "@unbanFromChat": { "type": "text", "placeholders": {} @@ -1320,7 +1320,7 @@ "type": "text", "placeholders": {} }, - "setPermissionsLevel": "設定權限級別", + "setPermissionsLevel": "設定權限等級", "@setPermissionsLevel": { "type": "text", "placeholders": {} @@ -1415,7 +1415,7 @@ "type": "text", "placeholders": {} }, - "toggleUnread": "標記為已讀/未讀", + "toggleUnread": "標示為已讀/未讀", "@toggleUnread": { "type": "text", "placeholders": {} @@ -1463,7 +1463,7 @@ "type": "text", "placeholders": {} }, - "unknownEvent": "未知事件「 {type} 」", + "unknownEvent": "未知事件「{type}」", "@unknownEvent": { "type": "text", "placeholders": { @@ -1625,7 +1625,7 @@ "type": "text", "placeholders": {} }, - "wipeChatBackup": "要清除您的聊天室記錄備份以建立新的安全金鑰嗎?", + "wipeChatBackup": "是否清除您的聊天室記錄備份以建立新的安全金鑰嗎?", "@wipeChatBackup": { "type": "text", "placeholders": {} @@ -1742,7 +1742,7 @@ }, "addToSpace": "加入空間", "@addToSpace": {}, - "cantOpenUri": "無法打開URI {uri}", + "cantOpenUri": "無法打開 URI {uri}", "@cantOpenUri": { "type": "text", "placeholders": { @@ -1758,7 +1758,7 @@ "type": "text", "placeholders": {} }, - "commandHint_myroomavatar": "設置您的聊天室頭貼(通過 mxc-uri)", + "commandHint_myroomavatar": "設定您的聊天室頭貼(通過 mxc-uri)", "@commandHint_myroomavatar": { "type": "text", "description": "Usage hint for the command /myroomavatar" @@ -1773,7 +1773,7 @@ "type": "text", "placeholder": {} }, - "sendOnEnter": "按 Enter 鍵發送", + "sendOnEnter": "按 Enter 鍵傳送", "@sendOnEnter": {}, "changeYourAvatar": "更改您的大頭貼", "@changeYourAvatar": { @@ -1791,7 +1791,7 @@ "senderName": {} } }, - "commandHint_cuddle": "發送一個摟抱表情", + "commandHint_cuddle": "傳送一個摟抱表情", "@commandHint_cuddle": {}, "supposedMxid": "此處應爲 {mxid}", "@supposedMxid": { @@ -1820,7 +1820,7 @@ }, "description": "State that {command} is not a valid /command." }, - "googlyEyesContent": "{senderName} 向您發送了瞪眼表情", + "googlyEyesContent": "{senderName} 向您傳送了瞪眼表情", "@googlyEyesContent": { "type": "text", "placeholders": { @@ -1833,7 +1833,7 @@ "@sendTypingNotifications": {}, "importEmojis": "匯入表情包", "@importEmojis": {}, - "confirmMatrixId": "如需要刪除你的帳戶,請確認你的 Matrix ID。", + "confirmMatrixId": "如需刪除你的帳戶,請確認你的 Matrix ID。", "@confirmMatrixId": {}, "notAnImage": "不是圖片檔案。", "@notAnImage": {}, @@ -1844,11 +1844,11 @@ "senderName": {} } }, - "commandHint_hug": "發送一個擁抱表情", + "commandHint_hug": "傳送一個擁抱表情", "@commandHint_hug": {}, "replace": "取代", "@replace": {}, - "commandHint_googly": "發送一些瞪眼表情", + "commandHint_googly": "傳送一些瞪眼表情", "@commandHint_googly": {}, "importNow": "立即匯入", "@importNow": {}, @@ -1864,31 +1864,31 @@ "@alwaysUse24HourFormat": { "description": "Set to true to always display time of day in 24 hour format." }, - "sender": "發送者", + "sender": "傳送者", "@sender": {}, "voiceCall": "語音通話", "@voiceCall": {}, - "blockUsername": "忽略使用者名稱", + "blockUsername": "無視使用者名稱", "@blockUsername": {}, "noBackupWarning": "警告!如果不啟用聊天室備份,您將失去對加密訊息的訪問。強烈建議在登出前先啟用聊天室備份。", "@noBackupWarning": {}, "addChatOrSubSpace": "新增聊天室或子空間", "@addChatOrSubSpace": {}, - "thisDevice": "這個設備:", + "thisDevice": "這個裝置:", "@thisDevice": {}, "separateChatTypes": "分開私訊和群組", "@separateChatTypes": { "type": "text", "placeholders": {} }, - "commandHint_markasdm": "將給定的 Matrix ID 標記為直接訊息房間", + "commandHint_markasdm": "將給定的 Matrix ID 標示為直接訊息房間", "@commandHint_markasdm": {}, - "commandHint_html": "發送 HTML 格式的文字", + "commandHint_html": "傳送 HTML 格式的文字", "@commandHint_html": { "type": "text", "description": "Usage hint for the command /html" }, - "commandHint_send": "發送文字", + "commandHint_send": "傳送文字", "@commandHint_send": { "type": "text", "description": "Usage hint for the command /send" @@ -1904,7 +1904,7 @@ "@optionalRedactReason": {}, "dehydrateWarning": "此操作不能反悔。請確保安全地存儲備份文件。", "@dehydrateWarning": {}, - "hydrateTorLong": "上次在 TOR 上匯出會話了嗎?快速匯入它並繼續聊天室。", + "hydrateTorLong": "上次在 TOR 上匯出會話了嗎?快速匯入它已繼續使用聊天室。", "@hydrateTorLong": {}, "hydrate": "從備份文件恢復", "@hydrate": {}, @@ -1930,7 +1930,7 @@ "type": "text", "placeholders": {} }, - "redactedByBecause": "由 {username} 編輯,原因:\"{reason}\"", + "redactedByBecause": "由 {username} 編輯,原因:「{reason}」", "@redactedByBecause": { "type": "text", "placeholders": { @@ -1955,9 +1955,9 @@ "@unlockOldMessages": {}, "callingAccountDetails": "允許 FluffyChat 使用原生 Android 撥號應用程式。", "@callingAccountDetails": {}, - "noOtherDevicesFound": "未找到其他設備", + "noOtherDevicesFound": "未找到其他裝置", "@noOtherDevicesFound": {}, - "noUsersFoundWithQuery": "很遺憾,找不到與「 {query} 」相符的使用者。請檢查是否有打錯字。", + "noUsersFoundWithQuery": "很遺憾,找不到與「{query}」相符的使用者。請檢查是否有打錯字。", "@noUsersFoundWithQuery": { "type": "text", "placeholders": { @@ -1982,7 +1982,7 @@ "@experimentalVideoCalls": {}, "youAcceptedTheInvitation": "👍 您接受了邀請", "@youAcceptedTheInvitation": {}, - "storeSecurlyOnThisDevice": "在此設備上安全存儲", + "storeSecurlyOnThisDevice": "在此裝置上安全存儲", "@storeSecurlyOnThisDevice": {}, "countFiles": "{count} 個文件", "@countFiles": { @@ -2001,13 +2001,13 @@ }, "archiveRoomDescription": "聊天室將被移動到存檔中。其他使用者將能看到您已離開聊天室。", "@archiveRoomDescription": {}, - "banUserDescription": "該使用者將被禁止進入聊天室,直到他們被解禁之前都無法再次進入聊天室。", + "banUserDescription": "該使用者將被禁止進入聊天室,直到他們被解封之前都無法再次進入聊天室。", "@banUserDescription": {}, "searchChatsRooms": "搜尋 #chats, @users...", "@searchChatsRooms": {}, "decline": "拒絕", "@decline": {}, - "sendReadReceipts": "發送已讀回條", + "sendReadReceipts": "傳送已讀回條", "@sendReadReceipts": {}, "formattedMessagesDescription": "使用 markdown 顯示豐富的訊息內容,如粗體文字。", "@formattedMessagesDescription": {}, @@ -2040,7 +2040,7 @@ "@enterRoom": {}, "allSpaces": "所有空間", "@allSpaces": {}, - "indexedDbErrorLong": "預設情況下,私密模式不啟用消息存儲。\n請訪問\n - about:config\n - 將 dom.indexedDB.privateBrowsing.enabled 設置為 true\n否則,無法運行 FluffyChat。", + "indexedDbErrorLong": "預設情況下,私密模式不啟用消息存儲。\n請訪問\n - about:config\n - 將 dom.indexedDB.privateBrowsing.enabled 設定為 true\n否則,無法運行 FluffyChat。", "@indexedDbErrorLong": {}, "youKickedAndBanned": "🙅 您踢出並封鎖了 {user}", "@youKickedAndBanned": { @@ -2087,21 +2087,21 @@ "@widgetName": {}, "jump": "跳轉", "@jump": {}, - "commandHint_unignore": "取消忽略已提供的 Matrix ID", + "commandHint_unignore": "取消無視已提供的 Matrix ID", "@commandHint_unignore": {}, - "commandHint_markasgroup": "標記為群組", + "commandHint_markasgroup": "標示為群組", "@commandHint_markasgroup": {}, "commandHint_me": "描述自己", "@commandHint_me": { "type": "text", "description": "Usage hint for the command /me" }, - "commandHint_plain": "發送未格式化的文字", + "commandHint_plain": "傳送未格式化的文字", "@commandHint_plain": { "type": "text", "description": "Usage hint for the command /plain" }, - "commandHint_react": "以反應的形式發送回覆", + "commandHint_react": "以反應的形式傳送回覆", "@commandHint_react": { "type": "text", "description": "Usage hint for the command /react" @@ -2133,7 +2133,7 @@ "@hideInvalidOrUnknownMessageFormats": {}, "dehydrateTorLong": "對 TOR 使用者,建議在關閉窗口前匯出會話。", "@dehydrateTorLong": {}, - "hydrateTor": "TOR 使用者:匯入會話匯出", + "hydrateTor": "TOR 使用者:匯入會話", "@hydrateTor": {}, "messagesStyle": "訊息樣式:", "@messagesStyle": {}, @@ -2250,7 +2250,7 @@ "user": {} } }, - "youInvitedToBy": "📩 您通過網址被邀請至:\n {alias}", + "youInvitedToBy": "📩 您通過網址被邀請至:\n{alias}", "@youInvitedToBy": { "placeholders": { "alias": {} @@ -2287,7 +2287,7 @@ "@invite": {}, "invitePrivateChat": "📨 邀請私人聊天室", "@invitePrivateChat": {}, - "removeDevicesDescription": "您將從這個設備登出,並將不再能夠接收消息。", + "removeDevicesDescription": "您將從這個裝置登出,並將不再能夠接收消息。", "@removeDevicesDescription": {}, "unbanUserDescription": "如果該使用者嘗試,他們將能夠再次進入聊天室。", "@unbanUserDescription": {}, @@ -2324,7 +2324,7 @@ "@select": {}, "files": "文件", "@files": {}, - "forwardMessageTo": "將訊息轉發至 {roomName} ?", + "forwardMessageTo": "將訊息轉發至 {roomName}?", "@forwardMessageTo": { "type": "text", "placeholders": { @@ -2405,7 +2405,7 @@ "@setTheme": {}, "knocking": "敲門", "@knocking": {}, - "sessionLostBody": "您的會話已丟失。請將此錯誤報告給開發人員,網址為 {url} 。錯誤訊息為:{error}", + "sessionLostBody": "您的會話已丟失。請將此錯誤報告給開發人員,網址為 {url}。錯誤訊息為:{error}", "@sessionLostBody": { "type": "text", "placeholders": { @@ -2504,11 +2504,11 @@ }, "otherCallingPermissions": "麥克風、相機和其他 FluffyChat 權限", "@otherCallingPermissions": {}, - "disableEncryptionWarning": "出於安全原因,您不能在之前已啟用加密的聊天室中停用加密。", + "disableEncryptionWarning": "出於安全原因,您不能在之前已加密的聊天室中停用加密。", "@disableEncryptionWarning": {}, - "deviceKeys": "設備密鑰:", + "deviceKeys": "裝置密鑰:", "@deviceKeys": {}, - "fileIsTooBigForServer": "伺服器報告該文件太大,無法發送。", + "fileIsTooBigForServer": "伺服器報告該文件太大,無法傳送。", "@fileIsTooBigForServer": {}, "fileHasBeenSavedAt": "文件已保存在 {path}", "@fileHasBeenSavedAt": { @@ -2529,7 +2529,7 @@ "@report": {}, "pleaseEnterANumber": "請輸入大於 0 的數字", "@pleaseEnterANumber": {}, - "roomUpgradeDescription": "然後,聊天室將使用新的房間版本重新建立。所有參與者將被通知他們需要切換到新的聊天室。您可以在 https://spec.matrix.org/latest/rooms/ 了解更多關於房間版本的信息。", + "roomUpgradeDescription": "將使用新版本聊天室來重新建立聊天室。所有本聊天室的參與者都會收到通知,他們都需要換到新的聊天室裡。若您想知道有關新版本的更多資訊,請前往 https://spec.matrix.org/latest/rooms/", "@roomUpgradeDescription": {}, "wrongRecoveryKey": "抱歉......這似乎不是正確的恢復密鑰。", "@wrongRecoveryKey": {}, @@ -2561,7 +2561,7 @@ "@incomingMessages": {}, "databaseMigrationTitle": "資料庫已最佳化", "@databaseMigrationTitle": {}, - "restoreSessionBody": "應用程式現在嘗試從備份中恢復您的會話。請將此錯誤報告給開發人員,網址為 {url} 。錯誤訊息為:{error}", + "restoreSessionBody": "應用程式現在嘗試從備份中恢復您的會話。請將此錯誤報告給開發人員,網址為 {url}。錯誤訊息為:{error}", "@restoreSessionBody": { "type": "text", "placeholders": { @@ -2587,7 +2587,7 @@ "type": "text", "placeholders": {} }, - "inviteContactToGroupQuestion": "您想邀請 {contact} 加入 \"{groupName}\" 聊天室嗎?", + "inviteContactToGroupQuestion": "您想邀請 {contact} 加入 「{groupName}」 聊天室嗎?", "@inviteContactToGroupQuestion": {}, "enableMultiAccounts": "(實驗性功能)在此裝置上啟用多個帳號", "@enableMultiAccounts": {}, @@ -2595,11 +2595,11 @@ "@hideMemberChangesInPublicChats": {}, "recoveryKeyLost": "遺失恢復金鑰?", "@recoveryKeyLost": {}, - "sendAsText": "以文字發送", + "sendAsText": "以文字傳送", "@sendAsText": { "type": "text" }, - "sendSticker": "發送貼圖", + "sendSticker": "傳送貼圖", "@sendSticker": { "type": "text", "placeholders": {} @@ -2615,13 +2615,13 @@ "server": {} } }, - "commandHint_sendraw": "發送原始 json", + "commandHint_sendraw": "傳送原始 json", "@commandHint_sendraw": {}, "newPassword": "新密碼", "@newPassword": {}, "createNewAddress": "建立新地址", "@createNewAddress": {}, - "searchIn": "在聊天室「 {chat} 」中搜尋......", + "searchIn": "在聊天室「{chat}」中搜尋......", "@searchIn": { "type": "text", "placeholders": { @@ -2632,7 +2632,7 @@ "@searchMore": {}, "gallery": "畫廊", "@gallery": {}, - "databaseBuildErrorBody": "無法建立 SQLite 資料庫。應用程式目前嘗試使用遺留資料庫。請將此錯誤報告給開發人員,網址為 {url} 。錯誤訊息為:{error}", + "databaseBuildErrorBody": "無法建立 SQLite 資料庫。應用程式目前嘗試使用遺留資料庫。請將此錯誤報告給開發人員,網址為 {url}。錯誤訊息為:{error}", "@databaseBuildErrorBody": { "type": "text", "placeholders": { @@ -2651,7 +2651,7 @@ "type": "text", "space": {} }, - "markAsUnread": "標記為未讀", + "markAsUnread": "標示為未讀", "@markAsUnread": {}, "noDatabaseEncryption": "此平台不支援資料庫加密", "@noDatabaseEncryption": {}, @@ -2659,11 +2659,11 @@ "@messageType": {}, "openGallery": "開啟畫廊", "@openGallery": {}, - "markAsRead": "標記為已讀", + "markAsRead": "標示為已讀", "@markAsRead": {}, "reportUser": "舉報使用者", "@reportUser": {}, - "unsupportedAndroidVersionLong": "此功能需要較新的Android版本。請檢查更新或Lineage OS支持。", + "unsupportedAndroidVersionLong": "此功能需要較新的 Android 版本。請檢查更新或 Lineage OS 支持。", "@unsupportedAndroidVersionLong": {}, "emailOrUsername": "電子郵件或使用者名", "@emailOrUsername": {}, @@ -2675,13 +2675,13 @@ "@usersMustKnock": {}, "knock": "敲門", "@knock": {}, - "storeInSecureStorageDescription": "將恢復密鑰存儲在此設備的安全存儲中。", + "storeInSecureStorageDescription": "將恢復密鑰存儲在此裝置的安全存儲中。", "@storeInSecureStorageDescription": {}, "appearOnTopDetails": "允許應用程式顯示在最上層(如果您已將 Fluffychat 設定為通話帳戶則不需要)", "@appearOnTopDetails": {}, "whyIsThisMessageEncrypted": "為什麼這條訊息無法讀取?", "@whyIsThisMessageEncrypted": {}, - "noKeyForThisMessage": "如果訊息是在您登入此設備之前發送的,就可能會發生這種情況。\n\n也有可能是發送者已經封鎖了您的設備,或者網絡連接出了問題。\n\n如果您能在另一個會話中讀取該訊息,那麼您可以從中轉移訊息!前往設定 > 設備,並確保您的設備已相互驗證。當您下次打開房間且兩個會話都在前景時,密鑰將自動傳輸。\n\n不想在登出或切換設備時丟失密鑰?請確保您已在設定中啟用了聊天室備份。", + "noKeyForThisMessage": "如果訊息是在您登入此裝置之前傳送的,就可能會發生這種情況。\n\n也有可能是傳送者已經封鎖了您的裝置,或者網絡連接出了問題。\n\n如果您能在另一個會話中讀取該訊息,那麼您可以從中轉移訊息!前往設定 > 裝置,並確保您的裝置已相互驗證。當您下次打開房間且兩個會話都在前景時,密鑰將自動傳輸。\n\n不想在登出或切換裝置時丟失密鑰?請確保您已在設定中啟用了聊天室備份。", "@noKeyForThisMessage": {}, "newSpaceDescription": "空間允許您整合您的聊天室並建立私人或公開社群。", "@newSpaceDescription": {}, @@ -2707,7 +2707,7 @@ "sender": {} } }, - "commandHint_ignore": "忽略已提供的 Matrix ID", + "commandHint_ignore": "無視已提供的 Matrix ID", "@commandHint_ignore": {}, "countChatsAndCountParticipants": "{chats} 個聊天室和 {participants} 位參與者", "@countChatsAndCountParticipants": { From df605d6e247f4f78beea460f8540bd8cdd4caac7 Mon Sep 17 00:00:00 2001 From: kdh8219 Date: Thu, 18 Jul 2024 13:18:56 +0000 Subject: [PATCH 097/288] Translated using Weblate (Arabic) Currently translated at 100.0% (642 of 642 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ar/ --- assets/l10n/intl_ar.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/l10n/intl_ar.arb b/assets/l10n/intl_ar.arb index 126dd8b5c9..6a27bd68d6 100644 --- a/assets/l10n/intl_ar.arb +++ b/assets/l10n/intl_ar.arb @@ -2709,7 +2709,7 @@ "@gallery": {}, "swipeRightToLeftToReply": "اسحب من اليمين إلى اليسار للرد", "@swipeRightToLeftToReply": {}, - "alwaysUse24HourFormat": "خاطئ", + "alwaysUse24HourFormat": "false", "@alwaysUse24HourFormat": { "description": "Set to true to always display time of day in 24 hour format." }, From d6a433c050d2c19afbd6e64d65ef1aeae5bb0208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Wed, 17 Jul 2024 20:13:38 +0000 Subject: [PATCH 098/288] Translated using Weblate (Estonian) Currently translated at 100.0% (642 of 642 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/ --- assets/l10n/intl_et.arb | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/assets/l10n/intl_et.arb b/assets/l10n/intl_et.arb index b53675eda0..3d25bdbb99 100644 --- a/assets/l10n/intl_et.arb +++ b/assets/l10n/intl_et.arb @@ -2712,5 +2712,30 @@ "alwaysUse24HourFormat": "vale", "@alwaysUse24HourFormat": { "description": "Set to true to always display time of day in 24 hour format." + }, + "noMoreChatsFound": "Rohkem vestlusi ei leidu...", + "@noMoreChatsFound": {}, + "joinedChats": "Vestlusi, millega oled liitunud", + "@joinedChats": {}, + "unread": "Lugemata", + "@unread": {}, + "space": "Kogukond", + "@space": {}, + "spaces": "Kogukonnad", + "@spaces": {}, + "goToSpace": "Ava kogukond: {space}", + "@goToSpace": { + "type": "text", + "space": {} + }, + "markAsUnread": "Märgi mitteloetuks", + "@markAsUnread": {}, + "countChatsAndCountParticipants": "{chats} vestlust ja {participants} osalejat", + "@countChatsAndCountParticipants": { + "type": "text", + "placeholders": { + "chats": {}, + "participants": {} + } } } From 18cf0a69e4784048c375ecef14d0b591bac81aec Mon Sep 17 00:00:00 2001 From: kdh8219 Date: Thu, 18 Jul 2024 13:20:48 +0000 Subject: [PATCH 099/288] Translated using Weblate (Estonian) Currently translated at 100.0% (642 of 642 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/ --- assets/l10n/intl_et.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/l10n/intl_et.arb b/assets/l10n/intl_et.arb index 3d25bdbb99..d19c823c31 100644 --- a/assets/l10n/intl_et.arb +++ b/assets/l10n/intl_et.arb @@ -2709,7 +2709,7 @@ "@files": {}, "swipeRightToLeftToReply": "Vastamiseks viipa paremalt vasakule", "@swipeRightToLeftToReply": {}, - "alwaysUse24HourFormat": "vale", + "alwaysUse24HourFormat": "false", "@alwaysUse24HourFormat": { "description": "Set to true to always display time of day in 24 hour format." }, From 45125e0329b495689563e91a9d5cd60f3b6e3fbb Mon Sep 17 00:00:00 2001 From: kdh8219 Date: Thu, 18 Jul 2024 13:18:05 +0000 Subject: [PATCH 100/288] Translated using Weblate (Basque) Currently translated at 100.0% (642 of 642 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/ --- assets/l10n/intl_eu.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/l10n/intl_eu.arb b/assets/l10n/intl_eu.arb index cefc59b531..cae8a51242 100644 --- a/assets/l10n/intl_eu.arb +++ b/assets/l10n/intl_eu.arb @@ -2709,7 +2709,7 @@ "@knockRestricted": {}, "swipeRightToLeftToReply": "Herrestatu eskuin-ezker erantzuteko", "@swipeRightToLeftToReply": {}, - "alwaysUse24HourFormat": "ez", + "alwaysUse24HourFormat": "false", "@alwaysUse24HourFormat": { "description": "Set to true to always display time of day in 24 hour format." }, From 661b381e0ec2df3b9f2fb1d765cdcfdf36f92ef3 Mon Sep 17 00:00:00 2001 From: kdh8219 Date: Thu, 18 Jul 2024 13:19:11 +0000 Subject: [PATCH 101/288] Translated using Weblate (Turkish) Currently translated at 100.0% (642 of 642 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/tr/ --- assets/l10n/intl_tr.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/l10n/intl_tr.arb b/assets/l10n/intl_tr.arb index 0fa7a18f24..c61face7f1 100644 --- a/assets/l10n/intl_tr.arb +++ b/assets/l10n/intl_tr.arb @@ -2709,7 +2709,7 @@ "@restricted": {}, "swipeRightToLeftToReply": "Yanıtlamak için sağdan sola kaydır", "@swipeRightToLeftToReply": {}, - "alwaysUse24HourFormat": "yanlış", + "alwaysUse24HourFormat": "false", "@alwaysUse24HourFormat": { "description": "Set to true to always display time of day in 24 hour format." }, From d186c9685657b4915cddb16176e8b55c1efdc05d Mon Sep 17 00:00:00 2001 From: kdh8219 Date: Thu, 18 Jul 2024 13:20:56 +0000 Subject: [PATCH 102/288] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (642 of 642 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hans/ --- assets/l10n/intl_zh.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/l10n/intl_zh.arb b/assets/l10n/intl_zh.arb index 1e31fdd78c..ad89922453 100644 --- a/assets/l10n/intl_zh.arb +++ b/assets/l10n/intl_zh.arb @@ -2709,7 +2709,7 @@ "@restricted": {}, "swipeRightToLeftToReply": "从右向左滑动进行回复", "@swipeRightToLeftToReply": {}, - "alwaysUse24HourFormat": "否", + "alwaysUse24HourFormat": "false", "@alwaysUse24HourFormat": { "description": "Set to true to always display time of day in 24 hour format." }, From 1a7860fa171f70c172bbbd2e2e70ed7773756805 Mon Sep 17 00:00:00 2001 From: kdh8219 Date: Thu, 18 Jul 2024 13:21:02 +0000 Subject: [PATCH 103/288] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (642 of 642 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hant/ --- assets/l10n/intl_zh_Hant.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/l10n/intl_zh_Hant.arb b/assets/l10n/intl_zh_Hant.arb index f7837bd4ee..1640033942 100644 --- a/assets/l10n/intl_zh_Hant.arb +++ b/assets/l10n/intl_zh_Hant.arb @@ -1860,7 +1860,7 @@ "@block": {}, "discover": "發現", "@discover": {}, - "alwaysUse24HourFormat": "否", + "alwaysUse24HourFormat": "false", "@alwaysUse24HourFormat": { "description": "Set to true to always display time of day in 24 hour format." }, From 182f39b6b89acb867e30f69daaea328a4dfda54d Mon Sep 17 00:00:00 2001 From: kdh8219 Date: Thu, 18 Jul 2024 13:18:19 +0000 Subject: [PATCH 104/288] Translated using Weblate (Korean) Currently translated at 100.0% (642 of 642 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ko/ --- assets/l10n/intl_ko.arb | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_ko.arb b/assets/l10n/intl_ko.arb index 0e049112e0..ce17351d60 100644 --- a/assets/l10n/intl_ko.arb +++ b/assets/l10n/intl_ko.arb @@ -2707,5 +2707,34 @@ "knockRestricted": "스페이스 멤버만 참가 요청 가능", "@knockRestricted": {}, "swipeRightToLeftToReply": "오른쪽에서 왼쪽으로 스와이프해서 답장", - "@swipeRightToLeftToReply": {} + "@swipeRightToLeftToReply": {}, + "alwaysUse24HourFormat": "false", + "@alwaysUse24HourFormat": { + "description": "Set to true to always display time of day in 24 hour format." + }, + "unread": "읽지 않은", + "@unread": {}, + "space": "스페이스", + "@space": {}, + "spaces": "스페이스", + "@spaces": {}, + "goToSpace": "스페이스로: {space}", + "@goToSpace": { + "type": "text", + "space": {} + }, + "markAsUnread": "읽지 않음으로 표시", + "@markAsUnread": {}, + "countChatsAndCountParticipants": "{chats} 채팅과 {participants} 참여자", + "@countChatsAndCountParticipants": { + "type": "text", + "placeholders": { + "chats": {}, + "participants": {} + } + }, + "joinedChats": "참여한 채팅", + "@joinedChats": {}, + "noMoreChatsFound": "채팅을 찾을 수 없습니다...", + "@noMoreChatsFound": {} } From a8d6c7ecfe735b5a3575f1bd2eb891a1d9e99eb0 Mon Sep 17 00:00:00 2001 From: Pixelcode Date: Sat, 20 Jul 2024 10:05:11 +0000 Subject: [PATCH 105/288] Translated using Weblate (German) Currently translated at 100.0% (642 of 642 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/de/ --- assets/l10n/intl_de.arb | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/assets/l10n/intl_de.arb b/assets/l10n/intl_de.arb index 9e8d2fc677..54aa417b3b 100644 --- a/assets/l10n/intl_de.arb +++ b/assets/l10n/intl_de.arb @@ -360,7 +360,7 @@ "type": "text", "description": "Usage hint for the command /invite" }, - "commandHint_join": "Betrete den ausgewählten Raum", + "commandHint_join": "Betritt den ausgewählten Raum", "@commandHint_join": { "type": "text", "description": "Usage hint for the command /join" @@ -1084,7 +1084,7 @@ "type": "text", "placeholders": {} }, - "noGoogleServicesWarning": "Firebase Cloud Messaging scheint auf deinem Gerät nicht verfügbar zu sein. Um trotzdem Push-Benachrichtigungen zu erhalten, empfehlen wir die Installation von ntfy. Mit ntfy oder einem anderen Unified Push Anbieter kannst du Push-Benachrichtigungen datensicher empfangen. Du kannst ntfy im PlayStore oder bei F-Droid herunterladen.", + "noGoogleServicesWarning": "Firebase Cloud Messaging scheint auf deinem Gerät nicht verfügbar zu sein. Um trotzdem Push-Benachrichtigungen zu erhalten, empfehlen wir die Installation von ntfy. Mit ntfy oder einem anderen Unified-Push-Anbieter kannst du Push-Benachrichtigungen datensicher empfangen. Du kannst ntfy im PlayStore oder bei F-Droid herunterladen.", "@noGoogleServicesWarning": { "type": "text", "placeholders": {} @@ -1667,7 +1667,7 @@ "type": "text", "placeholders": {} }, - "tryToSendAgain": "Nochmal versuchen zu senden", + "tryToSendAgain": "Noch mal versuchen zu senden", "@tryToSendAgain": { "type": "text", "placeholders": {} @@ -1965,7 +1965,7 @@ "@start": {}, "repeatPassword": "Passwort wiederholen", "@repeatPassword": {}, - "commandHint_dm": "Starte einen direkten Chat\nBenutze --no-encryption um die Verschlüsselung auszuschalten", + "commandHint_dm": "Starte einen direkten Chat\nBenutze --no-encryption, um die Verschlüsselung auszuschalten", "@commandHint_dm": { "type": "text", "description": "Usage hint for the command /dm" @@ -1980,7 +1980,7 @@ "type": "text", "description": "Usage hint for the command /clearcache" }, - "commandHint_create": "Erstelle ein leeren Gruppenchat\nBenutze --no-encryption um die Verschlüsselung auszuschalten", + "commandHint_create": "Erstelle ein leeren Gruppenchat\nBenutze --no-encryption, um die Verschlüsselung auszuschalten", "@commandHint_create": { "type": "text", "description": "Usage hint for the command /create" @@ -2013,7 +2013,7 @@ "@videoCallsBetaWarning": {}, "emailOrUsername": "E-Mail oder Benutzername", "@emailOrUsername": {}, - "unsupportedAndroidVersionLong": "Diese Funktion erfordert eine neuere Android-Version. Bitte suche nach Updates oder Lineage OS-Unterstützung.", + "unsupportedAndroidVersionLong": "Diese Funktion erfordert eine neuere Android-Version. Bitte suche nach Updates oder prüfe die Lineage-OS-Unterstützung.", "@unsupportedAndroidVersionLong": {}, "experimentalVideoCalls": "Experimentelle Videoanrufe", "@experimentalVideoCalls": {}, @@ -2151,7 +2151,7 @@ "@saveKeyManuallyDescription": {}, "hydrateTorLong": "Hast du deine Sitzung das letzte Mal auf TOR exportiert? Importiere sie schnell und chatte weiter.", "@hydrateTorLong": {}, - "pleaseEnterRecoveryKey": "Bitte gebe deinen Wiederherstellungsschlüssel ein:", + "pleaseEnterRecoveryKey": "Bitte gib deinen Wiederherstellungsschlüssel ein:", "@pleaseEnterRecoveryKey": {}, "countFiles": "{count} Dateien", "@countFiles": { @@ -2265,7 +2265,7 @@ "senderName": {} } }, - "commandHint_googly": "Googly Eyes senden", + "commandHint_googly": "Glupschaugen senden", "@commandHint_googly": {}, "disableEncryptionWarning": "Aus Sicherheitsgründen können Sie die Verschlüsselung in einem Chat nicht deaktivieren, wo sie zuvor aktiviert wurde.", "@disableEncryptionWarning": {}, @@ -2273,7 +2273,7 @@ "@reopenChat": {}, "fileIsTooBigForServer": "Der Server meldet, dass die Datei zu groß ist für eine Übermittlung ist.", "@fileIsTooBigForServer": {}, - "noBackupWarning": "Achtung! Ohne Aktivierung des Chat-Backups verlierst du den Zugriff auf deine verschlüsselten Nachrichten. Vor dem Ausloggen wird dringend empfohlen den Chat-Backup zu aktivieren.", + "noBackupWarning": "Achtung! Ohne Aktivierung des Chat-Backups verlierst du den Zugriff auf deine verschlüsselten Nachrichten. Vor dem Ausloggen wird dringend empfohlen, das Chat-Backup zu aktivieren.", "@noBackupWarning": {}, "noOtherDevicesFound": "Keine anderen Geräte anwesend", "@noOtherDevicesFound": {}, @@ -2589,7 +2589,7 @@ "sender": {} } }, - "verifyOtherDeviceDescription": "Wenn Sie ein anderes Gerät verifizieren, können diese Geräteschlüssel austauschen, was Ihre Sicherheit insgesamt erhöht. 💪 Wenn Sie eine Verifizierung starten, erscheint ein Pop-up in der App auf beiden Geräten. Dort sehen Sie dann eine Reihe von Emojis oder Zahlen, die Sie miteinander vergleichen müssen. Am besten hältst du beide Geräte bereit, bevor du die Verifizierung startest. 🤳", + "verifyOtherDeviceDescription": "Wenn Sie ein anderes Gerät verifizieren, können diese Geräteschlüssel austauschen, was Ihre Sicherheit insgesamt erhöht. 💪 Wenn Sie eine Verifizierung starten, erscheint ein Pop-up in der App auf beiden Geräten. Dort sehen Sie dann eine Reihe von Emojis oder Zahlen, die Sie miteinander vergleichen müssen. Am besten halten Sie beide Geräte bereit, bevor Sie die Verifizierung starten. 🤳", "@verifyOtherDeviceDescription": {}, "presenceStyle": "Statusmeldungen:", "@presenceStyle": { @@ -2727,5 +2727,15 @@ "space": {} }, "markAsUnread": "Als ungelesen markieren", - "@markAsUnread": {} + "@markAsUnread": {}, + "swipeRightToLeftToReply": "Wische von rechts nach links zum Antworten", + "@swipeRightToLeftToReply": {}, + "countChatsAndCountParticipants": "{chats} Chats und {participants} Teilnehmer", + "@countChatsAndCountParticipants": { + "type": "text", + "placeholders": { + "chats": {}, + "participants": {} + } + } } From 18198d41c25e15fcfd5f74604e00701ef11d58f6 Mon Sep 17 00:00:00 2001 From: xabirequejo Date: Mon, 22 Jul 2024 08:57:58 +0000 Subject: [PATCH 106/288] Translated using Weblate (Basque) Currently translated at 100.0% (642 of 642 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/ --- assets/l10n/intl_eu.arb | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/assets/l10n/intl_eu.arb b/assets/l10n/intl_eu.arb index cae8a51242..6f72715e21 100644 --- a/assets/l10n/intl_eu.arb +++ b/assets/l10n/intl_eu.arb @@ -23,7 +23,7 @@ "type": "text", "placeholders": {} }, - "activatedEndToEndEncryption": "🔐 {username}(e)k ertzetik ertzerako zifraketa gaitu du", + "activatedEndToEndEncryption": "🔐 {username}(e)k ertzetik ertzerako zifratzea gaitu du", "@activatedEndToEndEncryption": { "type": "text", "placeholders": { @@ -226,7 +226,7 @@ "type": "text", "placeholders": {} }, - "channelCorruptedDecryptError": "Zifraketa hondatu egin da", + "channelCorruptedDecryptError": "Zifratzea hondatu egin da", "@channelCorruptedDecryptError": { "type": "text", "placeholders": {} @@ -412,17 +412,17 @@ "type": "text", "placeholders": {} }, - "enableEncryptionWarning": "Ezingo duzu zifraketa ezgaitu. Ziur zaude?", + "enableEncryptionWarning": "Ezingo duzu zifratzea ezgaitu. Ziur zaude?", "@enableEncryptionWarning": { "type": "text", "placeholders": {} }, - "encryption": "Zifraketa", + "encryption": "Zifratzea", "@encryption": { "type": "text", "placeholders": {} }, - "encryptionNotEnabled": "Zifraketa ez dago gaituta", + "encryptionNotEnabled": "Zifratzea ez dago gaituta", "@encryptionNotEnabled": { "type": "text", "placeholders": {} @@ -666,7 +666,7 @@ "type": "text", "placeholders": {} }, - "needPantalaimonWarning": "Kontuan izan oraingoz Pantalaimon behar duzula puntuz puntuko zifraketarako.", + "needPantalaimonWarning": "Kontuan izan oraingoz Pantalaimon behar duzula ertzetik ertzerako zifratzerako.", "@needPantalaimonWarning": { "type": "text", "placeholders": {} @@ -1036,7 +1036,7 @@ "type": "text", "placeholders": {} }, - "unknownEncryptionAlgorithm": "Zifraketa-algoritmo ezezaguna", + "unknownEncryptionAlgorithm": "Zifratze-algoritmo ezezaguna", "@unknownEncryptionAlgorithm": { "type": "text", "placeholders": {} @@ -1649,7 +1649,7 @@ "type": "text", "placeholders": {} }, - "enableEncryption": "Gaitu zifraketa", + "enableEncryption": "Gaitu zifratzea", "@enableEncryption": { "type": "text", "placeholders": {} @@ -1841,7 +1841,7 @@ "type": "text", "placeholders": {} }, - "noEncryptionForPublicRooms": "Zifraketa aktiba dezakezu soilik gelak publikoa izateari utzi badio.", + "noEncryptionForPublicRooms": "Zifratzea aktiba dezakezu soilik gelak publikoa izateari utzi badio.", "@noEncryptionForPublicRooms": { "type": "text", "placeholders": {} @@ -2238,7 +2238,7 @@ "@startFirstChat": {}, "newSpaceDescription": "Guneek txatak taldekatzea ahalbidetzen dute eta komunitate pribatu edo publikoak osatzea.", "@newSpaceDescription": {}, - "disableEncryptionWarning": "Segurtasun arrazoiak direla-eta, ezin duzu lehendik zifratuta zegoen txat bateko zifraketa ezgaitu.", + "disableEncryptionWarning": "Segurtasun arrazoiak direla-eta, ezin duzu lehendik zifratuta zegoen txat bateko zifratzea ezgaitu.", "@disableEncryptionWarning": {}, "encryptThisChat": "Zifratu txata", "@encryptThisChat": {}, @@ -2520,7 +2520,7 @@ }, "transparent": "Gardena", "@transparent": {}, - "sendReadReceipts": "Bidali irakurri izanaren adierazlea", + "sendReadReceipts": "Bidali irakurri izanaren agiria", "@sendReadReceipts": {}, "formattedMessages": "Formatua duten mezuak", "@formattedMessages": {}, From e9141d5753f70cacdfce1ebecfaf23486ff9675f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jos=C3=A9=20m?= Date: Wed, 24 Jul 2024 04:49:49 +0000 Subject: [PATCH 107/288] Translated using Weblate (Galician) Currently translated at 100.0% (642 of 642 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/gl/ --- assets/l10n/intl_gl.arb | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_gl.arb b/assets/l10n/intl_gl.arb index 8a7ef79225..eefbbeaf9a 100644 --- a/assets/l10n/intl_gl.arb +++ b/assets/l10n/intl_gl.arb @@ -2712,5 +2712,30 @@ "alwaysUse24HourFormat": "falso", "@alwaysUse24HourFormat": { "description": "Set to true to always display time of day in 24 hour format." - } + }, + "noMoreChatsFound": "Non se atopan máis chats…", + "@noMoreChatsFound": {}, + "joinedChats": "Chats nos que participas", + "@joinedChats": {}, + "countChatsAndCountParticipants": "{chats} chats e {participants} participantes", + "@countChatsAndCountParticipants": { + "type": "text", + "placeholders": { + "chats": {}, + "participants": {} + } + }, + "unread": "Sen ler", + "@unread": {}, + "space": "Espazo", + "@space": {}, + "spaces": "Espazos", + "@spaces": {}, + "goToSpace": "Ir ao espazo: {space}", + "@goToSpace": { + "type": "text", + "space": {} + }, + "markAsUnread": "Marcar como non lido", + "@markAsUnread": {} } From 22ff4bd25a1630ee66909535fb18deeccfa359ba Mon Sep 17 00:00:00 2001 From: Guacamolie Date: Wed, 24 Jul 2024 09:32:00 +0000 Subject: [PATCH 108/288] Translated using Weblate (Dutch) Currently translated at 83.4% (536 of 642 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/nl/ --- assets/l10n/intl_nl.arb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_nl.arb b/assets/l10n/intl_nl.arb index 67d79c2b53..8453e47dbe 100644 --- a/assets/l10n/intl_nl.arb +++ b/assets/l10n/intl_nl.arb @@ -2417,5 +2417,9 @@ "pleaseEnterANumber": "Vul een getal in groter dan 0", "@pleaseEnterANumber": {}, "kickUserDescription": "De persoon is verwijderd uit de chat, maar is niet verbannen. In publieke chats kan de persoon op elk moment opnieuw deelnemen.", - "@kickUserDescription": {} + "@kickUserDescription": {}, + "alwaysUse24HourFormat": "true", + "@alwaysUse24HourFormat": { + "description": "Set to true to always display time of day in 24 hour format." + } } From 563f2fac82e8ce8bc1b245058714afffef58d3a3 Mon Sep 17 00:00:00 2001 From: Anonymous Date: Thu, 25 Jul 2024 05:09:38 +0000 Subject: [PATCH 109/288] Translated using Weblate (Czech) Currently translated at 78.6% (513 of 652 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/cs/ --- assets/l10n/intl_cs.arb | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/assets/l10n/intl_cs.arb b/assets/l10n/intl_cs.arb index c6a4820c40..34549bee4d 100644 --- a/assets/l10n/intl_cs.arb +++ b/assets/l10n/intl_cs.arb @@ -2349,48 +2349,11 @@ "count": {} } }, - "@reportErrorDescription": {}, - "@banUserDescription": {}, - "@removeDevicesDescription": {}, - "@unbanUserDescription": {}, - "@pushNotificationsNotAvailable": {}, - "@makeAdminDescription": {}, - "@archiveRoomDescription": {}, - "@invalidInput": {}, - "@report": {}, - "@hasKnocked": { - "placeholders": { - "user": {} - } - }, - "@wrongPinEntered": { - "type": "text", - "placeholders": { - "seconds": {} - } - }, - "@inviteGroupChat": {}, - "@invitePrivateChat": {}, - "@learnMore": {}, - "@roomUpgradeDescription": {}, - "@pleaseEnterANumber": {}, "emoteKeyboardNoRecents": "Naposledy použité emoce se zobrazí zde...", "@emoteKeyboardNoRecents": { "type": "text", "placeholders": {} }, - "@kickUserDescription": {}, - "@invite": {}, - "@indexedDbErrorLong": {}, - "@callingAccount": {}, - "@enterSpace": {}, - "@noKeyForThisMessage": {}, - "@readUpToHere": {}, - "@appearOnTopDetails": {}, - "@enterRoom": {}, - "@hideUnimportantStateEvents": {}, - "@noBackupWarning": {}, - "@indexedDbErrorTitle": {}, "appLockDescription": "Zamknout aplikaci pomocí PIN kódu když není používána", "@appLockDescription": {}, "globalChatId": "Globální ID chatu", From 8079f65755ed71d8cf1dd5fc1d6e09fa7b534ac3 Mon Sep 17 00:00:00 2001 From: Anonymous Date: Thu, 25 Jul 2024 05:09:39 +0000 Subject: [PATCH 110/288] Translated using Weblate (Spanish) Currently translated at 74.5% (486 of 652 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/es/ --- assets/l10n/intl_es.arb | 4695 +++++++++++++++++++-------------------- 1 file changed, 2317 insertions(+), 2378 deletions(-) diff --git a/assets/l10n/intl_es.arb b/assets/l10n/intl_es.arb index 88e9649c92..f11a7a5e7e 100644 --- a/assets/l10n/intl_es.arb +++ b/assets/l10n/intl_es.arb @@ -1,2380 +1,2319 @@ { - "@@locale": "es", - "@@last_modified": "2021-08-14 12:41:10.097243", - "about": "Acerca de", - "@about": { - "type": "text", - "placeholders": {} - }, - "accept": "Aceptar", - "@accept": { - "type": "text", - "placeholders": {} - }, - "acceptedTheInvitation": "{username} aceptó la invitación", - "@acceptedTheInvitation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "account": "Cuenta", - "@account": { - "type": "text", - "placeholders": {} - }, - "activatedEndToEndEncryption": "{username} activó el cifrado de extremo a extremo", - "@activatedEndToEndEncryption": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "addEmail": "Añadir dirección de correo", - "@addEmail": { - "type": "text", - "placeholders": {} - }, - "admin": "Administrador", - "@admin": { - "type": "text", - "placeholders": {} - }, - "alias": "alias", - "@alias": { - "type": "text", - "placeholders": {} - }, - "answeredTheCall": "{senderName} respondió a la llamada", - "@answeredTheCall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "anyoneCanJoin": "Cualquiera puede unirse", - "@anyoneCanJoin": { - "type": "text", - "placeholders": {} - }, - "archive": "Archivo", - "@archive": { - "type": "text", - "placeholders": {} - }, - "areGuestsAllowedToJoin": "¿Pueden unirse los usuarios visitantes?", - "@areGuestsAllowedToJoin": { - "type": "text", - "placeholders": {} - }, - "areYouSure": "¿Estás seguro?", - "@areYouSure": { - "type": "text", - "placeholders": {} - }, - "areYouSureYouWantToLogout": "¿Confirma que quiere cerrar sesión?", - "@areYouSureYouWantToLogout": { - "type": "text", - "placeholders": {} - }, - "askSSSSSign": "Para poder confirmar a la otra persona, ingrese su contraseña de almacenamiento segura o la clave de recuperación.", - "@askSSSSSign": { - "type": "text", - "placeholders": {} - }, - "askVerificationRequest": "¿Aceptar esta solicitud de verificación de {username}?", - "@askVerificationRequest": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "badServerLoginTypesException": "El servidor soporta los siguientes mecanismos para autenticación:\n{serverVersions}\npero esta aplicación sólo soporta:\n{supportedVersions}", - "@badServerLoginTypesException": { - "type": "text", - "placeholders": { - "serverVersions": {}, - "supportedVersions": {} - } - }, - "badServerVersionsException": "El servidor soporta las siguientes versiones de la especificación:\n{serverVersions}\npero esta aplicación sólo soporta las versiones {supportedVersions}", - "@badServerVersionsException": { - "type": "text", - "placeholders": { - "serverVersions": {}, - "supportedVersions": {} - } - }, - "banFromChat": "Vetar del chat", - "@banFromChat": { - "type": "text", - "placeholders": {} - }, - "banned": "Vetado", - "@banned": { - "type": "text", - "placeholders": {} - }, - "bannedUser": "{username} vetó a {targetName}", - "@bannedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "blockDevice": "Bloquear dispositivo", - "@blockDevice": { - "type": "text", - "placeholders": {} - }, - "blocked": "Bloqueado", - "@blocked": { - "type": "text", - "placeholders": {} - }, - "cancel": "Cancelar", - "@cancel": { - "type": "text", - "placeholders": {} - }, - "changeDeviceName": "Cambiar el nombre del dispositivo", - "@changeDeviceName": { - "type": "text", - "placeholders": {} - }, - "changedTheChatAvatar": "{username} cambió el icono del chat", - "@changedTheChatAvatar": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheChatDescriptionTo": "{username} cambió la descripción del chat a: '{description}'", - "@changedTheChatDescriptionTo": { - "type": "text", - "placeholders": { - "username": {}, - "description": {} - } - }, - "changedTheChatNameTo": "{username} cambió el nombre del chat a: '{chatname}'", - "@changedTheChatNameTo": { - "type": "text", - "placeholders": { - "username": {}, - "chatname": {} - } - }, - "changedTheChatPermissions": "{username} cambió los permisos del chat", - "@changedTheChatPermissions": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheDisplaynameTo": "{username} cambió su nombre visible a: {displayname}", - "@changedTheDisplaynameTo": { - "type": "text", - "placeholders": { - "username": {}, - "displayname": {} - } - }, - "changedTheGuestAccessRules": "{username} cambió las reglas de acceso de visitantes", - "@changedTheGuestAccessRules": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheGuestAccessRulesTo": "{username} cambió las reglas de acceso de visitantes a: {rules}", - "@changedTheGuestAccessRulesTo": { - "type": "text", - "placeholders": { - "username": {}, - "rules": {} - } - }, - "changedTheHistoryVisibility": "{username} cambió la visibilidad del historial", - "@changedTheHistoryVisibility": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheHistoryVisibilityTo": "{username} cambió la visibilidad del historial a: {rules}", - "@changedTheHistoryVisibilityTo": { - "type": "text", - "placeholders": { - "username": {}, - "rules": {} - } - }, - "changedTheJoinRules": "{username} cambió las reglas de ingreso", - "@changedTheJoinRules": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheJoinRulesTo": "{username} cambió las reglas de ingreso a {joinRules}", - "@changedTheJoinRulesTo": { - "type": "text", - "placeholders": { - "username": {}, - "joinRules": {} - } - }, - "changedTheProfileAvatar": "{username} cambió su imagen de perfil", - "@changedTheProfileAvatar": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheRoomAliases": "{username} cambió el alias de la sala", - "@changedTheRoomAliases": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheRoomInvitationLink": "{username} cambió el enlace de invitación", - "@changedTheRoomInvitationLink": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changePassword": "Cambiar la contraseña", - "@changePassword": { - "type": "text", - "placeholders": {} - }, - "changeTheHomeserver": "Cambiar el servidor", - "@changeTheHomeserver": { - "type": "text", - "placeholders": {} - }, - "changeTheme": "Cambia tu estilo", - "@changeTheme": { - "type": "text", - "placeholders": {} - }, - "changeTheNameOfTheGroup": "Cambiar el nombre del grupo", - "@changeTheNameOfTheGroup": { - "type": "text", - "placeholders": {} - }, - "channelCorruptedDecryptError": "El cifrado se ha corrompido", - "@channelCorruptedDecryptError": { - "type": "text", - "placeholders": {} - }, - "chat": "Chat", - "@chat": { - "type": "text", - "placeholders": {} - }, - "chatBackup": "Copia de respaldo del chat", - "@chatBackup": { - "type": "text", - "placeholders": {} - }, - "chatBackupDescription": "La copia de respaldo del chat está protegida por una clave de seguridad. Procure no perderla.", - "@chatBackupDescription": { - "type": "text", - "placeholders": {} - }, - "chatDetails": "Detalles del chat", - "@chatDetails": { - "type": "text", - "placeholders": {} - }, - "chats": "Conversaciones", - "@chats": { - "type": "text", - "placeholders": {} - }, - "chooseAStrongPassword": "Elija una contraseña segura", - "@chooseAStrongPassword": { - "type": "text", - "placeholders": {} - }, - "clearArchive": "Borrar archivo", - "@clearArchive": {}, - "close": "Cerrar", - "@close": { - "type": "text", - "placeholders": {} - }, - "compareEmojiMatch": "Por favor compare los emojis", - "@compareEmojiMatch": { - "type": "text", - "placeholders": {} - }, - "compareNumbersMatch": "Por favor compare los números", - "@compareNumbersMatch": { - "type": "text", - "placeholders": {} - }, - "confirm": "Confirmar", - "@confirm": { - "type": "text", - "placeholders": {} - }, - "connect": "Conectar", - "@connect": { - "type": "text", - "placeholders": {} - }, - "contactHasBeenInvitedToTheGroup": "El contacto ha sido invitado al grupo", - "@contactHasBeenInvitedToTheGroup": { - "type": "text", - "placeholders": {} - }, - "copiedToClipboard": "Copiado al portapapeles", - "@copiedToClipboard": { - "type": "text", - "placeholders": {} - }, - "copy": "Copiar", - "@copy": { - "type": "text", - "placeholders": {} - }, - "copyToClipboard": "Copiar al portapapeles", - "@copyToClipboard": { - "type": "text", - "placeholders": {} - }, - "couldNotDecryptMessage": "No se pudo descifrar el mensaje: {error}", - "@couldNotDecryptMessage": { - "type": "text", - "placeholders": { - "error": {} - } - }, - "countParticipants": "{count} participantes", - "@countParticipants": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "create": "Crear", - "@create": { - "type": "text", - "placeholders": {} - }, - "createdTheChat": "💬{username} creó el chat", - "@createdTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "currentlyActive": "Actualmente activo", - "@currentlyActive": { - "type": "text", - "placeholders": {} - }, - "darkTheme": "Oscuro", - "@darkTheme": { - "type": "text", - "placeholders": {} - }, - "dateAndTimeOfDay": "{date}, {timeOfDay}", - "@dateAndTimeOfDay": { - "type": "text", - "placeholders": { - "date": {}, - "timeOfDay": {} - } - }, - "dateWithoutYear": "{day}-{month}", - "@dateWithoutYear": { - "type": "text", - "placeholders": { - "month": {}, - "day": {} - } - }, - "dateWithYear": "{day}-{month}-{year}", - "@dateWithYear": { - "type": "text", - "placeholders": { - "year": {}, - "month": {}, - "day": {} - } - }, - "deactivateAccountWarning": "Se desactivará su cuenta de usuario. ¡La operación no se puede cancelar! ¿Está seguro?", - "@deactivateAccountWarning": { - "type": "text", - "placeholders": {} - }, - "delete": "Eliminar", - "@delete": { - "type": "text", - "placeholders": {} - }, - "deleteAccount": "Cancelar cuenta", - "@deleteAccount": { - "type": "text", - "placeholders": {} - }, - "deleteMessage": "Eliminar mensaje", - "@deleteMessage": { - "type": "text", - "placeholders": {} - }, - "device": "Dispositivo", - "@device": { - "type": "text", - "placeholders": {} - }, - "devices": "Dispositivos", - "@devices": { - "type": "text", - "placeholders": {} - }, - "displaynameHasBeenChanged": "El nombre visible ha cambiado", - "@displaynameHasBeenChanged": { - "type": "text", - "placeholders": {} - }, - "downloadFile": "Descargar archivo", - "@downloadFile": { - "type": "text", - "placeholders": {} - }, - "editDisplayname": "Editar nombre visible", - "@editDisplayname": { - "type": "text", - "placeholders": {} - }, - "editRoomAliases": "Editar alias de la sala", - "@editRoomAliases": { - "type": "text", - "placeholders": {} - }, - "emoteExists": "¡El emote ya existe!", - "@emoteExists": { - "type": "text", - "placeholders": {} - }, - "emoteInvalid": "¡El atajo del emote es inválido!", - "@emoteInvalid": { - "type": "text", - "placeholders": {} - }, - "emotePacks": "Paquetes de emoticonos para la habitación", - "@emotePacks": { - "type": "text", - "placeholders": {} - }, - "emoteSettings": "Configuración de emotes", - "@emoteSettings": { - "type": "text", - "placeholders": {} - }, - "emoteShortcode": "Atajo de emote", - "@emoteShortcode": { - "type": "text", - "placeholders": {} - }, - "emoteWarnNeedToPick": "¡Debes elegir un atajo de emote y una imagen!", - "@emoteWarnNeedToPick": { - "type": "text", - "placeholders": {} - }, - "emptyChat": "Chat vacío", - "@emptyChat": { - "type": "text", - "placeholders": {} - }, - "enableEmotesGlobally": "Habilitar paquete de emoticonos a nivel general", - "@enableEmotesGlobally": { - "type": "text", - "placeholders": {} - }, - "enableEncryptionWarning": "Ya no podrá deshabilitar el cifrado. ¿Estás seguro?", - "@enableEncryptionWarning": { - "type": "text", - "placeholders": {} - }, - "encryption": "Cifrado", - "@encryption": { - "type": "text", - "placeholders": {} - }, - "encryptionNotEnabled": "El cifrado no está habilitado", - "@encryptionNotEnabled": { - "type": "text", - "placeholders": {} - }, - "endedTheCall": "{senderName} terminó la llamada", - "@endedTheCall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "enterAnEmailAddress": "Introducir una dirección de correo electrónico", - "@enterAnEmailAddress": { - "type": "text", - "placeholders": {} - }, - "enterYourHomeserver": "Ingrese su servidor", - "@enterYourHomeserver": { - "type": "text", - "placeholders": {} - }, - "everythingReady": "¡Todo listo!", - "@everythingReady": { - "type": "text", - "placeholders": {} - }, - "fileName": "Nombre del archivo", - "@fileName": { - "type": "text", - "placeholders": {} - }, - "fluffychat": "FluffyChat", - "@fluffychat": { - "type": "text", - "placeholders": {} - }, - "forward": "Reenviar", - "@forward": { - "type": "text", - "placeholders": {} - }, - "fromJoining": "Desde que se unió", - "@fromJoining": { - "type": "text", - "placeholders": {} - }, - "fromTheInvitation": "Desde la invitación", - "@fromTheInvitation": { - "type": "text", - "placeholders": {} - }, - "group": "Grupo", - "@group": { - "type": "text", - "placeholders": {} - }, - "groupIsPublic": "El grupo es público", - "@groupIsPublic": { - "type": "text", - "placeholders": {} - }, - "groupWith": "Grupo con {displayname}", - "@groupWith": { - "type": "text", - "placeholders": { - "displayname": {} - } - }, - "guestsAreForbidden": "Los visitantes están prohibidos", - "@guestsAreForbidden": { - "type": "text", - "placeholders": {} - }, - "guestsCanJoin": "Los visitantes pueden unirse", - "@guestsCanJoin": { - "type": "text", - "placeholders": {} - }, - "hasWithdrawnTheInvitationFor": "{username} ha retirado la invitación para {targetName}", - "@hasWithdrawnTheInvitationFor": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "help": "Ayuda", - "@help": { - "type": "text", - "placeholders": {} - }, - "hideRedactedEvents": "Ocultar sucesos censurados", - "@hideRedactedEvents": { - "type": "text", - "placeholders": {} - }, - "hideUnknownEvents": "Ocultar sucesos desconocidos", - "@hideUnknownEvents": { - "type": "text", - "placeholders": {} - }, - "id": "Identificación", - "@id": { - "type": "text", - "placeholders": {} - }, - "identity": "Identidad", - "@identity": { - "type": "text", - "placeholders": {} - }, - "ignoredUsers": "Usuarios ignorados", - "@ignoredUsers": { - "type": "text", - "placeholders": {} - }, - "incorrectPassphraseOrKey": "Frase de contraseña o clave de recuperación incorrecta", - "@incorrectPassphraseOrKey": { - "type": "text", - "placeholders": {} - }, - "inviteContact": "Invitar contacto", - "@inviteContact": { - "type": "text", - "placeholders": {} - }, - "inviteContactToGroup": "Invitar contacto a {groupName}", - "@inviteContactToGroup": { - "type": "text", - "placeholders": { - "groupName": {} - } - }, - "invited": "Invitado", - "@invited": { - "type": "text", - "placeholders": {} - }, - "invitedUser": "📩{username} invitó a {targetName}", - "@invitedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "invitedUsersOnly": "Sólo usuarios invitados", - "@invitedUsersOnly": { - "type": "text", - "placeholders": {} - }, - "inviteText": "{username} te invitó a FluffyChat.\n1. Instale FluffyChat: https://fluffychat.im\n2. Regístrate o inicia sesión \n3. Abra el enlace de invitación: {link}", - "@inviteText": { - "type": "text", - "placeholders": { - "username": {}, - "link": {} - } - }, - "isTyping": "está escribiendo…", - "@isTyping": { - "type": "text", - "placeholders": {} - }, - "joinedTheChat": "👋{username} se unió al chat", - "@joinedTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "joinRoom": "Unirse a la sala", - "@joinRoom": { - "type": "text", - "placeholders": {} - }, - "kicked": "👞{username} echó a {targetName}", - "@kicked": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "kickedAndBanned": "🙅{username} echó y vetó a {targetName}", - "@kickedAndBanned": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "kickFromChat": "Echar del chat", - "@kickFromChat": { - "type": "text", - "placeholders": {} - }, - "lastActiveAgo": "Última vez activo: {localizedTimeShort}", - "@lastActiveAgo": { - "type": "text", - "placeholders": { - "localizedTimeShort": {} - } - }, - "leave": "Abandonar", - "@leave": { - "type": "text", - "placeholders": {} - }, - "leftTheChat": "Abandonó el chat", - "@leftTheChat": { - "type": "text", - "placeholders": {} - }, - "license": "Licencia", - "@license": { - "type": "text", - "placeholders": {} - }, - "lightTheme": "Claro", - "@lightTheme": { - "type": "text", - "placeholders": {} - }, - "loadCountMoreParticipants": "Mostrar {count} participantes más", - "@loadCountMoreParticipants": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "loadingPleaseWait": "Cargando… Por favor espere.", - "@loadingPleaseWait": { - "type": "text", - "placeholders": {} - }, - "loadMore": "Mostrar más…", - "@loadMore": { - "type": "text", - "placeholders": {} - }, - "login": "Acceso", - "@login": { - "type": "text", - "placeholders": {} - }, - "logInTo": "Iniciar sesión en {homeserver}", - "@logInTo": { - "type": "text", - "placeholders": { - "homeserver": {} - } - }, - "logout": "Cerrar sesión", - "@logout": { - "type": "text", - "placeholders": {} - }, - "mention": "Mencionar", - "@mention": { - "type": "text", - "placeholders": {} - }, - "moderator": "Moderador", - "@moderator": { - "type": "text", - "placeholders": {} - }, - "muteChat": "Silenciar chat", - "@muteChat": { - "type": "text", - "placeholders": {} - }, - "needPantalaimonWarning": "Tenga en cuenta que necesita Pantalaimon para utilizar el cifrado de extremo a extremo por ahora.", - "@needPantalaimonWarning": { - "type": "text", - "placeholders": {} - }, - "newMessageInFluffyChat": "Nuevo mensaje en FluffyChat", - "@newMessageInFluffyChat": { - "type": "text", - "placeholders": {} - }, - "newVerificationRequest": "¡Nueva solicitud de verificación!", - "@newVerificationRequest": { - "type": "text", - "placeholders": {} - }, - "next": "Siguiente", - "@next": { - "type": "text", - "placeholders": {} - }, - "no": "No", - "@no": { - "type": "text", - "placeholders": {} - }, - "noEmotesFound": "Ningún emote encontrado. 😕", - "@noEmotesFound": { - "type": "text", - "placeholders": {} - }, - "noEncryptionForPublicRooms": "Sólo se puede activar el cifrado en cuanto la sala deja de ser de acceso público.", - "@noEncryptionForPublicRooms": { - "type": "text", - "placeholders": {} - }, - "noGoogleServicesWarning": "Parece que no tienes servicios de Google en tu teléfono. ¡Esa es una buena decisión para tu privacidad! Para recibir notificaciones instantáneas en FluffyChat, recomendamos usar microG: https://microg.org/", - "@noGoogleServicesWarning": { - "type": "text", - "placeholders": {} - }, - "none": "Ninguno", - "@none": { - "type": "text", - "placeholders": {} - }, - "noPermission": "Sin autorización", - "@noPermission": { - "type": "text", - "placeholders": {} - }, - "noRoomsFound": "Ninguna sala encontrada…", - "@noRoomsFound": { - "type": "text", - "placeholders": {} - }, - "offline": "Desconectado", - "@offline": { - "type": "text", - "placeholders": {} - }, - "ok": "Ok", - "@ok": { - "type": "text", - "placeholders": {} - }, - "online": "Conectado", - "@online": { - "type": "text", - "placeholders": {} - }, - "onlineKeyBackupEnabled": "La copia de seguridad de la clave en línea está habilitada", - "@onlineKeyBackupEnabled": { - "type": "text", - "placeholders": {} - }, - "oopsSomethingWentWrong": "Ups, algo salió mal…", - "@oopsSomethingWentWrong": { - "type": "text", - "placeholders": {} - }, - "openAppToReadMessages": "Abrir la aplicación para leer los mensajes", - "@openAppToReadMessages": { - "type": "text", - "placeholders": {} - }, - "openCamera": "Abrir cámara", - "@openCamera": { - "type": "text", - "placeholders": {} - }, - "passphraseOrKey": "contraseña o clave de recuperación", - "@passphraseOrKey": { - "type": "text", - "placeholders": {} - }, - "password": "Contraseña", - "@password": { - "type": "text", - "placeholders": {} - }, - "passwordHasBeenChanged": "La contraseña ha sido cambiada", - "@passwordHasBeenChanged": { - "type": "text", - "placeholders": {} - }, - "people": "Personas", - "@people": { - "type": "text", - "placeholders": {} - }, - "pickImage": "Elegir imagen", - "@pickImage": { - "type": "text", - "placeholders": {} - }, - "pin": "Pin", - "@pin": { - "type": "text", - "placeholders": {} - }, - "play": "Reproducir {fileName}", - "@play": { - "type": "text", - "placeholders": { - "fileName": {} - } - }, - "pleaseClickOnLink": "Haga clic en el enlace del correo electrónico y luego continúe.", - "@pleaseClickOnLink": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourPassword": "Por favor ingrese su contraseña", - "@pleaseEnterYourPassword": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourUsername": "Por favor ingrese su nombre de usuario", - "@pleaseEnterYourUsername": { - "type": "text", - "placeholders": {} - }, - "pleaseFollowInstructionsOnWeb": "Por favor, siga las instrucciones del sitio web y presione \"siguiente\".", - "@pleaseFollowInstructionsOnWeb": { - "type": "text", - "placeholders": {} - }, - "privacy": "Privacidad", - "@privacy": { - "type": "text", - "placeholders": {} - }, - "publicRooms": "Salas públicas", - "@publicRooms": { - "type": "text", - "placeholders": {} - }, - "recording": "Grabando", - "@recording": { - "type": "text", - "placeholders": {} - }, - "redactedAnEvent": "{username} censuró un suceso", - "@redactedAnEvent": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "reject": "Rechazar", - "@reject": { - "type": "text", - "placeholders": {} - }, - "rejectedTheInvitation": "{username} rechazó la invitación", - "@rejectedTheInvitation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "rejoin": "Volver a unirse", - "@rejoin": { - "type": "text", - "placeholders": {} - }, - "remove": "Eliminar", - "@remove": { - "type": "text", - "placeholders": {} - }, - "removeAllOtherDevices": "Eliminar todos los otros dispositivos", - "@removeAllOtherDevices": { - "type": "text", - "placeholders": {} - }, - "removedBy": "Eliminado por {username}", - "@removedBy": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "removeDevice": "Eliminar dispositivo", - "@removeDevice": { - "type": "text", - "placeholders": {} - }, - "unbanFromChat": "Eliminar la expulsión", - "@unbanFromChat": { - "type": "text", - "placeholders": {} - }, - "renderRichContent": "Mostrar el contenido con mensajes enriquecidos", - "@renderRichContent": { - "type": "text", - "placeholders": {} - }, - "reply": "Responder", - "@reply": { - "type": "text", - "placeholders": {} - }, - "requestPermission": "Solicitar permiso", - "@requestPermission": { - "type": "text", - "placeholders": {} - }, - "roomHasBeenUpgraded": "La sala ha subido de categoría", - "@roomHasBeenUpgraded": { - "type": "text", - "placeholders": {} - }, - "roomVersion": "Versión de sala", - "@roomVersion": { - "type": "text", - "placeholders": {} - }, - "seenByUser": "Visto por {username}", - "@seenByUser": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "send": "Enviar", - "@send": { - "type": "text", - "placeholders": {} - }, - "sendAMessage": "Enviar un mensaje", - "@sendAMessage": { - "type": "text", - "placeholders": {} - }, - "sendAudio": "Enviar audio", - "@sendAudio": { - "type": "text", - "placeholders": {} - }, - "sendFile": "Enviar un archivo", - "@sendFile": { - "type": "text", - "placeholders": {} - }, - "sendImage": "Enviar una imagen", - "@sendImage": { - "type": "text", - "placeholders": {} - }, - "sendOriginal": "Enviar el original", - "@sendOriginal": { - "type": "text", - "placeholders": {} - }, - "sendVideo": "Enviar video", - "@sendVideo": { - "type": "text", - "placeholders": {} - }, - "sentAFile": "{username} envió un archivo", - "@sentAFile": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentAnAudio": "{username} envió un audio", - "@sentAnAudio": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentAPicture": "{username} envió una imagen", - "@sentAPicture": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentASticker": "{username} envió un sticker", - "@sentASticker": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentAVideo": "{username} envió un video", - "@sentAVideo": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentCallInformations": "{senderName} envió información de la llamada", - "@sentCallInformations": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "setAsCanonicalAlias": "Fijar alias principal", - "@setAsCanonicalAlias": { - "type": "text", - "placeholders": {} - }, - "setInvitationLink": "Establecer enlace de invitación", - "@setInvitationLink": { - "type": "text", - "placeholders": {} - }, - "setStatus": "Establecer estado", - "@setStatus": { - "type": "text", - "placeholders": {} - }, - "settings": "Ajustes", - "@settings": { - "type": "text", - "placeholders": {} - }, - "share": "Compartir", - "@share": { - "type": "text", - "placeholders": {} - }, - "sharedTheLocation": "{username} compartió la ubicación", - "@sharedTheLocation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "showPassword": "Mostrar contraseña", - "@showPassword": { - "type": "text", - "placeholders": {} - }, - "skip": "Omitir", - "@skip": { - "type": "text", - "placeholders": {} - }, - "sourceCode": "Código fuente", - "@sourceCode": { - "type": "text", - "placeholders": {} - }, - "startedACall": "{senderName} comenzó una llamada", - "@startedACall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "statusExampleMessage": "¿Cómo estás hoy?", - "@statusExampleMessage": { - "type": "text", - "placeholders": {} - }, - "submit": "Enviar", - "@submit": { - "type": "text", - "placeholders": {} - }, - "systemTheme": "Sistema", - "@systemTheme": { - "type": "text", - "placeholders": {} - }, - "theyDontMatch": "No coinciden", - "@theyDontMatch": { - "type": "text", - "placeholders": {} - }, - "theyMatch": "Coinciden", - "@theyMatch": { - "type": "text", - "placeholders": {} - }, - "title": "FluffyChat", - "@title": { - "description": "Title for the application", - "type": "text", - "placeholders": {} - }, - "transferFromAnotherDevice": "Transferir desde otro dispositivo", - "@transferFromAnotherDevice": { - "type": "text", - "placeholders": {} - }, - "tryToSendAgain": "Intentar enviar nuevamente", - "@tryToSendAgain": { - "type": "text", - "placeholders": {} - }, - "unavailable": "Indisponible", - "@unavailable": { - "type": "text", - "placeholders": {} - }, - "unbannedUser": "{username} admitió a {targetName} nuevamente", - "@unbannedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "unblockDevice": "Desbloquear dispositivo", - "@unblockDevice": { - "type": "text", - "placeholders": {} - }, - "unknownDevice": "Dispositivo desconocido", - "@unknownDevice": { - "type": "text", - "placeholders": {} - }, - "unknownEncryptionAlgorithm": "Algoritmo de cifrado desconocido", - "@unknownEncryptionAlgorithm": { - "type": "text", - "placeholders": {} - }, - "unknownEvent": "Evento desconocido '{type}'", - "@unknownEvent": { - "type": "text", - "placeholders": { - "type": {} - } - }, - "unmuteChat": "Dejar de silenciar el chat", - "@unmuteChat": { - "type": "text", - "placeholders": {} - }, - "unpin": "Despinchar", - "@unpin": { - "type": "text", - "placeholders": {} - }, - "unreadChats": "{unreadCount, plural, =1{1 chat no leído} other{{unreadCount} chats no leídos}}", - "@unreadChats": { - "type": "text", - "placeholders": { - "unreadCount": {} - } - }, - "userAndOthersAreTyping": "{username} y {count} más están escribiendo…", - "@userAndOthersAreTyping": { - "type": "text", - "placeholders": { - "username": {}, - "count": {} - } - }, - "userAndUserAreTyping": "{username} y {username2} están escribiendo…", - "@userAndUserAreTyping": { - "type": "text", - "placeholders": { - "username": {}, - "username2": {} - } - }, - "userIsTyping": "{username} está escribiendo…", - "@userIsTyping": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "userLeftTheChat": "{username} abandonó el chat", - "@userLeftTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "username": "Nombre de usuario", - "@username": { - "type": "text", - "placeholders": {} - }, - "userSentUnknownEvent": "{username} envió un evento {type}", - "@userSentUnknownEvent": { - "type": "text", - "placeholders": { - "username": {}, - "type": {} - } - }, - "verified": "Verificado", - "@verified": { - "type": "text", - "placeholders": {} - }, - "verify": "Verificar", - "@verify": { - "type": "text", - "placeholders": {} - }, - "verifyStart": "Comenzar verificación", - "@verifyStart": { - "type": "text", - "placeholders": {} - }, - "verifySuccess": "¡Has verificado exitosamente!", - "@verifySuccess": { - "type": "text", - "placeholders": {} - }, - "verifyTitle": "Verificando la otra cuenta", - "@verifyTitle": { - "type": "text", - "placeholders": {} - }, - "videoCall": "Video llamada", - "@videoCall": { - "type": "text", - "placeholders": {} - }, - "visibilityOfTheChatHistory": "Visibilidad del historial del chat", - "@visibilityOfTheChatHistory": { - "type": "text", - "placeholders": {} - }, - "visibleForAllParticipants": "Visible para todos los participantes", - "@visibleForAllParticipants": { - "type": "text", - "placeholders": {} - }, - "visibleForEveryone": "Visible para todo el mundo", - "@visibleForEveryone": { - "type": "text", - "placeholders": {} - }, - "voiceMessage": "Mensaje de voz", - "@voiceMessage": { - "type": "text", - "placeholders": {} - }, - "waitingPartnerAcceptRequest": "Esperando a que el socio acepte la solicitud…", - "@waitingPartnerAcceptRequest": { - "type": "text", - "placeholders": {} - }, - "waitingPartnerEmoji": "Esperando a que el socio acepte los emojis…", - "@waitingPartnerEmoji": { - "type": "text", - "placeholders": {} - }, - "waitingPartnerNumbers": "Esperando a que el socio acepte los números…", - "@waitingPartnerNumbers": { - "type": "text", - "placeholders": {} - }, - "wallpaper": "Fondo de pantalla:", - "@wallpaper": { - "type": "text", - "placeholders": {} - }, - "warning": "¡Advertencia!", - "@warning": { - "type": "text", - "placeholders": {} - }, - "weSentYouAnEmail": "Te enviamos un correo electrónico", - "@weSentYouAnEmail": { - "type": "text", - "placeholders": {} - }, - "whoIsAllowedToJoinThisGroup": "Quién tiene permitido unirse al grupo", - "@whoIsAllowedToJoinThisGroup": { - "type": "text", - "placeholders": {} - }, - "writeAMessage": "Escribe un mensaje…", - "@writeAMessage": { - "type": "text", - "placeholders": {} - }, - "yes": "Sí", - "@yes": { - "type": "text", - "placeholders": {} - }, - "you": "Tú", - "@you": { - "type": "text", - "placeholders": {} - }, - "youAreNoLongerParticipatingInThisChat": "Ya no estás participando en este chat", - "@youAreNoLongerParticipatingInThisChat": { - "type": "text", - "placeholders": {} - }, - "youHaveBeenBannedFromThisChat": "Has sido vetado de este chat", - "@youHaveBeenBannedFromThisChat": { - "type": "text", - "placeholders": {} - }, - "autoplayImages": "Reproducir emoticonos y stickers animados automáticamente", - "@autoplayImages": { - "type": "text", - "placeholder": {} - }, - "allChats": "Todos los chats", - "@allChats": { - "type": "text", - "placeholders": {} - }, - "addToSpace": "Agregar al espacio", - "@addToSpace": {}, - "cantOpenUri": "No puedo abrir el URI {uri}", - "@cantOpenUri": { - "type": "text", - "placeholders": { - "uri": {} - } - }, - "all": "Todo", - "@all": { - "type": "text", - "placeholders": {} - }, - "appLock": "Bloqueo de aplicación", - "@appLock": { - "type": "text", - "placeholders": {} - }, - "botMessages": "Mensajes de bot", - "@botMessages": { - "type": "text", - "placeholders": {} - }, - "oneClientLoggedOut": "Se ha cerrado en la sesión de uno de sus clientes", - "@oneClientLoggedOut": {}, - "addAccount": "Añadir cuenta", - "@addAccount": {}, - "editBundlesForAccount": "Editar paquetes para esta cuenta", - "@editBundlesForAccount": {}, - "addToBundle": "Agregar al paquete", - "@addToBundle": {}, - "bundleName": "Nombre del paquete", - "@bundleName": {}, - "saveFile": "Guardar el archivo", - "@saveFile": { - "type": "text", - "placeholders": {} - }, - "sendSticker": "Enviar stickers", - "@sendSticker": { - "type": "text", - "placeholders": {} - }, - "setPermissionsLevel": "Establecer nivel de permisos", - "@setPermissionsLevel": { - "type": "text", - "placeholders": {} - }, - "shareLocation": "Compartir ubicación", - "@shareLocation": { - "type": "text", - "placeholders": {} - }, - "singlesignon": "Inicio de sesión único", - "@singlesignon": { - "type": "text", - "placeholders": {} - }, - "status": "Estado", - "@status": { - "type": "text", - "placeholders": {} - }, - "synchronizingPleaseWait": "Sincronizando... por favor espere.", - "@synchronizingPleaseWait": { - "type": "text", - "placeholders": {} - }, - "iHaveClickedOnLink": "He hecho clic en el enlace", - "@iHaveClickedOnLink": { - "type": "text", - "placeholders": {} - }, - "directChats": "Chat directo", - "@directChats": { - "type": "text", - "placeholders": {} - }, - "errorObtainingLocation": "Error al obtener la ubicación: {error}", - "@errorObtainingLocation": { - "type": "text", - "placeholders": { - "error": {} - } - }, - "howOffensiveIsThisContent": "¿Cuán ofensivo es este contenido?", - "@howOffensiveIsThisContent": { - "type": "text", - "placeholders": {} - }, - "inviteForMe": "Invitar por mí", - "@inviteForMe": { - "type": "text", - "placeholders": {} - }, - "noPasswordRecoveryDescription": "Aún no ha agregado una forma de recuperar su contraseña.", - "@noPasswordRecoveryDescription": { - "type": "text", - "placeholders": {} - }, - "numUsersTyping": "{count} usuarios están escribiendo…", - "@numUsersTyping": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "or": "O", - "@or": { - "type": "text", - "placeholders": {} - }, - "spaceIsPublic": "El espacio es público", - "@spaceIsPublic": { - "type": "text", - "placeholders": {} - }, - "chatHasBeenAddedToThisSpace": "El chat se ha agregado a este espacio", - "@chatHasBeenAddedToThisSpace": {}, - "commandInvalid": "Comando inválido", - "@commandInvalid": { - "type": "text" - }, - "passwordRecovery": "Recuperación de contraseña", - "@passwordRecovery": { - "type": "text", - "placeholders": {} - }, - "security": "Seguridad", - "@security": { - "type": "text", - "placeholders": {} - }, - "extremeOffensive": "Extremadamente ofensivo", - "@extremeOffensive": { - "type": "text", - "placeholders": {} - }, - "editBlockedServers": "Editar servidores bloqueado", - "@editBlockedServers": { - "type": "text", - "placeholders": {} - }, - "configureChat": "Configurar chat", - "@configureChat": { - "type": "text", - "placeholders": {} - }, - "noConnectionToTheServer": "Sin conexión al servidor", - "@noConnectionToTheServer": { - "type": "text", - "placeholders": {} - }, - "yourPublicKey": "Tu clave pública", - "@yourPublicKey": { - "type": "text", - "placeholders": {} - }, - "passwordForgotten": "Contraseña olvidada", - "@passwordForgotten": { - "type": "text", - "placeholders": {} - }, - "inoffensive": "Inofensivo", - "@inoffensive": { - "type": "text", - "placeholders": {} - }, - "reason": "Razón", - "@reason": { - "type": "text", - "placeholders": {} - }, - "memberChanges": "Cambios de miembros", - "@memberChanges": { - "type": "text", - "placeholders": {} - }, - "createNewSpace": "Nuevo espacio", - "@createNewSpace": { - "type": "text", - "placeholders": {} - }, - "edit": "Editar", - "@edit": { - "type": "text", - "placeholders": {} - }, - "setCustomEmotes": "Establecer emoticonos personalizados", - "@setCustomEmotes": { - "type": "text", - "placeholders": {} - }, - "ignore": "Ignorar", - "@ignore": { - "type": "text", - "placeholders": {} - }, - "notifications": "Notificaciones", - "@notifications": { - "type": "text", - "placeholders": {} - }, - "notificationsEnabledForThisAccount": "Notificaciones habilitadas para esta cuenta", - "@notificationsEnabledForThisAccount": { - "type": "text", - "placeholders": {} - }, - "offensive": "Ofensiva", - "@offensive": { - "type": "text", - "placeholders": {} - }, - "serverRequiresEmail": "Este servidor necesita validar su dirección de correo electrónico para registrarse.", - "@serverRequiresEmail": {}, - "pleaseChoose": "Por favor elija", - "@pleaseChoose": { - "type": "text", - "placeholders": {} - }, - "replaceRoomWithNewerVersion": "Reemplazar habitación con una versión más nueva", - "@replaceRoomWithNewerVersion": { - "type": "text", - "placeholders": {} - }, - "contentHasBeenReported": "El contenido ha sido reportado a los administradores del servidor", - "@contentHasBeenReported": { - "type": "text", - "placeholders": {} - }, - "groups": "Grupos", - "@groups": { - "type": "text", - "placeholders": {} - }, - "reportMessage": "Mensaje de informe", - "@reportMessage": { - "type": "text", - "placeholders": {} - }, - "search": "Buscar", - "@search": { - "type": "text", - "placeholders": {} - }, - "locationPermissionDeniedNotice": "Permiso de ubicación denegado. Concédeles que puedan compartir tu ubicación.", - "@locationPermissionDeniedNotice": { - "type": "text", - "placeholders": {} - }, - "defaultPermissionLevel": "Nivel de permiso predeterminado", - "@defaultPermissionLevel": { - "type": "text", - "placeholders": {} - }, - "obtainingLocation": "Obteniendo ubicación…", - "@obtainingLocation": { - "type": "text", - "placeholders": {} - }, - "participant": "Participante", - "@participant": { - "type": "text", - "placeholders": {} - }, - "pushRules": "Regla de Push", - "@pushRules": { - "type": "text", - "placeholders": {} - }, - "register": "Registrarse", - "@register": { - "type": "text", - "placeholders": {} - }, - "sendAsText": "Enviar como texto", - "@sendAsText": { - "type": "text" - }, - "toggleMuted": "Alternar silenciado", - "@toggleMuted": { - "type": "text", - "placeholders": {} - }, - "tooManyRequestsWarning": "Demasiadas solicitudes. ¡Por favor inténtelo más tarde!", - "@tooManyRequestsWarning": { - "type": "text", - "placeholders": {} - }, - "withTheseAddressesRecoveryDescription": "Con esta dirección puede recuperar su contraseña.", - "@withTheseAddressesRecoveryDescription": { - "type": "text", - "placeholders": {} - }, - "commandHint_send": "Enviar texto", - "@commandHint_send": { - "type": "text", - "description": "Usage hint for the command /send" - }, - "deviceId": "ID del dispositivo", - "@deviceId": { - "type": "text", - "placeholders": {} - }, - "containsUserName": "Contiene nombre de usuario", - "@containsUserName": { - "type": "text", - "placeholders": {} - }, - "oopsPushError": "¡UPS¡ Desafortunadamente, se produjo un error al configurar las notificaciones push.", - "@oopsPushError": { - "type": "text", - "placeholders": {} - }, - "removeYourAvatar": "Quitar tu avatar", - "@removeYourAvatar": { - "type": "text", - "placeholders": {} - }, - "enableEncryption": "Habilitar la encriptación", - "@enableEncryption": { - "type": "text", - "placeholders": {} - }, - "messages": "Mensajes", - "@messages": { - "type": "text", - "placeholders": {} - }, - "fontSize": "Tamaño de fuente", - "@fontSize": { - "type": "text", - "placeholders": {} - }, - "commandHint_ban": "Prohibir al usuario dado en esta sala", - "@commandHint_ban": { - "type": "text", - "description": "Usage hint for the command /ban" - }, - "commandHint_unban": "Des banear al usuario dado en esta sala", - "@commandHint_unban": { - "type": "text", - "description": "Usage hint for the command /unban" - }, - "commandMissing": "{command} no es un comando.", - "@commandMissing": { - "type": "text", - "placeholders": { - "command": {} - }, - "description": "State that {command} is not a valid /command." - }, - "scanQrCode": "Escanear código QR", - "@scanQrCode": {}, - "homeserver": "Homeserver", - "@homeserver": {}, - "newChat": "Nuevo chat", - "@newChat": { - "type": "text", - "placeholders": {} - }, - "commandHint_join": "Únete a la sala indicada", - "@commandHint_join": { - "type": "text", - "description": "Usage hint for the command /join" - }, - "sendOnEnter": "Enviar con enter", - "@sendOnEnter": {}, - "changeYourAvatar": "Cambiar tu avatar", - "@changeYourAvatar": { - "type": "text", - "placeholders": {} - }, - "commandHint_me": "Descríbete", - "@commandHint_me": { - "type": "text", - "description": "Usage hint for the command /me" - }, - "commandHint_html": "Enviar texto con formato HTML", - "@commandHint_html": { - "type": "text", - "description": "Usage hint for the command /html" - }, - "commandHint_invite": "Invitar al usuario indicado a esta sala", - "@commandHint_invite": { - "type": "text", - "description": "Usage hint for the command /invite" - }, - "commandHint_kick": "Eliminar el usuario indicado de esta sala", - "@commandHint_kick": { - "type": "text", - "description": "Usage hint for the command /kick" - }, - "commandHint_leave": "Deja esta sala", - "@commandHint_leave": { - "type": "text", - "description": "Usage hint for the command /leave" - }, - "commandHint_myroomavatar": "Selecciona tu foto para esta sala (by mxc-uri)", - "@commandHint_myroomavatar": { - "type": "text", - "description": "Usage hint for the command /myroomavatar" - }, - "commandHint_myroomnick": "Establece tu nombre para mostrar para esta sala", - "@commandHint_myroomnick": { - "type": "text", - "description": "Usage hint for the command /myroomnick" - }, - "commandHint_op": "Establece el nivel de potencia del usuario dado (default: 50)", - "@commandHint_op": { - "type": "text", - "description": "Usage hint for the command /op" - }, - "commandHint_plain": "Enviar texto sin formato", - "@commandHint_plain": { - "type": "text", - "description": "Usage hint for the command /plain" - }, - "commandHint_react": "Enviar respuesta como reacción", - "@commandHint_react": { - "type": "text", - "description": "Usage hint for the command /react" - }, - "containsDisplayName": "Contiene nombre para mostrar", - "@containsDisplayName": { - "type": "text", - "placeholders": {} - }, - "editRoomAvatar": "Editar avatar de sala", - "@editRoomAvatar": { - "type": "text", - "placeholders": {} - }, - "locationDisabledNotice": "Los servicios de ubicación están deshabilitado. Habilite para poder compartir su ubicación.", - "@locationDisabledNotice": { - "type": "text", - "placeholders": {} - }, - "encrypted": "Encriptado", - "@encrypted": { - "type": "text", - "placeholders": {} - }, - "goToTheNewRoom": "Ir a la nueva sala", - "@goToTheNewRoom": { - "type": "text", - "placeholders": {} - }, - "noMatrixServer": "{server1} no es un servidor matrix, usar {server2} en su lugar?", - "@noMatrixServer": { - "type": "text", - "placeholders": { - "server1": {}, - "server2": {} - } - }, - "openInMaps": "Abrir en maps", - "@openInMaps": { - "type": "text", - "placeholders": {} - }, - "removeFromBundle": "Quitar de este paquete", - "@removeFromBundle": {}, - "link": "Link", - "@link": {}, - "enableMultiAccounts": "(BETA) habilite varias cuenta en este dispositivo", - "@enableMultiAccounts": {}, - "pleaseEnter4Digits": "Ingrese 4 dígitos o déjelo en blanco para deshabilitar el bloqueo de la aplicación.", - "@pleaseEnter4Digits": { - "type": "text", - "placeholders": {} - }, - "pleaseChooseAPasscode": "Elija un código de acceso", - "@pleaseChooseAPasscode": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourPin": "Por favor ingrese su PIN", - "@pleaseEnterYourPin": { - "type": "text", - "placeholders": {} - }, - "redactMessage": "Censurar mensaje", - "@redactMessage": { - "type": "text", - "placeholders": {} - }, - "sendMessages": "Enviar mensajes", - "@sendMessages": { - "type": "text", - "placeholders": {} - }, - "spaceName": "Nombre del espacio", - "@spaceName": { - "type": "text", - "placeholders": {} - }, - "toggleFavorite": "Alternar favorito", - "@toggleFavorite": { - "type": "text", - "placeholders": {} - }, - "toggleUnread": "Marcar como: leído / no leído", - "@toggleUnread": { - "type": "text", - "placeholders": {} - }, - "whoCanPerformWhichAction": "Quién puede realizar qué acción", - "@whoCanPerformWhichAction": { - "type": "text", - "placeholders": {} - }, - "whyDoYouWantToReportThis": "¿Por qué quieres denunciar esto?", - "@whyDoYouWantToReportThis": { - "type": "text", - "placeholders": {} - }, - "wipeChatBackup": "¿Limpiar la copia de seguridad de su chat para crear una nueva clave de seguridad?", - "@wipeChatBackup": { - "type": "text", - "placeholders": {} - }, - "yourChatBackupHasBeenSetUp": "Se ha configurado la copia de respaldo del chat.", - "@yourChatBackupHasBeenSetUp": {}, - "unverified": "No verificado", - "@unverified": {}, - "commandHint_clearcache": "Limpiar cache", - "@commandHint_clearcache": { - "type": "text", - "description": "Usage hint for the command /clearcache" - }, - "messageInfo": "Información del mensaje", - "@messageInfo": {}, - "time": "Tiempo", - "@time": {}, - "openVideoCamera": "Abrir la cámara para un video", - "@openVideoCamera": { - "type": "text", - "placeholders": {} - }, - "repeatPassword": "Repita la contraseña", - "@repeatPassword": {}, - "removeFromSpace": "Eliminar del espacio", - "@removeFromSpace": {}, - "addToSpaceDescription": "Elige un espacio para añadir este chat a el.", - "@addToSpaceDescription": {}, - "openGallery": "Abrir galería", - "@openGallery": {}, - "start": "Iniciar", - "@start": {}, - "commandHint_discardsession": "Descartar sesión", - "@commandHint_discardsession": { - "type": "text", - "description": "Usage hint for the command /discardsession" - }, - "messageType": "Tipo de Mensaje", - "@messageType": {}, - "videoWithSize": "Video ({size})", - "@videoWithSize": { - "type": "text", - "placeholders": { - "size": {} - } - }, - "publish": "Publicar", - "@publish": {}, - "newSpace": "Nuevo espacio", - "@newSpace": {}, - "allSpaces": "Todos los espacios", - "@allSpaces": {}, - "widgetUrlError": "Esta no es una URL válida.", - "@widgetUrlError": {}, - "commandHint_markasgroup": "Marcar como grupo", - "@commandHint_markasgroup": {}, - "nextAccount": "Siguiente cuenta", - "@nextAccount": {}, - "youRejectedTheInvitation": "Rechazaste la invitación", - "@youRejectedTheInvitation": {}, - "newGroup": "Nuevo grupo", - "@newGroup": {}, - "widgetJitsi": "Jitsi Meet", - "@widgetJitsi": {}, - "previousAccount": "Cuenta anterior", - "@previousAccount": {}, - "users": "Usuarios", - "@users": {}, - "youInvitedBy": "📩 Has sido invitado por {user}", - "@youInvitedBy": { - "placeholders": { - "user": {} - } - }, - "youAcceptedTheInvitation": "👍 Aceptaste la invitación", - "@youAcceptedTheInvitation": {}, - "widgetEtherpad": "Nota de texto", - "@widgetEtherpad": {}, - "commandHint_cuddle": "Mandar una carantoña", - "@commandHint_cuddle": {}, - "supposedMxid": "Esto debería ser {mxid}", - "@supposedMxid": { - "type": "text", - "placeholders": { - "mxid": {} - } - }, - "importFromZipFile": "Importar de un archivo .zip", - "@importFromZipFile": {}, - "exportEmotePack": "Exportar paquete de emotes como .zip", - "@exportEmotePack": {}, - "addChatDescription": "Añadir una descripción del chat", - "@addChatDescription": {}, - "sendTypingNotifications": "Enviar notificaciones \"está escribiendo\"", - "@sendTypingNotifications": {}, - "importEmojis": "Importar emojis", - "@importEmojis": {}, - "confirmMatrixId": "Por favor confirma tu Matrix ID para borrar tu cuenta.", - "@confirmMatrixId": {}, - "notAnImage": "El archivo no es una imagen.", - "@notAnImage": {}, - "commandHint_hug": "Mandar un abrazo", - "@commandHint_hug": {}, - "importNow": "Importar ahora", - "@importNow": {}, - "hugContent": "{senderName} te abraza", - "@hugContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "otherCallingPermissions": "Micrófono, cámara y otros permisos de FluffyChat", - "@otherCallingPermissions": {}, - "emailOrUsername": "Correo electrónico o nombre de usuario", - "@emailOrUsername": {}, - "countFiles": "{count} archivos", - "@countFiles": { - "placeholders": { - "count": {} - } - }, - "reportUser": "Reportar usuario", - "@reportUser": {}, - "voiceCall": "Llamada de voz", - "@voiceCall": {}, - "reactedWith": "{sender} reaccionó con {reaction}", - "@reactedWith": { - "type": "text", - "placeholders": { - "sender": {}, - "reaction": {} - } - }, - "markAsRead": "Marcar como leído", - "@markAsRead": {}, - "widgetName": "Nombre", - "@widgetName": {}, - "replace": "Reemplazar", - "@replace": {}, - "unsupportedAndroidVersionLong": "Esta característica requiere una versión más reciente de Android. Por favor, compruebe las actualizaciones o la compatibilidad de LineageOS.", - "@unsupportedAndroidVersionLong": {}, - "storeSecurlyOnThisDevice": "Almacenar de forma segura en este dispositivo", - "@storeSecurlyOnThisDevice": {}, - "@encryptThisChat": {}, - "openChat": "Abrir chat", - "@openChat": {}, - "screenSharingDetail": "Usted está compartiendo su pantalla en FluffyChat", - "@screenSharingDetail": {}, - "@jumpToLastReadMessage": {}, - "allRooms": "Todos los chats grupales", - "@allRooms": { - "type": "text", - "placeholders": {} - }, - "widgetVideo": "Vídeo", - "@widgetVideo": {}, - "dismiss": "Descartar", - "@dismiss": {}, - "@reportErrorDescription": {}, - "unsupportedAndroidVersion": "Versión de Android no compatible", - "@unsupportedAndroidVersion": {}, - "indexedDbErrorLong": "El almacenamiento de mensajes, por desgracia, no está habilitado en el modo privado por defecto.\nPor favor, visite\n - about:config\n - Establezca dom.indexedDB.privateBrowsing.enabled a true\nDe otra forma, no es posible usar FluffyChat.", - "@indexedDbErrorLong": {}, - "startFirstChat": "Comience su primer chat", - "@startFirstChat": {}, - "@callingAccount": {}, - "@setColorTheme": {}, - "commandHint_create": "Crear un chat grupal vacío\nUse --no-encryption para deshabilitar el cifrado", - "@commandHint_create": { - "type": "text", - "description": "Usage hint for the command /create" - }, - "user": "Usuario", - "@user": {}, - "@banUserDescription": {}, - "@removeDevicesDescription": {}, - "separateChatTypes": "Separar chats directos de grupos", - "@separateChatTypes": { - "type": "text", - "placeholders": {} - }, - "tryAgain": "Inténtelo de nuevo", - "@tryAgain": {}, - "youKickedAndBanned": "🙅 Usted expulsó y prohibió el acceso a {user}", - "@youKickedAndBanned": { - "placeholders": { - "user": {} - } - }, - "@unbanUserDescription": {}, - "messagesStyle": "Mensajes:", - "@messagesStyle": {}, - "@newSpaceDescription": {}, - "chatDescription": "Descripción del chat", - "@chatDescription": {}, - "callingAccountDetails": "Permite a FluffyChat utilizar la aplicación de llamadas nativa de Android.", - "@callingAccountDetails": {}, - "enterSpace": "Unirse al espacio", - "@enterSpace": {}, - "@reopenChat": {}, - "pleaseEnterRecoveryKey": "Por favor, introduzca su clave de recuperación:", - "@pleaseEnterRecoveryKey": {}, - "widgetNameError": "Por favor, introduzca un nombre a mostrar.", - "@widgetNameError": {}, - "addWidget": "Añadir widget", - "@addWidget": {}, - "@noKeyForThisMessage": {}, - "hydrateTor": "TOR: Importar sesión exportada", - "@hydrateTor": {}, - "@pushNotificationsNotAvailable": {}, - "storeInAppleKeyChain": "Almacenar en la KeyChain de Apple", - "@storeInAppleKeyChain": {}, - "hydrate": "Restaurar desde fichero de copia de seguridad", - "@hydrate": {}, - "invalidServerName": "Nombre del servidor no válido", - "@invalidServerName": {}, - "chatPermissions": "Permisos del chat", - "@chatPermissions": {}, - "sender": "Remitente", - "@sender": {}, - "storeInAndroidKeystore": "Almacenar en la KeyStore de Android", - "@storeInAndroidKeystore": {}, - "@signInWithPassword": {}, - "@makeAdminDescription": {}, - "saveKeyManuallyDescription": "Compartir esta clave manualmente usando el diálogo de compartir del sistema o el portapapeles.", - "@saveKeyManuallyDescription": {}, - "whyIsThisMessageEncrypted": "¿Por qué no se puede leer este mensaje?", - "@whyIsThisMessageEncrypted": {}, - "setChatDescription": "Establecer descripción del chat", - "@setChatDescription": {}, - "dehydrateWarning": "Esta acción no se puede deshacer. Asegúrese de que ha almacenado de forma segura el fichero de copia de seguridad.", - "@dehydrateWarning": {}, - "@noOtherDevicesFound": {}, - "redactedBy": "Censurado por {username}", - "@redactedBy": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "videoCallsBetaWarning": "Tenga en cuenta que las videollamadas están actualmente en fase beta. Es posible que no funcionen como se espera o que no funcionen de ninguna manera en algunas plataformas.", - "@videoCallsBetaWarning": {}, - "@signInWith": { - "type": "text", - "placeholders": { - "provider": {} - } - }, - "@fileIsTooBigForServer": {}, - "callingPermissions": "Permisos de llamadas", - "@callingPermissions": {}, - "@readUpToHere": {}, - "unlockOldMessages": "Desbloquear mensajes viejos", - "@unlockOldMessages": {}, - "numChats": "{number} chats", - "@numChats": { - "type": "number", - "placeholders": { - "number": {} - } - }, - "optionalRedactReason": "(Opcional) Motivo para censurar este mensaje...", - "@optionalRedactReason": {}, - "dehydrate": "Exportar sesión y limpiar dispositivo", - "@dehydrate": {}, - "@archiveRoomDescription": {}, - "switchToAccount": "Cambiar a la cuenta {number}", - "@switchToAccount": { - "type": "number", - "placeholders": { - "number": {} - } - }, - "experimentalVideoCalls": "Videollamadas experimentales", - "@experimentalVideoCalls": {}, - "pleaseEnterRecoveryKeyDescription": "Para desbloquear sus viejos mensajes, introduzca su clave de recuperación que se generó en una sesión anterior. Su clave de recuperación NO es su contraseña.", - "@pleaseEnterRecoveryKeyDescription": {}, - "inviteContactToGroupQuestion": "¿Quieres invitar a {contact} al chat {groupName}?", - "@inviteContactToGroupQuestion": {}, - "redactedByBecause": "Censurado por {username} porque: \"{reason}\"", - "@redactedByBecause": { - "type": "text", - "placeholders": { - "username": {}, - "reason": {} - } - }, - "youHaveWithdrawnTheInvitationFor": "Usted retiró la invitación a {user}", - "@youHaveWithdrawnTheInvitationFor": { - "placeholders": { - "user": {} - } - }, - "@appearOnTopDetails": {}, - "enterRoom": "Unirse a la sala", - "@enterRoom": {}, - "confirmEventUnpin": "¿Seguro que quiere desfijar permanentemente el evento?", - "@confirmEventUnpin": {}, - "youInvitedUser": "📩 Usted invitó a {user}", - "@youInvitedUser": { - "placeholders": { - "user": {} - } - }, - "@fileHasBeenSavedAt": { - "type": "text", - "placeholders": { - "path": {} - } - }, - "redactMessageDescription": "El mensaje será censurado para todas las personas participantes en la conversación. Esto no se puede deshacer.", - "@redactMessageDescription": {}, - "recoveryKey": "Clave de recuperación", - "@recoveryKey": {}, - "@invalidInput": {}, - "dehydrateTorLong": "Si está usando TOR, es recomendable exportar la sesión antes de cerrar la ventana.", - "@dehydrateTorLong": {}, - "doNotShowAgain": "No mostrar de nuevo", - "@doNotShowAgain": {}, - "@report": {}, - "hideUnimportantStateEvents": "Ocultar eventos de estado no importantes", - "@hideUnimportantStateEvents": {}, - "screenSharingTitle": "Compartir la pantalla", - "@screenSharingTitle": {}, - "widgetCustom": "Personalizado", - "@widgetCustom": {}, - "youBannedUser": "Usted prohibió el acceso a {user}", - "@youBannedUser": { - "placeholders": { - "user": {} - } - }, - "@hasKnocked": { - "placeholders": { - "user": {} - } - }, - "@openLinkInBrowser": {}, - "@disableEncryptionWarning": {}, - "directChat": "Chat directo", - "@directChat": {}, - "@wrongPinEntered": { - "type": "text", - "placeholders": { - "seconds": {} - } - }, - "@inviteGroupChat": {}, - "appearOnTop": "Aparecer en la cima", - "@appearOnTop": {}, - "@invitePrivateChat": {}, - "foregroundServiceRunning": "Esta notificación aparece cuando el servicio en segundo plano se está ejecutando.", - "@foregroundServiceRunning": {}, - "wasDirectChatDisplayName": "Chat vacío (era {oldDisplayName})", - "@wasDirectChatDisplayName": { - "type": "text", - "placeholders": { - "oldDisplayName": {} - } - }, - "noChatDescriptionYet": "No se ha creado una descripción del chat aún.", - "@noChatDescriptionYet": {}, - "@learnMore": {}, - "chatDescriptionHasBeenChanged": "Se ha cambiado la descripción del chat", - "@chatDescriptionHasBeenChanged": {}, - "dehydrateTor": "TOR: Exportar sesión", - "@dehydrateTor": {}, - "@roomUpgradeDescription": {}, - "@pleaseEnterANumber": {}, - "youKicked": "👞 Usted expulsó a {user}", - "@youKicked": { - "placeholders": { - "user": {} - } - }, - "@profileNotFound": {}, - "@jump": {}, - "@sorryThatsNotPossible": {}, - "shareInviteLink": "Compartir enlace de invitación", - "@shareInviteLink": {}, - "commandHint_markasdm": "Marcar como sala de mensajes directos para el ID de Matrix", - "@commandHint_markasdm": {}, - "recoveryKeyLost": "¿Perdió su clave de recuperación?", - "@recoveryKeyLost": {}, - "@deviceKeys": {}, - "emoteKeyboardNoRecents": "Los emotes usados recientemente aparecerán aquí...", - "@emoteKeyboardNoRecents": { - "type": "text", - "placeholders": {} - }, - "@setTheme": {}, - "youJoinedTheChat": "Usted se ha unido al chat", - "@youJoinedTheChat": {}, - "errorAddingWidget": "Fallo al añadir el widget.", - "@errorAddingWidget": {}, - "commandHint_dm": "Iniciar un chat directo\nUse --no-encryption para deshabilitar el cifrado", - "@commandHint_dm": { - "type": "text", - "description": "Usage hint for the command /dm" - }, - "youUnbannedUser": "Usted volvió a permitir el acceso a {user}", - "@youUnbannedUser": { - "placeholders": { - "user": {} - } - }, - "emojis": "Emojis", - "@emojis": {}, - "@pleaseTryAgainLaterOrChooseDifferentServer": {}, - "createGroup": "Crear grupo", - "@createGroup": {}, - "hydrateTorLong": "¿Exportó su sesión la última vez que estuvo en TOR? Impórtela rápidamente y continúe chateando.", - "@hydrateTorLong": {}, - "custom": "Personalizado", - "@custom": {}, - "@noBackupWarning": {}, - "storeInSecureStorageDescription": "Almacenar la clave de recuperación en el almacenamiento seguro de este dispositivo.", - "@storeInSecureStorageDescription": {}, - "@kickUserDescription": {}, - "pinMessage": "Anclar a la sala", - "@pinMessage": {}, - "@invite": {}, - "indexedDbErrorTitle": "Problemas con el modo privado", - "@indexedDbErrorTitle": {}, - "googlyEyesContent": "{senderName} te manda ojos saltones", - "@googlyEyesContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "cuddleContent": "{senderName} se acurruca contigo", - "@cuddleContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "commandHint_googly": "Enviar unos ojos saltones", - "@commandHint_googly": {}, - "@placeCall": {} + "@@locale": "es", + "@@last_modified": "2021-08-14 12:41:10.097243", + "about": "Acerca de", + "@about": { + "type": "text", + "placeholders": {} + }, + "accept": "Aceptar", + "@accept": { + "type": "text", + "placeholders": {} + }, + "acceptedTheInvitation": "{username} aceptó la invitación", + "@acceptedTheInvitation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "account": "Cuenta", + "@account": { + "type": "text", + "placeholders": {} + }, + "activatedEndToEndEncryption": "{username} activó el cifrado de extremo a extremo", + "@activatedEndToEndEncryption": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "addEmail": "Añadir dirección de correo", + "@addEmail": { + "type": "text", + "placeholders": {} + }, + "admin": "Administrador", + "@admin": { + "type": "text", + "placeholders": {} + }, + "alias": "alias", + "@alias": { + "type": "text", + "placeholders": {} + }, + "answeredTheCall": "{senderName} respondió a la llamada", + "@answeredTheCall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "anyoneCanJoin": "Cualquiera puede unirse", + "@anyoneCanJoin": { + "type": "text", + "placeholders": {} + }, + "archive": "Archivo", + "@archive": { + "type": "text", + "placeholders": {} + }, + "areGuestsAllowedToJoin": "¿Pueden unirse los usuarios visitantes?", + "@areGuestsAllowedToJoin": { + "type": "text", + "placeholders": {} + }, + "areYouSure": "¿Estás seguro?", + "@areYouSure": { + "type": "text", + "placeholders": {} + }, + "areYouSureYouWantToLogout": "¿Confirma que quiere cerrar sesión?", + "@areYouSureYouWantToLogout": { + "type": "text", + "placeholders": {} + }, + "askSSSSSign": "Para poder confirmar a la otra persona, ingrese su contraseña de almacenamiento segura o la clave de recuperación.", + "@askSSSSSign": { + "type": "text", + "placeholders": {} + }, + "askVerificationRequest": "¿Aceptar esta solicitud de verificación de {username}?", + "@askVerificationRequest": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "badServerLoginTypesException": "El servidor soporta los siguientes mecanismos para autenticación:\n{serverVersions}\npero esta aplicación sólo soporta:\n{supportedVersions}", + "@badServerLoginTypesException": { + "type": "text", + "placeholders": { + "serverVersions": {}, + "supportedVersions": {} + } + }, + "badServerVersionsException": "El servidor soporta las siguientes versiones de la especificación:\n{serverVersions}\npero esta aplicación sólo soporta las versiones {supportedVersions}", + "@badServerVersionsException": { + "type": "text", + "placeholders": { + "serverVersions": {}, + "supportedVersions": {} + } + }, + "banFromChat": "Vetar del chat", + "@banFromChat": { + "type": "text", + "placeholders": {} + }, + "banned": "Vetado", + "@banned": { + "type": "text", + "placeholders": {} + }, + "bannedUser": "{username} vetó a {targetName}", + "@bannedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "blockDevice": "Bloquear dispositivo", + "@blockDevice": { + "type": "text", + "placeholders": {} + }, + "blocked": "Bloqueado", + "@blocked": { + "type": "text", + "placeholders": {} + }, + "cancel": "Cancelar", + "@cancel": { + "type": "text", + "placeholders": {} + }, + "changeDeviceName": "Cambiar el nombre del dispositivo", + "@changeDeviceName": { + "type": "text", + "placeholders": {} + }, + "changedTheChatAvatar": "{username} cambió el icono del chat", + "@changedTheChatAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheChatDescriptionTo": "{username} cambió la descripción del chat a: '{description}'", + "@changedTheChatDescriptionTo": { + "type": "text", + "placeholders": { + "username": {}, + "description": {} + } + }, + "changedTheChatNameTo": "{username} cambió el nombre del chat a: '{chatname}'", + "@changedTheChatNameTo": { + "type": "text", + "placeholders": { + "username": {}, + "chatname": {} + } + }, + "changedTheChatPermissions": "{username} cambió los permisos del chat", + "@changedTheChatPermissions": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheDisplaynameTo": "{username} cambió su nombre visible a: {displayname}", + "@changedTheDisplaynameTo": { + "type": "text", + "placeholders": { + "username": {}, + "displayname": {} + } + }, + "changedTheGuestAccessRules": "{username} cambió las reglas de acceso de visitantes", + "@changedTheGuestAccessRules": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheGuestAccessRulesTo": "{username} cambió las reglas de acceso de visitantes a: {rules}", + "@changedTheGuestAccessRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "changedTheHistoryVisibility": "{username} cambió la visibilidad del historial", + "@changedTheHistoryVisibility": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheHistoryVisibilityTo": "{username} cambió la visibilidad del historial a: {rules}", + "@changedTheHistoryVisibilityTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "changedTheJoinRules": "{username} cambió las reglas de ingreso", + "@changedTheJoinRules": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheJoinRulesTo": "{username} cambió las reglas de ingreso a {joinRules}", + "@changedTheJoinRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "joinRules": {} + } + }, + "changedTheProfileAvatar": "{username} cambió su imagen de perfil", + "@changedTheProfileAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheRoomAliases": "{username} cambió el alias de la sala", + "@changedTheRoomAliases": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheRoomInvitationLink": "{username} cambió el enlace de invitación", + "@changedTheRoomInvitationLink": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changePassword": "Cambiar la contraseña", + "@changePassword": { + "type": "text", + "placeholders": {} + }, + "changeTheHomeserver": "Cambiar el servidor", + "@changeTheHomeserver": { + "type": "text", + "placeholders": {} + }, + "changeTheme": "Cambia tu estilo", + "@changeTheme": { + "type": "text", + "placeholders": {} + }, + "changeTheNameOfTheGroup": "Cambiar el nombre del grupo", + "@changeTheNameOfTheGroup": { + "type": "text", + "placeholders": {} + }, + "channelCorruptedDecryptError": "El cifrado se ha corrompido", + "@channelCorruptedDecryptError": { + "type": "text", + "placeholders": {} + }, + "chat": "Chat", + "@chat": { + "type": "text", + "placeholders": {} + }, + "chatBackup": "Copia de respaldo del chat", + "@chatBackup": { + "type": "text", + "placeholders": {} + }, + "chatBackupDescription": "La copia de respaldo del chat está protegida por una clave de seguridad. Procure no perderla.", + "@chatBackupDescription": { + "type": "text", + "placeholders": {} + }, + "chatDetails": "Detalles del chat", + "@chatDetails": { + "type": "text", + "placeholders": {} + }, + "chats": "Conversaciones", + "@chats": { + "type": "text", + "placeholders": {} + }, + "chooseAStrongPassword": "Elija una contraseña segura", + "@chooseAStrongPassword": { + "type": "text", + "placeholders": {} + }, + "clearArchive": "Borrar archivo", + "@clearArchive": {}, + "close": "Cerrar", + "@close": { + "type": "text", + "placeholders": {} + }, + "compareEmojiMatch": "Por favor compare los emojis", + "@compareEmojiMatch": { + "type": "text", + "placeholders": {} + }, + "compareNumbersMatch": "Por favor compare los números", + "@compareNumbersMatch": { + "type": "text", + "placeholders": {} + }, + "confirm": "Confirmar", + "@confirm": { + "type": "text", + "placeholders": {} + }, + "connect": "Conectar", + "@connect": { + "type": "text", + "placeholders": {} + }, + "contactHasBeenInvitedToTheGroup": "El contacto ha sido invitado al grupo", + "@contactHasBeenInvitedToTheGroup": { + "type": "text", + "placeholders": {} + }, + "copiedToClipboard": "Copiado al portapapeles", + "@copiedToClipboard": { + "type": "text", + "placeholders": {} + }, + "copy": "Copiar", + "@copy": { + "type": "text", + "placeholders": {} + }, + "copyToClipboard": "Copiar al portapapeles", + "@copyToClipboard": { + "type": "text", + "placeholders": {} + }, + "couldNotDecryptMessage": "No se pudo descifrar el mensaje: {error}", + "@couldNotDecryptMessage": { + "type": "text", + "placeholders": { + "error": {} + } + }, + "countParticipants": "{count} participantes", + "@countParticipants": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "create": "Crear", + "@create": { + "type": "text", + "placeholders": {} + }, + "createdTheChat": "💬{username} creó el chat", + "@createdTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "currentlyActive": "Actualmente activo", + "@currentlyActive": { + "type": "text", + "placeholders": {} + }, + "darkTheme": "Oscuro", + "@darkTheme": { + "type": "text", + "placeholders": {} + }, + "dateAndTimeOfDay": "{date}, {timeOfDay}", + "@dateAndTimeOfDay": { + "type": "text", + "placeholders": { + "date": {}, + "timeOfDay": {} + } + }, + "dateWithoutYear": "{day}-{month}", + "@dateWithoutYear": { + "type": "text", + "placeholders": { + "month": {}, + "day": {} + } + }, + "dateWithYear": "{day}-{month}-{year}", + "@dateWithYear": { + "type": "text", + "placeholders": { + "year": {}, + "month": {}, + "day": {} + } + }, + "deactivateAccountWarning": "Se desactivará su cuenta de usuario. ¡La operación no se puede cancelar! ¿Está seguro?", + "@deactivateAccountWarning": { + "type": "text", + "placeholders": {} + }, + "delete": "Eliminar", + "@delete": { + "type": "text", + "placeholders": {} + }, + "deleteAccount": "Cancelar cuenta", + "@deleteAccount": { + "type": "text", + "placeholders": {} + }, + "deleteMessage": "Eliminar mensaje", + "@deleteMessage": { + "type": "text", + "placeholders": {} + }, + "device": "Dispositivo", + "@device": { + "type": "text", + "placeholders": {} + }, + "devices": "Dispositivos", + "@devices": { + "type": "text", + "placeholders": {} + }, + "displaynameHasBeenChanged": "El nombre visible ha cambiado", + "@displaynameHasBeenChanged": { + "type": "text", + "placeholders": {} + }, + "downloadFile": "Descargar archivo", + "@downloadFile": { + "type": "text", + "placeholders": {} + }, + "editDisplayname": "Editar nombre visible", + "@editDisplayname": { + "type": "text", + "placeholders": {} + }, + "editRoomAliases": "Editar alias de la sala", + "@editRoomAliases": { + "type": "text", + "placeholders": {} + }, + "emoteExists": "¡El emote ya existe!", + "@emoteExists": { + "type": "text", + "placeholders": {} + }, + "emoteInvalid": "¡El atajo del emote es inválido!", + "@emoteInvalid": { + "type": "text", + "placeholders": {} + }, + "emotePacks": "Paquetes de emoticonos para la habitación", + "@emotePacks": { + "type": "text", + "placeholders": {} + }, + "emoteSettings": "Configuración de emotes", + "@emoteSettings": { + "type": "text", + "placeholders": {} + }, + "emoteShortcode": "Atajo de emote", + "@emoteShortcode": { + "type": "text", + "placeholders": {} + }, + "emoteWarnNeedToPick": "¡Debes elegir un atajo de emote y una imagen!", + "@emoteWarnNeedToPick": { + "type": "text", + "placeholders": {} + }, + "emptyChat": "Chat vacío", + "@emptyChat": { + "type": "text", + "placeholders": {} + }, + "enableEmotesGlobally": "Habilitar paquete de emoticonos a nivel general", + "@enableEmotesGlobally": { + "type": "text", + "placeholders": {} + }, + "enableEncryptionWarning": "Ya no podrá deshabilitar el cifrado. ¿Estás seguro?", + "@enableEncryptionWarning": { + "type": "text", + "placeholders": {} + }, + "encryption": "Cifrado", + "@encryption": { + "type": "text", + "placeholders": {} + }, + "encryptionNotEnabled": "El cifrado no está habilitado", + "@encryptionNotEnabled": { + "type": "text", + "placeholders": {} + }, + "endedTheCall": "{senderName} terminó la llamada", + "@endedTheCall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "enterAnEmailAddress": "Introducir una dirección de correo electrónico", + "@enterAnEmailAddress": { + "type": "text", + "placeholders": {} + }, + "enterYourHomeserver": "Ingrese su servidor", + "@enterYourHomeserver": { + "type": "text", + "placeholders": {} + }, + "everythingReady": "¡Todo listo!", + "@everythingReady": { + "type": "text", + "placeholders": {} + }, + "fileName": "Nombre del archivo", + "@fileName": { + "type": "text", + "placeholders": {} + }, + "fluffychat": "FluffyChat", + "@fluffychat": { + "type": "text", + "placeholders": {} + }, + "forward": "Reenviar", + "@forward": { + "type": "text", + "placeholders": {} + }, + "fromJoining": "Desde que se unió", + "@fromJoining": { + "type": "text", + "placeholders": {} + }, + "fromTheInvitation": "Desde la invitación", + "@fromTheInvitation": { + "type": "text", + "placeholders": {} + }, + "group": "Grupo", + "@group": { + "type": "text", + "placeholders": {} + }, + "groupIsPublic": "El grupo es público", + "@groupIsPublic": { + "type": "text", + "placeholders": {} + }, + "groupWith": "Grupo con {displayname}", + "@groupWith": { + "type": "text", + "placeholders": { + "displayname": {} + } + }, + "guestsAreForbidden": "Los visitantes están prohibidos", + "@guestsAreForbidden": { + "type": "text", + "placeholders": {} + }, + "guestsCanJoin": "Los visitantes pueden unirse", + "@guestsCanJoin": { + "type": "text", + "placeholders": {} + }, + "hasWithdrawnTheInvitationFor": "{username} ha retirado la invitación para {targetName}", + "@hasWithdrawnTheInvitationFor": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "help": "Ayuda", + "@help": { + "type": "text", + "placeholders": {} + }, + "hideRedactedEvents": "Ocultar sucesos censurados", + "@hideRedactedEvents": { + "type": "text", + "placeholders": {} + }, + "hideUnknownEvents": "Ocultar sucesos desconocidos", + "@hideUnknownEvents": { + "type": "text", + "placeholders": {} + }, + "id": "Identificación", + "@id": { + "type": "text", + "placeholders": {} + }, + "identity": "Identidad", + "@identity": { + "type": "text", + "placeholders": {} + }, + "ignoredUsers": "Usuarios ignorados", + "@ignoredUsers": { + "type": "text", + "placeholders": {} + }, + "incorrectPassphraseOrKey": "Frase de contraseña o clave de recuperación incorrecta", + "@incorrectPassphraseOrKey": { + "type": "text", + "placeholders": {} + }, + "inviteContact": "Invitar contacto", + "@inviteContact": { + "type": "text", + "placeholders": {} + }, + "inviteContactToGroup": "Invitar contacto a {groupName}", + "@inviteContactToGroup": { + "type": "text", + "placeholders": { + "groupName": {} + } + }, + "invited": "Invitado", + "@invited": { + "type": "text", + "placeholders": {} + }, + "invitedUser": "📩{username} invitó a {targetName}", + "@invitedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "invitedUsersOnly": "Sólo usuarios invitados", + "@invitedUsersOnly": { + "type": "text", + "placeholders": {} + }, + "inviteText": "{username} te invitó a FluffyChat.\n1. Instale FluffyChat: https://fluffychat.im\n2. Regístrate o inicia sesión \n3. Abra el enlace de invitación: {link}", + "@inviteText": { + "type": "text", + "placeholders": { + "username": {}, + "link": {} + } + }, + "isTyping": "está escribiendo…", + "@isTyping": { + "type": "text", + "placeholders": {} + }, + "joinedTheChat": "👋{username} se unió al chat", + "@joinedTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "joinRoom": "Unirse a la sala", + "@joinRoom": { + "type": "text", + "placeholders": {} + }, + "kicked": "👞{username} echó a {targetName}", + "@kicked": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "kickedAndBanned": "🙅{username} echó y vetó a {targetName}", + "@kickedAndBanned": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "kickFromChat": "Echar del chat", + "@kickFromChat": { + "type": "text", + "placeholders": {} + }, + "lastActiveAgo": "Última vez activo: {localizedTimeShort}", + "@lastActiveAgo": { + "type": "text", + "placeholders": { + "localizedTimeShort": {} + } + }, + "leave": "Abandonar", + "@leave": { + "type": "text", + "placeholders": {} + }, + "leftTheChat": "Abandonó el chat", + "@leftTheChat": { + "type": "text", + "placeholders": {} + }, + "license": "Licencia", + "@license": { + "type": "text", + "placeholders": {} + }, + "lightTheme": "Claro", + "@lightTheme": { + "type": "text", + "placeholders": {} + }, + "loadCountMoreParticipants": "Mostrar {count} participantes más", + "@loadCountMoreParticipants": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "loadingPleaseWait": "Cargando… Por favor espere.", + "@loadingPleaseWait": { + "type": "text", + "placeholders": {} + }, + "loadMore": "Mostrar más…", + "@loadMore": { + "type": "text", + "placeholders": {} + }, + "login": "Acceso", + "@login": { + "type": "text", + "placeholders": {} + }, + "logInTo": "Iniciar sesión en {homeserver}", + "@logInTo": { + "type": "text", + "placeholders": { + "homeserver": {} + } + }, + "logout": "Cerrar sesión", + "@logout": { + "type": "text", + "placeholders": {} + }, + "mention": "Mencionar", + "@mention": { + "type": "text", + "placeholders": {} + }, + "moderator": "Moderador", + "@moderator": { + "type": "text", + "placeholders": {} + }, + "muteChat": "Silenciar chat", + "@muteChat": { + "type": "text", + "placeholders": {} + }, + "needPantalaimonWarning": "Tenga en cuenta que necesita Pantalaimon para utilizar el cifrado de extremo a extremo por ahora.", + "@needPantalaimonWarning": { + "type": "text", + "placeholders": {} + }, + "newMessageInFluffyChat": "Nuevo mensaje en FluffyChat", + "@newMessageInFluffyChat": { + "type": "text", + "placeholders": {} + }, + "newVerificationRequest": "¡Nueva solicitud de verificación!", + "@newVerificationRequest": { + "type": "text", + "placeholders": {} + }, + "next": "Siguiente", + "@next": { + "type": "text", + "placeholders": {} + }, + "no": "No", + "@no": { + "type": "text", + "placeholders": {} + }, + "noEmotesFound": "Ningún emote encontrado. 😕", + "@noEmotesFound": { + "type": "text", + "placeholders": {} + }, + "noEncryptionForPublicRooms": "Sólo se puede activar el cifrado en cuanto la sala deja de ser de acceso público.", + "@noEncryptionForPublicRooms": { + "type": "text", + "placeholders": {} + }, + "noGoogleServicesWarning": "Parece que no tienes servicios de Google en tu teléfono. ¡Esa es una buena decisión para tu privacidad! Para recibir notificaciones instantáneas en FluffyChat, recomendamos usar microG: https://microg.org/", + "@noGoogleServicesWarning": { + "type": "text", + "placeholders": {} + }, + "none": "Ninguno", + "@none": { + "type": "text", + "placeholders": {} + }, + "noPermission": "Sin autorización", + "@noPermission": { + "type": "text", + "placeholders": {} + }, + "noRoomsFound": "Ninguna sala encontrada…", + "@noRoomsFound": { + "type": "text", + "placeholders": {} + }, + "offline": "Desconectado", + "@offline": { + "type": "text", + "placeholders": {} + }, + "ok": "Ok", + "@ok": { + "type": "text", + "placeholders": {} + }, + "online": "Conectado", + "@online": { + "type": "text", + "placeholders": {} + }, + "onlineKeyBackupEnabled": "La copia de seguridad de la clave en línea está habilitada", + "@onlineKeyBackupEnabled": { + "type": "text", + "placeholders": {} + }, + "oopsSomethingWentWrong": "Ups, algo salió mal…", + "@oopsSomethingWentWrong": { + "type": "text", + "placeholders": {} + }, + "openAppToReadMessages": "Abrir la aplicación para leer los mensajes", + "@openAppToReadMessages": { + "type": "text", + "placeholders": {} + }, + "openCamera": "Abrir cámara", + "@openCamera": { + "type": "text", + "placeholders": {} + }, + "passphraseOrKey": "contraseña o clave de recuperación", + "@passphraseOrKey": { + "type": "text", + "placeholders": {} + }, + "password": "Contraseña", + "@password": { + "type": "text", + "placeholders": {} + }, + "passwordHasBeenChanged": "La contraseña ha sido cambiada", + "@passwordHasBeenChanged": { + "type": "text", + "placeholders": {} + }, + "people": "Personas", + "@people": { + "type": "text", + "placeholders": {} + }, + "pickImage": "Elegir imagen", + "@pickImage": { + "type": "text", + "placeholders": {} + }, + "pin": "Pin", + "@pin": { + "type": "text", + "placeholders": {} + }, + "play": "Reproducir {fileName}", + "@play": { + "type": "text", + "placeholders": { + "fileName": {} + } + }, + "pleaseClickOnLink": "Haga clic en el enlace del correo electrónico y luego continúe.", + "@pleaseClickOnLink": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourPassword": "Por favor ingrese su contraseña", + "@pleaseEnterYourPassword": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourUsername": "Por favor ingrese su nombre de usuario", + "@pleaseEnterYourUsername": { + "type": "text", + "placeholders": {} + }, + "pleaseFollowInstructionsOnWeb": "Por favor, siga las instrucciones del sitio web y presione \"siguiente\".", + "@pleaseFollowInstructionsOnWeb": { + "type": "text", + "placeholders": {} + }, + "privacy": "Privacidad", + "@privacy": { + "type": "text", + "placeholders": {} + }, + "publicRooms": "Salas públicas", + "@publicRooms": { + "type": "text", + "placeholders": {} + }, + "recording": "Grabando", + "@recording": { + "type": "text", + "placeholders": {} + }, + "redactedAnEvent": "{username} censuró un suceso", + "@redactedAnEvent": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "reject": "Rechazar", + "@reject": { + "type": "text", + "placeholders": {} + }, + "rejectedTheInvitation": "{username} rechazó la invitación", + "@rejectedTheInvitation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "rejoin": "Volver a unirse", + "@rejoin": { + "type": "text", + "placeholders": {} + }, + "remove": "Eliminar", + "@remove": { + "type": "text", + "placeholders": {} + }, + "removeAllOtherDevices": "Eliminar todos los otros dispositivos", + "@removeAllOtherDevices": { + "type": "text", + "placeholders": {} + }, + "removedBy": "Eliminado por {username}", + "@removedBy": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "removeDevice": "Eliminar dispositivo", + "@removeDevice": { + "type": "text", + "placeholders": {} + }, + "unbanFromChat": "Eliminar la expulsión", + "@unbanFromChat": { + "type": "text", + "placeholders": {} + }, + "renderRichContent": "Mostrar el contenido con mensajes enriquecidos", + "@renderRichContent": { + "type": "text", + "placeholders": {} + }, + "reply": "Responder", + "@reply": { + "type": "text", + "placeholders": {} + }, + "requestPermission": "Solicitar permiso", + "@requestPermission": { + "type": "text", + "placeholders": {} + }, + "roomHasBeenUpgraded": "La sala ha subido de categoría", + "@roomHasBeenUpgraded": { + "type": "text", + "placeholders": {} + }, + "roomVersion": "Versión de sala", + "@roomVersion": { + "type": "text", + "placeholders": {} + }, + "seenByUser": "Visto por {username}", + "@seenByUser": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "send": "Enviar", + "@send": { + "type": "text", + "placeholders": {} + }, + "sendAMessage": "Enviar un mensaje", + "@sendAMessage": { + "type": "text", + "placeholders": {} + }, + "sendAudio": "Enviar audio", + "@sendAudio": { + "type": "text", + "placeholders": {} + }, + "sendFile": "Enviar un archivo", + "@sendFile": { + "type": "text", + "placeholders": {} + }, + "sendImage": "Enviar una imagen", + "@sendImage": { + "type": "text", + "placeholders": {} + }, + "sendOriginal": "Enviar el original", + "@sendOriginal": { + "type": "text", + "placeholders": {} + }, + "sendVideo": "Enviar video", + "@sendVideo": { + "type": "text", + "placeholders": {} + }, + "sentAFile": "{username} envió un archivo", + "@sentAFile": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAnAudio": "{username} envió un audio", + "@sentAnAudio": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAPicture": "{username} envió una imagen", + "@sentAPicture": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentASticker": "{username} envió un sticker", + "@sentASticker": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAVideo": "{username} envió un video", + "@sentAVideo": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentCallInformations": "{senderName} envió información de la llamada", + "@sentCallInformations": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "setAsCanonicalAlias": "Fijar alias principal", + "@setAsCanonicalAlias": { + "type": "text", + "placeholders": {} + }, + "setInvitationLink": "Establecer enlace de invitación", + "@setInvitationLink": { + "type": "text", + "placeholders": {} + }, + "setStatus": "Establecer estado", + "@setStatus": { + "type": "text", + "placeholders": {} + }, + "settings": "Ajustes", + "@settings": { + "type": "text", + "placeholders": {} + }, + "share": "Compartir", + "@share": { + "type": "text", + "placeholders": {} + }, + "sharedTheLocation": "{username} compartió la ubicación", + "@sharedTheLocation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "showPassword": "Mostrar contraseña", + "@showPassword": { + "type": "text", + "placeholders": {} + }, + "skip": "Omitir", + "@skip": { + "type": "text", + "placeholders": {} + }, + "sourceCode": "Código fuente", + "@sourceCode": { + "type": "text", + "placeholders": {} + }, + "startedACall": "{senderName} comenzó una llamada", + "@startedACall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "statusExampleMessage": "¿Cómo estás hoy?", + "@statusExampleMessage": { + "type": "text", + "placeholders": {} + }, + "submit": "Enviar", + "@submit": { + "type": "text", + "placeholders": {} + }, + "systemTheme": "Sistema", + "@systemTheme": { + "type": "text", + "placeholders": {} + }, + "theyDontMatch": "No coinciden", + "@theyDontMatch": { + "type": "text", + "placeholders": {} + }, + "theyMatch": "Coinciden", + "@theyMatch": { + "type": "text", + "placeholders": {} + }, + "title": "FluffyChat", + "@title": { + "description": "Title for the application", + "type": "text", + "placeholders": {} + }, + "transferFromAnotherDevice": "Transferir desde otro dispositivo", + "@transferFromAnotherDevice": { + "type": "text", + "placeholders": {} + }, + "tryToSendAgain": "Intentar enviar nuevamente", + "@tryToSendAgain": { + "type": "text", + "placeholders": {} + }, + "unavailable": "Indisponible", + "@unavailable": { + "type": "text", + "placeholders": {} + }, + "unbannedUser": "{username} admitió a {targetName} nuevamente", + "@unbannedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "unblockDevice": "Desbloquear dispositivo", + "@unblockDevice": { + "type": "text", + "placeholders": {} + }, + "unknownDevice": "Dispositivo desconocido", + "@unknownDevice": { + "type": "text", + "placeholders": {} + }, + "unknownEncryptionAlgorithm": "Algoritmo de cifrado desconocido", + "@unknownEncryptionAlgorithm": { + "type": "text", + "placeholders": {} + }, + "unknownEvent": "Evento desconocido '{type}'", + "@unknownEvent": { + "type": "text", + "placeholders": { + "type": {} + } + }, + "unmuteChat": "Dejar de silenciar el chat", + "@unmuteChat": { + "type": "text", + "placeholders": {} + }, + "unpin": "Despinchar", + "@unpin": { + "type": "text", + "placeholders": {} + }, + "unreadChats": "{unreadCount, plural, =1{1 chat no leído} other{{unreadCount} chats no leídos}}", + "@unreadChats": { + "type": "text", + "placeholders": { + "unreadCount": {} + } + }, + "userAndOthersAreTyping": "{username} y {count} más están escribiendo…", + "@userAndOthersAreTyping": { + "type": "text", + "placeholders": { + "username": {}, + "count": {} + } + }, + "userAndUserAreTyping": "{username} y {username2} están escribiendo…", + "@userAndUserAreTyping": { + "type": "text", + "placeholders": { + "username": {}, + "username2": {} + } + }, + "userIsTyping": "{username} está escribiendo…", + "@userIsTyping": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "userLeftTheChat": "{username} abandonó el chat", + "@userLeftTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "username": "Nombre de usuario", + "@username": { + "type": "text", + "placeholders": {} + }, + "userSentUnknownEvent": "{username} envió un evento {type}", + "@userSentUnknownEvent": { + "type": "text", + "placeholders": { + "username": {}, + "type": {} + } + }, + "verified": "Verificado", + "@verified": { + "type": "text", + "placeholders": {} + }, + "verify": "Verificar", + "@verify": { + "type": "text", + "placeholders": {} + }, + "verifyStart": "Comenzar verificación", + "@verifyStart": { + "type": "text", + "placeholders": {} + }, + "verifySuccess": "¡Has verificado exitosamente!", + "@verifySuccess": { + "type": "text", + "placeholders": {} + }, + "verifyTitle": "Verificando la otra cuenta", + "@verifyTitle": { + "type": "text", + "placeholders": {} + }, + "videoCall": "Video llamada", + "@videoCall": { + "type": "text", + "placeholders": {} + }, + "visibilityOfTheChatHistory": "Visibilidad del historial del chat", + "@visibilityOfTheChatHistory": { + "type": "text", + "placeholders": {} + }, + "visibleForAllParticipants": "Visible para todos los participantes", + "@visibleForAllParticipants": { + "type": "text", + "placeholders": {} + }, + "visibleForEveryone": "Visible para todo el mundo", + "@visibleForEveryone": { + "type": "text", + "placeholders": {} + }, + "voiceMessage": "Mensaje de voz", + "@voiceMessage": { + "type": "text", + "placeholders": {} + }, + "waitingPartnerAcceptRequest": "Esperando a que el socio acepte la solicitud…", + "@waitingPartnerAcceptRequest": { + "type": "text", + "placeholders": {} + }, + "waitingPartnerEmoji": "Esperando a que el socio acepte los emojis…", + "@waitingPartnerEmoji": { + "type": "text", + "placeholders": {} + }, + "waitingPartnerNumbers": "Esperando a que el socio acepte los números…", + "@waitingPartnerNumbers": { + "type": "text", + "placeholders": {} + }, + "wallpaper": "Fondo de pantalla:", + "@wallpaper": { + "type": "text", + "placeholders": {} + }, + "warning": "¡Advertencia!", + "@warning": { + "type": "text", + "placeholders": {} + }, + "weSentYouAnEmail": "Te enviamos un correo electrónico", + "@weSentYouAnEmail": { + "type": "text", + "placeholders": {} + }, + "whoIsAllowedToJoinThisGroup": "Quién tiene permitido unirse al grupo", + "@whoIsAllowedToJoinThisGroup": { + "type": "text", + "placeholders": {} + }, + "writeAMessage": "Escribe un mensaje…", + "@writeAMessage": { + "type": "text", + "placeholders": {} + }, + "yes": "Sí", + "@yes": { + "type": "text", + "placeholders": {} + }, + "you": "Tú", + "@you": { + "type": "text", + "placeholders": {} + }, + "youAreNoLongerParticipatingInThisChat": "Ya no estás participando en este chat", + "@youAreNoLongerParticipatingInThisChat": { + "type": "text", + "placeholders": {} + }, + "youHaveBeenBannedFromThisChat": "Has sido vetado de este chat", + "@youHaveBeenBannedFromThisChat": { + "type": "text", + "placeholders": {} + }, + "autoplayImages": "Reproducir emoticonos y stickers animados automáticamente", + "@autoplayImages": { + "type": "text", + "placeholder": {} + }, + "allChats": "Todos los chats", + "@allChats": { + "type": "text", + "placeholders": {} + }, + "addToSpace": "Agregar al espacio", + "@addToSpace": {}, + "cantOpenUri": "No puedo abrir el URI {uri}", + "@cantOpenUri": { + "type": "text", + "placeholders": { + "uri": {} + } + }, + "all": "Todo", + "@all": { + "type": "text", + "placeholders": {} + }, + "appLock": "Bloqueo de aplicación", + "@appLock": { + "type": "text", + "placeholders": {} + }, + "botMessages": "Mensajes de bot", + "@botMessages": { + "type": "text", + "placeholders": {} + }, + "oneClientLoggedOut": "Se ha cerrado en la sesión de uno de sus clientes", + "@oneClientLoggedOut": {}, + "addAccount": "Añadir cuenta", + "@addAccount": {}, + "editBundlesForAccount": "Editar paquetes para esta cuenta", + "@editBundlesForAccount": {}, + "addToBundle": "Agregar al paquete", + "@addToBundle": {}, + "bundleName": "Nombre del paquete", + "@bundleName": {}, + "saveFile": "Guardar el archivo", + "@saveFile": { + "type": "text", + "placeholders": {} + }, + "sendSticker": "Enviar stickers", + "@sendSticker": { + "type": "text", + "placeholders": {} + }, + "setPermissionsLevel": "Establecer nivel de permisos", + "@setPermissionsLevel": { + "type": "text", + "placeholders": {} + }, + "shareLocation": "Compartir ubicación", + "@shareLocation": { + "type": "text", + "placeholders": {} + }, + "singlesignon": "Inicio de sesión único", + "@singlesignon": { + "type": "text", + "placeholders": {} + }, + "status": "Estado", + "@status": { + "type": "text", + "placeholders": {} + }, + "synchronizingPleaseWait": "Sincronizando... por favor espere.", + "@synchronizingPleaseWait": { + "type": "text", + "placeholders": {} + }, + "iHaveClickedOnLink": "He hecho clic en el enlace", + "@iHaveClickedOnLink": { + "type": "text", + "placeholders": {} + }, + "directChats": "Chat directo", + "@directChats": { + "type": "text", + "placeholders": {} + }, + "errorObtainingLocation": "Error al obtener la ubicación: {error}", + "@errorObtainingLocation": { + "type": "text", + "placeholders": { + "error": {} + } + }, + "howOffensiveIsThisContent": "¿Cuán ofensivo es este contenido?", + "@howOffensiveIsThisContent": { + "type": "text", + "placeholders": {} + }, + "inviteForMe": "Invitar por mí", + "@inviteForMe": { + "type": "text", + "placeholders": {} + }, + "noPasswordRecoveryDescription": "Aún no ha agregado una forma de recuperar su contraseña.", + "@noPasswordRecoveryDescription": { + "type": "text", + "placeholders": {} + }, + "numUsersTyping": "{count} usuarios están escribiendo…", + "@numUsersTyping": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "or": "O", + "@or": { + "type": "text", + "placeholders": {} + }, + "spaceIsPublic": "El espacio es público", + "@spaceIsPublic": { + "type": "text", + "placeholders": {} + }, + "chatHasBeenAddedToThisSpace": "El chat se ha agregado a este espacio", + "@chatHasBeenAddedToThisSpace": {}, + "commandInvalid": "Comando inválido", + "@commandInvalid": { + "type": "text" + }, + "passwordRecovery": "Recuperación de contraseña", + "@passwordRecovery": { + "type": "text", + "placeholders": {} + }, + "security": "Seguridad", + "@security": { + "type": "text", + "placeholders": {} + }, + "extremeOffensive": "Extremadamente ofensivo", + "@extremeOffensive": { + "type": "text", + "placeholders": {} + }, + "editBlockedServers": "Editar servidores bloqueado", + "@editBlockedServers": { + "type": "text", + "placeholders": {} + }, + "configureChat": "Configurar chat", + "@configureChat": { + "type": "text", + "placeholders": {} + }, + "noConnectionToTheServer": "Sin conexión al servidor", + "@noConnectionToTheServer": { + "type": "text", + "placeholders": {} + }, + "yourPublicKey": "Tu clave pública", + "@yourPublicKey": { + "type": "text", + "placeholders": {} + }, + "passwordForgotten": "Contraseña olvidada", + "@passwordForgotten": { + "type": "text", + "placeholders": {} + }, + "inoffensive": "Inofensivo", + "@inoffensive": { + "type": "text", + "placeholders": {} + }, + "reason": "Razón", + "@reason": { + "type": "text", + "placeholders": {} + }, + "memberChanges": "Cambios de miembros", + "@memberChanges": { + "type": "text", + "placeholders": {} + }, + "createNewSpace": "Nuevo espacio", + "@createNewSpace": { + "type": "text", + "placeholders": {} + }, + "edit": "Editar", + "@edit": { + "type": "text", + "placeholders": {} + }, + "setCustomEmotes": "Establecer emoticonos personalizados", + "@setCustomEmotes": { + "type": "text", + "placeholders": {} + }, + "ignore": "Ignorar", + "@ignore": { + "type": "text", + "placeholders": {} + }, + "notifications": "Notificaciones", + "@notifications": { + "type": "text", + "placeholders": {} + }, + "notificationsEnabledForThisAccount": "Notificaciones habilitadas para esta cuenta", + "@notificationsEnabledForThisAccount": { + "type": "text", + "placeholders": {} + }, + "offensive": "Ofensiva", + "@offensive": { + "type": "text", + "placeholders": {} + }, + "serverRequiresEmail": "Este servidor necesita validar su dirección de correo electrónico para registrarse.", + "@serverRequiresEmail": {}, + "pleaseChoose": "Por favor elija", + "@pleaseChoose": { + "type": "text", + "placeholders": {} + }, + "replaceRoomWithNewerVersion": "Reemplazar habitación con una versión más nueva", + "@replaceRoomWithNewerVersion": { + "type": "text", + "placeholders": {} + }, + "contentHasBeenReported": "El contenido ha sido reportado a los administradores del servidor", + "@contentHasBeenReported": { + "type": "text", + "placeholders": {} + }, + "groups": "Grupos", + "@groups": { + "type": "text", + "placeholders": {} + }, + "reportMessage": "Mensaje de informe", + "@reportMessage": { + "type": "text", + "placeholders": {} + }, + "search": "Buscar", + "@search": { + "type": "text", + "placeholders": {} + }, + "locationPermissionDeniedNotice": "Permiso de ubicación denegado. Concédeles que puedan compartir tu ubicación.", + "@locationPermissionDeniedNotice": { + "type": "text", + "placeholders": {} + }, + "defaultPermissionLevel": "Nivel de permiso predeterminado", + "@defaultPermissionLevel": { + "type": "text", + "placeholders": {} + }, + "obtainingLocation": "Obteniendo ubicación…", + "@obtainingLocation": { + "type": "text", + "placeholders": {} + }, + "participant": "Participante", + "@participant": { + "type": "text", + "placeholders": {} + }, + "pushRules": "Regla de Push", + "@pushRules": { + "type": "text", + "placeholders": {} + }, + "register": "Registrarse", + "@register": { + "type": "text", + "placeholders": {} + }, + "sendAsText": "Enviar como texto", + "@sendAsText": { + "type": "text" + }, + "toggleMuted": "Alternar silenciado", + "@toggleMuted": { + "type": "text", + "placeholders": {} + }, + "tooManyRequestsWarning": "Demasiadas solicitudes. ¡Por favor inténtelo más tarde!", + "@tooManyRequestsWarning": { + "type": "text", + "placeholders": {} + }, + "withTheseAddressesRecoveryDescription": "Con esta dirección puede recuperar su contraseña.", + "@withTheseAddressesRecoveryDescription": { + "type": "text", + "placeholders": {} + }, + "commandHint_send": "Enviar texto", + "@commandHint_send": { + "type": "text", + "description": "Usage hint for the command /send" + }, + "deviceId": "ID del dispositivo", + "@deviceId": { + "type": "text", + "placeholders": {} + }, + "containsUserName": "Contiene nombre de usuario", + "@containsUserName": { + "type": "text", + "placeholders": {} + }, + "oopsPushError": "¡UPS¡ Desafortunadamente, se produjo un error al configurar las notificaciones push.", + "@oopsPushError": { + "type": "text", + "placeholders": {} + }, + "removeYourAvatar": "Quitar tu avatar", + "@removeYourAvatar": { + "type": "text", + "placeholders": {} + }, + "enableEncryption": "Habilitar la encriptación", + "@enableEncryption": { + "type": "text", + "placeholders": {} + }, + "messages": "Mensajes", + "@messages": { + "type": "text", + "placeholders": {} + }, + "fontSize": "Tamaño de fuente", + "@fontSize": { + "type": "text", + "placeholders": {} + }, + "commandHint_ban": "Prohibir al usuario dado en esta sala", + "@commandHint_ban": { + "type": "text", + "description": "Usage hint for the command /ban" + }, + "commandHint_unban": "Des banear al usuario dado en esta sala", + "@commandHint_unban": { + "type": "text", + "description": "Usage hint for the command /unban" + }, + "commandMissing": "{command} no es un comando.", + "@commandMissing": { + "type": "text", + "placeholders": { + "command": {} + }, + "description": "State that {command} is not a valid /command." + }, + "scanQrCode": "Escanear código QR", + "@scanQrCode": {}, + "homeserver": "Homeserver", + "@homeserver": {}, + "newChat": "Nuevo chat", + "@newChat": { + "type": "text", + "placeholders": {} + }, + "commandHint_join": "Únete a la sala indicada", + "@commandHint_join": { + "type": "text", + "description": "Usage hint for the command /join" + }, + "sendOnEnter": "Enviar con enter", + "@sendOnEnter": {}, + "changeYourAvatar": "Cambiar tu avatar", + "@changeYourAvatar": { + "type": "text", + "placeholders": {} + }, + "commandHint_me": "Descríbete", + "@commandHint_me": { + "type": "text", + "description": "Usage hint for the command /me" + }, + "commandHint_html": "Enviar texto con formato HTML", + "@commandHint_html": { + "type": "text", + "description": "Usage hint for the command /html" + }, + "commandHint_invite": "Invitar al usuario indicado a esta sala", + "@commandHint_invite": { + "type": "text", + "description": "Usage hint for the command /invite" + }, + "commandHint_kick": "Eliminar el usuario indicado de esta sala", + "@commandHint_kick": { + "type": "text", + "description": "Usage hint for the command /kick" + }, + "commandHint_leave": "Deja esta sala", + "@commandHint_leave": { + "type": "text", + "description": "Usage hint for the command /leave" + }, + "commandHint_myroomavatar": "Selecciona tu foto para esta sala (by mxc-uri)", + "@commandHint_myroomavatar": { + "type": "text", + "description": "Usage hint for the command /myroomavatar" + }, + "commandHint_myroomnick": "Establece tu nombre para mostrar para esta sala", + "@commandHint_myroomnick": { + "type": "text", + "description": "Usage hint for the command /myroomnick" + }, + "commandHint_op": "Establece el nivel de potencia del usuario dado (default: 50)", + "@commandHint_op": { + "type": "text", + "description": "Usage hint for the command /op" + }, + "commandHint_plain": "Enviar texto sin formato", + "@commandHint_plain": { + "type": "text", + "description": "Usage hint for the command /plain" + }, + "commandHint_react": "Enviar respuesta como reacción", + "@commandHint_react": { + "type": "text", + "description": "Usage hint for the command /react" + }, + "containsDisplayName": "Contiene nombre para mostrar", + "@containsDisplayName": { + "type": "text", + "placeholders": {} + }, + "editRoomAvatar": "Editar avatar de sala", + "@editRoomAvatar": { + "type": "text", + "placeholders": {} + }, + "locationDisabledNotice": "Los servicios de ubicación están deshabilitado. Habilite para poder compartir su ubicación.", + "@locationDisabledNotice": { + "type": "text", + "placeholders": {} + }, + "encrypted": "Encriptado", + "@encrypted": { + "type": "text", + "placeholders": {} + }, + "goToTheNewRoom": "Ir a la nueva sala", + "@goToTheNewRoom": { + "type": "text", + "placeholders": {} + }, + "noMatrixServer": "{server1} no es un servidor matrix, usar {server2} en su lugar?", + "@noMatrixServer": { + "type": "text", + "placeholders": { + "server1": {}, + "server2": {} + } + }, + "openInMaps": "Abrir en maps", + "@openInMaps": { + "type": "text", + "placeholders": {} + }, + "removeFromBundle": "Quitar de este paquete", + "@removeFromBundle": {}, + "link": "Link", + "@link": {}, + "enableMultiAccounts": "(BETA) habilite varias cuenta en este dispositivo", + "@enableMultiAccounts": {}, + "pleaseEnter4Digits": "Ingrese 4 dígitos o déjelo en blanco para deshabilitar el bloqueo de la aplicación.", + "@pleaseEnter4Digits": { + "type": "text", + "placeholders": {} + }, + "pleaseChooseAPasscode": "Elija un código de acceso", + "@pleaseChooseAPasscode": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourPin": "Por favor ingrese su PIN", + "@pleaseEnterYourPin": { + "type": "text", + "placeholders": {} + }, + "redactMessage": "Censurar mensaje", + "@redactMessage": { + "type": "text", + "placeholders": {} + }, + "sendMessages": "Enviar mensajes", + "@sendMessages": { + "type": "text", + "placeholders": {} + }, + "spaceName": "Nombre del espacio", + "@spaceName": { + "type": "text", + "placeholders": {} + }, + "toggleFavorite": "Alternar favorito", + "@toggleFavorite": { + "type": "text", + "placeholders": {} + }, + "toggleUnread": "Marcar como: leído / no leído", + "@toggleUnread": { + "type": "text", + "placeholders": {} + }, + "whoCanPerformWhichAction": "Quién puede realizar qué acción", + "@whoCanPerformWhichAction": { + "type": "text", + "placeholders": {} + }, + "whyDoYouWantToReportThis": "¿Por qué quieres denunciar esto?", + "@whyDoYouWantToReportThis": { + "type": "text", + "placeholders": {} + }, + "wipeChatBackup": "¿Limpiar la copia de seguridad de su chat para crear una nueva clave de seguridad?", + "@wipeChatBackup": { + "type": "text", + "placeholders": {} + }, + "yourChatBackupHasBeenSetUp": "Se ha configurado la copia de respaldo del chat.", + "@yourChatBackupHasBeenSetUp": {}, + "unverified": "No verificado", + "@unverified": {}, + "commandHint_clearcache": "Limpiar cache", + "@commandHint_clearcache": { + "type": "text", + "description": "Usage hint for the command /clearcache" + }, + "messageInfo": "Información del mensaje", + "@messageInfo": {}, + "time": "Tiempo", + "@time": {}, + "openVideoCamera": "Abrir la cámara para un video", + "@openVideoCamera": { + "type": "text", + "placeholders": {} + }, + "repeatPassword": "Repita la contraseña", + "@repeatPassword": {}, + "removeFromSpace": "Eliminar del espacio", + "@removeFromSpace": {}, + "addToSpaceDescription": "Elige un espacio para añadir este chat a el.", + "@addToSpaceDescription": {}, + "openGallery": "Abrir galería", + "@openGallery": {}, + "start": "Iniciar", + "@start": {}, + "commandHint_discardsession": "Descartar sesión", + "@commandHint_discardsession": { + "type": "text", + "description": "Usage hint for the command /discardsession" + }, + "messageType": "Tipo de Mensaje", + "@messageType": {}, + "videoWithSize": "Video ({size})", + "@videoWithSize": { + "type": "text", + "placeholders": { + "size": {} + } + }, + "publish": "Publicar", + "@publish": {}, + "newSpace": "Nuevo espacio", + "@newSpace": {}, + "allSpaces": "Todos los espacios", + "@allSpaces": {}, + "widgetUrlError": "Esta no es una URL válida.", + "@widgetUrlError": {}, + "commandHint_markasgroup": "Marcar como grupo", + "@commandHint_markasgroup": {}, + "nextAccount": "Siguiente cuenta", + "@nextAccount": {}, + "youRejectedTheInvitation": "Rechazaste la invitación", + "@youRejectedTheInvitation": {}, + "newGroup": "Nuevo grupo", + "@newGroup": {}, + "widgetJitsi": "Jitsi Meet", + "@widgetJitsi": {}, + "previousAccount": "Cuenta anterior", + "@previousAccount": {}, + "users": "Usuarios", + "@users": {}, + "youInvitedBy": "📩 Has sido invitado por {user}", + "@youInvitedBy": { + "placeholders": { + "user": {} + } + }, + "youAcceptedTheInvitation": "👍 Aceptaste la invitación", + "@youAcceptedTheInvitation": {}, + "widgetEtherpad": "Nota de texto", + "@widgetEtherpad": {}, + "commandHint_cuddle": "Mandar una carantoña", + "@commandHint_cuddle": {}, + "supposedMxid": "Esto debería ser {mxid}", + "@supposedMxid": { + "type": "text", + "placeholders": { + "mxid": {} + } + }, + "importFromZipFile": "Importar de un archivo .zip", + "@importFromZipFile": {}, + "exportEmotePack": "Exportar paquete de emotes como .zip", + "@exportEmotePack": {}, + "addChatDescription": "Añadir una descripción del chat", + "@addChatDescription": {}, + "sendTypingNotifications": "Enviar notificaciones \"está escribiendo\"", + "@sendTypingNotifications": {}, + "importEmojis": "Importar emojis", + "@importEmojis": {}, + "confirmMatrixId": "Por favor confirma tu Matrix ID para borrar tu cuenta.", + "@confirmMatrixId": {}, + "notAnImage": "El archivo no es una imagen.", + "@notAnImage": {}, + "commandHint_hug": "Mandar un abrazo", + "@commandHint_hug": {}, + "importNow": "Importar ahora", + "@importNow": {}, + "hugContent": "{senderName} te abraza", + "@hugContent": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "otherCallingPermissions": "Micrófono, cámara y otros permisos de FluffyChat", + "@otherCallingPermissions": {}, + "emailOrUsername": "Correo electrónico o nombre de usuario", + "@emailOrUsername": {}, + "countFiles": "{count} archivos", + "@countFiles": { + "placeholders": { + "count": {} + } + }, + "reportUser": "Reportar usuario", + "@reportUser": {}, + "voiceCall": "Llamada de voz", + "@voiceCall": {}, + "reactedWith": "{sender} reaccionó con {reaction}", + "@reactedWith": { + "type": "text", + "placeholders": { + "sender": {}, + "reaction": {} + } + }, + "markAsRead": "Marcar como leído", + "@markAsRead": {}, + "widgetName": "Nombre", + "@widgetName": {}, + "replace": "Reemplazar", + "@replace": {}, + "unsupportedAndroidVersionLong": "Esta característica requiere una versión más reciente de Android. Por favor, compruebe las actualizaciones o la compatibilidad de LineageOS.", + "@unsupportedAndroidVersionLong": {}, + "storeSecurlyOnThisDevice": "Almacenar de forma segura en este dispositivo", + "@storeSecurlyOnThisDevice": {}, + "openChat": "Abrir chat", + "@openChat": {}, + "screenSharingDetail": "Usted está compartiendo su pantalla en FluffyChat", + "@screenSharingDetail": {}, + "allRooms": "Todos los chats grupales", + "@allRooms": { + "type": "text", + "placeholders": {} + }, + "widgetVideo": "Vídeo", + "@widgetVideo": {}, + "dismiss": "Descartar", + "@dismiss": {}, + "unsupportedAndroidVersion": "Versión de Android no compatible", + "@unsupportedAndroidVersion": {}, + "indexedDbErrorLong": "El almacenamiento de mensajes, por desgracia, no está habilitado en el modo privado por defecto.\nPor favor, visite\n - about:config\n - Establezca dom.indexedDB.privateBrowsing.enabled a true\nDe otra forma, no es posible usar FluffyChat.", + "@indexedDbErrorLong": {}, + "startFirstChat": "Comience su primer chat", + "@startFirstChat": {}, + "commandHint_create": "Crear un chat grupal vacío\nUse --no-encryption para deshabilitar el cifrado", + "@commandHint_create": { + "type": "text", + "description": "Usage hint for the command /create" + }, + "user": "Usuario", + "@user": {}, + "separateChatTypes": "Separar chats directos de grupos", + "@separateChatTypes": { + "type": "text", + "placeholders": {} + }, + "tryAgain": "Inténtelo de nuevo", + "@tryAgain": {}, + "youKickedAndBanned": "🙅 Usted expulsó y prohibió el acceso a {user}", + "@youKickedAndBanned": { + "placeholders": { + "user": {} + } + }, + "messagesStyle": "Mensajes:", + "@messagesStyle": {}, + "chatDescription": "Descripción del chat", + "@chatDescription": {}, + "callingAccountDetails": "Permite a FluffyChat utilizar la aplicación de llamadas nativa de Android.", + "@callingAccountDetails": {}, + "enterSpace": "Unirse al espacio", + "@enterSpace": {}, + "pleaseEnterRecoveryKey": "Por favor, introduzca su clave de recuperación:", + "@pleaseEnterRecoveryKey": {}, + "widgetNameError": "Por favor, introduzca un nombre a mostrar.", + "@widgetNameError": {}, + "addWidget": "Añadir widget", + "@addWidget": {}, + "hydrateTor": "TOR: Importar sesión exportada", + "@hydrateTor": {}, + "storeInAppleKeyChain": "Almacenar en la KeyChain de Apple", + "@storeInAppleKeyChain": {}, + "hydrate": "Restaurar desde fichero de copia de seguridad", + "@hydrate": {}, + "invalidServerName": "Nombre del servidor no válido", + "@invalidServerName": {}, + "chatPermissions": "Permisos del chat", + "@chatPermissions": {}, + "sender": "Remitente", + "@sender": {}, + "storeInAndroidKeystore": "Almacenar en la KeyStore de Android", + "@storeInAndroidKeystore": {}, + "saveKeyManuallyDescription": "Compartir esta clave manualmente usando el diálogo de compartir del sistema o el portapapeles.", + "@saveKeyManuallyDescription": {}, + "whyIsThisMessageEncrypted": "¿Por qué no se puede leer este mensaje?", + "@whyIsThisMessageEncrypted": {}, + "setChatDescription": "Establecer descripción del chat", + "@setChatDescription": {}, + "dehydrateWarning": "Esta acción no se puede deshacer. Asegúrese de que ha almacenado de forma segura el fichero de copia de seguridad.", + "@dehydrateWarning": {}, + "redactedBy": "Censurado por {username}", + "@redactedBy": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "videoCallsBetaWarning": "Tenga en cuenta que las videollamadas están actualmente en fase beta. Es posible que no funcionen como se espera o que no funcionen de ninguna manera en algunas plataformas.", + "@videoCallsBetaWarning": {}, + "callingPermissions": "Permisos de llamadas", + "@callingPermissions": {}, + "unlockOldMessages": "Desbloquear mensajes viejos", + "@unlockOldMessages": {}, + "numChats": "{number} chats", + "@numChats": { + "type": "number", + "placeholders": { + "number": {} + } + }, + "optionalRedactReason": "(Opcional) Motivo para censurar este mensaje...", + "@optionalRedactReason": {}, + "dehydrate": "Exportar sesión y limpiar dispositivo", + "@dehydrate": {}, + "switchToAccount": "Cambiar a la cuenta {number}", + "@switchToAccount": { + "type": "number", + "placeholders": { + "number": {} + } + }, + "experimentalVideoCalls": "Videollamadas experimentales", + "@experimentalVideoCalls": {}, + "pleaseEnterRecoveryKeyDescription": "Para desbloquear sus viejos mensajes, introduzca su clave de recuperación que se generó en una sesión anterior. Su clave de recuperación NO es su contraseña.", + "@pleaseEnterRecoveryKeyDescription": {}, + "inviteContactToGroupQuestion": "¿Quieres invitar a {contact} al chat {groupName}?", + "@inviteContactToGroupQuestion": {}, + "redactedByBecause": "Censurado por {username} porque: \"{reason}\"", + "@redactedByBecause": { + "type": "text", + "placeholders": { + "username": {}, + "reason": {} + } + }, + "youHaveWithdrawnTheInvitationFor": "Usted retiró la invitación a {user}", + "@youHaveWithdrawnTheInvitationFor": { + "placeholders": { + "user": {} + } + }, + "enterRoom": "Unirse a la sala", + "@enterRoom": {}, + "confirmEventUnpin": "¿Seguro que quiere desfijar permanentemente el evento?", + "@confirmEventUnpin": {}, + "youInvitedUser": "📩 Usted invitó a {user}", + "@youInvitedUser": { + "placeholders": { + "user": {} + } + }, + "redactMessageDescription": "El mensaje será censurado para todas las personas participantes en la conversación. Esto no se puede deshacer.", + "@redactMessageDescription": {}, + "recoveryKey": "Clave de recuperación", + "@recoveryKey": {}, + "dehydrateTorLong": "Si está usando TOR, es recomendable exportar la sesión antes de cerrar la ventana.", + "@dehydrateTorLong": {}, + "doNotShowAgain": "No mostrar de nuevo", + "@doNotShowAgain": {}, + "hideUnimportantStateEvents": "Ocultar eventos de estado no importantes", + "@hideUnimportantStateEvents": {}, + "screenSharingTitle": "Compartir la pantalla", + "@screenSharingTitle": {}, + "widgetCustom": "Personalizado", + "@widgetCustom": {}, + "youBannedUser": "Usted prohibió el acceso a {user}", + "@youBannedUser": { + "placeholders": { + "user": {} + } + }, + "directChat": "Chat directo", + "@directChat": {}, + "appearOnTop": "Aparecer en la cima", + "@appearOnTop": {}, + "foregroundServiceRunning": "Esta notificación aparece cuando el servicio en segundo plano se está ejecutando.", + "@foregroundServiceRunning": {}, + "wasDirectChatDisplayName": "Chat vacío (era {oldDisplayName})", + "@wasDirectChatDisplayName": { + "type": "text", + "placeholders": { + "oldDisplayName": {} + } + }, + "noChatDescriptionYet": "No se ha creado una descripción del chat aún.", + "@noChatDescriptionYet": {}, + "chatDescriptionHasBeenChanged": "Se ha cambiado la descripción del chat", + "@chatDescriptionHasBeenChanged": {}, + "dehydrateTor": "TOR: Exportar sesión", + "@dehydrateTor": {}, + "youKicked": "👞 Usted expulsó a {user}", + "@youKicked": { + "placeholders": { + "user": {} + } + }, + "shareInviteLink": "Compartir enlace de invitación", + "@shareInviteLink": {}, + "commandHint_markasdm": "Marcar como sala de mensajes directos para el ID de Matrix", + "@commandHint_markasdm": {}, + "recoveryKeyLost": "¿Perdió su clave de recuperación?", + "@recoveryKeyLost": {}, + "emoteKeyboardNoRecents": "Los emotes usados recientemente aparecerán aquí...", + "@emoteKeyboardNoRecents": { + "type": "text", + "placeholders": {} + }, + "youJoinedTheChat": "Usted se ha unido al chat", + "@youJoinedTheChat": {}, + "errorAddingWidget": "Fallo al añadir el widget.", + "@errorAddingWidget": {}, + "commandHint_dm": "Iniciar un chat directo\nUse --no-encryption para deshabilitar el cifrado", + "@commandHint_dm": { + "type": "text", + "description": "Usage hint for the command /dm" + }, + "youUnbannedUser": "Usted volvió a permitir el acceso a {user}", + "@youUnbannedUser": { + "placeholders": { + "user": {} + } + }, + "emojis": "Emojis", + "@emojis": {}, + "createGroup": "Crear grupo", + "@createGroup": {}, + "hydrateTorLong": "¿Exportó su sesión la última vez que estuvo en TOR? Impórtela rápidamente y continúe chateando.", + "@hydrateTorLong": {}, + "custom": "Personalizado", + "@custom": {}, + "storeInSecureStorageDescription": "Almacenar la clave de recuperación en el almacenamiento seguro de este dispositivo.", + "@storeInSecureStorageDescription": {}, + "pinMessage": "Anclar a la sala", + "@pinMessage": {}, + "indexedDbErrorTitle": "Problemas con el modo privado", + "@indexedDbErrorTitle": {}, + "googlyEyesContent": "{senderName} te manda ojos saltones", + "@googlyEyesContent": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "cuddleContent": "{senderName} se acurruca contigo", + "@cuddleContent": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "commandHint_googly": "Enviar unos ojos saltones", + "@commandHint_googly": {} } From dba69bdd21135c05ed106fd38128608452e2cbae Mon Sep 17 00:00:00 2001 From: Anonymous Date: Thu, 25 Jul 2024 05:09:45 +0000 Subject: [PATCH 111/288] Translated using Weblate (Japanese) Currently translated at 69.9% (456 of 652 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ja/ --- assets/l10n/intl_ja.arb | 4554 +++++++++++++++++++-------------------- 1 file changed, 2206 insertions(+), 2348 deletions(-) diff --git a/assets/l10n/intl_ja.arb b/assets/l10n/intl_ja.arb index a5016852f1..70db07f8fb 100644 --- a/assets/l10n/intl_ja.arb +++ b/assets/l10n/intl_ja.arb @@ -1,2349 +1,2207 @@ { - "@@locale": "ja", - "@@last_modified": "2021-08-14 12:41:09.978060", - "about": "このアプリについて", - "@about": { - "type": "text", - "placeholders": {} - }, - "accept": "承諾する", - "@accept": { - "type": "text", - "placeholders": {} - }, - "acceptedTheInvitation": "👍{username}が招待を承諾しました", - "@acceptedTheInvitation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "account": "アカウント", - "@account": { - "type": "text", - "placeholders": {} - }, - "activatedEndToEndEncryption": "🔐{username}がエンドツーエンド暗号化を有効にしました", - "@activatedEndToEndEncryption": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "addEmail": "Eメールを追加", - "@addEmail": { - "type": "text", - "placeholders": {} - }, - "admin": "管理者", - "@admin": { - "type": "text", - "placeholders": {} - }, - "alias": "エイリアス", - "@alias": { - "type": "text", - "placeholders": {} - }, - "all": "すべて", - "@all": { - "type": "text", - "placeholders": {} - }, - "answeredTheCall": "{senderName}は通話に出ました", - "@answeredTheCall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "anyoneCanJoin": "誰でも参加できる", - "@anyoneCanJoin": { - "type": "text", - "placeholders": {} - }, - "appLock": "アプリのロック", - "@appLock": { - "type": "text", - "placeholders": {} - }, - "archive": "アーカイブ", - "@archive": { - "type": "text", - "placeholders": {} - }, - "areGuestsAllowedToJoin": "ゲストユーザーの参加を許可する", - "@areGuestsAllowedToJoin": { - "type": "text", - "placeholders": {} - }, - "areYouSure": "これでよろしいですか?", - "@areYouSure": { - "type": "text", - "placeholders": {} - }, - "areYouSureYouWantToLogout": "ログアウトしてよろしいですか?", - "@areYouSureYouWantToLogout": { - "type": "text", - "placeholders": {} - }, - "askSSSSSign": "他の人を署名するためにはパスフレーズやリカバリーキーを入力してください。", - "@askSSSSSign": { - "type": "text", - "placeholders": {} - }, - "askVerificationRequest": "{username}の検証リクエストを承認しますか?", - "@askVerificationRequest": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "badServerLoginTypesException": "ホームサーバーでサポートされているログインタイプ:\n{serverVersions}\nアプリがサポートしているログインタイプ:\n{supportedVersions}", - "@badServerLoginTypesException": { - "type": "text", - "placeholders": { - "serverVersions": {}, - "supportedVersions": {} - } - }, - "badServerVersionsException": "ホームサーバーでサポートされているバージョン:\n{serverVersions}\nアプリでは{supportedVersions}しかサポートされていません", - "@badServerVersionsException": { - "type": "text", - "placeholders": { - "serverVersions": {}, - "supportedVersions": {} - } - }, - "banFromChat": "チャットからBANする", - "@banFromChat": { - "type": "text", - "placeholders": {} - }, - "banned": "BANされています", - "@banned": { - "type": "text", - "placeholders": {} - }, - "bannedUser": "{username}が{targetName}をBANしました", - "@bannedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "blockDevice": "デバイスをブロックする", - "@blockDevice": { - "type": "text", - "placeholders": {} - }, - "blocked": "ブロックしました", - "@blocked": { - "type": "text", - "placeholders": {} - }, - "botMessages": "ボットメッセージ", - "@botMessages": { - "type": "text", - "placeholders": {} - }, - "cancel": "キャンセル", - "@cancel": { - "type": "text", - "placeholders": {} - }, - "changeDeviceName": "デバイス名を変更", - "@changeDeviceName": { - "type": "text", - "placeholders": {} - }, - "changedTheChatAvatar": "{username}がチャットアバターを変更しました", - "@changedTheChatAvatar": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheChatDescriptionTo": "{username}がチャットの説明を「{description}」に変更しました", - "@changedTheChatDescriptionTo": { - "type": "text", - "placeholders": { - "username": {}, - "description": {} - } - }, - "changedTheChatNameTo": "{username}がチャットの名前を「{chatname}」に変更しました", - "@changedTheChatNameTo": { - "type": "text", - "placeholders": { - "username": {}, - "chatname": {} - } - }, - "changedTheChatPermissions": "{username}がチャットの権限を変更しました", - "@changedTheChatPermissions": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheDisplaynameTo": "{username}が表示名を「{displayname}」に変更しました", - "@changedTheDisplaynameTo": { - "type": "text", - "placeholders": { - "username": {}, - "displayname": {} - } - }, - "changedTheGuestAccessRules": "{username}がゲストのアクセスルールを変更しました", - "@changedTheGuestAccessRules": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheGuestAccessRulesTo": "{username}がゲストのアクセスルールを{rules}に変更しました", - "@changedTheGuestAccessRulesTo": { - "type": "text", - "placeholders": { - "username": {}, - "rules": {} - } - }, - "changedTheHistoryVisibility": "{username}が履歴の表示設定を変更しました", - "@changedTheHistoryVisibility": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheHistoryVisibilityTo": "{username}が履歴の表示設定を{rules}に変更しました", - "@changedTheHistoryVisibilityTo": { - "type": "text", - "placeholders": { - "username": {}, - "rules": {} - } - }, - "changedTheJoinRules": "{username}が参加ルールを変更しました", - "@changedTheJoinRules": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheJoinRulesTo": "{username}が参加ルールを{joinRules}に変更しました", - "@changedTheJoinRulesTo": { - "type": "text", - "placeholders": { - "username": {}, - "joinRules": {} - } - }, - "changedTheProfileAvatar": "{username}がアバターを変更しました", - "@changedTheProfileAvatar": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheRoomAliases": "{username}が部屋のエイリアスを変更しました", - "@changedTheRoomAliases": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheRoomInvitationLink": "{username}が招待リンクを変更しました", - "@changedTheRoomInvitationLink": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changePassword": "パスワードを変更", - "@changePassword": { - "type": "text", - "placeholders": {} - }, - "changeTheHomeserver": "ホームサーバーの変更", - "@changeTheHomeserver": { - "type": "text", - "placeholders": {} - }, - "changeTheme": "スタイルを変更する", - "@changeTheme": { - "type": "text", - "placeholders": {} - }, - "changeTheNameOfTheGroup": "グループの名前を変更する", - "@changeTheNameOfTheGroup": { - "type": "text", - "placeholders": {} - }, - "channelCorruptedDecryptError": "暗号が破損しています", - "@channelCorruptedDecryptError": { - "type": "text", - "placeholders": {} - }, - "chat": "チャット", - "@chat": { - "type": "text", - "placeholders": {} - }, - "chatBackup": "チャットのバックアップ", - "@chatBackup": { - "type": "text", - "placeholders": {} - }, - "chatBackupDescription": "古いメッセージはリカバリーキーで保護されます。紛失しないようにご注意ください。", - "@chatBackupDescription": { - "type": "text", - "placeholders": {} - }, - "chatDetails": "チャットの詳細", - "@chatDetails": { - "type": "text", - "placeholders": {} - }, - "chats": "チャット", - "@chats": { - "type": "text", - "placeholders": {} - }, - "chooseAStrongPassword": "強いパスワードを選択してください", - "@chooseAStrongPassword": { - "type": "text", - "placeholders": {} - }, - "clearArchive": "アーカイブを消去", - "@clearArchive": {}, - "close": "閉じる", - "@close": { - "type": "text", - "placeholders": {} - }, - "compareEmojiMatch": "表示されている絵文字が他のデバイスで表示されているものと一致するか確認してください:", - "@compareEmojiMatch": { - "type": "text", - "placeholders": {} - }, - "compareNumbersMatch": "表示されている数字が他のデバイスで表示されているものと一致するか確認してください:", - "@compareNumbersMatch": { - "type": "text", - "placeholders": {} - }, - "configureChat": "チャットの設定", - "@configureChat": { - "type": "text", - "placeholders": {} - }, - "confirm": "確認", - "@confirm": { - "type": "text", - "placeholders": {} - }, - "connect": "接続", - "@connect": { - "type": "text", - "placeholders": {} - }, - "contactHasBeenInvitedToTheGroup": "連絡先に登録された人が招待されました", - "@contactHasBeenInvitedToTheGroup": { - "type": "text", - "placeholders": {} - }, - "containsDisplayName": "表示名を含んでいます", - "@containsDisplayName": { - "type": "text", - "placeholders": {} - }, - "containsUserName": "ユーザー名を含んでいます", - "@containsUserName": { - "type": "text", - "placeholders": {} - }, - "contentHasBeenReported": "サーバー管理者に通報されました", - "@contentHasBeenReported": { - "type": "text", - "placeholders": {} - }, - "copiedToClipboard": "クリップボードにコピーされました", - "@copiedToClipboard": { - "type": "text", - "placeholders": {} - }, - "copy": "コピー", - "@copy": { - "type": "text", - "placeholders": {} - }, - "copyToClipboard": "クリップボードにコピー", - "@copyToClipboard": { - "type": "text", - "placeholders": {} - }, - "couldNotDecryptMessage": "メッセージを解読できませんでした: {error}", - "@couldNotDecryptMessage": { - "type": "text", - "placeholders": { - "error": {} - } - }, - "countParticipants": "{count}名の参加者", - "@countParticipants": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "create": "作成", - "@create": { - "type": "text", - "placeholders": {} - }, - "createdTheChat": "💬 {username}がチャットを作成しました", - "@createdTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "currentlyActive": "現在アクティブです", - "@currentlyActive": { - "type": "text", - "placeholders": {} - }, - "darkTheme": "ダーク", - "@darkTheme": { - "type": "text", - "placeholders": {} - }, - "dateAndTimeOfDay": "{date}, {timeOfDay}", - "@dateAndTimeOfDay": { - "type": "text", - "placeholders": { - "date": {}, - "timeOfDay": {} - } - }, - "dateWithoutYear": "{month}-{day}", - "@dateWithoutYear": { - "type": "text", - "placeholders": { - "month": {}, - "day": {} - } - }, - "dateWithYear": "{year}/{month}/{day}", - "@dateWithYear": { - "type": "text", - "placeholders": { - "year": {}, - "month": {}, - "day": {} - } - }, - "deactivateAccountWarning": "あなたのアカウントを無効化します。この操作は元に戻せません!よろしいですか?", - "@deactivateAccountWarning": { - "type": "text", - "placeholders": {} - }, - "defaultPermissionLevel": "デフォルトの権限レベル", - "@defaultPermissionLevel": { - "type": "text", - "placeholders": {} - }, - "delete": "削除", - "@delete": { - "type": "text", - "placeholders": {} - }, - "deleteAccount": "アカウントの削除", - "@deleteAccount": { - "type": "text", - "placeholders": {} - }, - "deleteMessage": "メッセージの削除", - "@deleteMessage": { - "type": "text", - "placeholders": {} - }, - "device": "デバイス", - "@device": { - "type": "text", - "placeholders": {} - }, - "deviceId": "デバイスID", - "@deviceId": { - "type": "text", - "placeholders": {} - }, - "devices": "デバイス", - "@devices": { - "type": "text", - "placeholders": {} - }, - "directChats": "ダイレクトチャット", - "@directChats": { - "type": "text", - "placeholders": {} - }, - "displaynameHasBeenChanged": "表示名が変更されました", - "@displaynameHasBeenChanged": { - "type": "text", - "placeholders": {} - }, - "downloadFile": "ファイルのダウンロード", - "@downloadFile": { - "type": "text", - "placeholders": {} - }, - "edit": "編集", - "@edit": { - "type": "text", - "placeholders": {} - }, - "editBlockedServers": "ブロックしたサーバーを編集", - "@editBlockedServers": { - "type": "text", - "placeholders": {} - }, - "editDisplayname": "表示名を編集", - "@editDisplayname": { - "type": "text", - "placeholders": {} - }, - "editRoomAliases": "ルームエイリアスを編集", - "@editRoomAliases": { - "type": "text", - "placeholders": {} - }, - "editRoomAvatar": "部屋のアバターを編集する", - "@editRoomAvatar": { - "type": "text", - "placeholders": {} - }, - "emoteExists": "Emoteはすでに存在します!", - "@emoteExists": { - "type": "text", - "placeholders": {} - }, - "emoteInvalid": "不正なEmoteショートコード!", - "@emoteInvalid": { - "type": "text", - "placeholders": {} - }, - "emotePacks": "部屋のEmoteパック", - "@emotePacks": { - "type": "text", - "placeholders": {} - }, - "emoteSettings": "Emote設定", - "@emoteSettings": { - "type": "text", - "placeholders": {} - }, - "emoteShortcode": "Emoteショートコード", - "@emoteShortcode": { - "type": "text", - "placeholders": {} - }, - "emoteWarnNeedToPick": "Emoteショートコードと画像を選択してください!", - "@emoteWarnNeedToPick": { - "type": "text", - "placeholders": {} - }, - "emptyChat": "空のチャット", - "@emptyChat": { - "type": "text", - "placeholders": {} - }, - "enableEmotesGlobally": "emoteをグローバルに有効にする", - "@enableEmotesGlobally": { - "type": "text", - "placeholders": {} - }, - "enableEncryption": "暗号化を有効にする", - "@enableEncryption": { - "type": "text", - "placeholders": {} - }, - "enableEncryptionWarning": "一度暗号化を有効にするともとに戻せません。よろしいですか?", - "@enableEncryptionWarning": { - "type": "text", - "placeholders": {} - }, - "encrypted": "暗号化", - "@encrypted": { - "type": "text", - "placeholders": {} - }, - "encryption": "暗号化", - "@encryption": { - "type": "text", - "placeholders": {} - }, - "encryptionNotEnabled": "暗号化されていません", - "@encryptionNotEnabled": { - "type": "text", - "placeholders": {} - }, - "endedTheCall": "{senderName}は通話を切断しました", - "@endedTheCall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "enterAnEmailAddress": "メールアドレスを入力してください", - "@enterAnEmailAddress": { - "type": "text", - "placeholders": {} - }, - "enterYourHomeserver": "ホームサーバーを入力してください", - "@enterYourHomeserver": { - "type": "text", - "placeholders": {} - }, - "everythingReady": "すべての準備は完了しました!", - "@everythingReady": { - "type": "text", - "placeholders": {} - }, - "extremeOffensive": "とても攻撃的", - "@extremeOffensive": { - "type": "text", - "placeholders": {} - }, - "fileName": "ファイル名", - "@fileName": { - "type": "text", - "placeholders": {} - }, - "fluffychat": "FluffyChat", - "@fluffychat": { - "type": "text", - "placeholders": {} - }, - "fontSize": "フォントサイズ", - "@fontSize": { - "type": "text", - "placeholders": {} - }, - "forward": "進む", - "@forward": { - "type": "text", - "placeholders": {} - }, - "fromJoining": "参加時点から閲覧可能", - "@fromJoining": { - "type": "text", - "placeholders": {} - }, - "fromTheInvitation": "招待時点から閲覧可能", - "@fromTheInvitation": { - "type": "text", - "placeholders": {} - }, - "goToTheNewRoom": "新規ルームへ", - "@goToTheNewRoom": { - "type": "text", - "placeholders": {} - }, - "group": "グループ", - "@group": { - "type": "text", - "placeholders": {} - }, - "groupIsPublic": "グループは公開されています", - "@groupIsPublic": { - "type": "text", - "placeholders": {} - }, - "groups": "グループ", - "@groups": { - "type": "text", - "placeholders": {} - }, - "groupWith": "{displayname}とグループを作成する", - "@groupWith": { - "type": "text", - "placeholders": { - "displayname": {} - } - }, - "guestsAreForbidden": "ゲストは許可されていません", - "@guestsAreForbidden": { - "type": "text", - "placeholders": {} - }, - "guestsCanJoin": "ゲストが許可されています", - "@guestsCanJoin": { - "type": "text", - "placeholders": {} - }, - "hasWithdrawnTheInvitationFor": "{targetName}の招待を{username}が取り下げました", - "@hasWithdrawnTheInvitationFor": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "help": "ヘルプ", - "@help": { - "type": "text", - "placeholders": {} - }, - "hideRedactedEvents": "編集済みイベントを非表示にする", - "@hideRedactedEvents": { - "type": "text", - "placeholders": {} - }, - "hideUnknownEvents": "不明なイベントを非表示にする", - "@hideUnknownEvents": { - "type": "text", - "placeholders": {} - }, - "howOffensiveIsThisContent": "どのくらい攻撃的でしたか?", - "@howOffensiveIsThisContent": { - "type": "text", - "placeholders": {} - }, - "id": "ID", - "@id": { - "type": "text", - "placeholders": {} - }, - "identity": "アイデンティティ", - "@identity": { - "type": "text", - "placeholders": {} - }, - "ignore": "無視する", - "@ignore": { - "type": "text", - "placeholders": {} - }, - "ignoredUsers": "無視されたユーザー", - "@ignoredUsers": { - "type": "text", - "placeholders": {} - }, - "iHaveClickedOnLink": "リンクをクリックしました", - "@iHaveClickedOnLink": { - "type": "text", - "placeholders": {} - }, - "incorrectPassphraseOrKey": "パスフレーズかリカバリーキーが間違っています", - "@incorrectPassphraseOrKey": { - "type": "text", - "placeholders": {} - }, - "inoffensive": "非攻撃的", - "@inoffensive": { - "type": "text", - "placeholders": {} - }, - "inviteContact": "連絡先から招待する", - "@inviteContact": { - "type": "text", - "placeholders": {} - }, - "inviteContactToGroup": "連絡先から{groupName}に招待する", - "@inviteContactToGroup": { - "type": "text", - "placeholders": { - "groupName": {} - } - }, - "invited": "招待されました", - "@invited": { - "type": "text", - "placeholders": {} - }, - "invitedUser": "📩 {username} が {targetName} を招待しました", - "@invitedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "invitedUsersOnly": "招待されたユーザーのみ", - "@invitedUsersOnly": { - "type": "text", - "placeholders": {} - }, - "inviteForMe": "自分への招待", - "@inviteForMe": { - "type": "text", - "placeholders": {} - }, - "inviteText": "{username}がFluffyChatにあなたを招待しました. \n1. FluffyChatをインストールしてください: https://fluffychat.im \n2. 新しくアカウントを作成するかサインインしてください\n3. 招待リンクを開いてください: {link}", - "@inviteText": { - "type": "text", - "placeholders": { - "username": {}, - "link": {} - } - }, - "isTyping": "が入力しています…", - "@isTyping": { - "type": "text", - "placeholders": {} - }, - "joinedTheChat": "👋 {username} がチャットに参加しました", - "@joinedTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "joinRoom": "部屋に参加", - "@joinRoom": { - "type": "text", - "placeholders": {} - }, - "kicked": "👞 {username} は {targetName} をキックしました", - "@kicked": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "kickedAndBanned": "🙅 {username} が {targetName} をキックしブロックしました", - "@kickedAndBanned": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "kickFromChat": "チャットからキックする", - "@kickFromChat": { - "type": "text", - "placeholders": {} - }, - "lastActiveAgo": "最終アクティブ: {localizedTimeShort}", - "@lastActiveAgo": { - "type": "text", - "placeholders": { - "localizedTimeShort": {} - } - }, - "leave": "退室する", - "@leave": { - "type": "text", - "placeholders": {} - }, - "leftTheChat": "退室しました", - "@leftTheChat": { - "type": "text", - "placeholders": {} - }, - "license": "ライセンス", - "@license": { - "type": "text", - "placeholders": {} - }, - "lightTheme": "ライト", - "@lightTheme": { - "type": "text", - "placeholders": {} - }, - "loadCountMoreParticipants": "あと{count}名参加者を読み込む", - "@loadCountMoreParticipants": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "loadingPleaseWait": "読み込み中…お待ちください。", - "@loadingPleaseWait": { - "type": "text", - "placeholders": {} - }, - "loadMore": "更に読み込む…", - "@loadMore": { - "type": "text", - "placeholders": {} - }, - "login": "ログイン", - "@login": { - "type": "text", - "placeholders": {} - }, - "logInTo": "{homeserver}にログインする", - "@logInTo": { - "type": "text", - "placeholders": { - "homeserver": {} - } - }, - "logout": "ログアウト", - "@logout": { - "type": "text", - "placeholders": {} - }, - "memberChanges": "メンバーの変更", - "@memberChanges": { - "type": "text", - "placeholders": {} - }, - "mention": "メンション", - "@mention": { - "type": "text", - "placeholders": {} - }, - "messages": "メッセージ", - "@messages": { - "type": "text", - "placeholders": {} - }, - "moderator": "モデレータ", - "@moderator": { - "type": "text", - "placeholders": {} - }, - "muteChat": "チャットのミュート", - "@muteChat": { - "type": "text", - "placeholders": {} - }, - "needPantalaimonWarning": "現時点では、エンドツーエンドの暗号化を使用するにはPantalaimonが必要であることに注意してください。", - "@needPantalaimonWarning": { - "type": "text", - "placeholders": {} - }, - "newChat": "新規チャット", - "@newChat": { - "type": "text", - "placeholders": {} - }, - "newMessageInFluffyChat": "💬 FluffyChatに新しいメッセージがあります", - "@newMessageInFluffyChat": { - "type": "text", - "placeholders": {} - }, - "newVerificationRequest": "認証リクエスト!", - "@newVerificationRequest": { - "type": "text", - "placeholders": {} - }, - "next": "次へ", - "@next": { - "type": "text", - "placeholders": {} - }, - "no": "いいえ", - "@no": { - "type": "text", - "placeholders": {} - }, - "noConnectionToTheServer": "サーバーに接続できません", - "@noConnectionToTheServer": { - "type": "text", - "placeholders": {} - }, - "noEmotesFound": "Emoteは見つかりませんでした😕", - "@noEmotesFound": { - "type": "text", - "placeholders": {} - }, - "noEncryptionForPublicRooms": "ルームを非公開にした後暗号化を有効にできます。", - "@noEncryptionForPublicRooms": { - "type": "text", - "placeholders": {} - }, - "noGoogleServicesWarning": "あなたのスマホにはGoogleサービスがないようですね。プライバシーを保護するための良い選択です!プッシュ通知を受け取るには https://microg.org/ または https://unifiedpush.org/ を使うことをお勧めします。", - "@noGoogleServicesWarning": { - "type": "text", - "placeholders": {} - }, - "none": "なし", - "@none": { - "type": "text", - "placeholders": {} - }, - "noPasswordRecoveryDescription": "パスワードを回復する方法をまだ追加していません。", - "@noPasswordRecoveryDescription": { - "type": "text", - "placeholders": {} - }, - "noPermission": "権限がありません", - "@noPermission": { - "type": "text", - "placeholders": {} - }, - "noRoomsFound": "部屋は見つかりませんでした…", - "@noRoomsFound": { - "type": "text", - "placeholders": {} - }, - "notifications": "通知", - "@notifications": { - "type": "text", - "placeholders": {} - }, - "notificationsEnabledForThisAccount": "このアカウントでは通知が有効です", - "@notificationsEnabledForThisAccount": { - "type": "text", - "placeholders": {} - }, - "numUsersTyping": "{count}人が入力中…", - "@numUsersTyping": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "offensive": "攻撃的", - "@offensive": { - "type": "text", - "placeholders": {} - }, - "offline": "オフライン", - "@offline": { - "type": "text", - "placeholders": {} - }, - "ok": "OK", - "@ok": { - "type": "text", - "placeholders": {} - }, - "online": "オンライン", - "@online": { - "type": "text", - "placeholders": {} - }, - "onlineKeyBackupEnabled": "オンライン鍵バックアップは使用されています", - "@onlineKeyBackupEnabled": { - "type": "text", - "placeholders": {} - }, - "oopsSomethingWentWrong": "おっと、何かがうまくいきませんでした…", - "@oopsSomethingWentWrong": { - "type": "text", - "placeholders": {} - }, - "openAppToReadMessages": "アプリを開いてメッセージを確認してください", - "@openAppToReadMessages": { - "type": "text", - "placeholders": {} - }, - "openCamera": "カメラを開く", - "@openCamera": { - "type": "text", - "placeholders": {} - }, - "participant": "参加者", - "@participant": { - "type": "text", - "placeholders": {} - }, - "passphraseOrKey": "パスフレーズかリカバリーキー", - "@passphraseOrKey": { - "type": "text", - "placeholders": {} - }, - "password": "パスワード", - "@password": { - "type": "text", - "placeholders": {} - }, - "passwordForgotten": "パスワードを忘れた", - "@passwordForgotten": { - "type": "text", - "placeholders": {} - }, - "passwordHasBeenChanged": "パスワードが変更されました", - "@passwordHasBeenChanged": { - "type": "text", - "placeholders": {} - }, - "passwordRecovery": "パスワードリカバリー", - "@passwordRecovery": { - "type": "text", - "placeholders": {} - }, - "people": "人々", - "@people": { - "type": "text", - "placeholders": {} - }, - "pickImage": "画像を選択してください", - "@pickImage": { - "type": "text", - "placeholders": {} - }, - "pin": "ピン", - "@pin": { - "type": "text", - "placeholders": {} - }, - "play": "{fileName}を再生する", - "@play": { - "type": "text", - "placeholders": { - "fileName": {} - } - }, - "pleaseChooseAPasscode": "パスコードを選んでください", - "@pleaseChooseAPasscode": { - "type": "text", - "placeholders": {} - }, - "pleaseClickOnLink": "メールのリンクから進めてください。", - "@pleaseClickOnLink": { - "type": "text", - "placeholders": {} - }, - "pleaseEnter4Digits": "アプリのロック用に4桁の数字を入力してください。空欄の場合は無効になります。", - "@pleaseEnter4Digits": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourPassword": "パスワードを入力してください", - "@pleaseEnterYourPassword": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourPin": "PINを入力してください", - "@pleaseEnterYourPin": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourUsername": "ユーザー名を入力してください", - "@pleaseEnterYourUsername": { - "type": "text", - "placeholders": {} - }, - "pleaseFollowInstructionsOnWeb": "ウェブサイトにあるやり方を見てから次をタップしてください。", - "@pleaseFollowInstructionsOnWeb": { - "type": "text", - "placeholders": {} - }, - "privacy": "プライバシー", - "@privacy": { - "type": "text", - "placeholders": {} - }, - "publicRooms": "公開された部屋", - "@publicRooms": { - "type": "text", - "placeholders": {} - }, - "pushRules": "ルールを追加する", - "@pushRules": { - "type": "text", - "placeholders": {} - }, - "reason": "理由", - "@reason": { - "type": "text", - "placeholders": {} - }, - "recording": "録音中", - "@recording": { - "type": "text", - "placeholders": {} - }, - "redactedAnEvent": "{username}がイベントを編集しました", - "@redactedAnEvent": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "redactMessage": "メッセージを書く", - "@redactMessage": { - "type": "text", - "placeholders": {} - }, - "reject": "拒否", - "@reject": { - "type": "text", - "placeholders": {} - }, - "rejectedTheInvitation": "{username}は招待を拒否しました", - "@rejectedTheInvitation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "rejoin": "再参加", - "@rejoin": { - "type": "text", - "placeholders": {} - }, - "remove": "消去", - "@remove": { - "type": "text", - "placeholders": {} - }, - "removeAllOtherDevices": "他のデバイスをすべて削除", - "@removeAllOtherDevices": { - "type": "text", - "placeholders": {} - }, - "removedBy": "{username}によって削除されました", - "@removedBy": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "removeDevice": "デバイスの削除", - "@removeDevice": { - "type": "text", - "placeholders": {} - }, - "unbanFromChat": "チャットからのブロックを解除する", - "@unbanFromChat": { - "type": "text", - "placeholders": {} - }, - "renderRichContent": "リッチメッセージをレンダリングする", - "@renderRichContent": { - "type": "text", - "placeholders": {} - }, - "replaceRoomWithNewerVersion": "部屋を新しいバージョンに変更する", - "@replaceRoomWithNewerVersion": { - "type": "text", - "placeholders": {} - }, - "reply": "返信", - "@reply": { - "type": "text", - "placeholders": {} - }, - "reportMessage": "メッセージを通報", - "@reportMessage": { - "type": "text", - "placeholders": {} - }, - "requestPermission": "権限を要求する", - "@requestPermission": { - "type": "text", - "placeholders": {} - }, - "roomHasBeenUpgraded": "部屋はアップグレードされました", - "@roomHasBeenUpgraded": { - "type": "text", - "placeholders": {} - }, - "roomVersion": "ルームバージョン", - "@roomVersion": { - "type": "text", - "placeholders": {} - }, - "search": "検索", - "@search": { - "type": "text", - "placeholders": {} - }, - "security": "セキュリティ", - "@security": { - "type": "text", - "placeholders": {} - }, - "seenByUser": "{username}が既読", - "@seenByUser": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "send": "送信", - "@send": { - "type": "text", - "placeholders": {} - }, - "sendAMessage": "メッセージを送信", - "@sendAMessage": { - "type": "text", - "placeholders": {} - }, - "sendAudio": "音声の送信", - "@sendAudio": { - "type": "text", - "placeholders": {} - }, - "sendFile": "ファイルを送信", - "@sendFile": { - "type": "text", - "placeholders": {} - }, - "sendImage": "画像の送信", - "@sendImage": { - "type": "text", - "placeholders": {} - }, - "sendMessages": "メッセージを送る", - "@sendMessages": { - "type": "text", - "placeholders": {} - }, - "sendOriginal": "オリジナルの送信", - "@sendOriginal": { - "type": "text", - "placeholders": {} - }, - "sendVideo": "動画を送信", - "@sendVideo": { - "type": "text", - "placeholders": {} - }, - "sentAFile": "📁 {username}はファイルを送信しました", - "@sentAFile": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentAnAudio": "🎤 {username}は音声を送信しました", - "@sentAnAudio": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentAPicture": "🖼️ {username}は画像を送信しました", - "@sentAPicture": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentASticker": "😊 {username}はステッカーを送信しました", - "@sentASticker": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentAVideo": "🎥 {username}は動画を送信しました", - "@sentAVideo": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentCallInformations": "{senderName}は通話情報を送信しました", - "@sentCallInformations": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "setAsCanonicalAlias": "メインエイリアスに設定", - "@setAsCanonicalAlias": { - "type": "text", - "placeholders": {} - }, - "setCustomEmotes": "カスタムエモートの設定", - "@setCustomEmotes": { - "type": "text", - "placeholders": {} - }, - "setInvitationLink": "招待リンクを設定する", - "@setInvitationLink": { - "type": "text", - "placeholders": {} - }, - "setPermissionsLevel": "権限レベルをセット", - "@setPermissionsLevel": { - "type": "text", - "placeholders": {} - }, - "setStatus": "ステータスの設定", - "@setStatus": { - "type": "text", - "placeholders": {} - }, - "settings": "設定", - "@settings": { - "type": "text", - "placeholders": {} - }, - "share": "共有", - "@share": { - "type": "text", - "placeholders": {} - }, - "sharedTheLocation": "{username}は現在地を共有しました", - "@sharedTheLocation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "showPassword": "パスワードを表示", - "@showPassword": { - "type": "text", - "placeholders": {} - }, - "skip": "スキップ", - "@skip": { - "type": "text", - "placeholders": {} - }, - "sourceCode": "ソースコード", - "@sourceCode": { - "type": "text", - "placeholders": {} - }, - "startedACall": "{senderName}は通話を開始しました", - "@startedACall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "status": "ステータス", - "@status": { - "type": "text", - "placeholders": {} - }, - "statusExampleMessage": "お元気ですか?", - "@statusExampleMessage": { - "type": "text", - "placeholders": {} - }, - "submit": "送信", - "@submit": { - "type": "text", - "placeholders": {} - }, - "systemTheme": "システム", - "@systemTheme": { - "type": "text", - "placeholders": {} - }, - "theyDontMatch": "違います", - "@theyDontMatch": { - "type": "text", - "placeholders": {} - }, - "theyMatch": "一致しています", - "@theyMatch": { - "type": "text", - "placeholders": {} - }, - "title": "FluffyChat", - "@title": { - "description": "Title for the application", - "type": "text", - "placeholders": {} - }, - "toggleFavorite": "お気に入り切り替え", - "@toggleFavorite": { - "type": "text", - "placeholders": {} - }, - "toggleMuted": "ミュート切り替え", - "@toggleMuted": { - "type": "text", - "placeholders": {} - }, - "toggleUnread": "既読/未読にマーク", - "@toggleUnread": { - "type": "text", - "placeholders": {} - }, - "tooManyRequestsWarning": "リクエストが多すぎます。また後で試してみてください!", - "@tooManyRequestsWarning": { - "type": "text", - "placeholders": {} - }, - "transferFromAnotherDevice": "違うデバイスから移行する", - "@transferFromAnotherDevice": { - "type": "text", - "placeholders": {} - }, - "tryToSendAgain": "送信し直してみる", - "@tryToSendAgain": { - "type": "text", - "placeholders": {} - }, - "unavailable": "不在", - "@unavailable": { - "type": "text", - "placeholders": {} - }, - "unbannedUser": "{username}が{targetName}のBANを解除しました", - "@unbannedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "unblockDevice": "デバイスをブロック解除する", - "@unblockDevice": { - "type": "text", - "placeholders": {} - }, - "unknownDevice": "未知デバイス", - "@unknownDevice": { - "type": "text", - "placeholders": {} - }, - "unknownEncryptionAlgorithm": "未知の暗号化アルゴリズム", - "@unknownEncryptionAlgorithm": { - "type": "text", - "placeholders": {} - }, - "unknownEvent": "未知のイベント'{type}'", - "@unknownEvent": { - "type": "text", - "placeholders": { - "type": {} - } - }, - "unmuteChat": "チャットをミュート解除する", - "@unmuteChat": { - "type": "text", - "placeholders": {} - }, - "unpin": "ピンを外す", - "@unpin": { - "type": "text", - "placeholders": {} - }, - "unreadChats": "{unreadCount, plural, =1{1件の未読メッセージ} other{{unreadCount}件の未読メッセージ}}", - "@unreadChats": { - "type": "text", - "placeholders": { - "unreadCount": {} - } - }, - "userAndOthersAreTyping": "{username}と他{count}名が入力しています…", - "@userAndOthersAreTyping": { - "type": "text", - "placeholders": { - "username": {}, - "count": {} - } - }, - "userAndUserAreTyping": "{username}と{username2}が入力しています…", - "@userAndUserAreTyping": { - "type": "text", - "placeholders": { - "username": {}, - "username2": {} - } - }, - "userIsTyping": "{username}が入力しています…", - "@userIsTyping": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "userLeftTheChat": "🚪 {username}はチャットから退室しました", - "@userLeftTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "username": "ユーザー名", - "@username": { - "type": "text", - "placeholders": {} - }, - "userSentUnknownEvent": "{username}は{type}イベントを送信しました", - "@userSentUnknownEvent": { - "type": "text", - "placeholders": { - "username": {}, - "type": {} - } - }, - "verified": "検証済み", - "@verified": { - "type": "text", - "placeholders": {} - }, - "verify": "確認", - "@verify": { - "type": "text", - "placeholders": {} - }, - "verifyStart": "確認を始める", - "@verifyStart": { - "type": "text", - "placeholders": {} - }, - "verifySuccess": "確認が完了しました!", - "@verifySuccess": { - "type": "text", - "placeholders": {} - }, - "verifyTitle": "他のアカウントを確認中", - "@verifyTitle": { - "type": "text", - "placeholders": {} - }, - "videoCall": "音声通話", - "@videoCall": { - "type": "text", - "placeholders": {} - }, - "visibilityOfTheChatHistory": "チャット履歴の表示", - "@visibilityOfTheChatHistory": { - "type": "text", - "placeholders": {} - }, - "visibleForAllParticipants": "すべての参加者が閲覧可能", - "@visibleForAllParticipants": { - "type": "text", - "placeholders": {} - }, - "visibleForEveryone": "すべての人が閲覧可能", - "@visibleForEveryone": { - "type": "text", - "placeholders": {} - }, - "voiceMessage": "ボイスメッセージ", - "@voiceMessage": { - "type": "text", - "placeholders": {} - }, - "waitingPartnerAcceptRequest": "パートナーのリクエスト承諾待ちです...", - "@waitingPartnerAcceptRequest": { - "type": "text", - "placeholders": {} - }, - "waitingPartnerEmoji": "パートナーの絵文字承諾待ちです...", - "@waitingPartnerEmoji": { - "type": "text", - "placeholders": {} - }, - "waitingPartnerNumbers": "パートナーの数字承諾待ちです…", - "@waitingPartnerNumbers": { - "type": "text", - "placeholders": {} - }, - "wallpaper": "壁紙", - "@wallpaper": { - "type": "text", - "placeholders": {} - }, - "warning": "警告!", - "@warning": { - "type": "text", - "placeholders": {} - }, - "weSentYouAnEmail": "あなたにメールを送信しました", - "@weSentYouAnEmail": { - "type": "text", - "placeholders": {} - }, - "whoCanPerformWhichAction": "誰がどの操作を実行できるか", - "@whoCanPerformWhichAction": { - "type": "text", - "placeholders": {} - }, - "whoIsAllowedToJoinThisGroup": "誰がこのチャットに入れますか", - "@whoIsAllowedToJoinThisGroup": { - "type": "text", - "placeholders": {} - }, - "whyDoYouWantToReportThis": "これを通報する理由", - "@whyDoYouWantToReportThis": { - "type": "text", - "placeholders": {} - }, - "wipeChatBackup": "チャットのバックアップを消去して、新しいリカバリーキーを作りますか?", - "@wipeChatBackup": { - "type": "text", - "placeholders": {} - }, - "withTheseAddressesRecoveryDescription": "これらのアドレスを使用すると、パスワードを回復することができます。", - "@withTheseAddressesRecoveryDescription": { - "type": "text", - "placeholders": {} - }, - "writeAMessage": "メッセージを入力してください…", - "@writeAMessage": { - "type": "text", - "placeholders": {} - }, - "yes": "はい", - "@yes": { - "type": "text", - "placeholders": {} - }, - "you": "あなた", - "@you": { - "type": "text", - "placeholders": {} - }, - "youAreNoLongerParticipatingInThisChat": "あなたはもうこのチャットの参加者ではありません", - "@youAreNoLongerParticipatingInThisChat": { - "type": "text", - "placeholders": {} - }, - "youHaveBeenBannedFromThisChat": "チャットからBANされてしまいました", - "@youHaveBeenBannedFromThisChat": { - "type": "text", - "placeholders": {} - }, - "yourPublicKey": "あなたの公開鍵", - "@yourPublicKey": { - "type": "text", - "placeholders": {} - }, - "allChats": "すべて会話", - "@allChats": { - "type": "text", - "placeholders": {} - }, - "addToSpace": "スペースに追加", - "@addToSpace": {}, - "cantOpenUri": "URIが開けません {uri}", - "@cantOpenUri": { - "type": "text", - "placeholders": { - "uri": {} - } - }, - "repeatPassword": "パスワードを繰り返そ", - "@repeatPassword": {}, - "autoplayImages": "GIFを自動的に再生する", - "@autoplayImages": { - "type": "text", - "placeholder": {} - }, - "yourChatBackupHasBeenSetUp": "チャットバックアップを設定ました。", - "@yourChatBackupHasBeenSetUp": {}, - "sendOnEnter": "Enterで送信", - "@sendOnEnter": {}, - "changeYourAvatar": "アバタるを変化しする", - "@changeYourAvatar": { - "type": "text", - "placeholders": {} - }, - "chatHasBeenAddedToThisSpace": "このスペースにチャットが追加されました", - "@chatHasBeenAddedToThisSpace": {}, - "commandHint_ban": "このユーザーを禁止する", - "@commandHint_ban": { - "type": "text", - "description": "Usage hint for the command /ban" - }, - "commandHint_clearcache": "キャッシュをクリアする", - "@commandHint_clearcache": { - "type": "text", - "description": "Usage hint for the command /clearcache" - }, - "commandInvalid": "コマンドが無効", - "@commandInvalid": { - "type": "text" - }, - "commandHint_create": "空のグループチャットを作成\n暗号化を無効にするには、--no-encryption を使用", - "@commandHint_create": { - "type": "text", - "description": "Usage hint for the command /create" - }, - "commandHint_discardsession": "セッションを破棄", - "@commandHint_discardsession": { - "type": "text", - "description": "Usage hint for the command /discardsession" - }, - "confirmMatrixId": "アカウントを削除するには、Matrix IDを確認してください。", - "@confirmMatrixId": {}, - "commandHint_markasgroup": "グループとしてマーク", - "@commandHint_markasgroup": {}, - "commandHint_join": "指定した部屋に参加", - "@commandHint_join": { - "type": "text", - "description": "Usage hint for the command /join" - }, - "commandHint_send": "テキストを送信", - "@commandHint_send": { - "type": "text", - "description": "Usage hint for the command /send" - }, - "hydrate": "バックアップファイルから復元", - "@hydrate": {}, - "commandHint_html": "HTML形式のテキストを送信", - "@commandHint_html": { - "type": "text", - "description": "Usage hint for the command /html" - }, - "commandHint_invite": "指定したユーザーをこの部屋に招待", - "@commandHint_invite": { - "type": "text", - "description": "Usage hint for the command /invite" - }, - "commandMissing": "{command} はコマンドではありません。", - "@commandMissing": { - "type": "text", - "placeholders": { - "command": {} - }, - "description": "State that {command} is not a valid /command." - }, - "oneClientLoggedOut": "クライアントの 1つがログアウトしました", - "@oneClientLoggedOut": {}, - "addAccount": "アカウントを追加", - "@addAccount": {}, - "editBundlesForAccount": "このアカウントのバンドルを編集", - "@editBundlesForAccount": {}, - "unverified": "未検証", - "@unverified": {}, - "sender": "送信者", - "@sender": {}, - "placeCall": "電話をかける", - "@placeCall": {}, - "voiceCall": "音声通話", - "@voiceCall": {}, - "unsupportedAndroidVersionLong": "この機能を利用するには、より新しいAndroidのバージョンが必要です。アップデートまたはLineage OSのサポートをご確認ください。", - "@unsupportedAndroidVersionLong": {}, - "widgetVideo": "動画", - "@widgetVideo": {}, - "widgetName": "名称", - "@widgetName": {}, - "widgetCustom": "カスタム", - "@widgetCustom": {}, - "widgetJitsi": "Jitsi Meet", - "@widgetJitsi": {}, - "dehydrateWarning": "この操作は元に戻せません。バックアップファイルを安全に保存してください。", - "@dehydrateWarning": {}, - "dehydrate": "セッションのエクスポートとデバイスの消去", - "@dehydrate": {}, - "messageType": "メッセージの種類", - "@messageType": {}, - "start": "開始", - "@start": {}, - "publish": "公開", - "@publish": {}, - "indexedDbErrorTitle": "プライベートモードに関する問題", - "@indexedDbErrorTitle": {}, - "addWidget": "ウィジェットを追加", - "@addWidget": {}, - "youBannedUser": "{user} を禁止しました", - "@youBannedUser": { - "placeholders": { - "user": {} - } - }, - "youJoinedTheChat": "チャットに参加しました", - "@youJoinedTheChat": {}, - "youHaveWithdrawnTheInvitationFor": "{user} への招待を取り下げました", - "@youHaveWithdrawnTheInvitationFor": { - "placeholders": { - "user": {} - } - }, - "users": "ユーザー", - "@users": {}, - "youRejectedTheInvitation": "招待を拒否しました", - "@youRejectedTheInvitation": {}, - "screenSharingDetail": "FuffyChatで画面を共有しています", - "@screenSharingDetail": {}, - "homeserver": "ホームサーバー", - "@homeserver": {}, - "scanQrCode": "QRコードをスキャン", - "@scanQrCode": {}, - "obtainingLocation": "位置情報を取得しています…", - "@obtainingLocation": { - "type": "text", - "placeholders": {} - }, - "addToBundle": "バンドルに追加", - "@addToBundle": {}, - "removeFromBundle": "このバンドルから削除", - "@removeFromBundle": {}, - "bundleName": "バンドル名", - "@bundleName": {}, - "noMatrixServer": "{server1} はMatrixのサーバーではありません。代わりに {server2} を使用しますか?", - "@noMatrixServer": { - "type": "text", - "placeholders": { - "server1": {}, - "server2": {} - } - }, - "openVideoCamera": "ビデオ用にカメラを開く", - "@openVideoCamera": { - "type": "text", - "placeholders": {} - }, - "link": "リンク", - "@link": {}, - "or": "または", - "@or": { - "type": "text", - "placeholders": {} - }, - "register": "登録", - "@register": { - "type": "text", - "placeholders": {} - }, - "removeYourAvatar": "アバターを削除する", - "@removeYourAvatar": { - "type": "text", - "placeholders": {} - }, - "saveFile": "ファイルを保存", - "@saveFile": { - "type": "text", - "placeholders": {} - }, - "recoveryKey": "リカバリーキー", - "@recoveryKey": {}, - "singlesignon": "シングルサインオン", - "@singlesignon": { - "type": "text", - "placeholders": {} - }, - "spaceIsPublic": "スペースは公開されています", - "@spaceIsPublic": { - "type": "text", - "placeholders": {} - }, - "spaceName": "スペース名", - "@spaceName": { - "type": "text", - "placeholders": {} - }, - "startFirstChat": "最初のチャットを開始する", - "@startFirstChat": {}, - "addToSpaceDescription": "このチャットを追加するスペースを選択してください。", - "@addToSpaceDescription": {}, - "messageInfo": "メッセージの情報", - "@messageInfo": {}, - "openGallery": "ギャラリーを開く", - "@openGallery": {}, - "removeFromSpace": "スペースから削除", - "@removeFromSpace": {}, - "pleaseEnterRecoveryKeyDescription": "古いメッセージを解除するには、以前のセッションで生成されたリカバリーキーを入力してください。リカバリーキーはパスワードではありません。", - "@pleaseEnterRecoveryKeyDescription": {}, - "videoWithSize": "ビデオ ({size})", - "@videoWithSize": { - "type": "text", - "placeholders": { - "size": {} - } - }, - "openChat": "チャットを開く", - "@openChat": {}, - "experimentalVideoCalls": "実験的なビデオ通話", - "@experimentalVideoCalls": {}, - "emailOrUsername": "メールアドレスまたはユーザー名", - "@emailOrUsername": {}, - "switchToAccount": "アカウント {number} に切り替える", - "@switchToAccount": { - "type": "number", - "placeholders": { - "number": {} - } - }, - "nextAccount": "次のアカウント", - "@nextAccount": {}, - "youAcceptedTheInvitation": "👍 招待を承諾しました", - "@youAcceptedTheInvitation": {}, - "errorAddingWidget": "ウィジェットの追加中にエラーが発生しました。", - "@errorAddingWidget": {}, - "widgetNameError": "表示名を入力してください。", - "@widgetNameError": {}, - "youUnbannedUser": "{user} の禁止を解除しました", - "@youUnbannedUser": { - "placeholders": { - "user": {} - } - }, - "youInvitedBy": "📩 {user} から招待されました", - "@youInvitedBy": { - "placeholders": { - "user": {} - } - }, - "youKicked": "👞 {user} をキックしました", - "@youKicked": { - "placeholders": { - "user": {} - } - }, - "youKickedAndBanned": "🙅 {user} をキックしてブロックしました", - "@youKickedAndBanned": { - "placeholders": { - "user": {} - } - }, - "storeInAppleKeyChain": "Apple KeyChainに保存", - "@storeInAppleKeyChain": {}, - "storeInAndroidKeystore": "Android KeyStoreに保存する", - "@storeInAndroidKeystore": {}, - "storeInSecureStorageDescription": "このデバイスの安全なストレージにリカバリーキーを保存。", - "@storeInSecureStorageDescription": {}, - "unlockOldMessages": "古いメッセージのロックを解除する", - "@unlockOldMessages": {}, - "callingAccount": "通話アカウント", - "@callingAccount": {}, - "callingPermissions": "通話の権限", - "@callingPermissions": {}, - "screenSharingTitle": "画面共有", - "@screenSharingTitle": {}, - "foregroundServiceRunning": "この通知は、フォアグラウンド サービスの実行中に表示されます。", - "@foregroundServiceRunning": {}, - "custom": "カスタム", - "@custom": {}, - "countFiles": "{count}個のファイル", - "@countFiles": { - "placeholders": { - "count": {} - } - }, - "storeSecurlyOnThisDevice": "このデバイスに安全に保管する", - "@storeSecurlyOnThisDevice": {}, - "whyIsThisMessageEncrypted": "このメッセージが読めない理由", - "@whyIsThisMessageEncrypted": {}, - "otherCallingPermissions": "マイク、カメラ、その他FluffyChatの権限", - "@otherCallingPermissions": {}, - "appearOnTopDetails": "アプリをトップに表示できるようにする(すでに通話アカウントとしてFluffychatを設定している場合は必要ありません)", - "@appearOnTopDetails": {}, - "dehydrateTorLong": "TOR ユーザーの場合、ウィンドウを閉じる前にセッションをエクスポートすることをお勧めします。", - "@dehydrateTorLong": {}, - "hydrateTorLong": "前回、TOR でセッションをエクスポートしましたか?すぐにインポートしてチャットを続けましょう。", - "@hydrateTorLong": {}, - "enableMultiAccounts": "(ベータ版) このデバイスで複数のアカウントを有効にする", - "@enableMultiAccounts": {}, - "pleaseEnterRecoveryKey": "リカバリーキーを入力してください。", - "@pleaseEnterRecoveryKey": {}, - "serverRequiresEmail": "このサーバーは、登録のためにメールアドレスを検証する必要があります。", - "@serverRequiresEmail": {}, - "sendSticker": "ステッカーを送る", - "@sendSticker": { - "type": "text", - "placeholders": {} - }, - "synchronizingPleaseWait": "同期中...お待ちください。", - "@synchronizingPleaseWait": { - "type": "text", - "placeholders": {} - }, - "emojis": "絵文字", - "@emojis": {}, - "markAsRead": "既読にする", - "@markAsRead": {}, - "videoCallsBetaWarning": "ビデオ通話は、現在ベータ版であることにご注意ください。すべてのプラットフォームで期待通りに動作しない、あるいはまったく動作しない可能性があります。", - "@videoCallsBetaWarning": {}, - "confirmEventUnpin": "イベントの固定を完全に解除してもよろしいですか?", - "@confirmEventUnpin": {}, - "unsupportedAndroidVersion": "サポートされていないAndroidのバージョン", - "@unsupportedAndroidVersion": {}, - "user": "ユーザー", - "@user": {}, - "newGroup": "新しいグループ", - "@newGroup": {}, - "noBackupWarning": "警告!チャットのバックアップを有効にしないと、暗号化されたメッセージにアクセスできなくなります。ログアウトする前に、まずチャットのバックアップを有効にすることを強くお勧めします。", - "@noBackupWarning": {}, - "disableEncryptionWarning": "セキュリティ上の理由から、以前は暗号化が有効だったチャットで暗号化を無効にすることはできません。", - "@disableEncryptionWarning": {}, - "youInvitedUser": "📩 {user} を招待しました", - "@youInvitedUser": { - "placeholders": { - "user": {} - } - }, - "reactedWith": "{sender} が {reaction} で反応しました", - "@reactedWith": { - "type": "text", - "placeholders": { - "sender": {}, - "reaction": {} - } - }, - "createNewSpace": "新しいスペース", - "@createNewSpace": { - "type": "text", - "placeholders": {} - }, - "widgetUrlError": "有効なURLではありません。", - "@widgetUrlError": {}, - "reportUser": "ユーザーを報告", - "@reportUser": {}, - "errorObtainingLocation": "位置情報の取得中にエラーが発生しました: {error}", - "@errorObtainingLocation": { - "type": "text", - "placeholders": { - "error": {} - } - }, - "pinMessage": "部屋にピン留めする", - "@pinMessage": {}, - "previousAccount": "前のアカウント", - "@previousAccount": {}, - "pleaseChoose": "選択してください", - "@pleaseChoose": { - "type": "text", - "placeholders": {} - }, - "oopsPushError": "おっと!残念ながら、プッシュ通知の設定中にエラーが発生しました。", - "@oopsPushError": { - "type": "text", - "placeholders": {} - }, - "noOtherDevicesFound": "他のデバイスが見つかりません", - "@noOtherDevicesFound": {}, - "recoveryKeyLost": "リカバリーキーを紛失した場合", - "@recoveryKeyLost": {}, - "shareLocation": "位置情報の共有", - "@shareLocation": { - "type": "text", - "placeholders": {} - }, - "time": "時間", - "@time": {}, - "sendAsText": "テキストとして送信", - "@sendAsText": { - "type": "text" - }, - "commandHint_googly": "ぎょろ目を送る", - "@commandHint_googly": {}, - "commandHint_hug": "ハグを送る", - "@commandHint_hug": {}, - "encryptThisChat": "このチャットを暗号化する", - "@encryptThisChat": {}, - "commandHint_markasdm": "ダイレクトメッセージの部屋としてマークする", - "@commandHint_markasdm": {}, - "commandHint_dm": "ダイレクトチャットを開始する\n暗号化を無効にするには、--no-encryptionを使用してください", - "@commandHint_dm": { - "type": "text", - "description": "Usage hint for the command /dm" - }, - "commandHint_leave": "この部屋を退出", - "@commandHint_leave": { - "type": "text", - "description": "Usage hint for the command /leave" - }, - "commandHint_myroomavatar": "この部屋の写真を設定する (mxc-uriで)", - "@commandHint_myroomavatar": { - "type": "text", - "description": "Usage hint for the command /myroomavatar" - }, - "commandHint_myroomnick": "この部屋の表示名を設定する", - "@commandHint_myroomnick": { - "type": "text", - "description": "Usage hint for the command /myroomnick" - }, - "commandHint_plain": "書式設定されていないテキストを送信する", - "@commandHint_plain": { - "type": "text", - "description": "Usage hint for the command /plain" - }, - "commandHint_react": "リアクションとして返信を送信する", - "@commandHint_react": { - "type": "text", - "description": "Usage hint for the command /react" - }, - "dehydrateTor": "TOR ユーザー: セッションをエクスポート", - "@dehydrateTor": {}, - "hydrateTor": "TOR ユーザー: セッションのエクスポートをインポート", - "@hydrateTor": {}, - "locationDisabledNotice": "位置情報サービスが無効になっています。位置情報を共有できるようにするには、位置情報サービスを有効にしてください。", - "@locationDisabledNotice": { - "type": "text", - "placeholders": {} - }, - "locationPermissionDeniedNotice": "位置情報の権限が拒否されました。位置情報を共有できるように許可してください。", - "@locationPermissionDeniedNotice": { - "type": "text", - "placeholders": {} - }, - "deviceKeys": "デバイスキー:", - "@deviceKeys": {}, - "sorryThatsNotPossible": "申し訳ありません...それは不可能です", - "@sorryThatsNotPossible": {}, - "wasDirectChatDisplayName": "空のチャット (以前は {oldDisplayName})", - "@wasDirectChatDisplayName": { - "type": "text", - "placeholders": { - "oldDisplayName": {} - } - }, - "doNotShowAgain": "今後表示しない", - "@doNotShowAgain": {}, - "hideUnimportantStateEvents": "重要でない状態イベントを非表示にする", - "@hideUnimportantStateEvents": {}, - "numChats": "{number} チャット", - "@numChats": { - "type": "number", - "placeholders": { - "number": {} - } - }, - "allSpaces": "すべてのスペース", - "@allSpaces": {}, - "enterRoom": "部屋に入る", - "@enterRoom": {}, - "enterSpace": "スペースに入る", - "@enterSpace": {}, - "newSpace": "新しいスペース", - "@newSpace": {}, - "reopenChat": "チャットを再開する", - "@reopenChat": {}, - "signInWith": "{provider}でログイン", - "@signInWith": { - "type": "text", - "placeholders": { - "provider": {} - } - }, - "signInWithPassword": "パスワードでログイン", - "@signInWithPassword": {}, - "@hugContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@jumpToLastReadMessage": {}, - "@allRooms": { - "type": "text", - "placeholders": {} - }, - "@commandHint_cuddle": {}, - "@dismiss": {}, - "@reportErrorDescription": {}, - "@indexedDbErrorLong": {}, - "@setColorTheme": {}, - "@supposedMxid": { - "type": "text", - "placeholders": { - "mxid": {} - } - }, - "@banUserDescription": {}, - "@widgetEtherpad": {}, - "@removeDevicesDescription": {}, - "@separateChatTypes": { - "type": "text", - "placeholders": {} - }, - "@tryAgain": {}, - "@unbanUserDescription": {}, - "@messagesStyle": {}, - "@newSpaceDescription": {}, - "@chatDescription": {}, - "@callingAccountDetails": {}, - "@noKeyForThisMessage": {}, - "@pushNotificationsNotAvailable": {}, - "@invalidServerName": {}, - "@chatPermissions": {}, - "@makeAdminDescription": {}, - "@saveKeyManuallyDescription": {}, - "@setChatDescription": {}, - "@importFromZipFile": {}, - "@redactedBy": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@fileIsTooBigForServer": {}, - "@readUpToHere": {}, - "@optionalRedactReason": {}, - "@archiveRoomDescription": {}, - "@exportEmotePack": {}, - "@openInMaps": { - "type": "text", - "placeholders": {} - }, - "@inviteContactToGroupQuestion": {}, - "@redactedByBecause": { - "type": "text", - "placeholders": { - "username": {}, - "reason": {} - } - }, - "@fileHasBeenSavedAt": { - "type": "text", - "placeholders": { - "path": {} - } - }, - "@redactMessageDescription": {}, - "@invalidInput": {}, - "@report": {}, - "@googlyEyesContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@addChatDescription": {}, - "@hasKnocked": { - "placeholders": { - "user": {} - } - }, - "@openLinkInBrowser": {}, - "@commandHint_me": { - "type": "text", - "description": "Usage hint for the command /me" - }, - "@directChat": {}, - "@wrongPinEntered": { - "type": "text", - "placeholders": { - "seconds": {} - } - }, - "@sendTypingNotifications": {}, - "@inviteGroupChat": {}, - "@appearOnTop": {}, - "@invitePrivateChat": {}, - "@commandHint_kick": { - "type": "text", - "description": "Usage hint for the command /kick" - }, - "@commandHint_unban": { - "type": "text", - "description": "Usage hint for the command /unban" - }, - "@importEmojis": {}, - "@noChatDescriptionYet": {}, - "@learnMore": {}, - "@notAnImage": {}, - "@chatDescriptionHasBeenChanged": {}, - "@commandHint_op": { - "type": "text", - "description": "Usage hint for the command /op" - }, - "@roomUpgradeDescription": {}, - "@pleaseEnterANumber": {}, - "@profileNotFound": {}, - "@jump": {}, - "@shareInviteLink": {}, - "@cuddleContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@emoteKeyboardNoRecents": { - "type": "text", - "placeholders": {} - }, - "@setTheme": {}, - "@replace": {}, - "@pleaseTryAgainLaterOrChooseDifferentServer": {}, - "@createGroup": {}, - "@kickUserDescription": {}, - "@importNow": {}, - "@invite": {} -} \ No newline at end of file + "@@locale": "ja", + "@@last_modified": "2021-08-14 12:41:09.978060", + "about": "このアプリについて", + "@about": { + "type": "text", + "placeholders": {} + }, + "accept": "承諾する", + "@accept": { + "type": "text", + "placeholders": {} + }, + "acceptedTheInvitation": "👍{username}が招待を承諾しました", + "@acceptedTheInvitation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "account": "アカウント", + "@account": { + "type": "text", + "placeholders": {} + }, + "activatedEndToEndEncryption": "🔐{username}がエンドツーエンド暗号化を有効にしました", + "@activatedEndToEndEncryption": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "addEmail": "Eメールを追加", + "@addEmail": { + "type": "text", + "placeholders": {} + }, + "admin": "管理者", + "@admin": { + "type": "text", + "placeholders": {} + }, + "alias": "エイリアス", + "@alias": { + "type": "text", + "placeholders": {} + }, + "all": "すべて", + "@all": { + "type": "text", + "placeholders": {} + }, + "answeredTheCall": "{senderName}は通話に出ました", + "@answeredTheCall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "anyoneCanJoin": "誰でも参加できる", + "@anyoneCanJoin": { + "type": "text", + "placeholders": {} + }, + "appLock": "アプリのロック", + "@appLock": { + "type": "text", + "placeholders": {} + }, + "archive": "アーカイブ", + "@archive": { + "type": "text", + "placeholders": {} + }, + "areGuestsAllowedToJoin": "ゲストユーザーの参加を許可する", + "@areGuestsAllowedToJoin": { + "type": "text", + "placeholders": {} + }, + "areYouSure": "これでよろしいですか?", + "@areYouSure": { + "type": "text", + "placeholders": {} + }, + "areYouSureYouWantToLogout": "ログアウトしてよろしいですか?", + "@areYouSureYouWantToLogout": { + "type": "text", + "placeholders": {} + }, + "askSSSSSign": "他の人を署名するためにはパスフレーズやリカバリーキーを入力してください。", + "@askSSSSSign": { + "type": "text", + "placeholders": {} + }, + "askVerificationRequest": "{username}の検証リクエストを承認しますか?", + "@askVerificationRequest": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "badServerLoginTypesException": "ホームサーバーでサポートされているログインタイプ:\n{serverVersions}\nアプリがサポートしているログインタイプ:\n{supportedVersions}", + "@badServerLoginTypesException": { + "type": "text", + "placeholders": { + "serverVersions": {}, + "supportedVersions": {} + } + }, + "badServerVersionsException": "ホームサーバーでサポートされているバージョン:\n{serverVersions}\nアプリでは{supportedVersions}しかサポートされていません", + "@badServerVersionsException": { + "type": "text", + "placeholders": { + "serverVersions": {}, + "supportedVersions": {} + } + }, + "banFromChat": "チャットからBANする", + "@banFromChat": { + "type": "text", + "placeholders": {} + }, + "banned": "BANされています", + "@banned": { + "type": "text", + "placeholders": {} + }, + "bannedUser": "{username}が{targetName}をBANしました", + "@bannedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "blockDevice": "デバイスをブロックする", + "@blockDevice": { + "type": "text", + "placeholders": {} + }, + "blocked": "ブロックしました", + "@blocked": { + "type": "text", + "placeholders": {} + }, + "botMessages": "ボットメッセージ", + "@botMessages": { + "type": "text", + "placeholders": {} + }, + "cancel": "キャンセル", + "@cancel": { + "type": "text", + "placeholders": {} + }, + "changeDeviceName": "デバイス名を変更", + "@changeDeviceName": { + "type": "text", + "placeholders": {} + }, + "changedTheChatAvatar": "{username}がチャットアバターを変更しました", + "@changedTheChatAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheChatDescriptionTo": "{username}がチャットの説明を「{description}」に変更しました", + "@changedTheChatDescriptionTo": { + "type": "text", + "placeholders": { + "username": {}, + "description": {} + } + }, + "changedTheChatNameTo": "{username}がチャットの名前を「{chatname}」に変更しました", + "@changedTheChatNameTo": { + "type": "text", + "placeholders": { + "username": {}, + "chatname": {} + } + }, + "changedTheChatPermissions": "{username}がチャットの権限を変更しました", + "@changedTheChatPermissions": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheDisplaynameTo": "{username}が表示名を「{displayname}」に変更しました", + "@changedTheDisplaynameTo": { + "type": "text", + "placeholders": { + "username": {}, + "displayname": {} + } + }, + "changedTheGuestAccessRules": "{username}がゲストのアクセスルールを変更しました", + "@changedTheGuestAccessRules": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheGuestAccessRulesTo": "{username}がゲストのアクセスルールを{rules}に変更しました", + "@changedTheGuestAccessRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "changedTheHistoryVisibility": "{username}が履歴の表示設定を変更しました", + "@changedTheHistoryVisibility": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheHistoryVisibilityTo": "{username}が履歴の表示設定を{rules}に変更しました", + "@changedTheHistoryVisibilityTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "changedTheJoinRules": "{username}が参加ルールを変更しました", + "@changedTheJoinRules": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheJoinRulesTo": "{username}が参加ルールを{joinRules}に変更しました", + "@changedTheJoinRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "joinRules": {} + } + }, + "changedTheProfileAvatar": "{username}がアバターを変更しました", + "@changedTheProfileAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheRoomAliases": "{username}が部屋のエイリアスを変更しました", + "@changedTheRoomAliases": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheRoomInvitationLink": "{username}が招待リンクを変更しました", + "@changedTheRoomInvitationLink": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changePassword": "パスワードを変更", + "@changePassword": { + "type": "text", + "placeholders": {} + }, + "changeTheHomeserver": "ホームサーバーの変更", + "@changeTheHomeserver": { + "type": "text", + "placeholders": {} + }, + "changeTheme": "スタイルを変更する", + "@changeTheme": { + "type": "text", + "placeholders": {} + }, + "changeTheNameOfTheGroup": "グループの名前を変更する", + "@changeTheNameOfTheGroup": { + "type": "text", + "placeholders": {} + }, + "channelCorruptedDecryptError": "暗号が破損しています", + "@channelCorruptedDecryptError": { + "type": "text", + "placeholders": {} + }, + "chat": "チャット", + "@chat": { + "type": "text", + "placeholders": {} + }, + "chatBackup": "チャットのバックアップ", + "@chatBackup": { + "type": "text", + "placeholders": {} + }, + "chatBackupDescription": "古いメッセージはリカバリーキーで保護されます。紛失しないようにご注意ください。", + "@chatBackupDescription": { + "type": "text", + "placeholders": {} + }, + "chatDetails": "チャットの詳細", + "@chatDetails": { + "type": "text", + "placeholders": {} + }, + "chats": "チャット", + "@chats": { + "type": "text", + "placeholders": {} + }, + "chooseAStrongPassword": "強いパスワードを選択してください", + "@chooseAStrongPassword": { + "type": "text", + "placeholders": {} + }, + "clearArchive": "アーカイブを消去", + "@clearArchive": {}, + "close": "閉じる", + "@close": { + "type": "text", + "placeholders": {} + }, + "compareEmojiMatch": "表示されている絵文字が他のデバイスで表示されているものと一致するか確認してください:", + "@compareEmojiMatch": { + "type": "text", + "placeholders": {} + }, + "compareNumbersMatch": "表示されている数字が他のデバイスで表示されているものと一致するか確認してください:", + "@compareNumbersMatch": { + "type": "text", + "placeholders": {} + }, + "configureChat": "チャットの設定", + "@configureChat": { + "type": "text", + "placeholders": {} + }, + "confirm": "確認", + "@confirm": { + "type": "text", + "placeholders": {} + }, + "connect": "接続", + "@connect": { + "type": "text", + "placeholders": {} + }, + "contactHasBeenInvitedToTheGroup": "連絡先に登録された人が招待されました", + "@contactHasBeenInvitedToTheGroup": { + "type": "text", + "placeholders": {} + }, + "containsDisplayName": "表示名を含んでいます", + "@containsDisplayName": { + "type": "text", + "placeholders": {} + }, + "containsUserName": "ユーザー名を含んでいます", + "@containsUserName": { + "type": "text", + "placeholders": {} + }, + "contentHasBeenReported": "サーバー管理者に通報されました", + "@contentHasBeenReported": { + "type": "text", + "placeholders": {} + }, + "copiedToClipboard": "クリップボードにコピーされました", + "@copiedToClipboard": { + "type": "text", + "placeholders": {} + }, + "copy": "コピー", + "@copy": { + "type": "text", + "placeholders": {} + }, + "copyToClipboard": "クリップボードにコピー", + "@copyToClipboard": { + "type": "text", + "placeholders": {} + }, + "couldNotDecryptMessage": "メッセージを解読できませんでした: {error}", + "@couldNotDecryptMessage": { + "type": "text", + "placeholders": { + "error": {} + } + }, + "countParticipants": "{count}名の参加者", + "@countParticipants": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "create": "作成", + "@create": { + "type": "text", + "placeholders": {} + }, + "createdTheChat": "💬 {username}がチャットを作成しました", + "@createdTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "currentlyActive": "現在アクティブです", + "@currentlyActive": { + "type": "text", + "placeholders": {} + }, + "darkTheme": "ダーク", + "@darkTheme": { + "type": "text", + "placeholders": {} + }, + "dateAndTimeOfDay": "{date}, {timeOfDay}", + "@dateAndTimeOfDay": { + "type": "text", + "placeholders": { + "date": {}, + "timeOfDay": {} + } + }, + "dateWithoutYear": "{month}-{day}", + "@dateWithoutYear": { + "type": "text", + "placeholders": { + "month": {}, + "day": {} + } + }, + "dateWithYear": "{year}/{month}/{day}", + "@dateWithYear": { + "type": "text", + "placeholders": { + "year": {}, + "month": {}, + "day": {} + } + }, + "deactivateAccountWarning": "あなたのアカウントを無効化します。この操作は元に戻せません!よろしいですか?", + "@deactivateAccountWarning": { + "type": "text", + "placeholders": {} + }, + "defaultPermissionLevel": "デフォルトの権限レベル", + "@defaultPermissionLevel": { + "type": "text", + "placeholders": {} + }, + "delete": "削除", + "@delete": { + "type": "text", + "placeholders": {} + }, + "deleteAccount": "アカウントの削除", + "@deleteAccount": { + "type": "text", + "placeholders": {} + }, + "deleteMessage": "メッセージの削除", + "@deleteMessage": { + "type": "text", + "placeholders": {} + }, + "device": "デバイス", + "@device": { + "type": "text", + "placeholders": {} + }, + "deviceId": "デバイスID", + "@deviceId": { + "type": "text", + "placeholders": {} + }, + "devices": "デバイス", + "@devices": { + "type": "text", + "placeholders": {} + }, + "directChats": "ダイレクトチャット", + "@directChats": { + "type": "text", + "placeholders": {} + }, + "displaynameHasBeenChanged": "表示名が変更されました", + "@displaynameHasBeenChanged": { + "type": "text", + "placeholders": {} + }, + "downloadFile": "ファイルのダウンロード", + "@downloadFile": { + "type": "text", + "placeholders": {} + }, + "edit": "編集", + "@edit": { + "type": "text", + "placeholders": {} + }, + "editBlockedServers": "ブロックしたサーバーを編集", + "@editBlockedServers": { + "type": "text", + "placeholders": {} + }, + "editDisplayname": "表示名を編集", + "@editDisplayname": { + "type": "text", + "placeholders": {} + }, + "editRoomAliases": "ルームエイリアスを編集", + "@editRoomAliases": { + "type": "text", + "placeholders": {} + }, + "editRoomAvatar": "部屋のアバターを編集する", + "@editRoomAvatar": { + "type": "text", + "placeholders": {} + }, + "emoteExists": "Emoteはすでに存在します!", + "@emoteExists": { + "type": "text", + "placeholders": {} + }, + "emoteInvalid": "不正なEmoteショートコード!", + "@emoteInvalid": { + "type": "text", + "placeholders": {} + }, + "emotePacks": "部屋のEmoteパック", + "@emotePacks": { + "type": "text", + "placeholders": {} + }, + "emoteSettings": "Emote設定", + "@emoteSettings": { + "type": "text", + "placeholders": {} + }, + "emoteShortcode": "Emoteショートコード", + "@emoteShortcode": { + "type": "text", + "placeholders": {} + }, + "emoteWarnNeedToPick": "Emoteショートコードと画像を選択してください!", + "@emoteWarnNeedToPick": { + "type": "text", + "placeholders": {} + }, + "emptyChat": "空のチャット", + "@emptyChat": { + "type": "text", + "placeholders": {} + }, + "enableEmotesGlobally": "emoteをグローバルに有効にする", + "@enableEmotesGlobally": { + "type": "text", + "placeholders": {} + }, + "enableEncryption": "暗号化を有効にする", + "@enableEncryption": { + "type": "text", + "placeholders": {} + }, + "enableEncryptionWarning": "一度暗号化を有効にするともとに戻せません。よろしいですか?", + "@enableEncryptionWarning": { + "type": "text", + "placeholders": {} + }, + "encrypted": "暗号化", + "@encrypted": { + "type": "text", + "placeholders": {} + }, + "encryption": "暗号化", + "@encryption": { + "type": "text", + "placeholders": {} + }, + "encryptionNotEnabled": "暗号化されていません", + "@encryptionNotEnabled": { + "type": "text", + "placeholders": {} + }, + "endedTheCall": "{senderName}は通話を切断しました", + "@endedTheCall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "enterAnEmailAddress": "メールアドレスを入力してください", + "@enterAnEmailAddress": { + "type": "text", + "placeholders": {} + }, + "enterYourHomeserver": "ホームサーバーを入力してください", + "@enterYourHomeserver": { + "type": "text", + "placeholders": {} + }, + "everythingReady": "すべての準備は完了しました!", + "@everythingReady": { + "type": "text", + "placeholders": {} + }, + "extremeOffensive": "とても攻撃的", + "@extremeOffensive": { + "type": "text", + "placeholders": {} + }, + "fileName": "ファイル名", + "@fileName": { + "type": "text", + "placeholders": {} + }, + "fluffychat": "FluffyChat", + "@fluffychat": { + "type": "text", + "placeholders": {} + }, + "fontSize": "フォントサイズ", + "@fontSize": { + "type": "text", + "placeholders": {} + }, + "forward": "進む", + "@forward": { + "type": "text", + "placeholders": {} + }, + "fromJoining": "参加時点から閲覧可能", + "@fromJoining": { + "type": "text", + "placeholders": {} + }, + "fromTheInvitation": "招待時点から閲覧可能", + "@fromTheInvitation": { + "type": "text", + "placeholders": {} + }, + "goToTheNewRoom": "新規ルームへ", + "@goToTheNewRoom": { + "type": "text", + "placeholders": {} + }, + "group": "グループ", + "@group": { + "type": "text", + "placeholders": {} + }, + "groupIsPublic": "グループは公開されています", + "@groupIsPublic": { + "type": "text", + "placeholders": {} + }, + "groups": "グループ", + "@groups": { + "type": "text", + "placeholders": {} + }, + "groupWith": "{displayname}とグループを作成する", + "@groupWith": { + "type": "text", + "placeholders": { + "displayname": {} + } + }, + "guestsAreForbidden": "ゲストは許可されていません", + "@guestsAreForbidden": { + "type": "text", + "placeholders": {} + }, + "guestsCanJoin": "ゲストが許可されています", + "@guestsCanJoin": { + "type": "text", + "placeholders": {} + }, + "hasWithdrawnTheInvitationFor": "{targetName}の招待を{username}が取り下げました", + "@hasWithdrawnTheInvitationFor": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "help": "ヘルプ", + "@help": { + "type": "text", + "placeholders": {} + }, + "hideRedactedEvents": "編集済みイベントを非表示にする", + "@hideRedactedEvents": { + "type": "text", + "placeholders": {} + }, + "hideUnknownEvents": "不明なイベントを非表示にする", + "@hideUnknownEvents": { + "type": "text", + "placeholders": {} + }, + "howOffensiveIsThisContent": "どのくらい攻撃的でしたか?", + "@howOffensiveIsThisContent": { + "type": "text", + "placeholders": {} + }, + "id": "ID", + "@id": { + "type": "text", + "placeholders": {} + }, + "identity": "アイデンティティ", + "@identity": { + "type": "text", + "placeholders": {} + }, + "ignore": "無視する", + "@ignore": { + "type": "text", + "placeholders": {} + }, + "ignoredUsers": "無視されたユーザー", + "@ignoredUsers": { + "type": "text", + "placeholders": {} + }, + "iHaveClickedOnLink": "リンクをクリックしました", + "@iHaveClickedOnLink": { + "type": "text", + "placeholders": {} + }, + "incorrectPassphraseOrKey": "パスフレーズかリカバリーキーが間違っています", + "@incorrectPassphraseOrKey": { + "type": "text", + "placeholders": {} + }, + "inoffensive": "非攻撃的", + "@inoffensive": { + "type": "text", + "placeholders": {} + }, + "inviteContact": "連絡先から招待する", + "@inviteContact": { + "type": "text", + "placeholders": {} + }, + "inviteContactToGroup": "連絡先から{groupName}に招待する", + "@inviteContactToGroup": { + "type": "text", + "placeholders": { + "groupName": {} + } + }, + "invited": "招待されました", + "@invited": { + "type": "text", + "placeholders": {} + }, + "invitedUser": "📩 {username} が {targetName} を招待しました", + "@invitedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "invitedUsersOnly": "招待されたユーザーのみ", + "@invitedUsersOnly": { + "type": "text", + "placeholders": {} + }, + "inviteForMe": "自分への招待", + "@inviteForMe": { + "type": "text", + "placeholders": {} + }, + "inviteText": "{username}がFluffyChatにあなたを招待しました. \n1. FluffyChatをインストールしてください: https://fluffychat.im \n2. 新しくアカウントを作成するかサインインしてください\n3. 招待リンクを開いてください: {link}", + "@inviteText": { + "type": "text", + "placeholders": { + "username": {}, + "link": {} + } + }, + "isTyping": "が入力しています…", + "@isTyping": { + "type": "text", + "placeholders": {} + }, + "joinedTheChat": "👋 {username} がチャットに参加しました", + "@joinedTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "joinRoom": "部屋に参加", + "@joinRoom": { + "type": "text", + "placeholders": {} + }, + "kicked": "👞 {username} は {targetName} をキックしました", + "@kicked": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "kickedAndBanned": "🙅 {username} が {targetName} をキックしブロックしました", + "@kickedAndBanned": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "kickFromChat": "チャットからキックする", + "@kickFromChat": { + "type": "text", + "placeholders": {} + }, + "lastActiveAgo": "最終アクティブ: {localizedTimeShort}", + "@lastActiveAgo": { + "type": "text", + "placeholders": { + "localizedTimeShort": {} + } + }, + "leave": "退室する", + "@leave": { + "type": "text", + "placeholders": {} + }, + "leftTheChat": "退室しました", + "@leftTheChat": { + "type": "text", + "placeholders": {} + }, + "license": "ライセンス", + "@license": { + "type": "text", + "placeholders": {} + }, + "lightTheme": "ライト", + "@lightTheme": { + "type": "text", + "placeholders": {} + }, + "loadCountMoreParticipants": "あと{count}名参加者を読み込む", + "@loadCountMoreParticipants": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "loadingPleaseWait": "読み込み中…お待ちください。", + "@loadingPleaseWait": { + "type": "text", + "placeholders": {} + }, + "loadMore": "更に読み込む…", + "@loadMore": { + "type": "text", + "placeholders": {} + }, + "login": "ログイン", + "@login": { + "type": "text", + "placeholders": {} + }, + "logInTo": "{homeserver}にログインする", + "@logInTo": { + "type": "text", + "placeholders": { + "homeserver": {} + } + }, + "logout": "ログアウト", + "@logout": { + "type": "text", + "placeholders": {} + }, + "memberChanges": "メンバーの変更", + "@memberChanges": { + "type": "text", + "placeholders": {} + }, + "mention": "メンション", + "@mention": { + "type": "text", + "placeholders": {} + }, + "messages": "メッセージ", + "@messages": { + "type": "text", + "placeholders": {} + }, + "moderator": "モデレータ", + "@moderator": { + "type": "text", + "placeholders": {} + }, + "muteChat": "チャットのミュート", + "@muteChat": { + "type": "text", + "placeholders": {} + }, + "needPantalaimonWarning": "現時点では、エンドツーエンドの暗号化を使用するにはPantalaimonが必要であることに注意してください。", + "@needPantalaimonWarning": { + "type": "text", + "placeholders": {} + }, + "newChat": "新規チャット", + "@newChat": { + "type": "text", + "placeholders": {} + }, + "newMessageInFluffyChat": "💬 FluffyChatに新しいメッセージがあります", + "@newMessageInFluffyChat": { + "type": "text", + "placeholders": {} + }, + "newVerificationRequest": "認証リクエスト!", + "@newVerificationRequest": { + "type": "text", + "placeholders": {} + }, + "next": "次へ", + "@next": { + "type": "text", + "placeholders": {} + }, + "no": "いいえ", + "@no": { + "type": "text", + "placeholders": {} + }, + "noConnectionToTheServer": "サーバーに接続できません", + "@noConnectionToTheServer": { + "type": "text", + "placeholders": {} + }, + "noEmotesFound": "Emoteは見つかりませんでした😕", + "@noEmotesFound": { + "type": "text", + "placeholders": {} + }, + "noEncryptionForPublicRooms": "ルームを非公開にした後暗号化を有効にできます。", + "@noEncryptionForPublicRooms": { + "type": "text", + "placeholders": {} + }, + "noGoogleServicesWarning": "あなたのスマホにはGoogleサービスがないようですね。プライバシーを保護するための良い選択です!プッシュ通知を受け取るには https://microg.org/ または https://unifiedpush.org/ を使うことをお勧めします。", + "@noGoogleServicesWarning": { + "type": "text", + "placeholders": {} + }, + "none": "なし", + "@none": { + "type": "text", + "placeholders": {} + }, + "noPasswordRecoveryDescription": "パスワードを回復する方法をまだ追加していません。", + "@noPasswordRecoveryDescription": { + "type": "text", + "placeholders": {} + }, + "noPermission": "権限がありません", + "@noPermission": { + "type": "text", + "placeholders": {} + }, + "noRoomsFound": "部屋は見つかりませんでした…", + "@noRoomsFound": { + "type": "text", + "placeholders": {} + }, + "notifications": "通知", + "@notifications": { + "type": "text", + "placeholders": {} + }, + "notificationsEnabledForThisAccount": "このアカウントでは通知が有効です", + "@notificationsEnabledForThisAccount": { + "type": "text", + "placeholders": {} + }, + "numUsersTyping": "{count}人が入力中…", + "@numUsersTyping": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "offensive": "攻撃的", + "@offensive": { + "type": "text", + "placeholders": {} + }, + "offline": "オフライン", + "@offline": { + "type": "text", + "placeholders": {} + }, + "ok": "OK", + "@ok": { + "type": "text", + "placeholders": {} + }, + "online": "オンライン", + "@online": { + "type": "text", + "placeholders": {} + }, + "onlineKeyBackupEnabled": "オンライン鍵バックアップは使用されています", + "@onlineKeyBackupEnabled": { + "type": "text", + "placeholders": {} + }, + "oopsSomethingWentWrong": "おっと、何かがうまくいきませんでした…", + "@oopsSomethingWentWrong": { + "type": "text", + "placeholders": {} + }, + "openAppToReadMessages": "アプリを開いてメッセージを確認してください", + "@openAppToReadMessages": { + "type": "text", + "placeholders": {} + }, + "openCamera": "カメラを開く", + "@openCamera": { + "type": "text", + "placeholders": {} + }, + "participant": "参加者", + "@participant": { + "type": "text", + "placeholders": {} + }, + "passphraseOrKey": "パスフレーズかリカバリーキー", + "@passphraseOrKey": { + "type": "text", + "placeholders": {} + }, + "password": "パスワード", + "@password": { + "type": "text", + "placeholders": {} + }, + "passwordForgotten": "パスワードを忘れた", + "@passwordForgotten": { + "type": "text", + "placeholders": {} + }, + "passwordHasBeenChanged": "パスワードが変更されました", + "@passwordHasBeenChanged": { + "type": "text", + "placeholders": {} + }, + "passwordRecovery": "パスワードリカバリー", + "@passwordRecovery": { + "type": "text", + "placeholders": {} + }, + "people": "人々", + "@people": { + "type": "text", + "placeholders": {} + }, + "pickImage": "画像を選択してください", + "@pickImage": { + "type": "text", + "placeholders": {} + }, + "pin": "ピン", + "@pin": { + "type": "text", + "placeholders": {} + }, + "play": "{fileName}を再生する", + "@play": { + "type": "text", + "placeholders": { + "fileName": {} + } + }, + "pleaseChooseAPasscode": "パスコードを選んでください", + "@pleaseChooseAPasscode": { + "type": "text", + "placeholders": {} + }, + "pleaseClickOnLink": "メールのリンクから進めてください。", + "@pleaseClickOnLink": { + "type": "text", + "placeholders": {} + }, + "pleaseEnter4Digits": "アプリのロック用に4桁の数字を入力してください。空欄の場合は無効になります。", + "@pleaseEnter4Digits": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourPassword": "パスワードを入力してください", + "@pleaseEnterYourPassword": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourPin": "PINを入力してください", + "@pleaseEnterYourPin": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourUsername": "ユーザー名を入力してください", + "@pleaseEnterYourUsername": { + "type": "text", + "placeholders": {} + }, + "pleaseFollowInstructionsOnWeb": "ウェブサイトにあるやり方を見てから次をタップしてください。", + "@pleaseFollowInstructionsOnWeb": { + "type": "text", + "placeholders": {} + }, + "privacy": "プライバシー", + "@privacy": { + "type": "text", + "placeholders": {} + }, + "publicRooms": "公開された部屋", + "@publicRooms": { + "type": "text", + "placeholders": {} + }, + "pushRules": "ルールを追加する", + "@pushRules": { + "type": "text", + "placeholders": {} + }, + "reason": "理由", + "@reason": { + "type": "text", + "placeholders": {} + }, + "recording": "録音中", + "@recording": { + "type": "text", + "placeholders": {} + }, + "redactedAnEvent": "{username}がイベントを編集しました", + "@redactedAnEvent": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "redactMessage": "メッセージを書く", + "@redactMessage": { + "type": "text", + "placeholders": {} + }, + "reject": "拒否", + "@reject": { + "type": "text", + "placeholders": {} + }, + "rejectedTheInvitation": "{username}は招待を拒否しました", + "@rejectedTheInvitation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "rejoin": "再参加", + "@rejoin": { + "type": "text", + "placeholders": {} + }, + "remove": "消去", + "@remove": { + "type": "text", + "placeholders": {} + }, + "removeAllOtherDevices": "他のデバイスをすべて削除", + "@removeAllOtherDevices": { + "type": "text", + "placeholders": {} + }, + "removedBy": "{username}によって削除されました", + "@removedBy": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "removeDevice": "デバイスの削除", + "@removeDevice": { + "type": "text", + "placeholders": {} + }, + "unbanFromChat": "チャットからのブロックを解除する", + "@unbanFromChat": { + "type": "text", + "placeholders": {} + }, + "renderRichContent": "リッチメッセージをレンダリングする", + "@renderRichContent": { + "type": "text", + "placeholders": {} + }, + "replaceRoomWithNewerVersion": "部屋を新しいバージョンに変更する", + "@replaceRoomWithNewerVersion": { + "type": "text", + "placeholders": {} + }, + "reply": "返信", + "@reply": { + "type": "text", + "placeholders": {} + }, + "reportMessage": "メッセージを通報", + "@reportMessage": { + "type": "text", + "placeholders": {} + }, + "requestPermission": "権限を要求する", + "@requestPermission": { + "type": "text", + "placeholders": {} + }, + "roomHasBeenUpgraded": "部屋はアップグレードされました", + "@roomHasBeenUpgraded": { + "type": "text", + "placeholders": {} + }, + "roomVersion": "ルームバージョン", + "@roomVersion": { + "type": "text", + "placeholders": {} + }, + "search": "検索", + "@search": { + "type": "text", + "placeholders": {} + }, + "security": "セキュリティ", + "@security": { + "type": "text", + "placeholders": {} + }, + "seenByUser": "{username}が既読", + "@seenByUser": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "send": "送信", + "@send": { + "type": "text", + "placeholders": {} + }, + "sendAMessage": "メッセージを送信", + "@sendAMessage": { + "type": "text", + "placeholders": {} + }, + "sendAudio": "音声の送信", + "@sendAudio": { + "type": "text", + "placeholders": {} + }, + "sendFile": "ファイルを送信", + "@sendFile": { + "type": "text", + "placeholders": {} + }, + "sendImage": "画像の送信", + "@sendImage": { + "type": "text", + "placeholders": {} + }, + "sendMessages": "メッセージを送る", + "@sendMessages": { + "type": "text", + "placeholders": {} + }, + "sendOriginal": "オリジナルの送信", + "@sendOriginal": { + "type": "text", + "placeholders": {} + }, + "sendVideo": "動画を送信", + "@sendVideo": { + "type": "text", + "placeholders": {} + }, + "sentAFile": "📁 {username}はファイルを送信しました", + "@sentAFile": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAnAudio": "🎤 {username}は音声を送信しました", + "@sentAnAudio": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAPicture": "🖼️ {username}は画像を送信しました", + "@sentAPicture": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentASticker": "😊 {username}はステッカーを送信しました", + "@sentASticker": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAVideo": "🎥 {username}は動画を送信しました", + "@sentAVideo": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentCallInformations": "{senderName}は通話情報を送信しました", + "@sentCallInformations": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "setAsCanonicalAlias": "メインエイリアスに設定", + "@setAsCanonicalAlias": { + "type": "text", + "placeholders": {} + }, + "setCustomEmotes": "カスタムエモートの設定", + "@setCustomEmotes": { + "type": "text", + "placeholders": {} + }, + "setInvitationLink": "招待リンクを設定する", + "@setInvitationLink": { + "type": "text", + "placeholders": {} + }, + "setPermissionsLevel": "権限レベルをセット", + "@setPermissionsLevel": { + "type": "text", + "placeholders": {} + }, + "setStatus": "ステータスの設定", + "@setStatus": { + "type": "text", + "placeholders": {} + }, + "settings": "設定", + "@settings": { + "type": "text", + "placeholders": {} + }, + "share": "共有", + "@share": { + "type": "text", + "placeholders": {} + }, + "sharedTheLocation": "{username}は現在地を共有しました", + "@sharedTheLocation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "showPassword": "パスワードを表示", + "@showPassword": { + "type": "text", + "placeholders": {} + }, + "skip": "スキップ", + "@skip": { + "type": "text", + "placeholders": {} + }, + "sourceCode": "ソースコード", + "@sourceCode": { + "type": "text", + "placeholders": {} + }, + "startedACall": "{senderName}は通話を開始しました", + "@startedACall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "status": "ステータス", + "@status": { + "type": "text", + "placeholders": {} + }, + "statusExampleMessage": "お元気ですか?", + "@statusExampleMessage": { + "type": "text", + "placeholders": {} + }, + "submit": "送信", + "@submit": { + "type": "text", + "placeholders": {} + }, + "systemTheme": "システム", + "@systemTheme": { + "type": "text", + "placeholders": {} + }, + "theyDontMatch": "違います", + "@theyDontMatch": { + "type": "text", + "placeholders": {} + }, + "theyMatch": "一致しています", + "@theyMatch": { + "type": "text", + "placeholders": {} + }, + "title": "FluffyChat", + "@title": { + "description": "Title for the application", + "type": "text", + "placeholders": {} + }, + "toggleFavorite": "お気に入り切り替え", + "@toggleFavorite": { + "type": "text", + "placeholders": {} + }, + "toggleMuted": "ミュート切り替え", + "@toggleMuted": { + "type": "text", + "placeholders": {} + }, + "toggleUnread": "既読/未読にマーク", + "@toggleUnread": { + "type": "text", + "placeholders": {} + }, + "tooManyRequestsWarning": "リクエストが多すぎます。また後で試してみてください!", + "@tooManyRequestsWarning": { + "type": "text", + "placeholders": {} + }, + "transferFromAnotherDevice": "違うデバイスから移行する", + "@transferFromAnotherDevice": { + "type": "text", + "placeholders": {} + }, + "tryToSendAgain": "送信し直してみる", + "@tryToSendAgain": { + "type": "text", + "placeholders": {} + }, + "unavailable": "不在", + "@unavailable": { + "type": "text", + "placeholders": {} + }, + "unbannedUser": "{username}が{targetName}のBANを解除しました", + "@unbannedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "unblockDevice": "デバイスをブロック解除する", + "@unblockDevice": { + "type": "text", + "placeholders": {} + }, + "unknownDevice": "未知デバイス", + "@unknownDevice": { + "type": "text", + "placeholders": {} + }, + "unknownEncryptionAlgorithm": "未知の暗号化アルゴリズム", + "@unknownEncryptionAlgorithm": { + "type": "text", + "placeholders": {} + }, + "unknownEvent": "未知のイベント'{type}'", + "@unknownEvent": { + "type": "text", + "placeholders": { + "type": {} + } + }, + "unmuteChat": "チャットをミュート解除する", + "@unmuteChat": { + "type": "text", + "placeholders": {} + }, + "unpin": "ピンを外す", + "@unpin": { + "type": "text", + "placeholders": {} + }, + "unreadChats": "{unreadCount, plural, =1{1件の未読メッセージ} other{{unreadCount}件の未読メッセージ}}", + "@unreadChats": { + "type": "text", + "placeholders": { + "unreadCount": {} + } + }, + "userAndOthersAreTyping": "{username}と他{count}名が入力しています…", + "@userAndOthersAreTyping": { + "type": "text", + "placeholders": { + "username": {}, + "count": {} + } + }, + "userAndUserAreTyping": "{username}と{username2}が入力しています…", + "@userAndUserAreTyping": { + "type": "text", + "placeholders": { + "username": {}, + "username2": {} + } + }, + "userIsTyping": "{username}が入力しています…", + "@userIsTyping": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "userLeftTheChat": "🚪 {username}はチャットから退室しました", + "@userLeftTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "username": "ユーザー名", + "@username": { + "type": "text", + "placeholders": {} + }, + "userSentUnknownEvent": "{username}は{type}イベントを送信しました", + "@userSentUnknownEvent": { + "type": "text", + "placeholders": { + "username": {}, + "type": {} + } + }, + "verified": "検証済み", + "@verified": { + "type": "text", + "placeholders": {} + }, + "verify": "確認", + "@verify": { + "type": "text", + "placeholders": {} + }, + "verifyStart": "確認を始める", + "@verifyStart": { + "type": "text", + "placeholders": {} + }, + "verifySuccess": "確認が完了しました!", + "@verifySuccess": { + "type": "text", + "placeholders": {} + }, + "verifyTitle": "他のアカウントを確認中", + "@verifyTitle": { + "type": "text", + "placeholders": {} + }, + "videoCall": "音声通話", + "@videoCall": { + "type": "text", + "placeholders": {} + }, + "visibilityOfTheChatHistory": "チャット履歴の表示", + "@visibilityOfTheChatHistory": { + "type": "text", + "placeholders": {} + }, + "visibleForAllParticipants": "すべての参加者が閲覧可能", + "@visibleForAllParticipants": { + "type": "text", + "placeholders": {} + }, + "visibleForEveryone": "すべての人が閲覧可能", + "@visibleForEveryone": { + "type": "text", + "placeholders": {} + }, + "voiceMessage": "ボイスメッセージ", + "@voiceMessage": { + "type": "text", + "placeholders": {} + }, + "waitingPartnerAcceptRequest": "パートナーのリクエスト承諾待ちです...", + "@waitingPartnerAcceptRequest": { + "type": "text", + "placeholders": {} + }, + "waitingPartnerEmoji": "パートナーの絵文字承諾待ちです...", + "@waitingPartnerEmoji": { + "type": "text", + "placeholders": {} + }, + "waitingPartnerNumbers": "パートナーの数字承諾待ちです…", + "@waitingPartnerNumbers": { + "type": "text", + "placeholders": {} + }, + "wallpaper": "壁紙", + "@wallpaper": { + "type": "text", + "placeholders": {} + }, + "warning": "警告!", + "@warning": { + "type": "text", + "placeholders": {} + }, + "weSentYouAnEmail": "あなたにメールを送信しました", + "@weSentYouAnEmail": { + "type": "text", + "placeholders": {} + }, + "whoCanPerformWhichAction": "誰がどの操作を実行できるか", + "@whoCanPerformWhichAction": { + "type": "text", + "placeholders": {} + }, + "whoIsAllowedToJoinThisGroup": "誰がこのチャットに入れますか", + "@whoIsAllowedToJoinThisGroup": { + "type": "text", + "placeholders": {} + }, + "whyDoYouWantToReportThis": "これを通報する理由", + "@whyDoYouWantToReportThis": { + "type": "text", + "placeholders": {} + }, + "wipeChatBackup": "チャットのバックアップを消去して、新しいリカバリーキーを作りますか?", + "@wipeChatBackup": { + "type": "text", + "placeholders": {} + }, + "withTheseAddressesRecoveryDescription": "これらのアドレスを使用すると、パスワードを回復することができます。", + "@withTheseAddressesRecoveryDescription": { + "type": "text", + "placeholders": {} + }, + "writeAMessage": "メッセージを入力してください…", + "@writeAMessage": { + "type": "text", + "placeholders": {} + }, + "yes": "はい", + "@yes": { + "type": "text", + "placeholders": {} + }, + "you": "あなた", + "@you": { + "type": "text", + "placeholders": {} + }, + "youAreNoLongerParticipatingInThisChat": "あなたはもうこのチャットの参加者ではありません", + "@youAreNoLongerParticipatingInThisChat": { + "type": "text", + "placeholders": {} + }, + "youHaveBeenBannedFromThisChat": "チャットからBANされてしまいました", + "@youHaveBeenBannedFromThisChat": { + "type": "text", + "placeholders": {} + }, + "yourPublicKey": "あなたの公開鍵", + "@yourPublicKey": { + "type": "text", + "placeholders": {} + }, + "allChats": "すべて会話", + "@allChats": { + "type": "text", + "placeholders": {} + }, + "addToSpace": "スペースに追加", + "@addToSpace": {}, + "cantOpenUri": "URIが開けません {uri}", + "@cantOpenUri": { + "type": "text", + "placeholders": { + "uri": {} + } + }, + "repeatPassword": "パスワードを繰り返そ", + "@repeatPassword": {}, + "autoplayImages": "GIFを自動的に再生する", + "@autoplayImages": { + "type": "text", + "placeholder": {} + }, + "yourChatBackupHasBeenSetUp": "チャットバックアップを設定ました。", + "@yourChatBackupHasBeenSetUp": {}, + "sendOnEnter": "Enterで送信", + "@sendOnEnter": {}, + "changeYourAvatar": "アバタるを変化しする", + "@changeYourAvatar": { + "type": "text", + "placeholders": {} + }, + "chatHasBeenAddedToThisSpace": "このスペースにチャットが追加されました", + "@chatHasBeenAddedToThisSpace": {}, + "commandHint_ban": "このユーザーを禁止する", + "@commandHint_ban": { + "type": "text", + "description": "Usage hint for the command /ban" + }, + "commandHint_clearcache": "キャッシュをクリアする", + "@commandHint_clearcache": { + "type": "text", + "description": "Usage hint for the command /clearcache" + }, + "commandInvalid": "コマンドが無効", + "@commandInvalid": { + "type": "text" + }, + "commandHint_create": "空のグループチャットを作成\n暗号化を無効にするには、--no-encryption を使用", + "@commandHint_create": { + "type": "text", + "description": "Usage hint for the command /create" + }, + "commandHint_discardsession": "セッションを破棄", + "@commandHint_discardsession": { + "type": "text", + "description": "Usage hint for the command /discardsession" + }, + "confirmMatrixId": "アカウントを削除するには、Matrix IDを確認してください。", + "@confirmMatrixId": {}, + "commandHint_markasgroup": "グループとしてマーク", + "@commandHint_markasgroup": {}, + "commandHint_join": "指定した部屋に参加", + "@commandHint_join": { + "type": "text", + "description": "Usage hint for the command /join" + }, + "commandHint_send": "テキストを送信", + "@commandHint_send": { + "type": "text", + "description": "Usage hint for the command /send" + }, + "hydrate": "バックアップファイルから復元", + "@hydrate": {}, + "commandHint_html": "HTML形式のテキストを送信", + "@commandHint_html": { + "type": "text", + "description": "Usage hint for the command /html" + }, + "commandHint_invite": "指定したユーザーをこの部屋に招待", + "@commandHint_invite": { + "type": "text", + "description": "Usage hint for the command /invite" + }, + "commandMissing": "{command} はコマンドではありません。", + "@commandMissing": { + "type": "text", + "placeholders": { + "command": {} + }, + "description": "State that {command} is not a valid /command." + }, + "oneClientLoggedOut": "クライアントの 1つがログアウトしました", + "@oneClientLoggedOut": {}, + "addAccount": "アカウントを追加", + "@addAccount": {}, + "editBundlesForAccount": "このアカウントのバンドルを編集", + "@editBundlesForAccount": {}, + "unverified": "未検証", + "@unverified": {}, + "sender": "送信者", + "@sender": {}, + "placeCall": "電話をかける", + "@placeCall": {}, + "voiceCall": "音声通話", + "@voiceCall": {}, + "unsupportedAndroidVersionLong": "この機能を利用するには、より新しいAndroidのバージョンが必要です。アップデートまたはLineage OSのサポートをご確認ください。", + "@unsupportedAndroidVersionLong": {}, + "widgetVideo": "動画", + "@widgetVideo": {}, + "widgetName": "名称", + "@widgetName": {}, + "widgetCustom": "カスタム", + "@widgetCustom": {}, + "widgetJitsi": "Jitsi Meet", + "@widgetJitsi": {}, + "dehydrateWarning": "この操作は元に戻せません。バックアップファイルを安全に保存してください。", + "@dehydrateWarning": {}, + "dehydrate": "セッションのエクスポートとデバイスの消去", + "@dehydrate": {}, + "messageType": "メッセージの種類", + "@messageType": {}, + "start": "開始", + "@start": {}, + "publish": "公開", + "@publish": {}, + "indexedDbErrorTitle": "プライベートモードに関する問題", + "@indexedDbErrorTitle": {}, + "addWidget": "ウィジェットを追加", + "@addWidget": {}, + "youBannedUser": "{user} を禁止しました", + "@youBannedUser": { + "placeholders": { + "user": {} + } + }, + "youJoinedTheChat": "チャットに参加しました", + "@youJoinedTheChat": {}, + "youHaveWithdrawnTheInvitationFor": "{user} への招待を取り下げました", + "@youHaveWithdrawnTheInvitationFor": { + "placeholders": { + "user": {} + } + }, + "users": "ユーザー", + "@users": {}, + "youRejectedTheInvitation": "招待を拒否しました", + "@youRejectedTheInvitation": {}, + "screenSharingDetail": "FuffyChatで画面を共有しています", + "@screenSharingDetail": {}, + "homeserver": "ホームサーバー", + "@homeserver": {}, + "scanQrCode": "QRコードをスキャン", + "@scanQrCode": {}, + "obtainingLocation": "位置情報を取得しています…", + "@obtainingLocation": { + "type": "text", + "placeholders": {} + }, + "addToBundle": "バンドルに追加", + "@addToBundle": {}, + "removeFromBundle": "このバンドルから削除", + "@removeFromBundle": {}, + "bundleName": "バンドル名", + "@bundleName": {}, + "noMatrixServer": "{server1} はMatrixのサーバーではありません。代わりに {server2} を使用しますか?", + "@noMatrixServer": { + "type": "text", + "placeholders": { + "server1": {}, + "server2": {} + } + }, + "openVideoCamera": "ビデオ用にカメラを開く", + "@openVideoCamera": { + "type": "text", + "placeholders": {} + }, + "link": "リンク", + "@link": {}, + "or": "または", + "@or": { + "type": "text", + "placeholders": {} + }, + "register": "登録", + "@register": { + "type": "text", + "placeholders": {} + }, + "removeYourAvatar": "アバターを削除する", + "@removeYourAvatar": { + "type": "text", + "placeholders": {} + }, + "saveFile": "ファイルを保存", + "@saveFile": { + "type": "text", + "placeholders": {} + }, + "recoveryKey": "リカバリーキー", + "@recoveryKey": {}, + "singlesignon": "シングルサインオン", + "@singlesignon": { + "type": "text", + "placeholders": {} + }, + "spaceIsPublic": "スペースは公開されています", + "@spaceIsPublic": { + "type": "text", + "placeholders": {} + }, + "spaceName": "スペース名", + "@spaceName": { + "type": "text", + "placeholders": {} + }, + "startFirstChat": "最初のチャットを開始する", + "@startFirstChat": {}, + "addToSpaceDescription": "このチャットを追加するスペースを選択してください。", + "@addToSpaceDescription": {}, + "messageInfo": "メッセージの情報", + "@messageInfo": {}, + "openGallery": "ギャラリーを開く", + "@openGallery": {}, + "removeFromSpace": "スペースから削除", + "@removeFromSpace": {}, + "pleaseEnterRecoveryKeyDescription": "古いメッセージを解除するには、以前のセッションで生成されたリカバリーキーを入力してください。リカバリーキーはパスワードではありません。", + "@pleaseEnterRecoveryKeyDescription": {}, + "videoWithSize": "ビデオ ({size})", + "@videoWithSize": { + "type": "text", + "placeholders": { + "size": {} + } + }, + "openChat": "チャットを開く", + "@openChat": {}, + "experimentalVideoCalls": "実験的なビデオ通話", + "@experimentalVideoCalls": {}, + "emailOrUsername": "メールアドレスまたはユーザー名", + "@emailOrUsername": {}, + "switchToAccount": "アカウント {number} に切り替える", + "@switchToAccount": { + "type": "number", + "placeholders": { + "number": {} + } + }, + "nextAccount": "次のアカウント", + "@nextAccount": {}, + "youAcceptedTheInvitation": "👍 招待を承諾しました", + "@youAcceptedTheInvitation": {}, + "errorAddingWidget": "ウィジェットの追加中にエラーが発生しました。", + "@errorAddingWidget": {}, + "widgetNameError": "表示名を入力してください。", + "@widgetNameError": {}, + "youUnbannedUser": "{user} の禁止を解除しました", + "@youUnbannedUser": { + "placeholders": { + "user": {} + } + }, + "youInvitedBy": "📩 {user} から招待されました", + "@youInvitedBy": { + "placeholders": { + "user": {} + } + }, + "youKicked": "👞 {user} をキックしました", + "@youKicked": { + "placeholders": { + "user": {} + } + }, + "youKickedAndBanned": "🙅 {user} をキックしてブロックしました", + "@youKickedAndBanned": { + "placeholders": { + "user": {} + } + }, + "storeInAppleKeyChain": "Apple KeyChainに保存", + "@storeInAppleKeyChain": {}, + "storeInAndroidKeystore": "Android KeyStoreに保存する", + "@storeInAndroidKeystore": {}, + "storeInSecureStorageDescription": "このデバイスの安全なストレージにリカバリーキーを保存。", + "@storeInSecureStorageDescription": {}, + "unlockOldMessages": "古いメッセージのロックを解除する", + "@unlockOldMessages": {}, + "callingAccount": "通話アカウント", + "@callingAccount": {}, + "callingPermissions": "通話の権限", + "@callingPermissions": {}, + "screenSharingTitle": "画面共有", + "@screenSharingTitle": {}, + "foregroundServiceRunning": "この通知は、フォアグラウンド サービスの実行中に表示されます。", + "@foregroundServiceRunning": {}, + "custom": "カスタム", + "@custom": {}, + "countFiles": "{count}個のファイル", + "@countFiles": { + "placeholders": { + "count": {} + } + }, + "storeSecurlyOnThisDevice": "このデバイスに安全に保管する", + "@storeSecurlyOnThisDevice": {}, + "whyIsThisMessageEncrypted": "このメッセージが読めない理由", + "@whyIsThisMessageEncrypted": {}, + "otherCallingPermissions": "マイク、カメラ、その他FluffyChatの権限", + "@otherCallingPermissions": {}, + "appearOnTopDetails": "アプリをトップに表示できるようにする(すでに通話アカウントとしてFluffychatを設定している場合は必要ありません)", + "@appearOnTopDetails": {}, + "dehydrateTorLong": "TOR ユーザーの場合、ウィンドウを閉じる前にセッションをエクスポートすることをお勧めします。", + "@dehydrateTorLong": {}, + "hydrateTorLong": "前回、TOR でセッションをエクスポートしましたか?すぐにインポートしてチャットを続けましょう。", + "@hydrateTorLong": {}, + "enableMultiAccounts": "(ベータ版) このデバイスで複数のアカウントを有効にする", + "@enableMultiAccounts": {}, + "pleaseEnterRecoveryKey": "リカバリーキーを入力してください。", + "@pleaseEnterRecoveryKey": {}, + "serverRequiresEmail": "このサーバーは、登録のためにメールアドレスを検証する必要があります。", + "@serverRequiresEmail": {}, + "sendSticker": "ステッカーを送る", + "@sendSticker": { + "type": "text", + "placeholders": {} + }, + "synchronizingPleaseWait": "同期中...お待ちください。", + "@synchronizingPleaseWait": { + "type": "text", + "placeholders": {} + }, + "emojis": "絵文字", + "@emojis": {}, + "markAsRead": "既読にする", + "@markAsRead": {}, + "videoCallsBetaWarning": "ビデオ通話は、現在ベータ版であることにご注意ください。すべてのプラットフォームで期待通りに動作しない、あるいはまったく動作しない可能性があります。", + "@videoCallsBetaWarning": {}, + "confirmEventUnpin": "イベントの固定を完全に解除してもよろしいですか?", + "@confirmEventUnpin": {}, + "unsupportedAndroidVersion": "サポートされていないAndroidのバージョン", + "@unsupportedAndroidVersion": {}, + "user": "ユーザー", + "@user": {}, + "newGroup": "新しいグループ", + "@newGroup": {}, + "noBackupWarning": "警告!チャットのバックアップを有効にしないと、暗号化されたメッセージにアクセスできなくなります。ログアウトする前に、まずチャットのバックアップを有効にすることを強くお勧めします。", + "@noBackupWarning": {}, + "disableEncryptionWarning": "セキュリティ上の理由から、以前は暗号化が有効だったチャットで暗号化を無効にすることはできません。", + "@disableEncryptionWarning": {}, + "youInvitedUser": "📩 {user} を招待しました", + "@youInvitedUser": { + "placeholders": { + "user": {} + } + }, + "reactedWith": "{sender} が {reaction} で反応しました", + "@reactedWith": { + "type": "text", + "placeholders": { + "sender": {}, + "reaction": {} + } + }, + "createNewSpace": "新しいスペース", + "@createNewSpace": { + "type": "text", + "placeholders": {} + }, + "widgetUrlError": "有効なURLではありません。", + "@widgetUrlError": {}, + "reportUser": "ユーザーを報告", + "@reportUser": {}, + "errorObtainingLocation": "位置情報の取得中にエラーが発生しました: {error}", + "@errorObtainingLocation": { + "type": "text", + "placeholders": { + "error": {} + } + }, + "pinMessage": "部屋にピン留めする", + "@pinMessage": {}, + "previousAccount": "前のアカウント", + "@previousAccount": {}, + "pleaseChoose": "選択してください", + "@pleaseChoose": { + "type": "text", + "placeholders": {} + }, + "oopsPushError": "おっと!残念ながら、プッシュ通知の設定中にエラーが発生しました。", + "@oopsPushError": { + "type": "text", + "placeholders": {} + }, + "noOtherDevicesFound": "他のデバイスが見つかりません", + "@noOtherDevicesFound": {}, + "recoveryKeyLost": "リカバリーキーを紛失した場合", + "@recoveryKeyLost": {}, + "shareLocation": "位置情報の共有", + "@shareLocation": { + "type": "text", + "placeholders": {} + }, + "time": "時間", + "@time": {}, + "sendAsText": "テキストとして送信", + "@sendAsText": { + "type": "text" + }, + "commandHint_googly": "ぎょろ目を送る", + "@commandHint_googly": {}, + "commandHint_hug": "ハグを送る", + "@commandHint_hug": {}, + "encryptThisChat": "このチャットを暗号化する", + "@encryptThisChat": {}, + "commandHint_markasdm": "ダイレクトメッセージの部屋としてマークする", + "@commandHint_markasdm": {}, + "commandHint_dm": "ダイレクトチャットを開始する\n暗号化を無効にするには、--no-encryptionを使用してください", + "@commandHint_dm": { + "type": "text", + "description": "Usage hint for the command /dm" + }, + "commandHint_leave": "この部屋を退出", + "@commandHint_leave": { + "type": "text", + "description": "Usage hint for the command /leave" + }, + "commandHint_myroomavatar": "この部屋の写真を設定する (mxc-uriで)", + "@commandHint_myroomavatar": { + "type": "text", + "description": "Usage hint for the command /myroomavatar" + }, + "commandHint_myroomnick": "この部屋の表示名を設定する", + "@commandHint_myroomnick": { + "type": "text", + "description": "Usage hint for the command /myroomnick" + }, + "commandHint_plain": "書式設定されていないテキストを送信する", + "@commandHint_plain": { + "type": "text", + "description": "Usage hint for the command /plain" + }, + "commandHint_react": "リアクションとして返信を送信する", + "@commandHint_react": { + "type": "text", + "description": "Usage hint for the command /react" + }, + "dehydrateTor": "TOR ユーザー: セッションをエクスポート", + "@dehydrateTor": {}, + "hydrateTor": "TOR ユーザー: セッションのエクスポートをインポート", + "@hydrateTor": {}, + "locationDisabledNotice": "位置情報サービスが無効になっています。位置情報を共有できるようにするには、位置情報サービスを有効にしてください。", + "@locationDisabledNotice": { + "type": "text", + "placeholders": {} + }, + "locationPermissionDeniedNotice": "位置情報の権限が拒否されました。位置情報を共有できるように許可してください。", + "@locationPermissionDeniedNotice": { + "type": "text", + "placeholders": {} + }, + "deviceKeys": "デバイスキー:", + "@deviceKeys": {}, + "sorryThatsNotPossible": "申し訳ありません...それは不可能です", + "@sorryThatsNotPossible": {}, + "wasDirectChatDisplayName": "空のチャット (以前は {oldDisplayName})", + "@wasDirectChatDisplayName": { + "type": "text", + "placeholders": { + "oldDisplayName": {} + } + }, + "doNotShowAgain": "今後表示しない", + "@doNotShowAgain": {}, + "hideUnimportantStateEvents": "重要でない状態イベントを非表示にする", + "@hideUnimportantStateEvents": {}, + "numChats": "{number} チャット", + "@numChats": { + "type": "number", + "placeholders": { + "number": {} + } + }, + "allSpaces": "すべてのスペース", + "@allSpaces": {}, + "enterRoom": "部屋に入る", + "@enterRoom": {}, + "enterSpace": "スペースに入る", + "@enterSpace": {}, + "newSpace": "新しいスペース", + "@newSpace": {}, + "reopenChat": "チャットを再開する", + "@reopenChat": {}, + "signInWith": "{provider}でログイン", + "@signInWith": { + "type": "text", + "placeholders": { + "provider": {} + } + }, + "signInWithPassword": "パスワードでログイン", + "@signInWithPassword": {} +} From e81b14a8085c137ccb0a47de49cc12da73c29265 Mon Sep 17 00:00:00 2001 From: Anonymous Date: Thu, 25 Jul 2024 05:09:39 +0000 Subject: [PATCH 112/288] Translated using Weblate (Esperanto) Currently translated at 49.6% (324 of 652 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eo/ --- assets/l10n/intl_eo.arb | 4138 ++++++++++++++++++--------------------- 1 file changed, 1905 insertions(+), 2233 deletions(-) diff --git a/assets/l10n/intl_eo.arb b/assets/l10n/intl_eo.arb index 2cda9beb91..872e72ec03 100644 --- a/assets/l10n/intl_eo.arb +++ b/assets/l10n/intl_eo.arb @@ -1,2234 +1,1906 @@ { - "@@last_modified": "2021-08-14 12:41:10.107750", - "about": "Prio", - "@about": { - "type": "text", - "placeholders": {} - }, - "accept": "Akcepti", - "@accept": { - "type": "text", - "placeholders": {} - }, - "acceptedTheInvitation": "{username} akceptis la inviton", - "@acceptedTheInvitation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "account": "Konto", - "@account": { - "type": "text", - "placeholders": {} - }, - "activatedEndToEndEncryption": "{username} aktivigis tutvojan ĉifradon", - "@activatedEndToEndEncryption": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "addEmail": "Aldoni retpoŝtadreson", - "@addEmail": { - "type": "text", - "placeholders": {} - }, - "admin": "Administranto", - "@admin": { - "type": "text", - "placeholders": {} - }, - "alias": "kromnomo", - "@alias": { - "type": "text", - "placeholders": {} - }, - "all": "Ĉio", - "@all": { - "type": "text", - "placeholders": {} - }, - "allChats": "Ĉiuj babiloj", - "@allChats": { - "type": "text", - "placeholders": {} - }, - "answeredTheCall": "{senderName} respondis la vokon", - "@answeredTheCall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "anyoneCanJoin": "Ĉiu ajn povas aliĝi", - "@anyoneCanJoin": { - "type": "text", - "placeholders": {} - }, - "appLock": "Ŝlosado", - "@appLock": { - "type": "text", - "placeholders": {} - }, - "archive": "Arĥivo", - "@archive": { - "type": "text", - "placeholders": {} - }, - "areGuestsAllowedToJoin": "Ĉu gastoj rajtas aliĝi", - "@areGuestsAllowedToJoin": { - "type": "text", - "placeholders": {} - }, - "areYouSure": "Ĉu vi certas?", - "@areYouSure": { - "type": "text", - "placeholders": {} - }, - "areYouSureYouWantToLogout": "Ĉu vi certe volas adiaŭi?", - "@areYouSureYouWantToLogout": { - "type": "text", - "placeholders": {} - }, - "askSSSSSign": "Por ke vi povu kontroli (subskribi) la alian personon, bonvolu enigi pasfrazon de via sekreta deponejo aŭ vian rehavan ŝlosilon.", - "@askSSSSSign": { - "type": "text", - "placeholders": {} - }, - "askVerificationRequest": "Ĉu akcepti ĉi tiun kontrolpeton de {username}?", - "@askVerificationRequest": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "badServerLoginTypesException": "La hejmservilo subtenas la jenajn specojn de salutoj:\n{serverVersions}\nSed ĉi tiu aplikaĵo subtenas nur:\n{supportedVersions}", - "@badServerLoginTypesException": { - "type": "text", - "placeholders": { - "serverVersions": {}, - "supportedVersions": {} - } - }, - "badServerVersionsException": "La hejmservilo subtenas la jenajn version de la specifaĵo:\n{serverVersions}\nSed ĉi tiu aplikaĵo subtenas nur {supportedVersions}", - "@badServerVersionsException": { - "type": "text", - "placeholders": { - "serverVersions": {}, - "supportedVersions": {} - } - }, - "banFromChat": "Forbari de babilo", - "@banFromChat": { - "type": "text", - "placeholders": {} - }, - "banned": "Forbarita", - "@banned": { - "type": "text", - "placeholders": {} - }, - "bannedUser": "{username} forbaris uzanton {targetName}", - "@bannedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "blockDevice": "Bloki aparaton", - "@blockDevice": { - "type": "text", - "placeholders": {} - }, - "blocked": "Blokita", - "@blocked": { - "type": "text", - "placeholders": {} - }, - "botMessages": "Mesaĝoj de robotoj", - "@botMessages": { - "type": "text", - "placeholders": {} - }, - "cancel": "Nuligi", - "@cancel": { - "type": "text", - "placeholders": {} - }, - "cantOpenUri": "Ne povis malfermi URI {uri}", - "@cantOpenUri": { - "type": "text", - "placeholders": { - "uri": {} - } - }, - "changeDeviceName": "Ŝanĝi nomon de aparato", - "@changeDeviceName": { - "type": "text", - "placeholders": {} - }, - "changedTheChatAvatar": "{username} ŝanĝis bildon de la babilo", - "@changedTheChatAvatar": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheChatDescriptionTo": "{username} ŝanĝis priskribon de la babilo al: «{description}»", - "@changedTheChatDescriptionTo": { - "type": "text", - "placeholders": { - "username": {}, - "description": {} - } - }, - "changedTheChatNameTo": "{username} ŝanĝis nomon de la babilo al: «{chatname}»", - "@changedTheChatNameTo": { - "type": "text", - "placeholders": { - "username": {}, - "chatname": {} - } - }, - "changedTheChatPermissions": "{username} ŝanĝis permesojn pri la babilo", - "@changedTheChatPermissions": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheDisplaynameTo": "{username} ŝanĝis sian prezentan nomon al: {username}", - "@changedTheDisplaynameTo": { - "type": "text", - "placeholders": { - "username": {}, - "displayname": {} - } - }, - "changedTheGuestAccessRules": "{username} ŝanĝis regulojn pri aliro de gastoj", - "@changedTheGuestAccessRules": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheGuestAccessRulesTo": "{username} ŝanĝis regulojn pri aliro de gastoj al: {rules}", - "@changedTheGuestAccessRulesTo": { - "type": "text", - "placeholders": { - "username": {}, - "rules": {} - } - }, - "changedTheHistoryVisibility": "{username} ŝanĝis videblecon de la historio", - "@changedTheHistoryVisibility": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheHistoryVisibilityTo": "{username} ŝanĝis videblecon de la historio al: {rules}", - "@changedTheHistoryVisibilityTo": { - "type": "text", - "placeholders": { - "username": {}, - "rules": {} - } - }, - "changedTheJoinRules": "{username} ŝanĝis regulojn pri aliĝado", - "@changedTheJoinRules": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheJoinRulesTo": "{username} ŝanĝis regulojn pri aliĝado al: {joinRules}", - "@changedTheJoinRulesTo": { - "type": "text", - "placeholders": { - "username": {}, - "joinRules": {} - } - }, - "changedTheProfileAvatar": "{username} ŝanĝis sian profilbildon", - "@changedTheProfileAvatar": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheRoomAliases": "{username} ŝanĝis la kromnomojn de la ĉambro", - "@changedTheRoomAliases": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheRoomInvitationLink": "{username} ŝanĝis la invitan ligilon", - "@changedTheRoomInvitationLink": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changePassword": "Ŝanĝi pasvorton", - "@changePassword": { - "type": "text", - "placeholders": {} - }, - "changeTheHomeserver": "Ŝanĝi hejmservilon", - "@changeTheHomeserver": { - "type": "text", - "placeholders": {} - }, - "changeTheme": "Ŝanĝu la haŭton", - "@changeTheme": { - "type": "text", - "placeholders": {} - }, - "changeTheNameOfTheGroup": "Ŝanĝi nomon de la grupo", - "@changeTheNameOfTheGroup": { - "type": "text", - "placeholders": {} - }, - "changeYourAvatar": "Ŝanĝi vian profilbildon", - "@changeYourAvatar": { - "type": "text", - "placeholders": {} - }, - "channelCorruptedDecryptError": "La ĉifrado estas difektita", - "@channelCorruptedDecryptError": { - "type": "text", - "placeholders": {} - }, - "chat": "Babilo", - "@chat": { - "type": "text", - "placeholders": {} - }, - "chatBackup": "Savkopiado de babilo", - "@chatBackup": { - "type": "text", - "placeholders": {} - }, - "chatBackupDescription": "Via savkopio de babilo estas sekurigita per sekureca ŝlosilo. Bonvolu certigi, ke vi ne perdos ĝin.", - "@chatBackupDescription": { - "type": "text", - "placeholders": {} - }, - "chatDetails": "Detaloj pri babilo", - "@chatDetails": { - "type": "text", - "placeholders": {} - }, - "chats": "Babiloj", - "@chats": { - "type": "text", - "placeholders": {} - }, - "chooseAStrongPassword": "Elektu fortan pasvorton", - "@chooseAStrongPassword": { - "type": "text", - "placeholders": {} - }, - "clearArchive": "Vakigi arĥivon", - "@clearArchive": {}, - "close": "Fermi", - "@close": { - "type": "text", - "placeholders": {} - }, - "commandHint_ban": "Forbari la donitan uzanton de ĉi tiu ĉambro", - "@commandHint_ban": { - "type": "text", - "description": "Usage hint for the command /ban" - }, - "commandHint_html": "Sendi tekston formatan je HTML", - "@commandHint_html": { - "type": "text", - "description": "Usage hint for the command /html" - }, - "commandHint_invite": "Inviti la donitan uzanton al ĉi tiu ĉambro", - "@commandHint_invite": { - "type": "text", - "description": "Usage hint for the command /invite" - }, - "commandHint_join": "Aliĝi al la donita ĉambro", - "@commandHint_join": { - "type": "text", - "description": "Usage hint for the command /join" - }, - "commandHint_kick": "Forigi la donitan uzanton de ĉi tiu ĉambro", - "@commandHint_kick": { - "type": "text", - "description": "Usage hint for the command /kick" - }, - "commandHint_leave": "Foriri de ĉi tiu ĉambro", - "@commandHint_leave": { - "type": "text", - "description": "Usage hint for the command /leave" - }, - "commandHint_me": "Priskribu vian agon", - "@commandHint_me": { - "type": "text", - "description": "Usage hint for the command /me" - }, - "commandHint_myroomavatar": "Agordi vian profilbildon por ĉi tiu ĉambro (laŭ mxc-uri)", - "@commandHint_myroomavatar": { - "type": "text", - "description": "Usage hint for the command /myroomavatar" - }, - "commandHint_myroomnick": "Agordi vian prezentan nomon en ĉi tiu ĉambro", - "@commandHint_myroomnick": { - "type": "text", - "description": "Usage hint for the command /myroomnick" - }, - "commandHint_op": "Agordi povnivelon de la donita uzanto (implicite: 50)", - "@commandHint_op": { - "type": "text", - "description": "Usage hint for the command /op" - }, - "commandHint_plain": "Sendi senformatan tekston", - "@commandHint_plain": { - "type": "text", - "description": "Usage hint for the command /plain" - }, - "commandHint_react": "Sendi respondon kiel reagon", - "@commandHint_react": { - "type": "text", - "description": "Usage hint for the command /react" - }, - "commandHint_unban": "Malforbari la donitan uzanton de ĉi tiu ĉambro", - "@commandHint_unban": { - "type": "text", - "description": "Usage hint for the command /unban" - }, - "commandInvalid": "Nevalida ordono", - "@commandInvalid": { - "type": "text" - }, - "commandMissing": "{command} ne estas ordono.", - "@commandMissing": { - "type": "text", - "placeholders": { - "command": {} - }, - "description": "State that {command} is not a valid /command." - }, - "compareEmojiMatch": "Komparu kaj certigu, ke la jenaj bildosignoj samas en ambaŭ aparatoj:", - "@compareEmojiMatch": { - "type": "text", - "placeholders": {} - }, - "compareNumbersMatch": "Komparu kaj certigu, ke la jenaj numeroj samas en ambaŭ aparatoj:", - "@compareNumbersMatch": { - "type": "text", - "placeholders": {} - }, - "configureChat": "Agordi babilon", - "@configureChat": { - "type": "text", - "placeholders": {} - }, - "confirm": "Konfirmi", - "@confirm": { - "type": "text", - "placeholders": {} - }, - "connect": "Konektiĝi", - "@connect": { - "type": "text", - "placeholders": {} - }, - "contactHasBeenInvitedToTheGroup": "Kontakto invitiĝis al la grupo", - "@contactHasBeenInvitedToTheGroup": { - "type": "text", - "placeholders": {} - }, - "containsDisplayName": "Enhavas prezentan nomon", - "@containsDisplayName": { - "type": "text", - "placeholders": {} - }, - "containsUserName": "Enhavas uzantonomon", - "@containsUserName": { - "type": "text", - "placeholders": {} - }, - "contentHasBeenReported": "La enhavo raportiĝis al la administrantoj de la servilo", - "@contentHasBeenReported": { - "type": "text", - "placeholders": {} - }, - "copiedToClipboard": "Kopiite al tondujo", - "@copiedToClipboard": { - "type": "text", - "placeholders": {} - }, - "copy": "Kopii", - "@copy": { - "type": "text", - "placeholders": {} - }, - "copyToClipboard": "Kopii al tondujo", - "@copyToClipboard": { - "type": "text", - "placeholders": {} - }, - "couldNotDecryptMessage": "Ne povis malĉifri mesaĝon: {error}", - "@couldNotDecryptMessage": { - "type": "text", - "placeholders": { - "error": {} - } - }, - "countParticipants": "{count} partoprenantoj", - "@countParticipants": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "create": "Krei", - "@create": { - "type": "text", - "placeholders": {} - }, - "createdTheChat": "{username} kreis la babilon", - "@createdTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "createNewSpace": "Nova aro", - "@createNewSpace": { - "type": "text", - "placeholders": {} - }, - "currentlyActive": "Nun aktiva", - "@currentlyActive": { - "type": "text", - "placeholders": {} - }, - "darkTheme": "Malhela", - "@darkTheme": { - "type": "text", - "placeholders": {} - }, - "dateAndTimeOfDay": "{date}, {timeOfDay}", - "@dateAndTimeOfDay": { - "type": "text", - "placeholders": { - "date": {}, - "timeOfDay": {} - } - }, - "dateWithoutYear": "{day}a de la {month}a", - "@dateWithoutYear": { - "type": "text", - "placeholders": { - "month": {}, - "day": {} - } - }, - "dateWithYear": "{day}a de la {month}a de {year}", - "@dateWithYear": { - "type": "text", - "placeholders": { - "year": {}, - "month": {}, - "day": {} - } - }, - "deactivateAccountWarning": "Ĉi tio malaktivigos vian konton de uzanto. Ne eblas tion malfari! Ĉu certe vi certas?", - "@deactivateAccountWarning": { - "type": "text", - "placeholders": {} - }, - "defaultPermissionLevel": "Norma nivelo de permesoj", - "@defaultPermissionLevel": { - "type": "text", - "placeholders": {} - }, - "delete": "Forigi", - "@delete": { - "type": "text", - "placeholders": {} - }, - "deleteAccount": "Forigi konton", - "@deleteAccount": { - "type": "text", - "placeholders": {} - }, - "deleteMessage": "Forigi mesaĝon", - "@deleteMessage": { - "type": "text", - "placeholders": {} - }, - "device": "Aparato", - "@device": { - "type": "text", - "placeholders": {} - }, - "deviceId": "Identigilo de aparato", - "@deviceId": { - "type": "text", - "placeholders": {} - }, - "devices": "Aparatoj", - "@devices": { - "type": "text", - "placeholders": {} - }, - "directChats": "Rektaj babiloj", - "@directChats": { - "type": "text", - "placeholders": {} - }, - "displaynameHasBeenChanged": "Prezenta nomo ŝanĝiĝis", - "@displaynameHasBeenChanged": { - "type": "text", - "placeholders": {} - }, - "downloadFile": "Elŝuti dosieron", - "@downloadFile": { - "type": "text", - "placeholders": {} - }, - "edit": "Redakti", - "@edit": { - "type": "text", - "placeholders": {} - }, - "editBlockedServers": "Redakti blokitajn servilojn", - "@editBlockedServers": { - "type": "text", - "placeholders": {} - }, - "editDisplayname": "Redakti prezentan nomon", - "@editDisplayname": { - "type": "text", - "placeholders": {} - }, - "editRoomAliases": "Ŝanĝi kromnomojn de ĉambro", - "@editRoomAliases": { - "type": "text", - "placeholders": {} - }, - "editRoomAvatar": "Redakti bildon de ĉambro", - "@editRoomAvatar": { - "type": "text", - "placeholders": {} - }, - "emoteExists": "Mieneto jam ekzistas!", - "@emoteExists": { - "type": "text", - "placeholders": {} - }, - "emoteInvalid": "Nevalida mallongigo de mieneto!", - "@emoteInvalid": { - "type": "text", - "placeholders": {} - }, - "emotePacks": "Mienetaroj por la ĉambro", - "@emotePacks": { - "type": "text", - "placeholders": {} - }, - "emoteSettings": "Agordoj pri mienetoj", - "@emoteSettings": { - "type": "text", - "placeholders": {} - }, - "emoteShortcode": "Mallongigo de mieneto", - "@emoteShortcode": { - "type": "text", - "placeholders": {} - }, - "emoteWarnNeedToPick": "Vi devas elekti mallongigon de mieneto kaj bildon!", - "@emoteWarnNeedToPick": { - "type": "text", - "placeholders": {} - }, - "emptyChat": "Malplena babilo", - "@emptyChat": { - "type": "text", - "placeholders": {} - }, - "enableEmotesGlobally": "Ŝalti mienetaron ĉie", - "@enableEmotesGlobally": { - "type": "text", - "placeholders": {} - }, - "enableEncryption": "Ŝalti ĉifradon", - "@enableEncryption": { - "type": "text", - "placeholders": {} - }, - "enableEncryptionWarning": "Vi ne povos malŝalti la ĉifradon. Ĉu vi certas?", - "@enableEncryptionWarning": { - "type": "text", - "placeholders": {} - }, - "encrypted": "Ĉifrite", - "@encrypted": { - "type": "text", - "placeholders": {} - }, - "encryption": "Ĉifrado", - "@encryption": { - "type": "text", - "placeholders": {} - }, - "encryptionNotEnabled": "Ĉifrado ne estas ŝaltita", - "@encryptionNotEnabled": { - "type": "text", - "placeholders": {} - }, - "endedTheCall": "{senderName} finis la vokon", - "@endedTheCall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "enterAnEmailAddress": "Enigu retpoŝtadreson", - "@enterAnEmailAddress": { - "type": "text", - "placeholders": {} - }, - "enterYourHomeserver": "Enigu vian hejmservilon", - "@enterYourHomeserver": { - "type": "text", - "placeholders": {} - }, - "errorObtainingLocation": "Eraris akirado de loko: {error}", - "@errorObtainingLocation": { - "type": "text", - "placeholders": { - "error": {} - } - }, - "everythingReady": "Ĉio pretas!", - "@everythingReady": { - "type": "text", - "placeholders": {} - }, - "extremeOffensive": "Tre ofenda", - "@extremeOffensive": { - "type": "text", - "placeholders": {} - }, - "fileName": "Dosiernomo", - "@fileName": { - "type": "text", - "placeholders": {} - }, - "fluffychat": "FluffyChat", - "@fluffychat": { - "type": "text", - "placeholders": {} - }, - "fontSize": "Grandeco de tiparo", - "@fontSize": { - "type": "text", - "placeholders": {} - }, - "forward": "Plusendi", - "@forward": { - "type": "text", - "placeholders": {} - }, - "fromJoining": "Ekde aliĝo", - "@fromJoining": { - "type": "text", - "placeholders": {} - }, - "fromTheInvitation": "Ekde la invito", - "@fromTheInvitation": { - "type": "text", - "placeholders": {} - }, - "goToTheNewRoom": "Iri al la nova ĉambro", - "@goToTheNewRoom": { - "type": "text", - "placeholders": {} - }, - "group": "Grupo", - "@group": { - "type": "text", - "placeholders": {} - }, - "groupIsPublic": "Grupo estas publika", - "@groupIsPublic": { - "type": "text", - "placeholders": {} - }, - "groups": "Grupoj", - "@groups": { - "type": "text", - "placeholders": {} - }, - "groupWith": "Grupo kun {displayname}", - "@groupWith": { - "type": "text", - "placeholders": { - "displayname": {} - } - }, - "guestsAreForbidden": "Gastoj estas malpermesitaj", - "@guestsAreForbidden": { - "type": "text", - "placeholders": {} - }, - "guestsCanJoin": "Gastoj povas aliĝi", - "@guestsCanJoin": { - "type": "text", - "placeholders": {} - }, - "hasWithdrawnTheInvitationFor": "{username} nuligis la inviton por {targetName}", - "@hasWithdrawnTheInvitationFor": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "help": "Helpo", - "@help": { - "type": "text", - "placeholders": {} - }, - "hideRedactedEvents": "Kaŝi obskurigitajn eventojn", - "@hideRedactedEvents": { - "type": "text", - "placeholders": {} - }, - "hideUnknownEvents": "Kaŝi nekonatajn eventojn", - "@hideUnknownEvents": { - "type": "text", - "placeholders": {} - }, - "howOffensiveIsThisContent": "Kiel ofenda estas ĉi tiu enhavo?", - "@howOffensiveIsThisContent": { - "type": "text", - "placeholders": {} - }, - "id": "Identigilo", - "@id": { - "type": "text", - "placeholders": {} - }, - "identity": "Identeco", - "@identity": { - "type": "text", - "placeholders": {} - }, - "ignore": "Malatenti", - "@ignore": { - "type": "text", - "placeholders": {} - }, - "ignoredUsers": "Malatentitaj uzantoj", - "@ignoredUsers": { - "type": "text", - "placeholders": {} - }, - "iHaveClickedOnLink": "Mi klakis la ligilon", - "@iHaveClickedOnLink": { - "type": "text", - "placeholders": {} - }, - "incorrectPassphraseOrKey": "Neĝusta pasfrazo aŭ rehava ŝlosilo", - "@incorrectPassphraseOrKey": { - "type": "text", - "placeholders": {} - }, - "inoffensive": "Neofenda", - "@inoffensive": { - "type": "text", - "placeholders": {} - }, - "inviteContact": "Inviti kontakton", - "@inviteContact": { - "type": "text", - "placeholders": {} - }, - "inviteContactToGroup": "Inviti kontakton al {groupName}", - "@inviteContactToGroup": { - "type": "text", - "placeholders": { - "groupName": {} - } - }, - "invited": "Invitita", - "@invited": { - "type": "text", - "placeholders": {} - }, - "invitedUser": "{username} invitis uzanton {targetName}", - "@invitedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "invitedUsersOnly": "Nur invititoj", - "@invitedUsersOnly": { - "type": "text", - "placeholders": {} - }, - "inviteForMe": "Invito por mi", - "@inviteForMe": { - "type": "text", - "placeholders": {} - }, - "inviteText": "{username} invitis vin al FluffyChat. \n1. Instalu la aplikaĵon FluffyChat: https://fluffychat.im \n2. Registriĝu aŭ salutu \n3. Malfermu la invitan ligilon: {link}", - "@inviteText": { - "type": "text", - "placeholders": { - "username": {}, - "link": {} - } - }, - "isTyping": "tajpas…", - "@isTyping": { - "type": "text", - "placeholders": {} - }, - "joinedTheChat": "{username} aliĝis al la babilo", - "@joinedTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "joinRoom": "Aliĝi al ĉambro", - "@joinRoom": { - "type": "text", - "placeholders": {} - }, - "kicked": "{username} forpelis uzanton {targetName}", - "@kicked": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "kickedAndBanned": "{username} forpelis kaj forbaris uzanton {targetName}", - "@kickedAndBanned": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "kickFromChat": "Forpeli de babilo", - "@kickFromChat": { - "type": "text", - "placeholders": {} - }, - "lastActiveAgo": "Lastafoje aktiva: {localizedTimeShort}", - "@lastActiveAgo": { - "type": "text", - "placeholders": { - "localizedTimeShort": {} - } - }, - "leave": "Foriri", - "@leave": { - "type": "text", - "placeholders": {} - }, - "leftTheChat": "Foriris de la ĉambro", - "@leftTheChat": { - "type": "text", - "placeholders": {} - }, - "license": "Permesilo", - "@license": { - "type": "text", - "placeholders": {} - }, - "lightTheme": "Hela", - "@lightTheme": { - "type": "text", - "placeholders": {} - }, - "loadCountMoreParticipants": "Enlegi {count} pliajn partoprenantojn", - "@loadCountMoreParticipants": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "loadingPleaseWait": "Enlegante… bonvolu atendi.", - "@loadingPleaseWait": { - "type": "text", - "placeholders": {} - }, - "loadMore": "Enlegi pli…", - "@loadMore": { - "type": "text", - "placeholders": {} - }, - "login": "Saluti", - "@login": { - "type": "text", - "placeholders": {} - }, - "logInTo": "Saluti servilon {homeserver}", - "@logInTo": { - "type": "text", - "placeholders": { - "homeserver": {} - } - }, - "logout": "Adiaŭi", - "@logout": { - "type": "text", - "placeholders": {} - }, - "memberChanges": "Ŝanĝoj de anoj", - "@memberChanges": { - "type": "text", - "placeholders": {} - }, - "mention": "Mencii", - "@mention": { - "type": "text", - "placeholders": {} - }, - "messages": "Mesaĝoj", - "@messages": { - "type": "text", - "placeholders": {} - }, - "moderator": "Reguligisto", - "@moderator": { - "type": "text", - "placeholders": {} - }, - "muteChat": "Silentigi babilon", - "@muteChat": { - "type": "text", - "placeholders": {} - }, - "needPantalaimonWarning": "Bonvolu scii, ke vi ankoraŭ bezonas la programon Pantalaimon por uzi tutvojan ĉifradon.", - "@needPantalaimonWarning": { - "type": "text", - "placeholders": {} - }, - "newChat": "Nova babilo", - "@newChat": { - "type": "text", - "placeholders": {} - }, - "newMessageInFluffyChat": "Nova mesaĝo en FluffyChat", - "@newMessageInFluffyChat": { - "type": "text", - "placeholders": {} - }, - "newVerificationRequest": "Nova kontrolpeto!", - "@newVerificationRequest": { - "type": "text", - "placeholders": {} - }, - "next": "Sekva", - "@next": { - "type": "text", - "placeholders": {} - }, - "no": "Ne", - "@no": { - "type": "text", - "placeholders": {} - }, - "noConnectionToTheServer": "Neniu konekto al la servilo", - "@noConnectionToTheServer": { - "type": "text", - "placeholders": {} - }, - "noEmotesFound": "Neniuj mienetoj troviĝis. 😕", - "@noEmotesFound": { - "type": "text", - "placeholders": {} - }, - "noEncryptionForPublicRooms": "Vi nur povas aktivigi ĉifradon kiam la ĉambro ne plu estas publike alirebla.", - "@noEncryptionForPublicRooms": { - "type": "text", - "placeholders": {} - }, - "noGoogleServicesWarning": "Ŝajnas, ke via telefono ne havas servojn de Google. Tio estas bona decido por via privateco! Por ricevadi pasivajn sciigojn en FluffyChat, ni rekomendas, ke vi uzu la https://microg.org/ aŭ https://unifiedpush.org/.", - "@noGoogleServicesWarning": { - "type": "text", - "placeholders": {} - }, - "none": "Neniu", - "@none": { - "type": "text", - "placeholders": {} - }, - "noPasswordRecoveryDescription": "Vi ankoraŭ ne aldonis manieron rehavi vian pasvorton.", - "@noPasswordRecoveryDescription": { - "type": "text", - "placeholders": {} - }, - "noPermission": "Neniu permeso", - "@noPermission": { - "type": "text", - "placeholders": {} - }, - "noRoomsFound": "Neniuj ĉambroj troviĝis…", - "@noRoomsFound": { - "type": "text", - "placeholders": {} - }, - "notifications": "Sciigoj", - "@notifications": { - "type": "text", - "placeholders": {} - }, - "notificationsEnabledForThisAccount": "Sciigoj ŝaltiĝis por ĉi tiu konto", - "@notificationsEnabledForThisAccount": { - "type": "text", - "placeholders": {} - }, - "numUsersTyping": "{count} uzantoj tajpas…", - "@numUsersTyping": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "obtainingLocation": "Akirante lokon…", - "@obtainingLocation": { - "type": "text", - "placeholders": {} - }, - "offensive": "Ofenda", - "@offensive": { - "type": "text", - "placeholders": {} - }, - "offline": "Eksterrete", - "@offline": { - "type": "text", - "placeholders": {} - }, - "ok": "bone", - "@ok": { - "type": "text", - "placeholders": {} - }, - "online": "Enrete", - "@online": { - "type": "text", - "placeholders": {} - }, - "onlineKeyBackupEnabled": "Enreta savkopiado de ŝlosiloj estas ŝaltita", - "@onlineKeyBackupEnabled": { - "type": "text", - "placeholders": {} - }, - "oopsPushError": "Oj! Bedaŭrinde eraris la agordado de pasivaj sciigoj.", - "@oopsPushError": { - "type": "text", - "placeholders": {} - }, - "oopsSomethingWentWrong": "Oj! Io misokazis…", - "@oopsSomethingWentWrong": { - "type": "text", - "placeholders": {} - }, - "openAppToReadMessages": "Malfermu la aplikaĵon por legi mesaĝojn", - "@openAppToReadMessages": { - "type": "text", - "placeholders": {} - }, - "openCamera": "Malfermi fotilon", - "@openCamera": { - "type": "text", - "placeholders": {} - }, - "or": "Aŭ", - "@or": { - "type": "text", - "placeholders": {} - }, - "participant": "Partoprenanto", - "@participant": { - "type": "text", - "placeholders": {} - }, - "passphraseOrKey": "pasfrazo aŭ rehava ŝlosilo", - "@passphraseOrKey": { - "type": "text", - "placeholders": {} - }, - "password": "Pasvorto", - "@password": { - "type": "text", - "placeholders": {} - }, - "passwordForgotten": "Forgesita pasvorto", - "@passwordForgotten": { - "type": "text", - "placeholders": {} - }, - "passwordHasBeenChanged": "Pasvorto ŝanĝiĝis", - "@passwordHasBeenChanged": { - "type": "text", - "placeholders": {} - }, - "passwordRecovery": "Rehavo de pasvorto", - "@passwordRecovery": { - "type": "text", - "placeholders": {} - }, - "people": "Personoj", - "@people": { - "type": "text", - "placeholders": {} - }, - "pickImage": "Elekti bildon", - "@pickImage": { - "type": "text", - "placeholders": {} - }, - "pin": "Fiksi", - "@pin": { - "type": "text", - "placeholders": {} - }, - "play": "Ludi {fileName}", - "@play": { - "type": "text", - "placeholders": { - "fileName": {} - } - }, - "pleaseChoose": "Bonvolu elekti", - "@pleaseChoose": { - "type": "text", - "placeholders": {} - }, - "pleaseChooseAPasscode": "Bonvolu elekti paskodon", - "@pleaseChooseAPasscode": { - "type": "text", - "placeholders": {} - }, - "pleaseClickOnLink": "Bonvolu klaki la ligilon en la retletero kaj pluiĝi.", - "@pleaseClickOnLink": { - "type": "text", - "placeholders": {} - }, - "pleaseEnter4Digits": "Bonvolu enigi 4 ciferojn, aŭ nenion por malŝalti ŝlosadon de la aplikaĵo.", - "@pleaseEnter4Digits": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourPassword": "Bonvolu enigi vian pasvorton", - "@pleaseEnterYourPassword": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourPin": "Bonvolu enigi vian personan identigan numeron", - "@pleaseEnterYourPin": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourUsername": "Bonvolu enigi vian uzantonomon", - "@pleaseEnterYourUsername": { - "type": "text", - "placeholders": {} - }, - "pleaseFollowInstructionsOnWeb": "Bonvolu sekvi la instrukciojn de la retejo kaj tuŝetu al «Sekva».", - "@pleaseFollowInstructionsOnWeb": { - "type": "text", - "placeholders": {} - }, - "privacy": "Privateco", - "@privacy": { - "type": "text", - "placeholders": {} - }, - "publicRooms": "Publikaj ĉambroj", - "@publicRooms": { - "type": "text", - "placeholders": {} - }, - "pushRules": "Reguloj de pasivaj sciigoj", - "@pushRules": { - "type": "text", - "placeholders": {} - }, - "reason": "Kialo", - "@reason": { - "type": "text", - "placeholders": {} - }, - "recording": "Registrante", - "@recording": { - "type": "text", - "placeholders": {} - }, - "redactedAnEvent": "{username} obskurigis eventon", - "@redactedAnEvent": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "redactMessage": "Obskurigi mesaĝon", - "@redactMessage": { - "type": "text", - "placeholders": {} - }, - "register": "Registriĝi", - "@register": { - "type": "text", - "placeholders": {} - }, - "reject": "Rifuzi", - "@reject": { - "type": "text", - "placeholders": {} - }, - "rejectedTheInvitation": "{username} rifuzis la inviton", - "@rejectedTheInvitation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "rejoin": "Ree aliĝi", - "@rejoin": { - "type": "text", - "placeholders": {} - }, - "remove": "Forigi", - "@remove": { - "type": "text", - "placeholders": {} - }, - "removeAllOtherDevices": "Forigi ĉiujn aliajn aparatojn", - "@removeAllOtherDevices": { - "type": "text", - "placeholders": {} - }, - "removedBy": "Forigita de {username}", - "@removedBy": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "removeDevice": "Forigi aparaton", - "@removeDevice": { - "type": "text", - "placeholders": {} - }, - "unbanFromChat": "Malforbari", - "@unbanFromChat": { - "type": "text", - "placeholders": {} - }, - "removeYourAvatar": "Forigi vian profilbildon", - "@removeYourAvatar": { - "type": "text", - "placeholders": {} - }, - "renderRichContent": "Bildigi riĉforman enhavon de mesaĝoj", - "@renderRichContent": { - "type": "text", - "placeholders": {} - }, - "replaceRoomWithNewerVersion": "Anstataŭigi ĉambron per nova versio", - "@replaceRoomWithNewerVersion": { - "type": "text", - "placeholders": {} - }, - "reply": "Respondi", - "@reply": { - "type": "text", - "placeholders": {} - }, - "reportMessage": "Raporti mesaĝon", - "@reportMessage": { - "type": "text", - "placeholders": {} - }, - "requestPermission": "Peti permeson", - "@requestPermission": { - "type": "text", - "placeholders": {} - }, - "roomHasBeenUpgraded": "Ĉambro gradaltiĝis", - "@roomHasBeenUpgraded": { - "type": "text", - "placeholders": {} - }, - "roomVersion": "Versio de ĉambro", - "@roomVersion": { - "type": "text", - "placeholders": {} - }, - "saveFile": "Konservi dosieron", - "@saveFile": { - "type": "text", - "placeholders": {} - }, - "search": "Serĉi", - "@search": { - "type": "text", - "placeholders": {} - }, - "security": "Sekureco", - "@security": { - "type": "text", - "placeholders": {} - }, - "seenByUser": "Vidita de {username}", - "@seenByUser": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "send": "Sendi", - "@send": { - "type": "text", - "placeholders": {} - }, - "sendAMessage": "Sendi mesaĝon", - "@sendAMessage": { - "type": "text", - "placeholders": {} - }, - "sendAudio": "Sendi sondosieron", - "@sendAudio": { - "type": "text", - "placeholders": {} - }, - "sendFile": "Sendi dosieron", - "@sendFile": { - "type": "text", - "placeholders": {} - }, - "sendImage": "Sendi bildon", - "@sendImage": { - "type": "text", - "placeholders": {} - }, - "sendMessages": "Sendi mesaĝojn", - "@sendMessages": { - "type": "text", - "placeholders": {} - }, - "sendOriginal": "Sendi originalon", - "@sendOriginal": { - "type": "text", - "placeholders": {} - }, - "sendSticker": "Sendi glumarkon", - "@sendSticker": { - "type": "text", - "placeholders": {} - }, - "sendVideo": "Sendi filmon", - "@sendVideo": { - "type": "text", - "placeholders": {} - }, - "sentAFile": "{username} sendis dosieron", - "@sentAFile": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentAnAudio": "{username} sendis sondosieron", - "@sentAnAudio": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentAPicture": "{username} sendis bildon", - "@sentAPicture": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentASticker": "{username} sendis glumarkon", - "@sentASticker": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentAVideo": "{username} sendis filmon", - "@sentAVideo": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentCallInformations": "{senderName} sendis informojn pri voko", - "@sentCallInformations": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "setAsCanonicalAlias": "Agordi kiel ĉefan kromnomon", - "@setAsCanonicalAlias": { - "type": "text", - "placeholders": {} - }, - "setCustomEmotes": "Agordi proprajn mienetojn", - "@setCustomEmotes": { - "type": "text", - "placeholders": {} - }, - "setInvitationLink": "Agordi invitan ligilon", - "@setInvitationLink": { - "type": "text", - "placeholders": {} - }, - "setPermissionsLevel": "Agordi nivelon de permesoj", - "@setPermissionsLevel": { - "type": "text", - "placeholders": {} - }, - "setStatus": "Agordi staton", - "@setStatus": { - "type": "text", - "placeholders": {} - }, - "settings": "Agordoj", - "@settings": { - "type": "text", - "placeholders": {} - }, - "share": "Konigi", - "@share": { - "type": "text", - "placeholders": {} - }, - "sharedTheLocation": "{username} konigis sian lokon", - "@sharedTheLocation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "shareLocation": "Konigi lokon", - "@shareLocation": { - "type": "text", - "placeholders": {} - }, - "showPassword": "Montri pasvorton", - "@showPassword": { - "type": "text", - "placeholders": {} - }, - "singlesignon": "Ununura saluto", - "@singlesignon": { - "type": "text", - "placeholders": {} - }, - "skip": "Preterpasi", - "@skip": { - "type": "text", - "placeholders": {} - }, - "sourceCode": "Fontkodo", - "@sourceCode": { - "type": "text", - "placeholders": {} - }, - "spaceIsPublic": "Aro estas publika", - "@spaceIsPublic": { - "type": "text", - "placeholders": {} - }, - "spaceName": "Nomo de aro", - "@spaceName": { - "type": "text", - "placeholders": {} - }, - "startedACall": "{senderName} komencis vokon", - "@startedACall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "status": "Stato", - "@status": { - "type": "text", - "placeholders": {} - }, - "statusExampleMessage": "Kiel vi fartas?", - "@statusExampleMessage": { - "type": "text", - "placeholders": {} - }, - "submit": "Sendi", - "@submit": { - "type": "text", - "placeholders": {} - }, - "synchronizingPleaseWait": "Spegulante… Bonvolu atendi.", - "@synchronizingPleaseWait": { - "type": "text", - "placeholders": {} - }, - "systemTheme": "Sistema", - "@systemTheme": { - "type": "text", - "placeholders": {} - }, - "theyDontMatch": "Ili ne akordas", - "@theyDontMatch": { - "type": "text", - "placeholders": {} - }, - "theyMatch": "Ili akordas", - "@theyMatch": { - "type": "text", - "placeholders": {} - }, - "title": "FluffyChat", - "@title": { - "description": "Title for the application", - "type": "text", - "placeholders": {} - }, - "toggleFavorite": "Baskuli elstarigon", - "@toggleFavorite": { - "type": "text", - "placeholders": {} - }, - "toggleMuted": "Basklui silentigon", - "@toggleMuted": { - "type": "text", - "placeholders": {} - }, - "toggleUnread": "Baskuli legitecon", - "@toggleUnread": { - "type": "text", - "placeholders": {} - }, - "tooManyRequestsWarning": "Tro multaj petoj. Bonvolu reprovi poste!", - "@tooManyRequestsWarning": { - "type": "text", - "placeholders": {} - }, - "transferFromAnotherDevice": "Transporti de alia aparato", - "@transferFromAnotherDevice": { - "type": "text", - "placeholders": {} - }, - "tryToSendAgain": "Reprovi sendi", - "@tryToSendAgain": { - "type": "text", - "placeholders": {} - }, - "unavailable": "Nedisponeble", - "@unavailable": { - "type": "text", - "placeholders": {} - }, - "unbannedUser": "{username} malforbaris uzanton {targetName}", - "@unbannedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "unblockDevice": "Malbloki aparaton", - "@unblockDevice": { - "type": "text", - "placeholders": {} - }, - "unknownDevice": "Nekonata aparato", - "@unknownDevice": { - "type": "text", - "placeholders": {} - }, - "unknownEncryptionAlgorithm": "Nekonata ĉifra algoritmo", - "@unknownEncryptionAlgorithm": { - "type": "text", - "placeholders": {} - }, - "unknownEvent": "Nekonata evento «{type}»", - "@unknownEvent": { - "type": "text", - "placeholders": { - "type": {} - } - }, - "unmuteChat": "Malsilentigi babilon", - "@unmuteChat": { - "type": "text", - "placeholders": {} - }, - "unpin": "Malfiksi", - "@unpin": { - "type": "text", - "placeholders": {} - }, - "unreadChats": "{unreadCount, plural, =1{1 nelegita babilo} other{{unreadCount} nelegitaj babiloj}}", - "@unreadChats": { - "type": "text", - "placeholders": { - "unreadCount": {} - } - }, - "userAndOthersAreTyping": "{username} kaj {count} aliaj tajpas…", - "@userAndOthersAreTyping": { - "type": "text", - "placeholders": { - "username": {}, - "count": {} - } - }, - "userAndUserAreTyping": "{username} kaj {username2} tajpas…", - "@userAndUserAreTyping": { - "type": "text", - "placeholders": { - "username": {}, - "username2": {} - } - }, - "userIsTyping": "{username} tajpas…", - "@userIsTyping": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "userLeftTheChat": "{username} foriris de la babilo", - "@userLeftTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "username": "Uzantonomo", - "@username": { - "type": "text", - "placeholders": {} - }, - "userSentUnknownEvent": "{username} sendis eventon de speco {type}", - "@userSentUnknownEvent": { - "type": "text", - "placeholders": { - "username": {}, - "type": {} - } - }, - "verified": "Kontrolita", - "@verified": { - "type": "text", - "placeholders": {} - }, - "verify": "Kontroli", - "@verify": { - "type": "text", - "placeholders": {} - }, - "verifyStart": "Komenci kontrolon", - "@verifyStart": { - "type": "text", - "placeholders": {} - }, - "verifySuccess": "Vi sukcese kontrolis!", - "@verifySuccess": { - "type": "text", - "placeholders": {} - }, - "verifyTitle": "Kontrolante alian konton", - "@verifyTitle": { - "type": "text", - "placeholders": {} - }, - "videoCall": "Vidvoko", - "@videoCall": { - "type": "text", - "placeholders": {} - }, - "visibilityOfTheChatHistory": "Videbleco de historio de la babilo", - "@visibilityOfTheChatHistory": { - "type": "text", - "placeholders": {} - }, - "visibleForAllParticipants": "Videbla al ĉiuj partoprenantoj", - "@visibleForAllParticipants": { - "type": "text", - "placeholders": {} - }, - "visibleForEveryone": "Videbla al ĉiuj", - "@visibleForEveryone": { - "type": "text", - "placeholders": {} - }, - "voiceMessage": "Voĉmesaĝo", - "@voiceMessage": { - "type": "text", - "placeholders": {} - }, - "waitingPartnerAcceptRequest": "Atendante konfirmon de peto de la kunulo…", - "@waitingPartnerAcceptRequest": { - "type": "text", - "placeholders": {} - }, - "waitingPartnerEmoji": "Atendante akcepton de la bildosignoj de la kunulo…", - "@waitingPartnerEmoji": { - "type": "text", - "placeholders": {} - }, - "waitingPartnerNumbers": "Atendante akcepton de la numeroj, de la kunulo…", - "@waitingPartnerNumbers": { - "type": "text", - "placeholders": {} - }, - "wallpaper": "Fonbildo", - "@wallpaper": { - "type": "text", - "placeholders": {} - }, - "warning": "Averto!", - "@warning": { - "type": "text", - "placeholders": {} - }, - "weSentYouAnEmail": "Ni sendis retleteron al vi", - "@weSentYouAnEmail": { - "type": "text", - "placeholders": {} - }, - "whoCanPerformWhichAction": "Kiu povas kion", - "@whoCanPerformWhichAction": { - "type": "text", - "placeholders": {} - }, - "whoIsAllowedToJoinThisGroup": "Kiu rajtas aliĝi al ĉi tiu grupo", - "@whoIsAllowedToJoinThisGroup": { - "type": "text", - "placeholders": {} - }, - "whyDoYouWantToReportThis": "Kial vi volas tion ĉi raporti?", - "@whyDoYouWantToReportThis": { - "type": "text", - "placeholders": {} - }, - "wipeChatBackup": "Ĉu forviŝi la savkopion de via babilo por krei novan sekurecan ŝlosilon?", - "@wipeChatBackup": { - "type": "text", - "placeholders": {} - }, - "withTheseAddressesRecoveryDescription": "Per tiuj ĉi adresoj vi povas rehavi vian pasvorton.", - "@withTheseAddressesRecoveryDescription": { - "type": "text", - "placeholders": {} - }, - "writeAMessage": "Skribi mesaĝon…", - "@writeAMessage": { - "type": "text", - "placeholders": {} - }, - "yes": "Jes", - "@yes": { - "type": "text", - "placeholders": {} - }, - "you": "Vi", - "@you": { - "type": "text", - "placeholders": {} - }, - "youAreNoLongerParticipatingInThisChat": "Vi ne plu partoprenas ĉi tiun babilon", - "@youAreNoLongerParticipatingInThisChat": { - "type": "text", - "placeholders": {} - }, - "youHaveBeenBannedFromThisChat": "Vi estas forbarita de ĉi tiu babilo", - "@youHaveBeenBannedFromThisChat": { - "type": "text", - "placeholders": {} - }, - "yourPublicKey": "Via publika ŝlosilo", - "@yourPublicKey": { - "type": "text", - "placeholders": {} - }, - "sendAsText": "Sendi kiel tekston", - "@sendAsText": { - "type": "text" - }, - "noMatrixServer": "{server1} ne estas matriksa servilo, eble provu anstataŭe servilon {server2}?", - "@noMatrixServer": { - "type": "text", - "placeholders": { - "server1": {}, - "server2": {} - } - }, - "commandHint_send": "Sendi tekston", - "@commandHint_send": { - "type": "text", - "description": "Usage hint for the command /send" - }, - "chatHasBeenAddedToThisSpace": "Babilo aldoniĝis al ĉi tiu aro", - "@chatHasBeenAddedToThisSpace": {}, - "autoplayImages": "Memage ludi movbildajn glumarkojn kaj mienetojn", - "@autoplayImages": { - "type": "text", - "placeholder": {} - }, - "addToSpace": "Aldoni al aro", - "@addToSpace": {}, - "homeserver": "Hejmservilo", - "@homeserver": {}, - "sendOnEnter": "Sendi per eniga klavo", - "@sendOnEnter": {}, - "@hugContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@jumpToLastReadMessage": {}, - "@allRooms": { - "type": "text", - "placeholders": {} - }, - "@commandHint_cuddle": {}, - "@widgetVideo": {}, - "@dismiss": {}, - "@reportErrorDescription": {}, - "@addAccount": {}, - "@unsupportedAndroidVersion": {}, - "@widgetJitsi": {}, - "@messageType": {}, - "@indexedDbErrorLong": {}, - "@oneClientLoggedOut": {}, - "@startFirstChat": {}, - "@callingAccount": {}, - "@setColorTheme": {}, - "@nextAccount": {}, - "@commandHint_create": { - "type": "text", - "description": "Usage hint for the command /create" - }, - "@allSpaces": {}, - "@supposedMxid": { - "type": "text", - "placeholders": { - "mxid": {} - } - }, - "@user": {}, - "@youAcceptedTheInvitation": {}, - "@youInvitedBy": { - "placeholders": { - "user": {} - } - }, - "@banUserDescription": {}, - "@widgetEtherpad": {}, - "@removeDevicesDescription": {}, - "@separateChatTypes": { - "type": "text", - "placeholders": {} - }, - "@tryAgain": {}, - "@youKickedAndBanned": { - "placeholders": { - "user": {} - } - }, - "@unbanUserDescription": {}, - "@youRejectedTheInvitation": {}, - "@otherCallingPermissions": {}, - "@messagesStyle": {}, - "@link": {}, - "@widgetUrlError": {}, - "@emailOrUsername": {}, - "@newSpaceDescription": {}, - "@chatDescription": {}, - "@callingAccountDetails": {}, - "@enterSpace": {}, - "@encryptThisChat": {}, - "@previousAccount": {}, - "@reopenChat": {}, - "@pleaseEnterRecoveryKey": {}, - "@widgetNameError": {}, - "@addToBundle": {}, - "@addWidget": {}, - "@countFiles": { - "placeholders": { - "count": {} - } - }, - "@noKeyForThisMessage": {}, - "@commandHint_markasgroup": {}, - "@hydrateTor": {}, - "@pushNotificationsNotAvailable": {}, - "@storeInAppleKeyChain": {}, - "@hydrate": {}, - "@invalidServerName": {}, - "@chatPermissions": {}, - "@sender": {}, - "@storeInAndroidKeystore": {}, - "@signInWithPassword": {}, - "@makeAdminDescription": {}, - "@commandHint_clearcache": { - "type": "text", - "description": "Usage hint for the command /clearcache" - }, - "@saveKeyManuallyDescription": {}, - "@editBundlesForAccount": {}, - "@whyIsThisMessageEncrypted": {}, - "@setChatDescription": {}, - "@importFromZipFile": {}, - "@dehydrateWarning": {}, - "@noOtherDevicesFound": {}, - "@yourChatBackupHasBeenSetUp": {}, - "@redactedBy": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@videoCallsBetaWarning": {}, - "@signInWith": { - "type": "text", - "placeholders": { - "provider": {} - } - }, - "@fileIsTooBigForServer": {}, - "@repeatPassword": {}, - "@callingPermissions": {}, - "@readUpToHere": {}, - "@start": {}, - "@unlockOldMessages": {}, - "@numChats": { - "type": "number", - "placeholders": { - "number": {} - } - }, - "@optionalRedactReason": {}, - "@dehydrate": {}, - "@locationPermissionDeniedNotice": { - "type": "text", - "placeholders": {} - }, - "@archiveRoomDescription": {}, - "@exportEmotePack": {}, - "@switchToAccount": { - "type": "number", - "placeholders": { - "number": {} - } - }, - "@locationDisabledNotice": { - "type": "text", - "placeholders": {} - }, - "@experimentalVideoCalls": {}, - "@pleaseEnterRecoveryKeyDescription": {}, - "@openInMaps": { - "type": "text", - "placeholders": {} - }, - "@inviteContactToGroupQuestion": {}, - "@redactedByBecause": { - "type": "text", - "placeholders": { - "username": {}, - "reason": {} - } - }, - "@youHaveWithdrawnTheInvitationFor": { - "placeholders": { - "user": {} - } - }, - "@appearOnTopDetails": {}, - "@enterRoom": {}, - "@reportUser": {}, - "@confirmEventUnpin": {}, - "@youInvitedUser": { - "placeholders": { - "user": {} - } - }, - "@fileHasBeenSavedAt": { - "type": "text", - "placeholders": { - "path": {} - } - }, - "@redactMessageDescription": {}, - "@recoveryKey": {}, - "@commandHint_discardsession": { - "type": "text", - "description": "Usage hint for the command /discardsession" - }, - "@invalidInput": {}, - "@dehydrateTorLong": {}, - "@doNotShowAgain": {}, - "@report": {}, - "@unverified": {}, - "@serverRequiresEmail": {}, - "@hideUnimportantStateEvents": {}, - "@screenSharingTitle": {}, - "@widgetCustom": {}, - "@addToSpaceDescription": {}, - "@googlyEyesContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@youBannedUser": { - "placeholders": { - "user": {} - } - }, - "@addChatDescription": {}, - "@hasKnocked": { - "placeholders": { - "user": {} - } - }, - "@publish": {}, - "@openLinkInBrowser": {}, - "@messageInfo": {}, - "@disableEncryptionWarning": {}, - "@directChat": {}, - "@wrongPinEntered": { - "type": "text", - "placeholders": { - "seconds": {} - } - }, - "@sendTypingNotifications": {}, - "@inviteGroupChat": {}, - "@appearOnTop": {}, - "@invitePrivateChat": {}, - "@foregroundServiceRunning": {}, - "@voiceCall": {}, - "@importEmojis": {}, - "@wasDirectChatDisplayName": { - "type": "text", - "placeholders": { - "oldDisplayName": {} - } - }, - "@noChatDescriptionYet": {}, - "@removeFromBundle": {}, - "@confirmMatrixId": {}, - "@learnMore": {}, - "@notAnImage": {}, - "@users": {}, - "@openGallery": {}, - "@chatDescriptionHasBeenChanged": {}, - "@newGroup": {}, - "@bundleName": {}, - "@dehydrateTor": {}, - "@removeFromSpace": {}, - "@roomUpgradeDescription": {}, - "@scanQrCode": {}, - "@pleaseEnterANumber": {}, - "@youKicked": { - "placeholders": { - "user": {} - } - }, - "@profileNotFound": {}, - "@jump": {}, - "@reactedWith": { - "type": "text", - "placeholders": { - "sender": {}, - "reaction": {} - } - }, - "@sorryThatsNotPossible": {}, - "@videoWithSize": { - "type": "text", - "placeholders": { - "size": {} - } - }, - "@shareInviteLink": {}, - "@commandHint_markasdm": {}, - "@recoveryKeyLost": {}, - "@cuddleContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@deviceKeys": {}, - "@emoteKeyboardNoRecents": { - "type": "text", - "placeholders": {} - }, - "@setTheme": {}, - "@youJoinedTheChat": {}, - "@openVideoCamera": { - "type": "text", - "placeholders": {} - }, - "@markAsRead": {}, - "@widgetName": {}, - "@errorAddingWidget": {}, - "@commandHint_dm": { - "type": "text", - "description": "Usage hint for the command /dm" - }, - "@commandHint_hug": {}, - "@replace": {}, - "@youUnbannedUser": { - "placeholders": { - "user": {} - } - }, - "@newSpace": {}, - "@emojis": {}, - "@commandHint_googly": {}, - "@pleaseTryAgainLaterOrChooseDifferentServer": {}, - "@createGroup": {}, - "@hydrateTorLong": {}, - "@time": {}, - "@custom": {}, - "@noBackupWarning": {}, - "@storeInSecureStorageDescription": {}, - "@openChat": {}, - "@kickUserDescription": {}, - "@importNow": {}, - "@pinMessage": {}, - "@invite": {}, - "@enableMultiAccounts": {}, - "@indexedDbErrorTitle": {}, - "@unsupportedAndroidVersionLong": {}, - "@storeSecurlyOnThisDevice": {}, - "@screenSharingDetail": {}, - "@placeCall": {} -} \ No newline at end of file + "@@last_modified": "2021-08-14 12:41:10.107750", + "about": "Prio", + "@about": { + "type": "text", + "placeholders": {} + }, + "accept": "Akcepti", + "@accept": { + "type": "text", + "placeholders": {} + }, + "acceptedTheInvitation": "{username} akceptis la inviton", + "@acceptedTheInvitation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "account": "Konto", + "@account": { + "type": "text", + "placeholders": {} + }, + "activatedEndToEndEncryption": "{username} aktivigis tutvojan ĉifradon", + "@activatedEndToEndEncryption": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "addEmail": "Aldoni retpoŝtadreson", + "@addEmail": { + "type": "text", + "placeholders": {} + }, + "admin": "Administranto", + "@admin": { + "type": "text", + "placeholders": {} + }, + "alias": "kromnomo", + "@alias": { + "type": "text", + "placeholders": {} + }, + "all": "Ĉio", + "@all": { + "type": "text", + "placeholders": {} + }, + "allChats": "Ĉiuj babiloj", + "@allChats": { + "type": "text", + "placeholders": {} + }, + "answeredTheCall": "{senderName} respondis la vokon", + "@answeredTheCall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "anyoneCanJoin": "Ĉiu ajn povas aliĝi", + "@anyoneCanJoin": { + "type": "text", + "placeholders": {} + }, + "appLock": "Ŝlosado", + "@appLock": { + "type": "text", + "placeholders": {} + }, + "archive": "Arĥivo", + "@archive": { + "type": "text", + "placeholders": {} + }, + "areGuestsAllowedToJoin": "Ĉu gastoj rajtas aliĝi", + "@areGuestsAllowedToJoin": { + "type": "text", + "placeholders": {} + }, + "areYouSure": "Ĉu vi certas?", + "@areYouSure": { + "type": "text", + "placeholders": {} + }, + "areYouSureYouWantToLogout": "Ĉu vi certe volas adiaŭi?", + "@areYouSureYouWantToLogout": { + "type": "text", + "placeholders": {} + }, + "askSSSSSign": "Por ke vi povu kontroli (subskribi) la alian personon, bonvolu enigi pasfrazon de via sekreta deponejo aŭ vian rehavan ŝlosilon.", + "@askSSSSSign": { + "type": "text", + "placeholders": {} + }, + "askVerificationRequest": "Ĉu akcepti ĉi tiun kontrolpeton de {username}?", + "@askVerificationRequest": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "badServerLoginTypesException": "La hejmservilo subtenas la jenajn specojn de salutoj:\n{serverVersions}\nSed ĉi tiu aplikaĵo subtenas nur:\n{supportedVersions}", + "@badServerLoginTypesException": { + "type": "text", + "placeholders": { + "serverVersions": {}, + "supportedVersions": {} + } + }, + "badServerVersionsException": "La hejmservilo subtenas la jenajn version de la specifaĵo:\n{serverVersions}\nSed ĉi tiu aplikaĵo subtenas nur {supportedVersions}", + "@badServerVersionsException": { + "type": "text", + "placeholders": { + "serverVersions": {}, + "supportedVersions": {} + } + }, + "banFromChat": "Forbari de babilo", + "@banFromChat": { + "type": "text", + "placeholders": {} + }, + "banned": "Forbarita", + "@banned": { + "type": "text", + "placeholders": {} + }, + "bannedUser": "{username} forbaris uzanton {targetName}", + "@bannedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "blockDevice": "Bloki aparaton", + "@blockDevice": { + "type": "text", + "placeholders": {} + }, + "blocked": "Blokita", + "@blocked": { + "type": "text", + "placeholders": {} + }, + "botMessages": "Mesaĝoj de robotoj", + "@botMessages": { + "type": "text", + "placeholders": {} + }, + "cancel": "Nuligi", + "@cancel": { + "type": "text", + "placeholders": {} + }, + "cantOpenUri": "Ne povis malfermi URI {uri}", + "@cantOpenUri": { + "type": "text", + "placeholders": { + "uri": {} + } + }, + "changeDeviceName": "Ŝanĝi nomon de aparato", + "@changeDeviceName": { + "type": "text", + "placeholders": {} + }, + "changedTheChatAvatar": "{username} ŝanĝis bildon de la babilo", + "@changedTheChatAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheChatDescriptionTo": "{username} ŝanĝis priskribon de la babilo al: «{description}»", + "@changedTheChatDescriptionTo": { + "type": "text", + "placeholders": { + "username": {}, + "description": {} + } + }, + "changedTheChatNameTo": "{username} ŝanĝis nomon de la babilo al: «{chatname}»", + "@changedTheChatNameTo": { + "type": "text", + "placeholders": { + "username": {}, + "chatname": {} + } + }, + "changedTheChatPermissions": "{username} ŝanĝis permesojn pri la babilo", + "@changedTheChatPermissions": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheDisplaynameTo": "{username} ŝanĝis sian prezentan nomon al: {username}", + "@changedTheDisplaynameTo": { + "type": "text", + "placeholders": { + "username": {}, + "displayname": {} + } + }, + "changedTheGuestAccessRules": "{username} ŝanĝis regulojn pri aliro de gastoj", + "@changedTheGuestAccessRules": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheGuestAccessRulesTo": "{username} ŝanĝis regulojn pri aliro de gastoj al: {rules}", + "@changedTheGuestAccessRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "changedTheHistoryVisibility": "{username} ŝanĝis videblecon de la historio", + "@changedTheHistoryVisibility": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheHistoryVisibilityTo": "{username} ŝanĝis videblecon de la historio al: {rules}", + "@changedTheHistoryVisibilityTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "changedTheJoinRules": "{username} ŝanĝis regulojn pri aliĝado", + "@changedTheJoinRules": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheJoinRulesTo": "{username} ŝanĝis regulojn pri aliĝado al: {joinRules}", + "@changedTheJoinRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "joinRules": {} + } + }, + "changedTheProfileAvatar": "{username} ŝanĝis sian profilbildon", + "@changedTheProfileAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheRoomAliases": "{username} ŝanĝis la kromnomojn de la ĉambro", + "@changedTheRoomAliases": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheRoomInvitationLink": "{username} ŝanĝis la invitan ligilon", + "@changedTheRoomInvitationLink": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changePassword": "Ŝanĝi pasvorton", + "@changePassword": { + "type": "text", + "placeholders": {} + }, + "changeTheHomeserver": "Ŝanĝi hejmservilon", + "@changeTheHomeserver": { + "type": "text", + "placeholders": {} + }, + "changeTheme": "Ŝanĝu la haŭton", + "@changeTheme": { + "type": "text", + "placeholders": {} + }, + "changeTheNameOfTheGroup": "Ŝanĝi nomon de la grupo", + "@changeTheNameOfTheGroup": { + "type": "text", + "placeholders": {} + }, + "changeYourAvatar": "Ŝanĝi vian profilbildon", + "@changeYourAvatar": { + "type": "text", + "placeholders": {} + }, + "channelCorruptedDecryptError": "La ĉifrado estas difektita", + "@channelCorruptedDecryptError": { + "type": "text", + "placeholders": {} + }, + "chat": "Babilo", + "@chat": { + "type": "text", + "placeholders": {} + }, + "chatBackup": "Savkopiado de babilo", + "@chatBackup": { + "type": "text", + "placeholders": {} + }, + "chatBackupDescription": "Via savkopio de babilo estas sekurigita per sekureca ŝlosilo. Bonvolu certigi, ke vi ne perdos ĝin.", + "@chatBackupDescription": { + "type": "text", + "placeholders": {} + }, + "chatDetails": "Detaloj pri babilo", + "@chatDetails": { + "type": "text", + "placeholders": {} + }, + "chats": "Babiloj", + "@chats": { + "type": "text", + "placeholders": {} + }, + "chooseAStrongPassword": "Elektu fortan pasvorton", + "@chooseAStrongPassword": { + "type": "text", + "placeholders": {} + }, + "clearArchive": "Vakigi arĥivon", + "@clearArchive": {}, + "close": "Fermi", + "@close": { + "type": "text", + "placeholders": {} + }, + "commandHint_ban": "Forbari la donitan uzanton de ĉi tiu ĉambro", + "@commandHint_ban": { + "type": "text", + "description": "Usage hint for the command /ban" + }, + "commandHint_html": "Sendi tekston formatan je HTML", + "@commandHint_html": { + "type": "text", + "description": "Usage hint for the command /html" + }, + "commandHint_invite": "Inviti la donitan uzanton al ĉi tiu ĉambro", + "@commandHint_invite": { + "type": "text", + "description": "Usage hint for the command /invite" + }, + "commandHint_join": "Aliĝi al la donita ĉambro", + "@commandHint_join": { + "type": "text", + "description": "Usage hint for the command /join" + }, + "commandHint_kick": "Forigi la donitan uzanton de ĉi tiu ĉambro", + "@commandHint_kick": { + "type": "text", + "description": "Usage hint for the command /kick" + }, + "commandHint_leave": "Foriri de ĉi tiu ĉambro", + "@commandHint_leave": { + "type": "text", + "description": "Usage hint for the command /leave" + }, + "commandHint_me": "Priskribu vian agon", + "@commandHint_me": { + "type": "text", + "description": "Usage hint for the command /me" + }, + "commandHint_myroomavatar": "Agordi vian profilbildon por ĉi tiu ĉambro (laŭ mxc-uri)", + "@commandHint_myroomavatar": { + "type": "text", + "description": "Usage hint for the command /myroomavatar" + }, + "commandHint_myroomnick": "Agordi vian prezentan nomon en ĉi tiu ĉambro", + "@commandHint_myroomnick": { + "type": "text", + "description": "Usage hint for the command /myroomnick" + }, + "commandHint_op": "Agordi povnivelon de la donita uzanto (implicite: 50)", + "@commandHint_op": { + "type": "text", + "description": "Usage hint for the command /op" + }, + "commandHint_plain": "Sendi senformatan tekston", + "@commandHint_plain": { + "type": "text", + "description": "Usage hint for the command /plain" + }, + "commandHint_react": "Sendi respondon kiel reagon", + "@commandHint_react": { + "type": "text", + "description": "Usage hint for the command /react" + }, + "commandHint_unban": "Malforbari la donitan uzanton de ĉi tiu ĉambro", + "@commandHint_unban": { + "type": "text", + "description": "Usage hint for the command /unban" + }, + "commandInvalid": "Nevalida ordono", + "@commandInvalid": { + "type": "text" + }, + "commandMissing": "{command} ne estas ordono.", + "@commandMissing": { + "type": "text", + "placeholders": { + "command": {} + }, + "description": "State that {command} is not a valid /command." + }, + "compareEmojiMatch": "Komparu kaj certigu, ke la jenaj bildosignoj samas en ambaŭ aparatoj:", + "@compareEmojiMatch": { + "type": "text", + "placeholders": {} + }, + "compareNumbersMatch": "Komparu kaj certigu, ke la jenaj numeroj samas en ambaŭ aparatoj:", + "@compareNumbersMatch": { + "type": "text", + "placeholders": {} + }, + "configureChat": "Agordi babilon", + "@configureChat": { + "type": "text", + "placeholders": {} + }, + "confirm": "Konfirmi", + "@confirm": { + "type": "text", + "placeholders": {} + }, + "connect": "Konektiĝi", + "@connect": { + "type": "text", + "placeholders": {} + }, + "contactHasBeenInvitedToTheGroup": "Kontakto invitiĝis al la grupo", + "@contactHasBeenInvitedToTheGroup": { + "type": "text", + "placeholders": {} + }, + "containsDisplayName": "Enhavas prezentan nomon", + "@containsDisplayName": { + "type": "text", + "placeholders": {} + }, + "containsUserName": "Enhavas uzantonomon", + "@containsUserName": { + "type": "text", + "placeholders": {} + }, + "contentHasBeenReported": "La enhavo raportiĝis al la administrantoj de la servilo", + "@contentHasBeenReported": { + "type": "text", + "placeholders": {} + }, + "copiedToClipboard": "Kopiite al tondujo", + "@copiedToClipboard": { + "type": "text", + "placeholders": {} + }, + "copy": "Kopii", + "@copy": { + "type": "text", + "placeholders": {} + }, + "copyToClipboard": "Kopii al tondujo", + "@copyToClipboard": { + "type": "text", + "placeholders": {} + }, + "couldNotDecryptMessage": "Ne povis malĉifri mesaĝon: {error}", + "@couldNotDecryptMessage": { + "type": "text", + "placeholders": { + "error": {} + } + }, + "countParticipants": "{count} partoprenantoj", + "@countParticipants": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "create": "Krei", + "@create": { + "type": "text", + "placeholders": {} + }, + "createdTheChat": "{username} kreis la babilon", + "@createdTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "createNewSpace": "Nova aro", + "@createNewSpace": { + "type": "text", + "placeholders": {} + }, + "currentlyActive": "Nun aktiva", + "@currentlyActive": { + "type": "text", + "placeholders": {} + }, + "darkTheme": "Malhela", + "@darkTheme": { + "type": "text", + "placeholders": {} + }, + "dateAndTimeOfDay": "{date}, {timeOfDay}", + "@dateAndTimeOfDay": { + "type": "text", + "placeholders": { + "date": {}, + "timeOfDay": {} + } + }, + "dateWithoutYear": "{day}a de la {month}a", + "@dateWithoutYear": { + "type": "text", + "placeholders": { + "month": {}, + "day": {} + } + }, + "dateWithYear": "{day}a de la {month}a de {year}", + "@dateWithYear": { + "type": "text", + "placeholders": { + "year": {}, + "month": {}, + "day": {} + } + }, + "deactivateAccountWarning": "Ĉi tio malaktivigos vian konton de uzanto. Ne eblas tion malfari! Ĉu certe vi certas?", + "@deactivateAccountWarning": { + "type": "text", + "placeholders": {} + }, + "defaultPermissionLevel": "Norma nivelo de permesoj", + "@defaultPermissionLevel": { + "type": "text", + "placeholders": {} + }, + "delete": "Forigi", + "@delete": { + "type": "text", + "placeholders": {} + }, + "deleteAccount": "Forigi konton", + "@deleteAccount": { + "type": "text", + "placeholders": {} + }, + "deleteMessage": "Forigi mesaĝon", + "@deleteMessage": { + "type": "text", + "placeholders": {} + }, + "device": "Aparato", + "@device": { + "type": "text", + "placeholders": {} + }, + "deviceId": "Identigilo de aparato", + "@deviceId": { + "type": "text", + "placeholders": {} + }, + "devices": "Aparatoj", + "@devices": { + "type": "text", + "placeholders": {} + }, + "directChats": "Rektaj babiloj", + "@directChats": { + "type": "text", + "placeholders": {} + }, + "displaynameHasBeenChanged": "Prezenta nomo ŝanĝiĝis", + "@displaynameHasBeenChanged": { + "type": "text", + "placeholders": {} + }, + "downloadFile": "Elŝuti dosieron", + "@downloadFile": { + "type": "text", + "placeholders": {} + }, + "edit": "Redakti", + "@edit": { + "type": "text", + "placeholders": {} + }, + "editBlockedServers": "Redakti blokitajn servilojn", + "@editBlockedServers": { + "type": "text", + "placeholders": {} + }, + "editDisplayname": "Redakti prezentan nomon", + "@editDisplayname": { + "type": "text", + "placeholders": {} + }, + "editRoomAliases": "Ŝanĝi kromnomojn de ĉambro", + "@editRoomAliases": { + "type": "text", + "placeholders": {} + }, + "editRoomAvatar": "Redakti bildon de ĉambro", + "@editRoomAvatar": { + "type": "text", + "placeholders": {} + }, + "emoteExists": "Mieneto jam ekzistas!", + "@emoteExists": { + "type": "text", + "placeholders": {} + }, + "emoteInvalid": "Nevalida mallongigo de mieneto!", + "@emoteInvalid": { + "type": "text", + "placeholders": {} + }, + "emotePacks": "Mienetaroj por la ĉambro", + "@emotePacks": { + "type": "text", + "placeholders": {} + }, + "emoteSettings": "Agordoj pri mienetoj", + "@emoteSettings": { + "type": "text", + "placeholders": {} + }, + "emoteShortcode": "Mallongigo de mieneto", + "@emoteShortcode": { + "type": "text", + "placeholders": {} + }, + "emoteWarnNeedToPick": "Vi devas elekti mallongigon de mieneto kaj bildon!", + "@emoteWarnNeedToPick": { + "type": "text", + "placeholders": {} + }, + "emptyChat": "Malplena babilo", + "@emptyChat": { + "type": "text", + "placeholders": {} + }, + "enableEmotesGlobally": "Ŝalti mienetaron ĉie", + "@enableEmotesGlobally": { + "type": "text", + "placeholders": {} + }, + "enableEncryption": "Ŝalti ĉifradon", + "@enableEncryption": { + "type": "text", + "placeholders": {} + }, + "enableEncryptionWarning": "Vi ne povos malŝalti la ĉifradon. Ĉu vi certas?", + "@enableEncryptionWarning": { + "type": "text", + "placeholders": {} + }, + "encrypted": "Ĉifrite", + "@encrypted": { + "type": "text", + "placeholders": {} + }, + "encryption": "Ĉifrado", + "@encryption": { + "type": "text", + "placeholders": {} + }, + "encryptionNotEnabled": "Ĉifrado ne estas ŝaltita", + "@encryptionNotEnabled": { + "type": "text", + "placeholders": {} + }, + "endedTheCall": "{senderName} finis la vokon", + "@endedTheCall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "enterAnEmailAddress": "Enigu retpoŝtadreson", + "@enterAnEmailAddress": { + "type": "text", + "placeholders": {} + }, + "enterYourHomeserver": "Enigu vian hejmservilon", + "@enterYourHomeserver": { + "type": "text", + "placeholders": {} + }, + "errorObtainingLocation": "Eraris akirado de loko: {error}", + "@errorObtainingLocation": { + "type": "text", + "placeholders": { + "error": {} + } + }, + "everythingReady": "Ĉio pretas!", + "@everythingReady": { + "type": "text", + "placeholders": {} + }, + "extremeOffensive": "Tre ofenda", + "@extremeOffensive": { + "type": "text", + "placeholders": {} + }, + "fileName": "Dosiernomo", + "@fileName": { + "type": "text", + "placeholders": {} + }, + "fluffychat": "FluffyChat", + "@fluffychat": { + "type": "text", + "placeholders": {} + }, + "fontSize": "Grandeco de tiparo", + "@fontSize": { + "type": "text", + "placeholders": {} + }, + "forward": "Plusendi", + "@forward": { + "type": "text", + "placeholders": {} + }, + "fromJoining": "Ekde aliĝo", + "@fromJoining": { + "type": "text", + "placeholders": {} + }, + "fromTheInvitation": "Ekde la invito", + "@fromTheInvitation": { + "type": "text", + "placeholders": {} + }, + "goToTheNewRoom": "Iri al la nova ĉambro", + "@goToTheNewRoom": { + "type": "text", + "placeholders": {} + }, + "group": "Grupo", + "@group": { + "type": "text", + "placeholders": {} + }, + "groupIsPublic": "Grupo estas publika", + "@groupIsPublic": { + "type": "text", + "placeholders": {} + }, + "groups": "Grupoj", + "@groups": { + "type": "text", + "placeholders": {} + }, + "groupWith": "Grupo kun {displayname}", + "@groupWith": { + "type": "text", + "placeholders": { + "displayname": {} + } + }, + "guestsAreForbidden": "Gastoj estas malpermesitaj", + "@guestsAreForbidden": { + "type": "text", + "placeholders": {} + }, + "guestsCanJoin": "Gastoj povas aliĝi", + "@guestsCanJoin": { + "type": "text", + "placeholders": {} + }, + "hasWithdrawnTheInvitationFor": "{username} nuligis la inviton por {targetName}", + "@hasWithdrawnTheInvitationFor": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "help": "Helpo", + "@help": { + "type": "text", + "placeholders": {} + }, + "hideRedactedEvents": "Kaŝi obskurigitajn eventojn", + "@hideRedactedEvents": { + "type": "text", + "placeholders": {} + }, + "hideUnknownEvents": "Kaŝi nekonatajn eventojn", + "@hideUnknownEvents": { + "type": "text", + "placeholders": {} + }, + "howOffensiveIsThisContent": "Kiel ofenda estas ĉi tiu enhavo?", + "@howOffensiveIsThisContent": { + "type": "text", + "placeholders": {} + }, + "id": "Identigilo", + "@id": { + "type": "text", + "placeholders": {} + }, + "identity": "Identeco", + "@identity": { + "type": "text", + "placeholders": {} + }, + "ignore": "Malatenti", + "@ignore": { + "type": "text", + "placeholders": {} + }, + "ignoredUsers": "Malatentitaj uzantoj", + "@ignoredUsers": { + "type": "text", + "placeholders": {} + }, + "iHaveClickedOnLink": "Mi klakis la ligilon", + "@iHaveClickedOnLink": { + "type": "text", + "placeholders": {} + }, + "incorrectPassphraseOrKey": "Neĝusta pasfrazo aŭ rehava ŝlosilo", + "@incorrectPassphraseOrKey": { + "type": "text", + "placeholders": {} + }, + "inoffensive": "Neofenda", + "@inoffensive": { + "type": "text", + "placeholders": {} + }, + "inviteContact": "Inviti kontakton", + "@inviteContact": { + "type": "text", + "placeholders": {} + }, + "inviteContactToGroup": "Inviti kontakton al {groupName}", + "@inviteContactToGroup": { + "type": "text", + "placeholders": { + "groupName": {} + } + }, + "invited": "Invitita", + "@invited": { + "type": "text", + "placeholders": {} + }, + "invitedUser": "{username} invitis uzanton {targetName}", + "@invitedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "invitedUsersOnly": "Nur invititoj", + "@invitedUsersOnly": { + "type": "text", + "placeholders": {} + }, + "inviteForMe": "Invito por mi", + "@inviteForMe": { + "type": "text", + "placeholders": {} + }, + "inviteText": "{username} invitis vin al FluffyChat. \n1. Instalu la aplikaĵon FluffyChat: https://fluffychat.im \n2. Registriĝu aŭ salutu \n3. Malfermu la invitan ligilon: {link}", + "@inviteText": { + "type": "text", + "placeholders": { + "username": {}, + "link": {} + } + }, + "isTyping": "tajpas…", + "@isTyping": { + "type": "text", + "placeholders": {} + }, + "joinedTheChat": "{username} aliĝis al la babilo", + "@joinedTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "joinRoom": "Aliĝi al ĉambro", + "@joinRoom": { + "type": "text", + "placeholders": {} + }, + "kicked": "{username} forpelis uzanton {targetName}", + "@kicked": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "kickedAndBanned": "{username} forpelis kaj forbaris uzanton {targetName}", + "@kickedAndBanned": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "kickFromChat": "Forpeli de babilo", + "@kickFromChat": { + "type": "text", + "placeholders": {} + }, + "lastActiveAgo": "Lastafoje aktiva: {localizedTimeShort}", + "@lastActiveAgo": { + "type": "text", + "placeholders": { + "localizedTimeShort": {} + } + }, + "leave": "Foriri", + "@leave": { + "type": "text", + "placeholders": {} + }, + "leftTheChat": "Foriris de la ĉambro", + "@leftTheChat": { + "type": "text", + "placeholders": {} + }, + "license": "Permesilo", + "@license": { + "type": "text", + "placeholders": {} + }, + "lightTheme": "Hela", + "@lightTheme": { + "type": "text", + "placeholders": {} + }, + "loadCountMoreParticipants": "Enlegi {count} pliajn partoprenantojn", + "@loadCountMoreParticipants": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "loadingPleaseWait": "Enlegante… bonvolu atendi.", + "@loadingPleaseWait": { + "type": "text", + "placeholders": {} + }, + "loadMore": "Enlegi pli…", + "@loadMore": { + "type": "text", + "placeholders": {} + }, + "login": "Saluti", + "@login": { + "type": "text", + "placeholders": {} + }, + "logInTo": "Saluti servilon {homeserver}", + "@logInTo": { + "type": "text", + "placeholders": { + "homeserver": {} + } + }, + "logout": "Adiaŭi", + "@logout": { + "type": "text", + "placeholders": {} + }, + "memberChanges": "Ŝanĝoj de anoj", + "@memberChanges": { + "type": "text", + "placeholders": {} + }, + "mention": "Mencii", + "@mention": { + "type": "text", + "placeholders": {} + }, + "messages": "Mesaĝoj", + "@messages": { + "type": "text", + "placeholders": {} + }, + "moderator": "Reguligisto", + "@moderator": { + "type": "text", + "placeholders": {} + }, + "muteChat": "Silentigi babilon", + "@muteChat": { + "type": "text", + "placeholders": {} + }, + "needPantalaimonWarning": "Bonvolu scii, ke vi ankoraŭ bezonas la programon Pantalaimon por uzi tutvojan ĉifradon.", + "@needPantalaimonWarning": { + "type": "text", + "placeholders": {} + }, + "newChat": "Nova babilo", + "@newChat": { + "type": "text", + "placeholders": {} + }, + "newMessageInFluffyChat": "Nova mesaĝo en FluffyChat", + "@newMessageInFluffyChat": { + "type": "text", + "placeholders": {} + }, + "newVerificationRequest": "Nova kontrolpeto!", + "@newVerificationRequest": { + "type": "text", + "placeholders": {} + }, + "next": "Sekva", + "@next": { + "type": "text", + "placeholders": {} + }, + "no": "Ne", + "@no": { + "type": "text", + "placeholders": {} + }, + "noConnectionToTheServer": "Neniu konekto al la servilo", + "@noConnectionToTheServer": { + "type": "text", + "placeholders": {} + }, + "noEmotesFound": "Neniuj mienetoj troviĝis. 😕", + "@noEmotesFound": { + "type": "text", + "placeholders": {} + }, + "noEncryptionForPublicRooms": "Vi nur povas aktivigi ĉifradon kiam la ĉambro ne plu estas publike alirebla.", + "@noEncryptionForPublicRooms": { + "type": "text", + "placeholders": {} + }, + "noGoogleServicesWarning": "Ŝajnas, ke via telefono ne havas servojn de Google. Tio estas bona decido por via privateco! Por ricevadi pasivajn sciigojn en FluffyChat, ni rekomendas, ke vi uzu la https://microg.org/ aŭ https://unifiedpush.org/.", + "@noGoogleServicesWarning": { + "type": "text", + "placeholders": {} + }, + "none": "Neniu", + "@none": { + "type": "text", + "placeholders": {} + }, + "noPasswordRecoveryDescription": "Vi ankoraŭ ne aldonis manieron rehavi vian pasvorton.", + "@noPasswordRecoveryDescription": { + "type": "text", + "placeholders": {} + }, + "noPermission": "Neniu permeso", + "@noPermission": { + "type": "text", + "placeholders": {} + }, + "noRoomsFound": "Neniuj ĉambroj troviĝis…", + "@noRoomsFound": { + "type": "text", + "placeholders": {} + }, + "notifications": "Sciigoj", + "@notifications": { + "type": "text", + "placeholders": {} + }, + "notificationsEnabledForThisAccount": "Sciigoj ŝaltiĝis por ĉi tiu konto", + "@notificationsEnabledForThisAccount": { + "type": "text", + "placeholders": {} + }, + "numUsersTyping": "{count} uzantoj tajpas…", + "@numUsersTyping": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "obtainingLocation": "Akirante lokon…", + "@obtainingLocation": { + "type": "text", + "placeholders": {} + }, + "offensive": "Ofenda", + "@offensive": { + "type": "text", + "placeholders": {} + }, + "offline": "Eksterrete", + "@offline": { + "type": "text", + "placeholders": {} + }, + "ok": "bone", + "@ok": { + "type": "text", + "placeholders": {} + }, + "online": "Enrete", + "@online": { + "type": "text", + "placeholders": {} + }, + "onlineKeyBackupEnabled": "Enreta savkopiado de ŝlosiloj estas ŝaltita", + "@onlineKeyBackupEnabled": { + "type": "text", + "placeholders": {} + }, + "oopsPushError": "Oj! Bedaŭrinde eraris la agordado de pasivaj sciigoj.", + "@oopsPushError": { + "type": "text", + "placeholders": {} + }, + "oopsSomethingWentWrong": "Oj! Io misokazis…", + "@oopsSomethingWentWrong": { + "type": "text", + "placeholders": {} + }, + "openAppToReadMessages": "Malfermu la aplikaĵon por legi mesaĝojn", + "@openAppToReadMessages": { + "type": "text", + "placeholders": {} + }, + "openCamera": "Malfermi fotilon", + "@openCamera": { + "type": "text", + "placeholders": {} + }, + "or": "Aŭ", + "@or": { + "type": "text", + "placeholders": {} + }, + "participant": "Partoprenanto", + "@participant": { + "type": "text", + "placeholders": {} + }, + "passphraseOrKey": "pasfrazo aŭ rehava ŝlosilo", + "@passphraseOrKey": { + "type": "text", + "placeholders": {} + }, + "password": "Pasvorto", + "@password": { + "type": "text", + "placeholders": {} + }, + "passwordForgotten": "Forgesita pasvorto", + "@passwordForgotten": { + "type": "text", + "placeholders": {} + }, + "passwordHasBeenChanged": "Pasvorto ŝanĝiĝis", + "@passwordHasBeenChanged": { + "type": "text", + "placeholders": {} + }, + "passwordRecovery": "Rehavo de pasvorto", + "@passwordRecovery": { + "type": "text", + "placeholders": {} + }, + "people": "Personoj", + "@people": { + "type": "text", + "placeholders": {} + }, + "pickImage": "Elekti bildon", + "@pickImage": { + "type": "text", + "placeholders": {} + }, + "pin": "Fiksi", + "@pin": { + "type": "text", + "placeholders": {} + }, + "play": "Ludi {fileName}", + "@play": { + "type": "text", + "placeholders": { + "fileName": {} + } + }, + "pleaseChoose": "Bonvolu elekti", + "@pleaseChoose": { + "type": "text", + "placeholders": {} + }, + "pleaseChooseAPasscode": "Bonvolu elekti paskodon", + "@pleaseChooseAPasscode": { + "type": "text", + "placeholders": {} + }, + "pleaseClickOnLink": "Bonvolu klaki la ligilon en la retletero kaj pluiĝi.", + "@pleaseClickOnLink": { + "type": "text", + "placeholders": {} + }, + "pleaseEnter4Digits": "Bonvolu enigi 4 ciferojn, aŭ nenion por malŝalti ŝlosadon de la aplikaĵo.", + "@pleaseEnter4Digits": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourPassword": "Bonvolu enigi vian pasvorton", + "@pleaseEnterYourPassword": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourPin": "Bonvolu enigi vian personan identigan numeron", + "@pleaseEnterYourPin": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourUsername": "Bonvolu enigi vian uzantonomon", + "@pleaseEnterYourUsername": { + "type": "text", + "placeholders": {} + }, + "pleaseFollowInstructionsOnWeb": "Bonvolu sekvi la instrukciojn de la retejo kaj tuŝetu al «Sekva».", + "@pleaseFollowInstructionsOnWeb": { + "type": "text", + "placeholders": {} + }, + "privacy": "Privateco", + "@privacy": { + "type": "text", + "placeholders": {} + }, + "publicRooms": "Publikaj ĉambroj", + "@publicRooms": { + "type": "text", + "placeholders": {} + }, + "pushRules": "Reguloj de pasivaj sciigoj", + "@pushRules": { + "type": "text", + "placeholders": {} + }, + "reason": "Kialo", + "@reason": { + "type": "text", + "placeholders": {} + }, + "recording": "Registrante", + "@recording": { + "type": "text", + "placeholders": {} + }, + "redactedAnEvent": "{username} obskurigis eventon", + "@redactedAnEvent": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "redactMessage": "Obskurigi mesaĝon", + "@redactMessage": { + "type": "text", + "placeholders": {} + }, + "register": "Registriĝi", + "@register": { + "type": "text", + "placeholders": {} + }, + "reject": "Rifuzi", + "@reject": { + "type": "text", + "placeholders": {} + }, + "rejectedTheInvitation": "{username} rifuzis la inviton", + "@rejectedTheInvitation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "rejoin": "Ree aliĝi", + "@rejoin": { + "type": "text", + "placeholders": {} + }, + "remove": "Forigi", + "@remove": { + "type": "text", + "placeholders": {} + }, + "removeAllOtherDevices": "Forigi ĉiujn aliajn aparatojn", + "@removeAllOtherDevices": { + "type": "text", + "placeholders": {} + }, + "removedBy": "Forigita de {username}", + "@removedBy": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "removeDevice": "Forigi aparaton", + "@removeDevice": { + "type": "text", + "placeholders": {} + }, + "unbanFromChat": "Malforbari", + "@unbanFromChat": { + "type": "text", + "placeholders": {} + }, + "removeYourAvatar": "Forigi vian profilbildon", + "@removeYourAvatar": { + "type": "text", + "placeholders": {} + }, + "renderRichContent": "Bildigi riĉforman enhavon de mesaĝoj", + "@renderRichContent": { + "type": "text", + "placeholders": {} + }, + "replaceRoomWithNewerVersion": "Anstataŭigi ĉambron per nova versio", + "@replaceRoomWithNewerVersion": { + "type": "text", + "placeholders": {} + }, + "reply": "Respondi", + "@reply": { + "type": "text", + "placeholders": {} + }, + "reportMessage": "Raporti mesaĝon", + "@reportMessage": { + "type": "text", + "placeholders": {} + }, + "requestPermission": "Peti permeson", + "@requestPermission": { + "type": "text", + "placeholders": {} + }, + "roomHasBeenUpgraded": "Ĉambro gradaltiĝis", + "@roomHasBeenUpgraded": { + "type": "text", + "placeholders": {} + }, + "roomVersion": "Versio de ĉambro", + "@roomVersion": { + "type": "text", + "placeholders": {} + }, + "saveFile": "Konservi dosieron", + "@saveFile": { + "type": "text", + "placeholders": {} + }, + "search": "Serĉi", + "@search": { + "type": "text", + "placeholders": {} + }, + "security": "Sekureco", + "@security": { + "type": "text", + "placeholders": {} + }, + "seenByUser": "Vidita de {username}", + "@seenByUser": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "send": "Sendi", + "@send": { + "type": "text", + "placeholders": {} + }, + "sendAMessage": "Sendi mesaĝon", + "@sendAMessage": { + "type": "text", + "placeholders": {} + }, + "sendAudio": "Sendi sondosieron", + "@sendAudio": { + "type": "text", + "placeholders": {} + }, + "sendFile": "Sendi dosieron", + "@sendFile": { + "type": "text", + "placeholders": {} + }, + "sendImage": "Sendi bildon", + "@sendImage": { + "type": "text", + "placeholders": {} + }, + "sendMessages": "Sendi mesaĝojn", + "@sendMessages": { + "type": "text", + "placeholders": {} + }, + "sendOriginal": "Sendi originalon", + "@sendOriginal": { + "type": "text", + "placeholders": {} + }, + "sendSticker": "Sendi glumarkon", + "@sendSticker": { + "type": "text", + "placeholders": {} + }, + "sendVideo": "Sendi filmon", + "@sendVideo": { + "type": "text", + "placeholders": {} + }, + "sentAFile": "{username} sendis dosieron", + "@sentAFile": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAnAudio": "{username} sendis sondosieron", + "@sentAnAudio": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAPicture": "{username} sendis bildon", + "@sentAPicture": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentASticker": "{username} sendis glumarkon", + "@sentASticker": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAVideo": "{username} sendis filmon", + "@sentAVideo": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentCallInformations": "{senderName} sendis informojn pri voko", + "@sentCallInformations": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "setAsCanonicalAlias": "Agordi kiel ĉefan kromnomon", + "@setAsCanonicalAlias": { + "type": "text", + "placeholders": {} + }, + "setCustomEmotes": "Agordi proprajn mienetojn", + "@setCustomEmotes": { + "type": "text", + "placeholders": {} + }, + "setInvitationLink": "Agordi invitan ligilon", + "@setInvitationLink": { + "type": "text", + "placeholders": {} + }, + "setPermissionsLevel": "Agordi nivelon de permesoj", + "@setPermissionsLevel": { + "type": "text", + "placeholders": {} + }, + "setStatus": "Agordi staton", + "@setStatus": { + "type": "text", + "placeholders": {} + }, + "settings": "Agordoj", + "@settings": { + "type": "text", + "placeholders": {} + }, + "share": "Konigi", + "@share": { + "type": "text", + "placeholders": {} + }, + "sharedTheLocation": "{username} konigis sian lokon", + "@sharedTheLocation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "shareLocation": "Konigi lokon", + "@shareLocation": { + "type": "text", + "placeholders": {} + }, + "showPassword": "Montri pasvorton", + "@showPassword": { + "type": "text", + "placeholders": {} + }, + "singlesignon": "Ununura saluto", + "@singlesignon": { + "type": "text", + "placeholders": {} + }, + "skip": "Preterpasi", + "@skip": { + "type": "text", + "placeholders": {} + }, + "sourceCode": "Fontkodo", + "@sourceCode": { + "type": "text", + "placeholders": {} + }, + "spaceIsPublic": "Aro estas publika", + "@spaceIsPublic": { + "type": "text", + "placeholders": {} + }, + "spaceName": "Nomo de aro", + "@spaceName": { + "type": "text", + "placeholders": {} + }, + "startedACall": "{senderName} komencis vokon", + "@startedACall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "status": "Stato", + "@status": { + "type": "text", + "placeholders": {} + }, + "statusExampleMessage": "Kiel vi fartas?", + "@statusExampleMessage": { + "type": "text", + "placeholders": {} + }, + "submit": "Sendi", + "@submit": { + "type": "text", + "placeholders": {} + }, + "synchronizingPleaseWait": "Spegulante… Bonvolu atendi.", + "@synchronizingPleaseWait": { + "type": "text", + "placeholders": {} + }, + "systemTheme": "Sistema", + "@systemTheme": { + "type": "text", + "placeholders": {} + }, + "theyDontMatch": "Ili ne akordas", + "@theyDontMatch": { + "type": "text", + "placeholders": {} + }, + "theyMatch": "Ili akordas", + "@theyMatch": { + "type": "text", + "placeholders": {} + }, + "title": "FluffyChat", + "@title": { + "description": "Title for the application", + "type": "text", + "placeholders": {} + }, + "toggleFavorite": "Baskuli elstarigon", + "@toggleFavorite": { + "type": "text", + "placeholders": {} + }, + "toggleMuted": "Basklui silentigon", + "@toggleMuted": { + "type": "text", + "placeholders": {} + }, + "toggleUnread": "Baskuli legitecon", + "@toggleUnread": { + "type": "text", + "placeholders": {} + }, + "tooManyRequestsWarning": "Tro multaj petoj. Bonvolu reprovi poste!", + "@tooManyRequestsWarning": { + "type": "text", + "placeholders": {} + }, + "transferFromAnotherDevice": "Transporti de alia aparato", + "@transferFromAnotherDevice": { + "type": "text", + "placeholders": {} + }, + "tryToSendAgain": "Reprovi sendi", + "@tryToSendAgain": { + "type": "text", + "placeholders": {} + }, + "unavailable": "Nedisponeble", + "@unavailable": { + "type": "text", + "placeholders": {} + }, + "unbannedUser": "{username} malforbaris uzanton {targetName}", + "@unbannedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "unblockDevice": "Malbloki aparaton", + "@unblockDevice": { + "type": "text", + "placeholders": {} + }, + "unknownDevice": "Nekonata aparato", + "@unknownDevice": { + "type": "text", + "placeholders": {} + }, + "unknownEncryptionAlgorithm": "Nekonata ĉifra algoritmo", + "@unknownEncryptionAlgorithm": { + "type": "text", + "placeholders": {} + }, + "unknownEvent": "Nekonata evento «{type}»", + "@unknownEvent": { + "type": "text", + "placeholders": { + "type": {} + } + }, + "unmuteChat": "Malsilentigi babilon", + "@unmuteChat": { + "type": "text", + "placeholders": {} + }, + "unpin": "Malfiksi", + "@unpin": { + "type": "text", + "placeholders": {} + }, + "unreadChats": "{unreadCount, plural, =1{1 nelegita babilo} other{{unreadCount} nelegitaj babiloj}}", + "@unreadChats": { + "type": "text", + "placeholders": { + "unreadCount": {} + } + }, + "userAndOthersAreTyping": "{username} kaj {count} aliaj tajpas…", + "@userAndOthersAreTyping": { + "type": "text", + "placeholders": { + "username": {}, + "count": {} + } + }, + "userAndUserAreTyping": "{username} kaj {username2} tajpas…", + "@userAndUserAreTyping": { + "type": "text", + "placeholders": { + "username": {}, + "username2": {} + } + }, + "userIsTyping": "{username} tajpas…", + "@userIsTyping": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "userLeftTheChat": "{username} foriris de la babilo", + "@userLeftTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "username": "Uzantonomo", + "@username": { + "type": "text", + "placeholders": {} + }, + "userSentUnknownEvent": "{username} sendis eventon de speco {type}", + "@userSentUnknownEvent": { + "type": "text", + "placeholders": { + "username": {}, + "type": {} + } + }, + "verified": "Kontrolita", + "@verified": { + "type": "text", + "placeholders": {} + }, + "verify": "Kontroli", + "@verify": { + "type": "text", + "placeholders": {} + }, + "verifyStart": "Komenci kontrolon", + "@verifyStart": { + "type": "text", + "placeholders": {} + }, + "verifySuccess": "Vi sukcese kontrolis!", + "@verifySuccess": { + "type": "text", + "placeholders": {} + }, + "verifyTitle": "Kontrolante alian konton", + "@verifyTitle": { + "type": "text", + "placeholders": {} + }, + "videoCall": "Vidvoko", + "@videoCall": { + "type": "text", + "placeholders": {} + }, + "visibilityOfTheChatHistory": "Videbleco de historio de la babilo", + "@visibilityOfTheChatHistory": { + "type": "text", + "placeholders": {} + }, + "visibleForAllParticipants": "Videbla al ĉiuj partoprenantoj", + "@visibleForAllParticipants": { + "type": "text", + "placeholders": {} + }, + "visibleForEveryone": "Videbla al ĉiuj", + "@visibleForEveryone": { + "type": "text", + "placeholders": {} + }, + "voiceMessage": "Voĉmesaĝo", + "@voiceMessage": { + "type": "text", + "placeholders": {} + }, + "waitingPartnerAcceptRequest": "Atendante konfirmon de peto de la kunulo…", + "@waitingPartnerAcceptRequest": { + "type": "text", + "placeholders": {} + }, + "waitingPartnerEmoji": "Atendante akcepton de la bildosignoj de la kunulo…", + "@waitingPartnerEmoji": { + "type": "text", + "placeholders": {} + }, + "waitingPartnerNumbers": "Atendante akcepton de la numeroj, de la kunulo…", + "@waitingPartnerNumbers": { + "type": "text", + "placeholders": {} + }, + "wallpaper": "Fonbildo", + "@wallpaper": { + "type": "text", + "placeholders": {} + }, + "warning": "Averto!", + "@warning": { + "type": "text", + "placeholders": {} + }, + "weSentYouAnEmail": "Ni sendis retleteron al vi", + "@weSentYouAnEmail": { + "type": "text", + "placeholders": {} + }, + "whoCanPerformWhichAction": "Kiu povas kion", + "@whoCanPerformWhichAction": { + "type": "text", + "placeholders": {} + }, + "whoIsAllowedToJoinThisGroup": "Kiu rajtas aliĝi al ĉi tiu grupo", + "@whoIsAllowedToJoinThisGroup": { + "type": "text", + "placeholders": {} + }, + "whyDoYouWantToReportThis": "Kial vi volas tion ĉi raporti?", + "@whyDoYouWantToReportThis": { + "type": "text", + "placeholders": {} + }, + "wipeChatBackup": "Ĉu forviŝi la savkopion de via babilo por krei novan sekurecan ŝlosilon?", + "@wipeChatBackup": { + "type": "text", + "placeholders": {} + }, + "withTheseAddressesRecoveryDescription": "Per tiuj ĉi adresoj vi povas rehavi vian pasvorton.", + "@withTheseAddressesRecoveryDescription": { + "type": "text", + "placeholders": {} + }, + "writeAMessage": "Skribi mesaĝon…", + "@writeAMessage": { + "type": "text", + "placeholders": {} + }, + "yes": "Jes", + "@yes": { + "type": "text", + "placeholders": {} + }, + "you": "Vi", + "@you": { + "type": "text", + "placeholders": {} + }, + "youAreNoLongerParticipatingInThisChat": "Vi ne plu partoprenas ĉi tiun babilon", + "@youAreNoLongerParticipatingInThisChat": { + "type": "text", + "placeholders": {} + }, + "youHaveBeenBannedFromThisChat": "Vi estas forbarita de ĉi tiu babilo", + "@youHaveBeenBannedFromThisChat": { + "type": "text", + "placeholders": {} + }, + "yourPublicKey": "Via publika ŝlosilo", + "@yourPublicKey": { + "type": "text", + "placeholders": {} + }, + "sendAsText": "Sendi kiel tekston", + "@sendAsText": { + "type": "text" + }, + "noMatrixServer": "{server1} ne estas matriksa servilo, eble provu anstataŭe servilon {server2}?", + "@noMatrixServer": { + "type": "text", + "placeholders": { + "server1": {}, + "server2": {} + } + }, + "commandHint_send": "Sendi tekston", + "@commandHint_send": { + "type": "text", + "description": "Usage hint for the command /send" + }, + "chatHasBeenAddedToThisSpace": "Babilo aldoniĝis al ĉi tiu aro", + "@chatHasBeenAddedToThisSpace": {}, + "autoplayImages": "Memage ludi movbildajn glumarkojn kaj mienetojn", + "@autoplayImages": { + "type": "text", + "placeholder": {} + }, + "addToSpace": "Aldoni al aro", + "@addToSpace": {}, + "homeserver": "Hejmservilo", + "@homeserver": {}, + "sendOnEnter": "Sendi per eniga klavo", + "@sendOnEnter": {} +} From e5dd2e246d76ade8a82593670a2fe920430414ab Mon Sep 17 00:00:00 2001 From: Anonymous Date: Thu, 25 Jul 2024 05:09:46 +0000 Subject: [PATCH 113/288] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 41.8% (273 of 652 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/nb_NO/ --- assets/l10n/intl_nb.arb | 533 +--------------------------------------- 1 file changed, 1 insertion(+), 532 deletions(-) diff --git a/assets/l10n/intl_nb.arb b/assets/l10n/intl_nb.arb index 660517c76f..3835e0b762 100644 --- a/assets/l10n/intl_nb.arb +++ b/assets/l10n/intl_nb.arb @@ -1648,537 +1648,6 @@ "type": "text", "placeholders": {} }, - "@showPassword": { - "type": "text", - "placeholders": {} - }, - "@hugContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@jumpToLastReadMessage": {}, - "@allRooms": { - "type": "text", - "placeholders": {} - }, - "@obtainingLocation": { - "type": "text", - "placeholders": {} - }, - "@commandHint_cuddle": {}, - "@chats": { - "type": "text", - "placeholders": {} - }, - "@widgetVideo": {}, - "@dismiss": {}, - "@noEncryptionForPublicRooms": { - "type": "text", - "placeholders": {} - }, - "@reportErrorDescription": {}, - "@addAccount": {}, - "@chatHasBeenAddedToThisSpace": {}, - "@removeYourAvatar": { - "type": "text", - "placeholders": {} - }, - "@unsupportedAndroidVersion": {}, - "@commandHint_html": { - "type": "text", - "description": "Usage hint for the command /html" - }, - "@widgetJitsi": {}, - "@messageType": {}, - "@indexedDbErrorLong": {}, - "@oneClientLoggedOut": {}, - "@startFirstChat": {}, - "@callingAccount": {}, - "@setColorTheme": {}, - "@nextAccount": {}, - "@commandHint_create": { - "type": "text", - "description": "Usage hint for the command /create" - }, - "@singlesignon": { - "type": "text", - "placeholders": {} - }, - "@allSpaces": {}, - "@supposedMxid": { - "type": "text", - "placeholders": { - "mxid": {} - } - }, - "@user": {}, - "@roomVersion": { - "type": "text", - "placeholders": {} - }, - "@youAcceptedTheInvitation": {}, - "@noMatrixServer": { - "type": "text", - "placeholders": { - "server1": {}, - "server2": {} - } - }, - "@youInvitedBy": { - "placeholders": { - "user": {} - } - }, - "@banUserDescription": {}, - "@widgetEtherpad": {}, - "@removeDevicesDescription": {}, - "@separateChatTypes": { - "type": "text", - "placeholders": {} - }, - "@tryAgain": {}, - "@youKickedAndBanned": { - "placeholders": { - "user": {} - } - }, - "@unbanUserDescription": {}, - "@saveFile": { - "type": "text", - "placeholders": {} - }, - "@youRejectedTheInvitation": {}, - "@otherCallingPermissions": {}, - "@messagesStyle": {}, - "@link": {}, - "@widgetUrlError": {}, - "@emailOrUsername": {}, - "@newSpaceDescription": {}, - "@chatDescription": {}, - "@callingAccountDetails": {}, - "@editRoomAliases": { - "type": "text", - "placeholders": {} - }, - "@enterSpace": {}, - "@encryptThisChat": {}, - "@previousAccount": {}, - "@reopenChat": {}, - "@pleaseEnterRecoveryKey": {}, - "@widgetNameError": {}, - "@addToBundle": {}, - "@spaceIsPublic": { - "type": "text", - "placeholders": {} - }, - "@addWidget": {}, - "@countFiles": { - "placeholders": { - "count": {} - } - }, - "@noKeyForThisMessage": {}, - "@shareLocation": { - "type": "text", - "placeholders": {} - }, - "@commandHint_markasgroup": {}, - "@errorObtainingLocation": { - "type": "text", - "placeholders": { - "error": {} - } - }, - "@hydrateTor": {}, - "@pushNotificationsNotAvailable": {}, - "@storeInAppleKeyChain": {}, - "@hydrate": {}, - "@invalidServerName": {}, - "@chatPermissions": {}, - "@wipeChatBackup": { - "type": "text", - "placeholders": {} - }, - "@sender": {}, - "@storeInAndroidKeystore": {}, - "@signInWithPassword": {}, - "@makeAdminDescription": {}, - "@synchronizingPleaseWait": { - "type": "text", - "placeholders": {} - }, - "@goToTheNewRoom": { - "type": "text", - "placeholders": {} - }, - "@commandHint_clearcache": { - "type": "text", - "description": "Usage hint for the command /clearcache" - }, - "@saveKeyManuallyDescription": {}, - "@editBundlesForAccount": {}, - "@whyIsThisMessageEncrypted": {}, - "@setChatDescription": {}, - "@spaceName": { - "type": "text", - "placeholders": {} - }, - "@importFromZipFile": {}, - "@or": { - "type": "text", - "placeholders": {} - }, - "@dehydrateWarning": {}, - "@noOtherDevicesFound": {}, - "@yourChatBackupHasBeenSetUp": {}, - "@redactedBy": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@videoCallsBetaWarning": {}, - "@signInWith": { - "type": "text", - "placeholders": { - "provider": {} - } - }, - "@fileIsTooBigForServer": {}, - "@homeserver": {}, - "@people": { - "type": "text", - "placeholders": {} - }, - "@verified": { - "type": "text", - "placeholders": {} - }, - "@callingPermissions": {}, - "@readUpToHere": {}, - "@start": {}, - "@register": { - "type": "text", - "placeholders": {} - }, - "@unlockOldMessages": {}, - "@numChats": { - "type": "number", - "placeholders": { - "number": {} - } - }, - "@optionalRedactReason": {}, - "@dehydrate": {}, - "@locationPermissionDeniedNotice": { - "type": "text", - "placeholders": {} - }, - "@sendAsText": { - "type": "text" - }, - "@archiveRoomDescription": {}, - "@exportEmotePack": {}, - "@sendSticker": { - "type": "text", - "placeholders": {} - }, - "@switchToAccount": { - "type": "number", - "placeholders": { - "number": {} - } - }, - "@commandInvalid": { - "type": "text" - }, - "@setAsCanonicalAlias": { - "type": "text", - "placeholders": {} - }, - "@locationDisabledNotice": { - "type": "text", - "placeholders": {} - }, - "@commandHint_plain": { - "type": "text", - "description": "Usage hint for the command /plain" - }, - "@experimentalVideoCalls": {}, - "@pleaseEnterRecoveryKeyDescription": {}, - "@openInMaps": { - "type": "text", - "placeholders": {} - }, - "@inviteContactToGroupQuestion": {}, - "@redactedByBecause": { - "type": "text", - "placeholders": { - "username": {}, - "reason": {} - } - }, - "@youHaveWithdrawnTheInvitationFor": { - "placeholders": { - "user": {} - } - }, - "@appearOnTopDetails": {}, - "@enterRoom": {}, - "@pleaseChooseAPasscode": { - "type": "text", - "placeholders": {} - }, - "@reportUser": {}, - "@commandHint_send": { - "type": "text", - "description": "Usage hint for the command /send" - }, - "@confirmEventUnpin": {}, - "@youInvitedUser": { - "placeholders": { - "user": {} - } - }, - "@fileHasBeenSavedAt": { - "type": "text", - "placeholders": { - "path": {} - } - }, - "@commandMissing": { - "type": "text", - "placeholders": { - "command": {} - }, - "description": "State that {command} is not a valid /command." - }, - "@redactMessageDescription": {}, - "@recoveryKey": {}, - "@redactMessage": { - "type": "text", - "placeholders": {} - }, - "@commandHint_discardsession": { - "type": "text", - "description": "Usage hint for the command /discardsession" - }, - "@invalidInput": {}, - "@dehydrateTorLong": {}, - "@commandHint_myroomnick": { - "type": "text", - "description": "Usage hint for the command /myroomnick" - }, - "@doNotShowAgain": {}, - "@report": {}, - "@unverified": {}, - "@serverRequiresEmail": {}, - "@hideUnimportantStateEvents": {}, - "@screenSharingTitle": {}, - "@widgetCustom": {}, - "@addToSpaceDescription": {}, - "@googlyEyesContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@youBannedUser": { - "placeholders": { - "user": {} - } - }, - "@addChatDescription": {}, - "@commandHint_leave": { - "type": "text", - "description": "Usage hint for the command /leave" - }, - "@commandHint_myroomavatar": { - "type": "text", - "description": "Usage hint for the command /myroomavatar" - }, - "@hasKnocked": { - "placeholders": { - "user": {} - } - }, - "@publish": {}, - "@openLinkInBrowser": {}, - "@clearArchive": {}, - "@commandHint_react": { - "type": "text", - "description": "Usage hint for the command /react" - }, - "@commandHint_me": { - "type": "text", - "description": "Usage hint for the command /me" - }, - "@messageInfo": {}, - "@disableEncryptionWarning": {}, - "@directChat": {}, - "@wrongPinEntered": { - "type": "text", - "placeholders": { - "seconds": {} - } - }, - "@sendTypingNotifications": {}, - "@inviteGroupChat": {}, - "@appearOnTop": {}, - "@invitePrivateChat": {}, - "@foregroundServiceRunning": {}, - "@voiceCall": {}, - "@commandHint_kick": { - "type": "text", - "description": "Usage hint for the command /kick" - }, - "@createNewSpace": { - "type": "text", - "placeholders": {} - }, - "@commandHint_unban": { - "type": "text", - "description": "Usage hint for the command /unban" - }, - "@commandHint_ban": { - "type": "text", - "description": "Usage hint for the command /ban" - }, - "@importEmojis": {}, - "@wasDirectChatDisplayName": { - "type": "text", - "placeholders": { - "oldDisplayName": {} - } - }, - "@noChatDescriptionYet": {}, - "@removeFromBundle": {}, - "@confirmMatrixId": {}, - "@learnMore": {}, "notAnImage": "Ikke en bildefil.", - "@notAnImage": {}, - "@users": {}, - "@openGallery": {}, - "@chatDescriptionHasBeenChanged": {}, - "@newGroup": {}, - "@bundleName": {}, - "@dehydrateTor": {}, - "@removeFromSpace": {}, - "@commandHint_op": { - "type": "text", - "description": "Usage hint for the command /op" - }, - "@commandHint_join": { - "type": "text", - "description": "Usage hint for the command /join" - }, - "@roomUpgradeDescription": {}, - "@commandHint_invite": { - "type": "text", - "description": "Usage hint for the command /invite" - }, - "@scanQrCode": {}, - "@pleaseEnterANumber": {}, - "@youKicked": { - "placeholders": { - "user": {} - } - }, - "@profileNotFound": {}, - "@jump": {}, - "@reactedWith": { - "type": "text", - "placeholders": { - "sender": {}, - "reaction": {} - } - }, - "@sorryThatsNotPossible": {}, - "@videoWithSize": { - "type": "text", - "placeholders": { - "size": {} - } - }, - "@shareInviteLink": {}, - "@commandHint_markasdm": {}, - "@recoveryKeyLost": {}, - "@cuddleContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@deviceKeys": {}, - "@emoteKeyboardNoRecents": { - "type": "text", - "placeholders": {} - }, - "@setTheme": {}, - "@youJoinedTheChat": {}, - "@openVideoCamera": { - "type": "text", - "placeholders": {} - }, - "@markAsRead": {}, - "@widgetName": {}, - "@errorAddingWidget": {}, - "@commandHint_dm": { - "type": "text", - "description": "Usage hint for the command /dm" - }, - "@commandHint_hug": {}, - "@replace": {}, - "@oopsPushError": { - "type": "text", - "placeholders": {} - }, - "@youUnbannedUser": { - "placeholders": { - "user": {} - } - }, - "@pleaseEnter4Digits": { - "type": "text", - "placeholders": {} - }, - "@newSpace": {}, - "@emojis": {}, - "@pleaseEnterYourPin": { - "type": "text", - "placeholders": {} - }, - "@pleaseChoose": { - "type": "text", - "placeholders": {} - }, - "@commandHint_googly": {}, - "@pleaseTryAgainLaterOrChooseDifferentServer": {}, - "@createGroup": {}, - "@hydrateTorLong": {}, - "@time": {}, - "@custom": {}, - "@noBackupWarning": {}, - "@storeInSecureStorageDescription": {}, - "@openChat": {}, - "@kickUserDescription": {}, - "@importNow": {}, - "@pinMessage": {}, - "@invite": {}, - "@enableMultiAccounts": {}, - "@indexedDbErrorTitle": {}, - "@unsupportedAndroidVersionLong": {}, - "@storeSecurlyOnThisDevice": {}, - "@screenSharingDetail": {}, - "@waitingPartnerAcceptRequest": { - "type": "text", - "placeholders": {} - }, - "@waitingPartnerEmoji": { - "type": "text", - "placeholders": {} - }, - "@placeCall": {} + "@notAnImage": {} } From a7c1a760c787c63f9876cc39766f4115b25fd9f0 Mon Sep 17 00:00:00 2001 From: Anonymous Date: Thu, 25 Jul 2024 05:09:48 +0000 Subject: [PATCH 114/288] Translated using Weblate (Romanian) Currently translated at 74.3% (485 of 652 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ro/ --- assets/l10n/intl_ro.arb | 4677 +++++++++++++++++++-------------------- 1 file changed, 2302 insertions(+), 2375 deletions(-) diff --git a/assets/l10n/intl_ro.arb b/assets/l10n/intl_ro.arb index 00b17e74c0..4715436e7f 100644 --- a/assets/l10n/intl_ro.arb +++ b/assets/l10n/intl_ro.arb @@ -1,2376 +1,2303 @@ { - "@@last_modified": "2021-08-14 12:41:09.918296", - "about": "Despre", - "@about": { - "type": "text", - "placeholders": {} - }, - "accept": "Accept", - "@accept": { - "type": "text", - "placeholders": {} - }, - "acceptedTheInvitation": "{username} a aceptat invitați", - "@acceptedTheInvitation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "account": "Cont", - "@account": { - "type": "text", - "placeholders": {} - }, - "activatedEndToEndEncryption": "{username} a activat criptarea end-to-end", - "@activatedEndToEndEncryption": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "admin": "Administrator", - "@admin": { - "type": "text", - "placeholders": {} - }, - "alias": "poreclă", - "@alias": { - "type": "text", - "placeholders": {} - }, - "answeredTheCall": "{senderName} a acceptat apelul", - "@answeredTheCall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "anyoneCanJoin": "Oricine se poate alătura", - "@anyoneCanJoin": { - "type": "text", - "placeholders": {} - }, - "archive": "Arhivă", - "@archive": { - "type": "text", - "placeholders": {} - }, - "areGuestsAllowedToJoin": "Vizitatorii \"guest\" se pot alătura", - "@areGuestsAllowedToJoin": { - "type": "text", - "placeholders": {} - }, - "areYouSure": "Ești sigur?", - "@areYouSure": { - "type": "text", - "placeholders": {} - }, - "askSSSSSign": "Pentru a putea conecta cealaltă persoană, te rog introdu parola sau cheia ta de recuperare.", - "@askSSSSSign": { - "type": "text", - "placeholders": {} - }, - "askVerificationRequest": "Accepți cererea de verificare de la {username}?", - "@askVerificationRequest": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "banFromChat": "Interzis din conversație", - "@banFromChat": { - "type": "text", - "placeholders": {} - }, - "banned": "Interzis", - "@banned": { - "type": "text", - "placeholders": {} - }, - "bannedUser": "{username} a interzis pe {targetName}", - "@bannedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "blockDevice": "Blochează dispozitiv", - "@blockDevice": { - "type": "text", - "placeholders": {} - }, - "cancel": "Anulează", - "@cancel": { - "type": "text", - "placeholders": {} - }, - "changeDeviceName": "Schimbă numele dispozitiv", - "@changeDeviceName": { - "type": "text", - "placeholders": {} - }, - "changedTheChatAvatar": "{username} a schimbat poza conversați", - "@changedTheChatAvatar": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheChatDescriptionTo": "{username} a schimbat descrierea grupului în '{description}'", - "@changedTheChatDescriptionTo": { - "type": "text", - "placeholders": { - "username": {}, - "description": {} - } - }, - "changedTheChatNameTo": "{username} a schimbat porecla în '{chatname}'", - "@changedTheChatNameTo": { - "type": "text", - "placeholders": { - "username": {}, - "chatname": {} - } - }, - "commandHint_unban": "Dezinterziceți utilizatorul ales din această cameră", - "@commandHint_unban": { - "type": "text", - "description": "Usage hint for the command /unban" - }, - "dateAndTimeOfDay": "{date}, {timeOfDay}", - "@dateAndTimeOfDay": { - "type": "text", - "placeholders": { - "date": {}, - "timeOfDay": {} - } - }, - "deviceId": "ID-ul Dispozitiv", - "@deviceId": { - "type": "text", - "placeholders": {} - }, - "devices": "Dispozitive", - "@devices": { - "type": "text", - "placeholders": {} - }, - "directChats": "Chaturi directe", - "@directChats": { - "type": "text", - "placeholders": {} - }, - "endedTheCall": "{senderName} a terminat apelul", - "@endedTheCall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "enterYourHomeserver": "Introduceți homeserverul vostru", - "@enterYourHomeserver": { - "type": "text", - "placeholders": {} - }, - "groupWith": "Grup cu {displayname}", - "@groupWith": { - "type": "text", - "placeholders": { - "displayname": {} - } - }, - "howOffensiveIsThisContent": "Cât de ofensiv este acest conținut?", - "@howOffensiveIsThisContent": { - "type": "text", - "placeholders": {} - }, - "kickFromChat": "Dați afară din chat", - "@kickFromChat": { - "type": "text", - "placeholders": {} - }, - "rejoin": "Reintrați", - "@rejoin": { - "type": "text", - "placeholders": {} - }, - "sentCallInformations": "{senderName} a trimis informație de apel", - "@sentCallInformations": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "showPassword": "Afișați parola", - "@showPassword": { - "type": "text", - "placeholders": {} - }, - "no": "Nu", - "@no": { - "type": "text", - "placeholders": {} - }, - "sendMessages": "Trimiteți mesaje", - "@sendMessages": { - "type": "text", - "placeholders": {} - }, - "submit": "Trimiteți", - "@submit": { - "type": "text", - "placeholders": {} - }, - "unreadChats": "{unreadCount, plural, =1{Un chat necitit} other{{unreadCount} chaturi necitite}}", - "@unreadChats": { - "type": "text", - "placeholders": { - "unreadCount": {} - } - }, - "verifySuccess": "A reușit verificarea!", - "@verifySuccess": { - "type": "text", - "placeholders": {} - }, - "voiceMessage": "Mesaj vocal", - "@voiceMessage": { - "type": "text", - "placeholders": {} - }, - "wallpaper": "Imagine de fundal", - "@wallpaper": { - "type": "text", - "placeholders": {} - }, - "reactedWith": "{sender} a reacționat cu {reaction}", - "@reactedWith": { - "type": "text", - "placeholders": { - "sender": {}, - "reaction": {} - } - }, - "changePassword": "Schimbați parola", - "@changePassword": { - "type": "text", - "placeholders": {} - }, - "next": "Următor", - "@next": { - "type": "text", - "placeholders": {} - }, - "noConnectionToTheServer": "Fără conexiune la server", - "@noConnectionToTheServer": { - "type": "text", - "placeholders": {} - }, - "noPasswordRecoveryDescription": "Nu ați adăugat încă nici un mod de recuperare pentru parola voastră.", - "@noPasswordRecoveryDescription": { - "type": "text", - "placeholders": {} - }, - "notifications": "Notificări", - "@notifications": { - "type": "text", - "placeholders": {} - }, - "openVideoCamera": "Deschideți camera pentru video", - "@openVideoCamera": { - "type": "text", - "placeholders": {} - }, - "openAppToReadMessages": "Deschideți aplicația să citiți mesajele", - "@openAppToReadMessages": { - "type": "text", - "placeholders": {} - }, - "openCamera": "Deschideți camera", - "@openCamera": { - "type": "text", - "placeholders": {} - }, - "removedBy": "Eliminat de {username}", - "@removedBy": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "removeDevice": "Eliminați dispozitivul", - "@removeDevice": { - "type": "text", - "placeholders": {} - }, - "share": "Partajați", - "@share": { - "type": "text", - "placeholders": {} - }, - "shareLocation": "Partajați locația", - "@shareLocation": { - "type": "text", - "placeholders": {} - }, - "skip": "Săriți peste", - "@skip": { - "type": "text", - "placeholders": {} - }, - "sourceCode": "Codul surs", - "@sourceCode": { - "type": "text", - "placeholders": {} - }, - "spaceIsPublic": "Spațiul este public", - "@spaceIsPublic": { - "type": "text", - "placeholders": {} - }, - "spaceName": "Numele spațiului", - "@spaceName": { - "type": "text", - "placeholders": {} - }, - "toggleFavorite": "Comutați favoritul", - "@toggleFavorite": { - "type": "text", - "placeholders": {} - }, - "unblockDevice": "Debloca dispozitiv", - "@unblockDevice": { - "type": "text", - "placeholders": {} - }, - "unknownDevice": "Dispozitiv necunoscut", - "@unknownDevice": { - "type": "text", - "placeholders": {} - }, - "verify": "Verificați", - "@verify": { - "type": "text", - "placeholders": {} - }, - "weSentYouAnEmail": "V-am trimis un email", - "@weSentYouAnEmail": { - "type": "text", - "placeholders": {} - }, - "youAreNoLongerParticipatingInThisChat": "Nu mai participați în acest chat", - "@youAreNoLongerParticipatingInThisChat": { - "type": "text", - "placeholders": {} - }, - "yourPublicKey": "Cheia voastră publică", - "@yourPublicKey": { - "type": "text", - "placeholders": {} - }, - "addToSpaceDescription": "Alegeți un spațiu în care să adăugați acest chat.", - "@addToSpaceDescription": {}, - "placeCall": "Faceți apel", - "@placeCall": {}, - "voiceCall": "Apel vocal", - "@voiceCall": {}, - "unsupportedAndroidVersion": "Versiune de Android nesuportat", - "@unsupportedAndroidVersion": {}, - "previousAccount": "Contul anterior", - "@previousAccount": {}, - "userIsTyping": "{username} tastează…", - "@userIsTyping": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "widgetCustom": "Personalizat", - "@widgetCustom": {}, - "screenSharingTitle": "partajarea de ecran", - "@screenSharingTitle": {}, - "newGroup": "Grup nou", - "@newGroup": {}, - "changedTheRoomInvitationLink": "{username} a schimbat linkul de invitație", - "@changedTheRoomInvitationLink": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "chat": "Chat", - "@chat": { - "type": "text", - "placeholders": {} - }, - "chats": "Chaturi", - "@chats": { - "type": "text", - "placeholders": {} - }, - "invited": "Invitat", - "@invited": { - "type": "text", - "placeholders": {} - }, - "login": "Conectați-vă", - "@login": { - "type": "text", - "placeholders": {} - }, - "online": "Online", - "@online": { - "type": "text", - "placeholders": {} - }, - "onlineKeyBackupEnabled": "Backup de cheie online este activat", - "@onlineKeyBackupEnabled": { - "type": "text", - "placeholders": {} - }, - "removeFromBundle": "Stergeți din acest pachet", - "@removeFromBundle": {}, - "enableMultiAccounts": "(BETA) Activați multiple conturi pe acest dispozitiv", - "@enableMultiAccounts": {}, - "participant": "Participant", - "@participant": { - "type": "text", - "placeholders": {} - }, - "openInMaps": "Deschideți pe hartă", - "@openInMaps": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourPin": "Vă rugăm să introduceți pinul vostru", - "@pleaseEnterYourPin": { - "type": "text", - "placeholders": {} - }, - "privacy": "Confidențialitate", - "@privacy": { - "type": "text", - "placeholders": {} - }, - "pushRules": "Regulile Push", - "@pushRules": { - "type": "text", - "placeholders": {} - }, - "recording": "Înregistrare", - "@recording": { - "type": "text", - "placeholders": {} - }, - "register": "Înregistrați-vă", - "@register": { - "type": "text", - "placeholders": {} - }, - "redactMessage": "Redactați mesaj", - "@redactMessage": { - "type": "text", - "placeholders": {} - }, - "roomVersion": "Versiunea camerei", - "@roomVersion": { - "type": "text", - "placeholders": {} - }, - "security": "Securitate", - "@security": { - "type": "text", - "placeholders": {} - }, - "sendFile": "Trimiteți fișier", - "@sendFile": { - "type": "text", - "placeholders": {} - }, - "setStatus": "Stabiliți status", - "@setStatus": { - "type": "text", - "placeholders": {} - }, - "settings": "Configurări", - "@settings": { - "type": "text", - "placeholders": {} - }, - "inviteForMe": "Invitați pentru mine", - "@inviteForMe": { - "type": "text", - "placeholders": {} - }, - "fluffychat": "FluffyChat", - "@fluffychat": { - "type": "text", - "placeholders": {} - }, - "hasWithdrawnTheInvitationFor": "{username} a retras invitația pentru {targetName}", - "@hasWithdrawnTheInvitationFor": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "help": "Ajutor", - "@help": { - "type": "text", - "placeholders": {} - }, - "unknownEncryptionAlgorithm": "Algoritm de criptare necunoscut", - "@unknownEncryptionAlgorithm": { - "type": "text", - "placeholders": {} - }, - "unmuteChat": "Dezamuțați chat", - "@unmuteChat": { - "type": "text", - "placeholders": {} - }, - "unpin": "Anulează fixarea", - "@unpin": { - "type": "text", - "placeholders": {} - }, - "unbannedUser": "{username} a ridicat interzicerea lui {targetName}", - "@unbannedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "openChat": "Deschideți Chat", - "@openChat": {}, - "emailOrUsername": "Email sau nume de utilizator", - "@emailOrUsername": {}, - "youBannedUser": "Ați interzis pe {user}", - "@youBannedUser": { - "placeholders": { - "user": {} - } - }, - "fileIsTooBigForServer": "Serverul reportează că fișierul este prea mare să fie trimis.", - "@fileIsTooBigForServer": {}, - "widgetName": "Nume", - "@widgetName": {}, - "sorryThatsNotPossible": "Scuze... acest nu este posibil", - "@sorryThatsNotPossible": {}, - "enableEncryptionWarning": "Activând criptare, nu mai puteți să o dezactivați în viitor. Sunteți sigur?", - "@enableEncryptionWarning": { - "type": "text", - "placeholders": {} - }, - "commandMissing": "{command} nu este o comandă.", - "@commandMissing": { - "type": "text", - "placeholders": { - "command": {} - }, - "description": "State that {command} is not a valid /command." - }, - "status": "Status", - "@status": { - "type": "text", - "placeholders": {} - }, - "connect": "Conectați", - "@connect": { - "type": "text", - "placeholders": {} - }, - "you": "Voi", - "@you": { - "type": "text", - "placeholders": {} - }, - "start": "Începeți", - "@start": {}, - "videoCallsBetaWarning": "Vă rugăm să luați notă că apeluri video sunt în beta. Se poate că nu funcționează normal sau de loc pe fie care platformă.", - "@videoCallsBetaWarning": {}, - "pinMessage": "Fixați în cameră", - "@pinMessage": {}, - "wasDirectChatDisplayName": "Chat gol (a fost {oldDisplayName})", - "@wasDirectChatDisplayName": { - "type": "text", - "placeholders": { - "oldDisplayName": {} - } - }, - "pleaseClickOnLink": "Vă rugăm să deschideți linkul din email și apoi să procedați.", - "@pleaseClickOnLink": { - "type": "text", - "placeholders": {} - }, - "reportUser": "Reportați utilizator", - "@reportUser": {}, - "encryptionNotEnabled": "Criptare nu e activată", - "@encryptionNotEnabled": { - "type": "text", - "placeholders": {} - }, - "publicRooms": "Camere Publice", - "@publicRooms": { - "type": "text", - "placeholders": {} - }, - "addToBundle": "Adăugați în pachet", - "@addToBundle": {}, - "theyDontMatch": "Nu sunt asemănători", - "@theyDontMatch": { - "type": "text", - "placeholders": {} - }, - "loadingPleaseWait": "Încărcând... Vă rugăm să așteptați.", - "@loadingPleaseWait": { - "type": "text", - "placeholders": {} - }, - "dateWithoutYear": "{month}-{day}", - "@dateWithoutYear": { - "type": "text", - "placeholders": { - "month": {}, - "day": {} - } - }, - "theyMatch": "Sunt asemănători", - "@theyMatch": { - "type": "text", - "placeholders": {} - }, - "title": "FluffyChat", - "@title": { - "description": "Title for the application", - "type": "text", - "placeholders": {} - }, - "toggleMuted": "Comutați amuțeștarea", - "@toggleMuted": { - "type": "text", - "placeholders": {} - }, - "scanQrCode": "Scanați cod QR", - "@scanQrCode": {}, - "addAccount": "Adăugați cont", - "@addAccount": {}, - "experimentalVideoCalls": "Apeluri video experimentale", - "@experimentalVideoCalls": {}, - "confirmEventUnpin": "Sunteți sigur că doriți să anulați permanent fixarea evenimentului?", - "@confirmEventUnpin": {}, - "emojis": "Emoji-uri", - "@emojis": {}, - "switchToAccount": "Schimbați la contul {number}", - "@switchToAccount": { - "type": "number", - "placeholders": { - "number": {} - } - }, - "nextAccount": "Contul următor", - "@nextAccount": {}, - "indexedDbErrorTitle": "Probleme cu modul privat", - "@indexedDbErrorTitle": {}, - "users": "Utilizatori", - "@users": {}, - "startFirstChat": "Începeți primul chatul vostru", - "@startFirstChat": {}, - "callingPermissions": "Permisiuni de apel", - "@callingPermissions": {}, - "callingAccount": "Cont de apel", - "@callingAccount": {}, - "foregroundServiceRunning": "Această notificare apare când serviciul de foreground rulează.", - "@foregroundServiceRunning": {}, - "callingAccountDetails": "Permite FluffyChat să folosească aplicația de apeluri nativă android.", - "@callingAccountDetails": {}, - "appearOnTop": "Apare deasupra", - "@appearOnTop": {}, - "appearOnTopDetails": "Permite aplicația să apare deasupra (nu este necesar dacă aveți FluffyChat stabilit ca cont de apeluri)", - "@appearOnTopDetails": {}, - "currentlyActive": "Activ acum", - "@currentlyActive": { - "type": "text", - "placeholders": {} - }, - "containsDisplayName": "Conține displayname", - "@containsDisplayName": { - "type": "text", - "placeholders": {} - }, - "isTyping": "tastează…", - "@isTyping": { - "type": "text", - "placeholders": {} - }, - "chatBackup": "Backup de chat", - "@chatBackup": { - "type": "text", - "placeholders": {} - }, - "repeatPassword": "Repetați parola", - "@repeatPassword": {}, - "changeTheme": "Schimbați tema aplicației", - "@changeTheme": { - "type": "text", - "placeholders": {} - }, - "chatHasBeenAddedToThisSpace": "Chatul a fost adăugat la acest spațiu", - "@chatHasBeenAddedToThisSpace": {}, - "clearArchive": "Ștergeți arhiva", - "@clearArchive": {}, - "commandHint_markasdm": "Marcați ca cameră de mesaje directe", - "@commandHint_markasdm": {}, - "commandHint_markasgroup": "Marcați ca grup", - "@commandHint_markasgroup": {}, - "commandHint_ban": "Interziceți acesul utilizatorului ales din această cameră", - "@commandHint_ban": { - "type": "text", - "description": "Usage hint for the command /ban" - }, - "commandHint_clearcache": "Ștergeți cache", - "@commandHint_clearcache": { - "type": "text", - "description": "Usage hint for the command /clearcache" - }, - "commandHint_create": "Creați un grup de chat gol\nFolosiți --no-encryption să dezactivați criptare", - "@commandHint_create": { - "type": "text", - "description": "Usage hint for the command /create" - }, - "commandHint_discardsession": "Renunțați sesiunea", - "@commandHint_discardsession": { - "type": "text", - "description": "Usage hint for the command /discardsession" - }, - "commandHint_kick": "Dați afară pe utilizatorul ales din această cameră", - "@commandHint_kick": { - "type": "text", - "description": "Usage hint for the command /kick" - }, - "commandHint_leave": "Renunțați la această cameră", - "@commandHint_leave": { - "type": "text", - "description": "Usage hint for the command /leave" - }, - "commandHint_myroomavatar": "Alegeți un avatar pentru această cameră (foloșește mxc-uri)", - "@commandHint_myroomavatar": { - "type": "text", - "description": "Usage hint for the command /myroomavatar" - }, - "commandHint_myroomnick": "Alegeți un displayname pentru această cameră", - "@commandHint_myroomnick": { - "type": "text", - "description": "Usage hint for the command /myroomnick" - }, - "commandHint_op": "Stabiliți nivelul de putere a utilizatorul ales (implicit: 50)", - "@commandHint_op": { - "type": "text", - "description": "Usage hint for the command /op" - }, - "commandHint_plain": "Trimiteți text simplu/neformatat", - "@commandHint_plain": { - "type": "text", - "description": "Usage hint for the command /plain" - }, - "commandHint_react": "Trimiteți răspuns ca reacție", - "@commandHint_react": { - "type": "text", - "description": "Usage hint for the command /react" - }, - "commandHint_send": "Trimiteți text", - "@commandHint_send": { - "type": "text", - "description": "Usage hint for the command /send" - }, - "commandInvalid": "Comandă nevalibilă", - "@commandInvalid": { - "type": "text" - }, - "compareEmojiMatch": "Vă rugăm să comparați emoji-urile", - "@compareEmojiMatch": { - "type": "text", - "placeholders": {} - }, - "compareNumbersMatch": "Vă rugăm să comparați numerele", - "@compareNumbersMatch": { - "type": "text", - "placeholders": {} - }, - "couldNotDecryptMessage": "Dezcriptarea mesajului a eșuat: {error}", - "@couldNotDecryptMessage": { - "type": "text", - "placeholders": { - "error": {} - } - }, - "create": "Creați", - "@create": { - "type": "text", - "placeholders": {} - }, - "createdTheChat": "💬{username} a creat chatul", - "@createdTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "createNewSpace": "Spațiu nou", - "@createNewSpace": { - "type": "text", - "placeholders": {} - }, - "dateWithYear": "{year}-{month}-{day}", - "@dateWithYear": { - "type": "text", - "placeholders": { - "year": {}, - "month": {}, - "day": {} - } - }, - "allRooms": "Toate chaturi de grup", - "@allRooms": { - "type": "text", - "placeholders": {} - }, - "forward": "Înainte", - "@forward": { - "type": "text", - "placeholders": {} - }, - "groups": "Grupuri", - "@groups": { - "type": "text", - "placeholders": {} - }, - "hideRedactedEvents": "Ascunde evenimente redactate", - "@hideRedactedEvents": { - "type": "text", - "placeholders": {} - }, - "hideUnknownEvents": "Ascunde evenimente necunoscute", - "@hideUnknownEvents": { - "type": "text", - "placeholders": {} - }, - "identity": "Identitate", - "@identity": { - "type": "text", - "placeholders": {} - }, - "ignore": "Ignorați", - "@ignore": { - "type": "text", - "placeholders": {} - }, - "ignoredUsers": "Utilizatori ignorați", - "@ignoredUsers": { - "type": "text", - "placeholders": {} - }, - "iHaveClickedOnLink": "Am făcut click pe link", - "@iHaveClickedOnLink": { - "type": "text", - "placeholders": {} - }, - "incorrectPassphraseOrKey": "Parolă sau cheie de recuperare incorectă", - "@incorrectPassphraseOrKey": { - "type": "text", - "placeholders": {} - }, - "inoffensive": "Inofensiv", - "@inoffensive": { - "type": "text", - "placeholders": {} - }, - "inviteContact": "Invitați contact", - "@inviteContact": { - "type": "text", - "placeholders": {} - }, - "inviteContactToGroup": "Invitați contact la {groupName}", - "@inviteContactToGroup": { - "type": "text", - "placeholders": { - "groupName": {} - } - }, - "inviteText": "{username} v-a invitat la FluffyChat.\n1. Instalați FluffyChat: https://fluffychat.im\n2. Înregistrați-vă sau conectați-vă\n3. Deschideți invitația: {link}", - "@inviteText": { - "type": "text", - "placeholders": { - "username": {}, - "link": {} - } - }, - "joinedTheChat": "👋{username} a intrat în chat", - "@joinedTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "kicked": "👞{username} a dat afară pe {targetName}", - "@kicked": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "lastActiveAgo": "Ultima dată activ: {localizedTimeShort}", - "@lastActiveAgo": { - "type": "text", - "placeholders": { - "localizedTimeShort": {} - } - }, - "leave": "Renunțați", - "@leave": { - "type": "text", - "placeholders": {} - }, - "leftTheChat": "A plecat din chat", - "@leftTheChat": { - "type": "text", - "placeholders": {} - }, - "license": "Permis", - "@license": { - "type": "text", - "placeholders": {} - }, - "lightTheme": "Luminat", - "@lightTheme": { - "type": "text", - "placeholders": {} - }, - "loadCountMoreParticipants": "Încărcați încă mai {count} participanți", - "@loadCountMoreParticipants": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "dehydrate": "Exportați sesiunea și ștergeți dispozitivul", - "@dehydrate": {}, - "dehydrateTor": "Utilizatori de TOR: Exportați sesiunea", - "@dehydrateTor": {}, - "dehydrateTorLong": "Pentru utilizatori de TOR, este recomandat să exportați sesiunea înainte de a închideți fereastra.", - "@dehydrateTorLong": {}, - "hydrateTor": "Utilizatori TOR: Importați sesiune exportată", - "@hydrateTor": {}, - "hydrateTorLong": "Ați exportat sesiunea vostră ultima dată pe TOR? Importați-o repede și continuați să conversați.", - "@hydrateTorLong": {}, - "hydrate": "Restaurați din fișier backup", - "@hydrate": {}, - "loadMore": "Încarcă mai multe…", - "@loadMore": { - "type": "text", - "placeholders": {} - }, - "logout": "Deconectați-vă", - "@logout": { - "type": "text", - "placeholders": {} - }, - "mention": "Menționați", - "@mention": { - "type": "text", - "placeholders": {} - }, - "messages": "Mesaje", - "@messages": { - "type": "text", - "placeholders": {} - }, - "moderator": "Moderator", - "@moderator": { - "type": "text", - "placeholders": {} - }, - "noEmotesFound": "Nu s-a găsit nici un emote. 😕", - "@noEmotesFound": { - "type": "text", - "placeholders": {} - }, - "noEncryptionForPublicRooms": "Criptare nu poate fi activată până când camera este accesibilă public.", - "@noEncryptionForPublicRooms": { - "type": "text", - "placeholders": {} - }, - "none": "Niciunul", - "@none": { - "type": "text", - "placeholders": {} - }, - "noPermission": "Fără permisie", - "@noPermission": { - "type": "text", - "placeholders": {} - }, - "noRoomsFound": "Nici o cameră nu s-a găsit…", - "@noRoomsFound": { - "type": "text", - "placeholders": {} - }, - "notificationsEnabledForThisAccount": "Notificări activate pentru acest cont", - "@notificationsEnabledForThisAccount": { - "type": "text", - "placeholders": {} - }, - "obtainingLocation": "Obținând locație…", - "@obtainingLocation": { - "type": "text", - "placeholders": {} - }, - "offline": "Offline", - "@offline": { - "type": "text", - "placeholders": {} - }, - "oopsSomethingWentWrong": "Ups, ceva a eșuat…", - "@oopsSomethingWentWrong": { - "type": "text", - "placeholders": {} - }, - "reject": "Respingeți", - "@reject": { - "type": "text", - "placeholders": {} - }, - "unbanFromChat": "Revoca interzicerea din chat", - "@unbanFromChat": { - "type": "text", - "placeholders": {} - }, - "roomHasBeenUpgraded": "Camera a fost actualizată", - "@roomHasBeenUpgraded": { - "type": "text", - "placeholders": {} - }, - "setCustomEmotes": "Stabiliți emoji-uri personalizate", - "@setCustomEmotes": { - "type": "text", - "placeholders": {} - }, - "setPermissionsLevel": "Stabiliți nivelul de permisii", - "@setPermissionsLevel": { - "type": "text", - "placeholders": {} - }, - "singlesignon": "Autentificare unică", - "@singlesignon": { - "type": "text", - "placeholders": {} - }, - "startedACall": "{senderName} a început un apel", - "@startedACall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "statusExampleMessage": "Ce faceți?", - "@statusExampleMessage": { - "type": "text", - "placeholders": {} - }, - "tooManyRequestsWarning": "Prea multe cereri. Vă rugăm să încercați din nou mai tărziu!", - "@tooManyRequestsWarning": { - "type": "text", - "placeholders": {} - }, - "unknownEvent": "Evenimet necunoscut '{type}'", - "@unknownEvent": { - "type": "text", - "placeholders": { - "type": {} - } - }, - "verified": "Verificat", - "@verified": { - "type": "text", - "placeholders": {} - }, - "verifyStart": "Începeți verificare", - "@verifyStart": { - "type": "text", - "placeholders": {} - }, - "videoCall": "Apel video", - "@videoCall": { - "type": "text", - "placeholders": {} - }, - "visibilityOfTheChatHistory": "Vizibilitatea istoria chatului", - "@visibilityOfTheChatHistory": { - "type": "text", - "placeholders": {} - }, - "visibleForAllParticipants": "Vizibil pentru toți participanți", - "@visibleForAllParticipants": { - "type": "text", - "placeholders": {} - }, - "waitingPartnerEmoji": "Așteptând pe partenerul să accepte emoji-ul…", - "@waitingPartnerEmoji": { - "type": "text", - "placeholders": {} - }, - "waitingPartnerNumbers": "Așteptând pe partenerul să accepte numerele…", - "@waitingPartnerNumbers": { - "type": "text", - "placeholders": {} - }, - "warning": "Avertizment!", - "@warning": { - "type": "text", - "placeholders": {} - }, - "whoCanPerformWhichAction": "Cine poate face care acțiune", - "@whoCanPerformWhichAction": { - "type": "text", - "placeholders": {} - }, - "whoIsAllowedToJoinThisGroup": "Cine se poate alătura la acest grup", - "@whoIsAllowedToJoinThisGroup": { - "type": "text", - "placeholders": {} - }, - "whyDoYouWantToReportThis": "De ce doriți să reportați acest conținut?", - "@whyDoYouWantToReportThis": { - "type": "text", - "placeholders": {} - }, - "wipeChatBackup": "Ștergeți backup-ul vostru de chat să creați o nouă cheie de recuperare?", - "@wipeChatBackup": { - "type": "text", - "placeholders": {} - }, - "withTheseAddressesRecoveryDescription": "Cu acestea adrese puteți să vă recuperați parola.", - "@withTheseAddressesRecoveryDescription": { - "type": "text", - "placeholders": {} - }, - "youHaveBeenBannedFromThisChat": "Ați fost interzis din acest chat", - "@youHaveBeenBannedFromThisChat": { - "type": "text", - "placeholders": {} - }, - "messageInfo": "Info mesajului", - "@messageInfo": {}, - "time": "Timp", - "@time": {}, - "messageType": "Fel de mesaj", - "@messageType": {}, - "sender": "Trimițător", - "@sender": {}, - "openGallery": "Deschideți galeria", - "@openGallery": {}, - "removeFromSpace": "Eliminați din spațiu", - "@removeFromSpace": {}, - "publish": "Publicați", - "@publish": {}, - "videoWithSize": "Video ({size})", - "@videoWithSize": { - "type": "text", - "placeholders": { - "size": {} - } - }, - "unsupportedAndroidVersionLong": "Această funcție are nevoie de o versiune de Android mai nouă. Vă rugăm să verificați dacă sunt actualizări sau suport de la Lineage OS.", - "@unsupportedAndroidVersionLong": {}, - "dismiss": "Respingeți", - "@dismiss": {}, - "widgetVideo": "Video", - "@widgetVideo": {}, - "widgetEtherpad": "Notiță text", - "@widgetEtherpad": {}, - "widgetJitsi": "Jitsi Meet", - "@widgetJitsi": {}, - "widgetUrlError": "Acest URL nu este valibil.", - "@widgetUrlError": {}, - "widgetNameError": "Vă rugăm să introduceți un nume de afișare.", - "@widgetNameError": {}, - "errorAddingWidget": "Adăugarea widget-ului a eșuat.", - "@errorAddingWidget": {}, - "youRejectedTheInvitation": "Ați respins invitația", - "@youRejectedTheInvitation": {}, - "youJoinedTheChat": "Va-ți alăturat la chat", - "@youJoinedTheChat": {}, - "youAcceptedTheInvitation": "👍Ați acceptat invitația", - "@youAcceptedTheInvitation": {}, - "youHaveWithdrawnTheInvitationFor": "Ați retras invitația pentru {user}", - "@youHaveWithdrawnTheInvitationFor": { - "placeholders": { - "user": {} - } - }, - "youInvitedBy": "📩Ați fost invitat de {user}", - "@youInvitedBy": { - "placeholders": { - "user": {} - } - }, - "unlockOldMessages": "Deblocați mesajele vechi", - "@unlockOldMessages": {}, - "youInvitedUser": "📩Ați invitat pe {user}", - "@youInvitedUser": { - "placeholders": { - "user": {} - } - }, - "youKicked": "👞Ați dat afară pe {user}", - "@youKicked": { - "placeholders": { - "user": {} - } - }, - "youUnbannedUser": "Ați ridicat interzicerea lui {user}", - "@youUnbannedUser": { - "placeholders": { - "user": {} - } - }, - "storeInAndroidKeystore": "Stoca în Android KeyStore", - "@storeInAndroidKeystore": {}, - "user": "Utilizator", - "@user": {}, - "custom": "Personalizat", - "@custom": {}, - "screenSharingDetail": "Partajați ecranul în FluffyChat", - "@screenSharingDetail": {}, - "storeSecurlyOnThisDevice": "Stoca sigur pe acest dispozitiv", - "@storeSecurlyOnThisDevice": {}, - "otherCallingPermissions": "Microfon, cameră și alte permisiuni lui FluffyChat", - "@otherCallingPermissions": {}, - "whyIsThisMessageEncrypted": "De ce este acest mesaj ilizibil?", - "@whyIsThisMessageEncrypted": {}, - "newSpace": "Spațiu nou", - "@newSpace": {}, - "enterSpace": "Intrați în spațiu", - "@enterSpace": {}, - "enterRoom": "Intrați în cameră", - "@enterRoom": {}, - "allSpaces": "Toate spațiile", - "@allSpaces": {}, - "numChats": "{number} chaturi", - "@numChats": { - "type": "number", - "placeholders": { - "number": {} - } - }, - "hideUnimportantStateEvents": "Ascundeți evenimente de stare neimportante", - "@hideUnimportantStateEvents": {}, - "doNotShowAgain": "Nu se mai apară din nou", - "@doNotShowAgain": {}, - "newSpaceDescription": "Spațiile vă permit să vă consolidați chaturile și să stabiliți comunități private sau publice.", - "@newSpaceDescription": {}, - "encryptThisChat": "Criptați acest chat", - "@encryptThisChat": {}, - "disableEncryptionWarning": "Pentru motive de securitate, nu este posibil să dezactivați criptarea unui chat în care criptare este activată.", - "@disableEncryptionWarning": {}, - "noBackupWarning": "Avertisment! Fără să activați backup de chat, veți pierde accesul la mesajele voastre criptate. E foarte recomandat să activați backup de chat înainte să vă deconectați.", - "@noBackupWarning": {}, - "noOtherDevicesFound": "Nu s-a găsit alte dispozitive", - "@noOtherDevicesFound": {}, - "fileHasBeenSavedAt": "Fișierul a fost salvat la {path}", - "@fileHasBeenSavedAt": { - "type": "text", - "placeholders": { - "path": {} - } - }, - "jump": "Săriți", - "@jump": {}, - "report": "reportați", - "@report": {}, - "jumpToLastReadMessage": "Săriți la ultimul citit mesaj", - "@jumpToLastReadMessage": {}, - "memberChanges": "Schimbări de membri", - "@memberChanges": { - "type": "text", - "placeholders": {} - }, - "guestsCanJoin": "Musafiri pot să se alăture", - "@guestsCanJoin": { - "type": "text", - "placeholders": {} - }, - "fileName": "Nume de fișier", - "@fileName": { - "type": "text", - "placeholders": {} - }, - "fontSize": "Mărimea fontului", - "@fontSize": { - "type": "text", - "placeholders": {} - }, - "fromJoining": "De la alăturare", - "@fromJoining": { - "type": "text", - "placeholders": {} - }, - "fromTheInvitation": "De la invitația", - "@fromTheInvitation": { - "type": "text", - "placeholders": {} - }, - "goToTheNewRoom": "Mergeți la camera nouă", - "@goToTheNewRoom": { - "type": "text", - "placeholders": {} - }, - "group": "Grup", - "@group": { - "type": "text", - "placeholders": {} - }, - "groupIsPublic": "Grupul este public", - "@groupIsPublic": { - "type": "text", - "placeholders": {} - }, - "guestsAreForbidden": "Musafiri sunt interziși", - "@guestsAreForbidden": { - "type": "text", - "placeholders": {} - }, - "kickedAndBanned": "🙅{username} a dat afară și a interzis pe {targetName} din cameră", - "@kickedAndBanned": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "dehydrateWarning": "Această actiune nu poate fi anulată. Asigurați-vă că păstrați fișierul backup.", - "@dehydrateWarning": {}, - "joinRoom": "Alăturați la cameră", - "@joinRoom": { - "type": "text", - "placeholders": {} - }, - "logInTo": "Conectați-vă la {homeserver}", - "@logInTo": { - "type": "text", - "placeholders": { - "homeserver": {} - } - }, - "locationDisabledNotice": "Servicile de locație sunt dezactivate. Vă rugăm să le activați să împărțiți locația voastră.", - "@locationDisabledNotice": { - "type": "text", - "placeholders": {} - }, - "noGoogleServicesWarning": "Se pare că nu aveți serviciile google pe dispozitivul vostru. Această decizie este bună pentru confidențialitatea voastră! Să primiți notificari push în FluffyChat vă recomandăm https://microg.org/ sau https://unifiedpush.org/.", - "@noGoogleServicesWarning": { - "type": "text", - "placeholders": {} - }, - "noMatrixServer": "{server1} nu este server matrix, înlocuiți cu {server2}?", - "@noMatrixServer": { - "type": "text", - "placeholders": { - "server1": {}, - "server2": {} - } - }, - "numUsersTyping": "{count} utilizatori tastează…", - "@numUsersTyping": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "offensive": "Ofensiv", - "@offensive": { - "type": "text", - "placeholders": {} - }, - "confirm": "Confirmați", - "@confirm": { - "type": "text", - "placeholders": {} - }, - "or": "Sau", - "@or": { - "type": "text", - "placeholders": {} - }, - "serverRequiresEmail": "Acest server trebuie să valideze emailul vostru pentru înregistrare.", - "@serverRequiresEmail": {}, - "waitingPartnerAcceptRequest": "Așteptând pe partenerul să accepte cererea…", - "@waitingPartnerAcceptRequest": { - "type": "text", - "placeholders": {} - }, - "darkTheme": "Întunecat", - "@darkTheme": { - "type": "text", - "placeholders": {} - }, - "sharedTheLocation": "{username} sa partajat locația", - "@sharedTheLocation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "setInvitationLink": "Stabiliți linkul de invitație", - "@setInvitationLink": { - "type": "text", - "placeholders": {} - }, - "transferFromAnotherDevice": "Transfera de la alt dispozitiv", - "@transferFromAnotherDevice": { - "type": "text", - "placeholders": {} - }, - "synchronizingPleaseWait": "Sincronizează... Vă rugăm să așteptați.", - "@synchronizingPleaseWait": { - "type": "text", - "placeholders": {} - }, - "systemTheme": "Sistem", - "@systemTheme": { - "type": "text", - "placeholders": {} - }, - "toggleUnread": "Marcați Citit/Necitit", - "@toggleUnread": { - "type": "text", - "placeholders": {} - }, - "tryToSendAgain": "Încercați să trimiteți din nou", - "@tryToSendAgain": { - "type": "text", - "placeholders": {} - }, - "unavailable": "Nedisponibil", - "@unavailable": { - "type": "text", - "placeholders": {} - }, - "userAndUserAreTyping": "{username} și {username2} tastează…", - "@userAndUserAreTyping": { - "type": "text", - "placeholders": { - "username": {}, - "username2": {} - } - }, - "userLeftTheChat": "🚪{username} a plecat din chat", - "@userLeftTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "userAndOthersAreTyping": "{username} și {count} alți tastează…", - "@userAndOthersAreTyping": { - "type": "text", - "placeholders": { - "username": {}, - "count": {} - } - }, - "userSentUnknownEvent": "{username} a trimis un eveniment {type}", - "@userSentUnknownEvent": { - "type": "text", - "placeholders": { - "username": {}, - "type": {} - } - }, - "unverified": "Neverificat", - "@unverified": {}, - "verifyTitle": "Verificând celălalt cont", - "@verifyTitle": { - "type": "text", - "placeholders": {} - }, - "visibleForEveryone": "Vizibil pentru toți", - "@visibleForEveryone": { - "type": "text", - "placeholders": {} - }, - "readUpToHere": "Citit până aici", - "@readUpToHere": {}, - "changedTheHistoryVisibility": "{username} a schimbat vizibilitatea istoriei chatului", - "@changedTheHistoryVisibility": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "copy": "Copiați", - "@copy": { - "type": "text", - "placeholders": {} - }, - "displaynameHasBeenChanged": "Displayname a fost schimbat", - "@displaynameHasBeenChanged": { - "type": "text", - "placeholders": {} - }, - "invitedUsersOnly": "Numai utilizatori invitați", - "@invitedUsersOnly": { - "type": "text", - "placeholders": {} - }, - "configureChat": "Configurați chat", - "@configureChat": { - "type": "text", - "placeholders": {} - }, - "copiedToClipboard": "Copiat în clipboard", - "@copiedToClipboard": { - "type": "text", - "placeholders": {} - }, - "device": "Dispozitiv", - "@device": { - "type": "text", - "placeholders": {} - }, - "username": "Nume de utilizator", - "@username": { - "type": "text", - "placeholders": {} - }, - "sentAnAudio": "🎤{username} a trimis audio", - "@sentAnAudio": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentAFile": "📁{username} a trimis un fișier", - "@sentAFile": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "indexedDbErrorLong": "Stocarea de mesaje nu este activat implicit în modul privat.\nVă rugăm să vizitați\n- about:config\n- stabiliți dom.indexedDB.privateBrowsing.enabled la true\nAstfel, nu este posibil să folosiți FluffyChat.", - "@indexedDbErrorLong": {}, - "addWidget": "Adăugați widget", - "@addWidget": {}, - "locationPermissionDeniedNotice": "Permisiunea locației blocată. Vă rugăm să o dezblocați să împărțiți locația voastră.", - "@locationPermissionDeniedNotice": { - "type": "text", - "placeholders": {} - }, - "newChat": "Chat nou", - "@newChat": { - "type": "text", - "placeholders": {} - }, - "newMessageInFluffyChat": "💬 Mesaj nou în FluffyChat", - "@newMessageInFluffyChat": { - "type": "text", - "placeholders": {} - }, - "sentAPicture": "🖼️ {username} a trimis o poză", - "@sentAPicture": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentAVideo": "🎥{username} a trimis un video", - "@sentAVideo": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentASticker": "😊 {username} a trimis un sticker", - "@sentASticker": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "storeInSecureStorageDescription": "Păstrați cheia de recuperare în stocarea sigură a acestui dispozitiv.", - "@storeInSecureStorageDescription": {}, - "saveKeyManuallyDescription": "Activați dialogul de partajare sistemului sau folosiți clipboard-ul să salvați manual această cheie.", - "@saveKeyManuallyDescription": {}, - "countFiles": "{count} fișiere", - "@countFiles": { - "placeholders": { - "count": {} - } - }, - "hugContent": "{senderName} vă îmbrățișează", - "@hugContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "storeInAppleKeyChain": "Stoca în Apple KeyChain", - "@storeInAppleKeyChain": {}, - "addEmail": "Adăugați email", - "@addEmail": { - "type": "text", - "placeholders": {} - }, - "confirmMatrixId": "Vă rugăm să confirmați Matrix ID-ul vostru să ștergeți contul vostru.", - "@confirmMatrixId": {}, - "cuddleContent": "{senderName} vă îmbrățișează", - "@cuddleContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "supposedMxid": "ID-ul ar trebuii să fie {mxid}", - "@supposedMxid": { - "type": "text", - "placeholders": { - "mxid": {} - } - }, - "commandHint_html": "Trimiteți text format ca HTML", - "@commandHint_html": { - "type": "text", - "description": "Usage hint for the command /html" - }, - "addToSpace": "Adăugați la spațiu", - "@addToSpace": {}, - "commandHint_hug": "Trimiteți o îmbrățișare", - "@commandHint_hug": {}, - "badServerVersionsException": "Homeserver-ul suportă versiunele de Spec următoare:\n{serverVersions}\nDar această aplicație suportă numai {supportedVersions}", - "@badServerVersionsException": { - "type": "text", - "placeholders": { - "serverVersions": {}, - "supportedVersions": {} - } - }, - "badServerLoginTypesException": "Homeserver-ul suportă următoarele feluri de login:\n{serverVersions}\nDar această aplicație suportă numai:\n{supportedVersions}", - "@badServerLoginTypesException": { - "type": "text", - "placeholders": { - "serverVersions": {}, - "supportedVersions": {} - } - }, - "changedTheGuestAccessRulesTo": "{username} a schimbat regulile pentru acesul musafirilor la: {rules}", - "@changedTheGuestAccessRulesTo": { - "type": "text", - "placeholders": { - "username": {}, - "rules": {} - } - }, - "changedTheJoinRulesTo": "{username} a schimbat regulile de alăturare la: {joinRules}", - "@changedTheJoinRulesTo": { - "type": "text", - "placeholders": { - "username": {}, - "joinRules": {} - } - }, - "yourChatBackupHasBeenSetUp": "Backup-ul vostru de chat a fost configurat.", - "@yourChatBackupHasBeenSetUp": {}, - "cantOpenUri": "Nu se poate deschide URI-ul {uri}", - "@cantOpenUri": { - "type": "text", - "placeholders": { - "uri": {} - } - }, - "changedTheDisplaynameTo": "{username} s-a schimbat displayname la: '{displayname}'", - "@changedTheDisplaynameTo": { - "type": "text", - "placeholders": { - "username": {}, - "displayname": {} - } - }, - "changedTheGuestAccessRules": "{username} a schimbat regulile pentru acesul musafirilor", - "@changedTheGuestAccessRules": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changeTheHomeserver": "Schimbați homeserver-ul", - "@changeTheHomeserver": { - "type": "text", - "placeholders": {} - }, - "chatBackupDescription": "Mesajele voastre vechi sunt sigurate cu o cheie de recuperare. Vă rugăm să nu o pierdeți.", - "@chatBackupDescription": { - "type": "text", - "placeholders": {} - }, - "contentHasBeenReported": "Conținutul a fost reportat la administratori serverului", - "@contentHasBeenReported": { - "type": "text", - "placeholders": {} - }, - "chatDetails": "Detalii de chat", - "@chatDetails": { - "type": "text", - "placeholders": {} - }, - "commandHint_dm": "Porniți un chat direct\nFolosiți --no-encryption să dezactivați criptare", - "@commandHint_dm": { - "type": "text", - "description": "Usage hint for the command /dm" - }, - "commandHint_me": "Descrieți-vă", - "@commandHint_me": { - "type": "text", - "description": "Usage hint for the command /me" - }, - "contactHasBeenInvitedToTheGroup": "Contactul a fost invitat la grup", - "@contactHasBeenInvitedToTheGroup": { - "type": "text", - "placeholders": {} - }, - "containsUserName": "Conține nume de utilizator", - "@containsUserName": { - "type": "text", - "placeholders": {} - }, - "copyToClipboard": "Copiați în clipboard", - "@copyToClipboard": { - "type": "text", - "placeholders": {} - }, - "countParticipants": "{count} participanți", - "@countParticipants": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "delete": "Ștergeți", - "@delete": { - "type": "text", - "placeholders": {} - }, - "deactivateAccountWarning": "Această acțiune va dezactiva contul vostru. Nu poate fi anulat! Sunteți sigur?", - "@deactivateAccountWarning": { - "type": "text", - "placeholders": {} - }, - "deleteAccount": "Ștergeți contul", - "@deleteAccount": { - "type": "text", - "placeholders": {} - }, - "defaultPermissionLevel": "Nivel de permisiuni implicită", - "@defaultPermissionLevel": { - "type": "text", - "placeholders": {} - }, - "deleteMessage": "Ștergeți mesajul", - "@deleteMessage": { - "type": "text", - "placeholders": {} - }, - "downloadFile": "Descărcați fișierul", - "@downloadFile": { - "type": "text", - "placeholders": {} - }, - "enableEmotesGlobally": "Activați pachet de emote global", - "@enableEmotesGlobally": { - "type": "text", - "placeholders": {} - }, - "everythingReady": "Totul e gata!", - "@everythingReady": { - "type": "text", - "placeholders": {} - }, - "editBlockedServers": "Editați servere blocate", - "@editBlockedServers": { - "type": "text", - "placeholders": {} - }, - "emoteInvalid": "Shortcode de emote nevalibil!", - "@emoteInvalid": { - "type": "text", - "placeholders": {} - }, - "edit": "Editați", - "@edit": { - "type": "text", - "placeholders": {} - }, - "editRoomAliases": "Schimbați pseudonimele camerei", - "@editRoomAliases": { - "type": "text", - "placeholders": {} - }, - "emptyChat": "Chat gol", - "@emptyChat": { - "type": "text", - "placeholders": {} - }, - "enableEncryption": "Activați criptare", - "@enableEncryption": { - "type": "text", - "placeholders": {} - }, - "encrypted": "Criptat", - "@encrypted": { - "type": "text", - "placeholders": {} - }, - "editDisplayname": "Schimbați displayname", - "@editDisplayname": { - "type": "text", - "placeholders": {} - }, - "editRoomAvatar": "Schimbați avatarul din cameră", - "@editRoomAvatar": { - "type": "text", - "placeholders": {} - }, - "emoteExists": "Emote deja există!", - "@emoteExists": { - "type": "text", - "placeholders": {} - }, - "emotePacks": "Pachete de emoturi din cameră", - "@emotePacks": { - "type": "text", - "placeholders": {} - }, - "emoteSettings": "Configurări Emote", - "@emoteSettings": { - "type": "text", - "placeholders": {} - }, - "emoteShortcode": "Shortcode de emote", - "@emoteShortcode": { - "type": "text", - "placeholders": {} - }, - "emoteWarnNeedToPick": "Trebuie să alegeți shortcode pentru emote și o imagine!", - "@emoteWarnNeedToPick": { - "type": "text", - "placeholders": {} - }, - "encryption": "Criptare", - "@encryption": { - "type": "text", - "placeholders": {} - }, - "enterAnEmailAddress": "Introduceți o adresă email", - "@enterAnEmailAddress": { - "type": "text", - "placeholders": {} - }, - "homeserver": "Homeserver", - "@homeserver": {}, - "errorObtainingLocation": "Obținerea locației a eșuat: {error}", - "@errorObtainingLocation": { - "type": "text", - "placeholders": { - "error": {} - } - }, - "ok": "Ok", - "@ok": { - "type": "text", - "placeholders": {} - }, - "youKickedAndBanned": "🙅Ați dat afară și interzis pe {user}", - "@youKickedAndBanned": { - "placeholders": { - "user": {} - } - }, - "noKeyForThisMessage": "Această chestie poate să se întâmple când mesajul a fost trimis înainte să vă conectați contul cu acest dispozitiv.\n\nO altă explicație ar fi dacă trimițătorul a blocat dispozitivul vostru sau ceva s-a întâmplat cu conexiunea la internet\n\nPuteți să citiți mesajul în o altă seșiune? Atunci puteți să transferați mesajul de acolo! Mergeți la Configurări > Dispozitive și verificați că dispozitivele s-au verificat. Când deschideți camera în viitor și ambele seșiune sunt în foreground, cheile va fi transmise automat. \n\nDoriți să îți păstrați cheile când deconectați sau schimbați dispozitive? Fiți atenți să activați backup de chat în configurări.", - "@noKeyForThisMessage": {}, - "sendAsText": "Trimiteți ca text", - "@sendAsText": { - "type": "text" - }, - "reportErrorDescription": "Ceva a eșuat. Vă rugăm să încercați din nou mai tărziu. Dacă doriți, puteți să reportați problema la dezvoltatori.", - "@reportErrorDescription": {}, - "openLinkInBrowser": "Deschideți linkul în browser", - "@openLinkInBrowser": {}, - "send": "Trimiteți", - "@send": { - "type": "text", - "placeholders": {} - }, - "sendAMessage": "Trimiteți un mesaj", - "@sendAMessage": { - "type": "text", - "placeholders": {} - }, - "sendAudio": "Trimiteți audio", - "@sendAudio": { - "type": "text", - "placeholders": {} - }, - "sendOriginal": "Trimiteți original", - "@sendOriginal": { - "type": "text", - "placeholders": {} - }, - "sendVideo": "Trimiteți video", - "@sendVideo": { - "type": "text", - "placeholders": {} - }, - "sendImage": "Trimiteți imagine", - "@sendImage": { - "type": "text", - "placeholders": {} - }, - "sendSticker": "Trimiteți sticker", - "@sendSticker": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterRecoveryKeyDescription": "Să vă deblocați mesajele vechi, vă rugăm să introduceți cheia de recuperare creată de o seșiune anterioră. Cheia de recuperare NU este parola voastră.", - "@pleaseEnterRecoveryKeyDescription": {}, - "separateChatTypes": "Afișați chaturi directe și grupuri separat", - "@separateChatTypes": { - "type": "text", - "placeholders": {} - }, - "setAsCanonicalAlias": "Stabiliți ca pseudonimul primar", - "@setAsCanonicalAlias": { - "type": "text", - "placeholders": {} - }, - "writeAMessage": "Scrieți un mesaj…", - "@writeAMessage": { - "type": "text", - "placeholders": {} - }, - "yes": "Da", - "@yes": { - "type": "text", - "placeholders": {} - }, - "markAsRead": "Marcați ca citit", - "@markAsRead": {}, - "oopsPushError": "Ups! Din păcate, o eroare s-a întâmplat cu stabilirea de notificări push.", - "@oopsPushError": { - "type": "text", - "placeholders": {} - }, - "oneClientLoggedOut": "Unul dintre clienților voștri a fost deconectat", - "@oneClientLoggedOut": {}, - "editBundlesForAccount": "Editați pachetele pentru acest cont", - "@editBundlesForAccount": {}, - "bundleName": "Numele pachetului", - "@bundleName": {}, - "link": "Link", - "@link": {}, - "passphraseOrKey": "frază de acces sau cheie de recuperare", - "@passphraseOrKey": { - "type": "text", - "placeholders": {} - }, - "password": "Parolă", - "@password": { - "type": "text", - "placeholders": {} - }, - "passwordForgotten": "Parola uitată", - "@passwordForgotten": { - "type": "text", - "placeholders": {} - }, - "passwordHasBeenChanged": "Parola a fost schimbată", - "@passwordHasBeenChanged": { - "type": "text", - "placeholders": {} - }, - "passwordRecovery": "Recuperare parolei", - "@passwordRecovery": { - "type": "text", - "placeholders": {} - }, - "people": "Persoane", - "@people": { - "type": "text", - "placeholders": {} - }, - "pickImage": "Alegeți o imagine", - "@pickImage": { - "type": "text", - "placeholders": {} - }, - "pleaseChoose": "Vă rugăm să alegeți", - "@pleaseChoose": { - "type": "text", - "placeholders": {} - }, - "pin": "Fixați", - "@pin": { - "type": "text", - "placeholders": {} - }, - "pleaseChooseAPasscode": "Vă rugăm să alegeți un passcode", - "@pleaseChooseAPasscode": { - "type": "text", - "placeholders": {} - }, - "pleaseEnter4Digits": "Vă rugăm să introduceți 4 cifre sau puteți să lăsați gol să dezactivați lacătul aplicației.", - "@pleaseEnter4Digits": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourPassword": "Vă rugăm să introduceți parola voastră", - "@pleaseEnterYourPassword": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourUsername": "Vă rugăm să introduceți username-ul vostru", - "@pleaseEnterYourUsername": { - "type": "text", - "placeholders": {} - }, - "pleaseFollowInstructionsOnWeb": "Vă rugăm să urmați instrucțiunele pe website și apoi să apăsați pe următor.", - "@pleaseFollowInstructionsOnWeb": { - "type": "text", - "placeholders": {} - }, - "reason": "Motiv", - "@reason": { - "type": "text", - "placeholders": {} - }, - "rejectedTheInvitation": "{username} a respins invitația", - "@rejectedTheInvitation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "redactedAnEvent": "{username} a redactat un eveniment", - "@redactedAnEvent": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "removeAllOtherDevices": "Eliminați toate celelalte dispozitive", - "@removeAllOtherDevices": { - "type": "text", - "placeholders": {} - }, - "removeYourAvatar": "Ștergeți avatarul", - "@removeYourAvatar": { - "type": "text", - "placeholders": {} - }, - "renderRichContent": "Reda conținut bogat al mesajelor", - "@renderRichContent": { - "type": "text", - "placeholders": {} - }, - "replaceRoomWithNewerVersion": "Înlocuiți camera cu versiune mai nouă", - "@replaceRoomWithNewerVersion": { - "type": "text", - "placeholders": {} - }, - "reply": "Răspundeți", - "@reply": { - "type": "text", - "placeholders": {} - }, - "reportMessage": "Raportați mesajul", - "@reportMessage": { - "type": "text", - "placeholders": {} - }, - "requestPermission": "Cereți permisiune", - "@requestPermission": { - "type": "text", - "placeholders": {} - }, - "saveFile": "Salvați fișierul", - "@saveFile": { - "type": "text", - "placeholders": {} - }, - "search": "Căutați", - "@search": { - "type": "text", - "placeholders": {} - }, - "recoveryKey": "Cheie de recuperare", - "@recoveryKey": {}, - "recoveryKeyLost": "Cheia de recuperare pierdută?", - "@recoveryKeyLost": {}, - "seenByUser": "Văzut de {username}", - "@seenByUser": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "muteChat": "Amuțați chatul", - "@muteChat": { - "type": "text", - "placeholders": {} - }, - "needPantalaimonWarning": "Vă rugăm să fiți conștienți că e nevoie de Pantalaimon să folosiți criptare end-to-end deocamdată.", - "@needPantalaimonWarning": { - "type": "text", - "placeholders": {} - }, - "autoplayImages": "Anima automatic stickere și emote animate", - "@autoplayImages": { - "type": "text", - "placeholder": {} - }, - "sendOnEnter": "Trimite cu tasta enter", - "@sendOnEnter": {}, - "changedTheChatPermissions": "{username} a schimbat permisiunile chatului", - "@changedTheChatPermissions": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "extremeOffensive": "De foarte mare ofensă", - "@extremeOffensive": { - "type": "text", - "placeholders": {} - }, - "id": "ID", - "@id": { - "type": "text", - "placeholders": {} - }, - "invitedUser": "📩{username} a invitat {targetName}", - "@invitedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "deviceKeys": "Cheile dispozitivului:", - "@deviceKeys": {}, - "pleaseEnterRecoveryKey": "Vă rugăm să introduceți cheia voastră de recuperare:", - "@pleaseEnterRecoveryKey": {}, - "newVerificationRequest": "Cerere de verificare nouă!", - "@newVerificationRequest": { - "type": "text", - "placeholders": {} - }, - "remove": "Eliminați", - "@remove": { - "type": "text", - "placeholders": {} - }, - "play": "Redați {fileName}", - "@play": { - "type": "text", - "placeholders": { - "fileName": {} - } - }, - "channelCorruptedDecryptError": "Criptarea a fost corupată", - "@channelCorruptedDecryptError": { - "type": "text", - "placeholders": {} - }, - "chooseAStrongPassword": "Alegeți o parolă robustă", - "@chooseAStrongPassword": { - "type": "text", - "placeholders": {} - }, - "commandHint_cuddle": "Trimiteți o îmbrățișare", - "@commandHint_cuddle": {}, - "googlyEyesContent": "{senderName} v-a trimis ochi googly", - "@googlyEyesContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "areYouSureYouWantToLogout": "Sunteți sigur că doriți să vă deconectați?", - "@areYouSureYouWantToLogout": { - "type": "text", - "placeholders": {} - }, - "changedTheRoomAliases": "{username} a schimbat pseudonimele camerei", - "@changedTheRoomAliases": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changeYourAvatar": "Schimbați avatarul vostru", - "@changeYourAvatar": { - "type": "text", - "placeholders": {} - }, - "commandHint_join": "Alăturați-vă la camera alesă", - "@commandHint_join": { - "type": "text", - "description": "Usage hint for the command /join" - }, - "allChats": "Toate Chaturile", - "@allChats": { - "type": "text", - "placeholders": {} - }, - "commandHint_invite": "Invitați utilizatorul ales la această cameră", - "@commandHint_invite": { - "type": "text", - "description": "Usage hint for the command /invite" - }, - "changeTheNameOfTheGroup": "Schimbați numele grupului", - "@changeTheNameOfTheGroup": { - "type": "text", - "placeholders": {} - }, - "commandHint_googly": "Trimiteți câțiva ochi googly", - "@commandHint_googly": {}, - "botMessages": "Mesaje Bot", - "@botMessages": { - "type": "text", - "placeholders": {} - }, - "all": "Toate", - "@all": { - "type": "text", - "placeholders": {} - }, - "blocked": "Blocat", - "@blocked": { - "type": "text", - "placeholders": {} - }, - "changedTheJoinRules": "{username} a schimbat regulile de alăturare", - "@changedTheJoinRules": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheProfileAvatar": "{username} s-a schimbat avatarul", - "@changedTheProfileAvatar": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "appLock": "Lacăt aplicație", - "@appLock": { - "type": "text", - "placeholders": {} - }, - "changedTheHistoryVisibilityTo": "{username} a schimbat vizibilitatea istoriei chatului la: {rules}", - "@changedTheHistoryVisibilityTo": { - "type": "text", - "placeholders": { - "username": {}, - "rules": {} - } - }, - "close": "Închideți", - "@close": { - "type": "text", - "placeholders": {} - }, - "reopenChat": "Deschide din nou chatul", - "@reopenChat": {}, - "pleaseTryAgainLaterOrChooseDifferentServer": "Vă rugăm să încercați din nou mai târziu sau să alegeți un server diferit.", - "@pleaseTryAgainLaterOrChooseDifferentServer": {}, - "signInWithPassword": "Conectați-vă cu parolă", - "@signInWithPassword": {}, - "@setColorTheme": {}, - "@banUserDescription": {}, - "@removeDevicesDescription": {}, - "@tryAgain": {}, - "@unbanUserDescription": {}, - "@messagesStyle": {}, - "@chatDescription": {}, - "@pushNotificationsNotAvailable": {}, - "@invalidServerName": {}, - "@chatPermissions": {}, - "@makeAdminDescription": {}, - "@setChatDescription": {}, - "@importFromZipFile": {}, - "@redactedBy": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@signInWith": { - "type": "text", - "placeholders": { - "provider": {} - } - }, - "@optionalRedactReason": {}, - "@archiveRoomDescription": {}, - "@exportEmotePack": {}, - "@inviteContactToGroupQuestion": {}, - "@redactedByBecause": { - "type": "text", - "placeholders": { - "username": {}, - "reason": {} - } - }, - "@redactMessageDescription": {}, - "@invalidInput": {}, - "@addChatDescription": {}, - "@hasKnocked": { - "placeholders": { - "user": {} - } - }, - "@directChat": {}, - "@wrongPinEntered": { - "type": "text", - "placeholders": { - "seconds": {} - } - }, - "@sendTypingNotifications": {}, - "@inviteGroupChat": {}, - "@invitePrivateChat": {}, - "@importEmojis": {}, - "@noChatDescriptionYet": {}, - "@learnMore": {}, - "@notAnImage": {}, - "@chatDescriptionHasBeenChanged": {}, - "@roomUpgradeDescription": {}, - "@pleaseEnterANumber": {}, - "@profileNotFound": {}, - "@shareInviteLink": {}, - "@emoteKeyboardNoRecents": { - "type": "text", - "placeholders": {} - }, - "@setTheme": {}, - "@replace": {}, - "@createGroup": {}, - "@kickUserDescription": {}, - "@importNow": {}, - "@invite": {} -} \ No newline at end of file + "@@last_modified": "2021-08-14 12:41:09.918296", + "about": "Despre", + "@about": { + "type": "text", + "placeholders": {} + }, + "accept": "Accept", + "@accept": { + "type": "text", + "placeholders": {} + }, + "acceptedTheInvitation": "{username} a aceptat invitați", + "@acceptedTheInvitation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "account": "Cont", + "@account": { + "type": "text", + "placeholders": {} + }, + "activatedEndToEndEncryption": "{username} a activat criptarea end-to-end", + "@activatedEndToEndEncryption": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "admin": "Administrator", + "@admin": { + "type": "text", + "placeholders": {} + }, + "alias": "poreclă", + "@alias": { + "type": "text", + "placeholders": {} + }, + "answeredTheCall": "{senderName} a acceptat apelul", + "@answeredTheCall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "anyoneCanJoin": "Oricine se poate alătura", + "@anyoneCanJoin": { + "type": "text", + "placeholders": {} + }, + "archive": "Arhivă", + "@archive": { + "type": "text", + "placeholders": {} + }, + "areGuestsAllowedToJoin": "Vizitatorii \"guest\" se pot alătura", + "@areGuestsAllowedToJoin": { + "type": "text", + "placeholders": {} + }, + "areYouSure": "Ești sigur?", + "@areYouSure": { + "type": "text", + "placeholders": {} + }, + "askSSSSSign": "Pentru a putea conecta cealaltă persoană, te rog introdu parola sau cheia ta de recuperare.", + "@askSSSSSign": { + "type": "text", + "placeholders": {} + }, + "askVerificationRequest": "Accepți cererea de verificare de la {username}?", + "@askVerificationRequest": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "banFromChat": "Interzis din conversație", + "@banFromChat": { + "type": "text", + "placeholders": {} + }, + "banned": "Interzis", + "@banned": { + "type": "text", + "placeholders": {} + }, + "bannedUser": "{username} a interzis pe {targetName}", + "@bannedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "blockDevice": "Blochează dispozitiv", + "@blockDevice": { + "type": "text", + "placeholders": {} + }, + "cancel": "Anulează", + "@cancel": { + "type": "text", + "placeholders": {} + }, + "changeDeviceName": "Schimbă numele dispozitiv", + "@changeDeviceName": { + "type": "text", + "placeholders": {} + }, + "changedTheChatAvatar": "{username} a schimbat poza conversați", + "@changedTheChatAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheChatDescriptionTo": "{username} a schimbat descrierea grupului în '{description}'", + "@changedTheChatDescriptionTo": { + "type": "text", + "placeholders": { + "username": {}, + "description": {} + } + }, + "changedTheChatNameTo": "{username} a schimbat porecla în '{chatname}'", + "@changedTheChatNameTo": { + "type": "text", + "placeholders": { + "username": {}, + "chatname": {} + } + }, + "commandHint_unban": "Dezinterziceți utilizatorul ales din această cameră", + "@commandHint_unban": { + "type": "text", + "description": "Usage hint for the command /unban" + }, + "dateAndTimeOfDay": "{date}, {timeOfDay}", + "@dateAndTimeOfDay": { + "type": "text", + "placeholders": { + "date": {}, + "timeOfDay": {} + } + }, + "deviceId": "ID-ul Dispozitiv", + "@deviceId": { + "type": "text", + "placeholders": {} + }, + "devices": "Dispozitive", + "@devices": { + "type": "text", + "placeholders": {} + }, + "directChats": "Chaturi directe", + "@directChats": { + "type": "text", + "placeholders": {} + }, + "endedTheCall": "{senderName} a terminat apelul", + "@endedTheCall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "enterYourHomeserver": "Introduceți homeserverul vostru", + "@enterYourHomeserver": { + "type": "text", + "placeholders": {} + }, + "groupWith": "Grup cu {displayname}", + "@groupWith": { + "type": "text", + "placeholders": { + "displayname": {} + } + }, + "howOffensiveIsThisContent": "Cât de ofensiv este acest conținut?", + "@howOffensiveIsThisContent": { + "type": "text", + "placeholders": {} + }, + "kickFromChat": "Dați afară din chat", + "@kickFromChat": { + "type": "text", + "placeholders": {} + }, + "rejoin": "Reintrați", + "@rejoin": { + "type": "text", + "placeholders": {} + }, + "sentCallInformations": "{senderName} a trimis informație de apel", + "@sentCallInformations": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "showPassword": "Afișați parola", + "@showPassword": { + "type": "text", + "placeholders": {} + }, + "no": "Nu", + "@no": { + "type": "text", + "placeholders": {} + }, + "sendMessages": "Trimiteți mesaje", + "@sendMessages": { + "type": "text", + "placeholders": {} + }, + "submit": "Trimiteți", + "@submit": { + "type": "text", + "placeholders": {} + }, + "unreadChats": "{unreadCount, plural, =1{Un chat necitit} other{{unreadCount} chaturi necitite}}", + "@unreadChats": { + "type": "text", + "placeholders": { + "unreadCount": {} + } + }, + "verifySuccess": "A reușit verificarea!", + "@verifySuccess": { + "type": "text", + "placeholders": {} + }, + "voiceMessage": "Mesaj vocal", + "@voiceMessage": { + "type": "text", + "placeholders": {} + }, + "wallpaper": "Imagine de fundal", + "@wallpaper": { + "type": "text", + "placeholders": {} + }, + "reactedWith": "{sender} a reacționat cu {reaction}", + "@reactedWith": { + "type": "text", + "placeholders": { + "sender": {}, + "reaction": {} + } + }, + "changePassword": "Schimbați parola", + "@changePassword": { + "type": "text", + "placeholders": {} + }, + "next": "Următor", + "@next": { + "type": "text", + "placeholders": {} + }, + "noConnectionToTheServer": "Fără conexiune la server", + "@noConnectionToTheServer": { + "type": "text", + "placeholders": {} + }, + "noPasswordRecoveryDescription": "Nu ați adăugat încă nici un mod de recuperare pentru parola voastră.", + "@noPasswordRecoveryDescription": { + "type": "text", + "placeholders": {} + }, + "notifications": "Notificări", + "@notifications": { + "type": "text", + "placeholders": {} + }, + "openVideoCamera": "Deschideți camera pentru video", + "@openVideoCamera": { + "type": "text", + "placeholders": {} + }, + "openAppToReadMessages": "Deschideți aplicația să citiți mesajele", + "@openAppToReadMessages": { + "type": "text", + "placeholders": {} + }, + "openCamera": "Deschideți camera", + "@openCamera": { + "type": "text", + "placeholders": {} + }, + "removedBy": "Eliminat de {username}", + "@removedBy": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "removeDevice": "Eliminați dispozitivul", + "@removeDevice": { + "type": "text", + "placeholders": {} + }, + "share": "Partajați", + "@share": { + "type": "text", + "placeholders": {} + }, + "shareLocation": "Partajați locația", + "@shareLocation": { + "type": "text", + "placeholders": {} + }, + "skip": "Săriți peste", + "@skip": { + "type": "text", + "placeholders": {} + }, + "sourceCode": "Codul surs", + "@sourceCode": { + "type": "text", + "placeholders": {} + }, + "spaceIsPublic": "Spațiul este public", + "@spaceIsPublic": { + "type": "text", + "placeholders": {} + }, + "spaceName": "Numele spațiului", + "@spaceName": { + "type": "text", + "placeholders": {} + }, + "toggleFavorite": "Comutați favoritul", + "@toggleFavorite": { + "type": "text", + "placeholders": {} + }, + "unblockDevice": "Debloca dispozitiv", + "@unblockDevice": { + "type": "text", + "placeholders": {} + }, + "unknownDevice": "Dispozitiv necunoscut", + "@unknownDevice": { + "type": "text", + "placeholders": {} + }, + "verify": "Verificați", + "@verify": { + "type": "text", + "placeholders": {} + }, + "weSentYouAnEmail": "V-am trimis un email", + "@weSentYouAnEmail": { + "type": "text", + "placeholders": {} + }, + "youAreNoLongerParticipatingInThisChat": "Nu mai participați în acest chat", + "@youAreNoLongerParticipatingInThisChat": { + "type": "text", + "placeholders": {} + }, + "yourPublicKey": "Cheia voastră publică", + "@yourPublicKey": { + "type": "text", + "placeholders": {} + }, + "addToSpaceDescription": "Alegeți un spațiu în care să adăugați acest chat.", + "@addToSpaceDescription": {}, + "placeCall": "Faceți apel", + "@placeCall": {}, + "voiceCall": "Apel vocal", + "@voiceCall": {}, + "unsupportedAndroidVersion": "Versiune de Android nesuportat", + "@unsupportedAndroidVersion": {}, + "previousAccount": "Contul anterior", + "@previousAccount": {}, + "userIsTyping": "{username} tastează…", + "@userIsTyping": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "widgetCustom": "Personalizat", + "@widgetCustom": {}, + "screenSharingTitle": "partajarea de ecran", + "@screenSharingTitle": {}, + "newGroup": "Grup nou", + "@newGroup": {}, + "changedTheRoomInvitationLink": "{username} a schimbat linkul de invitație", + "@changedTheRoomInvitationLink": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "chat": "Chat", + "@chat": { + "type": "text", + "placeholders": {} + }, + "chats": "Chaturi", + "@chats": { + "type": "text", + "placeholders": {} + }, + "invited": "Invitat", + "@invited": { + "type": "text", + "placeholders": {} + }, + "login": "Conectați-vă", + "@login": { + "type": "text", + "placeholders": {} + }, + "online": "Online", + "@online": { + "type": "text", + "placeholders": {} + }, + "onlineKeyBackupEnabled": "Backup de cheie online este activat", + "@onlineKeyBackupEnabled": { + "type": "text", + "placeholders": {} + }, + "removeFromBundle": "Stergeți din acest pachet", + "@removeFromBundle": {}, + "enableMultiAccounts": "(BETA) Activați multiple conturi pe acest dispozitiv", + "@enableMultiAccounts": {}, + "participant": "Participant", + "@participant": { + "type": "text", + "placeholders": {} + }, + "openInMaps": "Deschideți pe hartă", + "@openInMaps": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourPin": "Vă rugăm să introduceți pinul vostru", + "@pleaseEnterYourPin": { + "type": "text", + "placeholders": {} + }, + "privacy": "Confidențialitate", + "@privacy": { + "type": "text", + "placeholders": {} + }, + "pushRules": "Regulile Push", + "@pushRules": { + "type": "text", + "placeholders": {} + }, + "recording": "Înregistrare", + "@recording": { + "type": "text", + "placeholders": {} + }, + "register": "Înregistrați-vă", + "@register": { + "type": "text", + "placeholders": {} + }, + "redactMessage": "Redactați mesaj", + "@redactMessage": { + "type": "text", + "placeholders": {} + }, + "roomVersion": "Versiunea camerei", + "@roomVersion": { + "type": "text", + "placeholders": {} + }, + "security": "Securitate", + "@security": { + "type": "text", + "placeholders": {} + }, + "sendFile": "Trimiteți fișier", + "@sendFile": { + "type": "text", + "placeholders": {} + }, + "setStatus": "Stabiliți status", + "@setStatus": { + "type": "text", + "placeholders": {} + }, + "settings": "Configurări", + "@settings": { + "type": "text", + "placeholders": {} + }, + "inviteForMe": "Invitați pentru mine", + "@inviteForMe": { + "type": "text", + "placeholders": {} + }, + "fluffychat": "FluffyChat", + "@fluffychat": { + "type": "text", + "placeholders": {} + }, + "hasWithdrawnTheInvitationFor": "{username} a retras invitația pentru {targetName}", + "@hasWithdrawnTheInvitationFor": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "help": "Ajutor", + "@help": { + "type": "text", + "placeholders": {} + }, + "unknownEncryptionAlgorithm": "Algoritm de criptare necunoscut", + "@unknownEncryptionAlgorithm": { + "type": "text", + "placeholders": {} + }, + "unmuteChat": "Dezamuțați chat", + "@unmuteChat": { + "type": "text", + "placeholders": {} + }, + "unpin": "Anulează fixarea", + "@unpin": { + "type": "text", + "placeholders": {} + }, + "unbannedUser": "{username} a ridicat interzicerea lui {targetName}", + "@unbannedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "openChat": "Deschideți Chat", + "@openChat": {}, + "emailOrUsername": "Email sau nume de utilizator", + "@emailOrUsername": {}, + "youBannedUser": "Ați interzis pe {user}", + "@youBannedUser": { + "placeholders": { + "user": {} + } + }, + "fileIsTooBigForServer": "Serverul reportează că fișierul este prea mare să fie trimis.", + "@fileIsTooBigForServer": {}, + "widgetName": "Nume", + "@widgetName": {}, + "sorryThatsNotPossible": "Scuze... acest nu este posibil", + "@sorryThatsNotPossible": {}, + "enableEncryptionWarning": "Activând criptare, nu mai puteți să o dezactivați în viitor. Sunteți sigur?", + "@enableEncryptionWarning": { + "type": "text", + "placeholders": {} + }, + "commandMissing": "{command} nu este o comandă.", + "@commandMissing": { + "type": "text", + "placeholders": { + "command": {} + }, + "description": "State that {command} is not a valid /command." + }, + "status": "Status", + "@status": { + "type": "text", + "placeholders": {} + }, + "connect": "Conectați", + "@connect": { + "type": "text", + "placeholders": {} + }, + "you": "Voi", + "@you": { + "type": "text", + "placeholders": {} + }, + "start": "Începeți", + "@start": {}, + "videoCallsBetaWarning": "Vă rugăm să luați notă că apeluri video sunt în beta. Se poate că nu funcționează normal sau de loc pe fie care platformă.", + "@videoCallsBetaWarning": {}, + "pinMessage": "Fixați în cameră", + "@pinMessage": {}, + "wasDirectChatDisplayName": "Chat gol (a fost {oldDisplayName})", + "@wasDirectChatDisplayName": { + "type": "text", + "placeholders": { + "oldDisplayName": {} + } + }, + "pleaseClickOnLink": "Vă rugăm să deschideți linkul din email și apoi să procedați.", + "@pleaseClickOnLink": { + "type": "text", + "placeholders": {} + }, + "reportUser": "Reportați utilizator", + "@reportUser": {}, + "encryptionNotEnabled": "Criptare nu e activată", + "@encryptionNotEnabled": { + "type": "text", + "placeholders": {} + }, + "publicRooms": "Camere Publice", + "@publicRooms": { + "type": "text", + "placeholders": {} + }, + "addToBundle": "Adăugați în pachet", + "@addToBundle": {}, + "theyDontMatch": "Nu sunt asemănători", + "@theyDontMatch": { + "type": "text", + "placeholders": {} + }, + "loadingPleaseWait": "Încărcând... Vă rugăm să așteptați.", + "@loadingPleaseWait": { + "type": "text", + "placeholders": {} + }, + "dateWithoutYear": "{month}-{day}", + "@dateWithoutYear": { + "type": "text", + "placeholders": { + "month": {}, + "day": {} + } + }, + "theyMatch": "Sunt asemănători", + "@theyMatch": { + "type": "text", + "placeholders": {} + }, + "title": "FluffyChat", + "@title": { + "description": "Title for the application", + "type": "text", + "placeholders": {} + }, + "toggleMuted": "Comutați amuțeștarea", + "@toggleMuted": { + "type": "text", + "placeholders": {} + }, + "scanQrCode": "Scanați cod QR", + "@scanQrCode": {}, + "addAccount": "Adăugați cont", + "@addAccount": {}, + "experimentalVideoCalls": "Apeluri video experimentale", + "@experimentalVideoCalls": {}, + "confirmEventUnpin": "Sunteți sigur că doriți să anulați permanent fixarea evenimentului?", + "@confirmEventUnpin": {}, + "emojis": "Emoji-uri", + "@emojis": {}, + "switchToAccount": "Schimbați la contul {number}", + "@switchToAccount": { + "type": "number", + "placeholders": { + "number": {} + } + }, + "nextAccount": "Contul următor", + "@nextAccount": {}, + "indexedDbErrorTitle": "Probleme cu modul privat", + "@indexedDbErrorTitle": {}, + "users": "Utilizatori", + "@users": {}, + "startFirstChat": "Începeți primul chatul vostru", + "@startFirstChat": {}, + "callingPermissions": "Permisiuni de apel", + "@callingPermissions": {}, + "callingAccount": "Cont de apel", + "@callingAccount": {}, + "foregroundServiceRunning": "Această notificare apare când serviciul de foreground rulează.", + "@foregroundServiceRunning": {}, + "callingAccountDetails": "Permite FluffyChat să folosească aplicația de apeluri nativă android.", + "@callingAccountDetails": {}, + "appearOnTop": "Apare deasupra", + "@appearOnTop": {}, + "appearOnTopDetails": "Permite aplicația să apare deasupra (nu este necesar dacă aveți FluffyChat stabilit ca cont de apeluri)", + "@appearOnTopDetails": {}, + "currentlyActive": "Activ acum", + "@currentlyActive": { + "type": "text", + "placeholders": {} + }, + "containsDisplayName": "Conține displayname", + "@containsDisplayName": { + "type": "text", + "placeholders": {} + }, + "isTyping": "tastează…", + "@isTyping": { + "type": "text", + "placeholders": {} + }, + "chatBackup": "Backup de chat", + "@chatBackup": { + "type": "text", + "placeholders": {} + }, + "repeatPassword": "Repetați parola", + "@repeatPassword": {}, + "changeTheme": "Schimbați tema aplicației", + "@changeTheme": { + "type": "text", + "placeholders": {} + }, + "chatHasBeenAddedToThisSpace": "Chatul a fost adăugat la acest spațiu", + "@chatHasBeenAddedToThisSpace": {}, + "clearArchive": "Ștergeți arhiva", + "@clearArchive": {}, + "commandHint_markasdm": "Marcați ca cameră de mesaje directe", + "@commandHint_markasdm": {}, + "commandHint_markasgroup": "Marcați ca grup", + "@commandHint_markasgroup": {}, + "commandHint_ban": "Interziceți acesul utilizatorului ales din această cameră", + "@commandHint_ban": { + "type": "text", + "description": "Usage hint for the command /ban" + }, + "commandHint_clearcache": "Ștergeți cache", + "@commandHint_clearcache": { + "type": "text", + "description": "Usage hint for the command /clearcache" + }, + "commandHint_create": "Creați un grup de chat gol\nFolosiți --no-encryption să dezactivați criptare", + "@commandHint_create": { + "type": "text", + "description": "Usage hint for the command /create" + }, + "commandHint_discardsession": "Renunțați sesiunea", + "@commandHint_discardsession": { + "type": "text", + "description": "Usage hint for the command /discardsession" + }, + "commandHint_kick": "Dați afară pe utilizatorul ales din această cameră", + "@commandHint_kick": { + "type": "text", + "description": "Usage hint for the command /kick" + }, + "commandHint_leave": "Renunțați la această cameră", + "@commandHint_leave": { + "type": "text", + "description": "Usage hint for the command /leave" + }, + "commandHint_myroomavatar": "Alegeți un avatar pentru această cameră (foloșește mxc-uri)", + "@commandHint_myroomavatar": { + "type": "text", + "description": "Usage hint for the command /myroomavatar" + }, + "commandHint_myroomnick": "Alegeți un displayname pentru această cameră", + "@commandHint_myroomnick": { + "type": "text", + "description": "Usage hint for the command /myroomnick" + }, + "commandHint_op": "Stabiliți nivelul de putere a utilizatorul ales (implicit: 50)", + "@commandHint_op": { + "type": "text", + "description": "Usage hint for the command /op" + }, + "commandHint_plain": "Trimiteți text simplu/neformatat", + "@commandHint_plain": { + "type": "text", + "description": "Usage hint for the command /plain" + }, + "commandHint_react": "Trimiteți răspuns ca reacție", + "@commandHint_react": { + "type": "text", + "description": "Usage hint for the command /react" + }, + "commandHint_send": "Trimiteți text", + "@commandHint_send": { + "type": "text", + "description": "Usage hint for the command /send" + }, + "commandInvalid": "Comandă nevalibilă", + "@commandInvalid": { + "type": "text" + }, + "compareEmojiMatch": "Vă rugăm să comparați emoji-urile", + "@compareEmojiMatch": { + "type": "text", + "placeholders": {} + }, + "compareNumbersMatch": "Vă rugăm să comparați numerele", + "@compareNumbersMatch": { + "type": "text", + "placeholders": {} + }, + "couldNotDecryptMessage": "Dezcriptarea mesajului a eșuat: {error}", + "@couldNotDecryptMessage": { + "type": "text", + "placeholders": { + "error": {} + } + }, + "create": "Creați", + "@create": { + "type": "text", + "placeholders": {} + }, + "createdTheChat": "💬{username} a creat chatul", + "@createdTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "createNewSpace": "Spațiu nou", + "@createNewSpace": { + "type": "text", + "placeholders": {} + }, + "dateWithYear": "{year}-{month}-{day}", + "@dateWithYear": { + "type": "text", + "placeholders": { + "year": {}, + "month": {}, + "day": {} + } + }, + "allRooms": "Toate chaturi de grup", + "@allRooms": { + "type": "text", + "placeholders": {} + }, + "forward": "Înainte", + "@forward": { + "type": "text", + "placeholders": {} + }, + "groups": "Grupuri", + "@groups": { + "type": "text", + "placeholders": {} + }, + "hideRedactedEvents": "Ascunde evenimente redactate", + "@hideRedactedEvents": { + "type": "text", + "placeholders": {} + }, + "hideUnknownEvents": "Ascunde evenimente necunoscute", + "@hideUnknownEvents": { + "type": "text", + "placeholders": {} + }, + "identity": "Identitate", + "@identity": { + "type": "text", + "placeholders": {} + }, + "ignore": "Ignorați", + "@ignore": { + "type": "text", + "placeholders": {} + }, + "ignoredUsers": "Utilizatori ignorați", + "@ignoredUsers": { + "type": "text", + "placeholders": {} + }, + "iHaveClickedOnLink": "Am făcut click pe link", + "@iHaveClickedOnLink": { + "type": "text", + "placeholders": {} + }, + "incorrectPassphraseOrKey": "Parolă sau cheie de recuperare incorectă", + "@incorrectPassphraseOrKey": { + "type": "text", + "placeholders": {} + }, + "inoffensive": "Inofensiv", + "@inoffensive": { + "type": "text", + "placeholders": {} + }, + "inviteContact": "Invitați contact", + "@inviteContact": { + "type": "text", + "placeholders": {} + }, + "inviteContactToGroup": "Invitați contact la {groupName}", + "@inviteContactToGroup": { + "type": "text", + "placeholders": { + "groupName": {} + } + }, + "inviteText": "{username} v-a invitat la FluffyChat.\n1. Instalați FluffyChat: https://fluffychat.im\n2. Înregistrați-vă sau conectați-vă\n3. Deschideți invitația: {link}", + "@inviteText": { + "type": "text", + "placeholders": { + "username": {}, + "link": {} + } + }, + "joinedTheChat": "👋{username} a intrat în chat", + "@joinedTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "kicked": "👞{username} a dat afară pe {targetName}", + "@kicked": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "lastActiveAgo": "Ultima dată activ: {localizedTimeShort}", + "@lastActiveAgo": { + "type": "text", + "placeholders": { + "localizedTimeShort": {} + } + }, + "leave": "Renunțați", + "@leave": { + "type": "text", + "placeholders": {} + }, + "leftTheChat": "A plecat din chat", + "@leftTheChat": { + "type": "text", + "placeholders": {} + }, + "license": "Permis", + "@license": { + "type": "text", + "placeholders": {} + }, + "lightTheme": "Luminat", + "@lightTheme": { + "type": "text", + "placeholders": {} + }, + "loadCountMoreParticipants": "Încărcați încă mai {count} participanți", + "@loadCountMoreParticipants": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "dehydrate": "Exportați sesiunea și ștergeți dispozitivul", + "@dehydrate": {}, + "dehydrateTor": "Utilizatori de TOR: Exportați sesiunea", + "@dehydrateTor": {}, + "dehydrateTorLong": "Pentru utilizatori de TOR, este recomandat să exportați sesiunea înainte de a închideți fereastra.", + "@dehydrateTorLong": {}, + "hydrateTor": "Utilizatori TOR: Importați sesiune exportată", + "@hydrateTor": {}, + "hydrateTorLong": "Ați exportat sesiunea vostră ultima dată pe TOR? Importați-o repede și continuați să conversați.", + "@hydrateTorLong": {}, + "hydrate": "Restaurați din fișier backup", + "@hydrate": {}, + "loadMore": "Încarcă mai multe…", + "@loadMore": { + "type": "text", + "placeholders": {} + }, + "logout": "Deconectați-vă", + "@logout": { + "type": "text", + "placeholders": {} + }, + "mention": "Menționați", + "@mention": { + "type": "text", + "placeholders": {} + }, + "messages": "Mesaje", + "@messages": { + "type": "text", + "placeholders": {} + }, + "moderator": "Moderator", + "@moderator": { + "type": "text", + "placeholders": {} + }, + "noEmotesFound": "Nu s-a găsit nici un emote. 😕", + "@noEmotesFound": { + "type": "text", + "placeholders": {} + }, + "noEncryptionForPublicRooms": "Criptare nu poate fi activată până când camera este accesibilă public.", + "@noEncryptionForPublicRooms": { + "type": "text", + "placeholders": {} + }, + "none": "Niciunul", + "@none": { + "type": "text", + "placeholders": {} + }, + "noPermission": "Fără permisie", + "@noPermission": { + "type": "text", + "placeholders": {} + }, + "noRoomsFound": "Nici o cameră nu s-a găsit…", + "@noRoomsFound": { + "type": "text", + "placeholders": {} + }, + "notificationsEnabledForThisAccount": "Notificări activate pentru acest cont", + "@notificationsEnabledForThisAccount": { + "type": "text", + "placeholders": {} + }, + "obtainingLocation": "Obținând locație…", + "@obtainingLocation": { + "type": "text", + "placeholders": {} + }, + "offline": "Offline", + "@offline": { + "type": "text", + "placeholders": {} + }, + "oopsSomethingWentWrong": "Ups, ceva a eșuat…", + "@oopsSomethingWentWrong": { + "type": "text", + "placeholders": {} + }, + "reject": "Respingeți", + "@reject": { + "type": "text", + "placeholders": {} + }, + "unbanFromChat": "Revoca interzicerea din chat", + "@unbanFromChat": { + "type": "text", + "placeholders": {} + }, + "roomHasBeenUpgraded": "Camera a fost actualizată", + "@roomHasBeenUpgraded": { + "type": "text", + "placeholders": {} + }, + "setCustomEmotes": "Stabiliți emoji-uri personalizate", + "@setCustomEmotes": { + "type": "text", + "placeholders": {} + }, + "setPermissionsLevel": "Stabiliți nivelul de permisii", + "@setPermissionsLevel": { + "type": "text", + "placeholders": {} + }, + "singlesignon": "Autentificare unică", + "@singlesignon": { + "type": "text", + "placeholders": {} + }, + "startedACall": "{senderName} a început un apel", + "@startedACall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "statusExampleMessage": "Ce faceți?", + "@statusExampleMessage": { + "type": "text", + "placeholders": {} + }, + "tooManyRequestsWarning": "Prea multe cereri. Vă rugăm să încercați din nou mai tărziu!", + "@tooManyRequestsWarning": { + "type": "text", + "placeholders": {} + }, + "unknownEvent": "Evenimet necunoscut '{type}'", + "@unknownEvent": { + "type": "text", + "placeholders": { + "type": {} + } + }, + "verified": "Verificat", + "@verified": { + "type": "text", + "placeholders": {} + }, + "verifyStart": "Începeți verificare", + "@verifyStart": { + "type": "text", + "placeholders": {} + }, + "videoCall": "Apel video", + "@videoCall": { + "type": "text", + "placeholders": {} + }, + "visibilityOfTheChatHistory": "Vizibilitatea istoria chatului", + "@visibilityOfTheChatHistory": { + "type": "text", + "placeholders": {} + }, + "visibleForAllParticipants": "Vizibil pentru toți participanți", + "@visibleForAllParticipants": { + "type": "text", + "placeholders": {} + }, + "waitingPartnerEmoji": "Așteptând pe partenerul să accepte emoji-ul…", + "@waitingPartnerEmoji": { + "type": "text", + "placeholders": {} + }, + "waitingPartnerNumbers": "Așteptând pe partenerul să accepte numerele…", + "@waitingPartnerNumbers": { + "type": "text", + "placeholders": {} + }, + "warning": "Avertizment!", + "@warning": { + "type": "text", + "placeholders": {} + }, + "whoCanPerformWhichAction": "Cine poate face care acțiune", + "@whoCanPerformWhichAction": { + "type": "text", + "placeholders": {} + }, + "whoIsAllowedToJoinThisGroup": "Cine se poate alătura la acest grup", + "@whoIsAllowedToJoinThisGroup": { + "type": "text", + "placeholders": {} + }, + "whyDoYouWantToReportThis": "De ce doriți să reportați acest conținut?", + "@whyDoYouWantToReportThis": { + "type": "text", + "placeholders": {} + }, + "wipeChatBackup": "Ștergeți backup-ul vostru de chat să creați o nouă cheie de recuperare?", + "@wipeChatBackup": { + "type": "text", + "placeholders": {} + }, + "withTheseAddressesRecoveryDescription": "Cu acestea adrese puteți să vă recuperați parola.", + "@withTheseAddressesRecoveryDescription": { + "type": "text", + "placeholders": {} + }, + "youHaveBeenBannedFromThisChat": "Ați fost interzis din acest chat", + "@youHaveBeenBannedFromThisChat": { + "type": "text", + "placeholders": {} + }, + "messageInfo": "Info mesajului", + "@messageInfo": {}, + "time": "Timp", + "@time": {}, + "messageType": "Fel de mesaj", + "@messageType": {}, + "sender": "Trimițător", + "@sender": {}, + "openGallery": "Deschideți galeria", + "@openGallery": {}, + "removeFromSpace": "Eliminați din spațiu", + "@removeFromSpace": {}, + "publish": "Publicați", + "@publish": {}, + "videoWithSize": "Video ({size})", + "@videoWithSize": { + "type": "text", + "placeholders": { + "size": {} + } + }, + "unsupportedAndroidVersionLong": "Această funcție are nevoie de o versiune de Android mai nouă. Vă rugăm să verificați dacă sunt actualizări sau suport de la Lineage OS.", + "@unsupportedAndroidVersionLong": {}, + "dismiss": "Respingeți", + "@dismiss": {}, + "widgetVideo": "Video", + "@widgetVideo": {}, + "widgetEtherpad": "Notiță text", + "@widgetEtherpad": {}, + "widgetJitsi": "Jitsi Meet", + "@widgetJitsi": {}, + "widgetUrlError": "Acest URL nu este valibil.", + "@widgetUrlError": {}, + "widgetNameError": "Vă rugăm să introduceți un nume de afișare.", + "@widgetNameError": {}, + "errorAddingWidget": "Adăugarea widget-ului a eșuat.", + "@errorAddingWidget": {}, + "youRejectedTheInvitation": "Ați respins invitația", + "@youRejectedTheInvitation": {}, + "youJoinedTheChat": "Va-ți alăturat la chat", + "@youJoinedTheChat": {}, + "youAcceptedTheInvitation": "👍Ați acceptat invitația", + "@youAcceptedTheInvitation": {}, + "youHaveWithdrawnTheInvitationFor": "Ați retras invitația pentru {user}", + "@youHaveWithdrawnTheInvitationFor": { + "placeholders": { + "user": {} + } + }, + "youInvitedBy": "📩Ați fost invitat de {user}", + "@youInvitedBy": { + "placeholders": { + "user": {} + } + }, + "unlockOldMessages": "Deblocați mesajele vechi", + "@unlockOldMessages": {}, + "youInvitedUser": "📩Ați invitat pe {user}", + "@youInvitedUser": { + "placeholders": { + "user": {} + } + }, + "youKicked": "👞Ați dat afară pe {user}", + "@youKicked": { + "placeholders": { + "user": {} + } + }, + "youUnbannedUser": "Ați ridicat interzicerea lui {user}", + "@youUnbannedUser": { + "placeholders": { + "user": {} + } + }, + "storeInAndroidKeystore": "Stoca în Android KeyStore", + "@storeInAndroidKeystore": {}, + "user": "Utilizator", + "@user": {}, + "custom": "Personalizat", + "@custom": {}, + "screenSharingDetail": "Partajați ecranul în FluffyChat", + "@screenSharingDetail": {}, + "storeSecurlyOnThisDevice": "Stoca sigur pe acest dispozitiv", + "@storeSecurlyOnThisDevice": {}, + "otherCallingPermissions": "Microfon, cameră și alte permisiuni lui FluffyChat", + "@otherCallingPermissions": {}, + "whyIsThisMessageEncrypted": "De ce este acest mesaj ilizibil?", + "@whyIsThisMessageEncrypted": {}, + "newSpace": "Spațiu nou", + "@newSpace": {}, + "enterSpace": "Intrați în spațiu", + "@enterSpace": {}, + "enterRoom": "Intrați în cameră", + "@enterRoom": {}, + "allSpaces": "Toate spațiile", + "@allSpaces": {}, + "numChats": "{number} chaturi", + "@numChats": { + "type": "number", + "placeholders": { + "number": {} + } + }, + "hideUnimportantStateEvents": "Ascundeți evenimente de stare neimportante", + "@hideUnimportantStateEvents": {}, + "doNotShowAgain": "Nu se mai apară din nou", + "@doNotShowAgain": {}, + "newSpaceDescription": "Spațiile vă permit să vă consolidați chaturile și să stabiliți comunități private sau publice.", + "@newSpaceDescription": {}, + "encryptThisChat": "Criptați acest chat", + "@encryptThisChat": {}, + "disableEncryptionWarning": "Pentru motive de securitate, nu este posibil să dezactivați criptarea unui chat în care criptare este activată.", + "@disableEncryptionWarning": {}, + "noBackupWarning": "Avertisment! Fără să activați backup de chat, veți pierde accesul la mesajele voastre criptate. E foarte recomandat să activați backup de chat înainte să vă deconectați.", + "@noBackupWarning": {}, + "noOtherDevicesFound": "Nu s-a găsit alte dispozitive", + "@noOtherDevicesFound": {}, + "fileHasBeenSavedAt": "Fișierul a fost salvat la {path}", + "@fileHasBeenSavedAt": { + "type": "text", + "placeholders": { + "path": {} + } + }, + "jump": "Săriți", + "@jump": {}, + "report": "reportați", + "@report": {}, + "jumpToLastReadMessage": "Săriți la ultimul citit mesaj", + "@jumpToLastReadMessage": {}, + "memberChanges": "Schimbări de membri", + "@memberChanges": { + "type": "text", + "placeholders": {} + }, + "guestsCanJoin": "Musafiri pot să se alăture", + "@guestsCanJoin": { + "type": "text", + "placeholders": {} + }, + "fileName": "Nume de fișier", + "@fileName": { + "type": "text", + "placeholders": {} + }, + "fontSize": "Mărimea fontului", + "@fontSize": { + "type": "text", + "placeholders": {} + }, + "fromJoining": "De la alăturare", + "@fromJoining": { + "type": "text", + "placeholders": {} + }, + "fromTheInvitation": "De la invitația", + "@fromTheInvitation": { + "type": "text", + "placeholders": {} + }, + "goToTheNewRoom": "Mergeți la camera nouă", + "@goToTheNewRoom": { + "type": "text", + "placeholders": {} + }, + "group": "Grup", + "@group": { + "type": "text", + "placeholders": {} + }, + "groupIsPublic": "Grupul este public", + "@groupIsPublic": { + "type": "text", + "placeholders": {} + }, + "guestsAreForbidden": "Musafiri sunt interziși", + "@guestsAreForbidden": { + "type": "text", + "placeholders": {} + }, + "kickedAndBanned": "🙅{username} a dat afară și a interzis pe {targetName} din cameră", + "@kickedAndBanned": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "dehydrateWarning": "Această actiune nu poate fi anulată. Asigurați-vă că păstrați fișierul backup.", + "@dehydrateWarning": {}, + "joinRoom": "Alăturați la cameră", + "@joinRoom": { + "type": "text", + "placeholders": {} + }, + "logInTo": "Conectați-vă la {homeserver}", + "@logInTo": { + "type": "text", + "placeholders": { + "homeserver": {} + } + }, + "locationDisabledNotice": "Servicile de locație sunt dezactivate. Vă rugăm să le activați să împărțiți locația voastră.", + "@locationDisabledNotice": { + "type": "text", + "placeholders": {} + }, + "noGoogleServicesWarning": "Se pare că nu aveți serviciile google pe dispozitivul vostru. Această decizie este bună pentru confidențialitatea voastră! Să primiți notificari push în FluffyChat vă recomandăm https://microg.org/ sau https://unifiedpush.org/.", + "@noGoogleServicesWarning": { + "type": "text", + "placeholders": {} + }, + "noMatrixServer": "{server1} nu este server matrix, înlocuiți cu {server2}?", + "@noMatrixServer": { + "type": "text", + "placeholders": { + "server1": {}, + "server2": {} + } + }, + "numUsersTyping": "{count} utilizatori tastează…", + "@numUsersTyping": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "offensive": "Ofensiv", + "@offensive": { + "type": "text", + "placeholders": {} + }, + "confirm": "Confirmați", + "@confirm": { + "type": "text", + "placeholders": {} + }, + "or": "Sau", + "@or": { + "type": "text", + "placeholders": {} + }, + "serverRequiresEmail": "Acest server trebuie să valideze emailul vostru pentru înregistrare.", + "@serverRequiresEmail": {}, + "waitingPartnerAcceptRequest": "Așteptând pe partenerul să accepte cererea…", + "@waitingPartnerAcceptRequest": { + "type": "text", + "placeholders": {} + }, + "darkTheme": "Întunecat", + "@darkTheme": { + "type": "text", + "placeholders": {} + }, + "sharedTheLocation": "{username} sa partajat locația", + "@sharedTheLocation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "setInvitationLink": "Stabiliți linkul de invitație", + "@setInvitationLink": { + "type": "text", + "placeholders": {} + }, + "transferFromAnotherDevice": "Transfera de la alt dispozitiv", + "@transferFromAnotherDevice": { + "type": "text", + "placeholders": {} + }, + "synchronizingPleaseWait": "Sincronizează... Vă rugăm să așteptați.", + "@synchronizingPleaseWait": { + "type": "text", + "placeholders": {} + }, + "systemTheme": "Sistem", + "@systemTheme": { + "type": "text", + "placeholders": {} + }, + "toggleUnread": "Marcați Citit/Necitit", + "@toggleUnread": { + "type": "text", + "placeholders": {} + }, + "tryToSendAgain": "Încercați să trimiteți din nou", + "@tryToSendAgain": { + "type": "text", + "placeholders": {} + }, + "unavailable": "Nedisponibil", + "@unavailable": { + "type": "text", + "placeholders": {} + }, + "userAndUserAreTyping": "{username} și {username2} tastează…", + "@userAndUserAreTyping": { + "type": "text", + "placeholders": { + "username": {}, + "username2": {} + } + }, + "userLeftTheChat": "🚪{username} a plecat din chat", + "@userLeftTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "userAndOthersAreTyping": "{username} și {count} alți tastează…", + "@userAndOthersAreTyping": { + "type": "text", + "placeholders": { + "username": {}, + "count": {} + } + }, + "userSentUnknownEvent": "{username} a trimis un eveniment {type}", + "@userSentUnknownEvent": { + "type": "text", + "placeholders": { + "username": {}, + "type": {} + } + }, + "unverified": "Neverificat", + "@unverified": {}, + "verifyTitle": "Verificând celălalt cont", + "@verifyTitle": { + "type": "text", + "placeholders": {} + }, + "visibleForEveryone": "Vizibil pentru toți", + "@visibleForEveryone": { + "type": "text", + "placeholders": {} + }, + "readUpToHere": "Citit până aici", + "@readUpToHere": {}, + "changedTheHistoryVisibility": "{username} a schimbat vizibilitatea istoriei chatului", + "@changedTheHistoryVisibility": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "copy": "Copiați", + "@copy": { + "type": "text", + "placeholders": {} + }, + "displaynameHasBeenChanged": "Displayname a fost schimbat", + "@displaynameHasBeenChanged": { + "type": "text", + "placeholders": {} + }, + "invitedUsersOnly": "Numai utilizatori invitați", + "@invitedUsersOnly": { + "type": "text", + "placeholders": {} + }, + "configureChat": "Configurați chat", + "@configureChat": { + "type": "text", + "placeholders": {} + }, + "copiedToClipboard": "Copiat în clipboard", + "@copiedToClipboard": { + "type": "text", + "placeholders": {} + }, + "device": "Dispozitiv", + "@device": { + "type": "text", + "placeholders": {} + }, + "username": "Nume de utilizator", + "@username": { + "type": "text", + "placeholders": {} + }, + "sentAnAudio": "🎤{username} a trimis audio", + "@sentAnAudio": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAFile": "📁{username} a trimis un fișier", + "@sentAFile": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "indexedDbErrorLong": "Stocarea de mesaje nu este activat implicit în modul privat.\nVă rugăm să vizitați\n- about:config\n- stabiliți dom.indexedDB.privateBrowsing.enabled la true\nAstfel, nu este posibil să folosiți FluffyChat.", + "@indexedDbErrorLong": {}, + "addWidget": "Adăugați widget", + "@addWidget": {}, + "locationPermissionDeniedNotice": "Permisiunea locației blocată. Vă rugăm să o dezblocați să împărțiți locația voastră.", + "@locationPermissionDeniedNotice": { + "type": "text", + "placeholders": {} + }, + "newChat": "Chat nou", + "@newChat": { + "type": "text", + "placeholders": {} + }, + "newMessageInFluffyChat": "💬 Mesaj nou în FluffyChat", + "@newMessageInFluffyChat": { + "type": "text", + "placeholders": {} + }, + "sentAPicture": "🖼️ {username} a trimis o poză", + "@sentAPicture": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAVideo": "🎥{username} a trimis un video", + "@sentAVideo": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentASticker": "😊 {username} a trimis un sticker", + "@sentASticker": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "storeInSecureStorageDescription": "Păstrați cheia de recuperare în stocarea sigură a acestui dispozitiv.", + "@storeInSecureStorageDescription": {}, + "saveKeyManuallyDescription": "Activați dialogul de partajare sistemului sau folosiți clipboard-ul să salvați manual această cheie.", + "@saveKeyManuallyDescription": {}, + "countFiles": "{count} fișiere", + "@countFiles": { + "placeholders": { + "count": {} + } + }, + "hugContent": "{senderName} vă îmbrățișează", + "@hugContent": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "storeInAppleKeyChain": "Stoca în Apple KeyChain", + "@storeInAppleKeyChain": {}, + "addEmail": "Adăugați email", + "@addEmail": { + "type": "text", + "placeholders": {} + }, + "confirmMatrixId": "Vă rugăm să confirmați Matrix ID-ul vostru să ștergeți contul vostru.", + "@confirmMatrixId": {}, + "cuddleContent": "{senderName} vă îmbrățișează", + "@cuddleContent": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "supposedMxid": "ID-ul ar trebuii să fie {mxid}", + "@supposedMxid": { + "type": "text", + "placeholders": { + "mxid": {} + } + }, + "commandHint_html": "Trimiteți text format ca HTML", + "@commandHint_html": { + "type": "text", + "description": "Usage hint for the command /html" + }, + "addToSpace": "Adăugați la spațiu", + "@addToSpace": {}, + "commandHint_hug": "Trimiteți o îmbrățișare", + "@commandHint_hug": {}, + "badServerVersionsException": "Homeserver-ul suportă versiunele de Spec următoare:\n{serverVersions}\nDar această aplicație suportă numai {supportedVersions}", + "@badServerVersionsException": { + "type": "text", + "placeholders": { + "serverVersions": {}, + "supportedVersions": {} + } + }, + "badServerLoginTypesException": "Homeserver-ul suportă următoarele feluri de login:\n{serverVersions}\nDar această aplicație suportă numai:\n{supportedVersions}", + "@badServerLoginTypesException": { + "type": "text", + "placeholders": { + "serverVersions": {}, + "supportedVersions": {} + } + }, + "changedTheGuestAccessRulesTo": "{username} a schimbat regulile pentru acesul musafirilor la: {rules}", + "@changedTheGuestAccessRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "changedTheJoinRulesTo": "{username} a schimbat regulile de alăturare la: {joinRules}", + "@changedTheJoinRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "joinRules": {} + } + }, + "yourChatBackupHasBeenSetUp": "Backup-ul vostru de chat a fost configurat.", + "@yourChatBackupHasBeenSetUp": {}, + "cantOpenUri": "Nu se poate deschide URI-ul {uri}", + "@cantOpenUri": { + "type": "text", + "placeholders": { + "uri": {} + } + }, + "changedTheDisplaynameTo": "{username} s-a schimbat displayname la: '{displayname}'", + "@changedTheDisplaynameTo": { + "type": "text", + "placeholders": { + "username": {}, + "displayname": {} + } + }, + "changedTheGuestAccessRules": "{username} a schimbat regulile pentru acesul musafirilor", + "@changedTheGuestAccessRules": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changeTheHomeserver": "Schimbați homeserver-ul", + "@changeTheHomeserver": { + "type": "text", + "placeholders": {} + }, + "chatBackupDescription": "Mesajele voastre vechi sunt sigurate cu o cheie de recuperare. Vă rugăm să nu o pierdeți.", + "@chatBackupDescription": { + "type": "text", + "placeholders": {} + }, + "contentHasBeenReported": "Conținutul a fost reportat la administratori serverului", + "@contentHasBeenReported": { + "type": "text", + "placeholders": {} + }, + "chatDetails": "Detalii de chat", + "@chatDetails": { + "type": "text", + "placeholders": {} + }, + "commandHint_dm": "Porniți un chat direct\nFolosiți --no-encryption să dezactivați criptare", + "@commandHint_dm": { + "type": "text", + "description": "Usage hint for the command /dm" + }, + "commandHint_me": "Descrieți-vă", + "@commandHint_me": { + "type": "text", + "description": "Usage hint for the command /me" + }, + "contactHasBeenInvitedToTheGroup": "Contactul a fost invitat la grup", + "@contactHasBeenInvitedToTheGroup": { + "type": "text", + "placeholders": {} + }, + "containsUserName": "Conține nume de utilizator", + "@containsUserName": { + "type": "text", + "placeholders": {} + }, + "copyToClipboard": "Copiați în clipboard", + "@copyToClipboard": { + "type": "text", + "placeholders": {} + }, + "countParticipants": "{count} participanți", + "@countParticipants": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "delete": "Ștergeți", + "@delete": { + "type": "text", + "placeholders": {} + }, + "deactivateAccountWarning": "Această acțiune va dezactiva contul vostru. Nu poate fi anulat! Sunteți sigur?", + "@deactivateAccountWarning": { + "type": "text", + "placeholders": {} + }, + "deleteAccount": "Ștergeți contul", + "@deleteAccount": { + "type": "text", + "placeholders": {} + }, + "defaultPermissionLevel": "Nivel de permisiuni implicită", + "@defaultPermissionLevel": { + "type": "text", + "placeholders": {} + }, + "deleteMessage": "Ștergeți mesajul", + "@deleteMessage": { + "type": "text", + "placeholders": {} + }, + "downloadFile": "Descărcați fișierul", + "@downloadFile": { + "type": "text", + "placeholders": {} + }, + "enableEmotesGlobally": "Activați pachet de emote global", + "@enableEmotesGlobally": { + "type": "text", + "placeholders": {} + }, + "everythingReady": "Totul e gata!", + "@everythingReady": { + "type": "text", + "placeholders": {} + }, + "editBlockedServers": "Editați servere blocate", + "@editBlockedServers": { + "type": "text", + "placeholders": {} + }, + "emoteInvalid": "Shortcode de emote nevalibil!", + "@emoteInvalid": { + "type": "text", + "placeholders": {} + }, + "edit": "Editați", + "@edit": { + "type": "text", + "placeholders": {} + }, + "editRoomAliases": "Schimbați pseudonimele camerei", + "@editRoomAliases": { + "type": "text", + "placeholders": {} + }, + "emptyChat": "Chat gol", + "@emptyChat": { + "type": "text", + "placeholders": {} + }, + "enableEncryption": "Activați criptare", + "@enableEncryption": { + "type": "text", + "placeholders": {} + }, + "encrypted": "Criptat", + "@encrypted": { + "type": "text", + "placeholders": {} + }, + "editDisplayname": "Schimbați displayname", + "@editDisplayname": { + "type": "text", + "placeholders": {} + }, + "editRoomAvatar": "Schimbați avatarul din cameră", + "@editRoomAvatar": { + "type": "text", + "placeholders": {} + }, + "emoteExists": "Emote deja există!", + "@emoteExists": { + "type": "text", + "placeholders": {} + }, + "emotePacks": "Pachete de emoturi din cameră", + "@emotePacks": { + "type": "text", + "placeholders": {} + }, + "emoteSettings": "Configurări Emote", + "@emoteSettings": { + "type": "text", + "placeholders": {} + }, + "emoteShortcode": "Shortcode de emote", + "@emoteShortcode": { + "type": "text", + "placeholders": {} + }, + "emoteWarnNeedToPick": "Trebuie să alegeți shortcode pentru emote și o imagine!", + "@emoteWarnNeedToPick": { + "type": "text", + "placeholders": {} + }, + "encryption": "Criptare", + "@encryption": { + "type": "text", + "placeholders": {} + }, + "enterAnEmailAddress": "Introduceți o adresă email", + "@enterAnEmailAddress": { + "type": "text", + "placeholders": {} + }, + "homeserver": "Homeserver", + "@homeserver": {}, + "errorObtainingLocation": "Obținerea locației a eșuat: {error}", + "@errorObtainingLocation": { + "type": "text", + "placeholders": { + "error": {} + } + }, + "ok": "Ok", + "@ok": { + "type": "text", + "placeholders": {} + }, + "youKickedAndBanned": "🙅Ați dat afară și interzis pe {user}", + "@youKickedAndBanned": { + "placeholders": { + "user": {} + } + }, + "noKeyForThisMessage": "Această chestie poate să se întâmple când mesajul a fost trimis înainte să vă conectați contul cu acest dispozitiv.\n\nO altă explicație ar fi dacă trimițătorul a blocat dispozitivul vostru sau ceva s-a întâmplat cu conexiunea la internet\n\nPuteți să citiți mesajul în o altă seșiune? Atunci puteți să transferați mesajul de acolo! Mergeți la Configurări > Dispozitive și verificați că dispozitivele s-au verificat. Când deschideți camera în viitor și ambele seșiune sunt în foreground, cheile va fi transmise automat. \n\nDoriți să îți păstrați cheile când deconectați sau schimbați dispozitive? Fiți atenți să activați backup de chat în configurări.", + "@noKeyForThisMessage": {}, + "sendAsText": "Trimiteți ca text", + "@sendAsText": { + "type": "text" + }, + "reportErrorDescription": "Ceva a eșuat. Vă rugăm să încercați din nou mai tărziu. Dacă doriți, puteți să reportați problema la dezvoltatori.", + "@reportErrorDescription": {}, + "openLinkInBrowser": "Deschideți linkul în browser", + "@openLinkInBrowser": {}, + "send": "Trimiteți", + "@send": { + "type": "text", + "placeholders": {} + }, + "sendAMessage": "Trimiteți un mesaj", + "@sendAMessage": { + "type": "text", + "placeholders": {} + }, + "sendAudio": "Trimiteți audio", + "@sendAudio": { + "type": "text", + "placeholders": {} + }, + "sendOriginal": "Trimiteți original", + "@sendOriginal": { + "type": "text", + "placeholders": {} + }, + "sendVideo": "Trimiteți video", + "@sendVideo": { + "type": "text", + "placeholders": {} + }, + "sendImage": "Trimiteți imagine", + "@sendImage": { + "type": "text", + "placeholders": {} + }, + "sendSticker": "Trimiteți sticker", + "@sendSticker": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterRecoveryKeyDescription": "Să vă deblocați mesajele vechi, vă rugăm să introduceți cheia de recuperare creată de o seșiune anterioră. Cheia de recuperare NU este parola voastră.", + "@pleaseEnterRecoveryKeyDescription": {}, + "separateChatTypes": "Afișați chaturi directe și grupuri separat", + "@separateChatTypes": { + "type": "text", + "placeholders": {} + }, + "setAsCanonicalAlias": "Stabiliți ca pseudonimul primar", + "@setAsCanonicalAlias": { + "type": "text", + "placeholders": {} + }, + "writeAMessage": "Scrieți un mesaj…", + "@writeAMessage": { + "type": "text", + "placeholders": {} + }, + "yes": "Da", + "@yes": { + "type": "text", + "placeholders": {} + }, + "markAsRead": "Marcați ca citit", + "@markAsRead": {}, + "oopsPushError": "Ups! Din păcate, o eroare s-a întâmplat cu stabilirea de notificări push.", + "@oopsPushError": { + "type": "text", + "placeholders": {} + }, + "oneClientLoggedOut": "Unul dintre clienților voștri a fost deconectat", + "@oneClientLoggedOut": {}, + "editBundlesForAccount": "Editați pachetele pentru acest cont", + "@editBundlesForAccount": {}, + "bundleName": "Numele pachetului", + "@bundleName": {}, + "link": "Link", + "@link": {}, + "passphraseOrKey": "frază de acces sau cheie de recuperare", + "@passphraseOrKey": { + "type": "text", + "placeholders": {} + }, + "password": "Parolă", + "@password": { + "type": "text", + "placeholders": {} + }, + "passwordForgotten": "Parola uitată", + "@passwordForgotten": { + "type": "text", + "placeholders": {} + }, + "passwordHasBeenChanged": "Parola a fost schimbată", + "@passwordHasBeenChanged": { + "type": "text", + "placeholders": {} + }, + "passwordRecovery": "Recuperare parolei", + "@passwordRecovery": { + "type": "text", + "placeholders": {} + }, + "people": "Persoane", + "@people": { + "type": "text", + "placeholders": {} + }, + "pickImage": "Alegeți o imagine", + "@pickImage": { + "type": "text", + "placeholders": {} + }, + "pleaseChoose": "Vă rugăm să alegeți", + "@pleaseChoose": { + "type": "text", + "placeholders": {} + }, + "pin": "Fixați", + "@pin": { + "type": "text", + "placeholders": {} + }, + "pleaseChooseAPasscode": "Vă rugăm să alegeți un passcode", + "@pleaseChooseAPasscode": { + "type": "text", + "placeholders": {} + }, + "pleaseEnter4Digits": "Vă rugăm să introduceți 4 cifre sau puteți să lăsați gol să dezactivați lacătul aplicației.", + "@pleaseEnter4Digits": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourPassword": "Vă rugăm să introduceți parola voastră", + "@pleaseEnterYourPassword": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourUsername": "Vă rugăm să introduceți username-ul vostru", + "@pleaseEnterYourUsername": { + "type": "text", + "placeholders": {} + }, + "pleaseFollowInstructionsOnWeb": "Vă rugăm să urmați instrucțiunele pe website și apoi să apăsați pe următor.", + "@pleaseFollowInstructionsOnWeb": { + "type": "text", + "placeholders": {} + }, + "reason": "Motiv", + "@reason": { + "type": "text", + "placeholders": {} + }, + "rejectedTheInvitation": "{username} a respins invitația", + "@rejectedTheInvitation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "redactedAnEvent": "{username} a redactat un eveniment", + "@redactedAnEvent": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "removeAllOtherDevices": "Eliminați toate celelalte dispozitive", + "@removeAllOtherDevices": { + "type": "text", + "placeholders": {} + }, + "removeYourAvatar": "Ștergeți avatarul", + "@removeYourAvatar": { + "type": "text", + "placeholders": {} + }, + "renderRichContent": "Reda conținut bogat al mesajelor", + "@renderRichContent": { + "type": "text", + "placeholders": {} + }, + "replaceRoomWithNewerVersion": "Înlocuiți camera cu versiune mai nouă", + "@replaceRoomWithNewerVersion": { + "type": "text", + "placeholders": {} + }, + "reply": "Răspundeți", + "@reply": { + "type": "text", + "placeholders": {} + }, + "reportMessage": "Raportați mesajul", + "@reportMessage": { + "type": "text", + "placeholders": {} + }, + "requestPermission": "Cereți permisiune", + "@requestPermission": { + "type": "text", + "placeholders": {} + }, + "saveFile": "Salvați fișierul", + "@saveFile": { + "type": "text", + "placeholders": {} + }, + "search": "Căutați", + "@search": { + "type": "text", + "placeholders": {} + }, + "recoveryKey": "Cheie de recuperare", + "@recoveryKey": {}, + "recoveryKeyLost": "Cheia de recuperare pierdută?", + "@recoveryKeyLost": {}, + "seenByUser": "Văzut de {username}", + "@seenByUser": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "muteChat": "Amuțați chatul", + "@muteChat": { + "type": "text", + "placeholders": {} + }, + "needPantalaimonWarning": "Vă rugăm să fiți conștienți că e nevoie de Pantalaimon să folosiți criptare end-to-end deocamdată.", + "@needPantalaimonWarning": { + "type": "text", + "placeholders": {} + }, + "autoplayImages": "Anima automatic stickere și emote animate", + "@autoplayImages": { + "type": "text", + "placeholder": {} + }, + "sendOnEnter": "Trimite cu tasta enter", + "@sendOnEnter": {}, + "changedTheChatPermissions": "{username} a schimbat permisiunile chatului", + "@changedTheChatPermissions": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "extremeOffensive": "De foarte mare ofensă", + "@extremeOffensive": { + "type": "text", + "placeholders": {} + }, + "id": "ID", + "@id": { + "type": "text", + "placeholders": {} + }, + "invitedUser": "📩{username} a invitat {targetName}", + "@invitedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "deviceKeys": "Cheile dispozitivului:", + "@deviceKeys": {}, + "pleaseEnterRecoveryKey": "Vă rugăm să introduceți cheia voastră de recuperare:", + "@pleaseEnterRecoveryKey": {}, + "newVerificationRequest": "Cerere de verificare nouă!", + "@newVerificationRequest": { + "type": "text", + "placeholders": {} + }, + "remove": "Eliminați", + "@remove": { + "type": "text", + "placeholders": {} + }, + "play": "Redați {fileName}", + "@play": { + "type": "text", + "placeholders": { + "fileName": {} + } + }, + "channelCorruptedDecryptError": "Criptarea a fost corupată", + "@channelCorruptedDecryptError": { + "type": "text", + "placeholders": {} + }, + "chooseAStrongPassword": "Alegeți o parolă robustă", + "@chooseAStrongPassword": { + "type": "text", + "placeholders": {} + }, + "commandHint_cuddle": "Trimiteți o îmbrățișare", + "@commandHint_cuddle": {}, + "googlyEyesContent": "{senderName} v-a trimis ochi googly", + "@googlyEyesContent": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "areYouSureYouWantToLogout": "Sunteți sigur că doriți să vă deconectați?", + "@areYouSureYouWantToLogout": { + "type": "text", + "placeholders": {} + }, + "changedTheRoomAliases": "{username} a schimbat pseudonimele camerei", + "@changedTheRoomAliases": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changeYourAvatar": "Schimbați avatarul vostru", + "@changeYourAvatar": { + "type": "text", + "placeholders": {} + }, + "commandHint_join": "Alăturați-vă la camera alesă", + "@commandHint_join": { + "type": "text", + "description": "Usage hint for the command /join" + }, + "allChats": "Toate Chaturile", + "@allChats": { + "type": "text", + "placeholders": {} + }, + "commandHint_invite": "Invitați utilizatorul ales la această cameră", + "@commandHint_invite": { + "type": "text", + "description": "Usage hint for the command /invite" + }, + "changeTheNameOfTheGroup": "Schimbați numele grupului", + "@changeTheNameOfTheGroup": { + "type": "text", + "placeholders": {} + }, + "commandHint_googly": "Trimiteți câțiva ochi googly", + "@commandHint_googly": {}, + "botMessages": "Mesaje Bot", + "@botMessages": { + "type": "text", + "placeholders": {} + }, + "all": "Toate", + "@all": { + "type": "text", + "placeholders": {} + }, + "blocked": "Blocat", + "@blocked": { + "type": "text", + "placeholders": {} + }, + "changedTheJoinRules": "{username} a schimbat regulile de alăturare", + "@changedTheJoinRules": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheProfileAvatar": "{username} s-a schimbat avatarul", + "@changedTheProfileAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "appLock": "Lacăt aplicație", + "@appLock": { + "type": "text", + "placeholders": {} + }, + "changedTheHistoryVisibilityTo": "{username} a schimbat vizibilitatea istoriei chatului la: {rules}", + "@changedTheHistoryVisibilityTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "close": "Închideți", + "@close": { + "type": "text", + "placeholders": {} + }, + "reopenChat": "Deschide din nou chatul", + "@reopenChat": {}, + "pleaseTryAgainLaterOrChooseDifferentServer": "Vă rugăm să încercați din nou mai târziu sau să alegeți un server diferit.", + "@pleaseTryAgainLaterOrChooseDifferentServer": {}, + "signInWithPassword": "Conectați-vă cu parolă", + "@signInWithPassword": {} +} From bc497ce738e7b50c154eaac305cd096ee4136fc9 Mon Sep 17 00:00:00 2001 From: Anonymous Date: Thu, 25 Jul 2024 05:09:49 +0000 Subject: [PATCH 115/288] Translated using Weblate (Serbian) Currently translated at 46.1% (301 of 652 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/sr/ --- assets/l10n/intl_sr.arb | 4016 ++++++++++++++++++--------------------- 1 file changed, 1804 insertions(+), 2212 deletions(-) diff --git a/assets/l10n/intl_sr.arb b/assets/l10n/intl_sr.arb index 2b0564d85e..afebfa2dbe 100644 --- a/assets/l10n/intl_sr.arb +++ b/assets/l10n/intl_sr.arb @@ -1,2213 +1,1805 @@ { - "@@last_modified": "2021-08-14 12:41:09.857024", - "about": "О програму", - "@about": { - "type": "text", - "placeholders": {} - }, - "accept": "Прихвати", - "@accept": { - "type": "text", - "placeholders": {} - }, - "acceptedTheInvitation": "{username} прихвата позивницу", - "@acceptedTheInvitation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "account": "Налог", - "@account": { - "type": "text", - "placeholders": {} - }, - "activatedEndToEndEncryption": "{username} укључи шифровање с краја на крај", - "@activatedEndToEndEncryption": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "addEmail": "Додај е-адресу", - "@addEmail": { - "type": "text", - "placeholders": {} - }, - "admin": "Админ", - "@admin": { - "type": "text", - "placeholders": {} - }, - "alias": "алијас", - "@alias": { - "type": "text", - "placeholders": {} - }, - "all": "Сви", - "@all": { - "type": "text", - "placeholders": {} - }, - "answeredTheCall": "{senderName} одговори на позив", - "@answeredTheCall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "anyoneCanJoin": "свако може да се придружи", - "@anyoneCanJoin": { - "type": "text", - "placeholders": {} - }, - "appLock": "Закључавање апликације", - "@appLock": { - "type": "text", - "placeholders": {} - }, - "archive": "Архива", - "@archive": { - "type": "text", - "placeholders": {} - }, - "areGuestsAllowedToJoin": "Да ли је гостима дозвољен приступ", - "@areGuestsAllowedToJoin": { - "type": "text", - "placeholders": {} - }, - "areYouSure": "Сигурни сте?", - "@areYouSure": { - "type": "text", - "placeholders": {} - }, - "areYouSureYouWantToLogout": "Заиста желите да се одјавите?", - "@areYouSureYouWantToLogout": { - "type": "text", - "placeholders": {} - }, - "askSSSSSign": "Да бисте могли да пријавите другу особу, унесите своју безбедносну фразу или кључ опоравка.", - "@askSSSSSign": { - "type": "text", - "placeholders": {} - }, - "askVerificationRequest": "Прихватате ли захтев за верификацију од корисника {username}?", - "@askVerificationRequest": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "badServerLoginTypesException": "Домаћи сервер подржава начине пријаве:\n{serverVersions}\nали ова апликација подржава само:\n{supportedVersions}", - "@badServerLoginTypesException": { - "type": "text", - "placeholders": { - "serverVersions": {}, - "supportedVersions": {} - } - }, - "badServerVersionsException": "Домаћи сервер подржава верзије:\n{serverVersions}\nали ова апликација подржава само {supportedVersions}", - "@badServerVersionsException": { - "type": "text", - "placeholders": { - "serverVersions": {}, - "supportedVersions": {} - } - }, - "banFromChat": "Забрани у ћаскању", - "@banFromChat": { - "type": "text", - "placeholders": {} - }, - "banned": "Забрањен", - "@banned": { - "type": "text", - "placeholders": {} - }, - "bannedUser": "{username} забрани корисника {targetName}", - "@bannedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "blockDevice": "Блокирај уређај", - "@blockDevice": { - "type": "text", - "placeholders": {} - }, - "blocked": "Блокиран", - "@blocked": { - "type": "text", - "placeholders": {} - }, - "botMessages": "Поруке Бота", - "@botMessages": { - "type": "text", - "placeholders": {} - }, - "cancel": "Откажи", - "@cancel": { - "type": "text", - "placeholders": {} - }, - "changeDeviceName": "Промени назив уређаја", - "@changeDeviceName": { - "type": "text", - "placeholders": {} - }, - "changedTheChatAvatar": "{username} промени аватар ћаскања", - "@changedTheChatAvatar": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheChatDescriptionTo": "{username} промени опис ћаскања у: „{description}“", - "@changedTheChatDescriptionTo": { - "type": "text", - "placeholders": { - "username": {}, - "description": {} - } - }, - "changedTheChatNameTo": "{username} промени назив ћаскања у: „{chatname}“", - "@changedTheChatNameTo": { - "type": "text", - "placeholders": { - "username": {}, - "chatname": {} - } - }, - "changedTheChatPermissions": "{username} измени дозволе ћаскања", - "@changedTheChatPermissions": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheDisplaynameTo": "{username} промени приказно име на: „{displayname}“", - "@changedTheDisplaynameTo": { - "type": "text", - "placeholders": { - "username": {}, - "displayname": {} - } - }, - "changedTheGuestAccessRules": "{username} измени правила за приступ гостију", - "@changedTheGuestAccessRules": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheGuestAccessRulesTo": "{username} измени правила за приступ гостију на: {rules}", - "@changedTheGuestAccessRulesTo": { - "type": "text", - "placeholders": { - "username": {}, - "rules": {} - } - }, - "changedTheHistoryVisibility": "{username} измени видљивост историје", - "@changedTheHistoryVisibility": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheHistoryVisibilityTo": "{username} измени видљивост историје на: {rules}", - "@changedTheHistoryVisibilityTo": { - "type": "text", - "placeholders": { - "username": {}, - "rules": {} - } - }, - "changedTheJoinRules": "{username} измени правила приступања", - "@changedTheJoinRules": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheJoinRulesTo": "{username} измени правила приступања на: {joinRules}", - "@changedTheJoinRulesTo": { - "type": "text", - "placeholders": { - "username": {}, - "joinRules": {} - } - }, - "changedTheProfileAvatar": "{username} измени свој аватар", - "@changedTheProfileAvatar": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheRoomAliases": "{username} измени алијас собе", - "@changedTheRoomAliases": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheRoomInvitationLink": "{username} измени везу позивнице", - "@changedTheRoomInvitationLink": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changePassword": "Измени лозинку", - "@changePassword": { - "type": "text", - "placeholders": {} - }, - "changeTheHomeserver": "Промени домаћи сервер", - "@changeTheHomeserver": { - "type": "text", - "placeholders": {} - }, - "changeTheme": "Измените изглед", - "@changeTheme": { - "type": "text", - "placeholders": {} - }, - "changeTheNameOfTheGroup": "Измени назив групе", - "@changeTheNameOfTheGroup": { - "type": "text", - "placeholders": {} - }, - "changeYourAvatar": "Измените свој аватар", - "@changeYourAvatar": { - "type": "text", - "placeholders": {} - }, - "channelCorruptedDecryptError": "Шифровање је покварено", - "@channelCorruptedDecryptError": { - "type": "text", - "placeholders": {} - }, - "chat": "Ћаскање", - "@chat": { - "type": "text", - "placeholders": {} - }, - "chatBackup": "Копија ћаскања", - "@chatBackup": { - "type": "text", - "placeholders": {} - }, - "chatBackupDescription": "Ваша резервна копија ћаскања је обезбеђена кључем. Немојте да га изгубите.", - "@chatBackupDescription": { - "type": "text", - "placeholders": {} - }, - "chatDetails": "Детаљи ћаскања", - "@chatDetails": { - "type": "text", - "placeholders": {} - }, - "chats": "Ћаскања", - "@chats": { - "type": "text", - "placeholders": {} - }, - "chooseAStrongPassword": "Изаберите јаку лозинку", - "@chooseAStrongPassword": { - "type": "text", - "placeholders": {} - }, - "clearArchive": "Очисти архиву", - "@clearArchive": {}, - "close": "Затвори", - "@close": { - "type": "text", - "placeholders": {} - }, - "commandHint_ban": "Блокирај задатог корисника за ову собу", - "@commandHint_ban": { - "type": "text", - "description": "Usage hint for the command /ban" - }, - "commandHint_html": "Шаљи ХТМЛ обликован текст", - "@commandHint_html": { - "type": "text", - "description": "Usage hint for the command /html" - }, - "commandHint_invite": "Позови задатог корисника у собу", - "@commandHint_invite": { - "type": "text", - "description": "Usage hint for the command /invite" - }, - "commandHint_join": "Придружи се наведеној соби", - "@commandHint_join": { - "type": "text", - "description": "Usage hint for the command /join" - }, - "commandHint_kick": "Уклони задатог корисника из собе", - "@commandHint_kick": { - "type": "text", - "description": "Usage hint for the command /kick" - }, - "commandHint_leave": "Напусти ову собу", - "@commandHint_leave": { - "type": "text", - "description": "Usage hint for the command /leave" - }, - "commandHint_me": "Опишите себе", - "@commandHint_me": { - "type": "text", - "description": "Usage hint for the command /me" - }, - "commandHint_myroomnick": "Поставља ваш надимак за ову собу", - "@commandHint_myroomnick": { - "type": "text", - "description": "Usage hint for the command /myroomnick" - }, - "commandHint_op": "Подеси ниво задатог корисника (подразумевано: 50)", - "@commandHint_op": { - "type": "text", - "description": "Usage hint for the command /op" - }, - "commandHint_plain": "Шаљи неформатиран текст", - "@commandHint_plain": { - "type": "text", - "description": "Usage hint for the command /plain" - }, - "commandHint_react": "Шаљи одговор као реакцију", - "@commandHint_react": { - "type": "text", - "description": "Usage hint for the command /react" - }, - "commandHint_send": "Пошаљи текст", - "@commandHint_send": { - "type": "text", - "description": "Usage hint for the command /send" - }, - "commandHint_unban": "Скини забрану задатом кориснику за ову собу", - "@commandHint_unban": { - "type": "text", - "description": "Usage hint for the command /unban" - }, - "compareEmojiMatch": "Упоредите и проверите да су емоџији идентични као на другом уређају:", - "@compareEmojiMatch": { - "type": "text", - "placeholders": {} - }, - "compareNumbersMatch": "Упоредите и проверите да су следећи бројеви идентични као на другом уређају:", - "@compareNumbersMatch": { - "type": "text", - "placeholders": {} - }, - "configureChat": "Подешавање ћаскања", - "@configureChat": { - "type": "text", - "placeholders": {} - }, - "confirm": "Потврди", - "@confirm": { - "type": "text", - "placeholders": {} - }, - "connect": "Повежи се", - "@connect": { - "type": "text", - "placeholders": {} - }, - "contactHasBeenInvitedToTheGroup": "Особа је позвана у групу", - "@contactHasBeenInvitedToTheGroup": { - "type": "text", - "placeholders": {} - }, - "containsDisplayName": "Садржи приказно име", - "@containsDisplayName": { - "type": "text", - "placeholders": {} - }, - "containsUserName": "Садржи корисничко име", - "@containsUserName": { - "type": "text", - "placeholders": {} - }, - "contentHasBeenReported": "Садржај је пријављен администраторима сервера", - "@contentHasBeenReported": { - "type": "text", - "placeholders": {} - }, - "copiedToClipboard": "Копирано у клипборд", - "@copiedToClipboard": { - "type": "text", - "placeholders": {} - }, - "copy": "Копирај", - "@copy": { - "type": "text", - "placeholders": {} - }, - "copyToClipboard": "Копирај у клипборд", - "@copyToClipboard": { - "type": "text", - "placeholders": {} - }, - "couldNotDecryptMessage": "Не могу да дешифрујем поруку: {error}", - "@couldNotDecryptMessage": { - "type": "text", - "placeholders": { - "error": {} - } - }, - "countParticipants": "учесника: {count}", - "@countParticipants": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "create": "Направи", - "@create": { - "type": "text", - "placeholders": {} - }, - "createdTheChat": "{username} направи ћаскање", - "@createdTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "currentlyActive": "Тренутно активно", - "@currentlyActive": { - "type": "text", - "placeholders": {} - }, - "darkTheme": "тамни", - "@darkTheme": { - "type": "text", - "placeholders": {} - }, - "dateAndTimeOfDay": "{date}, {timeOfDay}", - "@dateAndTimeOfDay": { - "type": "text", - "placeholders": { - "date": {}, - "timeOfDay": {} - } - }, - "dateWithoutYear": "{day} {month}", - "@dateWithoutYear": { - "type": "text", - "placeholders": { - "month": {}, - "day": {} - } - }, - "dateWithYear": "{day} {month} {year}", - "@dateWithYear": { - "type": "text", - "placeholders": { - "year": {}, - "month": {}, - "day": {} - } - }, - "deactivateAccountWarning": "Ово ће деактивирати ваш кориснички налог. Не може се повратити! Сигурни сте?", - "@deactivateAccountWarning": { - "type": "text", - "placeholders": {} - }, - "defaultPermissionLevel": "Подразумевани ниво приступа", - "@defaultPermissionLevel": { - "type": "text", - "placeholders": {} - }, - "delete": "Обриши", - "@delete": { - "type": "text", - "placeholders": {} - }, - "deleteAccount": "Обриши налог", - "@deleteAccount": { - "type": "text", - "placeholders": {} - }, - "deleteMessage": "Брисање поруке", - "@deleteMessage": { - "type": "text", - "placeholders": {} - }, - "device": "Уређај", - "@device": { - "type": "text", - "placeholders": {} - }, - "deviceId": "ИД уређаја", - "@deviceId": { - "type": "text", - "placeholders": {} - }, - "devices": "Уређаји", - "@devices": { - "type": "text", - "placeholders": {} - }, - "directChats": "Директна ћаскања", - "@directChats": { - "type": "text", - "placeholders": {} - }, - "displaynameHasBeenChanged": "Име за приказ је измењено", - "@displaynameHasBeenChanged": { - "type": "text", - "placeholders": {} - }, - "downloadFile": "Преузми фајл", - "@downloadFile": { - "type": "text", - "placeholders": {} - }, - "edit": "Уреди", - "@edit": { - "type": "text", - "placeholders": {} - }, - "editBlockedServers": "Уреди блокиране сервере", - "@editBlockedServers": { - "type": "text", - "placeholders": {} - }, - "editDisplayname": "Уреди име за приказ", - "@editDisplayname": { - "type": "text", - "placeholders": {} - }, - "editRoomAliases": "Уреди алијасе собе", - "@editRoomAliases": { - "type": "text", - "placeholders": {} - }, - "editRoomAvatar": "Уређује аватар собе", - "@editRoomAvatar": { - "type": "text", - "placeholders": {} - }, - "emoteExists": "Емоти већ постоји!", - "@emoteExists": { - "type": "text", - "placeholders": {} - }, - "emoteInvalid": "Неисправна скраћеница за емоти!", - "@emoteInvalid": { - "type": "text", - "placeholders": {} - }, - "emotePacks": "Пакети емотија за собу", - "@emotePacks": { - "type": "text", - "placeholders": {} - }, - "emoteSettings": "Поставке емотија", - "@emoteSettings": { - "type": "text", - "placeholders": {} - }, - "emoteShortcode": "скраћеница", - "@emoteShortcode": { - "type": "text", - "placeholders": {} - }, - "emoteWarnNeedToPick": "Морате да изаберете скраћеницу и слику за емоти!", - "@emoteWarnNeedToPick": { - "type": "text", - "placeholders": {} - }, - "emptyChat": "празно ћаскање", - "@emptyChat": { - "type": "text", - "placeholders": {} - }, - "enableEmotesGlobally": "Глобално укључи пакет емотија", - "@enableEmotesGlobally": { - "type": "text", - "placeholders": {} - }, - "enableEncryption": "Укључује шифровање", - "@enableEncryption": { - "type": "text", - "placeholders": {} - }, - "enableEncryptionWarning": "Шифровање више нећете моћи да искључите. Сигурни сте?", - "@enableEncryptionWarning": { - "type": "text", - "placeholders": {} - }, - "encrypted": "Шифровано", - "@encrypted": { - "type": "text", - "placeholders": {} - }, - "encryption": "Шифровање", - "@encryption": { - "type": "text", - "placeholders": {} - }, - "encryptionNotEnabled": "Шифровање није укључено", - "@encryptionNotEnabled": { - "type": "text", - "placeholders": {} - }, - "endedTheCall": "{senderName} заврши позив", - "@endedTheCall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "enterAnEmailAddress": "Унесите адресу е-поште", - "@enterAnEmailAddress": { - "type": "text", - "placeholders": {} - }, - "enterYourHomeserver": "Унесите свој домаћи сервер", - "@enterYourHomeserver": { - "type": "text", - "placeholders": {} - }, - "everythingReady": "Све је спремно!", - "@everythingReady": { - "type": "text", - "placeholders": {} - }, - "extremeOffensive": "Екстремно увредљив", - "@extremeOffensive": { - "type": "text", - "placeholders": {} - }, - "fileName": "Назив фајла", - "@fileName": { - "type": "text", - "placeholders": {} - }, - "fluffychat": "FluffyChat", - "@fluffychat": { - "type": "text", - "placeholders": {} - }, - "fontSize": "Величина фонта", - "@fontSize": { - "type": "text", - "placeholders": {} - }, - "forward": "Напред", - "@forward": { - "type": "text", - "placeholders": {} - }, - "fromJoining": "од приступања", - "@fromJoining": { - "type": "text", - "placeholders": {} - }, - "fromTheInvitation": "од позивања", - "@fromTheInvitation": { - "type": "text", - "placeholders": {} - }, - "goToTheNewRoom": "Иди у нову собу", - "@goToTheNewRoom": { - "type": "text", - "placeholders": {} - }, - "group": "Група", - "@group": { - "type": "text", - "placeholders": {} - }, - "groupIsPublic": "Група је јавна", - "@groupIsPublic": { - "type": "text", - "placeholders": {} - }, - "groups": "Групе", - "@groups": { - "type": "text", - "placeholders": {} - }, - "groupWith": "Група са корисником {displayname}", - "@groupWith": { - "type": "text", - "placeholders": { - "displayname": {} - } - }, - "guestsAreForbidden": "гости су забрањени", - "@guestsAreForbidden": { - "type": "text", - "placeholders": {} - }, - "guestsCanJoin": "гости могу приступити", - "@guestsCanJoin": { - "type": "text", - "placeholders": {} - }, - "hasWithdrawnTheInvitationFor": "{username} поништи позивницу за корисника {targetName}", - "@hasWithdrawnTheInvitationFor": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "help": "Помоћ", - "@help": { - "type": "text", - "placeholders": {} - }, - "hideRedactedEvents": "Сакриј редиговане догађаје", - "@hideRedactedEvents": { - "type": "text", - "placeholders": {} - }, - "hideUnknownEvents": "Сакриј непознате догађаје", - "@hideUnknownEvents": { - "type": "text", - "placeholders": {} - }, - "howOffensiveIsThisContent": "Колико је увредљив овај садржај?", - "@howOffensiveIsThisContent": { - "type": "text", - "placeholders": {} - }, - "id": "ИД", - "@id": { - "type": "text", - "placeholders": {} - }, - "identity": "Идентитет", - "@identity": { - "type": "text", - "placeholders": {} - }, - "ignore": "Игнориши", - "@ignore": { - "type": "text", - "placeholders": {} - }, - "ignoredUsers": "Игнорисани корисници", - "@ignoredUsers": { - "type": "text", - "placeholders": {} - }, - "iHaveClickedOnLink": "Кликнуо сам на везу", - "@iHaveClickedOnLink": { - "type": "text", - "placeholders": {} - }, - "incorrectPassphraseOrKey": "Неисправна фраза или кључ опоравка", - "@incorrectPassphraseOrKey": { - "type": "text", - "placeholders": {} - }, - "inoffensive": "Није увредљив", - "@inoffensive": { - "type": "text", - "placeholders": {} - }, - "inviteContact": "Позивање особа", - "@inviteContact": { - "type": "text", - "placeholders": {} - }, - "inviteContactToGroup": "Позови особу у групу {groupName}", - "@inviteContactToGroup": { - "type": "text", - "placeholders": { - "groupName": {} - } - }, - "invited": "Позван", - "@invited": { - "type": "text", - "placeholders": {} - }, - "invitedUser": "{username} позва корисника {targetName}", - "@invitedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "invitedUsersOnly": "само позвани корисници", - "@invitedUsersOnly": { - "type": "text", - "placeholders": {} - }, - "inviteForMe": "Позивнице за мене", - "@inviteForMe": { - "type": "text", - "placeholders": {} - }, - "inviteText": "{username} вас позива у FluffyChat. \n1. Инсталирајте FluffyChat: https://fluffychat.im \n2. Региструјте се или пријавите \n3. Отворите везу позивнице: {link}", - "@inviteText": { - "type": "text", - "placeholders": { - "username": {}, - "link": {} - } - }, - "isTyping": "куца…", - "@isTyping": { - "type": "text", - "placeholders": {} - }, - "joinedTheChat": "{username} се придружи ћаскању", - "@joinedTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "joinRoom": "Придружи се соби", - "@joinRoom": { - "type": "text", - "placeholders": {} - }, - "kicked": "{username} избаци корисника {targetName}", - "@kicked": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "kickedAndBanned": "{username} избаци и забрани корисника {targetName}", - "@kickedAndBanned": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "kickFromChat": "Избаци из ћаскања", - "@kickFromChat": { - "type": "text", - "placeholders": {} - }, - "lastActiveAgo": "Последња активност: {localizedTimeShort}", - "@lastActiveAgo": { - "type": "text", - "placeholders": { - "localizedTimeShort": {} - } - }, - "leave": "Напусти", - "@leave": { - "type": "text", - "placeholders": {} - }, - "leftTheChat": "Напусти ћаскање", - "@leftTheChat": { - "type": "text", - "placeholders": {} - }, - "license": "Лиценца", - "@license": { - "type": "text", - "placeholders": {} - }, - "lightTheme": "светли", - "@lightTheme": { - "type": "text", - "placeholders": {} - }, - "loadCountMoreParticipants": "Учитај још {count} учесника", - "@loadCountMoreParticipants": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "loadingPleaseWait": "Учитавам… Сачекајте.", - "@loadingPleaseWait": { - "type": "text", - "placeholders": {} - }, - "loadMore": "Учитај још…", - "@loadMore": { - "type": "text", - "placeholders": {} - }, - "login": "Пријава", - "@login": { - "type": "text", - "placeholders": {} - }, - "logInTo": "Пријава на {homeserver}", - "@logInTo": { - "type": "text", - "placeholders": { - "homeserver": {} - } - }, - "logout": "Одјава", - "@logout": { - "type": "text", - "placeholders": {} - }, - "memberChanges": "Измене чланова", - "@memberChanges": { - "type": "text", - "placeholders": {} - }, - "mention": "Спомени", - "@mention": { - "type": "text", - "placeholders": {} - }, - "messages": "Поруке", - "@messages": { - "type": "text", - "placeholders": {} - }, - "moderator": "Модератор", - "@moderator": { - "type": "text", - "placeholders": {} - }, - "muteChat": "Ућуткај ћаскање", - "@muteChat": { - "type": "text", - "placeholders": {} - }, - "needPantalaimonWarning": "За сада, потребан је Пантелејмон (Pantalaimon) да бисте користили шифровање с краја на крај.", - "@needPantalaimonWarning": { - "type": "text", - "placeholders": {} - }, - "newChat": "Ново ћаскање", - "@newChat": { - "type": "text", - "placeholders": {} - }, - "newMessageInFluffyChat": "Нова порука — FluffyChat", - "@newMessageInFluffyChat": { - "type": "text", - "placeholders": {} - }, - "newVerificationRequest": "Нови захтев за верификацију!", - "@newVerificationRequest": { - "type": "text", - "placeholders": {} - }, - "next": "Следеће", - "@next": { - "type": "text", - "placeholders": {} - }, - "no": "Не", - "@no": { - "type": "text", - "placeholders": {} - }, - "noConnectionToTheServer": "Нема везе са сервером", - "@noConnectionToTheServer": { - "type": "text", - "placeholders": {} - }, - "noEmotesFound": "Нема емотија. 😕", - "@noEmotesFound": { - "type": "text", - "placeholders": {} - }, - "noEncryptionForPublicRooms": "Шифровање се може активирати након што соба престане да буде јавно доступна.", - "@noEncryptionForPublicRooms": { - "type": "text", - "placeholders": {} - }, - "noGoogleServicesWarning": "Чини се да немате Гугл услуге на телефону. То је добра одлука за вашу приватност! Да би се протурале нотификације у FluffyChat, препоручујемо коришћење https://microg.org/ или https://unifiedpush.org/", - "@noGoogleServicesWarning": { - "type": "text", - "placeholders": {} - }, - "none": "Ништа", - "@none": { - "type": "text", - "placeholders": {} - }, - "noPasswordRecoveryDescription": "Још нисте одредили начин за опоравак лозинке.", - "@noPasswordRecoveryDescription": { - "type": "text", - "placeholders": {} - }, - "noPermission": "Нема дозвола", - "@noPermission": { - "type": "text", - "placeholders": {} - }, - "noRoomsFound": "Нисам нашао собе…", - "@noRoomsFound": { - "type": "text", - "placeholders": {} - }, - "notifications": "Обавештења", - "@notifications": { - "type": "text", - "placeholders": {} - }, - "notificationsEnabledForThisAccount": "Обавештења укључена за овај налог", - "@notificationsEnabledForThisAccount": { - "type": "text", - "placeholders": {} - }, - "numUsersTyping": "{count} корисника куца…", - "@numUsersTyping": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "offensive": "Увредљив", - "@offensive": { - "type": "text", - "placeholders": {} - }, - "offline": "Ван везе", - "@offline": { - "type": "text", - "placeholders": {} - }, - "ok": "у реду", - "@ok": { - "type": "text", - "placeholders": {} - }, - "online": "На вези", - "@online": { - "type": "text", - "placeholders": {} - }, - "onlineKeyBackupEnabled": "Резерва кључева на мрежи је укључена", - "@onlineKeyBackupEnabled": { - "type": "text", - "placeholders": {} - }, - "oopsPushError": "Нажалост, дошло је до грешке при подешавању дотурања обавештења.", - "@oopsPushError": { - "type": "text", - "placeholders": {} - }, - "oopsSomethingWentWrong": "Нешто је пошло наопако…", - "@oopsSomethingWentWrong": { - "type": "text", - "placeholders": {} - }, - "openAppToReadMessages": "Отворите апликацију да прочитате поруке", - "@openAppToReadMessages": { - "type": "text", - "placeholders": {} - }, - "openCamera": "Отвори камеру", - "@openCamera": { - "type": "text", - "placeholders": {} - }, - "or": "или", - "@or": { - "type": "text", - "placeholders": {} - }, - "participant": "Учесник", - "@participant": { - "type": "text", - "placeholders": {} - }, - "passphraseOrKey": "фраза или кључ опоравка", - "@passphraseOrKey": { - "type": "text", - "placeholders": {} - }, - "password": "Лозинка", - "@password": { - "type": "text", - "placeholders": {} - }, - "passwordForgotten": "Заборављена лозинка", - "@passwordForgotten": { - "type": "text", - "placeholders": {} - }, - "passwordHasBeenChanged": "Лозинка је промењена", - "@passwordHasBeenChanged": { - "type": "text", - "placeholders": {} - }, - "passwordRecovery": "Опоравак лозинке", - "@passwordRecovery": { - "type": "text", - "placeholders": {} - }, - "people": "Људи", - "@people": { - "type": "text", - "placeholders": {} - }, - "pickImage": "Избор слике", - "@pickImage": { - "type": "text", - "placeholders": {} - }, - "pin": "Закачи", - "@pin": { - "type": "text", - "placeholders": {} - }, - "play": "Пусти {fileName}", - "@play": { - "type": "text", - "placeholders": { - "fileName": {} - } - }, - "pleaseChoose": "Изаберите", - "@pleaseChoose": { - "type": "text", - "placeholders": {} - }, - "pleaseChooseAPasscode": "Изаберите код за пролаз", - "@pleaseChooseAPasscode": { - "type": "text", - "placeholders": {} - }, - "pleaseClickOnLink": "Кликните на везу у примљеној е-пошти па наставите.", - "@pleaseClickOnLink": { - "type": "text", - "placeholders": {} - }, - "pleaseEnter4Digits": "Унесите 4 цифре или оставите празно да не закључавате апликацију.", - "@pleaseEnter4Digits": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourPassword": "Унесите своју лозинку", - "@pleaseEnterYourPassword": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourPin": "Унесите свој пин", - "@pleaseEnterYourPin": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourUsername": "Унесите своје корисничко име", - "@pleaseEnterYourUsername": { - "type": "text", - "placeholders": {} - }, - "pleaseFollowInstructionsOnWeb": "Испратите упутства на веб сајту и тапните на „Следеће“.", - "@pleaseFollowInstructionsOnWeb": { - "type": "text", - "placeholders": {} - }, - "privacy": "Приватност", - "@privacy": { - "type": "text", - "placeholders": {} - }, - "publicRooms": "Јавне собе", - "@publicRooms": { - "type": "text", - "placeholders": {} - }, - "pushRules": "Правила протурања", - "@pushRules": { - "type": "text", - "placeholders": {} - }, - "reason": "Разлог", - "@reason": { - "type": "text", - "placeholders": {} - }, - "recording": "Снимам", - "@recording": { - "type": "text", - "placeholders": {} - }, - "redactedAnEvent": "{username} редигова догађај", - "@redactedAnEvent": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "redactMessage": "Редигуј поруку", - "@redactMessage": { - "type": "text", - "placeholders": {} - }, - "register": "Регистрација", - "@register": { - "type": "text", - "placeholders": {} - }, - "reject": "Одбиј", - "@reject": { - "type": "text", - "placeholders": {} - }, - "rejectedTheInvitation": "{username} одби позивницу", - "@rejectedTheInvitation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "rejoin": "Поново се придружи", - "@rejoin": { - "type": "text", - "placeholders": {} - }, - "remove": "Уклони", - "@remove": { - "type": "text", - "placeholders": {} - }, - "removeAllOtherDevices": "Уклони све остале уређаје", - "@removeAllOtherDevices": { - "type": "text", - "placeholders": {} - }, - "removedBy": "Уклонио корисник {username}", - "@removedBy": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "removeDevice": "Уклони уређај", - "@removeDevice": { - "type": "text", - "placeholders": {} - }, - "unbanFromChat": "Уклони изгнанство", - "@unbanFromChat": { - "type": "text", - "placeholders": {} - }, - "removeYourAvatar": "Уклоните свој аватар", - "@removeYourAvatar": { - "type": "text", - "placeholders": {} - }, - "renderRichContent": "Приказуј обогаћен садржај поруке", - "@renderRichContent": { - "type": "text", - "placeholders": {} - }, - "replaceRoomWithNewerVersion": "Замени собу новијом верзијом", - "@replaceRoomWithNewerVersion": { - "type": "text", - "placeholders": {} - }, - "reply": "Одговори", - "@reply": { - "type": "text", - "placeholders": {} - }, - "reportMessage": "Пријави поруку", - "@reportMessage": { - "type": "text", - "placeholders": {} - }, - "requestPermission": "Затражи дозволу", - "@requestPermission": { - "type": "text", - "placeholders": {} - }, - "roomHasBeenUpgraded": "Соба је надограђена", - "@roomHasBeenUpgraded": { - "type": "text", - "placeholders": {} - }, - "roomVersion": "Верзија собе", - "@roomVersion": { - "type": "text", - "placeholders": {} - }, - "search": "Претражи", - "@search": { - "type": "text", - "placeholders": {} - }, - "security": "Безбедност", - "@security": { - "type": "text", - "placeholders": {} - }, - "seenByUser": "{username} прегледа", - "@seenByUser": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "send": "Пошаљи", - "@send": { - "type": "text", - "placeholders": {} - }, - "sendAMessage": "Пошаљи поруку", - "@sendAMessage": { - "type": "text", - "placeholders": {} - }, - "sendAudio": "Пошаљи аудио", - "@sendAudio": { - "type": "text", - "placeholders": {} - }, - "sendFile": "Пошаљи фајл", - "@sendFile": { - "type": "text", - "placeholders": {} - }, - "sendImage": "Пошаљи слику", - "@sendImage": { - "type": "text", - "placeholders": {} - }, - "sendMessages": "Слање порука", - "@sendMessages": { - "type": "text", - "placeholders": {} - }, - "sendOriginal": "Пошаљи оригинал", - "@sendOriginal": { - "type": "text", - "placeholders": {} - }, - "sendVideo": "Пошаљи видео", - "@sendVideo": { - "type": "text", - "placeholders": {} - }, - "sentAFile": "{username} посла фајл", - "@sentAFile": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentAnAudio": "{username} посла аудио", - "@sentAnAudio": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentAPicture": "{username} посла слику", - "@sentAPicture": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentASticker": "{username} посла налепницу", - "@sentASticker": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentAVideo": "{username} посла видео", - "@sentAVideo": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentCallInformations": "{senderName} посла податке о позиву", - "@sentCallInformations": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "setAsCanonicalAlias": "Постави као главни алијас", - "@setAsCanonicalAlias": { - "type": "text", - "placeholders": {} - }, - "setCustomEmotes": "постави посебне емотије", - "@setCustomEmotes": { - "type": "text", - "placeholders": {} - }, - "setInvitationLink": "Поставља везу позивнице", - "@setInvitationLink": { - "type": "text", - "placeholders": {} - }, - "setPermissionsLevel": "Одреди ниво дозволе", - "@setPermissionsLevel": { - "type": "text", - "placeholders": {} - }, - "setStatus": "Постави статус", - "@setStatus": { - "type": "text", - "placeholders": {} - }, - "settings": "Поставке", - "@settings": { - "type": "text", - "placeholders": {} - }, - "share": "Подели", - "@share": { - "type": "text", - "placeholders": {} - }, - "sharedTheLocation": "{username} подели локацију", - "@sharedTheLocation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "showPassword": "Прикажи лозинку", - "@showPassword": { - "type": "text", - "placeholders": {} - }, - "singlesignon": "Јединствена пријава", - "@singlesignon": { - "type": "text", - "placeholders": {} - }, - "skip": "Прескочи", - "@skip": { - "type": "text", - "placeholders": {} - }, - "sourceCode": "Изворни код", - "@sourceCode": { - "type": "text", - "placeholders": {} - }, - "startedACall": "{senderName} започе позив", - "@startedACall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "status": "Стање", - "@status": { - "type": "text", - "placeholders": {} - }, - "statusExampleMessage": "Како сте данас?", - "@statusExampleMessage": { - "type": "text", - "placeholders": {} - }, - "submit": "Пошаљи", - "@submit": { - "type": "text", - "placeholders": {} - }, - "systemTheme": "системски", - "@systemTheme": { - "type": "text", - "placeholders": {} - }, - "theyDontMatch": "Не поклапају се", - "@theyDontMatch": { - "type": "text", - "placeholders": {} - }, - "theyMatch": "Поклапају се", - "@theyMatch": { - "type": "text", - "placeholders": {} - }, - "title": "FluffyChat", - "@title": { - "description": "Title for the application", - "type": "text", - "placeholders": {} - }, - "toggleFavorite": "Мењај омиљеност", - "@toggleFavorite": { - "type": "text", - "placeholders": {} - }, - "toggleMuted": "Мењај ућутканост", - "@toggleMuted": { - "type": "text", - "placeholders": {} - }, - "toggleUnread": "Означи не/прочитано", - "@toggleUnread": { - "type": "text", - "placeholders": {} - }, - "tooManyRequestsWarning": "Превише упита. Покушајте касније!", - "@tooManyRequestsWarning": { - "type": "text", - "placeholders": {} - }, - "transferFromAnotherDevice": "Пренос са другог уређаја", - "@transferFromAnotherDevice": { - "type": "text", - "placeholders": {} - }, - "tryToSendAgain": "Покушај слање поново", - "@tryToSendAgain": { - "type": "text", - "placeholders": {} - }, - "unavailable": "Недоступно", - "@unavailable": { - "type": "text", - "placeholders": {} - }, - "unbannedUser": "{username} одблокира корисника {targetName}", - "@unbannedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "unblockDevice": "Одблокирај уређај", - "@unblockDevice": { - "type": "text", - "placeholders": {} - }, - "unknownDevice": "Непознат уређај", - "@unknownDevice": { - "type": "text", - "placeholders": {} - }, - "unknownEncryptionAlgorithm": "Непознат алгоритам шифровања", - "@unknownEncryptionAlgorithm": { - "type": "text", - "placeholders": {} - }, - "unknownEvent": "Непознат догађај „{type}“", - "@unknownEvent": { - "type": "text", - "placeholders": { - "type": {} - } - }, - "unmuteChat": "Врати обавештења", - "@unmuteChat": { - "type": "text", - "placeholders": {} - }, - "unpin": "Откачи", - "@unpin": { - "type": "text", - "placeholders": {} - }, - "unreadChats": "{unreadCount, plural, other{непрочитаних ћаскања: {unreadCount}}}", - "@unreadChats": { - "type": "text", - "placeholders": { - "unreadCount": {} - } - }, - "userAndOthersAreTyping": "{username} и {count} корисника куцају…", - "@userAndOthersAreTyping": { - "type": "text", - "placeholders": { - "username": {}, - "count": {} - } - }, - "userAndUserAreTyping": "{username} и {username2} куцају…", - "@userAndUserAreTyping": { - "type": "text", - "placeholders": { - "username": {}, - "username2": {} - } - }, - "userIsTyping": "{username} куца…", - "@userIsTyping": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "userLeftTheChat": "{username} напусти ћаскање", - "@userLeftTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "username": "Корисничко име", - "@username": { - "type": "text", - "placeholders": {} - }, - "userSentUnknownEvent": "{username} посла {type} догађај", - "@userSentUnknownEvent": { - "type": "text", - "placeholders": { - "username": {}, - "type": {} - } - }, - "verified": "Оверен", - "@verified": { - "type": "text", - "placeholders": {} - }, - "verify": "Верификуј", - "@verify": { - "type": "text", - "placeholders": {} - }, - "verifyStart": "Покрени верификацију", - "@verifyStart": { - "type": "text", - "placeholders": {} - }, - "verifySuccess": "Успешно сте верификовали!", - "@verifySuccess": { - "type": "text", - "placeholders": {} - }, - "verifyTitle": "Верификујем други налог", - "@verifyTitle": { - "type": "text", - "placeholders": {} - }, - "videoCall": "Видео позив", - "@videoCall": { - "type": "text", - "placeholders": {} - }, - "visibilityOfTheChatHistory": "Одреди видљивост историје", - "@visibilityOfTheChatHistory": { - "type": "text", - "placeholders": {} - }, - "visibleForAllParticipants": "видљиво свим учесницима", - "@visibleForAllParticipants": { - "type": "text", - "placeholders": {} - }, - "visibleForEveryone": "видљиво свима", - "@visibleForEveryone": { - "type": "text", - "placeholders": {} - }, - "voiceMessage": "Гласовна порука", - "@voiceMessage": { - "type": "text", - "placeholders": {} - }, - "waitingPartnerAcceptRequest": "Чекам да саговорник прихвати захтев…", - "@waitingPartnerAcceptRequest": { - "type": "text", - "placeholders": {} - }, - "waitingPartnerEmoji": "Чекам да саговорник прихвати емоџије…", - "@waitingPartnerEmoji": { - "type": "text", - "placeholders": {} - }, - "waitingPartnerNumbers": "Чекам да саговорник прихвати бројеве…", - "@waitingPartnerNumbers": { - "type": "text", - "placeholders": {} - }, - "wallpaper": "Тапета", - "@wallpaper": { - "type": "text", - "placeholders": {} - }, - "warning": "Упозорење!", - "@warning": { - "type": "text", - "placeholders": {} - }, - "weSentYouAnEmail": "Послали смо вам е-пошту", - "@weSentYouAnEmail": { - "type": "text", - "placeholders": {} - }, - "whoCanPerformWhichAction": "ко може шта да ради", - "@whoCanPerformWhichAction": { - "type": "text", - "placeholders": {} - }, - "whoIsAllowedToJoinThisGroup": "Ко може да се придружи групи", - "@whoIsAllowedToJoinThisGroup": { - "type": "text", - "placeholders": {} - }, - "whyDoYouWantToReportThis": "Зашто желите ово да пријавите?", - "@whyDoYouWantToReportThis": { - "type": "text", - "placeholders": {} - }, - "wipeChatBackup": "Да обришем резервну копију како би направио нови сигурносни кључ?", - "@wipeChatBackup": { - "type": "text", - "placeholders": {} - }, - "withTheseAddressesRecoveryDescription": "Са овим адресама можете опоравити своју лозинку.", - "@withTheseAddressesRecoveryDescription": { - "type": "text", - "placeholders": {} - }, - "writeAMessage": "напишите поруку…", - "@writeAMessage": { - "type": "text", - "placeholders": {} - }, - "yes": "Да", - "@yes": { - "type": "text", - "placeholders": {} - }, - "you": "Ви", - "@you": { - "type": "text", - "placeholders": {} - }, - "youAreNoLongerParticipatingInThisChat": "Више не учествујете у овом ћаскању", - "@youAreNoLongerParticipatingInThisChat": { - "type": "text", - "placeholders": {} - }, - "youHaveBeenBannedFromThisChat": "Забрањено вам је ово ћаскање", - "@youHaveBeenBannedFromThisChat": { - "type": "text", - "placeholders": {} - }, - "yourPublicKey": "Ваш јавни кључ", - "@yourPublicKey": { - "type": "text", - "placeholders": {} - }, - "@hugContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@jumpToLastReadMessage": {}, - "@allRooms": { - "type": "text", - "placeholders": {} - }, - "@obtainingLocation": { - "type": "text", - "placeholders": {} - }, - "@commandHint_cuddle": {}, - "@widgetVideo": {}, - "@dismiss": {}, - "@reportErrorDescription": {}, - "@addAccount": {}, - "@chatHasBeenAddedToThisSpace": {}, - "@unsupportedAndroidVersion": {}, - "@widgetJitsi": {}, - "@messageType": {}, - "@indexedDbErrorLong": {}, - "@oneClientLoggedOut": {}, - "@startFirstChat": {}, - "@callingAccount": {}, - "@setColorTheme": {}, - "@nextAccount": {}, - "@commandHint_create": { - "type": "text", - "description": "Usage hint for the command /create" - }, - "@allSpaces": {}, - "@supposedMxid": { - "type": "text", - "placeholders": { - "mxid": {} - } - }, - "@user": {}, - "@youAcceptedTheInvitation": {}, - "@noMatrixServer": { - "type": "text", - "placeholders": { - "server1": {}, - "server2": {} - } - }, - "@youInvitedBy": { - "placeholders": { - "user": {} - } - }, - "@banUserDescription": {}, - "@widgetEtherpad": {}, - "@removeDevicesDescription": {}, - "@separateChatTypes": { - "type": "text", - "placeholders": {} - }, - "@tryAgain": {}, - "@youKickedAndBanned": { - "placeholders": { - "user": {} - } - }, - "@unbanUserDescription": {}, - "@saveFile": { - "type": "text", - "placeholders": {} - }, - "@sendOnEnter": {}, - "@youRejectedTheInvitation": {}, - "@otherCallingPermissions": {}, - "@messagesStyle": {}, - "@link": {}, - "@widgetUrlError": {}, - "@emailOrUsername": {}, - "@newSpaceDescription": {}, - "@chatDescription": {}, - "@callingAccountDetails": {}, - "@enterSpace": {}, - "@encryptThisChat": {}, - "@previousAccount": {}, - "@reopenChat": {}, - "@pleaseEnterRecoveryKey": {}, - "@widgetNameError": {}, - "@addToBundle": {}, - "@spaceIsPublic": { - "type": "text", - "placeholders": {} - }, - "@addWidget": {}, - "@countFiles": { - "placeholders": { - "count": {} - } - }, - "@noKeyForThisMessage": {}, - "@shareLocation": { - "type": "text", - "placeholders": {} - }, - "@commandHint_markasgroup": {}, - "@errorObtainingLocation": { - "type": "text", - "placeholders": { - "error": {} - } - }, - "@hydrateTor": {}, - "@pushNotificationsNotAvailable": {}, - "@storeInAppleKeyChain": {}, - "@hydrate": {}, - "@invalidServerName": {}, - "@chatPermissions": {}, - "@cantOpenUri": { - "type": "text", - "placeholders": { - "uri": {} - } - }, - "@sender": {}, - "@storeInAndroidKeystore": {}, - "@signInWithPassword": {}, - "@makeAdminDescription": {}, - "@synchronizingPleaseWait": { - "type": "text", - "placeholders": {} - }, - "@commandHint_clearcache": { - "type": "text", - "description": "Usage hint for the command /clearcache" - }, - "@saveKeyManuallyDescription": {}, - "@editBundlesForAccount": {}, - "@whyIsThisMessageEncrypted": {}, - "@setChatDescription": {}, - "@spaceName": { - "type": "text", - "placeholders": {} - }, - "@importFromZipFile": {}, - "@dehydrateWarning": {}, - "@noOtherDevicesFound": {}, - "@yourChatBackupHasBeenSetUp": {}, - "@redactedBy": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@videoCallsBetaWarning": {}, - "@autoplayImages": { - "type": "text", - "placeholder": {} - }, - "@signInWith": { - "type": "text", - "placeholders": { - "provider": {} - } - }, - "@fileIsTooBigForServer": {}, - "@homeserver": {}, - "@repeatPassword": {}, - "@callingPermissions": {}, - "@readUpToHere": {}, - "@start": {}, - "@unlockOldMessages": {}, - "@numChats": { - "type": "number", - "placeholders": { - "number": {} - } - }, - "@optionalRedactReason": {}, - "@dehydrate": {}, - "@locationPermissionDeniedNotice": { - "type": "text", - "placeholders": {} - }, - "@sendAsText": { - "type": "text" - }, - "@archiveRoomDescription": {}, - "@exportEmotePack": {}, - "@sendSticker": { - "type": "text", - "placeholders": {} - }, - "@switchToAccount": { - "type": "number", - "placeholders": { - "number": {} - } - }, - "@commandInvalid": { - "type": "text" - }, - "@locationDisabledNotice": { - "type": "text", - "placeholders": {} - }, - "@experimentalVideoCalls": {}, - "@pleaseEnterRecoveryKeyDescription": {}, - "@openInMaps": { - "type": "text", - "placeholders": {} - }, - "@inviteContactToGroupQuestion": {}, - "@redactedByBecause": { - "type": "text", - "placeholders": { - "username": {}, - "reason": {} - } - }, - "@youHaveWithdrawnTheInvitationFor": { - "placeholders": { - "user": {} - } - }, - "@appearOnTopDetails": {}, - "@enterRoom": {}, - "@allChats": { - "type": "text", - "placeholders": {} - }, - "@reportUser": {}, - "@confirmEventUnpin": {}, - "@youInvitedUser": { - "placeholders": { - "user": {} - } - }, - "@fileHasBeenSavedAt": { - "type": "text", - "placeholders": { - "path": {} - } - }, - "@addToSpace": {}, - "@commandMissing": { - "type": "text", - "placeholders": { - "command": {} - }, - "description": "State that {command} is not a valid /command." - }, - "@redactMessageDescription": {}, - "@recoveryKey": {}, - "@commandHint_discardsession": { - "type": "text", - "description": "Usage hint for the command /discardsession" - }, - "@invalidInput": {}, - "@dehydrateTorLong": {}, - "@doNotShowAgain": {}, - "@report": {}, - "@unverified": {}, - "@serverRequiresEmail": {}, - "@hideUnimportantStateEvents": {}, - "@screenSharingTitle": {}, - "@widgetCustom": {}, - "@addToSpaceDescription": {}, - "@googlyEyesContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@youBannedUser": { - "placeholders": { - "user": {} - } - }, - "@addChatDescription": {}, - "@commandHint_myroomavatar": { - "type": "text", - "description": "Usage hint for the command /myroomavatar" - }, - "@hasKnocked": { - "placeholders": { - "user": {} - } - }, - "@publish": {}, - "@openLinkInBrowser": {}, - "@messageInfo": {}, - "@disableEncryptionWarning": {}, - "@directChat": {}, - "@wrongPinEntered": { - "type": "text", - "placeholders": { - "seconds": {} - } - }, - "@sendTypingNotifications": {}, - "@inviteGroupChat": {}, - "@appearOnTop": {}, - "@invitePrivateChat": {}, - "@foregroundServiceRunning": {}, - "@voiceCall": {}, - "@createNewSpace": { - "type": "text", - "placeholders": {} - }, - "@importEmojis": {}, - "@wasDirectChatDisplayName": { - "type": "text", - "placeholders": { - "oldDisplayName": {} - } - }, - "@noChatDescriptionYet": {}, - "@removeFromBundle": {}, - "@confirmMatrixId": {}, - "@learnMore": {}, - "@notAnImage": {}, - "@users": {}, - "@openGallery": {}, - "@chatDescriptionHasBeenChanged": {}, - "@newGroup": {}, - "@bundleName": {}, - "@dehydrateTor": {}, - "@removeFromSpace": {}, - "@roomUpgradeDescription": {}, - "@scanQrCode": {}, - "@pleaseEnterANumber": {}, - "@youKicked": { - "placeholders": { - "user": {} - } - }, - "@profileNotFound": {}, - "@jump": {}, - "@reactedWith": { - "type": "text", - "placeholders": { - "sender": {}, - "reaction": {} - } - }, - "@sorryThatsNotPossible": {}, - "@videoWithSize": { - "type": "text", - "placeholders": { - "size": {} - } - }, - "@shareInviteLink": {}, - "@commandHint_markasdm": {}, - "@recoveryKeyLost": {}, - "@cuddleContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@deviceKeys": {}, - "@emoteKeyboardNoRecents": { - "type": "text", - "placeholders": {} - }, - "@setTheme": {}, - "@youJoinedTheChat": {}, - "@openVideoCamera": { - "type": "text", - "placeholders": {} - }, - "@markAsRead": {}, - "@widgetName": {}, - "@errorAddingWidget": {}, - "@commandHint_dm": { - "type": "text", - "description": "Usage hint for the command /dm" - }, - "@commandHint_hug": {}, - "@replace": {}, - "@youUnbannedUser": { - "placeholders": { - "user": {} - } - }, - "@newSpace": {}, - "@emojis": {}, - "@commandHint_googly": {}, - "@pleaseTryAgainLaterOrChooseDifferentServer": {}, - "@createGroup": {}, - "@hydrateTorLong": {}, - "@time": {}, - "@custom": {}, - "@noBackupWarning": {}, - "@storeInSecureStorageDescription": {}, - "@openChat": {}, - "@kickUserDescription": {}, - "@importNow": {}, - "@pinMessage": {}, - "@invite": {}, - "@enableMultiAccounts": {}, - "@indexedDbErrorTitle": {}, - "@unsupportedAndroidVersionLong": {}, - "@storeSecurlyOnThisDevice": {}, - "@screenSharingDetail": {}, - "@placeCall": {} -} \ No newline at end of file + "@@last_modified": "2021-08-14 12:41:09.857024", + "about": "О програму", + "@about": { + "type": "text", + "placeholders": {} + }, + "accept": "Прихвати", + "@accept": { + "type": "text", + "placeholders": {} + }, + "acceptedTheInvitation": "{username} прихвата позивницу", + "@acceptedTheInvitation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "account": "Налог", + "@account": { + "type": "text", + "placeholders": {} + }, + "activatedEndToEndEncryption": "{username} укључи шифровање с краја на крај", + "@activatedEndToEndEncryption": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "addEmail": "Додај е-адресу", + "@addEmail": { + "type": "text", + "placeholders": {} + }, + "admin": "Админ", + "@admin": { + "type": "text", + "placeholders": {} + }, + "alias": "алијас", + "@alias": { + "type": "text", + "placeholders": {} + }, + "all": "Сви", + "@all": { + "type": "text", + "placeholders": {} + }, + "answeredTheCall": "{senderName} одговори на позив", + "@answeredTheCall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "anyoneCanJoin": "свако може да се придружи", + "@anyoneCanJoin": { + "type": "text", + "placeholders": {} + }, + "appLock": "Закључавање апликације", + "@appLock": { + "type": "text", + "placeholders": {} + }, + "archive": "Архива", + "@archive": { + "type": "text", + "placeholders": {} + }, + "areGuestsAllowedToJoin": "Да ли је гостима дозвољен приступ", + "@areGuestsAllowedToJoin": { + "type": "text", + "placeholders": {} + }, + "areYouSure": "Сигурни сте?", + "@areYouSure": { + "type": "text", + "placeholders": {} + }, + "areYouSureYouWantToLogout": "Заиста желите да се одјавите?", + "@areYouSureYouWantToLogout": { + "type": "text", + "placeholders": {} + }, + "askSSSSSign": "Да бисте могли да пријавите другу особу, унесите своју безбедносну фразу или кључ опоравка.", + "@askSSSSSign": { + "type": "text", + "placeholders": {} + }, + "askVerificationRequest": "Прихватате ли захтев за верификацију од корисника {username}?", + "@askVerificationRequest": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "badServerLoginTypesException": "Домаћи сервер подржава начине пријаве:\n{serverVersions}\nали ова апликација подржава само:\n{supportedVersions}", + "@badServerLoginTypesException": { + "type": "text", + "placeholders": { + "serverVersions": {}, + "supportedVersions": {} + } + }, + "badServerVersionsException": "Домаћи сервер подржава верзије:\n{serverVersions}\nали ова апликација подржава само {supportedVersions}", + "@badServerVersionsException": { + "type": "text", + "placeholders": { + "serverVersions": {}, + "supportedVersions": {} + } + }, + "banFromChat": "Забрани у ћаскању", + "@banFromChat": { + "type": "text", + "placeholders": {} + }, + "banned": "Забрањен", + "@banned": { + "type": "text", + "placeholders": {} + }, + "bannedUser": "{username} забрани корисника {targetName}", + "@bannedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "blockDevice": "Блокирај уређај", + "@blockDevice": { + "type": "text", + "placeholders": {} + }, + "blocked": "Блокиран", + "@blocked": { + "type": "text", + "placeholders": {} + }, + "botMessages": "Поруке Бота", + "@botMessages": { + "type": "text", + "placeholders": {} + }, + "cancel": "Откажи", + "@cancel": { + "type": "text", + "placeholders": {} + }, + "changeDeviceName": "Промени назив уређаја", + "@changeDeviceName": { + "type": "text", + "placeholders": {} + }, + "changedTheChatAvatar": "{username} промени аватар ћаскања", + "@changedTheChatAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheChatDescriptionTo": "{username} промени опис ћаскања у: „{description}“", + "@changedTheChatDescriptionTo": { + "type": "text", + "placeholders": { + "username": {}, + "description": {} + } + }, + "changedTheChatNameTo": "{username} промени назив ћаскања у: „{chatname}“", + "@changedTheChatNameTo": { + "type": "text", + "placeholders": { + "username": {}, + "chatname": {} + } + }, + "changedTheChatPermissions": "{username} измени дозволе ћаскања", + "@changedTheChatPermissions": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheDisplaynameTo": "{username} промени приказно име на: „{displayname}“", + "@changedTheDisplaynameTo": { + "type": "text", + "placeholders": { + "username": {}, + "displayname": {} + } + }, + "changedTheGuestAccessRules": "{username} измени правила за приступ гостију", + "@changedTheGuestAccessRules": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheGuestAccessRulesTo": "{username} измени правила за приступ гостију на: {rules}", + "@changedTheGuestAccessRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "changedTheHistoryVisibility": "{username} измени видљивост историје", + "@changedTheHistoryVisibility": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheHistoryVisibilityTo": "{username} измени видљивост историје на: {rules}", + "@changedTheHistoryVisibilityTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "changedTheJoinRules": "{username} измени правила приступања", + "@changedTheJoinRules": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheJoinRulesTo": "{username} измени правила приступања на: {joinRules}", + "@changedTheJoinRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "joinRules": {} + } + }, + "changedTheProfileAvatar": "{username} измени свој аватар", + "@changedTheProfileAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheRoomAliases": "{username} измени алијас собе", + "@changedTheRoomAliases": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheRoomInvitationLink": "{username} измени везу позивнице", + "@changedTheRoomInvitationLink": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changePassword": "Измени лозинку", + "@changePassword": { + "type": "text", + "placeholders": {} + }, + "changeTheHomeserver": "Промени домаћи сервер", + "@changeTheHomeserver": { + "type": "text", + "placeholders": {} + }, + "changeTheme": "Измените изглед", + "@changeTheme": { + "type": "text", + "placeholders": {} + }, + "changeTheNameOfTheGroup": "Измени назив групе", + "@changeTheNameOfTheGroup": { + "type": "text", + "placeholders": {} + }, + "changeYourAvatar": "Измените свој аватар", + "@changeYourAvatar": { + "type": "text", + "placeholders": {} + }, + "channelCorruptedDecryptError": "Шифровање је покварено", + "@channelCorruptedDecryptError": { + "type": "text", + "placeholders": {} + }, + "chat": "Ћаскање", + "@chat": { + "type": "text", + "placeholders": {} + }, + "chatBackup": "Копија ћаскања", + "@chatBackup": { + "type": "text", + "placeholders": {} + }, + "chatBackupDescription": "Ваша резервна копија ћаскања је обезбеђена кључем. Немојте да га изгубите.", + "@chatBackupDescription": { + "type": "text", + "placeholders": {} + }, + "chatDetails": "Детаљи ћаскања", + "@chatDetails": { + "type": "text", + "placeholders": {} + }, + "chats": "Ћаскања", + "@chats": { + "type": "text", + "placeholders": {} + }, + "chooseAStrongPassword": "Изаберите јаку лозинку", + "@chooseAStrongPassword": { + "type": "text", + "placeholders": {} + }, + "clearArchive": "Очисти архиву", + "@clearArchive": {}, + "close": "Затвори", + "@close": { + "type": "text", + "placeholders": {} + }, + "commandHint_ban": "Блокирај задатог корисника за ову собу", + "@commandHint_ban": { + "type": "text", + "description": "Usage hint for the command /ban" + }, + "commandHint_html": "Шаљи ХТМЛ обликован текст", + "@commandHint_html": { + "type": "text", + "description": "Usage hint for the command /html" + }, + "commandHint_invite": "Позови задатог корисника у собу", + "@commandHint_invite": { + "type": "text", + "description": "Usage hint for the command /invite" + }, + "commandHint_join": "Придружи се наведеној соби", + "@commandHint_join": { + "type": "text", + "description": "Usage hint for the command /join" + }, + "commandHint_kick": "Уклони задатог корисника из собе", + "@commandHint_kick": { + "type": "text", + "description": "Usage hint for the command /kick" + }, + "commandHint_leave": "Напусти ову собу", + "@commandHint_leave": { + "type": "text", + "description": "Usage hint for the command /leave" + }, + "commandHint_me": "Опишите себе", + "@commandHint_me": { + "type": "text", + "description": "Usage hint for the command /me" + }, + "commandHint_myroomnick": "Поставља ваш надимак за ову собу", + "@commandHint_myroomnick": { + "type": "text", + "description": "Usage hint for the command /myroomnick" + }, + "commandHint_op": "Подеси ниво задатог корисника (подразумевано: 50)", + "@commandHint_op": { + "type": "text", + "description": "Usage hint for the command /op" + }, + "commandHint_plain": "Шаљи неформатиран текст", + "@commandHint_plain": { + "type": "text", + "description": "Usage hint for the command /plain" + }, + "commandHint_react": "Шаљи одговор као реакцију", + "@commandHint_react": { + "type": "text", + "description": "Usage hint for the command /react" + }, + "commandHint_send": "Пошаљи текст", + "@commandHint_send": { + "type": "text", + "description": "Usage hint for the command /send" + }, + "commandHint_unban": "Скини забрану задатом кориснику за ову собу", + "@commandHint_unban": { + "type": "text", + "description": "Usage hint for the command /unban" + }, + "compareEmojiMatch": "Упоредите и проверите да су емоџији идентични као на другом уређају:", + "@compareEmojiMatch": { + "type": "text", + "placeholders": {} + }, + "compareNumbersMatch": "Упоредите и проверите да су следећи бројеви идентични као на другом уређају:", + "@compareNumbersMatch": { + "type": "text", + "placeholders": {} + }, + "configureChat": "Подешавање ћаскања", + "@configureChat": { + "type": "text", + "placeholders": {} + }, + "confirm": "Потврди", + "@confirm": { + "type": "text", + "placeholders": {} + }, + "connect": "Повежи се", + "@connect": { + "type": "text", + "placeholders": {} + }, + "contactHasBeenInvitedToTheGroup": "Особа је позвана у групу", + "@contactHasBeenInvitedToTheGroup": { + "type": "text", + "placeholders": {} + }, + "containsDisplayName": "Садржи приказно име", + "@containsDisplayName": { + "type": "text", + "placeholders": {} + }, + "containsUserName": "Садржи корисничко име", + "@containsUserName": { + "type": "text", + "placeholders": {} + }, + "contentHasBeenReported": "Садржај је пријављен администраторима сервера", + "@contentHasBeenReported": { + "type": "text", + "placeholders": {} + }, + "copiedToClipboard": "Копирано у клипборд", + "@copiedToClipboard": { + "type": "text", + "placeholders": {} + }, + "copy": "Копирај", + "@copy": { + "type": "text", + "placeholders": {} + }, + "copyToClipboard": "Копирај у клипборд", + "@copyToClipboard": { + "type": "text", + "placeholders": {} + }, + "couldNotDecryptMessage": "Не могу да дешифрујем поруку: {error}", + "@couldNotDecryptMessage": { + "type": "text", + "placeholders": { + "error": {} + } + }, + "countParticipants": "учесника: {count}", + "@countParticipants": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "create": "Направи", + "@create": { + "type": "text", + "placeholders": {} + }, + "createdTheChat": "{username} направи ћаскање", + "@createdTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "currentlyActive": "Тренутно активно", + "@currentlyActive": { + "type": "text", + "placeholders": {} + }, + "darkTheme": "тамни", + "@darkTheme": { + "type": "text", + "placeholders": {} + }, + "dateAndTimeOfDay": "{date}, {timeOfDay}", + "@dateAndTimeOfDay": { + "type": "text", + "placeholders": { + "date": {}, + "timeOfDay": {} + } + }, + "dateWithoutYear": "{day} {month}", + "@dateWithoutYear": { + "type": "text", + "placeholders": { + "month": {}, + "day": {} + } + }, + "dateWithYear": "{day} {month} {year}", + "@dateWithYear": { + "type": "text", + "placeholders": { + "year": {}, + "month": {}, + "day": {} + } + }, + "deactivateAccountWarning": "Ово ће деактивирати ваш кориснички налог. Не може се повратити! Сигурни сте?", + "@deactivateAccountWarning": { + "type": "text", + "placeholders": {} + }, + "defaultPermissionLevel": "Подразумевани ниво приступа", + "@defaultPermissionLevel": { + "type": "text", + "placeholders": {} + }, + "delete": "Обриши", + "@delete": { + "type": "text", + "placeholders": {} + }, + "deleteAccount": "Обриши налог", + "@deleteAccount": { + "type": "text", + "placeholders": {} + }, + "deleteMessage": "Брисање поруке", + "@deleteMessage": { + "type": "text", + "placeholders": {} + }, + "device": "Уређај", + "@device": { + "type": "text", + "placeholders": {} + }, + "deviceId": "ИД уређаја", + "@deviceId": { + "type": "text", + "placeholders": {} + }, + "devices": "Уређаји", + "@devices": { + "type": "text", + "placeholders": {} + }, + "directChats": "Директна ћаскања", + "@directChats": { + "type": "text", + "placeholders": {} + }, + "displaynameHasBeenChanged": "Име за приказ је измењено", + "@displaynameHasBeenChanged": { + "type": "text", + "placeholders": {} + }, + "downloadFile": "Преузми фајл", + "@downloadFile": { + "type": "text", + "placeholders": {} + }, + "edit": "Уреди", + "@edit": { + "type": "text", + "placeholders": {} + }, + "editBlockedServers": "Уреди блокиране сервере", + "@editBlockedServers": { + "type": "text", + "placeholders": {} + }, + "editDisplayname": "Уреди име за приказ", + "@editDisplayname": { + "type": "text", + "placeholders": {} + }, + "editRoomAliases": "Уреди алијасе собе", + "@editRoomAliases": { + "type": "text", + "placeholders": {} + }, + "editRoomAvatar": "Уређује аватар собе", + "@editRoomAvatar": { + "type": "text", + "placeholders": {} + }, + "emoteExists": "Емоти већ постоји!", + "@emoteExists": { + "type": "text", + "placeholders": {} + }, + "emoteInvalid": "Неисправна скраћеница за емоти!", + "@emoteInvalid": { + "type": "text", + "placeholders": {} + }, + "emotePacks": "Пакети емотија за собу", + "@emotePacks": { + "type": "text", + "placeholders": {} + }, + "emoteSettings": "Поставке емотија", + "@emoteSettings": { + "type": "text", + "placeholders": {} + }, + "emoteShortcode": "скраћеница", + "@emoteShortcode": { + "type": "text", + "placeholders": {} + }, + "emoteWarnNeedToPick": "Морате да изаберете скраћеницу и слику за емоти!", + "@emoteWarnNeedToPick": { + "type": "text", + "placeholders": {} + }, + "emptyChat": "празно ћаскање", + "@emptyChat": { + "type": "text", + "placeholders": {} + }, + "enableEmotesGlobally": "Глобално укључи пакет емотија", + "@enableEmotesGlobally": { + "type": "text", + "placeholders": {} + }, + "enableEncryption": "Укључује шифровање", + "@enableEncryption": { + "type": "text", + "placeholders": {} + }, + "enableEncryptionWarning": "Шифровање више нећете моћи да искључите. Сигурни сте?", + "@enableEncryptionWarning": { + "type": "text", + "placeholders": {} + }, + "encrypted": "Шифровано", + "@encrypted": { + "type": "text", + "placeholders": {} + }, + "encryption": "Шифровање", + "@encryption": { + "type": "text", + "placeholders": {} + }, + "encryptionNotEnabled": "Шифровање није укључено", + "@encryptionNotEnabled": { + "type": "text", + "placeholders": {} + }, + "endedTheCall": "{senderName} заврши позив", + "@endedTheCall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "enterAnEmailAddress": "Унесите адресу е-поште", + "@enterAnEmailAddress": { + "type": "text", + "placeholders": {} + }, + "enterYourHomeserver": "Унесите свој домаћи сервер", + "@enterYourHomeserver": { + "type": "text", + "placeholders": {} + }, + "everythingReady": "Све је спремно!", + "@everythingReady": { + "type": "text", + "placeholders": {} + }, + "extremeOffensive": "Екстремно увредљив", + "@extremeOffensive": { + "type": "text", + "placeholders": {} + }, + "fileName": "Назив фајла", + "@fileName": { + "type": "text", + "placeholders": {} + }, + "fluffychat": "FluffyChat", + "@fluffychat": { + "type": "text", + "placeholders": {} + }, + "fontSize": "Величина фонта", + "@fontSize": { + "type": "text", + "placeholders": {} + }, + "forward": "Напред", + "@forward": { + "type": "text", + "placeholders": {} + }, + "fromJoining": "од приступања", + "@fromJoining": { + "type": "text", + "placeholders": {} + }, + "fromTheInvitation": "од позивања", + "@fromTheInvitation": { + "type": "text", + "placeholders": {} + }, + "goToTheNewRoom": "Иди у нову собу", + "@goToTheNewRoom": { + "type": "text", + "placeholders": {} + }, + "group": "Група", + "@group": { + "type": "text", + "placeholders": {} + }, + "groupIsPublic": "Група је јавна", + "@groupIsPublic": { + "type": "text", + "placeholders": {} + }, + "groups": "Групе", + "@groups": { + "type": "text", + "placeholders": {} + }, + "groupWith": "Група са корисником {displayname}", + "@groupWith": { + "type": "text", + "placeholders": { + "displayname": {} + } + }, + "guestsAreForbidden": "гости су забрањени", + "@guestsAreForbidden": { + "type": "text", + "placeholders": {} + }, + "guestsCanJoin": "гости могу приступити", + "@guestsCanJoin": { + "type": "text", + "placeholders": {} + }, + "hasWithdrawnTheInvitationFor": "{username} поништи позивницу за корисника {targetName}", + "@hasWithdrawnTheInvitationFor": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "help": "Помоћ", + "@help": { + "type": "text", + "placeholders": {} + }, + "hideRedactedEvents": "Сакриј редиговане догађаје", + "@hideRedactedEvents": { + "type": "text", + "placeholders": {} + }, + "hideUnknownEvents": "Сакриј непознате догађаје", + "@hideUnknownEvents": { + "type": "text", + "placeholders": {} + }, + "howOffensiveIsThisContent": "Колико је увредљив овај садржај?", + "@howOffensiveIsThisContent": { + "type": "text", + "placeholders": {} + }, + "id": "ИД", + "@id": { + "type": "text", + "placeholders": {} + }, + "identity": "Идентитет", + "@identity": { + "type": "text", + "placeholders": {} + }, + "ignore": "Игнориши", + "@ignore": { + "type": "text", + "placeholders": {} + }, + "ignoredUsers": "Игнорисани корисници", + "@ignoredUsers": { + "type": "text", + "placeholders": {} + }, + "iHaveClickedOnLink": "Кликнуо сам на везу", + "@iHaveClickedOnLink": { + "type": "text", + "placeholders": {} + }, + "incorrectPassphraseOrKey": "Неисправна фраза или кључ опоравка", + "@incorrectPassphraseOrKey": { + "type": "text", + "placeholders": {} + }, + "inoffensive": "Није увредљив", + "@inoffensive": { + "type": "text", + "placeholders": {} + }, + "inviteContact": "Позивање особа", + "@inviteContact": { + "type": "text", + "placeholders": {} + }, + "inviteContactToGroup": "Позови особу у групу {groupName}", + "@inviteContactToGroup": { + "type": "text", + "placeholders": { + "groupName": {} + } + }, + "invited": "Позван", + "@invited": { + "type": "text", + "placeholders": {} + }, + "invitedUser": "{username} позва корисника {targetName}", + "@invitedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "invitedUsersOnly": "само позвани корисници", + "@invitedUsersOnly": { + "type": "text", + "placeholders": {} + }, + "inviteForMe": "Позивнице за мене", + "@inviteForMe": { + "type": "text", + "placeholders": {} + }, + "inviteText": "{username} вас позива у FluffyChat. \n1. Инсталирајте FluffyChat: https://fluffychat.im \n2. Региструјте се или пријавите \n3. Отворите везу позивнице: {link}", + "@inviteText": { + "type": "text", + "placeholders": { + "username": {}, + "link": {} + } + }, + "isTyping": "куца…", + "@isTyping": { + "type": "text", + "placeholders": {} + }, + "joinedTheChat": "{username} се придружи ћаскању", + "@joinedTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "joinRoom": "Придружи се соби", + "@joinRoom": { + "type": "text", + "placeholders": {} + }, + "kicked": "{username} избаци корисника {targetName}", + "@kicked": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "kickedAndBanned": "{username} избаци и забрани корисника {targetName}", + "@kickedAndBanned": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "kickFromChat": "Избаци из ћаскања", + "@kickFromChat": { + "type": "text", + "placeholders": {} + }, + "lastActiveAgo": "Последња активност: {localizedTimeShort}", + "@lastActiveAgo": { + "type": "text", + "placeholders": { + "localizedTimeShort": {} + } + }, + "leave": "Напусти", + "@leave": { + "type": "text", + "placeholders": {} + }, + "leftTheChat": "Напусти ћаскање", + "@leftTheChat": { + "type": "text", + "placeholders": {} + }, + "license": "Лиценца", + "@license": { + "type": "text", + "placeholders": {} + }, + "lightTheme": "светли", + "@lightTheme": { + "type": "text", + "placeholders": {} + }, + "loadCountMoreParticipants": "Учитај још {count} учесника", + "@loadCountMoreParticipants": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "loadingPleaseWait": "Учитавам… Сачекајте.", + "@loadingPleaseWait": { + "type": "text", + "placeholders": {} + }, + "loadMore": "Учитај још…", + "@loadMore": { + "type": "text", + "placeholders": {} + }, + "login": "Пријава", + "@login": { + "type": "text", + "placeholders": {} + }, + "logInTo": "Пријава на {homeserver}", + "@logInTo": { + "type": "text", + "placeholders": { + "homeserver": {} + } + }, + "logout": "Одјава", + "@logout": { + "type": "text", + "placeholders": {} + }, + "memberChanges": "Измене чланова", + "@memberChanges": { + "type": "text", + "placeholders": {} + }, + "mention": "Спомени", + "@mention": { + "type": "text", + "placeholders": {} + }, + "messages": "Поруке", + "@messages": { + "type": "text", + "placeholders": {} + }, + "moderator": "Модератор", + "@moderator": { + "type": "text", + "placeholders": {} + }, + "muteChat": "Ућуткај ћаскање", + "@muteChat": { + "type": "text", + "placeholders": {} + }, + "needPantalaimonWarning": "За сада, потребан је Пантелејмон (Pantalaimon) да бисте користили шифровање с краја на крај.", + "@needPantalaimonWarning": { + "type": "text", + "placeholders": {} + }, + "newChat": "Ново ћаскање", + "@newChat": { + "type": "text", + "placeholders": {} + }, + "newMessageInFluffyChat": "Нова порука — FluffyChat", + "@newMessageInFluffyChat": { + "type": "text", + "placeholders": {} + }, + "newVerificationRequest": "Нови захтев за верификацију!", + "@newVerificationRequest": { + "type": "text", + "placeholders": {} + }, + "next": "Следеће", + "@next": { + "type": "text", + "placeholders": {} + }, + "no": "Не", + "@no": { + "type": "text", + "placeholders": {} + }, + "noConnectionToTheServer": "Нема везе са сервером", + "@noConnectionToTheServer": { + "type": "text", + "placeholders": {} + }, + "noEmotesFound": "Нема емотија. 😕", + "@noEmotesFound": { + "type": "text", + "placeholders": {} + }, + "noEncryptionForPublicRooms": "Шифровање се може активирати након што соба престане да буде јавно доступна.", + "@noEncryptionForPublicRooms": { + "type": "text", + "placeholders": {} + }, + "noGoogleServicesWarning": "Чини се да немате Гугл услуге на телефону. То је добра одлука за вашу приватност! Да би се протурале нотификације у FluffyChat, препоручујемо коришћење https://microg.org/ или https://unifiedpush.org/", + "@noGoogleServicesWarning": { + "type": "text", + "placeholders": {} + }, + "none": "Ништа", + "@none": { + "type": "text", + "placeholders": {} + }, + "noPasswordRecoveryDescription": "Још нисте одредили начин за опоравак лозинке.", + "@noPasswordRecoveryDescription": { + "type": "text", + "placeholders": {} + }, + "noPermission": "Нема дозвола", + "@noPermission": { + "type": "text", + "placeholders": {} + }, + "noRoomsFound": "Нисам нашао собе…", + "@noRoomsFound": { + "type": "text", + "placeholders": {} + }, + "notifications": "Обавештења", + "@notifications": { + "type": "text", + "placeholders": {} + }, + "notificationsEnabledForThisAccount": "Обавештења укључена за овај налог", + "@notificationsEnabledForThisAccount": { + "type": "text", + "placeholders": {} + }, + "numUsersTyping": "{count} корисника куца…", + "@numUsersTyping": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "offensive": "Увредљив", + "@offensive": { + "type": "text", + "placeholders": {} + }, + "offline": "Ван везе", + "@offline": { + "type": "text", + "placeholders": {} + }, + "ok": "у реду", + "@ok": { + "type": "text", + "placeholders": {} + }, + "online": "На вези", + "@online": { + "type": "text", + "placeholders": {} + }, + "onlineKeyBackupEnabled": "Резерва кључева на мрежи је укључена", + "@onlineKeyBackupEnabled": { + "type": "text", + "placeholders": {} + }, + "oopsPushError": "Нажалост, дошло је до грешке при подешавању дотурања обавештења.", + "@oopsPushError": { + "type": "text", + "placeholders": {} + }, + "oopsSomethingWentWrong": "Нешто је пошло наопако…", + "@oopsSomethingWentWrong": { + "type": "text", + "placeholders": {} + }, + "openAppToReadMessages": "Отворите апликацију да прочитате поруке", + "@openAppToReadMessages": { + "type": "text", + "placeholders": {} + }, + "openCamera": "Отвори камеру", + "@openCamera": { + "type": "text", + "placeholders": {} + }, + "or": "или", + "@or": { + "type": "text", + "placeholders": {} + }, + "participant": "Учесник", + "@participant": { + "type": "text", + "placeholders": {} + }, + "passphraseOrKey": "фраза или кључ опоравка", + "@passphraseOrKey": { + "type": "text", + "placeholders": {} + }, + "password": "Лозинка", + "@password": { + "type": "text", + "placeholders": {} + }, + "passwordForgotten": "Заборављена лозинка", + "@passwordForgotten": { + "type": "text", + "placeholders": {} + }, + "passwordHasBeenChanged": "Лозинка је промењена", + "@passwordHasBeenChanged": { + "type": "text", + "placeholders": {} + }, + "passwordRecovery": "Опоравак лозинке", + "@passwordRecovery": { + "type": "text", + "placeholders": {} + }, + "people": "Људи", + "@people": { + "type": "text", + "placeholders": {} + }, + "pickImage": "Избор слике", + "@pickImage": { + "type": "text", + "placeholders": {} + }, + "pin": "Закачи", + "@pin": { + "type": "text", + "placeholders": {} + }, + "play": "Пусти {fileName}", + "@play": { + "type": "text", + "placeholders": { + "fileName": {} + } + }, + "pleaseChoose": "Изаберите", + "@pleaseChoose": { + "type": "text", + "placeholders": {} + }, + "pleaseChooseAPasscode": "Изаберите код за пролаз", + "@pleaseChooseAPasscode": { + "type": "text", + "placeholders": {} + }, + "pleaseClickOnLink": "Кликните на везу у примљеној е-пошти па наставите.", + "@pleaseClickOnLink": { + "type": "text", + "placeholders": {} + }, + "pleaseEnter4Digits": "Унесите 4 цифре или оставите празно да не закључавате апликацију.", + "@pleaseEnter4Digits": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourPassword": "Унесите своју лозинку", + "@pleaseEnterYourPassword": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourPin": "Унесите свој пин", + "@pleaseEnterYourPin": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourUsername": "Унесите своје корисничко име", + "@pleaseEnterYourUsername": { + "type": "text", + "placeholders": {} + }, + "pleaseFollowInstructionsOnWeb": "Испратите упутства на веб сајту и тапните на „Следеће“.", + "@pleaseFollowInstructionsOnWeb": { + "type": "text", + "placeholders": {} + }, + "privacy": "Приватност", + "@privacy": { + "type": "text", + "placeholders": {} + }, + "publicRooms": "Јавне собе", + "@publicRooms": { + "type": "text", + "placeholders": {} + }, + "pushRules": "Правила протурања", + "@pushRules": { + "type": "text", + "placeholders": {} + }, + "reason": "Разлог", + "@reason": { + "type": "text", + "placeholders": {} + }, + "recording": "Снимам", + "@recording": { + "type": "text", + "placeholders": {} + }, + "redactedAnEvent": "{username} редигова догађај", + "@redactedAnEvent": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "redactMessage": "Редигуј поруку", + "@redactMessage": { + "type": "text", + "placeholders": {} + }, + "register": "Регистрација", + "@register": { + "type": "text", + "placeholders": {} + }, + "reject": "Одбиј", + "@reject": { + "type": "text", + "placeholders": {} + }, + "rejectedTheInvitation": "{username} одби позивницу", + "@rejectedTheInvitation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "rejoin": "Поново се придружи", + "@rejoin": { + "type": "text", + "placeholders": {} + }, + "remove": "Уклони", + "@remove": { + "type": "text", + "placeholders": {} + }, + "removeAllOtherDevices": "Уклони све остале уређаје", + "@removeAllOtherDevices": { + "type": "text", + "placeholders": {} + }, + "removedBy": "Уклонио корисник {username}", + "@removedBy": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "removeDevice": "Уклони уређај", + "@removeDevice": { + "type": "text", + "placeholders": {} + }, + "unbanFromChat": "Уклони изгнанство", + "@unbanFromChat": { + "type": "text", + "placeholders": {} + }, + "removeYourAvatar": "Уклоните свој аватар", + "@removeYourAvatar": { + "type": "text", + "placeholders": {} + }, + "renderRichContent": "Приказуј обогаћен садржај поруке", + "@renderRichContent": { + "type": "text", + "placeholders": {} + }, + "replaceRoomWithNewerVersion": "Замени собу новијом верзијом", + "@replaceRoomWithNewerVersion": { + "type": "text", + "placeholders": {} + }, + "reply": "Одговори", + "@reply": { + "type": "text", + "placeholders": {} + }, + "reportMessage": "Пријави поруку", + "@reportMessage": { + "type": "text", + "placeholders": {} + }, + "requestPermission": "Затражи дозволу", + "@requestPermission": { + "type": "text", + "placeholders": {} + }, + "roomHasBeenUpgraded": "Соба је надограђена", + "@roomHasBeenUpgraded": { + "type": "text", + "placeholders": {} + }, + "roomVersion": "Верзија собе", + "@roomVersion": { + "type": "text", + "placeholders": {} + }, + "search": "Претражи", + "@search": { + "type": "text", + "placeholders": {} + }, + "security": "Безбедност", + "@security": { + "type": "text", + "placeholders": {} + }, + "seenByUser": "{username} прегледа", + "@seenByUser": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "send": "Пошаљи", + "@send": { + "type": "text", + "placeholders": {} + }, + "sendAMessage": "Пошаљи поруку", + "@sendAMessage": { + "type": "text", + "placeholders": {} + }, + "sendAudio": "Пошаљи аудио", + "@sendAudio": { + "type": "text", + "placeholders": {} + }, + "sendFile": "Пошаљи фајл", + "@sendFile": { + "type": "text", + "placeholders": {} + }, + "sendImage": "Пошаљи слику", + "@sendImage": { + "type": "text", + "placeholders": {} + }, + "sendMessages": "Слање порука", + "@sendMessages": { + "type": "text", + "placeholders": {} + }, + "sendOriginal": "Пошаљи оригинал", + "@sendOriginal": { + "type": "text", + "placeholders": {} + }, + "sendVideo": "Пошаљи видео", + "@sendVideo": { + "type": "text", + "placeholders": {} + }, + "sentAFile": "{username} посла фајл", + "@sentAFile": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAnAudio": "{username} посла аудио", + "@sentAnAudio": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAPicture": "{username} посла слику", + "@sentAPicture": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentASticker": "{username} посла налепницу", + "@sentASticker": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAVideo": "{username} посла видео", + "@sentAVideo": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentCallInformations": "{senderName} посла податке о позиву", + "@sentCallInformations": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "setAsCanonicalAlias": "Постави као главни алијас", + "@setAsCanonicalAlias": { + "type": "text", + "placeholders": {} + }, + "setCustomEmotes": "постави посебне емотије", + "@setCustomEmotes": { + "type": "text", + "placeholders": {} + }, + "setInvitationLink": "Поставља везу позивнице", + "@setInvitationLink": { + "type": "text", + "placeholders": {} + }, + "setPermissionsLevel": "Одреди ниво дозволе", + "@setPermissionsLevel": { + "type": "text", + "placeholders": {} + }, + "setStatus": "Постави статус", + "@setStatus": { + "type": "text", + "placeholders": {} + }, + "settings": "Поставке", + "@settings": { + "type": "text", + "placeholders": {} + }, + "share": "Подели", + "@share": { + "type": "text", + "placeholders": {} + }, + "sharedTheLocation": "{username} подели локацију", + "@sharedTheLocation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "showPassword": "Прикажи лозинку", + "@showPassword": { + "type": "text", + "placeholders": {} + }, + "singlesignon": "Јединствена пријава", + "@singlesignon": { + "type": "text", + "placeholders": {} + }, + "skip": "Прескочи", + "@skip": { + "type": "text", + "placeholders": {} + }, + "sourceCode": "Изворни код", + "@sourceCode": { + "type": "text", + "placeholders": {} + }, + "startedACall": "{senderName} започе позив", + "@startedACall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "status": "Стање", + "@status": { + "type": "text", + "placeholders": {} + }, + "statusExampleMessage": "Како сте данас?", + "@statusExampleMessage": { + "type": "text", + "placeholders": {} + }, + "submit": "Пошаљи", + "@submit": { + "type": "text", + "placeholders": {} + }, + "systemTheme": "системски", + "@systemTheme": { + "type": "text", + "placeholders": {} + }, + "theyDontMatch": "Не поклапају се", + "@theyDontMatch": { + "type": "text", + "placeholders": {} + }, + "theyMatch": "Поклапају се", + "@theyMatch": { + "type": "text", + "placeholders": {} + }, + "title": "FluffyChat", + "@title": { + "description": "Title for the application", + "type": "text", + "placeholders": {} + }, + "toggleFavorite": "Мењај омиљеност", + "@toggleFavorite": { + "type": "text", + "placeholders": {} + }, + "toggleMuted": "Мењај ућутканост", + "@toggleMuted": { + "type": "text", + "placeholders": {} + }, + "toggleUnread": "Означи не/прочитано", + "@toggleUnread": { + "type": "text", + "placeholders": {} + }, + "tooManyRequestsWarning": "Превише упита. Покушајте касније!", + "@tooManyRequestsWarning": { + "type": "text", + "placeholders": {} + }, + "transferFromAnotherDevice": "Пренос са другог уређаја", + "@transferFromAnotherDevice": { + "type": "text", + "placeholders": {} + }, + "tryToSendAgain": "Покушај слање поново", + "@tryToSendAgain": { + "type": "text", + "placeholders": {} + }, + "unavailable": "Недоступно", + "@unavailable": { + "type": "text", + "placeholders": {} + }, + "unbannedUser": "{username} одблокира корисника {targetName}", + "@unbannedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "unblockDevice": "Одблокирај уређај", + "@unblockDevice": { + "type": "text", + "placeholders": {} + }, + "unknownDevice": "Непознат уређај", + "@unknownDevice": { + "type": "text", + "placeholders": {} + }, + "unknownEncryptionAlgorithm": "Непознат алгоритам шифровања", + "@unknownEncryptionAlgorithm": { + "type": "text", + "placeholders": {} + }, + "unknownEvent": "Непознат догађај „{type}“", + "@unknownEvent": { + "type": "text", + "placeholders": { + "type": {} + } + }, + "unmuteChat": "Врати обавештења", + "@unmuteChat": { + "type": "text", + "placeholders": {} + }, + "unpin": "Откачи", + "@unpin": { + "type": "text", + "placeholders": {} + }, + "unreadChats": "{unreadCount, plural, other{непрочитаних ћаскања: {unreadCount}}}", + "@unreadChats": { + "type": "text", + "placeholders": { + "unreadCount": {} + } + }, + "userAndOthersAreTyping": "{username} и {count} корисника куцају…", + "@userAndOthersAreTyping": { + "type": "text", + "placeholders": { + "username": {}, + "count": {} + } + }, + "userAndUserAreTyping": "{username} и {username2} куцају…", + "@userAndUserAreTyping": { + "type": "text", + "placeholders": { + "username": {}, + "username2": {} + } + }, + "userIsTyping": "{username} куца…", + "@userIsTyping": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "userLeftTheChat": "{username} напусти ћаскање", + "@userLeftTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "username": "Корисничко име", + "@username": { + "type": "text", + "placeholders": {} + }, + "userSentUnknownEvent": "{username} посла {type} догађај", + "@userSentUnknownEvent": { + "type": "text", + "placeholders": { + "username": {}, + "type": {} + } + }, + "verified": "Оверен", + "@verified": { + "type": "text", + "placeholders": {} + }, + "verify": "Верификуј", + "@verify": { + "type": "text", + "placeholders": {} + }, + "verifyStart": "Покрени верификацију", + "@verifyStart": { + "type": "text", + "placeholders": {} + }, + "verifySuccess": "Успешно сте верификовали!", + "@verifySuccess": { + "type": "text", + "placeholders": {} + }, + "verifyTitle": "Верификујем други налог", + "@verifyTitle": { + "type": "text", + "placeholders": {} + }, + "videoCall": "Видео позив", + "@videoCall": { + "type": "text", + "placeholders": {} + }, + "visibilityOfTheChatHistory": "Одреди видљивост историје", + "@visibilityOfTheChatHistory": { + "type": "text", + "placeholders": {} + }, + "visibleForAllParticipants": "видљиво свим учесницима", + "@visibleForAllParticipants": { + "type": "text", + "placeholders": {} + }, + "visibleForEveryone": "видљиво свима", + "@visibleForEveryone": { + "type": "text", + "placeholders": {} + }, + "voiceMessage": "Гласовна порука", + "@voiceMessage": { + "type": "text", + "placeholders": {} + }, + "waitingPartnerAcceptRequest": "Чекам да саговорник прихвати захтев…", + "@waitingPartnerAcceptRequest": { + "type": "text", + "placeholders": {} + }, + "waitingPartnerEmoji": "Чекам да саговорник прихвати емоџије…", + "@waitingPartnerEmoji": { + "type": "text", + "placeholders": {} + }, + "waitingPartnerNumbers": "Чекам да саговорник прихвати бројеве…", + "@waitingPartnerNumbers": { + "type": "text", + "placeholders": {} + }, + "wallpaper": "Тапета", + "@wallpaper": { + "type": "text", + "placeholders": {} + }, + "warning": "Упозорење!", + "@warning": { + "type": "text", + "placeholders": {} + }, + "weSentYouAnEmail": "Послали смо вам е-пошту", + "@weSentYouAnEmail": { + "type": "text", + "placeholders": {} + }, + "whoCanPerformWhichAction": "ко може шта да ради", + "@whoCanPerformWhichAction": { + "type": "text", + "placeholders": {} + }, + "whoIsAllowedToJoinThisGroup": "Ко може да се придружи групи", + "@whoIsAllowedToJoinThisGroup": { + "type": "text", + "placeholders": {} + }, + "whyDoYouWantToReportThis": "Зашто желите ово да пријавите?", + "@whyDoYouWantToReportThis": { + "type": "text", + "placeholders": {} + }, + "wipeChatBackup": "Да обришем резервну копију како би направио нови сигурносни кључ?", + "@wipeChatBackup": { + "type": "text", + "placeholders": {} + }, + "withTheseAddressesRecoveryDescription": "Са овим адресама можете опоравити своју лозинку.", + "@withTheseAddressesRecoveryDescription": { + "type": "text", + "placeholders": {} + }, + "writeAMessage": "напишите поруку…", + "@writeAMessage": { + "type": "text", + "placeholders": {} + }, + "yes": "Да", + "@yes": { + "type": "text", + "placeholders": {} + }, + "you": "Ви", + "@you": { + "type": "text", + "placeholders": {} + }, + "youAreNoLongerParticipatingInThisChat": "Више не учествујете у овом ћаскању", + "@youAreNoLongerParticipatingInThisChat": { + "type": "text", + "placeholders": {} + }, + "youHaveBeenBannedFromThisChat": "Забрањено вам је ово ћаскање", + "@youHaveBeenBannedFromThisChat": { + "type": "text", + "placeholders": {} + }, + "yourPublicKey": "Ваш јавни кључ", + "@yourPublicKey": { + "type": "text", + "placeholders": {} + } +} From 2cc859e788e8b5da34d375853cfada099a4be712 Mon Sep 17 00:00:00 2001 From: Anonymous Date: Thu, 25 Jul 2024 05:09:42 +0000 Subject: [PATCH 116/288] Translated using Weblate (Hebrew) Currently translated at 6.9% (45 of 652 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/he/ --- assets/l10n/intl_he.arb | 3428 +++++++++++++++------------------------ 1 file changed, 1300 insertions(+), 2128 deletions(-) diff --git a/assets/l10n/intl_he.arb b/assets/l10n/intl_he.arb index 87d50b339d..d33fc8af44 100644 --- a/assets/l10n/intl_he.arb +++ b/assets/l10n/intl_he.arb @@ -1,2129 +1,1301 @@ { - "@@last_modified": "2021-08-14 12:41:10.036931", - "about": "אודות", - "@about": { - "type": "text", - "placeholders": {} - }, - "accept": "קבל", - "@accept": { - "type": "text", - "placeholders": {} - }, - "acceptedTheInvitation": "{username} קיבל את ההזמנה", - "@acceptedTheInvitation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "account": "חשבון", - "@account": { - "type": "text", - "placeholders": {} - }, - "activatedEndToEndEncryption": "{username} הפעיל הצפנה מקצה לקצה", - "@activatedEndToEndEncryption": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "admin": "מנהל", - "@admin": { - "type": "text", - "placeholders": {} - }, - "alias": "כינוי", - "@alias": { - "type": "text", - "placeholders": {} - }, - "answeredTheCall": "{senderName} ענה לשיחה", - "@answeredTheCall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "anyoneCanJoin": "כל אחד יכול להצטרף", - "@anyoneCanJoin": { - "type": "text", - "placeholders": {} - }, - "archive": "ארכיון", - "@archive": { - "type": "text", - "placeholders": {} - }, - "areGuestsAllowedToJoin": "האם משתמשים אורחים מורשים להצטרף", - "@areGuestsAllowedToJoin": { - "type": "text", - "placeholders": {} - }, - "areYouSure": "האם אתה בטוח?", - "@areYouSure": { - "type": "text", - "placeholders": {} - }, - "askSSSSSign": "כדי שתוכל לחתום על משתמש אחר , הזן את הסיסמה שלך או את מפתח השחזור.", - "@askSSSSSign": { - "type": "text", - "placeholders": {} - }, - "askVerificationRequest": "לקבל בקשת אימות זו מ- {username}?", - "@askVerificationRequest": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changePassword": "שנה סיסמא", - "@changePassword": { - "type": "text", - "placeholders": {} - }, - "appLock": "נעילת אפליקציה", - "@appLock": { - "type": "text", - "placeholders": {} - }, - "cancel": "ביטול", - "@cancel": { - "type": "text", - "placeholders": {} - }, - "addEmail": "הוסף מייל", - "@addEmail": { - "type": "text", - "placeholders": {} - }, - "all": "הכל", - "@all": { - "type": "text", - "placeholders": {} - }, - "allChats": "כל הצ'אטים", - "@allChats": { - "type": "text", - "placeholders": {} - }, - "banned": "חסום", - "@banned": { - "type": "text", - "placeholders": {} - }, - "sendOnEnter": "שלח בכניסה", - "@sendOnEnter": {}, - "badServerLoginTypesException": "שרת הבית תומך בסוגי הכניסה:\n{serverVersions}\nאבל אפליקציה זו תומכת רק ב:\n{supportedVersions}", - "@badServerLoginTypesException": { - "type": "text", - "placeholders": { - "serverVersions": {}, - "supportedVersions": {} - } - }, - "changedTheGuestAccessRulesTo": "{username} שינה את כללי הגישה לאורחים ל: {rules}", - "@changedTheGuestAccessRulesTo": { - "type": "text", - "placeholders": { - "username": {}, - "rules": {} - } - }, - "changedTheJoinRules": "{username} שינה את כללי ההצטרפות", - "@changedTheJoinRules": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheChatNameTo": "{username} שינה את שם הצ'אט ל: '{chatname}'", - "@changedTheChatNameTo": { - "type": "text", - "placeholders": { - "username": {}, - "chatname": {} - } - }, - "changedTheRoomInvitationLink": "{username} שינה את קישור ההזמנה", - "@changedTheRoomInvitationLink": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "repeatPassword": "כתוב שוב את הסיסמה", - "@repeatPassword": {}, - "areYouSureYouWantToLogout": "האם אתה בטוח שברצונך לצאת?", - "@areYouSureYouWantToLogout": { - "type": "text", - "placeholders": {} - }, - "chat": "צ׳אט", - "@chat": { - "type": "text", - "placeholders": {} - }, - "autoplayImages": "הפעל אוטומטית מדבקות ואנימציות מונפשים", - "@autoplayImages": { - "type": "text", - "placeholder": {} - }, - "badServerVersionsException": "שרת הבית תומך בגרסאות:\n{serverVersions}\nאבל האפליקציה הזו תומכת רק ב-{supportedVersions}", - "@badServerVersionsException": { - "type": "text", - "placeholders": { - "serverVersions": {}, - "supportedVersions": {} - } - }, - "banFromChat": "צאט חסום", - "@banFromChat": { - "type": "text", - "placeholders": {} - }, - "bannedUser": "{username} חסם את {targetName}", - "@bannedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "blockDevice": "חסום מכשיר", - "@blockDevice": { - "type": "text", - "placeholders": {} - }, - "blocked": "חסום", - "@blocked": { - "type": "text", - "placeholders": {} - }, - "botMessages": "הודעות בוט", - "@botMessages": { - "type": "text", - "placeholders": {} - }, - "cantOpenUri": "לא ניתן לפתוח את ה-URI {uri}", - "@cantOpenUri": { - "type": "text", - "placeholders": { - "uri": {} - } - }, - "changeDeviceName": "שנה את שם המכשיר", - "@changeDeviceName": { - "type": "text", - "placeholders": {} - }, - "changedTheChatAvatar": "{username} שינה את האווטאר של הצ'אט", - "@changedTheChatAvatar": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheChatDescriptionTo": "{username} שינה את תיאור הצ'אט ל: '{description}'", - "@changedTheChatDescriptionTo": { - "type": "text", - "placeholders": { - "username": {}, - "description": {} - } - }, - "changedTheChatPermissions": "{username} שינה את הרשאות הצ'אט", - "@changedTheChatPermissions": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheDisplaynameTo": "{username} שינה את שם התצוגה שלו ל: '{displayname}'", - "@changedTheDisplaynameTo": { - "type": "text", - "placeholders": { - "username": {}, - "displayname": {} - } - }, - "changedTheGuestAccessRules": "{username} שינה את כללי הגישה לאורחים", - "@changedTheGuestAccessRules": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheHistoryVisibility": "{username} שינה את נראות ההיסטוריה", - "@changedTheHistoryVisibility": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheHistoryVisibilityTo": "{username} שינה את נראות ההיסטוריה ל: {rules}", - "@changedTheHistoryVisibilityTo": { - "type": "text", - "placeholders": { - "username": {}, - "rules": {} - } - }, - "changedTheJoinRulesTo": "{username} שינה את כללי ההצטרפות ל: {joinRules}", - "@changedTheJoinRulesTo": { - "type": "text", - "placeholders": { - "username": {}, - "joinRules": {} - } - }, - "changedTheProfileAvatar": "{username} שינה את האווטאר שלו", - "@changedTheProfileAvatar": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheRoomAliases": "{username} שינה את כינוי החדר", - "@changedTheRoomAliases": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changeTheHomeserver": "שנה את שרת הבית", - "@changeTheHomeserver": { - "type": "text", - "placeholders": {} - }, - "changeTheme": "שנה את הסגנון שלך", - "@changeTheme": { - "type": "text", - "placeholders": {} - }, - "changeTheNameOfTheGroup": "שנה את שם הקבוצה", - "@changeTheNameOfTheGroup": { - "type": "text", - "placeholders": {} - }, - "changeYourAvatar": "שינוי האווטאר שלך", - "@changeYourAvatar": { - "type": "text", - "placeholders": {} - }, - "channelCorruptedDecryptError": "ההצפנה נפגמה", - "@channelCorruptedDecryptError": { - "type": "text", - "placeholders": {} - }, - "yourChatBackupHasBeenSetUp": "גיבוי הצ'אט שלך הוגדר.", - "@yourChatBackupHasBeenSetUp": {}, - "chatBackup": "גיבוי צ'אט", - "@chatBackup": { - "type": "text", - "placeholders": {} - }, - "commandHint_ban": "חסום את המשתמש הנתון מהחדר הזה", - "@commandHint_ban": { - "type": "text", - "description": "Usage hint for the command /ban" - }, - "commandHint_clearcache": "נקה מטמון", - "@commandHint_clearcache": { - "type": "text", - "description": "Usage hint for the command /clearcache" - }, - "commandHint_create": "צור צ'אט קבוצתי ריק\nהשתמש ב--no-encryption כדי להשבית את ההצפנה", - "@commandHint_create": { - "type": "text", - "description": "Usage hint for the command /create" - }, - "commandHint_discardsession": "התעלם מהסשן", - "@commandHint_discardsession": { - "type": "text", - "description": "Usage hint for the command /discardsession" - }, - "commandHint_dm": "התחל צ'אט ישיר\nהשתמש ב--no-encryption כדי להשבית את ההצפנה", - "@commandHint_dm": { - "type": "text", - "description": "Usage hint for the command /dm" - }, - "commandHint_html": "שלח טקסט בתבנית HTML", - "@commandHint_html": { - "type": "text", - "description": "Usage hint for the command /html" - }, - "commandHint_invite": "הזמן את המשתמש הנתון לחדר זה", - "@commandHint_invite": { - "type": "text", - "description": "Usage hint for the command /invite" - }, - "commandHint_join": "הצטרף לחדר הנתון", - "@commandHint_join": { - "type": "text", - "description": "Usage hint for the command /join" - }, - "commandHint_kick": "הסר את המשתמש הנתון מהחדר הזה", - "@commandHint_kick": { - "type": "text", - "description": "Usage hint for the command /kick" - }, - "commandHint_leave": "עזוב את החדר הזה", - "@commandHint_leave": { - "type": "text", - "description": "Usage hint for the command /leave" - }, - "commandHint_me": "תאר את עצמך", - "@commandHint_me": { - "type": "text", - "description": "Usage hint for the command /me" - }, - "chatDetails": "פרטי צ'אט", - "@chatDetails": { - "type": "text", - "placeholders": {} - }, - "chatBackupDescription": "גיבוי הצ'אט שלך מאובטח באמצעות מפתח אבטחה. אנא וודא שאתה לא מאבד אותו.", - "@chatBackupDescription": { - "type": "text", - "placeholders": {} - }, - "chatHasBeenAddedToThisSpace": "צ'אט נוסף למרחב הזה", - "@chatHasBeenAddedToThisSpace": {}, - "chats": "צ'אטים", - "@chats": { - "type": "text", - "placeholders": {} - }, - "chooseAStrongPassword": "בחר סיסמה חזקה", - "@chooseAStrongPassword": { - "type": "text", - "placeholders": {} - }, - "clearArchive": "נקה ארכיון", - "@clearArchive": {}, - "close": "סגור", - "@close": { - "type": "text", - "placeholders": {} - }, - "commandHint_myroomavatar": "הגדר את התמונה שלך לחדר זה (על ידי mxc-uri)", - "@commandHint_myroomavatar": { - "type": "text", - "description": "Usage hint for the command /myroomavatar" - }, - "commandHint_myroomnick": "הגדר את שם התצוגה שלך עבור חדר זה", - "@commandHint_myroomnick": { - "type": "text", - "description": "Usage hint for the command /myroomnick" - }, - "addToSpace": "הוסף לחלל", - "@addToSpace": {}, - "commandHint_unban": "בטל את החסימה של המשתמש הנתון מהחדר הזה", - "@commandHint_unban": { - "type": "text", - "description": "Usage hint for the command /unban" - }, - "countParticipants": "{count} משתתפים", - "@countParticipants": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "create": "צור", - "@create": { - "type": "text", - "placeholders": {} - }, - "createdTheChat": "{username} יצר את הצ'אט", - "@createdTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "currentlyActive": "פעיל כעת", - "@currentlyActive": { - "type": "text", - "placeholders": {} - }, - "darkTheme": "כהה", - "@darkTheme": { - "type": "text", - "placeholders": {} - }, - "dateAndTimeOfDay": "{date}, {timeOfDay}", - "@dateAndTimeOfDay": { - "type": "text", - "placeholders": { - "date": {}, - "timeOfDay": {} - } - }, - "dateWithoutYear": "{month}-{day}", - "@dateWithoutYear": { - "type": "text", - "placeholders": { - "month": {}, - "day": {} - } - }, - "defaultPermissionLevel": "רמת הרשאת ברירת מחדל", - "@defaultPermissionLevel": { - "type": "text", - "placeholders": {} - }, - "deleteAccount": "מחק חשבון", - "@deleteAccount": { - "type": "text", - "placeholders": {} - }, - "deleteMessage": "מחק הודעה", - "@deleteMessage": { - "type": "text", - "placeholders": {} - }, - "deviceId": "מזהה מכשיר", - "@deviceId": { - "type": "text", - "placeholders": {} - }, - "devices": "התקנים", - "@devices": { - "type": "text", - "placeholders": {} - }, - "directChats": "צ'אטים ישירים", - "@directChats": { - "type": "text", - "placeholders": {} - }, - "downloadFile": "הורד קובץ", - "@downloadFile": { - "type": "text", - "placeholders": {} - }, - "edit": "ערוך", - "@edit": { - "type": "text", - "placeholders": {} - }, - "editDisplayname": "ערוך את שם התצוגה", - "@editDisplayname": { - "type": "text", - "placeholders": {} - }, - "editRoomAliases": "ערוך כינויים לחדר", - "@editRoomAliases": { - "type": "text", - "placeholders": {} - }, - "emoteExists": "אימוט כבר קיים!", - "@emoteExists": { - "type": "text", - "placeholders": {} - }, - "emptyChat": "צ'אט ריק", - "@emptyChat": { - "type": "text", - "placeholders": {} - }, - "encrypted": "מוצפן", - "@encrypted": { - "type": "text", - "placeholders": {} - }, - "enterAnEmailAddress": "הזן כתובת דואר אלקטרוני", - "@enterAnEmailAddress": { - "type": "text", - "placeholders": {} - }, - "enterYourHomeserver": "הזן את שרת הבית שלך", - "@enterYourHomeserver": { - "type": "text", - "placeholders": {} - }, - "everythingReady": "הכל מוכן!", - "@everythingReady": { - "type": "text", - "placeholders": {} - }, - "fileName": "שם קובץ", - "@fileName": { - "type": "text", - "placeholders": {} - }, - "fluffychat": "FluffyChat", - "@fluffychat": { - "type": "text", - "placeholders": {} - }, - "fontSize": "גודל גופן", - "@fontSize": { - "type": "text", - "placeholders": {} - }, - "forward": "העבר", - "@forward": { - "type": "text", - "placeholders": {} - }, - "fromJoining": "מהצטרפות", - "@fromJoining": { - "type": "text", - "placeholders": {} - }, - "fromTheInvitation": "מההזמנה", - "@fromTheInvitation": { - "type": "text", - "placeholders": {} - }, - "goToTheNewRoom": "עבור לחדר החדש", - "@goToTheNewRoom": { - "type": "text", - "placeholders": {} - }, - "groupIsPublic": "הקבוצה ציבורית", - "@groupIsPublic": { - "type": "text", - "placeholders": {} - }, - "groupWith": "קבוצה עם {displayname}", - "@groupWith": { - "type": "text", - "placeholders": { - "displayname": {} - } - }, - "guestsAreForbidden": "אורחים אסורים", - "@guestsAreForbidden": { - "type": "text", - "placeholders": {} - }, - "guestsCanJoin": "אורחים יכולים להצטרף", - "@guestsCanJoin": { - "type": "text", - "placeholders": {} - }, - "id": "מזהה", - "@id": { - "type": "text", - "placeholders": {} - }, - "identity": "זהות", - "@identity": { - "type": "text", - "placeholders": {} - }, - "ignoredUsers": "משתמשים שהתעלמו מהם", - "@ignoredUsers": { - "type": "text", - "placeholders": {} - }, - "incorrectPassphraseOrKey": "ביטוי סיסמה או מפתח שחזור שגויים", - "@incorrectPassphraseOrKey": { - "type": "text", - "placeholders": {} - }, - "inviteContact": "הזמן איש קשר", - "@inviteContact": { - "type": "text", - "placeholders": {} - }, - "invited": "הזמין", - "@invited": { - "type": "text", - "placeholders": {} - }, - "invitedUser": "{username} הזמין את {targetName}", - "@invitedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "invitedUsersOnly": "משתמשים מוזמנים בלבד", - "@invitedUsersOnly": { - "type": "text", - "placeholders": {} - }, - "inviteForMe": "הזמנה בשבילי", - "@inviteForMe": { - "type": "text", - "placeholders": {} - }, - "inviteText": "{username} הזמין אותך ל-FluffyChat.\n1. התקן את FluffyChat: https://fluffychat.im\n2. הירשם או היכנס\n3. פתח את קישור ההזמנה: {link}", - "@inviteText": { - "type": "text", - "placeholders": { - "username": {}, - "link": {} - } - }, - "isTyping": "מקליד/ה…", - "@isTyping": { - "type": "text", - "placeholders": {} - }, - "joinedTheChat": "{username} הצטרף לצ'אט", - "@joinedTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "joinRoom": "הצטרף לחדר", - "@joinRoom": { - "type": "text", - "placeholders": {} - }, - "kicked": "{username} בעט ב {targetName}", - "@kicked": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "kickedAndBanned": "{username} בעט וחסם {targetName}", - "@kickedAndBanned": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "kickFromChat": "בעיטה מהצ'אט", - "@kickFromChat": { - "type": "text", - "placeholders": {} - }, - "lastActiveAgo": "פעילות אחרונה: {localizedTimeShort}", - "@lastActiveAgo": { - "type": "text", - "placeholders": { - "localizedTimeShort": {} - } - }, - "leftTheChat": "עזב את הצ'אט", - "@leftTheChat": { - "type": "text", - "placeholders": {} - }, - "loadingPleaseWait": "טוען אנא המתן.", - "@loadingPleaseWait": { - "type": "text", - "placeholders": {} - }, - "loadMore": "טען עוד…", - "@loadMore": { - "type": "text", - "placeholders": {} - }, - "locationDisabledNotice": "שירותי המיקום מושבתים. אנא הפעל אותם כדי לשתף את המיקום שלך.", - "@locationDisabledNotice": { - "type": "text", - "placeholders": {} - }, - "copy": "העתק", - "@copy": { - "type": "text", - "placeholders": {} - }, - "commandHint_send": "שלח טקסט", - "@commandHint_send": { - "type": "text", - "description": "Usage hint for the command /send" - }, - "commandHint_op": "הגדרת רמת צריכת החשמל של המשתמש הנתון (ברירת מחדל: 50)", - "@commandHint_op": { - "type": "text", - "description": "Usage hint for the command /op" - }, - "commandHint_plain": "שלח טקסט לא מעוצב", - "@commandHint_plain": { - "type": "text", - "description": "Usage hint for the command /plain" - }, - "commandHint_react": "שלח תשובה כתגובה", - "@commandHint_react": { - "type": "text", - "description": "Usage hint for the command /react" - }, - "containsUserName": "מכיל שם משתמש", - "@containsUserName": { - "type": "text", - "placeholders": {} - }, - "createNewSpace": "חלל חדש", - "@createNewSpace": { - "type": "text", - "placeholders": {} - }, - "enableEncryption": "אפשר הצפנה", - "@enableEncryption": { - "type": "text", - "placeholders": {} - }, - "inviteContactToGroup": "הזמן איש קשר אל {groupName}", - "@inviteContactToGroup": { - "type": "text", - "placeholders": { - "groupName": {} - } - }, - "dateWithYear": "{year}-{month}-{day}", - "@dateWithYear": { - "type": "text", - "placeholders": { - "year": {}, - "month": {}, - "day": {} - } - }, - "deactivateAccountWarning": "פעולה זו תשבית את חשבון המשתמש שלך. אי אפשר לבטל את זה! האם אתה בטוח?", - "@deactivateAccountWarning": { - "type": "text", - "placeholders": {} - }, - "device": "מכשיר", - "@device": { - "type": "text", - "placeholders": {} - }, - "group": "קבוצה", - "@group": { - "type": "text", - "placeholders": {} - }, - "displaynameHasBeenChanged": "שם התצוגה השתנה", - "@displaynameHasBeenChanged": { - "type": "text", - "placeholders": {} - }, - "editBlockedServers": "ערוך שרתים חסומים", - "@editBlockedServers": { - "type": "text", - "placeholders": {} - }, - "editRoomAvatar": "עריכת אווטאר של חדר", - "@editRoomAvatar": { - "type": "text", - "placeholders": {} - }, - "endedTheCall": "{senderName} סיים את השיחה", - "@endedTheCall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "groups": "קבוצות", - "@groups": { - "type": "text", - "placeholders": {} - }, - "enableEncryptionWarning": "לא תוכל לבטל את ההצפנה יותר. האם אתה בטוח?", - "@enableEncryptionWarning": { - "type": "text", - "placeholders": {} - }, - "encryption": "הצפנה", - "@encryption": { - "type": "text", - "placeholders": {} - }, - "errorObtainingLocation": "שגיאה בהשגת מיקום: {error}", - "@errorObtainingLocation": { - "type": "text", - "placeholders": { - "error": {} - } - }, - "hasWithdrawnTheInvitationFor": "{username} ביטל את ההזמנה עבור {targetName}", - "@hasWithdrawnTheInvitationFor": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "hideRedactedEvents": "הסתר אירועים מצונזרים", - "@hideRedactedEvents": { - "type": "text", - "placeholders": {} - }, - "encryptionNotEnabled": "ההצפנה אינה מופעלת", - "@encryptionNotEnabled": { - "type": "text", - "placeholders": {} - }, - "extremeOffensive": "פוגעני ביותר", - "@extremeOffensive": { - "type": "text", - "placeholders": {} - }, - "hideUnknownEvents": "הסתר אירועים לא ידועים", - "@hideUnknownEvents": { - "type": "text", - "placeholders": {} - }, - "loadCountMoreParticipants": "טען {count} משתתפים נוספים", - "@loadCountMoreParticipants": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "homeserver": "שרת בית", - "@homeserver": {}, - "ignore": "התעלם", - "@ignore": { - "type": "text", - "placeholders": {} - }, - "iHaveClickedOnLink": "לחצתי על הקישור", - "@iHaveClickedOnLink": { - "type": "text", - "placeholders": {} - }, - "leave": "לעזוב", - "@leave": { - "type": "text", - "placeholders": {} - }, - "license": "רשיון", - "@license": { - "type": "text", - "placeholders": {} - }, - "lightTheme": "בהיר", - "@lightTheme": { - "type": "text", - "placeholders": {} - }, - "delete": "מחיקה", - "@delete": { - "type": "text", - "placeholders": {} - }, - "inoffensive": "לֹא פּוֹגֵעַ", - "@inoffensive": { - "type": "text", - "placeholders": {} - }, - "configureChat": "קביעת תצורה של צ'אט", - "@configureChat": { - "type": "text", - "placeholders": {} - }, - "confirm": "לאשר", - "@confirm": { - "type": "text", - "placeholders": {} - }, - "connect": "התחבר", - "@connect": { - "type": "text", - "placeholders": {} - }, - "contactHasBeenInvitedToTheGroup": "איש הקשר הוזמן לקבוצה", - "@contactHasBeenInvitedToTheGroup": { - "type": "text", - "placeholders": {} - }, - "containsDisplayName": "מכיל שם תצוגה", - "@containsDisplayName": { - "type": "text", - "placeholders": {} - }, - "contentHasBeenReported": "התוכן דווח למנהלי השרת", - "@contentHasBeenReported": { - "type": "text", - "placeholders": {} - }, - "copiedToClipboard": "הועתק ללוח הגזירים", - "@copiedToClipboard": { - "type": "text", - "placeholders": {} - }, - "commandInvalid": "הפקודה אינה חוקית", - "@commandInvalid": { - "type": "text" - }, - "commandMissing": "{command} אינו פקודה.", - "@commandMissing": { - "type": "text", - "placeholders": { - "command": {} - }, - "description": "State that {command} is not a valid /command." - }, - "compareEmojiMatch": "השווה וודא שהאימוג'י הבאים תואמים לאלו של המכשיר השני:", - "@compareEmojiMatch": { - "type": "text", - "placeholders": {} - }, - "compareNumbersMatch": "השווה וודא שהמספרים הבאים תואמים לאלה של המכשיר השני:", - "@compareNumbersMatch": { - "type": "text", - "placeholders": {} - }, - "copyToClipboard": "העתק ללוח", - "@copyToClipboard": { - "type": "text", - "placeholders": {} - }, - "couldNotDecryptMessage": "לא ניתן לפענח הודעה: {error}", - "@couldNotDecryptMessage": { - "type": "text", - "placeholders": { - "error": {} - } - }, - "help": "עזרה", - "@help": { - "type": "text", - "placeholders": {} - }, - "howOffensiveIsThisContent": "עד כמה התוכן הזה פוגעני?", - "@howOffensiveIsThisContent": { - "type": "text", - "placeholders": {} - }, - "locationPermissionDeniedNotice": "הרשאת המיקום נדחתה. אנא אפשר את היכולת לשתף את מיקומך.", - "@locationPermissionDeniedNotice": { - "type": "text", - "placeholders": {} - }, - "login": "כניסה", - "@login": { - "type": "text", - "placeholders": {} - }, - "moderator": "מנחה", - "@moderator": { - "type": "text", - "placeholders": {} - }, - "needPantalaimonWarning": "שים לב שאתה צריך Pantalaimon כדי להשתמש בהצפנה מקצה לקצה לעת עתה.", - "@needPantalaimonWarning": { - "type": "text", - "placeholders": {} - }, - "newChat": "צ'אט חדש", - "@newChat": { - "type": "text", - "placeholders": {} - }, - "newVerificationRequest": "בקשת אימות חדשה!", - "@newVerificationRequest": { - "type": "text", - "placeholders": {} - }, - "next": "הבא", - "@next": { - "type": "text", - "placeholders": {} - }, - "no": "לא", - "@no": { - "type": "text", - "placeholders": {} - }, - "noConnectionToTheServer": "אין חיבור לשרת", - "@noConnectionToTheServer": { - "type": "text", - "placeholders": {} - }, - "noEncryptionForPublicRooms": "אתה יכול להפעיל הצפנה רק כשהחדר כבר לא נגיש לציבור.", - "@noEncryptionForPublicRooms": { - "type": "text", - "placeholders": {} - }, - "noRoomsFound": "לא נמצאו חדרים…", - "@noRoomsFound": { - "type": "text", - "placeholders": {} - }, - "notifications": "התראות", - "@notifications": { - "type": "text", - "placeholders": {} - }, - "numUsersTyping": "{count} משתמשים מקלידים…", - "@numUsersTyping": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "obtainingLocation": "משיג מיקום…", - "@obtainingLocation": { - "type": "text", - "placeholders": {} - }, - "onlineKeyBackupEnabled": "גיבוי מפתח מקוון מופעל", - "@onlineKeyBackupEnabled": { - "type": "text", - "placeholders": {} - }, - "oopsPushError": "אופס! למרבה הצער, אירעה שגיאה בעת הגדרת התראות.", - "@oopsPushError": { - "type": "text", - "placeholders": {} - }, - "oopsSomethingWentWrong": "אופס, משהו השתבש…", - "@oopsSomethingWentWrong": { - "type": "text", - "placeholders": {} - }, - "openAppToReadMessages": "פתח את האפליקציה לקריאת הודעות", - "@openAppToReadMessages": { - "type": "text", - "placeholders": {} - }, - "oneClientLoggedOut": "אחד מהמכשירים שלך התנתק", - "@oneClientLoggedOut": {}, - "addAccount": "הוסף חשבון", - "@addAccount": {}, - "editBundlesForAccount": "ערוך חבילות עבור חשבון זה", - "@editBundlesForAccount": {}, - "participant": "משתתף", - "@participant": { - "type": "text", - "placeholders": {} - }, - "password": "סיסמה", - "@password": { - "type": "text", - "placeholders": {} - }, - "pleaseClickOnLink": "אנא לחץ על הקישור במייל ולאחר מכן המשך.", - "@pleaseClickOnLink": { - "type": "text", - "placeholders": {} - }, - "pleaseEnter4Digits": "אנא הזן 4 ספרות או השאר ריק כדי להשבית את נעילת האפליקציה.", - "@pleaseEnter4Digits": { - "type": "text", - "placeholders": {} - }, - "online": "מחובר/ת", - "@online": { - "type": "text", - "placeholders": {} - }, - "addToBundle": "הוסף לחבילה", - "@addToBundle": {}, - "passphraseOrKey": "ביטוי סיסמה או מפתח שחזור", - "@passphraseOrKey": { - "type": "text", - "placeholders": {} - }, - "passwordForgotten": "שכחתי סיסמה", - "@passwordForgotten": { - "type": "text", - "placeholders": {} - }, - "passwordHasBeenChanged": "הסיסמה שונתה", - "@passwordHasBeenChanged": { - "type": "text", - "placeholders": {} - }, - "passwordRecovery": "שחזור סיסמה", - "@passwordRecovery": { - "type": "text", - "placeholders": {} - }, - "people": "אנשים", - "@people": { - "type": "text", - "placeholders": {} - }, - "pickImage": "בחר תמונה", - "@pickImage": { - "type": "text", - "placeholders": {} - }, - "play": "הפעל {fileName}", - "@play": { - "type": "text", - "placeholders": { - "fileName": {} - } - }, - "pleaseChoose": "אנא בחר", - "@pleaseChoose": { - "type": "text", - "placeholders": {} - }, - "pleaseChooseAPasscode": "אנא בחר קוד גישה", - "@pleaseChooseAPasscode": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourPassword": "נא הזן את הסיסמה שלך", - "@pleaseEnterYourPassword": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourPin": "אנא הזן את קוד הpin שלך", - "@pleaseEnterYourPin": { - "type": "text", - "placeholders": {} - }, - "pin": "קוד pin", - "@pin": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourUsername": "אנא הזן שם משתמש", - "@pleaseEnterYourUsername": { - "type": "text", - "placeholders": {} - }, - "newMessageInFluffyChat": "הודעה חדשה ב-FluffyChat", - "@newMessageInFluffyChat": { - "type": "text", - "placeholders": {} - }, - "noGoogleServicesWarning": "נראה שאין לך שירותי גוגל בטלפון שלך. זו החלטה טובה לפרטיות שלך! כדי לקבל התרעות ב- FluffyChat אנו ממליצים להשתמש https://microg.org/ או https://unifiedpush.org/.", - "@noGoogleServicesWarning": { - "type": "text", - "placeholders": {} - }, - "noMatrixServer": "{server1} אינו שרת מטריקס, השתמש ב-{server2} במקום זאת?", - "@noMatrixServer": { - "type": "text", - "placeholders": { - "server1": {}, - "server2": {} - } - }, - "none": "ללא", - "@none": { - "type": "text", - "placeholders": {} - }, - "noPasswordRecoveryDescription": "עדיין לא הוספת דרך לשחזר את הסיסמה שלך.", - "@noPasswordRecoveryDescription": { - "type": "text", - "placeholders": {} - }, - "offensive": "פוגעני", - "@offensive": { - "type": "text", - "placeholders": {} - }, - "notificationsEnabledForThisAccount": "התראות הופעלו עבור חשבון זה", - "@notificationsEnabledForThisAccount": { - "type": "text", - "placeholders": {} - }, - "bundleName": "שם החבילה", - "@bundleName": {}, - "offline": "לא מקוון", - "@offline": { - "type": "text", - "placeholders": {} - }, - "openVideoCamera": "פתח את המצלמה לסרטון", - "@openVideoCamera": { - "type": "text", - "placeholders": {} - }, - "removeFromBundle": "הסר מחבילה זו", - "@removeFromBundle": {}, - "enableMultiAccounts": "(בטא) אפשר ריבוי חשבונות במכשיר זה", - "@enableMultiAccounts": {}, - "openInMaps": "פתיחה במפות", - "@openInMaps": { - "type": "text", - "placeholders": {} - }, - "link": "קישור", - "@link": {}, - "serverRequiresEmail": "שרת זה צריך לאמת את כתובת הדואר האלקטרוני שלך לרישום.", - "@serverRequiresEmail": {}, - "logout": "יציאה", - "@logout": { - "type": "text", - "placeholders": {} - }, - "muteChat": "השתקת הצ'אט", - "@muteChat": { - "type": "text", - "placeholders": {} - }, - "scanQrCode": "סרוק קוד QR", - "@scanQrCode": {}, - "noPermission": "אין הרשאה", - "@noPermission": { - "type": "text", - "placeholders": {} - }, - "or": "או", - "@or": { - "type": "text", - "placeholders": {} - }, - "logInTo": "היכנס אל {homeserver}", - "@logInTo": { - "type": "text", - "placeholders": { - "homeserver": {} - } - }, - "memberChanges": "שינויים בחבר", - "@memberChanges": { - "type": "text", - "placeholders": {} - }, - "mention": "הזכיר", - "@mention": { - "type": "text", - "placeholders": {} - }, - "messages": "הודעות", - "@messages": { - "type": "text", - "placeholders": {} - }, - "openCamera": "פתח מצלמה", - "@openCamera": { - "type": "text", - "placeholders": {} - }, - "@showPassword": { - "type": "text", - "placeholders": {} - }, - "@hugContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@theyMatch": { - "type": "text", - "placeholders": {} - }, - "@jumpToLastReadMessage": {}, - "@allRooms": { - "type": "text", - "placeholders": {} - }, - "@commandHint_cuddle": {}, - "@widgetVideo": {}, - "@dismiss": {}, - "@unknownDevice": { - "type": "text", - "placeholders": {} - }, - "@emoteShortcode": { - "type": "text", - "placeholders": {} - }, - "@reportErrorDescription": {}, - "@setPermissionsLevel": { - "type": "text", - "placeholders": {} - }, - "@reply": { - "type": "text", - "placeholders": {} - }, - "@removeYourAvatar": { - "type": "text", - "placeholders": {} - }, - "@unsupportedAndroidVersion": {}, - "@widgetJitsi": {}, - "@youAreNoLongerParticipatingInThisChat": { - "type": "text", - "placeholders": {} - }, - "@messageType": {}, - "@indexedDbErrorLong": {}, - "@toggleMuted": { - "type": "text", - "placeholders": {} - }, - "@title": { - "description": "Title for the application", - "type": "text", - "placeholders": {} - }, - "@verifySuccess": { - "type": "text", - "placeholders": {} - }, - "@sendFile": { - "type": "text", - "placeholders": {} - }, - "@startFirstChat": {}, - "@callingAccount": {}, - "@requestPermission": { - "type": "text", - "placeholders": {} - }, - "@sentAPicture": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@setColorTheme": {}, - "@nextAccount": {}, - "@singlesignon": { - "type": "text", - "placeholders": {} - }, - "@warning": { - "type": "text", - "placeholders": {} - }, - "@allSpaces": {}, - "@supposedMxid": { - "type": "text", - "placeholders": { - "mxid": {} - } - }, - "@user": {}, - "@roomVersion": { - "type": "text", - "placeholders": {} - }, - "@sentAFile": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@videoCall": { - "type": "text", - "placeholders": {} - }, - "@youAcceptedTheInvitation": {}, - "@userAndOthersAreTyping": { - "type": "text", - "placeholders": { - "username": {}, - "count": {} - } - }, - "@youInvitedBy": { - "placeholders": { - "user": {} - } - }, - "@userIsTyping": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@sentAVideo": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@banUserDescription": {}, - "@widgetEtherpad": {}, - "@waitingPartnerAcceptRequest": { - "type": "text", - "placeholders": {} - }, - "@remove": { - "type": "text", - "placeholders": {} - }, - "@writeAMessage": { - "type": "text", - "placeholders": {} - }, - "@removeDevicesDescription": {}, - "@separateChatTypes": { - "type": "text", - "placeholders": {} - }, - "@tryAgain": {}, - "@youKickedAndBanned": { - "placeholders": { - "user": {} - } - }, - "@removeDevice": { - "type": "text", - "placeholders": {} - }, - "@unbanUserDescription": {}, - "@userAndUserAreTyping": { - "type": "text", - "placeholders": { - "username": {}, - "username2": {} - } - }, - "@saveFile": { - "type": "text", - "placeholders": {} - }, - "@youRejectedTheInvitation": {}, - "@otherCallingPermissions": {}, - "@messagesStyle": {}, - "@widgetUrlError": {}, - "@emailOrUsername": {}, - "@newSpaceDescription": {}, - "@chatDescription": {}, - "@callingAccountDetails": {}, - "@pleaseFollowInstructionsOnWeb": { - "type": "text", - "placeholders": {} - }, - "@enterSpace": {}, - "@encryptThisChat": {}, - "@unavailable": { - "type": "text", - "placeholders": {} - }, - "@previousAccount": {}, - "@publicRooms": { - "type": "text", - "placeholders": {} - }, - "@sendMessages": { - "type": "text", - "placeholders": {} - }, - "@emoteWarnNeedToPick": { - "type": "text", - "placeholders": {} - }, - "@reopenChat": {}, - "@pleaseEnterRecoveryKey": {}, - "@toggleFavorite": { - "type": "text", - "placeholders": {} - }, - "@widgetNameError": {}, - "@unpin": { - "type": "text", - "placeholders": {} - }, - "@reportMessage": { - "type": "text", - "placeholders": {} - }, - "@spaceIsPublic": { - "type": "text", - "placeholders": {} - }, - "@addWidget": {}, - "@removeAllOtherDevices": { - "type": "text", - "placeholders": {} - }, - "@unblockDevice": { - "type": "text", - "placeholders": {} - }, - "@countFiles": { - "placeholders": { - "count": {} - } - }, - "@noKeyForThisMessage": {}, - "@shareLocation": { - "type": "text", - "placeholders": {} - }, - "@reason": { - "type": "text", - "placeholders": {} - }, - "@commandHint_markasgroup": {}, - "@hydrateTor": {}, - "@pushNotificationsNotAvailable": {}, - "@storeInAppleKeyChain": {}, - "@replaceRoomWithNewerVersion": { - "type": "text", - "placeholders": {} - }, - "@hydrate": {}, - "@invalidServerName": {}, - "@chatPermissions": {}, - "@voiceMessage": { - "type": "text", - "placeholders": {} - }, - "@wipeChatBackup": { - "type": "text", - "placeholders": {} - }, - "@sender": {}, - "@storeInAndroidKeystore": {}, - "@signInWithPassword": {}, - "@weSentYouAnEmail": { - "type": "text", - "placeholders": {} - }, - "@makeAdminDescription": {}, - "@noEmotesFound": { - "type": "text", - "placeholders": {} - }, - "@synchronizingPleaseWait": { - "type": "text", - "placeholders": {} - }, - "@transferFromAnotherDevice": { - "type": "text", - "placeholders": {} - }, - "@pushRules": { - "type": "text", - "placeholders": {} - }, - "@saveKeyManuallyDescription": {}, - "@renderRichContent": { - "type": "text", - "placeholders": {} - }, - "@whyIsThisMessageEncrypted": {}, - "@rejectedTheInvitation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@setChatDescription": {}, - "@userLeftTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@spaceName": { - "type": "text", - "placeholders": {} - }, - "@importFromZipFile": {}, - "@toggleUnread": { - "type": "text", - "placeholders": {} - }, - "@dehydrateWarning": {}, - "@sendOriginal": { - "type": "text", - "placeholders": {} - }, - "@noOtherDevicesFound": {}, - "@whoIsAllowedToJoinThisGroup": { - "type": "text", - "placeholders": {} - }, - "@seenByUser": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@redactedBy": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@submit": { - "type": "text", - "placeholders": {} - }, - "@videoCallsBetaWarning": {}, - "@unmuteChat": { - "type": "text", - "placeholders": {} - }, - "@redactedAnEvent": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@yes": { - "type": "text", - "placeholders": {} - }, - "@signInWith": { - "type": "text", - "placeholders": { - "provider": {} - } - }, - "@username": { - "type": "text", - "placeholders": {} - }, - "@fileIsTooBigForServer": {}, - "@verified": { - "type": "text", - "placeholders": {} - }, - "@setStatus": { - "type": "text", - "placeholders": {} - }, - "@callingPermissions": {}, - "@readUpToHere": {}, - "@start": {}, - "@register": { - "type": "text", - "placeholders": {} - }, - "@unlockOldMessages": {}, - "@numChats": { - "type": "number", - "placeholders": { - "number": {} - } - }, - "@recording": { - "type": "text", - "placeholders": {} - }, - "@optionalRedactReason": {}, - "@waitingPartnerEmoji": { - "type": "text", - "placeholders": {} - }, - "@tryToSendAgain": { - "type": "text", - "placeholders": {} - }, - "@dehydrate": {}, - "@send": { - "type": "text", - "placeholders": {} - }, - "@visibleForAllParticipants": { - "type": "text", - "placeholders": {} - }, - "@sendAsText": { - "type": "text" - }, - "@archiveRoomDescription": {}, - "@exportEmotePack": {}, - "@sendSticker": { - "type": "text", - "placeholders": {} - }, - "@switchToAccount": { - "type": "number", - "placeholders": { - "number": {} - } - }, - "@setAsCanonicalAlias": { - "type": "text", - "placeholders": {} - }, - "@whyDoYouWantToReportThis": { - "type": "text", - "placeholders": {} - }, - "@removedBy": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@emoteSettings": { - "type": "text", - "placeholders": {} - }, - "@experimentalVideoCalls": {}, - "@pleaseEnterRecoveryKeyDescription": {}, - "@withTheseAddressesRecoveryDescription": { - "type": "text", - "placeholders": {} - }, - "@inviteContactToGroupQuestion": {}, - "@redactedByBecause": { - "type": "text", - "placeholders": { - "username": {}, - "reason": {} - } - }, - "@youHaveWithdrawnTheInvitationFor": { - "placeholders": { - "user": {} - } - }, - "@skip": { - "type": "text", - "placeholders": {} - }, - "@appearOnTopDetails": {}, - "@roomHasBeenUpgraded": { - "type": "text", - "placeholders": {} - }, - "@enterRoom": {}, - "@enableEmotesGlobally": { - "type": "text", - "placeholders": {} - }, - "@reportUser": {}, - "@unbannedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "@confirmEventUnpin": {}, - "@youInvitedUser": { - "placeholders": { - "user": {} - } - }, - "@fileHasBeenSavedAt": { - "type": "text", - "placeholders": { - "path": {} - } - }, - "@redactMessageDescription": {}, - "@rejoin": { - "type": "text", - "placeholders": {} - }, - "@recoveryKey": {}, - "@redactMessage": { - "type": "text", - "placeholders": {} - }, - "@invalidInput": {}, - "@dehydrateTorLong": {}, - "@yourPublicKey": { - "type": "text", - "placeholders": {} - }, - "@tooManyRequestsWarning": { - "type": "text", - "placeholders": {} - }, - "@doNotShowAgain": {}, - "@report": {}, - "@status": { - "type": "text", - "placeholders": {} - }, - "@verifyStart": { - "type": "text", - "placeholders": {} - }, - "@unverified": {}, - "@hideUnimportantStateEvents": {}, - "@screenSharingTitle": {}, - "@widgetCustom": {}, - "@sentCallInformations": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@addToSpaceDescription": {}, - "@googlyEyesContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@youBannedUser": { - "placeholders": { - "user": {} - } - }, - "@theyDontMatch": { - "type": "text", - "placeholders": {} - }, - "@youHaveBeenBannedFromThisChat": { - "type": "text", - "placeholders": {} - }, - "@addChatDescription": {}, - "@sentAnAudio": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@hasKnocked": { - "placeholders": { - "user": {} - } - }, - "@publish": {}, - "@openLinkInBrowser": {}, - "@messageInfo": {}, - "@disableEncryptionWarning": {}, - "@directChat": {}, - "@wrongPinEntered": { - "type": "text", - "placeholders": { - "seconds": {} - } - }, - "@sendTypingNotifications": {}, - "@inviteGroupChat": {}, - "@appearOnTop": {}, - "@invitePrivateChat": {}, - "@verifyTitle": { - "type": "text", - "placeholders": {} - }, - "@foregroundServiceRunning": {}, - "@voiceCall": {}, - "@unknownEncryptionAlgorithm": { - "type": "text", - "placeholders": {} - }, - "@importEmojis": {}, - "@wasDirectChatDisplayName": { - "type": "text", - "placeholders": { - "oldDisplayName": {} - } - }, - "@noChatDescriptionYet": {}, - "@whoCanPerformWhichAction": { - "type": "text", - "placeholders": {} - }, - "@confirmMatrixId": {}, - "@learnMore": {}, - "@you": { - "type": "text", - "placeholders": {} - }, - "@notAnImage": {}, - "@users": {}, - "@openGallery": {}, - "@chatDescriptionHasBeenChanged": {}, - "@search": { - "type": "text", - "placeholders": {} - }, - "@newGroup": {}, - "@dehydrateTor": {}, - "@removeFromSpace": {}, - "@sourceCode": { - "type": "text", - "placeholders": {} - }, - "@roomUpgradeDescription": {}, - "@userSentUnknownEvent": { - "type": "text", - "placeholders": { - "username": {}, - "type": {} - } - }, - "@pleaseEnterANumber": {}, - "@youKicked": { - "placeholders": { - "user": {} - } - }, - "@profileNotFound": {}, - "@jump": {}, - "@reactedWith": { - "type": "text", - "placeholders": { - "sender": {}, - "reaction": {} - } - }, - "@sorryThatsNotPossible": {}, - "@videoWithSize": { - "type": "text", - "placeholders": { - "size": {} - } - }, - "@shareInviteLink": {}, - "@commandHint_markasdm": {}, - "@recoveryKeyLost": {}, - "@cuddleContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@deviceKeys": {}, - "@waitingPartnerNumbers": { - "type": "text", - "placeholders": {} - }, - "@emoteKeyboardNoRecents": { - "type": "text", - "placeholders": {} - }, - "@setCustomEmotes": { - "type": "text", - "placeholders": {} - }, - "@startedACall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@emoteInvalid": { - "type": "text", - "placeholders": {} - }, - "@systemTheme": { - "type": "text", - "placeholders": {} - }, - "@visibilityOfTheChatHistory": { - "type": "text", - "placeholders": {} - }, - "@settings": { - "type": "text", - "placeholders": {} - }, - "@setTheme": {}, - "@youJoinedTheChat": {}, - "@wallpaper": { - "type": "text", - "placeholders": {} - }, - "@statusExampleMessage": { - "type": "text", - "placeholders": {} - }, - "@security": { - "type": "text", - "placeholders": {} - }, - "@markAsRead": {}, - "@sendAudio": { - "type": "text", - "placeholders": {} - }, - "@widgetName": {}, - "@sentASticker": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@errorAddingWidget": {}, - "@commandHint_hug": {}, - "@replace": {}, - "@reject": { - "type": "text", - "placeholders": {} - }, - "@youUnbannedUser": { - "placeholders": { - "user": {} - } - }, - "@visibleForEveryone": { - "type": "text", - "placeholders": {} - }, - "@newSpace": {}, - "@unknownEvent": { - "type": "text", - "placeholders": { - "type": {} - } - }, - "@emojis": {}, - "@share": { - "type": "text", - "placeholders": {} - }, - "@commandHint_googly": {}, - "@pleaseTryAgainLaterOrChooseDifferentServer": {}, - "@createGroup": {}, - "@privacy": { - "type": "text", - "placeholders": {} - }, - "@sendImage": { - "type": "text", - "placeholders": {} - }, - "@hydrateTorLong": {}, - "@time": {}, - "@custom": {}, - "@noBackupWarning": {}, - "@verify": { - "type": "text", - "placeholders": {} - }, - "@sendVideo": { - "type": "text", - "placeholders": {} - }, - "@storeInSecureStorageDescription": {}, - "@openChat": {}, - "@kickUserDescription": {}, - "@sendAMessage": { - "type": "text", - "placeholders": {} - }, - "@importNow": {}, - "@setInvitationLink": { - "type": "text", - "placeholders": {} - }, - "@pinMessage": {}, - "@invite": {}, - "@emotePacks": { - "type": "text", - "placeholders": {} - }, - "@indexedDbErrorTitle": {}, - "@unsupportedAndroidVersionLong": {}, - "@storeSecurlyOnThisDevice": {}, - "@ok": { - "type": "text", - "placeholders": {} - }, - "@sharedTheLocation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@unbanFromChat": { - "type": "text", - "placeholders": {} - }, - "@screenSharingDetail": {}, - "@unreadChats": { - "type": "text", - "placeholders": { - "unreadCount": {} - } - }, - "@placeCall": {} -} \ No newline at end of file + "@@last_modified": "2021-08-14 12:41:10.036931", + "about": "אודות", + "@about": { + "type": "text", + "placeholders": {} + }, + "accept": "קבל", + "@accept": { + "type": "text", + "placeholders": {} + }, + "acceptedTheInvitation": "{username} קיבל את ההזמנה", + "@acceptedTheInvitation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "account": "חשבון", + "@account": { + "type": "text", + "placeholders": {} + }, + "activatedEndToEndEncryption": "{username} הפעיל הצפנה מקצה לקצה", + "@activatedEndToEndEncryption": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "admin": "מנהל", + "@admin": { + "type": "text", + "placeholders": {} + }, + "alias": "כינוי", + "@alias": { + "type": "text", + "placeholders": {} + }, + "answeredTheCall": "{senderName} ענה לשיחה", + "@answeredTheCall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "anyoneCanJoin": "כל אחד יכול להצטרף", + "@anyoneCanJoin": { + "type": "text", + "placeholders": {} + }, + "archive": "ארכיון", + "@archive": { + "type": "text", + "placeholders": {} + }, + "areGuestsAllowedToJoin": "האם משתמשים אורחים מורשים להצטרף", + "@areGuestsAllowedToJoin": { + "type": "text", + "placeholders": {} + }, + "areYouSure": "האם אתה בטוח?", + "@areYouSure": { + "type": "text", + "placeholders": {} + }, + "askSSSSSign": "כדי שתוכל לחתום על משתמש אחר , הזן את הסיסמה שלך או את מפתח השחזור.", + "@askSSSSSign": { + "type": "text", + "placeholders": {} + }, + "askVerificationRequest": "לקבל בקשת אימות זו מ- {username}?", + "@askVerificationRequest": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changePassword": "שנה סיסמא", + "@changePassword": { + "type": "text", + "placeholders": {} + }, + "appLock": "נעילת אפליקציה", + "@appLock": { + "type": "text", + "placeholders": {} + }, + "cancel": "ביטול", + "@cancel": { + "type": "text", + "placeholders": {} + }, + "addEmail": "הוסף מייל", + "@addEmail": { + "type": "text", + "placeholders": {} + }, + "all": "הכל", + "@all": { + "type": "text", + "placeholders": {} + }, + "allChats": "כל הצ'אטים", + "@allChats": { + "type": "text", + "placeholders": {} + }, + "banned": "חסום", + "@banned": { + "type": "text", + "placeholders": {} + }, + "sendOnEnter": "שלח בכניסה", + "@sendOnEnter": {}, + "badServerLoginTypesException": "שרת הבית תומך בסוגי הכניסה:\n{serverVersions}\nאבל אפליקציה זו תומכת רק ב:\n{supportedVersions}", + "@badServerLoginTypesException": { + "type": "text", + "placeholders": { + "serverVersions": {}, + "supportedVersions": {} + } + }, + "changedTheGuestAccessRulesTo": "{username} שינה את כללי הגישה לאורחים ל: {rules}", + "@changedTheGuestAccessRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "changedTheJoinRules": "{username} שינה את כללי ההצטרפות", + "@changedTheJoinRules": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheChatNameTo": "{username} שינה את שם הצ'אט ל: '{chatname}'", + "@changedTheChatNameTo": { + "type": "text", + "placeholders": { + "username": {}, + "chatname": {} + } + }, + "changedTheRoomInvitationLink": "{username} שינה את קישור ההזמנה", + "@changedTheRoomInvitationLink": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "repeatPassword": "כתוב שוב את הסיסמה", + "@repeatPassword": {}, + "areYouSureYouWantToLogout": "האם אתה בטוח שברצונך לצאת?", + "@areYouSureYouWantToLogout": { + "type": "text", + "placeholders": {} + }, + "chat": "צ׳אט", + "@chat": { + "type": "text", + "placeholders": {} + }, + "autoplayImages": "הפעל אוטומטית מדבקות ואנימציות מונפשים", + "@autoplayImages": { + "type": "text", + "placeholder": {} + }, + "badServerVersionsException": "שרת הבית תומך בגרסאות:\n{serverVersions}\nאבל האפליקציה הזו תומכת רק ב-{supportedVersions}", + "@badServerVersionsException": { + "type": "text", + "placeholders": { + "serverVersions": {}, + "supportedVersions": {} + } + }, + "banFromChat": "צאט חסום", + "@banFromChat": { + "type": "text", + "placeholders": {} + }, + "bannedUser": "{username} חסם את {targetName}", + "@bannedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "blockDevice": "חסום מכשיר", + "@blockDevice": { + "type": "text", + "placeholders": {} + }, + "blocked": "חסום", + "@blocked": { + "type": "text", + "placeholders": {} + }, + "botMessages": "הודעות בוט", + "@botMessages": { + "type": "text", + "placeholders": {} + }, + "cantOpenUri": "לא ניתן לפתוח את ה-URI {uri}", + "@cantOpenUri": { + "type": "text", + "placeholders": { + "uri": {} + } + }, + "changeDeviceName": "שנה את שם המכשיר", + "@changeDeviceName": { + "type": "text", + "placeholders": {} + }, + "changedTheChatAvatar": "{username} שינה את האווטאר של הצ'אט", + "@changedTheChatAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheChatDescriptionTo": "{username} שינה את תיאור הצ'אט ל: '{description}'", + "@changedTheChatDescriptionTo": { + "type": "text", + "placeholders": { + "username": {}, + "description": {} + } + }, + "changedTheChatPermissions": "{username} שינה את הרשאות הצ'אט", + "@changedTheChatPermissions": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheDisplaynameTo": "{username} שינה את שם התצוגה שלו ל: '{displayname}'", + "@changedTheDisplaynameTo": { + "type": "text", + "placeholders": { + "username": {}, + "displayname": {} + } + }, + "changedTheGuestAccessRules": "{username} שינה את כללי הגישה לאורחים", + "@changedTheGuestAccessRules": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheHistoryVisibility": "{username} שינה את נראות ההיסטוריה", + "@changedTheHistoryVisibility": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheHistoryVisibilityTo": "{username} שינה את נראות ההיסטוריה ל: {rules}", + "@changedTheHistoryVisibilityTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "changedTheJoinRulesTo": "{username} שינה את כללי ההצטרפות ל: {joinRules}", + "@changedTheJoinRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "joinRules": {} + } + }, + "changedTheProfileAvatar": "{username} שינה את האווטאר שלו", + "@changedTheProfileAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheRoomAliases": "{username} שינה את כינוי החדר", + "@changedTheRoomAliases": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changeTheHomeserver": "שנה את שרת הבית", + "@changeTheHomeserver": { + "type": "text", + "placeholders": {} + }, + "changeTheme": "שנה את הסגנון שלך", + "@changeTheme": { + "type": "text", + "placeholders": {} + }, + "changeTheNameOfTheGroup": "שנה את שם הקבוצה", + "@changeTheNameOfTheGroup": { + "type": "text", + "placeholders": {} + }, + "changeYourAvatar": "שינוי האווטאר שלך", + "@changeYourAvatar": { + "type": "text", + "placeholders": {} + }, + "channelCorruptedDecryptError": "ההצפנה נפגמה", + "@channelCorruptedDecryptError": { + "type": "text", + "placeholders": {} + }, + "yourChatBackupHasBeenSetUp": "גיבוי הצ'אט שלך הוגדר.", + "@yourChatBackupHasBeenSetUp": {}, + "chatBackup": "גיבוי צ'אט", + "@chatBackup": { + "type": "text", + "placeholders": {} + }, + "commandHint_ban": "חסום את המשתמש הנתון מהחדר הזה", + "@commandHint_ban": { + "type": "text", + "description": "Usage hint for the command /ban" + }, + "commandHint_clearcache": "נקה מטמון", + "@commandHint_clearcache": { + "type": "text", + "description": "Usage hint for the command /clearcache" + }, + "commandHint_create": "צור צ'אט קבוצתי ריק\nהשתמש ב--no-encryption כדי להשבית את ההצפנה", + "@commandHint_create": { + "type": "text", + "description": "Usage hint for the command /create" + }, + "commandHint_discardsession": "התעלם מהסשן", + "@commandHint_discardsession": { + "type": "text", + "description": "Usage hint for the command /discardsession" + }, + "commandHint_dm": "התחל צ'אט ישיר\nהשתמש ב--no-encryption כדי להשבית את ההצפנה", + "@commandHint_dm": { + "type": "text", + "description": "Usage hint for the command /dm" + }, + "commandHint_html": "שלח טקסט בתבנית HTML", + "@commandHint_html": { + "type": "text", + "description": "Usage hint for the command /html" + }, + "commandHint_invite": "הזמן את המשתמש הנתון לחדר זה", + "@commandHint_invite": { + "type": "text", + "description": "Usage hint for the command /invite" + }, + "commandHint_join": "הצטרף לחדר הנתון", + "@commandHint_join": { + "type": "text", + "description": "Usage hint for the command /join" + }, + "commandHint_kick": "הסר את המשתמש הנתון מהחדר הזה", + "@commandHint_kick": { + "type": "text", + "description": "Usage hint for the command /kick" + }, + "commandHint_leave": "עזוב את החדר הזה", + "@commandHint_leave": { + "type": "text", + "description": "Usage hint for the command /leave" + }, + "commandHint_me": "תאר את עצמך", + "@commandHint_me": { + "type": "text", + "description": "Usage hint for the command /me" + }, + "chatDetails": "פרטי צ'אט", + "@chatDetails": { + "type": "text", + "placeholders": {} + }, + "chatBackupDescription": "גיבוי הצ'אט שלך מאובטח באמצעות מפתח אבטחה. אנא וודא שאתה לא מאבד אותו.", + "@chatBackupDescription": { + "type": "text", + "placeholders": {} + }, + "chatHasBeenAddedToThisSpace": "צ'אט נוסף למרחב הזה", + "@chatHasBeenAddedToThisSpace": {}, + "chats": "צ'אטים", + "@chats": { + "type": "text", + "placeholders": {} + }, + "chooseAStrongPassword": "בחר סיסמה חזקה", + "@chooseAStrongPassword": { + "type": "text", + "placeholders": {} + }, + "clearArchive": "נקה ארכיון", + "@clearArchive": {}, + "close": "סגור", + "@close": { + "type": "text", + "placeholders": {} + }, + "commandHint_myroomavatar": "הגדר את התמונה שלך לחדר זה (על ידי mxc-uri)", + "@commandHint_myroomavatar": { + "type": "text", + "description": "Usage hint for the command /myroomavatar" + }, + "commandHint_myroomnick": "הגדר את שם התצוגה שלך עבור חדר זה", + "@commandHint_myroomnick": { + "type": "text", + "description": "Usage hint for the command /myroomnick" + }, + "addToSpace": "הוסף לחלל", + "@addToSpace": {}, + "commandHint_unban": "בטל את החסימה של המשתמש הנתון מהחדר הזה", + "@commandHint_unban": { + "type": "text", + "description": "Usage hint for the command /unban" + }, + "countParticipants": "{count} משתתפים", + "@countParticipants": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "create": "צור", + "@create": { + "type": "text", + "placeholders": {} + }, + "createdTheChat": "{username} יצר את הצ'אט", + "@createdTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "currentlyActive": "פעיל כעת", + "@currentlyActive": { + "type": "text", + "placeholders": {} + }, + "darkTheme": "כהה", + "@darkTheme": { + "type": "text", + "placeholders": {} + }, + "dateAndTimeOfDay": "{date}, {timeOfDay}", + "@dateAndTimeOfDay": { + "type": "text", + "placeholders": { + "date": {}, + "timeOfDay": {} + } + }, + "dateWithoutYear": "{month}-{day}", + "@dateWithoutYear": { + "type": "text", + "placeholders": { + "month": {}, + "day": {} + } + }, + "defaultPermissionLevel": "רמת הרשאת ברירת מחדל", + "@defaultPermissionLevel": { + "type": "text", + "placeholders": {} + }, + "deleteAccount": "מחק חשבון", + "@deleteAccount": { + "type": "text", + "placeholders": {} + }, + "deleteMessage": "מחק הודעה", + "@deleteMessage": { + "type": "text", + "placeholders": {} + }, + "deviceId": "מזהה מכשיר", + "@deviceId": { + "type": "text", + "placeholders": {} + }, + "devices": "התקנים", + "@devices": { + "type": "text", + "placeholders": {} + }, + "directChats": "צ'אטים ישירים", + "@directChats": { + "type": "text", + "placeholders": {} + }, + "downloadFile": "הורד קובץ", + "@downloadFile": { + "type": "text", + "placeholders": {} + }, + "edit": "ערוך", + "@edit": { + "type": "text", + "placeholders": {} + }, + "editDisplayname": "ערוך את שם התצוגה", + "@editDisplayname": { + "type": "text", + "placeholders": {} + }, + "editRoomAliases": "ערוך כינויים לחדר", + "@editRoomAliases": { + "type": "text", + "placeholders": {} + }, + "emoteExists": "אימוט כבר קיים!", + "@emoteExists": { + "type": "text", + "placeholders": {} + }, + "emptyChat": "צ'אט ריק", + "@emptyChat": { + "type": "text", + "placeholders": {} + }, + "encrypted": "מוצפן", + "@encrypted": { + "type": "text", + "placeholders": {} + }, + "enterAnEmailAddress": "הזן כתובת דואר אלקטרוני", + "@enterAnEmailAddress": { + "type": "text", + "placeholders": {} + }, + "enterYourHomeserver": "הזן את שרת הבית שלך", + "@enterYourHomeserver": { + "type": "text", + "placeholders": {} + }, + "everythingReady": "הכל מוכן!", + "@everythingReady": { + "type": "text", + "placeholders": {} + }, + "fileName": "שם קובץ", + "@fileName": { + "type": "text", + "placeholders": {} + }, + "fluffychat": "FluffyChat", + "@fluffychat": { + "type": "text", + "placeholders": {} + }, + "fontSize": "גודל גופן", + "@fontSize": { + "type": "text", + "placeholders": {} + }, + "forward": "העבר", + "@forward": { + "type": "text", + "placeholders": {} + }, + "fromJoining": "מהצטרפות", + "@fromJoining": { + "type": "text", + "placeholders": {} + }, + "fromTheInvitation": "מההזמנה", + "@fromTheInvitation": { + "type": "text", + "placeholders": {} + }, + "goToTheNewRoom": "עבור לחדר החדש", + "@goToTheNewRoom": { + "type": "text", + "placeholders": {} + }, + "groupIsPublic": "הקבוצה ציבורית", + "@groupIsPublic": { + "type": "text", + "placeholders": {} + }, + "groupWith": "קבוצה עם {displayname}", + "@groupWith": { + "type": "text", + "placeholders": { + "displayname": {} + } + }, + "guestsAreForbidden": "אורחים אסורים", + "@guestsAreForbidden": { + "type": "text", + "placeholders": {} + }, + "guestsCanJoin": "אורחים יכולים להצטרף", + "@guestsCanJoin": { + "type": "text", + "placeholders": {} + }, + "id": "מזהה", + "@id": { + "type": "text", + "placeholders": {} + }, + "identity": "זהות", + "@identity": { + "type": "text", + "placeholders": {} + }, + "ignoredUsers": "משתמשים שהתעלמו מהם", + "@ignoredUsers": { + "type": "text", + "placeholders": {} + }, + "incorrectPassphraseOrKey": "ביטוי סיסמה או מפתח שחזור שגויים", + "@incorrectPassphraseOrKey": { + "type": "text", + "placeholders": {} + }, + "inviteContact": "הזמן איש קשר", + "@inviteContact": { + "type": "text", + "placeholders": {} + }, + "invited": "הזמין", + "@invited": { + "type": "text", + "placeholders": {} + }, + "invitedUser": "{username} הזמין את {targetName}", + "@invitedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "invitedUsersOnly": "משתמשים מוזמנים בלבד", + "@invitedUsersOnly": { + "type": "text", + "placeholders": {} + }, + "inviteForMe": "הזמנה בשבילי", + "@inviteForMe": { + "type": "text", + "placeholders": {} + }, + "inviteText": "{username} הזמין אותך ל-FluffyChat.\n1. התקן את FluffyChat: https://fluffychat.im\n2. הירשם או היכנס\n3. פתח את קישור ההזמנה: {link}", + "@inviteText": { + "type": "text", + "placeholders": { + "username": {}, + "link": {} + } + }, + "isTyping": "מקליד/ה…", + "@isTyping": { + "type": "text", + "placeholders": {} + }, + "joinedTheChat": "{username} הצטרף לצ'אט", + "@joinedTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "joinRoom": "הצטרף לחדר", + "@joinRoom": { + "type": "text", + "placeholders": {} + }, + "kicked": "{username} בעט ב {targetName}", + "@kicked": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "kickedAndBanned": "{username} בעט וחסם {targetName}", + "@kickedAndBanned": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "kickFromChat": "בעיטה מהצ'אט", + "@kickFromChat": { + "type": "text", + "placeholders": {} + }, + "lastActiveAgo": "פעילות אחרונה: {localizedTimeShort}", + "@lastActiveAgo": { + "type": "text", + "placeholders": { + "localizedTimeShort": {} + } + }, + "leftTheChat": "עזב את הצ'אט", + "@leftTheChat": { + "type": "text", + "placeholders": {} + }, + "loadingPleaseWait": "טוען אנא המתן.", + "@loadingPleaseWait": { + "type": "text", + "placeholders": {} + }, + "loadMore": "טען עוד…", + "@loadMore": { + "type": "text", + "placeholders": {} + }, + "locationDisabledNotice": "שירותי המיקום מושבתים. אנא הפעל אותם כדי לשתף את המיקום שלך.", + "@locationDisabledNotice": { + "type": "text", + "placeholders": {} + }, + "copy": "העתק", + "@copy": { + "type": "text", + "placeholders": {} + }, + "commandHint_send": "שלח טקסט", + "@commandHint_send": { + "type": "text", + "description": "Usage hint for the command /send" + }, + "commandHint_op": "הגדרת רמת צריכת החשמל של המשתמש הנתון (ברירת מחדל: 50)", + "@commandHint_op": { + "type": "text", + "description": "Usage hint for the command /op" + }, + "commandHint_plain": "שלח טקסט לא מעוצב", + "@commandHint_plain": { + "type": "text", + "description": "Usage hint for the command /plain" + }, + "commandHint_react": "שלח תשובה כתגובה", + "@commandHint_react": { + "type": "text", + "description": "Usage hint for the command /react" + }, + "containsUserName": "מכיל שם משתמש", + "@containsUserName": { + "type": "text", + "placeholders": {} + }, + "createNewSpace": "חלל חדש", + "@createNewSpace": { + "type": "text", + "placeholders": {} + }, + "enableEncryption": "אפשר הצפנה", + "@enableEncryption": { + "type": "text", + "placeholders": {} + }, + "inviteContactToGroup": "הזמן איש קשר אל {groupName}", + "@inviteContactToGroup": { + "type": "text", + "placeholders": { + "groupName": {} + } + }, + "dateWithYear": "{year}-{month}-{day}", + "@dateWithYear": { + "type": "text", + "placeholders": { + "year": {}, + "month": {}, + "day": {} + } + }, + "deactivateAccountWarning": "פעולה זו תשבית את חשבון המשתמש שלך. אי אפשר לבטל את זה! האם אתה בטוח?", + "@deactivateAccountWarning": { + "type": "text", + "placeholders": {} + }, + "device": "מכשיר", + "@device": { + "type": "text", + "placeholders": {} + }, + "group": "קבוצה", + "@group": { + "type": "text", + "placeholders": {} + }, + "displaynameHasBeenChanged": "שם התצוגה השתנה", + "@displaynameHasBeenChanged": { + "type": "text", + "placeholders": {} + }, + "editBlockedServers": "ערוך שרתים חסומים", + "@editBlockedServers": { + "type": "text", + "placeholders": {} + }, + "editRoomAvatar": "עריכת אווטאר של חדר", + "@editRoomAvatar": { + "type": "text", + "placeholders": {} + }, + "endedTheCall": "{senderName} סיים את השיחה", + "@endedTheCall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "groups": "קבוצות", + "@groups": { + "type": "text", + "placeholders": {} + }, + "enableEncryptionWarning": "לא תוכל לבטל את ההצפנה יותר. האם אתה בטוח?", + "@enableEncryptionWarning": { + "type": "text", + "placeholders": {} + }, + "encryption": "הצפנה", + "@encryption": { + "type": "text", + "placeholders": {} + }, + "errorObtainingLocation": "שגיאה בהשגת מיקום: {error}", + "@errorObtainingLocation": { + "type": "text", + "placeholders": { + "error": {} + } + }, + "hasWithdrawnTheInvitationFor": "{username} ביטל את ההזמנה עבור {targetName}", + "@hasWithdrawnTheInvitationFor": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "hideRedactedEvents": "הסתר אירועים מצונזרים", + "@hideRedactedEvents": { + "type": "text", + "placeholders": {} + }, + "encryptionNotEnabled": "ההצפנה אינה מופעלת", + "@encryptionNotEnabled": { + "type": "text", + "placeholders": {} + }, + "extremeOffensive": "פוגעני ביותר", + "@extremeOffensive": { + "type": "text", + "placeholders": {} + }, + "hideUnknownEvents": "הסתר אירועים לא ידועים", + "@hideUnknownEvents": { + "type": "text", + "placeholders": {} + }, + "loadCountMoreParticipants": "טען {count} משתתפים נוספים", + "@loadCountMoreParticipants": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "homeserver": "שרת בית", + "@homeserver": {}, + "ignore": "התעלם", + "@ignore": { + "type": "text", + "placeholders": {} + }, + "iHaveClickedOnLink": "לחצתי על הקישור", + "@iHaveClickedOnLink": { + "type": "text", + "placeholders": {} + }, + "leave": "לעזוב", + "@leave": { + "type": "text", + "placeholders": {} + }, + "license": "רשיון", + "@license": { + "type": "text", + "placeholders": {} + }, + "lightTheme": "בהיר", + "@lightTheme": { + "type": "text", + "placeholders": {} + }, + "delete": "מחיקה", + "@delete": { + "type": "text", + "placeholders": {} + }, + "inoffensive": "לֹא פּוֹגֵעַ", + "@inoffensive": { + "type": "text", + "placeholders": {} + }, + "configureChat": "קביעת תצורה של צ'אט", + "@configureChat": { + "type": "text", + "placeholders": {} + }, + "confirm": "לאשר", + "@confirm": { + "type": "text", + "placeholders": {} + }, + "connect": "התחבר", + "@connect": { + "type": "text", + "placeholders": {} + }, + "contactHasBeenInvitedToTheGroup": "איש הקשר הוזמן לקבוצה", + "@contactHasBeenInvitedToTheGroup": { + "type": "text", + "placeholders": {} + }, + "containsDisplayName": "מכיל שם תצוגה", + "@containsDisplayName": { + "type": "text", + "placeholders": {} + }, + "contentHasBeenReported": "התוכן דווח למנהלי השרת", + "@contentHasBeenReported": { + "type": "text", + "placeholders": {} + }, + "copiedToClipboard": "הועתק ללוח הגזירים", + "@copiedToClipboard": { + "type": "text", + "placeholders": {} + }, + "commandInvalid": "הפקודה אינה חוקית", + "@commandInvalid": { + "type": "text" + }, + "commandMissing": "{command} אינו פקודה.", + "@commandMissing": { + "type": "text", + "placeholders": { + "command": {} + }, + "description": "State that {command} is not a valid /command." + }, + "compareEmojiMatch": "השווה וודא שהאימוג'י הבאים תואמים לאלו של המכשיר השני:", + "@compareEmojiMatch": { + "type": "text", + "placeholders": {} + }, + "compareNumbersMatch": "השווה וודא שהמספרים הבאים תואמים לאלה של המכשיר השני:", + "@compareNumbersMatch": { + "type": "text", + "placeholders": {} + }, + "copyToClipboard": "העתק ללוח", + "@copyToClipboard": { + "type": "text", + "placeholders": {} + }, + "couldNotDecryptMessage": "לא ניתן לפענח הודעה: {error}", + "@couldNotDecryptMessage": { + "type": "text", + "placeholders": { + "error": {} + } + }, + "help": "עזרה", + "@help": { + "type": "text", + "placeholders": {} + }, + "howOffensiveIsThisContent": "עד כמה התוכן הזה פוגעני?", + "@howOffensiveIsThisContent": { + "type": "text", + "placeholders": {} + }, + "locationPermissionDeniedNotice": "הרשאת המיקום נדחתה. אנא אפשר את היכולת לשתף את מיקומך.", + "@locationPermissionDeniedNotice": { + "type": "text", + "placeholders": {} + }, + "login": "כניסה", + "@login": { + "type": "text", + "placeholders": {} + }, + "moderator": "מנחה", + "@moderator": { + "type": "text", + "placeholders": {} + }, + "needPantalaimonWarning": "שים לב שאתה צריך Pantalaimon כדי להשתמש בהצפנה מקצה לקצה לעת עתה.", + "@needPantalaimonWarning": { + "type": "text", + "placeholders": {} + }, + "newChat": "צ'אט חדש", + "@newChat": { + "type": "text", + "placeholders": {} + }, + "newVerificationRequest": "בקשת אימות חדשה!", + "@newVerificationRequest": { + "type": "text", + "placeholders": {} + }, + "next": "הבא", + "@next": { + "type": "text", + "placeholders": {} + }, + "no": "לא", + "@no": { + "type": "text", + "placeholders": {} + }, + "noConnectionToTheServer": "אין חיבור לשרת", + "@noConnectionToTheServer": { + "type": "text", + "placeholders": {} + }, + "noEncryptionForPublicRooms": "אתה יכול להפעיל הצפנה רק כשהחדר כבר לא נגיש לציבור.", + "@noEncryptionForPublicRooms": { + "type": "text", + "placeholders": {} + }, + "noRoomsFound": "לא נמצאו חדרים…", + "@noRoomsFound": { + "type": "text", + "placeholders": {} + }, + "notifications": "התראות", + "@notifications": { + "type": "text", + "placeholders": {} + }, + "numUsersTyping": "{count} משתמשים מקלידים…", + "@numUsersTyping": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "obtainingLocation": "משיג מיקום…", + "@obtainingLocation": { + "type": "text", + "placeholders": {} + }, + "onlineKeyBackupEnabled": "גיבוי מפתח מקוון מופעל", + "@onlineKeyBackupEnabled": { + "type": "text", + "placeholders": {} + }, + "oopsPushError": "אופס! למרבה הצער, אירעה שגיאה בעת הגדרת התראות.", + "@oopsPushError": { + "type": "text", + "placeholders": {} + }, + "oopsSomethingWentWrong": "אופס, משהו השתבש…", + "@oopsSomethingWentWrong": { + "type": "text", + "placeholders": {} + }, + "openAppToReadMessages": "פתח את האפליקציה לקריאת הודעות", + "@openAppToReadMessages": { + "type": "text", + "placeholders": {} + }, + "oneClientLoggedOut": "אחד מהמכשירים שלך התנתק", + "@oneClientLoggedOut": {}, + "addAccount": "הוסף חשבון", + "@addAccount": {}, + "editBundlesForAccount": "ערוך חבילות עבור חשבון זה", + "@editBundlesForAccount": {}, + "participant": "משתתף", + "@participant": { + "type": "text", + "placeholders": {} + }, + "password": "סיסמה", + "@password": { + "type": "text", + "placeholders": {} + }, + "pleaseClickOnLink": "אנא לחץ על הקישור במייל ולאחר מכן המשך.", + "@pleaseClickOnLink": { + "type": "text", + "placeholders": {} + }, + "pleaseEnter4Digits": "אנא הזן 4 ספרות או השאר ריק כדי להשבית את נעילת האפליקציה.", + "@pleaseEnter4Digits": { + "type": "text", + "placeholders": {} + }, + "online": "מחובר/ת", + "@online": { + "type": "text", + "placeholders": {} + }, + "addToBundle": "הוסף לחבילה", + "@addToBundle": {}, + "passphraseOrKey": "ביטוי סיסמה או מפתח שחזור", + "@passphraseOrKey": { + "type": "text", + "placeholders": {} + }, + "passwordForgotten": "שכחתי סיסמה", + "@passwordForgotten": { + "type": "text", + "placeholders": {} + }, + "passwordHasBeenChanged": "הסיסמה שונתה", + "@passwordHasBeenChanged": { + "type": "text", + "placeholders": {} + }, + "passwordRecovery": "שחזור סיסמה", + "@passwordRecovery": { + "type": "text", + "placeholders": {} + }, + "people": "אנשים", + "@people": { + "type": "text", + "placeholders": {} + }, + "pickImage": "בחר תמונה", + "@pickImage": { + "type": "text", + "placeholders": {} + }, + "play": "הפעל {fileName}", + "@play": { + "type": "text", + "placeholders": { + "fileName": {} + } + }, + "pleaseChoose": "אנא בחר", + "@pleaseChoose": { + "type": "text", + "placeholders": {} + }, + "pleaseChooseAPasscode": "אנא בחר קוד גישה", + "@pleaseChooseAPasscode": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourPassword": "נא הזן את הסיסמה שלך", + "@pleaseEnterYourPassword": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourPin": "אנא הזן את קוד הpin שלך", + "@pleaseEnterYourPin": { + "type": "text", + "placeholders": {} + }, + "pin": "קוד pin", + "@pin": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourUsername": "אנא הזן שם משתמש", + "@pleaseEnterYourUsername": { + "type": "text", + "placeholders": {} + }, + "newMessageInFluffyChat": "הודעה חדשה ב-FluffyChat", + "@newMessageInFluffyChat": { + "type": "text", + "placeholders": {} + }, + "noGoogleServicesWarning": "נראה שאין לך שירותי גוגל בטלפון שלך. זו החלטה טובה לפרטיות שלך! כדי לקבל התרעות ב- FluffyChat אנו ממליצים להשתמש https://microg.org/ או https://unifiedpush.org/.", + "@noGoogleServicesWarning": { + "type": "text", + "placeholders": {} + }, + "noMatrixServer": "{server1} אינו שרת מטריקס, השתמש ב-{server2} במקום זאת?", + "@noMatrixServer": { + "type": "text", + "placeholders": { + "server1": {}, + "server2": {} + } + }, + "none": "ללא", + "@none": { + "type": "text", + "placeholders": {} + }, + "noPasswordRecoveryDescription": "עדיין לא הוספת דרך לשחזר את הסיסמה שלך.", + "@noPasswordRecoveryDescription": { + "type": "text", + "placeholders": {} + }, + "offensive": "פוגעני", + "@offensive": { + "type": "text", + "placeholders": {} + }, + "notificationsEnabledForThisAccount": "התראות הופעלו עבור חשבון זה", + "@notificationsEnabledForThisAccount": { + "type": "text", + "placeholders": {} + }, + "bundleName": "שם החבילה", + "@bundleName": {}, + "offline": "לא מקוון", + "@offline": { + "type": "text", + "placeholders": {} + }, + "openVideoCamera": "פתח את המצלמה לסרטון", + "@openVideoCamera": { + "type": "text", + "placeholders": {} + }, + "removeFromBundle": "הסר מחבילה זו", + "@removeFromBundle": {}, + "enableMultiAccounts": "(בטא) אפשר ריבוי חשבונות במכשיר זה", + "@enableMultiAccounts": {}, + "openInMaps": "פתיחה במפות", + "@openInMaps": { + "type": "text", + "placeholders": {} + }, + "link": "קישור", + "@link": {}, + "serverRequiresEmail": "שרת זה צריך לאמת את כתובת הדואר האלקטרוני שלך לרישום.", + "@serverRequiresEmail": {}, + "logout": "יציאה", + "@logout": { + "type": "text", + "placeholders": {} + }, + "muteChat": "השתקת הצ'אט", + "@muteChat": { + "type": "text", + "placeholders": {} + }, + "scanQrCode": "סרוק קוד QR", + "@scanQrCode": {}, + "noPermission": "אין הרשאה", + "@noPermission": { + "type": "text", + "placeholders": {} + }, + "or": "או", + "@or": { + "type": "text", + "placeholders": {} + }, + "logInTo": "היכנס אל {homeserver}", + "@logInTo": { + "type": "text", + "placeholders": { + "homeserver": {} + } + }, + "memberChanges": "שינויים בחבר", + "@memberChanges": { + "type": "text", + "placeholders": {} + }, + "mention": "הזכיר", + "@mention": { + "type": "text", + "placeholders": {} + }, + "messages": "הודעות", + "@messages": { + "type": "text", + "placeholders": {} + }, + "openCamera": "פתח מצלמה", + "@openCamera": { + "type": "text", + "placeholders": {} + } +} From e1f6a58100ad17c2f9ab27300f0e07b30418f83c Mon Sep 17 00:00:00 2001 From: Anonymous Date: Thu, 25 Jul 2024 05:09:40 +0000 Subject: [PATCH 117/288] Translated using Weblate (Persian) Currently translated at 75.3% (491 of 652 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/fa/ --- assets/l10n/intl_fa.arb | 74 +---------------------------------------- 1 file changed, 1 insertion(+), 73 deletions(-) diff --git a/assets/l10n/intl_fa.arb b/assets/l10n/intl_fa.arb index b51439b8e2..d62cb8884a 100644 --- a/assets/l10n/intl_fa.arb +++ b/assets/l10n/intl_fa.arb @@ -2300,78 +2300,6 @@ "@signInWithPassword": {}, "pleaseTryAgainLaterOrChooseDifferentServer": "لطفا بعدا تلاش کنید یا سرور دیگری انتخاب کنید.", "@pleaseTryAgainLaterOrChooseDifferentServer": {}, - "@setColorTheme": {}, - "@banUserDescription": {}, - "@removeDevicesDescription": {}, - "@tryAgain": {}, - "@unbanUserDescription": {}, - "@messagesStyle": {}, - "@chatDescription": {}, - "@pushNotificationsNotAvailable": {}, - "@invalidServerName": {}, - "@chatPermissions": {}, - "@makeAdminDescription": {}, - "@setChatDescription": {}, - "@importFromZipFile": {}, - "@redactedBy": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@signInWith": { - "type": "text", - "placeholders": { - "provider": {} - } - }, - "@optionalRedactReason": {}, - "@archiveRoomDescription": {}, - "@exportEmotePack": {}, - "@inviteContactToGroupQuestion": {}, - "@redactedByBecause": { - "type": "text", - "placeholders": { - "username": {}, - "reason": {} - } - }, - "@redactMessageDescription": {}, - "@invalidInput": {}, - "@addChatDescription": {}, - "@hasKnocked": { - "placeholders": { - "user": {} - } - }, - "@directChat": {}, - "@wrongPinEntered": { - "type": "text", - "placeholders": { - "seconds": {} - } - }, - "@sendTypingNotifications": {}, - "@inviteGroupChat": {}, - "@invitePrivateChat": {}, - "@importEmojis": {}, - "@noChatDescriptionYet": {}, - "@learnMore": {}, "notAnImage": "یک فایل تصویری نیست.", - "@notAnImage": {}, - "@chatDescriptionHasBeenChanged": {}, - "@roomUpgradeDescription": {}, - "@pleaseEnterANumber": {}, - "@profileNotFound": {}, - "@shareInviteLink": {}, - "@emoteKeyboardNoRecents": { - "type": "text", - "placeholders": {} - }, - "@setTheme": {}, - "@replace": {}, - "@createGroup": {}, - "@kickUserDescription": {}, - "@importNow": {}, - "@invite": {} + "@notAnImage": {} } From 628118b82bcf7d96429f3aa3790c243efc29e116 Mon Sep 17 00:00:00 2001 From: Anonymous Date: Thu, 25 Jul 2024 05:09:42 +0000 Subject: [PATCH 118/288] Translated using Weblate (Irish) Currently translated at 52.1% (340 of 652 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ga/ --- assets/l10n/intl_ga.arb | 4199 ++++++++++++++++++--------------------- 1 file changed, 1952 insertions(+), 2247 deletions(-) diff --git a/assets/l10n/intl_ga.arb b/assets/l10n/intl_ga.arb index c4ba3b5bdd..5d01bfef6f 100644 --- a/assets/l10n/intl_ga.arb +++ b/assets/l10n/intl_ga.arb @@ -1,2248 +1,1953 @@ { - "you": "Tú", - "@you": { - "type": "text", - "placeholders": {} - }, - "yes": "Tá", - "@yes": { - "type": "text", - "placeholders": {} - }, - "warning": "Rabhadh!", - "@warning": { - "type": "text", - "placeholders": {} - }, - "wallpaper": "Cúlbhrat", - "@wallpaper": { - "type": "text", - "placeholders": {} - }, - "verify": "Deimhnigh", - "@verify": { - "type": "text", - "placeholders": {} - }, - "verified": "Deimhnithe", - "@verified": { - "type": "text", - "placeholders": {} - }, - "username": "Ainm úsáideora", - "@username": { - "type": "text", - "placeholders": {} - }, - "unpin": "Bain biorán", - "@unpin": { - "type": "text", - "placeholders": {} - }, - "unavailable": "Níl ar fáil", - "@unavailable": { - "type": "text", - "placeholders": {} - }, - "title": "FluffyChat", - "@title": { - "description": "Title for the application", - "type": "text", - "placeholders": {} - }, - "systemTheme": "Córas", - "@systemTheme": { - "type": "text", - "placeholders": {} - }, - "submit": "Cuir isteach", - "@submit": { - "type": "text", - "placeholders": {} - }, - "statusExampleMessage": "Conas atá tú inniu?", - "@statusExampleMessage": { - "type": "text", - "placeholders": {} - }, - "status": "Staid", - "@status": { - "type": "text", - "placeholders": {} - }, - "skip": "Léim", - "@skip": { - "type": "text", - "placeholders": {} - }, - "share": "Roinn", - "@share": { - "type": "text", - "placeholders": {} - }, - "settings": "Socruithe", - "@settings": { - "type": "text", - "placeholders": {} - }, - "send": "Seol", - "@send": { - "type": "text", - "placeholders": {} - }, - "security": "Slándáil", - "@security": { - "type": "text", - "placeholders": {} - }, - "search": "Cuardaigh", - "@search": { - "type": "text", - "placeholders": {} - }, - "reply": "Freagair", - "@reply": { - "type": "text", - "placeholders": {} - }, - "remove": "Bain", - "@remove": { - "type": "text", - "placeholders": {} - }, - "rejoin": "Téigh ar ais isteach", - "@rejoin": { - "type": "text", - "placeholders": {} - }, - "reject": "Diúltaigh", - "@reject": { - "type": "text", - "placeholders": {} - }, - "register": "Cláraigh", - "@register": { - "type": "text", - "placeholders": {} - }, - "recording": "Ag Taifeadadh", - "@recording": { - "type": "text", - "placeholders": {} - }, - "reason": "Fáth", - "@reason": { - "type": "text", - "placeholders": {} - }, - "privacy": "Príobháideacht", - "@privacy": { - "type": "text", - "placeholders": {} - }, - "pin": "Biorán", - "@pin": { - "type": "text", - "placeholders": {} - }, - "people": "Daoine", - "@people": { - "type": "text", - "placeholders": {} - }, - "password": "Pasfhocal", - "@password": { - "type": "text", - "placeholders": {} - }, - "participant": "Rannpháirtí", - "@participant": { - "type": "text", - "placeholders": {} - }, - "directChats": "Comhráite Díreacha", - "@directChats": { - "type": "text", - "placeholders": {} - }, - "deviceId": "ID gléis", - "@deviceId": { - "type": "text", - "placeholders": {} - }, - "deleteMessage": "Scrios an teachtaireacht", - "@deleteMessage": { - "type": "text", - "placeholders": {} - }, - "deleteAccount": "Scrios an cuntas", - "@deleteAccount": { - "type": "text", - "placeholders": {} - }, - "createNewSpace": "Spás nua", - "@createNewSpace": { - "type": "text", - "placeholders": {} - }, - "countParticipants": "{count} rannpháirtithe", - "@countParticipants": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "or": "Nó", - "@or": { - "type": "text", - "placeholders": {} - }, - "online": "Ar líne", - "@online": { - "type": "text", - "placeholders": {} - }, - "ok": "togha", - "@ok": { - "type": "text", - "placeholders": {} - }, - "offline": "As líne", - "@offline": { - "type": "text", - "placeholders": {} - }, - "offensive": "Maslach", - "@offensive": { - "type": "text", - "placeholders": {} - }, - "notifications": "Fógraí", - "@notifications": { - "type": "text", - "placeholders": {} - }, - "none": "Aon cheann", - "@none": { - "type": "text", - "placeholders": {} - }, - "no": "Níl", - "@no": { - "type": "text", - "placeholders": {} - }, - "next": "Ar Aghaidh", - "@next": { - "type": "text", - "placeholders": {} - }, - "moderator": "Modhnóir", - "@moderator": { - "type": "text", - "placeholders": {} - }, - "messages": "Teachtaireachtaí", - "@messages": { - "type": "text", - "placeholders": {} - }, - "mention": "Luaigh", - "@mention": { - "type": "text", - "placeholders": {} - }, - "logout": "Logáil amach", - "@logout": { - "type": "text", - "placeholders": {} - }, - "login": "Logáil isteach", - "@login": { - "type": "text", - "placeholders": {} - }, - "lightTheme": "Solas", - "@lightTheme": { - "type": "text", - "placeholders": {} - }, - "license": "Ceadúnas", - "@license": { - "type": "text", - "placeholders": {} - }, - "leave": "Fág", - "@leave": { - "type": "text", - "placeholders": {} - }, - "invited": "Le cuireadh", - "@invited": { - "type": "text", - "placeholders": {} - }, - "inoffensive": "Neamhurchóideach", - "@inoffensive": { - "type": "text", - "placeholders": {} - }, - "ignore": "Tabhair neamhaird ar", - "@ignore": { - "type": "text", - "placeholders": {} - }, - "identity": "Aitheantas", - "@identity": { - "type": "text", - "placeholders": {} - }, - "id": "ID", - "@id": { - "type": "text", - "placeholders": {} - }, - "help": "Cabhair", - "@help": { - "type": "text", - "placeholders": {} - }, - "groups": "Grúpaí", - "@groups": { - "type": "text", - "placeholders": {} - }, - "group": "Grúpa", - "@group": { - "type": "text", - "placeholders": {} - }, - "forward": "Seol ar aghaidh", - "@forward": { - "type": "text", - "placeholders": {} - }, - "fluffychat": "FluffyChat", - "@fluffychat": { - "type": "text", - "placeholders": {} - }, - "homeserver": "Freastalaí baile", - "@homeserver": {}, - "encryption": "Criptiúchán", - "@encryption": { - "type": "text", - "placeholders": {} - }, - "encrypted": "Criptithe", - "@encrypted": { - "type": "text", - "placeholders": {} - }, - "edit": "Cuir in eagar", - "@edit": { - "type": "text", - "placeholders": {} - }, - "devices": "Gléasanna", - "@devices": { - "type": "text", - "placeholders": {} - }, - "device": "Gléas", - "@device": { - "type": "text", - "placeholders": {} - }, - "delete": "Scrios", - "@delete": { - "type": "text", - "placeholders": {} - }, - "dateWithYear": "{day}/{month}/{year}", - "@dateWithYear": { - "type": "text", - "placeholders": { - "year": {}, - "month": {}, - "day": {} - } - }, - "dateWithoutYear": "{day}/{month}", - "@dateWithoutYear": { - "type": "text", - "placeholders": { - "month": {}, - "day": {} - } - }, - "dateAndTimeOfDay": "{date}, {timeOfDay}", - "@dateAndTimeOfDay": { - "type": "text", - "placeholders": { - "date": {}, - "timeOfDay": {} - } - }, - "containsUserName": "Coinníonn sé ainm úsáideora", - "@containsUserName": { - "type": "text", - "placeholders": {} - }, - "configureChat": "Cumraigh comhrá", - "@configureChat": { - "type": "text", - "placeholders": {} - }, - "commandInvalid": "Ordú neamhbhailí", - "@commandInvalid": { - "type": "text" - }, - "commandHint_send": "Seol téacs", - "@commandHint_send": { - "type": "text", - "description": "Usage hint for the command /send" - }, - "commandHint_me": "Déan cur síos ort féin", - "@commandHint_me": { - "type": "text", - "description": "Usage hint for the command /me" - }, - "clearArchive": "Glan an cartlann", - "@clearArchive": {}, - "chatDetails": "Sonraí comhrá", - "@chatDetails": { - "type": "text", - "placeholders": {} - }, - "chatBackup": "Cúltaca comhrá", - "@chatBackup": { - "type": "text", - "placeholders": {} - }, - "changedTheChatAvatar": "D'athraigh {username} abhatár an chomhrá", - "@changedTheChatAvatar": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changeDeviceName": "Athraigh ainm an ghléis", - "@changeDeviceName": { - "type": "text", - "placeholders": {} - }, - "cantOpenUri": "Ní féidir an URI {uri} a oscailt", - "@cantOpenUri": { - "type": "text", - "placeholders": { - "uri": {} - } - }, - "cancel": "Cealaigh", - "@cancel": { - "type": "text", - "placeholders": {} - }, - "botMessages": "Teachtaireachtaí bota", - "@botMessages": { - "type": "text", - "placeholders": {} - }, - "blocked": "Bactha", - "@blocked": { - "type": "text", - "placeholders": {} - }, - "blockDevice": "Bac Gléas", - "@blockDevice": { - "type": "text", - "placeholders": {} - }, - "bannedUser": "Chuir {username} cosc ar {targetName}", - "@bannedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "banned": "Coiscthe", - "@banned": { - "type": "text", - "placeholders": {} - }, - "banFromChat": "Toirmisc ón gcomhrá", - "@banFromChat": { - "type": "text", - "placeholders": {} - }, - "sendOnEnter": "Seol ar iontráil", - "@sendOnEnter": {}, - "archive": "Cartlann", - "@archive": { - "type": "text", - "placeholders": {} - }, - "appLock": "Bac aip", - "@appLock": { - "type": "text", - "placeholders": {} - }, - "anyoneCanJoin": "Is féidir le aon duine dul isteach", - "@anyoneCanJoin": { - "type": "text", - "placeholders": {} - }, - "answeredTheCall": "D'fhreagair {senderName} an glao", - "@answeredTheCall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "allChats": "Gach comhrá", - "@allChats": { - "type": "text", - "placeholders": {} - }, - "all": "Gach", - "@all": { - "type": "text", - "placeholders": {} - }, - "alias": "ailias", - "@alias": { - "type": "text", - "placeholders": {} - }, - "admin": "Riarthóir", - "@admin": { - "type": "text", - "placeholders": {} - }, - "addToSpace": "Cuir go spás", - "@addToSpace": {}, - "addEmail": "Cuir ríomhphoist", - "@addEmail": { - "type": "text", - "placeholders": {} - }, - "activatedEndToEndEncryption": "Thosaigh {username} an criptiú ó dheireadh go deireadh", - "@activatedEndToEndEncryption": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "account": "Cuntas", - "@account": { - "type": "text", - "placeholders": {} - }, - "acceptedTheInvitation": "Ghlac {username} leis an cuireadh", - "@acceptedTheInvitation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "accept": "Glac", - "@accept": { - "type": "text", - "placeholders": {} - }, - "about": "Faoi", - "@about": { - "type": "text", - "placeholders": {} - }, - "askVerificationRequest": "Glac leis an iarratas fíoraithe seo ó {username}?", - "@askVerificationRequest": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "areYouSureYouWantToLogout": "An bhfuil tú cinnte gur mhaith leat logáil amach?", - "@areYouSureYouWantToLogout": { - "type": "text", - "placeholders": {} - }, - "areYouSure": "An bhfuil tú cinnte?", - "@areYouSure": { - "type": "text", - "placeholders": {} - }, - "areGuestsAllowedToJoin": "An bhfuil cead ag aoi-úsáideoirí a bheith páirteach", - "@areGuestsAllowedToJoin": { - "type": "text", - "placeholders": {} - }, - "invitedUser": "Thug {username} cuireadh do {targetName}", - "@invitedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "hideRedactedEvents": "Folaigh imeachtaí athdhéanta", - "@hideRedactedEvents": { - "type": "text", - "placeholders": {} - }, - "guestsCanJoin": "Is féidir le haíonna páirt a ghlacadh", - "@guestsCanJoin": { - "type": "text", - "placeholders": {} - }, - "guestsAreForbidden": "Tá cosc ar aíonna", - "@guestsAreForbidden": { - "type": "text", - "placeholders": {} - }, - "groupWith": "Grúpa le {displayname}", - "@groupWith": { - "type": "text", - "placeholders": { - "displayname": {} - } - }, - "groupIsPublic": "Tá an grúpa poiblí", - "@groupIsPublic": { - "type": "text", - "placeholders": {} - }, - "fromTheInvitation": "Ón gcuireadh", - "@fromTheInvitation": { - "type": "text", - "placeholders": {} - }, - "enterYourHomeserver": "Cuir isteach do fhreastalaí baile", - "@enterYourHomeserver": { - "type": "text", - "placeholders": {} - }, - "emoteInvalid": "Gearrchód emote neamhbhailí!", - "@emoteInvalid": { - "type": "text", - "placeholders": {} - }, - "emoteExists": "Tá iomaite ann cheana féin!", - "@emoteExists": { - "type": "text", - "placeholders": {} - }, - "editRoomAvatar": "Cuir in eagar abhatár an tseomra", - "@editRoomAvatar": { - "type": "text", - "placeholders": {} - }, - "editRoomAliases": "Cuir ailiasanna an tseomra in eagar", - "@editRoomAliases": { - "type": "text", - "placeholders": {} - }, - "editBlockedServers": "Cuir freastalaí blocáilte in eagar", - "@editBlockedServers": { - "type": "text", - "placeholders": {} - }, - "defaultPermissionLevel": "Leibhéal ceada réamhshocraithe", - "@defaultPermissionLevel": { - "type": "text", - "placeholders": {} - }, - "unblockDevice": "Díbhlocáil Gléas", - "@unblockDevice": { - "type": "text", - "placeholders": {} - }, - "contactHasBeenInvitedToTheGroup": "Tugadh cuireadh don theagmháil a thar isteach sa grúpa", - "@contactHasBeenInvitedToTheGroup": { - "type": "text", - "placeholders": {} - }, - "compareNumbersMatch": "Déan comparáid idir na huimhreacha seo a leanas agus déan cinnte go bhfuil na huimhreacha seo a leanas ag teacht le huimhreacha an ghléis eile:", - "@compareNumbersMatch": { - "type": "text", - "placeholders": {} - }, - "compareEmojiMatch": "Déan comparáid agus déan cinnte go bhfuil an emoji seo a leanas comhoiriúnach le emoji an ghléis eile:", - "@compareEmojiMatch": { - "type": "text", - "placeholders": {} - }, - "commandMissing": "Ní ordú é {command}.", - "@commandMissing": { - "type": "text", - "placeholders": { - "command": {} - }, - "description": "State that {command} is not a valid /command." - }, - "commandHint_react": "Seol freagra mar fhreagairt", - "@commandHint_react": { - "type": "text", - "description": "Usage hint for the command /react" - }, - "commandHint_op": "Socraigh leibhéal cumhachta an úsáideora áirithe (réamhshocrú: 50)", - "@commandHint_op": { - "type": "text", - "description": "Usage hint for the command /op" - }, - "commandHint_myroomnick": "Socraigh d'ainm taispeána don seomra seo", - "@commandHint_myroomnick": { - "type": "text", - "description": "Usage hint for the command /myroomnick" - }, - "commandHint_myroomavatar": "Cuir do phictiúr don seomra seo (le mxc-uri)", - "@commandHint_myroomavatar": { - "type": "text", - "description": "Usage hint for the command /myroomavatar" - }, - "commandHint_kick": "Bain an t-úsáideoir áirithe den seomra seo", - "@commandHint_kick": { - "type": "text", - "description": "Usage hint for the command /kick" - }, - "commandHint_join": "Téigh isteach sa seomra áirithe", - "@commandHint_join": { - "type": "text", - "description": "Usage hint for the command /join" - }, - "commandHint_ban": "Cuir cosc ar an úsáideoir áirithe ón seomra seo", - "@commandHint_ban": { - "type": "text", - "description": "Usage hint for the command /ban" - }, - "commandHint_invite": "Cuir cosc ar an úsáideoir áirithe ón seomra seo", - "@commandHint_invite": { - "type": "text", - "description": "Usage hint for the command /invite" - }, - "chooseAStrongPassword": "Roghnaigh pasfhocal láidir", - "@chooseAStrongPassword": { - "type": "text", - "placeholders": {} - }, - "chatHasBeenAddedToThisSpace": "Cuireadh comhrá leis an spás seo", - "@chatHasBeenAddedToThisSpace": {}, - "chatBackupDescription": "Tá do chúltaca comhrá daingnithe le heochair slándála. Déan cinnte nach gcaillfidh tú é.", - "@chatBackupDescription": { - "type": "text", - "placeholders": {} - }, - "channelCorruptedDecryptError": "Truaillíodh an criptiú", - "@channelCorruptedDecryptError": { - "type": "text", - "placeholders": {} - }, - "changeTheNameOfTheGroup": "Athraigh ainm an ghrúpa", - "@changeTheNameOfTheGroup": { - "type": "text", - "placeholders": {} - }, - "changedTheRoomInvitationLink": "D'athraigh {username} nasc an chuiridh", - "@changedTheRoomInvitationLink": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheRoomAliases": "D'athraigh {username} ailiasanna an tseomra", - "@changedTheRoomAliases": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheProfileAvatar": "D'athraigh {username} a n-abhatár", - "@changedTheProfileAvatar": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheJoinRulesTo": "D'athraigh {username} na rialacha ceangail go: {joinRules}", - "@changedTheJoinRulesTo": { - "type": "text", - "placeholders": { - "username": {}, - "joinRules": {} - } - }, - "changedTheJoinRules": "D'athraigh {username} na rialacha ceangail", - "@changedTheJoinRules": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheHistoryVisibilityTo": "D'athraigh {username} infheictheacht na staire go: {rules}", - "@changedTheHistoryVisibilityTo": { - "type": "text", - "placeholders": { - "username": {}, - "rules": {} - } - }, - "changedTheHistoryVisibility": "D'athraigh {username} infheictheacht na staire", - "@changedTheHistoryVisibility": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheDisplaynameTo": "D'athraigh {username} a n-ainm taispeána go: '{displayname}'", - "@changedTheDisplaynameTo": { - "type": "text", - "placeholders": { - "username": {}, - "displayname": {} - } - }, - "changedTheChatPermissions": "D'athraigh {username} na ceadanna comhrá", - "@changedTheChatPermissions": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheChatNameTo": "D'athraigh {username} an t-ainm comhrá go: '{chatname}'", - "@changedTheChatNameTo": { - "type": "text", - "placeholders": { - "username": {}, - "chatname": {} - } - }, - "changedTheChatDescriptionTo": "D'athraigh {username} an cur síos comhrá go: '{description}'", - "@changedTheChatDescriptionTo": { - "type": "text", - "placeholders": { - "username": {}, - "description": {} - } - }, - "autoplayImages": "Seinn greamáin agus straoiseog beoite go huathoibríoch", - "@autoplayImages": { - "type": "text", - "placeholder": {} - }, - "createdTheChat": "Rinne {username} an comhrá", - "@createdTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "copyToClipboard": "Cóipeáil ar an ghearrthaisce", - "@copyToClipboard": { - "type": "text", - "placeholders": {} - }, - "copiedToClipboard": "Cóipeáilte ar an ghearrthaisce", - "@copiedToClipboard": { - "type": "text", - "placeholders": {} - }, - "containsDisplayName": "Coinníonn sé ainm taispeána", - "@containsDisplayName": { - "type": "text", - "placeholders": {} - }, - "commandHint_plain": "Seol téacs neamhfhoirmithe", - "@commandHint_plain": { - "type": "text", - "description": "Usage hint for the command /plain" - }, - "commandHint_leave": "Fág an seomra seo", - "@commandHint_leave": { - "type": "text", - "description": "Usage hint for the command /leave" - }, - "commandHint_html": "Seol téacs HTML-formáidithe", - "@commandHint_html": { - "type": "text", - "description": "Usage hint for the command /html" - }, - "changeYourAvatar": "Athraigh do abhatár", - "@changeYourAvatar": { - "type": "text", - "placeholders": {} - }, - "changeTheme": "Athraigh do stíl", - "@changeTheme": { - "type": "text", - "placeholders": {} - }, - "changeTheHomeserver": "Athraigh an freastalaí baile", - "@changeTheHomeserver": { - "type": "text", - "placeholders": {} - }, - "voiceMessage": "Glórphost", - "@voiceMessage": { - "type": "text", - "placeholders": {} - }, - "videoCall": "Físghlao", - "@videoCall": { - "type": "text", - "placeholders": {} - }, - "verifyStart": "Tosaigh Fíorú", - "@verifyStart": { - "type": "text", - "placeholders": {} - }, - "unmuteChat": "Neamhciúnaigh comhrá", - "@unmuteChat": { - "type": "text", - "placeholders": {} - }, - "hideUnknownEvents": "Folaigh imeachtaí anaithnide", - "@hideUnknownEvents": { - "type": "text", - "placeholders": {} - }, - "unknownDevice": "Gléas anaithnid", - "@unknownDevice": { - "type": "text", - "placeholders": {} - }, - "toggleUnread": "Marcáil Léite/Neamhléite", - "@toggleUnread": { - "type": "text", - "placeholders": {} - }, - "toggleMuted": "Scoránaigh mar ciúnaithe", - "@toggleMuted": { - "type": "text", - "placeholders": {} - }, - "toggleFavorite": "Scoránaigh mar ceann is fearr leat", - "@toggleFavorite": { - "type": "text", - "placeholders": {} - }, - "theyMatch": "Tá siad céanna", - "@theyMatch": { - "type": "text", - "placeholders": {} - }, - "spaceName": "Ainm an spáis", - "@spaceName": { - "type": "text", - "placeholders": {} - }, - "sourceCode": "Cód foinseach", - "@sourceCode": { - "type": "text", - "placeholders": {} - }, - "showPassword": "Taispeáin pasfhocal", - "@showPassword": { - "type": "text", - "placeholders": {} - }, - "shareLocation": "Roinn suíomh", - "@shareLocation": { - "type": "text", - "placeholders": {} - }, - "setStatus": "Cuir stádas", - "@setStatus": { - "type": "text", - "placeholders": {} - }, - "sendVideo": "Seol físeán", - "@sendVideo": { - "type": "text", - "placeholders": {} - }, - "sendSticker": "Seol greamán", - "@sendSticker": { - "type": "text", - "placeholders": {} - }, - "sendOriginal": "Seol an bunchóip", - "@sendOriginal": { - "type": "text", - "placeholders": {} - }, - "sendMessages": "Seol teachtaireachtaí", - "@sendMessages": { - "type": "text", - "placeholders": {} - }, - "sendImage": "Seol íomhá", - "@sendImage": { - "type": "text", - "placeholders": {} - }, - "sendFile": "Seol comhad", - "@sendFile": { - "type": "text", - "placeholders": {} - }, - "sendAudio": "Seol fuaim", - "@sendAudio": { - "type": "text", - "placeholders": {} - }, - "saveFile": "Sábháil comhad", - "@saveFile": { - "type": "text", - "placeholders": {} - }, - "roomVersion": "Leagan seomra", - "@roomVersion": { - "type": "text", - "placeholders": {} - }, - "requestPermission": "Iarr cead", - "@requestPermission": { - "type": "text", - "placeholders": {} - }, - "reportMessage": "Tuairiscigh teachtaireacht", - "@reportMessage": { - "type": "text", - "placeholders": {} - }, - "removeDevice": "Bain gléas", - "@removeDevice": { - "type": "text", - "placeholders": {} - }, - "redactMessage": "Bain teachtaireacht amach", - "@redactMessage": { - "type": "text", - "placeholders": {} - }, - "pushRules": "Rialacha na bhfógraí", - "@pushRules": { - "type": "text", - "placeholders": {} - }, - "publicRooms": "Seomraí Poiblí", - "@publicRooms": { - "type": "text", - "placeholders": {} - }, - "pleaseChoose": "Roghnaigh le do thoil", - "@pleaseChoose": { - "type": "text", - "placeholders": {} - }, - "play": "Seinn {fileName}", - "@play": { - "type": "text", - "placeholders": { - "fileName": {} - } - }, - "passwordRecovery": "Aisfháil pasfhocail", - "@passwordRecovery": { - "type": "text", - "placeholders": {} - }, - "passwordForgotten": "Pasfhocal dearmadta", - "@passwordForgotten": { - "type": "text", - "placeholders": {} - }, - "openCamera": "Oscail ceamara", - "@openCamera": { - "type": "text", - "placeholders": {} - }, - "obtainingLocation": "ag Aimsiú an suíomh…", - "@obtainingLocation": { - "type": "text", - "placeholders": {} - }, - "noPermission": "Gan cead", - "@noPermission": { - "type": "text", - "placeholders": {} - }, - "newChat": "Comhrá nua", - "@newChat": { - "type": "text", - "placeholders": {} - }, - "muteChat": "Ciúnaigh comhrá", - "@muteChat": { - "type": "text", - "placeholders": {} - }, - "memberChanges": "Athruithe ball", - "@memberChanges": { - "type": "text", - "placeholders": {} - }, - "loadMore": "Lódáil níos mó…", - "@loadMore": { - "type": "text", - "placeholders": {} - }, - "joinRoom": "Téigh isteach sa seomra", - "@joinRoom": { - "type": "text", - "placeholders": {} - }, - "isTyping": "ag clóscríobh…", - "@isTyping": { - "type": "text", - "placeholders": {} - }, - "inviteContact": "Tabhair cuireadh do theagmháil", - "@inviteContact": { - "type": "text", - "placeholders": {} - }, - "ignoredUsers": "Úsáideoirí a dtugann tú neamhaird orthu", - "@ignoredUsers": { - "type": "text", - "placeholders": {} - }, - "fromJoining": "Ó tar isteach", - "@fromJoining": { - "type": "text", - "placeholders": {} - }, - "fontSize": "Méid cló", - "@fontSize": { - "type": "text", - "placeholders": {} - }, - "enableEncryption": "Tosaigh criptiú", - "@enableEncryption": { - "type": "text", - "placeholders": {} - }, - "editDisplayname": "Cuir ainm taispeána in eagar", - "@editDisplayname": { - "type": "text", - "placeholders": {} - }, - "currentlyActive": "Gníomhach faoi láthair", - "@currentlyActive": { - "type": "text", - "placeholders": {} - }, - "fileName": "Ainm an chomhaid", - "@fileName": { - "type": "text", - "placeholders": {} - }, - "everythingReady": "Gach rud réidh!", - "@everythingReady": { - "type": "text", - "placeholders": {} - }, - "emptyChat": "Comhrá folamh", - "@emptyChat": { - "type": "text", - "placeholders": {} - }, - "emoteShortcode": "Gearrchód straoiseoige", - "@emoteShortcode": { - "type": "text", - "placeholders": {} - }, - "emoteSettings": "Socruithe straoiseoige", - "@emoteSettings": { - "type": "text", - "placeholders": {} - }, - "downloadFile": "Íoslódáil comhad", - "@downloadFile": { - "type": "text", - "placeholders": {} - }, - "changePassword": "Athraigh an pasfhocal", - "@changePassword": { - "type": "text", - "placeholders": {} - }, - "darkTheme": "Dorcha", - "@darkTheme": { - "type": "text", - "placeholders": {} - }, - "create": "Cruthaigh", - "@create": { - "type": "text", - "placeholders": {} - }, - "copy": "Cóipeáil", - "@copy": { - "type": "text", - "placeholders": {} - }, - "connect": "Ceangail", - "@connect": { - "type": "text", - "placeholders": {} - }, - "confirm": "Deimhnigh", - "@confirm": { - "type": "text", - "placeholders": {} - }, - "close": "Dún", - "@close": { - "type": "text", - "placeholders": {} - }, - "chats": "Comhráite", - "@chats": { - "type": "text", - "placeholders": {} - }, - "chat": "Comhrá", - "@chat": { - "type": "text", - "placeholders": {} - }, - "scanQrCode": "Scan cód QR", - "@scanQrCode": {}, - "inviteText": "Thug {username} cuireadh duit chuig FluffyChat.\n1. Suiteáil FluffyChat: https://fluffychat.im\n2. Cláraigh nó sínigh isteach\n3. Oscail an nasc cuiridh: {link}", - "@inviteText": { - "type": "text", - "placeholders": { - "username": {}, - "link": {} - } - }, - "noMatrixServer": "Níl {server1} freastalaí Matrix. Úsáid {server2} ina áit sin?", - "@noMatrixServer": { - "type": "text", - "placeholders": { - "server1": {}, - "server2": {} - } - }, - "noGoogleServicesWarning": "Dealraíonn sé nach bhfuil aon seirbhísí google agat ar do ghuthán. Sin cinneadh maith le do phríobháideacht! Chun fógraí brú a fháil i FluffyChat molaimid https://microg.org/ nó https://unifiedpush.org/ a úsáid.", - "@noGoogleServicesWarning": { - "type": "text", - "placeholders": {} - }, - "noEncryptionForPublicRooms": "Ní féidir leat criptiú a ghníomhachtú ach a luaithe nach bhfuil an seomra inrochtana go poiblí a thuilleadh.", - "@noEncryptionForPublicRooms": { - "type": "text", - "placeholders": {} - }, - "noEmotesFound": "Níor aimsíodh aon straoiseoga. 😕", - "@noEmotesFound": { - "type": "text", - "placeholders": {} - }, - "noConnectionToTheServer": "Gan aon nasc leis an bhfreastalaí", - "@noConnectionToTheServer": { - "type": "text", - "placeholders": {} - }, - "newVerificationRequest": "Iarratas fíoraithe nua!", - "@newVerificationRequest": { - "type": "text", - "placeholders": {} - }, - "newMessageInFluffyChat": "Teachtaireacht nua i FluffyChat", - "@newMessageInFluffyChat": { - "type": "text", - "placeholders": {} - }, - "needPantalaimonWarning": "Bí ar an eolas go dteastaíonn Pantalaimon uait chun criptiú ó cheann go ceann a úsáid anois.", - "@needPantalaimonWarning": { - "type": "text", - "placeholders": {} - }, - "logInTo": "Logáil isteach chuig {homeserver}", - "@logInTo": { - "type": "text", - "placeholders": { - "homeserver": {} - } - }, - "locationPermissionDeniedNotice": "Diúltaíodh cead suímh. Deonaigh dóibh le do thoil go mbeidh tú in ann do shuíomh a roinnt.", - "@locationPermissionDeniedNotice": { - "type": "text", - "placeholders": {} - }, - "locationDisabledNotice": "Tá seirbhísí suímh díchumasaithe. Cuir ar a gcumas le do thoil a bheith in ann do shuíomh a roinnt.", - "@locationDisabledNotice": { - "type": "text", - "placeholders": {} - }, - "loadingPleaseWait": "Ag lódáil… Fan, le do thoil.", - "@loadingPleaseWait": { - "type": "text", - "placeholders": {} - }, - "loadCountMoreParticipants": "Lódáil {count} níos mó rannpháirtithe", - "@loadCountMoreParticipants": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "leftTheChat": "Fágadh an comhrá", - "@leftTheChat": { - "type": "text", - "placeholders": {} - }, - "lastActiveAgo": "Gníomhach deireanach: {localizedTimeShort}", - "@lastActiveAgo": { - "type": "text", - "placeholders": { - "localizedTimeShort": {} - } - }, - "kickFromChat": "Caith é amach as an comhrá", - "@kickFromChat": { - "type": "text", - "placeholders": {} - }, - "kicked": "Chaith {username} {targetName} amach", - "@kicked": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "kickedAndBanned": "Chaith {username} amach agus chuir cosc ar {targetName} freisin", - "@kickedAndBanned": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "joinedTheChat": "Tháinig {username} isteach sa chomhrá", - "@joinedTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "invitedUsersOnly": "Úsáideoirí le cuireadh amháin", - "@invitedUsersOnly": { - "type": "text", - "placeholders": {} - }, - "inviteContactToGroup": "Tabhair cuireadh do theagmháil chuig {groupName}", - "@inviteContactToGroup": { - "type": "text", - "placeholders": { - "groupName": {} - } - }, - "incorrectPassphraseOrKey": "Pasfhrása nó eochair téarnaimh mícheart", - "@incorrectPassphraseOrKey": { - "type": "text", - "placeholders": {} - }, - "iHaveClickedOnLink": "Chliceáil mé ar an nasc", - "@iHaveClickedOnLink": { - "type": "text", - "placeholders": {} - }, - "howOffensiveIsThisContent": "Cé chomh maslach atá an t-ábhar seo?", - "@howOffensiveIsThisContent": { - "type": "text", - "placeholders": {} - }, - "extremeOffensive": "Fíor-maslach", - "@extremeOffensive": { - "type": "text", - "placeholders": {} - }, - "hasWithdrawnTheInvitationFor": "Tharraing {username} an cuireadh do {targetName} siar", - "@hasWithdrawnTheInvitationFor": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "goToTheNewRoom": "Téigh go dtí an seomra nua", - "@goToTheNewRoom": { - "type": "text", - "placeholders": {} - }, - "errorObtainingLocation": "Earráid maidir le suíomh a fháil: {error}", - "@errorObtainingLocation": { - "type": "text", - "placeholders": { - "error": {} - } - }, - "enterAnEmailAddress": "Cuir isteach seoladh ríomhphoist", - "@enterAnEmailAddress": { - "type": "text", - "placeholders": {} - }, - "endedTheCall": "Chuir {senderName} deireadh leis an nglao", - "@endedTheCall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "encryptionNotEnabled": "Ní chumasaítear criptiú", - "@encryptionNotEnabled": { - "type": "text", - "placeholders": {} - }, - "enableEncryptionWarning": "Ní bheidh in ann an criptiú a dhíchumasú níos mó. An bhfuil tú cinnte?", - "@enableEncryptionWarning": { - "type": "text", - "placeholders": {} - }, - "enableEmotesGlobally": "Cumasaigh pacáiste straoiseoige go huilíoch", - "@enableEmotesGlobally": { - "type": "text", - "placeholders": {} - }, - "emoteWarnNeedToPick": "Caithfidh tú gearrchód straoiseoige agus íomhá a roghnú!", - "@emoteWarnNeedToPick": { - "type": "text", - "placeholders": {} - }, - "emotePacks": "Pacáistí straoiseoige don seomra", - "@emotePacks": { - "type": "text", - "placeholders": {} - }, - "displaynameHasBeenChanged": "Athraíodh an t-ainm taispeána", - "@displaynameHasBeenChanged": { - "type": "text", - "placeholders": {} - }, - "deactivateAccountWarning": "Díghníomhachtaeoidh sé seo do chuntas úsáideora. Ní féidir é seo a chealú! An bhfuil tú cinnte?", - "@deactivateAccountWarning": { - "type": "text", - "placeholders": {} - }, - "couldNotDecryptMessage": "Níorbh fhéidir teachtaireacht a dhíchriptiú: {error}", - "@couldNotDecryptMessage": { - "type": "text", - "placeholders": { - "error": {} - } - }, - "contentHasBeenReported": "Tuairiscíodh an t-ábhar do lucht riaracháin an fhreastalaí", - "@contentHasBeenReported": { - "type": "text", - "placeholders": {} - }, - "commandHint_unban": "Cuir deireadh an cosc den úsáideoir áirithe ón seomra seo", - "@commandHint_unban": { - "type": "text", - "description": "Usage hint for the command /unban" - }, - "changedTheGuestAccessRulesTo": "D'athraigh {username} na rialacha maidir le rochtain aoi chuig: {rules}", - "@changedTheGuestAccessRulesTo": { - "type": "text", - "placeholders": { - "username": {}, - "rules": {} - } - }, - "changedTheGuestAccessRules": "D'athraigh {username} na rialacha rochtana aoi", - "@changedTheGuestAccessRules": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "badServerVersionsException": "Tá na leaganacha sonraíochta seo ar fáil faoin freastalaí baile:\n{serverVersions}\nAch níl ach na ceann seo ar fáil faoin aip seo {supportedVersions}", - "@badServerVersionsException": { - "type": "text", - "placeholders": { - "serverVersions": {}, - "supportedVersions": {} - } - }, - "badServerLoginTypesException": "Tá na cineálacha logála isteach seo ar fáil faoin freastalaí baile:\n{serverVersions}\nAch níl ach na ceann seo ar fáil faoin aip seo:\n{supportedVersions}", - "@badServerLoginTypesException": { - "type": "text", - "placeholders": { - "serverVersions": {}, - "supportedVersions": {} - } - }, - "askSSSSSign": "Chun a bheith in ann an duine eile a shíniú, cuir isteach do phasfhrása stóir sábháilte nó d'eochair téarnaimh.", - "@askSSSSSign": { - "type": "text", - "placeholders": {} - }, - "yourPublicKey": "D'eochair phoiblí", - "@yourPublicKey": { - "type": "text", - "placeholders": {} - }, - "youHaveBeenBannedFromThisChat": "Cuireadh cosc ort ón gcomhrá seo", - "@youHaveBeenBannedFromThisChat": { - "type": "text", - "placeholders": {} - }, - "youAreNoLongerParticipatingInThisChat": "Níl tú ag glacadh páirte sa chomhrá seo a thuilleadh", - "@youAreNoLongerParticipatingInThisChat": { - "type": "text", - "placeholders": {} - }, - "writeAMessage": "Scríobh teachtaireacht…", - "@writeAMessage": { - "type": "text", - "placeholders": {} - }, - "withTheseAddressesRecoveryDescription": "Leis na seoltaí seo is féidir leat do phasfhocal a athshlánú.", - "@withTheseAddressesRecoveryDescription": { - "type": "text", - "placeholders": {} - }, - "wipeChatBackup": "Glan do cúltaca comhrá a chruthú eochair slándála nua?", - "@wipeChatBackup": { - "type": "text", - "placeholders": {} - }, - "whyDoYouWantToReportThis": "Cén fáth ar mhaith leat é seo a thuairisciú?", - "@whyDoYouWantToReportThis": { - "type": "text", - "placeholders": {} - }, - "whoIsAllowedToJoinThisGroup": "Cé a bhfuil cead aige/aici dul isteach sa ghrúpa seo", - "@whoIsAllowedToJoinThisGroup": { - "type": "text", - "placeholders": {} - }, - "whoCanPerformWhichAction": "Cé atá in ann an gníomh a dhéanamh", - "@whoCanPerformWhichAction": { - "type": "text", - "placeholders": {} - }, - "verifySuccess": "D'fhíoraigh tú go rathúil!", - "@verifySuccess": { - "type": "text", - "placeholders": {} - }, - "userLeftTheChat": "D'fhág {username} an comhrá", - "@userLeftTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "userAndUserAreTyping": "Tá {username} agus {username2} ag clóscríobh…", - "@userAndUserAreTyping": { - "type": "text", - "placeholders": { - "username": {}, - "username2": {} - } - }, - "userAndOthersAreTyping": "tá {username} agus {count} daoine eile ag clóscríobh…", - "@userAndOthersAreTyping": { - "type": "text", - "placeholders": { - "username": {}, - "count": {} - } - }, - "unreadChats": "{unreadCount, plural, =1{1 comhrá neamhléite} other{{unreadCount} comhráite neamhléite}}", - "@unreadChats": { - "type": "text", - "placeholders": { - "unreadCount": {} - } - }, - "unknownEncryptionAlgorithm": "Algartam criptithe anaithnid", - "@unknownEncryptionAlgorithm": { - "type": "text", - "placeholders": {} - }, - "unbannedUser": "Chuir {username} deireadh an cosc {targetName}", - "@unbannedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "tryToSendAgain": "Déan iarracht a sheoladh arís", - "@tryToSendAgain": { - "type": "text", - "placeholders": {} - }, - "transferFromAnotherDevice": "Aistriú ó ghléas eile", - "@transferFromAnotherDevice": { - "type": "text", - "placeholders": {} - }, - "tooManyRequestsWarning": "An iomarca iarratas. Bain triail eile as níos déanaí!", - "@tooManyRequestsWarning": { - "type": "text", - "placeholders": {} - }, - "theyDontMatch": "Níl siad céanna", - "@theyDontMatch": { - "type": "text", - "placeholders": {} - }, - "sharedTheLocation": "Roinn {username} an suíomh", - "@sharedTheLocation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "setPermissionsLevel": "Socraigh leibhéal ceadanna", - "@setPermissionsLevel": { - "type": "text", - "placeholders": {} - }, - "setInvitationLink": "Socraigh nasc cuiridh", - "@setInvitationLink": { - "type": "text", - "placeholders": {} - }, - "setCustomEmotes": "Socraigh straoiseoga saincheaptha", - "@setCustomEmotes": { - "type": "text", - "placeholders": {} - }, - "setAsCanonicalAlias": "Socraigh mar phríomh-ailias", - "@setAsCanonicalAlias": { - "type": "text", - "placeholders": {} - }, - "sentCallInformations": "Sheol {senderName} faisnéis maidir le glaonna", - "@sentCallInformations": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "sentAVideo": "Sheol {username} físeán", - "@sentAVideo": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentASticker": "Sheol {username} greamán", - "@sentASticker": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentAPicture": "Sheol {username} pictiúr", - "@sentAPicture": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "replaceRoomWithNewerVersion": "Cuir leagan seomra níos nuaí in ionad an tseomra", - "@replaceRoomWithNewerVersion": { - "type": "text", - "placeholders": {} - }, - "renderRichContent": "Taispeáin ábhar teachtaireachta saibhir", - "@renderRichContent": { - "type": "text", - "placeholders": {} - }, - "removeYourAvatar": "Bain d'abhatár", - "@removeYourAvatar": { - "type": "text", - "placeholders": {} - }, - "unbanFromChat": "Cuir deireadh an cosc ón gcomhrá", - "@unbanFromChat": { - "type": "text", - "placeholders": {} - }, - "removedBy": "Bainte de ag {username}", - "@removedBy": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "removeAllOtherDevices": "Bain gach gléas eile", - "@removeAllOtherDevices": { - "type": "text", - "placeholders": {} - }, - "rejectedTheInvitation": "Dhiúltaigh {username} don chuireadh", - "@rejectedTheInvitation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "redactedAnEvent": "Rinne {username} cinsire imeacht", - "@redactedAnEvent": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "pleaseFollowInstructionsOnWeb": "Lean na treoracha ar an suíomh gréasáin agus tapáil \"ar aghaidh\".", - "@pleaseFollowInstructionsOnWeb": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourUsername": "Cuir isteach d'ainm úsáideora le do thoil", - "@pleaseEnterYourUsername": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourPin": "Cuir isteach d'uimhir PIN le do thoil", - "@pleaseEnterYourPin": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourPassword": "Iontráil do phasfhocal le do thoil", - "@pleaseEnterYourPassword": { - "type": "text", - "placeholders": {} - }, - "pleaseEnter4Digits": "Iontráil 4 dhigit le do thoil nó fág folamh chun glas aipe a dhíchumasú.", - "@pleaseEnter4Digits": { - "type": "text", - "placeholders": {} - }, - "pleaseClickOnLink": "Cliceáil ar an nasc sa ríomhphost agus ansin lean ar aghaidh.", - "@pleaseClickOnLink": { - "type": "text", - "placeholders": {} - }, - "pleaseChooseAPasscode": "Roghnaigh paschód le do thoil", - "@pleaseChooseAPasscode": { - "type": "text", - "placeholders": {} - }, - "pickImage": "Roghnaigh íomhá", - "@pickImage": { - "type": "text", - "placeholders": {} - }, - "passwordHasBeenChanged": "Athraíodh an pasfhocal", - "@passwordHasBeenChanged": { - "type": "text", - "placeholders": {} - }, - "passphraseOrKey": "pasfhrása nó eochair téarnaimh", - "@passphraseOrKey": { - "type": "text", - "placeholders": {} - }, - "serverRequiresEmail": "Ní mór don fhreastalaí seo do sheoladh ríomhphoist a bhailíochtú le haghaidh clárúcháin.", - "@serverRequiresEmail": {}, - "openInMaps": "Oscail i léarscáileanna", - "@openInMaps": { - "type": "text", - "placeholders": {} - }, - "openAppToReadMessages": "Oscail an aip chun teachtaireachtaí a léamh", - "@openAppToReadMessages": { - "type": "text", - "placeholders": {} - }, - "oopsSomethingWentWrong": "Úps, chuaigh rud éigin mícheart …", - "@oopsSomethingWentWrong": { - "type": "text", - "placeholders": {} - }, - "oopsPushError": "Hoips! Ar an drochuair, tharla earráid nuair a bhí na fógraí brú á mbunú.", - "@oopsPushError": { - "type": "text", - "placeholders": {} - }, - "onlineKeyBackupEnabled": "Tá Cúltaca Eochair Ar Líne cumasaithe", - "@onlineKeyBackupEnabled": { - "type": "text", - "placeholders": {} - }, - "numUsersTyping": "Tá {count} úsáideoirí ag clóscríobh…", - "@numUsersTyping": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "notificationsEnabledForThisAccount": "Fógraí cumasaithe don chuntas seo", - "@notificationsEnabledForThisAccount": { - "type": "text", - "placeholders": {} - }, - "noRoomsFound": "Níor aimsíodh aon seomraí…", - "@noRoomsFound": { - "type": "text", - "placeholders": {} - }, - "noPasswordRecoveryDescription": "Níor chuir tú bealach leis do phasfhocal a aisghabháil fós.", - "@noPasswordRecoveryDescription": { - "type": "text", - "placeholders": {} - }, - "inviteForMe": "Tabhair cuireadh dom", - "@inviteForMe": { - "type": "text", - "placeholders": {} - }, - "weSentYouAnEmail": "Sheolamar ríomhphost chugat", - "@weSentYouAnEmail": { - "type": "text", - "placeholders": {} - }, - "waitingPartnerNumbers": "Ag fanacht le comhpháirtí glacadh leis na huimhreacha …", - "@waitingPartnerNumbers": { - "type": "text", - "placeholders": {} - }, - "waitingPartnerEmoji": "Ag fanacht le comhpháirtí glacadh leis na straoiseoga…", - "@waitingPartnerEmoji": { - "type": "text", - "placeholders": {} - }, - "waitingPartnerAcceptRequest": "Ag fanacht le comhpháirtí glacadh leis an iarratas…", - "@waitingPartnerAcceptRequest": { - "type": "text", - "placeholders": {} - }, - "visibleForEveryone": "Infheicthe do gach duine", - "@visibleForEveryone": { - "type": "text", - "placeholders": {} - }, - "visibleForAllParticipants": "Infheicthe do na rannpháirtithe go léir", - "@visibleForAllParticipants": { - "type": "text", - "placeholders": {} - }, - "visibilityOfTheChatHistory": "Infheictheacht stair na comhrá", - "@visibilityOfTheChatHistory": { - "type": "text", - "placeholders": {} - }, - "verifyTitle": "Ag fíorú cuntas eile", - "@verifyTitle": { - "type": "text", - "placeholders": {} - }, - "userSentUnknownEvent": "Sheol {username} imeacht {type}", - "@userSentUnknownEvent": { - "type": "text", - "placeholders": { - "username": {}, - "type": {} - } - }, - "userIsTyping": "Tá {username} ag clóscríobh…", - "@userIsTyping": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "unknownEvent": "Imeacht anaithnid '{type}'", - "@unknownEvent": { - "type": "text", - "placeholders": { - "type": {} - } - }, - "synchronizingPleaseWait": "Ag sioncrónú... Fan, le do thoil.", - "@synchronizingPleaseWait": { - "type": "text", - "placeholders": {} - }, - "startedACall": "Thosaigh {senderName} glao", - "@startedACall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "spaceIsPublic": "Tá an spás poiblí", - "@spaceIsPublic": { - "type": "text", - "placeholders": {} - }, - "singlesignon": "Sínigh Aonair ar", - "@singlesignon": { - "type": "text", - "placeholders": {} - }, - "sentAnAudio": "Sheol {username} fuaim", - "@sentAnAudio": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentAFile": "Sheol {username} comhad", - "@sentAFile": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sendAsText": "Seol mar théacs", - "@sendAsText": { - "type": "text" - }, - "sendAMessage": "Seol teachtaireacht", - "@sendAMessage": { - "type": "text", - "placeholders": {} - }, - "seenByUser": "Le feiceáil ag {username}", - "@seenByUser": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "roomHasBeenUpgraded": "Uasghrádaíodh an seomra", - "@roomHasBeenUpgraded": { - "type": "text", - "placeholders": {} - }, - "addAccount": "Breisigh cuntas", - "@addAccount": {}, - "enableMultiAccounts": "(BÉITE) Cumasaigh cuntais iomadúla ar an gléas seo", - "@enableMultiAccounts": {}, - "commandHint_create": "Cruthaigh comhrá grúpa folamh\nÚsáid --no-encryption chun criptiúchán a dhíchumasú", - "@commandHint_create": { - "type": "text", - "description": "Usage hint for the command /create" - }, - "link": "Nasc", - "@link": {}, - "commandHint_clearcache": "Glan an taisce", - "@commandHint_clearcache": { - "type": "text", - "description": "Usage hint for the command /clearcache" - }, - "videoCallsBetaWarning": "Tabhair faoi deara go bhfuil físglaonna i béite. B'fhéidir nach bhfeidhmíonn siad ar gach ardán chomh atá súil aige ná ar bith.", - "@videoCallsBetaWarning": {}, - "emailOrUsername": "Ríomhphost nó ainm úsáideora", - "@emailOrUsername": {}, - "repeatPassword": "Scríobh an pasfhocal arís", - "@repeatPassword": {}, - "yourChatBackupHasBeenSetUp": "Bunaíodh do chúltaca comhrá.", - "@yourChatBackupHasBeenSetUp": {}, - "openVideoCamera": "Oscail físcheamara", - "@openVideoCamera": { - "type": "text", - "placeholders": {} - }, - "@hugContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@jumpToLastReadMessage": {}, - "@allRooms": { - "type": "text", - "placeholders": {} - }, - "@commandHint_cuddle": {}, - "@widgetVideo": {}, - "@dismiss": {}, - "@reportErrorDescription": {}, - "@unsupportedAndroidVersion": {}, - "@widgetJitsi": {}, - "@messageType": {}, - "@indexedDbErrorLong": {}, - "@oneClientLoggedOut": {}, - "@startFirstChat": {}, - "@callingAccount": {}, - "@setColorTheme": {}, - "@nextAccount": {}, - "@allSpaces": {}, - "@supposedMxid": { - "type": "text", - "placeholders": { - "mxid": {} - } - }, - "@user": {}, - "@youAcceptedTheInvitation": {}, - "@youInvitedBy": { - "placeholders": { - "user": {} - } - }, - "@banUserDescription": {}, - "@widgetEtherpad": {}, - "@removeDevicesDescription": {}, - "@separateChatTypes": { - "type": "text", - "placeholders": {} - }, - "@tryAgain": {}, - "@youKickedAndBanned": { - "placeholders": { - "user": {} - } - }, - "@unbanUserDescription": {}, - "@youRejectedTheInvitation": {}, - "@otherCallingPermissions": {}, - "@messagesStyle": {}, - "@widgetUrlError": {}, - "@newSpaceDescription": {}, - "@chatDescription": {}, - "@callingAccountDetails": {}, - "@enterSpace": {}, - "@encryptThisChat": {}, - "@previousAccount": {}, - "@reopenChat": {}, - "@pleaseEnterRecoveryKey": {}, - "@widgetNameError": {}, - "@addToBundle": {}, - "@addWidget": {}, - "@countFiles": { - "placeholders": { - "count": {} - } - }, - "@noKeyForThisMessage": {}, - "@commandHint_markasgroup": {}, - "@hydrateTor": {}, - "@pushNotificationsNotAvailable": {}, - "@storeInAppleKeyChain": {}, - "@hydrate": {}, - "@invalidServerName": {}, - "@chatPermissions": {}, - "@sender": {}, - "@storeInAndroidKeystore": {}, - "@signInWithPassword": {}, - "@makeAdminDescription": {}, - "@saveKeyManuallyDescription": {}, - "@editBundlesForAccount": {}, - "@whyIsThisMessageEncrypted": {}, - "@setChatDescription": {}, - "@importFromZipFile": {}, - "@dehydrateWarning": {}, - "@noOtherDevicesFound": {}, - "@redactedBy": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@signInWith": { - "type": "text", - "placeholders": { - "provider": {} - } - }, - "@fileIsTooBigForServer": {}, - "@callingPermissions": {}, - "@readUpToHere": {}, - "@start": {}, - "@unlockOldMessages": {}, - "@numChats": { - "type": "number", - "placeholders": { - "number": {} - } - }, - "@optionalRedactReason": {}, - "@dehydrate": {}, - "@archiveRoomDescription": {}, - "@exportEmotePack": {}, - "@switchToAccount": { - "type": "number", - "placeholders": { - "number": {} - } - }, - "@experimentalVideoCalls": {}, - "@pleaseEnterRecoveryKeyDescription": {}, - "@inviteContactToGroupQuestion": {}, - "@redactedByBecause": { - "type": "text", - "placeholders": { - "username": {}, - "reason": {} - } - }, - "@youHaveWithdrawnTheInvitationFor": { - "placeholders": { - "user": {} - } - }, - "@appearOnTopDetails": {}, - "@enterRoom": {}, - "@reportUser": {}, - "@confirmEventUnpin": {}, - "@youInvitedUser": { - "placeholders": { - "user": {} - } - }, - "@fileHasBeenSavedAt": { - "type": "text", - "placeholders": { - "path": {} - } - }, - "@redactMessageDescription": {}, - "@recoveryKey": {}, - "@commandHint_discardsession": { - "type": "text", - "description": "Usage hint for the command /discardsession" - }, - "@invalidInput": {}, - "@dehydrateTorLong": {}, - "@doNotShowAgain": {}, - "@report": {}, - "@unverified": {}, - "@hideUnimportantStateEvents": {}, - "@screenSharingTitle": {}, - "@widgetCustom": {}, - "@addToSpaceDescription": {}, - "@googlyEyesContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@youBannedUser": { - "placeholders": { - "user": {} - } - }, - "@addChatDescription": {}, - "@hasKnocked": { - "placeholders": { - "user": {} - } - }, - "@publish": {}, - "@openLinkInBrowser": {}, - "@messageInfo": {}, - "@disableEncryptionWarning": {}, - "@directChat": {}, - "@wrongPinEntered": { - "type": "text", - "placeholders": { - "seconds": {} - } - }, - "@sendTypingNotifications": {}, - "@inviteGroupChat": {}, - "@appearOnTop": {}, - "@invitePrivateChat": {}, - "@foregroundServiceRunning": {}, - "@voiceCall": {}, - "@importEmojis": {}, - "@wasDirectChatDisplayName": { - "type": "text", - "placeholders": { - "oldDisplayName": {} - } - }, - "@noChatDescriptionYet": {}, - "@removeFromBundle": {}, - "@confirmMatrixId": {}, - "@learnMore": {}, - "@notAnImage": {}, - "@users": {}, - "@openGallery": {}, - "@chatDescriptionHasBeenChanged": {}, - "@newGroup": {}, - "@bundleName": {}, - "@dehydrateTor": {}, - "@removeFromSpace": {}, - "@roomUpgradeDescription": {}, - "@pleaseEnterANumber": {}, - "@youKicked": { - "placeholders": { - "user": {} - } - }, - "@profileNotFound": {}, - "@jump": {}, - "@reactedWith": { - "type": "text", - "placeholders": { - "sender": {}, - "reaction": {} - } - }, - "@sorryThatsNotPossible": {}, - "@videoWithSize": { - "type": "text", - "placeholders": { - "size": {} - } - }, - "@shareInviteLink": {}, - "@commandHint_markasdm": {}, - "@recoveryKeyLost": {}, - "@cuddleContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@deviceKeys": {}, - "@emoteKeyboardNoRecents": { - "type": "text", - "placeholders": {} - }, - "@setTheme": {}, - "@youJoinedTheChat": {}, - "@markAsRead": {}, - "@widgetName": {}, - "@errorAddingWidget": {}, - "@commandHint_dm": { - "type": "text", - "description": "Usage hint for the command /dm" - }, - "@commandHint_hug": {}, - "@replace": {}, - "@youUnbannedUser": { - "placeholders": { - "user": {} - } - }, - "@newSpace": {}, - "@emojis": {}, - "@commandHint_googly": {}, - "@pleaseTryAgainLaterOrChooseDifferentServer": {}, - "@createGroup": {}, - "@hydrateTorLong": {}, - "@time": {}, - "@custom": {}, - "@noBackupWarning": {}, - "@storeInSecureStorageDescription": {}, - "@openChat": {}, - "@kickUserDescription": {}, - "@importNow": {}, - "@pinMessage": {}, - "@invite": {}, - "@indexedDbErrorTitle": {}, - "@unsupportedAndroidVersionLong": {}, - "@storeSecurlyOnThisDevice": {}, - "@screenSharingDetail": {}, - "@placeCall": {} -} \ No newline at end of file + "you": "Tú", + "@you": { + "type": "text", + "placeholders": {} + }, + "yes": "Tá", + "@yes": { + "type": "text", + "placeholders": {} + }, + "warning": "Rabhadh!", + "@warning": { + "type": "text", + "placeholders": {} + }, + "wallpaper": "Cúlbhrat", + "@wallpaper": { + "type": "text", + "placeholders": {} + }, + "verify": "Deimhnigh", + "@verify": { + "type": "text", + "placeholders": {} + }, + "verified": "Deimhnithe", + "@verified": { + "type": "text", + "placeholders": {} + }, + "username": "Ainm úsáideora", + "@username": { + "type": "text", + "placeholders": {} + }, + "unpin": "Bain biorán", + "@unpin": { + "type": "text", + "placeholders": {} + }, + "unavailable": "Níl ar fáil", + "@unavailable": { + "type": "text", + "placeholders": {} + }, + "title": "FluffyChat", + "@title": { + "description": "Title for the application", + "type": "text", + "placeholders": {} + }, + "systemTheme": "Córas", + "@systemTheme": { + "type": "text", + "placeholders": {} + }, + "submit": "Cuir isteach", + "@submit": { + "type": "text", + "placeholders": {} + }, + "statusExampleMessage": "Conas atá tú inniu?", + "@statusExampleMessage": { + "type": "text", + "placeholders": {} + }, + "status": "Staid", + "@status": { + "type": "text", + "placeholders": {} + }, + "skip": "Léim", + "@skip": { + "type": "text", + "placeholders": {} + }, + "share": "Roinn", + "@share": { + "type": "text", + "placeholders": {} + }, + "settings": "Socruithe", + "@settings": { + "type": "text", + "placeholders": {} + }, + "send": "Seol", + "@send": { + "type": "text", + "placeholders": {} + }, + "security": "Slándáil", + "@security": { + "type": "text", + "placeholders": {} + }, + "search": "Cuardaigh", + "@search": { + "type": "text", + "placeholders": {} + }, + "reply": "Freagair", + "@reply": { + "type": "text", + "placeholders": {} + }, + "remove": "Bain", + "@remove": { + "type": "text", + "placeholders": {} + }, + "rejoin": "Téigh ar ais isteach", + "@rejoin": { + "type": "text", + "placeholders": {} + }, + "reject": "Diúltaigh", + "@reject": { + "type": "text", + "placeholders": {} + }, + "register": "Cláraigh", + "@register": { + "type": "text", + "placeholders": {} + }, + "recording": "Ag Taifeadadh", + "@recording": { + "type": "text", + "placeholders": {} + }, + "reason": "Fáth", + "@reason": { + "type": "text", + "placeholders": {} + }, + "privacy": "Príobháideacht", + "@privacy": { + "type": "text", + "placeholders": {} + }, + "pin": "Biorán", + "@pin": { + "type": "text", + "placeholders": {} + }, + "people": "Daoine", + "@people": { + "type": "text", + "placeholders": {} + }, + "password": "Pasfhocal", + "@password": { + "type": "text", + "placeholders": {} + }, + "participant": "Rannpháirtí", + "@participant": { + "type": "text", + "placeholders": {} + }, + "directChats": "Comhráite Díreacha", + "@directChats": { + "type": "text", + "placeholders": {} + }, + "deviceId": "ID gléis", + "@deviceId": { + "type": "text", + "placeholders": {} + }, + "deleteMessage": "Scrios an teachtaireacht", + "@deleteMessage": { + "type": "text", + "placeholders": {} + }, + "deleteAccount": "Scrios an cuntas", + "@deleteAccount": { + "type": "text", + "placeholders": {} + }, + "createNewSpace": "Spás nua", + "@createNewSpace": { + "type": "text", + "placeholders": {} + }, + "countParticipants": "{count} rannpháirtithe", + "@countParticipants": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "or": "Nó", + "@or": { + "type": "text", + "placeholders": {} + }, + "online": "Ar líne", + "@online": { + "type": "text", + "placeholders": {} + }, + "ok": "togha", + "@ok": { + "type": "text", + "placeholders": {} + }, + "offline": "As líne", + "@offline": { + "type": "text", + "placeholders": {} + }, + "offensive": "Maslach", + "@offensive": { + "type": "text", + "placeholders": {} + }, + "notifications": "Fógraí", + "@notifications": { + "type": "text", + "placeholders": {} + }, + "none": "Aon cheann", + "@none": { + "type": "text", + "placeholders": {} + }, + "no": "Níl", + "@no": { + "type": "text", + "placeholders": {} + }, + "next": "Ar Aghaidh", + "@next": { + "type": "text", + "placeholders": {} + }, + "moderator": "Modhnóir", + "@moderator": { + "type": "text", + "placeholders": {} + }, + "messages": "Teachtaireachtaí", + "@messages": { + "type": "text", + "placeholders": {} + }, + "mention": "Luaigh", + "@mention": { + "type": "text", + "placeholders": {} + }, + "logout": "Logáil amach", + "@logout": { + "type": "text", + "placeholders": {} + }, + "login": "Logáil isteach", + "@login": { + "type": "text", + "placeholders": {} + }, + "lightTheme": "Solas", + "@lightTheme": { + "type": "text", + "placeholders": {} + }, + "license": "Ceadúnas", + "@license": { + "type": "text", + "placeholders": {} + }, + "leave": "Fág", + "@leave": { + "type": "text", + "placeholders": {} + }, + "invited": "Le cuireadh", + "@invited": { + "type": "text", + "placeholders": {} + }, + "inoffensive": "Neamhurchóideach", + "@inoffensive": { + "type": "text", + "placeholders": {} + }, + "ignore": "Tabhair neamhaird ar", + "@ignore": { + "type": "text", + "placeholders": {} + }, + "identity": "Aitheantas", + "@identity": { + "type": "text", + "placeholders": {} + }, + "id": "ID", + "@id": { + "type": "text", + "placeholders": {} + }, + "help": "Cabhair", + "@help": { + "type": "text", + "placeholders": {} + }, + "groups": "Grúpaí", + "@groups": { + "type": "text", + "placeholders": {} + }, + "group": "Grúpa", + "@group": { + "type": "text", + "placeholders": {} + }, + "forward": "Seol ar aghaidh", + "@forward": { + "type": "text", + "placeholders": {} + }, + "fluffychat": "FluffyChat", + "@fluffychat": { + "type": "text", + "placeholders": {} + }, + "homeserver": "Freastalaí baile", + "@homeserver": {}, + "encryption": "Criptiúchán", + "@encryption": { + "type": "text", + "placeholders": {} + }, + "encrypted": "Criptithe", + "@encrypted": { + "type": "text", + "placeholders": {} + }, + "edit": "Cuir in eagar", + "@edit": { + "type": "text", + "placeholders": {} + }, + "devices": "Gléasanna", + "@devices": { + "type": "text", + "placeholders": {} + }, + "device": "Gléas", + "@device": { + "type": "text", + "placeholders": {} + }, + "delete": "Scrios", + "@delete": { + "type": "text", + "placeholders": {} + }, + "dateWithYear": "{day}/{month}/{year}", + "@dateWithYear": { + "type": "text", + "placeholders": { + "year": {}, + "month": {}, + "day": {} + } + }, + "dateWithoutYear": "{day}/{month}", + "@dateWithoutYear": { + "type": "text", + "placeholders": { + "month": {}, + "day": {} + } + }, + "dateAndTimeOfDay": "{date}, {timeOfDay}", + "@dateAndTimeOfDay": { + "type": "text", + "placeholders": { + "date": {}, + "timeOfDay": {} + } + }, + "containsUserName": "Coinníonn sé ainm úsáideora", + "@containsUserName": { + "type": "text", + "placeholders": {} + }, + "configureChat": "Cumraigh comhrá", + "@configureChat": { + "type": "text", + "placeholders": {} + }, + "commandInvalid": "Ordú neamhbhailí", + "@commandInvalid": { + "type": "text" + }, + "commandHint_send": "Seol téacs", + "@commandHint_send": { + "type": "text", + "description": "Usage hint for the command /send" + }, + "commandHint_me": "Déan cur síos ort féin", + "@commandHint_me": { + "type": "text", + "description": "Usage hint for the command /me" + }, + "clearArchive": "Glan an cartlann", + "@clearArchive": {}, + "chatDetails": "Sonraí comhrá", + "@chatDetails": { + "type": "text", + "placeholders": {} + }, + "chatBackup": "Cúltaca comhrá", + "@chatBackup": { + "type": "text", + "placeholders": {} + }, + "changedTheChatAvatar": "D'athraigh {username} abhatár an chomhrá", + "@changedTheChatAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changeDeviceName": "Athraigh ainm an ghléis", + "@changeDeviceName": { + "type": "text", + "placeholders": {} + }, + "cantOpenUri": "Ní féidir an URI {uri} a oscailt", + "@cantOpenUri": { + "type": "text", + "placeholders": { + "uri": {} + } + }, + "cancel": "Cealaigh", + "@cancel": { + "type": "text", + "placeholders": {} + }, + "botMessages": "Teachtaireachtaí bota", + "@botMessages": { + "type": "text", + "placeholders": {} + }, + "blocked": "Bactha", + "@blocked": { + "type": "text", + "placeholders": {} + }, + "blockDevice": "Bac Gléas", + "@blockDevice": { + "type": "text", + "placeholders": {} + }, + "bannedUser": "Chuir {username} cosc ar {targetName}", + "@bannedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "banned": "Coiscthe", + "@banned": { + "type": "text", + "placeholders": {} + }, + "banFromChat": "Toirmisc ón gcomhrá", + "@banFromChat": { + "type": "text", + "placeholders": {} + }, + "sendOnEnter": "Seol ar iontráil", + "@sendOnEnter": {}, + "archive": "Cartlann", + "@archive": { + "type": "text", + "placeholders": {} + }, + "appLock": "Bac aip", + "@appLock": { + "type": "text", + "placeholders": {} + }, + "anyoneCanJoin": "Is féidir le aon duine dul isteach", + "@anyoneCanJoin": { + "type": "text", + "placeholders": {} + }, + "answeredTheCall": "D'fhreagair {senderName} an glao", + "@answeredTheCall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "allChats": "Gach comhrá", + "@allChats": { + "type": "text", + "placeholders": {} + }, + "all": "Gach", + "@all": { + "type": "text", + "placeholders": {} + }, + "alias": "ailias", + "@alias": { + "type": "text", + "placeholders": {} + }, + "admin": "Riarthóir", + "@admin": { + "type": "text", + "placeholders": {} + }, + "addToSpace": "Cuir go spás", + "@addToSpace": {}, + "addEmail": "Cuir ríomhphoist", + "@addEmail": { + "type": "text", + "placeholders": {} + }, + "activatedEndToEndEncryption": "Thosaigh {username} an criptiú ó dheireadh go deireadh", + "@activatedEndToEndEncryption": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "account": "Cuntas", + "@account": { + "type": "text", + "placeholders": {} + }, + "acceptedTheInvitation": "Ghlac {username} leis an cuireadh", + "@acceptedTheInvitation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "accept": "Glac", + "@accept": { + "type": "text", + "placeholders": {} + }, + "about": "Faoi", + "@about": { + "type": "text", + "placeholders": {} + }, + "askVerificationRequest": "Glac leis an iarratas fíoraithe seo ó {username}?", + "@askVerificationRequest": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "areYouSureYouWantToLogout": "An bhfuil tú cinnte gur mhaith leat logáil amach?", + "@areYouSureYouWantToLogout": { + "type": "text", + "placeholders": {} + }, + "areYouSure": "An bhfuil tú cinnte?", + "@areYouSure": { + "type": "text", + "placeholders": {} + }, + "areGuestsAllowedToJoin": "An bhfuil cead ag aoi-úsáideoirí a bheith páirteach", + "@areGuestsAllowedToJoin": { + "type": "text", + "placeholders": {} + }, + "invitedUser": "Thug {username} cuireadh do {targetName}", + "@invitedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "hideRedactedEvents": "Folaigh imeachtaí athdhéanta", + "@hideRedactedEvents": { + "type": "text", + "placeholders": {} + }, + "guestsCanJoin": "Is féidir le haíonna páirt a ghlacadh", + "@guestsCanJoin": { + "type": "text", + "placeholders": {} + }, + "guestsAreForbidden": "Tá cosc ar aíonna", + "@guestsAreForbidden": { + "type": "text", + "placeholders": {} + }, + "groupWith": "Grúpa le {displayname}", + "@groupWith": { + "type": "text", + "placeholders": { + "displayname": {} + } + }, + "groupIsPublic": "Tá an grúpa poiblí", + "@groupIsPublic": { + "type": "text", + "placeholders": {} + }, + "fromTheInvitation": "Ón gcuireadh", + "@fromTheInvitation": { + "type": "text", + "placeholders": {} + }, + "enterYourHomeserver": "Cuir isteach do fhreastalaí baile", + "@enterYourHomeserver": { + "type": "text", + "placeholders": {} + }, + "emoteInvalid": "Gearrchód emote neamhbhailí!", + "@emoteInvalid": { + "type": "text", + "placeholders": {} + }, + "emoteExists": "Tá iomaite ann cheana féin!", + "@emoteExists": { + "type": "text", + "placeholders": {} + }, + "editRoomAvatar": "Cuir in eagar abhatár an tseomra", + "@editRoomAvatar": { + "type": "text", + "placeholders": {} + }, + "editRoomAliases": "Cuir ailiasanna an tseomra in eagar", + "@editRoomAliases": { + "type": "text", + "placeholders": {} + }, + "editBlockedServers": "Cuir freastalaí blocáilte in eagar", + "@editBlockedServers": { + "type": "text", + "placeholders": {} + }, + "defaultPermissionLevel": "Leibhéal ceada réamhshocraithe", + "@defaultPermissionLevel": { + "type": "text", + "placeholders": {} + }, + "unblockDevice": "Díbhlocáil Gléas", + "@unblockDevice": { + "type": "text", + "placeholders": {} + }, + "contactHasBeenInvitedToTheGroup": "Tugadh cuireadh don theagmháil a thar isteach sa grúpa", + "@contactHasBeenInvitedToTheGroup": { + "type": "text", + "placeholders": {} + }, + "compareNumbersMatch": "Déan comparáid idir na huimhreacha seo a leanas agus déan cinnte go bhfuil na huimhreacha seo a leanas ag teacht le huimhreacha an ghléis eile:", + "@compareNumbersMatch": { + "type": "text", + "placeholders": {} + }, + "compareEmojiMatch": "Déan comparáid agus déan cinnte go bhfuil an emoji seo a leanas comhoiriúnach le emoji an ghléis eile:", + "@compareEmojiMatch": { + "type": "text", + "placeholders": {} + }, + "commandMissing": "Ní ordú é {command}.", + "@commandMissing": { + "type": "text", + "placeholders": { + "command": {} + }, + "description": "State that {command} is not a valid /command." + }, + "commandHint_react": "Seol freagra mar fhreagairt", + "@commandHint_react": { + "type": "text", + "description": "Usage hint for the command /react" + }, + "commandHint_op": "Socraigh leibhéal cumhachta an úsáideora áirithe (réamhshocrú: 50)", + "@commandHint_op": { + "type": "text", + "description": "Usage hint for the command /op" + }, + "commandHint_myroomnick": "Socraigh d'ainm taispeána don seomra seo", + "@commandHint_myroomnick": { + "type": "text", + "description": "Usage hint for the command /myroomnick" + }, + "commandHint_myroomavatar": "Cuir do phictiúr don seomra seo (le mxc-uri)", + "@commandHint_myroomavatar": { + "type": "text", + "description": "Usage hint for the command /myroomavatar" + }, + "commandHint_kick": "Bain an t-úsáideoir áirithe den seomra seo", + "@commandHint_kick": { + "type": "text", + "description": "Usage hint for the command /kick" + }, + "commandHint_join": "Téigh isteach sa seomra áirithe", + "@commandHint_join": { + "type": "text", + "description": "Usage hint for the command /join" + }, + "commandHint_ban": "Cuir cosc ar an úsáideoir áirithe ón seomra seo", + "@commandHint_ban": { + "type": "text", + "description": "Usage hint for the command /ban" + }, + "commandHint_invite": "Cuir cosc ar an úsáideoir áirithe ón seomra seo", + "@commandHint_invite": { + "type": "text", + "description": "Usage hint for the command /invite" + }, + "chooseAStrongPassword": "Roghnaigh pasfhocal láidir", + "@chooseAStrongPassword": { + "type": "text", + "placeholders": {} + }, + "chatHasBeenAddedToThisSpace": "Cuireadh comhrá leis an spás seo", + "@chatHasBeenAddedToThisSpace": {}, + "chatBackupDescription": "Tá do chúltaca comhrá daingnithe le heochair slándála. Déan cinnte nach gcaillfidh tú é.", + "@chatBackupDescription": { + "type": "text", + "placeholders": {} + }, + "channelCorruptedDecryptError": "Truaillíodh an criptiú", + "@channelCorruptedDecryptError": { + "type": "text", + "placeholders": {} + }, + "changeTheNameOfTheGroup": "Athraigh ainm an ghrúpa", + "@changeTheNameOfTheGroup": { + "type": "text", + "placeholders": {} + }, + "changedTheRoomInvitationLink": "D'athraigh {username} nasc an chuiridh", + "@changedTheRoomInvitationLink": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheRoomAliases": "D'athraigh {username} ailiasanna an tseomra", + "@changedTheRoomAliases": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheProfileAvatar": "D'athraigh {username} a n-abhatár", + "@changedTheProfileAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheJoinRulesTo": "D'athraigh {username} na rialacha ceangail go: {joinRules}", + "@changedTheJoinRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "joinRules": {} + } + }, + "changedTheJoinRules": "D'athraigh {username} na rialacha ceangail", + "@changedTheJoinRules": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheHistoryVisibilityTo": "D'athraigh {username} infheictheacht na staire go: {rules}", + "@changedTheHistoryVisibilityTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "changedTheHistoryVisibility": "D'athraigh {username} infheictheacht na staire", + "@changedTheHistoryVisibility": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheDisplaynameTo": "D'athraigh {username} a n-ainm taispeána go: '{displayname}'", + "@changedTheDisplaynameTo": { + "type": "text", + "placeholders": { + "username": {}, + "displayname": {} + } + }, + "changedTheChatPermissions": "D'athraigh {username} na ceadanna comhrá", + "@changedTheChatPermissions": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheChatNameTo": "D'athraigh {username} an t-ainm comhrá go: '{chatname}'", + "@changedTheChatNameTo": { + "type": "text", + "placeholders": { + "username": {}, + "chatname": {} + } + }, + "changedTheChatDescriptionTo": "D'athraigh {username} an cur síos comhrá go: '{description}'", + "@changedTheChatDescriptionTo": { + "type": "text", + "placeholders": { + "username": {}, + "description": {} + } + }, + "autoplayImages": "Seinn greamáin agus straoiseog beoite go huathoibríoch", + "@autoplayImages": { + "type": "text", + "placeholder": {} + }, + "createdTheChat": "Rinne {username} an comhrá", + "@createdTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "copyToClipboard": "Cóipeáil ar an ghearrthaisce", + "@copyToClipboard": { + "type": "text", + "placeholders": {} + }, + "copiedToClipboard": "Cóipeáilte ar an ghearrthaisce", + "@copiedToClipboard": { + "type": "text", + "placeholders": {} + }, + "containsDisplayName": "Coinníonn sé ainm taispeána", + "@containsDisplayName": { + "type": "text", + "placeholders": {} + }, + "commandHint_plain": "Seol téacs neamhfhoirmithe", + "@commandHint_plain": { + "type": "text", + "description": "Usage hint for the command /plain" + }, + "commandHint_leave": "Fág an seomra seo", + "@commandHint_leave": { + "type": "text", + "description": "Usage hint for the command /leave" + }, + "commandHint_html": "Seol téacs HTML-formáidithe", + "@commandHint_html": { + "type": "text", + "description": "Usage hint for the command /html" + }, + "changeYourAvatar": "Athraigh do abhatár", + "@changeYourAvatar": { + "type": "text", + "placeholders": {} + }, + "changeTheme": "Athraigh do stíl", + "@changeTheme": { + "type": "text", + "placeholders": {} + }, + "changeTheHomeserver": "Athraigh an freastalaí baile", + "@changeTheHomeserver": { + "type": "text", + "placeholders": {} + }, + "voiceMessage": "Glórphost", + "@voiceMessage": { + "type": "text", + "placeholders": {} + }, + "videoCall": "Físghlao", + "@videoCall": { + "type": "text", + "placeholders": {} + }, + "verifyStart": "Tosaigh Fíorú", + "@verifyStart": { + "type": "text", + "placeholders": {} + }, + "unmuteChat": "Neamhciúnaigh comhrá", + "@unmuteChat": { + "type": "text", + "placeholders": {} + }, + "hideUnknownEvents": "Folaigh imeachtaí anaithnide", + "@hideUnknownEvents": { + "type": "text", + "placeholders": {} + }, + "unknownDevice": "Gléas anaithnid", + "@unknownDevice": { + "type": "text", + "placeholders": {} + }, + "toggleUnread": "Marcáil Léite/Neamhléite", + "@toggleUnread": { + "type": "text", + "placeholders": {} + }, + "toggleMuted": "Scoránaigh mar ciúnaithe", + "@toggleMuted": { + "type": "text", + "placeholders": {} + }, + "toggleFavorite": "Scoránaigh mar ceann is fearr leat", + "@toggleFavorite": { + "type": "text", + "placeholders": {} + }, + "theyMatch": "Tá siad céanna", + "@theyMatch": { + "type": "text", + "placeholders": {} + }, + "spaceName": "Ainm an spáis", + "@spaceName": { + "type": "text", + "placeholders": {} + }, + "sourceCode": "Cód foinseach", + "@sourceCode": { + "type": "text", + "placeholders": {} + }, + "showPassword": "Taispeáin pasfhocal", + "@showPassword": { + "type": "text", + "placeholders": {} + }, + "shareLocation": "Roinn suíomh", + "@shareLocation": { + "type": "text", + "placeholders": {} + }, + "setStatus": "Cuir stádas", + "@setStatus": { + "type": "text", + "placeholders": {} + }, + "sendVideo": "Seol físeán", + "@sendVideo": { + "type": "text", + "placeholders": {} + }, + "sendSticker": "Seol greamán", + "@sendSticker": { + "type": "text", + "placeholders": {} + }, + "sendOriginal": "Seol an bunchóip", + "@sendOriginal": { + "type": "text", + "placeholders": {} + }, + "sendMessages": "Seol teachtaireachtaí", + "@sendMessages": { + "type": "text", + "placeholders": {} + }, + "sendImage": "Seol íomhá", + "@sendImage": { + "type": "text", + "placeholders": {} + }, + "sendFile": "Seol comhad", + "@sendFile": { + "type": "text", + "placeholders": {} + }, + "sendAudio": "Seol fuaim", + "@sendAudio": { + "type": "text", + "placeholders": {} + }, + "saveFile": "Sábháil comhad", + "@saveFile": { + "type": "text", + "placeholders": {} + }, + "roomVersion": "Leagan seomra", + "@roomVersion": { + "type": "text", + "placeholders": {} + }, + "requestPermission": "Iarr cead", + "@requestPermission": { + "type": "text", + "placeholders": {} + }, + "reportMessage": "Tuairiscigh teachtaireacht", + "@reportMessage": { + "type": "text", + "placeholders": {} + }, + "removeDevice": "Bain gléas", + "@removeDevice": { + "type": "text", + "placeholders": {} + }, + "redactMessage": "Bain teachtaireacht amach", + "@redactMessage": { + "type": "text", + "placeholders": {} + }, + "pushRules": "Rialacha na bhfógraí", + "@pushRules": { + "type": "text", + "placeholders": {} + }, + "publicRooms": "Seomraí Poiblí", + "@publicRooms": { + "type": "text", + "placeholders": {} + }, + "pleaseChoose": "Roghnaigh le do thoil", + "@pleaseChoose": { + "type": "text", + "placeholders": {} + }, + "play": "Seinn {fileName}", + "@play": { + "type": "text", + "placeholders": { + "fileName": {} + } + }, + "passwordRecovery": "Aisfháil pasfhocail", + "@passwordRecovery": { + "type": "text", + "placeholders": {} + }, + "passwordForgotten": "Pasfhocal dearmadta", + "@passwordForgotten": { + "type": "text", + "placeholders": {} + }, + "openCamera": "Oscail ceamara", + "@openCamera": { + "type": "text", + "placeholders": {} + }, + "obtainingLocation": "ag Aimsiú an suíomh…", + "@obtainingLocation": { + "type": "text", + "placeholders": {} + }, + "noPermission": "Gan cead", + "@noPermission": { + "type": "text", + "placeholders": {} + }, + "newChat": "Comhrá nua", + "@newChat": { + "type": "text", + "placeholders": {} + }, + "muteChat": "Ciúnaigh comhrá", + "@muteChat": { + "type": "text", + "placeholders": {} + }, + "memberChanges": "Athruithe ball", + "@memberChanges": { + "type": "text", + "placeholders": {} + }, + "loadMore": "Lódáil níos mó…", + "@loadMore": { + "type": "text", + "placeholders": {} + }, + "joinRoom": "Téigh isteach sa seomra", + "@joinRoom": { + "type": "text", + "placeholders": {} + }, + "isTyping": "ag clóscríobh…", + "@isTyping": { + "type": "text", + "placeholders": {} + }, + "inviteContact": "Tabhair cuireadh do theagmháil", + "@inviteContact": { + "type": "text", + "placeholders": {} + }, + "ignoredUsers": "Úsáideoirí a dtugann tú neamhaird orthu", + "@ignoredUsers": { + "type": "text", + "placeholders": {} + }, + "fromJoining": "Ó tar isteach", + "@fromJoining": { + "type": "text", + "placeholders": {} + }, + "fontSize": "Méid cló", + "@fontSize": { + "type": "text", + "placeholders": {} + }, + "enableEncryption": "Tosaigh criptiú", + "@enableEncryption": { + "type": "text", + "placeholders": {} + }, + "editDisplayname": "Cuir ainm taispeána in eagar", + "@editDisplayname": { + "type": "text", + "placeholders": {} + }, + "currentlyActive": "Gníomhach faoi láthair", + "@currentlyActive": { + "type": "text", + "placeholders": {} + }, + "fileName": "Ainm an chomhaid", + "@fileName": { + "type": "text", + "placeholders": {} + }, + "everythingReady": "Gach rud réidh!", + "@everythingReady": { + "type": "text", + "placeholders": {} + }, + "emptyChat": "Comhrá folamh", + "@emptyChat": { + "type": "text", + "placeholders": {} + }, + "emoteShortcode": "Gearrchód straoiseoige", + "@emoteShortcode": { + "type": "text", + "placeholders": {} + }, + "emoteSettings": "Socruithe straoiseoige", + "@emoteSettings": { + "type": "text", + "placeholders": {} + }, + "downloadFile": "Íoslódáil comhad", + "@downloadFile": { + "type": "text", + "placeholders": {} + }, + "changePassword": "Athraigh an pasfhocal", + "@changePassword": { + "type": "text", + "placeholders": {} + }, + "darkTheme": "Dorcha", + "@darkTheme": { + "type": "text", + "placeholders": {} + }, + "create": "Cruthaigh", + "@create": { + "type": "text", + "placeholders": {} + }, + "copy": "Cóipeáil", + "@copy": { + "type": "text", + "placeholders": {} + }, + "connect": "Ceangail", + "@connect": { + "type": "text", + "placeholders": {} + }, + "confirm": "Deimhnigh", + "@confirm": { + "type": "text", + "placeholders": {} + }, + "close": "Dún", + "@close": { + "type": "text", + "placeholders": {} + }, + "chats": "Comhráite", + "@chats": { + "type": "text", + "placeholders": {} + }, + "chat": "Comhrá", + "@chat": { + "type": "text", + "placeholders": {} + }, + "scanQrCode": "Scan cód QR", + "@scanQrCode": {}, + "inviteText": "Thug {username} cuireadh duit chuig FluffyChat.\n1. Suiteáil FluffyChat: https://fluffychat.im\n2. Cláraigh nó sínigh isteach\n3. Oscail an nasc cuiridh: {link}", + "@inviteText": { + "type": "text", + "placeholders": { + "username": {}, + "link": {} + } + }, + "noMatrixServer": "Níl {server1} freastalaí Matrix. Úsáid {server2} ina áit sin?", + "@noMatrixServer": { + "type": "text", + "placeholders": { + "server1": {}, + "server2": {} + } + }, + "noGoogleServicesWarning": "Dealraíonn sé nach bhfuil aon seirbhísí google agat ar do ghuthán. Sin cinneadh maith le do phríobháideacht! Chun fógraí brú a fháil i FluffyChat molaimid https://microg.org/ nó https://unifiedpush.org/ a úsáid.", + "@noGoogleServicesWarning": { + "type": "text", + "placeholders": {} + }, + "noEncryptionForPublicRooms": "Ní féidir leat criptiú a ghníomhachtú ach a luaithe nach bhfuil an seomra inrochtana go poiblí a thuilleadh.", + "@noEncryptionForPublicRooms": { + "type": "text", + "placeholders": {} + }, + "noEmotesFound": "Níor aimsíodh aon straoiseoga. 😕", + "@noEmotesFound": { + "type": "text", + "placeholders": {} + }, + "noConnectionToTheServer": "Gan aon nasc leis an bhfreastalaí", + "@noConnectionToTheServer": { + "type": "text", + "placeholders": {} + }, + "newVerificationRequest": "Iarratas fíoraithe nua!", + "@newVerificationRequest": { + "type": "text", + "placeholders": {} + }, + "newMessageInFluffyChat": "Teachtaireacht nua i FluffyChat", + "@newMessageInFluffyChat": { + "type": "text", + "placeholders": {} + }, + "needPantalaimonWarning": "Bí ar an eolas go dteastaíonn Pantalaimon uait chun criptiú ó cheann go ceann a úsáid anois.", + "@needPantalaimonWarning": { + "type": "text", + "placeholders": {} + }, + "logInTo": "Logáil isteach chuig {homeserver}", + "@logInTo": { + "type": "text", + "placeholders": { + "homeserver": {} + } + }, + "locationPermissionDeniedNotice": "Diúltaíodh cead suímh. Deonaigh dóibh le do thoil go mbeidh tú in ann do shuíomh a roinnt.", + "@locationPermissionDeniedNotice": { + "type": "text", + "placeholders": {} + }, + "locationDisabledNotice": "Tá seirbhísí suímh díchumasaithe. Cuir ar a gcumas le do thoil a bheith in ann do shuíomh a roinnt.", + "@locationDisabledNotice": { + "type": "text", + "placeholders": {} + }, + "loadingPleaseWait": "Ag lódáil… Fan, le do thoil.", + "@loadingPleaseWait": { + "type": "text", + "placeholders": {} + }, + "loadCountMoreParticipants": "Lódáil {count} níos mó rannpháirtithe", + "@loadCountMoreParticipants": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "leftTheChat": "Fágadh an comhrá", + "@leftTheChat": { + "type": "text", + "placeholders": {} + }, + "lastActiveAgo": "Gníomhach deireanach: {localizedTimeShort}", + "@lastActiveAgo": { + "type": "text", + "placeholders": { + "localizedTimeShort": {} + } + }, + "kickFromChat": "Caith é amach as an comhrá", + "@kickFromChat": { + "type": "text", + "placeholders": {} + }, + "kicked": "Chaith {username} {targetName} amach", + "@kicked": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "kickedAndBanned": "Chaith {username} amach agus chuir cosc ar {targetName} freisin", + "@kickedAndBanned": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "joinedTheChat": "Tháinig {username} isteach sa chomhrá", + "@joinedTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "invitedUsersOnly": "Úsáideoirí le cuireadh amháin", + "@invitedUsersOnly": { + "type": "text", + "placeholders": {} + }, + "inviteContactToGroup": "Tabhair cuireadh do theagmháil chuig {groupName}", + "@inviteContactToGroup": { + "type": "text", + "placeholders": { + "groupName": {} + } + }, + "incorrectPassphraseOrKey": "Pasfhrása nó eochair téarnaimh mícheart", + "@incorrectPassphraseOrKey": { + "type": "text", + "placeholders": {} + }, + "iHaveClickedOnLink": "Chliceáil mé ar an nasc", + "@iHaveClickedOnLink": { + "type": "text", + "placeholders": {} + }, + "howOffensiveIsThisContent": "Cé chomh maslach atá an t-ábhar seo?", + "@howOffensiveIsThisContent": { + "type": "text", + "placeholders": {} + }, + "extremeOffensive": "Fíor-maslach", + "@extremeOffensive": { + "type": "text", + "placeholders": {} + }, + "hasWithdrawnTheInvitationFor": "Tharraing {username} an cuireadh do {targetName} siar", + "@hasWithdrawnTheInvitationFor": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "goToTheNewRoom": "Téigh go dtí an seomra nua", + "@goToTheNewRoom": { + "type": "text", + "placeholders": {} + }, + "errorObtainingLocation": "Earráid maidir le suíomh a fháil: {error}", + "@errorObtainingLocation": { + "type": "text", + "placeholders": { + "error": {} + } + }, + "enterAnEmailAddress": "Cuir isteach seoladh ríomhphoist", + "@enterAnEmailAddress": { + "type": "text", + "placeholders": {} + }, + "endedTheCall": "Chuir {senderName} deireadh leis an nglao", + "@endedTheCall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "encryptionNotEnabled": "Ní chumasaítear criptiú", + "@encryptionNotEnabled": { + "type": "text", + "placeholders": {} + }, + "enableEncryptionWarning": "Ní bheidh in ann an criptiú a dhíchumasú níos mó. An bhfuil tú cinnte?", + "@enableEncryptionWarning": { + "type": "text", + "placeholders": {} + }, + "enableEmotesGlobally": "Cumasaigh pacáiste straoiseoige go huilíoch", + "@enableEmotesGlobally": { + "type": "text", + "placeholders": {} + }, + "emoteWarnNeedToPick": "Caithfidh tú gearrchód straoiseoige agus íomhá a roghnú!", + "@emoteWarnNeedToPick": { + "type": "text", + "placeholders": {} + }, + "emotePacks": "Pacáistí straoiseoige don seomra", + "@emotePacks": { + "type": "text", + "placeholders": {} + }, + "displaynameHasBeenChanged": "Athraíodh an t-ainm taispeána", + "@displaynameHasBeenChanged": { + "type": "text", + "placeholders": {} + }, + "deactivateAccountWarning": "Díghníomhachtaeoidh sé seo do chuntas úsáideora. Ní féidir é seo a chealú! An bhfuil tú cinnte?", + "@deactivateAccountWarning": { + "type": "text", + "placeholders": {} + }, + "couldNotDecryptMessage": "Níorbh fhéidir teachtaireacht a dhíchriptiú: {error}", + "@couldNotDecryptMessage": { + "type": "text", + "placeholders": { + "error": {} + } + }, + "contentHasBeenReported": "Tuairiscíodh an t-ábhar do lucht riaracháin an fhreastalaí", + "@contentHasBeenReported": { + "type": "text", + "placeholders": {} + }, + "commandHint_unban": "Cuir deireadh an cosc den úsáideoir áirithe ón seomra seo", + "@commandHint_unban": { + "type": "text", + "description": "Usage hint for the command /unban" + }, + "changedTheGuestAccessRulesTo": "D'athraigh {username} na rialacha maidir le rochtain aoi chuig: {rules}", + "@changedTheGuestAccessRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "changedTheGuestAccessRules": "D'athraigh {username} na rialacha rochtana aoi", + "@changedTheGuestAccessRules": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "badServerVersionsException": "Tá na leaganacha sonraíochta seo ar fáil faoin freastalaí baile:\n{serverVersions}\nAch níl ach na ceann seo ar fáil faoin aip seo {supportedVersions}", + "@badServerVersionsException": { + "type": "text", + "placeholders": { + "serverVersions": {}, + "supportedVersions": {} + } + }, + "badServerLoginTypesException": "Tá na cineálacha logála isteach seo ar fáil faoin freastalaí baile:\n{serverVersions}\nAch níl ach na ceann seo ar fáil faoin aip seo:\n{supportedVersions}", + "@badServerLoginTypesException": { + "type": "text", + "placeholders": { + "serverVersions": {}, + "supportedVersions": {} + } + }, + "askSSSSSign": "Chun a bheith in ann an duine eile a shíniú, cuir isteach do phasfhrása stóir sábháilte nó d'eochair téarnaimh.", + "@askSSSSSign": { + "type": "text", + "placeholders": {} + }, + "yourPublicKey": "D'eochair phoiblí", + "@yourPublicKey": { + "type": "text", + "placeholders": {} + }, + "youHaveBeenBannedFromThisChat": "Cuireadh cosc ort ón gcomhrá seo", + "@youHaveBeenBannedFromThisChat": { + "type": "text", + "placeholders": {} + }, + "youAreNoLongerParticipatingInThisChat": "Níl tú ag glacadh páirte sa chomhrá seo a thuilleadh", + "@youAreNoLongerParticipatingInThisChat": { + "type": "text", + "placeholders": {} + }, + "writeAMessage": "Scríobh teachtaireacht…", + "@writeAMessage": { + "type": "text", + "placeholders": {} + }, + "withTheseAddressesRecoveryDescription": "Leis na seoltaí seo is féidir leat do phasfhocal a athshlánú.", + "@withTheseAddressesRecoveryDescription": { + "type": "text", + "placeholders": {} + }, + "wipeChatBackup": "Glan do cúltaca comhrá a chruthú eochair slándála nua?", + "@wipeChatBackup": { + "type": "text", + "placeholders": {} + }, + "whyDoYouWantToReportThis": "Cén fáth ar mhaith leat é seo a thuairisciú?", + "@whyDoYouWantToReportThis": { + "type": "text", + "placeholders": {} + }, + "whoIsAllowedToJoinThisGroup": "Cé a bhfuil cead aige/aici dul isteach sa ghrúpa seo", + "@whoIsAllowedToJoinThisGroup": { + "type": "text", + "placeholders": {} + }, + "whoCanPerformWhichAction": "Cé atá in ann an gníomh a dhéanamh", + "@whoCanPerformWhichAction": { + "type": "text", + "placeholders": {} + }, + "verifySuccess": "D'fhíoraigh tú go rathúil!", + "@verifySuccess": { + "type": "text", + "placeholders": {} + }, + "userLeftTheChat": "D'fhág {username} an comhrá", + "@userLeftTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "userAndUserAreTyping": "Tá {username} agus {username2} ag clóscríobh…", + "@userAndUserAreTyping": { + "type": "text", + "placeholders": { + "username": {}, + "username2": {} + } + }, + "userAndOthersAreTyping": "tá {username} agus {count} daoine eile ag clóscríobh…", + "@userAndOthersAreTyping": { + "type": "text", + "placeholders": { + "username": {}, + "count": {} + } + }, + "unreadChats": "{unreadCount, plural, =1{1 comhrá neamhléite} other{{unreadCount} comhráite neamhléite}}", + "@unreadChats": { + "type": "text", + "placeholders": { + "unreadCount": {} + } + }, + "unknownEncryptionAlgorithm": "Algartam criptithe anaithnid", + "@unknownEncryptionAlgorithm": { + "type": "text", + "placeholders": {} + }, + "unbannedUser": "Chuir {username} deireadh an cosc {targetName}", + "@unbannedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "tryToSendAgain": "Déan iarracht a sheoladh arís", + "@tryToSendAgain": { + "type": "text", + "placeholders": {} + }, + "transferFromAnotherDevice": "Aistriú ó ghléas eile", + "@transferFromAnotherDevice": { + "type": "text", + "placeholders": {} + }, + "tooManyRequestsWarning": "An iomarca iarratas. Bain triail eile as níos déanaí!", + "@tooManyRequestsWarning": { + "type": "text", + "placeholders": {} + }, + "theyDontMatch": "Níl siad céanna", + "@theyDontMatch": { + "type": "text", + "placeholders": {} + }, + "sharedTheLocation": "Roinn {username} an suíomh", + "@sharedTheLocation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "setPermissionsLevel": "Socraigh leibhéal ceadanna", + "@setPermissionsLevel": { + "type": "text", + "placeholders": {} + }, + "setInvitationLink": "Socraigh nasc cuiridh", + "@setInvitationLink": { + "type": "text", + "placeholders": {} + }, + "setCustomEmotes": "Socraigh straoiseoga saincheaptha", + "@setCustomEmotes": { + "type": "text", + "placeholders": {} + }, + "setAsCanonicalAlias": "Socraigh mar phríomh-ailias", + "@setAsCanonicalAlias": { + "type": "text", + "placeholders": {} + }, + "sentCallInformations": "Sheol {senderName} faisnéis maidir le glaonna", + "@sentCallInformations": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "sentAVideo": "Sheol {username} físeán", + "@sentAVideo": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentASticker": "Sheol {username} greamán", + "@sentASticker": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAPicture": "Sheol {username} pictiúr", + "@sentAPicture": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "replaceRoomWithNewerVersion": "Cuir leagan seomra níos nuaí in ionad an tseomra", + "@replaceRoomWithNewerVersion": { + "type": "text", + "placeholders": {} + }, + "renderRichContent": "Taispeáin ábhar teachtaireachta saibhir", + "@renderRichContent": { + "type": "text", + "placeholders": {} + }, + "removeYourAvatar": "Bain d'abhatár", + "@removeYourAvatar": { + "type": "text", + "placeholders": {} + }, + "unbanFromChat": "Cuir deireadh an cosc ón gcomhrá", + "@unbanFromChat": { + "type": "text", + "placeholders": {} + }, + "removedBy": "Bainte de ag {username}", + "@removedBy": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "removeAllOtherDevices": "Bain gach gléas eile", + "@removeAllOtherDevices": { + "type": "text", + "placeholders": {} + }, + "rejectedTheInvitation": "Dhiúltaigh {username} don chuireadh", + "@rejectedTheInvitation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "redactedAnEvent": "Rinne {username} cinsire imeacht", + "@redactedAnEvent": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "pleaseFollowInstructionsOnWeb": "Lean na treoracha ar an suíomh gréasáin agus tapáil \"ar aghaidh\".", + "@pleaseFollowInstructionsOnWeb": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourUsername": "Cuir isteach d'ainm úsáideora le do thoil", + "@pleaseEnterYourUsername": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourPin": "Cuir isteach d'uimhir PIN le do thoil", + "@pleaseEnterYourPin": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourPassword": "Iontráil do phasfhocal le do thoil", + "@pleaseEnterYourPassword": { + "type": "text", + "placeholders": {} + }, + "pleaseEnter4Digits": "Iontráil 4 dhigit le do thoil nó fág folamh chun glas aipe a dhíchumasú.", + "@pleaseEnter4Digits": { + "type": "text", + "placeholders": {} + }, + "pleaseClickOnLink": "Cliceáil ar an nasc sa ríomhphost agus ansin lean ar aghaidh.", + "@pleaseClickOnLink": { + "type": "text", + "placeholders": {} + }, + "pleaseChooseAPasscode": "Roghnaigh paschód le do thoil", + "@pleaseChooseAPasscode": { + "type": "text", + "placeholders": {} + }, + "pickImage": "Roghnaigh íomhá", + "@pickImage": { + "type": "text", + "placeholders": {} + }, + "passwordHasBeenChanged": "Athraíodh an pasfhocal", + "@passwordHasBeenChanged": { + "type": "text", + "placeholders": {} + }, + "passphraseOrKey": "pasfhrása nó eochair téarnaimh", + "@passphraseOrKey": { + "type": "text", + "placeholders": {} + }, + "serverRequiresEmail": "Ní mór don fhreastalaí seo do sheoladh ríomhphoist a bhailíochtú le haghaidh clárúcháin.", + "@serverRequiresEmail": {}, + "openInMaps": "Oscail i léarscáileanna", + "@openInMaps": { + "type": "text", + "placeholders": {} + }, + "openAppToReadMessages": "Oscail an aip chun teachtaireachtaí a léamh", + "@openAppToReadMessages": { + "type": "text", + "placeholders": {} + }, + "oopsSomethingWentWrong": "Úps, chuaigh rud éigin mícheart …", + "@oopsSomethingWentWrong": { + "type": "text", + "placeholders": {} + }, + "oopsPushError": "Hoips! Ar an drochuair, tharla earráid nuair a bhí na fógraí brú á mbunú.", + "@oopsPushError": { + "type": "text", + "placeholders": {} + }, + "onlineKeyBackupEnabled": "Tá Cúltaca Eochair Ar Líne cumasaithe", + "@onlineKeyBackupEnabled": { + "type": "text", + "placeholders": {} + }, + "numUsersTyping": "Tá {count} úsáideoirí ag clóscríobh…", + "@numUsersTyping": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "notificationsEnabledForThisAccount": "Fógraí cumasaithe don chuntas seo", + "@notificationsEnabledForThisAccount": { + "type": "text", + "placeholders": {} + }, + "noRoomsFound": "Níor aimsíodh aon seomraí…", + "@noRoomsFound": { + "type": "text", + "placeholders": {} + }, + "noPasswordRecoveryDescription": "Níor chuir tú bealach leis do phasfhocal a aisghabháil fós.", + "@noPasswordRecoveryDescription": { + "type": "text", + "placeholders": {} + }, + "inviteForMe": "Tabhair cuireadh dom", + "@inviteForMe": { + "type": "text", + "placeholders": {} + }, + "weSentYouAnEmail": "Sheolamar ríomhphost chugat", + "@weSentYouAnEmail": { + "type": "text", + "placeholders": {} + }, + "waitingPartnerNumbers": "Ag fanacht le comhpháirtí glacadh leis na huimhreacha …", + "@waitingPartnerNumbers": { + "type": "text", + "placeholders": {} + }, + "waitingPartnerEmoji": "Ag fanacht le comhpháirtí glacadh leis na straoiseoga…", + "@waitingPartnerEmoji": { + "type": "text", + "placeholders": {} + }, + "waitingPartnerAcceptRequest": "Ag fanacht le comhpháirtí glacadh leis an iarratas…", + "@waitingPartnerAcceptRequest": { + "type": "text", + "placeholders": {} + }, + "visibleForEveryone": "Infheicthe do gach duine", + "@visibleForEveryone": { + "type": "text", + "placeholders": {} + }, + "visibleForAllParticipants": "Infheicthe do na rannpháirtithe go léir", + "@visibleForAllParticipants": { + "type": "text", + "placeholders": {} + }, + "visibilityOfTheChatHistory": "Infheictheacht stair na comhrá", + "@visibilityOfTheChatHistory": { + "type": "text", + "placeholders": {} + }, + "verifyTitle": "Ag fíorú cuntas eile", + "@verifyTitle": { + "type": "text", + "placeholders": {} + }, + "userSentUnknownEvent": "Sheol {username} imeacht {type}", + "@userSentUnknownEvent": { + "type": "text", + "placeholders": { + "username": {}, + "type": {} + } + }, + "userIsTyping": "Tá {username} ag clóscríobh…", + "@userIsTyping": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "unknownEvent": "Imeacht anaithnid '{type}'", + "@unknownEvent": { + "type": "text", + "placeholders": { + "type": {} + } + }, + "synchronizingPleaseWait": "Ag sioncrónú... Fan, le do thoil.", + "@synchronizingPleaseWait": { + "type": "text", + "placeholders": {} + }, + "startedACall": "Thosaigh {senderName} glao", + "@startedACall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "spaceIsPublic": "Tá an spás poiblí", + "@spaceIsPublic": { + "type": "text", + "placeholders": {} + }, + "singlesignon": "Sínigh Aonair ar", + "@singlesignon": { + "type": "text", + "placeholders": {} + }, + "sentAnAudio": "Sheol {username} fuaim", + "@sentAnAudio": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAFile": "Sheol {username} comhad", + "@sentAFile": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sendAsText": "Seol mar théacs", + "@sendAsText": { + "type": "text" + }, + "sendAMessage": "Seol teachtaireacht", + "@sendAMessage": { + "type": "text", + "placeholders": {} + }, + "seenByUser": "Le feiceáil ag {username}", + "@seenByUser": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "roomHasBeenUpgraded": "Uasghrádaíodh an seomra", + "@roomHasBeenUpgraded": { + "type": "text", + "placeholders": {} + }, + "addAccount": "Breisigh cuntas", + "@addAccount": {}, + "enableMultiAccounts": "(BÉITE) Cumasaigh cuntais iomadúla ar an gléas seo", + "@enableMultiAccounts": {}, + "commandHint_create": "Cruthaigh comhrá grúpa folamh\nÚsáid --no-encryption chun criptiúchán a dhíchumasú", + "@commandHint_create": { + "type": "text", + "description": "Usage hint for the command /create" + }, + "link": "Nasc", + "@link": {}, + "commandHint_clearcache": "Glan an taisce", + "@commandHint_clearcache": { + "type": "text", + "description": "Usage hint for the command /clearcache" + }, + "videoCallsBetaWarning": "Tabhair faoi deara go bhfuil físglaonna i béite. B'fhéidir nach bhfeidhmíonn siad ar gach ardán chomh atá súil aige ná ar bith.", + "@videoCallsBetaWarning": {}, + "emailOrUsername": "Ríomhphost nó ainm úsáideora", + "@emailOrUsername": {}, + "repeatPassword": "Scríobh an pasfhocal arís", + "@repeatPassword": {}, + "yourChatBackupHasBeenSetUp": "Bunaíodh do chúltaca comhrá.", + "@yourChatBackupHasBeenSetUp": {}, + "openVideoCamera": "Oscail físcheamara", + "@openVideoCamera": { + "type": "text", + "placeholders": {} + } +} From 7bb7ad22d60977c4b68659443ca968a3b1b48e4f Mon Sep 17 00:00:00 2001 From: Anonymous Date: Thu, 25 Jul 2024 05:09:41 +0000 Subject: [PATCH 119/288] Translated using Weblate (Finnish) Currently translated at 80.5% (525 of 652 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/fi/ --- assets/l10n/intl_fi.arb | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/assets/l10n/intl_fi.arb b/assets/l10n/intl_fi.arb index 65465acfd6..b9123ff0f8 100644 --- a/assets/l10n/intl_fi.arb +++ b/assets/l10n/intl_fi.arb @@ -2402,12 +2402,5 @@ "importNow": "Tuo nyt", "@importNow": {}, "invite": "Kutsu", - "@invite": {}, - "@banUserDescription": {}, - "@removeDevicesDescription": {}, - "@unbanUserDescription": {}, - "@pushNotificationsNotAvailable": {}, - "@makeAdminDescription": {}, - "@learnMore": {}, - "@kickUserDescription": {} + "@invite": {} } From 77eaee890caec61a0b708dd251437fb6c10b1872 Mon Sep 17 00:00:00 2001 From: Anonymous Date: Thu, 25 Jul 2024 05:09:48 +0000 Subject: [PATCH 120/288] Translated using Weblate (Portuguese (Portugal)) Currently translated at 42.1% (275 of 652 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/pt_PT/ --- assets/l10n/intl_pt_PT.arb | 3749 +++++++++++++++--------------------- 1 file changed, 1568 insertions(+), 2181 deletions(-) diff --git a/assets/l10n/intl_pt_PT.arb b/assets/l10n/intl_pt_PT.arb index 9db37c78ee..1cdce9ee15 100644 --- a/assets/l10n/intl_pt_PT.arb +++ b/assets/l10n/intl_pt_PT.arb @@ -1,2182 +1,1569 @@ { - "repeatPassword": "Repete a palavra-passe", - "@repeatPassword": {}, - "about": "Acerca de", - "@about": { - "type": "text", - "placeholders": {} - }, - "accept": "Aceitar", - "@accept": { - "type": "text", - "placeholders": {} - }, - "acceptedTheInvitation": "{username} aceitou o convite", - "@acceptedTheInvitation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "account": "Conta", - "@account": { - "type": "text", - "placeholders": {} - }, - "activatedEndToEndEncryption": "{username} ativou encriptação ponta-a-ponta", - "@activatedEndToEndEncryption": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "addEmail": "Adicionar correio eletrónico", - "@addEmail": { - "type": "text", - "placeholders": {} - }, - "addToSpace": "Adicionar ao espaço", - "@addToSpace": {}, - "admin": "Admin", - "@admin": { - "type": "text", - "placeholders": {} - }, - "alias": "alcunha", - "@alias": { - "type": "text", - "placeholders": {} - }, - "all": "Todos(as)", - "@all": { - "type": "text", - "placeholders": {} - }, - "allChats": "Todas as conversas", - "@allChats": { - "type": "text", - "placeholders": {} - }, - "answeredTheCall": "{senderName} atendeu a chamada", - "@answeredTheCall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "anyoneCanJoin": "Qualquer pessoa pode entrar", - "@anyoneCanJoin": { - "type": "text", - "placeholders": {} - }, - "archive": "Arquivo", - "@archive": { - "type": "text", - "placeholders": {} - }, - "areGuestsAllowedToJoin": "Todos os visitantes podem entrar", - "@areGuestsAllowedToJoin": { - "type": "text", - "placeholders": {} - }, - "areYouSure": "Tens a certeza?", - "@areYouSure": { - "type": "text", - "placeholders": {} - }, - "areYouSureYouWantToLogout": "Tens a certeza que queres sair?", - "@areYouSureYouWantToLogout": { - "type": "text", - "placeholders": {} - }, - "askSSSSSign": "Para poderes assinar a outra pessoa, por favor, insere a tua senha de armazenamento seguro ou a chave de recuperação.", - "@askSSSSSign": { - "type": "text", - "placeholders": {} - }, - "askVerificationRequest": "Aceitar este pedido de verificação de {username}?", - "@askVerificationRequest": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "autoplayImages": "Automaticamente reproduzir autocolantes e emotes animados", - "@autoplayImages": { - "type": "text", - "placeholder": {} - }, - "sendOnEnter": "Enviar com Enter", - "@sendOnEnter": {}, - "badServerVersionsException": "O servidor suporta as versões Spec:\n{serverVersions}\nMas esta aplicação apenas suporta {suportedVersions}", - "@badServerVersionsException": { - "type": "text", - "placeholders": { - "serverVersions": {}, - "supportedVersions": {} - } - }, - "badServerLoginTypesException": "O servidor suporta os tipos de início de sessão:\n{serverVersions}\nMas esta aplicação apenas suporta:\n{suportedVersions}", - "@badServerLoginTypesException": { - "type": "text", - "placeholders": { - "serverVersions": {}, - "supportedVersions": {} - } - }, - "banFromChat": "Banir da conversa", - "@banFromChat": { - "type": "text", - "placeholders": {} - }, - "banned": "Banido(a)", - "@banned": { - "type": "text", - "placeholders": {} - }, - "bannedUser": "{username} baniu {targetName}", - "@bannedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "blockDevice": "Bloquear dispositivo", - "@blockDevice": { - "type": "text", - "placeholders": {} - }, - "blocked": "Bloqueado", - "@blocked": { - "type": "text", - "placeholders": {} - }, - "botMessages": "Mensagens de robôs", - "@botMessages": { - "type": "text", - "placeholders": {} - }, - "cancel": "Cancelar", - "@cancel": { - "type": "text", - "placeholders": {} - }, - "cantOpenUri": "Não é possível abrir o URI {uri}", - "@cantOpenUri": { - "type": "text", - "placeholders": { - "uri": {} - } - }, - "changeDeviceName": "Alterar nome do dispositivo", - "@changeDeviceName": { - "type": "text", - "placeholders": {} - }, - "changedTheChatPermissions": "{username} alterou as permissões da conversa", - "@changedTheChatPermissions": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheDisplaynameTo": "{username} alterou o seu nome para: '{displayname}'", - "@changedTheDisplaynameTo": { - "type": "text", - "placeholders": { - "username": {}, - "displayname": {} - } - }, - "changedTheGuestAccessRulesTo": "{username} alterou as regras de acesso de visitantes para: {rules}", - "@changedTheGuestAccessRulesTo": { - "type": "text", - "placeholders": { - "username": {}, - "rules": {} - } - }, - "changedTheJoinRules": "{username} alterou as regras de entrada", - "@changedTheJoinRules": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheJoinRulesTo": "{username} alterou as regras de entrada para: {joinRules}", - "@changedTheJoinRulesTo": { - "type": "text", - "placeholders": { - "username": {}, - "joinRules": {} - } - }, - "changedTheProfileAvatar": "{username} alterou o seu avatar", - "@changedTheProfileAvatar": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheRoomAliases": "{username} alterou as alcunhas da sala", - "@changedTheRoomAliases": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheRoomInvitationLink": "{username} alterou a ligação de convite", - "@changedTheRoomInvitationLink": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changePassword": "Alterar palavra-passe", - "@changePassword": { - "type": "text", - "placeholders": {} - }, - "changeTheHomeserver": "Alterar o servidor", - "@changeTheHomeserver": { - "type": "text", - "placeholders": {} - }, - "changeTheme": "Alterar o teu estilo", - "@changeTheme": { - "type": "text", - "placeholders": {} - }, - "changeTheNameOfTheGroup": "Alterar o nome do grupo", - "@changeTheNameOfTheGroup": { - "type": "text", - "placeholders": {} - }, - "changeYourAvatar": "Alterar o teu avatar", - "@changeYourAvatar": { - "type": "text", - "placeholders": {} - }, - "channelCorruptedDecryptError": "A encriptação foi corrompida", - "@channelCorruptedDecryptError": { - "type": "text", - "placeholders": {} - }, - "chat": "Conversa", - "@chat": { - "type": "text", - "placeholders": {} - }, - "yourChatBackupHasBeenSetUp": "A cópia de segurança foi configurada.", - "@yourChatBackupHasBeenSetUp": {}, - "chatBackup": "Cópia de segurança de conversas", - "@chatBackup": { - "type": "text", - "placeholders": {} - }, - "chatBackupDescription": "A tuas mensagens antigas estão protegidas com uma chave de recuperação. Por favor, certifica-te que não a perdes.", - "@chatBackupDescription": { - "type": "text", - "placeholders": {} - }, - "chatDetails": "Detalhes de conversa", - "@chatDetails": { - "type": "text", - "placeholders": {} - }, - "chatHasBeenAddedToThisSpace": "A conversa foi adicionada a este espaço", - "@chatHasBeenAddedToThisSpace": {}, - "chats": "Conversas", - "@chats": { - "type": "text", - "placeholders": {} - }, - "chooseAStrongPassword": "Escolhe uma palavra-passe forte", - "@chooseAStrongPassword": { - "type": "text", - "placeholders": {} - }, - "clearArchive": "Limpar arquivo", - "@clearArchive": {}, - "close": "Fechar", - "@close": { - "type": "text", - "placeholders": {} - }, - "commandHint_ban": "Banir o utilizador dado desta sala", - "@commandHint_ban": { - "type": "text", - "description": "Usage hint for the command /ban" - }, - "commandHint_html": "Enviar texto formatado com HTML", - "@commandHint_html": { - "type": "text", - "description": "Usage hint for the command /html" - }, - "commandHint_invite": "Convidar o utilizador dado a esta sala", - "@commandHint_invite": { - "type": "text", - "description": "Usage hint for the command /invite" - }, - "commandHint_join": "Entrar na sala dada", - "@commandHint_join": { - "type": "text", - "description": "Usage hint for the command /join" - }, - "commandHint_kick": "Remover o utilizador dado desta sala", - "@commandHint_kick": { - "type": "text", - "description": "Usage hint for the command /kick" - }, - "commandHint_leave": "Sair desta sala", - "@commandHint_leave": { - "type": "text", - "description": "Usage hint for the command /leave" - }, - "commandHint_me": "Descreve-te", - "@commandHint_me": { - "type": "text", - "description": "Usage hint for the command /me" - }, - "commandHint_myroomavatar": "Definir a tua imagem para esta sala (por mxc-uri)", - "@commandHint_myroomavatar": { - "type": "text", - "description": "Usage hint for the command /myroomavatar" - }, - "commandHint_myroomnick": "Definir o teu nome para esta sala", - "@commandHint_myroomnick": { - "type": "text", - "description": "Usage hint for the command /myroomnick" - }, - "commandHint_op": "Definir o nível de poder do utilizador dado (por omissão: 50)", - "@commandHint_op": { - "type": "text", - "description": "Usage hint for the command /op" - }, - "commandHint_plain": "Enviar texto não formatado", - "@commandHint_plain": { - "type": "text", - "description": "Usage hint for the command /plain" - }, - "commandHint_react": "Enviar respostas como reações", - "@commandHint_react": { - "type": "text", - "description": "Usage hint for the command /react" - }, - "commandHint_send": "Enviar texto", - "@commandHint_send": { - "type": "text", - "description": "Usage hint for the command /send" - }, - "commandHint_unban": "Perdoar o utilizador dado", - "@commandHint_unban": { - "type": "text", - "description": "Usage hint for the command /unban" - }, - "commandInvalid": "Comando inválido", - "@commandInvalid": { - "type": "text" - }, - "commandMissing": "{command} não é um comando.", - "@commandMissing": { - "type": "text", - "placeholders": { - "command": {} - }, - "description": "State that {command} is not a valid /command." - }, - "compareEmojiMatch": "Compara e certifica-te que os emojis que se seguem correspondem aos do outro dispositivo:", - "@compareEmojiMatch": { - "type": "text", - "placeholders": {} - }, - "compareNumbersMatch": "Compara e certifica-te que os números que se seguem correspondem aos do outro dispositivo:", - "@compareNumbersMatch": { - "type": "text", - "placeholders": {} - }, - "configureChat": "Configurar conversa", - "@configureChat": { - "type": "text", - "placeholders": {} - }, - "confirm": "Confirmar", - "@confirm": { - "type": "text", - "placeholders": {} - }, - "connect": "Ligar", - "@connect": { - "type": "text", - "placeholders": {} - }, - "contactHasBeenInvitedToTheGroup": "O contacto foi convidado para o grupo", - "@contactHasBeenInvitedToTheGroup": { - "type": "text", - "placeholders": {} - }, - "containsDisplayName": "Contém nome de exibição", - "@containsDisplayName": { - "type": "text", - "placeholders": {} - }, - "containsUserName": "Contém nome de utilizador", - "@containsUserName": { - "type": "text", - "placeholders": {} - }, - "contentHasBeenReported": "O conteúdo foi denunciado aos admins do servidor", - "@contentHasBeenReported": { - "type": "text", - "placeholders": {} - }, - "copiedToClipboard": "Copiado para a área de transferência", - "@copiedToClipboard": { - "type": "text", - "placeholders": {} - }, - "copy": "Copiar", - "@copy": { - "type": "text", - "placeholders": {} - }, - "copyToClipboard": "Copiar para a área de transferência", - "@copyToClipboard": { - "type": "text", - "placeholders": {} - }, - "couldNotDecryptMessage": "Não foi possível desencriptar mensagem: {error}", - "@couldNotDecryptMessage": { - "type": "text", - "placeholders": { - "error": {} - } - }, - "countParticipants": "{count} participantes", - "@countParticipants": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "create": "Criar", - "@create": { - "type": "text", - "placeholders": {} - }, - "createdTheChat": "{username} criou a conversa", - "@createdTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "createNewSpace": "Novo espaço", - "@createNewSpace": { - "type": "text", - "placeholders": {} - }, - "currentlyActive": "Ativo(a) agora", - "@currentlyActive": { - "type": "text", - "placeholders": {} - }, - "darkTheme": "Escuro", - "@darkTheme": { - "type": "text", - "placeholders": {} - }, - "dateAndTimeOfDay": "{date} às {timeOfDay}", - "@dateAndTimeOfDay": { - "type": "text", - "placeholders": { - "date": {}, - "timeOfDay": {} - } - }, - "dateWithoutYear": "{day}-{month}", - "@dateWithoutYear": { - "type": "text", - "placeholders": { - "month": {}, - "day": {} - } - }, - "dateWithYear": "{day}-{month}-{year}", - "@dateWithYear": { - "type": "text", - "placeholders": { - "year": {}, - "month": {}, - "day": {} - } - }, - "deactivateAccountWarning": "Isto irá desativar a tua conta. Não é reversível! Tens a certeza?", - "@deactivateAccountWarning": { - "type": "text", - "placeholders": {} - }, - "defaultPermissionLevel": "Nível de permissão normal", - "@defaultPermissionLevel": { - "type": "text", - "placeholders": {} - }, - "delete": "Eliminar", - "@delete": { - "type": "text", - "placeholders": {} - }, - "deleteAccount": "Eliminar conta", - "@deleteAccount": { - "type": "text", - "placeholders": {} - }, - "deleteMessage": "Eliminar mensagem", - "@deleteMessage": { - "type": "text", - "placeholders": {} - }, - "device": "Dispositivo", - "@device": { - "type": "text", - "placeholders": {} - }, - "deviceId": "ID de dispositivo", - "@deviceId": { - "type": "text", - "placeholders": {} - }, - "devices": "Dispositivos", - "@devices": { - "type": "text", - "placeholders": {} - }, - "directChats": "Conversas diretas", - "@directChats": { - "type": "text", - "placeholders": {} - }, - "displaynameHasBeenChanged": "Nome de exibição alterado", - "@displaynameHasBeenChanged": { - "type": "text", - "placeholders": {} - }, - "downloadFile": "Descarregar ficheiro", - "@downloadFile": { - "type": "text", - "placeholders": {} - }, - "edit": "Editar", - "@edit": { - "type": "text", - "placeholders": {} - }, - "editBlockedServers": "Editar servidores bloqueados", - "@editBlockedServers": { - "type": "text", - "placeholders": {} - }, - "editDisplayname": "Editar nome de exibição", - "@editDisplayname": { - "type": "text", - "placeholders": {} - }, - "editRoomAliases": "Editar alcunhas da sala", - "@editRoomAliases": { - "type": "text", - "placeholders": {} - }, - "editRoomAvatar": "Editar avatar da sala", - "@editRoomAvatar": { - "type": "text", - "placeholders": {} - }, - "emoteExists": "Emote já existente!", - "@emoteExists": { - "type": "text", - "placeholders": {} - }, - "emoteInvalid": "Código de emote inválido!", - "@emoteInvalid": { - "type": "text", - "placeholders": {} - }, - "emotePacks": "Pacotes de emotes da sala", - "@emotePacks": { - "type": "text", - "placeholders": {} - }, - "emoteSettings": "Configurações de emotes", - "@emoteSettings": { - "type": "text", - "placeholders": {} - }, - "emoteShortcode": "Código do emote", - "@emoteShortcode": { - "type": "text", - "placeholders": {} - }, - "emoteWarnNeedToPick": "Precisas de escolher um código de emote e uma imagem!", - "@emoteWarnNeedToPick": { - "type": "text", - "placeholders": {} - }, - "emptyChat": "Conversa vazia", - "@emptyChat": { - "type": "text", - "placeholders": {} - }, - "enableEmotesGlobally": "Ativar pacote de emotes globalmente", - "@enableEmotesGlobally": { - "type": "text", - "placeholders": {} - }, - "enableEncryption": "Ativar encriptação", - "@enableEncryption": { - "type": "text", - "placeholders": {} - }, - "enableEncryptionWarning": "Nunca mais poderás desativar a encriptação. Tens a certeza?", - "@enableEncryptionWarning": { - "type": "text", - "placeholders": {} - }, - "encrypted": "Encriptada", - "@encrypted": { - "type": "text", - "placeholders": {} - }, - "encryption": "Encriptação", - "@encryption": { - "type": "text", - "placeholders": {} - }, - "encryptionNotEnabled": "A encriptação não está ativada", - "@encryptionNotEnabled": { - "type": "text", - "placeholders": {} - }, - "endedTheCall": "{senderName} terminou a chamada", - "@endedTheCall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "enterAnEmailAddress": "Insere um endereço de correio eletrónico", - "@enterAnEmailAddress": { - "type": "text", - "placeholders": {} - }, - "homeserver": "Servidor", - "@homeserver": {}, - "enterYourHomeserver": "Insere o teu servidor", - "@enterYourHomeserver": { - "type": "text", - "placeholders": {} - }, - "errorObtainingLocation": "Erro ao obter localização: {error}", - "@errorObtainingLocation": { - "type": "text", - "placeholders": { - "error": {} - } - }, - "everythingReady": "Tudo a postos!", - "@everythingReady": { - "type": "text", - "placeholders": {} - }, - "extremeOffensive": "Extremamente ofensivo", - "@extremeOffensive": { - "type": "text", - "placeholders": {} - }, - "fileName": "Nome do ficheiro", - "@fileName": { - "type": "text", - "placeholders": {} - }, - "fluffychat": "FluffyChat", - "@fluffychat": { - "type": "text", - "placeholders": {} - }, - "fontSize": "Tamanho da letra", - "@fontSize": { - "type": "text", - "placeholders": {} - }, - "forward": "Reencaminhar", - "@forward": { - "type": "text", - "placeholders": {} - }, - "goToTheNewRoom": "Ir para a nova sala", - "@goToTheNewRoom": { - "type": "text", - "placeholders": {} - }, - "group": "Grupo", - "@group": { - "type": "text", - "placeholders": {} - }, - "groupIsPublic": "O grupo é público", - "@groupIsPublic": { - "type": "text", - "placeholders": {} - }, - "groups": "Grupos", - "@groups": { - "type": "text", - "placeholders": {} - }, - "groupWith": "Grupo com {displayname}", - "@groupWith": { - "type": "text", - "placeholders": { - "displayname": {} - } - }, - "guestsAreForbidden": "São proibidos visitantes", - "@guestsAreForbidden": { - "type": "text", - "placeholders": {} - }, - "guestsCanJoin": "Podem entrar visitantes", - "@guestsCanJoin": { - "type": "text", - "placeholders": {} - }, - "hasWithdrawnTheInvitationFor": "{username} revogou o convite para {targetName}", - "@hasWithdrawnTheInvitationFor": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "help": "Ajuda", - "@help": { - "type": "text", - "placeholders": {} - }, - "hideRedactedEvents": "Esconder eventos eliminados", - "@hideRedactedEvents": { - "type": "text", - "placeholders": {} - }, - "hideUnknownEvents": "Esconder eventos desconhecidos", - "@hideUnknownEvents": { - "type": "text", - "placeholders": {} - }, - "howOffensiveIsThisContent": "Quão ofensivo é este conteúdo?", - "@howOffensiveIsThisContent": { - "type": "text", - "placeholders": {} - }, - "id": "ID", - "@id": { - "type": "text", - "placeholders": {} - }, - "identity": "Identidade", - "@identity": { - "type": "text", - "placeholders": {} - }, - "ignore": "Ignorar", - "@ignore": { - "type": "text", - "placeholders": {} - }, - "ignoredUsers": "Utilizadores ignorados", - "@ignoredUsers": { - "type": "text", - "placeholders": {} - }, - "iHaveClickedOnLink": "Eu cliquei na ligação", - "@iHaveClickedOnLink": { - "type": "text", - "placeholders": {} - }, - "incorrectPassphraseOrKey": "Senha ou chave de recuperação incorretos", - "@incorrectPassphraseOrKey": { - "type": "text", - "placeholders": {} - }, - "inoffensive": "Inofensivo", - "@inoffensive": { - "type": "text", - "placeholders": {} - }, - "inviteContact": "Convidar contacto", - "@inviteContact": { - "type": "text", - "placeholders": {} - }, - "inviteContactToGroup": "Convidar contacto para {groupName}", - "@inviteContactToGroup": { - "type": "text", - "placeholders": { - "groupName": {} - } - }, - "invited": "Convidado(a)", - "@invited": { - "type": "text", - "placeholders": {} - }, - "invitedUser": "{username} convidou {targetName}", - "@invitedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "invitedUsersOnly": "Utilizadores(as) convidados(as) apenas", - "@invitedUsersOnly": { - "type": "text", - "placeholders": {} - }, - "inviteForMe": "Convite para mim", - "@inviteForMe": { - "type": "text", - "placeholders": {} - }, - "inviteText": "{username} convidou-te para o FluffyChat.\n1. Instala o FluffyChat: https://fluffychat.im\n2. Regista-te ou inicia sessão.\n3. Abre a ligação de convite: {link}", - "@inviteText": { - "type": "text", - "placeholders": { - "username": {}, - "link": {} - } - }, - "isTyping": "está a escrever…", - "@isTyping": { - "type": "text", - "placeholders": {} - }, - "joinedTheChat": "{username} entrou na conversa", - "@joinedTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "joinRoom": "Entrar na sala", - "@joinRoom": { - "type": "text", - "placeholders": {} - }, - "kicked": "{username} expulsou {targetName}", - "@kicked": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "kickedAndBanned": "{username} expulsou e baniu {targetName}", - "@kickedAndBanned": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "kickFromChat": "Expulsar da conversa", - "@kickFromChat": { - "type": "text", - "placeholders": {} - }, - "lastActiveAgo": "Ativo(a) pela última vez: {localizedTimeShort}", - "@lastActiveAgo": { - "type": "text", - "placeholders": { - "localizedTimeShort": {} - } - }, - "leave": "Sair", - "@leave": { - "type": "text", - "placeholders": {} - }, - "leftTheChat": "Saiu da conversa", - "@leftTheChat": { - "type": "text", - "placeholders": {} - }, - "license": "Licença", - "@license": { - "type": "text", - "placeholders": {} - }, - "lightTheme": "Claro", - "@lightTheme": { - "type": "text", - "placeholders": {} - }, - "loadCountMoreParticipants": "Carregar mais {count} participantes", - "@loadCountMoreParticipants": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "loadingPleaseWait": "A carregar... Por favor aguarde.", - "@loadingPleaseWait": { - "type": "text", - "placeholders": {} - }, - "loadMore": "Carregar mais…", - "@loadMore": { - "type": "text", - "placeholders": {} - }, - "locationDisabledNotice": "Os serviços de localização estão desativados. Por favor, ativa-os para poder partilhar a sua localização.", - "@locationDisabledNotice": { - "type": "text", - "placeholders": {} - }, - "locationPermissionDeniedNotice": "Permissão de localização recusada. Por favor, concede permissão para poderes partilhar a tua posição.", - "@locationPermissionDeniedNotice": { - "type": "text", - "placeholders": {} - }, - "login": "Entrar", - "@login": { - "type": "text", - "placeholders": {} - }, - "logInTo": "Entrar em {homeserver}", - "@logInTo": { - "type": "text", - "placeholders": { - "homeserver": {} - } - }, - "logout": "Sair", - "@logout": { - "type": "text", - "placeholders": {} - }, - "memberChanges": "Alterações de membros", - "@memberChanges": { - "type": "text", - "placeholders": {} - }, - "mention": "Mencionar", - "@mention": { - "type": "text", - "placeholders": {} - }, - "messages": "Mensagens", - "@messages": { - "type": "text", - "placeholders": {} - }, - "moderator": "Moderador", - "@moderator": { - "type": "text", - "placeholders": {} - }, - "muteChat": "Silenciar conversa", - "@muteChat": { - "type": "text", - "placeholders": {} - }, - "needPantalaimonWarning": "Por favor,", - "@needPantalaimonWarning": { - "type": "text", - "placeholders": {} - }, - "newChat": "Nova conversa", - "@newChat": { - "type": "text", - "placeholders": {} - }, - "newMessageInFluffyChat": "Nova mensagem no FluffyChat", - "@newMessageInFluffyChat": { - "type": "text", - "placeholders": {} - }, - "newVerificationRequest": "Novo pedido de verificação!", - "@newVerificationRequest": { - "type": "text", - "placeholders": {} - }, - "next": "Próximo", - "@next": { - "type": "text", - "placeholders": {} - }, - "no": "Não", - "@no": { - "type": "text", - "placeholders": {} - }, - "noConnectionToTheServer": "Nenhuma ligação ao servidor", - "@noConnectionToTheServer": { - "type": "text", - "placeholders": {} - }, - "noEmotesFound": "Nenhuns emotes encontrados. 😕", - "@noEmotesFound": { - "type": "text", - "placeholders": {} - }, - "noEncryptionForPublicRooms": "Só podes ativar a encriptação quando a sala não for publicamente acessível.", - "@noEncryptionForPublicRooms": { - "type": "text", - "placeholders": {} - }, - "noGoogleServicesWarning": "Parece que não tens nenhuns serviços da Google no seu telemóvel. É uma boa decisão para a sua privacidade! Para receber notificações instantâneas no FluffyChat, recomendamos que uses https://microg.org/ ou https://unifiedpush.org/.", - "@noGoogleServicesWarning": { - "type": "text", - "placeholders": {} - }, - "noMatrixServer": "{server1} não é um servidor Matrix, usar {server2}?", - "@noMatrixServer": { - "type": "text", - "placeholders": { - "server1": {}, - "server2": {} - } - }, - "none": "Nenhum", - "@none": { - "type": "text", - "placeholders": {} - }, - "changedTheChatAvatar": "{username} alterou o avatar da conversa", - "@changedTheChatAvatar": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheHistoryVisibilityTo": "{username} alterou a visibilidade do histórico para: {rules}", - "@changedTheHistoryVisibilityTo": { - "type": "text", - "placeholders": { - "username": {}, - "rules": {} - } - }, - "changedTheChatDescriptionTo": "{username} alterou a descrição da conversa para: '{description}'", - "@changedTheChatDescriptionTo": { - "type": "text", - "placeholders": { - "username": {}, - "description": {} - } - }, - "changedTheChatNameTo": "{username} alterou o nome da conversa para: '{chatname}'", - "@changedTheChatNameTo": { - "type": "text", - "placeholders": { - "username": {}, - "chatname": {} - } - }, - "changedTheGuestAccessRules": "{username} alterou as regras de acesso de visitantes", - "@changedTheGuestAccessRules": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheHistoryVisibility": "{username} alterou a visibilidade do histórico", - "@changedTheHistoryVisibility": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sendAMessage": "Enviar a mensagem", - "@sendAMessage": { - "type": "text", - "placeholders": {} - }, - "sendAudio": "Enviar áudio", - "@sendAudio": { - "type": "text", - "placeholders": {} - }, - "sendAsText": "Enviar como texto", - "@sendAsText": { - "type": "text" - }, - "send": "Enviar", - "@send": { - "type": "text", - "placeholders": {} - }, - "appLock": "Bloqueio da aplicação", - "@appLock": { - "type": "text", - "placeholders": {} - }, - "noPasswordRecoveryDescription": "Ainda não adicionaste uma forma de recuperar a tua palavra-passe.", - "@noPasswordRecoveryDescription": { - "type": "text", - "placeholders": {} - }, - "noPermission": "Sem permissão", - "@noPermission": { - "type": "text", - "placeholders": {} - }, - "noRoomsFound": "Não foram encontradas nenhumas salas…", - "@noRoomsFound": { - "type": "text", - "placeholders": {} - }, - "notifications": "Notificações", - "@notifications": { - "type": "text", - "placeholders": {} - }, - "notificationsEnabledForThisAccount": "Notificações ativadas para esta conta", - "@notificationsEnabledForThisAccount": { - "type": "text", - "placeholders": {} - }, - "numUsersTyping": "Estão {count} utilizadores(as) a escrever…", - "@numUsersTyping": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "obtainingLocation": "A obter localização…", - "@obtainingLocation": { - "type": "text", - "placeholders": {} - }, - "offensive": "Offensivo", - "@offensive": { - "type": "text", - "placeholders": {} - }, - "offline": "Offline", - "@offline": { - "type": "text", - "placeholders": {} - }, - "ok": "ok", - "@ok": { - "type": "text", - "placeholders": {} - }, - "online": "Online", - "@online": { - "type": "text", - "placeholders": {} - }, - "onlineKeyBackupEnabled": "A cópia de segurança online de chaves está ativada", - "@onlineKeyBackupEnabled": { - "type": "text", - "placeholders": {} - }, - "oopsPushError": "Ups! Infelizmente, ocorreu um erro ao configurar as notificações instantâneas.", - "@oopsPushError": { - "type": "text", - "placeholders": {} - }, - "oopsSomethingWentWrong": "Ups, algo correu mal…", - "@oopsSomethingWentWrong": { - "type": "text", - "placeholders": {} - }, - "openAppToReadMessages": "Abrir aplicação para ler mensagens", - "@openAppToReadMessages": { - "type": "text", - "placeholders": {} - }, - "openCamera": "Abrir câmara", - "@openCamera": { - "type": "text", - "placeholders": {} - }, - "oneClientLoggedOut": "Um dos teus clientes terminou sessão", - "@oneClientLoggedOut": {}, - "addAccount": "Adicionar conta", - "@addAccount": {}, - "editBundlesForAccount": "Editar pacotes para esta conta", - "@editBundlesForAccount": {}, - "addToBundle": "Adicionar ao pacote", - "@addToBundle": {}, - "removeFromBundle": "Remover deste pacote", - "@removeFromBundle": {}, - "bundleName": "Nome do pacote", - "@bundleName": {}, - "enableMultiAccounts": "(BETA) Ativar múltiplas contas neste dispositivo", - "@enableMultiAccounts": {}, - "openInMaps": "Abrir nos mapas", - "@openInMaps": { - "type": "text", - "placeholders": {} - }, - "link": "Ligação", - "@link": {}, - "serverRequiresEmail": "Este servidor precisa de validar o teu endereço de correio eletrónico para o registo.", - "@serverRequiresEmail": {}, - "or": "Ou", - "@or": { - "type": "text", - "placeholders": {} - }, - "participant": "Participante", - "@participant": { - "type": "text", - "placeholders": {} - }, - "passphraseOrKey": "senha ou chave de recuperação", - "@passphraseOrKey": { - "type": "text", - "placeholders": {} - }, - "password": "Palavra-passe", - "@password": { - "type": "text", - "placeholders": {} - }, - "passwordForgotten": "Palavra-passe esquecida", - "@passwordForgotten": { - "type": "text", - "placeholders": {} - }, - "passwordHasBeenChanged": "A palavra-passe foi alterada", - "@passwordHasBeenChanged": { - "type": "text", - "placeholders": {} - }, - "passwordRecovery": "Recuperação de palavra-passe", - "@passwordRecovery": { - "type": "text", - "placeholders": {} - }, - "people": "Pessoas", - "@people": { - "type": "text", - "placeholders": {} - }, - "pickImage": "Escolher uma imagem", - "@pickImage": { - "type": "text", - "placeholders": {} - }, - "pin": "Afixar", - "@pin": { - "type": "text", - "placeholders": {} - }, - "play": "Reproduzir {fileName}", - "@play": { - "type": "text", - "placeholders": { - "fileName": {} - } - }, - "pleaseChoose": "Por favor, escolhe", - "@pleaseChoose": { - "type": "text", - "placeholders": {} - }, - "pleaseChooseAPasscode": "Por favor, escolhe um código-passe", - "@pleaseChooseAPasscode": { - "type": "text", - "placeholders": {} - }, - "pleaseClickOnLink": "Por favor, clica na ligação no correio eletrónico e depois continua.", - "@pleaseClickOnLink": { - "type": "text", - "placeholders": {} - }, - "pleaseEnter4Digits": "Por favor, insere 4 dígitos ou deixa vazio para desativar o bloqueio da aplicação.", - "@pleaseEnter4Digits": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourPassword": "Por favor, insere a tua palavra-passe", - "@pleaseEnterYourPassword": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourPin": "Por favor, insere o teu código", - "@pleaseEnterYourPin": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourUsername": "Por favor, insere o teu nome de utilizador", - "@pleaseEnterYourUsername": { - "type": "text", - "placeholders": {} - }, - "pleaseFollowInstructionsOnWeb": "Por favor, segue as instruções no website e clica em \"Seguinte\".", - "@pleaseFollowInstructionsOnWeb": { - "type": "text", - "placeholders": {} - }, - "privacy": "Privacidade", - "@privacy": { - "type": "text", - "placeholders": {} - }, - "publicRooms": "Salas públicas", - "@publicRooms": { - "type": "text", - "placeholders": {} - }, - "reason": "Razão", - "@reason": { - "type": "text", - "placeholders": {} - }, - "redactedAnEvent": "{username} eliminou um evento", - "@redactedAnEvent": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "recording": "A gravar", - "@recording": { - "type": "text", - "placeholders": {} - }, - "redactMessage": "Eliminar mensagem", - "@redactMessage": { - "type": "text", - "placeholders": {} - }, - "register": "Registar", - "@register": { - "type": "text", - "placeholders": {} - }, - "reject": "Rejeitar", - "@reject": { - "type": "text", - "placeholders": {} - }, - "rejectedTheInvitation": "{username} rejeitou o convite", - "@rejectedTheInvitation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "rejoin": "Reentrar", - "@rejoin": { - "type": "text", - "placeholders": {} - }, - "remove": "Remover", - "@remove": { - "type": "text", - "placeholders": {} - }, - "removeAllOtherDevices": "Remover todos os outros dispositivos", - "@removeAllOtherDevices": { - "type": "text", - "placeholders": {} - }, - "removedBy": "Removido por {username}", - "@removedBy": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "removeDevice": "Remover dispositivo", - "@removeDevice": { - "type": "text", - "placeholders": {} - }, - "unbanFromChat": "Perdoar nesta conversa", - "@unbanFromChat": { - "type": "text", - "placeholders": {} - }, - "removeYourAvatar": "Remover o teu avatar", - "@removeYourAvatar": { - "type": "text", - "placeholders": {} - }, - "renderRichContent": "Exibir conteúdo de mensagem rico", - "@renderRichContent": { - "type": "text", - "placeholders": {} - }, - "replaceRoomWithNewerVersion": "Substituir sala com versão mais recente", - "@replaceRoomWithNewerVersion": { - "type": "text", - "placeholders": {} - }, - "reply": "Responder", - "@reply": { - "type": "text", - "placeholders": {} - }, - "reportMessage": "Reportar mensagem", - "@reportMessage": { - "type": "text", - "placeholders": {} - }, - "requestPermission": "Pedir permissão", - "@requestPermission": { - "type": "text", - "placeholders": {} - }, - "roomHasBeenUpgraded": "A sala foi atualizada", - "@roomHasBeenUpgraded": { - "type": "text", - "placeholders": {} - }, - "roomVersion": "Versão da sala", - "@roomVersion": { - "type": "text", - "placeholders": {} - }, - "saveFile": "Guardar ficheiro", - "@saveFile": { - "type": "text", - "placeholders": {} - }, - "search": "Procurar", - "@search": { - "type": "text", - "placeholders": {} - }, - "security": "Segurança", - "@security": { - "type": "text", - "placeholders": {} - }, - "seenByUser": "Visto por {username}", - "@seenByUser": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sendFile": "Enviar ficheiro", - "@sendFile": { - "type": "text", - "placeholders": {} - }, - "sendImage": "Enviar imagem", - "@sendImage": { - "type": "text", - "placeholders": {} - }, - "sendMessages": "Enviar mensagens", - "@sendMessages": { - "type": "text", - "placeholders": {} - }, - "sendOriginal": "Enviar original", - "@sendOriginal": { - "type": "text", - "placeholders": {} - }, - "sendSticker": "Enviar autocolante", - "@sendSticker": { - "type": "text", - "placeholders": {} - }, - "sendVideo": "Enviar vídeo", - "@sendVideo": { - "type": "text", - "placeholders": {} - }, - "sentAFile": "{username} enviar um ficheiro", - "@sentAFile": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentAnAudio": "{username} enviar um áudio", - "@sentAnAudio": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentAPicture": "{username} enviar uma imagem", - "@sentAPicture": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentASticker": "{username} enviou um autocolante", - "@sentASticker": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentAVideo": "{username} enviou um vídeo", - "@sentAVideo": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "commandHint_clearcache": "Limpar cache", - "@commandHint_clearcache": { - "type": "text", - "description": "Usage hint for the command /clearcache" - }, - "commandHint_create": "Criar uma conversa de grupo vazia\nUsa --no-encryption para desativar a encriptação", - "@commandHint_create": { - "type": "text", - "description": "Usage hint for the command /create" - }, - "commandHint_discardsession": "Descartar sessão", - "@commandHint_discardsession": { - "type": "text", - "description": "Usage hint for the command /discardsession" - }, - "commandHint_dm": "Iniciar uma conversa direta\nUsa --no-encryption para desativar a encriptação", - "@commandHint_dm": { - "type": "text", - "description": "Usage hint for the command /dm" - }, - "dehydrate": "Exportar sessão e limpar dispositivo", - "@dehydrate": {}, - "dehydrateWarning": "Esta ação não pode ser revertida. Assegura-te que guardas bem a cópia de segurança.", - "@dehydrateWarning": {}, - "hydrateTorLong": "Exportaste a tua sessão na última vez que estiveste no TOR? Importa-a rapidamente e continua a conversar.", - "@hydrateTorLong": {}, - "dehydrateTor": "Utilizadores do TOR: Exportar sessão", - "@dehydrateTor": {}, - "hydrate": "Restaurar a partir de cópia de segurança", - "@hydrate": {}, - "hydrateTor": "Utilizadores do TOR: Importar sessão", - "@hydrateTor": {}, - "dehydrateTorLong": "Para utilizadores do TOR, é recomendado exportar a sessão antes de fechar a janela.", - "@dehydrateTorLong": {}, - "@showPassword": { - "type": "text", - "placeholders": {} - }, - "@hugContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@theyMatch": { - "type": "text", - "placeholders": {} - }, - "@jumpToLastReadMessage": {}, - "@allRooms": { - "type": "text", - "placeholders": {} - }, - "@commandHint_cuddle": {}, - "@widgetVideo": {}, - "@dismiss": {}, - "@unknownDevice": { - "type": "text", - "placeholders": {} - }, - "@reportErrorDescription": {}, - "@setPermissionsLevel": { - "type": "text", - "placeholders": {} - }, - "@unsupportedAndroidVersion": {}, - "@widgetJitsi": {}, - "@youAreNoLongerParticipatingInThisChat": { - "type": "text", - "placeholders": {} - }, - "@messageType": {}, - "@indexedDbErrorLong": {}, - "@toggleMuted": { - "type": "text", - "placeholders": {} - }, - "@title": { - "description": "Title for the application", - "type": "text", - "placeholders": {} - }, - "@verifySuccess": { - "type": "text", - "placeholders": {} - }, - "@startFirstChat": {}, - "@callingAccount": {}, - "@setColorTheme": {}, - "@nextAccount": {}, - "@singlesignon": { - "type": "text", - "placeholders": {} - }, - "@warning": { - "type": "text", - "placeholders": {} - }, - "@allSpaces": {}, - "@supposedMxid": { - "type": "text", - "placeholders": { - "mxid": {} - } - }, - "@user": {}, - "@videoCall": { - "type": "text", - "placeholders": {} - }, - "@youAcceptedTheInvitation": {}, - "@userAndOthersAreTyping": { - "type": "text", - "placeholders": { - "username": {}, - "count": {} - } - }, - "@youInvitedBy": { - "placeholders": { - "user": {} - } - }, - "@userIsTyping": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@banUserDescription": {}, - "@widgetEtherpad": {}, - "@waitingPartnerAcceptRequest": { - "type": "text", - "placeholders": {} - }, - "@writeAMessage": { - "type": "text", - "placeholders": {} - }, - "@removeDevicesDescription": {}, - "@separateChatTypes": { - "type": "text", - "placeholders": {} - }, - "@tryAgain": {}, - "@youKickedAndBanned": { - "placeholders": { - "user": {} - } - }, - "@unbanUserDescription": {}, - "@userAndUserAreTyping": { - "type": "text", - "placeholders": { - "username": {}, - "username2": {} - } - }, - "@youRejectedTheInvitation": {}, - "@otherCallingPermissions": {}, - "@messagesStyle": {}, - "@widgetUrlError": {}, - "@emailOrUsername": {}, - "@newSpaceDescription": {}, - "@chatDescription": {}, - "@callingAccountDetails": {}, - "@enterSpace": {}, - "@encryptThisChat": {}, - "@unavailable": { - "type": "text", - "placeholders": {} - }, - "@previousAccount": {}, - "@fromTheInvitation": { - "type": "text", - "placeholders": {} - }, - "@reopenChat": {}, - "@pleaseEnterRecoveryKey": {}, - "@toggleFavorite": { - "type": "text", - "placeholders": {} - }, - "@widgetNameError": {}, - "@unpin": { - "type": "text", - "placeholders": {} - }, - "@spaceIsPublic": { - "type": "text", - "placeholders": {} - }, - "@addWidget": {}, - "@unblockDevice": { - "type": "text", - "placeholders": {} - }, - "@countFiles": { - "placeholders": { - "count": {} - } - }, - "@noKeyForThisMessage": {}, - "@shareLocation": { - "type": "text", - "placeholders": {} - }, - "@commandHint_markasgroup": {}, - "@pushNotificationsNotAvailable": {}, - "@storeInAppleKeyChain": {}, - "@invalidServerName": {}, - "@chatPermissions": {}, - "@voiceMessage": { - "type": "text", - "placeholders": {} - }, - "@wipeChatBackup": { - "type": "text", - "placeholders": {} - }, - "@sender": {}, - "@storeInAndroidKeystore": {}, - "@signInWithPassword": {}, - "@weSentYouAnEmail": { - "type": "text", - "placeholders": {} - }, - "@makeAdminDescription": {}, - "@synchronizingPleaseWait": { - "type": "text", - "placeholders": {} - }, - "@transferFromAnotherDevice": { - "type": "text", - "placeholders": {} - }, - "@pushRules": { - "type": "text", - "placeholders": {} - }, - "@saveKeyManuallyDescription": {}, - "@whyIsThisMessageEncrypted": {}, - "@unreadChats": { - "type": "text", - "placeholders": { - "unreadCount": {} - } - }, - "@setChatDescription": {}, - "@userLeftTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@spaceName": { - "type": "text", - "placeholders": {} - }, - "@importFromZipFile": {}, - "@toggleUnread": { - "type": "text", - "placeholders": {} - }, - "@noOtherDevicesFound": {}, - "@whoIsAllowedToJoinThisGroup": { - "type": "text", - "placeholders": {} - }, - "@redactedBy": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@submit": { - "type": "text", - "placeholders": {} - }, - "@videoCallsBetaWarning": {}, - "@unmuteChat": { - "type": "text", - "placeholders": {} - }, - "@yes": { - "type": "text", - "placeholders": {} - }, - "@signInWith": { - "type": "text", - "placeholders": { - "provider": {} - } - }, - "@username": { - "type": "text", - "placeholders": {} - }, - "@fileIsTooBigForServer": {}, - "@verified": { - "type": "text", - "placeholders": {} - }, - "@setStatus": { - "type": "text", - "placeholders": {} - }, - "@callingPermissions": {}, - "@readUpToHere": {}, - "@start": {}, - "@unlockOldMessages": {}, - "@numChats": { - "type": "number", - "placeholders": { - "number": {} - } - }, - "@optionalRedactReason": {}, - "@waitingPartnerEmoji": { - "type": "text", - "placeholders": {} - }, - "@tryToSendAgain": { - "type": "text", - "placeholders": {} - }, - "@visibleForAllParticipants": { - "type": "text", - "placeholders": {} - }, - "@archiveRoomDescription": {}, - "@exportEmotePack": {}, - "@switchToAccount": { - "type": "number", - "placeholders": { - "number": {} - } - }, - "@setAsCanonicalAlias": { - "type": "text", - "placeholders": {} - }, - "@whyDoYouWantToReportThis": { - "type": "text", - "placeholders": {} - }, - "@experimentalVideoCalls": {}, - "@pleaseEnterRecoveryKeyDescription": {}, - "@withTheseAddressesRecoveryDescription": { - "type": "text", - "placeholders": {} - }, - "@inviteContactToGroupQuestion": {}, - "@redactedByBecause": { - "type": "text", - "placeholders": { - "username": {}, - "reason": {} - } - }, - "@youHaveWithdrawnTheInvitationFor": { - "placeholders": { - "user": {} - } - }, - "@skip": { - "type": "text", - "placeholders": {} - }, - "@appearOnTopDetails": {}, - "@enterRoom": {}, - "@reportUser": {}, - "@sharedTheLocation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@unbannedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "@confirmEventUnpin": {}, - "@youInvitedUser": { - "placeholders": { - "user": {} - } - }, - "@fileHasBeenSavedAt": { - "type": "text", - "placeholders": { - "path": {} - } - }, - "@redactMessageDescription": {}, - "@recoveryKey": {}, - "@invalidInput": {}, - "@yourPublicKey": { - "type": "text", - "placeholders": {} - }, - "@tooManyRequestsWarning": { - "type": "text", - "placeholders": {} - }, - "@doNotShowAgain": {}, - "@report": {}, - "@status": { - "type": "text", - "placeholders": {} - }, - "@verifyStart": { - "type": "text", - "placeholders": {} - }, - "@unverified": {}, - "@hideUnimportantStateEvents": {}, - "@screenSharingTitle": {}, - "@widgetCustom": {}, - "@sentCallInformations": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@addToSpaceDescription": {}, - "@googlyEyesContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@youBannedUser": { - "placeholders": { - "user": {} - } - }, - "@theyDontMatch": { - "type": "text", - "placeholders": {} - }, - "@youHaveBeenBannedFromThisChat": { - "type": "text", - "placeholders": {} - }, - "@addChatDescription": {}, - "@hasKnocked": { - "placeholders": { - "user": {} - } - }, - "@publish": {}, - "@openLinkInBrowser": {}, - "@messageInfo": {}, - "@disableEncryptionWarning": {}, - "@directChat": {}, - "@wrongPinEntered": { - "type": "text", - "placeholders": { - "seconds": {} - } - }, - "@sendTypingNotifications": {}, - "@inviteGroupChat": {}, - "@appearOnTop": {}, - "@invitePrivateChat": {}, - "@verifyTitle": { - "type": "text", - "placeholders": {} - }, - "@foregroundServiceRunning": {}, - "@voiceCall": {}, - "@unknownEncryptionAlgorithm": { - "type": "text", - "placeholders": {} - }, - "@importEmojis": {}, - "@wasDirectChatDisplayName": { - "type": "text", - "placeholders": { - "oldDisplayName": {} - } - }, - "@noChatDescriptionYet": {}, - "@whoCanPerformWhichAction": { - "type": "text", - "placeholders": {} - }, - "@confirmMatrixId": {}, - "@learnMore": {}, - "@you": { - "type": "text", - "placeholders": {} - }, - "@notAnImage": {}, - "@users": {}, - "@openGallery": {}, - "@chatDescriptionHasBeenChanged": {}, - "@newGroup": {}, - "@removeFromSpace": {}, - "@sourceCode": { - "type": "text", - "placeholders": {} - }, - "@roomUpgradeDescription": {}, - "@userSentUnknownEvent": { - "type": "text", - "placeholders": { - "username": {}, - "type": {} - } - }, - "@scanQrCode": {}, - "@pleaseEnterANumber": {}, - "@youKicked": { - "placeholders": { - "user": {} - } - }, - "@profileNotFound": {}, - "@jump": {}, - "@reactedWith": { - "type": "text", - "placeholders": { - "sender": {}, - "reaction": {} - } - }, - "@sorryThatsNotPossible": {}, - "@videoWithSize": { - "type": "text", - "placeholders": { - "size": {} - } - }, - "@shareInviteLink": {}, - "@commandHint_markasdm": {}, - "@recoveryKeyLost": {}, - "@cuddleContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@deviceKeys": {}, - "@waitingPartnerNumbers": { - "type": "text", - "placeholders": {} - }, - "@emoteKeyboardNoRecents": { - "type": "text", - "placeholders": {} - }, - "@setCustomEmotes": { - "type": "text", - "placeholders": {} - }, - "@startedACall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@systemTheme": { - "type": "text", - "placeholders": {} - }, - "@visibilityOfTheChatHistory": { - "type": "text", - "placeholders": {} - }, - "@settings": { - "type": "text", - "placeholders": {} - }, - "@setTheme": {}, - "@youJoinedTheChat": {}, - "@wallpaper": { - "type": "text", - "placeholders": {} - }, - "@openVideoCamera": { - "type": "text", - "placeholders": {} - }, - "@statusExampleMessage": { - "type": "text", - "placeholders": {} - }, - "@markAsRead": {}, - "@widgetName": {}, - "@errorAddingWidget": {}, - "@commandHint_hug": {}, - "@replace": {}, - "@youUnbannedUser": { - "placeholders": { - "user": {} - } - }, - "@visibleForEveryone": { - "type": "text", - "placeholders": {} - }, - "@newSpace": {}, - "@unknownEvent": { - "type": "text", - "placeholders": { - "type": {} - } - }, - "@emojis": {}, - "@share": { - "type": "text", - "placeholders": {} - }, - "@commandHint_googly": {}, - "@pleaseTryAgainLaterOrChooseDifferentServer": {}, - "@createGroup": {}, - "@time": {}, - "@custom": {}, - "@noBackupWarning": {}, - "@fromJoining": { - "type": "text", - "placeholders": {} - }, - "@verify": { - "type": "text", - "placeholders": {} - }, - "@storeInSecureStorageDescription": {}, - "@openChat": {}, - "@kickUserDescription": {}, - "@importNow": {}, - "@setInvitationLink": { - "type": "text", - "placeholders": {} - }, - "@pinMessage": {}, - "@invite": {}, - "@indexedDbErrorTitle": {}, - "@unsupportedAndroidVersionLong": {}, - "@storeSecurlyOnThisDevice": {}, - "@screenSharingDetail": {}, - "@placeCall": {} -} \ No newline at end of file + "repeatPassword": "Repete a palavra-passe", + "@repeatPassword": {}, + "about": "Acerca de", + "@about": { + "type": "text", + "placeholders": {} + }, + "accept": "Aceitar", + "@accept": { + "type": "text", + "placeholders": {} + }, + "acceptedTheInvitation": "{username} aceitou o convite", + "@acceptedTheInvitation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "account": "Conta", + "@account": { + "type": "text", + "placeholders": {} + }, + "activatedEndToEndEncryption": "{username} ativou encriptação ponta-a-ponta", + "@activatedEndToEndEncryption": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "addEmail": "Adicionar correio eletrónico", + "@addEmail": { + "type": "text", + "placeholders": {} + }, + "addToSpace": "Adicionar ao espaço", + "@addToSpace": {}, + "admin": "Admin", + "@admin": { + "type": "text", + "placeholders": {} + }, + "alias": "alcunha", + "@alias": { + "type": "text", + "placeholders": {} + }, + "all": "Todos(as)", + "@all": { + "type": "text", + "placeholders": {} + }, + "allChats": "Todas as conversas", + "@allChats": { + "type": "text", + "placeholders": {} + }, + "answeredTheCall": "{senderName} atendeu a chamada", + "@answeredTheCall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "anyoneCanJoin": "Qualquer pessoa pode entrar", + "@anyoneCanJoin": { + "type": "text", + "placeholders": {} + }, + "archive": "Arquivo", + "@archive": { + "type": "text", + "placeholders": {} + }, + "areGuestsAllowedToJoin": "Todos os visitantes podem entrar", + "@areGuestsAllowedToJoin": { + "type": "text", + "placeholders": {} + }, + "areYouSure": "Tens a certeza?", + "@areYouSure": { + "type": "text", + "placeholders": {} + }, + "areYouSureYouWantToLogout": "Tens a certeza que queres sair?", + "@areYouSureYouWantToLogout": { + "type": "text", + "placeholders": {} + }, + "askSSSSSign": "Para poderes assinar a outra pessoa, por favor, insere a tua senha de armazenamento seguro ou a chave de recuperação.", + "@askSSSSSign": { + "type": "text", + "placeholders": {} + }, + "askVerificationRequest": "Aceitar este pedido de verificação de {username}?", + "@askVerificationRequest": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "autoplayImages": "Automaticamente reproduzir autocolantes e emotes animados", + "@autoplayImages": { + "type": "text", + "placeholder": {} + }, + "sendOnEnter": "Enviar com Enter", + "@sendOnEnter": {}, + "badServerVersionsException": "O servidor suporta as versões Spec:\n{serverVersions}\nMas esta aplicação apenas suporta {suportedVersions}", + "@badServerVersionsException": { + "type": "text", + "placeholders": { + "serverVersions": {}, + "supportedVersions": {} + } + }, + "badServerLoginTypesException": "O servidor suporta os tipos de início de sessão:\n{serverVersions}\nMas esta aplicação apenas suporta:\n{suportedVersions}", + "@badServerLoginTypesException": { + "type": "text", + "placeholders": { + "serverVersions": {}, + "supportedVersions": {} + } + }, + "banFromChat": "Banir da conversa", + "@banFromChat": { + "type": "text", + "placeholders": {} + }, + "banned": "Banido(a)", + "@banned": { + "type": "text", + "placeholders": {} + }, + "bannedUser": "{username} baniu {targetName}", + "@bannedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "blockDevice": "Bloquear dispositivo", + "@blockDevice": { + "type": "text", + "placeholders": {} + }, + "blocked": "Bloqueado", + "@blocked": { + "type": "text", + "placeholders": {} + }, + "botMessages": "Mensagens de robôs", + "@botMessages": { + "type": "text", + "placeholders": {} + }, + "cancel": "Cancelar", + "@cancel": { + "type": "text", + "placeholders": {} + }, + "cantOpenUri": "Não é possível abrir o URI {uri}", + "@cantOpenUri": { + "type": "text", + "placeholders": { + "uri": {} + } + }, + "changeDeviceName": "Alterar nome do dispositivo", + "@changeDeviceName": { + "type": "text", + "placeholders": {} + }, + "changedTheChatPermissions": "{username} alterou as permissões da conversa", + "@changedTheChatPermissions": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheDisplaynameTo": "{username} alterou o seu nome para: '{displayname}'", + "@changedTheDisplaynameTo": { + "type": "text", + "placeholders": { + "username": {}, + "displayname": {} + } + }, + "changedTheGuestAccessRulesTo": "{username} alterou as regras de acesso de visitantes para: {rules}", + "@changedTheGuestAccessRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "changedTheJoinRules": "{username} alterou as regras de entrada", + "@changedTheJoinRules": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheJoinRulesTo": "{username} alterou as regras de entrada para: {joinRules}", + "@changedTheJoinRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "joinRules": {} + } + }, + "changedTheProfileAvatar": "{username} alterou o seu avatar", + "@changedTheProfileAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheRoomAliases": "{username} alterou as alcunhas da sala", + "@changedTheRoomAliases": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheRoomInvitationLink": "{username} alterou a ligação de convite", + "@changedTheRoomInvitationLink": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changePassword": "Alterar palavra-passe", + "@changePassword": { + "type": "text", + "placeholders": {} + }, + "changeTheHomeserver": "Alterar o servidor", + "@changeTheHomeserver": { + "type": "text", + "placeholders": {} + }, + "changeTheme": "Alterar o teu estilo", + "@changeTheme": { + "type": "text", + "placeholders": {} + }, + "changeTheNameOfTheGroup": "Alterar o nome do grupo", + "@changeTheNameOfTheGroup": { + "type": "text", + "placeholders": {} + }, + "changeYourAvatar": "Alterar o teu avatar", + "@changeYourAvatar": { + "type": "text", + "placeholders": {} + }, + "channelCorruptedDecryptError": "A encriptação foi corrompida", + "@channelCorruptedDecryptError": { + "type": "text", + "placeholders": {} + }, + "chat": "Conversa", + "@chat": { + "type": "text", + "placeholders": {} + }, + "yourChatBackupHasBeenSetUp": "A cópia de segurança foi configurada.", + "@yourChatBackupHasBeenSetUp": {}, + "chatBackup": "Cópia de segurança de conversas", + "@chatBackup": { + "type": "text", + "placeholders": {} + }, + "chatBackupDescription": "A tuas mensagens antigas estão protegidas com uma chave de recuperação. Por favor, certifica-te que não a perdes.", + "@chatBackupDescription": { + "type": "text", + "placeholders": {} + }, + "chatDetails": "Detalhes de conversa", + "@chatDetails": { + "type": "text", + "placeholders": {} + }, + "chatHasBeenAddedToThisSpace": "A conversa foi adicionada a este espaço", + "@chatHasBeenAddedToThisSpace": {}, + "chats": "Conversas", + "@chats": { + "type": "text", + "placeholders": {} + }, + "chooseAStrongPassword": "Escolhe uma palavra-passe forte", + "@chooseAStrongPassword": { + "type": "text", + "placeholders": {} + }, + "clearArchive": "Limpar arquivo", + "@clearArchive": {}, + "close": "Fechar", + "@close": { + "type": "text", + "placeholders": {} + }, + "commandHint_ban": "Banir o utilizador dado desta sala", + "@commandHint_ban": { + "type": "text", + "description": "Usage hint for the command /ban" + }, + "commandHint_html": "Enviar texto formatado com HTML", + "@commandHint_html": { + "type": "text", + "description": "Usage hint for the command /html" + }, + "commandHint_invite": "Convidar o utilizador dado a esta sala", + "@commandHint_invite": { + "type": "text", + "description": "Usage hint for the command /invite" + }, + "commandHint_join": "Entrar na sala dada", + "@commandHint_join": { + "type": "text", + "description": "Usage hint for the command /join" + }, + "commandHint_kick": "Remover o utilizador dado desta sala", + "@commandHint_kick": { + "type": "text", + "description": "Usage hint for the command /kick" + }, + "commandHint_leave": "Sair desta sala", + "@commandHint_leave": { + "type": "text", + "description": "Usage hint for the command /leave" + }, + "commandHint_me": "Descreve-te", + "@commandHint_me": { + "type": "text", + "description": "Usage hint for the command /me" + }, + "commandHint_myroomavatar": "Definir a tua imagem para esta sala (por mxc-uri)", + "@commandHint_myroomavatar": { + "type": "text", + "description": "Usage hint for the command /myroomavatar" + }, + "commandHint_myroomnick": "Definir o teu nome para esta sala", + "@commandHint_myroomnick": { + "type": "text", + "description": "Usage hint for the command /myroomnick" + }, + "commandHint_op": "Definir o nível de poder do utilizador dado (por omissão: 50)", + "@commandHint_op": { + "type": "text", + "description": "Usage hint for the command /op" + }, + "commandHint_plain": "Enviar texto não formatado", + "@commandHint_plain": { + "type": "text", + "description": "Usage hint for the command /plain" + }, + "commandHint_react": "Enviar respostas como reações", + "@commandHint_react": { + "type": "text", + "description": "Usage hint for the command /react" + }, + "commandHint_send": "Enviar texto", + "@commandHint_send": { + "type": "text", + "description": "Usage hint for the command /send" + }, + "commandHint_unban": "Perdoar o utilizador dado", + "@commandHint_unban": { + "type": "text", + "description": "Usage hint for the command /unban" + }, + "commandInvalid": "Comando inválido", + "@commandInvalid": { + "type": "text" + }, + "commandMissing": "{command} não é um comando.", + "@commandMissing": { + "type": "text", + "placeholders": { + "command": {} + }, + "description": "State that {command} is not a valid /command." + }, + "compareEmojiMatch": "Compara e certifica-te que os emojis que se seguem correspondem aos do outro dispositivo:", + "@compareEmojiMatch": { + "type": "text", + "placeholders": {} + }, + "compareNumbersMatch": "Compara e certifica-te que os números que se seguem correspondem aos do outro dispositivo:", + "@compareNumbersMatch": { + "type": "text", + "placeholders": {} + }, + "configureChat": "Configurar conversa", + "@configureChat": { + "type": "text", + "placeholders": {} + }, + "confirm": "Confirmar", + "@confirm": { + "type": "text", + "placeholders": {} + }, + "connect": "Ligar", + "@connect": { + "type": "text", + "placeholders": {} + }, + "contactHasBeenInvitedToTheGroup": "O contacto foi convidado para o grupo", + "@contactHasBeenInvitedToTheGroup": { + "type": "text", + "placeholders": {} + }, + "containsDisplayName": "Contém nome de exibição", + "@containsDisplayName": { + "type": "text", + "placeholders": {} + }, + "containsUserName": "Contém nome de utilizador", + "@containsUserName": { + "type": "text", + "placeholders": {} + }, + "contentHasBeenReported": "O conteúdo foi denunciado aos admins do servidor", + "@contentHasBeenReported": { + "type": "text", + "placeholders": {} + }, + "copiedToClipboard": "Copiado para a área de transferência", + "@copiedToClipboard": { + "type": "text", + "placeholders": {} + }, + "copy": "Copiar", + "@copy": { + "type": "text", + "placeholders": {} + }, + "copyToClipboard": "Copiar para a área de transferência", + "@copyToClipboard": { + "type": "text", + "placeholders": {} + }, + "couldNotDecryptMessage": "Não foi possível desencriptar mensagem: {error}", + "@couldNotDecryptMessage": { + "type": "text", + "placeholders": { + "error": {} + } + }, + "countParticipants": "{count} participantes", + "@countParticipants": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "create": "Criar", + "@create": { + "type": "text", + "placeholders": {} + }, + "createdTheChat": "{username} criou a conversa", + "@createdTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "createNewSpace": "Novo espaço", + "@createNewSpace": { + "type": "text", + "placeholders": {} + }, + "currentlyActive": "Ativo(a) agora", + "@currentlyActive": { + "type": "text", + "placeholders": {} + }, + "darkTheme": "Escuro", + "@darkTheme": { + "type": "text", + "placeholders": {} + }, + "dateAndTimeOfDay": "{date} às {timeOfDay}", + "@dateAndTimeOfDay": { + "type": "text", + "placeholders": { + "date": {}, + "timeOfDay": {} + } + }, + "dateWithoutYear": "{day}-{month}", + "@dateWithoutYear": { + "type": "text", + "placeholders": { + "month": {}, + "day": {} + } + }, + "dateWithYear": "{day}-{month}-{year}", + "@dateWithYear": { + "type": "text", + "placeholders": { + "year": {}, + "month": {}, + "day": {} + } + }, + "deactivateAccountWarning": "Isto irá desativar a tua conta. Não é reversível! Tens a certeza?", + "@deactivateAccountWarning": { + "type": "text", + "placeholders": {} + }, + "defaultPermissionLevel": "Nível de permissão normal", + "@defaultPermissionLevel": { + "type": "text", + "placeholders": {} + }, + "delete": "Eliminar", + "@delete": { + "type": "text", + "placeholders": {} + }, + "deleteAccount": "Eliminar conta", + "@deleteAccount": { + "type": "text", + "placeholders": {} + }, + "deleteMessage": "Eliminar mensagem", + "@deleteMessage": { + "type": "text", + "placeholders": {} + }, + "device": "Dispositivo", + "@device": { + "type": "text", + "placeholders": {} + }, + "deviceId": "ID de dispositivo", + "@deviceId": { + "type": "text", + "placeholders": {} + }, + "devices": "Dispositivos", + "@devices": { + "type": "text", + "placeholders": {} + }, + "directChats": "Conversas diretas", + "@directChats": { + "type": "text", + "placeholders": {} + }, + "displaynameHasBeenChanged": "Nome de exibição alterado", + "@displaynameHasBeenChanged": { + "type": "text", + "placeholders": {} + }, + "downloadFile": "Descarregar ficheiro", + "@downloadFile": { + "type": "text", + "placeholders": {} + }, + "edit": "Editar", + "@edit": { + "type": "text", + "placeholders": {} + }, + "editBlockedServers": "Editar servidores bloqueados", + "@editBlockedServers": { + "type": "text", + "placeholders": {} + }, + "editDisplayname": "Editar nome de exibição", + "@editDisplayname": { + "type": "text", + "placeholders": {} + }, + "editRoomAliases": "Editar alcunhas da sala", + "@editRoomAliases": { + "type": "text", + "placeholders": {} + }, + "editRoomAvatar": "Editar avatar da sala", + "@editRoomAvatar": { + "type": "text", + "placeholders": {} + }, + "emoteExists": "Emote já existente!", + "@emoteExists": { + "type": "text", + "placeholders": {} + }, + "emoteInvalid": "Código de emote inválido!", + "@emoteInvalid": { + "type": "text", + "placeholders": {} + }, + "emotePacks": "Pacotes de emotes da sala", + "@emotePacks": { + "type": "text", + "placeholders": {} + }, + "emoteSettings": "Configurações de emotes", + "@emoteSettings": { + "type": "text", + "placeholders": {} + }, + "emoteShortcode": "Código do emote", + "@emoteShortcode": { + "type": "text", + "placeholders": {} + }, + "emoteWarnNeedToPick": "Precisas de escolher um código de emote e uma imagem!", + "@emoteWarnNeedToPick": { + "type": "text", + "placeholders": {} + }, + "emptyChat": "Conversa vazia", + "@emptyChat": { + "type": "text", + "placeholders": {} + }, + "enableEmotesGlobally": "Ativar pacote de emotes globalmente", + "@enableEmotesGlobally": { + "type": "text", + "placeholders": {} + }, + "enableEncryption": "Ativar encriptação", + "@enableEncryption": { + "type": "text", + "placeholders": {} + }, + "enableEncryptionWarning": "Nunca mais poderás desativar a encriptação. Tens a certeza?", + "@enableEncryptionWarning": { + "type": "text", + "placeholders": {} + }, + "encrypted": "Encriptada", + "@encrypted": { + "type": "text", + "placeholders": {} + }, + "encryption": "Encriptação", + "@encryption": { + "type": "text", + "placeholders": {} + }, + "encryptionNotEnabled": "A encriptação não está ativada", + "@encryptionNotEnabled": { + "type": "text", + "placeholders": {} + }, + "endedTheCall": "{senderName} terminou a chamada", + "@endedTheCall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "enterAnEmailAddress": "Insere um endereço de correio eletrónico", + "@enterAnEmailAddress": { + "type": "text", + "placeholders": {} + }, + "homeserver": "Servidor", + "@homeserver": {}, + "enterYourHomeserver": "Insere o teu servidor", + "@enterYourHomeserver": { + "type": "text", + "placeholders": {} + }, + "errorObtainingLocation": "Erro ao obter localização: {error}", + "@errorObtainingLocation": { + "type": "text", + "placeholders": { + "error": {} + } + }, + "everythingReady": "Tudo a postos!", + "@everythingReady": { + "type": "text", + "placeholders": {} + }, + "extremeOffensive": "Extremamente ofensivo", + "@extremeOffensive": { + "type": "text", + "placeholders": {} + }, + "fileName": "Nome do ficheiro", + "@fileName": { + "type": "text", + "placeholders": {} + }, + "fluffychat": "FluffyChat", + "@fluffychat": { + "type": "text", + "placeholders": {} + }, + "fontSize": "Tamanho da letra", + "@fontSize": { + "type": "text", + "placeholders": {} + }, + "forward": "Reencaminhar", + "@forward": { + "type": "text", + "placeholders": {} + }, + "goToTheNewRoom": "Ir para a nova sala", + "@goToTheNewRoom": { + "type": "text", + "placeholders": {} + }, + "group": "Grupo", + "@group": { + "type": "text", + "placeholders": {} + }, + "groupIsPublic": "O grupo é público", + "@groupIsPublic": { + "type": "text", + "placeholders": {} + }, + "groups": "Grupos", + "@groups": { + "type": "text", + "placeholders": {} + }, + "groupWith": "Grupo com {displayname}", + "@groupWith": { + "type": "text", + "placeholders": { + "displayname": {} + } + }, + "guestsAreForbidden": "São proibidos visitantes", + "@guestsAreForbidden": { + "type": "text", + "placeholders": {} + }, + "guestsCanJoin": "Podem entrar visitantes", + "@guestsCanJoin": { + "type": "text", + "placeholders": {} + }, + "hasWithdrawnTheInvitationFor": "{username} revogou o convite para {targetName}", + "@hasWithdrawnTheInvitationFor": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "help": "Ajuda", + "@help": { + "type": "text", + "placeholders": {} + }, + "hideRedactedEvents": "Esconder eventos eliminados", + "@hideRedactedEvents": { + "type": "text", + "placeholders": {} + }, + "hideUnknownEvents": "Esconder eventos desconhecidos", + "@hideUnknownEvents": { + "type": "text", + "placeholders": {} + }, + "howOffensiveIsThisContent": "Quão ofensivo é este conteúdo?", + "@howOffensiveIsThisContent": { + "type": "text", + "placeholders": {} + }, + "id": "ID", + "@id": { + "type": "text", + "placeholders": {} + }, + "identity": "Identidade", + "@identity": { + "type": "text", + "placeholders": {} + }, + "ignore": "Ignorar", + "@ignore": { + "type": "text", + "placeholders": {} + }, + "ignoredUsers": "Utilizadores ignorados", + "@ignoredUsers": { + "type": "text", + "placeholders": {} + }, + "iHaveClickedOnLink": "Eu cliquei na ligação", + "@iHaveClickedOnLink": { + "type": "text", + "placeholders": {} + }, + "incorrectPassphraseOrKey": "Senha ou chave de recuperação incorretos", + "@incorrectPassphraseOrKey": { + "type": "text", + "placeholders": {} + }, + "inoffensive": "Inofensivo", + "@inoffensive": { + "type": "text", + "placeholders": {} + }, + "inviteContact": "Convidar contacto", + "@inviteContact": { + "type": "text", + "placeholders": {} + }, + "inviteContactToGroup": "Convidar contacto para {groupName}", + "@inviteContactToGroup": { + "type": "text", + "placeholders": { + "groupName": {} + } + }, + "invited": "Convidado(a)", + "@invited": { + "type": "text", + "placeholders": {} + }, + "invitedUser": "{username} convidou {targetName}", + "@invitedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "invitedUsersOnly": "Utilizadores(as) convidados(as) apenas", + "@invitedUsersOnly": { + "type": "text", + "placeholders": {} + }, + "inviteForMe": "Convite para mim", + "@inviteForMe": { + "type": "text", + "placeholders": {} + }, + "inviteText": "{username} convidou-te para o FluffyChat.\n1. Instala o FluffyChat: https://fluffychat.im\n2. Regista-te ou inicia sessão.\n3. Abre a ligação de convite: {link}", + "@inviteText": { + "type": "text", + "placeholders": { + "username": {}, + "link": {} + } + }, + "isTyping": "está a escrever…", + "@isTyping": { + "type": "text", + "placeholders": {} + }, + "joinedTheChat": "{username} entrou na conversa", + "@joinedTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "joinRoom": "Entrar na sala", + "@joinRoom": { + "type": "text", + "placeholders": {} + }, + "kicked": "{username} expulsou {targetName}", + "@kicked": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "kickedAndBanned": "{username} expulsou e baniu {targetName}", + "@kickedAndBanned": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "kickFromChat": "Expulsar da conversa", + "@kickFromChat": { + "type": "text", + "placeholders": {} + }, + "lastActiveAgo": "Ativo(a) pela última vez: {localizedTimeShort}", + "@lastActiveAgo": { + "type": "text", + "placeholders": { + "localizedTimeShort": {} + } + }, + "leave": "Sair", + "@leave": { + "type": "text", + "placeholders": {} + }, + "leftTheChat": "Saiu da conversa", + "@leftTheChat": { + "type": "text", + "placeholders": {} + }, + "license": "Licença", + "@license": { + "type": "text", + "placeholders": {} + }, + "lightTheme": "Claro", + "@lightTheme": { + "type": "text", + "placeholders": {} + }, + "loadCountMoreParticipants": "Carregar mais {count} participantes", + "@loadCountMoreParticipants": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "loadingPleaseWait": "A carregar... Por favor aguarde.", + "@loadingPleaseWait": { + "type": "text", + "placeholders": {} + }, + "loadMore": "Carregar mais…", + "@loadMore": { + "type": "text", + "placeholders": {} + }, + "locationDisabledNotice": "Os serviços de localização estão desativados. Por favor, ativa-os para poder partilhar a sua localização.", + "@locationDisabledNotice": { + "type": "text", + "placeholders": {} + }, + "locationPermissionDeniedNotice": "Permissão de localização recusada. Por favor, concede permissão para poderes partilhar a tua posição.", + "@locationPermissionDeniedNotice": { + "type": "text", + "placeholders": {} + }, + "login": "Entrar", + "@login": { + "type": "text", + "placeholders": {} + }, + "logInTo": "Entrar em {homeserver}", + "@logInTo": { + "type": "text", + "placeholders": { + "homeserver": {} + } + }, + "logout": "Sair", + "@logout": { + "type": "text", + "placeholders": {} + }, + "memberChanges": "Alterações de membros", + "@memberChanges": { + "type": "text", + "placeholders": {} + }, + "mention": "Mencionar", + "@mention": { + "type": "text", + "placeholders": {} + }, + "messages": "Mensagens", + "@messages": { + "type": "text", + "placeholders": {} + }, + "moderator": "Moderador", + "@moderator": { + "type": "text", + "placeholders": {} + }, + "muteChat": "Silenciar conversa", + "@muteChat": { + "type": "text", + "placeholders": {} + }, + "needPantalaimonWarning": "Por favor,", + "@needPantalaimonWarning": { + "type": "text", + "placeholders": {} + }, + "newChat": "Nova conversa", + "@newChat": { + "type": "text", + "placeholders": {} + }, + "newMessageInFluffyChat": "Nova mensagem no FluffyChat", + "@newMessageInFluffyChat": { + "type": "text", + "placeholders": {} + }, + "newVerificationRequest": "Novo pedido de verificação!", + "@newVerificationRequest": { + "type": "text", + "placeholders": {} + }, + "next": "Próximo", + "@next": { + "type": "text", + "placeholders": {} + }, + "no": "Não", + "@no": { + "type": "text", + "placeholders": {} + }, + "noConnectionToTheServer": "Nenhuma ligação ao servidor", + "@noConnectionToTheServer": { + "type": "text", + "placeholders": {} + }, + "noEmotesFound": "Nenhuns emotes encontrados. 😕", + "@noEmotesFound": { + "type": "text", + "placeholders": {} + }, + "noEncryptionForPublicRooms": "Só podes ativar a encriptação quando a sala não for publicamente acessível.", + "@noEncryptionForPublicRooms": { + "type": "text", + "placeholders": {} + }, + "noGoogleServicesWarning": "Parece que não tens nenhuns serviços da Google no seu telemóvel. É uma boa decisão para a sua privacidade! Para receber notificações instantâneas no FluffyChat, recomendamos que uses https://microg.org/ ou https://unifiedpush.org/.", + "@noGoogleServicesWarning": { + "type": "text", + "placeholders": {} + }, + "noMatrixServer": "{server1} não é um servidor Matrix, usar {server2}?", + "@noMatrixServer": { + "type": "text", + "placeholders": { + "server1": {}, + "server2": {} + } + }, + "none": "Nenhum", + "@none": { + "type": "text", + "placeholders": {} + }, + "changedTheChatAvatar": "{username} alterou o avatar da conversa", + "@changedTheChatAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheHistoryVisibilityTo": "{username} alterou a visibilidade do histórico para: {rules}", + "@changedTheHistoryVisibilityTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "changedTheChatDescriptionTo": "{username} alterou a descrição da conversa para: '{description}'", + "@changedTheChatDescriptionTo": { + "type": "text", + "placeholders": { + "username": {}, + "description": {} + } + }, + "changedTheChatNameTo": "{username} alterou o nome da conversa para: '{chatname}'", + "@changedTheChatNameTo": { + "type": "text", + "placeholders": { + "username": {}, + "chatname": {} + } + }, + "changedTheGuestAccessRules": "{username} alterou as regras de acesso de visitantes", + "@changedTheGuestAccessRules": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheHistoryVisibility": "{username} alterou a visibilidade do histórico", + "@changedTheHistoryVisibility": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sendAMessage": "Enviar a mensagem", + "@sendAMessage": { + "type": "text", + "placeholders": {} + }, + "sendAudio": "Enviar áudio", + "@sendAudio": { + "type": "text", + "placeholders": {} + }, + "sendAsText": "Enviar como texto", + "@sendAsText": { + "type": "text" + }, + "send": "Enviar", + "@send": { + "type": "text", + "placeholders": {} + }, + "appLock": "Bloqueio da aplicação", + "@appLock": { + "type": "text", + "placeholders": {} + }, + "noPasswordRecoveryDescription": "Ainda não adicionaste uma forma de recuperar a tua palavra-passe.", + "@noPasswordRecoveryDescription": { + "type": "text", + "placeholders": {} + }, + "noPermission": "Sem permissão", + "@noPermission": { + "type": "text", + "placeholders": {} + }, + "noRoomsFound": "Não foram encontradas nenhumas salas…", + "@noRoomsFound": { + "type": "text", + "placeholders": {} + }, + "notifications": "Notificações", + "@notifications": { + "type": "text", + "placeholders": {} + }, + "notificationsEnabledForThisAccount": "Notificações ativadas para esta conta", + "@notificationsEnabledForThisAccount": { + "type": "text", + "placeholders": {} + }, + "numUsersTyping": "Estão {count} utilizadores(as) a escrever…", + "@numUsersTyping": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "obtainingLocation": "A obter localização…", + "@obtainingLocation": { + "type": "text", + "placeholders": {} + }, + "offensive": "Offensivo", + "@offensive": { + "type": "text", + "placeholders": {} + }, + "offline": "Offline", + "@offline": { + "type": "text", + "placeholders": {} + }, + "ok": "ok", + "@ok": { + "type": "text", + "placeholders": {} + }, + "online": "Online", + "@online": { + "type": "text", + "placeholders": {} + }, + "onlineKeyBackupEnabled": "A cópia de segurança online de chaves está ativada", + "@onlineKeyBackupEnabled": { + "type": "text", + "placeholders": {} + }, + "oopsPushError": "Ups! Infelizmente, ocorreu um erro ao configurar as notificações instantâneas.", + "@oopsPushError": { + "type": "text", + "placeholders": {} + }, + "oopsSomethingWentWrong": "Ups, algo correu mal…", + "@oopsSomethingWentWrong": { + "type": "text", + "placeholders": {} + }, + "openAppToReadMessages": "Abrir aplicação para ler mensagens", + "@openAppToReadMessages": { + "type": "text", + "placeholders": {} + }, + "openCamera": "Abrir câmara", + "@openCamera": { + "type": "text", + "placeholders": {} + }, + "oneClientLoggedOut": "Um dos teus clientes terminou sessão", + "@oneClientLoggedOut": {}, + "addAccount": "Adicionar conta", + "@addAccount": {}, + "editBundlesForAccount": "Editar pacotes para esta conta", + "@editBundlesForAccount": {}, + "addToBundle": "Adicionar ao pacote", + "@addToBundle": {}, + "removeFromBundle": "Remover deste pacote", + "@removeFromBundle": {}, + "bundleName": "Nome do pacote", + "@bundleName": {}, + "enableMultiAccounts": "(BETA) Ativar múltiplas contas neste dispositivo", + "@enableMultiAccounts": {}, + "openInMaps": "Abrir nos mapas", + "@openInMaps": { + "type": "text", + "placeholders": {} + }, + "link": "Ligação", + "@link": {}, + "serverRequiresEmail": "Este servidor precisa de validar o teu endereço de correio eletrónico para o registo.", + "@serverRequiresEmail": {}, + "or": "Ou", + "@or": { + "type": "text", + "placeholders": {} + }, + "participant": "Participante", + "@participant": { + "type": "text", + "placeholders": {} + }, + "passphraseOrKey": "senha ou chave de recuperação", + "@passphraseOrKey": { + "type": "text", + "placeholders": {} + }, + "password": "Palavra-passe", + "@password": { + "type": "text", + "placeholders": {} + }, + "passwordForgotten": "Palavra-passe esquecida", + "@passwordForgotten": { + "type": "text", + "placeholders": {} + }, + "passwordHasBeenChanged": "A palavra-passe foi alterada", + "@passwordHasBeenChanged": { + "type": "text", + "placeholders": {} + }, + "passwordRecovery": "Recuperação de palavra-passe", + "@passwordRecovery": { + "type": "text", + "placeholders": {} + }, + "people": "Pessoas", + "@people": { + "type": "text", + "placeholders": {} + }, + "pickImage": "Escolher uma imagem", + "@pickImage": { + "type": "text", + "placeholders": {} + }, + "pin": "Afixar", + "@pin": { + "type": "text", + "placeholders": {} + }, + "play": "Reproduzir {fileName}", + "@play": { + "type": "text", + "placeholders": { + "fileName": {} + } + }, + "pleaseChoose": "Por favor, escolhe", + "@pleaseChoose": { + "type": "text", + "placeholders": {} + }, + "pleaseChooseAPasscode": "Por favor, escolhe um código-passe", + "@pleaseChooseAPasscode": { + "type": "text", + "placeholders": {} + }, + "pleaseClickOnLink": "Por favor, clica na ligação no correio eletrónico e depois continua.", + "@pleaseClickOnLink": { + "type": "text", + "placeholders": {} + }, + "pleaseEnter4Digits": "Por favor, insere 4 dígitos ou deixa vazio para desativar o bloqueio da aplicação.", + "@pleaseEnter4Digits": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourPassword": "Por favor, insere a tua palavra-passe", + "@pleaseEnterYourPassword": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourPin": "Por favor, insere o teu código", + "@pleaseEnterYourPin": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourUsername": "Por favor, insere o teu nome de utilizador", + "@pleaseEnterYourUsername": { + "type": "text", + "placeholders": {} + }, + "pleaseFollowInstructionsOnWeb": "Por favor, segue as instruções no website e clica em \"Seguinte\".", + "@pleaseFollowInstructionsOnWeb": { + "type": "text", + "placeholders": {} + }, + "privacy": "Privacidade", + "@privacy": { + "type": "text", + "placeholders": {} + }, + "publicRooms": "Salas públicas", + "@publicRooms": { + "type": "text", + "placeholders": {} + }, + "reason": "Razão", + "@reason": { + "type": "text", + "placeholders": {} + }, + "redactedAnEvent": "{username} eliminou um evento", + "@redactedAnEvent": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "recording": "A gravar", + "@recording": { + "type": "text", + "placeholders": {} + }, + "redactMessage": "Eliminar mensagem", + "@redactMessage": { + "type": "text", + "placeholders": {} + }, + "register": "Registar", + "@register": { + "type": "text", + "placeholders": {} + }, + "reject": "Rejeitar", + "@reject": { + "type": "text", + "placeholders": {} + }, + "rejectedTheInvitation": "{username} rejeitou o convite", + "@rejectedTheInvitation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "rejoin": "Reentrar", + "@rejoin": { + "type": "text", + "placeholders": {} + }, + "remove": "Remover", + "@remove": { + "type": "text", + "placeholders": {} + }, + "removeAllOtherDevices": "Remover todos os outros dispositivos", + "@removeAllOtherDevices": { + "type": "text", + "placeholders": {} + }, + "removedBy": "Removido por {username}", + "@removedBy": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "removeDevice": "Remover dispositivo", + "@removeDevice": { + "type": "text", + "placeholders": {} + }, + "unbanFromChat": "Perdoar nesta conversa", + "@unbanFromChat": { + "type": "text", + "placeholders": {} + }, + "removeYourAvatar": "Remover o teu avatar", + "@removeYourAvatar": { + "type": "text", + "placeholders": {} + }, + "renderRichContent": "Exibir conteúdo de mensagem rico", + "@renderRichContent": { + "type": "text", + "placeholders": {} + }, + "replaceRoomWithNewerVersion": "Substituir sala com versão mais recente", + "@replaceRoomWithNewerVersion": { + "type": "text", + "placeholders": {} + }, + "reply": "Responder", + "@reply": { + "type": "text", + "placeholders": {} + }, + "reportMessage": "Reportar mensagem", + "@reportMessage": { + "type": "text", + "placeholders": {} + }, + "requestPermission": "Pedir permissão", + "@requestPermission": { + "type": "text", + "placeholders": {} + }, + "roomHasBeenUpgraded": "A sala foi atualizada", + "@roomHasBeenUpgraded": { + "type": "text", + "placeholders": {} + }, + "roomVersion": "Versão da sala", + "@roomVersion": { + "type": "text", + "placeholders": {} + }, + "saveFile": "Guardar ficheiro", + "@saveFile": { + "type": "text", + "placeholders": {} + }, + "search": "Procurar", + "@search": { + "type": "text", + "placeholders": {} + }, + "security": "Segurança", + "@security": { + "type": "text", + "placeholders": {} + }, + "seenByUser": "Visto por {username}", + "@seenByUser": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sendFile": "Enviar ficheiro", + "@sendFile": { + "type": "text", + "placeholders": {} + }, + "sendImage": "Enviar imagem", + "@sendImage": { + "type": "text", + "placeholders": {} + }, + "sendMessages": "Enviar mensagens", + "@sendMessages": { + "type": "text", + "placeholders": {} + }, + "sendOriginal": "Enviar original", + "@sendOriginal": { + "type": "text", + "placeholders": {} + }, + "sendSticker": "Enviar autocolante", + "@sendSticker": { + "type": "text", + "placeholders": {} + }, + "sendVideo": "Enviar vídeo", + "@sendVideo": { + "type": "text", + "placeholders": {} + }, + "sentAFile": "{username} enviar um ficheiro", + "@sentAFile": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAnAudio": "{username} enviar um áudio", + "@sentAnAudio": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAPicture": "{username} enviar uma imagem", + "@sentAPicture": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentASticker": "{username} enviou um autocolante", + "@sentASticker": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAVideo": "{username} enviou um vídeo", + "@sentAVideo": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "commandHint_clearcache": "Limpar cache", + "@commandHint_clearcache": { + "type": "text", + "description": "Usage hint for the command /clearcache" + }, + "commandHint_create": "Criar uma conversa de grupo vazia\nUsa --no-encryption para desativar a encriptação", + "@commandHint_create": { + "type": "text", + "description": "Usage hint for the command /create" + }, + "commandHint_discardsession": "Descartar sessão", + "@commandHint_discardsession": { + "type": "text", + "description": "Usage hint for the command /discardsession" + }, + "commandHint_dm": "Iniciar uma conversa direta\nUsa --no-encryption para desativar a encriptação", + "@commandHint_dm": { + "type": "text", + "description": "Usage hint for the command /dm" + }, + "dehydrate": "Exportar sessão e limpar dispositivo", + "@dehydrate": {}, + "dehydrateWarning": "Esta ação não pode ser revertida. Assegura-te que guardas bem a cópia de segurança.", + "@dehydrateWarning": {}, + "hydrateTorLong": "Exportaste a tua sessão na última vez que estiveste no TOR? Importa-a rapidamente e continua a conversar.", + "@hydrateTorLong": {}, + "dehydrateTor": "Utilizadores do TOR: Exportar sessão", + "@dehydrateTor": {}, + "hydrate": "Restaurar a partir de cópia de segurança", + "@hydrate": {}, + "hydrateTor": "Utilizadores do TOR: Importar sessão", + "@hydrateTor": {}, + "dehydrateTorLong": "Para utilizadores do TOR, é recomendado exportar a sessão antes de fechar a janela.", + "@dehydrateTorLong": {} +} From bcaa57a62c4223c2e0972eb4533b0dad052ddf45 Mon Sep 17 00:00:00 2001 From: Anonymous Date: Thu, 25 Jul 2024 05:09:49 +0000 Subject: [PATCH 121/288] Translated using Weblate (Slovenian) Currently translated at 14.8% (97 of 652 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/sl/ --- assets/l10n/intl_sl.arb | 2557 +++++++++------------------------------ 1 file changed, 574 insertions(+), 1983 deletions(-) diff --git a/assets/l10n/intl_sl.arb b/assets/l10n/intl_sl.arb index c826f047d5..b55d8dc7d2 100644 --- a/assets/l10n/intl_sl.arb +++ b/assets/l10n/intl_sl.arb @@ -1,1984 +1,575 @@ { - "repeatPassword": "Ponovite geslo", - "@repeatPassword": {}, - "about": "O aplikaciji", - "@about": { - "type": "text", - "placeholders": {} - }, - "accept": "Sprejmi", - "@accept": { - "type": "text", - "placeholders": {} - }, - "account": "Račun", - "@account": { - "type": "text", - "placeholders": {} - }, - "activatedEndToEndEncryption": "Uporabnik {username} je aktiviral šifriranje od konca do konca", - "@activatedEndToEndEncryption": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "addEmail": "Dodajte e-pošto", - "@addEmail": { - "type": "text", - "placeholders": {} - }, - "addToSpace": "Dodajte v prostor", - "@addToSpace": {}, - "alias": "vzdevek", - "@alias": { - "type": "text", - "placeholders": {} - }, - "all": "Vse", - "@all": { - "type": "text", - "placeholders": {} - }, - "allChats": "Vsi klepeti", - "@allChats": { - "type": "text", - "placeholders": {} - }, - "answeredTheCall": "Oseba {senderName} je odgovorila na klic", - "@answeredTheCall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "anyoneCanJoin": "Pridruži se lahko vsak", - "@anyoneCanJoin": { - "type": "text", - "placeholders": {} - }, - "appLock": "Zaklepanje aplikacije", - "@appLock": { - "type": "text", - "placeholders": {} - }, - "askSSSSSign": "Če želite podpisati drugo osebo, vnesite geslo za varno trgovino ali obnovitveni ključ.", - "@askSSSSSign": { - "type": "text", - "placeholders": {} - }, - "askVerificationRequest": "Ali želite sprejeti to zahtevo za preverjanje od {username}?", - "@askVerificationRequest": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "autoplayImages": "Samodejno predvajajte animirane nalepke in čustva", - "@autoplayImages": { - "type": "text", - "placeholder": {} - }, - "badServerLoginTypesException": "Domači strežnik podpira vrste prijave:\n{serverVersions}\nToda ta aplikacija podpira samo:\n{supportedVersions}", - "@badServerLoginTypesException": { - "type": "text", - "placeholders": { - "serverVersions": {}, - "supportedVersions": {} - } - }, - "sendOnEnter": "Pošlji ob vstopu", - "@sendOnEnter": {}, - "banFromChat": "Prepoved klepeta", - "@banFromChat": { - "type": "text", - "placeholders": {} - }, - "banned": "Prepovedano", - "@banned": { - "type": "text", - "placeholders": {} - }, - "bannedUser": "{username} je prepovedan v {targetName}", - "@bannedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "blockDevice": "Blokirana naprava", - "@blockDevice": { - "type": "text", - "placeholders": {} - }, - "blocked": "Blokirano", - "@blocked": { - "type": "text", - "placeholders": {} - }, - "botMessages": "Botova sporočila", - "@botMessages": { - "type": "text", - "placeholders": {} - }, - "cancel": "Prekliči", - "@cancel": { - "type": "text", - "placeholders": {} - }, - "cantOpenUri": "URI-ja {uri} ni mogoče odpreti", - "@cantOpenUri": { - "type": "text", - "placeholders": { - "uri": {} - } - }, - "changedTheChatAvatar": "{username} je spremenil avatar za klepet", - "@changedTheChatAvatar": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheChatPermissions": "{username} je spremenila dovoljenja za klepet", - "@changedTheChatPermissions": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheDisplaynameTo": "{username} je spremenil svoje prikazno ime v: '{displayname}'", - "@changedTheDisplaynameTo": { - "type": "text", - "placeholders": { - "username": {}, - "displayname": {} - } - }, - "changedTheGuestAccessRules": "{username} je spremenila pravila dostopa za goste", - "@changedTheGuestAccessRules": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheGuestAccessRulesTo": "{username} je spremenila pravila dostopa za goste v: {rules}", - "@changedTheGuestAccessRulesTo": { - "type": "text", - "placeholders": { - "username": {}, - "rules": {} - } - }, - "changedTheHistoryVisibilityTo": "{username} je spremenil vidnost zgodovine v: {rules}", - "@changedTheHistoryVisibilityTo": { - "type": "text", - "placeholders": { - "username": {}, - "rules": {} - } - }, - "changedTheJoinRules": "{username} je spremenil pravila za pridružitev", - "@changedTheJoinRules": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheJoinRulesTo": "{username} je spremenila pravila pridružitve v: {joinRules}", - "@changedTheJoinRulesTo": { - "type": "text", - "placeholders": { - "username": {}, - "joinRules": {} - } - }, - "changedTheProfileAvatar": "{username} je spremenil avatar", - "@changedTheProfileAvatar": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheRoomAliases": "{username} je spremenil vzdevke sobe", - "@changedTheRoomAliases": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheRoomInvitationLink": "{username} je spremenil povezavo za povabilo", - "@changedTheRoomInvitationLink": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changePassword": "Spremeni geslo", - "@changePassword": { - "type": "text", - "placeholders": {} - }, - "changeTheHomeserver": "Spremenite domači strežnik", - "@changeTheHomeserver": { - "type": "text", - "placeholders": {} - }, - "changeTheme": "Spremenite svoj slog", - "@changeTheme": { - "type": "text", - "placeholders": {} - }, - "changeTheNameOfTheGroup": "Spremenite ime skupine", - "@changeTheNameOfTheGroup": { - "type": "text", - "placeholders": {} - }, - "changeYourAvatar": "Spremenite svoj avatar", - "@changeYourAvatar": { - "type": "text", - "placeholders": {} - }, - "chat": "Klepet", - "@chat": { - "type": "text", - "placeholders": {} - }, - "yourChatBackupHasBeenSetUp": "Varnostna kopija klepeta je nastavljena.", - "@yourChatBackupHasBeenSetUp": {}, - "chatBackup": "Varnostno kopiranje klepeta", - "@chatBackup": { - "type": "text", - "placeholders": {} - }, - "chatDetails": "Podrobnosti klepeta", - "@chatDetails": { - "type": "text", - "placeholders": {} - }, - "chatHasBeenAddedToThisSpace": "Klepet je bil dodan v ta prostor", - "@chatHasBeenAddedToThisSpace": {}, - "chats": "Klepeti", - "@chats": { - "type": "text", - "placeholders": {} - }, - "chooseAStrongPassword": "Izberite močno geslo", - "@chooseAStrongPassword": { - "type": "text", - "placeholders": {} - }, - "clearArchive": "Počisti arhiv", - "@clearArchive": {}, - "close": "Zapri", - "@close": { - "type": "text", - "placeholders": {} - }, - "commandHint_ban": "Izključi določenega uporabnika iz te sobe", - "@commandHint_ban": { - "type": "text", - "description": "Usage hint for the command /ban" - }, - "commandHint_html": "Pošljite besedilo v obliki HTML", - "@commandHint_html": { - "type": "text", - "description": "Usage hint for the command /html" - }, - "commandHint_invite": "Povabi danega uporabnika v to sobo", - "@commandHint_invite": { - "type": "text", - "description": "Usage hint for the command /invite" - }, - "commandHint_join": "Pridružite se dani sobi", - "@commandHint_join": { - "type": "text", - "description": "Usage hint for the command /join" - }, - "commandHint_kick": "Odstranite danega uporabnika iz te sobe", - "@commandHint_kick": { - "type": "text", - "description": "Usage hint for the command /kick" - }, - "commandHint_me": "Opisi sebe", - "@commandHint_me": { - "type": "text", - "description": "Usage hint for the command /me" - }, - "commandHint_myroomavatar": "Nastavite svojo sliko za to sobo", - "@commandHint_myroomavatar": { - "type": "text", - "description": "Usage hint for the command /myroomavatar" - }, - "commandHint_op": "Nastavite raven moči danega uporabnika (privzeto: 50)", - "@commandHint_op": { - "type": "text", - "description": "Usage hint for the command /op" - }, - "commandHint_react": "Pošljite odgovor kot reakcijo", - "@commandHint_react": { - "type": "text", - "description": "Usage hint for the command /react" - }, - "commandHint_send": "Pošlji besedilo", - "@commandHint_send": { - "type": "text", - "description": "Usage hint for the command /send" - }, - "commandHint_unban": "Prekliči izključitev določenega uporabnika iz te sobe", - "@commandHint_unban": { - "type": "text", - "description": "Usage hint for the command /unban" - }, - "commandInvalid": "Ukaz ni veljaven", - "@commandInvalid": { - "type": "text" - }, - "commandMissing": "{command} is not a command.", - "@commandMissing": { - "type": "text", - "placeholders": { - "command": {} - }, - "description": "State that {command} is not a valid /command." - }, - "compareEmojiMatch": "Primerjajte in se prepričajte, da se naslednji emoji ujemajo s tistimi iz druge naprave:", - "@compareEmojiMatch": { - "type": "text", - "placeholders": {} - }, - "compareNumbersMatch": "Primerjajte in se prepričajte, da se naslednje številke ujemajo s številkami druge naprave:", - "@compareNumbersMatch": { - "type": "text", - "placeholders": {} - }, - "configureChat": "Konfigurirajte klepet", - "@configureChat": { - "type": "text", - "placeholders": {} - }, - "confirm": "Potrdi", - "@confirm": { - "type": "text", - "placeholders": {} - }, - "containsDisplayName": "Vsebuje prikazno ime", - "@containsDisplayName": { - "type": "text", - "placeholders": {} - }, - "containsUserName": "Vsebuje uporabniško ime", - "@containsUserName": { - "type": "text", - "placeholders": {} - }, - "archive": "Arhiv", - "@archive": { - "type": "text", - "placeholders": {} - }, - "areYouSure": "Ali si prepričan?", - "@areYouSure": { - "type": "text", - "placeholders": {} - }, - "acceptedTheInvitation": "{username} je sprejel povabilo", - "@acceptedTheInvitation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "areYouSureYouWantToLogout": "Ali ste prepričani, da se želite odjaviti?", - "@areYouSureYouWantToLogout": { - "type": "text", - "placeholders": {} - }, - "changedTheChatDescriptionTo": "{username} je spremenil opis klepeta v: '{description}'", - "@changedTheChatDescriptionTo": { - "type": "text", - "placeholders": { - "username": {}, - "description": {} - } - }, - "areGuestsAllowedToJoin": "Ali se lahko gostujoči uporabniki pridružijo", - "@areGuestsAllowedToJoin": { - "type": "text", - "placeholders": {} - }, - "admin": "Admin", - "@admin": { - "type": "text", - "placeholders": {} - }, - "badServerVersionsException": "Domači strežnik podpira različice Spec:\n{serverVersions}\nToda ta aplikacija podpira samo {supportedVersions}", - "@badServerVersionsException": { - "type": "text", - "placeholders": { - "serverVersions": {}, - "supportedVersions": {} - } - }, - "changedTheChatNameTo": "{username} je spremenil ime klepeta v: '{chatname}'", - "@changedTheChatNameTo": { - "type": "text", - "placeholders": { - "username": {}, - "chatname": {} - } - }, - "changeDeviceName": "Spremenite ime naprave", - "@changeDeviceName": { - "type": "text", - "placeholders": {} - }, - "changedTheHistoryVisibility": "{username} je spremenila vidnost zgodovine", - "@changedTheHistoryVisibility": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "channelCorruptedDecryptError": "Šifriranje je poškodovano", - "@channelCorruptedDecryptError": { - "type": "text", - "placeholders": {} - }, - "contentHasBeenReported": "Vsebina je bila prijavljena skrbnikom strežnika", - "@contentHasBeenReported": { - "type": "text", - "placeholders": {} - }, - "chatBackupDescription": "Varnostna kopija klepeta je zavarovana z varnostnim ključem. Prosimo, pazite, da ga ne izgubite.", - "@chatBackupDescription": { - "type": "text", - "placeholders": {} - }, - "commandHint_myroomnick": "Nastavite prikazno ime za to sobo", - "@commandHint_myroomnick": { - "type": "text", - "description": "Usage hint for the command /myroomnick" - }, - "connect": "Povežite se", - "@connect": { - "type": "text", - "placeholders": {} - }, - "contactHasBeenInvitedToTheGroup": "Kontakt je bil povabljen v skupino", - "@contactHasBeenInvitedToTheGroup": { - "type": "text", - "placeholders": {} - }, - "commandHint_leave": "Zapusti to sobo", - "@commandHint_leave": { - "type": "text", - "description": "Usage hint for the command /leave" - }, - "commandHint_plain": "Pošlji neformatirano besedilo", - "@commandHint_plain": { - "type": "text", - "description": "Usage hint for the command /plain" - }, - "copiedToClipboard": "Kopirano v odložišče", - "@copiedToClipboard": { - "type": "text", - "placeholders": {} - }, - "copy": "Kopiraj", - "@copy": { - "type": "text", - "placeholders": {} - }, - "copyToClipboard": "Kopiraj v odložišče", - "@copyToClipboard": { - "type": "text", - "placeholders": {} - }, - "couldNotDecryptMessage": "Sporočila ni bilo mogoče dešifrirati: {error}", - "@couldNotDecryptMessage": { - "type": "text", - "placeholders": { - "error": {} - } - }, - "countParticipants": "{count} udeležencev", - "@countParticipants": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "create": "Ustvari", - "@create": { - "type": "text", - "placeholders": {} - }, - "createNewSpace": "Nov prostor", - "@createNewSpace": { - "type": "text", - "placeholders": {} - }, - "currentlyActive": "Trenutno aktiven", - "@currentlyActive": { - "type": "text", - "placeholders": {} - }, - "darkTheme": "Temno", - "@darkTheme": { - "type": "text", - "placeholders": {} - }, - "defaultPermissionLevel": "Privzeta raven dovoljenja", - "@defaultPermissionLevel": { - "type": "text", - "placeholders": {} - }, - "dateWithYear": "{day}-{month}-{year}", - "@dateWithYear": { - "type": "text", - "placeholders": { - "year": {}, - "month": {}, - "day": {} - } - }, - "dateWithoutYear": "{month}-{day}", - "@dateWithoutYear": { - "type": "text", - "placeholders": { - "month": {}, - "day": {} - } - }, - "createdTheChat": "{username} je ustvaril klepet", - "@createdTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "dateAndTimeOfDay": "{date}, {timeOfDay}", - "@dateAndTimeOfDay": { - "type": "text", - "placeholders": { - "date": {}, - "timeOfDay": {} - } - }, - "deactivateAccountWarning": "S tem boste deaktivirali vaš uporabniški račun. Tega ni mogoče razveljaviti! Ali si prepričan?", - "@deactivateAccountWarning": { - "type": "text", - "placeholders": {} - }, - "@showPassword": { - "type": "text", - "placeholders": {} - }, - "@hugContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@passphraseOrKey": { - "type": "text", - "placeholders": {} - }, - "@pleaseEnterYourPassword": { - "type": "text", - "placeholders": {} - }, - "@theyMatch": { - "type": "text", - "placeholders": {} - }, - "@jumpToLastReadMessage": {}, - "@allRooms": { - "type": "text", - "placeholders": {} - }, - "@obtainingLocation": { - "type": "text", - "placeholders": {} - }, - "@commandHint_cuddle": {}, - "@widgetVideo": {}, - "@dismiss": {}, - "@unknownDevice": { - "type": "text", - "placeholders": {} - }, - "@emoteShortcode": { - "type": "text", - "placeholders": {} - }, - "@noEncryptionForPublicRooms": { - "type": "text", - "placeholders": {} - }, - "@reportErrorDescription": {}, - "@directChats": { - "type": "text", - "placeholders": {} - }, - "@setPermissionsLevel": { - "type": "text", - "placeholders": {} - }, - "@inviteContactToGroup": { - "type": "text", - "placeholders": { - "groupName": {} - } - }, - "@addAccount": {}, - "@reply": { - "type": "text", - "placeholders": {} - }, - "@removeYourAvatar": { - "type": "text", - "placeholders": {} - }, - "@unsupportedAndroidVersion": {}, - "@device": { - "type": "text", - "placeholders": {} - }, - "@widgetJitsi": {}, - "@youAreNoLongerParticipatingInThisChat": { - "type": "text", - "placeholders": {} - }, - "@encryption": { - "type": "text", - "placeholders": {} - }, - "@messageType": {}, - "@indexedDbErrorLong": {}, - "@oneClientLoggedOut": {}, - "@toggleMuted": { - "type": "text", - "placeholders": {} - }, - "@kicked": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "@title": { - "description": "Title for the application", - "type": "text", - "placeholders": {} - }, - "@verifySuccess": { - "type": "text", - "placeholders": {} - }, - "@sendFile": { - "type": "text", - "placeholders": {} - }, - "@newVerificationRequest": { - "type": "text", - "placeholders": {} - }, - "@startFirstChat": {}, - "@callingAccount": {}, - "@requestPermission": { - "type": "text", - "placeholders": {} - }, - "@sentAPicture": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@invited": { - "type": "text", - "placeholders": {} - }, - "@setColorTheme": {}, - "@nextAccount": {}, - "@commandHint_create": { - "type": "text", - "description": "Usage hint for the command /create" - }, - "@singlesignon": { - "type": "text", - "placeholders": {} - }, - "@warning": { - "type": "text", - "placeholders": {} - }, - "@password": { - "type": "text", - "placeholders": {} - }, - "@allSpaces": {}, - "@supposedMxid": { - "type": "text", - "placeholders": { - "mxid": {} - } - }, - "@editDisplayname": { - "type": "text", - "placeholders": {} - }, - "@user": {}, - "@roomVersion": { - "type": "text", - "placeholders": {} - }, - "@sentAFile": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@videoCall": { - "type": "text", - "placeholders": {} - }, - "@youAcceptedTheInvitation": {}, - "@noMatrixServer": { - "type": "text", - "placeholders": { - "server1": {}, - "server2": {} - } - }, - "@userAndOthersAreTyping": { - "type": "text", - "placeholders": { - "username": {}, - "count": {} - } - }, - "@youInvitedBy": { - "placeholders": { - "user": {} - } - }, - "@userIsTyping": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@openAppToReadMessages": { - "type": "text", - "placeholders": {} - }, - "@sentAVideo": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@banUserDescription": {}, - "@inviteContact": { - "type": "text", - "placeholders": {} - }, - "@widgetEtherpad": {}, - "@waitingPartnerAcceptRequest": { - "type": "text", - "placeholders": {} - }, - "@remove": { - "type": "text", - "placeholders": {} - }, - "@writeAMessage": { - "type": "text", - "placeholders": {} - }, - "@id": { - "type": "text", - "placeholders": {} - }, - "@removeDevicesDescription": {}, - "@separateChatTypes": { - "type": "text", - "placeholders": {} - }, - "@tryAgain": {}, - "@youKickedAndBanned": { - "placeholders": { - "user": {} - } - }, - "@removeDevice": { - "type": "text", - "placeholders": {} - }, - "@unbanUserDescription": {}, - "@userAndUserAreTyping": { - "type": "text", - "placeholders": { - "username": {}, - "username2": {} - } - }, - "@pleaseClickOnLink": { - "type": "text", - "placeholders": {} - }, - "@saveFile": { - "type": "text", - "placeholders": {} - }, - "@pickImage": { - "type": "text", - "placeholders": {} - }, - "@youRejectedTheInvitation": {}, - "@otherCallingPermissions": {}, - "@messagesStyle": {}, - "@invitedUsersOnly": { - "type": "text", - "placeholders": {} - }, - "@link": {}, - "@widgetUrlError": {}, - "@emailOrUsername": {}, - "@newSpaceDescription": {}, - "@chatDescription": {}, - "@callingAccountDetails": {}, - "@next": { - "type": "text", - "placeholders": {} - }, - "@pleaseFollowInstructionsOnWeb": { - "type": "text", - "placeholders": {} - }, - "@editRoomAliases": { - "type": "text", - "placeholders": {} - }, - "@enterSpace": {}, - "@encryptThisChat": {}, - "@fileName": { - "type": "text", - "placeholders": {} - }, - "@unavailable": { - "type": "text", - "placeholders": {} - }, - "@previousAccount": {}, - "@publicRooms": { - "type": "text", - "placeholders": {} - }, - "@fromTheInvitation": { - "type": "text", - "placeholders": {} - }, - "@sendMessages": { - "type": "text", - "placeholders": {} - }, - "@incorrectPassphraseOrKey": { - "type": "text", - "placeholders": {} - }, - "@emoteWarnNeedToPick": { - "type": "text", - "placeholders": {} - }, - "@reopenChat": {}, - "@pleaseEnterRecoveryKey": {}, - "@toggleFavorite": { - "type": "text", - "placeholders": {} - }, - "@no": { - "type": "text", - "placeholders": {} - }, - "@widgetNameError": {}, - "@inoffensive": { - "type": "text", - "placeholders": {} - }, - "@unpin": { - "type": "text", - "placeholders": {} - }, - "@addToBundle": {}, - "@reportMessage": { - "type": "text", - "placeholders": {} - }, - "@spaceIsPublic": { - "type": "text", - "placeholders": {} - }, - "@addWidget": {}, - "@removeAllOtherDevices": { - "type": "text", - "placeholders": {} - }, - "@unblockDevice": { - "type": "text", - "placeholders": {} - }, - "@countFiles": { - "placeholders": { - "count": {} - } - }, - "@noKeyForThisMessage": {}, - "@enableEncryptionWarning": { - "type": "text", - "placeholders": {} - }, - "@inviteText": { - "type": "text", - "placeholders": { - "username": {}, - "link": {} - } - }, - "@shareLocation": { - "type": "text", - "placeholders": {} - }, - "@reason": { - "type": "text", - "placeholders": {} - }, - "@commandHint_markasgroup": {}, - "@errorObtainingLocation": { - "type": "text", - "placeholders": { - "error": {} - } - }, - "@hydrateTor": {}, - "@pushNotificationsNotAvailable": {}, - "@passwordRecovery": { - "type": "text", - "placeholders": {} - }, - "@storeInAppleKeyChain": {}, - "@replaceRoomWithNewerVersion": { - "type": "text", - "placeholders": {} - }, - "@hydrate": {}, - "@invalidServerName": {}, - "@chatPermissions": {}, - "@voiceMessage": { - "type": "text", - "placeholders": {} - }, - "@wipeChatBackup": { - "type": "text", - "placeholders": {} - }, - "@sender": {}, - "@storeInAndroidKeystore": {}, - "@hideRedactedEvents": { - "type": "text", - "placeholders": {} - }, - "@online": { - "type": "text", - "placeholders": {} - }, - "@signInWithPassword": {}, - "@ignoredUsers": { - "type": "text", - "placeholders": {} - }, - "@lastActiveAgo": { - "type": "text", - "placeholders": { - "localizedTimeShort": {} - } - }, - "@weSentYouAnEmail": { - "type": "text", - "placeholders": {} - }, - "@offensive": { - "type": "text", - "placeholders": {} - }, - "@needPantalaimonWarning": { - "type": "text", - "placeholders": {} - }, - "@makeAdminDescription": {}, - "@edit": { - "type": "text", - "placeholders": {} - }, - "@loadMore": { - "type": "text", - "placeholders": {} - }, - "@noEmotesFound": { - "type": "text", - "placeholders": {} - }, - "@synchronizingPleaseWait": { - "type": "text", - "placeholders": {} - }, - "@transferFromAnotherDevice": { - "type": "text", - "placeholders": {} - }, - "@passwordHasBeenChanged": { - "type": "text", - "placeholders": {} - }, - "@pushRules": { - "type": "text", - "placeholders": {} - }, - "@goToTheNewRoom": { - "type": "text", - "placeholders": {} - }, - "@commandHint_clearcache": { - "type": "text", - "description": "Usage hint for the command /clearcache" - }, - "@loadingPleaseWait": { - "type": "text", - "placeholders": {} - }, - "@saveKeyManuallyDescription": {}, - "@none": { - "type": "text", - "placeholders": {} - }, - "@editBundlesForAccount": {}, - "@renderRichContent": { - "type": "text", - "placeholders": {} - }, - "@enableEncryption": { - "type": "text", - "placeholders": {} - }, - "@whyIsThisMessageEncrypted": {}, - "@unreadChats": { - "type": "text", - "placeholders": { - "unreadCount": {} - } - }, - "@rejectedTheInvitation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@setChatDescription": {}, - "@userLeftTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@spaceName": { - "type": "text", - "placeholders": {} - }, - "@importFromZipFile": {}, - "@toggleUnread": { - "type": "text", - "placeholders": {} - }, - "@or": { - "type": "text", - "placeholders": {} - }, - "@dehydrateWarning": {}, - "@sendOriginal": { - "type": "text", - "placeholders": {} - }, - "@noOtherDevicesFound": {}, - "@whoIsAllowedToJoinThisGroup": { - "type": "text", - "placeholders": {} - }, - "@emptyChat": { - "type": "text", - "placeholders": {} - }, - "@seenByUser": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@redactedBy": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@submit": { - "type": "text", - "placeholders": {} - }, - "@videoCallsBetaWarning": {}, - "@unmuteChat": { - "type": "text", - "placeholders": {} - }, - "@redactedAnEvent": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@participant": { - "type": "text", - "placeholders": {} - }, - "@logInTo": { - "type": "text", - "placeholders": { - "homeserver": {} - } - }, - "@yes": { - "type": "text", - "placeholders": {} - }, - "@signInWith": { - "type": "text", - "placeholders": { - "provider": {} - } - }, - "@username": { - "type": "text", - "placeholders": {} - }, - "@fileIsTooBigForServer": {}, - "@homeserver": {}, - "@help": { - "type": "text", - "placeholders": {} - }, - "@people": { - "type": "text", - "placeholders": {} - }, - "@leftTheChat": { - "type": "text", - "placeholders": {} - }, - "@verified": { - "type": "text", - "placeholders": {} - }, - "@setStatus": { - "type": "text", - "placeholders": {} - }, - "@groupWith": { - "type": "text", - "placeholders": { - "displayname": {} - } - }, - "@callingPermissions": {}, - "@delete": { - "type": "text", - "placeholders": {} - }, - "@newMessageInFluffyChat": { - "type": "text", - "placeholders": {} - }, - "@readUpToHere": {}, - "@start": {}, - "@downloadFile": { - "type": "text", - "placeholders": {} - }, - "@deviceId": { - "type": "text", - "placeholders": {} - }, - "@register": { - "type": "text", - "placeholders": {} - }, - "@unlockOldMessages": {}, - "@identity": { - "type": "text", - "placeholders": {} - }, - "@numChats": { - "type": "number", - "placeholders": { - "number": {} - } - }, - "@ignore": { - "type": "text", - "placeholders": {} - }, - "@recording": { - "type": "text", - "placeholders": {} - }, - "@moderator": { - "type": "text", - "placeholders": {} - }, - "@optionalRedactReason": {}, - "@waitingPartnerEmoji": { - "type": "text", - "placeholders": {} - }, - "@tryToSendAgain": { - "type": "text", - "placeholders": {} - }, - "@guestsCanJoin": { - "type": "text", - "placeholders": {} - }, - "@ok": { - "type": "text", - "placeholders": {} - }, - "@dehydrate": {}, - "@locationPermissionDeniedNotice": { - "type": "text", - "placeholders": {} - }, - "@send": { - "type": "text", - "placeholders": {} - }, - "@hasWithdrawnTheInvitationFor": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "@visibleForAllParticipants": { - "type": "text", - "placeholders": {} - }, - "@noRoomsFound": { - "type": "text", - "placeholders": {} - }, - "@sendAsText": { - "type": "text" - }, - "@inviteForMe": { - "type": "text", - "placeholders": {} - }, - "@archiveRoomDescription": {}, - "@exportEmotePack": {}, - "@sendSticker": { - "type": "text", - "placeholders": {} - }, - "@switchToAccount": { - "type": "number", - "placeholders": { - "number": {} - } - }, - "@setAsCanonicalAlias": { - "type": "text", - "placeholders": {} - }, - "@whyDoYouWantToReportThis": { - "type": "text", - "placeholders": {} - }, - "@locationDisabledNotice": { - "type": "text", - "placeholders": {} - }, - "@removedBy": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@newChat": { - "type": "text", - "placeholders": {} - }, - "@notifications": { - "type": "text", - "placeholders": {} - }, - "@emoteSettings": { - "type": "text", - "placeholders": {} - }, - "@experimentalVideoCalls": {}, - "@openCamera": { - "type": "text", - "placeholders": {} - }, - "@pleaseEnterRecoveryKeyDescription": {}, - "@guestsAreForbidden": { - "type": "text", - "placeholders": {} - }, - "@mention": { - "type": "text", - "placeholders": {} - }, - "@openInMaps": { - "type": "text", - "placeholders": {} - }, - "@withTheseAddressesRecoveryDescription": { - "type": "text", - "placeholders": {} - }, - "@inviteContactToGroupQuestion": {}, - "@emoteExists": { - "type": "text", - "placeholders": {} - }, - "@redactedByBecause": { - "type": "text", - "placeholders": { - "username": {}, - "reason": {} - } - }, - "@isTyping": { - "type": "text", - "placeholders": {} - }, - "@youHaveWithdrawnTheInvitationFor": { - "placeholders": { - "user": {} - } - }, - "@group": { - "type": "text", - "placeholders": {} - }, - "@leave": { - "type": "text", - "placeholders": {} - }, - "@skip": { - "type": "text", - "placeholders": {} - }, - "@appearOnTopDetails": {}, - "@roomHasBeenUpgraded": { - "type": "text", - "placeholders": {} - }, - "@enterRoom": {}, - "@enableEmotesGlobally": { - "type": "text", - "placeholders": {} - }, - "@pleaseChooseAPasscode": { - "type": "text", - "placeholders": {} - }, - "@noPasswordRecoveryDescription": { - "type": "text", - "placeholders": {} - }, - "@reportUser": {}, - "@sharedTheLocation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@onlineKeyBackupEnabled": { - "type": "text", - "placeholders": {} - }, - "@unbannedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "@confirmEventUnpin": {}, - "@youInvitedUser": { - "placeholders": { - "user": {} - } - }, - "@kickedAndBanned": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "@noConnectionToTheServer": { - "type": "text", - "placeholders": {} - }, - "@fileHasBeenSavedAt": { - "type": "text", - "placeholders": { - "path": {} - } - }, - "@license": { - "type": "text", - "placeholders": {} - }, - "@unbanFromChat": { - "type": "text", - "placeholders": {} - }, - "@redactMessageDescription": {}, - "@rejoin": { - "type": "text", - "placeholders": {} - }, - "@recoveryKey": {}, - "@redactMessage": { - "type": "text", - "placeholders": {} - }, - "@forward": { - "type": "text", - "placeholders": {} - }, - "@commandHint_discardsession": { - "type": "text", - "description": "Usage hint for the command /discardsession" - }, - "@invalidInput": {}, - "@hideUnknownEvents": { - "type": "text", - "placeholders": {} - }, - "@dehydrateTorLong": {}, - "@yourPublicKey": { - "type": "text", - "placeholders": {} - }, - "@tooManyRequestsWarning": { - "type": "text", - "placeholders": {} - }, - "@invitedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "@kickFromChat": { - "type": "text", - "placeholders": {} - }, - "@offline": { - "type": "text", - "placeholders": {} - }, - "@noPermission": { - "type": "text", - "placeholders": {} - }, - "@doNotShowAgain": {}, - "@report": {}, - "@status": { - "type": "text", - "placeholders": {} - }, - "@groupIsPublic": { - "type": "text", - "placeholders": {} - }, - "@verifyStart": { - "type": "text", - "placeholders": {} - }, - "@memberChanges": { - "type": "text", - "placeholders": {} - }, - "@joinRoom": { - "type": "text", - "placeholders": {} - }, - "@unverified": {}, - "@fluffychat": { - "type": "text", - "placeholders": {} - }, - "@howOffensiveIsThisContent": { - "type": "text", - "placeholders": {} - }, - "@serverRequiresEmail": {}, - "@hideUnimportantStateEvents": {}, - "@screenSharingTitle": {}, - "@widgetCustom": {}, - "@sentCallInformations": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@addToSpaceDescription": {}, - "@googlyEyesContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@youBannedUser": { - "placeholders": { - "user": {} - } - }, - "@theyDontMatch": { - "type": "text", - "placeholders": {} - }, - "@youHaveBeenBannedFromThisChat": { - "type": "text", - "placeholders": {} - }, - "@displaynameHasBeenChanged": { - "type": "text", - "placeholders": {} - }, - "@addChatDescription": {}, - "@sentAnAudio": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@editRoomAvatar": { - "type": "text", - "placeholders": {} - }, - "@encrypted": { - "type": "text", - "placeholders": {} - }, - "@hasKnocked": { - "placeholders": { - "user": {} - } - }, - "@publish": {}, - "@openLinkInBrowser": {}, - "@pleaseEnterYourUsername": { - "type": "text", - "placeholders": {} - }, - "@messageInfo": {}, - "@disableEncryptionWarning": {}, - "@directChat": {}, - "@encryptionNotEnabled": { - "type": "text", - "placeholders": {} - }, - "@wrongPinEntered": { - "type": "text", - "placeholders": { - "seconds": {} - } - }, - "@sendTypingNotifications": {}, - "@lightTheme": { - "type": "text", - "placeholders": {} - }, - "@inviteGroupChat": {}, - "@appearOnTop": {}, - "@invitePrivateChat": {}, - "@verifyTitle": { - "type": "text", - "placeholders": {} - }, - "@foregroundServiceRunning": {}, - "@enterAnEmailAddress": { - "type": "text", - "placeholders": {} - }, - "@voiceCall": {}, - "@unknownEncryptionAlgorithm": { - "type": "text", - "placeholders": {} - }, - "@importEmojis": {}, - "@wasDirectChatDisplayName": { - "type": "text", - "placeholders": { - "oldDisplayName": {} - } - }, - "@noChatDescriptionYet": {}, - "@removeFromBundle": {}, - "@numUsersTyping": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "@fontSize": { - "type": "text", - "placeholders": {} - }, - "@whoCanPerformWhichAction": { - "type": "text", - "placeholders": {} - }, - "@confirmMatrixId": {}, - "@learnMore": {}, - "@iHaveClickedOnLink": { - "type": "text", - "placeholders": {} - }, - "@you": { - "type": "text", - "placeholders": {} - }, - "@notAnImage": {}, - "@users": {}, - "@openGallery": {}, - "@chatDescriptionHasBeenChanged": {}, - "@search": { - "type": "text", - "placeholders": {} - }, - "@newGroup": {}, - "@bundleName": {}, - "@dehydrateTor": {}, - "@removeFromSpace": {}, - "@sourceCode": { - "type": "text", - "placeholders": {} - }, - "@roomUpgradeDescription": {}, - "@userSentUnknownEvent": { - "type": "text", - "placeholders": { - "username": {}, - "type": {} - } - }, - "@scanQrCode": {}, - "@logout": { - "type": "text", - "placeholders": {} - }, - "@pleaseEnterANumber": {}, - "@youKicked": { - "placeholders": { - "user": {} - } - }, - "@profileNotFound": {}, - "@jump": {}, - "@groups": { - "type": "text", - "placeholders": {} - }, - "@reactedWith": { - "type": "text", - "placeholders": { - "sender": {}, - "reaction": {} - } - }, - "@sorryThatsNotPossible": {}, - "@videoWithSize": { - "type": "text", - "placeholders": { - "size": {} - } - }, - "@oopsSomethingWentWrong": { - "type": "text", - "placeholders": {} - }, - "@loadCountMoreParticipants": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "@shareInviteLink": {}, - "@commandHint_markasdm": {}, - "@recoveryKeyLost": {}, - "@cuddleContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@messages": { - "type": "text", - "placeholders": {} - }, - "@login": { - "type": "text", - "placeholders": {} - }, - "@deviceKeys": {}, - "@waitingPartnerNumbers": { - "type": "text", - "placeholders": {} - }, - "@noGoogleServicesWarning": { - "type": "text", - "placeholders": {} - }, - "@everythingReady": { - "type": "text", - "placeholders": {} - }, - "@emoteKeyboardNoRecents": { - "type": "text", - "placeholders": {} - }, - "@setCustomEmotes": { - "type": "text", - "placeholders": {} - }, - "@startedACall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@emoteInvalid": { - "type": "text", - "placeholders": {} - }, - "@systemTheme": { - "type": "text", - "placeholders": {} - }, - "@notificationsEnabledForThisAccount": { - "type": "text", - "placeholders": {} - }, - "@deleteMessage": { - "type": "text", - "placeholders": {} - }, - "@visibilityOfTheChatHistory": { - "type": "text", - "placeholders": {} - }, - "@settings": { - "type": "text", - "placeholders": {} - }, - "@setTheme": {}, - "@youJoinedTheChat": {}, - "@wallpaper": { - "type": "text", - "placeholders": {} - }, - "@openVideoCamera": { - "type": "text", - "placeholders": {} - }, - "@play": { - "type": "text", - "placeholders": { - "fileName": {} - } - }, - "@passwordForgotten": { - "type": "text", - "placeholders": {} - }, - "@statusExampleMessage": { - "type": "text", - "placeholders": {} - }, - "@security": { - "type": "text", - "placeholders": {} - }, - "@markAsRead": {}, - "@sendAudio": { - "type": "text", - "placeholders": {} - }, - "@widgetName": {}, - "@sentASticker": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@errorAddingWidget": {}, - "@commandHint_dm": { - "type": "text", - "description": "Usage hint for the command /dm" - }, - "@commandHint_hug": {}, - "@replace": {}, - "@reject": { - "type": "text", - "placeholders": {} - }, - "@extremeOffensive": { - "type": "text", - "placeholders": {} - }, - "@editBlockedServers": { - "type": "text", - "placeholders": {} - }, - "@oopsPushError": { - "type": "text", - "placeholders": {} - }, - "@youUnbannedUser": { - "placeholders": { - "user": {} - } - }, - "@joinedTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@visibleForEveryone": { - "type": "text", - "placeholders": {} - }, - "@pleaseEnter4Digits": { - "type": "text", - "placeholders": {} - }, - "@newSpace": {}, - "@devices": { - "type": "text", - "placeholders": {} - }, - "@unknownEvent": { - "type": "text", - "placeholders": { - "type": {} - } - }, - "@emojis": {}, - "@pleaseEnterYourPin": { - "type": "text", - "placeholders": {} - }, - "@pleaseChoose": { - "type": "text", - "placeholders": {} - }, - "@share": { - "type": "text", - "placeholders": {} - }, - "@commandHint_googly": {}, - "@pleaseTryAgainLaterOrChooseDifferentServer": {}, - "@createGroup": {}, - "@privacy": { - "type": "text", - "placeholders": {} - }, - "@sendImage": { - "type": "text", - "placeholders": {} - }, - "@hydrateTorLong": {}, - "@time": {}, - "@enterYourHomeserver": { - "type": "text", - "placeholders": {} - }, - "@custom": {}, - "@noBackupWarning": {}, - "@fromJoining": { - "type": "text", - "placeholders": {} - }, - "@verify": { - "type": "text", - "placeholders": {} - }, - "@sendVideo": { - "type": "text", - "placeholders": {} - }, - "@storeInSecureStorageDescription": {}, - "@openChat": {}, - "@kickUserDescription": {}, - "@sendAMessage": { - "type": "text", - "placeholders": {} - }, - "@pin": { - "type": "text", - "placeholders": {} - }, - "@importNow": {}, - "@deleteAccount": { - "type": "text", - "placeholders": {} - }, - "@setInvitationLink": { - "type": "text", - "placeholders": {} - }, - "@pinMessage": {}, - "@muteChat": { - "type": "text", - "placeholders": {} - }, - "@invite": {}, - "@enableMultiAccounts": {}, - "@emotePacks": { - "type": "text", - "placeholders": {} - }, - "@indexedDbErrorTitle": {}, - "@endedTheCall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@unsupportedAndroidVersionLong": {}, - "@storeSecurlyOnThisDevice": {}, - "@screenSharingDetail": {}, - "@placeCall": {} -} \ No newline at end of file + "repeatPassword": "Ponovite geslo", + "@repeatPassword": {}, + "about": "O aplikaciji", + "@about": { + "type": "text", + "placeholders": {} + }, + "accept": "Sprejmi", + "@accept": { + "type": "text", + "placeholders": {} + }, + "account": "Račun", + "@account": { + "type": "text", + "placeholders": {} + }, + "activatedEndToEndEncryption": "Uporabnik {username} je aktiviral šifriranje od konca do konca", + "@activatedEndToEndEncryption": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "addEmail": "Dodajte e-pošto", + "@addEmail": { + "type": "text", + "placeholders": {} + }, + "addToSpace": "Dodajte v prostor", + "@addToSpace": {}, + "alias": "vzdevek", + "@alias": { + "type": "text", + "placeholders": {} + }, + "all": "Vse", + "@all": { + "type": "text", + "placeholders": {} + }, + "allChats": "Vsi klepeti", + "@allChats": { + "type": "text", + "placeholders": {} + }, + "answeredTheCall": "Oseba {senderName} je odgovorila na klic", + "@answeredTheCall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "anyoneCanJoin": "Pridruži se lahko vsak", + "@anyoneCanJoin": { + "type": "text", + "placeholders": {} + }, + "appLock": "Zaklepanje aplikacije", + "@appLock": { + "type": "text", + "placeholders": {} + }, + "askSSSSSign": "Če želite podpisati drugo osebo, vnesite geslo za varno trgovino ali obnovitveni ključ.", + "@askSSSSSign": { + "type": "text", + "placeholders": {} + }, + "askVerificationRequest": "Ali želite sprejeti to zahtevo za preverjanje od {username}?", + "@askVerificationRequest": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "autoplayImages": "Samodejno predvajajte animirane nalepke in čustva", + "@autoplayImages": { + "type": "text", + "placeholder": {} + }, + "badServerLoginTypesException": "Domači strežnik podpira vrste prijave:\n{serverVersions}\nToda ta aplikacija podpira samo:\n{supportedVersions}", + "@badServerLoginTypesException": { + "type": "text", + "placeholders": { + "serverVersions": {}, + "supportedVersions": {} + } + }, + "sendOnEnter": "Pošlji ob vstopu", + "@sendOnEnter": {}, + "banFromChat": "Prepoved klepeta", + "@banFromChat": { + "type": "text", + "placeholders": {} + }, + "banned": "Prepovedano", + "@banned": { + "type": "text", + "placeholders": {} + }, + "bannedUser": "{username} je prepovedan v {targetName}", + "@bannedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "blockDevice": "Blokirana naprava", + "@blockDevice": { + "type": "text", + "placeholders": {} + }, + "blocked": "Blokirano", + "@blocked": { + "type": "text", + "placeholders": {} + }, + "botMessages": "Botova sporočila", + "@botMessages": { + "type": "text", + "placeholders": {} + }, + "cancel": "Prekliči", + "@cancel": { + "type": "text", + "placeholders": {} + }, + "cantOpenUri": "URI-ja {uri} ni mogoče odpreti", + "@cantOpenUri": { + "type": "text", + "placeholders": { + "uri": {} + } + }, + "changedTheChatAvatar": "{username} je spremenil avatar za klepet", + "@changedTheChatAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheChatPermissions": "{username} je spremenila dovoljenja za klepet", + "@changedTheChatPermissions": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheDisplaynameTo": "{username} je spremenil svoje prikazno ime v: '{displayname}'", + "@changedTheDisplaynameTo": { + "type": "text", + "placeholders": { + "username": {}, + "displayname": {} + } + }, + "changedTheGuestAccessRules": "{username} je spremenila pravila dostopa za goste", + "@changedTheGuestAccessRules": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheGuestAccessRulesTo": "{username} je spremenila pravila dostopa za goste v: {rules}", + "@changedTheGuestAccessRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "changedTheHistoryVisibilityTo": "{username} je spremenil vidnost zgodovine v: {rules}", + "@changedTheHistoryVisibilityTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "changedTheJoinRules": "{username} je spremenil pravila za pridružitev", + "@changedTheJoinRules": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheJoinRulesTo": "{username} je spremenila pravila pridružitve v: {joinRules}", + "@changedTheJoinRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "joinRules": {} + } + }, + "changedTheProfileAvatar": "{username} je spremenil avatar", + "@changedTheProfileAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheRoomAliases": "{username} je spremenil vzdevke sobe", + "@changedTheRoomAliases": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheRoomInvitationLink": "{username} je spremenil povezavo za povabilo", + "@changedTheRoomInvitationLink": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changePassword": "Spremeni geslo", + "@changePassword": { + "type": "text", + "placeholders": {} + }, + "changeTheHomeserver": "Spremenite domači strežnik", + "@changeTheHomeserver": { + "type": "text", + "placeholders": {} + }, + "changeTheme": "Spremenite svoj slog", + "@changeTheme": { + "type": "text", + "placeholders": {} + }, + "changeTheNameOfTheGroup": "Spremenite ime skupine", + "@changeTheNameOfTheGroup": { + "type": "text", + "placeholders": {} + }, + "changeYourAvatar": "Spremenite svoj avatar", + "@changeYourAvatar": { + "type": "text", + "placeholders": {} + }, + "chat": "Klepet", + "@chat": { + "type": "text", + "placeholders": {} + }, + "yourChatBackupHasBeenSetUp": "Varnostna kopija klepeta je nastavljena.", + "@yourChatBackupHasBeenSetUp": {}, + "chatBackup": "Varnostno kopiranje klepeta", + "@chatBackup": { + "type": "text", + "placeholders": {} + }, + "chatDetails": "Podrobnosti klepeta", + "@chatDetails": { + "type": "text", + "placeholders": {} + }, + "chatHasBeenAddedToThisSpace": "Klepet je bil dodan v ta prostor", + "@chatHasBeenAddedToThisSpace": {}, + "chats": "Klepeti", + "@chats": { + "type": "text", + "placeholders": {} + }, + "chooseAStrongPassword": "Izberite močno geslo", + "@chooseAStrongPassword": { + "type": "text", + "placeholders": {} + }, + "clearArchive": "Počisti arhiv", + "@clearArchive": {}, + "close": "Zapri", + "@close": { + "type": "text", + "placeholders": {} + }, + "commandHint_ban": "Izključi določenega uporabnika iz te sobe", + "@commandHint_ban": { + "type": "text", + "description": "Usage hint for the command /ban" + }, + "commandHint_html": "Pošljite besedilo v obliki HTML", + "@commandHint_html": { + "type": "text", + "description": "Usage hint for the command /html" + }, + "commandHint_invite": "Povabi danega uporabnika v to sobo", + "@commandHint_invite": { + "type": "text", + "description": "Usage hint for the command /invite" + }, + "commandHint_join": "Pridružite se dani sobi", + "@commandHint_join": { + "type": "text", + "description": "Usage hint for the command /join" + }, + "commandHint_kick": "Odstranite danega uporabnika iz te sobe", + "@commandHint_kick": { + "type": "text", + "description": "Usage hint for the command /kick" + }, + "commandHint_me": "Opisi sebe", + "@commandHint_me": { + "type": "text", + "description": "Usage hint for the command /me" + }, + "commandHint_myroomavatar": "Nastavite svojo sliko za to sobo", + "@commandHint_myroomavatar": { + "type": "text", + "description": "Usage hint for the command /myroomavatar" + }, + "commandHint_op": "Nastavite raven moči danega uporabnika (privzeto: 50)", + "@commandHint_op": { + "type": "text", + "description": "Usage hint for the command /op" + }, + "commandHint_react": "Pošljite odgovor kot reakcijo", + "@commandHint_react": { + "type": "text", + "description": "Usage hint for the command /react" + }, + "commandHint_send": "Pošlji besedilo", + "@commandHint_send": { + "type": "text", + "description": "Usage hint for the command /send" + }, + "commandHint_unban": "Prekliči izključitev določenega uporabnika iz te sobe", + "@commandHint_unban": { + "type": "text", + "description": "Usage hint for the command /unban" + }, + "commandInvalid": "Ukaz ni veljaven", + "@commandInvalid": { + "type": "text" + }, + "commandMissing": "{command} is not a command.", + "@commandMissing": { + "type": "text", + "placeholders": { + "command": {} + }, + "description": "State that {command} is not a valid /command." + }, + "compareEmojiMatch": "Primerjajte in se prepričajte, da se naslednji emoji ujemajo s tistimi iz druge naprave:", + "@compareEmojiMatch": { + "type": "text", + "placeholders": {} + }, + "compareNumbersMatch": "Primerjajte in se prepričajte, da se naslednje številke ujemajo s številkami druge naprave:", + "@compareNumbersMatch": { + "type": "text", + "placeholders": {} + }, + "configureChat": "Konfigurirajte klepet", + "@configureChat": { + "type": "text", + "placeholders": {} + }, + "confirm": "Potrdi", + "@confirm": { + "type": "text", + "placeholders": {} + }, + "containsDisplayName": "Vsebuje prikazno ime", + "@containsDisplayName": { + "type": "text", + "placeholders": {} + }, + "containsUserName": "Vsebuje uporabniško ime", + "@containsUserName": { + "type": "text", + "placeholders": {} + }, + "archive": "Arhiv", + "@archive": { + "type": "text", + "placeholders": {} + }, + "areYouSure": "Ali si prepričan?", + "@areYouSure": { + "type": "text", + "placeholders": {} + }, + "acceptedTheInvitation": "{username} je sprejel povabilo", + "@acceptedTheInvitation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "areYouSureYouWantToLogout": "Ali ste prepričani, da se želite odjaviti?", + "@areYouSureYouWantToLogout": { + "type": "text", + "placeholders": {} + }, + "changedTheChatDescriptionTo": "{username} je spremenil opis klepeta v: '{description}'", + "@changedTheChatDescriptionTo": { + "type": "text", + "placeholders": { + "username": {}, + "description": {} + } + }, + "areGuestsAllowedToJoin": "Ali se lahko gostujoči uporabniki pridružijo", + "@areGuestsAllowedToJoin": { + "type": "text", + "placeholders": {} + }, + "admin": "Admin", + "@admin": { + "type": "text", + "placeholders": {} + }, + "badServerVersionsException": "Domači strežnik podpira različice Spec:\n{serverVersions}\nToda ta aplikacija podpira samo {supportedVersions}", + "@badServerVersionsException": { + "type": "text", + "placeholders": { + "serverVersions": {}, + "supportedVersions": {} + } + }, + "changedTheChatNameTo": "{username} je spremenil ime klepeta v: '{chatname}'", + "@changedTheChatNameTo": { + "type": "text", + "placeholders": { + "username": {}, + "chatname": {} + } + }, + "changeDeviceName": "Spremenite ime naprave", + "@changeDeviceName": { + "type": "text", + "placeholders": {} + }, + "changedTheHistoryVisibility": "{username} je spremenila vidnost zgodovine", + "@changedTheHistoryVisibility": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "channelCorruptedDecryptError": "Šifriranje je poškodovano", + "@channelCorruptedDecryptError": { + "type": "text", + "placeholders": {} + }, + "contentHasBeenReported": "Vsebina je bila prijavljena skrbnikom strežnika", + "@contentHasBeenReported": { + "type": "text", + "placeholders": {} + }, + "chatBackupDescription": "Varnostna kopija klepeta je zavarovana z varnostnim ključem. Prosimo, pazite, da ga ne izgubite.", + "@chatBackupDescription": { + "type": "text", + "placeholders": {} + }, + "commandHint_myroomnick": "Nastavite prikazno ime za to sobo", + "@commandHint_myroomnick": { + "type": "text", + "description": "Usage hint for the command /myroomnick" + }, + "connect": "Povežite se", + "@connect": { + "type": "text", + "placeholders": {} + }, + "contactHasBeenInvitedToTheGroup": "Kontakt je bil povabljen v skupino", + "@contactHasBeenInvitedToTheGroup": { + "type": "text", + "placeholders": {} + }, + "commandHint_leave": "Zapusti to sobo", + "@commandHint_leave": { + "type": "text", + "description": "Usage hint for the command /leave" + }, + "commandHint_plain": "Pošlji neformatirano besedilo", + "@commandHint_plain": { + "type": "text", + "description": "Usage hint for the command /plain" + }, + "copiedToClipboard": "Kopirano v odložišče", + "@copiedToClipboard": { + "type": "text", + "placeholders": {} + }, + "copy": "Kopiraj", + "@copy": { + "type": "text", + "placeholders": {} + }, + "copyToClipboard": "Kopiraj v odložišče", + "@copyToClipboard": { + "type": "text", + "placeholders": {} + }, + "couldNotDecryptMessage": "Sporočila ni bilo mogoče dešifrirati: {error}", + "@couldNotDecryptMessage": { + "type": "text", + "placeholders": { + "error": {} + } + }, + "countParticipants": "{count} udeležencev", + "@countParticipants": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "create": "Ustvari", + "@create": { + "type": "text", + "placeholders": {} + }, + "createNewSpace": "Nov prostor", + "@createNewSpace": { + "type": "text", + "placeholders": {} + }, + "currentlyActive": "Trenutno aktiven", + "@currentlyActive": { + "type": "text", + "placeholders": {} + }, + "darkTheme": "Temno", + "@darkTheme": { + "type": "text", + "placeholders": {} + }, + "defaultPermissionLevel": "Privzeta raven dovoljenja", + "@defaultPermissionLevel": { + "type": "text", + "placeholders": {} + }, + "dateWithYear": "{day}-{month}-{year}", + "@dateWithYear": { + "type": "text", + "placeholders": { + "year": {}, + "month": {}, + "day": {} + } + }, + "dateWithoutYear": "{month}-{day}", + "@dateWithoutYear": { + "type": "text", + "placeholders": { + "month": {}, + "day": {} + } + }, + "createdTheChat": "{username} je ustvaril klepet", + "@createdTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "dateAndTimeOfDay": "{date}, {timeOfDay}", + "@dateAndTimeOfDay": { + "type": "text", + "placeholders": { + "date": {}, + "timeOfDay": {} + } + }, + "deactivateAccountWarning": "S tem boste deaktivirali vaš uporabniški račun. Tega ni mogoče razveljaviti! Ali si prepričan?", + "@deactivateAccountWarning": { + "type": "text", + "placeholders": {} + } +} From 8689232ab7310acf07f6939467daacab865c0f9d Mon Sep 17 00:00:00 2001 From: Anonymous Date: Thu, 25 Jul 2024 05:09:46 +0000 Subject: [PATCH 122/288] Translated using Weblate (Lithuanian) Currently translated at 70.0% (457 of 652 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/lt/ --- assets/l10n/intl_lt.arb | 4563 +++++++++++++++++++-------------------- 1 file changed, 2217 insertions(+), 2346 deletions(-) diff --git a/assets/l10n/intl_lt.arb b/assets/l10n/intl_lt.arb index 0a77ba065b..0a45a29e28 100644 --- a/assets/l10n/intl_lt.arb +++ b/assets/l10n/intl_lt.arb @@ -1,2347 +1,2218 @@ { - "commandHint_leave": "Palikti pokalbių kambarį", - "@commandHint_leave": { - "type": "text", - "description": "Usage hint for the command /leave" - }, - "confirm": "Patvirtinti", - "@confirm": { - "type": "text", - "placeholders": {} - }, - "cancel": "Atšaukti", - "@cancel": { - "type": "text", - "placeholders": {} - }, - "edit": "Redaguoti", - "@edit": { - "type": "text", - "placeholders": {} - }, - "downloadFile": "Atsisiųsti failą", - "@downloadFile": { - "type": "text", - "placeholders": {} - }, - "about": "Apie", - "@about": { - "type": "text", - "placeholders": {} - }, - "all": "Visi", - "@all": { - "type": "text", - "placeholders": {} - }, - "fluffychat": "FluffyChat", - "@fluffychat": { - "type": "text", - "placeholders": {} - }, - "fileName": "Failo vardas", - "@fileName": { - "type": "text", - "placeholders": {} - }, - "changePassword": "Keisti slaptažodį", - "@changePassword": { - "type": "text", - "placeholders": {} - }, - "close": "Uždaryti", - "@close": { - "type": "text", - "placeholders": {} - }, - "archive": "Archyvas", - "@archive": { - "type": "text", - "placeholders": {} - }, - "skip": "Praleisti", - "@skip": { - "type": "text", - "placeholders": {} - }, - "share": "Bendrinti", - "@share": { - "type": "text", - "placeholders": {} - }, - "showPassword": "Rodyti slaptažodį", - "@showPassword": { - "type": "text", - "placeholders": {} - }, - "time": "Laikas", - "@time": {}, - "settings": "Nustatytmai", - "@settings": { - "type": "text", - "placeholders": {} - }, - "sender": "Siuntėjas", - "@sender": {}, - "yes": "Taip", - "@yes": { - "type": "text", - "placeholders": {} - }, - "you": "Jūs", - "@you": { - "type": "text", - "placeholders": {} - }, - "logout": "Atsijungti", - "@logout": { - "type": "text", - "placeholders": {} - }, - "messages": "Žinutės", - "@messages": { - "type": "text", - "placeholders": {} - }, - "scanQrCode": "Nuskanuokite QR kodą", - "@scanQrCode": {}, - "ok": "OK", - "@ok": { - "type": "text", - "placeholders": {} - }, - "addAccount": "Pridėti paskyrą", - "@addAccount": {}, - "or": "Arba", - "@or": { - "type": "text", - "placeholders": {} - }, - "password": "Slaptažodis", - "@password": { - "type": "text", - "placeholders": {} - }, - "passwordHasBeenChanged": "Slaptažodis pakeistas", - "@passwordHasBeenChanged": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourPassword": "Įveskite savo slaptažodį", - "@pleaseEnterYourPassword": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourUsername": "Įveskite savo vartotojo vardą", - "@pleaseEnterYourUsername": { - "type": "text", - "placeholders": {} - }, - "reply": "Atsakyti", - "@reply": { - "type": "text", - "placeholders": {} - }, - "blockDevice": "Blokuoti įrenginį", - "@blockDevice": { - "type": "text", - "placeholders": {} - }, - "blocked": "Užblokuotas", - "@blocked": { - "type": "text", - "placeholders": {} - }, - "chooseAStrongPassword": "Pasirinkite saugų slaptažodį", - "@chooseAStrongPassword": { - "type": "text", - "placeholders": {} - }, - "deleteMessage": "Ištrinti žinutę", - "@deleteMessage": { - "type": "text", - "placeholders": {} - }, - "device": "Įrenginys", - "@device": { - "type": "text", - "placeholders": {} - }, - "deviceId": "Įrenginio ID", - "@deviceId": { - "type": "text", - "placeholders": {} - }, - "devices": "Įrenginiai", - "@devices": { - "type": "text", - "placeholders": {} - }, - "homeserver": "Namų serveris", - "@homeserver": {}, - "enterYourHomeserver": "Įveskite namų serverį", - "@enterYourHomeserver": { - "type": "text", - "placeholders": {} - }, - "everythingReady": "Viskas paruošta!", - "@everythingReady": { - "type": "text", - "placeholders": {} - }, - "fontSize": "Šrifto dydis", - "@fontSize": { - "type": "text", - "placeholders": {} - }, - "clearArchive": "Išvalyti archyvą", - "@clearArchive": {}, - "create": "Sukurti", - "@create": { - "type": "text", - "placeholders": {} - }, - "connect": "Prisijungti", - "@connect": { - "type": "text", - "placeholders": {} - }, - "people": "Žmonės", - "@people": { - "type": "text", - "placeholders": {} - }, - "moderator": "Moderatorius", - "@moderator": { - "type": "text", - "placeholders": {} - }, - "muteChat": "Nutildyti pokalbį", - "@muteChat": { - "type": "text", - "placeholders": {} - }, - "newChat": "Naujas pokalbis", - "@newChat": { - "type": "text", - "placeholders": {} - }, - "none": "Nė vienas", - "@none": { - "type": "text", - "placeholders": {} - }, - "noPermission": "Nėra leidimo", - "@noPermission": { - "type": "text", - "placeholders": {} - }, - "noRoomsFound": "Nerasta kambarių…", - "@noRoomsFound": { - "type": "text", - "placeholders": {} - }, - "notifications": "Pranešimai", - "@notifications": { - "type": "text", - "placeholders": {} - }, - "notificationsEnabledForThisAccount": "Pranešimai aktyvuoti šitai paskyrai", - "@notificationsEnabledForThisAccount": { - "type": "text", - "placeholders": {} - }, - "obtainingLocation": "Gaunama vieta…", - "@obtainingLocation": { - "type": "text", - "placeholders": {} - }, - "offensive": "Agresyvus", - "@offensive": { - "type": "text", - "placeholders": {} - }, - "offline": "Neprisijungta", - "@offline": { - "type": "text", - "placeholders": {} - }, - "online": "Prisijungta", - "@online": { - "type": "text", - "placeholders": {} - }, - "oopsPushError": "Oi! Deja, nustatant tiesioginius pranešimus įvyko klaida.", - "@oopsPushError": { - "type": "text", - "placeholders": {} - }, - "oopsSomethingWentWrong": "Oi, kažkas nutiko ne taip…", - "@oopsSomethingWentWrong": { - "type": "text", - "placeholders": {} - }, - "openAppToReadMessages": "Atidarykite programėlę, kad perskaityti žinutes", - "@openAppToReadMessages": { - "type": "text", - "placeholders": {} - }, - "link": "Nuoroda", - "@link": {}, - "participant": "Dalyvis", - "@participant": { - "type": "text", - "placeholders": {} - }, - "passphraseOrKey": "Slapta frazė arba atkūrimo raktas", - "@passphraseOrKey": { - "type": "text", - "placeholders": {} - }, - "passwordForgotten": "Slaptažodis užmirštas", - "@passwordForgotten": { - "type": "text", - "placeholders": {} - }, - "passwordRecovery": "Slaptažodžio atkūrimas", - "@passwordRecovery": { - "type": "text", - "placeholders": {} - }, - "pickImage": "Pasirinkite paveiksliuką", - "@pickImage": { - "type": "text", - "placeholders": {} - }, - "pin": "Prisegti", - "@pin": { - "type": "text", - "placeholders": {} - }, - "pleaseChoose": "Prašome pasirinkti", - "@pleaseChoose": { - "type": "text", - "placeholders": {} - }, - "pleaseChooseAPasscode": "Pasirinkite slaptą kodą", - "@pleaseChooseAPasscode": { - "type": "text", - "placeholders": {} - }, - "pleaseClickOnLink": "Paspauskite nuorodą el. pašte ir tęskite toliau.", - "@pleaseClickOnLink": { - "type": "text", - "placeholders": {} - }, - "pleaseEnter4Digits": "Įveskite 4 skaitmenis arba palikite tuščią, jei norite išjungti programėlės užraktą.", - "@pleaseEnter4Digits": { - "type": "text", - "placeholders": {} - }, - "pleaseEnterYourPin": "Įveskite savo PIN kodą", - "@pleaseEnterYourPin": { - "type": "text", - "placeholders": {} - }, - "pleaseFollowInstructionsOnWeb": "Vadovaukitės svetainėje pateiktais nurodymais ir bakstelėkite Toliau.", - "@pleaseFollowInstructionsOnWeb": { - "type": "text", - "placeholders": {} - }, - "privacy": "Privatumas", - "@privacy": { - "type": "text", - "placeholders": {} - }, - "publicRooms": "Vieši kambariai", - "@publicRooms": { - "type": "text", - "placeholders": {} - }, - "reason": "Priežastis", - "@reason": { - "type": "text", - "placeholders": {} - }, - "recording": "Įrašymas", - "@recording": { - "type": "text", - "placeholders": {} - }, - "redactMessage": "Pašalinti žinutę", - "@redactMessage": { - "type": "text", - "placeholders": {} - }, - "register": "Registruotis", - "@register": { - "type": "text", - "placeholders": {} - }, - "reject": "Atmesti", - "@reject": { - "type": "text", - "placeholders": {} - }, - "rejoin": "Vėl prisijungti", - "@rejoin": { - "type": "text", - "placeholders": {} - }, - "remove": "Pašalinti", - "@remove": { - "type": "text", - "placeholders": {} - }, - "removeAllOtherDevices": "Pašalinti visus kitus įrenginius", - "@removeAllOtherDevices": { - "type": "text", - "placeholders": {} - }, - "removeDevice": "Pašalinti įrenginį", - "@removeDevice": { - "type": "text", - "placeholders": {} - }, - "removeYourAvatar": "Pašalinti savo avatarą", - "@removeYourAvatar": { - "type": "text", - "placeholders": {} - }, - "replaceRoomWithNewerVersion": "Pakeisti kambarį naujesne versija", - "@replaceRoomWithNewerVersion": { - "type": "text", - "placeholders": {} - }, - "requestPermission": "Prašyti leidimo", - "@requestPermission": { - "type": "text", - "placeholders": {} - }, - "roomHasBeenUpgraded": "Kambarys buvo atnaujintas", - "@roomHasBeenUpgraded": { - "type": "text", - "placeholders": {} - }, - "roomVersion": "Kambario versija", - "@roomVersion": { - "type": "text", - "placeholders": {} - }, - "search": "Ieškoti", - "@search": { - "type": "text", - "placeholders": {} - }, - "accept": "Sutinku", - "@accept": { - "type": "text", - "placeholders": {} - }, - "repeatPassword": "Pakartokite slaptažodį", - "@repeatPassword": {}, - "addEmail": "Pridėti el. paštą", - "@addEmail": { - "type": "text", - "placeholders": {} - }, - "admin": "Administratorius", - "@admin": { - "type": "text", - "placeholders": {} - }, - "alias": "slapyvardis", - "@alias": { - "type": "text", - "placeholders": {} - }, - "allChats": "Visi pokalbiai", - "@allChats": { - "type": "text", - "placeholders": {} - }, - "anyoneCanJoin": "Bet kas gali prisijungti", - "@anyoneCanJoin": { - "type": "text", - "placeholders": {} - }, - "areYouSure": "Ar esate tikri?", - "@areYouSure": { - "type": "text", - "placeholders": {} - }, - "areYouSureYouWantToLogout": "Ar tikrai norite atsijungti?", - "@areYouSureYouWantToLogout": { - "type": "text", - "placeholders": {} - }, - "changeTheHomeserver": "Pakeisti namų serverį", - "@changeTheHomeserver": { - "type": "text", - "placeholders": {} - }, - "changeTheme": "Keisti savo stilių", - "@changeTheme": { - "type": "text", - "placeholders": {} - }, - "changeTheNameOfTheGroup": "Keisti grupės pavadinimą", - "@changeTheNameOfTheGroup": { - "type": "text", - "placeholders": {} - }, - "changeYourAvatar": "Keisti savo avatarą", - "@changeYourAvatar": { - "type": "text", - "placeholders": {} - }, - "chat": "Pokalbis", - "@chat": { - "type": "text", - "placeholders": {} - }, - "chatDetails": "Pokalbio detalės", - "@chatDetails": { - "type": "text", - "placeholders": {} - }, - "chats": "Pokalbiai", - "@chats": { - "type": "text", - "placeholders": {} - }, - "commandHint_ban": "Užblokuoti vartotoją šiame kambaryje", - "@commandHint_ban": { - "type": "text", - "description": "Usage hint for the command /ban" - }, - "commandHint_clearcache": "Išvalyti laikiną talpyklą", - "@commandHint_clearcache": { - "type": "text", - "description": "Usage hint for the command /clearcache" - }, - "commandHint_discardsession": "Atmesti sesiją", - "@commandHint_discardsession": { - "type": "text", - "description": "Usage hint for the command /discardsession" - }, - "commandHint_html": "Siųsti tekstą HTML formatu", - "@commandHint_html": { - "type": "text", - "description": "Usage hint for the command /html" - }, - "commandHint_invite": "Pakviesti vartotoją į šitą kambarį", - "@commandHint_invite": { - "type": "text", - "description": "Usage hint for the command /invite" - }, - "commandHint_join": "Prisijungti prie nurodyto kambario", - "@commandHint_join": { - "type": "text", - "description": "Usage hint for the command /join" - }, - "commandHint_kick": "Pašalinti vartotoja iš šito kambario", - "@commandHint_kick": { - "type": "text", - "description": "Usage hint for the command /kick" - }, - "commandHint_myroomnick": "Nustatyti savo rodomą vardą šiame kambaryje", - "@commandHint_myroomnick": { - "type": "text", - "description": "Usage hint for the command /myroomnick" - }, - "commandHint_plain": "Siųsti neformatuotą tekstą", - "@commandHint_plain": { - "type": "text", - "description": "Usage hint for the command /plain" - }, - "commandHint_send": "Siųsti tekstą", - "@commandHint_send": { - "type": "text", - "description": "Usage hint for the command /send" - }, - "commandHint_unban": "Atblokuoti vartotoją šiame kambaryje", - "@commandHint_unban": { - "type": "text", - "description": "Usage hint for the command /unban" - }, - "commandInvalid": "Neteisinga komanda", - "@commandInvalid": { - "type": "text" - }, - "configureChat": "Konfigūruoti pokalbį", - "@configureChat": { - "type": "text", - "placeholders": {} - }, - "copiedToClipboard": "Nukopijuota į iškarpinę", - "@copiedToClipboard": { - "type": "text", - "placeholders": {} - }, - "copy": "Kopijuoti", - "@copy": { - "type": "text", - "placeholders": {} - }, - "copyToClipboard": "Koipjuoti į iškarpinę", - "@copyToClipboard": { - "type": "text", - "placeholders": {} - }, - "currentlyActive": "Šiuo metu aktyvus", - "@currentlyActive": { - "type": "text", - "placeholders": {} - }, - "darkTheme": "Tamsi", - "@darkTheme": { - "type": "text", - "placeholders": {} - }, - "delete": "Ištrinti", - "@delete": { - "type": "text", - "placeholders": {} - }, - "deleteAccount": "Panaikinti paskyra", - "@deleteAccount": { - "type": "text", - "placeholders": {} - }, - "directChats": "Tiesioginiai pokalbiai", - "@directChats": { - "type": "text", - "placeholders": {} - }, - "encrypted": "Užšifruotas", - "@encrypted": { - "type": "text", - "placeholders": {} - }, - "encryptionNotEnabled": "Šifravimas aktyvuotas", - "@encryptionNotEnabled": { - "type": "text", - "placeholders": {} - }, - "enterAnEmailAddress": "Įveskite el. pašto adresą", - "@enterAnEmailAddress": { - "type": "text", - "placeholders": {} - }, - "extremeOffensive": "Itin įžeidžiantis", - "@extremeOffensive": { - "type": "text", - "placeholders": {} - }, - "forward": "Toliau", - "@forward": { - "type": "text", - "placeholders": {} - }, - "fromJoining": "Nuo prisijungimo", - "@fromJoining": { - "type": "text", - "placeholders": {} - }, - "fromTheInvitation": "Nuo pakvietimo", - "@fromTheInvitation": { - "type": "text", - "placeholders": {} - }, - "goToTheNewRoom": "Eiti į naują kambarį", - "@goToTheNewRoom": { - "type": "text", - "placeholders": {} - }, - "group": "Grupė", - "@group": { - "type": "text", - "placeholders": {} - }, - "groupIsPublic": "Grupė yra vieša", - "@groupIsPublic": { - "type": "text", - "placeholders": {} - }, - "groups": "Grupės", - "@groups": { - "type": "text", - "placeholders": {} - }, - "guestsAreForbidden": "Svečiams draudžiama", - "@guestsAreForbidden": { - "type": "text", - "placeholders": {} - }, - "guestsCanJoin": "Svečiai gali prisijungti", - "@guestsCanJoin": { - "type": "text", - "placeholders": {} - }, - "help": "Pagalba", - "@help": { - "type": "text", - "placeholders": {} - }, - "hideRedactedEvents": "Slėpti pašalintus įvykius", - "@hideRedactedEvents": { - "type": "text", - "placeholders": {} - }, - "hideUnknownEvents": "Slėpti nežinomus įvykius", - "@hideUnknownEvents": { - "type": "text", - "placeholders": {} - }, - "identity": "Tapatybė", - "@identity": { - "type": "text", - "placeholders": {} - }, - "ignore": "Ignoruoti", - "@ignore": { - "type": "text", - "placeholders": {} - }, - "ignoredUsers": "Ignoruoti vartotojai", - "@ignoredUsers": { - "type": "text", - "placeholders": {} - }, - "leave": "Palikti", - "@leave": { - "type": "text", - "placeholders": {} - }, - "memberChanges": "Narių pokyčiai", - "@memberChanges": { - "type": "text", - "placeholders": {} - }, - "mention": "Paminėti", - "@mention": { - "type": "text", - "placeholders": {} - }, - "encryption": "Šifravimas", - "@encryption": { - "type": "text", - "placeholders": {} - }, - "enableEncryption": "Aktyvuoti šifravimą", - "@enableEncryption": { - "type": "text", - "placeholders": {} - }, - "editBlockedServers": "Redaguoti blokuotus serverius", - "@editBlockedServers": { - "type": "text", - "placeholders": {} - }, - "login": "Prisijungti", - "@login": { - "type": "text", - "placeholders": {} - }, - "sendOnEnter": "Išsiųsti paspaudus Enter", - "@sendOnEnter": {}, - "banFromChat": "Užblokuoti iš pokalbio", - "@banFromChat": { - "type": "text", - "placeholders": {} - }, - "banned": "Užblokuotas", - "@banned": { - "type": "text", - "placeholders": {} - }, - "changeDeviceName": "Pakeisti įrenginio vardą", - "@changeDeviceName": { - "type": "text", - "placeholders": {} - }, - "yourChatBackupHasBeenSetUp": "Jūsų pokalbio atsarginė kopija buvo nustatyta.", - "@yourChatBackupHasBeenSetUp": {}, - "chatBackup": "Pokalbio atsargine kopija", - "@chatBackup": { - "type": "text", - "placeholders": {} - }, - "commandHint_me": "Apibūdinkite save", - "@commandHint_me": { - "type": "text", - "description": "Usage hint for the command /me" - }, - "displaynameHasBeenChanged": "Rodomas vardas buvo pakeistas", - "@displaynameHasBeenChanged": { - "type": "text", - "placeholders": {} - }, - "editDisplayname": "Redaguoti rodomą vardą", - "@editDisplayname": { - "type": "text", - "placeholders": {} - }, - "editRoomAliases": "Redaguoti kambario pseudonimus", - "@editRoomAliases": { - "type": "text", - "placeholders": {} - }, - "editRoomAvatar": "Redaguoti kambario avatarą", - "@editRoomAvatar": { - "type": "text", - "placeholders": {} - }, - "howOffensiveIsThisContent": "Kiek įžeižiantis šis turinys?", - "@howOffensiveIsThisContent": { - "type": "text", - "placeholders": {} - }, - "id": "ID", - "@id": { - "type": "text", - "placeholders": {} - }, - "iHaveClickedOnLink": "Aš paspaudžiau nuorodą", - "@iHaveClickedOnLink": { - "type": "text", - "placeholders": {} - }, - "incorrectPassphraseOrKey": "Neteisinga slaptafrazė arba atkūrimo raktas", - "@incorrectPassphraseOrKey": { - "type": "text", - "placeholders": {} - }, - "inoffensive": "Neįžeidžiantis", - "@inoffensive": { - "type": "text", - "placeholders": {} - }, - "inviteContact": "Pakviesti kontaktą", - "@inviteContact": { - "type": "text", - "placeholders": {} - }, - "invited": "Pakviestas", - "@invited": { - "type": "text", - "placeholders": {} - }, - "invitedUsersOnly": "Tik pakviesti vartotojai", - "@invitedUsersOnly": { - "type": "text", - "placeholders": {} - }, - "isTyping": "rašo…", - "@isTyping": { - "type": "text", - "placeholders": {} - }, - "joinRoom": "Prisijungti prie kambario", - "@joinRoom": { - "type": "text", - "placeholders": {} - }, - "kickFromChat": "Išmesti iš pokalbio", - "@kickFromChat": { - "type": "text", - "placeholders": {} - }, - "leftTheChat": "Paliko pokalbį", - "@leftTheChat": { - "type": "text", - "placeholders": {} - }, - "license": "Licencija", - "@license": { - "type": "text", - "placeholders": {} - }, - "lightTheme": "Šviesi", - "@lightTheme": { - "type": "text", - "placeholders": {} - }, - "loadingPleaseWait": "Kraunama… Prašome palaukti.", - "@loadingPleaseWait": { - "type": "text", - "placeholders": {} - }, - "loadMore": "Rodyti daugiau…", - "@loadMore": { - "type": "text", - "placeholders": {} - }, - "newMessageInFluffyChat": "💬 Nauja žinutė FluffyChat'e", - "@newMessageInFluffyChat": { - "type": "text", - "placeholders": {} - }, - "newVerificationRequest": "Nauja patvirtinimo užklausa!", - "@newVerificationRequest": { - "type": "text", - "placeholders": {} - }, - "next": "Toliau", - "@next": { - "type": "text", - "placeholders": {} - }, - "no": "Ne", - "@no": { - "type": "text", - "placeholders": {} - }, - "noConnectionToTheServer": "Nėra ryšio su serveriu", - "@noConnectionToTheServer": { - "type": "text", - "placeholders": {} - }, - "setInvitationLink": "Nustatyti pakvietimo nuorodą", - "@setInvitationLink": { - "type": "text", - "placeholders": {} - }, - "singlesignon": "Vienkartinis prisijungimas", - "@singlesignon": { - "type": "text", - "placeholders": {} - }, - "sourceCode": "Programinis kodas", - "@sourceCode": { - "type": "text", - "placeholders": {} - }, - "spaceIsPublic": "Erdvė yra vieša", - "@spaceIsPublic": { - "type": "text", - "placeholders": {} - }, - "spaceName": "Erdvės pavadinimas", - "@spaceName": { - "type": "text", - "placeholders": {} - }, - "status": "Būsena", - "@status": { - "type": "text", - "placeholders": {} - }, - "statusExampleMessage": "Kaip sekasi šiandien?", - "@statusExampleMessage": { - "type": "text", - "placeholders": {} - }, - "submit": "Pateikti", - "@submit": { - "type": "text", - "placeholders": {} - }, - "synchronizingPleaseWait": "Sinchronizuojama… Prašome palaukti.", - "@synchronizingPleaseWait": { - "type": "text", - "placeholders": {} - }, - "transferFromAnotherDevice": "Perkėlimas iš kito įrenginio", - "@transferFromAnotherDevice": { - "type": "text", - "placeholders": {} - }, - "verify": "Patvirtinti", - "@verify": { - "type": "text", - "placeholders": {} - }, - "verifyStart": "Pradėti patvirtinimą", - "@verifyStart": { - "type": "text", - "placeholders": {} - }, - "verifySuccess": "Jūs sėkmingai patvirtinote!", - "@verifySuccess": { - "type": "text", - "placeholders": {} - }, - "verifyTitle": "Patvirtinama kita paskyra", - "@verifyTitle": { - "type": "text", - "placeholders": {} - }, - "visibilityOfTheChatHistory": "Pokalbių istorijos matomumas", - "@visibilityOfTheChatHistory": { - "type": "text", - "placeholders": {} - }, - "visibleForAllParticipants": "Matoma visiems dalyviams", - "@visibleForAllParticipants": { - "type": "text", - "placeholders": {} - }, - "waitingPartnerAcceptRequest": "Laukiama, kol dalyvis priims užklausą…", - "@waitingPartnerAcceptRequest": { - "type": "text", - "placeholders": {} - }, - "writeAMessage": "Rašyti žinutę…", - "@writeAMessage": { - "type": "text", - "placeholders": {} - }, - "youAreNoLongerParticipatingInThisChat": "Jūs nebedalyvaujate šiame pokalbyje", - "@youAreNoLongerParticipatingInThisChat": { - "type": "text", - "placeholders": {} - }, - "youHaveBeenBannedFromThisChat": "Jums buvo uždrausta dalyvauti šiame pokalbyje", - "@youHaveBeenBannedFromThisChat": { - "type": "text", - "placeholders": {} - }, - "messageInfo": "Žinutės informacija", - "@messageInfo": {}, - "removeFromSpace": "Pašalinti iš erdvės", - "@removeFromSpace": {}, - "security": "Apsauga", - "@security": { - "type": "text", - "placeholders": {} - }, - "sendAsText": "Siųsti kaip tekstą", - "@sendAsText": { - "type": "text" - }, - "sendAudio": "Siųsti garso įrašą", - "@sendAudio": { - "type": "text", - "placeholders": {} - }, - "sendImage": "Siųsti paveiksliuką", - "@sendImage": { - "type": "text", - "placeholders": {} - }, - "sendFile": "Sųsti bylą", - "@sendFile": { - "type": "text", - "placeholders": {} - }, - "sendMessages": "Siųsti žinutes", - "@sendMessages": { - "type": "text", - "placeholders": {} - }, - "sendOriginal": "Siųsti originalą", - "@sendOriginal": { - "type": "text", - "placeholders": {} - }, - "sendVideo": "Siųsti video", - "@sendVideo": { - "type": "text", - "placeholders": {} - }, - "separateChatTypes": "Atskirti tiesioginius pokalbius ir grupes", - "@separateChatTypes": { - "type": "text", - "placeholders": {} - }, - "setAsCanonicalAlias": "Nustatyti kaip pagrindinį slapyvardį", - "@setAsCanonicalAlias": { - "type": "text", - "placeholders": {} - }, - "setPermissionsLevel": "Nustatyti leidimų lygį", - "@setPermissionsLevel": { - "type": "text", - "placeholders": {} - }, - "setStatus": "Nustatyti būseną", - "@setStatus": { - "type": "text", - "placeholders": {} - }, - "shareLocation": "Bendrinti vietą", - "@shareLocation": { - "type": "text", - "placeholders": {} - }, - "systemTheme": "Sistema", - "@systemTheme": { - "type": "text", - "placeholders": {} - }, - "unavailable": "Nepasiekiamas", - "@unavailable": { - "type": "text", - "placeholders": {} - }, - "unblockDevice": "Atblokuoti įrenginį", - "@unblockDevice": { - "type": "text", - "placeholders": {} - }, - "unknownEncryptionAlgorithm": "Nežinomas šifravimo algoritmas", - "@unknownEncryptionAlgorithm": { - "type": "text", - "placeholders": {} - }, - "unmuteChat": "Įjungti pokalbio garsą", - "@unmuteChat": { - "type": "text", - "placeholders": {} - }, - "unpin": "Atsegti", - "@unpin": { - "type": "text", - "placeholders": {} - }, - "username": "Vartotojo vardas", - "@username": { - "type": "text", - "placeholders": {} - }, - "unverified": "Nepatvirtinta", - "@unverified": {}, - "verified": "Patvirtinta", - "@verified": { - "type": "text", - "placeholders": {} - }, - "videoCall": "Vaizdo skambutis", - "@videoCall": { - "type": "text", - "placeholders": {} - }, - "yourPublicKey": "Jūsų viešasis raktas", - "@yourPublicKey": { - "type": "text", - "placeholders": {} - }, - "addToSpaceDescription": "Pasirinkite erdvę, kad prie jos pridėtumėte šį pokalbį.", - "@addToSpaceDescription": {}, - "start": "Pradžia", - "@start": {}, - "account": "Paskyra", - "@account": { - "type": "text", - "placeholders": {} - }, - "addToSpace": "Pridėti į erdvę", - "@addToSpace": {}, - "appLock": "Programos užraktas", - "@appLock": { - "type": "text", - "placeholders": {} - }, - "areGuestsAllowedToJoin": "Ar svečiams leidžiama prisijungti", - "@areGuestsAllowedToJoin": { - "type": "text", - "placeholders": {} - }, - "botMessages": "Botų žinutės", - "@botMessages": { - "type": "text", - "placeholders": {} - }, - "channelCorruptedDecryptError": "Šifravimas buvo sugadintas", - "@channelCorruptedDecryptError": { - "type": "text", - "placeholders": {} - }, - "chatHasBeenAddedToThisSpace": "Pokalbis buvo pridėtas prie šios erdvės", - "@chatHasBeenAddedToThisSpace": {}, - "compareEmojiMatch": "Palyginkite jaustukus", - "@compareEmojiMatch": { - "type": "text", - "placeholders": {} - }, - "compareNumbersMatch": "Palyginkite skaičius", - "@compareNumbersMatch": { - "type": "text", - "placeholders": {} - }, - "contactHasBeenInvitedToTheGroup": "Kontaktas buvo pakviestas į grupę", - "@contactHasBeenInvitedToTheGroup": { - "type": "text", - "placeholders": {} - }, - "contentHasBeenReported": "Apie turinį pranešta serverio administratoriams", - "@contentHasBeenReported": { - "type": "text", - "placeholders": {} - }, - "createNewSpace": "Nauja erdvė", - "@createNewSpace": { - "type": "text", - "placeholders": {} - }, - "deactivateAccountWarning": "Tai deaktyvuos jūsų vartotojo paskyrą. Tai negali būti atšaukta! Ar jūs tuo tikri?", - "@deactivateAccountWarning": { - "type": "text", - "placeholders": {} - }, - "defaultPermissionLevel": "Numatytasis teisių lygis", - "@defaultPermissionLevel": { - "type": "text", - "placeholders": {} - }, - "enableEncryptionWarning": "Šifravimo nebegalėsite išjungti. Ar jūs tuo tikri?", - "@enableEncryptionWarning": { - "type": "text", - "placeholders": {} - }, - "send": "Siųsti", - "@send": { - "type": "text", - "placeholders": {} - }, - "sendAMessage": "Siųsti žinutę", - "@sendAMessage": { - "type": "text", - "placeholders": {} - }, - "toggleUnread": "Pažymėti kaip skaitytą/neskaitytą", - "@toggleUnread": { - "type": "text", - "placeholders": {} - }, - "tooManyRequestsWarning": "Per daug užklausų. Pabandykite dar kartą vėliau!", - "@tooManyRequestsWarning": { - "type": "text", - "placeholders": {} - }, - "waitingPartnerEmoji": "Laukiama, kol dalyvis priims jaustukus…", - "@waitingPartnerEmoji": { - "type": "text", - "placeholders": {} - }, - "waitingPartnerNumbers": "Laukiama, kol dalyvis priims skaičius…", - "@waitingPartnerNumbers": { - "type": "text", - "placeholders": {} - }, - "wallpaper": "Užsklanda", - "@wallpaper": { - "type": "text", - "placeholders": {} - }, - "warning": "Įspėjimas!", - "@warning": { - "type": "text", - "placeholders": {} - }, - "weSentYouAnEmail": "Išsiuntėme jums el. laišką", - "@weSentYouAnEmail": { - "type": "text", - "placeholders": {} - }, - "whoCanPerformWhichAction": "Kas gali atlikti kokį veiksmą", - "@whoCanPerformWhichAction": { - "type": "text", - "placeholders": {} - }, - "whoIsAllowedToJoinThisGroup": "Kam leidžiama prisijungti prie šios grupės", - "@whoIsAllowedToJoinThisGroup": { - "type": "text", - "placeholders": {} - }, - "whyDoYouWantToReportThis": "Kodėl norite apie tai pranešti?", - "@whyDoYouWantToReportThis": { - "type": "text", - "placeholders": {} - }, - "wipeChatBackup": "Ištrinti atsarginę pokalbių kopiją, kad sukurti naują atkūrimo raktą?", - "@wipeChatBackup": { - "type": "text", - "placeholders": {} - }, - "withTheseAddressesRecoveryDescription": "Naudodami šiuos adresus galite atkurti savo slaptažodį.", - "@withTheseAddressesRecoveryDescription": { - "type": "text", - "placeholders": {} - }, - "messageType": "Žinutės tipas", - "@messageType": {}, - "openGallery": "Atverti galeriją", - "@openGallery": {}, - "unknownDevice": "Nežinomas įrenginys", - "@unknownDevice": { - "type": "text", - "placeholders": {} - }, - "voiceMessage": "Balso žinutė", - "@voiceMessage": { - "type": "text", - "placeholders": {} - }, - "title": "FluffyChat", - "@title": { - "description": "Title for the application", - "type": "text", - "placeholders": {} - }, - "visibleForEveryone": "Matoma visiems", - "@visibleForEveryone": { - "type": "text", - "placeholders": {} - }, - "tryToSendAgain": "Pabandykite išsiųsti dar kartą", - "@tryToSendAgain": { - "type": "text", - "placeholders": {} - }, - "locationPermissionDeniedNotice": "Vietos leidimas atmestas. Suteikite leidimą kad galėtumėte bendrinti savo vietą.", - "@locationPermissionDeniedNotice": { - "type": "text", - "placeholders": {} - }, - "needPantalaimonWarning": "Atminkite, kad norint naudoti end-to-end šifravimą, reikalingas Pantalaimon.", - "@needPantalaimonWarning": { - "type": "text", - "placeholders": {} - }, - "noEncryptionForPublicRooms": "Šifravimą galite suaktyvinti tik tada, kai kambarys nebebus viešai pasiekiamas.", - "@noEncryptionForPublicRooms": { - "type": "text", - "placeholders": {} - }, - "noEmotesFound": "Nerasta jaustukų. 😕", - "@noEmotesFound": { - "type": "text", - "placeholders": {} - }, - "noGoogleServicesWarning": "Atrodo, kad jūsų telefone nėra Google Services. Tai geras sprendimas jūsų privatumui! Norėdami gauti tiesioginius pranešimus FluffyChat, rekomenduojame naudoti https://microg.org/ arba https://unifiedpush.org/.", - "@noGoogleServicesWarning": { - "type": "text", - "placeholders": {} - }, - "noPasswordRecoveryDescription": "Dar nepridėjote slaptažodžio atkūrimo būdo.", - "@noPasswordRecoveryDescription": { - "type": "text", - "placeholders": {} - }, - "oneClientLoggedOut": "Vienas iš jūsų klientų atsijungė", - "@oneClientLoggedOut": {}, - "onlineKeyBackupEnabled": "Internetinė atsarginė raktų kopija įjungta", - "@onlineKeyBackupEnabled": { - "type": "text", - "placeholders": {} - }, - "openCamera": "Atidarykite kamerą", - "@openCamera": { - "type": "text", - "placeholders": {} - }, - "openVideoCamera": "Atidarykite kamerą vaizdo įrašui", - "@openVideoCamera": { - "type": "text", - "placeholders": {} - }, - "editBundlesForAccount": "Redaguoti šios paskyros paketus", - "@editBundlesForAccount": {}, - "serverRequiresEmail": "Šis serveris turi patvirtinti jūsų el. pašto adresą registracijai.", - "@serverRequiresEmail": {}, - "addToBundle": "Pridėti prie paketų", - "@addToBundle": {}, - "removeFromBundle": "Pašalinkite iš šio paketo", - "@removeFromBundle": {}, - "bundleName": "Paketo vardas", - "@bundleName": {}, - "play": "Groti {fileName}", - "@play": { - "type": "text", - "placeholders": { - "fileName": {} - } - }, - "redactedAnEvent": "{username} pašalino įvykį", - "@redactedAnEvent": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "rejectedTheInvitation": "{username} atmetė kvietimą", - "@rejectedTheInvitation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "removedBy": "Pašalino vartotojas {username}", - "@removedBy": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "unbanFromChat": "Atblokuoti pokalbyje", - "@unbanFromChat": { - "type": "text", - "placeholders": {} - }, - "renderRichContent": "Atvaizduoti turtingą žinutės turinį", - "@renderRichContent": { - "type": "text", - "placeholders": {} - }, - "reportMessage": "Pranešti apie žinutę", - "@reportMessage": { - "type": "text", - "placeholders": {} - }, - "saveFile": "Išsaugoti failą", - "@saveFile": { - "type": "text", - "placeholders": {} - }, - "seenByUser": "Matė {username}", - "@seenByUser": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sendSticker": "Siųsti lipduką", - "@sendSticker": { - "type": "text", - "placeholders": {} - }, - "sentAFile": "📁 {username} atsiuntė failą", - "@sentAFile": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentAnAudio": "🎤 {username} atsiuntė garso įrašą", - "@sentAnAudio": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentASticker": "😊 {username} atsiuntė lipduką", - "@sentASticker": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sharedTheLocation": "{username} bendrino savo vietą", - "@sharedTheLocation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "startedACall": "{senderName} pradėjo skambutį", - "@startedACall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "theyDontMatch": "Jie nesutampa", - "@theyDontMatch": { - "type": "text", - "placeholders": {} - }, - "theyMatch": "Jie sutampa", - "@theyMatch": { - "type": "text", - "placeholders": {} - }, - "unbannedUser": "{username} atblokavo {targetName}", - "@unbannedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "unknownEvent": "Nežinomas įvykis '{type}'", - "@unknownEvent": { - "type": "text", - "placeholders": { - "type": {} - } - }, - "userAndOthersAreTyping": "{username} ir dar {count} kiti rašo…", - "@userAndOthersAreTyping": { - "type": "text", - "placeholders": { - "username": {}, - "count": {} - } - }, - "userAndUserAreTyping": "{username} ir {username2} rašo…", - "@userAndUserAreTyping": { - "type": "text", - "placeholders": { - "username": {}, - "username2": {} - } - }, - "userIsTyping": "{username} rašo…", - "@userIsTyping": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "userSentUnknownEvent": "{username} išsiuntė {type} įvykį", - "@userSentUnknownEvent": { - "type": "text", - "placeholders": { - "username": {}, - "type": {} - } - }, - "publish": "Paskelbti", - "@publish": {}, - "openChat": "Atverti pokalbį", - "@openChat": {}, - "reportUser": "Pranešti apie vartotoją", - "@reportUser": {}, - "dismiss": "Atsisakyti", - "@dismiss": {}, - "reactedWith": "{sender} sureagavo su {reaction}", - "@reactedWith": { - "type": "text", - "placeholders": { - "sender": {}, - "reaction": {} - } - }, - "unsupportedAndroidVersion": "Nepalaikoma Android versija", - "@unsupportedAndroidVersion": {}, - "emailOrUsername": "El. paštas arba vartotojo vardas", - "@emailOrUsername": {}, - "widgetVideo": "Video", - "@widgetVideo": {}, - "widgetNameError": "Pateikite rodomą vardą.", - "@widgetNameError": {}, - "acceptedTheInvitation": "👍 {username} priėmė kvietimą", - "@acceptedTheInvitation": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "activatedEndToEndEncryption": "🔐 {username} aktyvavo visapusį šifravimą", - "@activatedEndToEndEncryption": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "answeredTheCall": "{senderName} atsiliepė į skambutį", - "@answeredTheCall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "askVerificationRequest": "Priimti šią patvirtinimo užklausą iš {username}?", - "@askVerificationRequest": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "badServerLoginTypesException": "Namų serveris palaiko šiuos prisijungimo tipus:\n{serverVersions}\nTačiau ši programa palaiko tik:\n{supportedVersions}", - "@badServerLoginTypesException": { - "type": "text", - "placeholders": { - "serverVersions": {}, - "supportedVersions": {} - } - }, - "badServerVersionsException": "Namų serveris palaiko spec. versijas:\n{serverVersions}\nTačiau ši programa palaiko tik {supportedVersions}", - "@badServerVersionsException": { - "type": "text", - "placeholders": { - "serverVersions": {}, - "supportedVersions": {} - } - }, - "bannedUser": "{username} užblokavo {targetName}", - "@bannedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "changedTheHistoryVisibility": "{username} pakeitė istorijos matomumą", - "@changedTheHistoryVisibility": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheHistoryVisibilityTo": "{username} pakeitė istorijos matomumą į: {rules}", - "@changedTheHistoryVisibilityTo": { - "type": "text", - "placeholders": { - "username": {}, - "rules": {} - } - }, - "chatBackupDescription": "Jūsų senos žinutės yra apsaugotos atkūrimo raktu. Pasirūpinkite, kad jo neprarastumėte.", - "@chatBackupDescription": { - "type": "text", - "placeholders": {} - }, - "commandHint_create": "Sukurti tuščią grupinį pokalbį\nNaudokite --no-encryption kad išjungti šifravimą", - "@commandHint_create": { - "type": "text", - "description": "Usage hint for the command /create" - }, - "commandHint_dm": "Pradėti tiesioginį pokalbį\nNaudokite --no-encryption kad išjungti šifravimą", - "@commandHint_dm": { - "type": "text", - "description": "Usage hint for the command /dm" - }, - "commandHint_myroomavatar": "Nustatyti savo nuotrauką šiame kambaryje (su mxc-uri)", - "@commandHint_myroomavatar": { - "type": "text", - "description": "Usage hint for the command /myroomavatar" - }, - "commandHint_op": "Nustatyti naudotojo galios lygį (numatytasis: 50)", - "@commandHint_op": { - "type": "text", - "description": "Usage hint for the command /op" - }, - "commandHint_react": "Siųsti atsakymą kaip reakciją", - "@commandHint_react": { - "type": "text", - "description": "Usage hint for the command /react" - }, - "commandMissing": "{command} nėra komanda.", - "@commandMissing": { - "type": "text", - "placeholders": { - "command": {} - }, - "description": "State that {command} is not a valid /command." - }, - "containsDisplayName": "Turi rodomą vardą", - "@containsDisplayName": { - "type": "text", - "placeholders": {} - }, - "containsUserName": "Turi vartotojo vardą", - "@containsUserName": { - "type": "text", - "placeholders": {} - }, - "couldNotDecryptMessage": "Nepavyko iššifruoti pranešimo: {error}", - "@couldNotDecryptMessage": { - "type": "text", - "placeholders": { - "error": {} - } - }, - "countParticipants": "{count} dalyviai", - "@countParticipants": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "createdTheChat": "💬 {username} sukūrė pokalbį", - "@createdTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "emptyChat": "Tuščias pokalbis", - "@emptyChat": { - "type": "text", - "placeholders": {} - }, - "emoteExists": "Jaustukas jau egzistuoja!", - "@emoteExists": { - "type": "text", - "placeholders": {} - }, - "emoteInvalid": "Neteisingas jaustuko trumpasis kodas!", - "@emoteInvalid": { - "type": "text", - "placeholders": {} - }, - "emotePacks": "Jaustukų paketai kambariui", - "@emotePacks": { - "type": "text", - "placeholders": {} - }, - "emoteSettings": "Jaustukų nustatymai", - "@emoteSettings": { - "type": "text", - "placeholders": {} - }, - "emoteShortcode": "Jaustuko trumpasis kodas", - "@emoteShortcode": { - "type": "text", - "placeholders": {} - }, - "emoteWarnNeedToPick": "Turite pasirinkti jaustuko trumpąjį kodą ir paveiksliuką!", - "@emoteWarnNeedToPick": { - "type": "text", - "placeholders": {} - }, - "enableEmotesGlobally": "Įgalinti jaustukų paketą visur", - "@enableEmotesGlobally": { - "type": "text", - "placeholders": {} - }, - "endedTheCall": "{senderName} baigė skambutį", - "@endedTheCall": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "errorObtainingLocation": "Klaida nustatant vietą: {error}", - "@errorObtainingLocation": { - "type": "text", - "placeholders": { - "error": {} - } - }, - "groupWith": "Grupė su {displayname}", - "@groupWith": { - "type": "text", - "placeholders": { - "displayname": {} - } - }, - "hasWithdrawnTheInvitationFor": "{username} atšaukė {targetName} kvietimą", - "@hasWithdrawnTheInvitationFor": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "inviteForMe": "Pakvietimas man", - "@inviteForMe": { - "type": "text", - "placeholders": {} - }, - "inviteContactToGroup": "Pakviesti kontaktą į {groupName}", - "@inviteContactToGroup": { - "type": "text", - "placeholders": { - "groupName": {} - } - }, - "invitedUser": "📩 {username} pakvietė {targetName}", - "@invitedUser": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "inviteText": "{username} pakvietė jus prisijungti prie FluffyChat. \n1. Įdiekite FluffyChat: https://fluffychat.im \n2. Prisiregistruokite arba prisijunkite \n3. Atidarykite pakvietimo nuorodą: {link}", - "@inviteText": { - "type": "text", - "placeholders": { - "username": {}, - "link": {} - } - }, - "joinedTheChat": "👋 {username} prisijungė prie pokalbio", - "@joinedTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "kicked": "👞 {username} išmetė {targetName}", - "@kicked": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "kickedAndBanned": "🙅 {username} išmetė ir užblokavo {targetName}", - "@kickedAndBanned": { - "type": "text", - "placeholders": { - "username": {}, - "targetName": {} - } - }, - "lastActiveAgo": "Paskutinis aktyvumas: {localizedTimeShort}", - "@lastActiveAgo": { - "type": "text", - "placeholders": { - "localizedTimeShort": {} - } - }, - "loadCountMoreParticipants": "Įkelti dar {count} dalyvius", - "@loadCountMoreParticipants": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "logInTo": "Prisijungti prie {homeserver}", - "@logInTo": { - "type": "text", - "placeholders": { - "homeserver": {} - } - }, - "toggleFavorite": "Perjungti parankinius", - "@toggleFavorite": { - "type": "text", - "placeholders": {} - }, - "toggleMuted": "Perjungti nutildytą", - "@toggleMuted": { - "type": "text", - "placeholders": {} - }, - "cantOpenUri": "Nepavyksta atidaryti URI {uri}", - "@cantOpenUri": { - "type": "text", - "placeholders": { - "uri": {} - } - }, - "changedTheChatAvatar": "{username} pakeitė pokalbio avatarą", - "@changedTheChatAvatar": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheChatDescriptionTo": "{username} pakeitė pokalbio aprašymą į: '{description}'", - "@changedTheChatDescriptionTo": { - "type": "text", - "placeholders": { - "username": {}, - "description": {} - } - }, - "changedTheChatNameTo": "{username} pakeitė pokalbio pavadinimą į: '{chatname}'", - "@changedTheChatNameTo": { - "type": "text", - "placeholders": { - "username": {}, - "chatname": {} - } - }, - "changedTheChatPermissions": "{username} pakeitė pokalbių leidimus", - "@changedTheChatPermissions": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheDisplaynameTo": "{username} pakeitė rodomą vardą į: '{displayname}'", - "@changedTheDisplaynameTo": { - "type": "text", - "placeholders": { - "username": {}, - "displayname": {} - } - }, - "changedTheGuestAccessRules": "{username} pakeitė svečio prieigos taisykles", - "@changedTheGuestAccessRules": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheGuestAccessRulesTo": "{username} pakeitė svečio prieigos taisykles į: {rules}", - "@changedTheGuestAccessRulesTo": { - "type": "text", - "placeholders": { - "username": {}, - "rules": {} - } - }, - "changedTheJoinRules": "{username} pakeitė prisijungimo taisykles", - "@changedTheJoinRules": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheJoinRulesTo": "{username} pakeitė prisijungimo taisykles į: {joinRules}", - "@changedTheJoinRulesTo": { - "type": "text", - "placeholders": { - "username": {}, - "joinRules": {} - } - }, - "changedTheProfileAvatar": "{username} pakeitė savo avatarą", - "@changedTheProfileAvatar": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheRoomAliases": "{username} pakeitė kambario pseudonimus", - "@changedTheRoomAliases": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "changedTheRoomInvitationLink": "{username} pakeitė pakvietimo nuorodą", - "@changedTheRoomInvitationLink": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "videoWithSize": "Vaizdo įrašas ({size})", - "@videoWithSize": { - "type": "text", - "placeholders": { - "size": {} - } - }, - "pinMessage": "Prisegti prie kambario", - "@pinMessage": {}, - "confirmEventUnpin": "Ar tikrai norite visam laikui atsegti įvykį?", - "@confirmEventUnpin": {}, - "emojis": "Jaustukai", - "@emojis": {}, - "placeCall": "Skambinti", - "@placeCall": {}, - "voiceCall": "Balso skambutis", - "@voiceCall": {}, - "unsupportedAndroidVersionLong": "Šiai funkcijai reikalinga naujesnė Android versija. Patikrinkite, ar nėra naujinimų arba Lineage OS palaikymo.", - "@unsupportedAndroidVersionLong": {}, - "videoCallsBetaWarning": "Atminkite, kad vaizdo skambučiai šiuo metu yra beta versijos. Jie gali neveikti taip kaip tikėtasi, arba iš viso neveikti visose platformose.", - "@videoCallsBetaWarning": {}, - "experimentalVideoCalls": "Eksperimentiniai vaizdo skambučiai", - "@experimentalVideoCalls": {}, - "switchToAccount": "Perjungti paskyrą į {number}", - "@switchToAccount": { - "type": "number", - "placeholders": { - "number": {} - } - }, - "nextAccount": "Kita paskyra", - "@nextAccount": {}, - "previousAccount": "Ankstesnė paskyra", - "@previousAccount": {}, - "widgetEtherpad": "Teksto pastaba", - "@widgetEtherpad": {}, - "widgetJitsi": "Jitsi Meet", - "@widgetJitsi": {}, - "widgetName": "Vardas", - "@widgetName": {}, - "widgetUrlError": "Netinkamas URL.", - "@widgetUrlError": {}, - "youRejectedTheInvitation": "Jūs atmetėte kvietimą", - "@youRejectedTheInvitation": {}, - "youJoinedTheChat": "Jūs prisijungėte prie pokalbio", - "@youJoinedTheChat": {}, - "youAcceptedTheInvitation": "👍 Jūs priėmėte kvietimą", - "@youAcceptedTheInvitation": {}, - "youBannedUser": "Jūs užblokavote {user}", - "@youBannedUser": { - "placeholders": { - "user": {} - } - }, - "youHaveWithdrawnTheInvitationFor": "Jūs atšaukėte kvietimą {user}", - "@youHaveWithdrawnTheInvitationFor": { - "placeholders": { - "user": {} - } - }, - "youInvitedBy": "📩 Jus pakvietė {user}", - "@youInvitedBy": { - "placeholders": { - "user": {} - } - }, - "youKicked": "👞 Jūs išmetėte {user}", - "@youKicked": { - "placeholders": { - "user": {} - } - }, - "youInvitedUser": "📩 Pakvietėte {user}", - "@youInvitedUser": { - "placeholders": { - "user": {} - } - }, - "youKickedAndBanned": "🙅 Jūs išmetėte ir užblokavote {user}", - "@youKickedAndBanned": { - "placeholders": { - "user": {} - } - }, - "youUnbannedUser": "Jūs atblokavote {user}", - "@youUnbannedUser": { - "placeholders": { - "user": {} - } - }, - "dateAndTimeOfDay": "{date}, {timeOfDay}", - "@dateAndTimeOfDay": { - "type": "text", - "placeholders": { - "date": {}, - "timeOfDay": {} - } - }, - "dateWithoutYear": "{month}-{day}", - "@dateWithoutYear": { - "type": "text", - "placeholders": { - "month": {}, - "day": {} - } - }, - "dateWithYear": "{year}-{month}-{day}", - "@dateWithYear": { - "type": "text", - "placeholders": { - "year": {}, - "month": {}, - "day": {} - } - }, - "locationDisabledNotice": "Vietos nustatymo paslaugos yra išjungtos. Kad galėtumėte bendrinti savo buvimo vietą, įjunkite jas.", - "@locationDisabledNotice": { - "type": "text", - "placeholders": {} - }, - "noMatrixServer": "{server1} nėra Matrix serveris, ar vietoj jo naudoti {server2}?", - "@noMatrixServer": { - "type": "text", - "placeholders": { - "server1": {}, - "server2": {} - } - }, - "numUsersTyping": "{count} vartotojai rašo…", - "@numUsersTyping": { - "type": "text", - "placeholders": { - "count": {} - } - }, - "enableMultiAccounts": "(BETA) Įgalinkite kelias paskyras šiame įrenginyje", - "@enableMultiAccounts": {}, - "openInMaps": "Atidaryti žemėlapiuose", - "@openInMaps": { - "type": "text", - "placeholders": {} - }, - "sentAPicture": "🖼️ {username} atsiuntė nuotrauką", - "@sentAPicture": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentAVideo": "🎥 {username} atsiuntė vaizdo įrašą", - "@sentAVideo": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "sentCallInformations": "{senderName} išsiuntė skambučio informaciją", - "@sentCallInformations": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "setCustomEmotes": "Nustatyti pasirinktinius jaustukus", - "@setCustomEmotes": { - "type": "text", - "placeholders": {} - }, - "userLeftTheChat": "🚪 {username} paliko pokalbį", - "@userLeftTheChat": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "markAsRead": "Žymėti kaip skaitytą", - "@markAsRead": {}, - "pushRules": "Tiesioginių pranešimų taisyklės", - "@pushRules": { - "type": "text", - "placeholders": {} - }, - "unreadChats": "{unreadCount, plural, =1{1 unread chat} other{{unreadCount} neperskaityti pokalbiai}}", - "@unreadChats": { - "type": "text", - "placeholders": { - "unreadCount": {} - } - }, - "addWidget": "Pridėti programėlę", - "@addWidget": {}, - "widgetCustom": "Pasirinktinis", - "@widgetCustom": {}, - "errorAddingWidget": "Pridedant valdiklį įvyko klaida.", - "@errorAddingWidget": {}, - "askSSSSSign": "Kad galėtumėte prijungti kitą asmenį, įveskite savo saugyklos slaptafrazę arba atkūrimo raktą.", - "@askSSSSSign": { - "type": "text", - "placeholders": {} - }, - "autoplayImages": "Automatiškai leisti animuotus lipdukus ir jaustukus", - "@autoplayImages": { - "type": "text", - "placeholder": {} - }, - "commandHint_markasdm": "Pažymėti kaip tiesioginio pokalbio kambarį", - "@commandHint_markasdm": {}, - "dehydrateTorLong": "TOR naudotojams rekomenduojama eksportuoti sesiją prieš uždarant langą.", - "@dehydrateTorLong": {}, - "dehydrateWarning": "Šio veiksmo negalima atšaukti. Įsitikinkite, kad saugiai saugote atsarginę kopiją.", - "@dehydrateWarning": {}, - "hydrateTorLong": "Ar paskutinį kartą eksportavote savo sesiją naudodami TOR? Greitai ją importuokite ir tęskite pokalbį.", - "@hydrateTorLong": {}, - "commandHint_markasgroup": "Pažymėti kaip grupę", - "@commandHint_markasgroup": {}, - "pleaseEnterRecoveryKeyDescription": "Norėdami atrakinti senas žinutes, įveskite atkūrimo raktą, kuris buvo sukurtas ankstesnės sesijos metu. Atkūrimo raktas NĖRA jūsų slaptažodis.", - "@pleaseEnterRecoveryKeyDescription": {}, - "callingPermissions": "Skambinimo leidimai", - "@callingPermissions": {}, - "storeInAppleKeyChain": "Saugoti Apple raktų grandinėje", - "@storeInAppleKeyChain": {}, - "callingAccount": "Skambinimo paskyra", - "@callingAccount": {}, - "newSpace": "Nauja erdvė", - "@newSpace": {}, - "callingAccountDetails": "Leidžia FluffyChat naudoti vietinę Android rinkiklio programą.", - "@callingAccountDetails": {}, - "appearOnTop": "Rodyti viršuje", - "@appearOnTop": {}, - "enterSpace": "Įeiti į erdvę", - "@enterSpace": {}, - "enterRoom": "Įeiti į kambarį", - "@enterRoom": {}, - "allSpaces": "Visos erdvės", - "@allSpaces": {}, - "user": "Vartotojas", - "@user": {}, - "custom": "Pasirinktinis", - "@custom": {}, - "confirmMatrixId": "Norėdami ištrinti savo paskyrą, patvirtinkite savo Matrix ID.", - "@confirmMatrixId": {}, - "supposedMxid": "Tai turėtų būti {mxid}", - "@supposedMxid": { - "type": "text", - "placeholders": { - "mxid": {} - } - }, - "dehydrate": "Eksportuoti sesiją ir išvalyti įrenginį", - "@dehydrate": {}, - "dehydrateTor": "TOR Naudotojai: Eksportuoti sesiją", - "@dehydrateTor": {}, - "hydrateTor": "TOR Naudotojai: Importuoti sesijos eksportą", - "@hydrateTor": {}, - "hydrate": "Atkurti iš atsarginės kopijos failo", - "@hydrate": {}, - "pleaseEnterRecoveryKey": "Įveskite savo atkūrimo raktą:", - "@pleaseEnterRecoveryKey": {}, - "recoveryKey": "Atkūrimo raktas", - "@recoveryKey": {}, - "recoveryKeyLost": "Pamestas atkūrimo raktas?", - "@recoveryKeyLost": {}, - "indexedDbErrorLong": "Deja, pagal numatytuosius nustatymus žinučių saugojimas privačiame režime nėra įjungtas.\nPrašome apsilankyti\n - about:config\n - nustatykite dom.indexedDB.privateBrowsing.enabled į true\nPriešingu atveju FluffyChat paleisti neįmanoma.", - "@indexedDbErrorLong": {}, - "countFiles": "{count} failai", - "@countFiles": { - "placeholders": { - "count": {} - } - }, - "storeInSecureStorageDescription": "Atkūrimo raktą laikyti saugioje šio prietaiso saugykloje.", - "@storeInSecureStorageDescription": {}, - "saveKeyManuallyDescription": "Įrašykite šį raktą rankiniu būdu, įjungę sistemos bendrinimo dialogo langą arba iškarpinę.", - "@saveKeyManuallyDescription": {}, - "users": "Vartotojai", - "@users": {}, - "storeSecurlyOnThisDevice": "Saugiai laikyti šiame prietaise", - "@storeSecurlyOnThisDevice": {}, - "unlockOldMessages": "Atrakinti senas žinutes", - "@unlockOldMessages": {}, - "storeInAndroidKeystore": "Saugoti Android raktų saugykloje", - "@storeInAndroidKeystore": {}, - "indexedDbErrorTitle": "Privataus režimo problemos", - "@indexedDbErrorTitle": {}, - "noKeyForThisMessage": "Taip gali atsitikti, jei žinutė buvo išsiųsta prieš prisijungiant prie paskyros šiame prietaise.\n\nTaip pat gali būti, kad siuntėjas užblokavo jūsų prietaisą arba kažkas sutriko su interneto ryšiu.\n\nAr galite perskaityti žinutę kitoje sesijoje? Tada galite perkelti žinutę iš jos! Eikite į Nustatymai > Prietaisai ir įsitikinkite, kad jūsų prietaisai patvirtino vienas kitą. Kai kitą kartą atidarysite kambarį ir abi sesijos bus pirmame plane, raktai bus perduoti automatiškai.\n\nNenorite prarasti raktų atsijungdami arba keisdami įrenginius? Įsitikinkite, kad nustatymuose įjungėte pokalbių atsarginę kopiją.", - "@noKeyForThisMessage": {}, - "foregroundServiceRunning": "Šis pranešimas rodomas, kai veikia pirmojo plano paslauga.", - "@foregroundServiceRunning": {}, - "screenSharingTitle": "ekrano bendrinimas", - "@screenSharingTitle": {}, - "appearOnTopDetails": "Leidžia programėlę rodyti viršuje (nebūtina, jei jau esate nustatę Fluffychat kaip skambinimo paskyrą)", - "@appearOnTopDetails": {}, - "otherCallingPermissions": "Mikrofonas, kamera ir kiti FluffyChat leidimai", - "@otherCallingPermissions": {}, - "whyIsThisMessageEncrypted": "Kodėl ši žinutė neperskaitoma?", - "@whyIsThisMessageEncrypted": {}, - "newGroup": "Nauja grupė", - "@newGroup": {}, - "screenSharingDetail": "Bendrinate savo ekraną per FuffyChat", - "@screenSharingDetail": {}, - "numChats": "{number} pokalbiai", - "@numChats": { - "type": "number", - "placeholders": { - "number": {} - } - }, - "hideUnimportantStateEvents": "Slėpti nesvarbius būsenos įvykius", - "@hideUnimportantStateEvents": {}, - "@hugContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@jumpToLastReadMessage": {}, - "@allRooms": { - "type": "text", - "placeholders": {} - }, - "@commandHint_cuddle": {}, - "@reportErrorDescription": {}, - "@startFirstChat": {}, - "@setColorTheme": {}, - "@banUserDescription": {}, - "@removeDevicesDescription": {}, - "@tryAgain": {}, - "@unbanUserDescription": {}, - "@messagesStyle": {}, - "@newSpaceDescription": {}, - "@chatDescription": {}, - "@encryptThisChat": {}, - "@reopenChat": {}, - "@pushNotificationsNotAvailable": {}, - "@invalidServerName": {}, - "@chatPermissions": {}, - "@signInWithPassword": {}, - "@makeAdminDescription": {}, - "@setChatDescription": {}, - "@importFromZipFile": {}, - "@noOtherDevicesFound": {}, - "@redactedBy": { - "type": "text", - "placeholders": { - "username": {} - } - }, - "@signInWith": { - "type": "text", - "placeholders": { - "provider": {} - } - }, - "@fileIsTooBigForServer": {}, - "@readUpToHere": {}, - "@optionalRedactReason": {}, - "@archiveRoomDescription": {}, - "@exportEmotePack": {}, - "@inviteContactToGroupQuestion": {}, - "@redactedByBecause": { - "type": "text", - "placeholders": { - "username": {}, - "reason": {} - } - }, - "@fileHasBeenSavedAt": { - "type": "text", - "placeholders": { - "path": {} - } - }, - "@redactMessageDescription": {}, - "@invalidInput": {}, - "@doNotShowAgain": {}, - "@report": {}, - "@googlyEyesContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@addChatDescription": {}, - "@hasKnocked": { - "placeholders": { - "user": {} - } - }, - "@openLinkInBrowser": {}, - "@disableEncryptionWarning": {}, - "@directChat": {}, - "@wrongPinEntered": { - "type": "text", - "placeholders": { - "seconds": {} - } - }, - "@sendTypingNotifications": {}, - "@inviteGroupChat": {}, - "@invitePrivateChat": {}, - "@importEmojis": {}, - "@wasDirectChatDisplayName": { - "type": "text", - "placeholders": { - "oldDisplayName": {} - } - }, - "@noChatDescriptionYet": {}, - "@learnMore": {}, - "@notAnImage": {}, - "@chatDescriptionHasBeenChanged": {}, - "@roomUpgradeDescription": {}, - "@pleaseEnterANumber": {}, - "@profileNotFound": {}, - "@jump": {}, - "@sorryThatsNotPossible": {}, - "@shareInviteLink": {}, - "@cuddleContent": { - "type": "text", - "placeholders": { - "senderName": {} - } - }, - "@deviceKeys": {}, - "@emoteKeyboardNoRecents": { - "type": "text", - "placeholders": {} - }, - "@setTheme": {}, - "@commandHint_hug": {}, - "@replace": {}, - "@commandHint_googly": {}, - "@pleaseTryAgainLaterOrChooseDifferentServer": {}, - "@createGroup": {}, - "@noBackupWarning": {}, - "@kickUserDescription": {}, - "@importNow": {}, - "@invite": {} -} \ No newline at end of file + "commandHint_leave": "Palikti pokalbių kambarį", + "@commandHint_leave": { + "type": "text", + "description": "Usage hint for the command /leave" + }, + "confirm": "Patvirtinti", + "@confirm": { + "type": "text", + "placeholders": {} + }, + "cancel": "Atšaukti", + "@cancel": { + "type": "text", + "placeholders": {} + }, + "edit": "Redaguoti", + "@edit": { + "type": "text", + "placeholders": {} + }, + "downloadFile": "Atsisiųsti failą", + "@downloadFile": { + "type": "text", + "placeholders": {} + }, + "about": "Apie", + "@about": { + "type": "text", + "placeholders": {} + }, + "all": "Visi", + "@all": { + "type": "text", + "placeholders": {} + }, + "fluffychat": "FluffyChat", + "@fluffychat": { + "type": "text", + "placeholders": {} + }, + "fileName": "Failo vardas", + "@fileName": { + "type": "text", + "placeholders": {} + }, + "changePassword": "Keisti slaptažodį", + "@changePassword": { + "type": "text", + "placeholders": {} + }, + "close": "Uždaryti", + "@close": { + "type": "text", + "placeholders": {} + }, + "archive": "Archyvas", + "@archive": { + "type": "text", + "placeholders": {} + }, + "skip": "Praleisti", + "@skip": { + "type": "text", + "placeholders": {} + }, + "share": "Bendrinti", + "@share": { + "type": "text", + "placeholders": {} + }, + "showPassword": "Rodyti slaptažodį", + "@showPassword": { + "type": "text", + "placeholders": {} + }, + "time": "Laikas", + "@time": {}, + "settings": "Nustatytmai", + "@settings": { + "type": "text", + "placeholders": {} + }, + "sender": "Siuntėjas", + "@sender": {}, + "yes": "Taip", + "@yes": { + "type": "text", + "placeholders": {} + }, + "you": "Jūs", + "@you": { + "type": "text", + "placeholders": {} + }, + "logout": "Atsijungti", + "@logout": { + "type": "text", + "placeholders": {} + }, + "messages": "Žinutės", + "@messages": { + "type": "text", + "placeholders": {} + }, + "scanQrCode": "Nuskanuokite QR kodą", + "@scanQrCode": {}, + "ok": "OK", + "@ok": { + "type": "text", + "placeholders": {} + }, + "addAccount": "Pridėti paskyrą", + "@addAccount": {}, + "or": "Arba", + "@or": { + "type": "text", + "placeholders": {} + }, + "password": "Slaptažodis", + "@password": { + "type": "text", + "placeholders": {} + }, + "passwordHasBeenChanged": "Slaptažodis pakeistas", + "@passwordHasBeenChanged": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourPassword": "Įveskite savo slaptažodį", + "@pleaseEnterYourPassword": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourUsername": "Įveskite savo vartotojo vardą", + "@pleaseEnterYourUsername": { + "type": "text", + "placeholders": {} + }, + "reply": "Atsakyti", + "@reply": { + "type": "text", + "placeholders": {} + }, + "blockDevice": "Blokuoti įrenginį", + "@blockDevice": { + "type": "text", + "placeholders": {} + }, + "blocked": "Užblokuotas", + "@blocked": { + "type": "text", + "placeholders": {} + }, + "chooseAStrongPassword": "Pasirinkite saugų slaptažodį", + "@chooseAStrongPassword": { + "type": "text", + "placeholders": {} + }, + "deleteMessage": "Ištrinti žinutę", + "@deleteMessage": { + "type": "text", + "placeholders": {} + }, + "device": "Įrenginys", + "@device": { + "type": "text", + "placeholders": {} + }, + "deviceId": "Įrenginio ID", + "@deviceId": { + "type": "text", + "placeholders": {} + }, + "devices": "Įrenginiai", + "@devices": { + "type": "text", + "placeholders": {} + }, + "homeserver": "Namų serveris", + "@homeserver": {}, + "enterYourHomeserver": "Įveskite namų serverį", + "@enterYourHomeserver": { + "type": "text", + "placeholders": {} + }, + "everythingReady": "Viskas paruošta!", + "@everythingReady": { + "type": "text", + "placeholders": {} + }, + "fontSize": "Šrifto dydis", + "@fontSize": { + "type": "text", + "placeholders": {} + }, + "clearArchive": "Išvalyti archyvą", + "@clearArchive": {}, + "create": "Sukurti", + "@create": { + "type": "text", + "placeholders": {} + }, + "connect": "Prisijungti", + "@connect": { + "type": "text", + "placeholders": {} + }, + "people": "Žmonės", + "@people": { + "type": "text", + "placeholders": {} + }, + "moderator": "Moderatorius", + "@moderator": { + "type": "text", + "placeholders": {} + }, + "muteChat": "Nutildyti pokalbį", + "@muteChat": { + "type": "text", + "placeholders": {} + }, + "newChat": "Naujas pokalbis", + "@newChat": { + "type": "text", + "placeholders": {} + }, + "none": "Nė vienas", + "@none": { + "type": "text", + "placeholders": {} + }, + "noPermission": "Nėra leidimo", + "@noPermission": { + "type": "text", + "placeholders": {} + }, + "noRoomsFound": "Nerasta kambarių…", + "@noRoomsFound": { + "type": "text", + "placeholders": {} + }, + "notifications": "Pranešimai", + "@notifications": { + "type": "text", + "placeholders": {} + }, + "notificationsEnabledForThisAccount": "Pranešimai aktyvuoti šitai paskyrai", + "@notificationsEnabledForThisAccount": { + "type": "text", + "placeholders": {} + }, + "obtainingLocation": "Gaunama vieta…", + "@obtainingLocation": { + "type": "text", + "placeholders": {} + }, + "offensive": "Agresyvus", + "@offensive": { + "type": "text", + "placeholders": {} + }, + "offline": "Neprisijungta", + "@offline": { + "type": "text", + "placeholders": {} + }, + "online": "Prisijungta", + "@online": { + "type": "text", + "placeholders": {} + }, + "oopsPushError": "Oi! Deja, nustatant tiesioginius pranešimus įvyko klaida.", + "@oopsPushError": { + "type": "text", + "placeholders": {} + }, + "oopsSomethingWentWrong": "Oi, kažkas nutiko ne taip…", + "@oopsSomethingWentWrong": { + "type": "text", + "placeholders": {} + }, + "openAppToReadMessages": "Atidarykite programėlę, kad perskaityti žinutes", + "@openAppToReadMessages": { + "type": "text", + "placeholders": {} + }, + "link": "Nuoroda", + "@link": {}, + "participant": "Dalyvis", + "@participant": { + "type": "text", + "placeholders": {} + }, + "passphraseOrKey": "Slapta frazė arba atkūrimo raktas", + "@passphraseOrKey": { + "type": "text", + "placeholders": {} + }, + "passwordForgotten": "Slaptažodis užmirštas", + "@passwordForgotten": { + "type": "text", + "placeholders": {} + }, + "passwordRecovery": "Slaptažodžio atkūrimas", + "@passwordRecovery": { + "type": "text", + "placeholders": {} + }, + "pickImage": "Pasirinkite paveiksliuką", + "@pickImage": { + "type": "text", + "placeholders": {} + }, + "pin": "Prisegti", + "@pin": { + "type": "text", + "placeholders": {} + }, + "pleaseChoose": "Prašome pasirinkti", + "@pleaseChoose": { + "type": "text", + "placeholders": {} + }, + "pleaseChooseAPasscode": "Pasirinkite slaptą kodą", + "@pleaseChooseAPasscode": { + "type": "text", + "placeholders": {} + }, + "pleaseClickOnLink": "Paspauskite nuorodą el. pašte ir tęskite toliau.", + "@pleaseClickOnLink": { + "type": "text", + "placeholders": {} + }, + "pleaseEnter4Digits": "Įveskite 4 skaitmenis arba palikite tuščią, jei norite išjungti programėlės užraktą.", + "@pleaseEnter4Digits": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourPin": "Įveskite savo PIN kodą", + "@pleaseEnterYourPin": { + "type": "text", + "placeholders": {} + }, + "pleaseFollowInstructionsOnWeb": "Vadovaukitės svetainėje pateiktais nurodymais ir bakstelėkite Toliau.", + "@pleaseFollowInstructionsOnWeb": { + "type": "text", + "placeholders": {} + }, + "privacy": "Privatumas", + "@privacy": { + "type": "text", + "placeholders": {} + }, + "publicRooms": "Vieši kambariai", + "@publicRooms": { + "type": "text", + "placeholders": {} + }, + "reason": "Priežastis", + "@reason": { + "type": "text", + "placeholders": {} + }, + "recording": "Įrašymas", + "@recording": { + "type": "text", + "placeholders": {} + }, + "redactMessage": "Pašalinti žinutę", + "@redactMessage": { + "type": "text", + "placeholders": {} + }, + "register": "Registruotis", + "@register": { + "type": "text", + "placeholders": {} + }, + "reject": "Atmesti", + "@reject": { + "type": "text", + "placeholders": {} + }, + "rejoin": "Vėl prisijungti", + "@rejoin": { + "type": "text", + "placeholders": {} + }, + "remove": "Pašalinti", + "@remove": { + "type": "text", + "placeholders": {} + }, + "removeAllOtherDevices": "Pašalinti visus kitus įrenginius", + "@removeAllOtherDevices": { + "type": "text", + "placeholders": {} + }, + "removeDevice": "Pašalinti įrenginį", + "@removeDevice": { + "type": "text", + "placeholders": {} + }, + "removeYourAvatar": "Pašalinti savo avatarą", + "@removeYourAvatar": { + "type": "text", + "placeholders": {} + }, + "replaceRoomWithNewerVersion": "Pakeisti kambarį naujesne versija", + "@replaceRoomWithNewerVersion": { + "type": "text", + "placeholders": {} + }, + "requestPermission": "Prašyti leidimo", + "@requestPermission": { + "type": "text", + "placeholders": {} + }, + "roomHasBeenUpgraded": "Kambarys buvo atnaujintas", + "@roomHasBeenUpgraded": { + "type": "text", + "placeholders": {} + }, + "roomVersion": "Kambario versija", + "@roomVersion": { + "type": "text", + "placeholders": {} + }, + "search": "Ieškoti", + "@search": { + "type": "text", + "placeholders": {} + }, + "accept": "Sutinku", + "@accept": { + "type": "text", + "placeholders": {} + }, + "repeatPassword": "Pakartokite slaptažodį", + "@repeatPassword": {}, + "addEmail": "Pridėti el. paštą", + "@addEmail": { + "type": "text", + "placeholders": {} + }, + "admin": "Administratorius", + "@admin": { + "type": "text", + "placeholders": {} + }, + "alias": "slapyvardis", + "@alias": { + "type": "text", + "placeholders": {} + }, + "allChats": "Visi pokalbiai", + "@allChats": { + "type": "text", + "placeholders": {} + }, + "anyoneCanJoin": "Bet kas gali prisijungti", + "@anyoneCanJoin": { + "type": "text", + "placeholders": {} + }, + "areYouSure": "Ar esate tikri?", + "@areYouSure": { + "type": "text", + "placeholders": {} + }, + "areYouSureYouWantToLogout": "Ar tikrai norite atsijungti?", + "@areYouSureYouWantToLogout": { + "type": "text", + "placeholders": {} + }, + "changeTheHomeserver": "Pakeisti namų serverį", + "@changeTheHomeserver": { + "type": "text", + "placeholders": {} + }, + "changeTheme": "Keisti savo stilių", + "@changeTheme": { + "type": "text", + "placeholders": {} + }, + "changeTheNameOfTheGroup": "Keisti grupės pavadinimą", + "@changeTheNameOfTheGroup": { + "type": "text", + "placeholders": {} + }, + "changeYourAvatar": "Keisti savo avatarą", + "@changeYourAvatar": { + "type": "text", + "placeholders": {} + }, + "chat": "Pokalbis", + "@chat": { + "type": "text", + "placeholders": {} + }, + "chatDetails": "Pokalbio detalės", + "@chatDetails": { + "type": "text", + "placeholders": {} + }, + "chats": "Pokalbiai", + "@chats": { + "type": "text", + "placeholders": {} + }, + "commandHint_ban": "Užblokuoti vartotoją šiame kambaryje", + "@commandHint_ban": { + "type": "text", + "description": "Usage hint for the command /ban" + }, + "commandHint_clearcache": "Išvalyti laikiną talpyklą", + "@commandHint_clearcache": { + "type": "text", + "description": "Usage hint for the command /clearcache" + }, + "commandHint_discardsession": "Atmesti sesiją", + "@commandHint_discardsession": { + "type": "text", + "description": "Usage hint for the command /discardsession" + }, + "commandHint_html": "Siųsti tekstą HTML formatu", + "@commandHint_html": { + "type": "text", + "description": "Usage hint for the command /html" + }, + "commandHint_invite": "Pakviesti vartotoją į šitą kambarį", + "@commandHint_invite": { + "type": "text", + "description": "Usage hint for the command /invite" + }, + "commandHint_join": "Prisijungti prie nurodyto kambario", + "@commandHint_join": { + "type": "text", + "description": "Usage hint for the command /join" + }, + "commandHint_kick": "Pašalinti vartotoja iš šito kambario", + "@commandHint_kick": { + "type": "text", + "description": "Usage hint for the command /kick" + }, + "commandHint_myroomnick": "Nustatyti savo rodomą vardą šiame kambaryje", + "@commandHint_myroomnick": { + "type": "text", + "description": "Usage hint for the command /myroomnick" + }, + "commandHint_plain": "Siųsti neformatuotą tekstą", + "@commandHint_plain": { + "type": "text", + "description": "Usage hint for the command /plain" + }, + "commandHint_send": "Siųsti tekstą", + "@commandHint_send": { + "type": "text", + "description": "Usage hint for the command /send" + }, + "commandHint_unban": "Atblokuoti vartotoją šiame kambaryje", + "@commandHint_unban": { + "type": "text", + "description": "Usage hint for the command /unban" + }, + "commandInvalid": "Neteisinga komanda", + "@commandInvalid": { + "type": "text" + }, + "configureChat": "Konfigūruoti pokalbį", + "@configureChat": { + "type": "text", + "placeholders": {} + }, + "copiedToClipboard": "Nukopijuota į iškarpinę", + "@copiedToClipboard": { + "type": "text", + "placeholders": {} + }, + "copy": "Kopijuoti", + "@copy": { + "type": "text", + "placeholders": {} + }, + "copyToClipboard": "Koipjuoti į iškarpinę", + "@copyToClipboard": { + "type": "text", + "placeholders": {} + }, + "currentlyActive": "Šiuo metu aktyvus", + "@currentlyActive": { + "type": "text", + "placeholders": {} + }, + "darkTheme": "Tamsi", + "@darkTheme": { + "type": "text", + "placeholders": {} + }, + "delete": "Ištrinti", + "@delete": { + "type": "text", + "placeholders": {} + }, + "deleteAccount": "Panaikinti paskyra", + "@deleteAccount": { + "type": "text", + "placeholders": {} + }, + "directChats": "Tiesioginiai pokalbiai", + "@directChats": { + "type": "text", + "placeholders": {} + }, + "encrypted": "Užšifruotas", + "@encrypted": { + "type": "text", + "placeholders": {} + }, + "encryptionNotEnabled": "Šifravimas aktyvuotas", + "@encryptionNotEnabled": { + "type": "text", + "placeholders": {} + }, + "enterAnEmailAddress": "Įveskite el. pašto adresą", + "@enterAnEmailAddress": { + "type": "text", + "placeholders": {} + }, + "extremeOffensive": "Itin įžeidžiantis", + "@extremeOffensive": { + "type": "text", + "placeholders": {} + }, + "forward": "Toliau", + "@forward": { + "type": "text", + "placeholders": {} + }, + "fromJoining": "Nuo prisijungimo", + "@fromJoining": { + "type": "text", + "placeholders": {} + }, + "fromTheInvitation": "Nuo pakvietimo", + "@fromTheInvitation": { + "type": "text", + "placeholders": {} + }, + "goToTheNewRoom": "Eiti į naują kambarį", + "@goToTheNewRoom": { + "type": "text", + "placeholders": {} + }, + "group": "Grupė", + "@group": { + "type": "text", + "placeholders": {} + }, + "groupIsPublic": "Grupė yra vieša", + "@groupIsPublic": { + "type": "text", + "placeholders": {} + }, + "groups": "Grupės", + "@groups": { + "type": "text", + "placeholders": {} + }, + "guestsAreForbidden": "Svečiams draudžiama", + "@guestsAreForbidden": { + "type": "text", + "placeholders": {} + }, + "guestsCanJoin": "Svečiai gali prisijungti", + "@guestsCanJoin": { + "type": "text", + "placeholders": {} + }, + "help": "Pagalba", + "@help": { + "type": "text", + "placeholders": {} + }, + "hideRedactedEvents": "Slėpti pašalintus įvykius", + "@hideRedactedEvents": { + "type": "text", + "placeholders": {} + }, + "hideUnknownEvents": "Slėpti nežinomus įvykius", + "@hideUnknownEvents": { + "type": "text", + "placeholders": {} + }, + "identity": "Tapatybė", + "@identity": { + "type": "text", + "placeholders": {} + }, + "ignore": "Ignoruoti", + "@ignore": { + "type": "text", + "placeholders": {} + }, + "ignoredUsers": "Ignoruoti vartotojai", + "@ignoredUsers": { + "type": "text", + "placeholders": {} + }, + "leave": "Palikti", + "@leave": { + "type": "text", + "placeholders": {} + }, + "memberChanges": "Narių pokyčiai", + "@memberChanges": { + "type": "text", + "placeholders": {} + }, + "mention": "Paminėti", + "@mention": { + "type": "text", + "placeholders": {} + }, + "encryption": "Šifravimas", + "@encryption": { + "type": "text", + "placeholders": {} + }, + "enableEncryption": "Aktyvuoti šifravimą", + "@enableEncryption": { + "type": "text", + "placeholders": {} + }, + "editBlockedServers": "Redaguoti blokuotus serverius", + "@editBlockedServers": { + "type": "text", + "placeholders": {} + }, + "login": "Prisijungti", + "@login": { + "type": "text", + "placeholders": {} + }, + "sendOnEnter": "Išsiųsti paspaudus Enter", + "@sendOnEnter": {}, + "banFromChat": "Užblokuoti iš pokalbio", + "@banFromChat": { + "type": "text", + "placeholders": {} + }, + "banned": "Užblokuotas", + "@banned": { + "type": "text", + "placeholders": {} + }, + "changeDeviceName": "Pakeisti įrenginio vardą", + "@changeDeviceName": { + "type": "text", + "placeholders": {} + }, + "yourChatBackupHasBeenSetUp": "Jūsų pokalbio atsarginė kopija buvo nustatyta.", + "@yourChatBackupHasBeenSetUp": {}, + "chatBackup": "Pokalbio atsargine kopija", + "@chatBackup": { + "type": "text", + "placeholders": {} + }, + "commandHint_me": "Apibūdinkite save", + "@commandHint_me": { + "type": "text", + "description": "Usage hint for the command /me" + }, + "displaynameHasBeenChanged": "Rodomas vardas buvo pakeistas", + "@displaynameHasBeenChanged": { + "type": "text", + "placeholders": {} + }, + "editDisplayname": "Redaguoti rodomą vardą", + "@editDisplayname": { + "type": "text", + "placeholders": {} + }, + "editRoomAliases": "Redaguoti kambario pseudonimus", + "@editRoomAliases": { + "type": "text", + "placeholders": {} + }, + "editRoomAvatar": "Redaguoti kambario avatarą", + "@editRoomAvatar": { + "type": "text", + "placeholders": {} + }, + "howOffensiveIsThisContent": "Kiek įžeižiantis šis turinys?", + "@howOffensiveIsThisContent": { + "type": "text", + "placeholders": {} + }, + "id": "ID", + "@id": { + "type": "text", + "placeholders": {} + }, + "iHaveClickedOnLink": "Aš paspaudžiau nuorodą", + "@iHaveClickedOnLink": { + "type": "text", + "placeholders": {} + }, + "incorrectPassphraseOrKey": "Neteisinga slaptafrazė arba atkūrimo raktas", + "@incorrectPassphraseOrKey": { + "type": "text", + "placeholders": {} + }, + "inoffensive": "Neįžeidžiantis", + "@inoffensive": { + "type": "text", + "placeholders": {} + }, + "inviteContact": "Pakviesti kontaktą", + "@inviteContact": { + "type": "text", + "placeholders": {} + }, + "invited": "Pakviestas", + "@invited": { + "type": "text", + "placeholders": {} + }, + "invitedUsersOnly": "Tik pakviesti vartotojai", + "@invitedUsersOnly": { + "type": "text", + "placeholders": {} + }, + "isTyping": "rašo…", + "@isTyping": { + "type": "text", + "placeholders": {} + }, + "joinRoom": "Prisijungti prie kambario", + "@joinRoom": { + "type": "text", + "placeholders": {} + }, + "kickFromChat": "Išmesti iš pokalbio", + "@kickFromChat": { + "type": "text", + "placeholders": {} + }, + "leftTheChat": "Paliko pokalbį", + "@leftTheChat": { + "type": "text", + "placeholders": {} + }, + "license": "Licencija", + "@license": { + "type": "text", + "placeholders": {} + }, + "lightTheme": "Šviesi", + "@lightTheme": { + "type": "text", + "placeholders": {} + }, + "loadingPleaseWait": "Kraunama… Prašome palaukti.", + "@loadingPleaseWait": { + "type": "text", + "placeholders": {} + }, + "loadMore": "Rodyti daugiau…", + "@loadMore": { + "type": "text", + "placeholders": {} + }, + "newMessageInFluffyChat": "💬 Nauja žinutė FluffyChat'e", + "@newMessageInFluffyChat": { + "type": "text", + "placeholders": {} + }, + "newVerificationRequest": "Nauja patvirtinimo užklausa!", + "@newVerificationRequest": { + "type": "text", + "placeholders": {} + }, + "next": "Toliau", + "@next": { + "type": "text", + "placeholders": {} + }, + "no": "Ne", + "@no": { + "type": "text", + "placeholders": {} + }, + "noConnectionToTheServer": "Nėra ryšio su serveriu", + "@noConnectionToTheServer": { + "type": "text", + "placeholders": {} + }, + "setInvitationLink": "Nustatyti pakvietimo nuorodą", + "@setInvitationLink": { + "type": "text", + "placeholders": {} + }, + "singlesignon": "Vienkartinis prisijungimas", + "@singlesignon": { + "type": "text", + "placeholders": {} + }, + "sourceCode": "Programinis kodas", + "@sourceCode": { + "type": "text", + "placeholders": {} + }, + "spaceIsPublic": "Erdvė yra vieša", + "@spaceIsPublic": { + "type": "text", + "placeholders": {} + }, + "spaceName": "Erdvės pavadinimas", + "@spaceName": { + "type": "text", + "placeholders": {} + }, + "status": "Būsena", + "@status": { + "type": "text", + "placeholders": {} + }, + "statusExampleMessage": "Kaip sekasi šiandien?", + "@statusExampleMessage": { + "type": "text", + "placeholders": {} + }, + "submit": "Pateikti", + "@submit": { + "type": "text", + "placeholders": {} + }, + "synchronizingPleaseWait": "Sinchronizuojama… Prašome palaukti.", + "@synchronizingPleaseWait": { + "type": "text", + "placeholders": {} + }, + "transferFromAnotherDevice": "Perkėlimas iš kito įrenginio", + "@transferFromAnotherDevice": { + "type": "text", + "placeholders": {} + }, + "verify": "Patvirtinti", + "@verify": { + "type": "text", + "placeholders": {} + }, + "verifyStart": "Pradėti patvirtinimą", + "@verifyStart": { + "type": "text", + "placeholders": {} + }, + "verifySuccess": "Jūs sėkmingai patvirtinote!", + "@verifySuccess": { + "type": "text", + "placeholders": {} + }, + "verifyTitle": "Patvirtinama kita paskyra", + "@verifyTitle": { + "type": "text", + "placeholders": {} + }, + "visibilityOfTheChatHistory": "Pokalbių istorijos matomumas", + "@visibilityOfTheChatHistory": { + "type": "text", + "placeholders": {} + }, + "visibleForAllParticipants": "Matoma visiems dalyviams", + "@visibleForAllParticipants": { + "type": "text", + "placeholders": {} + }, + "waitingPartnerAcceptRequest": "Laukiama, kol dalyvis priims užklausą…", + "@waitingPartnerAcceptRequest": { + "type": "text", + "placeholders": {} + }, + "writeAMessage": "Rašyti žinutę…", + "@writeAMessage": { + "type": "text", + "placeholders": {} + }, + "youAreNoLongerParticipatingInThisChat": "Jūs nebedalyvaujate šiame pokalbyje", + "@youAreNoLongerParticipatingInThisChat": { + "type": "text", + "placeholders": {} + }, + "youHaveBeenBannedFromThisChat": "Jums buvo uždrausta dalyvauti šiame pokalbyje", + "@youHaveBeenBannedFromThisChat": { + "type": "text", + "placeholders": {} + }, + "messageInfo": "Žinutės informacija", + "@messageInfo": {}, + "removeFromSpace": "Pašalinti iš erdvės", + "@removeFromSpace": {}, + "security": "Apsauga", + "@security": { + "type": "text", + "placeholders": {} + }, + "sendAsText": "Siųsti kaip tekstą", + "@sendAsText": { + "type": "text" + }, + "sendAudio": "Siųsti garso įrašą", + "@sendAudio": { + "type": "text", + "placeholders": {} + }, + "sendImage": "Siųsti paveiksliuką", + "@sendImage": { + "type": "text", + "placeholders": {} + }, + "sendFile": "Sųsti bylą", + "@sendFile": { + "type": "text", + "placeholders": {} + }, + "sendMessages": "Siųsti žinutes", + "@sendMessages": { + "type": "text", + "placeholders": {} + }, + "sendOriginal": "Siųsti originalą", + "@sendOriginal": { + "type": "text", + "placeholders": {} + }, + "sendVideo": "Siųsti video", + "@sendVideo": { + "type": "text", + "placeholders": {} + }, + "separateChatTypes": "Atskirti tiesioginius pokalbius ir grupes", + "@separateChatTypes": { + "type": "text", + "placeholders": {} + }, + "setAsCanonicalAlias": "Nustatyti kaip pagrindinį slapyvardį", + "@setAsCanonicalAlias": { + "type": "text", + "placeholders": {} + }, + "setPermissionsLevel": "Nustatyti leidimų lygį", + "@setPermissionsLevel": { + "type": "text", + "placeholders": {} + }, + "setStatus": "Nustatyti būseną", + "@setStatus": { + "type": "text", + "placeholders": {} + }, + "shareLocation": "Bendrinti vietą", + "@shareLocation": { + "type": "text", + "placeholders": {} + }, + "systemTheme": "Sistema", + "@systemTheme": { + "type": "text", + "placeholders": {} + }, + "unavailable": "Nepasiekiamas", + "@unavailable": { + "type": "text", + "placeholders": {} + }, + "unblockDevice": "Atblokuoti įrenginį", + "@unblockDevice": { + "type": "text", + "placeholders": {} + }, + "unknownEncryptionAlgorithm": "Nežinomas šifravimo algoritmas", + "@unknownEncryptionAlgorithm": { + "type": "text", + "placeholders": {} + }, + "unmuteChat": "Įjungti pokalbio garsą", + "@unmuteChat": { + "type": "text", + "placeholders": {} + }, + "unpin": "Atsegti", + "@unpin": { + "type": "text", + "placeholders": {} + }, + "username": "Vartotojo vardas", + "@username": { + "type": "text", + "placeholders": {} + }, + "unverified": "Nepatvirtinta", + "@unverified": {}, + "verified": "Patvirtinta", + "@verified": { + "type": "text", + "placeholders": {} + }, + "videoCall": "Vaizdo skambutis", + "@videoCall": { + "type": "text", + "placeholders": {} + }, + "yourPublicKey": "Jūsų viešasis raktas", + "@yourPublicKey": { + "type": "text", + "placeholders": {} + }, + "addToSpaceDescription": "Pasirinkite erdvę, kad prie jos pridėtumėte šį pokalbį.", + "@addToSpaceDescription": {}, + "start": "Pradžia", + "@start": {}, + "account": "Paskyra", + "@account": { + "type": "text", + "placeholders": {} + }, + "addToSpace": "Pridėti į erdvę", + "@addToSpace": {}, + "appLock": "Programos užraktas", + "@appLock": { + "type": "text", + "placeholders": {} + }, + "areGuestsAllowedToJoin": "Ar svečiams leidžiama prisijungti", + "@areGuestsAllowedToJoin": { + "type": "text", + "placeholders": {} + }, + "botMessages": "Botų žinutės", + "@botMessages": { + "type": "text", + "placeholders": {} + }, + "channelCorruptedDecryptError": "Šifravimas buvo sugadintas", + "@channelCorruptedDecryptError": { + "type": "text", + "placeholders": {} + }, + "chatHasBeenAddedToThisSpace": "Pokalbis buvo pridėtas prie šios erdvės", + "@chatHasBeenAddedToThisSpace": {}, + "compareEmojiMatch": "Palyginkite jaustukus", + "@compareEmojiMatch": { + "type": "text", + "placeholders": {} + }, + "compareNumbersMatch": "Palyginkite skaičius", + "@compareNumbersMatch": { + "type": "text", + "placeholders": {} + }, + "contactHasBeenInvitedToTheGroup": "Kontaktas buvo pakviestas į grupę", + "@contactHasBeenInvitedToTheGroup": { + "type": "text", + "placeholders": {} + }, + "contentHasBeenReported": "Apie turinį pranešta serverio administratoriams", + "@contentHasBeenReported": { + "type": "text", + "placeholders": {} + }, + "createNewSpace": "Nauja erdvė", + "@createNewSpace": { + "type": "text", + "placeholders": {} + }, + "deactivateAccountWarning": "Tai deaktyvuos jūsų vartotojo paskyrą. Tai negali būti atšaukta! Ar jūs tuo tikri?", + "@deactivateAccountWarning": { + "type": "text", + "placeholders": {} + }, + "defaultPermissionLevel": "Numatytasis teisių lygis", + "@defaultPermissionLevel": { + "type": "text", + "placeholders": {} + }, + "enableEncryptionWarning": "Šifravimo nebegalėsite išjungti. Ar jūs tuo tikri?", + "@enableEncryptionWarning": { + "type": "text", + "placeholders": {} + }, + "send": "Siųsti", + "@send": { + "type": "text", + "placeholders": {} + }, + "sendAMessage": "Siųsti žinutę", + "@sendAMessage": { + "type": "text", + "placeholders": {} + }, + "toggleUnread": "Pažymėti kaip skaitytą/neskaitytą", + "@toggleUnread": { + "type": "text", + "placeholders": {} + }, + "tooManyRequestsWarning": "Per daug užklausų. Pabandykite dar kartą vėliau!", + "@tooManyRequestsWarning": { + "type": "text", + "placeholders": {} + }, + "waitingPartnerEmoji": "Laukiama, kol dalyvis priims jaustukus…", + "@waitingPartnerEmoji": { + "type": "text", + "placeholders": {} + }, + "waitingPartnerNumbers": "Laukiama, kol dalyvis priims skaičius…", + "@waitingPartnerNumbers": { + "type": "text", + "placeholders": {} + }, + "wallpaper": "Užsklanda", + "@wallpaper": { + "type": "text", + "placeholders": {} + }, + "warning": "Įspėjimas!", + "@warning": { + "type": "text", + "placeholders": {} + }, + "weSentYouAnEmail": "Išsiuntėme jums el. laišką", + "@weSentYouAnEmail": { + "type": "text", + "placeholders": {} + }, + "whoCanPerformWhichAction": "Kas gali atlikti kokį veiksmą", + "@whoCanPerformWhichAction": { + "type": "text", + "placeholders": {} + }, + "whoIsAllowedToJoinThisGroup": "Kam leidžiama prisijungti prie šios grupės", + "@whoIsAllowedToJoinThisGroup": { + "type": "text", + "placeholders": {} + }, + "whyDoYouWantToReportThis": "Kodėl norite apie tai pranešti?", + "@whyDoYouWantToReportThis": { + "type": "text", + "placeholders": {} + }, + "wipeChatBackup": "Ištrinti atsarginę pokalbių kopiją, kad sukurti naują atkūrimo raktą?", + "@wipeChatBackup": { + "type": "text", + "placeholders": {} + }, + "withTheseAddressesRecoveryDescription": "Naudodami šiuos adresus galite atkurti savo slaptažodį.", + "@withTheseAddressesRecoveryDescription": { + "type": "text", + "placeholders": {} + }, + "messageType": "Žinutės tipas", + "@messageType": {}, + "openGallery": "Atverti galeriją", + "@openGallery": {}, + "unknownDevice": "Nežinomas įrenginys", + "@unknownDevice": { + "type": "text", + "placeholders": {} + }, + "voiceMessage": "Balso žinutė", + "@voiceMessage": { + "type": "text", + "placeholders": {} + }, + "title": "FluffyChat", + "@title": { + "description": "Title for the application", + "type": "text", + "placeholders": {} + }, + "visibleForEveryone": "Matoma visiems", + "@visibleForEveryone": { + "type": "text", + "placeholders": {} + }, + "tryToSendAgain": "Pabandykite išsiųsti dar kartą", + "@tryToSendAgain": { + "type": "text", + "placeholders": {} + }, + "locationPermissionDeniedNotice": "Vietos leidimas atmestas. Suteikite leidimą kad galėtumėte bendrinti savo vietą.", + "@locationPermissionDeniedNotice": { + "type": "text", + "placeholders": {} + }, + "needPantalaimonWarning": "Atminkite, kad norint naudoti end-to-end šifravimą, reikalingas Pantalaimon.", + "@needPantalaimonWarning": { + "type": "text", + "placeholders": {} + }, + "noEncryptionForPublicRooms": "Šifravimą galite suaktyvinti tik tada, kai kambarys nebebus viešai pasiekiamas.", + "@noEncryptionForPublicRooms": { + "type": "text", + "placeholders": {} + }, + "noEmotesFound": "Nerasta jaustukų. 😕", + "@noEmotesFound": { + "type": "text", + "placeholders": {} + }, + "noGoogleServicesWarning": "Atrodo, kad jūsų telefone nėra Google Services. Tai geras sprendimas jūsų privatumui! Norėdami gauti tiesioginius pranešimus FluffyChat, rekomenduojame naudoti https://microg.org/ arba https://unifiedpush.org/.", + "@noGoogleServicesWarning": { + "type": "text", + "placeholders": {} + }, + "noPasswordRecoveryDescription": "Dar nepridėjote slaptažodžio atkūrimo būdo.", + "@noPasswordRecoveryDescription": { + "type": "text", + "placeholders": {} + }, + "oneClientLoggedOut": "Vienas iš jūsų klientų atsijungė", + "@oneClientLoggedOut": {}, + "onlineKeyBackupEnabled": "Internetinė atsarginė raktų kopija įjungta", + "@onlineKeyBackupEnabled": { + "type": "text", + "placeholders": {} + }, + "openCamera": "Atidarykite kamerą", + "@openCamera": { + "type": "text", + "placeholders": {} + }, + "openVideoCamera": "Atidarykite kamerą vaizdo įrašui", + "@openVideoCamera": { + "type": "text", + "placeholders": {} + }, + "editBundlesForAccount": "Redaguoti šios paskyros paketus", + "@editBundlesForAccount": {}, + "serverRequiresEmail": "Šis serveris turi patvirtinti jūsų el. pašto adresą registracijai.", + "@serverRequiresEmail": {}, + "addToBundle": "Pridėti prie paketų", + "@addToBundle": {}, + "removeFromBundle": "Pašalinkite iš šio paketo", + "@removeFromBundle": {}, + "bundleName": "Paketo vardas", + "@bundleName": {}, + "play": "Groti {fileName}", + "@play": { + "type": "text", + "placeholders": { + "fileName": {} + } + }, + "redactedAnEvent": "{username} pašalino įvykį", + "@redactedAnEvent": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "rejectedTheInvitation": "{username} atmetė kvietimą", + "@rejectedTheInvitation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "removedBy": "Pašalino vartotojas {username}", + "@removedBy": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "unbanFromChat": "Atblokuoti pokalbyje", + "@unbanFromChat": { + "type": "text", + "placeholders": {} + }, + "renderRichContent": "Atvaizduoti turtingą žinutės turinį", + "@renderRichContent": { + "type": "text", + "placeholders": {} + }, + "reportMessage": "Pranešti apie žinutę", + "@reportMessage": { + "type": "text", + "placeholders": {} + }, + "saveFile": "Išsaugoti failą", + "@saveFile": { + "type": "text", + "placeholders": {} + }, + "seenByUser": "Matė {username}", + "@seenByUser": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sendSticker": "Siųsti lipduką", + "@sendSticker": { + "type": "text", + "placeholders": {} + }, + "sentAFile": "📁 {username} atsiuntė failą", + "@sentAFile": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAnAudio": "🎤 {username} atsiuntė garso įrašą", + "@sentAnAudio": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentASticker": "😊 {username} atsiuntė lipduką", + "@sentASticker": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sharedTheLocation": "{username} bendrino savo vietą", + "@sharedTheLocation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "startedACall": "{senderName} pradėjo skambutį", + "@startedACall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "theyDontMatch": "Jie nesutampa", + "@theyDontMatch": { + "type": "text", + "placeholders": {} + }, + "theyMatch": "Jie sutampa", + "@theyMatch": { + "type": "text", + "placeholders": {} + }, + "unbannedUser": "{username} atblokavo {targetName}", + "@unbannedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "unknownEvent": "Nežinomas įvykis '{type}'", + "@unknownEvent": { + "type": "text", + "placeholders": { + "type": {} + } + }, + "userAndOthersAreTyping": "{username} ir dar {count} kiti rašo…", + "@userAndOthersAreTyping": { + "type": "text", + "placeholders": { + "username": {}, + "count": {} + } + }, + "userAndUserAreTyping": "{username} ir {username2} rašo…", + "@userAndUserAreTyping": { + "type": "text", + "placeholders": { + "username": {}, + "username2": {} + } + }, + "userIsTyping": "{username} rašo…", + "@userIsTyping": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "userSentUnknownEvent": "{username} išsiuntė {type} įvykį", + "@userSentUnknownEvent": { + "type": "text", + "placeholders": { + "username": {}, + "type": {} + } + }, + "publish": "Paskelbti", + "@publish": {}, + "openChat": "Atverti pokalbį", + "@openChat": {}, + "reportUser": "Pranešti apie vartotoją", + "@reportUser": {}, + "dismiss": "Atsisakyti", + "@dismiss": {}, + "reactedWith": "{sender} sureagavo su {reaction}", + "@reactedWith": { + "type": "text", + "placeholders": { + "sender": {}, + "reaction": {} + } + }, + "unsupportedAndroidVersion": "Nepalaikoma Android versija", + "@unsupportedAndroidVersion": {}, + "emailOrUsername": "El. paštas arba vartotojo vardas", + "@emailOrUsername": {}, + "widgetVideo": "Video", + "@widgetVideo": {}, + "widgetNameError": "Pateikite rodomą vardą.", + "@widgetNameError": {}, + "acceptedTheInvitation": "👍 {username} priėmė kvietimą", + "@acceptedTheInvitation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "activatedEndToEndEncryption": "🔐 {username} aktyvavo visapusį šifravimą", + "@activatedEndToEndEncryption": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "answeredTheCall": "{senderName} atsiliepė į skambutį", + "@answeredTheCall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "askVerificationRequest": "Priimti šią patvirtinimo užklausą iš {username}?", + "@askVerificationRequest": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "badServerLoginTypesException": "Namų serveris palaiko šiuos prisijungimo tipus:\n{serverVersions}\nTačiau ši programa palaiko tik:\n{supportedVersions}", + "@badServerLoginTypesException": { + "type": "text", + "placeholders": { + "serverVersions": {}, + "supportedVersions": {} + } + }, + "badServerVersionsException": "Namų serveris palaiko spec. versijas:\n{serverVersions}\nTačiau ši programa palaiko tik {supportedVersions}", + "@badServerVersionsException": { + "type": "text", + "placeholders": { + "serverVersions": {}, + "supportedVersions": {} + } + }, + "bannedUser": "{username} užblokavo {targetName}", + "@bannedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "changedTheHistoryVisibility": "{username} pakeitė istorijos matomumą", + "@changedTheHistoryVisibility": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheHistoryVisibilityTo": "{username} pakeitė istorijos matomumą į: {rules}", + "@changedTheHistoryVisibilityTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "chatBackupDescription": "Jūsų senos žinutės yra apsaugotos atkūrimo raktu. Pasirūpinkite, kad jo neprarastumėte.", + "@chatBackupDescription": { + "type": "text", + "placeholders": {} + }, + "commandHint_create": "Sukurti tuščią grupinį pokalbį\nNaudokite --no-encryption kad išjungti šifravimą", + "@commandHint_create": { + "type": "text", + "description": "Usage hint for the command /create" + }, + "commandHint_dm": "Pradėti tiesioginį pokalbį\nNaudokite --no-encryption kad išjungti šifravimą", + "@commandHint_dm": { + "type": "text", + "description": "Usage hint for the command /dm" + }, + "commandHint_myroomavatar": "Nustatyti savo nuotrauką šiame kambaryje (su mxc-uri)", + "@commandHint_myroomavatar": { + "type": "text", + "description": "Usage hint for the command /myroomavatar" + }, + "commandHint_op": "Nustatyti naudotojo galios lygį (numatytasis: 50)", + "@commandHint_op": { + "type": "text", + "description": "Usage hint for the command /op" + }, + "commandHint_react": "Siųsti atsakymą kaip reakciją", + "@commandHint_react": { + "type": "text", + "description": "Usage hint for the command /react" + }, + "commandMissing": "{command} nėra komanda.", + "@commandMissing": { + "type": "text", + "placeholders": { + "command": {} + }, + "description": "State that {command} is not a valid /command." + }, + "containsDisplayName": "Turi rodomą vardą", + "@containsDisplayName": { + "type": "text", + "placeholders": {} + }, + "containsUserName": "Turi vartotojo vardą", + "@containsUserName": { + "type": "text", + "placeholders": {} + }, + "couldNotDecryptMessage": "Nepavyko iššifruoti pranešimo: {error}", + "@couldNotDecryptMessage": { + "type": "text", + "placeholders": { + "error": {} + } + }, + "countParticipants": "{count} dalyviai", + "@countParticipants": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "createdTheChat": "💬 {username} sukūrė pokalbį", + "@createdTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "emptyChat": "Tuščias pokalbis", + "@emptyChat": { + "type": "text", + "placeholders": {} + }, + "emoteExists": "Jaustukas jau egzistuoja!", + "@emoteExists": { + "type": "text", + "placeholders": {} + }, + "emoteInvalid": "Neteisingas jaustuko trumpasis kodas!", + "@emoteInvalid": { + "type": "text", + "placeholders": {} + }, + "emotePacks": "Jaustukų paketai kambariui", + "@emotePacks": { + "type": "text", + "placeholders": {} + }, + "emoteSettings": "Jaustukų nustatymai", + "@emoteSettings": { + "type": "text", + "placeholders": {} + }, + "emoteShortcode": "Jaustuko trumpasis kodas", + "@emoteShortcode": { + "type": "text", + "placeholders": {} + }, + "emoteWarnNeedToPick": "Turite pasirinkti jaustuko trumpąjį kodą ir paveiksliuką!", + "@emoteWarnNeedToPick": { + "type": "text", + "placeholders": {} + }, + "enableEmotesGlobally": "Įgalinti jaustukų paketą visur", + "@enableEmotesGlobally": { + "type": "text", + "placeholders": {} + }, + "endedTheCall": "{senderName} baigė skambutį", + "@endedTheCall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "errorObtainingLocation": "Klaida nustatant vietą: {error}", + "@errorObtainingLocation": { + "type": "text", + "placeholders": { + "error": {} + } + }, + "groupWith": "Grupė su {displayname}", + "@groupWith": { + "type": "text", + "placeholders": { + "displayname": {} + } + }, + "hasWithdrawnTheInvitationFor": "{username} atšaukė {targetName} kvietimą", + "@hasWithdrawnTheInvitationFor": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "inviteForMe": "Pakvietimas man", + "@inviteForMe": { + "type": "text", + "placeholders": {} + }, + "inviteContactToGroup": "Pakviesti kontaktą į {groupName}", + "@inviteContactToGroup": { + "type": "text", + "placeholders": { + "groupName": {} + } + }, + "invitedUser": "📩 {username} pakvietė {targetName}", + "@invitedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "inviteText": "{username} pakvietė jus prisijungti prie FluffyChat. \n1. Įdiekite FluffyChat: https://fluffychat.im \n2. Prisiregistruokite arba prisijunkite \n3. Atidarykite pakvietimo nuorodą: {link}", + "@inviteText": { + "type": "text", + "placeholders": { + "username": {}, + "link": {} + } + }, + "joinedTheChat": "👋 {username} prisijungė prie pokalbio", + "@joinedTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "kicked": "👞 {username} išmetė {targetName}", + "@kicked": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "kickedAndBanned": "🙅 {username} išmetė ir užblokavo {targetName}", + "@kickedAndBanned": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "lastActiveAgo": "Paskutinis aktyvumas: {localizedTimeShort}", + "@lastActiveAgo": { + "type": "text", + "placeholders": { + "localizedTimeShort": {} + } + }, + "loadCountMoreParticipants": "Įkelti dar {count} dalyvius", + "@loadCountMoreParticipants": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "logInTo": "Prisijungti prie {homeserver}", + "@logInTo": { + "type": "text", + "placeholders": { + "homeserver": {} + } + }, + "toggleFavorite": "Perjungti parankinius", + "@toggleFavorite": { + "type": "text", + "placeholders": {} + }, + "toggleMuted": "Perjungti nutildytą", + "@toggleMuted": { + "type": "text", + "placeholders": {} + }, + "cantOpenUri": "Nepavyksta atidaryti URI {uri}", + "@cantOpenUri": { + "type": "text", + "placeholders": { + "uri": {} + } + }, + "changedTheChatAvatar": "{username} pakeitė pokalbio avatarą", + "@changedTheChatAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheChatDescriptionTo": "{username} pakeitė pokalbio aprašymą į: '{description}'", + "@changedTheChatDescriptionTo": { + "type": "text", + "placeholders": { + "username": {}, + "description": {} + } + }, + "changedTheChatNameTo": "{username} pakeitė pokalbio pavadinimą į: '{chatname}'", + "@changedTheChatNameTo": { + "type": "text", + "placeholders": { + "username": {}, + "chatname": {} + } + }, + "changedTheChatPermissions": "{username} pakeitė pokalbių leidimus", + "@changedTheChatPermissions": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheDisplaynameTo": "{username} pakeitė rodomą vardą į: '{displayname}'", + "@changedTheDisplaynameTo": { + "type": "text", + "placeholders": { + "username": {}, + "displayname": {} + } + }, + "changedTheGuestAccessRules": "{username} pakeitė svečio prieigos taisykles", + "@changedTheGuestAccessRules": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheGuestAccessRulesTo": "{username} pakeitė svečio prieigos taisykles į: {rules}", + "@changedTheGuestAccessRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "changedTheJoinRules": "{username} pakeitė prisijungimo taisykles", + "@changedTheJoinRules": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheJoinRulesTo": "{username} pakeitė prisijungimo taisykles į: {joinRules}", + "@changedTheJoinRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "joinRules": {} + } + }, + "changedTheProfileAvatar": "{username} pakeitė savo avatarą", + "@changedTheProfileAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheRoomAliases": "{username} pakeitė kambario pseudonimus", + "@changedTheRoomAliases": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheRoomInvitationLink": "{username} pakeitė pakvietimo nuorodą", + "@changedTheRoomInvitationLink": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "videoWithSize": "Vaizdo įrašas ({size})", + "@videoWithSize": { + "type": "text", + "placeholders": { + "size": {} + } + }, + "pinMessage": "Prisegti prie kambario", + "@pinMessage": {}, + "confirmEventUnpin": "Ar tikrai norite visam laikui atsegti įvykį?", + "@confirmEventUnpin": {}, + "emojis": "Jaustukai", + "@emojis": {}, + "placeCall": "Skambinti", + "@placeCall": {}, + "voiceCall": "Balso skambutis", + "@voiceCall": {}, + "unsupportedAndroidVersionLong": "Šiai funkcijai reikalinga naujesnė Android versija. Patikrinkite, ar nėra naujinimų arba Lineage OS palaikymo.", + "@unsupportedAndroidVersionLong": {}, + "videoCallsBetaWarning": "Atminkite, kad vaizdo skambučiai šiuo metu yra beta versijos. Jie gali neveikti taip kaip tikėtasi, arba iš viso neveikti visose platformose.", + "@videoCallsBetaWarning": {}, + "experimentalVideoCalls": "Eksperimentiniai vaizdo skambučiai", + "@experimentalVideoCalls": {}, + "switchToAccount": "Perjungti paskyrą į {number}", + "@switchToAccount": { + "type": "number", + "placeholders": { + "number": {} + } + }, + "nextAccount": "Kita paskyra", + "@nextAccount": {}, + "previousAccount": "Ankstesnė paskyra", + "@previousAccount": {}, + "widgetEtherpad": "Teksto pastaba", + "@widgetEtherpad": {}, + "widgetJitsi": "Jitsi Meet", + "@widgetJitsi": {}, + "widgetName": "Vardas", + "@widgetName": {}, + "widgetUrlError": "Netinkamas URL.", + "@widgetUrlError": {}, + "youRejectedTheInvitation": "Jūs atmetėte kvietimą", + "@youRejectedTheInvitation": {}, + "youJoinedTheChat": "Jūs prisijungėte prie pokalbio", + "@youJoinedTheChat": {}, + "youAcceptedTheInvitation": "👍 Jūs priėmėte kvietimą", + "@youAcceptedTheInvitation": {}, + "youBannedUser": "Jūs užblokavote {user}", + "@youBannedUser": { + "placeholders": { + "user": {} + } + }, + "youHaveWithdrawnTheInvitationFor": "Jūs atšaukėte kvietimą {user}", + "@youHaveWithdrawnTheInvitationFor": { + "placeholders": { + "user": {} + } + }, + "youInvitedBy": "📩 Jus pakvietė {user}", + "@youInvitedBy": { + "placeholders": { + "user": {} + } + }, + "youKicked": "👞 Jūs išmetėte {user}", + "@youKicked": { + "placeholders": { + "user": {} + } + }, + "youInvitedUser": "📩 Pakvietėte {user}", + "@youInvitedUser": { + "placeholders": { + "user": {} + } + }, + "youKickedAndBanned": "🙅 Jūs išmetėte ir užblokavote {user}", + "@youKickedAndBanned": { + "placeholders": { + "user": {} + } + }, + "youUnbannedUser": "Jūs atblokavote {user}", + "@youUnbannedUser": { + "placeholders": { + "user": {} + } + }, + "dateAndTimeOfDay": "{date}, {timeOfDay}", + "@dateAndTimeOfDay": { + "type": "text", + "placeholders": { + "date": {}, + "timeOfDay": {} + } + }, + "dateWithoutYear": "{month}-{day}", + "@dateWithoutYear": { + "type": "text", + "placeholders": { + "month": {}, + "day": {} + } + }, + "dateWithYear": "{year}-{month}-{day}", + "@dateWithYear": { + "type": "text", + "placeholders": { + "year": {}, + "month": {}, + "day": {} + } + }, + "locationDisabledNotice": "Vietos nustatymo paslaugos yra išjungtos. Kad galėtumėte bendrinti savo buvimo vietą, įjunkite jas.", + "@locationDisabledNotice": { + "type": "text", + "placeholders": {} + }, + "noMatrixServer": "{server1} nėra Matrix serveris, ar vietoj jo naudoti {server2}?", + "@noMatrixServer": { + "type": "text", + "placeholders": { + "server1": {}, + "server2": {} + } + }, + "numUsersTyping": "{count} vartotojai rašo…", + "@numUsersTyping": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "enableMultiAccounts": "(BETA) Įgalinkite kelias paskyras šiame įrenginyje", + "@enableMultiAccounts": {}, + "openInMaps": "Atidaryti žemėlapiuose", + "@openInMaps": { + "type": "text", + "placeholders": {} + }, + "sentAPicture": "🖼️ {username} atsiuntė nuotrauką", + "@sentAPicture": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAVideo": "🎥 {username} atsiuntė vaizdo įrašą", + "@sentAVideo": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentCallInformations": "{senderName} išsiuntė skambučio informaciją", + "@sentCallInformations": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "setCustomEmotes": "Nustatyti pasirinktinius jaustukus", + "@setCustomEmotes": { + "type": "text", + "placeholders": {} + }, + "userLeftTheChat": "🚪 {username} paliko pokalbį", + "@userLeftTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "markAsRead": "Žymėti kaip skaitytą", + "@markAsRead": {}, + "pushRules": "Tiesioginių pranešimų taisyklės", + "@pushRules": { + "type": "text", + "placeholders": {} + }, + "unreadChats": "{unreadCount, plural, =1{1 unread chat} other{{unreadCount} neperskaityti pokalbiai}}", + "@unreadChats": { + "type": "text", + "placeholders": { + "unreadCount": {} + } + }, + "addWidget": "Pridėti programėlę", + "@addWidget": {}, + "widgetCustom": "Pasirinktinis", + "@widgetCustom": {}, + "errorAddingWidget": "Pridedant valdiklį įvyko klaida.", + "@errorAddingWidget": {}, + "askSSSSSign": "Kad galėtumėte prijungti kitą asmenį, įveskite savo saugyklos slaptafrazę arba atkūrimo raktą.", + "@askSSSSSign": { + "type": "text", + "placeholders": {} + }, + "autoplayImages": "Automatiškai leisti animuotus lipdukus ir jaustukus", + "@autoplayImages": { + "type": "text", + "placeholder": {} + }, + "commandHint_markasdm": "Pažymėti kaip tiesioginio pokalbio kambarį", + "@commandHint_markasdm": {}, + "dehydrateTorLong": "TOR naudotojams rekomenduojama eksportuoti sesiją prieš uždarant langą.", + "@dehydrateTorLong": {}, + "dehydrateWarning": "Šio veiksmo negalima atšaukti. Įsitikinkite, kad saugiai saugote atsarginę kopiją.", + "@dehydrateWarning": {}, + "hydrateTorLong": "Ar paskutinį kartą eksportavote savo sesiją naudodami TOR? Greitai ją importuokite ir tęskite pokalbį.", + "@hydrateTorLong": {}, + "commandHint_markasgroup": "Pažymėti kaip grupę", + "@commandHint_markasgroup": {}, + "pleaseEnterRecoveryKeyDescription": "Norėdami atrakinti senas žinutes, įveskite atkūrimo raktą, kuris buvo sukurtas ankstesnės sesijos metu. Atkūrimo raktas NĖRA jūsų slaptažodis.", + "@pleaseEnterRecoveryKeyDescription": {}, + "callingPermissions": "Skambinimo leidimai", + "@callingPermissions": {}, + "storeInAppleKeyChain": "Saugoti Apple raktų grandinėje", + "@storeInAppleKeyChain": {}, + "callingAccount": "Skambinimo paskyra", + "@callingAccount": {}, + "newSpace": "Nauja erdvė", + "@newSpace": {}, + "callingAccountDetails": "Leidžia FluffyChat naudoti vietinę Android rinkiklio programą.", + "@callingAccountDetails": {}, + "appearOnTop": "Rodyti viršuje", + "@appearOnTop": {}, + "enterSpace": "Įeiti į erdvę", + "@enterSpace": {}, + "enterRoom": "Įeiti į kambarį", + "@enterRoom": {}, + "allSpaces": "Visos erdvės", + "@allSpaces": {}, + "user": "Vartotojas", + "@user": {}, + "custom": "Pasirinktinis", + "@custom": {}, + "confirmMatrixId": "Norėdami ištrinti savo paskyrą, patvirtinkite savo Matrix ID.", + "@confirmMatrixId": {}, + "supposedMxid": "Tai turėtų būti {mxid}", + "@supposedMxid": { + "type": "text", + "placeholders": { + "mxid": {} + } + }, + "dehydrate": "Eksportuoti sesiją ir išvalyti įrenginį", + "@dehydrate": {}, + "dehydrateTor": "TOR Naudotojai: Eksportuoti sesiją", + "@dehydrateTor": {}, + "hydrateTor": "TOR Naudotojai: Importuoti sesijos eksportą", + "@hydrateTor": {}, + "hydrate": "Atkurti iš atsarginės kopijos failo", + "@hydrate": {}, + "pleaseEnterRecoveryKey": "Įveskite savo atkūrimo raktą:", + "@pleaseEnterRecoveryKey": {}, + "recoveryKey": "Atkūrimo raktas", + "@recoveryKey": {}, + "recoveryKeyLost": "Pamestas atkūrimo raktas?", + "@recoveryKeyLost": {}, + "indexedDbErrorLong": "Deja, pagal numatytuosius nustatymus žinučių saugojimas privačiame režime nėra įjungtas.\nPrašome apsilankyti\n - about:config\n - nustatykite dom.indexedDB.privateBrowsing.enabled į true\nPriešingu atveju FluffyChat paleisti neįmanoma.", + "@indexedDbErrorLong": {}, + "countFiles": "{count} failai", + "@countFiles": { + "placeholders": { + "count": {} + } + }, + "storeInSecureStorageDescription": "Atkūrimo raktą laikyti saugioje šio prietaiso saugykloje.", + "@storeInSecureStorageDescription": {}, + "saveKeyManuallyDescription": "Įrašykite šį raktą rankiniu būdu, įjungę sistemos bendrinimo dialogo langą arba iškarpinę.", + "@saveKeyManuallyDescription": {}, + "users": "Vartotojai", + "@users": {}, + "storeSecurlyOnThisDevice": "Saugiai laikyti šiame prietaise", + "@storeSecurlyOnThisDevice": {}, + "unlockOldMessages": "Atrakinti senas žinutes", + "@unlockOldMessages": {}, + "storeInAndroidKeystore": "Saugoti Android raktų saugykloje", + "@storeInAndroidKeystore": {}, + "indexedDbErrorTitle": "Privataus režimo problemos", + "@indexedDbErrorTitle": {}, + "noKeyForThisMessage": "Taip gali atsitikti, jei žinutė buvo išsiųsta prieš prisijungiant prie paskyros šiame prietaise.\n\nTaip pat gali būti, kad siuntėjas užblokavo jūsų prietaisą arba kažkas sutriko su interneto ryšiu.\n\nAr galite perskaityti žinutę kitoje sesijoje? Tada galite perkelti žinutę iš jos! Eikite į Nustatymai > Prietaisai ir įsitikinkite, kad jūsų prietaisai patvirtino vienas kitą. Kai kitą kartą atidarysite kambarį ir abi sesijos bus pirmame plane, raktai bus perduoti automatiškai.\n\nNenorite prarasti raktų atsijungdami arba keisdami įrenginius? Įsitikinkite, kad nustatymuose įjungėte pokalbių atsarginę kopiją.", + "@noKeyForThisMessage": {}, + "foregroundServiceRunning": "Šis pranešimas rodomas, kai veikia pirmojo plano paslauga.", + "@foregroundServiceRunning": {}, + "screenSharingTitle": "ekrano bendrinimas", + "@screenSharingTitle": {}, + "appearOnTopDetails": "Leidžia programėlę rodyti viršuje (nebūtina, jei jau esate nustatę Fluffychat kaip skambinimo paskyrą)", + "@appearOnTopDetails": {}, + "otherCallingPermissions": "Mikrofonas, kamera ir kiti FluffyChat leidimai", + "@otherCallingPermissions": {}, + "whyIsThisMessageEncrypted": "Kodėl ši žinutė neperskaitoma?", + "@whyIsThisMessageEncrypted": {}, + "newGroup": "Nauja grupė", + "@newGroup": {}, + "screenSharingDetail": "Bendrinate savo ekraną per FuffyChat", + "@screenSharingDetail": {}, + "numChats": "{number} pokalbiai", + "@numChats": { + "type": "number", + "placeholders": { + "number": {} + } + }, + "hideUnimportantStateEvents": "Slėpti nesvarbius būsenos įvykius", + "@hideUnimportantStateEvents": {} +} From 434ef60b48e4067309c20c0045506bfda349ba10 Mon Sep 17 00:00:00 2001 From: Rex_sa Date: Thu, 25 Jul 2024 15:49:12 +0000 Subject: [PATCH 123/288] Translated using Weblate (Arabic) Currently translated at 100.0% (652 of 652 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ar/ --- assets/l10n/intl_ar.arb | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/assets/l10n/intl_ar.arb b/assets/l10n/intl_ar.arb index 6a27bd68d6..c3fbde0389 100644 --- a/assets/l10n/intl_ar.arb +++ b/assets/l10n/intl_ar.arb @@ -1821,7 +1821,7 @@ "type": "text", "placeholders": {} }, - "defaultPermissionLevel": "مستوى الأذونات الإفتراضي", + "defaultPermissionLevel": "مستوى الأذونات الافتراضية للمستخدمين الجدد", "@defaultPermissionLevel": { "type": "text", "placeholders": {} @@ -2737,5 +2737,40 @@ "@goToSpace": { "type": "text", "space": {} - } + }, + "userLevel": "{level} - مستخدم", + "@userLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "moderatorLevel": "{level} - مشرف", + "@moderatorLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "adminLevel": "{level} - مدير", + "@adminLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "changeGeneralChatSettings": "تغيير إعدادات الدردشة العامة", + "@changeGeneralChatSettings": {}, + "inviteOtherUsers": "دعوة مستخدمين آخرين إلى هذه الدردشة", + "@inviteOtherUsers": {}, + "changeTheChatPermissions": "تغيير أذونات الدردشة", + "@changeTheChatPermissions": {}, + "changeTheVisibilityOfChatHistory": "تغيير رؤية سجل الدردشة", + "@changeTheVisibilityOfChatHistory": {}, + "changeTheCanonicalRoomAlias": "تغيير عنوان الدردشة العامة الرئيسي", + "@changeTheCanonicalRoomAlias": {}, + "sendRoomNotifications": "إرسال إشعارات @room", + "@sendRoomNotifications": {}, + "changeTheDescriptionOfTheGroup": "تغيير وصف الدردشة", + "@changeTheDescriptionOfTheGroup": {} } From 6c396ec68273668f79c9af343880f8c047f7a843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Thu, 25 Jul 2024 10:40:52 +0000 Subject: [PATCH 124/288] Translated using Weblate (Estonian) Currently translated at 100.0% (652 of 652 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/ --- assets/l10n/intl_et.arb | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/assets/l10n/intl_et.arb b/assets/l10n/intl_et.arb index d19c823c31..c94ad99387 100644 --- a/assets/l10n/intl_et.arb +++ b/assets/l10n/intl_et.arb @@ -559,7 +559,7 @@ "type": "text", "placeholders": {} }, - "defaultPermissionLevel": "Vaikimisi õigused", + "defaultPermissionLevel": "Vaikimisi õigused uutele kasutajatele", "@defaultPermissionLevel": { "type": "text", "placeholders": {} @@ -2737,5 +2737,40 @@ "chats": {}, "participants": {} } - } + }, + "userLevel": "{level} - kasutaja", + "@userLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "moderatorLevel": "{level} - moderaator", + "@moderatorLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "adminLevel": "{level} - peakasutaja", + "@adminLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "changeTheVisibilityOfChatHistory": "Muuda vestluse ajaloo nähtavust", + "@changeTheVisibilityOfChatHistory": {}, + "sendRoomNotifications": "Saada @jututuba teavitusi", + "@sendRoomNotifications": {}, + "changeTheCanonicalRoomAlias": "Muuda vestluse põhilist avalikult nähtavat aadressi", + "@changeTheCanonicalRoomAlias": {}, + "changeGeneralChatSettings": "Muuda vestluse üldiseid seadistusi", + "@changeGeneralChatSettings": {}, + "inviteOtherUsers": "Kutsu teisi osalejaid sellesse vestlusesse", + "@inviteOtherUsers": {}, + "changeTheChatPermissions": "Muuda vestluse õigusi", + "@changeTheChatPermissions": {}, + "changeTheDescriptionOfTheGroup": "Muuda vestluse kirjeldust", + "@changeTheDescriptionOfTheGroup": {} } From 7f6ff69d0bd12408c536800a37ed220ed62e81e1 Mon Sep 17 00:00:00 2001 From: xabirequejo Date: Thu, 25 Jul 2024 10:50:56 +0000 Subject: [PATCH 125/288] Translated using Weblate (Basque) Currently translated at 100.0% (652 of 652 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/ --- assets/l10n/intl_eu.arb | 43 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/assets/l10n/intl_eu.arb b/assets/l10n/intl_eu.arb index 6f72715e21..d810bcc955 100644 --- a/assets/l10n/intl_eu.arb +++ b/assets/l10n/intl_eu.arb @@ -1560,7 +1560,7 @@ "type": "text", "placeholders": {} }, - "contentHasBeenReported": "Edukia zerbitzariko administrariei jakinarazi zaie", + "contentHasBeenReported": "Edukia zerbitzariko administratzaileei jakinarazi zaie", "@contentHasBeenReported": { "type": "text", "placeholders": {} @@ -1781,7 +1781,7 @@ "type": "text", "placeholders": {} }, - "defaultPermissionLevel": "Defektuzko botere-maila", + "defaultPermissionLevel": "Erabiltzaile berrien defektuzko botere-maila", "@defaultPermissionLevel": { "type": "text", "placeholders": {} @@ -2357,7 +2357,7 @@ }, "redactMessageDescription": "Mezua elkarrizketa honetako partaide guztientzat botako da atzera. Ezin da desegin.", "@redactMessageDescription": {}, - "addChatDescription": "Gehitu txat honen deskribapena…", + "addChatDescription": "Gehitu txataren deskribapena…", "@addChatDescription": {}, "directChat": "Banakako txata", "@directChat": {}, @@ -2737,5 +2737,40 @@ } }, "spaces": "Guneak", - "@spaces": {} + "@spaces": {}, + "adminLevel": "{level} - Administratzailea", + "@adminLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "changeTheChatPermissions": "Aldatu txataren baimenak", + "@changeTheChatPermissions": {}, + "inviteOtherUsers": "Gonbidatu beste erabiltzaileak txat honetara", + "@inviteOtherUsers": {}, + "userLevel": "{level} - Erabiltzailea", + "@userLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "moderatorLevel": "{level} - Moderatzailea", + "@moderatorLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "sendRoomNotifications": "Bidali @gela jakinarazpena", + "@sendRoomNotifications": {}, + "changeTheDescriptionOfTheGroup": "Aldatu txataren deskribapena", + "@changeTheDescriptionOfTheGroup": {}, + "changeGeneralChatSettings": "Aldatu txataren ezarpen orokorrak", + "@changeGeneralChatSettings": {}, + "changeTheVisibilityOfChatHistory": "Aldatu txataren historiaren ikusgaitasuna", + "@changeTheVisibilityOfChatHistory": {}, + "changeTheCanonicalRoomAlias": "Aldatu txataren helbide publiko nagusia", + "@changeTheCanonicalRoomAlias": {} } From 9a7dcbfbfb759f7b1bf0a8b57ccda4d2fb12af31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jos=C3=A9=20m?= Date: Fri, 26 Jul 2024 04:58:04 +0000 Subject: [PATCH 126/288] Translated using Weblate (Galician) Currently translated at 100.0% (652 of 652 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/gl/ --- assets/l10n/intl_gl.arb | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/assets/l10n/intl_gl.arb b/assets/l10n/intl_gl.arb index eefbbeaf9a..94f76ff12d 100644 --- a/assets/l10n/intl_gl.arb +++ b/assets/l10n/intl_gl.arb @@ -559,7 +559,7 @@ "type": "text", "placeholders": {} }, - "defaultPermissionLevel": "Nivel de permisos por omisión", + "defaultPermissionLevel": "Nivel de permisos por defecto para novas usuarias", "@defaultPermissionLevel": { "type": "text", "placeholders": {} @@ -2737,5 +2737,40 @@ "space": {} }, "markAsUnread": "Marcar como non lido", - "@markAsUnread": {} + "@markAsUnread": {}, + "userLevel": "{level} - Usuaria", + "@userLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "moderatorLevel": "{level} - Moderadora", + "@moderatorLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "adminLevel": "{level} - Administradora", + "@adminLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "changeGeneralChatSettings": "Cambiar os axustes xerais do chat", + "@changeGeneralChatSettings": {}, + "inviteOtherUsers": "Convidar a outras usuarias a este chat", + "@inviteOtherUsers": {}, + "changeTheChatPermissions": "Cambiar os permisos no chat", + "@changeTheChatPermissions": {}, + "changeTheVisibilityOfChatHistory": "Cambiar a visibilidade do historial do chat", + "@changeTheVisibilityOfChatHistory": {}, + "changeTheCanonicalRoomAlias": "Cambiar o enderezo público principal do chat", + "@changeTheCanonicalRoomAlias": {}, + "sendRoomNotifications": "Enviar notificacións a @room", + "@sendRoomNotifications": {}, + "changeTheDescriptionOfTheGroup": "Cambiar a descrición do chat", + "@changeTheDescriptionOfTheGroup": {} } From 22b91c61f0f2d6657573e0ac5d5a2adb1ee2bbf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Thu, 25 Jul 2024 17:16:28 +0000 Subject: [PATCH 127/288] Translated using Weblate (Turkish) Currently translated at 100.0% (652 of 652 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/tr/ --- assets/l10n/intl_tr.arb | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/assets/l10n/intl_tr.arb b/assets/l10n/intl_tr.arb index c61face7f1..a0bf354571 100644 --- a/assets/l10n/intl_tr.arb +++ b/assets/l10n/intl_tr.arb @@ -563,7 +563,7 @@ "type": "text", "placeholders": {} }, - "defaultPermissionLevel": "Öntanımlı izin seviyesi", + "defaultPermissionLevel": "Yeni kullanıcılar içi öntanımlı izin seviyesi", "@defaultPermissionLevel": { "type": "text", "placeholders": {} @@ -2737,5 +2737,40 @@ "space": "Alan", "@space": {}, "spaces": "Alanlar", - "@spaces": {} + "@spaces": {}, + "inviteOtherUsers": "Diğer kullanıcıları bu sohbete davet et", + "@inviteOtherUsers": {}, + "changeTheChatPermissions": "Sohbet izinlerini değiştir", + "@changeTheChatPermissions": {}, + "changeTheCanonicalRoomAlias": "Ana herkese açık sohbet adresini değiştir", + "@changeTheCanonicalRoomAlias": {}, + "sendRoomNotifications": "@oda bildirimleri gönder", + "@sendRoomNotifications": {}, + "changeTheDescriptionOfTheGroup": "Sohbetin açıklamasını değiştir", + "@changeTheDescriptionOfTheGroup": {}, + "userLevel": "{level} - Kullanıcı", + "@userLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "moderatorLevel": "{level} - Moderatör", + "@moderatorLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "adminLevel": "{level} - Yönetici", + "@adminLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "changeGeneralChatSettings": "Genel sohbet ayarlarını değiştir", + "@changeGeneralChatSettings": {}, + "changeTheVisibilityOfChatHistory": "Sohbet geçmişinin görünürlüğünü değiştir", + "@changeTheVisibilityOfChatHistory": {} } From 4dcd2dac7cac863ff0d3cec4e552c5a24c9ada9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E7=8E=8B=E5=8F=AB=E6=88=91=E6=9D=A5=E5=B7=A1?= =?UTF-8?q?=E5=B1=B1?= Date: Fri, 26 Jul 2024 01:09:53 +0000 Subject: [PATCH 128/288] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (652 of 652 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hans/ --- assets/l10n/intl_zh.arb | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/assets/l10n/intl_zh.arb b/assets/l10n/intl_zh.arb index ad89922453..67b1e96aa3 100644 --- a/assets/l10n/intl_zh.arb +++ b/assets/l10n/intl_zh.arb @@ -537,7 +537,7 @@ "type": "text", "placeholders": {} }, - "defaultPermissionLevel": "默认权限级别", + "defaultPermissionLevel": "新用户默认权限级别", "@defaultPermissionLevel": { "type": "text", "placeholders": {} @@ -2737,5 +2737,40 @@ } }, "unread": "未读", - "@unread": {} + "@unread": {}, + "userLevel": "{level} - 用户", + "@userLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "moderatorLevel": "{level} - 主持人", + "@moderatorLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "adminLevel": "{level} - 管理员", + "@adminLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "inviteOtherUsers": "邀请其他用户到这个聊天", + "@inviteOtherUsers": {}, + "changeTheChatPermissions": "更改聊天权限", + "@changeTheChatPermissions": {}, + "changeTheVisibilityOfChatHistory": "更改聊天历史的可见性", + "@changeTheVisibilityOfChatHistory": {}, + "changeTheCanonicalRoomAlias": "更改主公共聊天地址", + "@changeTheCanonicalRoomAlias": {}, + "sendRoomNotifications": "发送通知聊天室所有人的通知", + "@sendRoomNotifications": {}, + "changeTheDescriptionOfTheGroup": "更改聊天描述", + "@changeTheDescriptionOfTheGroup": {}, + "changeGeneralChatSettings": "更改常规聊天设置", + "@changeGeneralChatSettings": {} } From 16cf4e5e6c85381974f24359a037cee5a4871746 Mon Sep 17 00:00:00 2001 From: Krille Date: Fri, 26 Jul 2024 14:52:41 +0200 Subject: [PATCH 129/288] refactor: Design polishment and better user viewer --- lib/pages/chat_details/chat_details_view.dart | 54 +++------ lib/pages/new_group/new_group_view.dart | 4 - lib/pages/new_space/new_space_view.dart | 4 - lib/pages/settings/settings_view.dart | 61 +++------- .../settings_3pid/settings_3pid_view.dart | 2 +- .../settings_chat/settings_chat_view.dart | 10 +- .../settings_emotes/settings_emotes_view.dart | 2 +- .../settings_security_view.dart | 1 - .../settings_style/settings_style_view.dart | 3 - .../user_bottom_sheet/user_bottom_sheet.dart | 39 +++++++ .../user_bottom_sheet_view.dart | 106 ++++++++++-------- 11 files changed, 134 insertions(+), 152 deletions(-) diff --git a/lib/pages/chat_details/chat_details_view.dart b/lib/pages/chat_details/chat_details_view.dart index 4e19e43cab..3c17112a5b 100644 --- a/lib/pages/chat_details/chat_details_view.dart +++ b/lib/pages/chat_details/chat_details_view.dart @@ -85,33 +85,16 @@ class ChatDetailsView extends StatelessWidget { padding: const EdgeInsets.all(32.0), child: Stack( children: [ - Material( - elevation: Theme.of(context) - .appBarTheme - .scrolledUnderElevation ?? - 4, - shadowColor: Theme.of(context) - .appBarTheme - .shadowColor, - shape: RoundedRectangleBorder( - side: BorderSide( - color: Theme.of(context).dividerColor, - ), - borderRadius: BorderRadius.circular( - Avatar.defaultSize * 2.5, - ), - ), - child: Hero( - tag: controller - .widget.embeddedCloseButton != - null - ? 'embedded_content_banner' - : 'content_banner', - child: Avatar( - mxContent: room.avatar, - name: displayname, - size: Avatar.defaultSize * 2.5, - ), + Hero( + tag: + controller.widget.embeddedCloseButton != + null + ? 'embedded_content_banner' + : 'content_banner', + child: Avatar( + mxContent: room.avatar, + name: displayname, + size: Avatar.defaultSize * 2.5, ), ), if (!room.isDirectChat && @@ -170,7 +153,7 @@ class ChatDetailsView extends StatelessWidget { : displayname, maxLines: 1, overflow: TextOverflow.ellipsis, - // style: const TextStyle(fontSize: 18), + style: const TextStyle(fontSize: 18), ), ), TextButton.icon( @@ -202,10 +185,7 @@ class ChatDetailsView extends StatelessWidget { ), ], ), - Divider( - height: 1, - color: Theme.of(context).dividerColor, - ), + Divider(color: Theme.of(context).dividerColor), if (!room.canChangeStateEvent(EventTypes.RoomTopic)) ListTile( title: Text( @@ -261,10 +241,7 @@ class ChatDetailsView extends StatelessWidget { ), ), const SizedBox(height: 16), - Divider( - height: 1, - color: Theme.of(context).dividerColor, - ), + Divider(color: Theme.of(context).dividerColor), ListTile( leading: CircleAvatar( backgroundColor: @@ -316,10 +293,7 @@ class ChatDetailsView extends StatelessWidget { onTap: () => context .push('/rooms/${room.id}/details/permissions'), ), - Divider( - height: 1, - color: Theme.of(context).dividerColor, - ), + Divider(color: Theme.of(context).dividerColor), ListTile( title: Text( L10n.of(context)!.countParticipants( diff --git a/lib/pages/new_group/new_group_view.dart b/lib/pages/new_group/new_group_view.dart index a83497d7b9..df2a4ac49d 100644 --- a/lib/pages/new_group/new_group_view.dart +++ b/lib/pages/new_group/new_group_view.dart @@ -102,10 +102,6 @@ class NewGroupView extends StatelessWidget { child: SizedBox( width: double.infinity, child: ElevatedButton( - style: ElevatedButton.styleFrom( - foregroundColor: Theme.of(context).colorScheme.onPrimary, - backgroundColor: Theme.of(context).colorScheme.primary, - ), onPressed: controller.loading ? null : controller.submitAction, child: controller.loading diff --git a/lib/pages/new_space/new_space_view.dart b/lib/pages/new_space/new_space_view.dart index e90c7a84e6..2f46e5e739 100644 --- a/lib/pages/new_space/new_space_view.dart +++ b/lib/pages/new_space/new_space_view.dart @@ -74,10 +74,6 @@ class NewSpaceView extends StatelessWidget { child: SizedBox( width: double.infinity, child: ElevatedButton( - style: ElevatedButton.styleFrom( - foregroundColor: Theme.of(context).colorScheme.onPrimary, - backgroundColor: Theme.of(context).colorScheme.primary, - ), onPressed: controller.loading ? null : controller.submitAction, child: controller.loading diff --git a/lib/pages/settings/settings_view.dart b/lib/pages/settings/settings_view.dart index b1d60f1429..b2293f4b23 100644 --- a/lib/pages/settings/settings_view.dart +++ b/lib/pages/settings/settings_view.dart @@ -28,13 +28,6 @@ class SettingsView extends StatelessWidget { ), ), title: Text(L10n.of(context)!.settings), - actions: [ - TextButton.icon( - onPressed: controller.logoutAction, - label: Text(L10n.of(context)!.logout), - icon: const Icon(Icons.logout_outlined), - ), - ], ), body: ListTileTheme( iconColor: Theme.of(context).colorScheme.onSurface, @@ -55,32 +48,17 @@ class SettingsView extends StatelessWidget { padding: const EdgeInsets.all(32.0), child: Stack( children: [ - Material( - elevation: Theme.of(context) - .appBarTheme - .scrolledUnderElevation ?? - 4, - shadowColor: - Theme.of(context).appBarTheme.shadowColor, - shape: RoundedRectangleBorder( - side: BorderSide( - color: Theme.of(context).dividerColor, - ), - borderRadius: BorderRadius.circular( - Avatar.defaultSize * 2.5, - ), - ), - child: Avatar( - mxContent: profile?.avatarUrl, - name: displayname, - size: Avatar.defaultSize * 2.5, - ), + Avatar( + mxContent: profile?.avatarUrl, + name: displayname, + size: Avatar.defaultSize * 2.5, ), if (profile != null) Positioned( bottom: 0, right: 0, child: FloatingActionButton.small( + elevation: 2, onPressed: controller.setAvatarAction, heroTag: null, child: const Icon(Icons.camera_alt_outlined), @@ -108,7 +86,9 @@ class SettingsView extends StatelessWidget { displayname, maxLines: 1, overflow: TextOverflow.ellipsis, - // style: const TextStyle(fontSize: 18), + style: const TextStyle( + fontSize: 18, + ), ), ), TextButton.icon( @@ -135,10 +115,7 @@ class SettingsView extends StatelessWidget { ); }, ), - Divider( - height: 1, - color: Theme.of(context).dividerColor, - ), + Divider(color: Theme.of(context).dividerColor), if (showChatBackupBanner == null) ListTile( leading: const Icon(Icons.backup_outlined), @@ -154,60 +131,54 @@ class SettingsView extends StatelessWidget { onChanged: controller.firstRunBootstrapAction, ), Divider( - height: 1, color: Theme.of(context).dividerColor, ), ListTile( leading: const Icon(Icons.format_paint_outlined), title: Text(L10n.of(context)!.changeTheme), onTap: () => context.go('/rooms/settings/style'), - trailing: const Icon(Icons.chevron_right_outlined), ), ListTile( leading: const Icon(Icons.notifications_outlined), title: Text(L10n.of(context)!.notifications), onTap: () => context.go('/rooms/settings/notifications'), - trailing: const Icon(Icons.chevron_right_outlined), ), ListTile( leading: const Icon(Icons.devices_outlined), title: Text(L10n.of(context)!.devices), onTap: () => context.go('/rooms/settings/devices'), - trailing: const Icon(Icons.chevron_right_outlined), ), ListTile( leading: const Icon(Icons.forum_outlined), title: Text(L10n.of(context)!.chat), onTap: () => context.go('/rooms/settings/chat'), - trailing: const Icon(Icons.chevron_right_outlined), ), ListTile( leading: const Icon(Icons.shield_outlined), title: Text(L10n.of(context)!.security), onTap: () => context.go('/rooms/settings/security'), - trailing: const Icon(Icons.chevron_right_outlined), - ), - Divider( - height: 1, - color: Theme.of(context).dividerColor, ), + Divider(color: Theme.of(context).dividerColor), ListTile( leading: const Icon(Icons.help_outline_outlined), title: Text(L10n.of(context)!.help), onTap: () => launchUrlString(AppConfig.supportUrl), - trailing: const Icon(Icons.open_in_new_outlined), ), ListTile( leading: const Icon(Icons.shield_sharp), title: Text(L10n.of(context)!.privacy), onTap: () => launchUrlString(AppConfig.privacyUrl), - trailing: const Icon(Icons.open_in_new_outlined), ), ListTile( leading: const Icon(Icons.info_outline_rounded), title: Text(L10n.of(context)!.about), onTap: () => PlatformInfos.showDialog(context), - trailing: const Icon(Icons.chevron_right_outlined), + ), + Divider(color: Theme.of(context).dividerColor), + ListTile( + leading: const Icon(Icons.logout_outlined), + title: Text(L10n.of(context)!.logout), + onTap: controller.logoutAction, ), ], ), diff --git a/lib/pages/settings_3pid/settings_3pid_view.dart b/lib/pages/settings_3pid/settings_3pid_view.dart index 49f8b44894..4c5377c640 100644 --- a/lib/pages/settings_3pid/settings_3pid_view.dart +++ b/lib/pages/settings_3pid/settings_3pid_view.dart @@ -69,7 +69,7 @@ class Settings3PidView extends StatelessWidget { .withTheseAddressesRecoveryDescription, ), ), - const Divider(height: 1), + const Divider(), Expanded( child: ListView.builder( itemCount: identifier.length, diff --git a/lib/pages/settings_chat/settings_chat_view.dart b/lib/pages/settings_chat/settings_chat_view.dart index c3abe990e0..01cc7af291 100644 --- a/lib/pages/settings_chat/settings_chat_view.dart +++ b/lib/pages/settings_chat/settings_chat_view.dart @@ -71,10 +71,7 @@ class SettingsChatView extends StatelessWidget { storeKey: SettingKeys.swipeRightToLeftToReply, defaultValue: AppConfig.swipeRightToLeftToReply, ), - Divider( - height: 1, - color: Theme.of(context).dividerColor, - ), + Divider(color: Theme.of(context).dividerColor), ListTile( title: Text( L10n.of(context)!.customEmojisAndStickers, @@ -93,10 +90,7 @@ class SettingsChatView extends StatelessWidget { child: Icon(Icons.chevron_right_outlined), ), ), - Divider( - height: 1, - color: Theme.of(context).dividerColor, - ), + Divider(color: Theme.of(context).dividerColor), ListTile( title: Text( L10n.of(context)!.calls, diff --git a/lib/pages/settings_emotes/settings_emotes_view.dart b/lib/pages/settings_emotes/settings_emotes_view.dart index 7e72cd4648..be1e36f923 100644 --- a/lib/pages/settings_emotes/settings_emotes_view.dart +++ b/lib/pages/settings_emotes/settings_emotes_view.dart @@ -117,7 +117,7 @@ class EmotesSettingsView extends StatelessWidget { onChanged: controller.setIsGloballyActive, ), if (!controller.readonly || controller.room != null) - const Divider(thickness: 1), + const Divider(), imageKeys.isEmpty ? Center( child: Padding( diff --git a/lib/pages/settings_security/settings_security_view.dart b/lib/pages/settings_security/settings_security_view.dart index 49b39d7502..6ff7e1696b 100644 --- a/lib/pages/settings_security/settings_security_view.dart +++ b/lib/pages/settings_security/settings_security_view.dart @@ -86,7 +86,6 @@ class SettingsSecurityView extends StatelessWidget { ), }, Divider( - height: 1, color: Theme.of(context).dividerColor, ), ListTile( diff --git a/lib/pages/settings_style/settings_style_view.dart b/lib/pages/settings_style/settings_style_view.dart index 86f48fe879..1a07dd2bc2 100644 --- a/lib/pages/settings_style/settings_style_view.dart +++ b/lib/pages/settings_style/settings_style_view.dart @@ -136,7 +136,6 @@ class SettingsStyleView extends StatelessWidget { ), const SizedBox(height: 8), Divider( - height: 1, color: Theme.of(context).dividerColor, ), ListTile( @@ -167,7 +166,6 @@ class SettingsStyleView extends StatelessWidget { onChanged: controller.switchTheme, ), Divider( - height: 1, color: Theme.of(context).dividerColor, ), ListTile( @@ -192,7 +190,6 @@ class SettingsStyleView extends StatelessWidget { defaultValue: AppConfig.separateChatTypes, ), Divider( - height: 1, color: Theme.of(context).dividerColor, ), ListTile( diff --git a/lib/pages/user_bottom_sheet/user_bottom_sheet.dart b/lib/pages/user_bottom_sheet/user_bottom_sheet.dart index 5279150724..1ae1733414 100644 --- a/lib/pages/user_bottom_sheet/user_bottom_sheet.dart +++ b/lib/pages/user_bottom_sheet/user_bottom_sheet.dart @@ -226,6 +226,45 @@ class UserBottomSheetController extends State { } } + bool isSending = false; + + Object? sendError; + + final TextEditingController sendController = TextEditingController(); + + void sendAction([_]) async { + final userId = widget.user?.id ?? widget.profile?.userId; + final client = Matrix.of(context).client; + if (userId == null) throw ('user or profile must not be null!'); + + final input = sendController.text.trim(); + if (input.isEmpty) return; + + setState(() { + isSending = true; + sendError = null; + }); + try { + final roomId = await client.startDirectChat(userId); + if (!mounted) return; + final room = client.getRoomById(roomId); + if (room == null) { + throw ('DM Room found or created but room not found in client'); + } + await room.sendTextEvent(input); + setState(() { + isSending = false; + sendController.clear(); + }); + } catch (e, s) { + Logs().d('Unable to send message', e, s); + setState(() { + isSending = false; + sendError = e; + }); + } + } + void knockAccept() async { final user = widget.user!; final result = await showFutureLoadingDialog( diff --git a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart index 9e72a4a3fb..c6d3b5979c 100644 --- a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart +++ b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart @@ -7,6 +7,7 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/utils/date_time_extension.dart'; import 'package:fluffychat/utils/fluffy_share.dart'; +import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/url_launcher.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/presence_builder.dart'; @@ -29,6 +30,7 @@ class UserBottomSheetView extends StatelessWidget { final client = Matrix.of(controller.widget.outerContext).client; final profileSearchError = controller.widget.profileSearchError; + final dmRoomId = client.getDirectChatFromUserId(userId); return SafeArea( child: Scaffold( appBar: AppBar( @@ -90,19 +92,19 @@ class UserBottomSheetView extends StatelessWidget { ), ], ), - actions: [ - if (userId != client.userID && - !client.ignoredUsers.contains(userId)) - Padding( - padding: const EdgeInsets.only(right: 8.0), - child: IconButton( - icon: const Icon(Icons.block_outlined), - tooltip: L10n.of(context)!.block, - onPressed: () => controller - .participantAction(UserBottomSheetAction.ignore), - ), - ), - ], + actions: dmRoomId == null + ? null + : [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: FloatingActionButton.small( + elevation: 0, + onPressed: () => controller + .participantAction(UserBottomSheetAction.message), + child: const Icon(Icons.chat_outlined), + ), + ), + ], ), body: StreamBuilder( stream: user?.room.client.onSync.stream.where( @@ -169,25 +171,10 @@ class UserBottomSheetView extends StatelessWidget { children: [ Padding( padding: const EdgeInsets.all(16.0), - child: Material( - elevation: Theme.of(context) - .appBarTheme - .scrolledUnderElevation ?? - 4, - shadowColor: Theme.of(context).appBarTheme.shadowColor, - shape: RoundedRectangleBorder( - side: BorderSide( - color: Theme.of(context).dividerColor, - ), - borderRadius: BorderRadius.circular( - Avatar.defaultSize * 2.5, - ), - ), - child: Avatar( - mxContent: avatarUrl, - name: displayname, - size: Avatar.defaultSize * 2.5, - ), + child: Avatar( + mxContent: avatarUrl, + name: displayname, + size: Avatar.defaultSize * 2.5, ), ), Expanded( @@ -212,7 +199,7 @@ class UserBottomSheetView extends StatelessWidget { displayname, maxLines: 1, overflow: TextOverflow.ellipsis, - // style: const TextStyle(fontSize: 18), + style: const TextStyle(fontSize: 18), ), ), TextButton.icon( @@ -247,16 +234,35 @@ class UserBottomSheetView extends StatelessWidget { horizontal: 16.0, vertical: 8.0, ), - child: ElevatedButton.icon( - onPressed: () => controller - .participantAction(UserBottomSheetAction.message), - icon: const Icon(Icons.forum_outlined), - label: Text( - controller.widget.user == null - ? L10n.of(context)!.startConversation - : L10n.of(context)!.sendAMessage, - ), - ), + child: dmRoomId == null + ? ElevatedButton.icon( + onPressed: () => controller.participantAction( + UserBottomSheetAction.message, + ), + icon: const Icon(Icons.chat_outlined), + label: Text(L10n.of(context)!.startConversation), + ) + : TextField( + controller: controller.sendController, + readOnly: controller.isSending, + onSubmitted: controller.sendAction, + minLines: 1, + maxLines: 1, + textInputAction: TextInputAction.send, + decoration: InputDecoration( + errorText: controller.sendError + ?.toLocalizedString(context), + hintText: L10n.of(context)!.sendMessages, + suffixIcon: controller.isSending + ? const CircularProgressIndicator.adaptive( + strokeWidth: 2, + ) + : IconButton( + icon: const Icon(Icons.send_outlined), + onPressed: controller.sendAction, + ), + ), + ), ), PresenceBuilder( userId: userId, @@ -334,8 +340,8 @@ class UserBottomSheetView extends StatelessWidget { ), ), ), - Divider(color: Theme.of(context).dividerColor), ], + Divider(color: Theme.of(context).dividerColor), if (user != null && user.canKick) ListTile( textColor: Theme.of(context).colorScheme.error, @@ -370,7 +376,7 @@ class UserBottomSheetView extends StatelessWidget { textColor: Theme.of(context).colorScheme.onErrorContainer, iconColor: Theme.of(context).colorScheme.onErrorContainer, title: Text(L10n.of(context)!.reportUser), - leading: const Icon(Icons.report_outlined), + leading: const Icon(Icons.gavel_outlined), onTap: () => controller .participantAction(UserBottomSheetAction.report), ), @@ -385,6 +391,16 @@ class UserBottomSheetView extends StatelessWidget { style: const TextStyle(color: Colors.orange), ), ), + if (userId != client.userID && + !client.ignoredUsers.contains(userId)) + ListTile( + textColor: Theme.of(context).colorScheme.onErrorContainer, + iconColor: Theme.of(context).colorScheme.onErrorContainer, + leading: const Icon(Icons.block_outlined), + title: Text(L10n.of(context)!.block), + onTap: () => controller + .participantAction(UserBottomSheetAction.ignore), + ), ], ); }, From 39a66f678676acb67b1abf79a73fabd64b6c9d83 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Fri, 26 Jul 2024 16:20:13 +0200 Subject: [PATCH 130/288] chore: Follow up user bottom sheet --- .../user_bottom_sheet/user_bottom_sheet_view.dart | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart index c6d3b5979c..fbdcb52ece 100644 --- a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart +++ b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart @@ -96,7 +96,7 @@ class UserBottomSheetView extends StatelessWidget { ? null : [ Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), + padding: const EdgeInsets.symmetric(horizontal: 8), child: FloatingActionButton.small( elevation: 0, onPressed: () => controller @@ -253,10 +253,17 @@ class UserBottomSheetView extends StatelessWidget { errorText: controller.sendError ?.toLocalizedString(context), hintText: L10n.of(context)!.sendMessages, - suffixIcon: controller.isSending - ? const CircularProgressIndicator.adaptive( - strokeWidth: 2, + suffix: controller.isSending + ? const SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator.adaptive( + strokeWidth: 2, + ), ) + : null, + suffixIcon: controller.isSending + ? null : IconButton( icon: const Icon(Icons.send_outlined), onPressed: controller.sendAction, From 22cfdd7689c27aeb1f326aadef52571d2a4bbf5b Mon Sep 17 00:00:00 2001 From: krille-chan Date: Fri, 26 Jul 2024 16:50:41 +0200 Subject: [PATCH 131/288] chore: Follow up user bottom sheet --- .../user_bottom_sheet_view.dart | 131 ++++++++---------- 1 file changed, 55 insertions(+), 76 deletions(-) diff --git a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart index fbdcb52ece..8630faba00 100644 --- a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart +++ b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart @@ -38,60 +38,7 @@ class UserBottomSheetView extends StatelessWidget { onPressed: Navigator.of(context, rootNavigator: false).pop, ), centerTitle: false, - title: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(displayname), - PresenceBuilder( - userId: userId, - client: client, - builder: (context, presence) { - if (presence == null || - (presence.presence == PresenceType.offline && - presence.lastActiveTimestamp == null)) { - return const SizedBox.shrink(); - } - - final dotColor = presence.presence.isOnline - ? Colors.green - : presence.presence.isUnavailable - ? Colors.orange - : Colors.grey; - - final lastActiveTimestamp = presence.lastActiveTimestamp; - - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 8, - height: 8, - margin: const EdgeInsets.only(right: 8), - decoration: BoxDecoration( - color: dotColor, - borderRadius: BorderRadius.circular(16), - ), - ), - if (presence.currentlyActive == true) - Text( - L10n.of(context)!.currentlyActive, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodySmall, - ) - else if (lastActiveTimestamp != null) - Text( - L10n.of(context)!.lastActiveAgo( - lastActiveTimestamp.localizedTimeShort(context), - ), - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodySmall, - ), - ], - ); - }, - ), - ], - ), + title: Text(displayname), actions: dmRoomId == null ? null : [ @@ -182,26 +129,6 @@ class UserBottomSheetView extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ - TextButton.icon( - onPressed: () => FluffyShare.share( - 'https://matrix.to/#/$userId', - context, - ), - icon: Icon( - Icons.adaptive.share_outlined, - size: 16, - ), - style: TextButton.styleFrom( - foregroundColor: - Theme.of(context).colorScheme.onSurface, - ), - label: Text( - displayname, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 18), - ), - ), TextButton.icon( onPressed: () => FluffyShare.share( userId, @@ -214,15 +141,67 @@ class UserBottomSheetView extends StatelessWidget { ), style: TextButton.styleFrom( foregroundColor: - Theme.of(context).colorScheme.secondary, + Theme.of(context).colorScheme.onSurface, ), label: Text( userId, maxLines: 1, overflow: TextOverflow.ellipsis, - // style: const TextStyle(fontSize: 12), ), ), + PresenceBuilder( + userId: userId, + client: client, + builder: (context, presence) { + if (presence == null || + (presence.presence == PresenceType.offline && + presence.lastActiveTimestamp == null)) { + return const SizedBox.shrink(); + } + + final dotColor = presence.presence.isOnline + ? Colors.green + : presence.presence.isUnavailable + ? Colors.orange + : Colors.grey; + + final lastActiveTimestamp = + presence.lastActiveTimestamp; + + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(width: 16), + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: dotColor, + borderRadius: BorderRadius.circular(16), + ), + ), + const SizedBox(width: 12), + if (presence.currentlyActive == true) + Text( + L10n.of(context)!.currentlyActive, + overflow: TextOverflow.ellipsis, + style: + Theme.of(context).textTheme.bodySmall, + ) + else if (lastActiveTimestamp != null) + Text( + L10n.of(context)!.lastActiveAgo( + lastActiveTimestamp + .localizedTimeShort(context), + ), + overflow: TextOverflow.ellipsis, + style: + Theme.of(context).textTheme.bodySmall, + ), + ], + ); + }, + ), ], ), ), From d71d633ccef6e86b215a011dddb7f826b2ce34a8 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Fri, 26 Jul 2024 17:32:36 +0200 Subject: [PATCH 132/288] chore: nicer bottom sheets --- .../user_bottom_sheet/user_bottom_sheet.dart | 2 +- .../user_bottom_sheet_view.dart | 46 +++++++++---------- lib/utils/adaptive_bottom_sheet.dart | 7 +-- lib/widgets/public_room_bottom_sheet.dart | 10 ++-- 4 files changed, 33 insertions(+), 32 deletions(-) diff --git a/lib/pages/user_bottom_sheet/user_bottom_sheet.dart b/lib/pages/user_bottom_sheet/user_bottom_sheet.dart index 1ae1733414..45be5c975f 100644 --- a/lib/pages/user_bottom_sheet/user_bottom_sheet.dart +++ b/lib/pages/user_bottom_sheet/user_bottom_sheet.dart @@ -234,7 +234,7 @@ class UserBottomSheetController extends State { void sendAction([_]) async { final userId = widget.user?.id ?? widget.profile?.userId; - final client = Matrix.of(context).client; + final client = Matrix.of(widget.outerContext).client; if (userId == null) throw ('user or profile must not be null!'); final input = sendController.text.trim(); diff --git a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart index 8630faba00..01035bb777 100644 --- a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart +++ b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart @@ -207,6 +207,29 @@ class UserBottomSheetView extends StatelessWidget { ), ], ), + PresenceBuilder( + userId: userId, + client: client, + builder: (context, presence) { + final status = presence?.statusMsg; + if (status == null || status.isEmpty) { + return const SizedBox.shrink(); + } + return ListTile( + title: SelectableLinkify( + text: status, + style: const TextStyle(fontSize: 16), + options: const LinkifyOptions(humanize: false), + linkStyle: const TextStyle( + color: Colors.blueAccent, + decorationColor: Colors.blueAccent, + ), + onOpen: (url) => + UrlLauncher(context, url.url).launchUrl(), + ), + ); + }, + ), if (userId != client.userID) Padding( padding: const EdgeInsets.symmetric( @@ -250,29 +273,6 @@ class UserBottomSheetView extends StatelessWidget { ), ), ), - PresenceBuilder( - userId: userId, - client: client, - builder: (context, presence) { - final status = presence?.statusMsg; - if (status == null || status.isEmpty) { - return const SizedBox.shrink(); - } - return ListTile( - title: SelectableLinkify( - text: status, - style: const TextStyle(fontSize: 16), - options: const LinkifyOptions(humanize: false), - linkStyle: const TextStyle( - color: Colors.blueAccent, - decorationColor: Colors.blueAccent, - ), - onOpen: (url) => - UrlLauncher(context, url.url).launchUrl(), - ), - ); - }, - ), if (controller.widget.onMention != null) ListTile( leading: const Icon(Icons.alternate_email_outlined), diff --git a/lib/utils/adaptive_bottom_sheet.dart b/lib/utils/adaptive_bottom_sheet.dart index cfe487ffd4..d21ca6c445 100644 --- a/lib/utils/adaptive_bottom_sheet.dart +++ b/lib/utils/adaptive_bottom_sheet.dart @@ -8,18 +8,19 @@ Future showAdaptiveBottomSheet({ required Widget Function(BuildContext) builder, bool isDismissible = true, bool isScrollControlled = true, - double maxHeight = 480.0, + double maxHeight = 512, + bool useRootNavigator = true, }) => showModalBottomSheet( context: context, builder: builder, // this sadly is ugly on desktops but otherwise breaks `.of(context)` calls - useRootNavigator: false, + useRootNavigator: useRootNavigator, isDismissible: isDismissible, isScrollControlled: isScrollControlled, constraints: BoxConstraints( maxHeight: maxHeight, - maxWidth: FluffyThemes.columnWidth * 1.5, + maxWidth: FluffyThemes.columnWidth * 1.25, ), clipBehavior: Clip.hardEdge, shape: const RoundedRectangleBorder( diff --git a/lib/widgets/public_room_bottom_sheet.dart b/lib/widgets/public_room_bottom_sheet.dart index 8bd354ef5e..4f5806c2d8 100644 --- a/lib/widgets/public_room_bottom_sheet.dart +++ b/lib/widgets/public_room_bottom_sheet.dart @@ -64,17 +64,17 @@ class PublicRoomBottomSheet extends StatelessWidget { bool _testRoom(PublicRoomsChunk r) => r.canonicalAlias == roomAlias; - Future _search(BuildContext context) async { + Future _search() async { final chunk = this.chunk; if (chunk != null) return chunk; - final query = await Matrix.of(context).client.queryPublicRooms( + final query = await Matrix.of(outerContext).client.queryPublicRooms( server: roomAlias!.domain, filter: PublicRoomQueryFilter( genericSearchTerm: roomAlias, ), ); if (!query.chunk.any(_testRoom)) { - throw (L10n.of(context)!.noRoomsFound); + throw (L10n.of(outerContext)!.noRoomsFound); } return query.chunk.firstWhere(_testRoom); } @@ -108,7 +108,7 @@ class PublicRoomBottomSheet extends StatelessWidget { ], ), body: FutureBuilder( - future: _search(context), + future: _search(), builder: (context, snapshot) { final profile = snapshot.data; return ListView( @@ -142,7 +142,7 @@ class PublicRoomBottomSheet extends StatelessWidget { onPressed: () => _joinRoom(context), label: Text( chunk?.joinRule == 'knock' && - Matrix.of(context) + Matrix.of(outerContext) .client .getRoomById(chunk!.roomId) == null From b20cee34d4275e97ba680d30b52b95d6e6e6c2f6 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Fri, 26 Jul 2024 17:57:30 +0200 Subject: [PATCH 133/288] chore: Follow up nicer max width pages --- lib/pages/chat_list/chat_list.dart | 7 +-- lib/widgets/layouts/max_width_body.dart | 62 ++++++++++++------------- 2 files changed, 33 insertions(+), 36 deletions(-) diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 63cb35571e..99d31bf587 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -716,9 +716,10 @@ class ChatListController extends State isDestructiveAction: true, ); if (confirmed == OkCancelResult.cancel) return; - if (!mounted) { - await showFutureLoadingDialog(context: context, future: room.leave); - } + if (!mounted) return; + + await showFutureLoadingDialog(context: context, future: room.leave); + return; } } diff --git a/lib/widgets/layouts/max_width_body.dart b/lib/widgets/layouts/max_width_body.dart index e9499d31eb..97c3402318 100644 --- a/lib/widgets/layouts/max_width_body.dart +++ b/lib/widgets/layouts/max_width_body.dart @@ -1,20 +1,17 @@ -import 'dart:math'; - import 'package:flutter/material.dart'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/config/themes.dart'; class MaxWidthBody extends StatelessWidget { - final Widget? child; + final Widget child; final double maxWidth; - final bool withFrame; final bool withScrolling; final EdgeInsets? innerPadding; const MaxWidthBody({ - this.child, + required this.child, this.maxWidth = 600, - this.withFrame = true, this.withScrolling = true, this.innerPadding, super.key, @@ -24,36 +21,35 @@ class MaxWidthBody extends StatelessWidget { return SafeArea( child: LayoutBuilder( builder: (context, constraints) { - final paddingVal = max(0, (constraints.maxWidth - maxWidth) / 2); - final hasPadding = paddingVal > 0; - final padding = EdgeInsets.symmetric( - vertical: hasPadding ? 32 : 0, - horizontal: max(0, (constraints.maxWidth - maxWidth) / 2), - ); - final childWithPadding = Padding( - padding: padding, - child: withFrame && hasPadding - ? Material( - elevation: - Theme.of(context).appBarTheme.scrolledUnderElevation ?? - 4, - clipBehavior: Clip.hardEdge, - borderRadius: BorderRadius.circular(AppConfig.borderRadius), - shadowColor: Theme.of(context).appBarTheme.shadowColor, - child: child, - ) - : child, - ); - if (!withScrolling) { - return Padding( - padding: innerPadding ?? EdgeInsets.zero, - child: childWithPadding, - ); - } + const desiredWidth = FluffyThemes.columnWidth * 1.5; + final body = constraints.maxWidth <= desiredWidth + ? child + : Container( + alignment: Alignment.topCenter, + padding: const EdgeInsets.all(32), + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: FluffyThemes.columnWidth * 1.5, + ), + child: Material( + elevation: Theme.of(context) + .appBarTheme + .scrolledUnderElevation ?? + 4, + clipBehavior: Clip.hardEdge, + borderRadius: + BorderRadius.circular(AppConfig.borderRadius), + shadowColor: Theme.of(context).appBarTheme.shadowColor, + child: child, + ), + ), + ); + if (!withScrolling) return body; + return SingleChildScrollView( padding: innerPadding, physics: const ScrollPhysics(), - child: childWithPadding, + child: body, ); }, ), From 8128c960dc5523a99049c84d58f1b45e7a3848d5 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Fri, 26 Jul 2024 18:07:54 +0200 Subject: [PATCH 134/288] chore: Follow up pinned events --- lib/pages/chat/chat_app_bar_list_tile.dart | 61 ++++++++++++---------- lib/pages/chat/chat_view.dart | 6 +-- lib/pages/chat/pinned_events.dart | 4 +- 3 files changed, 37 insertions(+), 34 deletions(-) diff --git a/lib/pages/chat/chat_app_bar_list_tile.dart b/lib/pages/chat/chat_app_bar_list_tile.dart index 1e0ec82599..2591442519 100644 --- a/lib/pages/chat/chat_app_bar_list_tile.dart +++ b/lib/pages/chat/chat_app_bar_list_tile.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_linkify/flutter_linkify.dart'; -import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/utils/url_launcher.dart'; class ChatAppBarListTile extends StatelessWidget { @@ -11,6 +10,8 @@ class ChatAppBarListTile extends StatelessWidget { final Widget? trailing; final void Function()? onTap; + static const double fixedHeight = 40.0; + const ChatAppBarListTile({ super.key, this.leading, @@ -23,38 +24,40 @@ class ChatAppBarListTile extends StatelessWidget { Widget build(BuildContext context) { final leading = this.leading; final trailing = this.trailing; - final fontSize = AppConfig.messageFontSize * AppConfig.fontSizeFactor; - return InkWell( - onTap: onTap, - child: Row( - children: [ - if (leading != null) leading, - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 4.0), - child: Linkify( - text: title, - options: const LinkifyOptions(humanize: false), - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, + return SizedBox( + height: fixedHeight, + child: InkWell( + onTap: onTap, + child: Row( + children: [ + if (leading != null) leading, + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: Linkify( + text: title, + options: const LinkifyOptions(humanize: false), + maxLines: 1, overflow: TextOverflow.ellipsis, - fontSize: fontSize, - ), - linkStyle: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, - fontSize: fontSize, - decoration: TextDecoration.underline, - decorationColor: - Theme.of(context).colorScheme.onSurfaceVariant, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + overflow: TextOverflow.ellipsis, + fontSize: 14, + ), + linkStyle: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + fontSize: 14, + decoration: TextDecoration.underline, + decorationColor: + Theme.of(context).colorScheme.onSurfaceVariant, + ), + onOpen: (url) => UrlLauncher(context, url.url).launchUrl(), ), - onOpen: (url) => UrlLauncher(context, url.url).launchUrl(), ), ), - ), - if (trailing != null) trailing, - ], + if (trailing != null) trailing, + ], + ), ), ); } diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 80a84fa41e..422175f0fa 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -158,15 +158,15 @@ class ChatView extends StatelessWidget { builder: (BuildContext context, snapshot) { var appbarBottomHeight = 0.0; if (controller.room.pinnedEventIds.isNotEmpty) { - appbarBottomHeight += 42; + appbarBottomHeight += ChatAppBarListTile.fixedHeight; } if (scrollUpBannerEventId != null) { - appbarBottomHeight += 42; + appbarBottomHeight += ChatAppBarListTile.fixedHeight; } final tombstoneEvent = controller.room.getState(EventTypes.RoomTombstone); if (tombstoneEvent != null) { - appbarBottomHeight += 42; + appbarBottomHeight += ChatAppBarListTile.fixedHeight; } return Scaffold( appBar: AppBar( diff --git a/lib/pages/chat/pinned_events.dart b/lib/pages/chat/pinned_events.dart index 0940a786f3..7afe413dbf 100644 --- a/lib/pages/chat/pinned_events.dart +++ b/lib/pages/chat/pinned_events.dart @@ -71,8 +71,8 @@ class PinnedEvents extends StatelessWidget { ) ?? L10n.of(context)!.loadingPleaseWait, leading: IconButton( - splashRadius: 20, - iconSize: 20, + splashRadius: 18, + iconSize: 18, color: Theme.of(context).colorScheme.onSurfaceVariant, icon: const Icon(Icons.push_pin), tooltip: L10n.of(context)!.unpin, From 3d965bf6ac7c06d01531e04041086114bd40a17e Mon Sep 17 00:00:00 2001 From: krille-chan Date: Fri, 26 Jul 2024 18:31:27 +0200 Subject: [PATCH 135/288] chore: Follow up user bottom sheet --- lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart index 01035bb777..0b78c27f98 100644 --- a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart +++ b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart @@ -119,6 +119,8 @@ class UserBottomSheetView extends StatelessWidget { Padding( padding: const EdgeInsets.all(16.0), child: Avatar( + client: + Matrix.of(controller.widget.outerContext).client, mxContent: avatarUrl, name: displayname, size: Avatar.defaultSize * 2.5, From b1a9c6e92db32b69be43cf847ef60b71b97c9a2b Mon Sep 17 00:00:00 2001 From: krille-chan Date: Fri, 26 Jul 2024 19:36:07 +0200 Subject: [PATCH 136/288] chore: Follow up user bottom sheet --- lib/widgets/avatar.dart | 1 + lib/widgets/mxc_image.dart | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/widgets/avatar.dart b/lib/widgets/avatar.dart index c3e24fe169..c8096ed85b 100644 --- a/lib/widgets/avatar.dart +++ b/lib/widgets/avatar.dart @@ -79,6 +79,7 @@ class Avatar extends StatelessWidget { child: noPic ? textWidget : MxcImage( + client: client, key: ValueKey(mxContent.toString()), cacheKey: '${mxContent}_$size', uri: mxContent, diff --git a/lib/widgets/mxc_image.dart b/lib/widgets/mxc_image.dart index 34c53cb577..2126a8cbe6 100644 --- a/lib/widgets/mxc_image.dart +++ b/lib/widgets/mxc_image.dart @@ -23,6 +23,7 @@ class MxcImage extends StatefulWidget { final ThumbnailMethod thumbnailMethod; final Widget Function(BuildContext context)? placeholder; final String? cacheKey; + final Client? client; const MxcImage({ this.uri, @@ -38,6 +39,7 @@ class MxcImage extends StatefulWidget { this.animationCurve = FluffyThemes.animationCurve, this.thumbnailMethod = ThumbnailMethod.scale, this.cacheKey, + this.client, super.key, }); @@ -64,7 +66,7 @@ class _MxcImageState extends State { bool? _isCached; Future _load() async { - final client = Matrix.of(context).client; + final client = widget.client ?? Matrix.of(context).client; final uri = widget.uri; final event = widget.event; From 3434741bc5f8b7d82e0e4c6cfb7f6d0c7472d05c Mon Sep 17 00:00:00 2001 From: krille-chan Date: Fri, 26 Jul 2024 21:02:58 +0200 Subject: [PATCH 137/288] refactor: Clean up some widths --- lib/config/app_config.dart | 2 +- lib/pages/chat_list/navi_rail_item.dart | 4 ++-- lib/widgets/layouts/two_column_layout.dart | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index 841d810ec6..298b8743c6 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -12,7 +12,7 @@ abstract class AppConfig { static double fontSizeFactor = 1; static const Color chatColor = primaryColor; static Color? colorSchemeSeed = primaryColor; - static const double messageFontSize = 15.75; + static const double messageFontSize = 16.0; static const bool allowOtherHomeservers = true; static const bool enableRegistration = true; static const Color primaryColor = Color(0xFF5625BA); diff --git a/lib/pages/chat_list/navi_rail_item.dart b/lib/pages/chat_list/navi_rail_item.dart index 66ad7c0418..6cbb70e2ec 100644 --- a/lib/pages/chat_list/navi_rail_item.dart +++ b/lib/pages/chat_list/navi_rail_item.dart @@ -33,8 +33,8 @@ class NaviRailItem extends StatelessWidget { return HoverBuilder( builder: (context, hovered) { return SizedBox( - height: 64, - width: 64, + height: FluffyThemes.navRailWidth, + width: FluffyThemes.navRailWidth, child: Stack( children: [ Positioned( diff --git a/lib/widgets/layouts/two_column_layout.dart b/lib/widgets/layouts/two_column_layout.dart index a6f4c8bdf6..33003472e6 100644 --- a/lib/widgets/layouts/two_column_layout.dart +++ b/lib/widgets/layouts/two_column_layout.dart @@ -1,3 +1,4 @@ +import 'package:fluffychat/config/themes.dart'; import 'package:flutter/material.dart'; class TwoColumnLayout extends StatelessWidget { @@ -20,7 +21,8 @@ class TwoColumnLayout extends StatelessWidget { Container( clipBehavior: Clip.antiAlias, decoration: const BoxDecoration(), - width: 360.0 + (displayNavigationRail ? 64 : 0), + width: FluffyThemes.columnWidth + + (displayNavigationRail ? FluffyThemes.navRailWidth : 0), child: mainView, ), Container( From 30e3a4f2ff8bd840f23fc115efd1176cea66dc59 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Fri, 26 Jul 2024 21:06:53 +0200 Subject: [PATCH 138/288] chore: Nicer background for sate message and time in chat --- lib/pages/chat/events/message.dart | 30 ++++++++++------------ lib/pages/chat/events/state_message.dart | 10 +++++--- lib/widgets/layouts/two_column_layout.dart | 3 ++- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index 6a4ec3f2e5..5f1e7dc35b 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -435,22 +435,20 @@ class Message extends StatelessWidget { ? const EdgeInsets.symmetric(vertical: 8.0) : EdgeInsets.zero, child: Center( - child: Material( - color: displayTime - ? Theme.of(context).colorScheme.surface - : Theme.of(context).colorScheme.surface.withOpacity(0.33), - borderRadius: - BorderRadius.circular(AppConfig.borderRadius / 2), - clipBehavior: Clip.antiAlias, - child: Padding( - padding: const EdgeInsets.only(top: 4.0), - child: Text( - event.originServerTs.localizedTime(context), - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 12 * AppConfig.fontSizeFactor, - color: Theme.of(context).colorScheme.secondary, - ), + child: Padding( + padding: const EdgeInsets.only(top: 4.0), + child: Text( + event.originServerTs.localizedTime(context), + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 12 * AppConfig.fontSizeFactor, + color: Theme.of(context).colorScheme.secondary, + shadows: [ + Shadow( + color: Theme.of(context).colorScheme.surface, + blurRadius: 3, + ), + ], ), ), ), diff --git a/lib/pages/chat/events/state_message.dart b/lib/pages/chat/events/state_message.dart index 0aa5d9dc23..ebd6f7373a 100644 --- a/lib/pages/chat/events/state_message.dart +++ b/lib/pages/chat/events/state_message.dart @@ -17,10 +17,6 @@ class StateMessage extends StatelessWidget { child: Center( child: Container( padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), - ), child: Text( event.calcLocalizedBodyFallback( MatrixLocals(L10n.of(context)!), @@ -29,6 +25,12 @@ class StateMessage extends StatelessWidget { style: TextStyle( fontSize: 12 * AppConfig.fontSizeFactor, decoration: event.redacted ? TextDecoration.lineThrough : null, + shadows: [ + Shadow( + color: Theme.of(context).colorScheme.surface, + blurRadius: 3, + ), + ], ), ), ), diff --git a/lib/widgets/layouts/two_column_layout.dart b/lib/widgets/layouts/two_column_layout.dart index 33003472e6..cf1083a3f6 100644 --- a/lib/widgets/layouts/two_column_layout.dart +++ b/lib/widgets/layouts/two_column_layout.dart @@ -1,6 +1,7 @@ -import 'package:fluffychat/config/themes.dart'; import 'package:flutter/material.dart'; +import 'package:fluffychat/config/themes.dart'; + class TwoColumnLayout extends StatelessWidget { final Widget mainView; final Widget sideView; From 5b648624dae097755bd69ea0563a8e52e4b3c188 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Fri, 26 Jul 2024 21:25:45 +0200 Subject: [PATCH 139/288] chore: Chat permissions page follow up --- assets/l10n/intl_en.arb | 3 ++- .../chat_permissions_settings_view.dart | 16 ++++++++++++++ .../permission_list_tile.dart | 22 ++++++++++++------- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index f7016107be..7132187c10 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -2739,5 +2739,6 @@ "changeTheVisibilityOfChatHistory": "Change the visibility of the chat history", "changeTheCanonicalRoomAlias": "Change the main public chat address", "sendRoomNotifications": "Send a @room notifications", - "changeTheDescriptionOfTheGroup": "Change the description of the chat" + "changeTheDescriptionOfTheGroup": "Change the description of the chat", + "chatPermissionsDescription": "Define which power level is necessary for certain actions in this chat. The power levels 0, 50 and 100 are usually representing users, moderators and admins, but any gradation is possible." } diff --git a/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart b/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart index 50d46c08e0..610f6b0e8b 100644 --- a/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart +++ b/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart @@ -41,6 +41,22 @@ class ChatPermissionsSettingsView extends StatelessWidget { )..removeWhere((k, v) => v is! int); return Column( children: [ + ListTile( + leading: const Icon(Icons.info_outlined), + subtitle: Text( + L10n.of(context)!.chatPermissionsDescription, + ), + ), + Divider(color: Theme.of(context).dividerColor), + ListTile( + title: Text( + L10n.of(context)!.chatPermissions, + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.bold, + ), + ), + ), Column( mainAxisSize: MainAxisSize.min, children: [ diff --git a/lib/pages/chat_permissions_settings/permission_list_tile.dart b/lib/pages/chat_permissions_settings/permission_list_tile.dart index 99f9e553b3..118982d378 100644 --- a/lib/pages/chat_permissions_settings/permission_list_tile.dart +++ b/lib/pages/chat_permissions_settings/permission_list_tile.dart @@ -82,11 +82,8 @@ class PermissionsListTile extends StatelessWidget { style: Theme.of(context).textTheme.titleSmall, ), trailing: Material( - color: color.withAlpha(64), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), - side: BorderSide(color: color), - ), + color: color.withAlpha(32), + borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), child: DropdownButton( padding: const EdgeInsets.symmetric(horizontal: 8.0), borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), @@ -96,15 +93,24 @@ class PermissionsListTile extends StatelessWidget { items: [ DropdownMenuItem( value: permission < 50 ? permission : 0, - child: Text(L10n.of(context)!.userLevel(permission)), + child: Text( + L10n.of(context)!.userLevel(permission < 50 ? permission : 0), + ), ), DropdownMenuItem( value: permission < 100 && permission >= 50 ? permission : 50, - child: Text(L10n.of(context)!.moderatorLevel(permission)), + child: Text( + L10n.of(context)!.moderatorLevel( + permission < 100 && permission >= 50 ? permission : 50, + ), + ), ), DropdownMenuItem( value: permission >= 100 ? permission : 100, - child: Text(L10n.of(context)!.adminLevel(permission)), + child: Text( + L10n.of(context)! + .adminLevel(permission >= 100 ? permission : 100), + ), ), DropdownMenuItem( value: null, From c7b9acfcdf828a029cdbda5c3876cded45324c84 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Fri, 26 Jul 2024 22:05:51 +0200 Subject: [PATCH 140/288] chore: Polish public room bottom sheet --- lib/widgets/public_room_bottom_sheet.dart | 111 +++++++++++++--------- 1 file changed, 64 insertions(+), 47 deletions(-) diff --git a/lib/widgets/public_room_bottom_sheet.dart b/lib/widgets/public_room_bottom_sheet.dart index 4f5806c2d8..3ac568d8ce 100644 --- a/lib/widgets/public_room_bottom_sheet.dart +++ b/lib/widgets/public_room_bottom_sheet.dart @@ -10,7 +10,6 @@ import 'package:fluffychat/utils/fluffy_share.dart'; import 'package:fluffychat/utils/url_launcher.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/matrix.dart'; -import '../utils/localized_exception_extension.dart'; class PublicRoomBottomSheet extends StatelessWidget { final String? roomAlias; @@ -82,6 +81,7 @@ class PublicRoomBottomSheet extends StatelessWidget { @override Widget build(BuildContext context) { final roomAlias = this.roomAlias ?? chunk?.canonicalAlias; + final roomLink = roomAlias ?? chunk?.roomId; return SafeArea( child: Scaffold( appBar: AppBar( @@ -114,28 +114,71 @@ class PublicRoomBottomSheet extends StatelessWidget { return ListView( padding: EdgeInsets.zero, children: [ - if (profile == null) - Container( - height: 156, - alignment: Alignment.center, - color: Theme.of(context).secondaryHeaderColor, - child: snapshot.hasError - ? Text(snapshot.error!.toLocalizedString(context)) - : const CircularProgressIndicator.adaptive( - strokeWidth: 2, - ), - ) - else - Center( - child: Padding( + Row( + children: [ + Padding( padding: const EdgeInsets.all(16.0), - child: Avatar( - mxContent: profile.avatarUrl, - name: profile.name ?? roomAlias, - size: Avatar.defaultSize * 3, + child: profile == null + ? const Center( + child: CircularProgressIndicator.adaptive(), + ) + : Avatar( + client: Matrix.of(outerContext).client, + mxContent: profile.avatarUrl, + name: profile.name ?? roomAlias, + size: Avatar.defaultSize * 3, + ), + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextButton.icon( + onPressed: roomLink != null + ? () => FluffyShare.share( + roomLink, + context, + copyOnly: true, + ) + : null, + icon: const Icon( + Icons.copy_outlined, + size: 14, + ), + style: TextButton.styleFrom( + foregroundColor: + Theme.of(context).colorScheme.onSurface, + ), + label: Text( + roomLink ?? '...', + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + TextButton.icon( + onPressed: () {}, + icon: const Icon( + Icons.groups_3_outlined, + size: 14, + ), + style: TextButton.styleFrom( + foregroundColor: + Theme.of(context).colorScheme.onSurface, + ), + label: Text( + L10n.of(context)!.countParticipants( + profile?.numJoinedMembers ?? 0, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], ), ), - ), + ], + ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: ElevatedButton.icon( @@ -151,36 +194,10 @@ class PublicRoomBottomSheet extends StatelessWidget { ? L10n.of(context)!.joinSpace : L10n.of(context)!.joinRoom, ), - icon: const Icon(Icons.login_outlined), + icon: const Icon(Icons.navigate_next), ), ), const SizedBox(height: 16), - ListTile( - title: Text( - profile?.name ?? - roomAlias?.localpart ?? - chunk?.roomId.localpart ?? - L10n.of(context)!.chat, - ), - subtitle: Text( - '${L10n.of(context)!.participant}: ${profile?.numJoinedMembers ?? 0}', - ), - trailing: const Icon(Icons.account_box_outlined), - ), - if (roomAlias != null) - ListTile( - title: Text(L10n.of(context)!.publicLink), - subtitle: SelectableText(roomAlias), - contentPadding: - const EdgeInsets.symmetric(horizontal: 16.0), - trailing: IconButton( - icon: const Icon(Icons.copy_outlined), - onPressed: () => FluffyShare.share( - roomAlias, - context, - ), - ), - ), if (profile?.topic?.isNotEmpty ?? false) ListTile( subtitle: SelectableLinkify( From 76b7fbf36f7446037b131b148643e5fc6c21203f Mon Sep 17 00:00:00 2001 From: krille-chan Date: Fri, 26 Jul 2024 22:12:03 +0200 Subject: [PATCH 141/288] chore: Follow up join space children --- lib/pages/chat_list/space_view.dart | 47 ++++++++--------------- lib/widgets/public_room_bottom_sheet.dart | 13 ++++--- 2 files changed, 25 insertions(+), 35 deletions(-) diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index 5ad99faa88..94341ed9e8 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -11,10 +11,12 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/chat_list/chat_list_item.dart'; import 'package:fluffychat/pages/chat_list/search_title.dart'; +import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/stream_extension.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:fluffychat/widgets/public_room_bottom_sheet.dart'; enum AddRoomType { chat, subspace } @@ -95,38 +97,23 @@ class _SpaceViewState extends State { final client = Matrix.of(context).client; final space = client.getRoomById(widget.spaceId); - final consent = await showOkCancelAlertDialog( + final joined = await showAdaptiveBottomSheet( context: context, - title: item.name ?? item.canonicalAlias ?? L10n.of(context)!.emptyChat, - message: item.topic, - okLabel: L10n.of(context)!.joinRoom, - cancelLabel: L10n.of(context)!.cancel, - ); - if (consent != OkCancelResult.ok) return; - if (!mounted) return; - - await showFutureLoadingDialog( - context: context, - future: () async { - await client.joinRoom( - item.roomId, - serverName: space?.spaceChildren - .firstWhereOrNull( - (child) => child.roomId == item.roomId, - ) - ?.via, - ); - if (client.getRoomById(item.roomId) == null) { - // Wait for room actually appears in sync - await client.waitForRoomInSync(item.roomId, join: true); - } - }, + builder: (_) => PublicRoomBottomSheet( + outerContext: context, + chunk: item, + via: space?.spaceChildren + .firstWhereOrNull( + (child) => child.roomId == item.roomId, + ) + ?.via, + ), ); - if (!mounted) return; - - setState(() { - _discoveredChildren.remove(item); - }); + if (mounted && joined == true) { + setState(() { + _discoveredChildren.remove(item); + }); + } } void _onSpaceAction(SpaceActions action) async { diff --git a/lib/widgets/public_room_bottom_sheet.dart b/lib/widgets/public_room_bottom_sheet.dart index 3ac568d8ce..3105cc576c 100644 --- a/lib/widgets/public_room_bottom_sheet.dart +++ b/lib/widgets/public_room_bottom_sheet.dart @@ -15,13 +15,13 @@ class PublicRoomBottomSheet extends StatelessWidget { final String? roomAlias; final BuildContext outerContext; final PublicRoomsChunk? chunk; - final VoidCallback? onRoomJoined; + final List? via; PublicRoomBottomSheet({ this.roomAlias, required this.outerContext, this.chunk, - this.onRoomJoined, + this.via, super.key, }) { assert(roomAlias != null || chunk != null); @@ -38,8 +38,11 @@ class PublicRoomBottomSheet extends StatelessWidget { return chunk.roomId; } final roomId = chunk != null && knock - ? await client.knockRoom(chunk.roomId) - : await client.joinRoom(roomAlias ?? chunk!.roomId); + ? await client.knockRoom(chunk.roomId, serverName: via) + : await client.joinRoom( + roomAlias ?? chunk!.roomId, + serverName: via, + ); if (!knock && client.getRoomById(roomId) == null) { await client.waitForRoomInSync(roomId); @@ -51,7 +54,7 @@ class PublicRoomBottomSheet extends StatelessWidget { return; } if (result.error == null) { - Navigator.of(context).pop(); + Navigator.of(context).pop(true); // don't open the room if the joined room is a space if (chunk?.roomType != 'm.space' && !client.getRoomById(result.result!)!.isSpace) { From 8dd43d8a7fb6d69abd5bdb454476dfc2ab0bb79c Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sat, 27 Jul 2024 19:02:47 +0200 Subject: [PATCH 142/288] build: Update to Flutter 3.22.3 --- .github/workflows/versions.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/versions.env b/.github/workflows/versions.env index 4d10c42f47..eaa595930e 100644 --- a/.github/workflows/versions.env +++ b/.github/workflows/versions.env @@ -1,2 +1,2 @@ -FLUTTER_VERSION=3.22.2 +FLUTTER_VERSION=3.22.3 JAVA_VERSION=17 From 8ae8dbe45e6b58f68c8c5b3ef690bce3ba013954 Mon Sep 17 00:00:00 2001 From: Krille Date: Sun, 28 Jul 2024 09:52:27 +0200 Subject: [PATCH 143/288] fix: Scroll to event missing the position --- lib/pages/chat/chat.dart | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 2592f560c3..ccfb1eb843 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -29,6 +29,7 @@ import 'package:fluffychat/pages/chat/recording_dialog.dart'; import 'package:fluffychat/pages/chat_details/chat_details.dart'; import 'package:fluffychat/utils/error_reporter.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions/filtered_timeline_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/app_lock.dart'; @@ -292,7 +293,7 @@ class ChatController extends State if (timeline?.events.any((event) => event.eventId == fullyRead) ?? false) { Logs().v('Scroll up to visible event', fullyRead); - setReadMarker(); + scrollToEventId(fullyRead); return; } if (!mounted) return; @@ -901,7 +902,10 @@ class ChatController extends State } void scrollToEventId(String eventId) async { - final eventIndex = timeline!.events.indexWhere((e) => e.eventId == eventId); + final eventIndex = timeline!.events + .where((event) => event.isVisibleInGui) + .toList() + .indexWhere((e) => e.eventId == eventId); if (eventIndex == -1) { setState(() { timeline = null; @@ -921,7 +925,8 @@ class ChatController extends State scrollToEventIdMarker = eventId; }); await scrollController.scrollToIndex( - eventIndex, + eventIndex + 1, + duration: FluffyThemes.animationDuration, preferPosition: AutoScrollPosition.middle, ); _updateScrollController(); From 5f9aa943390c9c587b2e6cf86f5ee56dee91ccff Mon Sep 17 00:00:00 2001 From: Krille Date: Sun, 28 Jul 2024 09:54:07 +0200 Subject: [PATCH 144/288] chore: Follow up highlight event on room open --- lib/pages/chat/chat.dart | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index ccfb1eb843..129bcb4efe 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -293,7 +293,7 @@ class ChatController extends State if (timeline?.events.any((event) => event.eventId == fullyRead) ?? false) { Logs().v('Scroll up to visible event', fullyRead); - scrollToEventId(fullyRead); + scrollToEventId(fullyRead, highlightEvent: false); return; } if (!mounted) return; @@ -901,7 +901,10 @@ class ChatController extends State inputFocus.requestFocus(); } - void scrollToEventId(String eventId) async { + void scrollToEventId( + String eventId, { + bool highlightEvent = true, + }) async { final eventIndex = timeline!.events .where((event) => event.isVisibleInGui) .toList() @@ -921,9 +924,11 @@ class ChatController extends State }); return; } - setState(() { - scrollToEventIdMarker = eventId; - }); + if (highlightEvent) { + setState(() { + scrollToEventIdMarker = eventId; + }); + } await scrollController.scrollToIndex( eventIndex + 1, duration: FluffyThemes.animationDuration, From 83490de6fd967ba8545d5960c0a6cebd410e0580 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sun, 28 Jul 2024 14:02:18 +0200 Subject: [PATCH 145/288] fix: Path correct userId to ignore list --- lib/pages/settings_ignore_list/settings_ignore_list.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/pages/settings_ignore_list/settings_ignore_list.dart b/lib/pages/settings_ignore_list/settings_ignore_list.dart index d64be058bd..0468d267b5 100644 --- a/lib/pages/settings_ignore_list/settings_ignore_list.dart +++ b/lib/pages/settings_ignore_list/settings_ignore_list.dart @@ -22,8 +22,9 @@ class SettingsIgnoreListController extends State { @override void initState() { super.initState(); - if (widget.initialUserId != null) { - controller.text = widget.initialUserId!.replaceAll('@', ''); + final initialUserId = widget.initialUserId; + if (initialUserId != null) { + controller.text = initialUserId; } } From 21e7c3f8cb3898306ba47788a962fba4ef24673d Mon Sep 17 00:00:00 2001 From: Krille Date: Mon, 29 Jul 2024 08:36:54 +0200 Subject: [PATCH 146/288] build: Update matrix dart sdk --- assets/l10n/intl_en.arb | 6 ++++++ lib/pages/settings/settings.dart | 2 -- lib/utils/matrix_sdk_extensions/matrix_locals.dart | 3 +++ pubspec.lock | 4 ++-- pubspec.yaml | 4 ++-- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 7132187c10..30a8f9131c 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -2279,6 +2279,12 @@ "user": {} } }, + "invitedBy": "📩 Invited by {user}", + "@invitedBy": { + "placeholders": { + "user": {} + } + }, "youInvitedUser": "📩 You invited {user}", "@youInvitedUser": { "placeholders": { diff --git a/lib/pages/settings/settings.dart b/lib/pages/settings/settings.dart index 97bbec66d3..d844063b10 100644 --- a/lib/pages/settings/settings.dart +++ b/lib/pages/settings/settings.dart @@ -209,8 +209,6 @@ class SettingsController extends State { final client = Matrix.of(context).client; profileFuture ??= client.getProfileFromUserId( client.userID!, - cache: !profileUpdated, - getFromRooms: !profileUpdated, ); return SettingsView(this); } diff --git a/lib/utils/matrix_sdk_extensions/matrix_locals.dart b/lib/utils/matrix_sdk_extensions/matrix_locals.dart index 5714baa226..cbba6b6302 100644 --- a/lib/utils/matrix_sdk_extensions/matrix_locals.dart +++ b/lib/utils/matrix_sdk_extensions/matrix_locals.dart @@ -344,4 +344,7 @@ class MatrixLocals extends MatrixLocalizations { @override String startedKeyVerification(String senderName) => l10n.startedKeyVerification(senderName); + + @override + String invitedBy(String senderName) => l10n.invitedBy(senderName); } diff --git a/pubspec.lock b/pubspec.lock index 7e3909e546..c46a3944fb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1210,10 +1210,10 @@ packages: dependency: "direct main" description: name: matrix - sha256: a27c2f73d28ea292e0f67f3d36396fb8acd7cfc97a07901dc7b22f46e082c3d6 + sha256: d1955846aaf5a5c6d353a90ce4133b9c99581cd64f4fe9e389e5f8b95157ca3b url: "https://pub.dev" source: hosted - version: "0.30.0" + version: "0.31.0" meta: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a013aeea32..69c80d6e1a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -64,7 +64,7 @@ dependencies: keyboard_shortcuts: ^0.1.4 latlong2: ^0.9.1 linkify: ^5.0.0 - matrix: ^0.30.0 + matrix: ^0.31.0 native_imaging: ^0.1.1 package_info_plus: ^6.0.0 pasteboard: ^0.2.0 @@ -160,4 +160,4 @@ dependency_overrides: git: url: https://github.com/TheOneWithTheBraid/keyboard_shortcuts.git ref: null-safety - win32: 5.5.0 \ No newline at end of file + win32: 5.5.0 From c5187c7639b3e4745a5649227de2346e40c43c20 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 29 Jul 2024 14:52:09 -0400 Subject: [PATCH 147/288] added learning summary to chat list, removed references to summary analytics --- assets/l10n/intl_en.arb | 5 +- lib/config/routes.dart | 24 +- lib/pages/chat/chat.dart | 2 +- lib/pages/chat_list/chat_list_body.dart | 4 + .../chat_list/client_chooser_button.dart | 35 +- .../message_analytics_controller.dart | 587 +++---------- .../controllers/my_analytics_controller.dart | 24 +- lib/pangea/enum/progress_indicators_enum.dart | 54 ++ lib/pangea/enum/time_span.dart | 59 +- .../client_analytics_extension.dart | 1 - .../events_extension.dart | 86 +- .../pangea_room_extension.dart | 19 +- .../room_analytics_extension.dart | 46 +- .../models/analytics/analytics_event.dart | 29 - .../models/analytics/analytics_model.dart | 23 - .../analytics/chart_analytics_model.dart | 298 +++---- .../models/analytics/constructs_event.dart | 11 +- .../models/analytics/constructs_model.dart | 3 +- .../analytics/summary_analytics_event.dart | 21 - .../analytics/summary_analytics_model.dart | 111 --- .../pages/analytics/analytics_list_tile.dart | 282 +++--- .../pages/analytics/base_analytics.dart | 422 ++++----- .../pages/analytics/base_analytics_view.dart | 476 +++++------ .../pages/analytics/construct_list.dart | 100 +-- .../analytics/list_summary_analytics.dart | 184 ++-- .../pages/analytics/messages_bar_chart.dart | 804 +++++++++--------- .../space_analytics/space_analytics.dart | 207 +++-- .../space_analytics/space_analytics_view.dart | 124 +-- .../analytics/space_list/space_list.dart | 172 ++-- .../analytics/space_list/space_list_view.dart | 172 ++-- .../student_analytics/student_analytics.dart | 156 ++-- .../student_analytics_view.dart | 124 +-- .../learning_progress_indicators.dart | 195 +++++ .../analytics_summary/progress_indicator.dart | 51 ++ lib/utils/client_manager.dart | 1 - 35 files changed, 2235 insertions(+), 2677 deletions(-) create mode 100644 lib/pangea/enum/progress_indicators_enum.dart delete mode 100644 lib/pangea/models/analytics/analytics_event.dart delete mode 100644 lib/pangea/models/analytics/analytics_model.dart delete mode 100644 lib/pangea/models/analytics/summary_analytics_event.dart delete mode 100644 lib/pangea/models/analytics/summary_analytics_model.dart create mode 100644 lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart create mode 100644 lib/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 385009ff6d..eaccf93a41 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4113,5 +4113,8 @@ "createSpace": "Create space", "createChat": "Create chat", "error520Title": "Please try again.", - "error520Desc": "Sorry, we could not understand your message..." + "error520Desc": "Sorry, we could not understand your message...", + "wordsUsed": "Words Used", + "errorTypes": "Error Types", + "level": "Level" } \ No newline at end of file diff --git a/lib/config/routes.dart b/lib/config/routes.dart index d9c7a70f7d..1cf7c70f22 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -26,9 +26,7 @@ import 'package:fluffychat/pages/settings_notifications/settings_notifications.d import 'package:fluffychat/pages/settings_password/settings_password.dart'; import 'package:fluffychat/pages/settings_security/settings_security.dart'; import 'package:fluffychat/pages/settings_style/settings_style.dart'; -import 'package:fluffychat/pangea/enum/bar_chart_view_enum.dart'; import 'package:fluffychat/pangea/guard/p_vguard.dart'; -import 'package:fluffychat/pangea/pages/analytics/student_analytics/student_analytics.dart'; import 'package:fluffychat/pangea/pages/find_partner/find_partner.dart'; import 'package:fluffychat/pangea/pages/p_user_age/p_user_age.dart'; import 'package:fluffychat/pangea/pages/settings_learning/settings_learning.dart'; @@ -162,17 +160,17 @@ abstract class AppRoutes { ), routes: [ // #Pangea - GoRoute( - path: 'mylearning', - pageBuilder: (context, state) => defaultPageBuilder( - context, - state, - const StudentAnalyticsPage( - selectedView: BarChartViewSelection.messages, - ), - ), - redirect: loggedOutRedirect, - ), + // GoRoute( + // path: 'mylearning', + // pageBuilder: (context, state) => defaultPageBuilder( + // context, + // state, + // const StudentAnalyticsPage( + // selectedView: BarChartViewSelection.messages, + // ), + // ), + // redirect: loggedOutRedirect, + // ), // GoRoute( // path: 'analytics', // pageBuilder: (context, state) => defaultPageBuilder( diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index aa2b78d4dd..f4ab25c6f6 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -475,7 +475,7 @@ class ChatController extends State // Pangea# if (kIsWeb && !Matrix.of(context).webHasFocus) return; // #Pangea - } catch (err, s) { + } catch (err) { return; } // Pangea# diff --git a/lib/pages/chat_list/chat_list_body.dart b/lib/pages/chat_list/chat_list_body.dart index b9b388da98..1993a97a6d 100644 --- a/lib/pages/chat_list/chat_list_body.dart +++ b/lib/pages/chat_list/chat_list_body.dart @@ -3,6 +3,7 @@ import 'package:fluffychat/pages/chat_list/chat_list.dart'; import 'package:fluffychat/pages/chat_list/search_title.dart'; import 'package:fluffychat/pages/chat_list/space_view.dart'; import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart'; +import 'package:fluffychat/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart'; import 'package:fluffychat/pangea/widgets/chat_list/chat_list_body_text.dart'; import 'package:fluffychat/pangea/widgets/chat_list/chat_list_header_wrapper.dart'; import 'package:fluffychat/pangea/widgets/chat_list/chat_list_item_wrapper.dart'; @@ -164,6 +165,9 @@ class ChatListViewBody extends StatelessWidget { title: L10n.of(context)!.chats, icon: const Icon(Icons.forum_outlined), ), + // #Pangea + const LearningProgressIndicators(), + // Pangea# if (client.prevBatch != null && rooms.isEmpty && !controller.isSearchMode) ...[ diff --git a/lib/pages/chat_list/client_chooser_button.dart b/lib/pages/chat_list/client_chooser_button.dart index 15f52ebe2d..a6bf71e54e 100644 --- a/lib/pages/chat_list/client_chooser_button.dart +++ b/lib/pages/chat_list/client_chooser_button.dart @@ -1,5 +1,4 @@ import 'package:adaptive_dialog/adaptive_dialog.dart'; -import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; import 'package:fluffychat/pangea/utils/find_conversation_partner_dialog.dart'; import 'package:fluffychat/pangea/utils/logout.dart'; import 'package:fluffychat/pangea/utils/space_code.dart'; @@ -67,19 +66,19 @@ class ClientChooserButton extends StatelessWidget { // ], // ), // ), - PopupMenuItem( - enabled: matrix.client.rooms.any( - (room) => !room.isSpace && !room.isArchived && !room.isAnalyticsRoom, - ), - value: SettingsAction.myAnalytics, - child: Row( - children: [ - const Icon(Icons.analytics_outlined), - const SizedBox(width: 18), - Expanded(child: Text(L10n.of(context)!.myLearning)), - ], - ), - ), + // PopupMenuItem( + // enabled: matrix.client.rooms.any( + // (room) => !room.isSpace && !room.isArchived && !room.isAnalyticsRoom, + // ), + // value: SettingsAction.myAnalytics, + // child: Row( + // children: [ + // const Icon(Icons.analytics_outlined), + // const SizedBox(width: 18), + // Expanded(child: Text(L10n.of(context)!.myLearning)), + // ], + // ), + // ), PopupMenuItem( value: SettingsAction.newClass, child: Row( @@ -404,9 +403,9 @@ class ClientChooserButton extends StatelessWidget { // case SettingsAction.spaceAnalytics: // context.go('/rooms/analytics'); // break; - case SettingsAction.myAnalytics: - context.go('/rooms/mylearning'); - break; + // case SettingsAction.myAnalytics: + // context.go('/rooms/mylearning'); + // break; case SettingsAction.logout: pLogoutAction(context); break; @@ -497,7 +496,7 @@ enum SettingsAction { learning, joinWithClassCode, // spaceAnalytics, - myAnalytics, + // myAnalytics, findAConversationPartner, logout, newClass, diff --git a/lib/pangea/controllers/message_analytics_controller.dart b/lib/pangea/controllers/message_analytics_controller.dart index 32aaf66fc3..1883e96dc6 100644 --- a/lib/pangea/controllers/message_analytics_controller.dart +++ b/lib/pangea/controllers/message_analytics_controller.dart @@ -1,16 +1,9 @@ import 'dart:async'; -import 'dart:developer'; -import 'package:collection/collection.dart'; import 'package:fluffychat/pangea/constants/match_rule_ids.dart'; -import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; -import 'package:fluffychat/pangea/controllers/language_list_controller.dart'; import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; import 'package:fluffychat/pangea/enum/time_span.dart'; -import 'package:fluffychat/pangea/models/analytics/analytics_event.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_event.dart'; -import 'package:fluffychat/pangea/models/analytics/summary_analytics_event.dart'; -import 'package:fluffychat/pangea/models/language_model.dart'; import 'package:fluffychat/pangea/pages/analytics/base_analytics.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:flutter/foundation.dart'; @@ -20,89 +13,87 @@ import 'package:sentry_flutter/sentry_flutter.dart'; import '../constants/class_default_values.dart'; import '../extensions/client_extension/client_extension.dart'; import '../extensions/pangea_room_extension/pangea_room_extension.dart'; -import '../models/analytics/chart_analytics_model.dart'; import 'base_controller.dart'; import 'pangea_controller.dart'; // controls the fetching of analytics data class AnalyticsController extends BaseController { late PangeaController _pangeaController; - - final List _cachedAnalyticsModels = []; final List _cachedConstructs = []; AnalyticsController(PangeaController pangeaController) : super() { _pangeaController = pangeaController; } - ///////// TIME SPANS ////////// - String get _analyticsTimeSpanKey => "ANALYTICS_TIME_SPAN_KEY"; - - TimeSpan get currentAnalyticsTimeSpan { - try { - final String? str = _pangeaController.pStoreService.read( - _analyticsTimeSpanKey, - ); - return str != null - ? TimeSpan.values.firstWhere((e) { - final spanString = e.toString(); - return spanString == str; - }) - : ClassDefaultValues.defaultTimeSpan; - } catch (err) { - debugger(when: kDebugMode); - return ClassDefaultValues.defaultTimeSpan; - } - } - - Future setCurrentAnalyticsTimeSpan(TimeSpan timeSpan) async { - await _pangeaController.pStoreService.save( - _analyticsTimeSpanKey, - timeSpan.toString(), - ); - setState(); - } - - ///////// SPACE ANALYTICS LANGUAGES ////////// - String get _analyticsSpaceLangKey => "ANALYTICS_SPACE_LANG_KEY"; - - LanguageModel get currentAnalyticsLang { - try { - final String? str = _pangeaController.pStoreService.read( - _analyticsSpaceLangKey, - ); - return str != null - ? PangeaLanguage.byLangCode(str) - : _pangeaController.languageController.userL2 ?? - _pangeaController.pLanguageStore.targetOptions.first; - } catch (err) { - debugger(when: kDebugMode); - return _pangeaController.pLanguageStore.targetOptions.first; - } - } - - Future setCurrentAnalyticsLang(LanguageModel lang) async { - await _pangeaController.pStoreService.save( - _analyticsSpaceLangKey, - lang.langCode, - ); - setState(); - } - - /// given an analytics event type and the current analytics language, - /// get the last time the user updated their analytics - Future myAnalyticsLastUpdated(String type) async { - final List analyticsRooms = _pangeaController - .matrixState.client.allMyAnalyticsRooms - .where((room) => room.isAnalyticsRoom) - .toList(); + String get langCode => + _pangeaController.languageController.userL2?.langCode ?? + _pangeaController.pLanguageStore.targetOptions.first.langCode; + + // String get _analyticsTimeSpanKey => "ANALYTICS_TIME_SPAN_KEY"; + + // TimeSpan get currentAnalyticsTimeSpan { + // try { + // final String? str = _pangeaController.pStoreService.read( + // _analyticsTimeSpanKey, + // ); + // return str != null + // ? TimeSpan.values.firstWhere((e) { + // final spanString = e.toString(); + // return spanString == str; + // }) + // : ClassDefaultValues.defaultTimeSpan; + // } catch (err) { + // debugger(when: kDebugMode); + // return ClassDefaultValues.defaultTimeSpan; + // } + // } + + // Future setCurrentAnalyticsTimeSpan(TimeSpan timeSpan) async { + // await _pangeaController.pStoreService.save( + // _analyticsTimeSpanKey, + // timeSpan.toString(), + // ); + // setState(); + // } + + // String get _analyticsSpaceLangKey => "ANALYTICS_SPACE_LANG_KEY"; + + // LanguageModel get currentAnalyticsLang { + // try { + // final String? str = _pangeaController.pStoreService.read( + // _analyticsSpaceLangKey, + // ); + // return str != null + // ? PangeaLanguage.byLangCode(str) + // : _pangeaController.languageController.userL2 ?? + // _pangeaController.pLanguageStore.targetOptions.first; + // } catch (err) { + // debugger(when: kDebugMode); + // return _pangeaController.pLanguageStore.targetOptions.first; + // } + // } + + // Future setCurrentAnalyticsLang(LanguageModel lang) async { + // await _pangeaController.pStoreService.save( + // _analyticsSpaceLangKey, + // lang.langCode, + // ); + // setState(); + // } + + /// Get the last time the user updated their analytics. + /// Tries to get the last time the user updated analytics for their current L2. + /// If there isn't yet an analytics room reacted for their L2, checks if the + /// user has any other analytics rooms and returns the most recent update time. + Future myAnalyticsLastUpdated() async { + final List analyticsRooms = + _pangeaController.matrixState.client.allMyAnalyticsRooms; final Map langCodeLastUpdates = {}; for (final Room analyticsRoom in analyticsRooms) { final String? roomLang = analyticsRoom.madeForLang; if (roomLang == null) continue; final DateTime? lastUpdated = await analyticsRoom.analyticsLastUpdated( - type, _pangeaController.matrixState.client.userID!, ); if (lastUpdated != null) { @@ -121,25 +112,20 @@ class AnalyticsController extends BaseController { ); } + /// check if any students have recently updated their analytics + /// if any have, then the cache needs to be updated Future spaceAnalyticsLastUpdated( - String type, Room space, ) async { - // check if any students have recently updated their analytics - // if any have, then the cache needs to be updated - // TODO - figure out how to do this on a per-student basis await space.requestParticipants(); final List> lastUpdatedFutures = []; for (final student in space.students) { final Room? analyticsRoom = _pangeaController.matrixState.client - .analyticsRoomLocal(currentAnalyticsLang.langCode, student.id); + .analyticsRoomLocal(langCode, student.id); if (analyticsRoom == null) continue; lastUpdatedFutures.add( - analyticsRoom.analyticsLastUpdated( - type, - student.id, - ), + analyticsRoom.analyticsLastUpdated(student.id), ); } @@ -155,372 +141,16 @@ class AnalyticsController extends BaseController { return null; } - // Map of space ids to the last fetched hierarchy. Used when filtering - // private chat analytics to determine which children are already visible - // in the chat list - final Map> _lastFetchedHierarchies = {}; - - void setLatestHierarchy(String spaceId, GetSpaceHierarchyResponse resp) { - final List roomIds = resp.rooms.map((room) => room.roomId).toList(); - _lastFetchedHierarchies[spaceId] = roomIds; - } - - Future> getLatestSpaceHierarchy(String spaceId) async { - if (!_lastFetchedHierarchies.containsKey(spaceId)) { - final resp = - await _pangeaController.matrixState.client.getSpaceHierarchy(spaceId); - setLatestHierarchy(spaceId, resp); - } - return _lastFetchedHierarchies[spaceId] ?? []; - } - - //////////////////////////// MESSAGE SUMMARY ANALYTICS //////////////////////////// - - /// get all the summary analytics events for the current user - /// in the current language's analytics room - Future> mySummaryAnalytics() async { - final Room? analyticsRoom = _pangeaController.matrixState.client - .analyticsRoomLocal(currentAnalyticsLang.langCode); - if (analyticsRoom == null) return []; - - final List? roomEvents = - await analyticsRoom.getAnalyticsEvents( - type: PangeaEventTypes.summaryAnalytics, - since: currentAnalyticsTimeSpan.cutOffDate, - userId: _pangeaController.matrixState.client.userID!, - ); - return roomEvents?.cast() ?? []; - } - - Future> spaceMemberAnalytics( - Room space, - ) async { - // gets all the summary analytics events for the students - // in a space since the current timespace's cut off date - - // ensure that the participants of the space are loaded - await space.requestParticipants(); - - // TODO switch to using list of futures - final List analyticsEvents = []; - for (final student in space.students) { - final Room? analyticsRoom = _pangeaController.matrixState.client - .analyticsRoomLocal(currentAnalyticsLang.langCode, student.id); - - if (analyticsRoom != null) { - final List? roomEvents = - await analyticsRoom.getAnalyticsEvents( - type: PangeaEventTypes.summaryAnalytics, - since: currentAnalyticsTimeSpan.cutOffDate, - userId: student.id, - ); - analyticsEvents.addAll( - roomEvents?.cast() ?? [], - ); - } - } - - final List spaceChildrenIds = space.allSpaceChildRoomIds; - - // filter out the analyics events that don't belong to the space's children - final List allAnalyticsEvents = []; - for (final analyticsEvent in analyticsEvents) { - analyticsEvent.content.messages.removeWhere( - (msg) => !spaceChildrenIds.contains(msg.chatId), - ); - allAnalyticsEvents.add(analyticsEvent); - } - - return allAnalyticsEvents; - } - - ChartAnalyticsModel? getAnalyticsLocal({ - TimeSpan? timeSpan, - required AnalyticsSelected defaultSelected, - AnalyticsSelected? selected, - bool forceUpdate = false, - bool updateExpired = false, - DateTime? lastUpdated, - }) { - timeSpan ??= currentAnalyticsTimeSpan; - final int index = _cachedAnalyticsModels.indexWhere( - (e) => - (e.timeSpan == timeSpan) && - (e.defaultSelected.id == defaultSelected.id) && - (e.defaultSelected.type == defaultSelected.type) && - (e.selected?.id == selected?.id) && - (e.selected?.type == selected?.type) && - (e.langCode == currentAnalyticsLang.langCode), - ); - - if (index != -1) { - if ((updateExpired && _cachedAnalyticsModels[index].isExpired) || - forceUpdate || - _cachedAnalyticsModels[index].needsUpdate(lastUpdated)) { - _cachedAnalyticsModels.removeAt(index); - } else { - return _cachedAnalyticsModels[index].chartAnalyticsModel; - } - } - - return null; - } - - void cacheAnalytics({ - required ChartAnalyticsModel chartAnalyticsModel, - required AnalyticsSelected defaultSelected, - AnalyticsSelected? selected, - TimeSpan? timeSpan, - }) { - _cachedAnalyticsModels.add( - AnalyticsCacheModel( - timeSpan: timeSpan ?? currentAnalyticsTimeSpan, - chartAnalyticsModel: chartAnalyticsModel, - defaultSelected: defaultSelected, - selected: selected, - langCode: currentAnalyticsLang.langCode, - ), - ); - } - - List filterStudentAnalytics( - List unfiltered, - String? studentId, - ) { - final List filtered = - List.from(unfiltered); - filtered.removeWhere((e) => e.event.senderId != studentId); - return filtered; - } - - Future> filterRoomAnalytics( - List unfiltered, - String? roomID, - ) async { - List filtered = [...unfiltered]; - Room? room; - if (roomID != null) { - room = _pangeaController.matrixState.client.getRoomById(roomID); - if (room?.isSpace == true) { - return await filterSpaceAnalytics(unfiltered, roomID); - } - } - - filtered = filtered - .where( - (e) => (e.content).messages.any((u) => u.chatId == roomID), - ) - .toList(); - filtered.forEachIndexed( - (i, _) => (filtered[i].content).messages.removeWhere( - (u) => u.chatId != roomID, - ), - ); - return filtered; - } - - Future> filterPrivateChatAnalytics( - List unfiltered, - Room space, - ) async { - final List privateChatIds = space.allSpaceChildRoomIds; - final List lastFetched = await getLatestSpaceHierarchy(space.id); - for (final id in lastFetched) { - privateChatIds.removeWhere((e) => e == id); - } - - List filtered = - List.from(unfiltered); - filtered = filtered.where((e) { - return (e.content).messages.any( - (u) => privateChatIds.contains(u.chatId), - ); - }).toList(); - filtered.forEachIndexed( - (i, _) => (filtered[i].content).messages.removeWhere( - (u) => !privateChatIds.contains(u.chatId), - ), - ); - return filtered; - } - - Future> filterSpaceAnalytics( - List unfiltered, - String spaceId, + Future> allMyConstructs( + TimeSpan timeSpan, ) async { - final List chatIds = await getLatestSpaceHierarchy(spaceId); - List filtered = - List.from(unfiltered); - - filtered = filtered - .where( - (e) => e.content.messages.any((u) => chatIds.contains(u.chatId)), - ) - .toList(); - - filtered.forEachIndexed( - (i, _) => (filtered[i].content).messages.removeWhere( - (u) => !chatIds.contains(u.chatId), - ), - ); - return filtered; - } - - Future> filterAnalytics({ - required List unfilteredAnalytics, - required AnalyticsSelected defaultSelected, - Room? space, - AnalyticsSelected? selected, - }) async { - for (int i = 0; i < unfilteredAnalytics.length; i++) { - unfilteredAnalytics[i].content.messages.removeWhere( - (record) => record.time.isBefore( - currentAnalyticsTimeSpan.cutOffDate, - ), - ); - } - - switch (selected?.type) { - case null: - return unfilteredAnalytics; - case AnalyticsEntryType.student: - if (defaultSelected.type != AnalyticsEntryType.space) { - throw Exception( - "student filtering not available for default filter ${defaultSelected.type}", - ); - } - return filterStudentAnalytics(unfilteredAnalytics, selected?.id); - case AnalyticsEntryType.room: - return filterRoomAnalytics(unfilteredAnalytics, selected?.id); - case AnalyticsEntryType.privateChats: - if (defaultSelected.type == AnalyticsEntryType.student) { - throw "private chat filtering not available for my analytics"; - } - if (space == null) { - throw "space is null in filterAnalytics with selected type privateChats"; - } - return await filterPrivateChatAnalytics( - unfilteredAnalytics, - space, - ); - case AnalyticsEntryType.space: - return await filterSpaceAnalytics(unfilteredAnalytics, selected!.id); - default: - throw Exception("invalid filter type - ${selected?.type}"); - } - } - - Future getAnalytics({ - required AnalyticsSelected defaultSelected, - AnalyticsSelected? selected, - bool forceUpdate = false, - }) async { - try { - await _pangeaController.matrixState.client.roomsLoading; - - // if the user is looking at space analytics, then fetch the space - Room? space; - if (defaultSelected.type == AnalyticsEntryType.space) { - space = _pangeaController.matrixState.client.getRoomById( - defaultSelected.id, - ); - if (space == null) { - ErrorHandler.logError( - m: "space not found in getAnalytics", - data: { - "defaultSelected": defaultSelected, - "selected": selected, - }, - ); - return ChartAnalyticsModel( - msgs: [], - timeSpan: currentAnalyticsTimeSpan, - ); - } - } - - DateTime? lastUpdated; - if (defaultSelected.type != AnalyticsEntryType.space) { - // if default selected view is my analytics, check for the last - // time the logged in user updated their analytics events - // this gets passed to getAnalyticsLocal to determine if the cached - // entry is out-of-date - lastUpdated = await myAnalyticsLastUpdated( - PangeaEventTypes.summaryAnalytics, - ); - } else { - // else, get the last time a student in the space updated their analytics - lastUpdated = await spaceAnalyticsLastUpdated( - PangeaEventTypes.summaryAnalytics, - space!, - ); - } - - final ChartAnalyticsModel? local = getAnalyticsLocal( - defaultSelected: defaultSelected, - selected: selected, - forceUpdate: forceUpdate, - lastUpdated: lastUpdated, - ); - if (local != null && !forceUpdate) { - debugPrint("returning local analytics"); - return local; - } - debugPrint("fetching new analytics"); - - // get all the relevant summary analytics events for the current timespan - final List summaryEvents = - defaultSelected.type == AnalyticsEntryType.space - ? await spaceMemberAnalytics(space!) - : await mySummaryAnalytics(); - - // filter out the analytics events based on filters the user has chosen - final List filteredAnalytics = - await filterAnalytics( - unfilteredAnalytics: summaryEvents, - defaultSelected: defaultSelected, - space: space, - selected: selected, - ); - - // then create and return the model to be displayed - final ChartAnalyticsModel newModel = ChartAnalyticsModel( - timeSpan: currentAnalyticsTimeSpan, - msgs: filteredAnalytics - .map((event) => event.content.messages) - .expand((msgs) => msgs) - .toList(), - ); - - cacheAnalytics( - chartAnalyticsModel: newModel, - defaultSelected: defaultSelected, - selected: selected, - timeSpan: currentAnalyticsTimeSpan, - ); - - return newModel; - } catch (err, s) { - debugger(when: kDebugMode); - ErrorHandler.logError(e: err, s: s); - return ChartAnalyticsModel( - msgs: [], - timeSpan: currentAnalyticsTimeSpan, - ); - } - } - - //////////////////////////// CONSTRUCTS //////////////////////////// - - Future> allMyConstructs() async { - final Room? analyticsRoom = _pangeaController.matrixState.client - .analyticsRoomLocal(currentAnalyticsLang.langCode); + final Room? analyticsRoom = + _pangeaController.matrixState.client.analyticsRoomLocal(langCode); if (analyticsRoom == null) return []; final List? roomEvents = (await analyticsRoom.getAnalyticsEvents( - type: PangeaEventTypes.construct, - since: currentAnalyticsTimeSpan.cutOffDate, + since: timeSpan.cutOffDate, userId: _pangeaController.matrixState.client.userID!, )) ?.cast(); @@ -541,17 +171,17 @@ class AnalyticsController extends BaseController { Future> allSpaceMemberConstructs( Room space, + TimeSpan timeSpan, ) async { await space.requestParticipants(); final List constructEvents = []; for (final student in space.students) { final Room? analyticsRoom = _pangeaController.matrixState.client - .analyticsRoomLocal(currentAnalyticsLang.langCode, student.id); + .analyticsRoomLocal(langCode, student.id); if (analyticsRoom != null) { final List? roomEvents = (await analyticsRoom.getAnalyticsEvents( - type: PangeaEventTypes.construct, - since: currentAnalyticsTimeSpan.cutOffDate, + since: timeSpan.cutOffDate, userId: student.id, )) ?.cast(); @@ -600,8 +230,9 @@ class AnalyticsController extends BaseController { Room space, ) async { final List privateChatIds = space.allSpaceChildRoomIds; - final List lastFetched = await getLatestSpaceHierarchy(space.id); - for (final id in lastFetched) { + final resp = await space.client.getSpaceHierarchy(space.id); + final List chatIds = resp.rooms.map((room) => room.roomId).toList(); + for (final id in chatIds) { privateChatIds.removeWhere((e) => e == id); } final List filtered = @@ -618,7 +249,8 @@ class AnalyticsController extends BaseController { List unfilteredConstructs, Room space, ) async { - final List chatIds = await getLatestSpaceHierarchy(space.id); + final resp = await space.client.getSpaceHierarchy(space.id); + final List chatIds = resp.rooms.map((room) => room.roomId).toList(); final List filtered = List.from(unfilteredConstructs); @@ -633,10 +265,10 @@ class AnalyticsController extends BaseController { List? getConstructsLocal({ required TimeSpan timeSpan, - required ConstructTypeEnum constructType, required AnalyticsSelected defaultSelected, AnalyticsSelected? selected, DateTime? lastUpdated, + ConstructTypeEnum? constructType, }) { final index = _cachedConstructs.indexWhere( (e) => @@ -646,7 +278,7 @@ class AnalyticsController extends BaseController { e.defaultSelected.type == defaultSelected.type && e.selected?.id == selected?.id && e.selected?.type == selected?.type && - e.langCode == currentAnalyticsLang.langCode, + e.langCode == langCode, ); if (index > -1) { @@ -661,29 +293,31 @@ class AnalyticsController extends BaseController { } void cacheConstructs({ - required ConstructTypeEnum constructType, required List events, required AnalyticsSelected defaultSelected, + required TimeSpan timeSpan, AnalyticsSelected? selected, + ConstructTypeEnum? constructType, }) { final entry = ConstructCacheEntry( - timeSpan: currentAnalyticsTimeSpan, + timeSpan: timeSpan, type: constructType, events: List.from(events), defaultSelected: defaultSelected, selected: selected, - langCode: currentAnalyticsLang.langCode, + langCode: langCode, ); _cachedConstructs.add(entry); } Future> getMyConstructs({ required AnalyticsSelected defaultSelected, - required ConstructTypeEnum constructType, + required TimeSpan timeSpan, + ConstructTypeEnum? constructType, AnalyticsSelected? selected, }) async { final List unfilteredConstructs = - await allMyConstructs(); + await allMyConstructs(timeSpan); final Room? space = selected?.type == AnalyticsEntryType.space ? _pangeaController.matrixState.client.getRoomById(selected!.id) @@ -694,18 +328,21 @@ class AnalyticsController extends BaseController { space: space, defaultSelected: defaultSelected, selected: selected, + timeSpan: timeSpan, ); } Future> getSpaceConstructs({ - required ConstructTypeEnum constructType, required Room space, required AnalyticsSelected defaultSelected, + required TimeSpan timeSpan, AnalyticsSelected? selected, + ConstructTypeEnum? constructType, }) async { final List unfilteredConstructs = await allSpaceMemberConstructs( space, + timeSpan, ); return filterConstructs( @@ -713,12 +350,14 @@ class AnalyticsController extends BaseController { space: space, defaultSelected: defaultSelected, selected: selected, + timeSpan: timeSpan, ); } Future> filterConstructs({ required List unfilteredConstructs, required AnalyticsSelected defaultSelected, + required TimeSpan timeSpan, Room? space, AnalyticsSelected? selected, }) async { @@ -730,7 +369,7 @@ class AnalyticsController extends BaseController { for (int i = 0; i < unfilteredConstructs.length; i++) { final construct = unfilteredConstructs[i]; construct.content.uses.removeWhere( - (use) => use.timeStamp.isBefore(currentAnalyticsTimeSpan.cutOffDate), + (use) => use.timeStamp.isBefore(timeSpan.cutOffDate), ); } @@ -760,11 +399,12 @@ class AnalyticsController extends BaseController { } Future?> getConstructs({ - required ConstructTypeEnum constructType, required AnalyticsSelected defaultSelected, + required TimeSpan timeSpan, AnalyticsSelected? selected, bool removeIT = true, bool forceUpdate = false, + ConstructTypeEnum? constructType, }) async { debugPrint("getting constructs"); await _pangeaController.matrixState.client.roomsLoading; @@ -792,19 +432,16 @@ class AnalyticsController extends BaseController { // time the logged in user updated their analytics events // this gets passed to getAnalyticsLocal to determine if the cached // entry is out-of-date - lastUpdated = await myAnalyticsLastUpdated( - PangeaEventTypes.construct, - ); + lastUpdated = await myAnalyticsLastUpdated(); } else { // else, get the last time a student in the space updated their analytics lastUpdated = await spaceAnalyticsLastUpdated( - PangeaEventTypes.construct, space!, ); } final List? local = getConstructsLocal( - timeSpan: currentAnalyticsTimeSpan, + timeSpan: timeSpan, constructType: constructType, defaultSelected: defaultSelected, selected: selected, @@ -821,12 +458,14 @@ class AnalyticsController extends BaseController { constructType: constructType, defaultSelected: defaultSelected, selected: selected, + timeSpan: timeSpan, ) : await getSpaceConstructs( constructType: constructType, space: space, defaultSelected: defaultSelected, selected: selected, + timeSpan: timeSpan, ); if (removeIT) { @@ -846,6 +485,7 @@ class AnalyticsController extends BaseController { events: filteredConstructs, defaultSelected: defaultSelected, selected: selected, + timeSpan: timeSpan, ); } @@ -889,32 +529,15 @@ abstract class CacheEntry { } class ConstructCacheEntry extends CacheEntry { - final ConstructTypeEnum type; + final ConstructTypeEnum? type; final List events; ConstructCacheEntry({ - required this.type, required this.events, required super.timeSpan, required super.langCode, required super.defaultSelected, + this.type, super.selected, }); } - -class AnalyticsCacheModel extends CacheEntry { - final ChartAnalyticsModel chartAnalyticsModel; - - AnalyticsCacheModel({ - required this.chartAnalyticsModel, - required super.timeSpan, - required super.langCode, - required super.defaultSelected, - super.selected, - }); - - @override - bool get isExpired => - DateTime.now().difference(_createdAt).inMinutes > - ClassDefaultValues.minutesDelayToMakeNewChartAnalytics; -} diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart index 12baeb689e..a13397e86a 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/my_analytics_controller.dart @@ -4,18 +4,16 @@ import 'dart:developer'; import 'package:fluffychat/pangea/constants/local.key.dart'; import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; +import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart'; +import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/practice_activity_record_event.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; -import 'package:fluffychat/pangea/models/analytics/summary_analytics_model.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:flutter/foundation.dart'; import 'package:matrix/matrix.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; -import '../extensions/client_extension/client_extension.dart'; -import '../extensions/pangea_room_extension/pangea_room_extension.dart'; - /// handles the processing of analytics for /// 1) messages sent by the user and /// 2) constructs used by the user, both in sending messages and doing practice activities @@ -56,15 +54,14 @@ class MyAnalyticsController { /// If analytics haven't been updated in the last day, update them Future _refreshAnalyticsIfOutdated() async { - DateTime? lastUpdated = await _pangeaController.analytics - .myAnalyticsLastUpdated(PangeaEventTypes.summaryAnalytics); + DateTime? lastUpdated = + await _pangeaController.analytics.myAnalyticsLastUpdated(); final DateTime yesterday = DateTime.now().subtract(_timeSinceUpdate); if (lastUpdated?.isBefore(yesterday) ?? true) { debugPrint("analytics out-of-date, updating"); await updateAnalytics(); - lastUpdated = await _pangeaController.analytics - .myAnalyticsLastUpdated(PangeaEventTypes.summaryAnalytics); + lastUpdated = await _pangeaController.analytics.myAnalyticsLastUpdated(); } return lastUpdated; } @@ -238,7 +235,6 @@ class MyAnalyticsController { // get the last time analytics were updated for this room final DateTime? l2AnalyticsLastUpdated = await analyticsRoom.analyticsLastUpdated( - PangeaEventTypes.summaryAnalytics, _client.userID!, ); @@ -302,16 +298,6 @@ class MyAnalyticsController { final List allRecentMessages = recentPangeaMessageEvents.expand((e) => e).toList(); - final List summaryContent = - SummaryAnalyticsModel.formatSummaryContent(allRecentMessages); - // if there's new content to be sent, or if lastUpdated hasn't been - // set yet for this room, send the analytics events - if (summaryContent.isNotEmpty || l2AnalyticsLastUpdated == null) { - await analyticsRoom.sendSummaryAnalyticsEvent( - summaryContent, - ); - } - // get constructs for messages final List recentConstructUses = []; for (final PangeaMessageEvent message in allRecentMessages) { diff --git a/lib/pangea/enum/progress_indicators_enum.dart b/lib/pangea/enum/progress_indicators_enum.dart new file mode 100644 index 0000000000..39032433e8 --- /dev/null +++ b/lib/pangea/enum/progress_indicators_enum.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +enum ProgressIndicatorEnum { + level, + wordsUsed, + errorTypes, +} + +extension ProgressIndicatorsExtension on ProgressIndicatorEnum { + IconData get icon { + switch (this) { + case ProgressIndicatorEnum.wordsUsed: + return Icons.text_fields_outlined; + case ProgressIndicatorEnum.errorTypes: + return Icons.error_outline; + case ProgressIndicatorEnum.level: + return Icons.star; + } + } + + static bool isDarkMode(BuildContext context) => + Theme.of(context).brightness == Brightness.dark; + + Color color(BuildContext context) { + switch (this) { + case ProgressIndicatorEnum.wordsUsed: + return Theme.of(context).brightness == Brightness.dark + ? const Color.fromARGB(255, 169, 183, 237) + : const Color.fromARGB(255, 38, 59, 141); + case ProgressIndicatorEnum.errorTypes: + return Theme.of(context).brightness == Brightness.dark + ? const Color.fromARGB(255, 212, 144, 216) + : const Color.fromARGB(255, 163, 39, 169); + case ProgressIndicatorEnum.level: + return Theme.of(context).brightness == Brightness.dark + ? const Color.fromARGB(255, 250, 220, 129) + : const Color.fromARGB(255, 255, 208, 67); + default: + return Theme.of(context).textTheme.bodyLarge!.color ?? Colors.blueGrey; + } + } + + String tooltip(BuildContext context) { + switch (this) { + case ProgressIndicatorEnum.wordsUsed: + return L10n.of(context)!.wordsUsed; + case ProgressIndicatorEnum.errorTypes: + return L10n.of(context)!.errorTypes; + case ProgressIndicatorEnum.level: + return L10n.of(context)!.level; + } + } +} diff --git a/lib/pangea/enum/time_span.dart b/lib/pangea/enum/time_span.dart index ddc9ce32b1..4aaca7527c 100644 --- a/lib/pangea/enum/time_span.dart +++ b/lib/pangea/enum/time_span.dart @@ -1,9 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import '../models/analytics/chart_analytics_model.dart'; - -enum TimeSpan { day, week, month, sixmonths, year } +enum TimeSpan { day, week, month, sixmonths, year, forever } extension TimeSpanFunctions on TimeSpan { String string(BuildContext context) { @@ -35,19 +33,8 @@ extension TimeSpanFunctions on TimeSpan { return 6; case TimeSpan.year: return 12; - } - } - - Duration timeAgo(int index) { - switch (this) { - case TimeSpan.day: - return Duration(hours: index); - case TimeSpan.week: - case TimeSpan.month: - return Duration(days: index); - case TimeSpan.year: - case TimeSpan.sixmonths: - return Duration(days: index * 32); + case TimeSpan.forever: + return 0; } } @@ -65,44 +52,8 @@ extension TimeSpanFunctions on TimeSpan { return DateTime.now().subtract(Duration(days: numberOfIntervals * 30)); case TimeSpan.year: return DateTime.now().subtract(const Duration(days: 365)); + case TimeSpan.forever: + return DateTime(2020); } } - - String getMapKey(DateTime date) { - switch (this) { - case TimeSpan.day: - return date.hour.toString(); - case TimeSpan.week: - return date.weekday.toString(); - case TimeSpan.month: - return date.day.toString(); - case TimeSpan.sixmonths: - case TimeSpan.year: - return date.month.toString(); - } - } - - /// Note: end is same as start!! - Map get emptyIntervals { - final DateTime now = DateTime.now(); - final List numbers = - List.generate(numberOfIntervals, (index) => index); - final Map map = {}; - - // debugger(when: kDebugMode); - for (final index in numbers) { - final timeAgos = timeAgo(index); - final DateTime end = now.subtract(timeAgos); - // debugger(when: end.isBefore(now.subtract(const Duration(days: 30)))); - final String mapKey = getMapKey(end); - // debugger(when: mapKey.toString() == "5"); - map[mapKey] = TimeSeriesInterval( - start: end, - end: end, - totals: TimeSeriesTotals.empty, - ); - } - // debugger(when: kDebugMode); - return map; - } } diff --git a/lib/pangea/extensions/client_extension/client_analytics_extension.dart b/lib/pangea/extensions/client_extension/client_analytics_extension.dart index 396e09f8da..2d2e19bc3f 100644 --- a/lib/pangea/extensions/client_extension/client_analytics_extension.dart +++ b/lib/pangea/extensions/client_extension/client_analytics_extension.dart @@ -152,7 +152,6 @@ extension AnalyticsClientExtension on Client { final Map lastUpdatedMap = {}; for (final analyticsRoom in allMyAnalyticsRooms) { final DateTime? lastUpdated = await analyticsRoom.analyticsLastUpdated( - PangeaEventTypes.summaryAnalytics, userID!, ); lastUpdatedMap[analyticsRoom.id] = lastUpdated; diff --git a/lib/pangea/extensions/pangea_room_extension/events_extension.dart b/lib/pangea/extensions/pangea_room_extension/events_extension.dart index bc820998c3..3fe14750d6 100644 --- a/lib/pangea/extensions/pangea_room_extension/events_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/events_extension.dart @@ -282,83 +282,6 @@ extension EventsRoomExtension on Room { ); } - Future> get _messageListForAllChildChats async { - try { - if (!isSpace) return []; - final List spaceChats = spaceChildren - .where((e) => e.roomId != null) - .map((e) => client.getRoomById(e.roomId!)) - .where((element) => element != null) - .cast() - .where((element) => !element.isSpace) - .toList(); - - final List>> msgListFutures = []; - for (final chat in spaceChats) { - msgListFutures.add(chat._messageListForChat); - } - final List> msgLists = - await Future.wait(msgListFutures); - - final List joined = []; - for (final msgList in msgLists) { - joined.addAll(msgList); - } - return joined; - } catch (err) { - // debugger(when: kDebugMode); - rethrow; - } - } - - Future> get _messageListForChat async { - try { - int numberOfSearches = 0; - - if (isSpace) { - throw Exception( - "In messageListForChat with room that is not a chat", - ); - } - final Timeline timeline = await getTimeline(); - - while (timeline.canRequestHistory && numberOfSearches < 50) { - await timeline.requestHistory(historyCount: 100); - numberOfSearches += 1; - } - if (timeline.canRequestHistory) { - debugger(when: kDebugMode); - } - - final List msgs = []; - for (final event in timeline.events) { - if (event.senderId == client.userID && - event.type == EventTypes.Message && - event.content['msgtype'] == MessageTypes.Text) { - final PangeaMessageEvent pMsgEvent = PangeaMessageEvent( - event: event, - timeline: timeline, - ownMessage: true, - ); - msgs.add( - RecentMessageRecord( - eventId: event.eventId, - chatId: id, - useType: pMsgEvent.msgUseType, - time: event.originServerTs, - ), - ); - } - } - return msgs; - } catch (err, s) { - if (kDebugMode) rethrow; - debugger(when: kDebugMode); - ErrorHandler.logError(e: err, s: s); - return []; - } - } - // ConstructEvent? _vocabEventLocal(String lemma) { // if (!isAnalyticsRoom) throw Exception("not an analytics room"); @@ -451,14 +374,11 @@ extension EventsRoomExtension on Room { return false; } - while (timeline.canRequestHistory && - !reachedEnd() && - numberOfSearches < 10) { + while (timeline.canRequestHistory && numberOfSearches < 10) { await timeline.requestHistory(historyCount: 100); numberOfSearches += 1; - if (reachedEnd()) { - break; - } + if (!timeline.canRequestHistory) break; + if (reachedEnd()) break; } final List fetchedEvents = timeline.events diff --git a/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart b/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart index c22ba9a235..8bfb09c8a4 100644 --- a/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart @@ -9,12 +9,8 @@ import 'package:fluffychat/pangea/constants/language_constants.dart'; import 'package:fluffychat/pangea/constants/model_keys.dart'; import 'package:fluffychat/pangea/constants/pangea_room_types.dart'; import 'package:fluffychat/pangea/controllers/language_list_controller.dart'; -import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; -import 'package:fluffychat/pangea/models/analytics/analytics_event.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_event.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; -import 'package:fluffychat/pangea/models/analytics/summary_analytics_event.dart'; -import 'package:fluffychat/pangea/models/analytics/summary_analytics_model.dart'; import 'package:fluffychat/pangea/models/bot_options_model.dart'; import 'package:fluffychat/pangea/models/language_model.dart'; import 'package:fluffychat/pangea/models/space_model.dart'; @@ -80,22 +76,15 @@ extension PangeaRoom on Room { void inviteSpaceTeachersToAnalyticsRooms() => _inviteSpaceTeachersToAnalyticsRooms(); - Future getLastAnalyticsEvent( - String type, - String userId, - ) async => - await _getLastAnalyticsEvent(type, userId); - - Future analyticsLastUpdated(String type, String userId) async { - return await _analyticsLastUpdated(type, userId); + Future analyticsLastUpdated(String userId) async { + return await _analyticsLastUpdated(userId); } - Future?> getAnalyticsEvents({ - required String type, + Future?> getAnalyticsEvents({ required String userId, DateTime? since, }) async => - await _getAnalyticsEvents(type: type, since: since, userId: userId); + await _getAnalyticsEvents(since: since, userId: userId); String? get madeForLang => _madeForLang; diff --git a/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart b/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart index 7bdd137433..dabbd65bf5 100644 --- a/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart @@ -140,52 +140,36 @@ extension AnalyticsRoomExtension on Room { ); } - Future _getLastAnalyticsEvent( - String type, + Future _getLastAnalyticsEvent( String userId, ) async { final List events = await getEventsBySender( - type: type, + type: PangeaEventTypes.construct, sender: userId, count: 10, ); if (events.isEmpty) return null; final Event event = events.first; - AnalyticsEvent? analyticsEvent; - switch (type) { - case PangeaEventTypes.summaryAnalytics: - analyticsEvent = SummaryAnalyticsEvent(event: event); - case PangeaEventTypes.construct: - analyticsEvent = ConstructAnalyticsEvent(event: event); - } - return analyticsEvent; + return ConstructAnalyticsEvent(event: event); } - Future _analyticsLastUpdated(String type, String userId) async { - final lastEvent = await _getLastAnalyticsEvent(type, userId); + Future _analyticsLastUpdated(String userId) async { + final lastEvent = await _getLastAnalyticsEvent(userId); return lastEvent?.event.originServerTs; } - Future?> _getAnalyticsEvents({ - required String type, + Future?> _getAnalyticsEvents({ required String userId, DateTime? since, }) async { final List events = await getEventsBySender( - type: type, + type: PangeaEventTypes.construct, sender: userId, since: since, ); - final List analyticsEvents = []; + final List analyticsEvents = []; for (final Event event in events) { - switch (type) { - case PangeaEventTypes.summaryAnalytics: - analyticsEvents.add(SummaryAnalyticsEvent(event: event)); - break; - case PangeaEventTypes.construct: - analyticsEvents.add(ConstructAnalyticsEvent(event: event)); - break; - } + analyticsEvents.add(ConstructAnalyticsEvent(event: event)); } return analyticsEvents; @@ -203,18 +187,6 @@ extension AnalyticsRoomExtension on Room { creationContent?.tryGet(ModelKey.oldLangCode) == langCode; } - Future sendSummaryAnalyticsEvent( - List records, - ) async { - final SummaryAnalyticsModel analyticsModel = SummaryAnalyticsModel( - messages: records, - ); - await sendEvent( - analyticsModel.toJson(), - type: PangeaEventTypes.summaryAnalytics, - ); - } - /// Sends construct events to the server. /// /// The [uses] parameter is a list of [OneConstructUse] objects representing the diff --git a/lib/pangea/models/analytics/analytics_event.dart b/lib/pangea/models/analytics/analytics_event.dart deleted file mode 100644 index 7010d3591b..0000000000 --- a/lib/pangea/models/analytics/analytics_event.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; -import 'package:fluffychat/pangea/models/analytics/analytics_model.dart'; -import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; -import 'package:fluffychat/pangea/models/analytics/summary_analytics_model.dart'; -import 'package:matrix/matrix.dart'; - -// superclass for all analytics events -abstract class AnalyticsEvent { - late Event _event; - AnalyticsModel? contentCache; - - AnalyticsEvent({required Event event}) { - _event = event; - } - - Event get event => _event; - - AnalyticsModel get content { - switch (_event.type) { - case PangeaEventTypes.summaryAnalytics: - contentCache ??= SummaryAnalyticsModel.fromJson(event.content); - break; - case PangeaEventTypes.construct: - contentCache ??= ConstructAnalyticsModel.fromJson(event.content); - break; - } - return contentCache!; - } -} diff --git a/lib/pangea/models/analytics/analytics_model.dart b/lib/pangea/models/analytics/analytics_model.dart deleted file mode 100644 index d8732ad977..0000000000 --- a/lib/pangea/models/analytics/analytics_model.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; -import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; -import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; -import 'package:fluffychat/pangea/models/analytics/summary_analytics_model.dart'; - -abstract class AnalyticsModel { - static List formatAnalyticsContent( - List recentMsgs, - String type, - ) { - switch (type) { - case PangeaEventTypes.summaryAnalytics: - return SummaryAnalyticsModel.formatSummaryContent(recentMsgs); - case PangeaEventTypes.construct: - final List uses = []; - for (final msg in recentMsgs) { - uses.addAll(msg.allConstructUses); - } - return uses; - } - return []; - } -} diff --git a/lib/pangea/models/analytics/chart_analytics_model.dart b/lib/pangea/models/analytics/chart_analytics_model.dart index 7430ede2f2..4a9f278be1 100644 --- a/lib/pangea/models/analytics/chart_analytics_model.dart +++ b/lib/pangea/models/analytics/chart_analytics_model.dart @@ -1,149 +1,149 @@ -import 'dart:developer'; - -import 'package:fluffychat/pangea/enum/time_span.dart'; -import 'package:fluffychat/pangea/models/analytics/summary_analytics_model.dart'; -import 'package:flutter/foundation.dart'; - -import '../../enum/use_type.dart'; - -class TimeSeriesTotals { - int ta; - int ga; - int wa; - int un; - - int get all => ta + ga + wa + un; - - TimeSeriesTotals({ - required this.ta, - required this.ga, - required this.wa, - required this.un, - }); - - Map toJson() => { - UseType.ta.string: ta, - UseType.ga.string: ga, - UseType.wa.string: wa, - UseType.un.string: un, - }; - - factory TimeSeriesTotals.fromJson(json) => TimeSeriesTotals( - ta: json[UseType.ta.string], - ga: json[UseType.ga.string], - wa: json[UseType.wa.string], - un: json[UseType.un.string], - ); - - static get empty => TimeSeriesTotals(ta: 0, ga: 0, wa: 0, un: 0); - - int get taPercent => all != 0 ? (ta / all * 100).round() : 0; - int get gaPercent => all != 0 ? (ga / all * 100).round() : 0; - int get waPercent => all != 0 ? (wa / all * 100).round() : 0; - int get unPercent => all != 0 ? (un / all * 100).round() : 0; - - void increment(RecentMessageRecord msg) { - switch (msg.useType) { - case UseType.ta: - ta += 1; - break; - case UseType.wa: - wa += 1; - break; - case UseType.ga: - ga += 1; - break; - case UseType.un: - un += 1; - break; - default: - debugger(when: kDebugMode); - debugPrint("message with bad type ${msg.toJson()}"); - } - } -} - -class TimeSeriesInterval { - DateTime start; - DateTime end; - TimeSeriesTotals totals; - - TimeSeriesInterval({ - required this.start, - required this.end, - required this.totals, - }); - - Map toJson() => { - "strt": start.toIso8601String(), - "end": end.toIso8601String(), - "totals": totals.toJson(), - }; - - factory TimeSeriesInterval.fromJson(json) => TimeSeriesInterval( - start: DateTime.parse(json["strt"]), - end: DateTime.parse(json["end"]), - totals: TimeSeriesTotals.fromJson(json["totals"]), - ); -} - -class ChartAnalyticsModel { - final TimeSpan timeSpan; - final TimeSeriesTotals totals = TimeSeriesTotals.empty; - final List msgs; - final String? chatId; - - late DateTime fetchedAt; - late List timeSeries; - DateTime? lastMessage; - - ChartAnalyticsModel({ - required this.timeSpan, - required this.msgs, - this.chatId, - }) { - fetchedAt = DateTime.now(); - calculate(); - } - - bool get isEmpty => (totals.ga + totals.ta + totals.wa == 0); - - void calculate() { - final Map intervals = timeSpan.emptyIntervals; - final DateTime cutOff = timeSpan.cutOffDate; - - final filtered = msgs.where( - (msg) => - (chatId == null || msg.chatId == chatId) && msg.time.isAfter(cutOff), - ); - - //remove msgs with duplicate ids - final Map unique = {}; - for (final msg in filtered) { - if (unique[msg.eventId] == null) { - unique[msg.eventId] = msg; - } - } - - for (final msg in unique.values) { - final String key = timeSpan.getMapKey(msg.time); - if (intervals[key] == null) { - debugger(when: kDebugMode); - } else { - intervals[key]!.totals.increment(msg); - totals.increment(msg); - lastMessage = msg.time; - } - } - timeSeries = intervals.values.toList().reversed.toList(); - } - - DateTime? get lastMessageTime { - if (msgs.isEmpty) { - return null; - } - return msgs.map((msg) => msg.time).reduce( - (compare, recent) => compare.isAfter(recent) ? compare : recent, - ); - } -} +// import 'dart:developer'; + +// import 'package:fluffychat/pangea/enum/time_span.dart'; +// import 'package:fluffychat/pangea/models/analytics/summary_analytics_model.dart'; +// import 'package:flutter/foundation.dart'; + +// import '../../enum/use_type.dart'; + +// class TimeSeriesTotals { +// int ta; +// int ga; +// int wa; +// int un; + +// int get all => ta + ga + wa + un; + +// TimeSeriesTotals({ +// required this.ta, +// required this.ga, +// required this.wa, +// required this.un, +// }); + +// Map toJson() => { +// UseType.ta.string: ta, +// UseType.ga.string: ga, +// UseType.wa.string: wa, +// UseType.un.string: un, +// }; + +// factory TimeSeriesTotals.fromJson(json) => TimeSeriesTotals( +// ta: json[UseType.ta.string], +// ga: json[UseType.ga.string], +// wa: json[UseType.wa.string], +// un: json[UseType.un.string], +// ); + +// static get empty => TimeSeriesTotals(ta: 0, ga: 0, wa: 0, un: 0); + +// int get taPercent => all != 0 ? (ta / all * 100).round() : 0; +// int get gaPercent => all != 0 ? (ga / all * 100).round() : 0; +// int get waPercent => all != 0 ? (wa / all * 100).round() : 0; +// int get unPercent => all != 0 ? (un / all * 100).round() : 0; + +// void increment(RecentMessageRecord msg) { +// switch (msg.useType) { +// case UseType.ta: +// ta += 1; +// break; +// case UseType.wa: +// wa += 1; +// break; +// case UseType.ga: +// ga += 1; +// break; +// case UseType.un: +// un += 1; +// break; +// default: +// debugger(when: kDebugMode); +// debugPrint("message with bad type ${msg.toJson()}"); +// } +// } +// } + +// class TimeSeriesInterval { +// DateTime start; +// DateTime end; +// TimeSeriesTotals totals; + +// TimeSeriesInterval({ +// required this.start, +// required this.end, +// required this.totals, +// }); + +// Map toJson() => { +// "strt": start.toIso8601String(), +// "end": end.toIso8601String(), +// "totals": totals.toJson(), +// }; + +// factory TimeSeriesInterval.fromJson(json) => TimeSeriesInterval( +// start: DateTime.parse(json["strt"]), +// end: DateTime.parse(json["end"]), +// totals: TimeSeriesTotals.fromJson(json["totals"]), +// ); +// } + +// class ChartAnalyticsModel { +// final TimeSpan timeSpan; +// final TimeSeriesTotals totals = TimeSeriesTotals.empty; +// final List msgs; +// final String? chatId; + +// late DateTime fetchedAt; +// late List timeSeries; +// DateTime? lastMessage; + +// ChartAnalyticsModel({ +// required this.timeSpan, +// required this.msgs, +// this.chatId, +// }) { +// fetchedAt = DateTime.now(); +// calculate(); +// } + +// bool get isEmpty => (totals.ga + totals.ta + totals.wa == 0); + +// void calculate() { +// final Map intervals = timeSpan.emptyIntervals; +// final DateTime cutOff = timeSpan.cutOffDate; + +// final filtered = msgs.where( +// (msg) => +// (chatId == null || msg.chatId == chatId) && msg.time.isAfter(cutOff), +// ); + +// //remove msgs with duplicate ids +// final Map unique = {}; +// for (final msg in filtered) { +// if (unique[msg.eventId] == null) { +// unique[msg.eventId] = msg; +// } +// } + +// for (final msg in unique.values) { +// final String key = timeSpan.getMapKey(msg.time); +// if (intervals[key] == null) { +// debugger(when: kDebugMode); +// } else { +// intervals[key]!.totals.increment(msg); +// totals.increment(msg); +// lastMessage = msg.time; +// } +// } +// timeSeries = intervals.values.toList().reversed.toList(); +// } + +// DateTime? get lastMessageTime { +// if (msgs.isEmpty) { +// return null; +// } +// return msgs.map((msg) => msg.time).reduce( +// (compare, recent) => compare.isAfter(recent) ? compare : recent, +// ); +// } +// } diff --git a/lib/pangea/models/analytics/constructs_event.dart b/lib/pangea/models/analytics/constructs_event.dart index 481051b1c3..89336bbbc1 100644 --- a/lib/pangea/models/analytics/constructs_event.dart +++ b/lib/pangea/models/analytics/constructs_event.dart @@ -1,11 +1,13 @@ -import 'package:fluffychat/pangea/models/analytics/analytics_event.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; import 'package:matrix/matrix.dart'; import '../../constants/pangea_event_types.dart'; -class ConstructAnalyticsEvent extends AnalyticsEvent { - ConstructAnalyticsEvent({required Event event}) : super(event: event) { +class ConstructAnalyticsEvent { + late Event _event; + ConstructAnalyticsModel? contentCache; + ConstructAnalyticsEvent({required Event event}) { + _event = event; if (event.type != PangeaEventTypes.construct) { throw Exception( "${event.type} should not be used to make a ConstructAnalyticsEvent", @@ -13,7 +15,8 @@ class ConstructAnalyticsEvent extends AnalyticsEvent { } } - @override + Event get event => _event; + ConstructAnalyticsModel get content { contentCache ??= ConstructAnalyticsModel.fromJson(event.content); return contentCache as ConstructAnalyticsModel; diff --git a/lib/pangea/models/analytics/constructs_model.dart b/lib/pangea/models/analytics/constructs_model.dart index 54e81789fc..ff6b55aefb 100644 --- a/lib/pangea/models/analytics/constructs_model.dart +++ b/lib/pangea/models/analytics/constructs_model.dart @@ -1,14 +1,13 @@ import 'dart:developer'; import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart'; -import 'package:fluffychat/pangea/models/analytics/analytics_model.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:flutter/foundation.dart'; import 'package:matrix/matrix.dart'; import '../../enum/construct_type_enum.dart'; -class ConstructAnalyticsModel extends AnalyticsModel { +class ConstructAnalyticsModel { List uses; ConstructAnalyticsModel({ diff --git a/lib/pangea/models/analytics/summary_analytics_event.dart b/lib/pangea/models/analytics/summary_analytics_event.dart deleted file mode 100644 index a764d5597a..0000000000 --- a/lib/pangea/models/analytics/summary_analytics_event.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:fluffychat/pangea/models/analytics/analytics_event.dart'; -import 'package:fluffychat/pangea/models/analytics/summary_analytics_model.dart'; -import 'package:matrix/matrix.dart'; - -import '../../constants/pangea_event_types.dart'; - -class SummaryAnalyticsEvent extends AnalyticsEvent { - SummaryAnalyticsEvent({required Event event}) : super(event: event) { - if (event.type != PangeaEventTypes.summaryAnalytics) { - throw Exception( - "${event.type} should not be used to make a SummaryAnalyticsEvent", - ); - } - } - - @override - SummaryAnalyticsModel get content { - contentCache ??= SummaryAnalyticsModel.fromJson(event.content); - return contentCache as SummaryAnalyticsModel; - } -} diff --git a/lib/pangea/models/analytics/summary_analytics_model.dart b/lib/pangea/models/analytics/summary_analytics_model.dart deleted file mode 100644 index 0b8e4b27ce..0000000000 --- a/lib/pangea/models/analytics/summary_analytics_model.dart +++ /dev/null @@ -1,111 +0,0 @@ -import 'dart:convert'; - -import 'package:fluffychat/pangea/enum/use_type.dart'; -import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; -import 'package:fluffychat/pangea/models/analytics/analytics_model.dart'; -import 'package:fluffychat/pangea/utils/error_handler.dart'; -import 'package:flutter/foundation.dart'; - -class SummaryAnalyticsModel extends AnalyticsModel { - late List _messages; - - SummaryAnalyticsModel({ - required List messages, - }) { - _messages = messages; - } - - List get messages => _messages; - - static const _messagesKey = "msgs"; - - Map toJson() => { - _messagesKey: jsonEncode(_messages.map((e) => e.toJson()).toList()), - }; - - factory SummaryAnalyticsModel.fromJson(json) { - List savedMessages = []; - try { - savedMessages = json[_messagesKey] != null - ? (jsonDecode(json[_messagesKey] ?? "[]") as Iterable) - .map((e) => RecentMessageRecord.fromJson(e)) - .toList() - .cast() - : []; - } catch (err, stack) { - if (kDebugMode) rethrow; - ErrorHandler.logError(e: err, s: stack); - } - return SummaryAnalyticsModel( - messages: savedMessages, - ); - } - - static List formatSummaryContent( - List recentMsgs, - ) { - final List filtered = List.from(recentMsgs); - final List records = filtered - .map( - (msg) => RecentMessageRecord( - eventId: msg.eventId, - chatId: msg.room.id, - useType: msg.msgUseType, - time: msg.originServerTs, - ), - ) - .toList(); - - return records; - } -} - -class RecentMessageRecord { - String eventId; - String chatId; - UseType useType; - DateTime time; - - RecentMessageRecord({ - required this.eventId, - required this.chatId, - required this.useType, - required this.time, - }); - - factory RecentMessageRecord.fromJson(Map json) => - RecentMessageRecord( - eventId: json[_eventIdKey], - chatId: json[_chatIdKey], - useType: _typeStringToEnum(json[_typeOfUseKey]), - time: DateTime.parse(json[_timeKey]), - ); - - Map toJson() => { - _eventIdKey: eventId, - _chatIdKey: chatId, - _typeOfUseKey: _typeEnumToString(useType), - _timeKey: time.toIso8601String(), - }; - - String _typeEnumToString(dynamic status) => status.toString().split('.').last; - - static UseType _typeStringToEnum(String useType) { - final String lastPart = useType.toString().split('.').last; - switch (lastPart) { - case 'ta': - return UseType.ta; - case 'ga': - return UseType.ga; - case 'wa': - return UseType.wa; - default: - return UseType.un; - } - } - - static const _eventIdKey = "m.id"; - static const _chatIdKey = "c.id"; - static const _typeOfUseKey = "typ"; - static const _timeKey = "t"; -} diff --git a/lib/pangea/pages/analytics/analytics_list_tile.dart b/lib/pangea/pages/analytics/analytics_list_tile.dart index a49ef4cb47..1f26dada93 100644 --- a/lib/pangea/pages/analytics/analytics_list_tile.dart +++ b/lib/pangea/pages/analytics/analytics_list_tile.dart @@ -1,156 +1,156 @@ -import 'dart:async'; +// import 'dart:async'; -import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; -import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:go_router/go_router.dart'; -import 'package:matrix/matrix.dart'; +// import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; +// import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter_gen/gen_l10n/l10n.dart'; +// import 'package:go_router/go_router.dart'; +// import 'package:matrix/matrix.dart'; -import '../../../../utils/date_time_extension.dart'; -import '../../../widgets/avatar.dart'; -import '../../../widgets/matrix.dart'; -import '../../models/analytics/chart_analytics_model.dart'; -import 'base_analytics.dart'; -import 'list_summary_analytics.dart'; +// import '../../../../utils/date_time_extension.dart'; +// import '../../../widgets/avatar.dart'; +// import '../../../widgets/matrix.dart'; +// import '../../models/analytics/chart_analytics_model.dart'; +// import 'base_analytics.dart'; +// import 'list_summary_analytics.dart'; -class AnalyticsListTile extends StatefulWidget { - const AnalyticsListTile({ - super.key, - required this.defaultSelected, - required this.selected, - required this.avatar, - required this.allowNavigateOnSelect, - required this.isSelected, - required this.onTap, - required this.pangeaController, - this.controller, - this.refreshStream, - }); +// class AnalyticsListTile extends StatefulWidget { +// const AnalyticsListTile({ +// super.key, +// required this.defaultSelected, +// required this.selected, +// required this.avatar, +// required this.allowNavigateOnSelect, +// required this.isSelected, +// required this.onTap, +// required this.pangeaController, +// this.controller, +// this.refreshStream, +// }); - final void Function(AnalyticsSelected) onTap; +// final void Function(AnalyticsSelected) onTap; - final AnalyticsSelected defaultSelected; - final AnalyticsSelected selected; +// final AnalyticsSelected defaultSelected; +// final AnalyticsSelected selected; - final Uri? avatar; +// final Uri? avatar; - final bool allowNavigateOnSelect; - final bool isSelected; +// final bool allowNavigateOnSelect; +// final bool isSelected; - final PangeaController pangeaController; - final BaseAnalyticsController? controller; - final StreamController? refreshStream; +// final PangeaController pangeaController; +// final BaseAnalyticsController? controller; +// final StreamController? refreshStream; - @override - AnalyticsListTileState createState() => AnalyticsListTileState(); -} +// @override +// AnalyticsListTileState createState() => AnalyticsListTileState(); +// } -class AnalyticsListTileState extends State { - ChartAnalyticsModel? tileData; - StreamSubscription? refreshSubscription; +// class AnalyticsListTileState extends State { +// ChartAnalyticsModel? tileData; +// StreamSubscription? refreshSubscription; - @override - void initState() { - super.initState(); - setTileData(); - refreshSubscription = widget.refreshStream?.stream.listen((forceUpdate) { - setTileData(forceUpdate: forceUpdate); - }); - } +// @override +// void initState() { +// super.initState(); +// setTileData(); +// refreshSubscription = widget.refreshStream?.stream.listen((forceUpdate) { +// setTileData(forceUpdate: forceUpdate); +// }); +// } - @override - void didUpdateWidget(covariant AnalyticsListTile oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.selected != widget.selected) { - setTileData(); - } - } +// @override +// void didUpdateWidget(covariant AnalyticsListTile oldWidget) { +// super.didUpdateWidget(oldWidget); +// if (oldWidget.selected != widget.selected) { +// setTileData(); +// } +// } - @override - void dispose() { - refreshSubscription?.cancel(); - super.dispose(); - } +// @override +// void dispose() { +// refreshSubscription?.cancel(); +// super.dispose(); +// } - Future setTileData({forceUpdate = false}) async { - tileData = await MatrixState.pangeaController.analytics.getAnalytics( - defaultSelected: widget.defaultSelected, - selected: widget.selected, - forceUpdate: forceUpdate, - ); - if (mounted) setState(() {}); - } +// Future setTileData({forceUpdate = false}) async { +// tileData = await MatrixState.pangeaController.analytics.getAnalytics( +// defaultSelected: widget.defaultSelected, +// selected: widget.selected, +// forceUpdate: forceUpdate, +// ); +// if (mounted) setState(() {}); +// } - @override - Widget build(BuildContext context) { - final Room? room = - Matrix.of(context).client.getRoomById(widget.selected.id); - return Material( - color: widget.isSelected - ? Theme.of(context).colorScheme.secondaryContainer - : Colors.transparent, - child: ListTile( - leading: widget.selected.type == AnalyticsEntryType.privateChats - ? CircleAvatar( - backgroundColor: Theme.of(context).primaryColor, - foregroundColor: Colors.white, - radius: Avatar.defaultSize / 2, - child: const Icon(Icons.forum), - ) - : Avatar( - mxContent: widget.avatar, - name: widget.selected.displayName, - littleIcon: room?.roomTypeIcon, - ), - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - widget.selected.displayName, - maxLines: 1, - overflow: TextOverflow.ellipsis, - softWrap: false, - style: TextStyle( - fontWeight: FontWeight.bold, - color: Theme.of(context).textTheme.bodyLarge!.color, - ), - ), - ), - Tooltip( - message: L10n.of(context)!.timeOfLastMessage, - child: Text( - tileData?.lastMessageTime?.localizedTimeShort(context) ?? "", - style: TextStyle( - fontSize: 13, - color: Theme.of(context).textTheme.bodyMedium!.color, - ), - ), - ), - ], - ), - subtitle: ListSummaryAnalytics( - chartAnalytics: tileData, - ), - selected: widget.isSelected, - onTap: () { - if (widget.controller?.widget.selectedView == null) { - widget.onTap(widget.selected); - return; - } - if ((room?.isSpace ?? false) && widget.allowNavigateOnSelect) { - context.go('/rooms/analytics/${room!.id}'); - return; - } - widget.onTap(widget.selected); - }, - trailing: (room?.isSpace ?? false) && - widget.selected.type != AnalyticsEntryType.privateChats && - widget.allowNavigateOnSelect - ? const Icon(Icons.chevron_right) - : null, - ), - ); - } -} +// @override +// Widget build(BuildContext context) { +// final Room? room = +// Matrix.of(context).client.getRoomById(widget.selected.id); +// return Material( +// color: widget.isSelected +// ? Theme.of(context).colorScheme.secondaryContainer +// : Colors.transparent, +// child: ListTile( +// leading: widget.selected.type == AnalyticsEntryType.privateChats +// ? CircleAvatar( +// backgroundColor: Theme.of(context).primaryColor, +// foregroundColor: Colors.white, +// radius: Avatar.defaultSize / 2, +// child: const Icon(Icons.forum), +// ) +// : Avatar( +// mxContent: widget.avatar, +// name: widget.selected.displayName, +// littleIcon: room?.roomTypeIcon, +// ), +// title: Row( +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// children: [ +// Expanded( +// child: Text( +// widget.selected.displayName, +// maxLines: 1, +// overflow: TextOverflow.ellipsis, +// softWrap: false, +// style: TextStyle( +// fontWeight: FontWeight.bold, +// color: Theme.of(context).textTheme.bodyLarge!.color, +// ), +// ), +// ), +// Tooltip( +// message: L10n.of(context)!.timeOfLastMessage, +// child: Text( +// tileData?.lastMessageTime?.localizedTimeShort(context) ?? "", +// style: TextStyle( +// fontSize: 13, +// color: Theme.of(context).textTheme.bodyMedium!.color, +// ), +// ), +// ), +// ], +// ), +// subtitle: ListSummaryAnalytics( +// chartAnalytics: tileData, +// ), +// selected: widget.isSelected, +// onTap: () { +// if (widget.controller?.widget.selectedView == null) { +// widget.onTap(widget.selected); +// return; +// } +// if ((room?.isSpace ?? false) && widget.allowNavigateOnSelect) { +// context.go('/rooms/analytics/${room!.id}'); +// return; +// } +// widget.onTap(widget.selected); +// }, +// trailing: (room?.isSpace ?? false) && +// widget.selected.type != AnalyticsEntryType.privateChats && +// widget.allowNavigateOnSelect +// ? const Icon(Icons.chevron_right) +// : null, +// ), +// ); +// } +// } diff --git a/lib/pangea/pages/analytics/base_analytics.dart b/lib/pangea/pages/analytics/base_analytics.dart index cd41d2312f..408505987e 100644 --- a/lib/pangea/pages/analytics/base_analytics.dart +++ b/lib/pangea/pages/analytics/base_analytics.dart @@ -1,214 +1,214 @@ -import 'dart:async'; - -import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; -import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart'; -import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; -import 'package:fluffychat/pangea/models/analytics/analytics_event.dart'; -import 'package:fluffychat/pangea/models/language_model.dart'; -import 'package:fluffychat/pangea/pages/analytics/base_analytics_view.dart'; -import 'package:fluffychat/pangea/pages/analytics/student_analytics/student_analytics.dart'; -import 'package:flutter/material.dart'; -import 'package:future_loading_dialog/future_loading_dialog.dart'; -import 'package:matrix/matrix.dart'; - -import '../../../widgets/matrix.dart'; -import '../../controllers/pangea_controller.dart'; -import '../../enum/bar_chart_view_enum.dart'; -import '../../enum/time_span.dart'; -import '../../models/analytics/chart_analytics_model.dart'; - -class BaseAnalyticsPage extends StatefulWidget { - final String pageTitle; - final List tabs; - final BarChartViewSelection selectedView; - - final AnalyticsSelected defaultSelected; - final AnalyticsSelected? alwaysSelected; - final StudentAnalyticsController? myAnalyticsController; - final List targetLanguages; - - BaseAnalyticsPage({ - super.key, - required this.pageTitle, - required this.tabs, - required this.alwaysSelected, - required this.defaultSelected, - required this.selectedView, - this.myAnalyticsController, - targetLanguages, - }) : targetLanguages = (targetLanguages?.isNotEmpty ?? false) - ? targetLanguages - : MatrixState.pangeaController.pLanguageStore.targetOptions; - - @override - State createState() => BaseAnalyticsController(); -} - -class BaseAnalyticsController extends State { - final PangeaController pangeaController = MatrixState.pangeaController; - AnalyticsSelected? selected; - String? currentLemma; - ChartAnalyticsModel? chartData; - StreamController refreshStream = StreamController.broadcast(); - BarChartViewSelection currentView = BarChartViewSelection.messages; - - bool isSelected(String chatOrStudentId) => chatOrStudentId == selected?.id; - - Room? get activeSpace { - if (widget.defaultSelected.type == AnalyticsEntryType.space) { - return Matrix.of(context).client.getRoomById(widget.defaultSelected.id); - } - return null; - } - - @override - void initState() { - super.initState(); - currentView = widget.selectedView; - if (widget.defaultSelected.type == AnalyticsEntryType.student) { - runFirstRefresh(); - } - setChartData(); - } - - @override - void didUpdateWidget(covariant BaseAnalyticsPage oldWidget) { - // when a user is a parent space's analytics and clicks on a subspace - super.didUpdateWidget(oldWidget); - if (oldWidget.defaultSelected.id != widget.defaultSelected.id) { - setChartData(); - refreshStream.add(false); - } - } - - Future runFirstRefresh() async { - final analyticsRooms = - pangeaController.matrixState.client.allMyAnalyticsRooms; - - final List analyticsEvent = []; - for (final analyticsRoom in analyticsRooms) { - final lastSummaryEvent = await analyticsRoom.getLastAnalyticsEvent( - PangeaEventTypes.summaryAnalytics, - Matrix.of(context).client.userID!, - ); - final lastConstructEvent = await analyticsRoom.getLastAnalyticsEvent( - PangeaEventTypes.construct, - Matrix.of(context).client.userID!, - ); - if (lastSummaryEvent != null) { - analyticsEvent.add(lastSummaryEvent); - } - if (lastConstructEvent != null) { - analyticsEvent.add(lastConstructEvent); - } - } - - if (analyticsEvent.isNotEmpty) return; - onRefresh(); - } - - Future onRefresh() async { - // postframe callback to avoid calling this function during build - WidgetsBinding.instance.addPostFrameCallback((_) async { - await showFutureLoadingDialog( - context: context, - future: () async { - debugPrint("updating analytics"); - await pangeaController.myAnalytics.updateAnalytics(); - await setChartData(forceUpdate: true); - refreshStream.add(true); - }, - ); - }); - } - - Future fetchChartData( - AnalyticsSelected? params, { - forceUpdate = false, - }) async { - final ChartAnalyticsModel data = - await pangeaController.analytics.getAnalytics( - defaultSelected: widget.defaultSelected, - selected: params, - forceUpdate: forceUpdate, - ); - - return data; - } - - Future setChartData({forceUpdate = false}) async { - final ChartAnalyticsModel newData = await fetchChartData( - selected, - forceUpdate: forceUpdate, - ); - setState(() => chartData = newData); - } - - TimeSpan get currentTimeSpan => - pangeaController.analytics.currentAnalyticsTimeSpan; - - Future toggleSelection(AnalyticsSelected selectedParam) async { - setState(() { - debugPrint("selectedParam.id is ${selectedParam.id}"); - currentLemma = null; - selected = isSelected(selectedParam.id) ? null : selectedParam; - }); - await setChartData(); - refreshStream.add(false); - Future.delayed(Duration.zero, () => setState(() {})); - } - - Future toggleTimeSpan(BuildContext context, TimeSpan timeSpan) async { - await pangeaController.analytics.setCurrentAnalyticsTimeSpan(timeSpan); - await setChartData(); - refreshStream.add(false); - } - - Future toggleSpaceLang(LanguageModel lang) async { - await pangeaController.analytics.setCurrentAnalyticsLang(lang); - await setChartData(); - refreshStream.add(false); - } - - Future toggleView(BarChartViewSelection view) async { - currentView = view; - await setChartData(); - refreshStream.add(false); - } - - void setCurrentLemma(String? lemma) { - currentLemma = lemma; - setState(() {}); - refreshStream.add(false); - } - - @override - Widget build(BuildContext context) { - return BaseAnalyticsView(controller: this); - } -} - -class TabData { - AnalyticsEntryType type; - IconData icon; - List items; - bool allowNavigateOnSelect; - - TabData({ - required this.type, - required this.items, - required this.icon, - this.allowNavigateOnSelect = true, - }); -} - -class TabItem { - Uri? avatar; - String displayName; - String id; - - TabItem({required this.avatar, required this.displayName, required this.id}); -} +// import 'dart:async'; + +// import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; +// import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart'; +// import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; +// import 'package:fluffychat/pangea/models/analytics/analytics_event.dart'; +// import 'package:fluffychat/pangea/models/language_model.dart'; +// import 'package:fluffychat/pangea/pages/analytics/base_analytics_view.dart'; +// import 'package:fluffychat/pangea/pages/analytics/student_analytics/student_analytics.dart'; +// import 'package:flutter/material.dart'; +// import 'package:future_loading_dialog/future_loading_dialog.dart'; +// import 'package:matrix/matrix.dart'; + +// import '../../../widgets/matrix.dart'; +// import '../../controllers/pangea_controller.dart'; +// import '../../enum/bar_chart_view_enum.dart'; +// import '../../enum/time_span.dart'; +// import '../../models/analytics/chart_analytics_model.dart'; + +// class BaseAnalyticsPage extends StatefulWidget { +// final String pageTitle; +// final List tabs; +// final BarChartViewSelection selectedView; + +// final AnalyticsSelected defaultSelected; +// final AnalyticsSelected? alwaysSelected; +// final StudentAnalyticsController? myAnalyticsController; +// final List targetLanguages; + +// BaseAnalyticsPage({ +// super.key, +// required this.pageTitle, +// required this.tabs, +// required this.alwaysSelected, +// required this.defaultSelected, +// required this.selectedView, +// this.myAnalyticsController, +// targetLanguages, +// }) : targetLanguages = (targetLanguages?.isNotEmpty ?? false) +// ? targetLanguages +// : MatrixState.pangeaController.pLanguageStore.targetOptions; + +// @override +// State createState() => BaseAnalyticsController(); +// } + +// class BaseAnalyticsController extends State { +// final PangeaController pangeaController = MatrixState.pangeaController; +// AnalyticsSelected? selected; +// String? currentLemma; +// ChartAnalyticsModel? chartData; +// StreamController refreshStream = StreamController.broadcast(); +// BarChartViewSelection currentView = BarChartViewSelection.messages; + +// bool isSelected(String chatOrStudentId) => chatOrStudentId == selected?.id; + +// Room? get activeSpace { +// if (widget.defaultSelected.type == AnalyticsEntryType.space) { +// return Matrix.of(context).client.getRoomById(widget.defaultSelected.id); +// } +// return null; +// } + +// @override +// void initState() { +// super.initState(); +// currentView = widget.selectedView; +// if (widget.defaultSelected.type == AnalyticsEntryType.student) { +// runFirstRefresh(); +// } +// setChartData(); +// } + +// @override +// void didUpdateWidget(covariant BaseAnalyticsPage oldWidget) { +// // when a user is a parent space's analytics and clicks on a subspace +// super.didUpdateWidget(oldWidget); +// if (oldWidget.defaultSelected.id != widget.defaultSelected.id) { +// setChartData(); +// refreshStream.add(false); +// } +// } + +// Future runFirstRefresh() async { +// final analyticsRooms = +// pangeaController.matrixState.client.allMyAnalyticsRooms; + +// final List analyticsEvent = []; +// for (final analyticsRoom in analyticsRooms) { +// final lastSummaryEvent = await analyticsRoom.getLastAnalyticsEvent( +// PangeaEventTypes.summaryAnalytics, +// Matrix.of(context).client.userID!, +// ); +// final lastConstructEvent = await analyticsRoom.getLastAnalyticsEvent( +// PangeaEventTypes.construct, +// Matrix.of(context).client.userID!, +// ); +// if (lastSummaryEvent != null) { +// analyticsEvent.add(lastSummaryEvent); +// } +// if (lastConstructEvent != null) { +// analyticsEvent.add(lastConstructEvent); +// } +// } + +// if (analyticsEvent.isNotEmpty) return; +// onRefresh(); +// } + +// Future onRefresh() async { +// // postframe callback to avoid calling this function during build +// WidgetsBinding.instance.addPostFrameCallback((_) async { +// await showFutureLoadingDialog( +// context: context, +// future: () async { +// debugPrint("updating analytics"); +// await pangeaController.myAnalytics.updateAnalytics(); +// await setChartData(forceUpdate: true); +// refreshStream.add(true); +// }, +// ); +// }); +// } + +// Future fetchChartData( +// AnalyticsSelected? params, { +// forceUpdate = false, +// }) async { +// final ChartAnalyticsModel data = +// await pangeaController.analytics.getAnalytics( +// defaultSelected: widget.defaultSelected, +// selected: params, +// forceUpdate: forceUpdate, +// ); + +// return data; +// } + +// Future setChartData({forceUpdate = false}) async { +// final ChartAnalyticsModel newData = await fetchChartData( +// selected, +// forceUpdate: forceUpdate, +// ); +// setState(() => chartData = newData); +// } + +// TimeSpan get currentTimeSpan => +// pangeaController.analytics.currentAnalyticsTimeSpan; + +// Future toggleSelection(AnalyticsSelected selectedParam) async { +// setState(() { +// debugPrint("selectedParam.id is ${selectedParam.id}"); +// currentLemma = null; +// selected = isSelected(selectedParam.id) ? null : selectedParam; +// }); +// await setChartData(); +// refreshStream.add(false); +// Future.delayed(Duration.zero, () => setState(() {})); +// } + +// Future toggleTimeSpan(BuildContext context, TimeSpan timeSpan) async { +// await pangeaController.analytics.setCurrentAnalyticsTimeSpan(timeSpan); +// await setChartData(); +// refreshStream.add(false); +// } + +// Future toggleSpaceLang(LanguageModel lang) async { +// await pangeaController.analytics.setCurrentAnalyticsLang(lang); +// await setChartData(); +// refreshStream.add(false); +// } + +// Future toggleView(BarChartViewSelection view) async { +// currentView = view; +// await setChartData(); +// refreshStream.add(false); +// } + +// void setCurrentLemma(String? lemma) { +// currentLemma = lemma; +// setState(() {}); +// refreshStream.add(false); +// } + +// @override +// Widget build(BuildContext context) { +// return BaseAnalyticsView(controller: this); +// } +// } + +// class TabData { +// AnalyticsEntryType type; +// IconData icon; +// List items; +// bool allowNavigateOnSelect; + +// TabData({ +// required this.type, +// required this.items, +// required this.icon, +// this.allowNavigateOnSelect = true, +// }); +// } + +// class TabItem { +// Uri? avatar; +// String displayName; +// String id; + +// TabItem({required this.avatar, required this.displayName, required this.id}); +// } enum AnalyticsEntryType { student, room, space, privateChats } diff --git a/lib/pangea/pages/analytics/base_analytics_view.dart b/lib/pangea/pages/analytics/base_analytics_view.dart index 13e49aa388..60e31c5c87 100644 --- a/lib/pangea/pages/analytics/base_analytics_view.dart +++ b/lib/pangea/pages/analytics/base_analytics_view.dart @@ -1,243 +1,243 @@ -import 'dart:math'; +// import 'dart:math'; -import 'package:fluffychat/pangea/enum/bar_chart_view_enum.dart'; -import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; -import 'package:fluffychat/pangea/enum/time_span.dart'; -import 'package:fluffychat/pangea/pages/analytics/analytics_language_button.dart'; -import 'package:fluffychat/pangea/pages/analytics/analytics_list_tile.dart'; -import 'package:fluffychat/pangea/pages/analytics/analytics_view_button.dart'; -import 'package:fluffychat/pangea/pages/analytics/base_analytics.dart'; -import 'package:fluffychat/pangea/pages/analytics/construct_list.dart'; -import 'package:fluffychat/pangea/pages/analytics/messages_bar_chart.dart'; -import 'package:fluffychat/pangea/pages/analytics/time_span_menu_button.dart'; -import 'package:fluffychat/widgets/layouts/max_width_body.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:go_router/go_router.dart'; +// import 'package:fluffychat/pangea/enum/bar_chart_view_enum.dart'; +// import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; +// import 'package:fluffychat/pangea/enum/time_span.dart'; +// import 'package:fluffychat/pangea/pages/analytics/analytics_language_button.dart'; +// import 'package:fluffychat/pangea/pages/analytics/analytics_list_tile.dart'; +// import 'package:fluffychat/pangea/pages/analytics/analytics_view_button.dart'; +// import 'package:fluffychat/pangea/pages/analytics/base_analytics.dart'; +// import 'package:fluffychat/pangea/pages/analytics/construct_list.dart'; +// import 'package:fluffychat/pangea/pages/analytics/messages_bar_chart.dart'; +// import 'package:fluffychat/pangea/pages/analytics/time_span_menu_button.dart'; +// import 'package:fluffychat/widgets/layouts/max_width_body.dart'; +// import 'package:flutter/gestures.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter_gen/gen_l10n/l10n.dart'; +// import 'package:go_router/go_router.dart'; -class BaseAnalyticsView extends StatelessWidget { - const BaseAnalyticsView({ - super.key, - required this.controller, - }); +// class BaseAnalyticsView extends StatelessWidget { +// const BaseAnalyticsView({ +// super.key, +// required this.controller, +// }); - final BaseAnalyticsController controller; +// final BaseAnalyticsController controller; - Widget chartView(BuildContext context) { - switch (controller.currentView) { - case BarChartViewSelection.messages: - return MessagesBarChart( - chartAnalytics: controller.chartData, - ); - case BarChartViewSelection.grammar: - return ConstructList( - constructType: ConstructTypeEnum.grammar, - defaultSelected: controller.widget.defaultSelected, - selected: controller.selected, - controller: controller, - pangeaController: controller.pangeaController, - refreshStream: controller.refreshStream, - ); - } - } +// Widget chartView(BuildContext context) { +// switch (controller.currentView) { +// case BarChartViewSelection.messages: +// return MessagesBarChart( +// chartAnalytics: controller.chartData, +// ); +// case BarChartViewSelection.grammar: +// return ConstructList( +// constructType: ConstructTypeEnum.grammar, +// defaultSelected: controller.widget.defaultSelected, +// selected: controller.selected, +// controller: controller, +// pangeaController: controller.pangeaController, +// refreshStream: controller.refreshStream, +// ); +// } +// } - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - centerTitle: true, - title: RichText( - text: TextSpan( - style: TextStyle( - color: Theme.of(context).textTheme.bodyLarge!.color, - fontSize: 18, - fontWeight: FontWeight.w700, - ), - children: [ - TextSpan( - text: controller.widget.pageTitle, - style: const TextStyle(decoration: TextDecoration.underline), - recognizer: TapGestureRecognizer() - ..onTap = () { - final String route = - "/rooms/${controller.widget.defaultSelected.type.route}"; - context.go(route); - }, - ), - if (controller.activeSpace != null) - const TextSpan( - text: " > ", - ), - if (controller.activeSpace != null) - TextSpan( - text: controller.activeSpace!.getLocalizedDisplayname(), - ), - const TextSpan( - text: " > ", - ), - TextSpan( - text: controller.currentView.string(context), - ), - ], - ), - overflow: TextOverflow.ellipsis, - textAlign: TextAlign.center, - ), - ), - body: MaxWidthBody( - withScrolling: false, - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - TimeSpanMenuButton( - value: controller.currentTimeSpan, - onChange: (TimeSpan value) => - controller.toggleTimeSpan(context, value), - ), - AnalyticsViewButton( - value: controller.currentView, - onChange: controller.toggleView, - ), - AnalyticsLanguageButton( - value: controller - .pangeaController.analytics.currentAnalyticsLang, - onChange: (lang) => controller.toggleSpaceLang(lang), - languages: controller.widget.targetLanguages, - ), - ], - ), - const SizedBox( - height: 10, - ), - Expanded( - flex: 1, - child: chartView(context), - ), - Expanded( - flex: 1, - child: DefaultTabController( - length: 2, - child: Column( - children: [ - TabBar( - tabs: [ - ...controller.widget.tabs.map( - (tab) => Tab( - icon: Icon( - tab.icon, - color: Theme.of(context) - .colorScheme - .onSurfaceVariant, - ), - ), - ), - ], - ), - Expanded( - child: SingleChildScrollView( - child: SizedBox( - height: max( - controller.widget.tabs[0].items.length + 1, - controller.widget.tabs[1].items.length, - ) * - 72, - child: TabBarView( - physics: const NeverScrollableScrollPhysics(), - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - ...controller.widget.tabs[0].items.map( - (item) => AnalyticsListTile( - refreshStream: controller.refreshStream, - avatar: item.avatar, - defaultSelected: - controller.widget.defaultSelected, - selected: AnalyticsSelected( - item.id, - controller.widget.tabs[0].type, - item.displayName, - ), - isSelected: - controller.isSelected(item.id), - onTap: (_) => controller.toggleSelection( - AnalyticsSelected( - item.id, - controller.widget.tabs[0].type, - item.displayName, - ), - ), - allowNavigateOnSelect: controller - .widget.tabs[0].allowNavigateOnSelect, - pangeaController: - controller.pangeaController, - controller: controller, - ), - ), - if (controller.widget.defaultSelected.type == - AnalyticsEntryType.space) - AnalyticsListTile( - refreshStream: controller.refreshStream, - defaultSelected: - controller.widget.defaultSelected, - avatar: null, - selected: AnalyticsSelected( - controller.widget.defaultSelected.id, - AnalyticsEntryType.privateChats, - L10n.of(context)!.allPrivateChats, - ), - allowNavigateOnSelect: false, - isSelected: controller.isSelected( - controller.widget.defaultSelected.id, - ), - onTap: controller.toggleSelection, - pangeaController: - controller.pangeaController, - controller: controller, - ), - ], - ), - Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: controller.widget.tabs[1].items - .map( - (item) => AnalyticsListTile( - refreshStream: controller.refreshStream, - avatar: item.avatar, - defaultSelected: - controller.widget.defaultSelected, - selected: AnalyticsSelected( - item.id, - controller.widget.tabs[1].type, - item.displayName, - ), - isSelected: - controller.isSelected(item.id), - onTap: controller.toggleSelection, - allowNavigateOnSelect: controller.widget - .tabs[1].allowNavigateOnSelect, - pangeaController: - controller.pangeaController, - controller: controller, - ), - ) - .toList(), - ), - ], - ), - ), - ), - ), - ], - ), - ), - ), - ], - ), - ), - ); - } -} +// @override +// Widget build(BuildContext context) { +// return Scaffold( +// appBar: AppBar( +// centerTitle: true, +// title: RichText( +// text: TextSpan( +// style: TextStyle( +// color: Theme.of(context).textTheme.bodyLarge!.color, +// fontSize: 18, +// fontWeight: FontWeight.w700, +// ), +// children: [ +// TextSpan( +// text: controller.widget.pageTitle, +// style: const TextStyle(decoration: TextDecoration.underline), +// recognizer: TapGestureRecognizer() +// ..onTap = () { +// final String route = +// "/rooms/${controller.widget.defaultSelected.type.route}"; +// context.go(route); +// }, +// ), +// if (controller.activeSpace != null) +// const TextSpan( +// text: " > ", +// ), +// if (controller.activeSpace != null) +// TextSpan( +// text: controller.activeSpace!.getLocalizedDisplayname(), +// ), +// const TextSpan( +// text: " > ", +// ), +// TextSpan( +// text: controller.currentView.string(context), +// ), +// ], +// ), +// overflow: TextOverflow.ellipsis, +// textAlign: TextAlign.center, +// ), +// ), +// body: MaxWidthBody( +// withScrolling: false, +// child: Column( +// children: [ +// Row( +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// children: [ +// TimeSpanMenuButton( +// value: controller.currentTimeSpan, +// onChange: (TimeSpan value) => +// controller.toggleTimeSpan(context, value), +// ), +// AnalyticsViewButton( +// value: controller.currentView, +// onChange: controller.toggleView, +// ), +// AnalyticsLanguageButton( +// value: controller +// .pangeaController.analytics.currentAnalyticsLang, +// onChange: (lang) => controller.toggleSpaceLang(lang), +// languages: controller.widget.targetLanguages, +// ), +// ], +// ), +// const SizedBox( +// height: 10, +// ), +// Expanded( +// flex: 1, +// child: chartView(context), +// ), +// Expanded( +// flex: 1, +// child: DefaultTabController( +// length: 2, +// child: Column( +// children: [ +// TabBar( +// tabs: [ +// ...controller.widget.tabs.map( +// (tab) => Tab( +// icon: Icon( +// tab.icon, +// color: Theme.of(context) +// .colorScheme +// .onSurfaceVariant, +// ), +// ), +// ), +// ], +// ), +// Expanded( +// child: SingleChildScrollView( +// child: SizedBox( +// height: max( +// controller.widget.tabs[0].items.length + 1, +// controller.widget.tabs[1].items.length, +// ) * +// 72, +// child: TabBarView( +// physics: const NeverScrollableScrollPhysics(), +// children: [ +// Column( +// crossAxisAlignment: CrossAxisAlignment.stretch, +// children: [ +// ...controller.widget.tabs[0].items.map( +// (item) => AnalyticsListTile( +// refreshStream: controller.refreshStream, +// avatar: item.avatar, +// defaultSelected: +// controller.widget.defaultSelected, +// selected: AnalyticsSelected( +// item.id, +// controller.widget.tabs[0].type, +// item.displayName, +// ), +// isSelected: +// controller.isSelected(item.id), +// onTap: (_) => controller.toggleSelection( +// AnalyticsSelected( +// item.id, +// controller.widget.tabs[0].type, +// item.displayName, +// ), +// ), +// allowNavigateOnSelect: controller +// .widget.tabs[0].allowNavigateOnSelect, +// pangeaController: +// controller.pangeaController, +// controller: controller, +// ), +// ), +// if (controller.widget.defaultSelected.type == +// AnalyticsEntryType.space) +// AnalyticsListTile( +// refreshStream: controller.refreshStream, +// defaultSelected: +// controller.widget.defaultSelected, +// avatar: null, +// selected: AnalyticsSelected( +// controller.widget.defaultSelected.id, +// AnalyticsEntryType.privateChats, +// L10n.of(context)!.allPrivateChats, +// ), +// allowNavigateOnSelect: false, +// isSelected: controller.isSelected( +// controller.widget.defaultSelected.id, +// ), +// onTap: controller.toggleSelection, +// pangeaController: +// controller.pangeaController, +// controller: controller, +// ), +// ], +// ), +// Column( +// crossAxisAlignment: CrossAxisAlignment.stretch, +// children: controller.widget.tabs[1].items +// .map( +// (item) => AnalyticsListTile( +// refreshStream: controller.refreshStream, +// avatar: item.avatar, +// defaultSelected: +// controller.widget.defaultSelected, +// selected: AnalyticsSelected( +// item.id, +// controller.widget.tabs[1].type, +// item.displayName, +// ), +// isSelected: +// controller.isSelected(item.id), +// onTap: controller.toggleSelection, +// allowNavigateOnSelect: controller.widget +// .tabs[1].allowNavigateOnSelect, +// pangeaController: +// controller.pangeaController, +// controller: controller, +// ), +// ) +// .toList(), +// ), +// ], +// ), +// ), +// ), +// ), +// ], +// ), +// ), +// ), +// ], +// ), +// ), +// ); +// } +// } diff --git a/lib/pangea/pages/analytics/construct_list.dart b/lib/pangea/pages/analytics/construct_list.dart index d46936c864..0f06ddc8d9 100644 --- a/lib/pangea/pages/analytics/construct_list.dart +++ b/lib/pangea/pages/analytics/construct_list.dart @@ -4,6 +4,7 @@ import 'package:collection/collection.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; +import 'package:fluffychat/pangea/enum/time_span.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_representation_event.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_event.dart'; @@ -22,7 +23,7 @@ class ConstructList extends StatefulWidget { final ConstructTypeEnum constructType; final AnalyticsSelected defaultSelected; final AnalyticsSelected? selected; - final BaseAnalyticsController controller; + final TimeSpan timeSpan; final PangeaController pangeaController; final StreamController refreshStream; @@ -30,9 +31,9 @@ class ConstructList extends StatefulWidget { super.key, required this.constructType, required this.defaultSelected, - required this.controller, required this.pangeaController, required this.refreshStream, + required this.timeSpan, this.selected, }); @@ -53,11 +54,11 @@ class ConstructListState extends State { : Column( children: [ ConstructListView( - controller: widget.controller, pangeaController: widget.pangeaController, defaultSelected: widget.defaultSelected, selected: widget.selected, refreshStream: widget.refreshStream, + timeSpan: widget.timeSpan, ), ], ); @@ -74,17 +75,17 @@ class ConstructListState extends State { // subtitle = total uses, equal to construct.content.uses.length // list has a fixed height of 400 and is scrollable class ConstructListView extends StatefulWidget { - final BaseAnalyticsController controller; final PangeaController pangeaController; final AnalyticsSelected defaultSelected; + final TimeSpan timeSpan; final AnalyticsSelected? selected; final StreamController refreshStream; const ConstructListView({ super.key, - required this.controller, required this.pangeaController, required this.defaultSelected, + required this.timeSpan, required this.refreshStream, this.selected, }); @@ -101,6 +102,7 @@ class ConstructListViewState extends State { bool fetchingConstructs = true; bool fetchingUses = false; StreamSubscription? refreshSubscription; + String? currentLemma; @override void initState() { @@ -112,6 +114,7 @@ class ConstructListViewState extends State { defaultSelected: widget.defaultSelected, selected: widget.selected, forceUpdate: true, + timeSpan: widget.timeSpan, ) .whenComplete(() => setState(() => fetchingConstructs = false)) .then((value) => setState(() => _constructs = value)); @@ -127,6 +130,7 @@ class ConstructListViewState extends State { defaultSelected: widget.defaultSelected, selected: widget.selected, forceUpdate: true, + timeSpan: widget.timeSpan, ) .then( (value) => setState(() { @@ -143,9 +147,14 @@ class ConstructListViewState extends State { super.dispose(); } + void setCurrentLemma(String? lemma) { + currentLemma = lemma; + setState(() {}); + } + int get lemmaIndex => constructs?.indexWhere( - (element) => element.lemma == widget.controller.currentLemma, + (element) => element.lemma == currentLemma, ) ?? -1; @@ -258,7 +267,7 @@ class ConstructListViewState extends State { } ConstructUses? get currentConstruct => constructs?.firstWhereOrNull( - (element) => element.lemma == widget.controller.currentLemma, + (element) => element.lemma == currentLemma, ); // given the current lemma and list of message events, return a list of @@ -266,7 +275,7 @@ class ConstructListViewState extends State { // this is because some message events may have has more than one PangeaMatch of a // given lemma type. List getMessageEventMatches() { - if (widget.controller.currentLemma == null) return []; + if (currentLemma == null) return []; final List allMsgErrorSteps = []; for (final msgEvent in _msgEvents) { @@ -277,7 +286,7 @@ class ConstructListViewState extends State { } // get all the pangea matches in that message which have that lemma final List? msgErrorSteps = msgEvent.errorSteps( - widget.controller.currentLemma!, + currentLemma!, ); if (msgErrorSteps == null) continue; @@ -327,7 +336,7 @@ class ConstructListViewState extends State { ), onTap: () async { final String lemma = constructs![index].lemma; - widget.controller.setCurrentLemma(lemma); + setCurrentLemma(lemma); fetchUses().then((_) => showConstructMessagesDialog()); }, ); @@ -346,7 +355,7 @@ class ConstructMessagesDialog extends StatelessWidget { @override Widget build(BuildContext context) { - if (controller.widget.controller.currentLemma == null || + if (controller.currentLemma == null || controller.constructs == null || controller.lemmaIndex < 0 || controller.lemmaIndex >= controller.constructs!.length) { @@ -359,7 +368,7 @@ class ConstructMessagesDialog extends StatelessWidget { controller._msgEvents.length; return AlertDialog( - title: Center(child: Text(controller.widget.controller.currentLemma!)), + title: Center(child: Text(controller.currentLemma!)), content: SizedBox( height: noData ? 90 : 250, width: noData ? 200 : 400, @@ -380,7 +389,7 @@ class ConstructMessagesDialog extends StatelessWidget { children: [ ConstructMessage( msgEvent: event.msgEvent, - lemma: controller.widget.controller.currentLemma!, + lemma: controller.currentLemma!, errorMessage: event.lemmaMatch, ), if (index < msgEventMatches.length - 1) @@ -528,42 +537,37 @@ class ConstructMessageBubble extends StatelessWidget { vertical: 8, ), child: RichText( - text: (end == null) - ? TextSpan( - text: errorText, - style: defaultStyle, - ) - : TextSpan( - children: [ - TextSpan( - text: errorText.substring(0, start), - style: defaultStyle, - ), - TextSpan( - text: errorText.substring(start, end), - style: defaultStyle.merge( - TextStyle( - backgroundColor: Colors.red.withOpacity(0.25), - decoration: TextDecoration.lineThrough, - decorationThickness: 2.5, - ), - ), - ), - const TextSpan(text: " "), - TextSpan( - text: replacementText, - style: defaultStyle.merge( - TextStyle( - backgroundColor: Colors.green.withOpacity(0.25), - ), - ), - ), - TextSpan( - text: errorText.substring(end), - style: defaultStyle, - ), - ], + text: TextSpan( + children: [ + TextSpan( + text: errorText.substring(0, start), + style: defaultStyle, + ), + TextSpan( + text: errorText.substring(start, end), + style: defaultStyle.merge( + TextStyle( + backgroundColor: Colors.red.withOpacity(0.25), + decoration: TextDecoration.lineThrough, + decorationThickness: 2.5, + ), + ), + ), + const TextSpan(text: " "), + TextSpan( + text: replacementText, + style: defaultStyle.merge( + TextStyle( + backgroundColor: Colors.green.withOpacity(0.25), + ), ), + ), + TextSpan( + text: errorText.substring(end), + style: defaultStyle, + ), + ], + ), ), ), ), diff --git a/lib/pangea/pages/analytics/list_summary_analytics.dart b/lib/pangea/pages/analytics/list_summary_analytics.dart index bf388cea74..02bb5d3fbb 100644 --- a/lib/pangea/pages/analytics/list_summary_analytics.dart +++ b/lib/pangea/pages/analytics/list_summary_analytics.dart @@ -1,101 +1,101 @@ -import 'dart:math'; +// import 'dart:math'; -import 'package:fluffychat/pangea/models/analytics/chart_analytics_model.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; +// import 'package:fluffychat/pangea/models/analytics/chart_analytics_model.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter_gen/gen_l10n/l10n.dart'; -import '../../enum/use_type.dart'; +// import '../../enum/use_type.dart'; -class ListSummaryAnalytics extends StatelessWidget { - final ChartAnalyticsModel? chartAnalytics; +// class ListSummaryAnalytics extends StatelessWidget { +// final ChartAnalyticsModel? chartAnalytics; - const ListSummaryAnalytics({super.key, this.chartAnalytics}); +// const ListSummaryAnalytics({super.key, this.chartAnalytics}); - TimeSeriesTotals? get totals => chartAnalytics?.totals; +// TimeSeriesTotals? get totals => chartAnalytics?.totals; - String spacer(int baseLength, int number) => - " " * max(baseLength - number.toString().length, 0); +// String spacer(int baseLength, int number) => +// " " * max(baseLength - number.toString().length, 0); - WidgetSpan spacerIconText( - String toolTip, - String space, - IconData icon, - int value, - Color? color, [ - percentage = true, - ]) => - WidgetSpan( - child: Tooltip( - message: toolTip, - child: RichText( - text: TextSpan( - children: [ - TextSpan( - text: space, - ), - WidgetSpan(child: Icon(icon, size: 14, color: color)), - TextSpan( - text: " $value${percentage ? "%" : ""}", - style: TextStyle(color: color), - ), - ], - ), - ), - ), - ); +// WidgetSpan spacerIconText( +// String toolTip, +// String space, +// IconData icon, +// int value, +// Color? color, [ +// percentage = true, +// ]) => +// WidgetSpan( +// child: Tooltip( +// message: toolTip, +// child: RichText( +// text: TextSpan( +// children: [ +// TextSpan( +// text: space, +// ), +// WidgetSpan(child: Icon(icon, size: 14, color: color)), +// TextSpan( +// text: " $value${percentage ? "%" : ""}", +// style: TextStyle(color: color), +// ), +// ], +// ), +// ), +// ), +// ); - @override - Widget build(BuildContext context) { - if (totals == null) { - return const LinearProgressIndicator(); - } - final l10n = L10n.of(context); +// @override +// Widget build(BuildContext context) { +// if (totals == null) { +// return const LinearProgressIndicator(); +// } +// final l10n = L10n.of(context); - return RichText( - text: TextSpan( - children: [ - spacerIconText( - L10n.of(context) != null - ? L10n.of(context)!.totalMessages - : "Total messages sent", - "", - Icons.chat_bubble, - totals!.all, - Theme.of(context).textTheme.bodyLarge!.color, - false, - ), - if (totals!.all != 0) ...[ - spacerIconText( - l10n != null ? l10n.taTooltip : "With translation assistance", - spacer(8, totals!.all), - UseType.ta.iconData, - totals!.taPercent, - UseType.ta.color(context), - ), - spacerIconText( - l10n != null ? l10n.gaTooltip : "With grammar assistance", - spacer(4, totals!.taPercent), - UseType.ga.iconData, - totals!.gaPercent, - UseType.ga.color(context), - ), - spacerIconText( - l10n != null ? l10n.waTooltip : "Without assistance", - spacer(4, totals!.gaPercent), - UseType.wa.iconData, - totals!.waPercent, - UseType.wa.color(context), - ), - spacerIconText( - l10n != null ? l10n.unTooltip : "Other", - spacer(4, totals!.waPercent), - UseType.un.iconData, - totals!.unPercent, - UseType.un.color(context), - ), - ], - ], - ), - ); - } -} +// return RichText( +// text: TextSpan( +// children: [ +// spacerIconText( +// L10n.of(context) != null +// ? L10n.of(context)!.totalMessages +// : "Total messages sent", +// "", +// Icons.chat_bubble, +// totals!.all, +// Theme.of(context).textTheme.bodyLarge!.color, +// false, +// ), +// if (totals!.all != 0) ...[ +// spacerIconText( +// l10n != null ? l10n.taTooltip : "With translation assistance", +// spacer(8, totals!.all), +// UseType.ta.iconData, +// totals!.taPercent, +// UseType.ta.color(context), +// ), +// spacerIconText( +// l10n != null ? l10n.gaTooltip : "With grammar assistance", +// spacer(4, totals!.taPercent), +// UseType.ga.iconData, +// totals!.gaPercent, +// UseType.ga.color(context), +// ), +// spacerIconText( +// l10n != null ? l10n.waTooltip : "Without assistance", +// spacer(4, totals!.gaPercent), +// UseType.wa.iconData, +// totals!.waPercent, +// UseType.wa.color(context), +// ), +// spacerIconText( +// l10n != null ? l10n.unTooltip : "Other", +// spacer(4, totals!.waPercent), +// UseType.un.iconData, +// totals!.unPercent, +// UseType.un.color(context), +// ), +// ], +// ], +// ), +// ); +// } +// } diff --git a/lib/pangea/pages/analytics/messages_bar_chart.dart b/lib/pangea/pages/analytics/messages_bar_chart.dart index 509270edb5..5a90022439 100644 --- a/lib/pangea/pages/analytics/messages_bar_chart.dart +++ b/lib/pangea/pages/analytics/messages_bar_chart.dart @@ -1,402 +1,402 @@ -import 'dart:developer'; - -import 'package:fl_chart/fl_chart.dart'; -import 'package:fluffychat/config/themes.dart'; -import 'package:fluffychat/pangea/pages/analytics/bar_chart_placeholder_data.dart'; -import 'package:fluffychat/pangea/utils/error_handler.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; - -import '../../enum/time_span.dart'; -import '../../enum/use_type.dart'; -import '../../models/analytics/chart_analytics_model.dart'; -import 'bar_chart_card.dart'; -import 'messages_legend_widget.dart'; - -class MessagesBarChart extends StatefulWidget { - final ChartAnalyticsModel? chartAnalytics; - - const MessagesBarChart({ - super.key, - required this.chartAnalytics, - }); - - @override - State createState() => MessagesBarChartState(); -} - -class MessagesBarChartState extends State { - final double barSpace = 16; - final List> intervalGroupings = []; - - @override - initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - final flLine = FlLine( - color: Theme.of(context).dividerColor, - strokeWidth: 1, - ); - - final flTitlesData = FlTitlesData( - show: true, - bottomTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - reservedSize: 28, - getTitlesWidget: bottomTitles, - ), - ), - leftTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - reservedSize: 40, - getTitlesWidget: leftTitles, - ), - ), - topTitles: const AxisTitles( - sideTitles: SideTitles(showTitles: false), - ), - rightTitles: const AxisTitles( - sideTitles: SideTitles(showTitles: false), - ), - ); - final barChartData = BarChartData( - alignment: BarChartAlignment.spaceEvenly, - barTouchData: BarTouchData( - enabled: false, - ), - // barTouchData: barTouchData, - titlesData: flTitlesData, - gridData: FlGridData( - show: true, - // checkToShowHorizontalLine: (value) => value % 10 == 0, - checkToShowHorizontalLine: (value) => true, - getDrawingHorizontalLine: (value) => flLine, - checkToShowVerticalLine: (value) => false, - getDrawingVerticalLine: (value) => flLine, - ), - borderData: FlBorderData( - show: false, - ), - groupsSpace: barSpace, - barGroups: barChartGroupData, - backgroundColor: Theme.of(context).scaffoldBackgroundColor, - ); - final barChart = BarChart( - barChartData, - swapAnimationDuration: const Duration(milliseconds: 250), - ); - - return BarChartCard( - barChart: barChart, - loadingData: widget.chartAnalytics == null, - legend: const MessagesLegendsListWidget(), - ); - } - - bool showLabelBasedOnTimeSpan( - TimeSpan timeSpan, - TimeSeriesInterval current, - TimeSeriesInterval? last, - int labelIndex, - ) { - switch (timeSpan) { - case TimeSpan.day: - return current.end.hour % 3 == 0; - case TimeSpan.month: - if (current.end.month != last?.end.month) { - return true; - } - double width = MediaQuery.of(context).size.width; - if (FluffyThemes.isColumnMode(context)) { - width = width - FluffyThemes.navRailWidth - FluffyThemes.columnWidth; - } - const int numDays = 28; - const int minSpacePerDay = 20; - final int availableSpaces = width ~/ minSpacePerDay; - final int showAtInterval = (numDays / availableSpaces).floor() + 1; - - final int lastDayOfCurrentMonth = - DateTime(current.end.year, current.end.month + 1, 0).day; - final bool isNextToMonth = labelIndex == 1 || - current.end.day == 2 || - current.end.day == lastDayOfCurrentMonth; - final bool shouldShowNextToMonth = showAtInterval <= 1; - return (current.end.day % showAtInterval == 0) && - (!isNextToMonth || shouldShowNextToMonth); - case TimeSpan.week: - case TimeSpan.sixmonths: - case TimeSpan.year: - default: - return true; - } - } - - String getLabelBasedOnTimeSpan( - TimeSpan timeSpan, - TimeSeriesInterval current, - TimeSeriesInterval? last, - int labelIndex, - ) { - final bool showLabel = showLabelBasedOnTimeSpan( - timeSpan, - current, - last, - labelIndex, - ); - - if (widget.chartAnalytics == null || !showLabel) { - return ""; - } - if (isInSameGroup(last, current, timeSpan)) { - return "-"; - } - - switch (widget.chartAnalytics?.timeSpan ?? TimeSpan.month) { - case TimeSpan.day: - return DateFormat(DateFormat.HOUR).format(current.end); - case TimeSpan.week: - return DateFormat(DateFormat.ABBR_WEEKDAY).format(current.end); - case TimeSpan.month: - return current.end.month != last?.end.month - ? DateFormat(DateFormat.ABBR_MONTH).format(current.end) - : DateFormat(DateFormat.DAY).format(current.end); - case TimeSpan.sixmonths: - case TimeSpan.year: - return DateFormat(DateFormat.ABBR_STANDALONE_MONTH).format(current.end); - default: - return ''; - } - } - - Widget bottomTitles(double value, TitleMeta meta) { - if (widget.chartAnalytics == null) { - return Container(); - } - String text; - final index = value.toInt(); - final TimeSpan timeSpan = widget.chartAnalytics?.timeSpan ?? TimeSpan.month; - final TimeSeriesInterval? last = - index != 0 ? intervalGroupings[index - 1].last : null; - final TimeSeriesInterval current = intervalGroupings[index].last; - - text = getLabelBasedOnTimeSpan(timeSpan, current, last, index); - - return SideTitleWidget( - axisSide: meta.axisSide, - child: Text( - text, - style: titleTextStyle(context), - ), - ); - } - - TextStyle titleTextStyle(context) => TextStyle( - color: Theme.of(context).textTheme.bodyLarge!.color, - fontSize: 10, - ); - - Widget leftTitles(double value, TitleMeta meta) { - Widget textWidget; - if (value != meta.max) { - textWidget = Text(meta.formattedValue, style: titleTextStyle(context)); - } else { - textWidget = const Icon(Icons.chat_bubble, size: 14); - } - return SideTitleWidget( - axisSide: meta.axisSide, - child: textWidget, - ); - } - - bool isInSameGroup( - TimeSeriesInterval? t1, - TimeSeriesInterval t2, - TimeSpan timeSpan, - ) { - final DateTime? date1 = t1?.end; - final DateTime date2 = t2.end; - if (timeSpan == TimeSpan.sixmonths || timeSpan == TimeSpan.year) { - return date1?.month == date2.month; - } else if (timeSpan == TimeSpan.week) { - return date1?.day == date2.day; - } else { - return false; - } - } - - void makeIntervalGroupings() { - intervalGroupings.clear(); - try { - for (final timeSeriesInterval - in widget.chartAnalytics?.timeSeries ?? []) { - //Note: if we decide we'd like to do some sort of grouping in the future, - // this is where that could happen. Currently, we're just putting one - // BarChartRod in each BarChartGroup - final TimeSeriesInterval? last = - intervalGroupings.isNotEmpty ? intervalGroupings.last.last : null; - - if (widget.chartAnalytics != null && - isInSameGroup( - last, - timeSeriesInterval, - widget.chartAnalytics!.timeSpan, - )) { - intervalGroupings.last.add(timeSeriesInterval); - } else { - intervalGroupings.add([timeSeriesInterval]); - } - } - } catch (err, stack) { - debugger(when: kDebugMode); - ErrorHandler.logError(e: err, s: stack); - } - } - - List get barChartGroupData { - if (widget.chartAnalytics == null) { - return BarChartPlaceHolderData.getRandomData(context); - } - - makeIntervalGroupings(); - - final List chartData = []; - - intervalGroupings.asMap().forEach((index, intervalGroup) { - chartData.add( - BarChartGroupData( - x: index, - barsSpace: barSpace, - // barRods: intervalGroup.map(constructBarChartRodData).toList(), - barRods: constructBarChartRodData(intervalGroup), - ), - ); - }); - return chartData; - } - - // BarChartRodData constructBarChartRodData(TimeSeriesInterval timeSeriesInterval) { - // final double y1 = timeSeriesInterval.spanIT.toDouble(); - // final double y2 = - // (timeSeriesInterval.spanIT + timeSeriesInterval.spanIGC).toDouble(); - // final double y3 = timeSeriesInterval.spanTotal.toDouble(); - // return BarChartRodData( - // toY: y3, - // width: 10.toDouble(), - // rodStackItems: [ - // BarChartRodStackItem(0, y1, UseType.ta.color(context)), - // BarChartRodStackItem(y1, y2, UseType.ga.color(context)), - // BarChartRodStackItem(y2, y3, UseType.wa.color(context)), - // ], - // borderRadius: BorderRadius.zero, - // ); - // } - - List constructBarChartRodData( - List timeSeriesIntervalGroup, - ) { - int y1 = 0; - int y2 = 0; - int y3 = 0; - int y4 = 0; - for (final e in timeSeriesIntervalGroup) { - y1 += e.totals.ta; - y2 += y1 + e.totals.ga; - y3 += y2 + e.totals.wa; - y4 += y3 + e.totals.un; - } - return [ - BarChartRodData( - toY: y4.toDouble(), - width: 10.toDouble(), - rodStackItems: [ - BarChartRodStackItem(0, y1.toDouble(), UseType.ta.color(context)), - BarChartRodStackItem( - y1.toDouble(), - y2.toDouble(), - UseType.ga.color(context), - ), - BarChartRodStackItem( - y2.toDouble(), - y3.toDouble(), - UseType.wa.color(context), - ), - BarChartRodStackItem( - y3.toDouble(), - y4.toDouble(), - UseType.un.color(context), - ), - ], - borderRadius: BorderRadius.zero, - ), - ]; - } - - // BarTouchData get barTouchData => BarTouchData( - // touchTooltipData: BarTouchTooltipData( - // fitInsideVertically: true, - // tooltipBgColor: Colors.blueGrey, - // getTooltipItem: (group, groupIndex, rod, rodIndex) { - // return BarTooltipItem( - // "groupindex $groupIndex rodIndex $rodIndex", - // const TextStyle( - // color: Colors.white, - // fontWeight: FontWeight.bold, - // fontSize: 18, - // ), - // children: [ - // toolTipText(rod), - // ], - // ); - // }, - // ), - // // touchCallback: (FlTouchEvent event, barTouchResponse) { - // // setState(() { - // // if (!event.isInterestedForInteractions || - // // barTouchResponse == null || - // // barTouchResponse.spot == null) { - // // touchedIndex = -1; - // // return; - // // } - // // touchedIndex = barTouchResponse.spot!.touchedBarGroupIndex; - // // }); - // // }, - // ); - - // TextSpan toolTipText(BarChartRodData rodData) { - // double rodPercentage(int index) { - // return (rodData.rodStackItems[index].toY - - // rodData.rodStackItems[index].fromY) / - // rodData.toY * - // 100; - // } - - // return TextSpan( - // children: [ - // const WidgetSpan( - // child: Icon(Icons.chat_bubble, size: 14), - // ), - // TextSpan( - // text: " ${rodData.toY}", - // ), - // TextSpan( - // text: "/nIT ${rodPercentage(0)}%", - // style: TextStyle(color: UseType.ta.color(context)), - // ), - // TextSpan( - // text: " IGC ${rodPercentage(1)}%", - // style: TextStyle(color: UseType.ga.color(context)), - // ), - // TextSpan( - // text: " Direct ${rodPercentage(2)}%", - // style: TextStyle(color: UseType.wa.color(context)), - // ), - // ], - // ); - // } -} +// import 'dart:developer'; + +// import 'package:fl_chart/fl_chart.dart'; +// import 'package:fluffychat/config/themes.dart'; +// import 'package:fluffychat/pangea/pages/analytics/bar_chart_placeholder_data.dart'; +// import 'package:fluffychat/pangea/utils/error_handler.dart'; +// import 'package:flutter/foundation.dart'; +// import 'package:flutter/material.dart'; +// import 'package:intl/intl.dart'; + +// import '../../enum/time_span.dart'; +// import '../../enum/use_type.dart'; +// import '../../models/analytics/chart_analytics_model.dart'; +// import 'bar_chart_card.dart'; +// import 'messages_legend_widget.dart'; + +// class MessagesBarChart extends StatefulWidget { +// final ChartAnalyticsModel? chartAnalytics; + +// const MessagesBarChart({ +// super.key, +// required this.chartAnalytics, +// }); + +// @override +// State createState() => MessagesBarChartState(); +// } + +// class MessagesBarChartState extends State { +// final double barSpace = 16; +// final List> intervalGroupings = []; + +// @override +// initState() { +// super.initState(); +// } + +// @override +// Widget build(BuildContext context) { +// final flLine = FlLine( +// color: Theme.of(context).dividerColor, +// strokeWidth: 1, +// ); + +// final flTitlesData = FlTitlesData( +// show: true, +// bottomTitles: AxisTitles( +// sideTitles: SideTitles( +// showTitles: true, +// reservedSize: 28, +// getTitlesWidget: bottomTitles, +// ), +// ), +// leftTitles: AxisTitles( +// sideTitles: SideTitles( +// showTitles: true, +// reservedSize: 40, +// getTitlesWidget: leftTitles, +// ), +// ), +// topTitles: const AxisTitles( +// sideTitles: SideTitles(showTitles: false), +// ), +// rightTitles: const AxisTitles( +// sideTitles: SideTitles(showTitles: false), +// ), +// ); +// final barChartData = BarChartData( +// alignment: BarChartAlignment.spaceEvenly, +// barTouchData: BarTouchData( +// enabled: false, +// ), +// // barTouchData: barTouchData, +// titlesData: flTitlesData, +// gridData: FlGridData( +// show: true, +// // checkToShowHorizontalLine: (value) => value % 10 == 0, +// checkToShowHorizontalLine: (value) => true, +// getDrawingHorizontalLine: (value) => flLine, +// checkToShowVerticalLine: (value) => false, +// getDrawingVerticalLine: (value) => flLine, +// ), +// borderData: FlBorderData( +// show: false, +// ), +// groupsSpace: barSpace, +// barGroups: barChartGroupData, +// backgroundColor: Theme.of(context).scaffoldBackgroundColor, +// ); +// final barChart = BarChart( +// barChartData, +// swapAnimationDuration: const Duration(milliseconds: 250), +// ); + +// return BarChartCard( +// barChart: barChart, +// loadingData: widget.chartAnalytics == null, +// legend: const MessagesLegendsListWidget(), +// ); +// } + +// bool showLabelBasedOnTimeSpan( +// TimeSpan timeSpan, +// TimeSeriesInterval current, +// TimeSeriesInterval? last, +// int labelIndex, +// ) { +// switch (timeSpan) { +// case TimeSpan.day: +// return current.end.hour % 3 == 0; +// case TimeSpan.month: +// if (current.end.month != last?.end.month) { +// return true; +// } +// double width = MediaQuery.of(context).size.width; +// if (FluffyThemes.isColumnMode(context)) { +// width = width - FluffyThemes.navRailWidth - FluffyThemes.columnWidth; +// } +// const int numDays = 28; +// const int minSpacePerDay = 20; +// final int availableSpaces = width ~/ minSpacePerDay; +// final int showAtInterval = (numDays / availableSpaces).floor() + 1; + +// final int lastDayOfCurrentMonth = +// DateTime(current.end.year, current.end.month + 1, 0).day; +// final bool isNextToMonth = labelIndex == 1 || +// current.end.day == 2 || +// current.end.day == lastDayOfCurrentMonth; +// final bool shouldShowNextToMonth = showAtInterval <= 1; +// return (current.end.day % showAtInterval == 0) && +// (!isNextToMonth || shouldShowNextToMonth); +// case TimeSpan.week: +// case TimeSpan.sixmonths: +// case TimeSpan.year: +// default: +// return true; +// } +// } + +// String getLabelBasedOnTimeSpan( +// TimeSpan timeSpan, +// TimeSeriesInterval current, +// TimeSeriesInterval? last, +// int labelIndex, +// ) { +// final bool showLabel = showLabelBasedOnTimeSpan( +// timeSpan, +// current, +// last, +// labelIndex, +// ); + +// if (widget.chartAnalytics == null || !showLabel) { +// return ""; +// } +// if (isInSameGroup(last, current, timeSpan)) { +// return "-"; +// } + +// switch (widget.chartAnalytics?.timeSpan ?? TimeSpan.month) { +// case TimeSpan.day: +// return DateFormat(DateFormat.HOUR).format(current.end); +// case TimeSpan.week: +// return DateFormat(DateFormat.ABBR_WEEKDAY).format(current.end); +// case TimeSpan.month: +// return current.end.month != last?.end.month +// ? DateFormat(DateFormat.ABBR_MONTH).format(current.end) +// : DateFormat(DateFormat.DAY).format(current.end); +// case TimeSpan.sixmonths: +// case TimeSpan.year: +// return DateFormat(DateFormat.ABBR_STANDALONE_MONTH).format(current.end); +// default: +// return ''; +// } +// } + +// Widget bottomTitles(double value, TitleMeta meta) { +// if (widget.chartAnalytics == null) { +// return Container(); +// } +// String text; +// final index = value.toInt(); +// final TimeSpan timeSpan = widget.chartAnalytics?.timeSpan ?? TimeSpan.month; +// final TimeSeriesInterval? last = +// index != 0 ? intervalGroupings[index - 1].last : null; +// final TimeSeriesInterval current = intervalGroupings[index].last; + +// text = getLabelBasedOnTimeSpan(timeSpan, current, last, index); + +// return SideTitleWidget( +// axisSide: meta.axisSide, +// child: Text( +// text, +// style: titleTextStyle(context), +// ), +// ); +// } + +// TextStyle titleTextStyle(context) => TextStyle( +// color: Theme.of(context).textTheme.bodyLarge!.color, +// fontSize: 10, +// ); + +// Widget leftTitles(double value, TitleMeta meta) { +// Widget textWidget; +// if (value != meta.max) { +// textWidget = Text(meta.formattedValue, style: titleTextStyle(context)); +// } else { +// textWidget = const Icon(Icons.chat_bubble, size: 14); +// } +// return SideTitleWidget( +// axisSide: meta.axisSide, +// child: textWidget, +// ); +// } + +// bool isInSameGroup( +// TimeSeriesInterval? t1, +// TimeSeriesInterval t2, +// TimeSpan timeSpan, +// ) { +// final DateTime? date1 = t1?.end; +// final DateTime date2 = t2.end; +// if (timeSpan == TimeSpan.sixmonths || timeSpan == TimeSpan.year) { +// return date1?.month == date2.month; +// } else if (timeSpan == TimeSpan.week) { +// return date1?.day == date2.day; +// } else { +// return false; +// } +// } + +// void makeIntervalGroupings() { +// intervalGroupings.clear(); +// try { +// for (final timeSeriesInterval +// in widget.chartAnalytics?.timeSeries ?? []) { +// //Note: if we decide we'd like to do some sort of grouping in the future, +// // this is where that could happen. Currently, we're just putting one +// // BarChartRod in each BarChartGroup +// final TimeSeriesInterval? last = +// intervalGroupings.isNotEmpty ? intervalGroupings.last.last : null; + +// if (widget.chartAnalytics != null && +// isInSameGroup( +// last, +// timeSeriesInterval, +// widget.chartAnalytics!.timeSpan, +// )) { +// intervalGroupings.last.add(timeSeriesInterval); +// } else { +// intervalGroupings.add([timeSeriesInterval]); +// } +// } +// } catch (err, stack) { +// debugger(when: kDebugMode); +// ErrorHandler.logError(e: err, s: stack); +// } +// } + +// List get barChartGroupData { +// if (widget.chartAnalytics == null) { +// return BarChartPlaceHolderData.getRandomData(context); +// } + +// makeIntervalGroupings(); + +// final List chartData = []; + +// intervalGroupings.asMap().forEach((index, intervalGroup) { +// chartData.add( +// BarChartGroupData( +// x: index, +// barsSpace: barSpace, +// // barRods: intervalGroup.map(constructBarChartRodData).toList(), +// barRods: constructBarChartRodData(intervalGroup), +// ), +// ); +// }); +// return chartData; +// } + +// // BarChartRodData constructBarChartRodData(TimeSeriesInterval timeSeriesInterval) { +// // final double y1 = timeSeriesInterval.spanIT.toDouble(); +// // final double y2 = +// // (timeSeriesInterval.spanIT + timeSeriesInterval.spanIGC).toDouble(); +// // final double y3 = timeSeriesInterval.spanTotal.toDouble(); +// // return BarChartRodData( +// // toY: y3, +// // width: 10.toDouble(), +// // rodStackItems: [ +// // BarChartRodStackItem(0, y1, UseType.ta.color(context)), +// // BarChartRodStackItem(y1, y2, UseType.ga.color(context)), +// // BarChartRodStackItem(y2, y3, UseType.wa.color(context)), +// // ], +// // borderRadius: BorderRadius.zero, +// // ); +// // } + +// List constructBarChartRodData( +// List timeSeriesIntervalGroup, +// ) { +// int y1 = 0; +// int y2 = 0; +// int y3 = 0; +// int y4 = 0; +// for (final e in timeSeriesIntervalGroup) { +// y1 += e.totals.ta; +// y2 += y1 + e.totals.ga; +// y3 += y2 + e.totals.wa; +// y4 += y3 + e.totals.un; +// } +// return [ +// BarChartRodData( +// toY: y4.toDouble(), +// width: 10.toDouble(), +// rodStackItems: [ +// BarChartRodStackItem(0, y1.toDouble(), UseType.ta.color(context)), +// BarChartRodStackItem( +// y1.toDouble(), +// y2.toDouble(), +// UseType.ga.color(context), +// ), +// BarChartRodStackItem( +// y2.toDouble(), +// y3.toDouble(), +// UseType.wa.color(context), +// ), +// BarChartRodStackItem( +// y3.toDouble(), +// y4.toDouble(), +// UseType.un.color(context), +// ), +// ], +// borderRadius: BorderRadius.zero, +// ), +// ]; +// } + +// // BarTouchData get barTouchData => BarTouchData( +// // touchTooltipData: BarTouchTooltipData( +// // fitInsideVertically: true, +// // tooltipBgColor: Colors.blueGrey, +// // getTooltipItem: (group, groupIndex, rod, rodIndex) { +// // return BarTooltipItem( +// // "groupindex $groupIndex rodIndex $rodIndex", +// // const TextStyle( +// // color: Colors.white, +// // fontWeight: FontWeight.bold, +// // fontSize: 18, +// // ), +// // children: [ +// // toolTipText(rod), +// // ], +// // ); +// // }, +// // ), +// // // touchCallback: (FlTouchEvent event, barTouchResponse) { +// // // setState(() { +// // // if (!event.isInterestedForInteractions || +// // // barTouchResponse == null || +// // // barTouchResponse.spot == null) { +// // // touchedIndex = -1; +// // // return; +// // // } +// // // touchedIndex = barTouchResponse.spot!.touchedBarGroupIndex; +// // // }); +// // // }, +// // ); + +// // TextSpan toolTipText(BarChartRodData rodData) { +// // double rodPercentage(int index) { +// // return (rodData.rodStackItems[index].toY - +// // rodData.rodStackItems[index].fromY) / +// // rodData.toY * +// // 100; +// // } + +// // return TextSpan( +// // children: [ +// // const WidgetSpan( +// // child: Icon(Icons.chat_bubble, size: 14), +// // ), +// // TextSpan( +// // text: " ${rodData.toY}", +// // ), +// // TextSpan( +// // text: "/nIT ${rodPercentage(0)}%", +// // style: TextStyle(color: UseType.ta.color(context)), +// // ), +// // TextSpan( +// // text: " IGC ${rodPercentage(1)}%", +// // style: TextStyle(color: UseType.ga.color(context)), +// // ), +// // TextSpan( +// // text: " Direct ${rodPercentage(2)}%", +// // style: TextStyle(color: UseType.wa.color(context)), +// // ), +// // ], +// // ); +// // } +// } diff --git a/lib/pangea/pages/analytics/space_analytics/space_analytics.dart b/lib/pangea/pages/analytics/space_analytics/space_analytics.dart index 50a8cb7c29..3f752bd4ac 100644 --- a/lib/pangea/pages/analytics/space_analytics/space_analytics.dart +++ b/lib/pangea/pages/analytics/space_analytics/space_analytics.dart @@ -1,121 +1,114 @@ -import 'dart:async'; -import 'dart:developer'; +// import 'dart:async'; +// import 'dart:developer'; -import 'package:fluffychat/pangea/constants/pangea_room_types.dart'; -import 'package:fluffychat/pangea/enum/bar_chart_view_enum.dart'; -import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; -import 'package:fluffychat/pangea/models/language_model.dart'; -import 'package:fluffychat/pangea/utils/error_handler.dart'; -import 'package:fluffychat/pangea/widgets/common/list_placeholder.dart'; -import 'package:fluffychat/pangea/widgets/common/p_circular_loader.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:matrix/matrix.dart'; +// import 'package:fluffychat/pangea/constants/pangea_room_types.dart'; +// import 'package:fluffychat/pangea/enum/bar_chart_view_enum.dart'; +// import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; +// import 'package:fluffychat/pangea/models/language_model.dart'; +// import 'package:fluffychat/pangea/utils/error_handler.dart'; +// import 'package:fluffychat/pangea/widgets/common/list_placeholder.dart'; +// import 'package:fluffychat/pangea/widgets/common/p_circular_loader.dart'; +// import 'package:flutter/foundation.dart'; +// import 'package:flutter/material.dart'; +// import 'package:go_router/go_router.dart'; +// import 'package:matrix/matrix.dart'; -import '../../../../widgets/matrix.dart'; -import '../../../utils/sync_status_util_v2.dart'; -import 'space_analytics_view.dart'; +// import '../../../../widgets/matrix.dart'; +// import '../../../utils/sync_status_util_v2.dart'; -class SpaceAnalyticsPage extends StatefulWidget { - final BarChartViewSelection selectedView; - const SpaceAnalyticsPage({super.key, required this.selectedView}); +// class SpaceAnalyticsPage extends StatefulWidget { +// final BarChartViewSelection selectedView; +// const SpaceAnalyticsPage({super.key, required this.selectedView}); - @override - State createState() => SpaceAnalyticsV2Controller(); -} +// @override +// State createState() => SpaceAnalyticsV2Controller(); +// } -class SpaceAnalyticsV2Controller extends State { - bool _initialized = false; - // StreamSubscription? stateSub; - // Timer? refreshTimer; +// class SpaceAnalyticsV2Controller extends State { +// bool _initialized = false; +// // StreamSubscription? stateSub; +// // Timer? refreshTimer; - List chats = []; - List students = []; - String? get spaceId => GoRouterState.of(context).pathParameters['spaceid']; - Room? _spaceRoom; - List targetLanguages = []; +// List chats = []; +// List students = []; +// String? get spaceId => GoRouterState.of(context).pathParameters['spaceid']; +// Room? _spaceRoom; +// List targetLanguages = []; - @override - void initState() { - super.initState(); - Future.delayed(Duration.zero, () async { - if (spaceRoom == null || (!(spaceRoom?.isSpace ?? false))) { - context.go('/rooms'); - } - getChatAndStudents(); - }); - } +// @override +// void initState() { +// super.initState(); +// Future.delayed(Duration.zero, () async { +// if (spaceRoom == null || (!(spaceRoom?.isSpace ?? false))) { +// context.go('/rooms'); +// } +// getChatAndStudents(); +// }); +// } - Room? get spaceRoom { - if (_spaceRoom == null || _spaceRoom!.id != spaceId) { - debugPrint("updating _spaceRoom"); - _spaceRoom = spaceId != null - ? Matrix.of(context).client.getRoomById(spaceId!) - : null; - if (_spaceRoom == null) { - context.go('/rooms/analytics'); - return null; - } - getChatAndStudents().then((_) => setTargetLanguages()); - } - return _spaceRoom; - } +// Room? get spaceRoom { +// if (_spaceRoom == null || _spaceRoom!.id != spaceId) { +// debugPrint("updating _spaceRoom"); +// _spaceRoom = spaceId != null +// ? Matrix.of(context).client.getRoomById(spaceId!) +// : null; +// if (_spaceRoom == null) { +// context.go('/rooms/analytics'); +// return null; +// } +// getChatAndStudents().then((_) => setTargetLanguages()); +// } +// return _spaceRoom; +// } - Future getChatAndStudents() async { - try { - await spaceRoom?.requestParticipants(); +// Future getChatAndStudents() async { +// try { +// await spaceRoom?.requestParticipants(); - if (spaceRoom != null) { - final response = await Matrix.of(context).client.getSpaceHierarchy( - spaceRoom!.id, - ); +// if (spaceRoom != null) { +// final response = await Matrix.of(context).client.getSpaceHierarchy( +// spaceRoom!.id, +// ); - // set the latest fetched full hierarchy in message analytics controller - // we want to avoid calling this endpoint again and again, so whenever the - // data is made available, set it in the controller - MatrixState.pangeaController.analytics - .setLatestHierarchy(_spaceRoom!.id, response); +// students = spaceRoom!.students; +// chats = response.rooms +// .where( +// (room) => +// room.roomId != spaceRoom!.id && +// room.roomType != PangeaRoomTypes.analytics, +// ) +// .toList(); +// chats.sort((a, b) => a.roomType == 'm.space' ? -1 : 1); +// } - students = spaceRoom!.students; - chats = response.rooms - .where( - (room) => - room.roomId != spaceRoom!.id && - room.roomType != PangeaRoomTypes.analytics, - ) - .toList(); - chats.sort((a, b) => a.roomType == 'm.space' ? -1 : 1); - } +// setState(() { +// _initialized = true; +// }); +// } catch (err, s) { +// debugger(when: kDebugMode); +// ErrorHandler.logError(e: err, s: s); +// } +// } - setState(() { - _initialized = true; - }); - } catch (err, s) { - debugger(when: kDebugMode); - ErrorHandler.logError(e: err, s: s); - } - } +// Future setTargetLanguages() async { +// // get a list of language models, sorted by the +// // number of students who are learning that language +// targetLanguages = await spaceRoom?.targetLanguages() ?? []; +// setState(() {}); +// } - Future setTargetLanguages() async { - // get a list of language models, sorted by the - // number of students who are learning that language - targetLanguages = await spaceRoom?.targetLanguages() ?? []; - setState(() {}); - } - - @override - Widget build(BuildContext context) { - if (!_initialized) return const PCircular(); - return PLoadingStatusV2( - // if we everr want it rebuild the whole thing each time (and run initState again) - // but this is computationally expensive! - // key: UniqueKey(), - shimmerChild: const ListPlaceholder(), - // onFinish: () { - // getChatAndStudentAnalytics(context); - // }, - child: SpaceAnalyticsView(this), - ); - } -} +// @override +// Widget build(BuildContext context) { +// if (!_initialized) return const PCircular(); +// return PLoadingStatusV2( +// // if we everr want it rebuild the whole thing each time (and run initState again) +// // but this is computationally expensive! +// // key: UniqueKey(), +// shimmerChild: const ListPlaceholder(), +// // onFinish: () { +// // getChatAndStudentAnalytics(context); +// // }, +// child: SpaceAnalyticsView(this), +// ); +// } +// } diff --git a/lib/pangea/pages/analytics/space_analytics/space_analytics_view.dart b/lib/pangea/pages/analytics/space_analytics/space_analytics_view.dart index c72ec3c266..02c5cb133e 100644 --- a/lib/pangea/pages/analytics/space_analytics/space_analytics_view.dart +++ b/lib/pangea/pages/analytics/space_analytics/space_analytics_view.dart @@ -1,66 +1,66 @@ -import 'package:fluffychat/widgets/matrix.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; +// import 'package:fluffychat/widgets/matrix.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter_gen/gen_l10n/l10n.dart'; -import '../base_analytics.dart'; -import 'space_analytics.dart'; +// import '../base_analytics.dart'; +// import 'space_analytics.dart'; -class SpaceAnalyticsView extends StatelessWidget { - final SpaceAnalyticsV2Controller controller; - const SpaceAnalyticsView(this.controller, {super.key}); +// class SpaceAnalyticsView extends StatelessWidget { +// final SpaceAnalyticsV2Controller controller; +// const SpaceAnalyticsView(this.controller, {super.key}); - @override - Widget build(BuildContext context) { - final String pageTitle = L10n.of(context)!.spaceAnalytics; - final TabData tab1 = TabData( - type: AnalyticsEntryType.room, - icon: Icons.chat_bubble_outline, - items: controller.chats - .map( - (room) => TabItem( - avatar: room.avatarUrl, - displayName: room.name ?? - Matrix.of(context) - .client - .getRoomById(room.roomId) - ?.getLocalizedDisplayname() ?? - "", - id: room.roomId, - ), - ) - .toList(), - ); - final TabData tab2 = TabData( - type: AnalyticsEntryType.student, - icon: Icons.people_outline, - items: controller.students - .map( - (s) => TabItem( - avatar: s.avatarUrl, - displayName: s.calcDisplayname(), - id: s.id, - ), - ) - .toList(), - ); +// @override +// Widget build(BuildContext context) { +// final String pageTitle = L10n.of(context)!.spaceAnalytics; +// final TabData tab1 = TabData( +// type: AnalyticsEntryType.room, +// icon: Icons.chat_bubble_outline, +// items: controller.chats +// .map( +// (room) => TabItem( +// avatar: room.avatarUrl, +// displayName: room.name ?? +// Matrix.of(context) +// .client +// .getRoomById(room.roomId) +// ?.getLocalizedDisplayname() ?? +// "", +// id: room.roomId, +// ), +// ) +// .toList(), +// ); +// final TabData tab2 = TabData( +// type: AnalyticsEntryType.student, +// icon: Icons.people_outline, +// items: controller.students +// .map( +// (s) => TabItem( +// avatar: s.avatarUrl, +// displayName: s.calcDisplayname(), +// id: s.id, +// ), +// ) +// .toList(), +// ); - return controller.spaceId != null - ? BaseAnalyticsPage( - selectedView: controller.widget.selectedView, - pageTitle: pageTitle, - tabs: [tab1, tab2], - alwaysSelected: AnalyticsSelected( - controller.spaceId!, - AnalyticsEntryType.space, - controller.spaceRoom?.name ?? "", - ), - defaultSelected: AnalyticsSelected( - controller.spaceId!, - AnalyticsEntryType.space, - controller.spaceRoom?.name ?? "", - ), - targetLanguages: controller.targetLanguages, - ) - : const SizedBox(); - } -} +// return controller.spaceId != null +// ? BaseAnalyticsPage( +// selectedView: controller.widget.selectedView, +// pageTitle: pageTitle, +// tabs: [tab1, tab2], +// alwaysSelected: AnalyticsSelected( +// controller.spaceId!, +// AnalyticsEntryType.space, +// controller.spaceRoom?.name ?? "", +// ), +// defaultSelected: AnalyticsSelected( +// controller.spaceId!, +// AnalyticsEntryType.space, +// controller.spaceRoom?.name ?? "", +// ), +// targetLanguages: controller.targetLanguages, +// ) +// : const SizedBox(); +// } +// } diff --git a/lib/pangea/pages/analytics/space_list/space_list.dart b/lib/pangea/pages/analytics/space_list/space_list.dart index e65bb6152e..f9dbe4d311 100644 --- a/lib/pangea/pages/analytics/space_list/space_list.dart +++ b/lib/pangea/pages/analytics/space_list/space_list.dart @@ -1,100 +1,100 @@ -import 'dart:async'; +// import 'dart:async'; -import 'package:fluffychat/pangea/enum/time_span.dart'; -import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart'; -import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; -import 'package:fluffychat/pangea/models/language_model.dart'; -import 'package:fluffychat/pangea/pages/analytics/space_list/space_list_view.dart'; -import 'package:flutter/material.dart'; -import 'package:matrix/matrix.dart'; +// import 'package:fluffychat/pangea/enum/time_span.dart'; +// import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart'; +// import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; +// import 'package:fluffychat/pangea/models/language_model.dart'; +// import 'package:fluffychat/pangea/pages/analytics/space_list/space_list_view.dart'; +// import 'package:flutter/material.dart'; +// import 'package:matrix/matrix.dart'; -import '../../../../widgets/matrix.dart'; -import '../../../controllers/pangea_controller.dart'; -import '../../../utils/sync_status_util_v2.dart'; -import '../../../widgets/common/list_placeholder.dart'; +// import '../../../../widgets/matrix.dart'; +// import '../../../controllers/pangea_controller.dart'; +// import '../../../utils/sync_status_util_v2.dart'; +// import '../../../widgets/common/list_placeholder.dart'; -class AnalyticsSpaceList extends StatefulWidget { - const AnalyticsSpaceList({super.key}); +// class AnalyticsSpaceList extends StatefulWidget { +// const AnalyticsSpaceList({super.key}); - @override - State createState() => AnalyticsSpaceListController(); -} +// @override +// State createState() => AnalyticsSpaceListController(); +// } -class AnalyticsSpaceListController extends State { - PangeaController pangeaController = MatrixState.pangeaController; - List spaces = []; - StreamSubscription? stateSub; - List targetLanguages = []; +// class AnalyticsSpaceListController extends State { +// PangeaController pangeaController = MatrixState.pangeaController; +// List spaces = []; +// StreamSubscription? stateSub; +// List targetLanguages = []; - @override - void initState() { - super.initState(); - setSpaceList().then((_) => setTargetLanguages()); +// @override +// void initState() { +// super.initState(); +// setSpaceList().then((_) => setTargetLanguages()); - // reload dropdowns when their values change in analytics page - stateSub = pangeaController.analytics.stateStream.listen( - (_) => setState(() {}), - ); - } +// // reload dropdowns when their values change in analytics page +// stateSub = pangeaController.analytics.stateStream.listen( +// (_) => setState(() {}), +// ); +// } - @override - void dispose() { - stateSub?.cancel(); - super.dispose(); - } +// @override +// void dispose() { +// stateSub?.cancel(); +// super.dispose(); +// } - StreamController refreshStream = StreamController.broadcast(); +// StreamController refreshStream = StreamController.broadcast(); - Future setSpaceList() async { - final spaceList = await Matrix.of(context).client.spacesImTeaching; - spaces = spaceList - .where( - (space) => !spaceList.any( - (parentSpace) => parentSpace.spaceChildren - .any((child) => child.roomId == space.id), - ), - ) - .toList(); - setState(() {}); - } +// Future setSpaceList() async { +// final spaceList = Matrix.of(context).client.spacesImTeaching; +// spaces = spaceList +// .where( +// (space) => !spaceList.any( +// (parentSpace) => parentSpace.spaceChildren +// .any((child) => child.roomId == space.id), +// ), +// ) +// .toList(); +// setState(() {}); +// } - Future setTargetLanguages() async { - if (spaces.isEmpty) return; - final Map langCounts = {}; - for (final Room space in spaces) { - final List targetLangs = await space.targetLanguages(); - for (final LanguageModel lang in targetLangs) { - langCounts[lang] ??= 0; - langCounts[lang] = langCounts[lang]! + 1; - } - } - targetLanguages = langCounts.entries.map((entry) => entry.key).toList() - ..sort( - (a, b) => langCounts[b]!.compareTo(langCounts[a]!), - ); - setState(() {}); - } +// Future setTargetLanguages() async { +// if (spaces.isEmpty) return; +// final Map langCounts = {}; +// for (final Room space in spaces) { +// final List targetLangs = await space.targetLanguages(); +// for (final LanguageModel lang in targetLangs) { +// langCounts[lang] ??= 0; +// langCounts[lang] = langCounts[lang]! + 1; +// } +// } +// targetLanguages = langCounts.entries.map((entry) => entry.key).toList() +// ..sort( +// (a, b) => langCounts[b]!.compareTo(langCounts[a]!), +// ); +// setState(() {}); +// } - void toggleTimeSpan(BuildContext context, TimeSpan timeSpan) { - pangeaController.analytics.setCurrentAnalyticsTimeSpan(timeSpan); - refreshStream.add(false); - setState(() {}); - } +// void toggleTimeSpan(BuildContext context, TimeSpan timeSpan) { +// pangeaController.analytics.setCurrentAnalyticsTimeSpan(timeSpan); +// refreshStream.add(false); +// setState(() {}); +// } - Future toggleSpaceLang(LanguageModel lang) async { - await pangeaController.analytics.setCurrentAnalyticsLang(lang); - refreshStream.add(false); - setState(() {}); - } +// Future toggleSpaceLang(LanguageModel lang) async { +// await pangeaController.analytics.setCurrentAnalyticsLang(lang); +// refreshStream.add(false); +// setState(() {}); +// } - @override - Widget build(BuildContext context) { - return PLoadingStatusV2( - shimmerChild: const ListPlaceholder(), - child: AnalyticsSpaceListView(this), - onFinish: () { - // getAllClassAnalytics(context); - }, - ); - } -} +// @override +// Widget build(BuildContext context) { +// return PLoadingStatusV2( +// shimmerChild: const ListPlaceholder(), +// child: AnalyticsSpaceListView(this), +// onFinish: () { +// // getAllClassAnalytics(context); +// }, +// ); +// } +// } diff --git a/lib/pangea/pages/analytics/space_list/space_list_view.dart b/lib/pangea/pages/analytics/space_list/space_list_view.dart index 7ef5fb45e7..c9b63a62ca 100644 --- a/lib/pangea/pages/analytics/space_list/space_list_view.dart +++ b/lib/pangea/pages/analytics/space_list/space_list_view.dart @@ -1,89 +1,89 @@ -import 'package:fluffychat/pangea/enum/time_span.dart'; -import 'package:fluffychat/pangea/pages/analytics/analytics_language_button.dart'; -import 'package:fluffychat/pangea/pages/analytics/analytics_list_tile.dart'; -import 'package:fluffychat/pangea/pages/analytics/time_span_menu_button.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:go_router/go_router.dart'; +// import 'package:fluffychat/pangea/enum/time_span.dart'; +// import 'package:fluffychat/pangea/pages/analytics/analytics_language_button.dart'; +// import 'package:fluffychat/pangea/pages/analytics/analytics_list_tile.dart'; +// import 'package:fluffychat/pangea/pages/analytics/time_span_menu_button.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter_gen/gen_l10n/l10n.dart'; +// import 'package:go_router/go_router.dart'; -import '../base_analytics.dart'; -import 'space_list.dart'; +// import '../base_analytics.dart'; +// import 'space_list.dart'; -class AnalyticsSpaceListView extends StatelessWidget { - final AnalyticsSpaceListController controller; - const AnalyticsSpaceListView(this.controller, {super.key}); +// class AnalyticsSpaceListView extends StatelessWidget { +// final AnalyticsSpaceListController controller; +// const AnalyticsSpaceListView(this.controller, {super.key}); - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - centerTitle: true, - title: Text( - L10n.of(context)!.spaceAnalytics, - style: TextStyle( - color: Theme.of(context).textTheme.bodyLarge!.color, - fontSize: 18, - fontWeight: FontWeight.w700, - ), - overflow: TextOverflow.clip, - textAlign: TextAlign.center, - ), - leading: IconButton( - icon: const Icon(Icons.close_outlined), - onPressed: () => context.pop(), - ), - ), - body: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - TimeSpanMenuButton( - value: controller - .pangeaController.analytics.currentAnalyticsTimeSpan, - onChange: (TimeSpan value) => controller.toggleTimeSpan( - context, - value, - ), - ), - AnalyticsLanguageButton( - value: - controller.pangeaController.analytics.currentAnalyticsLang, - onChange: (lang) => controller.toggleSpaceLang(lang), - languages: - controller.pangeaController.pLanguageStore.targetOptions, - ), - ], - ), - Flexible( - child: ListView.builder( - itemCount: controller.spaces.length, - itemBuilder: (context, i) => AnalyticsListTile( - defaultSelected: AnalyticsSelected( - controller.spaces[i].id, - AnalyticsEntryType.space, - controller.spaces[i].name, - ), - avatar: controller.spaces[i].avatar, - selected: AnalyticsSelected( - controller.spaces[i].id, - AnalyticsEntryType.space, - controller.spaces[i].name, - ), - onTap: (selected) { - context.go( - '/rooms/analytics/${selected.id}', - ); - }, - allowNavigateOnSelect: true, - isSelected: false, - pangeaController: controller.pangeaController, - refreshStream: controller.refreshStream, - ), - ), - ), - ], - ), - ); - } -} +// @override +// Widget build(BuildContext context) { +// return Scaffold( +// appBar: AppBar( +// centerTitle: true, +// title: Text( +// L10n.of(context)!.spaceAnalytics, +// style: TextStyle( +// color: Theme.of(context).textTheme.bodyLarge!.color, +// fontSize: 18, +// fontWeight: FontWeight.w700, +// ), +// overflow: TextOverflow.clip, +// textAlign: TextAlign.center, +// ), +// leading: IconButton( +// icon: const Icon(Icons.close_outlined), +// onPressed: () => context.pop(), +// ), +// ), +// body: Column( +// children: [ +// Row( +// mainAxisAlignment: MainAxisAlignment.spaceEvenly, +// children: [ +// TimeSpanMenuButton( +// value: controller +// .pangeaController.analytics.currentAnalyticsTimeSpan, +// onChange: (TimeSpan value) => controller.toggleTimeSpan( +// context, +// value, +// ), +// ), +// AnalyticsLanguageButton( +// value: +// controller.pangeaController.analytics.currentAnalyticsLang, +// onChange: (lang) => controller.toggleSpaceLang(lang), +// languages: +// controller.pangeaController.pLanguageStore.targetOptions, +// ), +// ], +// ), +// Flexible( +// child: ListView.builder( +// itemCount: controller.spaces.length, +// itemBuilder: (context, i) => AnalyticsListTile( +// defaultSelected: AnalyticsSelected( +// controller.spaces[i].id, +// AnalyticsEntryType.space, +// controller.spaces[i].name, +// ), +// avatar: controller.spaces[i].avatar, +// selected: AnalyticsSelected( +// controller.spaces[i].id, +// AnalyticsEntryType.space, +// controller.spaces[i].name, +// ), +// onTap: (selected) { +// context.go( +// '/rooms/analytics/${selected.id}', +// ); +// }, +// allowNavigateOnSelect: true, +// isSelected: false, +// pangeaController: controller.pangeaController, +// refreshStream: controller.refreshStream, +// ), +// ), +// ), +// ], +// ), +// ); +// } +// } diff --git a/lib/pangea/pages/analytics/student_analytics/student_analytics.dart b/lib/pangea/pages/analytics/student_analytics/student_analytics.dart index 007a04d935..3a5309e5e3 100644 --- a/lib/pangea/pages/analytics/student_analytics/student_analytics.dart +++ b/lib/pangea/pages/analytics/student_analytics/student_analytics.dart @@ -1,90 +1,90 @@ -import 'dart:developer'; +// import 'dart:developer'; -import 'package:fluffychat/pangea/constants/language_constants.dart'; -import 'package:fluffychat/pangea/controllers/language_list_controller.dart'; -import 'package:fluffychat/pangea/enum/bar_chart_view_enum.dart'; -import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart'; -import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; -import 'package:fluffychat/pangea/models/language_model.dart'; -import 'package:fluffychat/pangea/widgets/common/list_placeholder.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:matrix/matrix.dart'; +// import 'package:fluffychat/pangea/constants/language_constants.dart'; +// import 'package:fluffychat/pangea/controllers/language_list_controller.dart'; +// import 'package:fluffychat/pangea/enum/bar_chart_view_enum.dart'; +// import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart'; +// import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; +// import 'package:fluffychat/pangea/models/language_model.dart'; +// import 'package:fluffychat/pangea/widgets/common/list_placeholder.dart'; +// import 'package:flutter/foundation.dart'; +// import 'package:flutter/material.dart'; +// import 'package:matrix/matrix.dart'; -import '../../../../widgets/matrix.dart'; -import '../../../controllers/pangea_controller.dart'; -import '../../../utils/sync_status_util_v2.dart'; -import '../base_analytics.dart'; -import 'student_analytics_view.dart'; +// import '../../../../widgets/matrix.dart'; +// import '../../../controllers/pangea_controller.dart'; +// import '../../../utils/sync_status_util_v2.dart'; +// import '../base_analytics.dart'; +// import 'student_analytics_view.dart'; -class StudentAnalyticsPage extends StatefulWidget { - final BarChartViewSelection selectedView; - const StudentAnalyticsPage({super.key, required this.selectedView}); +// class StudentAnalyticsPage extends StatefulWidget { +// final BarChartViewSelection selectedView; +// const StudentAnalyticsPage({super.key, required this.selectedView}); - @override - State createState() => StudentAnalyticsController(); -} +// @override +// State createState() => StudentAnalyticsController(); +// } -class StudentAnalyticsController extends State { - final PangeaController _pangeaController = MatrixState.pangeaController; - AnalyticsSelected? selected; +// class StudentAnalyticsController extends State { +// final PangeaController _pangeaController = MatrixState.pangeaController; +// AnalyticsSelected? selected; - @override - void initState() { - super.initState(); - } +// @override +// void initState() { +// super.initState(); +// } - @override - void dispose() { - super.dispose(); - } +// @override +// void dispose() { +// super.dispose(); +// } - List _chats = []; - List get chats { - if (_chats.isEmpty) { - _pangeaController.matrixState.client.chatsImAStudentIn.then((result) { - setState(() => _chats = result); - }); - } - return _chats; - } +// List _chats = []; +// List get chats { +// if (_chats.isEmpty) { +// _pangeaController.matrixState.client.chatsImAStudentIn.then((result) { +// setState(() => _chats = result); +// }); +// } +// return _chats; +// } - List get spaces => - _pangeaController.matrixState.client.spacesImAStudentIn; +// List get spaces => +// _pangeaController.matrixState.client.spacesImAStudentIn; - String? get userId { - final id = _pangeaController.matrixState.client.userID; - debugger(when: kDebugMode && id == null); - return id; - } +// String? get userId { +// final id = _pangeaController.matrixState.client.userID; +// debugger(when: kDebugMode && id == null); +// return id; +// } - List get targetLanguages { - final LanguageModel? l2 = - _pangeaController.languageController.activeL2Model(); - final List analyticsRoomLangs = - _pangeaController.matrixState.client.allMyAnalyticsRooms - .map((analyticsRoom) => analyticsRoom.madeForLang) - .where((langCode) => langCode != null) - .map((langCode) => PangeaLanguage.byLangCode(langCode!)) - .where( - (langModel) => langModel.langCode != LanguageKeys.unknownLanguage, - ) - .toList(); - if (l2 != null) { - analyticsRoomLangs.add(l2); - } - return analyticsRoomLangs.toSet().toList(); - } +// List get targetLanguages { +// final LanguageModel? l2 = +// _pangeaController.languageController.activeL2Model(); +// final List analyticsRoomLangs = +// _pangeaController.matrixState.client.allMyAnalyticsRooms +// .map((analyticsRoom) => analyticsRoom.madeForLang) +// .where((langCode) => langCode != null) +// .map((langCode) => PangeaLanguage.byLangCode(langCode!)) +// .where( +// (langModel) => langModel.langCode != LanguageKeys.unknownLanguage, +// ) +// .toList(); +// if (l2 != null) { +// analyticsRoomLangs.add(l2); +// } +// return analyticsRoomLangs.toSet().toList(); +// } - @override - Widget build(BuildContext context) { - return PLoadingStatusV2( - // if we everr want it rebuild the whole thing each time (and run initState again) - // but this is computationally expensive! - // key: UniqueKey(), - shimmerChild: const ListPlaceholder(), - // onFinish: initialize, - child: StudentAnalyticsView(this), - ); - } -} +// @override +// Widget build(BuildContext context) { +// return PLoadingStatusV2( +// // if we everr want it rebuild the whole thing each time (and run initState again) +// // but this is computationally expensive! +// // key: UniqueKey(), +// shimmerChild: const ListPlaceholder(), +// // onFinish: initialize, +// child: StudentAnalyticsView(this), +// ); +// } +// } diff --git a/lib/pangea/pages/analytics/student_analytics/student_analytics_view.dart b/lib/pangea/pages/analytics/student_analytics/student_analytics_view.dart index 6ea7548919..a778f35d84 100644 --- a/lib/pangea/pages/analytics/student_analytics/student_analytics_view.dart +++ b/lib/pangea/pages/analytics/student_analytics/student_analytics_view.dart @@ -1,66 +1,66 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter_gen/gen_l10n/l10n.dart'; -import '../../../../utils/matrix_sdk_extensions/matrix_locals.dart'; -import '../base_analytics.dart'; -import 'student_analytics.dart'; +// import '../../../../utils/matrix_sdk_extensions/matrix_locals.dart'; +// import '../base_analytics.dart'; +// import 'student_analytics.dart'; -class StudentAnalyticsView extends StatelessWidget { - final StudentAnalyticsController controller; - const StudentAnalyticsView(this.controller, {super.key}); +// class StudentAnalyticsView extends StatelessWidget { +// final StudentAnalyticsController controller; +// const StudentAnalyticsView(this.controller, {super.key}); - @override - Widget build(BuildContext context) { - final String pageTitle = L10n.of(context)!.myLearning; - final TabData chatTabData = TabData( - type: AnalyticsEntryType.room, - icon: Icons.chat_bubble_outline, - items: (controller.chats) - .map( - (c) => TabItem( - avatar: c.avatar, - displayName: - c.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)), - id: c.id, - ), - ) - .toList(), - allowNavigateOnSelect: false, - ); - final TabData classTabData = TabData( - type: AnalyticsEntryType.space, - icon: Icons.workspaces, - items: (controller.spaces ?? []) - .map( - (c) => TabItem( - avatar: c.avatar, - displayName: - c.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)), - id: c.id, - ), - ) - .toList(), - allowNavigateOnSelect: false, - ); +// @override +// Widget build(BuildContext context) { +// final String pageTitle = L10n.of(context)!.myLearning; +// final TabData chatTabData = TabData( +// type: AnalyticsEntryType.room, +// icon: Icons.chat_bubble_outline, +// items: (controller.chats) +// .map( +// (c) => TabItem( +// avatar: c.avatar, +// displayName: +// c.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)), +// id: c.id, +// ), +// ) +// .toList(), +// allowNavigateOnSelect: false, +// ); +// final TabData classTabData = TabData( +// type: AnalyticsEntryType.space, +// icon: Icons.workspaces, +// items: (controller.spaces ?? []) +// .map( +// (c) => TabItem( +// avatar: c.avatar, +// displayName: +// c.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)), +// id: c.id, +// ), +// ) +// .toList(), +// allowNavigateOnSelect: false, +// ); - return controller.userId != null - ? BaseAnalyticsPage( - selectedView: controller.widget.selectedView, - pageTitle: pageTitle, - tabs: [chatTabData, classTabData], - alwaysSelected: AnalyticsSelected( - controller.userId!, - AnalyticsEntryType.student, - L10n.of(context)!.allChatsAndClasses, - ), - myAnalyticsController: controller, - defaultSelected: AnalyticsSelected( - controller.userId!, - AnalyticsEntryType.student, - L10n.of(context)!.allChatsAndClasses, - ), - targetLanguages: controller.targetLanguages, - ) - : const SizedBox(); - } -} +// return controller.userId != null +// ? BaseAnalyticsPage( +// selectedView: controller.widget.selectedView, +// pageTitle: pageTitle, +// tabs: [chatTabData, classTabData], +// alwaysSelected: AnalyticsSelected( +// controller.userId!, +// AnalyticsEntryType.student, +// L10n.of(context)!.allChatsAndClasses, +// ), +// myAnalyticsController: controller, +// defaultSelected: AnalyticsSelected( +// controller.userId!, +// AnalyticsEntryType.student, +// L10n.of(context)!.allChatsAndClasses, +// ), +// targetLanguages: controller.targetLanguages, +// ) +// : const SizedBox(); +// } +// } diff --git a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart new file mode 100644 index 0000000000..ae6b13e300 --- /dev/null +++ b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart @@ -0,0 +1,195 @@ +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; +import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; +import 'package:fluffychat/pangea/enum/progress_indicators_enum.dart'; +import 'package:fluffychat/pangea/enum/time_span.dart'; +import 'package:fluffychat/pangea/pages/analytics/base_analytics.dart'; +import 'package:fluffychat/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart'; +import 'package:fluffychat/utils/string_color.dart'; +import 'package:fluffychat/widgets/avatar.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:matrix/matrix.dart'; + +/// A summary of "My Analytics" shown at the top of the chat list +/// It shows a variety of progress indicators such as +/// messages sent, words used, and error types, which can +/// be clicked to access more fine-grained analytics data. +class LearningProgressIndicators extends StatefulWidget { + const LearningProgressIndicators({ + super.key, + }); + + @override + LearningProgressIndicatorsState createState() => + LearningProgressIndicatorsState(); +} + +class LearningProgressIndicatorsState + extends State { + final PangeaController _pangeaController = MatrixState.pangeaController; + int? wordsUsed; + int? errorTypes; + + @override + void initState() { + super.initState(); + setData(); + } + + AnalyticsSelected get defaultSelected => AnalyticsSelected( + _pangeaController.matrixState.client.userID!, + AnalyticsEntryType.student, + "", + ); + + Future setData() async { + await getNumLemmasUsed(); + setState(() {}); + } + + Future getNumLemmasUsed() async { + final constructs = await _pangeaController.analytics.getConstructs( + defaultSelected: defaultSelected, + timeSpan: TimeSpan.forever, + ); + if (constructs == null) { + errorTypes = 0; + wordsUsed = 0; + return; + } + + final List errorLemmas = []; + final List vocabLemmas = []; + for (final event in constructs) { + for (final use in event.content.uses) { + if (use.lemma == null) continue; + switch (use.constructType) { + case ConstructTypeEnum.grammar: + errorLemmas.add(use.lemma!); + break; + case ConstructTypeEnum.vocab: + vocabLemmas.add(use.lemma!); + break; + default: + break; + } + } + } + errorTypes = errorLemmas.toSet().length; + wordsUsed = vocabLemmas.toSet().length; + } + + int? getProgressPoints(ProgressIndicatorEnum indicator) { + switch (indicator) { + case ProgressIndicatorEnum.wordsUsed: + return wordsUsed; + case ProgressIndicatorEnum.errorTypes: + return errorTypes; + case ProgressIndicatorEnum.level: + return level; + } + } + + int get xpPoints { + final points = [ + wordsUsed ?? 0, + errorTypes ?? 0, + ]; + return points.reduce((a, b) => a + b); + } + + int get level => xpPoints ~/ 100; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 36, + vertical: 16, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + FutureBuilder( + future: + _pangeaController.matrixState.client.getProfileFromUserId( + _pangeaController.matrixState.client.userID!, + ), + builder: (context, snapshot) { + final mxid = Matrix.of(context).client.userID ?? + L10n.of(context)!.user; + return Avatar( + name: snapshot.data?.displayName ?? mxid.localpart ?? mxid, + mxContent: snapshot.data?.avatarUrl, + ); + }, + ), + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: ProgressIndicatorEnum.values + .where( + (indicator) => indicator != ProgressIndicatorEnum.level, + ) + .map( + (indicator) => ProgressIndicatorBadge( + points: getProgressPoints(indicator), + onTap: () {}, + progressIndicator: indicator, + ), + ) + .toList(), + ), + ), + ], + ), + const SizedBox(height: 4), + SizedBox( + height: 35, + child: Stack( + alignment: Alignment.centerLeft, + children: [ + Positioned( + right: 0, + child: Row( + children: [ + SizedBox( + width: FluffyThemes.columnWidth - (36 * 2) - 25, + child: LinearProgressIndicator( + value: (xpPoints % 100) / 100, + color: Theme.of(context).colorScheme.primary, + backgroundColor: + Theme.of(context).colorScheme.onPrimary, + minHeight: 15, + borderRadius: + BorderRadius.circular(AppConfig.borderRadius), + ), + ), + ], + ), + ), + Positioned( + left: 0, + child: CircleAvatar( + backgroundColor: "$level $xpPoints".lightColorAvatar, + radius: 16, + child: Text( + "$level", + style: const TextStyle(color: Colors.white), + ), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart b/lib/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart new file mode 100644 index 0000000000..93e932aa20 --- /dev/null +++ b/lib/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart @@ -0,0 +1,51 @@ +import 'package:fluffychat/pangea/enum/progress_indicators_enum.dart'; +import 'package:flutter/material.dart'; + +/// A badge that represents one learning progress indicator (i.e., construct uses) +class ProgressIndicatorBadge extends StatelessWidget { + final int? points; + final VoidCallback onTap; + final ProgressIndicatorEnum progressIndicator; + + const ProgressIndicatorBadge({ + super.key, + required this.points, + required this.onTap, + required this.progressIndicator, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 5), + child: Tooltip( + message: progressIndicator.tooltip(context), + child: InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + progressIndicator.icon, + color: progressIndicator.color(context), + ), + const SizedBox(width: 5), + points != null + ? Text( + points.toString(), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ) + : const CircularProgressIndicator.adaptive(), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/utils/client_manager.dart b/lib/utils/client_manager.dart index 808dfd9fca..5ea52f5e5e 100644 --- a/lib/utils/client_manager.dart +++ b/lib/utils/client_manager.dart @@ -139,7 +139,6 @@ abstract class ClientManager { timeline: StateFilter( notTypes: [ PangeaEventTypes.construct, - PangeaEventTypes.summaryAnalytics, ], ), ), From bead112d0d3616799ce47ff217b172549d0ecbe3 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 29 Jul 2024 15:25:30 -0400 Subject: [PATCH 148/288] update analytics sending data on message send, instead of listening to onSync stream --- lib/pages/chat/chat.dart | 7 ++ lib/pages/chat_list/chat_list.dart | 1 - .../controllers/my_analytics_controller.dart | 83 +++++++------------ .../room_analytics_extension.dart | 12 +++ 4 files changed, 47 insertions(+), 56 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index f4ab25c6f6..4cbcc8abfb 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -638,6 +638,13 @@ class ChatController extends State .then( (String? msgEventId) async { // #Pangea + // There's a listen in my_analytics_controller that decides when to auto-update + // analytics based on when / how many messages the logged in user send. This + // stream sends the data for newly sent messages. + if (msgEventId != null) { + pangeaController.myAnalytics.setState(data: {'eventID': msgEventId}); + } + if (previousEdit != null) { pangeaEditingEvent = previousEdit; } diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 8f5515fc41..77546522c9 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -934,7 +934,6 @@ class ChatListController extends State // TODO try not to await so much GoogleAnalytics.analyticsUserUpdate(client.userID); await pangeaController.subscriptionController.initialize(); - await pangeaController.myAnalytics.initialize(); pangeaController.afterSyncAndFirstLoginInitialization(context); await pangeaController.inviteBotToExistingSpaces(); await pangeaController.setPangeaPushRules(); diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart index a13397e86a..20c9a28034 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/my_analytics_controller.dart @@ -3,6 +3,7 @@ import 'dart:developer'; import 'package:fluffychat/pangea/constants/local.key.dart'; import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; +import 'package:fluffychat/pangea/controllers/base_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; @@ -17,7 +18,7 @@ import 'package:sentry_flutter/sentry_flutter.dart'; /// handles the processing of analytics for /// 1) messages sent by the user and /// 2) constructs used by the user, both in sending messages and doing practice activities -class MyAnalyticsController { +class MyAnalyticsController extends BaseController { late PangeaController _pangeaController; Timer? _updateTimer; @@ -33,27 +34,25 @@ class MyAnalyticsController { MyAnalyticsController(PangeaController pangeaController) { _pangeaController = pangeaController; - } - - /// adds the listener that handles when to run automatic updates - /// to analytics - either after a certain number of messages sent - /// received or after a certain amount of time [_timeSinceUpdate] without an update - Future initialize() async { - final lastUpdated = await _refreshAnalyticsIfOutdated(); - - // listen for new messages and updateAnalytics timer - // we are doing this in an attempt to update analytics when activitiy is low - // both in messages sent by this client and other clients that you're connected with - // doesn't account for messages sent by other clients that you're not connected with - _client.onSync.stream - .where((SyncUpdate update) => update.rooms?.join != null) - .listen((update) { - updateAnalyticsTimer(update, lastUpdated); + _refreshAnalyticsIfOutdated(); + + // Listen to a stream that provides the eventIDs + // of new messages sent by the logged in user + stateStream + .where((data) => data is Map && data.containsKey("eventID")) + .listen((data) { + updateAnalyticsTimer(data['eventID']); }); } /// If analytics haven't been updated in the last day, update them Future _refreshAnalyticsIfOutdated() async { + /// wait for the initial sync to finish, so the + /// timeline data from analytics rooms is accurate + if (_client.prevBatch == null) { + await _client.onSync.stream.first; + } + DateTime? lastUpdated = await _pangeaController.analytics.myAnalyticsLastUpdated(); final DateTime yesterday = DateTime.now().subtract(_timeSinceUpdate); @@ -68,44 +67,18 @@ class MyAnalyticsController { Client get _client => _pangeaController.matrixState.client; - /// Given an update from sync stream, check if the update contains - /// messages for which analytics will be saved. If so, reset the timer + /// Given an newly sent message, reset the timer /// and add the event ID to the cache of un-added event IDs - void updateAnalyticsTimer(SyncUpdate update, DateTime? lastUpdated) { - for (final entry in update.rooms!.join!.entries) { - final Room room = _client.getRoomById(entry.key)!; - - // get the new events in this sync that are messages - final List? events = entry.value.timeline?.events - ?.map((event) => Event.fromMatrixEvent(event, room)) - .where((event) => hasUserAnalyticsToCache(event, lastUpdated)) - .toList(); - - // add their event IDs to the cache of un-added event IDs - if (events == null || events.isEmpty) continue; - for (final event in events) { - addMessageSinceUpdate(event.eventId); - } - - // cancel the last timer that was set on message event and - // reset it to fire after _minutesBeforeUpdate minutes - _updateTimer?.cancel(); - _updateTimer = Timer(Duration(minutes: _minutesBeforeUpdate), () { - debugPrint("timer fired, updating analytics"); - updateAnalytics(); - }); - } - } - - // checks if event from sync update is a message that should have analytics - bool hasUserAnalyticsToCache(Event event, DateTime? lastUpdated) { - return event.senderId == _client.userID && - (lastUpdated == null || event.originServerTs.isAfter(lastUpdated)) && - event.type == EventTypes.Message && - event.messageType == MessageTypes.Text && - !(event.eventId.contains("web") && - !(event.eventId.contains("android")) && - !(event.eventId.contains("iOS"))); + void updateAnalyticsTimer(String newEventId) { + addMessageSinceUpdate(newEventId); + + // cancel the last timer that was set on message event and + // reset it to fire after _minutesBeforeUpdate minutes + _updateTimer?.cancel(); + _updateTimer = Timer(Duration(minutes: _minutesBeforeUpdate), () { + debugPrint("timer fired, updating analytics"); + updateAnalytics(); + }); } // adds an event ID to the cache of un-added event IDs @@ -332,7 +305,7 @@ class MyAnalyticsController { // (allRecentMessages.isNotEmpty || recentActivityRecords.isNotEmpty), // ); - if (recentConstructUses.isNotEmpty) { + if (recentConstructUses.isNotEmpty || l2AnalyticsLastUpdated == null) { await analyticsRoom.sendConstructsEvent( recentConstructUses, ); diff --git a/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart b/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart index dabbd65bf5..c09e01a65e 100644 --- a/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart @@ -195,6 +195,18 @@ extension AnalyticsRoomExtension on Room { Future sendConstructsEvent( List uses, ) async { + // It's possible that the user has no info to send yet, but to prevent trying + // to load the data over and over again, we'll sometimes send an empty event to + // indicate that we have checked and there was no data. + if (uses.isEmpty) { + final constructsModel = ConstructAnalyticsModel(uses: []); + await sendEvent( + constructsModel.toJson(), + type: PangeaEventTypes.construct, + ); + return; + } + // these events can get big, so we chunk them to prevent hitting the max event size. // go through each of the uses being sent and add them to the current chunk until // the size (in bytes) of the current chunk is greater than the max event size, then From e7d176d1827f5fc2a06982b5f821a2fac4424d43 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 30 Jul 2024 10:21:33 -0400 Subject: [PATCH 149/288] moved functions for constructing constructs out of pangea message event so they can be created without direct access to the representation event --- .../pangea_message_event.dart | 160 ++---------------- .../practice_activity_record_event.dart | 8 +- .../models/analytics/constructs_model.dart | 68 +++++--- lib/pangea/models/choreo_record.dart | 100 +++++++++++ lib/pangea/models/lemma.dart | 18 ++ .../models/representation_content_model.dart | 116 +++++++++++++ 6 files changed, 292 insertions(+), 178 deletions(-) diff --git a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart index f1c9e50823..06359a24b3 100644 --- a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart +++ b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart @@ -2,20 +2,15 @@ import 'dart:convert'; import 'dart:developer'; import 'package:collection/collection.dart'; -import 'package:fluffychat/pangea/constants/choreo_constants.dart'; import 'package:fluffychat/pangea/constants/model_keys.dart'; import 'package:fluffychat/pangea/controllers/text_to_speech_controller.dart'; import 'package:fluffychat/pangea/enum/audio_encoding_enum.dart'; -import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; -import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_representation_event.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/practice_activity_event.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; import 'package:fluffychat/pangea/models/choreo_record.dart'; -import 'package:fluffychat/pangea/models/lemma.dart'; import 'package:fluffychat/pangea/models/pangea_match_model.dart'; -import 'package:fluffychat/pangea/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/models/representation_content_model.dart'; import 'package:fluffychat/pangea/models/space_model.dart'; import 'package:fluffychat/pangea/models/speech_to_text_models.dart'; @@ -673,158 +668,25 @@ class PangeaMessageEvent { List get allConstructUses => [..._grammarConstructUses, ..._vocabUses, ..._itStepsToConstructUses]; - /// Returns a list of [OneConstructUse] from itSteps for which the continuance - /// was selected or ignored. Correct selections are considered in the tokens - /// flow. Once all continuances have lemmas, we can do both correct and incorrect - /// in this flow. It actually doesn't do anything at all right now, because the - /// choregrapher is not returning lemmas for continuances. This is a TODO. - /// So currently only the lemmas can be gotten from the tokens for choices that - /// are actually in the final message. - List get _itStepsToConstructUses { - final List uses = []; - if (originalSent?.choreo == null) return uses; - - for (final itStep in originalSent!.choreo!.itSteps) { - for (final continuance in itStep.continuances) { - final List tokensToSave = - continuance.tokens.where((t) => t.lemma.saveVocab).toList(); - - if (originalSent!.choreo!.finalMessage.contains(continuance.text)) { - continue; - } - if (continuance.wasClicked) { - //PTODO - account for end of flow score - if (continuance.level != ChoreoConstants.levelThresholdForGreen) { - for (final token in tokensToSave) { - uses.add( - _lemmaToVocabUse( - token.lemma, - ConstructUseTypeEnum.incIt, - ), - ); - } - } - } else { - if (continuance.level != ChoreoConstants.levelThresholdForGreen) { - for (final token in tokensToSave) { - uses.add( - _lemmaToVocabUse( - token.lemma, - ConstructUseTypeEnum.ignIt, - ), - ); - } - } - } - } - } - return uses; - } + /// Returns a list of [OneConstructUse] from itSteps + List get _itStepsToConstructUses => + originalSent?.choreo?.itStepsToConstructUses(event: event) ?? []; /// get construct uses of type vocab for the message List get _vocabUses { - final List uses = []; - - // missing vital info so return - if (event.roomId == null || originalSent?.tokens == null) { - // debugger(when: kDebugMode); - return uses; - } - - // for each token, record whether selected in ga, ta, or wa - for (final token in originalSent!.tokens! - .where((token) => token.lemma.saveVocab) - .toList()) { - uses.add(_getVocabUseForToken(token)); - } - - return uses; - } - - /// Returns a [OneConstructUse] for the given [token] - /// If there is no [originalSent] or [originalSent.choreo], the [token] is - /// considered to be a [ConstructUseTypeEnum.wa] as long as it matches the target language. - /// Later on, we may want to consider putting it in some category of like 'pending' - /// If the [token] is in the [originalSent.choreo.acceptedOrIgnoredMatch], - /// it is considered to be a [ConstructUseTypeEnum.ga]. - /// If the [token] is in the [originalSent.choreo.acceptedOrIgnoredMatch.choices], - /// it is considered to be a [ConstructUseTypeEnum.corIt]. - /// If the [token] is not included in any choreoStep, it is considered to be a [ConstructUseTypeEnum.wa]. - OneConstructUse _getVocabUseForToken(PangeaToken token) { - if (originalSent?.choreo == null) { - final bool inUserL2 = originalSent?.langCode == l2Code; - return _lemmaToVocabUse( - token.lemma, - inUserL2 ? ConstructUseTypeEnum.wa : ConstructUseTypeEnum.unk, + if (originalSent?.tokens != null) { + return originalSent!.content.vocabUses( + event: event, + choreo: originalSent!.choreo, + tokens: originalSent!.tokens!, ); } - - for (final step in originalSent!.choreo!.choreoSteps) { - /// if 1) accepted match 2) token is in the replacement and 3) replacement - /// is in the overall step text, then token was a ga - if (step.acceptedOrIgnoredMatch?.status == PangeaMatchStatus.accepted && - (step.acceptedOrIgnoredMatch!.match.choices?.any( - (r) => - r.value.contains(token.text.content) && - step.text.contains(r.value), - ) ?? - false)) { - return _lemmaToVocabUse(token.lemma, ConstructUseTypeEnum.ga); - } - if (step.itStep != null) { - final bool pickedThroughIT = - step.itStep!.chosenContinuance?.text.contains(token.text.content) ?? - false; - if (pickedThroughIT) { - return _lemmaToVocabUse(token.lemma, ConstructUseTypeEnum.corIt); - //PTODO - check if added via custom input in IT flow - } - } - } - return _lemmaToVocabUse(token.lemma, ConstructUseTypeEnum.wa); + return []; } - OneConstructUse _lemmaToVocabUse( - Lemma lemma, - ConstructUseTypeEnum type, - ) => - OneConstructUse( - useType: type, - chatId: event.roomId!, - timeStamp: event.originServerTs, - lemma: lemma.text, - form: lemma.form, - msgId: event.eventId, - constructType: ConstructTypeEnum.vocab, - ); - /// get construct uses of type grammar for the message - List get _grammarConstructUses { - final List uses = []; - - if (originalSent?.choreo == null || event.roomId == null) return uses; - - for (final step in originalSent!.choreo!.choreoSteps) { - if (step.acceptedOrIgnoredMatch?.status == PangeaMatchStatus.accepted) { - final String name = step.acceptedOrIgnoredMatch!.match.rule?.id ?? - step.acceptedOrIgnoredMatch!.match.shortMessage ?? - step.acceptedOrIgnoredMatch!.match.type.typeName.name; - uses.add( - OneConstructUse( - useType: ConstructUseTypeEnum.ga, - chatId: event.roomId!, - timeStamp: event.originServerTs, - lemma: name, - form: name, - msgId: event.eventId, - constructType: ConstructTypeEnum.grammar, - id: "${event.eventId}_${step.acceptedOrIgnoredMatch!.match.offset}_${step.acceptedOrIgnoredMatch!.match.length}", - ), - ); - } - } - return uses; - } + List get _grammarConstructUses => + originalSent?.choreo?.grammarConstructUses(event: event) ?? []; } class URLFinder { diff --git a/lib/pangea/matrix_event_wrappers/practice_activity_record_event.dart b/lib/pangea/matrix_event_wrappers/practice_activity_record_event.dart index 77b4948fdd..d0f11da3c2 100644 --- a/lib/pangea/matrix_event_wrappers/practice_activity_record_event.dart +++ b/lib/pangea/matrix_event_wrappers/practice_activity_record_event.dart @@ -72,9 +72,11 @@ class PracticeActivityRecordEvent { //TODO - find form of construct within the message //this is related to the feature of highlighting the target construct in the message form: construct.lemma, - chatId: event.roomId ?? practiceEvent.roomId ?? timeline.room.id, - msgId: practiceActivity.parentMessageId, - timeStamp: event.originServerTs, + metadata: ConstructUseMetaData( + roomId: event.roomId ?? practiceEvent.roomId ?? timeline.room.id, + eventId: practiceActivity.parentMessageId, + timeStamp: event.originServerTs, + ), ), ); } diff --git a/lib/pangea/models/analytics/constructs_model.dart b/lib/pangea/models/analytics/constructs_model.dart index ff6b55aefb..4346ec1f1b 100644 --- a/lib/pangea/models/analytics/constructs_model.dart +++ b/lib/pangea/models/analytics/constructs_model.dart @@ -37,12 +37,14 @@ class ConstructAnalyticsModel { for (final useData in lemmaUses) { final use = OneConstructUse( useType: ConstructUseTypeEnum.ga, - chatId: useData["chatId"], - timeStamp: DateTime.parse(useData["timeStamp"]), lemma: lemma, form: useData["form"], - msgId: useData["msgId"], constructType: ConstructTypeEnum.grammar, + metadata: ConstructUseMetaData( + eventId: useData["msgId"], + roomId: useData["chatId"], + timeStamp: DateTime.parse(useData["timeStamp"]), + ), ); uses.add(use); } @@ -69,50 +71,64 @@ class ConstructAnalyticsModel { } } +class ConstructUses { + final List uses; + final ConstructTypeEnum constructType; + final String lemma; + + ConstructUses({ + required this.uses, + required this.constructType, + required this.lemma, + }); +} + class OneConstructUse { String? lemma; ConstructTypeEnum? constructType; String? form; ConstructUseTypeEnum useType; - String chatId; - String? msgId; - DateTime timeStamp; String? id; + ConstructUseMetaData metadata; OneConstructUse({ required this.useType, - required this.chatId, - required this.timeStamp, required this.lemma, required this.form, - required this.msgId, required this.constructType, + required this.metadata, this.id, }); + String get chatId => metadata.roomId; + String get msgId => metadata.eventId!; + DateTime get timeStamp => metadata.timeStamp; + factory OneConstructUse.fromJson(Map json) { return OneConstructUse( useType: ConstructUseTypeEnum.values .firstWhere((e) => e.string == json['useType']), - chatId: json['chatId'], - timeStamp: DateTime.parse(json['timeStamp']), lemma: json['lemma'], form: json['form'], - msgId: json['msgId'], constructType: json['constructType'] != null ? ConstructTypeUtil.fromString(json['constructType']) : null, id: json['id'], + metadata: ConstructUseMetaData( + eventId: json['msgId'], + roomId: json['chatId'], + timeStamp: DateTime.parse(json['timeStamp']), + ), ); } Map toJson([bool condensed = false]) { final Map data = { 'useType': useType.string, - 'chatId': chatId, - 'timeStamp': timeStamp.toIso8601String(), + 'chatId': metadata.roomId, + 'timeStamp': metadata.timeStamp.toIso8601String(), 'form': form, - 'msgId': msgId, + 'msgId': metadata.eventId, }; if (!condensed && lemma != null) data['lemma'] = lemma!; if (!condensed && constructType != null) { @@ -124,24 +140,24 @@ class OneConstructUse { } Room? getRoom(Client client) { - return client.getRoomById(chatId); + return client.getRoomById(metadata.roomId); } Future getEvent(Client client) async { final Room? room = getRoom(client); - if (room == null || msgId == null) return null; - return room.getEventById(msgId!); + if (room == null || metadata.eventId == null) return null; + return room.getEventById(metadata.eventId!); } } -class ConstructUses { - final List uses; - final ConstructTypeEnum constructType; - final String lemma; +class ConstructUseMetaData { + String? eventId; + String roomId; + DateTime timeStamp; - ConstructUses({ - required this.uses, - required this.constructType, - required this.lemma, + ConstructUseMetaData({ + required this.roomId, + required this.timeStamp, + this.eventId, }); } diff --git a/lib/pangea/models/choreo_record.dart b/lib/pangea/models/choreo_record.dart index 3586fcee10..64ed4741f8 100644 --- a/lib/pangea/models/choreo_record.dart +++ b/lib/pangea/models/choreo_record.dart @@ -1,6 +1,12 @@ import 'dart:convert'; +import 'package:fluffychat/pangea/constants/choreo_constants.dart'; +import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; +import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart'; +import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; import 'package:fluffychat/pangea/models/pangea_match_model.dart'; +import 'package:fluffychat/pangea/models/pangea_token_model.dart'; +import 'package:matrix/matrix.dart'; import 'it_step.dart'; @@ -111,6 +117,100 @@ class ChoreoRecord { String get finalMessage => choreoSteps.isNotEmpty ? choreoSteps.last.text : ""; + + /// get construct uses of type grammar for the message + List grammarConstructUses({ + Event? event, + ConstructUseMetaData? metadata, + }) { + final List uses = []; + if (event?.roomId == null && metadata?.roomId == null) { + return uses; + } + metadata ??= ConstructUseMetaData( + roomId: event!.roomId!, + eventId: event.eventId, + timeStamp: event.originServerTs, + ); + + for (final step in choreoSteps) { + if (step.acceptedOrIgnoredMatch?.status == PangeaMatchStatus.accepted) { + final String name = step.acceptedOrIgnoredMatch!.match.rule?.id ?? + step.acceptedOrIgnoredMatch!.match.shortMessage ?? + step.acceptedOrIgnoredMatch!.match.type.typeName.name; + uses.add( + OneConstructUse( + useType: ConstructUseTypeEnum.ga, + lemma: name, + form: name, + constructType: ConstructTypeEnum.grammar, + id: "${metadata.eventId}_${step.acceptedOrIgnoredMatch!.match.offset}_${step.acceptedOrIgnoredMatch!.match.length}", + metadata: metadata, + ), + ); + } + } + return uses; + } + + /// Returns a list of [OneConstructUse] from itSteps for which the continuance + /// was selected or ignored. Correct selections are considered in the tokens + /// flow. Once all continuances have lemmas, we can do both correct and incorrect + /// in this flow. It actually doesn't do anything at all right now, because the + /// choregrapher is not returning lemmas for continuances. This is a TODO. + /// So currently only the lemmas can be gotten from the tokens for choices that + /// are actually in the final message. + List itStepsToConstructUses({ + Event? event, + ConstructUseMetaData? metadata, + }) { + final List uses = []; + if (event == null && metadata == null) { + return uses; + } + + metadata ??= ConstructUseMetaData( + roomId: event!.roomId!, + eventId: event.eventId, + timeStamp: event.originServerTs, + ); + + for (final itStep in itSteps) { + for (final continuance in itStep.continuances) { + final List tokensToSave = + continuance.tokens.where((t) => t.lemma.saveVocab).toList(); + + if (finalMessage.contains(continuance.text)) { + continue; + } + if (continuance.wasClicked) { + //PTODO - account for end of flow score + if (continuance.level != ChoreoConstants.levelThresholdForGreen) { + for (final token in tokensToSave) { + uses.add( + token.lemma.toVocabUse( + ConstructUseTypeEnum.incIt, + metadata, + ), + ); + } + } + } else { + if (continuance.level != ChoreoConstants.levelThresholdForGreen) { + for (final token in tokensToSave) { + uses.add( + token.lemma.toVocabUse( + ConstructUseTypeEnum.ignIt, + metadata, + ), + ); + } + } + } + } + } + return uses; + } } /// A new ChoreoRecordStep is saved in the following cases: diff --git a/lib/pangea/models/lemma.dart b/lib/pangea/models/lemma.dart index 1dc44c4b58..f297d60b06 100644 --- a/lib/pangea/models/lemma.dart +++ b/lib/pangea/models/lemma.dart @@ -1,3 +1,7 @@ +import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; +import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart'; +import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; + /// Represents a lemma object class Lemma { /// [text] ex "ir" - text of the lemma of the word @@ -35,4 +39,18 @@ class Lemma { static Lemma create(String form) => Lemma(text: '', saveVocab: true, form: form); + + /// Given a [type] and [metadata], returns a [OneConstructUse] for this lemma + OneConstructUse toVocabUse( + ConstructUseTypeEnum type, + ConstructUseMetaData metadata, + ) { + return OneConstructUse( + useType: type, + lemma: text, + form: form, + constructType: ConstructTypeEnum.vocab, + metadata: metadata, + ); + } } diff --git a/lib/pangea/models/representation_content_model.dart b/lib/pangea/models/representation_content_model.dart index f49a465b4c..3600267f34 100644 --- a/lib/pangea/models/representation_content_model.dart +++ b/lib/pangea/models/representation_content_model.dart @@ -1,4 +1,10 @@ +import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart'; +import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; +import 'package:fluffychat/pangea/models/choreo_record.dart'; +import 'package:fluffychat/pangea/models/pangea_match_model.dart'; +import 'package:fluffychat/pangea/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/models/speech_to_text_models.dart'; +import 'package:fluffychat/widgets/matrix.dart'; import 'package:matrix/matrix.dart'; /// this class is contained within a [RepresentationEvent] @@ -81,4 +87,114 @@ class PangeaRepresentation { } return data; } + + /// Get construct uses of type vocab for the message. + /// Takes a list of tokens and a choreo record, which is searched + /// through for each token for its construct use type. + /// Also takes either an event (typically when the Representation itself is + /// available) or construct use metadata (when the event is not available, + /// i.e. immediately after message send) to create the construct use. + List vocabUses({ + required List tokens, + Event? event, + ConstructUseMetaData? metadata, + ChoreoRecord? choreo, + }) { + final List uses = []; + + // missing vital info so return + if (event?.roomId == null && metadata?.roomId == null) { + // debugger(when: kDebugMode); + return uses; + } + + metadata ??= ConstructUseMetaData( + roomId: event!.roomId!, + eventId: event.eventId, + timeStamp: event.originServerTs, + ); + + // for each token, record whether selected in ga, ta, or wa + final tokensToSave = + tokens.where((token) => token.lemma.saveVocab).toList(); + for (final token in tokensToSave) { + uses.add( + getVocabUseForToken( + token, + metadata, + choreo: choreo, + ), + ); + } + + return uses; + } + + /// Returns a [OneConstructUse] for the given [token] + /// If there is no [choreo], the [token] is + /// considered to be a [ConstructUseTypeEnum.wa] as long as it matches the target language. + /// Later on, we may want to consider putting it in some category of like 'pending' + /// If the [token] is in the [choreo.acceptedOrIgnoredMatch], it is considered to be a [ConstructUseTypeEnum.ga]. + /// If the [token] is in the [choreo.acceptedOrIgnoredMatch.choices], it is considered to be a [ConstructUseTypeEnum.corIt]. + /// If the [token] is not included in any choreoStep, it is considered to be a [ConstructUseTypeEnum.wa]. + OneConstructUse getVocabUseForToken( + PangeaToken token, + ConstructUseMetaData metadata, { + ChoreoRecord? choreo, + }) { + final lemma = token.lemma; + final content = token.text.content; + + if (choreo == null) { + final bool inUserL2 = langCode == + MatrixState.pangeaController.languageController.activeL2Code(); + return lemma.toVocabUse( + inUserL2 ? ConstructUseTypeEnum.wa : ConstructUseTypeEnum.unk, + metadata, + ); + } + + for (final step in choreo.choreoSteps) { + /// if 1) accepted match 2) token is in the replacement and 3) replacement + /// is in the overall step text, then token was a ga + final bool isAcceptedMatch = + step.acceptedOrIgnoredMatch?.status == PangeaMatchStatus.accepted; + final bool isITStep = step.itStep != null; + if (!isAcceptedMatch && !isITStep) continue; + + if (isAcceptedMatch && + step.acceptedOrIgnoredMatch?.match.choices != null) { + final choices = step.acceptedOrIgnoredMatch!.match.choices!; + final bool stepContainedToken = choices.any( + (choice) => + // if this choice contains the token's content + choice.value.contains(content) && + // if the complete input text after this step + // contains the choice (why is this here?) + step.text.contains(choice.value), + ); + if (stepContainedToken) { + return lemma.toVocabUse( + ConstructUseTypeEnum.ga, + metadata, + ); + } + } + + if (isITStep && step.itStep?.chosenContinuance != null) { + final bool pickedThroughIT = + step.itStep!.chosenContinuance!.text.contains(content); + if (pickedThroughIT) { + return lemma.toVocabUse( + ConstructUseTypeEnum.corIt, + metadata, + ); + } + } + } + return lemma.toVocabUse( + ConstructUseTypeEnum.wa, + metadata, + ); + } } From 4ede7c9bdd8469ac8e0adbc2d9f932f157c6f3c8 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 30 Jul 2024 16:44:12 -0400 Subject: [PATCH 150/288] store new construct uses locally. use a combination of those and stored analytics events to update mini analytics UI. --- lib/pages/chat/chat.dart | 10 +- .../widgets/start_igc_button.dart | 4 +- .../controllers/get_analytics_controller.dart | 212 ++++++++++++++++++ .../controllers/my_analytics_controller.dart | 157 ++++++------- lib/pangea/controllers/pangea_controller.dart | 8 +- .../analytics/construct_list_model.dart | 50 +++++ lib/pangea/models/choreo_record.dart | 5 +- .../pages/analytics/construct_list.dart | 16 +- .../learning_progress_indicators.dart | 144 +++++++----- .../analytics_summary/progress_indicator.dart | 49 +++- 10 files changed, 499 insertions(+), 156 deletions(-) create mode 100644 lib/pangea/controllers/get_analytics_controller.dart create mode 100644 lib/pangea/models/analytics/construct_list_model.dart diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index a6eb9d43e9..bf142b1852 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -636,7 +636,15 @@ class ChatController extends State // analytics based on when / how many messages the logged in user send. This // stream sends the data for newly sent messages. if (msgEventId != null) { - pangeaController.myAnalytics.setState(data: {'eventID': msgEventId}); + pangeaController.myAnalytics.setState( + data: { + 'eventID': msgEventId, + 'roomID': room.id, + 'originalSent': originalSent, + 'tokensSent': tokensSent, + 'choreo': choreo, + }, + ); } if (previousEdit != null) { diff --git a/lib/pangea/choreographer/widgets/start_igc_button.dart b/lib/pangea/choreographer/widgets/start_igc_button.dart index e8625da955..08bcdf839c 100644 --- a/lib/pangea/choreographer/widgets/start_igc_button.dart +++ b/lib/pangea/choreographer/widgets/start_igc_button.dart @@ -50,7 +50,9 @@ class StartIGCButtonState extends State _controller?.stop(); _controller?.reverse(); } - setState(() => prevState = assistanceState); + if (mounted) { + setState(() => prevState = assistanceState); + } } bool get itEnabled => widget.controller.choreographer.itEnabled; diff --git a/lib/pangea/controllers/get_analytics_controller.dart b/lib/pangea/controllers/get_analytics_controller.dart new file mode 100644 index 0000000000..1de8c8f159 --- /dev/null +++ b/lib/pangea/controllers/get_analytics_controller.dart @@ -0,0 +1,212 @@ +import 'package:fluffychat/pangea/constants/class_default_values.dart'; +import 'package:fluffychat/pangea/constants/local.key.dart'; +import 'package:fluffychat/pangea/constants/match_rule_ids.dart'; +import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; +import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; +import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart'; +import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; +import 'package:fluffychat/pangea/models/analytics/constructs_event.dart'; +import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; +import 'package:fluffychat/pangea/utils/error_handler.dart'; +import 'package:flutter/material.dart'; +import 'package:matrix/matrix.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; + +/// A minimized version of AnalyticsController that get the logged in user's analytics +class GetAnalyticsController { + late PangeaController _pangeaController; + final List _cache = []; + + GetAnalyticsController(PangeaController pangeaController) { + _pangeaController = pangeaController; + } + + String? get l2Code => _pangeaController.languageController.userL2?.langCode; + + // A local cache of eventIds and construct uses for messages sent since the last update + Map> get messagesSinceUpdate { + try { + final dynamic locallySaved = _pangeaController.pStoreService.read( + PLocalKey.messagesSinceUpdate, + ); + if (locallySaved == null) { + _pangeaController.myAnalytics.setMessagesSinceUpdate({}); + return {}; + } + try { + // try to get the local cache of messages and format them as OneConstructUses + final Map> cache = + Map>.from(locallySaved); + final Map> formattedCache = {}; + for (final entry in cache.entries) { + formattedCache[entry.key] = + entry.value.map((e) => OneConstructUse.fromJson(e)).toList(); + } + return formattedCache; + } catch (err) { + // if something goes wrong while trying to format the local data, clear it + _pangeaController.myAnalytics.setMessagesSinceUpdate({}); + return {}; + } + } catch (exception, stackTrace) { + ErrorHandler.logError( + e: PangeaWarningError( + "Failed to get messages since update: $exception", + ), + s: stackTrace, + m: 'Failed to retrieve messages since update', + ); + return {}; + } + } + + /// Get a list of all the construct analytics events + /// for the logged in user in their current L2 + Future?> getConstructs({ + bool forceUpdate = false, + ConstructTypeEnum? constructType, + }) async { + debugPrint("getting constructs"); + await _pangeaController.matrixState.client.roomsLoading; + + final DateTime? lastUpdated = await myAnalyticsLastUpdated(); + final List? local = getConstructsLocal( + constructType: constructType, + lastUpdated: lastUpdated, + ); + if (local != null && !forceUpdate) { + debugPrint("returning local constructs"); + return local; + } + debugPrint("fetching new constructs"); + + final unfilteredConstructs = await allMyConstructs(); + final filteredConstructs = await filterConstructs( + unfilteredConstructs: unfilteredConstructs, + ); + + if (local == null) { + cacheConstructs( + constructType: constructType, + events: filteredConstructs, + ); + } + + return filteredConstructs; + } + + /// Get the last time the user updated their analytics for their current l2 + Future myAnalyticsLastUpdated() async { + if (l2Code == null) return null; + final Room? analyticsRoom = + _pangeaController.matrixState.client.analyticsRoomLocal(l2Code!); + if (analyticsRoom == null) return null; + final DateTime? lastUpdated = await analyticsRoom.analyticsLastUpdated( + _pangeaController.matrixState.client.userID!, + ); + return lastUpdated; + } + + /// Get the cached construct analytics events for the current user, if it exists + List? getConstructsLocal({ + DateTime? lastUpdated, + ConstructTypeEnum? constructType, + }) { + final index = _cache.indexWhere( + (e) => e.type == constructType && e.langCode == l2Code, + ); + + if (index > -1) { + if (_cache[index].needsUpdate(lastUpdated)) { + _cache.removeAt(index); + return null; + } + return _cache[index].events; + } + + return null; + } + + /// Get all the construct analytics events for the logged in user + Future> allMyConstructs() async { + if (l2Code == null) return []; + final Room? analyticsRoom = + _pangeaController.matrixState.client.analyticsRoomLocal(l2Code!); + if (analyticsRoom == null) return []; + + return await analyticsRoom.getAnalyticsEvents( + userId: _pangeaController.matrixState.client.userID!, + ) ?? + []; + } + + /// Filter out constructs that are not relevant to the user, specifically those from + /// rooms in which the user is a teacher and those that are interative translation span constructs + Future> filterConstructs({ + required List unfilteredConstructs, + }) async { + final List adminSpaceRooms = + await _pangeaController.matrixState.client.teacherRoomIds; + for (final construct in unfilteredConstructs) { + construct.content.uses.removeWhere( + (use) { + if (adminSpaceRooms.contains(use.chatId)) { + return true; + } + return use.lemma == "Try interactive translation" || + use.lemma == "itStart" || + use.lemma == MatchRuleIds.interactiveTranslation; + }, + ); + } + unfilteredConstructs.removeWhere((e) => e.content.uses.isEmpty); + return unfilteredConstructs; + } + + /// Cache the construct analytics events for the current user + void cacheConstructs({ + required List events, + ConstructTypeEnum? constructType, + }) { + if (l2Code == null) return; + final entry = AnalyticsCacheEntry( + type: constructType, + events: List.from(events), + langCode: l2Code!, + ); + _cache.add(entry); + } +} + +class AnalyticsCacheEntry { + final String langCode; + final ConstructTypeEnum? type; + final List events; + late final DateTime _createdAt; + + AnalyticsCacheEntry({ + required this.langCode, + required this.type, + required this.events, + }) { + _createdAt = DateTime.now(); + } + + bool get isExpired => + DateTime.now().difference(_createdAt).inMinutes > + ClassDefaultValues.minutesDelayToMakeNewChartAnalytics; + + bool needsUpdate(DateTime? lastEventUpdated) { + // cache entry is invalid if it's older than the last event update + // if lastEventUpdated is null, that would indicate that no events + // of this type have been sent to the room. In this case, there + // shouldn't be any cached data. + if (lastEventUpdated == null) { + Sentry.addBreadcrumb( + Breadcrumb(message: "lastEventUpdated is null in needsUpdate"), + ); + return false; + } + return _createdAt.isBefore(lastEventUpdated); + } +} diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart index 20c9a28034..0a1048c725 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/my_analytics_controller.dart @@ -10,16 +10,19 @@ import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_e import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/practice_activity_record_event.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; +import 'package:fluffychat/pangea/models/choreo_record.dart'; +import 'package:fluffychat/pangea/models/representation_content_model.dart'; +import 'package:fluffychat/pangea/models/tokens_event_content_model.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:flutter/foundation.dart'; import 'package:matrix/matrix.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; /// handles the processing of analytics for /// 1) messages sent by the user and /// 2) constructs used by the user, both in sending messages and doing practice activities class MyAnalyticsController extends BaseController { late PangeaController _pangeaController; + final StreamController analyticsUpdateStream = StreamController.broadcast(); Timer? _updateTimer; /// the max number of messages that will be cached before @@ -38,10 +41,8 @@ class MyAnalyticsController extends BaseController { // Listen to a stream that provides the eventIDs // of new messages sent by the logged in user - stateStream - .where((data) => data is Map && data.containsKey("eventID")) - .listen((data) { - updateAnalyticsTimer(data['eventID']); + stateStream.where((data) => data is Map).listen((data) { + onMessageSent(data as Map); }); } @@ -67,11 +68,9 @@ class MyAnalyticsController extends BaseController { Client get _client => _pangeaController.matrixState.client; - /// Given an newly sent message, reset the timer - /// and add the event ID to the cache of un-added event IDs - void updateAnalyticsTimer(String newEventId) { - addMessageSinceUpdate(newEventId); - + /// Given the data from a newly sent message, format and cache + /// the message's construct data locally and reset the update timer + void onMessageSent(Map data) { // cancel the last timer that was set on message event and // reset it to fire after _minutesBeforeUpdate minutes _updateTimer?.cancel(); @@ -79,97 +78,88 @@ class MyAnalyticsController extends BaseController { debugPrint("timer fired, updating analytics"); updateAnalytics(); }); + + // extract the relevant data about this message + final String? eventID = data['eventID']; + final String? roomID = data['roomID']; + final PangeaRepresentation? originalSent = data['originalSent']; + final PangeaMessageTokens? tokensSent = data['tokensSent']; + final ChoreoRecord? choreo = data['choreo']; + + if (roomID == null || eventID == null) return; + + // convert that data into construct uses and add it to the cache + final metadata = ConstructUseMetaData( + roomId: roomID, + eventId: eventID, + timeStamp: DateTime.now(), + ); + + final grammarConstructs = choreo?.grammarConstructUses(metadata: metadata); + final itConstructs = choreo?.itStepsToConstructUses(metadata: metadata); + final vocabUses = tokensSent != null + ? originalSent?.vocabUses( + choreo: choreo, + tokens: tokensSent.tokens, + metadata: metadata, + ) + : null; + final List constructs = [ + ...(grammarConstructs ?? []), + ...(itConstructs ?? []), + ...(vocabUses ?? []), + ]; + addMessageSinceUpdate( + eventID, + constructs, + ); } - // adds an event ID to the cache of un-added event IDs - // if the event IDs isn't already added - void addMessageSinceUpdate(String eventId) { + /// Add a list of construct uses for a new message to the local + /// cache of recently sent messages + void addMessageSinceUpdate( + String eventID, + List constructs, + ) { try { - final List currentCache = messagesSinceUpdate; - if (!currentCache.contains(eventId)) { - currentCache.add(eventId); - _pangeaController.pStoreService.save( - PLocalKey.messagesSinceUpdate, - currentCache, - ); + final currentCache = _pangeaController.analytics.messagesSinceUpdate; + if (!currentCache.containsKey(eventID)) { + currentCache[eventID] = constructs; + setMessagesSinceUpdate(currentCache); } // if the cached has reached if max-length, update analytics - if (messagesSinceUpdate.length > _maxMessagesCached) { + if (_pangeaController.analytics.messagesSinceUpdate.length > + _maxMessagesCached) { debugPrint("reached max messages, updating"); updateAnalytics(); } - } catch (exception, stackTrace) { + } catch (e, s) { ErrorHandler.logError( - e: PangeaWarningError("Failed to add message since update: $exception"), - s: stackTrace, - m: 'Failed to add message since update for eventId: $eventId', - ); - Sentry.captureException( - exception, - stackTrace: stackTrace, - withScope: (scope) { - scope.setExtra( - 'extra_info', - 'Failed during addMessageSinceUpdate with eventId: $eventId', - ); - scope.setTag('where', 'addMessageSinceUpdate'); - }, + e: PangeaWarningError("Failed to add message since update: $e"), + s: s, + m: 'Failed to add message since update for eventId: $eventID', ); } } - // called before updating analytics + /// Clears the local cache of recently sent constructs. Called before updating analytics void clearMessagesSinceUpdate() { - _pangeaController.pStoreService.save( - PLocalKey.messagesSinceUpdate, - [], - ); + setMessagesSinceUpdate({}); } - // a local cache of eventIds for messages sent since the last update - // it's possible for this cache to be invalid or deleted - // It's a proxy measure for messages sent since last update - List get messagesSinceUpdate { - try { - Logs().d('Reading messages since update from local storage'); - final dynamic locallySaved = _pangeaController.pStoreService.read( - PLocalKey.messagesSinceUpdate, - ); - if (locallySaved == null) { - Logs().d('No locally saved messages found, initializing empty list.'); - _pangeaController.pStoreService.save( - PLocalKey.messagesSinceUpdate, - [], - ); - return []; - } - return locallySaved.cast(); - } catch (exception, stackTrace) { - ErrorHandler.logError( - e: PangeaWarningError( - "Failed to get messages since update: $exception", - ), - s: stackTrace, - m: 'Failed to retrieve messages since update', - ); - Sentry.captureException( - exception, - stackTrace: stackTrace, - withScope: (scope) { - scope.setExtra( - 'extra_info', - 'Error during messagesSinceUpdate getter', - ); - scope.setTag('where', 'messagesSinceUpdate'); - }, - ); - _pangeaController.pStoreService.save( - PLocalKey.messagesSinceUpdate, - [], - ); - return []; + /// Save the local cache of recently sent constructs to the local storage + void setMessagesSinceUpdate(Map> cache) { + final formattedCache = {}; + for (final entry in cache.entries) { + final constructJsons = entry.value.map((e) => e.toJson()).toList(); + formattedCache[entry.key] = constructJsons; } + _pangeaController.pStoreService.save( + PLocalKey.messagesSinceUpdate, + formattedCache, + ); + analyticsUpdateStream.add(null); } Completer? _updateCompleter; @@ -182,6 +172,7 @@ class MyAnalyticsController extends BaseController { try { await _updateAnalytics(); clearMessagesSinceUpdate(); + analyticsUpdateStream.add(null); } catch (err, s) { ErrorHandler.logError( e: err, diff --git a/lib/pangea/controllers/pangea_controller.dart b/lib/pangea/controllers/pangea_controller.dart index 79222bbeda..a62ec04281 100644 --- a/lib/pangea/controllers/pangea_controller.dart +++ b/lib/pangea/controllers/pangea_controller.dart @@ -6,6 +6,7 @@ import 'package:fluffychat/pangea/constants/class_default_values.dart'; import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/controllers/class_controller.dart'; import 'package:fluffychat/pangea/controllers/contextual_definition_controller.dart'; +import 'package:fluffychat/pangea/controllers/get_analytics_controller.dart'; import 'package:fluffychat/pangea/controllers/language_controller.dart'; import 'package:fluffychat/pangea/controllers/language_detection_controller.dart'; import 'package:fluffychat/pangea/controllers/language_list_controller.dart'; @@ -35,7 +36,6 @@ import '../../config/app_config.dart'; import '../utils/firebase_analytics.dart'; import '../utils/p_store.dart'; import 'it_feedback_controller.dart'; -import 'message_analytics_controller.dart'; class PangeaController { ///pangeaControllers @@ -43,7 +43,8 @@ class PangeaController { late LanguageController languageController; late ClassController classController; late PermissionsController permissionsController; - late AnalyticsController analytics; + // late AnalyticsController analytics; + late GetAnalyticsController analytics; late MyAnalyticsController myAnalytics; late WordController wordNet; late MessageDataController messageData; @@ -91,7 +92,8 @@ class PangeaController { languageController = LanguageController(this); classController = ClassController(this); permissionsController = PermissionsController(this); - analytics = AnalyticsController(this); + // analytics = AnalyticsController(this); + analytics = GetAnalyticsController(this); myAnalytics = MyAnalyticsController(this); messageData = MessageDataController(this); wordNet = WordController(this); diff --git a/lib/pangea/models/analytics/construct_list_model.dart b/lib/pangea/models/analytics/construct_list_model.dart new file mode 100644 index 0000000000..a19fb16760 --- /dev/null +++ b/lib/pangea/models/analytics/construct_list_model.dart @@ -0,0 +1,50 @@ +import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; +import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; + +/// A wrapper around a list of [OneConstructUse]s, used to simplify +/// the process of filtering / sorting / displaying the events. +/// Takes a construct type and a list of events +class ConstructListModel { + ConstructTypeEnum type; + List uses; + + ConstructListModel({ + required this.type, + required this.uses, + }); + + /// All unique lemmas used in the construct events + List get lemmas => constructs.map((e) => e.lemma).toSet().toList(); + + /// A list of ConstructUses, each of which contains a lemma and + /// a list of uses, sorted by the number of uses + List get constructs { + final List filtered = + uses.where((use) => use.constructType == type).toList(); + + final Map> lemmaToUses = {}; + for (final use in filtered) { + if (use.lemma == null) continue; + lemmaToUses[use.lemma!] ??= []; + lemmaToUses[use.lemma!]!.add(use); + } + + final constructUses = lemmaToUses.entries + .map( + (entry) => ConstructUses( + lemma: entry.key, + uses: entry.value, + constructType: type, + ), + ) + .toList(); + + constructUses.sort((a, b) { + final comp = b.uses.length.compareTo(a.uses.length); + if (comp != 0) return comp; + return a.lemma.compareTo(b.lemma); + }); + + return constructUses; + } +} diff --git a/lib/pangea/models/choreo_record.dart b/lib/pangea/models/choreo_record.dart index 64ed4741f8..6fdde333a4 100644 --- a/lib/pangea/models/choreo_record.dart +++ b/lib/pangea/models/choreo_record.dart @@ -118,7 +118,10 @@ class ChoreoRecord { String get finalMessage => choreoSteps.isNotEmpty ? choreoSteps.last.text : ""; - /// get construct uses of type grammar for the message + /// Get construct uses of type grammar for the message from this ChoreoRecord. + /// Takes either an event (typically when the Representation itself is + /// available) or construct use metadata (when the event is not available, + /// i.e. immediately after message send) to create the construct uses. List grammarConstructUses({ Event? event, ConstructUseMetaData? metadata, diff --git a/lib/pangea/pages/analytics/construct_list.dart b/lib/pangea/pages/analytics/construct_list.dart index 0f06ddc8d9..9d032293d2 100644 --- a/lib/pangea/pages/analytics/construct_list.dart +++ b/lib/pangea/pages/analytics/construct_list.dart @@ -110,11 +110,7 @@ class ConstructListViewState extends State { widget.pangeaController.analytics .getConstructs( constructType: constructType, - removeIT: true, - defaultSelected: widget.defaultSelected, - selected: widget.selected, forceUpdate: true, - timeSpan: widget.timeSpan, ) .whenComplete(() => setState(() => fetchingConstructs = false)) .then((value) => setState(() => _constructs = value)); @@ -126,11 +122,7 @@ class ConstructListViewState extends State { widget.pangeaController.analytics .getConstructs( constructType: constructType, - removeIT: true, - defaultSelected: widget.defaultSelected, - selected: widget.selected, forceUpdate: true, - timeSpan: widget.timeSpan, ) .then( (value) => setState(() { @@ -163,11 +155,11 @@ class ConstructListViewState extends State { ) async { final Client client = Matrix.of(context).client; PangeaMessageEvent msgEvent; - if (_msgEventCache.containsKey(use.msgId!)) { - return _msgEventCache[use.msgId!]!; + if (_msgEventCache.containsKey(use.msgId)) { + return _msgEventCache[use.msgId]!; } final Room? msgRoom = use.getRoom(client); - if (msgRoom == null || use.msgId == null) { + if (msgRoom == null) { return null; } @@ -189,7 +181,7 @@ class ConstructListViewState extends State { timeline: timeline, ownMessage: event.senderId == client.userID, ); - _msgEventCache[use.msgId!] = msgEvent; + _msgEventCache[use.msgId] = msgEvent; return msgEvent; } diff --git a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart index ae6b13e300..5a30b9d0d0 100644 --- a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart +++ b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart @@ -1,10 +1,12 @@ +import 'dart:async'; + import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; import 'package:fluffychat/pangea/enum/progress_indicators_enum.dart'; -import 'package:fluffychat/pangea/enum/time_span.dart'; -import 'package:fluffychat/pangea/pages/analytics/base_analytics.dart'; +import 'package:fluffychat/pangea/models/analytics/construct_list_model.dart'; +import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; import 'package:fluffychat/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart'; import 'package:fluffychat/utils/string_color.dart'; import 'package:fluffychat/widgets/avatar.dart'; @@ -30,64 +32,78 @@ class LearningProgressIndicators extends StatefulWidget { class LearningProgressIndicatorsState extends State { final PangeaController _pangeaController = MatrixState.pangeaController; - int? wordsUsed; - int? errorTypes; + + /// A stream subscription to listen for updates to + /// the analytics data, either locally or from events + StreamSubscription? _onAnalyticsUpdate; + + /// Vocabulary constructs model + ConstructListModel? words; + + /// Grammar constructs model + ConstructListModel? errors; @override void initState() { super.initState(); - setData(); + updateAnalyticsData(); + // listen for changes to analytics data and update the UI + _onAnalyticsUpdate = _pangeaController + .myAnalytics.analyticsUpdateStream.stream + .listen((_) => updateAnalyticsData()); } - AnalyticsSelected get defaultSelected => AnalyticsSelected( - _pangeaController.matrixState.client.userID!, - AnalyticsEntryType.student, - "", - ); - - Future setData() async { - await getNumLemmasUsed(); - setState(() {}); + @override + void dispose() { + _onAnalyticsUpdate?.cancel(); + super.dispose(); } - Future getNumLemmasUsed() async { - final constructs = await _pangeaController.analytics.getConstructs( - defaultSelected: defaultSelected, - timeSpan: TimeSpan.forever, - ); - if (constructs == null) { - errorTypes = 0; - wordsUsed = 0; - return; + /// Update the analytics data shown in the UI. This comes from a + /// combination of stored events and locally cached data. + Future updateAnalyticsData() async { + final constructEvents = await _pangeaController.analytics.getConstructs(); + final List localUses = []; + for (final uses in _pangeaController.analytics.messagesSinceUpdate.values) { + localUses.addAll(uses); } - final List errorLemmas = []; - final List vocabLemmas = []; - for (final event in constructs) { - for (final use in event.content.uses) { - if (use.lemma == null) continue; - switch (use.constructType) { - case ConstructTypeEnum.grammar: - errorLemmas.add(use.lemma!); - break; - case ConstructTypeEnum.vocab: - vocabLemmas.add(use.lemma!); - break; - default: - break; - } - } + if (constructEvents == null || constructEvents.isEmpty) { + words = ConstructListModel( + type: ConstructTypeEnum.vocab, + uses: localUses, + ); + errors = ConstructListModel( + type: ConstructTypeEnum.grammar, + uses: localUses, + ); + return; } - errorTypes = errorLemmas.toSet().length; - wordsUsed = vocabLemmas.toSet().length; + + final List storedConstruct = + constructEvents.expand((e) => e.content.uses).toList(); + final List allConstructs = [ + ...storedConstruct, + ...localUses, + ]; + + words = ConstructListModel( + type: ConstructTypeEnum.vocab, + uses: allConstructs, + ); + errors = ConstructListModel( + type: ConstructTypeEnum.grammar, + uses: allConstructs, + ); + setState(() {}); } int? getProgressPoints(ProgressIndicatorEnum indicator) { switch (indicator) { case ProgressIndicatorEnum.wordsUsed: - return wordsUsed; + return words?.lemmas.length; case ProgressIndicatorEnum.errorTypes: - return errorTypes; + return errors?.lemmas.length; case ProgressIndicatorEnum.level: return level; } @@ -95,8 +111,8 @@ class LearningProgressIndicatorsState int get xpPoints { final points = [ - wordsUsed ?? 0, - errorTypes ?? 0, + words?.lemmas.length ?? 0, + errors?.lemmas.length ?? 0, ]; return points.reduce((a, b) => a + b); } @@ -161,14 +177,36 @@ class LearningProgressIndicatorsState children: [ SizedBox( width: FluffyThemes.columnWidth - (36 * 2) - 25, - child: LinearProgressIndicator( - value: (xpPoints % 100) / 100, - color: Theme.of(context).colorScheme.primary, - backgroundColor: - Theme.of(context).colorScheme.onPrimary, - minHeight: 15, - borderRadius: - BorderRadius.circular(AppConfig.borderRadius), + child: Expanded( + child: Stack( + alignment: Alignment.centerLeft, + children: [ + Container( + height: 15, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + AppConfig.borderRadius, + ), + color: + Theme.of(context).colorScheme.onPrimary, + ), + ), + AnimatedContainer( + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + height: 15, + width: + (FluffyThemes.columnWidth - (36 * 2) - 25) * + ((xpPoints % 100) / 100), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + AppConfig.borderRadius, + ), + color: Theme.of(context).colorScheme.primary, + ), + ), + ], + ), ), ), ], diff --git a/lib/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart b/lib/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart index 93e932aa20..9fc9bb55c5 100644 --- a/lib/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart +++ b/lib/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart @@ -1,3 +1,4 @@ +import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pangea/enum/progress_indicators_enum.dart'; import 'package:flutter/material.dart'; @@ -33,8 +34,8 @@ class ProgressIndicatorBadge extends StatelessWidget { ), const SizedBox(width: 5), points != null - ? Text( - points.toString(), + ? AnimatedCount( + count: points!, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, @@ -49,3 +50,47 @@ class ProgressIndicatorBadge extends StatelessWidget { ); } } + +class AnimatedCount extends ImplicitlyAnimatedWidget { + const AnimatedCount({ + super.key, + required this.count, + this.style, + super.duration = const Duration(seconds: 1), + super.curve = FluffyThemes.animationCurve, + }); + + final int count; + final TextStyle? style; + + @override + ImplicitlyAnimatedWidgetState createState() { + return _AnimatedCountState(); + } +} + +class _AnimatedCountState extends AnimatedWidgetBaseState { + IntTween _intCount = IntTween(begin: 0, end: 1); + + @override + void initState() { + super.initState(); + _intCount = IntTween(begin: 0, end: widget.count.toInt()); + controller.forward(); + } + + @override + Widget build(BuildContext context) { + final String text = _intCount.evaluate(animation).toString(); + return Text(text, style: widget.style); + } + + @override + void forEachTween(TweenVisitor visitor) { + _intCount = visitor( + _intCount, + widget.count, + (dynamic value) => IntTween(begin: value), + ) as IntTween; + } +} From e5bbb755d99c54ec051d66c9cdbf37b2f8568e18 Mon Sep 17 00:00:00 2001 From: Krille Date: Wed, 31 Jul 2024 15:23:25 +0200 Subject: [PATCH 151/288] design: Add snackbar with link to changelog on new version --- assets/l10n/intl_en.arb | 10 +++- lib/config/app_config.dart | 2 + lib/config/themes.dart | 3 -- lib/pages/chat_list/chat_list.dart | 2 + .../chat_list/client_chooser_button.dart | 6 ++- lib/utils/show_update_snackbar.dart | 52 +++++++++++++++++++ 6 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 lib/utils/show_update_snackbar.dart diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 30a8f9131c..1ec5f83277 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -2746,5 +2746,13 @@ "changeTheCanonicalRoomAlias": "Change the main public chat address", "sendRoomNotifications": "Send a @room notifications", "changeTheDescriptionOfTheGroup": "Change the description of the chat", - "chatPermissionsDescription": "Define which power level is necessary for certain actions in this chat. The power levels 0, 50 and 100 are usually representing users, moderators and admins, but any gradation is possible." + "chatPermissionsDescription": "Define which power level is necessary for certain actions in this chat. The power levels 0, 50 and 100 are usually representing users, moderators and admins, but any gradation is possible.", + "updateInstalled": "🎉 Update {version} installed!", + "@updateInstalled": { + "type": "text", + "placeholders": { + "version": {} + } + }, + "changelog": "Changelog" } diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index 298b8743c6..2c1ee1f4ca 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -35,6 +35,8 @@ abstract class AppConfig { 'https://github.com/krille-chan/fluffychat'; static const String supportUrl = 'https://github.com/krille-chan/fluffychat/issues'; + static const String changelogUrl = + 'https://github.com/krille-chan/fluffychat/blob/main/CHANGELOG.md'; static final Uri newIssueUrl = Uri( scheme: 'https', host: 'github.com', diff --git a/lib/config/themes.dart b/lib/config/themes.dart index fc1ec0b4cc..f83b69a2b1 100644 --- a/lib/config/themes.dart +++ b/lib/config/themes.dart @@ -77,9 +77,6 @@ abstract class FluffyThemes { ? Typography.material2018().black.merge(fallbackTextTheme) : Typography.material2018().white.merge(fallbackTextTheme) : null, - snackBarTheme: const SnackBarThemeData( - behavior: SnackBarBehavior.floating, - ), dividerColor: brightness == Brightness.light ? Colors.blueGrey.shade50 : Colors.blueGrey.shade900, diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 99d31bf587..7b89f0997a 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -21,6 +21,7 @@ import 'package:fluffychat/pages/chat_list/chat_list_view.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:fluffychat/utils/show_update_snackbar.dart'; import 'package:fluffychat/widgets/avatar.dart'; import '../../../utils/account_bundles.dart'; import '../../config/setting_keys.dart'; @@ -511,6 +512,7 @@ class ChatListController extends State searchServer = Matrix.of(context).store.getString(_serverStoreNamespace); Matrix.of(context).backgroundPush?.setupPush(); + UpdateNotifier.showUpdateSnackBar(context); } // Workaround for system UI overlay style not applied on app start diff --git a/lib/pages/chat_list/client_chooser_button.dart b/lib/pages/chat_list/client_chooser_button.dart index d937b7cb16..c004bd1673 100644 --- a/lib/pages/chat_list/client_chooser_button.dart +++ b/lib/pages/chat_list/client_chooser_button.dart @@ -68,7 +68,9 @@ class ClientChooserButton extends StatelessWidget { ], ), ), - PopupMenuItem( + // Currently disabled because of: + // https://github.com/matrix-org/matrix-react-sdk/pull/12286 + /*PopupMenuItem( value: SettingsAction.archive, child: Row( children: [ @@ -77,7 +79,7 @@ class ClientChooserButton extends StatelessWidget { Text(L10n.of(context)!.archive), ], ), - ), + ),*/ PopupMenuItem( value: SettingsAction.settings, child: Row( diff --git a/lib/utils/show_update_snackbar.dart b/lib/utils/show_update_snackbar.dart new file mode 100644 index 0000000000..96d08378e4 --- /dev/null +++ b/lib/utils/show_update_snackbar.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; + +abstract class UpdateNotifier { + static const String versionStoreKey = 'last_known_version'; + + static void showUpdateSnackBar(BuildContext context) async { + final scaffoldMessenger = ScaffoldMessenger.of(context); + final currentVersion = await PlatformInfos.getVersion(); + final store = await SharedPreferences.getInstance(); + final storedVersion = store.getString(versionStoreKey); + + if (currentVersion != storedVersion) { + if (storedVersion != null) { + ScaffoldFeatureController? controller; + controller = scaffoldMessenger.showSnackBar( + SnackBar( + duration: const Duration(seconds: 30), + content: Row( + children: [ + IconButton( + icon: Icon( + Icons.close_outlined, + size: 20, + color: Theme.of(context).colorScheme.onPrimary, + ), + onPressed: () => controller?.close(), + ), + Expanded( + child: Text( + L10n.of(context)!.updateInstalled(currentVersion), + ), + ), + ], + ), + action: SnackBarAction( + label: L10n.of(context)!.changelog, + onPressed: () => launchUrlString(AppConfig.changelogUrl), + ), + ), + ); + } + await store.setString(versionStoreKey, currentVersion); + } + } +} From 9f69360f243f50c3fea5d54a635dbbce300b032b Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 31 Jul 2024 12:52:43 -0400 Subject: [PATCH 152/288] use construct use type pointValues to calculate XP and level --- assets/l10n/intl_en.arb | 3 +- .../controllers/get_analytics_controller.dart | 91 +++++++------ .../controllers/my_analytics_controller.dart | 16 ++- .../analytics/construct_list_model.dart | 95 ++++++++++++-- .../models/analytics/constructs_model.dart | 14 +- .../pages/analytics/construct_list.dart | 99 +++++--------- .../learning_progress_indicators.dart | 123 +++++++++++------- .../matrix_sdk_extensions/matrix_locals.dart | 8 +- 8 files changed, 260 insertions(+), 189 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index eaccf93a41..4b7befd46b 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4116,5 +4116,6 @@ "error520Desc": "Sorry, we could not understand your message...", "wordsUsed": "Words Used", "errorTypes": "Error Types", - "level": "Level" + "level": "Level", + "canceledSend": "Canceled send" } \ No newline at end of file diff --git a/lib/pangea/controllers/get_analytics_controller.dart b/lib/pangea/controllers/get_analytics_controller.dart index 1de8c8f159..ac4efb668b 100644 --- a/lib/pangea/controllers/get_analytics_controller.dart +++ b/lib/pangea/controllers/get_analytics_controller.dart @@ -23,6 +23,8 @@ class GetAnalyticsController { String? get l2Code => _pangeaController.languageController.userL2?.langCode; + Client get client => _pangeaController.matrixState.client; + // A local cache of eventIds and construct uses for messages sent since the last update Map> get messagesSinceUpdate { try { @@ -60,17 +62,17 @@ class GetAnalyticsController { } } - /// Get a list of all the construct analytics events - /// for the logged in user in their current L2 - Future?> getConstructs({ + /// Get a list of all constructs used by the logged in user in their current L2 + Future> getConstructs({ bool forceUpdate = false, ConstructTypeEnum? constructType, }) async { debugPrint("getting constructs"); - await _pangeaController.matrixState.client.roomsLoading; + await client.roomsLoading; + // first, try to get a cached list of all uses, if it exists and is valid final DateTime? lastUpdated = await myAnalyticsLastUpdated(); - final List? local = getConstructsLocal( + final List? local = getConstructsLocal( constructType: constructType, lastUpdated: lastUpdated, ); @@ -80,35 +82,46 @@ class GetAnalyticsController { } debugPrint("fetching new constructs"); - final unfilteredConstructs = await allMyConstructs(); - final filteredConstructs = await filterConstructs( - unfilteredConstructs: unfilteredConstructs, + // if there is no cached data (or if force updating), + // get all the construct events for the user from analytics room + // and convert their content into a list of construct uses + final List constructEvents = + await allMyConstructs(); + + final List unfilteredUses = []; + for (final event in constructEvents) { + unfilteredUses.addAll(event.content.uses); + } + + // filter out any constructs that are not relevant to the user + final List filteredUses = await filterConstructs( + unfilteredConstructs: unfilteredUses, ); + // if there isn't already a valid, local cache, cache the filtered uses if (local == null) { cacheConstructs( constructType: constructType, - events: filteredConstructs, + uses: filteredUses, ); } - return filteredConstructs; + return filteredUses; } /// Get the last time the user updated their analytics for their current l2 Future myAnalyticsLastUpdated() async { if (l2Code == null) return null; - final Room? analyticsRoom = - _pangeaController.matrixState.client.analyticsRoomLocal(l2Code!); + final Room? analyticsRoom = client.analyticsRoomLocal(l2Code!); if (analyticsRoom == null) return null; final DateTime? lastUpdated = await analyticsRoom.analyticsLastUpdated( - _pangeaController.matrixState.client.userID!, + client.userID!, ); return lastUpdated; } - /// Get the cached construct analytics events for the current user, if it exists - List? getConstructsLocal({ + /// Get the cached construct uses for the current user, if it exists + List? getConstructsLocal({ DateTime? lastUpdated, ConstructTypeEnum? constructType, }) { @@ -121,7 +134,7 @@ class GetAnalyticsController { _cache.removeAt(index); return null; } - return _cache[index].events; + return _cache[index].uses; } return null; @@ -130,48 +143,34 @@ class GetAnalyticsController { /// Get all the construct analytics events for the logged in user Future> allMyConstructs() async { if (l2Code == null) return []; - final Room? analyticsRoom = - _pangeaController.matrixState.client.analyticsRoomLocal(l2Code!); + final Room? analyticsRoom = client.analyticsRoomLocal(l2Code!); if (analyticsRoom == null) return []; - - return await analyticsRoom.getAnalyticsEvents( - userId: _pangeaController.matrixState.client.userID!, - ) ?? - []; + return await analyticsRoom.getAnalyticsEvents(userId: client.userID!) ?? []; } /// Filter out constructs that are not relevant to the user, specifically those from /// rooms in which the user is a teacher and those that are interative translation span constructs - Future> filterConstructs({ - required List unfilteredConstructs, + Future> filterConstructs({ + required List unfilteredConstructs, }) async { - final List adminSpaceRooms = - await _pangeaController.matrixState.client.teacherRoomIds; - for (final construct in unfilteredConstructs) { - construct.content.uses.removeWhere( - (use) { - if (adminSpaceRooms.contains(use.chatId)) { - return true; - } - return use.lemma == "Try interactive translation" || - use.lemma == "itStart" || - use.lemma == MatchRuleIds.interactiveTranslation; - }, - ); - } - unfilteredConstructs.removeWhere((e) => e.content.uses.isEmpty); - return unfilteredConstructs; + final List adminSpaceRooms = await client.teacherRoomIds; + return unfilteredConstructs.where((use) { + if (adminSpaceRooms.contains(use.chatId)) return false; + return use.lemma != "Try interactive translation" && + use.lemma != "itStart" || + use.lemma != MatchRuleIds.interactiveTranslation; + }).toList(); } - /// Cache the construct analytics events for the current user + /// Cache the construct uses for the current user void cacheConstructs({ - required List events, + required List uses, ConstructTypeEnum? constructType, }) { if (l2Code == null) return; final entry = AnalyticsCacheEntry( type: constructType, - events: List.from(events), + uses: List.from(uses), langCode: l2Code!, ); _cache.add(entry); @@ -181,13 +180,13 @@ class GetAnalyticsController { class AnalyticsCacheEntry { final String langCode; final ConstructTypeEnum? type; - final List events; + final List uses; late final DateTime _createdAt; AnalyticsCacheEntry({ required this.langCode, required this.type, - required this.events, + required this.uses, }) { _createdAt = DateTime.now(); } diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart index 0a1048c725..591b7a3722 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/my_analytics_controller.dart @@ -25,9 +25,13 @@ class MyAnalyticsController extends BaseController { final StreamController analyticsUpdateStream = StreamController.broadcast(); Timer? _updateTimer; + Client get _client => _pangeaController.matrixState.client; + + String? get userL2 => _pangeaController.languageController.activeL2Code(); + /// the max number of messages that will be cached before /// an automatic update is triggered - final int _maxMessagesCached = 10; + final int _maxMessagesCached = 1; /// the number of minutes before an automatic update is triggered final int _minutesBeforeUpdate = 5; @@ -37,7 +41,11 @@ class MyAnalyticsController extends BaseController { MyAnalyticsController(PangeaController pangeaController) { _pangeaController = pangeaController; - _refreshAnalyticsIfOutdated(); + + // Wait for the next sync in the stream to ensure that the pangea controller + // is fully initialized. It will throw an error if it is not. + _pangeaController.matrixState.client.onSync.stream.first + .then((_) => _refreshAnalyticsIfOutdated()); // Listen to a stream that provides the eventIDs // of new messages sent by the logged in user @@ -66,8 +74,6 @@ class MyAnalyticsController extends BaseController { return lastUpdated; } - Client get _client => _pangeaController.matrixState.client; - /// Given the data from a newly sent message, format and cache /// the message's construct data locally and reset the update timer void onMessageSent(Map data) { @@ -185,8 +191,6 @@ class MyAnalyticsController extends BaseController { } } - String? get userL2 => _pangeaController.languageController.activeL2Code(); - /// top level analytics sending function. Gather recent messages and activity records, /// convert them into the correct formats, and send them to the analytics room Future _updateAnalytics() async { diff --git a/lib/pangea/models/analytics/construct_list_model.dart b/lib/pangea/models/analytics/construct_list_model.dart index a19fb16760..a22aa82e1d 100644 --- a/lib/pangea/models/analytics/construct_list_model.dart +++ b/lib/pangea/models/analytics/construct_list_model.dart @@ -1,17 +1,24 @@ import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; +import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; /// A wrapper around a list of [OneConstructUse]s, used to simplify /// the process of filtering / sorting / displaying the events. /// Takes a construct type and a list of events class ConstructListModel { - ConstructTypeEnum type; - List uses; + final ConstructTypeEnum type; + final List _uses; ConstructListModel({ required this.type, - required this.uses, - }); + uses, + }) : _uses = uses ?? []; + + List? _constructs; + List? _typedConstructs; + + List get uses => + _uses.where((use) => use.constructType == type).toList(); /// All unique lemmas used in the construct events List get lemmas => constructs.map((e) => e.lemma).toSet().toList(); @@ -19,11 +26,10 @@ class ConstructListModel { /// A list of ConstructUses, each of which contains a lemma and /// a list of uses, sorted by the number of uses List get constructs { - final List filtered = - uses.where((use) => use.constructType == type).toList(); - + // the list of uses doesn't change so we don't have to re-calculate this + if (_constructs != null) return _constructs!; final Map> lemmaToUses = {}; - for (final use in filtered) { + for (final use in uses) { if (use.lemma == null) continue; lemmaToUses[use.lemma!] ??= []; lemmaToUses[use.lemma!]!.add(use); @@ -45,6 +51,79 @@ class ConstructListModel { return a.lemma.compareTo(b.lemma); }); + _constructs = constructUses; return constructUses; } + + /// A list of ConstructUseTypeUses, each of which + /// contains a lemma, a use type, and a list of uses + List get typedConstructs { + if (_typedConstructs != null) return _typedConstructs!; + final List typedConstructs = []; + for (final construct in constructs) { + final typeToUses = >{}; + for (final use in construct.uses) { + typeToUses[use.useType] ??= []; + typeToUses[use.useType]!.add(use); + } + for (final typeEntry in typeToUses.entries) { + typedConstructs.add( + ConstructUseTypeUses( + lemma: construct.lemma, + constructType: type, + useType: typeEntry.key, + uses: typeEntry.value, + ), + ); + } + } + return typedConstructs; + } + + /// The total number of points for all uses of this construct type + int get points { + double totalPoints = 0; + // Minimize the amount of points given for repeated uses of the same lemma. + // i.e., if a lemma is used 4 times without assistance, the point value for + // a use without assistance is 3. So the points would be + // 3/1 + 3/2 + 3/3 + 3/4 = 3 + 1.5 + 1 + 0.75 = 5.25 (instead of 12) + for (final typedConstruct in typedConstructs) { + final pointValue = typedConstruct.useType.pointValue; + double calc = 0.0; + for (int k = 1; k <= typedConstruct.uses.length; k++) { + calc += pointValue / k; + } + totalPoints += calc; + } + return totalPoints.round(); + } +} + +/// One lemma and a list of construct uses for that lemma +class ConstructUses { + final List uses; + final ConstructTypeEnum constructType; + final String lemma; + + ConstructUses({ + required this.uses, + required this.constructType, + required this.lemma, + }); +} + +/// One lemma, a use type, and a list of uses +/// for that lemma and use type +class ConstructUseTypeUses { + final ConstructUseTypeEnum useType; + final ConstructTypeEnum constructType; + final String lemma; + final List uses; + + ConstructUseTypeUses({ + required this.useType, + required this.constructType, + required this.lemma, + required this.uses, + }); } diff --git a/lib/pangea/models/analytics/constructs_model.dart b/lib/pangea/models/analytics/constructs_model.dart index 4346ec1f1b..0e62419f02 100644 --- a/lib/pangea/models/analytics/constructs_model.dart +++ b/lib/pangea/models/analytics/constructs_model.dart @@ -71,18 +71,6 @@ class ConstructAnalyticsModel { } } -class ConstructUses { - final List uses; - final ConstructTypeEnum constructType; - final String lemma; - - ConstructUses({ - required this.uses, - required this.constructType, - required this.lemma, - }); -} - class OneConstructUse { String? lemma; ConstructTypeEnum? constructType; @@ -148,6 +136,8 @@ class OneConstructUse { if (room == null || metadata.eventId == null) return null; return room.getEventById(metadata.eventId!); } + + int get pointValue => useType.pointValue; } class ConstructUseMetaData { diff --git a/lib/pangea/pages/analytics/construct_list.dart b/lib/pangea/pages/analytics/construct_list.dart index 9d032293d2..cc79983ba4 100644 --- a/lib/pangea/pages/analytics/construct_list.dart +++ b/lib/pangea/pages/analytics/construct_list.dart @@ -7,7 +7,7 @@ import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; import 'package:fluffychat/pangea/enum/time_span.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_representation_event.dart'; -import 'package:fluffychat/pangea/models/analytics/constructs_event.dart'; +import 'package:fluffychat/pangea/models/analytics/construct_list_model.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; import 'package:fluffychat/pangea/models/pangea_match_model.dart'; import 'package:fluffychat/pangea/pages/analytics/base_analytics.dart'; @@ -113,7 +113,14 @@ class ConstructListViewState extends State { forceUpdate: true, ) .whenComplete(() => setState(() => fetchingConstructs = false)) - .then((value) => setState(() => _constructs = value)); + .then( + (value) => setState( + () => constructs = ConstructListModel( + type: constructType, + uses: value, + ), + ), + ); refreshSubscription = widget.refreshStream.stream.listen((forceUpdate) { // postframe callback to let widget rebuild with the new selected parameter @@ -126,7 +133,10 @@ class ConstructListViewState extends State { ) .then( (value) => setState(() { - _constructs = value; + ConstructListModel( + type: constructType, + uses: value, + ); }), ); }); @@ -144,12 +154,6 @@ class ConstructListViewState extends State { setState(() {}); } - int get lemmaIndex => - constructs?.indexWhere( - (element) => element.lemma == currentLemma, - ) ?? - -1; - Future getMessageEvent( OneConstructUse use, ) async { @@ -187,14 +191,19 @@ class ConstructListViewState extends State { Future fetchUses() async { if (fetchingUses) return; - if (currentConstruct == null) { + if (currentLemma == null) { setState(() => _msgEvents.clear()); return; } setState(() => fetchingUses = true); try { - final List uses = currentConstruct!.uses; + final List uses = constructs?.constructs + .firstWhereOrNull( + (element) => element.lemma == currentLemma, + ) + ?.uses ?? + []; _msgEvents.clear(); for (final OneConstructUse use in uses) { @@ -213,54 +222,12 @@ class ConstructListViewState extends State { ErrorHandler.logError( e: err, s: s, - m: "Failed to fetch uses for current construct ${currentConstruct?.lemma}", + m: "Failed to fetch uses for current construct $currentLemma", ); } } - List? _constructs; - - List? get constructs { - if (_constructs == null) { - return null; - } - - final List filtered = List.from(_constructs!) - .map((event) => event.content.uses) - .expand((uses) => uses) - .cast() - .where((use) => use.constructType == constructType) - .toList(); - - final Map> lemmaToUses = {}; - for (final use in filtered) { - if (use.lemma == null) continue; - lemmaToUses[use.lemma!] ??= []; - lemmaToUses[use.lemma!]!.add(use); - } - - final constructUses = lemmaToUses.entries - .map( - (entry) => ConstructUses( - lemma: entry.key, - uses: entry.value, - constructType: constructType, - ), - ) - .toList(); - - constructUses.sort((a, b) { - final comp = b.uses.length.compareTo(a.uses.length); - if (comp != 0) return comp; - return a.lemma.compareTo(b.lemma); - }); - - return constructUses; - } - - ConstructUses? get currentConstruct => constructs?.firstWhereOrNull( - (element) => element.lemma == currentLemma, - ); + ConstructListModel? constructs; // given the current lemma and list of message events, return a list of // MessageEventMatch objects, which contain one PangeaMessageEvent to one PangeaMatch @@ -309,7 +276,7 @@ class ConstructListViewState extends State { ); } - if (constructs?.isEmpty ?? true) { + if (constructs?.constructs.isEmpty ?? true) { return Expanded( child: Center(child: Text(L10n.of(context)!.noDataFound)), ); @@ -317,17 +284,17 @@ class ConstructListViewState extends State { return Expanded( child: ListView.builder( - itemCount: constructs!.length, + itemCount: constructs!.constructs.length, itemBuilder: (context, index) { return ListTile( title: Text( - constructs![index].lemma, + constructs!.constructs[index].lemma, ), subtitle: Text( - '${L10n.of(context)!.total} ${constructs![index].uses.length}', + '${L10n.of(context)!.total} ${constructs!.constructs[index].uses.length}', ), onTap: () async { - final String lemma = constructs![index].lemma; + final String lemma = constructs!.constructs[index].lemma; setCurrentLemma(lemma); fetchUses().then((_) => showConstructMessagesDialog()); }, @@ -347,17 +314,17 @@ class ConstructMessagesDialog extends StatelessWidget { @override Widget build(BuildContext context) { - if (controller.currentLemma == null || - controller.constructs == null || - controller.lemmaIndex < 0 || - controller.lemmaIndex >= controller.constructs!.length) { + if (controller.currentLemma == null || controller.constructs == null) { return const AlertDialog(content: CircularProgressIndicator.adaptive()); } final msgEventMatches = controller.getMessageEventMatches(); - final noData = controller.constructs![controller.lemmaIndex].uses.length > - controller._msgEvents.length; + final currentConstruct = controller.constructs!.constructs.firstWhereOrNull( + (construct) => construct.lemma == controller.currentLemma, + ); + final noData = currentConstruct == null || + currentConstruct.uses.length > controller._msgEvents.length; return AlertDialog( title: Center(child: Text(controller.currentLemma!)), diff --git a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart index 5a30b9d0d0..a127a68315 100644 --- a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart +++ b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart @@ -8,7 +8,6 @@ import 'package:fluffychat/pangea/enum/progress_indicators_enum.dart'; import 'package:fluffychat/pangea/models/analytics/construct_list_model.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; import 'package:fluffychat/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart'; -import 'package:fluffychat/utils/string_color.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; @@ -62,13 +61,14 @@ class LearningProgressIndicatorsState /// Update the analytics data shown in the UI. This comes from a /// combination of stored events and locally cached data. Future updateAnalyticsData() async { - final constructEvents = await _pangeaController.analytics.getConstructs(); + final List storedUses = + await _pangeaController.analytics.getConstructs(); final List localUses = []; for (final uses in _pangeaController.analytics.messagesSinceUpdate.values) { localUses.addAll(uses); } - if (constructEvents == null || constructEvents.isEmpty) { + if (storedUses.isEmpty) { words = ConstructListModel( type: ConstructTypeEnum.vocab, uses: localUses, @@ -80,10 +80,8 @@ class LearningProgressIndicatorsState return; } - final List storedConstruct = - constructEvents.expand((e) => e.content.uses).toList(); final List allConstructs = [ - ...storedConstruct, + ...storedUses, ...localUses, ]; @@ -98,6 +96,7 @@ class LearningProgressIndicatorsState setState(() {}); } + /// Get the number of points for a given progress indicator int? getProgressPoints(ProgressIndicatorEnum indicator) { switch (indicator) { case ProgressIndicatorEnum.wordsUsed: @@ -109,15 +108,31 @@ class LearningProgressIndicatorsState } } + /// Get the total number of xp points, based on the point values of use types int get xpPoints { - final points = [ - words?.lemmas.length ?? 0, - errors?.lemmas.length ?? 0, - ]; - return points.reduce((a, b) => a + b); + return (words?.points ?? 0) + (errors?.points ?? 0); } - int get level => xpPoints ~/ 100; + /// Get the current level based on the number of xp points + int get level => xpPoints ~/ 500; + + double get levelBarWidth => FluffyThemes.columnWidth - (36 * 2) - 25; + double get pointsBarWidth { + final percent = (xpPoints % 500) / 500; + return levelBarWidth * percent; + } + + Color levelColor(int level) { + final colors = [ + const Color.fromARGB(255, 33, 97, 140), // Dark blue + const Color.fromARGB(255, 186, 104, 200), // Soft purple + const Color.fromARGB(255, 123, 31, 162), // Deep purple + const Color.fromARGB(255, 0, 150, 136), // Teal + const Color.fromARGB(255, 247, 143, 143), // Light pink + const Color.fromARGB(255, 220, 20, 60), // Crimson red + ]; + return colors[level % colors.length]; + } @override Widget build(BuildContext context) { @@ -130,7 +145,7 @@ class LearningProgressIndicatorsState mainAxisSize: MainAxisSize.min, children: [ Row( - crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.center, children: [ FutureBuilder( future: @@ -143,25 +158,25 @@ class LearningProgressIndicatorsState return Avatar( name: snapshot.data?.displayName ?? mxid.localpart ?? mxid, mxContent: snapshot.data?.avatarUrl, + size: 40, ); }, ), - Expanded( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: ProgressIndicatorEnum.values - .where( - (indicator) => indicator != ProgressIndicatorEnum.level, - ) - .map( - (indicator) => ProgressIndicatorBadge( - points: getProgressPoints(indicator), - onTap: () {}, - progressIndicator: indicator, - ), - ) - .toList(), - ), + const SizedBox(width: 10), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: ProgressIndicatorEnum.values + .where( + (indicator) => indicator != ProgressIndicatorEnum.level, + ) + .map( + (indicator) => ProgressIndicatorBadge( + points: getProgressPoints(indicator), + onTap: () {}, + progressIndicator: indicator, + ), + ) + .toList(), ), ], ), @@ -173,31 +188,41 @@ class LearningProgressIndicatorsState children: [ Positioned( right: 0, + left: 10, child: Row( children: [ SizedBox( - width: FluffyThemes.columnWidth - (36 * 2) - 25, + width: levelBarWidth, child: Expanded( child: Stack( alignment: Alignment.centerLeft, children: [ Container( - height: 15, + height: 20, decoration: BoxDecoration( - borderRadius: BorderRadius.circular( - AppConfig.borderRadius, + border: Border.all( + color: Theme.of(context) + .colorScheme + .primary + .withOpacity(0.5), + width: 2, ), - color: - Theme.of(context).colorScheme.onPrimary, + borderRadius: const BorderRadius.only( + topRight: + Radius.circular(AppConfig.borderRadius), + bottomRight: + Radius.circular(AppConfig.borderRadius), + ), + color: Theme.of(context) + .colorScheme + .primary + .withOpacity(0.2), ), ), AnimatedContainer( duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - height: 15, - width: - (FluffyThemes.columnWidth - (36 * 2) - 25) * - ((xpPoints % 100) / 100), + height: 16, + width: pointsBarWidth, decoration: BoxDecoration( borderRadius: BorderRadius.circular( AppConfig.borderRadius, @@ -214,12 +239,18 @@ class LearningProgressIndicatorsState ), Positioned( left: 0, - child: CircleAvatar( - backgroundColor: "$level $xpPoints".lightColorAvatar, - radius: 16, - child: Text( - "$level", - style: const TextStyle(color: Colors.white), + child: Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: levelColor(level), + borderRadius: BorderRadius.circular(32), + ), + child: Center( + child: Text( + "$level", + style: const TextStyle(color: Colors.white), + ), ), ), ), diff --git a/lib/utils/matrix_sdk_extensions/matrix_locals.dart b/lib/utils/matrix_sdk_extensions/matrix_locals.dart index b4536b6db4..333993442b 100644 --- a/lib/utils/matrix_sdk_extensions/matrix_locals.dart +++ b/lib/utils/matrix_sdk_extensions/matrix_locals.dart @@ -346,8 +346,8 @@ class MatrixLocals extends MatrixLocalizations { l10n.startedKeyVerification(senderName); @override - String invitedBy(String senderName) { - // TODO: implement invitedBy - throw UnimplementedError(); - } + String invitedBy(String senderName) => l10n.youInvitedBy(senderName); + + @override + String get cancelledSend => l10n.canceledSend; } From 785dd47cd9ef85ea86d80c042b9a0882538cb232 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 31 Jul 2024 13:08:48 -0400 Subject: [PATCH 153/288] fixed _maxMessagesCached after testing --- lib/pangea/controllers/my_analytics_controller.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart index bf58019f13..4389f297f1 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/my_analytics_controller.dart @@ -31,7 +31,7 @@ class MyAnalyticsController extends BaseController { /// the max number of messages that will be cached before /// an automatic update is triggered - final int _maxMessagesCached = 1; + final int _maxMessagesCached = 10; /// the number of minutes before an automatic update is triggered final int _minutesBeforeUpdate = 5; From 91a5d8414ccafa49825b9b1c624503f424c87679 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 31 Jul 2024 16:57:20 -0400 Subject: [PATCH 154/288] updated mini analytics UI to look better on mobile --- .../learning_progress_indicators.dart | 157 ++++++++---------- .../analytics_summary/progress_indicator.dart | 8 +- 2 files changed, 75 insertions(+), 90 deletions(-) diff --git a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart index a127a68315..d485be8519 100644 --- a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart +++ b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart @@ -116,7 +116,7 @@ class LearningProgressIndicatorsState /// Get the current level based on the number of xp points int get level => xpPoints ~/ 500; - double get levelBarWidth => FluffyThemes.columnWidth - (36 * 2) - 25; + double get levelBarWidth => FluffyThemes.columnWidth - (32 * 2) - 25; double get pointsBarWidth { final percent = (xpPoints % 500) / 500; return levelBarWidth * percent; @@ -136,16 +136,57 @@ class LearningProgressIndicatorsState @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 36, - vertical: 16, + final levelBar = Container( + height: 20, + width: levelBarWidth, + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).colorScheme.primary.withOpacity(0.5), + width: 2, + ), + borderRadius: const BorderRadius.only( + topRight: Radius.circular(AppConfig.borderRadius), + bottomRight: Radius.circular(AppConfig.borderRadius), + ), + color: Theme.of(context).colorScheme.primary.withOpacity(0.2), ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, + ); + + final xpBar = AnimatedContainer( + duration: FluffyThemes.animationDuration, + height: 16, + width: pointsBarWidth, + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topRight: Radius.circular(AppConfig.borderRadius), + bottomRight: Radius.circular(AppConfig.borderRadius), + ), + color: Theme.of(context).colorScheme.primary, + ), + ); + + final levelBadge = Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: levelColor(level), + borderRadius: BorderRadius.circular(32), + ), + child: Center( + child: Text( + "$level", + style: const TextStyle(color: Colors.white), + ), + ), + ); + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(46, 0, 32, 4), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ FutureBuilder( future: @@ -162,7 +203,6 @@ class LearningProgressIndicatorsState ); }, ), - const SizedBox(width: 10), Row( mainAxisAlignment: MainAxisAlignment.center, children: ProgressIndicatorEnum.values @@ -180,85 +220,24 @@ class LearningProgressIndicatorsState ), ], ), - const SizedBox(height: 4), - SizedBox( - height: 35, - child: Stack( - alignment: Alignment.centerLeft, - children: [ - Positioned( - right: 0, - left: 10, - child: Row( - children: [ - SizedBox( - width: levelBarWidth, - child: Expanded( - child: Stack( - alignment: Alignment.centerLeft, - children: [ - Container( - height: 20, - decoration: BoxDecoration( - border: Border.all( - color: Theme.of(context) - .colorScheme - .primary - .withOpacity(0.5), - width: 2, - ), - borderRadius: const BorderRadius.only( - topRight: - Radius.circular(AppConfig.borderRadius), - bottomRight: - Radius.circular(AppConfig.borderRadius), - ), - color: Theme.of(context) - .colorScheme - .primary - .withOpacity(0.2), - ), - ), - AnimatedContainer( - duration: FluffyThemes.animationDuration, - height: 16, - width: pointsBarWidth, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular( - AppConfig.borderRadius, - ), - color: Theme.of(context).colorScheme.primary, - ), - ), - ], - ), - ), - ), - ], - ), - ), - Positioned( - left: 0, - child: Container( - width: 32, - height: 32, - decoration: BoxDecoration( - color: levelColor(level), - borderRadius: BorderRadius.circular(32), - ), - child: Center( - child: Text( - "$level", - style: const TextStyle(color: Colors.white), - ), - ), - ), - ), - ], - ), + ), + Container( + // decoration: BoxDecoration( + // border: Border.all(color: Colors.green), + // ), + height: 36, + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Stack( + alignment: Alignment.center, + children: [ + Positioned(left: 16, right: 0, child: levelBar), + Positioned(left: 16, child: xpBar), + Positioned(left: 0, child: levelBadge), + ], ), - ], - ), + ), + const SizedBox(height: 16), + ], ); } } diff --git a/lib/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart b/lib/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart index 9fc9bb55c5..8fe774f1c4 100644 --- a/lib/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart +++ b/lib/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart @@ -41,7 +41,13 @@ class ProgressIndicatorBadge extends StatelessWidget { fontWeight: FontWeight.bold, ), ) - : const CircularProgressIndicator.adaptive(), + : const SizedBox( + height: 8, + width: 8, + child: CircularProgressIndicator.adaptive( + strokeWidth: 2, + ), + ), ], ), ), From d3a13705bde9195d0c86825382d787493607a2d2 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Thu, 1 Aug 2024 09:18:12 +0200 Subject: [PATCH 155/288] refactor: Only initialize FlutterLocalNotificationsPlugin once --- lib/utils/background_push.dart | 4 +- lib/utils/push_helper.dart | 601 +++++++++++++++++---------------- 2 files changed, 309 insertions(+), 296 deletions(-) diff --git a/lib/utils/background_push.dart b/lib/utils/background_push.dart index 039dde895e..a561ea3710 100644 --- a/lib/utils/background_push.dart +++ b/lib/utils/background_push.dart @@ -71,7 +71,7 @@ class BackgroundPush { BackgroundPush._(this.client) { firebase?.setListeners( - onMessage: (message) => pushHelper( + onMessage: (message) => PushHelper.processNotification( PushNotification.fromJson( Map.from(message['data'] ?? message), ), @@ -393,7 +393,7 @@ class BackgroundPush { ); // UP may strip the devices list data['devices'] ??= []; - await pushHelper( + await PushHelper.processNotification( PushNotification.fromJson(data), client: client, l10n: l10n, diff --git a/lib/utils/push_helper.dart b/lib/utils/push_helper.dart index edfdb49418..5a7e725ad8 100644 --- a/lib/utils/push_helper.dart +++ b/lib/utils/push_helper.dart @@ -18,27 +18,20 @@ import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/voip/callkeep_manager.dart'; -Future pushHelper( - PushNotification notification, { - Client? client, - L10n? l10n, - String? activeRoomId, - void Function(NotificationResponse?)? onSelectNotification, -}) async { - try { - await _tryPushHelper( - notification, - client: client, - l10n: l10n, - activeRoomId: activeRoomId, - onSelectNotification: onSelectNotification, - ); - } catch (e, s) { - Logs().v('Push Helper has crashed!', e, s); +abstract class PushHelper { + static FlutterLocalNotificationsPlugin? _flutterLocalNotificationsPlugin; + + static Future _getLocalNotificationsPlugin({ + void Function(NotificationResponse?)? onSelectNotification, + }) async { + var flutterlocalNotifcationsPlugin = _flutterLocalNotificationsPlugin; + if (flutterlocalNotifcationsPlugin != null) { + return flutterlocalNotifcationsPlugin; + } - // Initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project - final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); - await flutterLocalNotificationsPlugin.initialize( + flutterlocalNotifcationsPlugin = + _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); + await flutterlocalNotifcationsPlugin.initialize( const InitializationSettings( android: AndroidInitializationSettings('notifications_icon'), iOS: DarwinInitializationSettings(), @@ -46,299 +39,319 @@ Future pushHelper( onDidReceiveNotificationResponse: onSelectNotification, onDidReceiveBackgroundNotificationResponse: onSelectNotification, ); + return flutterlocalNotifcationsPlugin; + } - l10n ??= lookupL10n(const Locale('en')); - flutterLocalNotificationsPlugin.show( - notification.roomId?.hashCode ?? 0, - l10n.newMessageInFluffyChat, - l10n.openAppToReadMessages, - NotificationDetails( - iOS: const DarwinNotificationDetails(), - android: AndroidNotificationDetails( - AppConfig.pushNotificationsChannelId, - l10n.incomingMessages, - number: notification.counts?.unread, - ticker: l10n.unreadChatsInApp( - AppConfig.applicationName, - (notification.counts?.unread ?? 0).toString(), + static Future processNotification( + PushNotification notification, { + Client? client, + L10n? l10n, + String? activeRoomId, + void Function(NotificationResponse?)? onSelectNotification, + }) async { + try { + await _tryPushHelper( + notification, + client: client, + l10n: l10n, + activeRoomId: activeRoomId, + onSelectNotification: onSelectNotification, + ); + } catch (e, s) { + Logs().v('Push Helper has crashed!', e, s); + + final flutterLocalNotificationsPlugin = + await _getLocalNotificationsPlugin( + onSelectNotification: onSelectNotification, + ); + + l10n ??= lookupL10n(const Locale('en')); + flutterLocalNotificationsPlugin.show( + notification.roomId?.hashCode ?? 0, + l10n.newMessageInFluffyChat, + l10n.openAppToReadMessages, + NotificationDetails( + iOS: const DarwinNotificationDetails(), + android: AndroidNotificationDetails( + AppConfig.pushNotificationsChannelId, + l10n.incomingMessages, + number: notification.counts?.unread, + ticker: l10n.unreadChatsInApp( + AppConfig.applicationName, + (notification.counts?.unread ?? 0).toString(), + ), + importance: Importance.high, + priority: Priority.max, + shortcutId: notification.roomId, ), - importance: Importance.high, - priority: Priority.max, - shortcutId: notification.roomId, ), - ), - ); - rethrow; + ); + rethrow; + } } -} -Future _tryPushHelper( - PushNotification notification, { - Client? client, - L10n? l10n, - String? activeRoomId, - void Function(NotificationResponse?)? onSelectNotification, -}) async { - final isBackgroundMessage = client == null; - Logs().v( - 'Push helper has been started (background=$isBackgroundMessage).', - notification.toJson(), - ); - - if (notification.roomId != null && - activeRoomId == notification.roomId && - WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed) { - Logs().v('Room is in foreground. Stop push helper here.'); - return; - } + static Future _tryPushHelper( + PushNotification notification, { + Client? client, + L10n? l10n, + String? activeRoomId, + void Function(NotificationResponse?)? onSelectNotification, + }) async { + final isBackgroundMessage = client == null; + Logs().v( + 'Push helper has been started (background=$isBackgroundMessage).', + notification.toJson(), + ); + + if (notification.roomId != null && + activeRoomId == notification.roomId && + WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed) { + Logs().v('Room is in foreground. Stop push helper here.'); + return; + } + + final flutterLocalNotificationsPlugin = await _getLocalNotificationsPlugin( + onSelectNotification: onSelectNotification, + ); + + client ??= (await ClientManager.getClients( + initialize: false, + store: await SharedPreferences.getInstance(), + )) + .first; + final event = await client.getEventByPushNotification( + notification, + storeInDatabase: isBackgroundMessage, + ); - // Initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project - final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); - await flutterLocalNotificationsPlugin.initialize( - const InitializationSettings( - android: AndroidInitializationSettings('notifications_icon'), - iOS: DarwinInitializationSettings(), - ), - onDidReceiveNotificationResponse: onSelectNotification, - //onDidReceiveBackgroundNotificationResponse: onSelectNotification, - ); - - client ??= (await ClientManager.getClients( - initialize: false, - store: await SharedPreferences.getInstance(), - )) - .first; - final event = await client.getEventByPushNotification( - notification, - storeInDatabase: isBackgroundMessage, - ); - - if (event == null) { - Logs().v('Notification is a clearing indicator.'); - if (notification.counts?.unread == null || - notification.counts?.unread == 0) { - await flutterLocalNotificationsPlugin.cancelAll(); - } else { - // Make sure client is fully loaded and synced before dismiss notifications: - await client.roomsLoading; - await client.oneShotSync(); - final activeNotifications = - await flutterLocalNotificationsPlugin.getActiveNotifications(); - for (final activeNotification in activeNotifications) { - final room = client.rooms.singleWhereOrNull( - (room) => room.id.hashCode == activeNotification.id, - ); - if (room == null || !room.isUnreadOrInvited) { - flutterLocalNotificationsPlugin.cancel(activeNotification.id!); + if (event == null) { + Logs().v('Notification is a clearing indicator.'); + if (notification.counts?.unread == null || + notification.counts?.unread == 0) { + await flutterLocalNotificationsPlugin.cancelAll(); + } else { + // Make sure client is fully loaded and synced before dismiss notifications: + await client.roomsLoading; + await client.oneShotSync(); + final activeNotifications = + await flutterLocalNotificationsPlugin.getActiveNotifications(); + for (final activeNotification in activeNotifications) { + final room = client.rooms.singleWhereOrNull( + (room) => room.id.hashCode == activeNotification.id, + ); + if (room == null || !room.isUnreadOrInvited) { + flutterLocalNotificationsPlugin.cancel(activeNotification.id!); + } } } + return; } - return; - } - Logs().v('Push helper got notification event of type ${event.type}.'); + Logs().v('Push helper got notification event of type ${event.type}.'); - if (event.type.startsWith('m.call')) { - // make sure bg sync is on (needed to update hold, unhold events) - // prevent over write from app life cycle change - client.backgroundSync = true; - } + if (event.type.startsWith('m.call')) { + // make sure bg sync is on (needed to update hold, unhold events) + // prevent over write from app life cycle change + client.backgroundSync = true; + } - if (event.type == EventTypes.CallInvite) { - CallKeepManager().initialize(); - } else if (event.type == EventTypes.CallHangup) { - client.backgroundSync = false; - } + if (event.type == EventTypes.CallInvite) { + CallKeepManager().initialize(); + } else if (event.type == EventTypes.CallHangup) { + client.backgroundSync = false; + } - if (event.type.startsWith('m.call') && event.type != EventTypes.CallInvite) { - Logs().v('Push message is a m.call but not invite. Do not display.'); - return; - } + if (event.type.startsWith('m.call') && + event.type != EventTypes.CallInvite) { + Logs().v('Push message is a m.call but not invite. Do not display.'); + return; + } - if ((event.type.startsWith('m.call') && - event.type != EventTypes.CallInvite) || - event.type == 'org.matrix.call.sdp_stream_metadata_changed') { - Logs().v('Push message was for a call, but not call invite.'); - return; - } + if ((event.type.startsWith('m.call') && + event.type != EventTypes.CallInvite) || + event.type == 'org.matrix.call.sdp_stream_metadata_changed') { + Logs().v('Push message was for a call, but not call invite.'); + return; + } - l10n ??= await L10n.delegate.load(PlatformDispatcher.instance.locale); - final matrixLocals = MatrixLocals(l10n); - - // Calculate the body - final body = event.type == EventTypes.Encrypted - ? l10n.newMessageInFluffyChat - : await event.calcLocalizedBody( - matrixLocals, - plaintextBody: true, - withSenderNamePrefix: false, - hideReply: true, - hideEdit: true, - removeMarkdown: true, - ); - - // The person object for the android message style notification - final avatar = event.room.avatar - ?.getThumbnail( - client, - width: 256, - height: 256, - ) - .toString(); - final senderAvatar = event.room.isDirectChat - ? avatar - : event.senderFromMemoryOrFallback.avatarUrl - ?.getThumbnail( - client, - width: 256, - height: 256, - ) - .toString(); - - File? roomAvatarFile, senderAvatarFile; - try { - roomAvatarFile = avatar == null - ? null - : await DefaultCacheManager().getSingleFile(avatar); - } catch (e, s) { - Logs().e('Unable to get avatar picture', e, s); - } - try { - senderAvatarFile = event.room.isDirectChat - ? roomAvatarFile - : senderAvatar == null + l10n ??= await L10n.delegate.load(PlatformDispatcher.instance.locale); + final matrixLocals = MatrixLocals(l10n); + + // Calculate the body + final body = event.type == EventTypes.Encrypted + ? l10n.newMessageInFluffyChat + : await event.calcLocalizedBody( + matrixLocals, + plaintextBody: true, + withSenderNamePrefix: false, + hideReply: true, + hideEdit: true, + removeMarkdown: true, + ); + + // The person object for the android message style notification + final avatar = event.room.avatar + ?.getThumbnail( + client, + width: 256, + height: 256, + ) + .toString(); + final senderAvatar = event.room.isDirectChat + ? avatar + : event.senderFromMemoryOrFallback.avatarUrl + ?.getThumbnail( + client, + width: 256, + height: 256, + ) + .toString(); + + File? roomAvatarFile, senderAvatarFile; + try { + roomAvatarFile = avatar == null + ? null + : await DefaultCacheManager().getSingleFile(avatar); + } catch (e, s) { + Logs().e('Unable to get avatar picture', e, s); + } + try { + senderAvatarFile = event.room.isDirectChat + ? roomAvatarFile + : senderAvatar == null + ? null + : await DefaultCacheManager().getSingleFile(senderAvatar); + } catch (e, s) { + Logs().e('Unable to get avatar picture', e, s); + } + + final id = notification.roomId.hashCode; + + // Show notification + + final newMessage = Message( + body, + event.originServerTs, + Person( + bot: event.messageType == MessageTypes.Notice, + key: event.senderId, + name: event.senderFromMemoryOrFallback.calcDisplayname(), + icon: senderAvatarFile == null ? null - : await DefaultCacheManager().getSingleFile(senderAvatar); - } catch (e, s) { - Logs().e('Unable to get avatar picture', e, s); - } + : BitmapFilePathAndroidIcon(senderAvatarFile.path), + ), + ); - final id = notification.roomId.hashCode; + final messagingStyleInformation = PlatformInfos.isAndroid + ? await AndroidFlutterLocalNotificationsPlugin() + .getActiveNotificationMessagingStyle(id) + : null; + messagingStyleInformation?.messages?.add(newMessage); - // Show notification + final roomName = event.room.getLocalizedDisplayname(MatrixLocals(l10n)); - final newMessage = Message( - body, - event.originServerTs, - Person( - bot: event.messageType == MessageTypes.Notice, - key: event.senderId, - name: event.senderFromMemoryOrFallback.calcDisplayname(), - icon: senderAvatarFile == null - ? null - : BitmapFilePathAndroidIcon(senderAvatarFile.path), - ), - ); - - final messagingStyleInformation = PlatformInfos.isAndroid - ? await AndroidFlutterLocalNotificationsPlugin() - .getActiveNotificationMessagingStyle(id) - : null; - messagingStyleInformation?.messages?.add(newMessage); - - final roomName = event.room.getLocalizedDisplayname(MatrixLocals(l10n)); - - final notificationGroupId = - event.room.isDirectChat ? 'directChats' : 'groupChats'; - final groupName = event.room.isDirectChat ? l10n.directChats : l10n.groups; - - final messageRooms = AndroidNotificationChannelGroup( - notificationGroupId, - groupName, - ); - final roomsChannel = AndroidNotificationChannel( - event.room.id, - roomName, - groupId: notificationGroupId, - ); - - await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin>() - ?.createNotificationChannelGroup(messageRooms); - await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin>() - ?.createNotificationChannel(roomsChannel); - - final androidPlatformChannelSpecifics = AndroidNotificationDetails( - AppConfig.pushNotificationsChannelId, - l10n.incomingMessages, - number: notification.counts?.unread, - category: AndroidNotificationCategory.message, - shortcutId: event.room.id, - styleInformation: messagingStyleInformation ?? - MessagingStyleInformation( - Person( - name: event.senderFromMemoryOrFallback.calcDisplayname(), - icon: roomAvatarFile == null - ? null - : BitmapFilePathAndroidIcon(roomAvatarFile.path), - key: event.roomId, - important: event.room.isFavourite, + final notificationGroupId = + event.room.isDirectChat ? 'directChats' : 'groupChats'; + final groupName = event.room.isDirectChat ? l10n.directChats : l10n.groups; + + final messageRooms = AndroidNotificationChannelGroup( + notificationGroupId, + groupName, + ); + final roomsChannel = AndroidNotificationChannel( + event.room.id, + roomName, + groupId: notificationGroupId, + ); + + await flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.createNotificationChannelGroup(messageRooms); + await flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.createNotificationChannel(roomsChannel); + + final androidPlatformChannelSpecifics = AndroidNotificationDetails( + AppConfig.pushNotificationsChannelId, + l10n.incomingMessages, + number: notification.counts?.unread, + category: AndroidNotificationCategory.message, + shortcutId: event.room.id, + styleInformation: messagingStyleInformation ?? + MessagingStyleInformation( + Person( + name: event.senderFromMemoryOrFallback.calcDisplayname(), + icon: roomAvatarFile == null + ? null + : BitmapFilePathAndroidIcon(roomAvatarFile.path), + key: event.roomId, + important: event.room.isFavourite, + ), + conversationTitle: roomName, + groupConversation: !event.room.isDirectChat, + messages: [newMessage], ), - conversationTitle: roomName, - groupConversation: !event.room.isDirectChat, - messages: [newMessage], - ), - ticker: event.calcLocalizedBodyFallback( - matrixLocals, - plaintextBody: true, - withSenderNamePrefix: true, - hideReply: true, - hideEdit: true, - removeMarkdown: true, - ), - importance: Importance.high, - priority: Priority.max, - groupKey: notificationGroupId, - ); - const iOSPlatformChannelSpecifics = DarwinNotificationDetails(); - final platformChannelSpecifics = NotificationDetails( - android: androidPlatformChannelSpecifics, - iOS: iOSPlatformChannelSpecifics, - ); - - final title = event.room.getLocalizedDisplayname(MatrixLocals(l10n)); - - if (PlatformInfos.isAndroid && messagingStyleInformation == null) { - await _setShortcut(event, l10n, title, roomAvatarFile); - } + ticker: event.calcLocalizedBodyFallback( + matrixLocals, + plaintextBody: true, + withSenderNamePrefix: true, + hideReply: true, + hideEdit: true, + removeMarkdown: true, + ), + importance: Importance.high, + priority: Priority.max, + groupKey: notificationGroupId, + ); + const iOSPlatformChannelSpecifics = DarwinNotificationDetails(); + final platformChannelSpecifics = NotificationDetails( + android: androidPlatformChannelSpecifics, + iOS: iOSPlatformChannelSpecifics, + ); - await flutterLocalNotificationsPlugin.show( - id, - title, - body, - platformChannelSpecifics, - payload: event.roomId, - ); - Logs().v('Push helper has been completed!'); -} + final title = event.room.getLocalizedDisplayname(MatrixLocals(l10n)); -/// Creates a shortcut for Android platform but does not block displaying the -/// notification. This is optional but provides a nicer view of the -/// notification popup. -Future _setShortcut( - Event event, - L10n l10n, - String title, - File? avatarFile, -) async { - final flutterShortcuts = FlutterShortcuts(); - await flutterShortcuts.initialize(debug: !kReleaseMode); - await flutterShortcuts.pushShortcutItem( - shortcut: ShortcutItem( - id: event.room.id, - action: AppConfig.inviteLinkPrefix + event.room.id, - shortLabel: title, - conversationShortcut: true, - icon: avatarFile == null - ? null - : ShortcutMemoryIcon(jpegImage: await avatarFile.readAsBytes()) - .toString(), - shortcutIconAsset: avatarFile == null - ? ShortcutIconAsset.androidAsset - : ShortcutIconAsset.memoryAsset, - isImportant: event.room.isFavourite, - ), - ); + if (PlatformInfos.isAndroid && messagingStyleInformation == null) { + await _setShortcut(event, l10n, title, roomAvatarFile); + } + + await flutterLocalNotificationsPlugin.show( + id, + title, + body, + platformChannelSpecifics, + payload: event.roomId, + ); + Logs().v('Push helper has been completed!'); + } + + /// Creates a shortcut for Android platform but does not block displaying the + /// notification. This is optional but provides a nicer view of the + /// notification popup. + static Future _setShortcut( + Event event, + L10n l10n, + String title, + File? avatarFile, + ) async { + final flutterShortcuts = FlutterShortcuts(); + await flutterShortcuts.initialize(debug: !kReleaseMode); + await flutterShortcuts.pushShortcutItem( + shortcut: ShortcutItem( + id: event.room.id, + action: AppConfig.inviteLinkPrefix + event.room.id, + shortLabel: title, + conversationShortcut: true, + icon: avatarFile == null + ? null + : ShortcutMemoryIcon(jpegImage: await avatarFile.readAsBytes()) + .toString(), + shortcutIconAsset: avatarFile == null + ? ShortcutIconAsset.androidAsset + : ShortcutIconAsset.memoryAsset, + isImportant: event.room.isFavourite, + ), + ); + } } From a928ecec1e0272417fa35ef97112032e6de184f0 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Thu, 1 Aug 2024 09:47:49 +0200 Subject: [PATCH 156/288] Revert "refactor: Only initialize FlutterLocalNotificationsPlugin once" This reverts commit d3a13705bde9195d0c86825382d787493607a2d2. --- lib/utils/background_push.dart | 4 +- lib/utils/push_helper.dart | 601 ++++++++++++++++----------------- 2 files changed, 296 insertions(+), 309 deletions(-) diff --git a/lib/utils/background_push.dart b/lib/utils/background_push.dart index a561ea3710..039dde895e 100644 --- a/lib/utils/background_push.dart +++ b/lib/utils/background_push.dart @@ -71,7 +71,7 @@ class BackgroundPush { BackgroundPush._(this.client) { firebase?.setListeners( - onMessage: (message) => PushHelper.processNotification( + onMessage: (message) => pushHelper( PushNotification.fromJson( Map.from(message['data'] ?? message), ), @@ -393,7 +393,7 @@ class BackgroundPush { ); // UP may strip the devices list data['devices'] ??= []; - await PushHelper.processNotification( + await pushHelper( PushNotification.fromJson(data), client: client, l10n: l10n, diff --git a/lib/utils/push_helper.dart b/lib/utils/push_helper.dart index 5a7e725ad8..edfdb49418 100644 --- a/lib/utils/push_helper.dart +++ b/lib/utils/push_helper.dart @@ -18,20 +18,27 @@ import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/voip/callkeep_manager.dart'; -abstract class PushHelper { - static FlutterLocalNotificationsPlugin? _flutterLocalNotificationsPlugin; - - static Future _getLocalNotificationsPlugin({ - void Function(NotificationResponse?)? onSelectNotification, - }) async { - var flutterlocalNotifcationsPlugin = _flutterLocalNotificationsPlugin; - if (flutterlocalNotifcationsPlugin != null) { - return flutterlocalNotifcationsPlugin; - } +Future pushHelper( + PushNotification notification, { + Client? client, + L10n? l10n, + String? activeRoomId, + void Function(NotificationResponse?)? onSelectNotification, +}) async { + try { + await _tryPushHelper( + notification, + client: client, + l10n: l10n, + activeRoomId: activeRoomId, + onSelectNotification: onSelectNotification, + ); + } catch (e, s) { + Logs().v('Push Helper has crashed!', e, s); - flutterlocalNotifcationsPlugin = - _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); - await flutterlocalNotifcationsPlugin.initialize( + // Initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project + final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); + await flutterLocalNotificationsPlugin.initialize( const InitializationSettings( android: AndroidInitializationSettings('notifications_icon'), iOS: DarwinInitializationSettings(), @@ -39,319 +46,299 @@ abstract class PushHelper { onDidReceiveNotificationResponse: onSelectNotification, onDidReceiveBackgroundNotificationResponse: onSelectNotification, ); - return flutterlocalNotifcationsPlugin; - } - static Future processNotification( - PushNotification notification, { - Client? client, - L10n? l10n, - String? activeRoomId, - void Function(NotificationResponse?)? onSelectNotification, - }) async { - try { - await _tryPushHelper( - notification, - client: client, - l10n: l10n, - activeRoomId: activeRoomId, - onSelectNotification: onSelectNotification, - ); - } catch (e, s) { - Logs().v('Push Helper has crashed!', e, s); - - final flutterLocalNotificationsPlugin = - await _getLocalNotificationsPlugin( - onSelectNotification: onSelectNotification, - ); - - l10n ??= lookupL10n(const Locale('en')); - flutterLocalNotificationsPlugin.show( - notification.roomId?.hashCode ?? 0, - l10n.newMessageInFluffyChat, - l10n.openAppToReadMessages, - NotificationDetails( - iOS: const DarwinNotificationDetails(), - android: AndroidNotificationDetails( - AppConfig.pushNotificationsChannelId, - l10n.incomingMessages, - number: notification.counts?.unread, - ticker: l10n.unreadChatsInApp( - AppConfig.applicationName, - (notification.counts?.unread ?? 0).toString(), - ), - importance: Importance.high, - priority: Priority.max, - shortcutId: notification.roomId, + l10n ??= lookupL10n(const Locale('en')); + flutterLocalNotificationsPlugin.show( + notification.roomId?.hashCode ?? 0, + l10n.newMessageInFluffyChat, + l10n.openAppToReadMessages, + NotificationDetails( + iOS: const DarwinNotificationDetails(), + android: AndroidNotificationDetails( + AppConfig.pushNotificationsChannelId, + l10n.incomingMessages, + number: notification.counts?.unread, + ticker: l10n.unreadChatsInApp( + AppConfig.applicationName, + (notification.counts?.unread ?? 0).toString(), ), + importance: Importance.high, + priority: Priority.max, + shortcutId: notification.roomId, ), - ); - rethrow; - } - } - - static Future _tryPushHelper( - PushNotification notification, { - Client? client, - L10n? l10n, - String? activeRoomId, - void Function(NotificationResponse?)? onSelectNotification, - }) async { - final isBackgroundMessage = client == null; - Logs().v( - 'Push helper has been started (background=$isBackgroundMessage).', - notification.toJson(), - ); - - if (notification.roomId != null && - activeRoomId == notification.roomId && - WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed) { - Logs().v('Room is in foreground. Stop push helper here.'); - return; - } - - final flutterLocalNotificationsPlugin = await _getLocalNotificationsPlugin( - onSelectNotification: onSelectNotification, + ), ); + rethrow; + } +} - client ??= (await ClientManager.getClients( - initialize: false, - store: await SharedPreferences.getInstance(), - )) - .first; - final event = await client.getEventByPushNotification( - notification, - storeInDatabase: isBackgroundMessage, - ); +Future _tryPushHelper( + PushNotification notification, { + Client? client, + L10n? l10n, + String? activeRoomId, + void Function(NotificationResponse?)? onSelectNotification, +}) async { + final isBackgroundMessage = client == null; + Logs().v( + 'Push helper has been started (background=$isBackgroundMessage).', + notification.toJson(), + ); + + if (notification.roomId != null && + activeRoomId == notification.roomId && + WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed) { + Logs().v('Room is in foreground. Stop push helper here.'); + return; + } - if (event == null) { - Logs().v('Notification is a clearing indicator.'); - if (notification.counts?.unread == null || - notification.counts?.unread == 0) { - await flutterLocalNotificationsPlugin.cancelAll(); - } else { - // Make sure client is fully loaded and synced before dismiss notifications: - await client.roomsLoading; - await client.oneShotSync(); - final activeNotifications = - await flutterLocalNotificationsPlugin.getActiveNotifications(); - for (final activeNotification in activeNotifications) { - final room = client.rooms.singleWhereOrNull( - (room) => room.id.hashCode == activeNotification.id, - ); - if (room == null || !room.isUnreadOrInvited) { - flutterLocalNotificationsPlugin.cancel(activeNotification.id!); - } + // Initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project + final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); + await flutterLocalNotificationsPlugin.initialize( + const InitializationSettings( + android: AndroidInitializationSettings('notifications_icon'), + iOS: DarwinInitializationSettings(), + ), + onDidReceiveNotificationResponse: onSelectNotification, + //onDidReceiveBackgroundNotificationResponse: onSelectNotification, + ); + + client ??= (await ClientManager.getClients( + initialize: false, + store: await SharedPreferences.getInstance(), + )) + .first; + final event = await client.getEventByPushNotification( + notification, + storeInDatabase: isBackgroundMessage, + ); + + if (event == null) { + Logs().v('Notification is a clearing indicator.'); + if (notification.counts?.unread == null || + notification.counts?.unread == 0) { + await flutterLocalNotificationsPlugin.cancelAll(); + } else { + // Make sure client is fully loaded and synced before dismiss notifications: + await client.roomsLoading; + await client.oneShotSync(); + final activeNotifications = + await flutterLocalNotificationsPlugin.getActiveNotifications(); + for (final activeNotification in activeNotifications) { + final room = client.rooms.singleWhereOrNull( + (room) => room.id.hashCode == activeNotification.id, + ); + if (room == null || !room.isUnreadOrInvited) { + flutterLocalNotificationsPlugin.cancel(activeNotification.id!); } } - return; - } - Logs().v('Push helper got notification event of type ${event.type}.'); - - if (event.type.startsWith('m.call')) { - // make sure bg sync is on (needed to update hold, unhold events) - // prevent over write from app life cycle change - client.backgroundSync = true; - } - - if (event.type == EventTypes.CallInvite) { - CallKeepManager().initialize(); - } else if (event.type == EventTypes.CallHangup) { - client.backgroundSync = false; - } - - if (event.type.startsWith('m.call') && - event.type != EventTypes.CallInvite) { - Logs().v('Push message is a m.call but not invite. Do not display.'); - return; } + return; + } + Logs().v('Push helper got notification event of type ${event.type}.'); - if ((event.type.startsWith('m.call') && - event.type != EventTypes.CallInvite) || - event.type == 'org.matrix.call.sdp_stream_metadata_changed') { - Logs().v('Push message was for a call, but not call invite.'); - return; - } + if (event.type.startsWith('m.call')) { + // make sure bg sync is on (needed to update hold, unhold events) + // prevent over write from app life cycle change + client.backgroundSync = true; + } - l10n ??= await L10n.delegate.load(PlatformDispatcher.instance.locale); - final matrixLocals = MatrixLocals(l10n); - - // Calculate the body - final body = event.type == EventTypes.Encrypted - ? l10n.newMessageInFluffyChat - : await event.calcLocalizedBody( - matrixLocals, - plaintextBody: true, - withSenderNamePrefix: false, - hideReply: true, - hideEdit: true, - removeMarkdown: true, - ); - - // The person object for the android message style notification - final avatar = event.room.avatar - ?.getThumbnail( - client, - width: 256, - height: 256, - ) - .toString(); - final senderAvatar = event.room.isDirectChat - ? avatar - : event.senderFromMemoryOrFallback.avatarUrl - ?.getThumbnail( - client, - width: 256, - height: 256, - ) - .toString(); - - File? roomAvatarFile, senderAvatarFile; - try { - roomAvatarFile = avatar == null - ? null - : await DefaultCacheManager().getSingleFile(avatar); - } catch (e, s) { - Logs().e('Unable to get avatar picture', e, s); - } - try { - senderAvatarFile = event.room.isDirectChat - ? roomAvatarFile - : senderAvatar == null - ? null - : await DefaultCacheManager().getSingleFile(senderAvatar); - } catch (e, s) { - Logs().e('Unable to get avatar picture', e, s); - } + if (event.type == EventTypes.CallInvite) { + CallKeepManager().initialize(); + } else if (event.type == EventTypes.CallHangup) { + client.backgroundSync = false; + } - final id = notification.roomId.hashCode; + if (event.type.startsWith('m.call') && event.type != EventTypes.CallInvite) { + Logs().v('Push message is a m.call but not invite. Do not display.'); + return; + } - // Show notification + if ((event.type.startsWith('m.call') && + event.type != EventTypes.CallInvite) || + event.type == 'org.matrix.call.sdp_stream_metadata_changed') { + Logs().v('Push message was for a call, but not call invite.'); + return; + } - final newMessage = Message( - body, - event.originServerTs, - Person( - bot: event.messageType == MessageTypes.Notice, - key: event.senderId, - name: event.senderFromMemoryOrFallback.calcDisplayname(), - icon: senderAvatarFile == null + l10n ??= await L10n.delegate.load(PlatformDispatcher.instance.locale); + final matrixLocals = MatrixLocals(l10n); + + // Calculate the body + final body = event.type == EventTypes.Encrypted + ? l10n.newMessageInFluffyChat + : await event.calcLocalizedBody( + matrixLocals, + plaintextBody: true, + withSenderNamePrefix: false, + hideReply: true, + hideEdit: true, + removeMarkdown: true, + ); + + // The person object for the android message style notification + final avatar = event.room.avatar + ?.getThumbnail( + client, + width: 256, + height: 256, + ) + .toString(); + final senderAvatar = event.room.isDirectChat + ? avatar + : event.senderFromMemoryOrFallback.avatarUrl + ?.getThumbnail( + client, + width: 256, + height: 256, + ) + .toString(); + + File? roomAvatarFile, senderAvatarFile; + try { + roomAvatarFile = avatar == null + ? null + : await DefaultCacheManager().getSingleFile(avatar); + } catch (e, s) { + Logs().e('Unable to get avatar picture', e, s); + } + try { + senderAvatarFile = event.room.isDirectChat + ? roomAvatarFile + : senderAvatar == null ? null - : BitmapFilePathAndroidIcon(senderAvatarFile.path), - ), - ); - - final messagingStyleInformation = PlatformInfos.isAndroid - ? await AndroidFlutterLocalNotificationsPlugin() - .getActiveNotificationMessagingStyle(id) - : null; - messagingStyleInformation?.messages?.add(newMessage); - - final roomName = event.room.getLocalizedDisplayname(MatrixLocals(l10n)); + : await DefaultCacheManager().getSingleFile(senderAvatar); + } catch (e, s) { + Logs().e('Unable to get avatar picture', e, s); + } - final notificationGroupId = - event.room.isDirectChat ? 'directChats' : 'groupChats'; - final groupName = event.room.isDirectChat ? l10n.directChats : l10n.groups; + final id = notification.roomId.hashCode; - final messageRooms = AndroidNotificationChannelGroup( - notificationGroupId, - groupName, - ); - final roomsChannel = AndroidNotificationChannel( - event.room.id, - roomName, - groupId: notificationGroupId, - ); + // Show notification - await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin>() - ?.createNotificationChannelGroup(messageRooms); - await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin>() - ?.createNotificationChannel(roomsChannel); - - final androidPlatformChannelSpecifics = AndroidNotificationDetails( - AppConfig.pushNotificationsChannelId, - l10n.incomingMessages, - number: notification.counts?.unread, - category: AndroidNotificationCategory.message, - shortcutId: event.room.id, - styleInformation: messagingStyleInformation ?? - MessagingStyleInformation( - Person( - name: event.senderFromMemoryOrFallback.calcDisplayname(), - icon: roomAvatarFile == null - ? null - : BitmapFilePathAndroidIcon(roomAvatarFile.path), - key: event.roomId, - important: event.room.isFavourite, - ), - conversationTitle: roomName, - groupConversation: !event.room.isDirectChat, - messages: [newMessage], + final newMessage = Message( + body, + event.originServerTs, + Person( + bot: event.messageType == MessageTypes.Notice, + key: event.senderId, + name: event.senderFromMemoryOrFallback.calcDisplayname(), + icon: senderAvatarFile == null + ? null + : BitmapFilePathAndroidIcon(senderAvatarFile.path), + ), + ); + + final messagingStyleInformation = PlatformInfos.isAndroid + ? await AndroidFlutterLocalNotificationsPlugin() + .getActiveNotificationMessagingStyle(id) + : null; + messagingStyleInformation?.messages?.add(newMessage); + + final roomName = event.room.getLocalizedDisplayname(MatrixLocals(l10n)); + + final notificationGroupId = + event.room.isDirectChat ? 'directChats' : 'groupChats'; + final groupName = event.room.isDirectChat ? l10n.directChats : l10n.groups; + + final messageRooms = AndroidNotificationChannelGroup( + notificationGroupId, + groupName, + ); + final roomsChannel = AndroidNotificationChannel( + event.room.id, + roomName, + groupId: notificationGroupId, + ); + + await flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.createNotificationChannelGroup(messageRooms); + await flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.createNotificationChannel(roomsChannel); + + final androidPlatformChannelSpecifics = AndroidNotificationDetails( + AppConfig.pushNotificationsChannelId, + l10n.incomingMessages, + number: notification.counts?.unread, + category: AndroidNotificationCategory.message, + shortcutId: event.room.id, + styleInformation: messagingStyleInformation ?? + MessagingStyleInformation( + Person( + name: event.senderFromMemoryOrFallback.calcDisplayname(), + icon: roomAvatarFile == null + ? null + : BitmapFilePathAndroidIcon(roomAvatarFile.path), + key: event.roomId, + important: event.room.isFavourite, ), - ticker: event.calcLocalizedBodyFallback( - matrixLocals, - plaintextBody: true, - withSenderNamePrefix: true, - hideReply: true, - hideEdit: true, - removeMarkdown: true, - ), - importance: Importance.high, - priority: Priority.max, - groupKey: notificationGroupId, - ); - const iOSPlatformChannelSpecifics = DarwinNotificationDetails(); - final platformChannelSpecifics = NotificationDetails( - android: androidPlatformChannelSpecifics, - iOS: iOSPlatformChannelSpecifics, - ); - - final title = event.room.getLocalizedDisplayname(MatrixLocals(l10n)); - - if (PlatformInfos.isAndroid && messagingStyleInformation == null) { - await _setShortcut(event, l10n, title, roomAvatarFile); - } - - await flutterLocalNotificationsPlugin.show( - id, - title, - body, - platformChannelSpecifics, - payload: event.roomId, - ); - Logs().v('Push helper has been completed!'); + conversationTitle: roomName, + groupConversation: !event.room.isDirectChat, + messages: [newMessage], + ), + ticker: event.calcLocalizedBodyFallback( + matrixLocals, + plaintextBody: true, + withSenderNamePrefix: true, + hideReply: true, + hideEdit: true, + removeMarkdown: true, + ), + importance: Importance.high, + priority: Priority.max, + groupKey: notificationGroupId, + ); + const iOSPlatformChannelSpecifics = DarwinNotificationDetails(); + final platformChannelSpecifics = NotificationDetails( + android: androidPlatformChannelSpecifics, + iOS: iOSPlatformChannelSpecifics, + ); + + final title = event.room.getLocalizedDisplayname(MatrixLocals(l10n)); + + if (PlatformInfos.isAndroid && messagingStyleInformation == null) { + await _setShortcut(event, l10n, title, roomAvatarFile); } - /// Creates a shortcut for Android platform but does not block displaying the - /// notification. This is optional but provides a nicer view of the - /// notification popup. - static Future _setShortcut( - Event event, - L10n l10n, - String title, - File? avatarFile, - ) async { - final flutterShortcuts = FlutterShortcuts(); - await flutterShortcuts.initialize(debug: !kReleaseMode); - await flutterShortcuts.pushShortcutItem( - shortcut: ShortcutItem( - id: event.room.id, - action: AppConfig.inviteLinkPrefix + event.room.id, - shortLabel: title, - conversationShortcut: true, - icon: avatarFile == null - ? null - : ShortcutMemoryIcon(jpegImage: await avatarFile.readAsBytes()) - .toString(), - shortcutIconAsset: avatarFile == null - ? ShortcutIconAsset.androidAsset - : ShortcutIconAsset.memoryAsset, - isImportant: event.room.isFavourite, - ), - ); - } + await flutterLocalNotificationsPlugin.show( + id, + title, + body, + platformChannelSpecifics, + payload: event.roomId, + ); + Logs().v('Push helper has been completed!'); +} + +/// Creates a shortcut for Android platform but does not block displaying the +/// notification. This is optional but provides a nicer view of the +/// notification popup. +Future _setShortcut( + Event event, + L10n l10n, + String title, + File? avatarFile, +) async { + final flutterShortcuts = FlutterShortcuts(); + await flutterShortcuts.initialize(debug: !kReleaseMode); + await flutterShortcuts.pushShortcutItem( + shortcut: ShortcutItem( + id: event.room.id, + action: AppConfig.inviteLinkPrefix + event.room.id, + shortLabel: title, + conversationShortcut: true, + icon: avatarFile == null + ? null + : ShortcutMemoryIcon(jpegImage: await avatarFile.readAsBytes()) + .toString(), + shortcutIconAsset: avatarFile == null + ? ShortcutIconAsset.androidAsset + : ShortcutIconAsset.memoryAsset, + isImportant: event.room.isFavourite, + ), + ); } From 3f02c5010b62c3a51ee8f43d6aa8a6a9e3722abb Mon Sep 17 00:00:00 2001 From: Krille Date: Sun, 4 Aug 2024 16:50:37 +0200 Subject: [PATCH 157/288] refactor: Migrate android gradle plugin --- android/app/build.gradle | 19 ++--- android/build.gradle | 14 ---- android/settings.gradle | 31 ++++++-- scripts/enable-android-google-services.patch | 83 +++++++------------- 4 files changed, 59 insertions(+), 88 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index fba1b51896..7520ff2ab9 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,3 +1,10 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" + //id "com.google.gms.google-services" +} + def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { @@ -6,11 +13,6 @@ if (localPropertiesFile.exists()) { } } -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' @@ -21,10 +23,6 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - def keystoreProperties = new Properties() def keystorePropertiesFile = rootProject.file('key.properties') if (keystorePropertiesFile.exists()) { @@ -85,9 +83,6 @@ flutter { } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" //implementation 'com.google.firebase:firebase-messaging:19.0.1' // Workaround for https://github.com/microg/android_packages_apps_GmsCore/issues/313#issuecomment-617651698 implementation 'androidx.multidex:multidex:2.0.1' } - -//apply plugin: 'com.google.gms.google-services' diff --git a/android/build.gradle b/android/build.gradle index a8c597ff19..bc157bd1a1 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,17 +1,3 @@ -buildscript { - ext.kotlin_version = '1.8.0' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:7.1.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - //classpath 'com.google.gms:google-services:4.3.8' - } -} - allprojects { repositories { google() diff --git a/android/settings.gradle b/android/settings.gradle index 44e62bcf06..b2fd960a7b 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,11 +1,26 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.1.2" apply false + id "org.jetbrains.kotlin.android" version "1.8.0" apply false + // id "com.google.gms.google-services" version "4.3.8" apply false +} + +include ":app" \ No newline at end of file diff --git a/scripts/enable-android-google-services.patch b/scripts/enable-android-google-services.patch index 88bfb5ce8f..7c783015cd 100644 --- a/scripts/enable-android-google-services.patch +++ b/scripts/enable-android-google-services.patch @@ -1,32 +1,24 @@ diff --git a/android/app/build.gradle b/android/app/build.gradle -index bf972f30..46cebdc6 100644 +index 7520ff2a..ae376d9d 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle -@@ -70,6 +70,10 @@ - } - release { - signingConfig signingConfigs.release -+ minifyEnabled false -+ shrinkResources false -+ -+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - // https://stackoverflow.com/a/77494454/8222484 -@@ -78,8 +82,11 @@ flutter { +@@ -2,7 +2,7 @@ plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +- //id "com.google.gms.google-services" ++ id "com.google.gms.google-services" + } + + def localProperties = new Properties() +@@ -83,6 +83,6 @@ flutter { + } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - //implementation 'com.google.firebase:firebase-messaging:19.0.1' // Workaround for https://github.com/microg/android_packages_apps_GmsCore/issues/313#issuecomment-617651698 + implementation 'com.google.firebase:firebase-messaging:19.0.1' // Workaround for https://github.com/microg/android_packages_apps_GmsCore/issues/313#issuecomment-617651698 -+ testImplementation 'junit:junit:4.12' -+ androidTestImplementation 'androidx.test:runner:1.1.1' -+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' implementation 'androidx.multidex:multidex:2.0.1' } - --//apply plugin: 'com.google.gms.google-services' -+apply plugin: 'com.google.gms.google-services' diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index d0e0fbc9..0a546da0 100644 --- a/android/app/proguard-rules.pro @@ -93,42 +85,25 @@ index d9930f55..510e9845 100644 } -*/ \ No newline at end of file -diff --git a/android/app/src/main/kotlin/chat/fluffy/fluffychat/MainActivity.kt b/android/app/src/main/kotlin/chat/fluffy/fluffychat/MainActivity.kt -index 1afc4606..894d1571 100644 ---- a/android/app/src/main/kotlin/chat/fluffy/fluffychat/MainActivity.kt -+++ b/android/app/src/main/kotlin/chat/fluffy/fluffychat/MainActivity.kt -@@ -7,13 +7,11 @@ import android.content.Context - import androidx.multidex.MultiDex - - class MainActivity : FlutterActivity() { -- - override fun attachBaseContext(base: Context) { - super.attachBaseContext(base) - MultiDex.install(this) - } - -- - override fun provideFlutterEngine(context: Context): FlutterEngine? { - return provideEngine(this) - } -diff --git a/android/build.gradle b/android/build.gradle -index bd394967..2e9d54de 100644 ---- a/android/build.gradle -+++ b/android/build.gradle -@@ -8,7 +8,7 @@ buildscript { - dependencies { - classpath 'com.android.tools.build:gradle:7.1.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" -- //classpath 'com.google.gms:google-services:4.3.8' -+ classpath 'com.google.gms:google-services:4.3.8' - } +diff --git a/android/settings.gradle b/android/settings.gradle +index b2fd960a..fdb01a4d 100644 +--- a/android/settings.gradle ++++ b/android/settings.gradle +@@ -20,7 +20,7 @@ plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.1.2" apply false + id "org.jetbrains.kotlin.android" version "1.8.0" apply false +- // id "com.google.gms.google-services" version "4.3.8" apply false ++ id "com.google.gms.google-services" version "4.3.8" apply false } + include ":app" +\ No newline at end of file diff --git a/lib/utils/background_push.dart b/lib/utils/background_push.dart -index 8e67ae92..da4da5c3 100644 +index 039dde89..1cefdd71 100644 --- a/lib/utils/background_push.dart +++ b/lib/utils/background_push.dart -@@ -39,7 +39,7 @@ import '../config/setting_keys.dart'; +@@ -38,7 +38,7 @@ import '../config/setting_keys.dart'; import '../widgets/matrix.dart'; import 'platform_infos.dart'; @@ -137,7 +112,7 @@ index 8e67ae92..da4da5c3 100644 class NoTokenException implements Exception { String get cause => 'Cannot get firebase token'; -@@ -64,7 +64,7 @@ class BackgroundPush { +@@ -63,7 +63,7 @@ class BackgroundPush { final pendingTests = >{}; @@ -147,10 +122,10 @@ index 8e67ae92..da4da5c3 100644 DateTime? lastReceivedPush; diff --git a/pubspec.yaml b/pubspec.yaml -index 193e6ed6..f70e48d4 100644 +index 69c80d6e..efd32d89 100644 --- a/pubspec.yaml +++ b/pubspec.yaml -@@ -26,7 +26,7 @@ dependencies: +@@ -25,7 +25,7 @@ dependencies: emoji_picker_flutter: ^2.1.1 emoji_proposal: ^0.0.1 emojis: ^0.9.9 From 0bd61ced3fbb0cfd970a2fb23eef7fbd6676872c Mon Sep 17 00:00:00 2001 From: Krille Date: Sun, 4 Aug 2024 15:00:23 +0200 Subject: [PATCH 158/288] feat: Convert opus to aac on iOS before playing --- ios/Podfile | 2 +- ios/Runner.xcodeproj/project.pbxproj | 6 ++-- lib/pages/chat/events/audio_player.dart | 28 +++++++++++++++++++ macos/Flutter/GeneratedPluginRegistrant.swift | 2 ++ pubspec.lock | 24 ++++++++++++++++ pubspec.yaml | 1 + 6 files changed, 59 insertions(+), 4 deletions(-) diff --git a/ios/Podfile b/ios/Podfile index c8df069d31..1f9db6f6e3 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '12.0' +platform :ios, '12.1' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 8d75b924ac..9d57b73aad 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -475,7 +475,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -564,7 +564,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -613,7 +613,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/lib/pages/chat/events/audio_player.dart b/lib/pages/chat/events/audio_player.dart index 40d4cf29fa..a3de4e02bb 100644 --- a/lib/pages/chat/events/audio_player.dart +++ b/lib/pages/chat/events/audio_player.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:just_audio/just_audio.dart'; import 'package:matrix/matrix.dart'; +import 'package:nyx_converter/nyx_converter.dart'; import 'package:path_provider/path_provider.dart'; import 'package:fluffychat/utils/error_reporter.dart'; @@ -70,7 +71,34 @@ class AudioPlayerState extends State { widget.event.attachmentOrThumbnailMxcUrl()!.pathSegments.last, ); file = File('${tempDir.path}/${fileName}_${matrixFile.name}'); + await file.writeAsBytes(matrixFile.bytes); + + if (Platform.isIOS && + matrixFile.mimeType.toLowerCase() == 'audio/ogg') { + Logs().v('Convert ogg audio file for iOS...'); + final convertedFile = File('${file.path}.aac'); + if (await convertedFile.exists()) { + file = convertedFile; + } else { + final completer = Completer(); + NyxConverter.convertTo( + file.path, + tempDir.path, + fileName: '${fileName}_${matrixFile.name}', + container: NyxContainer.aac, + execution: ( + String? path, + NyxStatus status, { + String? errorMessage, + }) { + if (path != null) completer.complete(File(path)); + if (errorMessage != null) completer.completeError(errorMessage); + }, + ); + file = await completer.future; + } + } } setState(() { diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 50894732e6..552a22ed93 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -11,6 +11,7 @@ import desktop_drop import device_info_plus import dynamic_color import emoji_picker_flutter +import ffmpeg_kit_flutter_full_gpl import file_selector_macos import flutter_app_badger import flutter_local_notifications @@ -42,6 +43,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) EmojiPickerFlutterPlugin.register(with: registry.registrar(forPlugin: "EmojiPickerFlutterPlugin")) + FFmpegKitFlutterPlugin.register(with: registry.registrar(forPlugin: "FFmpegKitFlutterPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FlutterAppBadgerPlugin.register(with: registry.registrar(forPlugin: "FlutterAppBadgerPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) diff --git a/pubspec.lock b/pubspec.lock index c46a3944fb..98e3900cfd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -385,6 +385,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + ffmpeg_kit_flutter_full_gpl: + dependency: transitive + description: + name: ffmpeg_kit_flutter_full_gpl + sha256: "4f269bcb636bfcb544e5b4d65c706a3d311839970cb42638e72406410c1b5b7b" + url: "https://pub.dev" + source: hosted + version: "6.0.3" + ffmpeg_kit_flutter_platform_interface: + dependency: transitive + description: + name: ffmpeg_kit_flutter_platform_interface + sha256: addf046ae44e190ad0101b2fde2ad909a3cd08a2a109f6106d2f7048b7abedee + url: "https://pub.dev" + source: hosted + version: "0.2.1" file: dependency: transitive description: @@ -1270,6 +1286,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" + nyx_converter: + dependency: "direct main" + description: + name: nyx_converter + sha256: "29684f29a650119f0417f7faa736c5b8ac65e5b32dbae40412bd6c3d7a692fc7" + url: "https://pub.dev" + source: hosted + version: "0.1.1" olm: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 69c80d6e1a..ac33419754 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -66,6 +66,7 @@ dependencies: linkify: ^5.0.0 matrix: ^0.31.0 native_imaging: ^0.1.1 + nyx_converter: ^0.1.1 package_info_plus: ^6.0.0 pasteboard: ^0.2.0 path: ^1.9.0 From a7473af40f8d118741204ad86d2a786400da6a1d Mon Sep 17 00:00:00 2001 From: Krille Date: Sun, 4 Aug 2024 19:22:08 +0200 Subject: [PATCH 159/288] feat: Record voice message with opus/ogg if supported --- lib/pages/chat/events/audio_player.dart | 26 +++------------ lib/pages/chat/recording_dialog.dart | 9 ++++-- licenses.yaml | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 -- pubspec.lock | 32 +++++-------------- pubspec.yaml | 2 +- 6 files changed, 21 insertions(+), 51 deletions(-) diff --git a/lib/pages/chat/events/audio_player.dart b/lib/pages/chat/events/audio_player.dart index a3de4e02bb..430bac2d71 100644 --- a/lib/pages/chat/events/audio_player.dart +++ b/lib/pages/chat/events/audio_player.dart @@ -6,7 +6,7 @@ import 'package:flutter/material.dart'; import 'package:just_audio/just_audio.dart'; import 'package:matrix/matrix.dart'; -import 'package:nyx_converter/nyx_converter.dart'; +import 'package:opus_caf_converter_dart/opus_caf_converter_dart.dart'; import 'package:path_provider/path_provider.dart'; import 'package:fluffychat/utils/error_reporter.dart'; @@ -77,27 +77,11 @@ class AudioPlayerState extends State { if (Platform.isIOS && matrixFile.mimeType.toLowerCase() == 'audio/ogg') { Logs().v('Convert ogg audio file for iOS...'); - final convertedFile = File('${file.path}.aac'); - if (await convertedFile.exists()) { - file = convertedFile; - } else { - final completer = Completer(); - NyxConverter.convertTo( - file.path, - tempDir.path, - fileName: '${fileName}_${matrixFile.name}', - container: NyxContainer.aac, - execution: ( - String? path, - NyxStatus status, { - String? errorMessage, - }) { - if (path != null) completer.complete(File(path)); - if (errorMessage != null) completer.completeError(errorMessage); - }, - ); - file = await completer.future; + final convertedFile = File('${file.path}.caf'); + if (await convertedFile.exists() == false) { + OpusCaf().convertOpusToCaf(file.path, convertedFile.path); } + file = convertedFile; } } diff --git a/lib/pages/chat/recording_dialog.dart b/lib/pages/chat/recording_dialog.dart index bc92f3ff54..be22ff3086 100644 --- a/lib/pages/chat/recording_dialog.dart +++ b/lib/pages/chat/recording_dialog.dart @@ -13,7 +13,6 @@ import 'package:fluffychat/utils/platform_infos.dart'; import 'events/audio_player.dart'; class RecordingDialog extends StatefulWidget { - static const String recordingFileType = 'm4a'; const RecordingDialog({ super.key, }); @@ -36,9 +35,11 @@ class RecordingDialogState extends State { Future startRecording() async { try { + final useOpus = + await _audioRecorder.isEncoderSupported(AudioEncoder.opus); final tempDir = await getTemporaryDirectory(); final path = _recordedPath = - '${tempDir.path}/recording${DateTime.now().microsecondsSinceEpoch}.${RecordingDialog.recordingFileType}'; + '${tempDir.path}/recording${DateTime.now().microsecondsSinceEpoch}.${useOpus ? 'ogg' : 'm4a'}'; final result = await _audioRecorder.hasPermission(); if (result != true) { @@ -46,14 +47,16 @@ class RecordingDialogState extends State { return; } await WakelockPlus.enable(); + await _audioRecorder.start( - const RecordConfig( + RecordConfig( bitRate: bitRate, sampleRate: samplingRate, numChannels: 1, autoGain: true, echoCancel: true, noiseSuppress: true, + encoder: useOpus ? AudioEncoder.opus : AudioEncoder.aacLc, ), path: path, ); diff --git a/licenses.yaml b/licenses.yaml index 7b2f79bbfe..ebab434402 100644 --- a/licenses.yaml +++ b/licenses.yaml @@ -12,6 +12,7 @@ permittedLicenses: - BSD-2-Clause - BSD-3-Clause - EUPL-1.2 + - LGPL-3.0 - MIT - MPL-2.0 - Zlib diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 552a22ed93..50894732e6 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -11,7 +11,6 @@ import desktop_drop import device_info_plus import dynamic_color import emoji_picker_flutter -import ffmpeg_kit_flutter_full_gpl import file_selector_macos import flutter_app_badger import flutter_local_notifications @@ -43,7 +42,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) EmojiPickerFlutterPlugin.register(with: registry.registrar(forPlugin: "EmojiPickerFlutterPlugin")) - FFmpegKitFlutterPlugin.register(with: registry.registrar(forPlugin: "FFmpegKitFlutterPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FlutterAppBadgerPlugin.register(with: registry.registrar(forPlugin: "FlutterAppBadgerPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 98e3900cfd..0979b4a90a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -385,22 +385,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" - ffmpeg_kit_flutter_full_gpl: - dependency: transitive - description: - name: ffmpeg_kit_flutter_full_gpl - sha256: "4f269bcb636bfcb544e5b4d65c706a3d311839970cb42638e72406410c1b5b7b" - url: "https://pub.dev" - source: hosted - version: "6.0.3" - ffmpeg_kit_flutter_platform_interface: - dependency: transitive - description: - name: ffmpeg_kit_flutter_platform_interface - sha256: addf046ae44e190ad0101b2fde2ad909a3cd08a2a109f6106d2f7048b7abedee - url: "https://pub.dev" - source: hosted - version: "0.2.1" file: dependency: transitive description: @@ -1286,14 +1270,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" - nyx_converter: - dependency: "direct main" - description: - name: nyx_converter - sha256: "29684f29a650119f0417f7faa736c5b8ac65e5b32dbae40412bd6c3d7a692fc7" - url: "https://pub.dev" - source: hosted - version: "0.1.1" olm: dependency: transitive description: @@ -1302,6 +1278,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + opus_caf_converter_dart: + dependency: "direct main" + description: + name: opus_caf_converter_dart + sha256: e08156066916f790a54df305e103d6dec4d853ec23147e6a02eda3c06f67ba1a + url: "https://pub.dev" + source: hosted + version: "1.0.1" package_config: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index ac33419754..9d00c0d9d6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -66,7 +66,7 @@ dependencies: linkify: ^5.0.0 matrix: ^0.31.0 native_imaging: ^0.1.1 - nyx_converter: ^0.1.1 + opus_caf_converter_dart: ^1.0.1 package_info_plus: ^6.0.0 pasteboard: ^0.2.0 path: ^1.9.0 From dd3e4449f6677197e6c1fa9c3b8ebb73c79c790b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 4 Aug 2024 17:45:27 +0000 Subject: [PATCH 160/288] build(deps): bump rexml from 3.2.8 to 3.3.3 in /ios Bumps [rexml](https://github.com/ruby/rexml) from 3.2.8 to 3.3.3. - [Release notes](https://github.com/ruby/rexml/releases) - [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md) - [Commits](https://github.com/ruby/rexml/compare/v3.2.8...v3.3.3) --- updated-dependencies: - dependency-name: rexml dependency-type: indirect ... Signed-off-by: dependabot[bot] --- ios/Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/Gemfile.lock b/ios/Gemfile.lock index aaaffc7b4d..c2a9c1b968 100644 --- a/ios/Gemfile.lock +++ b/ios/Gemfile.lock @@ -156,8 +156,8 @@ GEM trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.2.8) - strscan (>= 3.0.9) + rexml (3.3.3) + strscan rouge (2.0.7) ruby2_keywords (0.0.4) rubyzip (2.3.0) From 3286b1938732b76d0825ae3e7841474ad37e14c7 Mon Sep 17 00:00:00 2001 From: Krille Date: Mon, 5 Aug 2024 13:21:12 +0200 Subject: [PATCH 161/288] refactor: Recording dialog --- lib/pages/chat/chat.dart | 6 ++--- lib/pages/chat/recording_dialog.dart | 38 +++++++++++++--------------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 129bcb4efe..6f4a83e9de 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -621,10 +621,10 @@ class ChatController extends State builder: (c) => const RecordingDialog(), ); if (result == null) return; - final audioFile = File(result.path); + final audioFile = XFile(result.path); final file = MatrixAudioFile( - bytes: audioFile.readAsBytesSync(), - name: audioFile.path, + bytes: await audioFile.readAsBytes(), + name: result.fileName ?? audioFile.path, ); await room.sendFileEvent( file, diff --git a/lib/pages/chat/recording_dialog.dart b/lib/pages/chat/recording_dialog.dart index be22ff3086..37d3703d34 100644 --- a/lib/pages/chat/recording_dialog.dart +++ b/lib/pages/chat/recording_dialog.dart @@ -1,9 +1,11 @@ import 'dart:async'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:path/path.dart' as path_lib; import 'package:path_provider/path_provider.dart'; import 'package:record/record.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; @@ -26,10 +28,12 @@ class RecordingDialogState extends State { Duration _duration = Duration.zero; bool error = false; - String? _recordedPath; + final _audioRecorder = AudioRecorder(); final List amplitudeTimeline = []; + String? fileName; + static const int bitRate = 64000; static const int samplingRate = 44100; @@ -37,9 +41,13 @@ class RecordingDialogState extends State { try { final useOpus = await _audioRecorder.isEncoderSupported(AudioEncoder.opus); - final tempDir = await getTemporaryDirectory(); - final path = _recordedPath = - '${tempDir.path}/recording${DateTime.now().microsecondsSinceEpoch}.${useOpus ? 'ogg' : 'm4a'}'; + fileName = + 'recording${DateTime.now().microsecondsSinceEpoch}.${useOpus ? 'ogg' : 'm4a'}'; + String? path; + if (!kIsWeb) { + final tempDir = await getTemporaryDirectory(); + path = path_lib.join(tempDir.path, fileName); + } final result = await _audioRecorder.hasPermission(); if (result != true) { @@ -58,7 +66,7 @@ class RecordingDialogState extends State { noiseSuppress: true, encoder: useOpus ? AudioEncoder.opus : AudioEncoder.aacLc, ), - path: path, + path: path ?? '', ); setState(() => _duration = Duration.zero); _recorderSubscription?.cancel(); @@ -94,8 +102,8 @@ class RecordingDialogState extends State { void _stopAndSend() async { _recorderSubscription?.cancel(); - await _audioRecorder.stop(); - final path = _recordedPath; + final path = await _audioRecorder.stop(); + if (path == null) throw ('Recording failed!'); const waveCount = AudioPlayerWidget.wavesCount; final step = amplitudeTimeline.length < waveCount @@ -110,6 +118,7 @@ class RecordingDialogState extends State { path: path, duration: _duration.inMilliseconds, waveform: waveform, + fileName: fileName, ), ); } @@ -220,23 +229,12 @@ class RecordingResult { final String path; final int duration; final List waveform; + final String? fileName; const RecordingResult({ required this.path, required this.duration, required this.waveform, + required this.fileName, }); - - factory RecordingResult.fromJson(Map json) => - RecordingResult( - path: json['path'], - duration: json['duration'], - waveform: List.from(json['waveform']), - ); - - Map toJson() => { - 'path': path, - 'duration': duration, - 'waveform': waveform, - }; } From 47481eb6769134b405c649e238d5db7472d7cf09 Mon Sep 17 00:00:00 2001 From: Krille Date: Tue, 6 Aug 2024 11:55:01 +0200 Subject: [PATCH 162/288] feat: Send voice messages from web --- lib/pages/chat/recording_dialog.dart | 34 ++++++++++++++++++++++++---- lib/utils/platform_infos.dart | 2 +- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/lib/pages/chat/recording_dialog.dart b/lib/pages/chat/recording_dialog.dart index 37d3703d34..6d8718854f 100644 --- a/lib/pages/chat/recording_dialog.dart +++ b/lib/pages/chat/recording_dialog.dart @@ -39,10 +39,16 @@ class RecordingDialogState extends State { Future startRecording() async { try { - final useOpus = - await _audioRecorder.isEncoderSupported(AudioEncoder.opus); + final codec = kIsWeb + // Web seems to create webm instead of ogg when using opus encoder + // which does not play on iOS right now. So we use wav for now: + ? AudioEncoder.wav + // Everywhere else we use opus if supported by the platform: + : await _audioRecorder.isEncoderSupported(AudioEncoder.opus) + ? AudioEncoder.opus + : AudioEncoder.aacLc; fileName = - 'recording${DateTime.now().microsecondsSinceEpoch}.${useOpus ? 'ogg' : 'm4a'}'; + 'recording${DateTime.now().microsecondsSinceEpoch}.${codec.fileExtension}'; String? path; if (!kIsWeb) { final tempDir = await getTemporaryDirectory(); @@ -64,7 +70,7 @@ class RecordingDialogState extends State { autoGain: true, echoCancel: true, noiseSuppress: true, - encoder: useOpus ? AudioEncoder.opus : AudioEncoder.aacLc, + encoder: codec, ), path: path ?? '', ); @@ -238,3 +244,23 @@ class RecordingResult { required this.fileName, }); } + +extension on AudioEncoder { + String get fileExtension { + switch (this) { + case AudioEncoder.aacLc: + case AudioEncoder.aacEld: + case AudioEncoder.aacHe: + return 'm4a'; + case AudioEncoder.opus: + return 'ogg'; + case AudioEncoder.wav: + return 'wav'; + case AudioEncoder.amrNb: + case AudioEncoder.amrWb: + case AudioEncoder.flac: + case AudioEncoder.pcm16bits: + throw UnsupportedError('Not yet used'); + } + } +} diff --git a/lib/utils/platform_infos.dart b/lib/utils/platform_infos.dart index b96104df43..638c5bcc2f 100644 --- a/lib/utils/platform_infos.dart +++ b/lib/utils/platform_infos.dart @@ -29,7 +29,7 @@ abstract class PlatformInfos { static bool get usesTouchscreen => !isMobile; - static bool get platformCanRecord => (isMobile || isMacOS); + static bool get platformCanRecord => (isMobile || isMacOS || isWeb); static String get clientName => '${AppConfig.applicationName} ${isWeb ? 'web' : Platform.operatingSystem}${kReleaseMode ? '' : 'Debug'}'; From 5d2aaef3cadd6d0841780d2e37c31c3c632ef34f Mon Sep 17 00:00:00 2001 From: Thomas Klein Langenhorst Date: Sun, 4 Aug 2024 14:09:36 +0200 Subject: [PATCH 163/288] Refactor: Reduce .of(context) calls theme Signed commit --- lib/pages/bootstrap/bootstrap_dialog.dart | 23 ++--- lib/pages/chat/chat.dart | 81 ++++++++-------- lib/pages/chat/chat_app_bar_list_tile.dart | 8 +- lib/pages/chat/chat_emoji_picker.dart | 5 +- lib/pages/chat/chat_input_row.dart | 14 ++- lib/pages/chat/chat_view.dart | 19 ++-- lib/pages/chat/event_info_dialog.dart | 5 +- lib/pages/chat/events/audio_player.dart | 6 +- lib/pages/chat/events/image_bubble.dart | 4 +- lib/pages/chat/events/map_bubble.dart | 7 +- lib/pages/chat/events/message.dart | 37 ++++---- lib/pages/chat/events/message_reactions.dart | 12 +-- lib/pages/chat/events/reply_content.dart | 15 ++- lib/pages/chat/events/state_message.dart | 4 +- .../events/verification_request_content.dart | 6 +- lib/pages/chat/events/video_player.dart | 4 +- lib/pages/chat/input_bar.dart | 3 +- lib/pages/chat/pinned_events.dart | 4 +- lib/pages/chat/reactions_picker.dart | 6 +- lib/pages/chat/recording_dialog.dart | 13 +-- lib/pages/chat/reply_display.dart | 9 +- lib/pages/chat/seen_by_row.dart | 4 +- lib/pages/chat/send_file_dialog.dart | 7 +- lib/pages/chat/sticker_picker_dialog.dart | 4 +- lib/pages/chat/typing_indicators.dart | 7 +- .../chat_access_settings_page.dart | 26 +++--- lib/pages/chat_details/chat_details_view.dart | 65 ++++++------- .../chat_details/participant_list_item.dart | 10 +- .../chat_encryption_settings_view.dart | 23 ++--- lib/pages/chat_list/chat_list_body.dart | 28 +++--- lib/pages/chat_list/chat_list_header.dart | 13 +-- lib/pages/chat_list/chat_list_item.dart | 7 +- lib/pages/chat_list/nav_rail_item.dart | 8 +- lib/pages/chat_list/navi_rail_item.dart | 8 +- lib/pages/chat_list/search_title.dart | 92 ++++++++++--------- lib/pages/chat_list/space_view.dart | 13 +-- lib/pages/chat_list/status_msg_list.dart | 7 +- .../chat_permissions_settings_view.dart | 14 +-- .../permission_list_tile.dart | 4 +- .../chat_search/chat_search_files_tab.dart | 15 ++- .../chat_search/chat_search_images_tab.dart | 13 ++- .../chat_search/chat_search_message_tab.dart | 15 +-- .../device_settings/device_settings_view.dart | 13 ++- .../homeserver_picker/homeserver_app_bar.dart | 11 ++- .../homeserver_picker_view.dart | 21 +++-- .../invitation_selection_view.dart | 4 +- .../key_verification_dialog.dart | 8 +- lib/pages/login/login_view.dart | 12 ++- lib/pages/new_group/new_group_view.dart | 10 +- .../new_private_chat_view.dart | 40 ++++---- lib/pages/settings/settings_view.dart | 17 ++-- .../settings_3pid/settings_3pid_view.dart | 7 +- .../settings_chat/settings_chat_view.dart | 12 ++- .../import_archive_dialog.dart | 10 +- .../settings_emotes/settings_emotes_view.dart | 16 ++-- .../settings_ignore_list_view.dart | 4 +- .../settings_notifications_view.dart | 9 +- .../settings_password_view.dart | 6 +- .../settings_security_view.dart | 10 +- .../settings_style/settings_style_view.dart | 35 ++++--- .../user_bottom_sheet_view.dart | 50 +++++----- lib/utils/show_update_snackbar.dart | 3 +- lib/widgets/avatar.dart | 11 ++- lib/widgets/connection_status_header.dart | 4 +- lib/widgets/layouts/login_scaffold.dart | 14 +-- lib/widgets/layouts/max_width_body.dart | 9 +- lib/widgets/layouts/two_column_layout.dart | 4 +- lib/widgets/public_room_bottom_sheet.dart | 10 +- lib/widgets/unread_rooms_badge.dart | 8 +- 69 files changed, 525 insertions(+), 501 deletions(-) diff --git a/lib/pages/bootstrap/bootstrap_dialog.dart b/lib/pages/bootstrap/bootstrap_dialog.dart index e1c4cde71f..64e7febadb 100644 --- a/lib/pages/bootstrap/bootstrap_dialog.dart +++ b/lib/pages/bootstrap/bootstrap_dialog.dart @@ -88,6 +88,7 @@ class BootstrapDialogState extends State { @override Widget build(BuildContext context) { + final theme = Theme.of(context); _wipe ??= widget.wipe; final buttons = []; Widget body = const CircularProgressIndicator.adaptive(); @@ -119,7 +120,7 @@ class BootstrapDialogState extends State { backgroundColor: Colors.transparent, child: Icon( Icons.info_outlined, - color: Theme.of(context).colorScheme.primary, + color: theme.colorScheme.primary, ), ), subtitle: Text(L10n.of(context)!.chatBackupDescription), @@ -144,7 +145,7 @@ class BootstrapDialogState extends State { CheckboxListTile.adaptive( contentPadding: const EdgeInsets.symmetric(horizontal: 8.0), value: _storeInSecureStorage, - activeColor: Theme.of(context).colorScheme.primary, + activeColor: theme.colorScheme.primary, onChanged: (b) { setState(() { _storeInSecureStorage = b; @@ -158,7 +159,7 @@ class BootstrapDialogState extends State { CheckboxListTile.adaptive( contentPadding: const EdgeInsets.symmetric(horizontal: 8.0), value: _recoveryKeyCopied, - activeColor: Theme.of(context).colorScheme.primary, + activeColor: theme.colorScheme.primary, onChanged: (b) { FluffyShare.share(key!, context); setState(() => _recoveryKeyCopied = true); @@ -241,7 +242,7 @@ class BootstrapDialogState extends State { const EdgeInsets.symmetric(horizontal: 8.0), trailing: Icon( Icons.info_outlined, - color: Theme.of(context).colorScheme.primary, + color: theme.colorScheme.primary, ), subtitle: Text( L10n.of(context)!.pleaseEnterRecoveryKeyDescription, @@ -261,8 +262,7 @@ class BootstrapDialogState extends State { decoration: InputDecoration( contentPadding: const EdgeInsets.all(16), hintStyle: TextStyle( - fontFamily: - Theme.of(context).textTheme.bodyLarge?.fontFamily, + fontFamily: theme.textTheme.bodyLarge?.fontFamily, ), hintText: L10n.of(context)!.recoveryKey, errorText: _recoveryKeyInputError, @@ -272,9 +272,8 @@ class BootstrapDialogState extends State { const SizedBox(height: 16), ElevatedButton.icon( style: ElevatedButton.styleFrom( - foregroundColor: - Theme.of(context).colorScheme.onPrimary, - backgroundColor: Theme.of(context).colorScheme.primary, + foregroundColor: theme.colorScheme.onPrimary, + backgroundColor: theme.colorScheme.primary, ), icon: _recoveryKeyInputLoading ? const CircularProgressIndicator.adaptive() @@ -386,10 +385,8 @@ class BootstrapDialogState extends State { const SizedBox(height: 16), ElevatedButton.icon( style: ElevatedButton.styleFrom( - backgroundColor: - Theme.of(context).colorScheme.errorContainer, - foregroundColor: - Theme.of(context).colorScheme.onErrorContainer, + backgroundColor: theme.colorScheme.errorContainer, + foregroundColor: theme.colorScheme.onErrorContainer, ), icon: const Icon(Icons.delete_outlined), label: Text(L10n.of(context)!.recoveryKeyLost), diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 129bcb4efe..1f54a6074e 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -1307,49 +1307,52 @@ class ChatController extends State } @override - Widget build(BuildContext context) => Row( - children: [ - Expanded( - child: ChatView(this), - ), - AnimatedSize( - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - child: ValueListenableBuilder( - valueListenable: _displayChatDetailsColumn, - builder: (context, displayChatDetailsColumn, _) { - if (!FluffyThemes.isThreeColumnMode(context) || - room.membership != Membership.join || - !displayChatDetailsColumn) { - return const SizedBox( - height: double.infinity, - width: 0, - ); - } - return Container( - width: FluffyThemes.columnWidth, - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - border: Border( - left: BorderSide( - width: 1, - color: Theme.of(context).dividerColor, - ), + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Row( + children: [ + Expanded( + child: ChatView(this), + ), + AnimatedSize( + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + child: ValueListenableBuilder( + valueListenable: _displayChatDetailsColumn, + builder: (context, displayChatDetailsColumn, _) { + if (!FluffyThemes.isThreeColumnMode(context) || + room.membership != Membership.join || + !displayChatDetailsColumn) { + return const SizedBox( + height: double.infinity, + width: 0, + ); + } + return Container( + width: FluffyThemes.columnWidth, + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + border: Border( + left: BorderSide( + width: 1, + color: theme.dividerColor, ), ), - child: ChatDetails( - roomId: roomId, - embeddedCloseButton: IconButton( - icon: const Icon(Icons.close), - onPressed: toggleDisplayChatDetailsColumn, - ), + ), + child: ChatDetails( + roomId: roomId, + embeddedCloseButton: IconButton( + icon: const Icon(Icons.close), + onPressed: toggleDisplayChatDetailsColumn, ), - ); - }, - ), + ), + ); + }, ), - ], - ); + ), + ], + ); + } } enum EmojiPickerType { reaction, keyboard } diff --git a/lib/pages/chat/chat_app_bar_list_tile.dart b/lib/pages/chat/chat_app_bar_list_tile.dart index 2591442519..1ca0c5fba9 100644 --- a/lib/pages/chat/chat_app_bar_list_tile.dart +++ b/lib/pages/chat/chat_app_bar_list_tile.dart @@ -22,6 +22,7 @@ class ChatAppBarListTile extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); final leading = this.leading; final trailing = this.trailing; return SizedBox( @@ -40,16 +41,15 @@ class ChatAppBarListTile extends StatelessWidget { maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, + color: theme.colorScheme.onSurfaceVariant, overflow: TextOverflow.ellipsis, fontSize: 14, ), linkStyle: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, + color: theme.colorScheme.onSurfaceVariant, fontSize: 14, decoration: TextDecoration.underline, - decorationColor: - Theme.of(context).colorScheme.onSurfaceVariant, + decorationColor: theme.colorScheme.onSurfaceVariant, ), onOpen: (url) => UrlLauncher(context, url.url).launchUrl(), ), diff --git a/lib/pages/chat/chat_emoji_picker.dart b/lib/pages/chat/chat_emoji_picker.dart index 0225e7350c..1ef7e23636 100644 --- a/lib/pages/chat/chat_emoji_picker.dart +++ b/lib/pages/chat/chat_emoji_picker.dart @@ -43,9 +43,8 @@ class ChatEmojiPicker extends StatelessWidget { config: Config( emojiViewConfig: EmojiViewConfig( noRecents: const NoRecent(), - backgroundColor: Theme.of(context) - .colorScheme - .onInverseSurface, + backgroundColor: + theme.colorScheme.onInverseSurface, ), bottomActionBarConfig: const BottomActionBarConfig( enabled: false, diff --git a/lib/pages/chat/chat_input_row.dart b/lib/pages/chat/chat_input_row.dart index b548c606bc..3ad7503768 100644 --- a/lib/pages/chat/chat_input_row.dart +++ b/lib/pages/chat/chat_input_row.dart @@ -21,6 +21,7 @@ class ChatInputRow extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); if (controller.showEmojiPicker && controller.emojiPickerType == EmojiPickerType.reaction) { return const SizedBox.shrink(); @@ -37,7 +38,7 @@ class ChatInputRow extends StatelessWidget { height: height, child: TextButton( style: TextButton.styleFrom( - foregroundColor: Theme.of(context).colorScheme.error, + foregroundColor: theme.colorScheme.error, ), onPressed: controller.deleteErrorEventsAction, child: Row( @@ -278,9 +279,8 @@ class ChatInputRow extends StatelessWidget { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(height), ), - backgroundColor: Theme.of(context).colorScheme.primary, - foregroundColor: - Theme.of(context).colorScheme.onPrimary, + backgroundColor: theme.colorScheme.primary, + foregroundColor: theme.colorScheme.onPrimary, child: const Icon(Icons.mic_none_outlined), ) : FloatingActionButton.small( @@ -291,10 +291,8 @@ class ChatInputRow extends StatelessWidget { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(height), ), - backgroundColor: - Theme.of(context).colorScheme.onPrimaryContainer, - foregroundColor: - Theme.of(context).colorScheme.onPrimary, + backgroundColor: theme.colorScheme.onPrimaryContainer, + foregroundColor: theme.colorScheme.onPrimary, child: const Icon(Icons.send_outlined), ), ), diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 422175f0fa..b354e2c198 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -128,6 +128,7 @@ class ChatView extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); if (controller.room.membership == Membership.invite) { showFutureLoadingDialog( context: context, @@ -173,14 +174,14 @@ class ChatView extends StatelessWidget { actionsIconTheme: IconThemeData( color: controller.selectedEvents.isEmpty ? null - : Theme.of(context).colorScheme.primary, + : theme.colorScheme.primary, ), leading: controller.selectMode ? IconButton( icon: const Icon(Icons.close), onPressed: controller.clearSelectedEvents, tooltip: L10n.of(context)!.close, - color: Theme.of(context).colorScheme.primary, + color: theme.colorScheme.primary, ) : StreamBuilder( stream: Matrix.of(context) @@ -218,8 +219,7 @@ class ChatView extends StatelessWidget { if (scrollUpBannerEventId != null) ChatAppBarListTile( leading: IconButton( - color: - Theme.of(context).colorScheme.onSurfaceVariant, + color: theme.colorScheme.onSurfaceVariant, icon: const Icon(Icons.close), tooltip: L10n.of(context)!.close, onPressed: () { @@ -308,7 +308,7 @@ class ChatView extends StatelessWidget { alignment: Alignment.center, child: Material( clipBehavior: Clip.hardEdge, - color: Theme.of(context) + color: theme .colorScheme // ignore: deprecated_member_use .surfaceVariant, @@ -325,9 +325,8 @@ class ChatView extends StatelessWidget { padding: const EdgeInsets.all( 16, ), - foregroundColor: Theme.of(context) - .colorScheme - .error, + foregroundColor: + theme.colorScheme.error, ), icon: const Icon( Icons.archive_outlined, @@ -370,9 +369,7 @@ class ChatView extends StatelessWidget { ), if (controller.dragging) Container( - color: Theme.of(context) - .scaffoldBackgroundColor - .withOpacity(0.9), + color: theme.scaffoldBackgroundColor.withOpacity(0.9), alignment: Alignment.center, child: const Icon( Icons.upload_outlined, diff --git a/lib/pages/chat/event_info_dialog.dart b/lib/pages/chat/event_info_dialog.dart index 3b35035933..c930e4b14e 100644 --- a/lib/pages/chat/event_info_dialog.dart +++ b/lib/pages/chat/event_info_dialog.dart @@ -36,6 +36,7 @@ class EventInfoDialog extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); return Scaffold( appBar: AppBar( title: Text(L10n.of(context)!.messageInfo), @@ -72,14 +73,14 @@ class EventInfoDialog extends StatelessWidget { padding: const EdgeInsets.all(12.0), child: Material( borderRadius: BorderRadius.circular(AppConfig.borderRadius), - color: Theme.of(context).colorScheme.inverseSurface, + color: theme.colorScheme.inverseSurface, child: SingleChildScrollView( padding: const EdgeInsets.all(8), scrollDirection: Axis.horizontal, child: SelectableText( prettyJson, style: TextStyle( - color: Theme.of(context).colorScheme.onInverseSurface, + color: theme.colorScheme.onInverseSurface, ), ), ), diff --git a/lib/pages/chat/events/audio_player.dart b/lib/pages/chat/events/audio_player.dart index 40d4cf29fa..39d7e71c12 100644 --- a/lib/pages/chat/events/audio_player.dart +++ b/lib/pages/chat/events/audio_player.dart @@ -206,6 +206,8 @@ class AudioPlayerState extends State { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final statusText = this.statusText ??= _durationString ?? '00:00'; final audioPlayer = this.audioPlayer; return Padding( @@ -292,8 +294,8 @@ class AudioPlayerState extends State { : Text( '${audioPlayer.speed.toString()}x', ), - backgroundColor: Theme.of(context).colorScheme.secondary, - textColor: Theme.of(context).colorScheme.onSecondary, + backgroundColor: theme.colorScheme.secondary, + textColor: theme.colorScheme.onSecondary, child: InkWell( splashColor: widget.color.withAlpha(128), borderRadius: BorderRadius.circular(64), diff --git a/lib/pages/chat/events/image_bubble.dart b/lib/pages/chat/events/image_bubble.dart index f5219b0545..3ee745eeb7 100644 --- a/lib/pages/chat/events/image_bubble.dart +++ b/lib/pages/chat/events/image_bubble.dart @@ -67,6 +67,8 @@ class ImageBubble extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final borderRadius = this.borderRadius ?? BorderRadius.circular(AppConfig.borderRadius); return Material( @@ -77,7 +79,7 @@ class ImageBubble extends StatelessWidget { side: BorderSide( color: event.messageType == MessageTypes.Sticker ? Colors.transparent - : Theme.of(context).dividerColor, + : theme.dividerColor, ), ), child: InkWell( diff --git a/lib/pages/chat/events/map_bubble.dart b/lib/pages/chat/events/map_bubble.dart index e9eab056b5..4a2befae29 100644 --- a/lib/pages/chat/events/map_bubble.dart +++ b/lib/pages/chat/events/map_bubble.dart @@ -22,6 +22,8 @@ class MapBubble extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + return ClipRRect( borderRadius: BorderRadius.circular(radius), child: Container( @@ -72,11 +74,10 @@ class MapBubble extends StatelessWidget { child: Text( ' © OpenStreetMap contributors ', style: TextStyle( - color: Theme.of(context).brightness == Brightness.dark + color: theme.brightness == Brightness.dark ? Colors.white : Colors.black, - backgroundColor: - Theme.of(context).appBarTheme.backgroundColor, + backgroundColor: theme.appBarTheme.backgroundColor, ), ), ), diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index 5f1e7dc35b..08bf9e3061 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -57,6 +57,8 @@ class Message extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + if (!{ EventTypes.Message, EventTypes.Sticker, @@ -78,7 +80,7 @@ class Message extends StatelessWidget { final ownMessage = event.senderId == client.userID; final alignment = ownMessage ? Alignment.topRight : Alignment.topLeft; // ignore: deprecated_member_use - var color = Theme.of(context).colorScheme.surfaceVariant; + var color = theme.colorScheme.surfaceVariant; final displayTime = event.type == EventTypes.RoomCreate || nextEvent == null || !event.originServerTs.sameEnvironment(nextEvent!.originServerTs); @@ -100,9 +102,8 @@ class Message extends StatelessWidget { previousEvent!.senderId == event.senderId && previousEvent!.originServerTs.sameEnvironment(event.originServerTs); - final textColor = ownMessage - ? Theme.of(context).colorScheme.onPrimary - : Theme.of(context).colorScheme.onSurface; + final textColor = + ownMessage ? theme.colorScheme.onPrimary : theme.colorScheme.onSurface; final rowMainAxisAlignment = ownMessage ? MainAxisAlignment.end : MainAxisAlignment.start; @@ -131,7 +132,7 @@ class Message extends StatelessWidget { if (ownMessage) { color = displayEvent.status.isError ? Colors.redAccent - : Theme.of(context).colorScheme.primary; + : theme.colorScheme.primary; } final resetAnimateIn = this.resetAnimateIn; @@ -168,14 +169,10 @@ class Message extends StatelessWidget { borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), color: selected - ? Theme.of(context) - .colorScheme - .secondaryContainer + ? theme.colorScheme.secondaryContainer .withAlpha(100) : highlightMarker - ? Theme.of(context) - .colorScheme - .tertiaryContainer + ? theme.colorScheme.tertiaryContainer .withAlpha(100) : Colors.transparent, ), @@ -253,8 +250,7 @@ class Message extends StatelessWidget { displayname, style: TextStyle( fontSize: 12, - color: (Theme.of(context) - .brightness == + color: (theme.brightness == Brightness.light ? displayname.color : displayname @@ -442,10 +438,10 @@ class Message extends StatelessWidget { style: TextStyle( fontWeight: FontWeight.bold, fontSize: 12 * AppConfig.fontSizeFactor, - color: Theme.of(context).colorScheme.secondary, + color: theme.colorScheme.secondary, shadows: [ Shadow( - color: Theme.of(context).colorScheme.surface, + color: theme.colorScheme.surface, blurRadius: 3, ), ], @@ -473,14 +469,14 @@ class Message extends StatelessWidget { Row( children: [ Expanded( - child: Divider(color: Theme.of(context).colorScheme.primary), + child: Divider(color: theme.colorScheme.primary), ), Container( decoration: BoxDecoration( border: Border.all( - color: Theme.of(context).colorScheme.primary, + color: theme.colorScheme.primary, ), - color: Theme.of(context).colorScheme.primaryContainer, + color: theme.colorScheme.primaryContainer, borderRadius: BorderRadius.circular(4), ), margin: const EdgeInsets.all(8.0), @@ -489,12 +485,11 @@ class Message extends StatelessWidget { ), child: Text( L10n.of(context)!.readUpToHere, - style: - TextStyle(color: Theme.of(context).colorScheme.primary), + style: TextStyle(color: theme.colorScheme.primary), ), ), Expanded( - child: Divider(color: Theme.of(context).colorScheme.primary), + child: Divider(color: theme.colorScheme.primary), ), ], ), diff --git a/lib/pages/chat/events/message_reactions.dart b/lib/pages/chat/events/message_reactions.dart index 56286cae0a..a4b2addbc7 100644 --- a/lib/pages/chat/events/message_reactions.dart +++ b/lib/pages/chat/events/message_reactions.dart @@ -108,10 +108,10 @@ class _Reaction extends StatelessWidget { @override Widget build(BuildContext context) { - final textColor = Theme.of(context).brightness == Brightness.dark - ? Colors.white - : Colors.black; - final color = Theme.of(context).colorScheme.surface; + final theme = Theme.of(context); + final textColor = + theme.brightness == Brightness.dark ? Colors.white : Colors.black; + final color = theme.colorScheme.surface; Widget content; if (reactionKey.startsWith('mxc://')) { content = Row( @@ -158,8 +158,8 @@ class _Reaction extends StatelessWidget { border: Border.all( width: 1, color: reacted! - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.primaryContainer, + ? theme.colorScheme.primary + : theme.colorScheme.primaryContainer, ), borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), ), diff --git a/lib/pages/chat/events/reply_content.dart b/lib/pages/chat/events/reply_content.dart index 945ae22ac3..02e986f817 100644 --- a/lib/pages/chat/events/reply_content.dart +++ b/lib/pages/chat/events/reply_content.dart @@ -27,20 +27,19 @@ class ReplyContent extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final timeline = this.timeline; final displayEvent = timeline != null ? replyEvent.getDisplayEvent(timeline) : replyEvent; final fontSize = AppConfig.messageFontSize * AppConfig.fontSizeFactor; final color = ownMessage - ? Theme.of(context).colorScheme.primaryContainer - : Theme.of(context).colorScheme.primary; + ? theme.colorScheme.primaryContainer + : theme.colorScheme.primary; return Material( color: backgroundColor ?? - Theme.of(context) - .colorScheme - .surface - .withOpacity(ownMessage ? 0.2 : 0.33), + theme.colorScheme.surface.withOpacity(ownMessage ? 0.2 : 0.33), borderRadius: borderRadius, child: Row( mainAxisSize: MainAxisSize.min, @@ -81,8 +80,8 @@ class ReplyContent extends StatelessWidget { maxLines: 1, style: TextStyle( color: ownMessage - ? Theme.of(context).colorScheme.onPrimary - : Theme.of(context).colorScheme.onSurface, + ? theme.colorScheme.onPrimary + : theme.colorScheme.onSurface, fontSize: fontSize, ), ), diff --git a/lib/pages/chat/events/state_message.dart b/lib/pages/chat/events/state_message.dart index ebd6f7373a..e38f758154 100644 --- a/lib/pages/chat/events/state_message.dart +++ b/lib/pages/chat/events/state_message.dart @@ -12,6 +12,8 @@ class StateMessage extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + return Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Center( @@ -27,7 +29,7 @@ class StateMessage extends StatelessWidget { decoration: event.redacted ? TextDecoration.lineThrough : null, shadows: [ Shadow( - color: Theme.of(context).colorScheme.surface, + color: theme.colorScheme.surface, blurRadius: 3, ), ], diff --git a/lib/pages/chat/events/verification_request_content.dart b/lib/pages/chat/events/verification_request_content.dart index b6ec3fd09b..a09f007f76 100644 --- a/lib/pages/chat/events/verification_request_content.dart +++ b/lib/pages/chat/events/verification_request_content.dart @@ -17,6 +17,8 @@ class VerificationRequestContent extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final events = event.aggregatedEvents(timeline, 'm.reference'); final done = events.where((e) => e.type == EventTypes.KeyVerificationDone); final start = @@ -36,10 +38,10 @@ class VerificationRequestContent extends StatelessWidget { padding: const EdgeInsets.all(8), decoration: BoxDecoration( border: Border.all( - color: Theme.of(context).dividerColor, + color: theme.dividerColor, ), borderRadius: BorderRadius.circular(AppConfig.borderRadius), - color: Theme.of(context).colorScheme.surface, + color: theme.colorScheme.surface, ), child: Row( mainAxisSize: MainAxisSize.min, diff --git a/lib/pages/chat/events/video_player.dart b/lib/pages/chat/events/video_player.dart index 5ccd560f97..a23ab7bb0b 100644 --- a/lib/pages/chat/events/video_player.dart +++ b/lib/pages/chat/events/video_player.dart @@ -96,6 +96,8 @@ class EventVideoPlayerState extends State { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final hasThumbnail = widget.event.hasThumbnail; final blurHash = (widget.event.infoMap as Map) .tryGet('xyz.amorgan.blurhash') ?? @@ -123,7 +125,7 @@ class EventVideoPlayerState extends State { Center( child: IconButton( style: IconButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.surface, + backgroundColor: theme.colorScheme.surface, ), icon: _isDownloading ? const SizedBox( diff --git a/lib/pages/chat/input_bar.dart b/lib/pages/chat/input_bar.dart index d4917c1dc9..1e1f3f40ec 100644 --- a/lib/pages/chat/input_bar.dart +++ b/lib/pages/chat/input_bar.dart @@ -221,6 +221,7 @@ class InputBar extends StatelessWidget { Map suggestion, Client? client, ) { + final theme = Theme.of(context); const size = 30.0; const padding = EdgeInsets.all(4.0); if (suggestion['type'] == 'command') { @@ -242,7 +243,7 @@ class InputBar extends StatelessWidget { hint, maxLines: 1, overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodySmall, + style: theme.textTheme.bodySmall, ), ], ), diff --git a/lib/pages/chat/pinned_events.dart b/lib/pages/chat/pinned_events.dart index 7afe413dbf..1baa488c5c 100644 --- a/lib/pages/chat/pinned_events.dart +++ b/lib/pages/chat/pinned_events.dart @@ -53,6 +53,8 @@ class PinnedEvents extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final pinnedEventIds = controller.room.pinnedEventIds; if (pinnedEventIds.isEmpty) { @@ -73,7 +75,7 @@ class PinnedEvents extends StatelessWidget { leading: IconButton( splashRadius: 18, iconSize: 18, - color: Theme.of(context).colorScheme.onSurfaceVariant, + color: theme.colorScheme.onSurfaceVariant, icon: const Icon(Icons.push_pin), tooltip: L10n.of(context)!.unpin, onPressed: controller.room.canSendEvent(EventTypes.RoomPinnedEvents) diff --git a/lib/pages/chat/reactions_picker.dart b/lib/pages/chat/reactions_picker.dart index 7256e00bc4..4488e05df7 100644 --- a/lib/pages/chat/reactions_picker.dart +++ b/lib/pages/chat/reactions_picker.dart @@ -15,6 +15,8 @@ class ReactionsPicker extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + if (controller.showEmojiPicker) return const SizedBox.shrink(); final display = controller.editEvent == null && controller.replyEvent == null && @@ -60,7 +62,7 @@ class ReactionsPicker extends StatelessWidget { Expanded( child: Container( decoration: BoxDecoration( - color: Theme.of(context).colorScheme.onInverseSurface, + color: theme.colorScheme.onInverseSurface, borderRadius: const BorderRadius.only( bottomRight: Radius.circular(AppConfig.borderRadius), ), @@ -92,7 +94,7 @@ class ReactionsPicker extends StatelessWidget { width: 36, height: 56, decoration: BoxDecoration( - color: Theme.of(context).colorScheme.onInverseSurface, + color: theme.colorScheme.onInverseSurface, shape: BoxShape.circle, ), child: const Icon(Icons.add_outlined), diff --git a/lib/pages/chat/recording_dialog.dart b/lib/pages/chat/recording_dialog.dart index bc92f3ff54..363d423458 100644 --- a/lib/pages/chat/recording_dialog.dart +++ b/lib/pages/chat/recording_dialog.dart @@ -113,6 +113,8 @@ class RecordingDialogState extends State { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + const maxDecibalWidth = 64.0; final time = '${_duration.inMinutes.toString().padLeft(2, '0')}:${(_duration.inSeconds % 60).toString().padLeft(2, '0')}'; @@ -141,7 +143,7 @@ class RecordingDialogState extends State { margin: const EdgeInsets.only(left: 2), width: 4, decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary, + color: theme.colorScheme.primary, borderRadius: BorderRadius.circular(AppConfig.borderRadius), ), @@ -167,11 +169,7 @@ class RecordingDialogState extends State { child: Text( L10n.of(context)!.cancel.toUpperCase(), style: TextStyle( - color: Theme.of(context) - .textTheme - .bodyMedium - ?.color - ?.withAlpha(150), + color: theme.textTheme.bodyMedium?.color?.withAlpha(150), ), ), ), @@ -191,8 +189,7 @@ class RecordingDialogState extends State { child: Text( L10n.of(context)!.cancel.toUpperCase(), style: TextStyle( - color: - Theme.of(context).textTheme.bodyMedium?.color?.withAlpha(150), + color: theme.textTheme.bodyMedium?.color?.withAlpha(150), ), ), ), diff --git a/lib/pages/chat/reply_display.dart b/lib/pages/chat/reply_display.dart index 03acd269e4..8de51d0f3b 100644 --- a/lib/pages/chat/reply_display.dart +++ b/lib/pages/chat/reply_display.dart @@ -14,6 +14,8 @@ class ReplyDisplay extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + return AnimatedContainer( duration: FluffyThemes.animationDuration, curve: FluffyThemes.animationCurve, @@ -22,7 +24,7 @@ class ReplyDisplay extends StatelessWidget { : 0, clipBehavior: Clip.hardEdge, decoration: BoxDecoration( - color: Theme.of(context).colorScheme.onInverseSurface, + color: theme.colorScheme.onInverseSurface, ), child: Row( children: [ @@ -55,6 +57,7 @@ class _EditContent extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); final event = this.event; if (event == null) { return const SizedBox.shrink(); @@ -63,7 +66,7 @@ class _EditContent extends StatelessWidget { children: [ Icon( Icons.edit, - color: Theme.of(context).colorScheme.primary, + color: theme.colorScheme.primary, ), Container(width: 15.0), Text( @@ -75,7 +78,7 @@ class _EditContent extends StatelessWidget { overflow: TextOverflow.ellipsis, maxLines: 1, style: TextStyle( - color: Theme.of(context).textTheme.bodyMedium!.color, + color: theme.textTheme.bodyMedium!.color, ), ), ], diff --git a/lib/pages/chat/seen_by_row.dart b/lib/pages/chat/seen_by_row.dart index 9b1ad8953f..76fa7106f0 100644 --- a/lib/pages/chat/seen_by_row.dart +++ b/lib/pages/chat/seen_by_row.dart @@ -12,6 +12,8 @@ class SeenByRow extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final seenByUsers = controller.room.getSeenByUsers(controller.timeline!); const maxAvatars = 7; return Container( @@ -49,7 +51,7 @@ class SeenByRow extends StatelessWidget { width: 16, height: 16, child: Material( - color: Theme.of(context).colorScheme.surface, + color: theme.colorScheme.surface, borderRadius: BorderRadius.circular(32), child: Center( child: Text( diff --git a/lib/pages/chat/send_file_dialog.dart b/lib/pages/chat/send_file_dialog.dart index f97a4f2c62..79b4eed83e 100644 --- a/lib/pages/chat/send_file_dialog.dart +++ b/lib/pages/chat/send_file_dialog.dart @@ -66,6 +66,8 @@ class SendFileDialogState extends State { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + var sendStr = L10n.of(context)!.sendFile; final allFilesAreImages = widget.files.every((file) => file is MatrixImageFile); @@ -91,9 +93,8 @@ class SendFileDialogState extends State { Flexible( child: Material( borderRadius: BorderRadius.circular(AppConfig.borderRadius), - elevation: - Theme.of(context).appBarTheme.scrolledUnderElevation ?? 4, - shadowColor: Theme.of(context).appBarTheme.shadowColor, + elevation: theme.appBarTheme.scrolledUnderElevation ?? 4, + shadowColor: theme.appBarTheme.shadowColor, clipBehavior: Clip.hardEdge, child: Image.memory( widget.files.first.bytes, diff --git a/lib/pages/chat/sticker_picker_dialog.dart b/lib/pages/chat/sticker_picker_dialog.dart index c0a6ec76bf..1517b8a40d 100644 --- a/lib/pages/chat/sticker_picker_dialog.dart +++ b/lib/pages/chat/sticker_picker_dialog.dart @@ -27,6 +27,8 @@ class StickerPickerDialogState extends State { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final stickerPacks = widget.room.getImagePacks(ImagePackUsage.sticker); final packSlugs = stickerPacks.keys.toList(); @@ -100,7 +102,7 @@ class StickerPickerDialogState extends State { }; return Scaffold( - backgroundColor: Theme.of(context).colorScheme.onInverseSurface, + backgroundColor: theme.colorScheme.onInverseSurface, body: SizedBox( width: double.maxFinite, child: CustomScrollView( diff --git a/lib/pages/chat/typing_indicators.dart b/lib/pages/chat/typing_indicators.dart index 35fbf5d258..a58138a901 100644 --- a/lib/pages/chat/typing_indicators.dart +++ b/lib/pages/chat/typing_indicators.dart @@ -14,6 +14,8 @@ class TypingIndicators extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + const avatarSize = Avatar.defaultSize / 2; return StreamBuilder( @@ -81,7 +83,7 @@ class TypingIndicators extends StatelessWidget { Material( color: // ignore: deprecated_member_use - Theme.of(context).colorScheme.surfaceVariant, + theme.colorScheme.surfaceVariant, borderRadius: const BorderRadius.all( Radius.circular(AppConfig.borderRadius), ), @@ -137,6 +139,7 @@ class __TypingDotsState extends State<_TypingDots> { @override Widget build(BuildContext context) { + final theme = Theme.of(context); const size = 8.0; return Row( @@ -154,7 +157,7 @@ class __TypingDotsState extends State<_TypingDots> { ), decoration: BoxDecoration( borderRadius: BorderRadius.circular(size * 2), - color: Theme.of(context).colorScheme.secondary, + color: theme.colorScheme.secondary, ), ), ], 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 c23d9dc6b5..a4a6538a6b 100644 --- a/lib/pages/chat_access_settings/chat_access_settings_page.dart +++ b/lib/pages/chat_access_settings/chat_access_settings_page.dart @@ -14,6 +14,8 @@ class ChatAccessSettingsPageView extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final room = controller.room; return Scaffold( appBar: AppBar( @@ -38,7 +40,7 @@ class ChatAccessSettingsPageView extends StatelessWidget { title: Text( L10n.of(context)!.visibilityOfTheChatHistory, style: TextStyle( - color: Theme.of(context).colorScheme.secondary, + color: theme.colorScheme.secondary, fontWeight: FontWeight.bold, ), ), @@ -56,12 +58,12 @@ class ChatAccessSettingsPageView extends StatelessWidget { ? null : controller.setHistoryVisibility, ), - Divider(color: Theme.of(context).dividerColor), + Divider(color: theme.dividerColor), ListTile( title: Text( L10n.of(context)!.whoIsAllowedToJoinThisGroup, style: TextStyle( - color: Theme.of(context).colorScheme.secondary, + color: theme.colorScheme.secondary, fontWeight: FontWeight.bold, ), ), @@ -79,14 +81,14 @@ class ChatAccessSettingsPageView extends StatelessWidget { ? null : controller.setJoinRule, ), - Divider(color: Theme.of(context).dividerColor), + Divider(color: theme.dividerColor), if ({JoinRules.public, JoinRules.knock} .contains(room.joinRules)) ...[ ListTile( title: Text( L10n.of(context)!.areGuestsAllowedToJoin, style: TextStyle( - color: Theme.of(context).colorScheme.secondary, + color: theme.colorScheme.secondary, fontWeight: FontWeight.bold, ), ), @@ -105,12 +107,12 @@ class ChatAccessSettingsPageView extends StatelessWidget { ? null : controller.setGuestAccess, ), - Divider(color: Theme.of(context).dividerColor), + Divider(color: theme.dividerColor), ListTile( title: Text( L10n.of(context)!.publicChatAddresses, style: TextStyle( - color: Theme.of(context).colorScheme.secondary, + color: theme.colorScheme.secondary, fontWeight: FontWeight.bold, ), ), @@ -163,7 +165,7 @@ class ChatAccessSettingsPageView extends StatelessWidget { ); }, ), - Divider(color: Theme.of(context).dividerColor), + Divider(color: theme.dividerColor), FutureBuilder( future: room.client.getRoomVisibilityOnDirectory(room.id), builder: (context, snapshot) => SwitchListTile.adaptive( @@ -225,6 +227,8 @@ class _AliasListTile extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + return ListTile( leading: isCanonicalAlias ? const Icon(Icons.star) @@ -238,15 +242,15 @@ class _AliasListTile extends StatelessWidget { 'https://matrix.to/#/$alias', style: TextStyle( decoration: TextDecoration.underline, - decorationColor: Theme.of(context).colorScheme.primary, - color: Theme.of(context).colorScheme.primary, + decorationColor: theme.colorScheme.primary, + color: theme.colorScheme.primary, fontSize: 14, ), ), ), trailing: onDelete != null ? IconButton( - color: Theme.of(context).colorScheme.error, + color: theme.colorScheme.error, icon: const Icon(Icons.delete_outlined), onPressed: onDelete, ) diff --git a/lib/pages/chat_details/chat_details_view.dart b/lib/pages/chat_details/chat_details_view.dart index 3c17112a5b..c55e3fe0f3 100644 --- a/lib/pages/chat_details/chat_details_view.dart +++ b/lib/pages/chat_details/chat_details_view.dart @@ -23,6 +23,8 @@ class ChatDetailsView extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final room = Matrix.of(context).client.getRoomById(controller.roomId!); if (room == null) { return Scaffold( @@ -45,7 +47,7 @@ class ChatDetailsView extends StatelessWidget { final actualMembersCount = (room.summary.mInvitedMemberCount ?? 0) + (room.summary.mJoinedMemberCount ?? 0); final canRequestMoreMembers = members.length < actualMembersCount; - final iconColor = Theme.of(context).textTheme.bodyLarge!.color; + final iconColor = theme.textTheme.bodyLarge!.color; final displayname = room.getLocalizedDisplayname( MatrixLocals(L10n.of(context)!), ); @@ -53,7 +55,7 @@ class ChatDetailsView extends StatelessWidget { appBar: AppBar( leading: controller.widget.embeddedCloseButton ?? const Center(child: BackButton()), - elevation: Theme.of(context).appBarTheme.elevation, + elevation: theme.appBarTheme.elevation, actions: [ if (room.canonicalAlias.isNotEmpty) IconButton( @@ -68,7 +70,7 @@ class ChatDetailsView extends StatelessWidget { ChatSettingsPopupMenu(room, false), ], title: Text(L10n.of(context)!.chatDetails), - backgroundColor: Theme.of(context).appBarTheme.backgroundColor, + backgroundColor: theme.appBarTheme.backgroundColor, ), body: MaxWidthBody( child: ListView.builder( @@ -143,9 +145,8 @@ class ChatDetailsView extends StatelessWidget { size: 16, ), style: TextButton.styleFrom( - foregroundColor: Theme.of(context) - .colorScheme - .onSurface, + foregroundColor: + theme.colorScheme.onSurface, ), label: Text( room.isDirectChat @@ -167,9 +168,8 @@ class ChatDetailsView extends StatelessWidget { size: 14, ), style: TextButton.styleFrom( - foregroundColor: Theme.of(context) - .colorScheme - .secondary, + foregroundColor: + theme.colorScheme.secondary, ), label: Text( L10n.of(context)!.countParticipants( @@ -185,13 +185,13 @@ class ChatDetailsView extends StatelessWidget { ), ], ), - Divider(color: Theme.of(context).dividerColor), + Divider(color: theme.dividerColor), if (!room.canChangeStateEvent(EventTypes.RoomTopic)) ListTile( title: Text( L10n.of(context)!.chatDescription, style: TextStyle( - color: Theme.of(context).colorScheme.secondary, + color: theme.colorScheme.secondary, fontWeight: FontWeight.bold, ), ), @@ -204,12 +204,10 @@ class ChatDetailsView extends StatelessWidget { label: Text(L10n.of(context)!.setChatDescription), icon: const Icon(Icons.edit_outlined), style: TextButton.styleFrom( - backgroundColor: Theme.of(context) - .colorScheme - .secondaryContainer, - foregroundColor: Theme.of(context) - .colorScheme - .onSecondaryContainer, + backgroundColor: + theme.colorScheme.secondaryContainer, + foregroundColor: + theme.colorScheme.onSecondaryContainer, ), ), ), @@ -231,21 +229,19 @@ class ChatDetailsView extends StatelessWidget { fontStyle: room.topic.isEmpty ? FontStyle.italic : FontStyle.normal, - color: - Theme.of(context).textTheme.bodyMedium!.color, + color: theme.textTheme.bodyMedium!.color, decorationColor: - Theme.of(context).textTheme.bodyMedium!.color, + theme.textTheme.bodyMedium!.color, ), onOpen: (url) => UrlLauncher(context, url.url).launchUrl(), ), ), const SizedBox(height: 16), - Divider(color: Theme.of(context).dividerColor), + Divider(color: theme.dividerColor), ListTile( leading: CircleAvatar( - backgroundColor: - Theme.of(context).scaffoldBackgroundColor, + backgroundColor: theme.scaffoldBackgroundColor, foregroundColor: iconColor, child: const Icon( Icons.insert_emoticon_outlined, @@ -260,8 +256,7 @@ class ChatDetailsView extends StatelessWidget { if (!room.isDirectChat) ListTile( leading: CircleAvatar( - backgroundColor: - Theme.of(context).scaffoldBackgroundColor, + backgroundColor: theme.scaffoldBackgroundColor, foregroundColor: iconColor, child: const Icon(Icons.shield_outlined), ), @@ -282,8 +277,7 @@ class ChatDetailsView extends StatelessWidget { L10n.of(context)!.whoCanPerformWhichAction, ), leading: CircleAvatar( - backgroundColor: - Theme.of(context).scaffoldBackgroundColor, + backgroundColor: theme.scaffoldBackgroundColor, foregroundColor: iconColor, child: const Icon( Icons.edit_attributes_outlined, @@ -293,14 +287,14 @@ class ChatDetailsView extends StatelessWidget { onTap: () => context .push('/rooms/${room.id}/details/permissions'), ), - Divider(color: Theme.of(context).dividerColor), + Divider(color: theme.dividerColor), ListTile( title: Text( L10n.of(context)!.countParticipants( actualMembersCount.toString(), ), style: TextStyle( - color: Theme.of(context).colorScheme.secondary, + color: theme.colorScheme.secondary, fontWeight: FontWeight.bold, ), ), @@ -309,12 +303,10 @@ class ChatDetailsView extends StatelessWidget { ListTile( title: Text(L10n.of(context)!.inviteContact), leading: CircleAvatar( - backgroundColor: Theme.of(context) - .colorScheme - .primaryContainer, - foregroundColor: Theme.of(context) - .colorScheme - .onPrimaryContainer, + backgroundColor: + theme.colorScheme.primaryContainer, + foregroundColor: + theme.colorScheme.onPrimaryContainer, radius: Avatar.defaultSize / 2, child: const Icon(Icons.add_outlined), ), @@ -332,8 +324,7 @@ class ChatDetailsView extends StatelessWidget { ), ), leading: CircleAvatar( - backgroundColor: - Theme.of(context).scaffoldBackgroundColor, + backgroundColor: theme.scaffoldBackgroundColor, child: const Icon( Icons.group_outlined, color: Colors.grey, diff --git a/lib/pages/chat_details/participant_list_item.dart b/lib/pages/chat_details/participant_list_item.dart index 6611536342..ddacbb7c1b 100644 --- a/lib/pages/chat_details/participant_list_item.dart +++ b/lib/pages/chat_details/participant_list_item.dart @@ -14,6 +14,8 @@ class ParticipantListItem extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final membershipBatch = switch (user.membership) { Membership.ban => L10n.of(context)!.banned, Membership.invite => L10n.of(context)!.invited, @@ -54,17 +56,17 @@ class ParticipantListItem extends StatelessWidget { ), margin: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primaryContainer, + color: theme.colorScheme.primaryContainer, borderRadius: BorderRadius.circular(8), border: Border.all( - color: Theme.of(context).colorScheme.primary, + color: theme.colorScheme.primary, ), ), child: Text( permissionBatch, style: TextStyle( fontSize: 14, - color: Theme.of(context).colorScheme.primary, + color: theme.colorScheme.primary, ), ), ), @@ -74,7 +76,7 @@ class ParticipantListItem extends StatelessWidget { padding: const EdgeInsets.all(4), margin: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( - color: Theme.of(context).secondaryHeaderColor, + color: theme.secondaryHeaderColor, borderRadius: BorderRadius.circular(8), ), child: Center(child: Text(membershipBatch)), diff --git a/lib/pages/chat_encryption_settings/chat_encryption_settings_view.dart b/lib/pages/chat_encryption_settings/chat_encryption_settings_view.dart index d1be4e7d13..2a17a5a336 100644 --- a/lib/pages/chat_encryption_settings/chat_encryption_settings_view.dart +++ b/lib/pages/chat_encryption_settings/chat_encryption_settings_view.dart @@ -18,6 +18,8 @@ class ChatEncryptionSettingsView extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final room = controller.room; return StreamBuilder( stream: room.client.onSync.stream.where( @@ -43,10 +45,8 @@ class ChatEncryptionSettingsView extends StatelessWidget { children: [ SwitchListTile( secondary: CircleAvatar( - foregroundColor: - Theme.of(context).colorScheme.onPrimaryContainer, - backgroundColor: - Theme.of(context).colorScheme.primaryContainer, + foregroundColor: theme.colorScheme.onPrimaryContainer, + backgroundColor: theme.colorScheme.primaryContainer, child: const Icon(Icons.lock_outlined), ), title: Text(L10n.of(context)!.encryptThisChat), @@ -56,7 +56,7 @@ class ChatEncryptionSettingsView extends StatelessWidget { Icon( CupertinoIcons.lock_shield, size: 128, - color: Theme.of(context).colorScheme.onInverseSurface, + color: theme.colorScheme.onInverseSurface, ), const Divider(), if (room.isDirectChat) @@ -144,13 +144,10 @@ class ChatEncryptionSettingsView extends StatelessWidget { AppConfig.borderRadius, ), side: BorderSide( - color: - Theme.of(context).colorScheme.primary, + color: theme.colorScheme.primary, ), ), - color: Theme.of(context) - .colorScheme - .primaryContainer, + color: theme.colorScheme.primaryContainer, child: Padding( padding: const EdgeInsets.all(4.0), child: Text( @@ -158,9 +155,7 @@ class ChatEncryptionSettingsView extends StatelessWidget { maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( - color: Theme.of(context) - .colorScheme - .primary, + color: theme.colorScheme.primary, fontSize: 12, fontStyle: FontStyle.italic, ), @@ -175,7 +170,7 @@ class ChatEncryptionSettingsView extends StatelessWidget { L10n.of(context)!.unknownEncryptionAlgorithm, style: TextStyle( fontFamily: 'RobotoMono', - color: Theme.of(context).colorScheme.secondary, + color: theme.colorScheme.secondary, ), ), ), diff --git a/lib/pages/chat_list/chat_list_body.dart b/lib/pages/chat_list/chat_list_body.dart index d78a88e34a..2ce6446252 100644 --- a/lib/pages/chat_list/chat_list_body.dart +++ b/lib/pages/chat_list/chat_list_body.dart @@ -28,6 +28,8 @@ class ChatListViewBody extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final client = Matrix.of(context).client; final activeSpace = controller.activeSpaceId; if (activeSpace != null) { @@ -59,10 +61,8 @@ class ChatListViewBody extends StatelessWidget { .toList(); final userSearchResult = controller.userSearchResult; const dummyChatCount = 4; - final titleColor = - Theme.of(context).textTheme.bodyLarge!.color!.withAlpha(100); - final subtitleColor = - Theme.of(context).textTheme.bodyLarge!.color!.withAlpha(50); + final titleColor = theme.textTheme.bodyLarge!.color!.withAlpha(100); + final subtitleColor = theme.textTheme.bodyLarge!.color!.withAlpha(50); final filter = controller.searchController.text.toLowerCase(); return StreamBuilder( key: ValueKey( @@ -144,7 +144,7 @@ class ChatListViewBody extends StatelessWidget { clipBehavior: Clip.hardEdge, decoration: const BoxDecoration(), child: Material( - color: Theme.of(context).colorScheme.surface, + color: theme.colorScheme.surface, child: ListTile( leading: const Icon(Icons.vpn_key), title: Text(L10n.of(context)!.dehydrateTor), @@ -199,11 +199,8 @@ class ChatListViewBody extends StatelessWidget { decoration: BoxDecoration( color: filter == controller.activeFilter - ? Theme.of(context) - .colorScheme - .primary - : Theme.of(context) - .colorScheme + ? theme.colorScheme.primary + : theme.colorScheme .secondaryContainer, borderRadius: BorderRadius.circular( AppConfig.borderRadius, @@ -219,11 +216,8 @@ class ChatListViewBody extends StatelessWidget { : FontWeight.normal, color: filter == controller.activeFilter - ? Theme.of(context) - .colorScheme - .onPrimary - : Theme.of(context) - .colorScheme + ? theme.colorScheme.onPrimary + : theme.colorScheme .onSecondaryContainer, ), ), @@ -249,7 +243,7 @@ class ChatListViewBody extends StatelessWidget { child: Icon( CupertinoIcons.chat_bubble_2, size: 128, - color: Theme.of(context).colorScheme.secondary, + color: theme.colorScheme.secondary, ), ), ], @@ -266,7 +260,7 @@ class ChatListViewBody extends StatelessWidget { backgroundColor: titleColor, child: CircularProgressIndicator( strokeWidth: 1, - color: Theme.of(context).textTheme.bodyLarge!.color, + color: theme.textTheme.bodyLarge!.color, ), ), title: Row( diff --git a/lib/pages/chat_list/chat_list_header.dart b/lib/pages/chat_list/chat_list_header.dart index d60cc9ba2d..864a7a7f88 100644 --- a/lib/pages/chat_list/chat_list_header.dart +++ b/lib/pages/chat_list/chat_list_header.dart @@ -19,6 +19,8 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final selectMode = controller.selectMode; return SliverAppBar( @@ -36,7 +38,7 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget { tooltip: L10n.of(context)!.cancel, icon: const Icon(Icons.close_outlined), onPressed: controller.cancelAction, - color: Theme.of(context).colorScheme.primary, + color: theme.colorScheme.primary, ), title: selectMode == SelectMode.share ? Text( @@ -52,7 +54,7 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget { globalSearch: globalSearch, ), decoration: InputDecoration( - fillColor: Theme.of(context).colorScheme.secondaryContainer, + fillColor: theme.colorScheme.secondaryContainer, border: OutlineInputBorder( borderSide: BorderSide.none, borderRadius: BorderRadius.circular(99), @@ -60,7 +62,7 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget { contentPadding: EdgeInsets.zero, hintText: L10n.of(context)!.searchChatsRooms, hintStyle: TextStyle( - color: Theme.of(context).colorScheme.onPrimaryContainer, + color: theme.colorScheme.onPrimaryContainer, fontWeight: FontWeight.normal, ), floatingLabelBehavior: FloatingLabelBehavior.never, @@ -69,14 +71,13 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget { tooltip: L10n.of(context)!.cancel, icon: const Icon(Icons.close_outlined), onPressed: controller.cancelSearch, - color: Theme.of(context).colorScheme.onPrimaryContainer, + color: theme.colorScheme.onPrimaryContainer, ) : IconButton( onPressed: controller.startSearch, icon: Icon( Icons.search_outlined, - color: - Theme.of(context).colorScheme.onPrimaryContainer, + color: theme.colorScheme.onPrimaryContainer, ), ), suffixIcon: controller.isSearchMode && globalSearch diff --git a/lib/pages/chat_list/chat_list_item.dart b/lib/pages/chat_list/chat_list_item.dart index 1ba515e29b..dacc6bec06 100644 --- a/lib/pages/chat_list/chat_list_item.dart +++ b/lib/pages/chat_list/chat_list_item.dart @@ -63,12 +63,13 @@ class ChatListItem extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final isMuted = room.pushRuleState != PushRuleState.notify; 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 theme = Theme.of(context); final directChatMatrixId = room.directChatMatrixID; final isDirectChat = directChatMatrixId != null; final unreadBubbleSize = unread || room.hasNewMessages @@ -126,7 +127,7 @@ class ChatListItem extends StatelessWidget { border: BorderSide( width: 2, color: backgroundColor ?? - Theme.of(context).colorScheme.surface, + theme.colorScheme.surface, ), borderRadius: BorderRadius.circular( AppConfig.borderRadius / 4, @@ -146,7 +147,7 @@ class ChatListItem extends StatelessWidget { : BorderSide( width: 2, color: backgroundColor ?? - Theme.of(context).colorScheme.surface, + theme.colorScheme.surface, ), borderRadius: room.isSpace ? BorderRadius.circular( diff --git a/lib/pages/chat_list/nav_rail_item.dart b/lib/pages/chat_list/nav_rail_item.dart index d09659f886..6264b0b185 100644 --- a/lib/pages/chat_list/nav_rail_item.dart +++ b/lib/pages/chat_list/nav_rail_item.dart @@ -35,6 +35,8 @@ class _NaviRailItemState extends State { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final borderRadius = BorderRadius.circular(AppConfig.borderRadius); return SizedBox( height: 64, @@ -50,7 +52,7 @@ class _NaviRailItemState extends State { duration: FluffyThemes.animationDuration, curve: FluffyThemes.animationCurve, decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary, + color: theme.colorScheme.primary, borderRadius: const BorderRadius.only( topRight: Radius.circular(90), bottomRight: Radius.circular(90), @@ -66,8 +68,8 @@ class _NaviRailItemState extends State { child: Material( borderRadius: borderRadius, color: widget.isSelected - ? Theme.of(context).colorScheme.primaryContainer - : Theme.of(context).colorScheme.surface, + ? theme.colorScheme.primaryContainer + : theme.colorScheme.surface, child: Tooltip( message: widget.toolTip, child: InkWell( diff --git a/lib/pages/chat_list/navi_rail_item.dart b/lib/pages/chat_list/navi_rail_item.dart index 6cbb70e2ec..77837bfef6 100644 --- a/lib/pages/chat_list/navi_rail_item.dart +++ b/lib/pages/chat_list/navi_rail_item.dart @@ -27,6 +27,8 @@ class NaviRailItem extends StatelessWidget { }); @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final borderRadius = BorderRadius.circular(AppConfig.borderRadius); final icon = isSelected ? selectedIcon ?? this.icon : this.icon; final unreadBadgeFilter = this.unreadBadgeFilter; @@ -46,7 +48,7 @@ class NaviRailItem extends StatelessWidget { duration: FluffyThemes.animationDuration, curve: FluffyThemes.animationCurve, decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary, + color: theme.colorScheme.primary, borderRadius: const BorderRadius.only( topRight: Radius.circular(90), bottomRight: Radius.circular(90), @@ -62,8 +64,8 @@ class NaviRailItem extends StatelessWidget { child: Material( borderRadius: borderRadius, color: isSelected - ? Theme.of(context).colorScheme.primaryContainer - : Theme.of(context).colorScheme.surface, + ? theme.colorScheme.primaryContainer + : theme.colorScheme.surface, child: Tooltip( message: toolTip, child: InkWell( diff --git a/lib/pages/chat_list/search_title.dart b/lib/pages/chat_list/search_title.dart index 62bcfb6848..496a5feec4 100644 --- a/lib/pages/chat_list/search_title.dart +++ b/lib/pages/chat_list/search_title.dart @@ -17,55 +17,59 @@ class SearchTitle extends StatelessWidget { }); @override - Widget build(BuildContext context) => Material( - shape: Border( - top: BorderSide( - color: Theme.of(context).dividerColor, - width: 1, - ), - bottom: BorderSide( - color: Theme.of(context).dividerColor, - width: 1, - ), + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Material( + shape: Border( + top: BorderSide( + color: theme.dividerColor, + width: 1, ), - color: color ?? Theme.of(context).colorScheme.surface, - child: InkWell( - onTap: onTap, - splashColor: Theme.of(context).colorScheme.surface, - child: Align( - alignment: Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 8, - ), - child: IconTheme( - data: Theme.of(context).iconTheme.copyWith(size: 16), - child: Row( - children: [ - icon, - const SizedBox(width: 16), - Text( - title, - textAlign: TextAlign.left, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface, - fontSize: 12, - fontWeight: FontWeight.bold, - ), + bottom: BorderSide( + color: theme.dividerColor, + width: 1, + ), + ), + color: color ?? theme.colorScheme.surface, + child: InkWell( + onTap: onTap, + splashColor: theme.colorScheme.surface, + child: Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + child: IconTheme( + data: theme.iconTheme.copyWith(size: 16), + child: Row( + children: [ + icon, + const SizedBox(width: 16), + Text( + title, + textAlign: TextAlign.left, + style: TextStyle( + color: theme.colorScheme.onSurface, + fontSize: 12, + fontWeight: FontWeight.bold, ), - if (trailing != null) - Expanded( - child: Align( - alignment: Alignment.centerRight, - child: trailing!, - ), + ), + if (trailing != null) + Expanded( + child: Align( + alignment: Alignment.centerRight, + child: trailing!, ), - ], - ), + ), + ], ), ), ), ), - ); + ), + ); + } } diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index 94341ed9e8..0295d2b554 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -241,6 +241,8 @@ class _SpaceViewState extends State { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final room = Matrix.of(context).client.getRoomById(widget.spaceId); final displayname = room?.getLocalizedDisplayname() ?? L10n.of(context)!.nothingFound; @@ -359,8 +361,7 @@ class _SpaceViewState extends State { onChanged: (_) => setState(() {}), textInputAction: TextInputAction.search, decoration: InputDecoration( - fillColor: - Theme.of(context).colorScheme.secondaryContainer, + fillColor: theme.colorScheme.secondaryContainer, border: OutlineInputBorder( borderSide: BorderSide.none, borderRadius: BorderRadius.circular(99), @@ -368,9 +369,7 @@ class _SpaceViewState extends State { contentPadding: EdgeInsets.zero, hintText: L10n.of(context)!.search, hintStyle: TextStyle( - color: Theme.of(context) - .colorScheme - .onPrimaryContainer, + color: theme.colorScheme.onPrimaryContainer, fontWeight: FontWeight.normal, ), floatingLabelBehavior: FloatingLabelBehavior.never, @@ -378,9 +377,7 @@ class _SpaceViewState extends State { onPressed: () {}, icon: Icon( Icons.search_outlined, - color: Theme.of(context) - .colorScheme - .onPrimaryContainer, + color: theme.colorScheme.onPrimaryContainer, ), ), ), diff --git a/lib/pages/chat_list/status_msg_list.dart b/lib/pages/chat_list/status_msg_list.dart index 9b69e2a163..9039f908ad 100644 --- a/lib/pages/chat_list/status_msg_list.dart +++ b/lib/pages/chat_list/status_msg_list.dart @@ -116,6 +116,8 @@ class PresenceAvatar extends StatelessWidget { return FutureBuilder( future: client.getProfileFromUserId(presence.userid), builder: (context, snapshot) { + final theme = Theme.of(context); + final profile = snapshot.data; final displayName = profile?.displayName ?? presence.userid.localpart ?? @@ -123,9 +125,8 @@ class PresenceAvatar extends StatelessWidget { final statusMsg = presence.statusMsg; final statusMsgBubbleElevation = - Theme.of(context).appBarTheme.scrolledUnderElevation ?? 4; - final statusMsgBubbleShadowColor = - Theme.of(context).colorScheme.onSurface; + theme.appBarTheme.scrolledUnderElevation ?? 4; + final statusMsgBubbleShadowColor = theme.colorScheme.onSurface; final statusMsgBubbleColor = Colors.white.withAlpha(245); return Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), diff --git a/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart b/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart index 610f6b0e8b..458e1c5031 100644 --- a/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart +++ b/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart @@ -15,6 +15,8 @@ class ChatPermissionsSettingsView extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + return Scaffold( appBar: AppBar( leading: const Center(child: BackButton()), @@ -47,12 +49,12 @@ class ChatPermissionsSettingsView extends StatelessWidget { L10n.of(context)!.chatPermissionsDescription, ), ), - Divider(color: Theme.of(context).dividerColor), + Divider(color: theme.dividerColor), ListTile( title: Text( L10n.of(context)!.chatPermissions, style: TextStyle( - color: Theme.of(context).colorScheme.primary, + color: theme.colorScheme.primary, fontWeight: FontWeight.bold, ), ), @@ -72,12 +74,12 @@ class ChatPermissionsSettingsView extends StatelessWidget { ), canEdit: room.canChangePowerLevel, ), - Divider(color: Theme.of(context).dividerColor), + Divider(color: theme.dividerColor), ListTile( title: Text( L10n.of(context)!.notifications, style: TextStyle( - color: Theme.of(context).colorScheme.primary, + color: theme.colorScheme.primary, fontWeight: FontWeight.bold, ), ), @@ -107,12 +109,12 @@ class ChatPermissionsSettingsView extends StatelessWidget { ); }, ), - Divider(color: Theme.of(context).dividerColor), + Divider(color: theme.dividerColor), ListTile( title: Text( L10n.of(context)!.configureChat, style: TextStyle( - color: Theme.of(context).colorScheme.primary, + color: theme.colorScheme.primary, fontWeight: FontWeight.bold, ), ), diff --git a/lib/pages/chat_permissions_settings/permission_list_tile.dart b/lib/pages/chat_permissions_settings/permission_list_tile.dart index 118982d378..0ca5169656 100644 --- a/lib/pages/chat_permissions_settings/permission_list_tile.dart +++ b/lib/pages/chat_permissions_settings/permission_list_tile.dart @@ -71,6 +71,8 @@ class PermissionsListTile extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final color = permission >= 100 ? Colors.orangeAccent : permission >= 50 @@ -79,7 +81,7 @@ class PermissionsListTile extends StatelessWidget { return ListTile( title: Text( getLocalizedPowerLevelString(context), - style: Theme.of(context).textTheme.titleSmall, + style: theme.textTheme.titleSmall, ), trailing: Material( color: color.withAlpha(32), diff --git a/lib/pages/chat_search/chat_search_files_tab.dart b/lib/pages/chat_search/chat_search_files_tab.dart index 85525ab6f8..e981efb19d 100644 --- a/lib/pages/chat_search/chat_search_files_tab.dart +++ b/lib/pages/chat_search/chat_search_files_tab.dart @@ -28,6 +28,7 @@ class ChatSearchFilesTab extends StatelessWidget { return StreamBuilder( stream: searchStream, builder: (context, snapshot) { + final theme = Theme.of(context); final events = snapshot.data?.$1; if (searchStream == null || events == null) { return Column( @@ -82,10 +83,8 @@ class ChatSearchFilesTab extends StatelessWidget { padding: const EdgeInsets.all(16.0), child: TextButton.icon( style: TextButton.styleFrom( - backgroundColor: - Theme.of(context).colorScheme.secondaryContainer, - foregroundColor: - Theme.of(context).colorScheme.onSecondaryContainer, + backgroundColor: theme.colorScheme.secondaryContainer, + foregroundColor: theme.colorScheme.onSecondaryContainer, ), onPressed: () => startSearch( prevBatch: nextBatch, @@ -127,21 +126,21 @@ class ChatSearchFilesTab extends StatelessWidget { Expanded( child: Container( height: 1, - color: Theme.of(context).dividerColor, + color: theme.dividerColor, ), ), Padding( padding: const EdgeInsets.all(8.0), child: Text( event.originServerTs.localizedTime(context), - style: Theme.of(context).textTheme.labelSmall, + style: theme.textTheme.labelSmall, textAlign: TextAlign.center, ), ), Expanded( child: Container( height: 1, - color: Theme.of(context).dividerColor, + color: theme.dividerColor, ), ), ], @@ -151,7 +150,7 @@ class ChatSearchFilesTab extends StatelessWidget { Material( borderRadius: BorderRadius.circular(AppConfig.borderRadius), - color: Theme.of(context).colorScheme.onInverseSurface, + color: theme.colorScheme.onInverseSurface, clipBehavior: Clip.hardEdge, child: ListTile( leading: const Icon(Icons.file_present_outlined), diff --git a/lib/pages/chat_search/chat_search_images_tab.dart b/lib/pages/chat_search/chat_search_images_tab.dart index 9f8c1f0c93..90cdc839b4 100644 --- a/lib/pages/chat_search/chat_search_images_tab.dart +++ b/lib/pages/chat_search/chat_search_images_tab.dart @@ -28,6 +28,7 @@ class ChatSearchImagesTab extends StatelessWidget { return StreamBuilder( stream: searchStream, builder: (context, snapshot) { + final theme = Theme.of(context); final events = snapshot.data?.$1; if (searchStream == null || events == null) { return Column( @@ -91,10 +92,8 @@ class ChatSearchImagesTab extends StatelessWidget { padding: const EdgeInsets.all(16.0), child: TextButton.icon( style: TextButton.styleFrom( - backgroundColor: - Theme.of(context).colorScheme.secondaryContainer, - foregroundColor: - Theme.of(context).colorScheme.onSecondaryContainer, + backgroundColor: theme.colorScheme.secondaryContainer, + foregroundColor: theme.colorScheme.onSecondaryContainer, ), onPressed: () => startSearch( prevBatch: nextBatch, @@ -119,7 +118,7 @@ class ChatSearchImagesTab extends StatelessWidget { Expanded( child: Container( height: 1, - color: Theme.of(context).dividerColor, + color: theme.dividerColor, ), ), Padding( @@ -128,14 +127,14 @@ class ChatSearchImagesTab extends StatelessWidget { DateFormat.yMMMM( Localizations.localeOf(context).languageCode, ).format(eventsByMonthList[i].key), - style: Theme.of(context).textTheme.labelSmall, + style: theme.textTheme.labelSmall, textAlign: TextAlign.center, ), ), Expanded( child: Container( height: 1, - color: Theme.of(context).dividerColor, + color: theme.dividerColor, ), ), ], diff --git a/lib/pages/chat_search/chat_search_message_tab.dart b/lib/pages/chat_search/chat_search_message_tab.dart index 7542d6ae9e..a2d34fa9e9 100644 --- a/lib/pages/chat_search/chat_search_message_tab.dart +++ b/lib/pages/chat_search/chat_search_message_tab.dart @@ -33,6 +33,7 @@ class ChatSearchMessageTab extends StatelessWidget { key: ValueKey(searchQuery), stream: searchStream, builder: (context, snapshot) { + final theme = Theme.of(context); if (searchStream == null) { return Column( mainAxisAlignment: MainAxisAlignment.center, @@ -55,7 +56,7 @@ class ChatSearchMessageTab extends StatelessWidget { child: ListView.separated( itemCount: events.length + 1, separatorBuilder: (context, _) => Divider( - color: Theme.of(context).dividerColor, + color: theme.dividerColor, height: 1, ), itemBuilder: (context, i) { @@ -79,10 +80,8 @@ class ChatSearchMessageTab extends StatelessWidget { padding: const EdgeInsets.all(16.0), child: TextButton.icon( style: TextButton.styleFrom( - backgroundColor: - Theme.of(context).colorScheme.secondaryContainer, - foregroundColor: - Theme.of(context).colorScheme.onSecondaryContainer, + backgroundColor: theme.colorScheme.secondaryContainer, + foregroundColor: theme.colorScheme.onSecondaryContainer, ), onPressed: () => startSearch( prevBatch: nextBatch, @@ -130,6 +129,8 @@ class _MessageSearchResultListTile extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + return ListTile( title: Row( children: [ @@ -153,9 +154,9 @@ class _MessageSearchResultListTile extends StatelessWidget { subtitle: Linkify( options: const LinkifyOptions(humanize: false), linkStyle: TextStyle( - color: Theme.of(context).colorScheme.primary, + color: theme.colorScheme.primary, decoration: TextDecoration.underline, - decorationColor: Theme.of(context).colorScheme.primary, + decorationColor: theme.colorScheme.primary, ), onOpen: (url) => UrlLauncher(context, url.url).launchUrl(), text: event diff --git a/lib/pages/device_settings/device_settings_view.dart b/lib/pages/device_settings/device_settings_view.dart index e107d30c91..20fb9e8007 100644 --- a/lib/pages/device_settings/device_settings_view.dart +++ b/lib/pages/device_settings/device_settings_view.dart @@ -22,6 +22,7 @@ class DevicesSettingsView extends StatelessWidget { child: FutureBuilder( future: controller.loadUserDevices(context), builder: (BuildContext context, snapshot) { + final theme = Theme.of(context); if (snapshot.hasError) { return Center( child: Column( @@ -58,7 +59,7 @@ class DevicesSettingsView extends StatelessWidget { L10n.of(context)!.thisDevice, style: TextStyle( fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.primary, + color: theme.colorScheme.primary, ), textAlign: TextAlign.left, ), @@ -86,12 +87,10 @@ class DevicesSettingsView extends StatelessWidget { L10n.of(context)!.removeAllOtherDevices, ), style: TextButton.styleFrom( - foregroundColor: Theme.of(context) - .colorScheme - .onErrorContainer, - backgroundColor: Theme.of(context) - .colorScheme - .errorContainer, + foregroundColor: + theme.colorScheme.onErrorContainer, + backgroundColor: + theme.colorScheme.errorContainer, ), icon: controller.loadingDeletingDevices ? const CircularProgressIndicator.adaptive( diff --git a/lib/pages/homeserver_picker/homeserver_app_bar.dart b/lib/pages/homeserver_picker/homeserver_app_bar.dart index 8a1ad4e2b5..3ba5064549 100644 --- a/lib/pages/homeserver_picker/homeserver_app_bar.dart +++ b/lib/pages/homeserver_picker/homeserver_app_bar.dart @@ -17,14 +17,15 @@ class HomeserverAppBar extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + return TypeAheadField( decorationBuilder: (context, child) => ConstrainedBox( constraints: const BoxConstraints(maxHeight: 256), child: Material( borderRadius: BorderRadius.circular(AppConfig.borderRadius), - elevation: Theme.of(context).appBarTheme.scrolledUnderElevation ?? 4, - shadowColor: - Theme.of(context).appBarTheme.shadowColor ?? Colors.black, + elevation: theme.appBarTheme.scrolledUnderElevation ?? 4, + shadowColor: theme.appBarTheme.shadowColor ?? Colors.black, child: child, ), ), @@ -92,9 +93,9 @@ class HomeserverAppBar extends StatelessWidget { ) : null, fillColor: FluffyThemes.isColumnMode(context) - ? Theme.of(context).colorScheme.surface + ? theme.colorScheme.surface // ignore: deprecated_member_use - : Theme.of(context).colorScheme.surfaceVariant, + : theme.colorScheme.surfaceVariant, prefixText: '${L10n.of(context)!.homeserver}: ', hintText: L10n.of(context)!.enterYourHomeserver, suffixIcon: const Icon(Icons.search), diff --git a/lib/pages/homeserver_picker/homeserver_picker_view.dart b/lib/pages/homeserver_picker/homeserver_picker_view.dart index ce86abec04..dd6c50d044 100644 --- a/lib/pages/homeserver_picker/homeserver_picker_view.dart +++ b/lib/pages/homeserver_picker/homeserver_picker_view.dart @@ -19,6 +19,8 @@ class HomeserverPickerView extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final identityProviders = controller.identityProviders; final errorText = controller.error; final publicHomeserver = controller.cachedHomeservers?.singleWhereOrNull( @@ -32,7 +34,7 @@ class HomeserverPickerView extends StatelessWidget { appBar: AppBar( titleSpacing: 12, automaticallyImplyLeading: false, - surfaceTintColor: Theme.of(context).colorScheme.surface, + surfaceTintColor: theme.colorScheme.surface, title: HomeserverAppBar(controller: controller), ), body: Column( @@ -50,7 +52,7 @@ class HomeserverPickerView extends StatelessWidget { clipBehavior: Clip.hardEdge, borderRadius: const BorderRadius.vertical(bottom: Radius.circular(8)), - color: Theme.of(context).colorScheme.surface, + color: theme.colorScheme.surface, child: ListTile( leading: const Icon(Icons.vpn_key), title: Text(L10n.of(context)!.hydrateTor), @@ -80,7 +82,7 @@ class HomeserverPickerView extends StatelessWidget { errorText, textAlign: TextAlign.center, style: TextStyle( - color: Theme.of(context).colorScheme.error, + color: theme.colorScheme.error, fontSize: 18, ), ), @@ -91,7 +93,7 @@ class HomeserverPickerView extends StatelessWidget { .pleaseTryAgainLaterOrChooseDifferentServer, textAlign: TextAlign.center, style: TextStyle( - color: Theme.of(context).colorScheme.error, + color: theme.colorScheme.error, fontSize: 12, ), ), @@ -189,6 +191,8 @@ class _LoginButton extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final icon = this.icon; return Container( margin: const EdgeInsets.only(bottom: 12), @@ -201,16 +205,15 @@ class _LoginButton extends StatelessWidget { side: FluffyThemes.isColumnMode(context) ? BorderSide.none : BorderSide( - color: Theme.of(context).colorScheme.outlineVariant, + color: theme.colorScheme.outlineVariant, width: 1, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(99), ), - foregroundColor: Theme.of(context).colorScheme.onSurface, - backgroundColor: withBorder - ? Theme.of(context).colorScheme.surface - : Colors.transparent, + foregroundColor: theme.colorScheme.onSurface, + backgroundColor: + withBorder ? theme.colorScheme.surface : Colors.transparent, ), onPressed: onPressed, label: Text(label), diff --git a/lib/pages/invitation_selection/invitation_selection_view.dart b/lib/pages/invitation_selection/invitation_selection_view.dart index 92596e894c..795334e04c 100644 --- a/lib/pages/invitation_selection/invitation_selection_view.dart +++ b/lib/pages/invitation_selection/invitation_selection_view.dart @@ -153,6 +153,8 @@ class _InviteContactListTile extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + return Opacity( opacity: isMember ? 0.5 : 1, child: ListTile( @@ -171,7 +173,7 @@ class _InviteContactListTile extends StatelessWidget { maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( - color: Theme.of(context).colorScheme.secondary, + color: theme.colorScheme.secondary, ), ), onTap: isMember ? null : onTap, diff --git a/lib/pages/key_verification/key_verification_dialog.dart b/lib/pages/key_verification/key_verification_dialog.dart index 99d082a765..b4e5a91ae3 100644 --- a/lib/pages/key_verification/key_verification_dialog.dart +++ b/lib/pages/key_verification/key_verification_dialog.dart @@ -94,6 +94,8 @@ class KeyVerificationPageState extends State { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + User? user; final directChatId = widget.request.client.getDirectChatFromUserId(widget.request.userId); @@ -139,10 +141,8 @@ class KeyVerificationPageState extends State { obscureText: true, decoration: InputDecoration( hintText: L10n.of(context)!.passphraseOrKey, - prefixStyle: - TextStyle(color: Theme.of(context).colorScheme.primary), - suffixStyle: - TextStyle(color: Theme.of(context).colorScheme.primary), + prefixStyle: TextStyle(color: theme.colorScheme.primary), + suffixStyle: TextStyle(color: theme.colorScheme.primary), border: const OutlineInputBorder(), ), ), diff --git a/lib/pages/login/login_view.dart b/lib/pages/login/login_view.dart index 8ec092cbf8..0358c44a62 100644 --- a/lib/pages/login/login_view.dart +++ b/lib/pages/login/login_view.dart @@ -14,6 +14,8 @@ class LoginView extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final homeserver = Matrix.of(context) .getLoginClient() .homeserver @@ -23,9 +25,9 @@ class LoginView extends StatelessWidget { final titleParts = title.split(homeserver); final textFieldFillColor = FluffyThemes.isColumnMode(context) - ? Theme.of(context).colorScheme.surface + ? theme.colorScheme.surface // ignore: deprecated_member_use - : Theme.of(context).colorScheme.surfaceVariant; + : theme.colorScheme.surfaceVariant; return LoginScaffold( enforceMobileMode: Matrix.of(context).client.isLogged(), @@ -111,8 +113,8 @@ class LoginView extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 8.0), child: ElevatedButton.icon( style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.primary, - foregroundColor: Theme.of(context).colorScheme.onPrimary, + backgroundColor: theme.colorScheme.primary, + foregroundColor: theme.colorScheme.onPrimary, ), onPressed: controller.loading ? null : controller.login, icon: const Icon(Icons.login_outlined), @@ -129,7 +131,7 @@ class LoginView extends StatelessWidget { ? () {} : controller.passwordForgotten, style: TextButton.styleFrom( - foregroundColor: Theme.of(context).colorScheme.error, + foregroundColor: theme.colorScheme.error, ), icon: const Icon(Icons.safety_check_outlined), label: Text(L10n.of(context)!.passwordForgotten), diff --git a/lib/pages/new_group/new_group_view.dart b/lib/pages/new_group/new_group_view.dart index df2a4ac49d..0f86efb45b 100644 --- a/lib/pages/new_group/new_group_view.dart +++ b/lib/pages/new_group/new_group_view.dart @@ -15,6 +15,8 @@ class NewGroupView extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final avatar = controller.avatar; final error = controller.error; return Scaffold( @@ -86,12 +88,12 @@ class NewGroupView extends StatelessWidget { SwitchListTile.adaptive( secondary: Icon( Icons.lock_outlined, - color: Theme.of(context).colorScheme.onSurface, + color: theme.colorScheme.onSurface, ), title: Text( L10n.of(context)!.enableEncryption, style: TextStyle( - color: Theme.of(context).colorScheme.onSurface, + color: theme.colorScheme.onSurface, ), ), value: !controller.publicGroup, @@ -126,12 +128,12 @@ class NewGroupView extends StatelessWidget { : ListTile( leading: Icon( Icons.warning_outlined, - color: Theme.of(context).colorScheme.error, + color: theme.colorScheme.error, ), title: Text( error.toLocalizedString(context), style: TextStyle( - color: Theme.of(context).colorScheme.error, + color: theme.colorScheme.error, ), ), ), diff --git a/lib/pages/new_private_chat/new_private_chat_view.dart b/lib/pages/new_private_chat/new_private_chat_view.dart index 81d10bcf8d..ceee0ef45e 100644 --- a/lib/pages/new_private_chat/new_private_chat_view.dart +++ b/lib/pages/new_private_chat/new_private_chat_view.dart @@ -22,13 +22,15 @@ class NewPrivateChatView extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final searchResponse = controller.searchResponse; return Scaffold( appBar: AppBar( scrolledUnderElevation: 0, leading: const Center(child: BackButton()), title: Text(L10n.of(context)!.newChat), - backgroundColor: Theme.of(context).scaffoldBackgroundColor, + backgroundColor: theme.scaffoldBackgroundColor, actions: [ TextButton( onPressed: @@ -109,7 +111,7 @@ class NewPrivateChatView extends StatelessWidget { ], ), style: TextStyle( - color: Theme.of(context).colorScheme.onSurface, + color: theme.colorScheme.onSurface, fontSize: 13, ), ), @@ -117,10 +119,8 @@ class NewPrivateChatView extends StatelessWidget { const SizedBox(height: 8), ListTile( leading: CircleAvatar( - backgroundColor: - Theme.of(context).colorScheme.secondaryContainer, - foregroundColor: - Theme.of(context).colorScheme.onSecondaryContainer, + backgroundColor: theme.colorScheme.secondaryContainer, + foregroundColor: theme.colorScheme.onSecondaryContainer, child: Icon(Icons.adaptive.share_outlined), ), title: Text(L10n.of(context)!.shareInviteLink), @@ -128,10 +128,8 @@ class NewPrivateChatView extends StatelessWidget { ), ListTile( leading: CircleAvatar( - backgroundColor: - Theme.of(context).colorScheme.tertiaryContainer, - foregroundColor: - Theme.of(context).colorScheme.onTertiaryContainer, + backgroundColor: theme.colorScheme.tertiaryContainer, + foregroundColor: theme.colorScheme.onTertiaryContainer, child: const Icon(Icons.group_add_outlined), ), title: Text(L10n.of(context)!.createGroup), @@ -140,10 +138,8 @@ class NewPrivateChatView extends StatelessWidget { if (PlatformInfos.isMobile) ListTile( leading: CircleAvatar( - backgroundColor: - Theme.of(context).colorScheme.primaryContainer, - foregroundColor: - Theme.of(context).colorScheme.onPrimaryContainer, + backgroundColor: theme.colorScheme.primaryContainer, + foregroundColor: theme.colorScheme.onPrimaryContainer, child: const Icon(Icons.qr_code_scanner_outlined), ), title: Text(L10n.of(context)!.scanQrCode), @@ -158,8 +154,7 @@ class NewPrivateChatView extends StatelessWidget { borderRadius: BorderRadius.circular(12), elevation: 10, color: Colors.white, - shadowColor: - Theme.of(context).appBarTheme.shadowColor, + shadowColor: theme.appBarTheme.shadowColor, clipBehavior: Clip.hardEdge, child: Padding( padding: const EdgeInsets.all(8), @@ -169,12 +164,9 @@ class NewPrivateChatView extends StatelessWidget { decoration: PrettyQrDecoration( shape: PrettyQrSmoothSymbol( roundFactor: 1, - color: Theme.of(context).brightness == - Brightness.light - ? Theme.of(context).colorScheme.primary - : Theme.of(context) - .colorScheme - .onPrimary, + color: theme.brightness == Brightness.light + ? theme.colorScheme.primary + : theme.colorScheme.onPrimary, ), ), ), @@ -198,7 +190,7 @@ class NewPrivateChatView extends StatelessWidget { error.toLocalizedString(context), textAlign: TextAlign.center, style: TextStyle( - color: Theme.of(context).colorScheme.error, + color: theme.colorScheme.error, ), ), const SizedBox(height: 12), @@ -227,7 +219,7 @@ class NewPrivateChatView extends StatelessWidget { controller.controller.text, ), style: TextStyle( - color: Theme.of(context).colorScheme.primary, + color: theme.colorScheme.primary, ), textAlign: TextAlign.center, ), diff --git a/lib/pages/settings/settings_view.dart b/lib/pages/settings/settings_view.dart index b2293f4b23..70133433b6 100644 --- a/lib/pages/settings/settings_view.dart +++ b/lib/pages/settings/settings_view.dart @@ -19,6 +19,7 @@ class SettingsView extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); final showChatBackupBanner = controller.showChatBackupBanner; return Scaffold( appBar: AppBar( @@ -30,7 +31,7 @@ class SettingsView extends StatelessWidget { title: Text(L10n.of(context)!.settings), ), body: ListTileTheme( - iconColor: Theme.of(context).colorScheme.onSurface, + iconColor: theme.colorScheme.onSurface, child: ListView( key: const Key('SettingsListViewContent'), children: [ @@ -79,8 +80,7 @@ class SettingsView extends StatelessWidget { size: 16, ), style: TextButton.styleFrom( - foregroundColor: - Theme.of(context).colorScheme.onSurface, + foregroundColor: theme.colorScheme.onSurface, ), label: Text( displayname, @@ -98,8 +98,7 @@ class SettingsView extends StatelessWidget { size: 14, ), style: TextButton.styleFrom( - foregroundColor: - Theme.of(context).colorScheme.secondary, + foregroundColor: theme.colorScheme.secondary, ), label: Text( mxid, @@ -115,7 +114,7 @@ class SettingsView extends StatelessWidget { ); }, ), - Divider(color: Theme.of(context).dividerColor), + Divider(color: theme.dividerColor), if (showChatBackupBanner == null) ListTile( leading: const Icon(Icons.backup_outlined), @@ -131,7 +130,7 @@ class SettingsView extends StatelessWidget { onChanged: controller.firstRunBootstrapAction, ), Divider( - color: Theme.of(context).dividerColor, + color: theme.dividerColor, ), ListTile( leading: const Icon(Icons.format_paint_outlined), @@ -158,7 +157,7 @@ class SettingsView extends StatelessWidget { title: Text(L10n.of(context)!.security), onTap: () => context.go('/rooms/settings/security'), ), - Divider(color: Theme.of(context).dividerColor), + Divider(color: theme.dividerColor), ListTile( leading: const Icon(Icons.help_outline_outlined), title: Text(L10n.of(context)!.help), @@ -174,7 +173,7 @@ class SettingsView extends StatelessWidget { title: Text(L10n.of(context)!.about), onTap: () => PlatformInfos.showDialog(context), ), - Divider(color: Theme.of(context).dividerColor), + Divider(color: theme.dividerColor), ListTile( leading: const Icon(Icons.logout_outlined), title: Text(L10n.of(context)!.logout), diff --git a/lib/pages/settings_3pid/settings_3pid_view.dart b/lib/pages/settings_3pid/settings_3pid_view.dart index 4c5377c640..af5e1f757f 100644 --- a/lib/pages/settings_3pid/settings_3pid_view.dart +++ b/lib/pages/settings_3pid/settings_3pid_view.dart @@ -14,6 +14,8 @@ class Settings3PidView extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + controller.request ??= Matrix.of(context).client.getAccount3PIDs(); return Scaffold( appBar: AppBar( @@ -53,7 +55,7 @@ class Settings3PidView extends StatelessWidget { children: [ ListTile( leading: CircleAvatar( - backgroundColor: Theme.of(context).scaffoldBackgroundColor, + backgroundColor: theme.scaffoldBackgroundColor, foregroundColor: identifier.isEmpty ? Colors.orange : Colors.grey, child: Icon( @@ -75,8 +77,7 @@ class Settings3PidView extends StatelessWidget { itemCount: identifier.length, itemBuilder: (BuildContext context, int i) => ListTile( leading: CircleAvatar( - backgroundColor: - Theme.of(context).scaffoldBackgroundColor, + backgroundColor: theme.scaffoldBackgroundColor, foregroundColor: Colors.grey, child: Icon(identifier[i].iconData), ), diff --git a/lib/pages/settings_chat/settings_chat_view.dart b/lib/pages/settings_chat/settings_chat_view.dart index 01cc7af291..e23829d870 100644 --- a/lib/pages/settings_chat/settings_chat_view.dart +++ b/lib/pages/settings_chat/settings_chat_view.dart @@ -18,10 +18,12 @@ class SettingsChatView extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + return Scaffold( appBar: AppBar(title: Text(L10n.of(context)!.chat)), body: ListTileTheme( - iconColor: Theme.of(context).textTheme.bodyLarge!.color, + iconColor: theme.textTheme.bodyLarge!.color, child: MaxWidthBody( child: Column( children: [ @@ -71,12 +73,12 @@ class SettingsChatView extends StatelessWidget { storeKey: SettingKeys.swipeRightToLeftToReply, defaultValue: AppConfig.swipeRightToLeftToReply, ), - Divider(color: Theme.of(context).dividerColor), + Divider(color: theme.dividerColor), ListTile( title: Text( L10n.of(context)!.customEmojisAndStickers, style: TextStyle( - color: Theme.of(context).colorScheme.secondary, + color: theme.colorScheme.secondary, fontWeight: FontWeight.bold, ), ), @@ -90,12 +92,12 @@ class SettingsChatView extends StatelessWidget { child: Icon(Icons.chevron_right_outlined), ), ), - Divider(color: Theme.of(context).dividerColor), + Divider(color: theme.dividerColor), ListTile( title: Text( L10n.of(context)!.calls, style: TextStyle( - color: Theme.of(context).colorScheme.secondary, + color: theme.colorScheme.secondary, fontWeight: FontWeight.bold, ), ), diff --git a/lib/pages/settings_emotes/import_archive_dialog.dart b/lib/pages/settings_emotes/import_archive_dialog.dart index 0ed5bb21f4..5c63615772 100644 --- a/lib/pages/settings_emotes/import_archive_dialog.dart +++ b/lib/pages/settings_emotes/import_archive_dialog.dart @@ -231,6 +231,8 @@ class _EmojiImportPreviewState extends State<_EmojiImportPreview> { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + // TODO: support Lottie here as well ... final controller = TextEditingController(text: widget.entry.value); @@ -281,11 +283,11 @@ class _EmojiImportPreviewState extends State<_EmojiImportPreview> { suffixText: ':', border: const OutlineInputBorder(), prefixStyle: TextStyle( - color: Theme.of(context).colorScheme.secondary, + color: theme.colorScheme.secondary, fontWeight: FontWeight.bold, ), suffixStyle: TextStyle( - color: Theme.of(context).colorScheme.secondary, + color: theme.colorScheme.secondary, fontWeight: FontWeight.bold, ), ), @@ -314,6 +316,8 @@ class _ImageFileError extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + return SizedBox.square( dimension: 64, child: Tooltip( @@ -327,7 +331,7 @@ class _ImageFileError extends StatelessWidget { Text( L10n.of(context)!.notAnImage, textAlign: TextAlign.center, - style: Theme.of(context).textTheme.labelSmall, + style: theme.textTheme.labelSmall, ), ], ), diff --git a/lib/pages/settings_emotes/settings_emotes_view.dart b/lib/pages/settings_emotes/settings_emotes_view.dart index be1e36f923..1337873a27 100644 --- a/lib/pages/settings_emotes/settings_emotes_view.dart +++ b/lib/pages/settings_emotes/settings_emotes_view.dart @@ -19,6 +19,8 @@ class EmotesSettingsView extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final client = Matrix.of(context).client; final imageKeys = controller.pack!.images.keys.toList(); return Scaffold( @@ -73,7 +75,7 @@ class EmotesSettingsView extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(10)), - color: Theme.of(context).secondaryHeaderColor, + color: theme.secondaryHeaderColor, ), child: TextField( controller: controller.newImageCodeController, @@ -85,11 +87,11 @@ class EmotesSettingsView extends StatelessWidget { prefixText: ': ', suffixText: ':', prefixStyle: TextStyle( - color: Theme.of(context).colorScheme.secondary, + color: theme.colorScheme.secondary, fontWeight: FontWeight.bold, ), suffixStyle: TextStyle( - color: Theme.of(context).colorScheme.secondary, + color: theme.colorScheme.secondary, fontWeight: FontWeight.bold, ), border: InputBorder.none, @@ -152,7 +154,7 @@ class EmotesSettingsView extends StatelessWidget { decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(10)), - color: Theme.of(context).secondaryHeaderColor, + color: theme.secondaryHeaderColor, ), child: Shortcuts( shortcuts: !useShortCuts @@ -188,13 +190,11 @@ class EmotesSettingsView extends StatelessWidget { prefixText: ': ', suffixText: ':', prefixStyle: TextStyle( - color: - Theme.of(context).colorScheme.secondary, + color: theme.colorScheme.secondary, fontWeight: FontWeight.bold, ), suffixStyle: TextStyle( - color: - Theme.of(context).colorScheme.secondary, + color: theme.colorScheme.secondary, fontWeight: FontWeight.bold, ), border: InputBorder.none, diff --git a/lib/pages/settings_ignore_list/settings_ignore_list_view.dart b/lib/pages/settings_ignore_list/settings_ignore_list_view.dart index dee5e808a0..e66387503d 100644 --- a/lib/pages/settings_ignore_list/settings_ignore_list_view.dart +++ b/lib/pages/settings_ignore_list/settings_ignore_list_view.dart @@ -16,6 +16,8 @@ class SettingsIgnoreListView extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final client = Matrix.of(context).client; return Scaffold( appBar: AppBar( @@ -58,7 +60,7 @@ class SettingsIgnoreListView extends StatelessWidget { ), ), Divider( - color: Theme.of(context).dividerColor, + color: theme.dividerColor, ), Expanded( child: StreamBuilder( diff --git a/lib/pages/settings_notifications/settings_notifications_view.dart b/lib/pages/settings_notifications/settings_notifications_view.dart index 6c1d45cc5d..0128b7593d 100644 --- a/lib/pages/settings_notifications/settings_notifications_view.dart +++ b/lib/pages/settings_notifications/settings_notifications_view.dart @@ -30,6 +30,7 @@ class SettingsNotificationsView extends StatelessWidget { false, ), builder: (BuildContext context, _) { + final theme = Theme.of(context); return Column( children: [ SwitchListTile.adaptive( @@ -41,12 +42,12 @@ class SettingsNotificationsView extends StatelessWidget { ? null : (_) => controller.onToggleMuteAllNotifications(), ), - Divider(color: Theme.of(context).dividerColor), + Divider(color: theme.dividerColor), ListTile( title: Text( L10n.of(context)!.notifyMeFor, style: TextStyle( - color: Theme.of(context).colorScheme.secondary, + color: theme.colorScheme.secondary, fontWeight: FontWeight.bold, ), ), @@ -64,12 +65,12 @@ class SettingsNotificationsView extends StatelessWidget { : (bool enabled) => controller .setNotificationSetting(item, enabled), ), - Divider(color: Theme.of(context).dividerColor), + Divider(color: theme.dividerColor), ListTile( title: Text( L10n.of(context)!.devices, style: TextStyle( - color: Theme.of(context).colorScheme.secondary, + color: theme.colorScheme.secondary, fontWeight: FontWeight.bold, ), ), diff --git a/lib/pages/settings_password/settings_password_view.dart b/lib/pages/settings_password/settings_password_view.dart index 813bebd18d..ee80e93611 100644 --- a/lib/pages/settings_password/settings_password_view.dart +++ b/lib/pages/settings_password/settings_password_view.dart @@ -12,6 +12,8 @@ class SettingsPasswordView extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + return Scaffold( appBar: AppBar( title: Text(L10n.of(context)!.changePassword), @@ -23,7 +25,7 @@ class SettingsPasswordView extends StatelessWidget { ], ), body: ListTileTheme( - iconColor: Theme.of(context).colorScheme.onSurface, + iconColor: theme.colorScheme.onSurface, child: MaxWidthBody( child: Padding( padding: const EdgeInsets.all(16.0), @@ -32,7 +34,7 @@ class SettingsPasswordView extends StatelessWidget { Center( child: Icon( Icons.key_outlined, - color: Theme.of(context).dividerColor, + color: theme.dividerColor, size: 80, ), ), diff --git a/lib/pages/settings_security/settings_security_view.dart b/lib/pages/settings_security/settings_security_view.dart index 6ff7e1696b..76ea243a06 100644 --- a/lib/pages/settings_security/settings_security_view.dart +++ b/lib/pages/settings_security/settings_security_view.dart @@ -18,10 +18,12 @@ class SettingsSecurityView extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + return Scaffold( appBar: AppBar(title: Text(L10n.of(context)!.security)), body: ListTileTheme( - iconColor: Theme.of(context).colorScheme.onSurface, + iconColor: theme.colorScheme.onSurface, child: MaxWidthBody( child: FutureBuilder( future: Matrix.of(context) @@ -45,7 +47,7 @@ class SettingsSecurityView extends StatelessWidget { title: Text( L10n.of(context)!.privacy, style: TextStyle( - color: Theme.of(context).colorScheme.secondary, + color: theme.colorScheme.secondary, fontWeight: FontWeight.bold, ), ), @@ -86,13 +88,13 @@ class SettingsSecurityView extends StatelessWidget { ), }, Divider( - color: Theme.of(context).dividerColor, + color: theme.dividerColor, ), ListTile( title: Text( L10n.of(context)!.account, style: TextStyle( - color: Theme.of(context).colorScheme.secondary, + color: theme.colorScheme.secondary, fontWeight: FontWeight.bold, ), ), diff --git a/lib/pages/settings_style/settings_style_view.dart b/lib/pages/settings_style/settings_style_view.dart index 1a07dd2bc2..fd0a4c4f9d 100644 --- a/lib/pages/settings_style/settings_style_view.dart +++ b/lib/pages/settings_style/settings_style_view.dart @@ -20,6 +20,8 @@ class SettingsStyleView extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + const colorPickerSize = 32.0; final client = Matrix.of(context).client; return Scaffold( @@ -27,7 +29,7 @@ class SettingsStyleView extends StatelessWidget { leading: const Center(child: BackButton()), title: Text(L10n.of(context)!.changeTheme), ), - backgroundColor: Theme.of(context).colorScheme.surface, + backgroundColor: theme.colorScheme.surface, body: MaxWidthBody( child: Column( children: [ @@ -35,7 +37,7 @@ class SettingsStyleView extends StatelessWidget { title: Text( L10n.of(context)!.setColorTheme, style: TextStyle( - color: Theme.of(context).colorScheme.secondary, + color: theme.colorScheme.secondary, fontWeight: FontWeight.bold, ), ), @@ -88,18 +90,16 @@ class SettingsStyleView extends StatelessWidget { child: Icon( Icons.check, size: 16, - color: Theme.of(context) - .colorScheme - .onSurface, + color: theme + .colorScheme.onSurface, ), ), Text( L10n.of(context)!.systemTheme, textAlign: TextAlign.center, style: TextStyle( - color: Theme.of(context) - .colorScheme - .onSurface, + color: theme + .colorScheme.onSurface, ), ), ], @@ -136,13 +136,13 @@ class SettingsStyleView extends StatelessWidget { ), const SizedBox(height: 8), Divider( - color: Theme.of(context).dividerColor, + color: theme.dividerColor, ), ListTile( title: Text( L10n.of(context)!.setTheme, style: TextStyle( - color: Theme.of(context).colorScheme.secondary, + color: theme.colorScheme.secondary, fontWeight: FontWeight.bold, ), ), @@ -166,13 +166,13 @@ class SettingsStyleView extends StatelessWidget { onChanged: controller.switchTheme, ), Divider( - color: Theme.of(context).dividerColor, + color: theme.dividerColor, ), ListTile( title: Text( L10n.of(context)!.overview, style: TextStyle( - color: Theme.of(context).colorScheme.secondary, + color: theme.colorScheme.secondary, fontWeight: FontWeight.bold, ), ), @@ -190,13 +190,13 @@ class SettingsStyleView extends StatelessWidget { defaultValue: AppConfig.separateChatTypes, ), Divider( - color: Theme.of(context).dividerColor, + color: theme.dividerColor, ), ListTile( title: Text( L10n.of(context)!.messagesStyle, style: TextStyle( - color: Theme.of(context).colorScheme.secondary, + color: theme.colorScheme.secondary, fontWeight: FontWeight.bold, ), ), @@ -246,7 +246,7 @@ class SettingsStyleView extends StatelessWidget { bottom: 12, ), child: Material( - color: Theme.of(context).colorScheme.primary, + color: theme.colorScheme.primary, borderRadius: BorderRadius.circular( AppConfig.borderRadius, ), @@ -258,8 +258,7 @@ class SettingsStyleView extends StatelessWidget { child: Text( 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor', style: TextStyle( - color: - Theme.of(context).colorScheme.onPrimary, + color: theme.colorScheme.onPrimary, fontSize: AppConfig.messageFontSize * AppConfig.fontSizeFactor, ), @@ -277,7 +276,7 @@ class SettingsStyleView extends StatelessWidget { ? null : IconButton( icon: const Icon(Icons.delete_outlined), - color: Theme.of(context).colorScheme.error, + color: theme.colorScheme.error, onPressed: controller.deleteChatWallpaper, ), onTap: controller.setWallpaper, diff --git a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart index 0b78c27f98..16426f8711 100644 --- a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart +++ b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart @@ -62,6 +62,7 @@ class UserBottomSheetView extends StatelessWidget { false, ), builder: (context, snapshot) { + final theme = Theme.of(context); return ListView( children: [ if (user?.membership == Membership.knock) @@ -70,7 +71,7 @@ class UserBottomSheetView extends StatelessWidget { child: Material( color: // ignore: deprecated_member_use - Theme.of(context).colorScheme.surfaceVariant, + theme.colorScheme.surfaceVariant, borderRadius: BorderRadius.circular(AppConfig.borderRadius), child: ListTile( @@ -86,10 +87,8 @@ class UserBottomSheetView extends StatelessWidget { children: [ TextButton.icon( style: TextButton.styleFrom( - backgroundColor: - Theme.of(context).colorScheme.surface, - foregroundColor: - Theme.of(context).colorScheme.primary, + backgroundColor: theme.colorScheme.surface, + foregroundColor: theme.colorScheme.primary, ), onPressed: controller.knockAccept, icon: const Icon(Icons.check_outlined), @@ -98,12 +97,10 @@ class UserBottomSheetView extends StatelessWidget { const SizedBox(width: 12), TextButton.icon( style: TextButton.styleFrom( - backgroundColor: Theme.of(context) - .colorScheme - .errorContainer, - foregroundColor: Theme.of(context) - .colorScheme - .onErrorContainer, + backgroundColor: + theme.colorScheme.errorContainer, + foregroundColor: + theme.colorScheme.onErrorContainer, ), onPressed: controller.knockDecline, icon: const Icon(Icons.cancel_outlined), @@ -142,8 +139,7 @@ class UserBottomSheetView extends StatelessWidget { size: 14, ), style: TextButton.styleFrom( - foregroundColor: - Theme.of(context).colorScheme.onSurface, + foregroundColor: theme.colorScheme.onSurface, ), label: Text( userId, @@ -187,8 +183,7 @@ class UserBottomSheetView extends StatelessWidget { Text( L10n.of(context)!.currentlyActive, overflow: TextOverflow.ellipsis, - style: - Theme.of(context).textTheme.bodySmall, + style: theme.textTheme.bodySmall, ) else if (lastActiveTimestamp != null) Text( @@ -197,8 +192,7 @@ class UserBottomSheetView extends StatelessWidget { .localizedTimeShort(context), ), overflow: TextOverflow.ellipsis, - style: - Theme.of(context).textTheme.bodySmall, + style: theme.textTheme.bodySmall, ), ], ); @@ -283,7 +277,7 @@ class UserBottomSheetView extends StatelessWidget { .participantAction(UserBottomSheetAction.mention), ), if (user != null) ...[ - Divider(color: Theme.of(context).dividerColor), + Divider(color: theme.dividerColor), ListTile( title: Text( '${L10n.of(context)!.userRole} (${user.powerLevel})', @@ -292,7 +286,7 @@ class UserBottomSheetView extends StatelessWidget { trailing: Material( borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), - color: Theme.of(context).colorScheme.onInverseSurface, + color: theme.colorScheme.onInverseSurface, child: DropdownButton( onChanged: user.canChangeUserPowerLevel || // Workaround until https://github.com/famedly/matrix-dart-sdk/pull/1765 @@ -329,11 +323,11 @@ class UserBottomSheetView extends StatelessWidget { ), ), ], - Divider(color: Theme.of(context).dividerColor), + Divider(color: theme.dividerColor), if (user != null && user.canKick) ListTile( - textColor: Theme.of(context).colorScheme.error, - iconColor: Theme.of(context).colorScheme.error, + textColor: theme.colorScheme.error, + iconColor: theme.colorScheme.error, title: Text(L10n.of(context)!.kickFromChat), leading: const Icon(Icons.exit_to_app_outlined), onTap: () => controller @@ -343,8 +337,8 @@ class UserBottomSheetView extends StatelessWidget { user.canBan && user.membership != Membership.ban) ListTile( - textColor: Theme.of(context).colorScheme.onErrorContainer, - iconColor: Theme.of(context).colorScheme.onErrorContainer, + textColor: theme.colorScheme.onErrorContainer, + iconColor: theme.colorScheme.onErrorContainer, title: Text(L10n.of(context)!.banFromChat), leading: const Icon(Icons.warning_sharp), onTap: () => @@ -361,8 +355,8 @@ class UserBottomSheetView extends StatelessWidget { ), if (user != null && user.id != client.userID) ListTile( - textColor: Theme.of(context).colorScheme.onErrorContainer, - iconColor: Theme.of(context).colorScheme.onErrorContainer, + textColor: theme.colorScheme.onErrorContainer, + iconColor: theme.colorScheme.onErrorContainer, title: Text(L10n.of(context)!.reportUser), leading: const Icon(Icons.gavel_outlined), onTap: () => controller @@ -382,8 +376,8 @@ class UserBottomSheetView extends StatelessWidget { if (userId != client.userID && !client.ignoredUsers.contains(userId)) ListTile( - textColor: Theme.of(context).colorScheme.onErrorContainer, - iconColor: Theme.of(context).colorScheme.onErrorContainer, + textColor: theme.colorScheme.onErrorContainer, + iconColor: theme.colorScheme.onErrorContainer, leading: const Icon(Icons.block_outlined), title: Text(L10n.of(context)!.block), onTap: () => controller diff --git a/lib/utils/show_update_snackbar.dart b/lib/utils/show_update_snackbar.dart index 96d08378e4..38334a508e 100644 --- a/lib/utils/show_update_snackbar.dart +++ b/lib/utils/show_update_snackbar.dart @@ -11,6 +11,7 @@ abstract class UpdateNotifier { static const String versionStoreKey = 'last_known_version'; static void showUpdateSnackBar(BuildContext context) async { + final theme = Theme.of(context); final scaffoldMessenger = ScaffoldMessenger.of(context); final currentVersion = await PlatformInfos.getVersion(); final store = await SharedPreferences.getInstance(); @@ -28,7 +29,7 @@ abstract class UpdateNotifier { icon: Icon( Icons.close_outlined, size: 20, - color: Theme.of(context).colorScheme.onPrimary, + color: theme.colorScheme.onPrimary, ), onPressed: () => controller?.close(), ), diff --git a/lib/widgets/avatar.dart b/lib/widgets/avatar.dart index c8096ed85b..c6bcaf9f13 100644 --- a/lib/widgets/avatar.dart +++ b/lib/widgets/avatar.dart @@ -35,6 +35,8 @@ class Avatar extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + var fallbackLetters = '@'; final name = this.name; if (name != null) { @@ -68,7 +70,7 @@ class Avatar extends StatelessWidget { width: size, height: size, child: Material( - color: Theme.of(context).brightness == Brightness.light + color: theme.brightness == Brightness.light ? Colors.white : Colors.black, shape: RoundedRectangleBorder( @@ -89,7 +91,7 @@ class Avatar extends StatelessWidget { placeholder: (_) => Center( child: Icon( Icons.person_2, - color: Theme.of(context).colorScheme.tertiary, + color: theme.colorScheme.tertiary, size: size / 1.5, ), ), @@ -118,8 +120,7 @@ class Avatar extends StatelessWidget { width: 16, height: 16, decoration: BoxDecoration( - color: presenceBackgroundColor ?? - Theme.of(context).colorScheme.surface, + color: presenceBackgroundColor ?? theme.colorScheme.surface, borderRadius: BorderRadius.circular(32), ), alignment: Alignment.center, @@ -131,7 +132,7 @@ class Avatar extends StatelessWidget { borderRadius: BorderRadius.circular(16), border: Border.all( width: 1, - color: Theme.of(context).colorScheme.surface, + color: theme.colorScheme.surface, ), ), ), diff --git a/lib/widgets/connection_status_header.dart b/lib/widgets/connection_status_header.dart index 285ebb8bfc..eef2b795d1 100644 --- a/lib/widgets/connection_status_header.dart +++ b/lib/widgets/connection_status_header.dart @@ -35,6 +35,8 @@ class ConnectionStatusHeaderState extends State { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final client = Matrix.of(context).client; final status = client.onSyncStatus.value ?? const SyncStatusUpdate(SyncStatus.waitingForResponse); @@ -65,7 +67,7 @@ class ConnectionStatusHeaderState extends State { status.toLocalizedString(context), maxLines: 1, overflow: TextOverflow.ellipsis, - style: TextStyle(color: Theme.of(context).colorScheme.onSurface), + style: TextStyle(color: theme.colorScheme.onSurface), ), ], ), diff --git a/lib/widgets/layouts/login_scaffold.dart b/lib/widgets/layouts/login_scaffold.dart index b253ec4fa8..7337e8ebd0 100644 --- a/lib/widgets/layouts/login_scaffold.dart +++ b/lib/widgets/layouts/login_scaffold.dart @@ -23,6 +23,8 @@ class LoginScaffold extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final isMobileMode = enforceMobileMode || !FluffyThemes.isColumnMode(context); final scaffold = Scaffold( @@ -40,13 +42,12 @@ class LoginScaffold extends StatelessWidget { backgroundColor: isMobileMode ? null : Colors.transparent, ), body: body, - backgroundColor: isMobileMode - ? null - : Theme.of(context).colorScheme.surface.withOpacity(0.8), + backgroundColor: + isMobileMode ? null : theme.colorScheme.surface.withOpacity(0.8), bottomNavigationBar: isMobileMode ? Material( elevation: 4, - shadowColor: Theme.of(context).colorScheme.onSurface, + shadowColor: theme.colorScheme.onSurface, child: const _PrivacyButtons( mainAxisAlignment: MainAxisAlignment.center, ), @@ -72,9 +73,8 @@ class LoginScaffold extends StatelessWidget { color: Colors.transparent, borderRadius: BorderRadius.circular(AppConfig.borderRadius), clipBehavior: Clip.hardEdge, - elevation: - Theme.of(context).appBarTheme.scrolledUnderElevation ?? 4, - shadowColor: Theme.of(context).appBarTheme.shadowColor, + elevation: theme.appBarTheme.scrolledUnderElevation ?? 4, + shadowColor: theme.appBarTheme.shadowColor, child: ConstrainedBox( constraints: isMobileMode ? const BoxConstraints() diff --git a/lib/widgets/layouts/max_width_body.dart b/lib/widgets/layouts/max_width_body.dart index 97c3402318..4e44cc796d 100644 --- a/lib/widgets/layouts/max_width_body.dart +++ b/lib/widgets/layouts/max_width_body.dart @@ -21,6 +21,8 @@ class MaxWidthBody extends StatelessWidget { return SafeArea( child: LayoutBuilder( builder: (context, constraints) { + final theme = Theme.of(context); + const desiredWidth = FluffyThemes.columnWidth * 1.5; final body = constraints.maxWidth <= desiredWidth ? child @@ -32,14 +34,11 @@ class MaxWidthBody extends StatelessWidget { maxWidth: FluffyThemes.columnWidth * 1.5, ), child: Material( - elevation: Theme.of(context) - .appBarTheme - .scrolledUnderElevation ?? - 4, + elevation: theme.appBarTheme.scrolledUnderElevation ?? 4, clipBehavior: Clip.hardEdge, borderRadius: BorderRadius.circular(AppConfig.borderRadius), - shadowColor: Theme.of(context).appBarTheme.shadowColor, + shadowColor: theme.appBarTheme.shadowColor, child: child, ), ), diff --git a/lib/widgets/layouts/two_column_layout.dart b/lib/widgets/layouts/two_column_layout.dart index cf1083a3f6..36e815ebb8 100644 --- a/lib/widgets/layouts/two_column_layout.dart +++ b/lib/widgets/layouts/two_column_layout.dart @@ -15,6 +15,8 @@ class TwoColumnLayout extends StatelessWidget { }); @override Widget build(BuildContext context) { + final theme = Theme.of(context); + return ScaffoldMessenger( child: Scaffold( body: Row( @@ -28,7 +30,7 @@ class TwoColumnLayout extends StatelessWidget { ), Container( width: 1.0, - color: Theme.of(context).dividerColor, + color: theme.dividerColor, ), Expanded( child: ClipRRect( diff --git a/lib/widgets/public_room_bottom_sheet.dart b/lib/widgets/public_room_bottom_sheet.dart index 3105cc576c..991739ac6d 100644 --- a/lib/widgets/public_room_bottom_sheet.dart +++ b/lib/widgets/public_room_bottom_sheet.dart @@ -113,6 +113,8 @@ class PublicRoomBottomSheet extends StatelessWidget { body: FutureBuilder( future: _search(), builder: (context, snapshot) { + final theme = Theme.of(context); + final profile = snapshot.data; return ListView( padding: EdgeInsets.zero, @@ -150,8 +152,7 @@ class PublicRoomBottomSheet extends StatelessWidget { size: 14, ), style: TextButton.styleFrom( - foregroundColor: - Theme.of(context).colorScheme.onSurface, + foregroundColor: theme.colorScheme.onSurface, ), label: Text( roomLink ?? '...', @@ -166,8 +167,7 @@ class PublicRoomBottomSheet extends StatelessWidget { size: 14, ), style: TextButton.styleFrom( - foregroundColor: - Theme.of(context).colorScheme.onSurface, + foregroundColor: theme.colorScheme.onSurface, ), label: Text( L10n.of(context)!.countParticipants( @@ -211,7 +211,7 @@ class PublicRoomBottomSheet extends StatelessWidget { ), style: TextStyle( fontSize: 14, - color: Theme.of(context).textTheme.bodyMedium!.color, + color: theme.textTheme.bodyMedium!.color, ), options: const LinkifyOptions(humanize: false), onOpen: (url) => diff --git a/lib/widgets/unread_rooms_badge.dart b/lib/widgets/unread_rooms_badge.dart index 5270c0db35..4951734961 100644 --- a/lib/widgets/unread_rooms_badge.dart +++ b/lib/widgets/unread_rooms_badge.dart @@ -19,6 +19,8 @@ class UnreadRoomsBadge extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final unreadCount = Matrix.of(context) .client .rooms @@ -27,17 +29,17 @@ class UnreadRoomsBadge extends StatelessWidget { .length; return b.Badge( badgeStyle: b.BadgeStyle( - badgeColor: Theme.of(context).colorScheme.primary, + badgeColor: theme.colorScheme.primary, elevation: 4, borderSide: BorderSide( - color: Theme.of(context).colorScheme.surface, + color: theme.colorScheme.surface, width: 2, ), ), badgeContent: Text( unreadCount.toString(), style: TextStyle( - color: Theme.of(context).colorScheme.onPrimary, + color: theme.colorScheme.onPrimary, fontSize: 12, ), ), From 4278f7b196ffe54d03de5cc84dc76603d3ff18c0 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 6 Aug 2024 13:40:53 -0400 Subject: [PATCH 164/288] get analytics events directly from server, cache last update time for user's l2 --- lib/pages/chat/chat.dart | 1 + .../controllers/get_analytics_controller.dart | 85 +++--- .../message_analytics_controller.dart | 8 - .../controllers/my_analytics_controller.dart | 242 +++++++----------- lib/pangea/controllers/user_controller.dart | 7 +- .../client_analytics_extension.dart | 12 - .../client_extension/client_extension.dart | 8 - .../general_info_extension.dart | 10 - .../client_extension/space_extension.dart | 12 - .../events_extension.dart | 99 +++---- .../pangea_room_extension.dart | 10 + .../room_analytics_extension.dart | 26 +- .../practice_activity_record_event.dart | 67 ----- .../practice_activity_record_model.dart | 57 ++++- lib/pangea/utils/logout.dart | 14 +- .../learning_progress_indicators.dart | 18 +- .../analytics_summary/progress_indicator.dart | 6 +- .../practice_activity_card.dart | 15 ++ .../user_settings/p_language_dialog.dart | 23 +- 19 files changed, 327 insertions(+), 393 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index bf142b1852..7c97c5e088 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -639,6 +639,7 @@ class ChatController extends State pangeaController.myAnalytics.setState( data: { 'eventID': msgEventId, + 'eventType': EventTypes.Message, 'roomID': room.id, 'originalSent': originalSent, 'tokensSent': tokensSent, diff --git a/lib/pangea/controllers/get_analytics_controller.dart b/lib/pangea/controllers/get_analytics_controller.dart index ac4efb668b..431cf69ee5 100644 --- a/lib/pangea/controllers/get_analytics_controller.dart +++ b/lib/pangea/controllers/get_analytics_controller.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:fluffychat/pangea/constants/class_default_values.dart'; import 'package:fluffychat/pangea/constants/local.key.dart'; import 'package:fluffychat/pangea/constants/match_rule_ids.dart'; @@ -25,16 +27,13 @@ class GetAnalyticsController { Client get client => _pangeaController.matrixState.client; - // A local cache of eventIds and construct uses for messages sent since the last update + /// A local cache of eventIds and construct uses for messages sent since the last update Map> get messagesSinceUpdate { try { final dynamic locallySaved = _pangeaController.pStoreService.read( PLocalKey.messagesSinceUpdate, ); - if (locallySaved == null) { - _pangeaController.myAnalytics.setMessagesSinceUpdate({}); - return {}; - } + if (locallySaved == null) return {}; try { // try to get the local cache of messages and format them as OneConstructUses final Map> cache = @@ -47,7 +46,7 @@ class GetAnalyticsController { return formattedCache; } catch (err) { // if something goes wrong while trying to format the local data, clear it - _pangeaController.myAnalytics.setMessagesSinceUpdate({}); + _pangeaController.myAnalytics.clearMessagesSinceUpdate(); return {}; } } catch (exception, stackTrace) { @@ -70,13 +69,24 @@ class GetAnalyticsController { debugPrint("getting constructs"); await client.roomsLoading; - // first, try to get a cached list of all uses, if it exists and is valid - final DateTime? lastUpdated = await myAnalyticsLastUpdated(); + // don't try to get constructs until last updated time has been loaded + await _pangeaController.myAnalytics.lastUpdatedCompleter.future; + + // if forcing a refreshing, clear the cache + if (forceUpdate) clearCache(); + + // get the last time the user updated their analytics for their current l2 + // then try to get local cache of construct uses. lastUpdate time is used to + // determine if cached data is still valid. + final DateTime? lastUpdated = _pangeaController.myAnalytics.lastUpdated ?? + await myAnalyticsLastUpdated(); + final List? local = getConstructsLocal( constructType: constructType, lastUpdated: lastUpdated, ); - if (local != null && !forceUpdate) { + + if (local != null) { debugPrint("returning local constructs"); return local; } @@ -111,7 +121,12 @@ class GetAnalyticsController { /// Get the last time the user updated their analytics for their current l2 Future myAnalyticsLastUpdated() async { - if (l2Code == null) return null; + // this function gets called soon after login, so first + // make sure that the user's l2 is loaded, if the user has set their l2 + if (client.userID != null && l2Code == null) { + await _pangeaController.matrixState.client.waitForAccountData(); + if (l2Code == null) return null; + } final Room? analyticsRoom = client.analyticsRoomLocal(l2Code!); if (analyticsRoom == null) return null; final DateTime? lastUpdated = await analyticsRoom.analyticsLastUpdated( @@ -120,6 +135,29 @@ class GetAnalyticsController { return lastUpdated; } + /// Get all the construct analytics events for the logged in user + Future> allMyConstructs() async { + if (l2Code == null) return []; + final Room? analyticsRoom = client.analyticsRoomLocal(l2Code!); + if (analyticsRoom == null) return []; + return await analyticsRoom.getAnalyticsEvents(userId: client.userID!) ?? []; + } + + /// Filter out constructs that are not relevant to the user, specifically those from + /// rooms in which the user is a teacher and those that are interative translation span constructs + Future> filterConstructs({ + required List unfilteredConstructs, + }) async { + return unfilteredConstructs + .where( + (use) => + use.lemma != "Try interactive translation" && + use.lemma != "itStart" || + use.lemma != MatchRuleIds.interactiveTranslation, + ) + .toList(); + } + /// Get the cached construct uses for the current user, if it exists List? getConstructsLocal({ DateTime? lastUpdated, @@ -140,28 +178,6 @@ class GetAnalyticsController { return null; } - /// Get all the construct analytics events for the logged in user - Future> allMyConstructs() async { - if (l2Code == null) return []; - final Room? analyticsRoom = client.analyticsRoomLocal(l2Code!); - if (analyticsRoom == null) return []; - return await analyticsRoom.getAnalyticsEvents(userId: client.userID!) ?? []; - } - - /// Filter out constructs that are not relevant to the user, specifically those from - /// rooms in which the user is a teacher and those that are interative translation span constructs - Future> filterConstructs({ - required List unfilteredConstructs, - }) async { - final List adminSpaceRooms = await client.teacherRoomIds; - return unfilteredConstructs.where((use) { - if (adminSpaceRooms.contains(use.chatId)) return false; - return use.lemma != "Try interactive translation" && - use.lemma != "itStart" || - use.lemma != MatchRuleIds.interactiveTranslation; - }).toList(); - } - /// Cache the construct uses for the current user void cacheConstructs({ required List uses, @@ -175,6 +191,11 @@ class GetAnalyticsController { ); _cache.add(entry); } + + /// Clear all cached analytics data. + void clearCache() { + _cache.clear(); + } } class AnalyticsCacheEntry { diff --git a/lib/pangea/controllers/message_analytics_controller.dart b/lib/pangea/controllers/message_analytics_controller.dart index 1883e96dc6..29cbef9eab 100644 --- a/lib/pangea/controllers/message_analytics_controller.dart +++ b/lib/pangea/controllers/message_analytics_controller.dart @@ -156,14 +156,6 @@ class AnalyticsController extends BaseController { ?.cast(); final List allConstructs = roomEvents ?? []; - final List adminSpaceRooms = - await _pangeaController.matrixState.client.teacherRoomIds; - for (final construct in allConstructs) { - construct.content.uses.removeWhere( - (use) => adminSpaceRooms.contains(use.chatId), - ); - } - return allConstructs .where((construct) => construct.content.uses.isNotEmpty) .toList(); diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart index 4389f297f1..11840836bd 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/my_analytics_controller.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:developer'; import 'package:fluffychat/pangea/constants/local.key.dart'; import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; @@ -7,10 +6,10 @@ import 'package:fluffychat/pangea/controllers/base_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; -import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; -import 'package:fluffychat/pangea/matrix_event_wrappers/practice_activity_record_event.dart'; +import 'package:fluffychat/pangea/matrix_event_wrappers/practice_activity_event.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; import 'package:fluffychat/pangea/models/choreo_record.dart'; +import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_record_model.dart'; import 'package:fluffychat/pangea/models/representation_content_model.dart'; import 'package:fluffychat/pangea/models/tokens_event_content_model.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; @@ -29,9 +28,16 @@ class MyAnalyticsController extends BaseController { String? get userL2 => _pangeaController.languageController.activeL2Code(); + /// the last time that matrix analytics events were updated for the user's current l2 + DateTime? lastUpdated; + + /// Last updated completer. Used to wait for the last + /// updated time to be set before setting analytics data. + Completer lastUpdatedCompleter = Completer(); + /// the max number of messages that will be cached before /// an automatic update is triggered - final int _maxMessagesCached = 10; + final int _maxMessagesCached = 1; /// the number of minutes before an automatic update is triggered final int _minutesBeforeUpdate = 5; @@ -44,8 +50,9 @@ class MyAnalyticsController extends BaseController { // Wait for the next sync in the stream to ensure that the pangea controller // is fully initialized. It will throw an error if it is not. - _pangeaController.matrixState.client.onSync.stream.first - .then((_) => _refreshAnalyticsIfOutdated()); + _pangeaController.matrixState.client.onSync.stream.first.then((_) { + _refreshAnalyticsIfOutdated(); + }); // Listen to a stream that provides the eventIDs // of new messages sent by the logged in user @@ -55,23 +62,31 @@ class MyAnalyticsController extends BaseController { } /// If analytics haven't been updated in the last day, update them - Future _refreshAnalyticsIfOutdated() async { - /// wait for the initial sync to finish, so the - /// timeline data from analytics rooms is accurate - if (_client.prevBatch == null) { - await _client.onSync.stream.first; + Future _refreshAnalyticsIfOutdated() async { + // don't set anything is the user is not logged in + if (_pangeaController.matrixState.client.userID == null) return; + try { + // if lastUpdated hasn't been set yet, set it + lastUpdated ??= + await _pangeaController.analytics.myAnalyticsLastUpdated(); + } catch (err, s) { + ErrorHandler.logError( + s: s, + e: err, + m: "Failed to get last updated time for analytics", + ); + } finally { + // if this is the initial load, complete the lastUpdatedCompleter + if (!lastUpdatedCompleter.isCompleted) { + lastUpdatedCompleter.complete(lastUpdated); + } } - DateTime? lastUpdated = - await _pangeaController.analytics.myAnalyticsLastUpdated(); final DateTime yesterday = DateTime.now().subtract(_timeSinceUpdate); - if (lastUpdated?.isBefore(yesterday) ?? true) { debugPrint("analytics out-of-date, updating"); await updateAnalytics(); - lastUpdated = await _pangeaController.analytics.myAnalyticsLastUpdated(); } - return lastUpdated; } /// Given the data from a newly sent message, format and cache @@ -88,9 +103,12 @@ class MyAnalyticsController extends BaseController { // extract the relevant data about this message final String? eventID = data['eventID']; final String? roomID = data['roomID']; + final String? eventType = data['eventType']; final PangeaRepresentation? originalSent = data['originalSent']; final PangeaMessageTokens? tokensSent = data['tokensSent']; final ChoreoRecord? choreo = data['choreo']; + final PracticeActivityEvent? practiceActivity = data['practiceActivity']; + final PracticeActivityRecordModel? recordModel = data['recordModel']; if (roomID == null || eventID == null) return; @@ -101,24 +119,38 @@ class MyAnalyticsController extends BaseController { timeStamp: DateTime.now(), ); - final grammarConstructs = choreo?.grammarConstructUses(metadata: metadata); - final itConstructs = choreo?.itStepsToConstructUses(metadata: metadata); - final vocabUses = tokensSent != null - ? originalSent?.vocabUses( - choreo: choreo, - tokens: tokensSent.tokens, - metadata: metadata, - ) - : null; - final List constructs = [ - ...(grammarConstructs ?? []), - ...(itConstructs ?? []), - ...(vocabUses ?? []), - ]; - addMessageSinceUpdate( - eventID, - constructs, - ); + final List constructs = []; + + if (eventType == EventTypes.Message) { + final grammarConstructs = + choreo?.grammarConstructUses(metadata: metadata); + final itConstructs = choreo?.itStepsToConstructUses(metadata: metadata); + final vocabUses = tokensSent != null + ? originalSent?.vocabUses( + choreo: choreo, + tokens: tokensSent.tokens, + metadata: metadata, + ) + : null; + constructs.addAll([ + ...(grammarConstructs ?? []), + ...(itConstructs ?? []), + ...(vocabUses ?? []), + ]); + } + + if (eventType == PangeaEventTypes.activityRecord && + practiceActivity != null) { + final activityConstructs = recordModel?.uses( + practiceActivity, + metadata: metadata, + ); + constructs.addAll(activityConstructs ?? []); + } + + _pangeaController.analytics + .filterConstructs(unfilteredConstructs: constructs) + .then((filtered) => addMessageSinceUpdate(eventID, filtered)); } /// Add a list of construct uses for a new message to the local @@ -129,10 +161,9 @@ class MyAnalyticsController extends BaseController { ) { try { final currentCache = _pangeaController.analytics.messagesSinceUpdate; - if (!currentCache.containsKey(eventID)) { - currentCache[eventID] = constructs; - setMessagesSinceUpdate(currentCache); - } + constructs.addAll(currentCache[eventID] ?? []); + currentCache[eventID] = constructs; + setMessagesSinceUpdate(currentCache); // if the cached has reached if max-length, update analytics if (_pangeaController.analytics.messagesSinceUpdate.length > @@ -151,7 +182,7 @@ class MyAnalyticsController extends BaseController { /// Clears the local cache of recently sent constructs. Called before updating analytics void clearMessagesSinceUpdate() { - setMessagesSinceUpdate({}); + _pangeaController.pStoreService.delete(PLocalKey.messagesSinceUpdate); } /// Save the local cache of recently sent constructs to the local storage @@ -168,8 +199,18 @@ class MyAnalyticsController extends BaseController { analyticsUpdateStream.add(null); } + /// Prevent concurrent updates to analytics Completer? _updateCompleter; + + /// Updates learning analytics. + /// + /// This method is responsible for updating the analytics. It first checks if an update is already in progress + /// by checking the completion status of the [_updateCompleter]. If an update is already in progress, it waits + /// for the completion of the previous update and returns. Otherwise, it creates a new [_updateCompleter] and + /// proceeds with the update process. If the update is successful, it clears any messages that were received + /// since the last update and notifies the [analyticsUpdateStream]. Future updateAnalytics() async { + if (_pangeaController.matrixState.client.userID == null) return; if (!(_updateCompleter?.isCompleted ?? true)) { await _updateCompleter!.future; return; @@ -178,6 +219,7 @@ class MyAnalyticsController extends BaseController { try { await _updateAnalytics(); clearMessagesSinceUpdate(); + lastUpdated = DateTime.now(); analyticsUpdateStream.add(null); } catch (err, s) { ErrorHandler.logError( @@ -191,119 +233,31 @@ class MyAnalyticsController extends BaseController { } } - /// top level analytics sending function. Gather recent messages and activity records, - /// convert them into the correct formats, and send them to the analytics room + /// Updates the analytics by sending cached analytics data to the analytics room. + /// The analytics room is determined based on the user's current target language. Future _updateAnalytics() async { + // if there's no cached construct data, there's nothing to send + if (_pangeaController.analytics.messagesSinceUpdate.isEmpty) return; + // if missing important info, don't send analytics. Could happen if user just signed up. if (userL2 == null || _client.userID == null) return; // analytics room for the user and current target language final Room? analyticsRoom = await _client.getMyAnalyticsRoom(userL2!); - // get the last time analytics were updated for this room - final DateTime? l2AnalyticsLastUpdated = - await analyticsRoom?.analyticsLastUpdated( - _client.userID!, + // and send cached analytics data to the room + await analyticsRoom?.sendConstructsEvent( + _pangeaController.analytics.messagesSinceUpdate.values + .expand((e) => e) + .toList(), ); + } - // all chats in which user is a student - final List chats = _client.rooms - .where((room) => !room.isSpace && !room.isAnalyticsRoom) - .toList(); - - // get the recent message events and activity records for each chat - final List>> recentMsgFutures = []; - final List>> recentActivityFutures = []; - for (final Room chat in chats) { - recentMsgFutures.add( - chat.getEventsBySender( - type: EventTypes.Message, - sender: _client.userID!, - since: l2AnalyticsLastUpdated, - ), - ); - recentActivityFutures.add( - chat.getEventsBySender( - type: PangeaEventTypes.activityRecord, - sender: _client.userID!, - since: l2AnalyticsLastUpdated, - ), - ); - } - final List> recentMsgs = - (await Future.wait(recentMsgFutures)).toList(); - final List recentActivityRecords = - (await Future.wait(recentActivityFutures)) - .expand((e) => e) - .map((event) => PracticeActivityRecordEvent(event: event)) - .toList(); - - // get the timelines for each chat - final List> timelineFutures = []; - for (final chat in chats) { - timelineFutures.add(chat.getTimeline()); - } - final List timelines = await Future.wait(timelineFutures); - final Map timelineMap = - Map.fromIterables(chats.map((e) => e.id), timelines); - - //convert into PangeaMessageEvents - final List> recentPangeaMessageEvents = []; - for (final (index, eventList) in recentMsgs.indexed) { - recentPangeaMessageEvents.add( - eventList - .map( - (event) => PangeaMessageEvent( - event: event, - timeline: timelines[index], - ownMessage: true, - ), - ) - .toList(), - ); - } - - final List allRecentMessages = - recentPangeaMessageEvents.expand((e) => e).toList(); - - // get constructs for messages - final List recentConstructUses = []; - for (final PangeaMessageEvent message in allRecentMessages) { - recentConstructUses.addAll(message.allConstructUses); - } - - // get constructs for practice activities - final List>> constructFutures = []; - for (final PracticeActivityRecordEvent activity in recentActivityRecords) { - final Timeline? timeline = timelineMap[activity.event.roomId!]; - if (timeline == null) { - debugger(when: kDebugMode); - ErrorHandler.logError( - m: "PracticeActivityRecordEvent has null timeline", - data: activity.event.toJson(), - ); - continue; - } - constructFutures.add(activity.uses(timeline)); - } - final List> constructLists = - await Future.wait(constructFutures); - - recentConstructUses.addAll(constructLists.expand((e) => e)); - - //TODO - confirm that this is the correct construct content - // debugger( - // when: kDebugMode, - // ); - // ; debugger( - // when: kDebugMode && - // (allRecentMessages.isNotEmpty || recentActivityRecords.isNotEmpty), - // ); - - if (recentConstructUses.isNotEmpty || l2AnalyticsLastUpdated == null) { - await analyticsRoom?.sendConstructsEvent( - recentConstructUses, - ); - } + /// Reset analytics last updated time to null. + void clearCache() { + _updateTimer?.cancel(); + lastUpdated = null; + lastUpdatedCompleter = Completer(); + _refreshAnalyticsIfOutdated(); } } diff --git a/lib/pangea/controllers/user_controller.dart b/lib/pangea/controllers/user_controller.dart index bf8ce3c112..38645cfcd7 100644 --- a/lib/pangea/controllers/user_controller.dart +++ b/lib/pangea/controllers/user_controller.dart @@ -71,9 +71,12 @@ class UserController extends BaseController { } /// Updates the user's profile with the given [update] function and saves it. - void updateProfile(Profile Function(Profile) update) { + Future updateProfile( + Profile Function(Profile) update, { + waitForDataInSync = false, + }) async { final Profile updatedProfile = update(profile); - updatedProfile.saveProfileData(); + await updatedProfile.saveProfileData(waitForDataInSync: waitForDataInSync); } /// Creates a new profile for the user with the given date of birth. diff --git a/lib/pangea/extensions/client_extension/client_analytics_extension.dart b/lib/pangea/extensions/client_extension/client_analytics_extension.dart index 82f1b621b8..64691f98b6 100644 --- a/lib/pangea/extensions/client_extension/client_analytics_extension.dart +++ b/lib/pangea/extensions/client_extension/client_analytics_extension.dart @@ -153,16 +153,4 @@ extension AnalyticsClientExtension on Client { _joinAnalyticsRoomsInAllSpaces(); }); } - - Future> _allAnalyticsRoomsLastUpdated() async { - // get the last updated time for each analytics room - final Map lastUpdatedMap = {}; - for (final analyticsRoom in allMyAnalyticsRooms) { - final DateTime? lastUpdated = await analyticsRoom.analyticsLastUpdated( - userID!, - ); - lastUpdatedMap[analyticsRoom.id] = lastUpdated; - } - return lastUpdatedMap; - } } diff --git a/lib/pangea/extensions/client_extension/client_extension.dart b/lib/pangea/extensions/client_extension/client_extension.dart index 7af50d5014..0971ab04dd 100644 --- a/lib/pangea/extensions/client_extension/client_extension.dart +++ b/lib/pangea/extensions/client_extension/client_extension.dart @@ -2,7 +2,6 @@ import 'dart:developer'; import 'package:collection/collection.dart'; import 'package:fluffychat/pangea/constants/model_keys.dart'; -import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/constants/pangea_room_types.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; import 'package:fluffychat/pangea/models/space_model.dart'; @@ -39,15 +38,10 @@ extension PangeaClient on Client { /// and set up those rooms to be joined by other users. void migrateAnalyticsRooms() => _migrateAnalyticsRooms(); - Future> allAnalyticsRoomsLastUpdated() async => - await _allAnalyticsRoomsLastUpdated(); - // spaces List get spacesImTeaching => _spacesImTeaching; - Future> get chatsImAStudentIn async => await _chatsImAStudentIn; - List get spacesImAStudentIn => _spacesImStudyingIn; List get spacesImIn => _spacesImIn; @@ -56,8 +50,6 @@ extension PangeaClient on Client { // general_info - Future> get teacherRoomIds async => await _teacherRoomIds; - Future> get myTeachers async => await _myTeachers; Future getReportsDM(User teacher, Room space) async => diff --git a/lib/pangea/extensions/client_extension/general_info_extension.dart b/lib/pangea/extensions/client_extension/general_info_extension.dart index ca5df40cc8..a32c43d617 100644 --- a/lib/pangea/extensions/client_extension/general_info_extension.dart +++ b/lib/pangea/extensions/client_extension/general_info_extension.dart @@ -1,16 +1,6 @@ part of "client_extension.dart"; extension GeneralInfoClientExtension on Client { - Future> get _teacherRoomIds async { - final List adminRoomIds = []; - for (final Room adminSpace in (await _spacesImTeaching)) { - adminRoomIds.add(adminSpace.id); - final List adminSpaceRooms = adminSpace.allSpaceChildRoomIds; - adminRoomIds.addAll(adminSpaceRooms); - } - return adminRoomIds; - } - Future> get _myTeachers async { final List teachers = []; for (final classRoom in spacesImIn) { diff --git a/lib/pangea/extensions/client_extension/space_extension.dart b/lib/pangea/extensions/client_extension/space_extension.dart index 89a09a7cd3..8d9182d85f 100644 --- a/lib/pangea/extensions/client_extension/space_extension.dart +++ b/lib/pangea/extensions/client_extension/space_extension.dart @@ -4,18 +4,6 @@ extension SpaceClientExtension on Client { List get _spacesImTeaching => rooms.where((e) => e.isSpace && e.isRoomAdmin).toList(); - Future> get _chatsImAStudentIn async { - final List nowteacherRoomIds = await teacherRoomIds; - return rooms - .where( - (r) => - !r.isSpace && - !r.isAnalyticsRoom && - !nowteacherRoomIds.contains(r.id), - ) - .toList(); - } - List get _spacesImStudyingIn => rooms.where((e) => e.isSpace && !e.isRoomAdmin).toList(); diff --git a/lib/pangea/extensions/pangea_room_extension/events_extension.dart b/lib/pangea/extensions/pangea_room_extension/events_extension.dart index 3fe14750d6..d8e2545ffd 100644 --- a/lib/pangea/extensions/pangea_room_extension/events_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/events_extension.dart @@ -346,66 +346,51 @@ extension EventsRoomExtension on Room { // } // } - // fetch event of a certain type by a certain sender - // since a certain time or up to a certain amount - Future> getEventsBySender({ - required String type, - required String sender, - DateTime? since, + /// Get a list of events in the room that are of type [PangeaEventTypes.construct] + /// and have the sender as [userID]. If [count] is provided, the function will + /// return at most [count] events. + Future> getRoomAnalyticsEvents({ + String? userID, int? count, }) async { - try { - int numberOfSearches = 0; - final Timeline timeline = await getTimeline(); - - List relevantEvents() => timeline.events - .where((event) => event.senderId == sender && event.type == type) - .toList(); - - bool reachedEnd() { - if (since != null) { - return relevantEvents().any( - (event) => event.originServerTs.isBefore(since), - ); - } - if (count != null) { - return relevantEvents().length >= count; - } - return false; - } - - while (timeline.canRequestHistory && numberOfSearches < 10) { - await timeline.requestHistory(historyCount: 100); - numberOfSearches += 1; - if (!timeline.canRequestHistory) break; - if (reachedEnd()) break; - } - - final List fetchedEvents = timeline.events - .where((event) => event.senderId == sender && event.type == type) - .toList(); - - if (since != null) { - fetchedEvents.removeWhere( - (event) => event.originServerTs.isBefore(since), - ); - } - - final List events = []; - for (Event event in fetchedEvents) { - if (event.relationshipType == RelationshipTypes.edit) continue; - if (event.hasAggregatedEvents(timeline, RelationshipTypes.edit)) { - event = event.getDisplayEvent(timeline); - } - events.add(event); - } + userID ??= client.userID; + if (userID == null) return []; + GetRoomEventsResponse resp = await client.getRoomEvents( + id, + Direction.b, + limit: count ?? 100, + filter: jsonEncode( + StateFilter( + types: [ + PangeaEventTypes.construct, + ], + senders: [userID], + ), + ), + ); - return events; - } catch (err, s) { - if (kDebugMode) rethrow; - debugger(when: kDebugMode); - ErrorHandler.logError(e: err, s: s); - return []; + int numSearches = 0; + while (numSearches < 10 && resp.end != null) { + if (count != null && resp.chunk.length <= count) break; + final nextResp = await client.getRoomEvents( + id, + Direction.b, + limit: count ?? 100, + filter: jsonEncode( + StateFilter( + types: [ + PangeaEventTypes.construct, + ], + senders: [userID], + ), + ), + from: resp.end, + ); + nextResp.chunk.addAll(resp.chunk); + resp = nextResp; + numSearches += 1; } + + return resp.chunk.map((e) => Event.fromMatrixEvent(e, this)).toList(); } } diff --git a/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart b/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart index 8bfb09c8a4..bf9e10953d 100644 --- a/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart @@ -90,6 +90,16 @@ extension PangeaRoom on Room { bool isMadeForLang(String langCode) => _isMadeForLang(langCode); + /// Sends construct events to the server. + /// + /// The [uses] parameter is a list of [OneConstructUse] objects representing the + /// constructs to be sent. To prevent hitting the maximum event size, the events + /// are chunked into smaller lists. Each chunk is sent as a separate event. + Future sendConstructsEvent( + List uses, + ) async => + await _sendConstructsEvent(uses); + // children_and_parents List get joinedChildren => _joinedChildren; diff --git a/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart b/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart index c09e01a65e..73371b080d 100644 --- a/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart @@ -140,33 +140,17 @@ extension AnalyticsRoomExtension on Room { ); } - Future _getLastAnalyticsEvent( - String userId, - ) async { - final List events = await getEventsBySender( - type: PangeaEventTypes.construct, - sender: userId, - count: 10, - ); - if (events.isEmpty) return null; - final Event event = events.first; - return ConstructAnalyticsEvent(event: event); - } - Future _analyticsLastUpdated(String userId) async { - final lastEvent = await _getLastAnalyticsEvent(userId); - return lastEvent?.event.originServerTs; + final List events = await getRoomAnalyticsEvents(count: 1); + if (events.isEmpty) return null; + return events.first.originServerTs; } Future?> _getAnalyticsEvents({ required String userId, DateTime? since, }) async { - final List events = await getEventsBySender( - type: PangeaEventTypes.construct, - sender: userId, - since: since, - ); + final events = await getRoomAnalyticsEvents(); final List analyticsEvents = []; for (final Event event in events) { analyticsEvents.add(ConstructAnalyticsEvent(event: event)); @@ -192,7 +176,7 @@ extension AnalyticsRoomExtension on Room { /// The [uses] parameter is a list of [OneConstructUse] objects representing the /// constructs to be sent. To prevent hitting the maximum event size, the events /// are chunked into smaller lists. Each chunk is sent as a separate event. - Future sendConstructsEvent( + Future _sendConstructsEvent( List uses, ) async { // It's possible that the user has no info to send yet, but to prevent trying diff --git a/lib/pangea/matrix_event_wrappers/practice_activity_record_event.dart b/lib/pangea/matrix_event_wrappers/practice_activity_record_event.dart index d0f11da3c2..8378d9f88e 100644 --- a/lib/pangea/matrix_event_wrappers/practice_activity_record_event.dart +++ b/lib/pangea/matrix_event_wrappers/practice_activity_record_event.dart @@ -1,12 +1,5 @@ -import 'dart:developer'; - import 'package:fluffychat/pangea/extensions/pangea_event_extension.dart'; -import 'package:fluffychat/pangea/matrix_event_wrappers/practice_activity_event.dart'; -import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; -import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_model.dart'; import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_record_model.dart'; -import 'package:fluffychat/pangea/utils/error_handler.dart'; -import 'package:flutter/foundation.dart'; import 'package:matrix/matrix.dart'; import '../constants/pangea_event_types.dart'; @@ -28,64 +21,4 @@ class PracticeActivityRecordEvent { _content ??= event.getPangeaContent(); return _content!; } - - Future> uses(Timeline timeline) async { - try { - final String? parent = event.relationshipEventId; - if (parent == null) { - debugger(when: kDebugMode); - ErrorHandler.logError( - m: "PracticeActivityRecordEvent has null event.relationshipEventId", - data: event.toJson(), - ); - return []; - } - - final Event? practiceEvent = - await timeline.getEventById(event.relationshipEventId!); - - if (practiceEvent == null) { - debugger(when: kDebugMode); - ErrorHandler.logError( - m: "PracticeActivityRecordEvent has null practiceActivityEvent with id $parent", - data: event.toJson(), - ); - return []; - } - - final PracticeActivityEvent practiceActivity = PracticeActivityEvent( - event: practiceEvent, - timeline: timeline, - ); - - final List uses = []; - - final List constructIds = - practiceActivity.practiceActivity.tgtConstructs; - - for (final construct in constructIds) { - uses.add( - OneConstructUse( - lemma: construct.lemma, - constructType: construct.type, - useType: record.useType, - //TODO - find form of construct within the message - //this is related to the feature of highlighting the target construct in the message - form: construct.lemma, - metadata: ConstructUseMetaData( - roomId: event.roomId ?? practiceEvent.roomId ?? timeline.room.id, - eventId: practiceActivity.parentMessageId, - timeStamp: event.originServerTs, - ), - ), - ); - } - - return uses; - } catch (e, s) { - debugger(when: kDebugMode); - ErrorHandler.logError(e: e, s: s, data: event.toJson()); - rethrow; - } - } } diff --git a/lib/pangea/models/practice_activities.dart/practice_activity_record_model.dart b/lib/pangea/models/practice_activities.dart/practice_activity_record_model.dart index 0c4ea52bf6..34f73e7350 100644 --- a/lib/pangea/models/practice_activities.dart/practice_activity_record_model.dart +++ b/lib/pangea/models/practice_activities.dart/practice_activity_record_model.dart @@ -3,9 +3,14 @@ // the user might have selected multiple options before // finding the answer import 'dart:developer'; -import 'dart:typed_data'; import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart'; +import 'package:fluffychat/pangea/matrix_event_wrappers/practice_activity_event.dart'; +import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; +import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_model.dart'; +import 'package:fluffychat/pangea/utils/error_handler.dart'; +import 'package:flutter/foundation.dart'; +import 'package:matrix/matrix.dart'; class PracticeActivityRecordModel { final String? question; @@ -79,6 +84,56 @@ class PracticeActivityRecordModel { } } + /// Returns a list of [OneConstructUse] objects representing the uses of the practice activity. + /// + /// The [practiceActivity] parameter is the parent event, representing the activity itself. + /// The [event] parameter is the record event, if available. + /// The [metadata] parameter is the metadata for the construct use, used if the record event isn't available. + /// + /// If [event] and [metadata] are both null, an empty list is returned. + /// + /// The method iterates over the [tgtConstructs] of the [practiceActivity] and creates a [OneConstructUse] object for each construct. + List uses( + PracticeActivityEvent practiceActivity, { + Event? event, + ConstructUseMetaData? metadata, + }) { + try { + if (event == null && metadata == null) { + debugger(when: kDebugMode); + return []; + } + + final List uses = []; + final List constructIds = + practiceActivity.practiceActivity.tgtConstructs; + + for (final construct in constructIds) { + uses.add( + OneConstructUse( + lemma: construct.lemma, + constructType: construct.type, + useType: useType, + //TODO - find form of construct within the message + //this is related to the feature of highlighting the target construct in the message + form: construct.lemma, + metadata: ConstructUseMetaData( + roomId: event?.roomId ?? metadata!.roomId, + eventId: practiceActivity.parentMessageId, + timeStamp: event?.originServerTs ?? metadata!.timeStamp, + ), + ), + ); + } + + return uses; + } catch (e, s) { + debugger(when: kDebugMode); + ErrorHandler.logError(e: e, s: s, data: event?.toJson()); + rethrow; + } + } + @override bool operator ==(Object other) { if (identical(this, other)) return true; diff --git a/lib/pangea/utils/logout.dart b/lib/pangea/utils/logout.dart index aa4441e138..3d3e780ba6 100644 --- a/lib/pangea/utils/logout.dart +++ b/lib/pangea/utils/logout.dart @@ -1,11 +1,9 @@ -import 'package:flutter/material.dart'; - import 'package:adaptive_dialog/adaptive_dialog.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; -import 'package:fluffychat/widgets/matrix.dart'; - void pLogoutAction(BuildContext context, {bool? isDestructiveAction}) async { if (await showOkCancelAlertDialog( useRootNavigator: false, @@ -20,6 +18,14 @@ void pLogoutAction(BuildContext context, {bool? isDestructiveAction}) async { return; } final matrix = Matrix.of(context); + + // before wiping out locally cached construct data, save it to the server + await MatrixState.pangeaController.myAnalytics.updateAnalytics(); + + // Reset cached analytics data + MatrixState.pangeaController.myAnalytics.clearCache(); + MatrixState.pangeaController.analytics.clearCache(); + await showFutureLoadingDialog( context: context, future: () => matrix.client.logout(), diff --git a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart index d485be8519..3a2accad8d 100644 --- a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart +++ b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart @@ -42,10 +42,14 @@ class LearningProgressIndicatorsState /// Grammar constructs model ConstructListModel? errors; + bool loading = true; + @override void initState() { super.initState(); - updateAnalyticsData(); + updateAnalyticsData().then((_) { + setState(() => loading = false); + }); // listen for changes to analytics data and update the UI _onAnalyticsUpdate = _pangeaController .myAnalytics.analyticsUpdateStream.stream @@ -77,6 +81,7 @@ class LearningProgressIndicatorsState type: ConstructTypeEnum.grammar, uses: localUses, ); + setState(() {}); return; } @@ -93,7 +98,8 @@ class LearningProgressIndicatorsState type: ConstructTypeEnum.grammar, uses: allConstructs, ); - setState(() {}); + + if (mounted) setState(() {}); } /// Get the number of points for a given progress indicator @@ -136,6 +142,10 @@ class LearningProgressIndicatorsState @override Widget build(BuildContext context) { + if (Matrix.of(context).client.userID == null) { + return const SizedBox(); + } + final levelBar = Container( height: 20, width: levelBarWidth, @@ -214,6 +224,7 @@ class LearningProgressIndicatorsState points: getProgressPoints(indicator), onTap: () {}, progressIndicator: indicator, + loading: loading, ), ) .toList(), @@ -222,9 +233,6 @@ class LearningProgressIndicatorsState ), ), Container( - // decoration: BoxDecoration( - // border: Border.all(color: Colors.green), - // ), height: 36, padding: const EdgeInsets.symmetric(horizontal: 32), child: Stack( diff --git a/lib/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart b/lib/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart index 8fe774f1c4..f9ef427e4c 100644 --- a/lib/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart +++ b/lib/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart @@ -7,12 +7,14 @@ class ProgressIndicatorBadge extends StatelessWidget { final int? points; final VoidCallback onTap; final ProgressIndicatorEnum progressIndicator; + final bool loading; const ProgressIndicatorBadge({ super.key, required this.points, required this.onTap, required this.progressIndicator, + required this.loading, }); @override @@ -33,9 +35,9 @@ class ProgressIndicatorBadge extends StatelessWidget { color: progressIndicator.color(context), ), const SizedBox(width: 5), - points != null + !loading ? AnimatedCount( - count: points!, + count: points ?? 0, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, diff --git a/lib/pangea/widgets/practice_activity/practice_activity_card.dart b/lib/pangea/widgets/practice_activity/practice_activity_card.dart index 5d0b816625..6e24c9e8bf 100644 --- a/lib/pangea/widgets/practice_activity/practice_activity_card.dart +++ b/lib/pangea/widgets/practice_activity/practice_activity_card.dart @@ -1,4 +1,5 @@ import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/practice_activity_event.dart'; import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_record_model.dart'; @@ -102,6 +103,20 @@ class MessagePracticeActivityCardState extends State { }, ); return null; + }).then((event) { + // The record event is processed into construct uses for learning analytics, so if the + // event went through without error, send it to analytics to be processed + if (event != null && currentActivity != null) { + MatrixState.pangeaController.myAnalytics.setState( + data: { + 'eventID': widget.pangeaMessageEvent.eventId, + 'eventType': PangeaEventTypes.activityRecord, + 'roomID': event.room.id, + 'practiceActivity': currentActivity!, + 'recordModel': currentRecordModel!, + }, + ); + } }).whenComplete(() => setState(() => sending = false)); } diff --git a/lib/pangea/widgets/user_settings/p_language_dialog.dart b/lib/pangea/widgets/user_settings/p_language_dialog.dart index bfc85528b9..5b08d3cadd 100644 --- a/lib/pangea/widgets/user_settings/p_language_dialog.dart +++ b/lib/pangea/widgets/user_settings/p_language_dialog.dart @@ -91,15 +91,22 @@ Future pLanguageDialog( context: context, future: () async { try { - pangeaController.userController - .updateProfile((profile) { - profile.userSettings.sourceLanguage = - selectedSourceLanguage.langCode; - profile.userSettings.targetLanguage = - selectedTargetLanguage.langCode; - return profile; + pangeaController.userController.updateProfile( + (profile) { + profile.userSettings.sourceLanguage = + selectedSourceLanguage.langCode; + profile.userSettings.targetLanguage = + selectedTargetLanguage.langCode; + return profile; + }, + waitForDataInSync: true, + ).then((_) { + // if the profile update is successful, reset cached analytics + // data, since analytics data corresponds to the user's L2 + pangeaController.myAnalytics.clearCache(); + pangeaController.analytics.clearCache(); + Navigator.pop(context); }); - Navigator.pop(context); } catch (err, s) { debugger(when: kDebugMode); ErrorHandler.logError(e: err, s: s); From 15c2d1dfd98e51aceeacf2fdab3c3478cbeb128d Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 6 Aug 2024 15:39:30 -0400 Subject: [PATCH 165/288] added analytics view to space view, removed number ticker animation --- lib/pages/chat_list/space_view.dart | 11 +++++ .../controllers/my_analytics_controller.dart | 8 ++- .../analytics_summary/progress_indicator.dart | 49 +------------------ 3 files changed, 19 insertions(+), 49 deletions(-) diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index a90c0d0946..ebf343bbf6 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -11,6 +11,7 @@ import 'package:fluffychat/pangea/constants/pangea_room_types.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; import 'package:fluffychat/pangea/utils/chat_list_handle_space_tap.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; +import 'package:fluffychat/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart'; import 'package:fluffychat/pangea/widgets/chat_list/chat_list_header_wrapper.dart'; import 'package:fluffychat/pangea/widgets/chat_list/chat_list_item_wrapper.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; @@ -733,6 +734,11 @@ class _SpaceViewState extends State { // #Pangea // ChatListHeader(controller: widget.controller), ChatListHeaderWrapper(controller: widget.controller), + SliverList( + delegate: SliverChildListDelegate( + [const LearningProgressIndicators()], + ), + ), // Pangea# SliverList( delegate: SliverChildBuilderDelegate( @@ -819,6 +825,11 @@ class _SpaceViewState extends State { controller: widget.controller, globalSearch: false, ), + SliverList( + delegate: SliverChildListDelegate( + [const LearningProgressIndicators()], + ), + ), // Pangea# SliverAppBar( automaticallyImplyLeading: false, diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart index 11840836bd..1b94a12ba4 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/my_analytics_controller.dart @@ -50,9 +50,13 @@ class MyAnalyticsController extends BaseController { // Wait for the next sync in the stream to ensure that the pangea controller // is fully initialized. It will throw an error if it is not. - _pangeaController.matrixState.client.onSync.stream.first.then((_) { + if (_pangeaController.matrixState.client.prevBatch == null) { + _pangeaController.matrixState.client.onSync.stream.first.then( + (_) => _refreshAnalyticsIfOutdated(), + ); + } else { _refreshAnalyticsIfOutdated(); - }); + } // Listen to a stream that provides the eventIDs // of new messages sent by the logged in user diff --git a/lib/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart b/lib/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart index f9ef427e4c..bd24b206f9 100644 --- a/lib/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart +++ b/lib/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart @@ -1,4 +1,3 @@ -import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pangea/enum/progress_indicators_enum.dart'; import 'package:flutter/material.dart'; @@ -36,8 +35,8 @@ class ProgressIndicatorBadge extends StatelessWidget { ), const SizedBox(width: 5), !loading - ? AnimatedCount( - count: points ?? 0, + ? Text( + points?.toString() ?? '0', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, @@ -58,47 +57,3 @@ class ProgressIndicatorBadge extends StatelessWidget { ); } } - -class AnimatedCount extends ImplicitlyAnimatedWidget { - const AnimatedCount({ - super.key, - required this.count, - this.style, - super.duration = const Duration(seconds: 1), - super.curve = FluffyThemes.animationCurve, - }); - - final int count; - final TextStyle? style; - - @override - ImplicitlyAnimatedWidgetState createState() { - return _AnimatedCountState(); - } -} - -class _AnimatedCountState extends AnimatedWidgetBaseState { - IntTween _intCount = IntTween(begin: 0, end: 1); - - @override - void initState() { - super.initState(); - _intCount = IntTween(begin: 0, end: widget.count.toInt()); - controller.forward(); - } - - @override - Widget build(BuildContext context) { - final String text = _intCount.evaluate(animation).toString(); - return Text(text, style: widget.style); - } - - @override - void forEachTween(TweenVisitor visitor) { - _intCount = visitor( - _intCount, - widget.count, - (dynamic value) => IntTween(begin: value), - ) as IntTween; - } -} From cc310acf9970119c673b5f6470345c53f76b1126 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 7 Aug 2024 09:34:09 -0400 Subject: [PATCH 166/288] don't animate level bar unless XP points have changed --- .../learning_progress_indicators.dart | 53 +++++++++++++------ 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart index 3a2accad8d..07fd026040 100644 --- a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart +++ b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart @@ -44,6 +44,9 @@ class LearningProgressIndicatorsState bool loading = true; + /// The previous number of XP points, used to determine when to animate the level bar + int? previousXP; + @override void initState() { super.initState(); @@ -65,6 +68,8 @@ class LearningProgressIndicatorsState /// Update the analytics data shown in the UI. This comes from a /// combination of stored events and locally cached data. Future updateAnalyticsData() async { + previousXP = xpPoints; + final List storedUses = await _pangeaController.analytics.getConstructs(); final List localUses = []; @@ -114,17 +119,21 @@ class LearningProgressIndicatorsState } } - /// Get the total number of xp points, based on the point values of use types - int get xpPoints { - return (words?.points ?? 0) + (errors?.points ?? 0); + /// Get the total number of xp points, based on the point values of use types. + /// Null if niether words nor error constructs are available. + int? get xpPoints { + if (words == null && errors == null) return null; + if (words == null) return errors!.points; + if (errors == null) return words!.points; + return words!.points + errors!.points; } /// Get the current level based on the number of xp points - int get level => xpPoints ~/ 500; + int get level => (xpPoints ?? 0) ~/ 500; double get levelBarWidth => FluffyThemes.columnWidth - (32 * 2) - 25; double get pointsBarWidth { - final percent = (xpPoints % 500) / 500; + final percent = ((xpPoints ?? 0) % 500) / 500; return levelBarWidth * percent; } @@ -140,6 +149,12 @@ class LearningProgressIndicatorsState return colors[level % colors.length]; } + /// Whether to animate the level bar increase. Prevents this bar from seeming to + /// reload each time the user navigates to a different space or back to the chat list. + /// PreviousXP would be null if this widget just mounted. Also handles case of rebuilds + /// without any change in XP points. + bool get animate => previousXP != null && previousXP != xpPoints; + @override Widget build(BuildContext context) { if (Matrix.of(context).client.userID == null) { @@ -162,19 +177,27 @@ class LearningProgressIndicatorsState ), ); - final xpBar = AnimatedContainer( - duration: FluffyThemes.animationDuration, - height: 16, - width: pointsBarWidth, - decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topRight: Radius.circular(AppConfig.borderRadius), - bottomRight: Radius.circular(AppConfig.borderRadius), - ), - color: Theme.of(context).colorScheme.primary, + final xpBarDecoration = BoxDecoration( + borderRadius: const BorderRadius.only( + topRight: Radius.circular(AppConfig.borderRadius), + bottomRight: Radius.circular(AppConfig.borderRadius), ), + color: Theme.of(context).colorScheme.primary, ); + final xpBar = animate + ? AnimatedContainer( + duration: FluffyThemes.animationDuration, + height: 16, + width: pointsBarWidth, + decoration: xpBarDecoration, + ) + : Container( + height: 16, + width: pointsBarWidth, + decoration: xpBarDecoration, + ); + final levelBadge = Container( width: 32, height: 32, From 641343d5fe3c6485df987462d825ca76b532c65a Mon Sep 17 00:00:00 2001 From: Krille Date: Thu, 8 Aug 2024 19:06:42 +0200 Subject: [PATCH 167/288] chore: Follow up scroll to event id fix --- lib/pages/chat/chat.dart | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 6f4a83e9de..f287d29cfc 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:adaptive_dialog/adaptive_dialog.dart'; +import 'package:collection/collection.dart'; import 'package:desktop_drop/desktop_drop.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:emoji_picker_flutter/emoji_picker_flutter.dart'; @@ -216,15 +217,13 @@ class ChatController extends State EmojiPickerType emojiPickerType = EmojiPickerType.keyboard; void requestHistory([_]) async { - if (!timeline!.canRequestHistory) return; Logs().v('Requesting history...'); - await timeline!.requestHistory(historyCount: _loadHistoryCount); + await timeline?.requestHistory(historyCount: _loadHistoryCount); } void requestFuture() async { final timeline = this.timeline; if (timeline == null) return; - if (!timeline.canRequestFuture) return; Logs().v('Requesting future...'); final mostRecentEventId = timeline.events.first.eventId; await timeline.requestFuture(historyCount: _loadHistoryCount); @@ -343,6 +342,7 @@ class ChatController extends State eventContextId = null; } try { + timeline?.cancelSubscriptions(); timeline = await room.getTimeline( onUpdate: updateView, eventContextId: eventContextId, @@ -905,10 +905,16 @@ class ChatController extends State String eventId, { bool highlightEvent = true, }) async { - final eventIndex = timeline!.events - .where((event) => event.isVisibleInGui) - .toList() - .indexWhere((e) => e.eventId == eventId); + final foundEvent = + timeline!.events.firstWhereOrNull((event) => event.eventId == eventId); + + final eventIndex = foundEvent == null + ? -1 + : timeline!.events + .where((event) => event.isVisibleInGui || event.eventId == eventId) + .toList() + .indexOf(foundEvent); + if (eventIndex == -1) { setState(() { timeline = null; From 84075133fa09d5892f25cc98a691de9fe7024a29 Mon Sep 17 00:00:00 2001 From: Krille Date: Thu, 8 Aug 2024 19:13:08 +0200 Subject: [PATCH 168/288] chore: Bring back add to space feature --- lib/pages/chat_list/chat_list.dart | 41 ++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 7b89f0997a..9df247e4c4 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -563,6 +563,15 @@ class ChatListController extends State final displayname = room.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)); + final spacesWithPowerLevels = room.client.rooms + .where( + (space) => + space.isSpace && + space.canChangeStateEvent(EventTypes.SpaceChild) && + !space.spaceChildren.any((c) => c.roomId == room.id), + ) + .toList(); + final action = await showMenu( context: posContext, position: position, @@ -661,6 +670,18 @@ class ChatListController extends State ], ), ), + if (spacesWithPowerLevels.isNotEmpty) + PopupMenuItem( + value: ChatContextAction.addToSpace, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.group_work_outlined), + const SizedBox(width: 12), + Text(L10n.of(context)!.addToSpace), + ], + ), + ), PopupMenuItem( value: ChatContextAction.leave, child: Row( @@ -723,6 +744,25 @@ class ChatListController extends State await showFutureLoadingDialog(context: context, future: room.leave); return; + case ChatContextAction.addToSpace: + final space = await showConfirmationDialog( + context: context, + title: L10n.of(context)!.space, + actions: spacesWithPowerLevels + .map( + (space) => AlertDialogAction( + key: space, + label: space + .getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)), + ), + ) + .toList(), + ); + if (space == null) return; + await showFutureLoadingDialog( + context: context, + future: () => space.setSpaceChild(room.id), + ); } } @@ -927,4 +967,5 @@ enum ChatContextAction { markUnread, mute, leave, + addToSpace, } From 890a4ee746234b1a57e4cdb459749125c0fb6596 Mon Sep 17 00:00:00 2001 From: - Date: Sun, 28 Jul 2024 12:28:47 +0000 Subject: [PATCH 169/288] Translated using Weblate (Russian) Currently translated at 99.0% (646 of 652 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ru/ --- assets/l10n/intl_ru.arb | 56 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/assets/l10n/intl_ru.arb b/assets/l10n/intl_ru.arb index 7cc58523a8..ea5c7bbc1d 100644 --- a/assets/l10n/intl_ru.arb +++ b/assets/l10n/intl_ru.arb @@ -2369,7 +2369,7 @@ "reason": {} } }, - "setChatDescription": "Изменить описание чата", + "setChatDescription": "Установить описание чата", "@setChatDescription": {}, "setColorTheme": "Цветовая тема:", "@setColorTheme": {}, @@ -2708,5 +2708,57 @@ "files": "Файлы", "@files": {}, "swipeRightToLeftToReply": "Для ответа проведите с права на лево", - "@swipeRightToLeftToReply": {} + "@swipeRightToLeftToReply": {}, + "userLevel": "{level} - Пользователь", + "@userLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "moderatorLevel": "{level} - Модератор", + "@moderatorLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "adminLevel": "{level} - Администратор", + "@adminLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "changeGeneralChatSettings": "Изменить общие настройки чата", + "@changeGeneralChatSettings": {}, + "changeTheChatPermissions": "Изменить права доступа к чату", + "@changeTheChatPermissions": {}, + "changeTheDescriptionOfTheGroup": "Изменить описание чата", + "@changeTheDescriptionOfTheGroup": {}, + "inviteOtherUsers": "Пригласить других пользователей в этот чат", + "@inviteOtherUsers": {}, + "changeTheVisibilityOfChatHistory": "Изменить видимость истории чата", + "@changeTheVisibilityOfChatHistory": {}, + "countChatsAndCountParticipants": "{chats} чатов и {participants} участников", + "@countChatsAndCountParticipants": { + "type": "text", + "placeholders": { + "chats": {}, + "participants": {} + } + }, + "unread": "Непрочитанные", + "@unread": {}, + "space": "Пространство", + "@space": {}, + "spaces": "Пространства", + "@spaces": {}, + "markAsUnread": "Отметить как непрочитанное", + "@markAsUnread": {}, + "goToSpace": "Перейти к пространству: {space}", + "@goToSpace": { + "type": "text", + "space": {} + } } From 38a0407f93e1363df410398f8330afa5c4c64e80 Mon Sep 17 00:00:00 2001 From: Rex_sa Date: Tue, 30 Jul 2024 04:06:26 +0000 Subject: [PATCH 170/288] Translated using Weblate (Arabic) Currently translated at 100.0% (654 of 654 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ar/ --- assets/l10n/intl_ar.arb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_ar.arb b/assets/l10n/intl_ar.arb index c3fbde0389..77c32fb5ba 100644 --- a/assets/l10n/intl_ar.arb +++ b/assets/l10n/intl_ar.arb @@ -2772,5 +2772,13 @@ "sendRoomNotifications": "إرسال إشعارات @room", "@sendRoomNotifications": {}, "changeTheDescriptionOfTheGroup": "تغيير وصف الدردشة", - "@changeTheDescriptionOfTheGroup": {} + "@changeTheDescriptionOfTheGroup": {}, + "invitedBy": "📩 تمت دعوته من قبل {user}", + "@invitedBy": { + "placeholders": { + "user": {} + } + }, + "chatPermissionsDescription": "‪حدد مستوى الصلاحية الضروري لإجراءات معينة في هذه الدردشة. عادة ما تمثل مستويات الصلاحية 0 و 50 و 100 المستخدمين والمشرفين ولكن أي تدرج ممكن.", + "@chatPermissionsDescription": {} } From 1944ea4e8b1d4d703842fe65d8abb6d54c4bbb63 Mon Sep 17 00:00:00 2001 From: tct123 Date: Mon, 29 Jul 2024 13:53:05 +0000 Subject: [PATCH 171/288] Translated using Weblate (German) Currently translated at 100.0% (654 of 654 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/de/ --- assets/l10n/intl_de.arb | 47 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/assets/l10n/intl_de.arb b/assets/l10n/intl_de.arb index 54aa417b3b..810ba47e95 100644 --- a/assets/l10n/intl_de.arb +++ b/assets/l10n/intl_de.arb @@ -558,7 +558,7 @@ "type": "text", "placeholders": {} }, - "defaultPermissionLevel": "Standardberechtigungsstufe", + "defaultPermissionLevel": "Standardberechtigungsstufe für neue Benutzer", "@defaultPermissionLevel": { "type": "text", "placeholders": {} @@ -2737,5 +2737,48 @@ "chats": {}, "participants": {} } - } + }, + "changeGeneralChatSettings": "Allgemeine Chat-Einstellungen ändern", + "@changeGeneralChatSettings": {}, + "userLevel": "{level} - Benutzer", + "@userLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "moderatorLevel": "{level} - Moderator", + "@moderatorLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "changeTheChatPermissions": "Ändere die Chat-Berechtigungen", + "@changeTheChatPermissions": {}, + "changeTheVisibilityOfChatHistory": "Wechsele die Sichtbarkeit der Chat-Historie", + "@changeTheVisibilityOfChatHistory": {}, + "chatPermissionsDescription": "Definieren Sie, welche Befugnisstufe für bestimmte Aktionen in diesem Chat erforderlich ist. Die Befugnisstufen 0, 50 und 100 stehen üblicherweise für Benutzer, Moderatoren und Admins, aber jede Abstufung ist möglich.", + "@chatPermissionsDescription": {}, + "invitedBy": "📩 Eingeladen von {user}", + "@invitedBy": { + "placeholders": { + "user": {} + } + }, + "adminLevel": "{level} - Administrator", + "@adminLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "inviteOtherUsers": "Lade andere Benutzer in diesen Chat ein", + "@inviteOtherUsers": {}, + "changeTheCanonicalRoomAlias": "Ändern der Hauptadresse für den öffentlichen Chat", + "@changeTheCanonicalRoomAlias": {}, + "sendRoomNotifications": "Senden Sie eine @room-Benachrichtigung", + "@sendRoomNotifications": {}, + "changeTheDescriptionOfTheGroup": "Ändern Sie die Beschreibung des Chats", + "@changeTheDescriptionOfTheGroup": {} } From 6d662b74ee97c3b0e4be4ef6237fb426504a345b Mon Sep 17 00:00:00 2001 From: xabirequejo Date: Mon, 29 Jul 2024 19:41:45 +0000 Subject: [PATCH 172/288] Translated using Weblate (Basque) Currently translated at 99.8% (653 of 654 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/ --- assets/l10n/intl_eu.arb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_eu.arb b/assets/l10n/intl_eu.arb index d810bcc955..50e389633e 100644 --- a/assets/l10n/intl_eu.arb +++ b/assets/l10n/intl_eu.arb @@ -2772,5 +2772,11 @@ "changeTheVisibilityOfChatHistory": "Aldatu txataren historiaren ikusgaitasuna", "@changeTheVisibilityOfChatHistory": {}, "changeTheCanonicalRoomAlias": "Aldatu txataren helbide publiko nagusia", - "@changeTheCanonicalRoomAlias": {} + "@changeTheCanonicalRoomAlias": {}, + "invitedBy": "📩 {user}(e)k gonbidatua", + "@invitedBy": { + "placeholders": { + "user": {} + } + } } From bd67390916a5c889e024aec3161ba8964112300d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Mon, 29 Jul 2024 17:18:12 +0000 Subject: [PATCH 173/288] Translated using Weblate (Turkish) Currently translated at 100.0% (654 of 654 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/tr/ --- assets/l10n/intl_tr.arb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_tr.arb b/assets/l10n/intl_tr.arb index a0bf354571..3437f1f99a 100644 --- a/assets/l10n/intl_tr.arb +++ b/assets/l10n/intl_tr.arb @@ -2772,5 +2772,13 @@ "changeGeneralChatSettings": "Genel sohbet ayarlarını değiştir", "@changeGeneralChatSettings": {}, "changeTheVisibilityOfChatHistory": "Sohbet geçmişinin görünürlüğünü değiştir", - "@changeTheVisibilityOfChatHistory": {} + "@changeTheVisibilityOfChatHistory": {}, + "invitedBy": "📩 {user} davet etti", + "@invitedBy": { + "placeholders": { + "user": {} + } + }, + "chatPermissionsDescription": "Bu sohbette belirli eylemler için hangi güç düzeyinin gerekli olduğunu tanımlayın. 0, 50 ve 100 güç düzeyleri genellikle kullanıcıları, moderatörleri ve yöneticileri temsil eder, ancak herhangi bir derecelendirme mümkündür.", + "@chatPermissionsDescription": {} } From fec4f2533bf86eb9eee9775d2ec643bb85a4a135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E7=8E=8B=E5=8F=AB=E6=88=91=E6=9D=A5=E5=B7=A1?= =?UTF-8?q?=E5=B1=B1?= Date: Tue, 30 Jul 2024 01:59:03 +0000 Subject: [PATCH 174/288] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (654 of 654 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hans/ --- assets/l10n/intl_zh.arb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_zh.arb b/assets/l10n/intl_zh.arb index 67b1e96aa3..c62304cf84 100644 --- a/assets/l10n/intl_zh.arb +++ b/assets/l10n/intl_zh.arb @@ -2772,5 +2772,13 @@ "changeTheDescriptionOfTheGroup": "更改聊天描述", "@changeTheDescriptionOfTheGroup": {}, "changeGeneralChatSettings": "更改常规聊天设置", - "@changeGeneralChatSettings": {} + "@changeGeneralChatSettings": {}, + "invitedBy": "📩 邀请人 {user}", + "@invitedBy": { + "placeholders": { + "user": {} + } + }, + "chatPermissionsDescription": "定义此聊天中哪个权限等级对特定操作是必需的。权限等级 0、50 和 100 通常代表用户、主持人和管理员,但你可以自定义任何等级。", + "@chatPermissionsDescription": {} } From 2b2b625e0b487bc3e07ec5f736f8d89acbe617ed Mon Sep 17 00:00:00 2001 From: Jelv Date: Tue, 30 Jul 2024 06:33:40 +0000 Subject: [PATCH 175/288] Translated using Weblate (Dutch) Currently translated at 82.1% (537 of 654 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/nl/ --- assets/l10n/intl_nl.arb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/l10n/intl_nl.arb b/assets/l10n/intl_nl.arb index 8453e47dbe..01a75dcd9c 100644 --- a/assets/l10n/intl_nl.arb +++ b/assets/l10n/intl_nl.arb @@ -562,7 +562,7 @@ "type": "text", "placeholders": {} }, - "defaultPermissionLevel": "Standaardmachtigingsniveau", + "defaultPermissionLevel": "Standaard machtigingsniveau voor nieuwe personen", "@defaultPermissionLevel": { "type": "text", "placeholders": {} @@ -2404,7 +2404,7 @@ "@makeAdminDescription": {}, "archiveRoomDescription": "De chat zal naar het archief worden verplaatst. Andere personen zullen in staat zijn te zien dat je de chat hebt verlaten.", "@archiveRoomDescription": {}, - "hasKnocked": "{user} heeft geklopt", + "hasKnocked": "🚪 {user} heeft geklopt", "@hasKnocked": { "placeholders": { "user": {} @@ -2418,7 +2418,7 @@ "@pleaseEnterANumber": {}, "kickUserDescription": "De persoon is verwijderd uit de chat, maar is niet verbannen. In publieke chats kan de persoon op elk moment opnieuw deelnemen.", "@kickUserDescription": {}, - "alwaysUse24HourFormat": "true", + "alwaysUse24HourFormat": "fout", "@alwaysUse24HourFormat": { "description": "Set to true to always display time of day in 24 hour format." } From cb4c9cc35117fb174e2e73f6f957b6a1f58f8513 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Wed, 31 Jul 2024 18:36:12 +0000 Subject: [PATCH 176/288] Translated using Weblate (Estonian) Currently translated at 100.0% (654 of 654 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/ --- assets/l10n/intl_et.arb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_et.arb b/assets/l10n/intl_et.arb index c94ad99387..83c52d1e37 100644 --- a/assets/l10n/intl_et.arb +++ b/assets/l10n/intl_et.arb @@ -2772,5 +2772,13 @@ "changeTheChatPermissions": "Muuda vestluse õigusi", "@changeTheChatPermissions": {}, "changeTheDescriptionOfTheGroup": "Muuda vestluse kirjeldust", - "@changeTheDescriptionOfTheGroup": {} + "@changeTheDescriptionOfTheGroup": {}, + "chatPermissionsDescription": "Määra erinevatele kasutajatele selles vestluses vajalikud õigused. Tüüpiliselt on need 0, 50 ja 100 (vastavalt kasutajad, moderaatorid ja peakasutajad), kuid igasugused vahepealsed variatsioonid on ka võimalikud.", + "@chatPermissionsDescription": {}, + "invitedBy": "📩 Kutsujaks {user}", + "@invitedBy": { + "placeholders": { + "user": {} + } + } } From 97cf6cfc97cd098e0a1f02e20bcc8b5ac33754b9 Mon Sep 17 00:00:00 2001 From: Rex_sa Date: Fri, 2 Aug 2024 07:20:43 +0000 Subject: [PATCH 177/288] Translated using Weblate (Arabic) Currently translated at 100.0% (656 of 656 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ar/ --- assets/l10n/intl_ar.arb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_ar.arb b/assets/l10n/intl_ar.arb index 77c32fb5ba..6ad065889e 100644 --- a/assets/l10n/intl_ar.arb +++ b/assets/l10n/intl_ar.arb @@ -2780,5 +2780,14 @@ } }, "chatPermissionsDescription": "‪حدد مستوى الصلاحية الضروري لإجراءات معينة في هذه الدردشة. عادة ما تمثل مستويات الصلاحية 0 و 50 و 100 المستخدمين والمشرفين ولكن أي تدرج ممكن.", - "@chatPermissionsDescription": {} + "@chatPermissionsDescription": {}, + "changelog": "سجل التغييرات", + "@changelog": {}, + "updateInstalled": "تم تثبيت🎉 تحديث {version}!", + "@updateInstalled": { + "type": "text", + "placeholders": { + "version": {} + } + } } From 47a137547388de716acfefc6fbbad008d3d0fe1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Fri, 2 Aug 2024 20:28:15 +0000 Subject: [PATCH 178/288] Translated using Weblate (Estonian) Currently translated at 100.0% (656 of 656 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/ --- assets/l10n/intl_et.arb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_et.arb b/assets/l10n/intl_et.arb index 83c52d1e37..5c686459ed 100644 --- a/assets/l10n/intl_et.arb +++ b/assets/l10n/intl_et.arb @@ -2780,5 +2780,14 @@ "placeholders": { "user": {} } - } + }, + "updateInstalled": "🎉 Versiooniuuendus {version} on paigaldatud!", + "@updateInstalled": { + "type": "text", + "placeholders": { + "version": {} + } + }, + "changelog": "Muudatuste logi", + "@changelog": {} } From 7ef23712b24c288b42b6f728dc4c8e8f73549a03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jos=C3=A9=20m?= Date: Fri, 2 Aug 2024 04:20:23 +0000 Subject: [PATCH 179/288] Translated using Weblate (Galician) Currently translated at 100.0% (656 of 656 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/gl/ --- assets/l10n/intl_gl.arb | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_gl.arb b/assets/l10n/intl_gl.arb index 94f76ff12d..4d2d6cfaa0 100644 --- a/assets/l10n/intl_gl.arb +++ b/assets/l10n/intl_gl.arb @@ -2772,5 +2772,22 @@ "sendRoomNotifications": "Enviar notificacións a @room", "@sendRoomNotifications": {}, "changeTheDescriptionOfTheGroup": "Cambiar a descrición do chat", - "@changeTheDescriptionOfTheGroup": {} + "@changeTheDescriptionOfTheGroup": {}, + "invitedBy": "📩 Convidada por {user}", + "@invitedBy": { + "placeholders": { + "user": {} + } + }, + "changelog": "Novidades na versión", + "@changelog": {}, + "chatPermissionsDescription": "Define que nivel de permisos son necesarios para realizar certas accións neste chat. Os niveis de permiso 0, 50 e 100 normalmente representan, usuarias, moderadoras e administradoras, pero son posibles outras escalas.", + "@chatPermissionsDescription": {}, + "updateInstalled": "🎉 Instalouse a actualización a {version}!", + "@updateInstalled": { + "type": "text", + "placeholders": { + "version": {} + } + } } From 876121eaacc42ef451853463ec686ec523af3cda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Fri, 2 Aug 2024 16:42:32 +0000 Subject: [PATCH 180/288] Translated using Weblate (Turkish) Currently translated at 100.0% (656 of 656 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/tr/ --- assets/l10n/intl_tr.arb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_tr.arb b/assets/l10n/intl_tr.arb index 3437f1f99a..dbb60c3d8d 100644 --- a/assets/l10n/intl_tr.arb +++ b/assets/l10n/intl_tr.arb @@ -2780,5 +2780,14 @@ } }, "chatPermissionsDescription": "Bu sohbette belirli eylemler için hangi güç düzeyinin gerekli olduğunu tanımlayın. 0, 50 ve 100 güç düzeyleri genellikle kullanıcıları, moderatörleri ve yöneticileri temsil eder, ancak herhangi bir derecelendirme mümkündür.", - "@chatPermissionsDescription": {} + "@chatPermissionsDescription": {}, + "changelog": "Değişiklik günlüğü", + "@changelog": {}, + "updateInstalled": "🎉 Güncelleme {version} kuruldu!", + "@updateInstalled": { + "type": "text", + "placeholders": { + "version": {} + } + } } From 1f1c3dd2527f2e464dcf60e70d60575c261e3e0c Mon Sep 17 00:00:00 2001 From: Bezruchenko Simon Date: Fri, 2 Aug 2024 17:59:20 +0000 Subject: [PATCH 181/288] Translated using Weblate (Ukrainian) Currently translated at 98.4% (646 of 656 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/uk/ --- assets/l10n/intl_uk.arb | 48 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/assets/l10n/intl_uk.arb b/assets/l10n/intl_uk.arb index 9af791b258..4e29a75bdc 100644 --- a/assets/l10n/intl_uk.arb +++ b/assets/l10n/intl_uk.arb @@ -1557,7 +1557,7 @@ "type": "text", "placeholders": {} }, - "defaultPermissionLevel": "Типовий рівень дозволів", + "defaultPermissionLevel": "Типовий рівень дозволів для нових користувачів", "@defaultPermissionLevel": { "type": "text", "placeholders": {} @@ -2708,5 +2708,49 @@ "@thereAreCountUsersBlocked": { "type": "text", "count": {} - } + }, + "moderatorLevel": "{level} - Модератор", + "@moderatorLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "adminLevel": "{level} - Адміністратор", + "@adminLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "userLevel": "{level} - Користувач", + "@userLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "changeGeneralChatSettings": "Змінити загальні налаштування чату", + "@changeGeneralChatSettings": {}, + "inviteOtherUsers": "Запросіть інших користувачів до цього чату", + "@inviteOtherUsers": {}, + "changeTheChatPermissions": "Змінити права доступу до чату", + "@changeTheChatPermissions": {}, + "changeTheVisibilityOfChatHistory": "Змінити видимість історії чату", + "@changeTheVisibilityOfChatHistory": {}, + "changeTheCanonicalRoomAlias": "Змініть основну адресу публічного чату", + "@changeTheCanonicalRoomAlias": {}, + "sendRoomNotifications": "Надсилати сповіщення в @кімнату", + "@sendRoomNotifications": {}, + "space": "Простір", + "@space": {}, + "spaces": "Простори", + "@spaces": {}, + "goToSpace": "Перейти до простору: {space}", + "@goToSpace": { + "type": "text", + "space": {} + }, + "markAsUnread": "Позначити як непрочитане", + "@markAsUnread": {} } From 38af858136a635c8e3fd8a49caa04c395cf0b42f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E7=8E=8B=E5=8F=AB=E6=88=91=E6=9D=A5=E5=B7=A1?= =?UTF-8?q?=E5=B1=B1?= Date: Fri, 2 Aug 2024 01:44:46 +0000 Subject: [PATCH 182/288] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (656 of 656 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hans/ --- assets/l10n/intl_zh.arb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_zh.arb b/assets/l10n/intl_zh.arb index c62304cf84..a188872b42 100644 --- a/assets/l10n/intl_zh.arb +++ b/assets/l10n/intl_zh.arb @@ -2780,5 +2780,14 @@ } }, "chatPermissionsDescription": "定义此聊天中哪个权限等级对特定操作是必需的。权限等级 0、50 和 100 通常代表用户、主持人和管理员,但你可以自定义任何等级。", - "@chatPermissionsDescription": {} + "@chatPermissionsDescription": {}, + "changelog": "更新记录", + "@changelog": {}, + "updateInstalled": "🎉 已安装更新 {version} !", + "@updateInstalled": { + "type": "text", + "placeholders": { + "version": {} + } + } } From bfdcc6b8c5df10624f94ae775b0ff9ac94f189cf Mon Sep 17 00:00:00 2001 From: xabirequejo Date: Sun, 4 Aug 2024 10:55:08 +0000 Subject: [PATCH 183/288] Translated using Weblate (Basque) Currently translated at 100.0% (656 of 656 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/ --- assets/l10n/intl_eu.arb | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/assets/l10n/intl_eu.arb b/assets/l10n/intl_eu.arb index 50e389633e..64765054d5 100644 --- a/assets/l10n/intl_eu.arb +++ b/assets/l10n/intl_eu.arb @@ -2679,7 +2679,7 @@ "@noPublicLinkHasBeenCreatedYet": {}, "userRole": "Erabiltzailearen rola", "@userRole": {}, - "minimumPowerLevel": "{level} da gutxieneko botere maila.", + "minimumPowerLevel": "{level} da gutxieneko botere-maila.", "@minimumPowerLevel": { "type": "text", "placeholders": { @@ -2778,5 +2778,16 @@ "placeholders": { "user": {} } - } + }, + "updateInstalled": "🎉 {version} bertsioa instalatu da!", + "@updateInstalled": { + "type": "text", + "placeholders": { + "version": {} + } + }, + "changelog": "Aldaketak", + "@changelog": {}, + "chatPermissionsDescription": "Definitu zer botere-maila behar den txat honetako ekintza jakinetarako. 0, 50 eta 100 botere-mailek erabiltzaileak, moderatzaileak eta administratzaileak ordezkatzen dituzte, baina edozein graduazio posible da.", + "@chatPermissionsDescription": {} } From b14d4c010b146b14978e5ee3bd647899004a8108 Mon Sep 17 00:00:00 2001 From: Bezruchenko Simon Date: Sat, 3 Aug 2024 10:57:20 +0000 Subject: [PATCH 184/288] Translated using Weblate (Ukrainian) Currently translated at 100.0% (656 of 656 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/uk/ --- assets/l10n/intl_uk.arb | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/assets/l10n/intl_uk.arb b/assets/l10n/intl_uk.arb index 4e29a75bdc..d7009c3daf 100644 --- a/assets/l10n/intl_uk.arb +++ b/assets/l10n/intl_uk.arb @@ -1913,7 +1913,7 @@ }, "unverified": "Неперевірений", "@unverified": {}, - "locationDisabledNotice": "Служби визначення місцеположення вимкнені. Увімкніть їх, щоб могти надавати доступ до вашого місцеположення.", + "locationDisabledNotice": "Послуги визначення місцеперебування вимкнені. Будь ласка, увімкніть їх, щоб мати змогу ділитися своїм місцеперебуванням.", "@locationDisabledNotice": { "type": "text", "placeholders": {} @@ -2752,5 +2752,42 @@ "space": {} }, "markAsUnread": "Позначити як непрочитане", - "@markAsUnread": {} + "@markAsUnread": {}, + "alwaysUse24HourFormat": "ні", + "@alwaysUse24HourFormat": { + "description": "Set to true to always display time of day in 24 hour format." + }, + "invitedBy": "📩 Запрошений {user}", + "@invitedBy": { + "placeholders": { + "user": {} + } + }, + "changeTheDescriptionOfTheGroup": "Змінити опис чату", + "@changeTheDescriptionOfTheGroup": {}, + "updateInstalled": "🎉 Оновлення {version} встановлено!", + "@updateInstalled": { + "type": "text", + "placeholders": { + "version": {} + } + }, + "changelog": "Зміни", + "@changelog": {}, + "chatPermissionsDescription": "Визначте, який рівень повноважень необхідний для певних дій у цьому чаті. Рівні повноважень 0, 50 і 100 зазвичай представляють користувачів, модераторів та адміністраторів, але можливі будь-які градації.", + "@chatPermissionsDescription": {}, + "countChatsAndCountParticipants": "{chats} чати та {participants} учасників", + "@countChatsAndCountParticipants": { + "type": "text", + "placeholders": { + "chats": {}, + "participants": {} + } + }, + "noMoreChatsFound": "Більше чатів не знайдено...", + "@noMoreChatsFound": {}, + "joinedChats": "Приєднані чати", + "@joinedChats": {}, + "unread": "Непрочитані", + "@unread": {} } From 3ce9aaca0ce2017fed11413e5b47f028e2026a30 Mon Sep 17 00:00:00 2001 From: tct123 Date: Mon, 5 Aug 2024 02:47:04 +0000 Subject: [PATCH 185/288] Translated using Weblate (German) Currently translated at 100.0% (656 of 656 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/de/ --- assets/l10n/intl_de.arb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_de.arb b/assets/l10n/intl_de.arb index 810ba47e95..cc05a4ddcc 100644 --- a/assets/l10n/intl_de.arb +++ b/assets/l10n/intl_de.arb @@ -2780,5 +2780,14 @@ "sendRoomNotifications": "Senden Sie eine @room-Benachrichtigung", "@sendRoomNotifications": {}, "changeTheDescriptionOfTheGroup": "Ändern Sie die Beschreibung des Chats", - "@changeTheDescriptionOfTheGroup": {} + "@changeTheDescriptionOfTheGroup": {}, + "updateInstalled": "🎉 Update {version} installiert!", + "@updateInstalled": { + "type": "text", + "placeholders": { + "version": {} + } + }, + "changelog": "Änderungsprotokoll", + "@changelog": {} } From bb93f50f99952f91f0624888f03e18f1ff53b4d4 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Sun, 4 Aug 2024 11:57:55 +0000 Subject: [PATCH 186/288] Translated using Weblate (Ukrainian) Currently translated at 100.0% (656 of 656 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/uk/ --- assets/l10n/intl_uk.arb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/assets/l10n/intl_uk.arb b/assets/l10n/intl_uk.arb index d7009c3daf..4a70f11aec 100644 --- a/assets/l10n/intl_uk.arb +++ b/assets/l10n/intl_uk.arb @@ -1913,12 +1913,12 @@ }, "unverified": "Неперевірений", "@unverified": {}, - "locationDisabledNotice": "Послуги визначення місцеперебування вимкнені. Будь ласка, увімкніть їх, щоб мати змогу ділитися своїм місцеперебуванням.", + "locationDisabledNotice": "Служби визначення розташування вимкнені. Увімкніть їх, щоб мати змогу ділитися своїм розташуванням.", "@locationDisabledNotice": { "type": "text", "placeholders": {} }, - "locationPermissionDeniedNotice": "Дозвіл на розташування відхилено. Надайте можливість ділитися своїм місцеперебуванням.", + "locationPermissionDeniedNotice": "Дозвіл на розташування відхилено. Надайте можливість ділитися своїм розташуванням.", "@locationPermissionDeniedNotice": { "type": "text", "placeholders": {} @@ -2732,15 +2732,15 @@ }, "changeGeneralChatSettings": "Змінити загальні налаштування чату", "@changeGeneralChatSettings": {}, - "inviteOtherUsers": "Запросіть інших користувачів до цього чату", + "inviteOtherUsers": "Запросити інших користувачів до цього чату", "@inviteOtherUsers": {}, "changeTheChatPermissions": "Змінити права доступу до чату", "@changeTheChatPermissions": {}, "changeTheVisibilityOfChatHistory": "Змінити видимість історії чату", "@changeTheVisibilityOfChatHistory": {}, - "changeTheCanonicalRoomAlias": "Змініть основну адресу публічного чату", + "changeTheCanonicalRoomAlias": "Змінити основну адресу загальнодоступного чату", "@changeTheCanonicalRoomAlias": {}, - "sendRoomNotifications": "Надсилати сповіщення в @кімнату", + "sendRoomNotifications": "Надсилати сповіщення @room", "@sendRoomNotifications": {}, "space": "Простір", "@space": {}, @@ -2751,7 +2751,7 @@ "type": "text", "space": {} }, - "markAsUnread": "Позначити як непрочитане", + "markAsUnread": "Позначити непрочитаним", "@markAsUnread": {}, "alwaysUse24HourFormat": "ні", "@alwaysUse24HourFormat": { From 0be1743857ad5d50b180642426a09fb9a5ef06e4 Mon Sep 17 00:00:00 2001 From: Thomas Klein Langenhorst Date: Tue, 6 Aug 2024 12:56:52 +0000 Subject: [PATCH 187/288] Translated using Weblate (Dutch) Currently translated at 87.1% (572 of 656 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/nl/ --- assets/l10n/intl_nl.arb | 89 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_nl.arb b/assets/l10n/intl_nl.arb index 01a75dcd9c..3846eeec80 100644 --- a/assets/l10n/intl_nl.arb +++ b/assets/l10n/intl_nl.arb @@ -2421,5 +2421,92 @@ "alwaysUse24HourFormat": "fout", "@alwaysUse24HourFormat": { "description": "Set to true to always display time of day in 24 hour format." - } + }, + "joinSpace": "Deelname aan ruimte", + "@joinSpace": {}, + "block": "Blokkeren", + "@block": {}, + "blockedUsers": "Geblokkeerde gebruikers", + "@blockedUsers": {}, + "presenceStyle": "Aanwezigheid:", + "@presenceStyle": { + "type": "text", + "placeholders": {} + }, + "searchChatsRooms": "Zoek naar #chats, @gebruikers...", + "@searchChatsRooms": {}, + "swipeRightToLeftToReply": "Veeg van rechts naar links om te reageren", + "@swipeRightToLeftToReply": {}, + "calls": "Gesprekken", + "@calls": {}, + "customEmojisAndStickers": "Aangepaste emojis and stickers", + "@customEmojisAndStickers": {}, + "accessAndVisibilityDescription": "Wie mag meedoen in deze chat en hoe de chat ontdekt kan worden.", + "@accessAndVisibilityDescription": {}, + "customEmojisAndStickersBody": "Voeg toe of deel aangepaste emojis of stickers die gebruikt kunnen worden in elke chat.", + "@customEmojisAndStickersBody": {}, + "hideRedactedMessages": "Verberg verwijderde berichten", + "@hideRedactedMessages": {}, + "hideRedactedMessagesBody": "Als iemand een bericht verwijdert, is dit bericht niet meer zichtbaar in de chat.", + "@hideRedactedMessagesBody": {}, + "hideInvalidOrUnknownMessageFormats": "Verberg ongeldige of onbekende bericht formaten", + "@hideInvalidOrUnknownMessageFormats": {}, + "passwordRecoverySettings": "Wachtwoord herstel instellingen", + "@passwordRecoverySettings": {}, + "youInvitedToBy": "📩 Je bent uitgenodigd via een link voor:\n{alias}", + "@youInvitedToBy": { + "placeholders": { + "alias": {} + } + }, + "knock": "Kloppen", + "@knock": {}, + "overview": "Overzicht", + "@overview": {}, + "hidePresences": "Verberg Status Lijst?", + "@hidePresences": {}, + "noOneCanJoin": "Niemand kan meedoen", + "@noOneCanJoin": {}, + "yourGlobalUserIdIs": "Je globale gebruikers-ID is: ", + "@yourGlobalUserIdIs": {}, + "appLockDescription": "Vergendel de app wanneer het niet gebruikt wordt met een pin code", + "@appLockDescription": {}, + "globalChatId": "Globale chat ID", + "@globalChatId": {}, + "accessAndVisibility": "Toegang en zichtbaarheid", + "@accessAndVisibility": {}, + "invitedBy": "📩 Uitgenodigd door: {user}", + "@invitedBy": { + "placeholders": { + "user": {} + } + }, + "publicSpaces": "Publieke ruimtes", + "@publicSpaces": {}, + "blockUsername": "Negeer gebruikersnaam", + "@blockUsername": {}, + "publicChatAddresses": "Publieke chat adressen", + "@publicChatAddresses": {}, + "createNewAddress": "Creëer nieuw adres", + "@createNewAddress": {}, + "countChatsAndCountParticipants": "{chats} chats en {participants} deelnemers", + "@countChatsAndCountParticipants": { + "type": "text", + "placeholders": { + "chats": {}, + "participants": {} + } + }, + "noMoreChatsFound": "Geen chats gevonden...", + "@noMoreChatsFound": {}, + "joinedChats": "Deelnemende chats", + "@joinedChats": {}, + "knocking": "Kloppen", + "@knocking": {}, + "space": "Ruimte", + "@space": {}, + "spaces": "Ruimtes", + "@spaces": {}, + "unread": "Zet als ongelezen", + "@unread": {} } From c0c27ffce33b44b2ac6a1faf1cef2374a5408f4f Mon Sep 17 00:00:00 2001 From: Jelv Date: Tue, 6 Aug 2024 21:40:02 +0000 Subject: [PATCH 188/288] Translated using Weblate (Dutch) Currently translated at 87.1% (572 of 656 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/nl/ --- assets/l10n/intl_nl.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/l10n/intl_nl.arb b/assets/l10n/intl_nl.arb index 3846eeec80..729d1bbcff 100644 --- a/assets/l10n/intl_nl.arb +++ b/assets/l10n/intl_nl.arb @@ -2449,7 +2449,7 @@ "@hideRedactedMessages": {}, "hideRedactedMessagesBody": "Als iemand een bericht verwijdert, is dit bericht niet meer zichtbaar in de chat.", "@hideRedactedMessagesBody": {}, - "hideInvalidOrUnknownMessageFormats": "Verberg ongeldige of onbekende bericht formaten", + "hideInvalidOrUnknownMessageFormats": "Verberg ongeldige of onbekende berichtformaten", "@hideInvalidOrUnknownMessageFormats": {}, "passwordRecoverySettings": "Wachtwoord herstel instellingen", "@passwordRecoverySettings": {}, From 3002a7903b512b9df6db791af0202f809f818f88 Mon Sep 17 00:00:00 2001 From: Thomas Klein Langenhorst Date: Wed, 7 Aug 2024 13:38:01 +0000 Subject: [PATCH 189/288] Translated using Weblate (Dutch) Currently translated at 90.8% (596 of 656 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/nl/ --- assets/l10n/intl_nl.arb | 105 +++++++++++++++++++++++++++++++++++----- 1 file changed, 94 insertions(+), 11 deletions(-) diff --git a/assets/l10n/intl_nl.arb b/assets/l10n/intl_nl.arb index 729d1bbcff..e1fae1e916 100644 --- a/assets/l10n/intl_nl.arb +++ b/assets/l10n/intl_nl.arb @@ -2313,7 +2313,7 @@ "@replace": {}, "report": "rapporteer", "@report": {}, - "reportErrorDescription": "Oh nee. Er is iets misgegaan. Probeer het later nog eens. Als je wilt, kun je de bug rapporteren aan de ontwikkelaars.", + "reportErrorDescription": "😭 Oh nee. Er is iets misgegaan. Probeer het later nog eens. Als je wilt, kun je de bug rapporteren aan de ontwikkelaars.", "@reportErrorDescription": {}, "sendTypingNotifications": "Typemeldingen verzenden", "@sendTypingNotifications": {}, @@ -2349,7 +2349,7 @@ "@inviteContactToGroupQuestion": {}, "optionalRedactReason": "(Optioneel) Reden voor aanpassing van dit bericht...", "@optionalRedactReason": {}, - "addChatDescription": "Voeg een chatbeschrijving toe", + "addChatDescription": "Voeg een chatbeschrijving toe...", "@addChatDescription": {}, "invalidServerName": "Foute servernaam", "@invalidServerName": {}, @@ -2422,18 +2422,18 @@ "@alwaysUse24HourFormat": { "description": "Set to true to always display time of day in 24 hour format." }, - "joinSpace": "Deelname aan ruimte", + "joinSpace": "Deelname aan space", "@joinSpace": {}, "block": "Blokkeren", "@block": {}, - "blockedUsers": "Geblokkeerde gebruikers", + "blockedUsers": "Geblokkeerde personen", "@blockedUsers": {}, "presenceStyle": "Aanwezigheid:", "@presenceStyle": { "type": "text", "placeholders": {} }, - "searchChatsRooms": "Zoek naar #chats, @gebruikers...", + "searchChatsRooms": "Zoek naar #chats, @personen...", "@searchChatsRooms": {}, "swipeRightToLeftToReply": "Veeg van rechts naar links om te reageren", "@swipeRightToLeftToReply": {}, @@ -2465,11 +2465,11 @@ "@overview": {}, "hidePresences": "Verberg Status Lijst?", "@hidePresences": {}, - "noOneCanJoin": "Niemand kan meedoen", + "noOneCanJoin": "Niemand kan deelnemen", "@noOneCanJoin": {}, "yourGlobalUserIdIs": "Je globale gebruikers-ID is: ", "@yourGlobalUserIdIs": {}, - "appLockDescription": "Vergendel de app wanneer het niet gebruikt wordt met een pin code", + "appLockDescription": "Vergendel de app wanneer het niet gebruikt wordt met een pincode", "@appLockDescription": {}, "globalChatId": "Globale chat ID", "@globalChatId": {}, @@ -2481,7 +2481,7 @@ "user": {} } }, - "publicSpaces": "Publieke ruimtes", + "publicSpaces": "Publieke spaces", "@publicSpaces": {}, "blockUsername": "Negeer gebruikersnaam", "@blockUsername": {}, @@ -2503,10 +2503,93 @@ "@joinedChats": {}, "knocking": "Kloppen", "@knocking": {}, - "space": "Ruimte", + "space": "Space", "@space": {}, - "spaces": "Ruimtes", + "spaces": "Spaces", "@spaces": {}, "unread": "Zet als ongelezen", - "@unread": {} + "@unread": {}, + "databaseBuildErrorBody": "Het aanmaken van de SQlite database is mislukt. De app probeert nu een traditionele database te gebruiken. Meldt alsjeblieft deze fout aan de ontwikkelaars via deze {url}. De foutmelding is: {error}", + "@databaseBuildErrorBody": { + "type": "text", + "placeholders": { + "url": {}, + "error": {} + } + }, + "groupName": "Groepsnaam", + "@groupName": {}, + "changeGeneralChatSettings": "Wijzig algemene chat instellingen", + "@changeGeneralChatSettings": {}, + "restricted": "Beperkt", + "@restricted": {}, + "searchForUsers": "Zoek naar @personen...", + "@searchForUsers": {}, + "searchMore": "Zoek meer...", + "@searchMore": {}, + "noPublicLinkHasBeenCreatedYet": "Publieke link is nog niet gecreëerd", + "@noPublicLinkHasBeenCreatedYet": {}, + "groupCanBeFoundViaSearch": "Groep kan gevonden worden via zoeken", + "@groupCanBeFoundViaSearch": {}, + "searchIn": "Zoek in chat \"{chat}\"...", + "@searchIn": { + "type": "text", + "placeholders": { + "chat": {} + } + }, + "files": "Bestanden", + "@files": {}, + "unreadChatsInApp": "{appname}: {unread} ongelezen chats", + "@unreadChatsInApp": { + "type": "text", + "placeholders": { + "appname": {}, + "unread": {} + } + }, + "noDatabaseEncryption": "Database versleuteling is niet ondersteund op dit platform", + "@noDatabaseEncryption": {}, + "thereAreCountUsersBlocked": "Nu zijn er {count} personen geblokkeerd.", + "@thereAreCountUsersBlocked": { + "type": "text", + "count": {} + }, + "markAsUnread": "Markeer als ongelezen", + "@markAsUnread": {}, + "userLevel": "{level} - Persoon", + "@userLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "moderatorLevel": "{level} - Moderator", + "@moderatorLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "adminLevel": "{level} - Administrator", + "@adminLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "stickers": "Stickers", + "@stickers": {}, + "nothingFound": "Niets gevonden...", + "@nothingFound": {}, + "gallery": "Gallerij", + "@gallery": {}, + "transparent": "Transparant", + "@transparent": {}, + "incomingMessages": "Inkomende berichten", + "@incomingMessages": {}, + "discover": "Ontdek", + "@discover": {}, + "commandHint_ignore": "Negeer de gegeven matrix ID", + "@commandHint_ignore": {} } From 0d3e6178241f42930531cae06dafc2ca92ac11fd Mon Sep 17 00:00:00 2001 From: Jelv Date: Tue, 6 Aug 2024 21:41:10 +0000 Subject: [PATCH 190/288] Translated using Weblate (Dutch) Currently translated at 90.8% (596 of 656 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/nl/ --- assets/l10n/intl_nl.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/l10n/intl_nl.arb b/assets/l10n/intl_nl.arb index e1fae1e916..9bc789c007 100644 --- a/assets/l10n/intl_nl.arb +++ b/assets/l10n/intl_nl.arb @@ -2418,7 +2418,7 @@ "@pleaseEnterANumber": {}, "kickUserDescription": "De persoon is verwijderd uit de chat, maar is niet verbannen. In publieke chats kan de persoon op elk moment opnieuw deelnemen.", "@kickUserDescription": {}, - "alwaysUse24HourFormat": "fout", + "alwaysUse24HourFormat": "true", "@alwaysUse24HourFormat": { "description": "Set to true to always display time of day in 24 hour format." }, From 57c65a46897470ffa9bf820ce73e10934a0c1013 Mon Sep 17 00:00:00 2001 From: Lukas Date: Thu, 8 Aug 2024 08:40:23 +0000 Subject: [PATCH 191/288] Translated using Weblate (Chinese (Traditional)) Currently translated at 98.4% (646 of 656 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hant/ --- assets/l10n/intl_zh_Hant.arb | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/assets/l10n/intl_zh_Hant.arb b/assets/l10n/intl_zh_Hant.arb index 1640033942..9570c7214a 100644 --- a/assets/l10n/intl_zh_Hant.arb +++ b/assets/l10n/intl_zh_Hant.arb @@ -2736,5 +2736,32 @@ "appname": {}, "unread": {} } + }, + "adminLevel": "{level} - 管理員", + "@adminLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "userLevel": "{level} - 用戶", + "@userLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "moderatorLevel": "{level} - 管理員", + "@moderatorLevel": { + "type": "text", + "placeholders": { + "level": {} + } + }, + "invitedBy": "📩 由 {user} 邀請", + "@invitedBy": { + "placeholders": { + "user": {} + } } } From 02430a952da3ef513943f898fe8f066daf005079 Mon Sep 17 00:00:00 2001 From: Krille Date: Fri, 9 Aug 2024 11:35:11 +0200 Subject: [PATCH 192/288] build: Update to Matrix SDK 0.32.0 --- assets/l10n/intl_en.arb | 3 ++- lib/utils/matrix_sdk_extensions/matrix_locals.dart | 3 +++ pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 1ec5f83277..8441314e14 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -2754,5 +2754,6 @@ "version": {} } }, - "changelog": "Changelog" + "changelog": "Changelog", + "sendCanceled": "Sending canceled" } diff --git a/lib/utils/matrix_sdk_extensions/matrix_locals.dart b/lib/utils/matrix_sdk_extensions/matrix_locals.dart index cbba6b6302..a492b87ee9 100644 --- a/lib/utils/matrix_sdk_extensions/matrix_locals.dart +++ b/lib/utils/matrix_sdk_extensions/matrix_locals.dart @@ -347,4 +347,7 @@ class MatrixLocals extends MatrixLocalizations { @override String invitedBy(String senderName) => l10n.invitedBy(senderName); + + @override + String get cancelledSend => l10n.sendCanceled; } diff --git a/pubspec.lock b/pubspec.lock index 0979b4a90a..7632df4b46 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1210,10 +1210,10 @@ packages: dependency: "direct main" description: name: matrix - sha256: d1955846aaf5a5c6d353a90ce4133b9c99581cd64f4fe9e389e5f8b95157ca3b + sha256: "4357245df2a64c435456d1faee55cb33a9fd30aa0df97aacd6abd52b68f70aa1" url: "https://pub.dev" source: hosted - version: "0.31.0" + version: "0.32.0" meta: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 9d00c0d9d6..93ea6e2e98 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -64,7 +64,7 @@ dependencies: keyboard_shortcuts: ^0.1.4 latlong2: ^0.9.1 linkify: ^5.0.0 - matrix: ^0.31.0 + matrix: ^0.32.0 native_imaging: ^0.1.1 opus_caf_converter_dart: ^1.0.1 package_info_plus: ^6.0.0 From 55196e7c9dc1d96b701ccd7580df335c2ecc31d6 Mon Sep 17 00:00:00 2001 From: "lauren n. liberda" Date: Fri, 9 Aug 2024 01:48:42 +0200 Subject: [PATCH 193/288] chore: upgrade flutter to 3.24 --- .github/workflows/versions.env | 2 +- lib/pages/chat/add_widget_tile_view.dart | 2 +- lib/pages/chat/chat_view.dart | 2 +- lib/pages/chat_list/chat_list_view.dart | 2 +- pubspec.lock | 40 ++++++++++++------------ pubspec.yaml | 2 +- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/.github/workflows/versions.env b/.github/workflows/versions.env index eaa595930e..8bc2c377f1 100644 --- a/.github/workflows/versions.env +++ b/.github/workflows/versions.env @@ -1,2 +1,2 @@ -FLUTTER_VERSION=3.22.3 +FLUTTER_VERSION=3.24.0 JAVA_VERSION=17 diff --git a/lib/pages/chat/add_widget_tile_view.dart b/lib/pages/chat/add_widget_tile_view.dart index d7ac53ef2b..18153fabaa 100644 --- a/lib/pages/chat/add_widget_tile_view.dart +++ b/lib/pages/chat/add_widget_tile_view.dart @@ -59,7 +59,7 @@ class AddWidgetTileView extends StatelessWidget { ), ), ), - ButtonBar( + OverflowBar( children: [ TextButton( onPressed: controller.addWidget, diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 422175f0fa..9b07eaf31b 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -141,7 +141,7 @@ class ChatView extends StatelessWidget { return PopScope( canPop: controller.selectedEvents.isEmpty && !controller.showEmojiPicker, - onPopInvoked: (pop) async { + onPopInvokedWithResult: (pop, _) async { if (pop) return; if (controller.selectedEvents.isNotEmpty) { controller.clearSelectedEvents(); diff --git a/lib/pages/chat_list/chat_list_view.dart b/lib/pages/chat_list/chat_list_view.dart index a106e5dd7a..71c559d702 100644 --- a/lib/pages/chat_list/chat_list_view.dart +++ b/lib/pages/chat_list/chat_list_view.dart @@ -32,7 +32,7 @@ class ChatListView extends StatelessWidget { canPop: controller.selectMode == SelectMode.normal && !controller.isSearchMode && controller.activeSpaceId == null, - onPopInvoked: (pop) { + onPopInvokedWithResult: (pop, _) { if (pop) return; if (controller.activeSpaceId != null) { controller.clearActiveSpace(); diff --git a/pubspec.lock b/pubspec.lock index 7632df4b46..eed883c848 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1090,18 +1090,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -1202,10 +1202,10 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" matrix: dependency: "direct main" description: @@ -1218,10 +1218,10 @@ packages: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mgrs_dart: dependency: transitive description: @@ -1450,10 +1450,10 @@ packages: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" platform_detect: dependency: transitive description: @@ -1991,26 +1991,26 @@ packages: dependency: transitive description: name: test - sha256: "7ee446762c2c50b3bd4ea96fe13ffac69919352bd3b4b17bac3f3465edc58073" + sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e" url: "https://pub.dev" source: hosted - version: "1.25.2" + version: "1.25.7" test_api: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" test_core: dependency: transitive description: name: test_core - sha256: "2bc4b4ecddd75309300d8096f781c0e3280ca1ef85beda558d33fcbedc2eead4" + sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.4" timezone: dependency: transitive description: @@ -2303,10 +2303,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.4" wakelock_plus: dependency: "direct main" description: @@ -2375,10 +2375,10 @@ packages: dependency: "direct overridden" description: name: win32 - sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" + sha256: "015002c060f1ae9f41a818f2d5640389cc05283e368be19dc8d77cecb43c40c9" url: "https://pub.dev" source: hosted - version: "5.5.0" + version: "5.5.3" win32_registry: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 93ea6e2e98..f850428b85 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -161,4 +161,4 @@ dependency_overrides: git: url: https://github.com/TheOneWithTheBraid/keyboard_shortcuts.git ref: null-safety - win32: 5.5.0 + win32: 5.5.3 From 89309ca15ee6364b013e03ba72230fb7206d0c08 Mon Sep 17 00:00:00 2001 From: Krille Date: Sat, 10 Aug 2024 10:23:41 +0200 Subject: [PATCH 194/288] chore: Follow up chatlist design --- lib/pages/chat_list/chat_list_body.dart | 4 ++-- lib/pages/chat_list/status_msg_list.dart | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/pages/chat_list/chat_list_body.dart b/lib/pages/chat_list/chat_list_body.dart index 2ce6446252..27a640d6a8 100644 --- a/lib/pages/chat_list/chat_list_body.dart +++ b/lib/pages/chat_list/chat_list_body.dart @@ -156,11 +156,11 @@ class ChatListViewBody extends StatelessWidget { ), if (client.rooms.isNotEmpty && !controller.isSearchMode) SizedBox( - height: 44, + height: 64, child: ListView( padding: const EdgeInsets.symmetric( horizontal: 12.0, - vertical: 6, + vertical: 16.0, ), shrinkWrap: true, scrollDirection: Axis.horizontal, diff --git a/lib/pages/chat_list/status_msg_list.dart b/lib/pages/chat_list/status_msg_list.dart index 9039f908ad..217dc3933c 100644 --- a/lib/pages/chat_list/status_msg_list.dart +++ b/lib/pages/chat_list/status_msg_list.dart @@ -124,10 +124,10 @@ class PresenceAvatar extends StatelessWidget { presence.userid; final statusMsg = presence.statusMsg; - final statusMsgBubbleElevation = - theme.appBarTheme.scrolledUnderElevation ?? 4; - final statusMsgBubbleShadowColor = theme.colorScheme.onSurface; - final statusMsgBubbleColor = Colors.white.withAlpha(245); + const statusMsgBubbleElevation = 6.0; + final statusMsgBubbleShadowColor = + Theme.of(context).colorScheme.surface; + final statusMsgBubbleColor = Colors.white.withOpacity(0.9); return Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: SizedBox( From d0e1e0229c2d6b8c00e3278d71651003614e38d2 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sat, 10 Aug 2024 19:19:00 +0200 Subject: [PATCH 195/288] chore: Follow up linter fix --- lib/pages/chat_list/status_msg_list.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/pages/chat_list/status_msg_list.dart b/lib/pages/chat_list/status_msg_list.dart index 217dc3933c..2d31537cc1 100644 --- a/lib/pages/chat_list/status_msg_list.dart +++ b/lib/pages/chat_list/status_msg_list.dart @@ -125,8 +125,7 @@ class PresenceAvatar extends StatelessWidget { final statusMsg = presence.statusMsg; const statusMsgBubbleElevation = 6.0; - final statusMsgBubbleShadowColor = - Theme.of(context).colorScheme.surface; + final statusMsgBubbleShadowColor = theme.colorScheme.surface; final statusMsgBubbleColor = Colors.white.withOpacity(0.9); return Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), From f556ac4593f7f248141c5a3af77dae1579e25b1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Bed=C3=A1=C5=88?= Date: Sat, 10 Aug 2024 05:21:01 +0000 Subject: [PATCH 196/288] Translated using Weblate (Czech) Currently translated at 78.6% (516 of 656 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/cs/ --- assets/l10n/intl_cs.arb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_cs.arb b/assets/l10n/intl_cs.arb index 34549bee4d..73ed1592ad 100644 --- a/assets/l10n/intl_cs.arb +++ b/assets/l10n/intl_cs.arb @@ -2367,5 +2367,17 @@ "accessAndVisibilityDescription": "Kdo se může připojit a najít tuto konverzaci.", "@accessAndVisibilityDescription": {}, "customEmojisAndStickersBody": "Přidat nebo sdílet vlastní emoji nebo nálepky, které mohou být použité v konverzaci.", - "@customEmojisAndStickersBody": {} + "@customEmojisAndStickersBody": {}, + "swipeRightToLeftToReply": "Potáhněte z prava do leva pro odpověď", + "@swipeRightToLeftToReply": {}, + "countChatsAndCountParticipants": "{chats} konverzaci a {participants} účastníci", + "@countChatsAndCountParticipants": { + "type": "text", + "placeholders": { + "chats": {}, + "participants": {} + } + }, + "noMoreChatsFound": "Žádné další konverzace nalezeny...", + "@noMoreChatsFound": {} } From d3298f0b09ec6a2258a841e66df473e3060a28f9 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sun, 11 Aug 2024 08:55:10 +0200 Subject: [PATCH 197/288] chore: Follow up display read marker in timeline --- lib/pages/chat/chat.dart | 38 ++++++++++++++++++----------- lib/pages/chat/chat_event_list.dart | 3 +-- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 990557ee57..a373b87eaa 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -102,7 +102,7 @@ class ChatController extends State Timeline? timeline; - String? readMarkerEventId; + late final String readMarkerEventId; String get roomId => widget.room.id; @@ -270,6 +270,7 @@ class ChatController extends State ); sendingClient = Matrix.of(context).client; + readMarkerEventId = room.hasNewMessages ? room.fullyRead : ''; WidgetsBinding.instance.addObserver(this); _tryLoadTimeline(); if (kIsWeb) { @@ -284,19 +285,22 @@ class ChatController extends State await loadTimelineFuture; if (initialEventId != null) scrollToEventId(initialEventId); - final fullyRead = room.fullyRead; - if (fullyRead.isEmpty) { - setReadMarker(); - return; - } - if (timeline?.events.any((event) => event.eventId == fullyRead) ?? - false) { - Logs().v('Scroll up to visible event', fullyRead); - scrollToEventId(fullyRead, highlightEvent: false); + final readMarkerEventIndex = readMarkerEventId.isEmpty + ? -1 + : timeline!.events + .where((e) => e.isVisibleInGui) + .toList() + .indexWhere((e) => e.eventId == readMarkerEventId); + + if (readMarkerEventIndex > 1) { + Logs().v('Scroll up to visible event', readMarkerEventId); + scrollToEventId(readMarkerEventId, highlightEvent: false); return; + } else if (readMarkerEventId.isNotEmpty && readMarkerEventIndex == -1) { + _showScrollUpMaterialBanner(readMarkerEventId); } + if (!mounted) return; - _showScrollUpMaterialBanner(fullyRead); } catch (e, s) { ErrorReporter(context, 'Unable to load timeline').onErrorCallback(e, s); rethrow; @@ -323,13 +327,16 @@ class ChatController extends State int? animateInEventIndex; void onInsert(int i) { + onChange(i); + // setState will be called by updateView() anyway + animateInEventIndex = i; + } + + void onChange(int i) { if (timeline?.events[i].status == EventStatus.synced) { final index = timeline!.events.firstIndexWhereNotError; if (i == index) setReadMarker(eventId: timeline?.events[i].eventId); } - - // setState will be called by updateView() anyway - animateInEventIndex = i; } Future _getTimeline({ @@ -347,6 +354,7 @@ class ChatController extends State onUpdate: updateView, eventContextId: eventContextId, onInsert: onInsert, + onChange: onChange, ); } catch (e, s) { Logs().w('Unable to load timeline on event ID $eventContextId', e, s); @@ -354,6 +362,7 @@ class ChatController extends State timeline = await room.getTimeline( onUpdate: updateView, onInsert: onInsert, + onChange: onChange, ); if (!mounted) return; if (e is TimeoutException || e is IOException) { @@ -380,6 +389,7 @@ class ChatController extends State if (_setReadMarkerFuture != null) return; if (_scrolledUp) return; if (scrollUpBannerEventId != null) return; + if (eventId == null && !room.hasNewMessages && room.notificationCount == 0) { diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index 2d56aa5c0e..65c920360a 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -141,8 +141,7 @@ class ChatEventList extends StatelessWidget { .any((e) => e.eventId == event.eventId), timeline: controller.timeline!, displayReadMarker: - controller.readMarkerEventId == event.eventId && - controller.timeline?.allowNewEvent == false, + i > 0 && controller.readMarkerEventId == event.eventId, nextEvent: i + 1 < events.length ? events[i + 1] : null, previousEvent: i > 0 ? events[i - 1] : null, avatarPresenceBackgroundColor: From 1c6d8a05793846a809aa83c534a2320e65420b8b Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sun, 11 Aug 2024 08:56:48 +0200 Subject: [PATCH 198/288] chore: Make VOIP plugin less noisy in logs --- lib/utils/voip_plugin.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/utils/voip_plugin.dart b/lib/utils/voip_plugin.dart index 8a71390ef7..d2ca1f999c 100644 --- a/lib/utils/voip_plugin.dart +++ b/lib/utils/voip_plugin.dart @@ -36,7 +36,6 @@ class VoipPlugin with WidgetsBindingObserver implements WebRTCDelegate { void didChangeAppLifecycleState(AppLifecycleState? state) { background = (state == AppLifecycleState.detached || state == AppLifecycleState.paused); - Logs().w('Set background mode in VOIP plugin', background); } void addCallingOverlay(String callId, CallSession call) { From 664548d937367a4e8d4f4dd8bb8c90ba3833bd2d Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sun, 11 Aug 2024 09:40:08 +0200 Subject: [PATCH 199/288] chore: Nicer empty chat list placeholder --- assets/l10n/intl_en.arb | 1 + lib/pages/chat_list/chat_list_body.dart | 99 ++++++++----------- lib/pages/chat_list/dummy_chat_list_item.dart | 72 ++++++++++++++ 3 files changed, 114 insertions(+), 58 deletions(-) create mode 100644 lib/pages/chat_list/dummy_chat_list_item.dart diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 8441314e14..aa2d054206 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -205,6 +205,7 @@ } }, "noMoreChatsFound": "No more chats found...", + "noChatsFoundHere": "No chats found here yet. Start a new chat with someone by using the button below. ⤵️", "joinedChats": "Joined chats", "unread": "Unread", "space": "Space", diff --git a/lib/pages/chat_list/chat_list_body.dart b/lib/pages/chat_list/chat_list_body.dart index 27a640d6a8..0f06fde971 100644 --- a/lib/pages/chat_list/chat_list_body.dart +++ b/lib/pages/chat_list/chat_list_body.dart @@ -7,6 +7,7 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/chat_list/chat_list.dart'; import 'package:fluffychat/pages/chat_list/chat_list_item.dart'; +import 'package:fluffychat/pages/chat_list/dummy_chat_list_item.dart'; import 'package:fluffychat/pages/chat_list/search_title.dart'; import 'package:fluffychat/pages/chat_list/space_view.dart'; import 'package:fluffychat/pages/chat_list/status_msg_list.dart'; @@ -61,8 +62,6 @@ class ChatListViewBody extends StatelessWidget { .toList(); final userSearchResult = controller.userSearchResult; const dummyChatCount = 4; - final titleColor = theme.textTheme.bodyLarge!.color!.withAlpha(100); - final subtitleColor = theme.textTheme.bodyLarge!.color!.withAlpha(50); final filter = controller.searchController.text.toLowerCase(); return StreamBuilder( key: ValueKey( @@ -238,13 +237,44 @@ class ChatListViewBody extends StatelessWidget { if (client.prevBatch != null && rooms.isEmpty && !controller.isSearchMode) ...[ - Padding( - padding: const EdgeInsets.all(32.0), - child: Icon( - CupertinoIcons.chat_bubble_2, - size: 128, - color: theme.colorScheme.secondary, - ), + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Stack( + alignment: Alignment.center, + children: [ + const Column( + mainAxisSize: MainAxisSize.min, + children: [ + DummyChatListItem( + opacity: 0.5, + animate: false, + ), + DummyChatListItem( + opacity: 0.3, + animate: false, + ), + ], + ), + Icon( + CupertinoIcons.chat_bubble_text_fill, + size: 128, + color: theme.colorScheme.secondary, + ), + ], + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + L10n.of(context)!.noChatsFoundHere, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + color: theme.colorScheme.secondary, + ), + ), + ), + ], ), ], ], @@ -253,56 +283,9 @@ class ChatListViewBody extends StatelessWidget { if (client.prevBatch == null) SliverList( delegate: SliverChildBuilderDelegate( - (context, i) => Opacity( + (context, i) => DummyChatListItem( opacity: (dummyChatCount - i) / dummyChatCount, - child: ListTile( - leading: CircleAvatar( - backgroundColor: titleColor, - child: CircularProgressIndicator( - strokeWidth: 1, - color: theme.textTheme.bodyLarge!.color, - ), - ), - title: Row( - children: [ - Expanded( - child: Container( - height: 14, - decoration: BoxDecoration( - color: titleColor, - borderRadius: BorderRadius.circular(3), - ), - ), - ), - const SizedBox(width: 36), - Container( - height: 14, - width: 14, - decoration: BoxDecoration( - color: subtitleColor, - borderRadius: BorderRadius.circular(14), - ), - ), - const SizedBox(width: 12), - Container( - height: 14, - width: 14, - decoration: BoxDecoration( - color: subtitleColor, - borderRadius: BorderRadius.circular(14), - ), - ), - ], - ), - subtitle: Container( - decoration: BoxDecoration( - color: subtitleColor, - borderRadius: BorderRadius.circular(3), - ), - height: 12, - margin: const EdgeInsets.only(right: 22), - ), - ), + animate: true, ), childCount: dummyChatCount, ), diff --git a/lib/pages/chat_list/dummy_chat_list_item.dart b/lib/pages/chat_list/dummy_chat_list_item.dart new file mode 100644 index 0000000000..8652cccd12 --- /dev/null +++ b/lib/pages/chat_list/dummy_chat_list_item.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; + +class DummyChatListItem extends StatelessWidget { + final double opacity; + final bool animate; + + const DummyChatListItem({ + required this.opacity, + required this.animate, + super.key, + }); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final titleColor = theme.textTheme.bodyLarge!.color!.withAlpha(100); + final subtitleColor = theme.textTheme.bodyLarge!.color!.withAlpha(50); + return Opacity( + opacity: opacity, + child: ListTile( + leading: CircleAvatar( + backgroundColor: titleColor, + child: animate + ? CircularProgressIndicator( + strokeWidth: 1, + color: theme.textTheme.bodyLarge!.color, + ) + : const SizedBox.shrink(), + ), + title: Row( + children: [ + Expanded( + child: Container( + height: 14, + decoration: BoxDecoration( + color: titleColor, + borderRadius: BorderRadius.circular(3), + ), + ), + ), + const SizedBox(width: 36), + Container( + height: 14, + width: 14, + decoration: BoxDecoration( + color: subtitleColor, + borderRadius: BorderRadius.circular(14), + ), + ), + const SizedBox(width: 12), + Container( + height: 14, + width: 14, + decoration: BoxDecoration( + color: subtitleColor, + borderRadius: BorderRadius.circular(14), + ), + ), + ], + ), + subtitle: Container( + decoration: BoxDecoration( + color: subtitleColor, + borderRadius: BorderRadius.circular(3), + ), + height: 12, + margin: const EdgeInsets.only(right: 22), + ), + ), + ); + } +} From 612711d60213c9152acea6567bfc9c3559ad89da Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sun, 11 Aug 2024 11:35:26 +0200 Subject: [PATCH 200/288] chore: Follow up no more chats found label --- lib/pages/chat_list/chat_list_body.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/pages/chat_list/chat_list_body.dart b/lib/pages/chat_list/chat_list_body.dart index 0f06fde971..bf23e841eb 100644 --- a/lib/pages/chat_list/chat_list_body.dart +++ b/lib/pages/chat_list/chat_list_body.dart @@ -266,7 +266,9 @@ class ChatListViewBody extends StatelessWidget { Padding( padding: const EdgeInsets.all(16.0), child: Text( - L10n.of(context)!.noChatsFoundHere, + client.rooms.isEmpty + ? L10n.of(context)!.noChatsFoundHere + : L10n.of(context)!.noMoreChatsFound, textAlign: TextAlign.center, style: TextStyle( fontSize: 18, From ebd8b700b10ee9693ad194382d2b3faeef3de892 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sun, 11 Aug 2024 12:31:10 +0200 Subject: [PATCH 201/288] chore: Show short forms of months and week days in UI --- lib/utils/date_time_extension.dart | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/lib/utils/date_time_extension.dart b/lib/utils/date_time_extension.dart index 13bae4efab..0346dd289c 100644 --- a/lib/utils/date_time_extension.dart +++ b/lib/utils/date_time_extension.dart @@ -56,19 +56,14 @@ extension DateTimeExtension on DateTime { if (sameDay) { return localizedTimeOfDay(context); } else if (sameWeek) { - return DateFormat.EEEE(Localizations.localeOf(context).languageCode) + return DateFormat.E(Localizations.localeOf(context).languageCode) .format(this); } else if (sameYear) { - return L10n.of(context)!.dateWithoutYear( - month.toString().padLeft(2, '0'), - day.toString().padLeft(2, '0'), - ); + return DateFormat.MMMd(Localizations.localeOf(context).languageCode) + .format(this); } - return L10n.of(context)!.dateWithYear( - year.toString(), - month.toString().padLeft(2, '0'), - day.toString().padLeft(2, '0'), - ); + return DateFormat.yMMMd(Localizations.localeOf(context).languageCode) + .format(this); } /// If the DateTime is today, this returns [localizedTimeOfDay()], if not it also From 6cc8f6aa9b7f3051aaf906c3ee242b24a297dbac Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sun, 11 Aug 2024 12:42:10 +0200 Subject: [PATCH 202/288] docs: Update privacy policy --- PRIVACY.md | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/PRIVACY.md b/PRIVACY.md index dd978f243c..8c1ca45e58 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -3,31 +3,29 @@ FluffyChat is available on Android, iOS and as a web version. Desktop versions for Windows, Linux and macOS may follow. * [Matrix](#matrix) -* Sentry * [Database](#database) * [Encryption](#encryption) * [App Permissions](#app-permissions) * [Push Notifications](#push-notifications) -* [Stories](#stories) ## Matrix FluffyChat uses the Matrix protocol. This means that FluffyChat is just a client that can be connected to any compatible matrix server. The respective data protection agreement of the server selected by the user then applies. For convenience, one or more servers are set as default that the FluffyChat developers consider trustworthy. The developers of FluffyChat do not guarantee their trustworthiness. Before the first communication, users are informed which server they are connecting to. -FluffyChat only communicates with the selected server, with sentry.io if enabled and with [OpenStreetMap](https://openstreetmap.org) to display maps. +FluffyChat only communicates with the selected server and with [OpenStreetMap](https://openstreetmap.org) to display maps. More information is available at: [https://matrix.org](https://matrix.org) ## Database -FluffyChat caches some data received from the server in a local database on the device of the user. +FluffyChat caches some data received from the server in a local sqflite database on the device of the user. On web indexedDB is used. FluffyChat always tries to encrypt the database by using SQLCipher and stores the encryption key in the [Secure Storage](https://pub.dev/packages/flutter_secure_storage) of the device. -More information is available at: [https://pub.dev/packages/hive](https://pub.dev/packages/hive) +More information is available at: [https://pub.dev/packages/sqflite](https://pub.dev/packages/sqflite) and [https://pub.dev/packages/sqlcipher_flutter_libs](https://pub.dev/packages/sqlcipher_flutter_libs) ## Encryption All communication of substantive content between Fluffychat and any server is done in secure way, using transport encryption to protect it. -FluffyChat is able to use End-To-End-Encryption as a tech preview. +FluffyChat also uses End-To-End-Encryption by using [libolm](https://gitlab.matrix.org/matrix-org/olm) and enables it by default for private chats. ## App Permissions @@ -94,23 +92,3 @@ A typical push notification could look like this: ``` FluffyChat sets the `event_id_only` flag at the Matrix Server. This server is then responsible to send the correct data. - -## Stories - -FluffyChat supports stories which is a feature similar to WhatsApp status or Instagram stories. However it is just a different GUI for the same room-related communication. More information about the feature can be found here: - -https://github.com/krillefear/matrix-doc/blob/main/proposals/3588-stories-as-rooms.md - -Stories are basically: - -- End to end encrypted rooms -- Read-only rooms with only one admin who can post stuff (while there is no technical limitation to have multiple admins) - -By default: - -- The user has to invite all contacts manually to a story room -- The user can only invite contacts (matrix users the user shares a DM room with) to the story room -- The story room is created when the first story is posted -- User can mute and leave story rooms - -The user is informed in the app that in theory all contacts can see each other in the story room. The user must give consent here. However the user is at any time able to create a group chat and invite all of their contacts to this chat in any matrix client which has the same result. From 28473cda605b3e0f5ed362b786817bb8012ab2e9 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sun, 11 Aug 2024 15:12:11 +0200 Subject: [PATCH 203/288] build: Bump version to v1.22.0 --- CHANGELOG.md | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++ pubspec.yaml | 2 +- 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 627272439a..7132708af8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,88 @@ +## v1.22.0 + +FluffyChat v1.22.0 brings a new design for spaces, replaces the bottom navigation bar with filter chips and makes it finally possible to play ogg audio messages on iOS. A lot of other fixes and improvements have also been added to this release. + +- build: (deps): bump docker/build-push-action from 5 to 6 (dependabot[bot]) +- build(deps): bump rexml from 3.2.8 to 3.3.3 in /ios (dependabot[bot]) +- build: Remove permissions for screensharing until it is fixed (Krille) +- build: Update android target sdk to 34 (Krille) +- build: Update dependencies after release (krille-chan) +- build: Update to Flutter 3.22.3 (krille-chan) +- build: Update to Matrix SDK 0.32.0 (Krille) +- chore: Bring back add to space feature (Krille) +- chore: Bring back navrail (krille-chan) +- chore: Bring back separate chat types (krille-chan) +- chore: Chat permissions page follow up (krille-chan) +- chore: Do not hide error on file sending (Krille) +- chore: Improved create group and space design (Krille) +- chore: Make VOIP plugin less noisy in logs (krille-chan) +- chore: Move default PR template to correct dir (krille-chan) +- chore: nicer bottom sheets (krille-chan) +- chore: Nicer empty chat list placeholder (krille-chan) +- chore: Polish public room bottom sheet (krille-chan) +- chore: Show short forms of months and week days in UI (krille-chan) +- chore: Sligthly improve chat permissions page design (krille-chan) +- design: Add snackbar with link to changelog on new version (Krille) +- docs: Update privacy policy (krille-chan) +- feat: Convert opus to aac on iOS before playing (Krille) +- feat: New spaces and chat list design (krille-chan) +- feat: Record voice message with opus/ogg if supported (Krille) +- feat: Send voice messages from web (Krille) +- fix: Display only available join rules (Krille) +- fix: Path correct userId to ignore list (krille-chan) +- fix: Scroll to event missing the position (Krille) +- Fix web base url and privacy url configuration processing (dlyrsk) +- refactor: Clean up some widths (krille-chan) +- refactor: Design polishment and better user viewer (Krille) +- refactor: Migrate android gradle plugin (Krille) +- refactor: Only initialize FlutterLocalNotificationsPlugin once (krille-chan) +- refactor: Recording dialog (Krille) +- Refactor: Reduce .of(context) calls theme (Thomas Klein Langenhorst) +- refactor: Use cached network image for mxc image uris (Krille) +- Translated using Weblate (Arabic) (kdh8219) +- Translated using Weblate (Arabic) (Rex_sa) +- Translated using Weblate (Basque) (kdh8219) +- Translated using Weblate (Basque) (xabirequejo) +- Translated using Weblate (Chinese (Simplified)) (kdh8219) +- Translated using Weblate (Chinese (Simplified)) (大王叫我来巡山) +- Translated using Weblate (Chinese (Traditional)) (kdh8219) +- Translated using Weblate (Chinese (Traditional)) (Lukas) +- Translated using Weblate (Chinese (Traditional)) (Ricky From Hong Kong) +- Translated using Weblate (Chinese (Traditional)) (不知火 Shiranui) +- Translated using Weblate (Croatian) (Milo Ivir) +- Translated using Weblate (Czech) (Anonymous) +- Translated using Weblate (Czech) (Michal Bedáň) +- Translated using Weblate (Dutch) (Guacamolie) +- Translated using Weblate (Dutch) (Jelv) +- Translated using Weblate (Dutch) (Thomas Klein Langenhorst) +- Translated using Weblate (Esperanto) (Anonymous) +- Translated using Weblate (Estonian) (kdh8219) +- Translated using Weblate (Estonian) (Priit Jõerüüt) +- Translated using Weblate (Finnish) (Anonymous) +- Translated using Weblate (French) (Sovkipyk) +- Translated using Weblate (Galician) (josé m) +- Translated using Weblate (German) (Christian) +- Translated using Weblate (German) (Pixelcode) +- Translated using Weblate (German) (tct123) +- Translated using Weblate (Hebrew) (Anonymous) +- Translated using Weblate (Indonesian) (Linerly) +- Translated using Weblate (Irish) (Anonymous) +- Translated using Weblate (Japanese) (Anonymous) +- Translated using Weblate (Korean) (kdh8219) +- Translated using Weblate (Lithuanian) (Anonymous) +- Translated using Weblate (Norwegian Bokmål) (Anonymous) +- Translated using Weblate (Persian) (Anonymous) +- Translated using Weblate (Portuguese (Portugal)) (Anonymous) +- Translated using Weblate (Romanian) (Anonymous) +- Translated using Weblate (Russian) (-) +- Translated using Weblate (Serbian) (Anonymous) +- Translated using Weblate (Slovenian) (Anonymous) +- Translated using Weblate (Spanish) (Anonymous) +- Translated using Weblate (Turkish) (kdh8219) +- Translated using Weblate (Turkish) (Oğuz Ersen) +- Translated using Weblate (Ukrainian) (Bezruchenko Simon) +- Translated using Weblate (Ukrainian) (Ihor Hordiichuk) + ## v1.21.2 Updates the Matrix Dart SDK to fix some minor bugs. diff --git a/pubspec.yaml b/pubspec.yaml index 93ea6e2e98..5e6d4a25d9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: fluffychat description: Chat with your friends. publish_to: none # On version bump also increase the build number for F-Droid -version: 1.21.2+3534 +version: 1.22.0+3535 environment: sdk: ">=3.0.0 <4.0.0" From 8784acc7e6202d2a5d629fbec0014794c30a204e Mon Sep 17 00:00:00 2001 From: Krille Date: Mon, 12 Aug 2024 09:00:10 +0200 Subject: [PATCH 204/288] chore: Disable record on web --- lib/utils/platform_infos.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/utils/platform_infos.dart b/lib/utils/platform_infos.dart index 638c5bcc2f..d43176d7ad 100644 --- a/lib/utils/platform_infos.dart +++ b/lib/utils/platform_infos.dart @@ -29,7 +29,8 @@ abstract class PlatformInfos { static bool get usesTouchscreen => !isMobile; - static bool get platformCanRecord => (isMobile || isMacOS || isWeb); + /// Web could also record in theory but currently only wav which is too large + static bool get platformCanRecord => (isMobile || isMacOS); static String get clientName => '${AppConfig.applicationName} ${isWeb ? 'web' : Platform.operatingSystem}${kReleaseMode ? '' : 'Debug'}'; From 9089f89cff32af428ef534e7af13f142df420396 Mon Sep 17 00:00:00 2001 From: Rex_sa Date: Sun, 11 Aug 2024 18:14:52 +0000 Subject: [PATCH 205/288] Translated using Weblate (Arabic) Currently translated at 100.0% (657 of 657 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ar/ --- assets/l10n/intl_ar.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_ar.arb b/assets/l10n/intl_ar.arb index 6ad065889e..59859a8a91 100644 --- a/assets/l10n/intl_ar.arb +++ b/assets/l10n/intl_ar.arb @@ -2789,5 +2789,7 @@ "placeholders": { "version": {} } - } + }, + "sendCanceled": "تم إلغاء الإرسال", + "@sendCanceled": {} } From 885f84a84e5366cde0ea1cb670fbcad2e54db40f Mon Sep 17 00:00:00 2001 From: xabirequejo Date: Sun, 11 Aug 2024 10:38:45 +0000 Subject: [PATCH 206/288] Translated using Weblate (Basque) Currently translated at 100.0% (657 of 657 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/ --- assets/l10n/intl_eu.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_eu.arb b/assets/l10n/intl_eu.arb index 64765054d5..39b93f6f1e 100644 --- a/assets/l10n/intl_eu.arb +++ b/assets/l10n/intl_eu.arb @@ -2789,5 +2789,7 @@ "changelog": "Aldaketak", "@changelog": {}, "chatPermissionsDescription": "Definitu zer botere-maila behar den txat honetako ekintza jakinetarako. 0, 50 eta 100 botere-mailek erabiltzaileak, moderatzaileak eta administratzaileak ordezkatzen dituzte, baina edozein graduazio posible da.", - "@chatPermissionsDescription": {} + "@chatPermissionsDescription": {}, + "sendCanceled": "Bidalketa bertan behera utzi da", + "@sendCanceled": {} } From 50f3fe26d9d1ce745e66e6ab93eff0c5c5daf655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Sun, 11 Aug 2024 07:26:21 +0000 Subject: [PATCH 207/288] Translated using Weblate (Turkish) Currently translated at 100.0% (657 of 657 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/tr/ --- assets/l10n/intl_tr.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_tr.arb b/assets/l10n/intl_tr.arb index dbb60c3d8d..ec387220af 100644 --- a/assets/l10n/intl_tr.arb +++ b/assets/l10n/intl_tr.arb @@ -2789,5 +2789,7 @@ "placeholders": { "version": {} } - } + }, + "sendCanceled": "Gönderme iptal edildi", + "@sendCanceled": {} } From c6ab9e9300506eb97bace28933fbe8f02582f812 Mon Sep 17 00:00:00 2001 From: Bezruchenko Simon Date: Sun, 11 Aug 2024 18:08:44 +0000 Subject: [PATCH 208/288] Translated using Weblate (Ukrainian) Currently translated at 100.0% (657 of 657 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/uk/ --- assets/l10n/intl_uk.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_uk.arb b/assets/l10n/intl_uk.arb index 4a70f11aec..7c39b63d5d 100644 --- a/assets/l10n/intl_uk.arb +++ b/assets/l10n/intl_uk.arb @@ -2789,5 +2789,7 @@ "joinedChats": "Приєднані чати", "@joinedChats": {}, "unread": "Непрочитані", - "@unread": {} + "@unread": {}, + "sendCanceled": "Надсилання скасовано", + "@sendCanceled": {} } From 2615632881f80b7aea3b3c02f9573b80be1bf670 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Mon, 12 Aug 2024 17:54:02 +0200 Subject: [PATCH 209/288] chore: Remove emoji proposals feature --- lib/pages/chat/reactions_picker.dart | 10 +--------- pubspec.lock | 24 ------------------------ pubspec.yaml | 1 - 3 files changed, 1 insertion(+), 34 deletions(-) diff --git a/lib/pages/chat/reactions_picker.dart b/lib/pages/chat/reactions_picker.dart index 4488e05df7..2610a0d509 100644 --- a/lib/pages/chat/reactions_picker.dart +++ b/lib/pages/chat/reactions_picker.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:emoji_proposal/emoji_proposal.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; @@ -33,14 +32,7 @@ class ReactionsPicker extends StatelessWidget { if (!display) { return const SizedBox.shrink(); } - final proposals = proposeEmojis( - controller.selectedEvents.first.plaintextBody, - number: 25, - languageCodes: EmojiProposalLanguageCodes.values.toSet(), - ); - final emojis = proposals.isNotEmpty - ? proposals.map((e) => e.char).toList() - : List.from(AppEmojis.emojis); + final emojis = List.from(AppEmojis.emojis); final allReactionEvents = controller.selectedEvents.first .aggregatedEvents( controller.timeline!, diff --git a/pubspec.lock b/pubspec.lock index 7632df4b46..2163a8d874 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -337,14 +337,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" - emoji_proposal: - dependency: "direct main" - description: - name: emoji_proposal - sha256: e931bc42b54a65397b3df7915bb58ee7dcbd3ed81c3b8c256b9a5b210e94ea63 - url: "https://pub.dev" - source: hosted - version: "0.0.1" emojis: dependency: "direct main" description: @@ -1670,14 +1662,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.3" - remove_emoji: - dependency: transitive - description: - name: remove_emoji - sha256: ed9e8463e8c9ca05b86fcddd4c0dbd2c2605a50d267f4ffa05496607924809e3 - url: "https://pub.dev" - source: hosted - version: "0.0.10" retry: dependency: transitive description: @@ -1718,14 +1702,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.2" - sentiment_dart: - dependency: transitive - description: - name: sentiment_dart - sha256: ddac8742cf5141f531eb1510b074ce715b9958cb02a763a4cc0a918768e4a0c8 - url: "https://pub.dev" - source: hosted - version: "0.0.5" share_plus: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 5e6d4a25d9..d256a852f2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,7 +23,6 @@ dependencies: device_info_plus: ^10.0.1 dynamic_color: ^1.7.0 emoji_picker_flutter: ^2.1.1 - emoji_proposal: ^0.0.1 emojis: ^0.9.9 #fcm_shared_isolate: ^0.1.0 file_picker: ^8.0.6 From 156f38cf4656e5b98a28f78fbe4496e35591f276 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Mon, 12 Aug 2024 18:43:12 +0200 Subject: [PATCH 210/288] chore: Follow up fix google services patch --- scripts/enable-android-google-services.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/enable-android-google-services.patch b/scripts/enable-android-google-services.patch index 7c783015cd..4ec4b8f13c 100644 --- a/scripts/enable-android-google-services.patch +++ b/scripts/enable-android-google-services.patch @@ -126,8 +126,8 @@ index 69c80d6e..efd32d89 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -25,7 +25,7 @@ dependencies: + dynamic_color: ^1.7.0 emoji_picker_flutter: ^2.1.1 - emoji_proposal: ^0.0.1 emojis: ^0.9.9 - #fcm_shared_isolate: ^0.1.0 + fcm_shared_isolate: ^0.1.0 From 75234e6a6f3aac3e0b610199f8e0fa3cdce5bd00 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 14 Aug 2024 09:07:10 -0400 Subject: [PATCH 211/288] inital work for having a 2-stage progress bar and saving draft analytics locally --- lib/pages/chat/chat_view.dart | 12 +- lib/pages/chat_list/chat_list.dart | 2 + .../controllers/choreographer.dart | 1 + lib/pangea/choreographer/widgets/it_bar.dart | 47 ++-- lib/pangea/constants/analytics_constants.dart | 3 + .../controllers/get_analytics_controller.dart | 123 +++++++-- .../controllers/my_analytics_controller.dart | 165 +++++++++--- lib/pangea/controllers/pangea_controller.dart | 13 + .../analytics/construct_list_model.dart | 27 +- lib/pangea/models/igc_text_data_model.dart | 49 +--- lib/pangea/models/span_data.dart | 27 +- lib/pangea/utils/logout.dart | 4 - .../widgets/animations/gain_points.dart | 103 ++++++++ .../progress_bar/animated_level_dart.dart | 87 +++++++ .../animations/progress_bar/level_bar.dart | 59 +++++ .../animations/progress_bar/progress_bar.dart | 36 +++ .../progress_bar/progress_bar_background.dart | 30 +++ .../progress_bar/progress_bar_details.dart | 23 ++ .../learning_progress_indicators.dart | 244 +++++++----------- lib/pangea/widgets/igc/span_card.dart | 143 +++++----- lib/pangea/widgets/igc/span_data.dart | 184 ------------- .../user_settings/p_language_dialog.dart | 32 ++- 22 files changed, 858 insertions(+), 556 deletions(-) create mode 100644 lib/pangea/constants/analytics_constants.dart create mode 100644 lib/pangea/widgets/animations/gain_points.dart create mode 100644 lib/pangea/widgets/animations/progress_bar/animated_level_dart.dart create mode 100644 lib/pangea/widgets/animations/progress_bar/level_bar.dart create mode 100644 lib/pangea/widgets/animations/progress_bar/progress_bar.dart create mode 100644 lib/pangea/widgets/animations/progress_bar/progress_bar_background.dart create mode 100644 lib/pangea/widgets/animations/progress_bar/progress_bar_details.dart delete mode 100644 lib/pangea/widgets/igc/span_data.dart diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 405e164187..4a7d288d86 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -12,6 +12,7 @@ import 'package:fluffychat/pages/chat/reply_display.dart'; import 'package:fluffychat/pangea/choreographer/widgets/it_bar.dart'; import 'package:fluffychat/pangea/choreographer/widgets/start_igc_button.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; +import 'package:fluffychat/pangea/widgets/animations/gain_points.dart'; import 'package:fluffychat/pangea/widgets/chat/chat_floating_action_button.dart'; import 'package:fluffychat/utils/account_config.dart'; import 'package:fluffychat/widgets/chat_settings_popup_menu.dart'; @@ -466,8 +467,15 @@ class ChatView extends StatelessWidget { StartIGCButton( controller: controller, ), - ChatFloatingActionButton( - controller: controller, + Row( + children: [ + const PointsGainedAnimation( + color: Colors.blue, + ), + ChatFloatingActionButton( + controller: controller, + ), + ], ), ], ), diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index cea2e511aa..6148573b89 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -931,6 +931,8 @@ class ChatListController extends State } // #Pangea + MatrixState.pangeaController.myAnalytics.initialize(); + MatrixState.pangeaController.analytics.initialize(); await _initPangeaControllers(client); // Pangea# if (!mounted) return; diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index 1a11de0e3f..bafd1bbefd 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -411,6 +411,7 @@ class Choreographer { choreoRecord = ChoreoRecord.newRecord; itController.clear(); igc.clear(); + pangeaController.myAnalytics.clearDraftConstructUses(roomId); // errorService.clear(); _resetDebounceTimer(); } diff --git a/lib/pangea/choreographer/widgets/it_bar.dart b/lib/pangea/choreographer/widgets/it_bar.dart index 28b0f8bd81..536fd4610a 100644 --- a/lib/pangea/choreographer/widgets/it_bar.dart +++ b/lib/pangea/choreographer/widgets/it_bar.dart @@ -318,6 +318,26 @@ class ITChoices extends StatelessWidget { ); } + void selectContinuance(int index, BuildContext context) { + final Continuance continuance = + controller.currentITStep!.continuances[index]; + if (continuance.level == 1 || continuance.wasClicked) { + Future.delayed( + const Duration(milliseconds: 500), + () => controller.selectTranslation(index), + ); + } else { + showCard( + context, + index, + continuance.level == 2 ? ChoreoConstants.yellow : ChoreoConstants.red, + continuance.feedbackText(context), + ); + } + controller.currentITStep!.continuances[index].wasClicked = true; + controller.choreographer.setState(); + } + @override Widget build(BuildContext context) { try { @@ -342,31 +362,8 @@ class ITChoices extends StatelessWidget { return Choice(text: "error", color: Colors.red); } }).toList(), - onPressed: (int index) { - final Continuance continuance = - controller.currentITStep!.continuances[index]; - debugPrint("is gold? ${continuance.gold}"); - if (continuance.level == 1 || continuance.wasClicked) { - Future.delayed( - const Duration(milliseconds: 500), - () => controller.selectTranslation(index), - ); - } else { - showCard( - context, - index, - continuance.level == 2 - ? ChoreoConstants.yellow - : ChoreoConstants.red, - continuance.feedbackText(context), - ); - } - controller.currentITStep!.continuances[index].wasClicked = true; - controller.choreographer.setState(); - }, - onLongPress: (int index) { - showCard(context, index); - }, + onPressed: (int index) => selectContinuance(index, context), + onLongPress: (int index) => showCard(context, index), uniqueKeyForLayerLink: (int index) => "itChoices$index", selectedChoiceIndex: null, ); diff --git a/lib/pangea/constants/analytics_constants.dart b/lib/pangea/constants/analytics_constants.dart new file mode 100644 index 0000000000..1608ef4225 --- /dev/null +++ b/lib/pangea/constants/analytics_constants.dart @@ -0,0 +1,3 @@ +class AnalyticsConstants { + static const int xpPerLevel = 2000; +} diff --git a/lib/pangea/controllers/get_analytics_controller.dart b/lib/pangea/controllers/get_analytics_controller.dart index 431cf69ee5..dff3bee90a 100644 --- a/lib/pangea/controllers/get_analytics_controller.dart +++ b/lib/pangea/controllers/get_analytics_controller.dart @@ -1,33 +1,122 @@ import 'dart:async'; +import 'package:fluffychat/pangea/constants/analytics_constants.dart'; import 'package:fluffychat/pangea/constants/class_default_values.dart'; import 'package:fluffychat/pangea/constants/local.key.dart'; import 'package:fluffychat/pangea/constants/match_rule_ids.dart'; +import 'package:fluffychat/pangea/controllers/my_analytics_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; +import 'package:fluffychat/pangea/models/analytics/construct_list_model.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_event.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:flutter/material.dart'; import 'package:matrix/matrix.dart'; +import 'package:matrix/src/utils/cached_stream_controller.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; /// A minimized version of AnalyticsController that get the logged in user's analytics class GetAnalyticsController { late PangeaController _pangeaController; final List _cache = []; + StreamSubscription? _analyticsUpdateSubscription; + CachedStreamController> analyticsStream = + CachedStreamController>(); + + /// The previous XP points of the user, before the last update. + /// Used for animating analytics updates. + int? prevXP; GetAnalyticsController(PangeaController pangeaController) { _pangeaController = pangeaController; } String? get l2Code => _pangeaController.languageController.userL2?.langCode; - Client get client => _pangeaController.matrixState.client; - /// A local cache of eventIds and construct uses for messages sent since the last update + int get currentXP => calcXP(allConstructUses); + int get localXP => calcXP(locallyCachedConstructs); + int get serverXP => currentXP - localXP; + + /// Get the current level based on the number of xp points + int get level => currentXP ~/ AnalyticsConstants.xpPerLevel; + + void initialize() { + _analyticsUpdateSubscription ??= _pangeaController + .myAnalytics.analyticsUpdateStream.stream + .listen(onAnalyticsUpdate); + + _pangeaController.myAnalytics.lastUpdatedCompleter.future.then((_) { + getConstructs().then((_) => updateAnalyticsStream()); + }); + } + + /// Clear all cached analytics data. + void dispose() { + _analyticsUpdateSubscription?.cancel(); + _analyticsUpdateSubscription = null; + _cache.clear(); + } + + Future onAnalyticsUpdate(AnalyticsUpdateType type) async { + if (type == AnalyticsUpdateType.server) { + await getConstructs(forceUpdate: true); + } + updateAnalyticsStream(); + } + + void updateAnalyticsStream() { + // if there are no construct uses, or if the last update in this + // stream has the same length as this update, don't update the stream + if (allConstructUses.isEmpty || + allConstructUses.length == analyticsStream.value?.length) { + return; + } + + // set the previous XP to the currentXP + if (analyticsStream.value != null) { + prevXP = calcXP(analyticsStream.value!); + } + + // finally, add to the stream + analyticsStream.add(allConstructUses); + } + + /// Calculates the user's xpPoints for their current L2, + /// based on matrix analytics event and locally cached data. + /// Has to be async because cached matrix events may be out of date, + /// and updating those is async. + int calcXP(List constructs) { + final words = ConstructListModel( + uses: constructs, + type: ConstructTypeEnum.vocab, + ); + final errors = ConstructListModel( + uses: constructs, + type: ConstructTypeEnum.grammar, + ); + return words.points + errors.points; + } + + List get allConstructUses { + final List storedUses = getConstructsLocal() ?? []; + final List localUses = locallyCachedConstructs; + + final List allConstructs = [ + ...storedUses, + ...localUses, + ]; + + return allConstructs; + } + + /// A local cache of eventIds and construct uses for messages sent since the last update. + /// It's a map of eventIDs to a list of OneConstructUses. Not just a list of OneConstructUses + /// because, with practice activity constructs, we might need to add to the list for a given + /// eventID. Map> get messagesSinceUpdate { try { final dynamic locallySaved = _pangeaController.pStoreService.read( @@ -61,29 +150,34 @@ class GetAnalyticsController { } } + /// A flat list of all locally cached construct uses + List get locallyCachedConstructs => + messagesSinceUpdate.values.expand((e) => e).toList(); + + /// A flat list of all locally cached construct uses that are not drafts + List get locallyCachedSentConstructs => + messagesSinceUpdate.entries + .where((entry) => !entry.key.startsWith('draft')) + .expand((e) => e.value) + .toList(); + /// Get a list of all constructs used by the logged in user in their current L2 Future> getConstructs({ bool forceUpdate = false, ConstructTypeEnum? constructType, }) async { - debugPrint("getting constructs"); + // if the user isn't logged in, return an empty list + if (client.userID == null) return []; await client.roomsLoading; // don't try to get constructs until last updated time has been loaded await _pangeaController.myAnalytics.lastUpdatedCompleter.future; // if forcing a refreshing, clear the cache - if (forceUpdate) clearCache(); - - // get the last time the user updated their analytics for their current l2 - // then try to get local cache of construct uses. lastUpdate time is used to - // determine if cached data is still valid. - final DateTime? lastUpdated = _pangeaController.myAnalytics.lastUpdated ?? - await myAnalyticsLastUpdated(); + if (forceUpdate) _cache.clear(); final List? local = getConstructsLocal( constructType: constructType, - lastUpdated: lastUpdated, ); if (local != null) { @@ -160,7 +254,6 @@ class GetAnalyticsController { /// Get the cached construct uses for the current user, if it exists List? getConstructsLocal({ - DateTime? lastUpdated, ConstructTypeEnum? constructType, }) { final index = _cache.indexWhere( @@ -168,6 +261,7 @@ class GetAnalyticsController { ); if (index > -1) { + final DateTime? lastUpdated = _pangeaController.myAnalytics.lastUpdated; if (_cache[index].needsUpdate(lastUpdated)) { _cache.removeAt(index); return null; @@ -191,11 +285,6 @@ class GetAnalyticsController { ); _cache.add(entry); } - - /// Clear all cached analytics data. - void clearCache() { - _cache.clear(); - } } class AnalyticsCacheEntry { diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart index 1b94a12ba4..daefe1cd98 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/my_analytics_controller.dart @@ -4,24 +4,32 @@ import 'package:fluffychat/pangea/constants/local.key.dart'; import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/controllers/base_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; +import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; +import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart'; import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/practice_activity_event.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; import 'package:fluffychat/pangea/models/choreo_record.dart'; +import 'package:fluffychat/pangea/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_record_model.dart'; import 'package:fluffychat/pangea/models/representation_content_model.dart'; import 'package:fluffychat/pangea/models/tokens_event_content_model.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:flutter/foundation.dart'; import 'package:matrix/matrix.dart'; +import 'package:matrix/src/utils/cached_stream_controller.dart'; + +enum AnalyticsUpdateType { server, local } /// handles the processing of analytics for /// 1) messages sent by the user and /// 2) constructs used by the user, both in sending messages and doing practice activities class MyAnalyticsController extends BaseController { late PangeaController _pangeaController; - final StreamController analyticsUpdateStream = StreamController.broadcast(); + CachedStreamController analyticsUpdateStream = + CachedStreamController(); + StreamSubscription? _messageSendSubscription; Timer? _updateTimer; Client get _client => _pangeaController.matrixState.client; @@ -37,7 +45,7 @@ class MyAnalyticsController extends BaseController { /// the max number of messages that will be cached before /// an automatic update is triggered - final int _maxMessagesCached = 1; + final int _maxMessagesCached = 10; /// the number of minutes before an automatic update is triggered final int _minutesBeforeUpdate = 5; @@ -47,22 +55,28 @@ class MyAnalyticsController extends BaseController { MyAnalyticsController(PangeaController pangeaController) { _pangeaController = pangeaController; + } - // Wait for the next sync in the stream to ensure that the pangea controller - // is fully initialized. It will throw an error if it is not. - if (_pangeaController.matrixState.client.prevBatch == null) { - _pangeaController.matrixState.client.onSync.stream.first.then( - (_) => _refreshAnalyticsIfOutdated(), - ); - } else { - _refreshAnalyticsIfOutdated(); - } - + void initialize() { // Listen to a stream that provides the eventIDs // of new messages sent by the logged in user - stateStream.where((data) => data is Map).listen((data) { - onMessageSent(data as Map); - }); + _messageSendSubscription ??= stateStream + .where((data) => data is Map) + .listen((data) => onMessageSent(data as Map)); + + _refreshAnalyticsIfOutdated(); + } + + /// Reset analytics last updated time to null. + @override + void dispose() { + _updateTimer?.cancel(); + lastUpdated = null; + lastUpdatedCompleter = Completer(); + _messageSendSubscription?.cancel(); + _messageSendSubscription = null; + _refreshAnalyticsIfOutdated(); + clearMessagesSinceUpdate(); } /// If analytics haven't been updated in the last day, update them @@ -98,6 +112,7 @@ class MyAnalyticsController extends BaseController { void onMessageSent(Map data) { // cancel the last timer that was set on message event and // reset it to fire after _minutesBeforeUpdate minutes + debugPrint("ONE MESSAGE SENT"); _updateTimer?.cancel(); _updateTimer = Timer(Duration(minutes: _minutesBeforeUpdate), () { debugPrint("timer fired, updating analytics"); @@ -154,27 +169,82 @@ class MyAnalyticsController extends BaseController { _pangeaController.analytics .filterConstructs(unfilteredConstructs: constructs) - .then((filtered) => addMessageSinceUpdate(eventID, filtered)); + .then((filtered) { + if (filtered.isEmpty) return; + final level = _pangeaController.analytics.level; + addLocalMessage(eventID, filtered).then( + (_) => afterAddLocalMessages(level), + ); + }); + } + + /// Called when the user selects a replacement during IGC + void onReplacementSelected( + List tokens, + String roomID, + bool isBestCorrection, + ) { + final useType = isBestCorrection + ? ConstructUseTypeEnum.corIGC + : ConstructUseTypeEnum.incIGC; + setDraftConstructUses(tokens, roomID, useType); + } + + /// Called when the user ignores a match during IGC + void onIgnoreMatch( + List tokens, + String roomID, + ) { + const useType = ConstructUseTypeEnum.ignIGC; + setDraftConstructUses(tokens, roomID, useType); + } + + void setDraftConstructUses( + List tokens, + String roomID, + ConstructUseTypeEnum useType, + ) { + final metadata = ConstructUseMetaData( + roomId: roomID, + timeStamp: DateTime.now(), + ); + + final uses = tokens + .map( + (token) => OneConstructUse( + useType: useType, + lemma: token.lemma.text, + form: token.lemma.form, + constructType: ConstructTypeEnum.vocab, + metadata: metadata, + ), + ) + .toList(); + addLocalMessage('draft$roomID', uses); } + void clearDraftConstructUses(String roomID) { + final currentCache = _pangeaController.analytics.messagesSinceUpdate; + currentCache.remove('draft$roomID'); + setMessagesSinceUpdate(currentCache); + } + + /// Called when the user selects a continuance during IT + /// TODO implement + void onSelectContinuance() {} + /// Add a list of construct uses for a new message to the local /// cache of recently sent messages - void addMessageSinceUpdate( + Future addLocalMessage( String eventID, List constructs, - ) { + ) async { try { final currentCache = _pangeaController.analytics.messagesSinceUpdate; constructs.addAll(currentCache[eventID] ?? []); currentCache[eventID] = constructs; - setMessagesSinceUpdate(currentCache); - // if the cached has reached if max-length, update analytics - if (_pangeaController.analytics.messagesSinceUpdate.length > - _maxMessagesCached) { - debugPrint("reached max messages, updating"); - updateAnalytics(); - } + await setMessagesSinceUpdate(currentCache); } catch (e, s) { ErrorHandler.logError( e: PangeaWarningError("Failed to add message since update: $e"), @@ -184,23 +254,42 @@ class MyAnalyticsController extends BaseController { } } + /// Handles cleanup after adding a new message to the local cache. + /// If the addition brought the total number of messages in the cache + /// to the max, or if the addition triggered a level-up, update the analytics. + /// Otherwise, add a local update to the alert stream. + void afterAddLocalMessages(int prevLevel) { + if (_pangeaController.analytics.messagesSinceUpdate.length > + _maxMessagesCached) { + debugPrint("reached max messages, updating"); + updateAnalytics(); + return; + } + + final int newLevel = _pangeaController.analytics.level; + newLevel > prevLevel + ? updateAnalytics() + : analyticsUpdateStream.add(AnalyticsUpdateType.local); + } + /// Clears the local cache of recently sent constructs. Called before updating analytics void clearMessagesSinceUpdate() { _pangeaController.pStoreService.delete(PLocalKey.messagesSinceUpdate); } /// Save the local cache of recently sent constructs to the local storage - void setMessagesSinceUpdate(Map> cache) { + Future setMessagesSinceUpdate( + Map> cache, + ) async { final formattedCache = {}; for (final entry in cache.entries) { final constructJsons = entry.value.map((e) => e.toJson()).toList(); formattedCache[entry.key] = constructJsons; } - _pangeaController.pStoreService.save( + await _pangeaController.pStoreService.save( PLocalKey.messagesSinceUpdate, formattedCache, ); - analyticsUpdateStream.add(null); } /// Prevent concurrent updates to analytics @@ -223,8 +312,9 @@ class MyAnalyticsController extends BaseController { try { await _updateAnalytics(); clearMessagesSinceUpdate(); + lastUpdated = DateTime.now(); - analyticsUpdateStream.add(null); + analyticsUpdateStream.add(AnalyticsUpdateType.server); } catch (err, s) { ErrorHandler.logError( e: err, @@ -241,7 +331,10 @@ class MyAnalyticsController extends BaseController { /// The analytics room is determined based on the user's current target language. Future _updateAnalytics() async { // if there's no cached construct data, there's nothing to send - if (_pangeaController.analytics.messagesSinceUpdate.isEmpty) return; + final cachedConstructs = _pangeaController.analytics.messagesSinceUpdate; + final bool onlyDraft = cachedConstructs.length == 1 && + cachedConstructs.keys.single.startsWith('draft'); + if (cachedConstructs.isEmpty || onlyDraft) return; // if missing important info, don't send analytics. Could happen if user just signed up. if (userL2 == null || _client.userID == null) return; @@ -251,17 +344,7 @@ class MyAnalyticsController extends BaseController { // and send cached analytics data to the room await analyticsRoom?.sendConstructsEvent( - _pangeaController.analytics.messagesSinceUpdate.values - .expand((e) => e) - .toList(), + _pangeaController.analytics.locallyCachedSentConstructs, ); } - - /// Reset analytics last updated time to null. - void clearCache() { - _updateTimer?.cancel(); - lastUpdated = null; - lastUpdatedCompleter = Completer(); - _refreshAnalyticsIfOutdated(); - } } diff --git a/lib/pangea/controllers/pangea_controller.dart b/lib/pangea/controllers/pangea_controller.dart index a62ec04281..243a49174c 100644 --- a/lib/pangea/controllers/pangea_controller.dart +++ b/lib/pangea/controllers/pangea_controller.dart @@ -141,6 +141,19 @@ class PangeaController { /// check user information if not found then redirect to Date of birth page _handleLoginStateChange(LoginState state) { + switch (state) { + case LoginState.loggedOut: + case LoginState.softLoggedOut: + // Reset cached analytics data + MatrixState.pangeaController.myAnalytics.dispose(); + MatrixState.pangeaController.analytics.dispose(); + break; + case LoginState.loggedIn: + // Initialize analytics data + MatrixState.pangeaController.myAnalytics.initialize(); + MatrixState.pangeaController.analytics.initialize(); + break; + } if (state != LoginState.loggedIn) { _logOutfromPangea(); } diff --git a/lib/pangea/models/analytics/construct_list_model.dart b/lib/pangea/models/analytics/construct_list_model.dart index a22aa82e1d..8d8aeaff35 100644 --- a/lib/pangea/models/analytics/construct_list_model.dart +++ b/lib/pangea/models/analytics/construct_list_model.dart @@ -82,20 +82,27 @@ class ConstructListModel { /// The total number of points for all uses of this construct type int get points { - double totalPoints = 0; + // double totalPoints = 0; + return typedConstructs.fold( + 0, + (total, typedConstruct) => + total + + typedConstruct.useType.pointValue * typedConstruct.uses.length, + ); + // Commenting this out for now // Minimize the amount of points given for repeated uses of the same lemma. // i.e., if a lemma is used 4 times without assistance, the point value for // a use without assistance is 3. So the points would be // 3/1 + 3/2 + 3/3 + 3/4 = 3 + 1.5 + 1 + 0.75 = 5.25 (instead of 12) - for (final typedConstruct in typedConstructs) { - final pointValue = typedConstruct.useType.pointValue; - double calc = 0.0; - for (int k = 1; k <= typedConstruct.uses.length; k++) { - calc += pointValue / k; - } - totalPoints += calc; - } - return totalPoints.round(); + // for (final typedConstruct in typedConstructs) { + // final pointValue = typedConstruct.useType.pointValue; + // double calc = 0.0; + // for (int k = 1; k <= typedConstruct.uses.length; k++) { + // calc += pointValue / k; + // } + // totalPoints += calc; + // } + // return totalPoints.round(); } } diff --git a/lib/pangea/models/igc_text_data_model.dart b/lib/pangea/models/igc_text_data_model.dart index 5f32f92d15..014b39524d 100644 --- a/lib/pangea/models/igc_text_data_model.dart +++ b/lib/pangea/models/igc_text_data_model.dart @@ -227,37 +227,6 @@ class IGCTextData { decorationThickness: 5, ); - List getMatchTokens() { - final List matchTokens = []; - int? endTokenIndex; - PangeaMatch? topMatch; - for (final (i, token) in tokens.indexed) { - if (endTokenIndex != null) { - if (i <= endTokenIndex) { - matchTokens.add( - MatchToken( - token: token, - match: topMatch, - ), - ); - continue; - } - endTokenIndex = null; - } - topMatch = getTopMatchForToken(token); - if (topMatch != null) { - endTokenIndex = tokens.indexWhere((e) => e.end >= topMatch!.end, i); - } - matchTokens.add( - MatchToken( - token: token, - match: topMatch, - ), - ); - } - return matchTokens; - } - TextSpan getSpanItem({ required int start, required int end, @@ -347,11 +316,19 @@ class IGCTextData { return items; } -} -class MatchToken { - final PangeaToken token; - final PangeaMatch? match; + List matchTokens(int matchIndex) { + if (matchIndex >= matches.length) { + return []; + } - MatchToken({required this.token, this.match}); + final PangeaMatch match = matches[matchIndex]; + final List tokensForMatch = []; + for (final token in tokens) { + if (match.isOffsetInMatchSpan(token.text.offset)) { + tokensForMatch.add(token); + } + } + return tokensForMatch; + } } diff --git a/lib/pangea/models/span_data.dart b/lib/pangea/models/span_data.dart index bf8ab8eca4..8b7aaddac0 100644 --- a/lib/pangea/models/span_data.dart +++ b/lib/pangea/models/span_data.dart @@ -5,6 +5,8 @@ // Call to server for additional/followup info import 'package:collection/collection.dart'; +import 'package:fluffychat/pangea/constants/model_keys.dart'; +import 'package:fluffychat/pangea/models/pangea_token_model.dart'; import 'package:flutter/material.dart'; import '../enum/span_choice_type.dart'; @@ -99,14 +101,31 @@ class Context { } class SpanChoice { + String value; + SpanChoiceType type; + bool selected; + String? feedback; + DateTime? timestamp; + List tokens; + SpanChoice({ required this.value, required this.type, this.feedback, this.selected = false, this.timestamp, + this.tokens = const [], }); + factory SpanChoice.fromJson(Map json) { + final List tokensInternal = (json[ModelKey.tokens] != null) + ? (json[ModelKey.tokens] as Iterable) + .map( + (e) => PangeaToken.fromJson(e as Map), + ) + .toList() + .cast() + : []; return SpanChoice( value: json['value'] as String, type: json['type'] != null @@ -119,21 +138,17 @@ class SpanChoice { selected: json['selected'] ?? false, timestamp: json['timestamp'] != null ? DateTime.parse(json['timestamp']) : null, + tokens: tokensInternal, ); } - String value; - SpanChoiceType type; - bool selected; - String? feedback; - DateTime? timestamp; - Map toJson() => { 'value': value, 'type': type.name, 'selected': selected, 'feedback': feedback, 'timestamp': timestamp?.toIso8601String(), + 'tokens': tokens.map((e) => e.toJson()).toList(), }; String feedbackToDisplay(BuildContext context) { diff --git a/lib/pangea/utils/logout.dart b/lib/pangea/utils/logout.dart index 3d3e780ba6..6c57754ef9 100644 --- a/lib/pangea/utils/logout.dart +++ b/lib/pangea/utils/logout.dart @@ -22,10 +22,6 @@ void pLogoutAction(BuildContext context, {bool? isDestructiveAction}) async { // before wiping out locally cached construct data, save it to the server await MatrixState.pangeaController.myAnalytics.updateAnalytics(); - // Reset cached analytics data - MatrixState.pangeaController.myAnalytics.clearCache(); - MatrixState.pangeaController.analytics.clearCache(); - await showFutureLoadingDialog( context: context, future: () => matrix.client.logout(), diff --git a/lib/pangea/widgets/animations/gain_points.dart b/lib/pangea/widgets/animations/gain_points.dart new file mode 100644 index 0000000000..dfbee2931c --- /dev/null +++ b/lib/pangea/widgets/animations/gain_points.dart @@ -0,0 +1,103 @@ +import 'dart:async'; + +import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; +import 'package:fluffychat/pangea/utils/bot_style.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; + +class PointsGainedAnimation extends StatefulWidget { + final Color? color; + const PointsGainedAnimation({super.key, this.color}); + + @override + PointsGainedAnimationState createState() => PointsGainedAnimationState(); +} + +class PointsGainedAnimationState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _offsetAnimation; + late Animation _fadeAnimation; + + StreamSubscription? _pointsSubscription; + int? get _prevXP => MatrixState.pangeaController.analytics.prevXP; + int? get _currentXP => MatrixState.pangeaController.analytics.currentXP; + int? _addedPoints; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + duration: const Duration(seconds: 2), + vsync: this, + ); + + _offsetAnimation = Tween( + begin: const Offset(0.0, 0.0), + end: const Offset(0.0, -1.0), + ).animate( + CurvedAnimation( + parent: _controller, + curve: Curves.easeOut, + ), + ); + + _fadeAnimation = Tween( + begin: 1.0, + end: 0.0, + ).animate( + CurvedAnimation( + parent: _controller, + curve: Curves.easeOut, + ), + ); + + _pointsSubscription = MatrixState + .pangeaController.analytics.analyticsStream.stream + .listen(_showPointsGained); + } + + @override + void dispose() { + _controller.dispose(); + _pointsSubscription?.cancel(); + super.dispose(); + } + + void _showPointsGained(List constructs) { + setState(() => _addedPoints = (_currentXP ?? 0) - (_prevXP ?? 0)); + if (_prevXP != _currentXP && !_controller.isAnimating) { + _controller.reset(); + _controller.forward(); + } + } + + bool get animate => + _currentXP != null && + _prevXP != null && + _addedPoints != null && + _prevXP! != _currentXP!; + + @override + Widget build(BuildContext context) { + if (!animate) return const SizedBox(); + + return SlideTransition( + position: _offsetAnimation, + child: FadeTransition( + opacity: _fadeAnimation, + child: Text( + '+$_addedPoints', + style: BotStyle.text( + context, + big: true, + setColor: widget.color == null, + existingStyle: TextStyle( + color: widget.color, + ), + ), + ), + ), + ); + } +} diff --git a/lib/pangea/widgets/animations/progress_bar/animated_level_dart.dart b/lib/pangea/widgets/animations/progress_bar/animated_level_dart.dart new file mode 100644 index 0000000000..adbc32e331 --- /dev/null +++ b/lib/pangea/widgets/animations/progress_bar/animated_level_dart.dart @@ -0,0 +1,87 @@ +import 'package:fluffychat/config/themes.dart'; +import 'package:flutter/material.dart'; + +class AnimatedLevelBar extends StatefulWidget { + final double height; + final double beginWidth; + final double endWidth; + final BoxDecoration? decoration; + + const AnimatedLevelBar({ + super.key, + required this.height, + required this.beginWidth, + required this.endWidth, + this.decoration, + }); + + @override + AnimatedLevelBarState createState() => AnimatedLevelBarState(); +} + +class AnimatedLevelBarState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + + /// Whether the animation has run for the first time during initState. Don't + /// want the animation to run when the widget mounts, only when points are gained. + bool _init = true; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: FluffyThemes.animationDuration, + ); + + _controller.forward().then((_) => _init = false); + } + + @override + void didUpdateWidget(covariant AnimatedLevelBar oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.endWidth != widget.endWidth) { + _controller.reset(); + _controller.forward(); + } + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + Animation get _animation { + // If this is the first run of the animation, don't animate. This is just the widget mounting, + // not a points gain. This could instead be 'if going from 0 to a non-zero value', but that + // would remove the animation for first points gained. It would remove the need for a flag though. + if (_init) { + return Tween( + begin: widget.endWidth, + end: widget.endWidth, + ).animate(_controller); + } + + // animate the width of the bar + return Tween( + begin: widget.beginWidth, + end: widget.endWidth, + ).animate(_controller); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _animation, + builder: (context, child) { + return Container( + height: widget.height, + width: _animation.value, + decoration: widget.decoration, + ); + }, + ); + } +} diff --git a/lib/pangea/widgets/animations/progress_bar/level_bar.dart b/lib/pangea/widgets/animations/progress_bar/level_bar.dart new file mode 100644 index 0000000000..fb57a3bd5c --- /dev/null +++ b/lib/pangea/widgets/animations/progress_bar/level_bar.dart @@ -0,0 +1,59 @@ +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pangea/constants/analytics_constants.dart'; +import 'package:fluffychat/pangea/widgets/animations/progress_bar/animated_level_dart.dart'; +import 'package:fluffychat/pangea/widgets/animations/progress_bar/progress_bar_details.dart'; +import 'package:flutter/material.dart'; + +class LevelBar extends StatefulWidget { + final LevelBarDetails details; + final ProgressBarDetails progressBarDetails; + + const LevelBar({ + super.key, + required this.details, + required this.progressBarDetails, + }); + + @override + LevelBarState createState() => LevelBarState(); +} + +class LevelBarState extends State { + double prevWidth = 0; + + double get width { + const perLevel = AnalyticsConstants.xpPerLevel; + final percent = (widget.details.currentPoints % perLevel) / perLevel; + return widget.progressBarDetails.totalWidth * percent; + } + + @override + void didUpdateWidget(covariant LevelBar oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.details.currentPoints != widget.details.currentPoints) { + setState(() => prevWidth = width); + } + } + + @override + Widget build(BuildContext context) { + return AnimatedLevelBar( + height: widget.progressBarDetails.height, + beginWidth: prevWidth, + endWidth: width, + decoration: BoxDecoration( + borderRadius: const BorderRadius.all( + Radius.circular(AppConfig.borderRadius), + ), + color: widget.details.fillColor, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.2), + blurRadius: 5, + offset: const Offset(5, 0), + ), + ], + ), + ); + } +} diff --git a/lib/pangea/widgets/animations/progress_bar/progress_bar.dart b/lib/pangea/widgets/animations/progress_bar/progress_bar.dart new file mode 100644 index 0000000000..ea0263a3c6 --- /dev/null +++ b/lib/pangea/widgets/animations/progress_bar/progress_bar.dart @@ -0,0 +1,36 @@ +import 'package:fluffychat/pangea/widgets/animations/progress_bar/level_bar.dart'; +import 'package:fluffychat/pangea/widgets/animations/progress_bar/progress_bar_background.dart'; +import 'package:fluffychat/pangea/widgets/animations/progress_bar/progress_bar_details.dart'; +import 'package:flutter/material.dart'; + +// Provide an order list of level indicators, each with it's color +// and stream. Also provide an overall width and pointsPerLevel. + +class ProgressBar extends StatelessWidget { + final List levelBars; + final ProgressBarDetails progressBarDetails; + + const ProgressBar({ + super.key, + required this.levelBars, + required this.progressBarDetails, + }); + + @override + Widget build(BuildContext context) { + return Stack( + alignment: Alignment.centerLeft, + children: [ + ProgressBarBackground(details: progressBarDetails), + for (final levelBar in levelBars) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 2), + child: LevelBar( + details: levelBar, + progressBarDetails: progressBarDetails, + ), + ), + ], + ); + } +} diff --git a/lib/pangea/widgets/animations/progress_bar/progress_bar_background.dart b/lib/pangea/widgets/animations/progress_bar/progress_bar_background.dart new file mode 100644 index 0000000000..1ebfe145d2 --- /dev/null +++ b/lib/pangea/widgets/animations/progress_bar/progress_bar_background.dart @@ -0,0 +1,30 @@ +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pangea/widgets/animations/progress_bar/progress_bar_details.dart'; +import 'package:flutter/material.dart'; + +class ProgressBarBackground extends StatelessWidget { + final ProgressBarDetails details; + + const ProgressBarBackground({ + super.key, + required this.details, + }); + + @override + Widget build(BuildContext context) { + return Container( + height: details.height + 4, + width: details.totalWidth + 4, + decoration: BoxDecoration( + border: Border.all( + color: details.borderColor.withOpacity(0.5), + width: 2, + ), + borderRadius: const BorderRadius.all( + Radius.circular(AppConfig.borderRadius), + ), + color: details.borderColor.withOpacity(0.2), + ), + ); + } +} diff --git a/lib/pangea/widgets/animations/progress_bar/progress_bar_details.dart b/lib/pangea/widgets/animations/progress_bar/progress_bar_details.dart new file mode 100644 index 0000000000..debe938168 --- /dev/null +++ b/lib/pangea/widgets/animations/progress_bar/progress_bar_details.dart @@ -0,0 +1,23 @@ +import 'dart:ui'; + +class LevelBarDetails { + final Color fillColor; + final int currentPoints; + + const LevelBarDetails({ + required this.fillColor, + required this.currentPoints, + }); +} + +class ProgressBarDetails { + final double totalWidth; + final Color borderColor; + final double height; + + const ProgressBarDetails({ + required this.totalWidth, + required this.borderColor, + this.height = 16, + }); +} diff --git a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart index 07fd026040..bf8bf2f96f 100644 --- a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart +++ b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart @@ -1,12 +1,15 @@ import 'dart:async'; -import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/pangea/constants/analytics_constants.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; import 'package:fluffychat/pangea/enum/progress_indicators_enum.dart'; import 'package:fluffychat/pangea/models/analytics/construct_list_model.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; +import 'package:fluffychat/pangea/widgets/animations/gain_points.dart'; +import 'package:fluffychat/pangea/widgets/animations/progress_bar/progress_bar.dart'; +import 'package:fluffychat/pangea/widgets/animations/progress_bar/progress_bar_details.dart'; import 'package:fluffychat/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -34,7 +37,7 @@ class LearningProgressIndicatorsState /// A stream subscription to listen for updates to /// the analytics data, either locally or from events - StreamSubscription? _onAnalyticsUpdate; + StreamSubscription>? _analyticsUpdateSubscription; /// Vocabulary constructs model ConstructListModel? words; @@ -44,66 +47,39 @@ class LearningProgressIndicatorsState bool loading = true; - /// The previous number of XP points, used to determine when to animate the level bar - int? previousXP; + int get serverXP => _pangeaController.analytics.serverXP; + int get totalXP => _pangeaController.analytics.currentXP; + int get level => _pangeaController.analytics.level; @override void initState() { super.initState(); - updateAnalyticsData().then((_) { - setState(() => loading = false); - }); - // listen for changes to analytics data and update the UI - _onAnalyticsUpdate = _pangeaController - .myAnalytics.analyticsUpdateStream.stream - .listen((_) => updateAnalyticsData()); + updateAnalyticsData( + _pangeaController.analytics.analyticsStream.value ?? [], + ); + _pangeaController.analytics.analyticsStream.stream + .listen(updateAnalyticsData); } @override void dispose() { - _onAnalyticsUpdate?.cancel(); + _analyticsUpdateSubscription?.cancel(); super.dispose(); } /// Update the analytics data shown in the UI. This comes from a /// combination of stored events and locally cached data. - Future updateAnalyticsData() async { - previousXP = xpPoints; - - final List storedUses = - await _pangeaController.analytics.getConstructs(); - final List localUses = []; - for (final uses in _pangeaController.analytics.messagesSinceUpdate.values) { - localUses.addAll(uses); - } - - if (storedUses.isEmpty) { - words = ConstructListModel( - type: ConstructTypeEnum.vocab, - uses: localUses, - ); - errors = ConstructListModel( - type: ConstructTypeEnum.grammar, - uses: localUses, - ); - setState(() {}); - return; - } - - final List allConstructs = [ - ...storedUses, - ...localUses, - ]; - + Future updateAnalyticsData(List constructs) async { words = ConstructListModel( type: ConstructTypeEnum.vocab, - uses: allConstructs, + uses: constructs, ); errors = ConstructListModel( type: ConstructTypeEnum.grammar, - uses: allConstructs, + uses: constructs, ); + if (loading) loading = false; if (mounted) setState(() {}); } @@ -119,21 +95,10 @@ class LearningProgressIndicatorsState } } - /// Get the total number of xp points, based on the point values of use types. - /// Null if niether words nor error constructs are available. - int? get xpPoints { - if (words == null && errors == null) return null; - if (words == null) return errors!.points; - if (errors == null) return words!.points; - return words!.points + errors!.points; - } - - /// Get the current level based on the number of xp points - int get level => (xpPoints ?? 0) ~/ 500; - double get levelBarWidth => FluffyThemes.columnWidth - (32 * 2) - 25; double get pointsBarWidth { - final percent = ((xpPoints ?? 0) % 500) / 500; + final percent = (totalXP % AnalyticsConstants.xpPerLevel) / + AnalyticsConstants.xpPerLevel; return levelBarWidth * percent; } @@ -149,61 +114,42 @@ class LearningProgressIndicatorsState return colors[level % colors.length]; } - /// Whether to animate the level bar increase. Prevents this bar from seeming to - /// reload each time the user navigates to a different space or back to the chat list. - /// PreviousXP would be null if this widget just mounted. Also handles case of rebuilds - /// without any change in XP points. - bool get animate => previousXP != null && previousXP != xpPoints; - @override Widget build(BuildContext context) { if (Matrix.of(context).client.userID == null) { return const SizedBox(); } - final levelBar = Container( - height: 20, - width: levelBarWidth, - decoration: BoxDecoration( - border: Border.all( - color: Theme.of(context).colorScheme.primary.withOpacity(0.5), - width: 2, + final progressBar = ProgressBar( + levelBars: [ + LevelBarDetails( + fillColor: const Color.fromARGB(255, 0, 190, 83), + currentPoints: totalXP, ), - borderRadius: const BorderRadius.only( - topRight: Radius.circular(AppConfig.borderRadius), - bottomRight: Radius.circular(AppConfig.borderRadius), + LevelBarDetails( + fillColor: Theme.of(context).colorScheme.primary, + currentPoints: serverXP, ), - color: Theme.of(context).colorScheme.primary.withOpacity(0.2), - ), - ); - - final xpBarDecoration = BoxDecoration( - borderRadius: const BorderRadius.only( - topRight: Radius.circular(AppConfig.borderRadius), - bottomRight: Radius.circular(AppConfig.borderRadius), + ], + progressBarDetails: ProgressBarDetails( + totalWidth: levelBarWidth, + borderColor: Theme.of(context).colorScheme.primary.withOpacity(0.5), ), - color: Theme.of(context).colorScheme.primary, ); - final xpBar = animate - ? AnimatedContainer( - duration: FluffyThemes.animationDuration, - height: 16, - width: pointsBarWidth, - decoration: xpBarDecoration, - ) - : Container( - height: 16, - width: pointsBarWidth, - decoration: xpBarDecoration, - ); - final levelBadge = Container( width: 32, height: 32, decoration: BoxDecoration( color: levelColor(level), borderRadius: BorderRadius.circular(32), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.2), + blurRadius: 5, + offset: const Offset(5, 0), + ), + ], ), child: Center( child: Text( @@ -213,61 +159,71 @@ class LearningProgressIndicatorsState ), ); - return Column( - mainAxisSize: MainAxisSize.min, + return Stack( + alignment: Alignment.topCenter, children: [ - Padding( - padding: const EdgeInsets.fromLTRB(46, 0, 32, 4), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - FutureBuilder( - future: - _pangeaController.matrixState.client.getProfileFromUserId( - _pangeaController.matrixState.client.userID!, - ), - builder: (context, snapshot) { - final mxid = Matrix.of(context).client.userID ?? - L10n.of(context)!.user; - return Avatar( - name: snapshot.data?.displayName ?? mxid.localpart ?? mxid, - mxContent: snapshot.data?.avatarUrl, - size: 40, - ); - }, + const Positioned( + child: PointsGainedAnimation(), + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(46, 0, 32, 4), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + FutureBuilder( + future: _pangeaController.matrixState.client + .getProfileFromUserId( + _pangeaController.matrixState.client.userID!, + ), + builder: (context, snapshot) { + final mxid = Matrix.of(context).client.userID ?? + L10n.of(context)!.user; + return Avatar( + name: snapshot.data?.displayName ?? + mxid.localpart ?? + mxid, + mxContent: snapshot.data?.avatarUrl, + size: 40, + ); + }, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: ProgressIndicatorEnum.values + .where( + (indicator) => + indicator != ProgressIndicatorEnum.level, + ) + .map( + (indicator) => ProgressIndicatorBadge( + points: getProgressPoints(indicator), + onTap: () {}, + progressIndicator: indicator, + loading: loading, + ), + ) + .toList(), + ), + ], ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: ProgressIndicatorEnum.values - .where( - (indicator) => indicator != ProgressIndicatorEnum.level, - ) - .map( - (indicator) => ProgressIndicatorBadge( - points: getProgressPoints(indicator), - onTap: () {}, - progressIndicator: indicator, - loading: loading, - ), - ) - .toList(), + ), + Container( + height: 36, + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Stack( + alignment: Alignment.center, + children: [ + Positioned(left: 16, right: 0, child: progressBar), + Positioned(left: 0, child: levelBadge), + ], ), - ], - ), - ), - Container( - height: 36, - padding: const EdgeInsets.symmetric(horizontal: 32), - child: Stack( - alignment: Alignment.center, - children: [ - Positioned(left: 16, right: 0, child: levelBar), - Positioned(left: 16, child: xpBar), - Positioned(left: 0, child: levelBadge), - ], - ), + ), + const SizedBox(height: 16), + ], ), - const SizedBox(height: 16), ], ); } diff --git a/lib/pangea/widgets/igc/span_card.dart b/lib/pangea/widgets/igc/span_card.dart index 26046f8d0e..a431f56663 100644 --- a/lib/pangea/widgets/igc/span_card.dart +++ b/lib/pangea/widgets/igc/span_card.dart @@ -20,8 +20,6 @@ import '../common/bot_face_svg.dart'; import 'card_header.dart'; import 'why_button.dart'; -const wordMatchResultsCount = 5; - //switch for definition vs correction vs practice //always show a title @@ -32,12 +30,12 @@ const wordMatchResultsCount = 5; class SpanCard extends StatefulWidget { final PangeaController pangeaController = MatrixState.pangeaController; final SpanCardModel scm; - final String? roomId; + final String roomId; SpanCard({ super.key, required this.scm, - this.roomId, + required this.roomId, }); @override @@ -62,12 +60,16 @@ class SpanCardState extends State { //get selected choice SpanChoice? get selectedChoice { - if (selectedChoiceIndex == null || - widget.scm.pangeaMatch?.match.choices == null || - widget.scm.pangeaMatch!.match.choices!.length <= selectedChoiceIndex!) { + if (selectedChoiceIndex == null) return null; + return choiceByIndex(selectedChoiceIndex!); + } + + SpanChoice? choiceByIndex(int index) { + if (widget.scm.pangeaMatch?.match.choices == null || + widget.scm.pangeaMatch!.match.choices!.length <= index) { return null; } - return widget.scm.pangeaMatch?.match.choices?[selectedChoiceIndex!]; + return widget.scm.pangeaMatch?.match.choices?[index]; } void fetchSelected() { @@ -76,8 +78,9 @@ class SpanCardState extends State { } if (selectedChoiceIndex == null) { DateTime? mostRecent; - for (int i = 0; i < widget.scm.pangeaMatch!.match.choices!.length; i++) { - final choice = widget.scm.pangeaMatch?.match.choices![i]; + final numChoices = widget.scm.pangeaMatch!.match.choices!.length; + for (int i = 0; i < numChoices; i++) { + final choice = choiceByIndex(i); if (choice!.timestamp != null && (mostRecent == null || choice.timestamp!.isAfter(mostRecent))) { mostRecent = choice.timestamp; @@ -114,6 +117,58 @@ class SpanCardState extends State { } } + Future onChoiceSelect(int index) async { + selectedChoiceIndex = index; + if (selectedChoice != null) { + selectedChoice!.timestamp = DateTime.now(); + selectedChoice!.selected = true; + setState( + () => (selectedChoice!.isBestCorrection + ? BotExpression.gold + : BotExpression.surprised), + ); + } + } + + void onReplaceSelected() { + if (selectedChoice != null) { + final tokens = widget.scm.choreographer.igc.igcTextData + ?.matchTokens(widget.scm.matchIndex) ?? + []; + MatrixState.pangeaController.myAnalytics.onReplacementSelected( + tokens, + widget.roomId, + selectedChoice!.isBestCorrection, + ); + } + + widget.scm + .onReplacementSelect( + matchIndex: widget.scm.matchIndex, + choiceIndex: selectedChoiceIndex!, + ) + .then((value) { + setState(() {}); + }); + } + + void onIgnoreMatch() { + MatrixState.pAnyState.closeOverlay(); + Future.delayed( + Duration.zero, + () { + widget.scm.onIgnore(); + final tokens = widget.scm.choreographer.igc.igcTextData + ?.matchTokens(widget.scm.matchIndex) ?? + []; + MatrixState.pangeaController.myAnalytics.onIgnoreMatch( + tokens, + widget.roomId, + ); + }, + ); + } + @override Widget build(BuildContext context) { return WordMatchContent(controller: this); @@ -129,61 +184,6 @@ class WordMatchContent extends StatelessWidget { super.key, }); - Future onChoiceSelect(int index) async { - controller.selectedChoiceIndex = index; - controller - .widget - .scm - .choreographer - .igc - .igcTextData - ?.matches[controller.widget.scm.matchIndex] - .match - .choices?[index] - .timestamp = DateTime.now(); - controller - .widget - .scm - .choreographer - .igc - .igcTextData - ?.matches[controller.widget.scm.matchIndex] - .match - .choices?[index] - .selected = true; - - controller.setState( - () => (controller.currentExpression = controller - .widget - .scm - .choreographer - .igc - .igcTextData! - .matches[controller.widget.scm.matchIndex] - .match - .choices![index] - .isBestCorrection - ? BotExpression.gold - : BotExpression.surprised), - ); - // if (controller.widget.scm.pangeaMatch.match.choices![index].type == - // SpanChoiceType.distractor) { - // await controller.getSpanDetails(); - // } - // controller.setState(() {}); - } - - void onReplaceSelected() { - controller.widget.scm - .onReplacementSelect( - matchIndex: controller.widget.scm.matchIndex, - choiceIndex: controller.selectedChoiceIndex!, - ) - .then((value) { - controller.setState(() {}); - }); - } - @override Widget build(BuildContext context) { if (controller.widget.scm.pangeaMatch == null) { @@ -248,7 +248,7 @@ class WordMatchContent extends StatelessWidget { ), ) .toList(), - onPressed: onChoiceSelect, + onPressed: controller.onChoiceSelect, uniqueKeyForLayerLink: (int index) => "wordMatch$index", selectedChoiceIndex: controller.selectedChoiceIndex, ), @@ -272,13 +272,7 @@ class WordMatchContent extends StatelessWidget { AppConfig.primaryColor.withOpacity(0.1), ), ), - onPressed: () { - MatrixState.pAnyState.closeOverlay(); - Future.delayed( - Duration.zero, - () => controller.widget.scm.onIgnore(), - ); - }, + onPressed: controller.onIgnoreMatch, child: Center( child: Text(L10n.of(context)!.ignoreInThisText), ), @@ -292,7 +286,7 @@ class WordMatchContent extends StatelessWidget { opacity: controller.selectedChoiceIndex != null ? 1.0 : 0.5, child: TextButton( onPressed: controller.selectedChoiceIndex != null - ? onReplaceSelected + ? controller.onReplaceSelected : null, style: ButtonStyle( backgroundColor: WidgetStateProperty.all( @@ -352,7 +346,6 @@ class WordMatchContent extends StatelessWidget { } on Exception catch (e) { debugger(when: kDebugMode); ErrorHandler.logError(e: e, s: StackTrace.current); - print(e); rethrow; } } diff --git a/lib/pangea/widgets/igc/span_data.dart b/lib/pangea/widgets/igc/span_data.dart deleted file mode 100644 index a92f32f741..0000000000 --- a/lib/pangea/widgets/igc/span_data.dart +++ /dev/null @@ -1,184 +0,0 @@ -//Possible actions/effects from cards -// Nothing -// useType of viewed definitions -// SpanChoice of text in message from options -// Call to server for additional/followup info - -import 'package:collection/collection.dart'; - -import '../../enum/span_data_type.dart'; - -class SpanData { - String? message; - String? shortMessage; - List? choices; - List? replacements; - int offset; - int length; - String fullText; - Context? context; - SpanDataTypeEnum type; - Rule? rule; - - SpanData({ - this.message, - this.shortMessage, - this.choices, - this.replacements, - required this.offset, - required this.length, - required this.fullText, - this.context, - required this.type, - this.rule, - }); - - factory SpanData.fromJson(Map json) { - return SpanData( - message: json['message'], - shortMessage: json['shortMessage'], - choices: json['choices'] != null - ? List.from( - json['choices'].map((x) => SpanChoice.fromJson(x)), - ) - : null, - replacements: json['replacements'] != null - ? List.from( - json['replacements'].map((x) => Replacement.fromJson(x)), - ) - : null, - offset: json['offset'], - length: json['length'], - fullText: json['full_text'], - context: - json['context'] != null ? Context.fromJson(json['context']) : null, - type: SpanDataTypeEnum.values.firstWhereOrNull( - (e) => e.toString() == 'SpanDataTypeEnum.${json['type']}', - ) ?? - SpanDataTypeEnum.correction, - rule: json['rule'] != null ? Rule.fromJson(json['rule']) : null, - ); - } - - Map toJson() { - final Map data = {}; - data['message'] = message; - data['shortMessage'] = shortMessage; - if (choices != null) { - data['choices'] = choices!.map((x) => x.toJson()).toList(); - } - if (replacements != null) { - data['replacements'] = replacements!.map((x) => x.toJson()).toList(); - } - data['offset'] = offset; - data['length'] = length; - data['full_text'] = fullText; - if (context != null) { - data['context'] = context!.toJson(); - } - data['type'] = type.toString().split('.').last; - if (rule != null) { - data['rule'] = rule!.toJson(); - } - return data; - } -} - -class Context { - String sentence; - int offset; - int length; - - Context({required this.sentence, required this.offset, required this.length}); - - factory Context.fromJson(Map json) { - return Context( - sentence: json['sentence'], - offset: json['offset'], - length: json['length'], - ); - } - - Map toJson() { - final Map data = {}; - data['sentence'] = sentence; - data['offset'] = offset; - data['length'] = length; - return data; - } -} - -class SpanChoice { - String value; - bool selected; - - SpanChoice({required this.value, required this.selected}); - - factory SpanChoice.fromJson(Map json) { - return SpanChoice( - value: json['value'], - selected: json['selected'], - ); - } - - Map toJson() { - final Map data = {}; - data['value'] = value; - data['selected'] = selected; - return data; - } -} - -class Replacement { - String value; - - Replacement({required this.value}); - - factory Replacement.fromJson(Map json) { - return Replacement( - value: json['value'], - ); - } - - Map toJson() { - final Map data = {}; - data['value'] = value; - return data; - } -} - -class Rule { - String id; - - Rule({required this.id}); - - factory Rule.fromJson(Map json) { - return Rule( - id: json['id'], - ); - } - - Map toJson() { - final Map data = {}; - data['id'] = id; - return data; - } -} - -class SpanDataType { - String type; - - SpanDataType({required this.type}); - - factory SpanDataType.fromJson(Map json) { - return SpanDataType( - type: json['type'], - ); - } - - Map toJson() { - final Map data = {}; - data['type'] = type; - return data; - } -} diff --git a/lib/pangea/widgets/user_settings/p_language_dialog.dart b/lib/pangea/widgets/user_settings/p_language_dialog.dart index 5b08d3cadd..43096ea545 100644 --- a/lib/pangea/widgets/user_settings/p_language_dialog.dart +++ b/lib/pangea/widgets/user_settings/p_language_dialog.dart @@ -91,20 +91,28 @@ Future pLanguageDialog( context: context, future: () async { try { - pangeaController.userController.updateProfile( - (profile) { - profile.userSettings.sourceLanguage = - selectedSourceLanguage.langCode; - profile.userSettings.targetLanguage = - selectedTargetLanguage.langCode; - return profile; - }, - waitForDataInSync: true, - ).then((_) { + pangeaController.myAnalytics + .updateAnalytics() + .then((_) { + pangeaController.userController.updateProfile( + (profile) { + profile.userSettings.sourceLanguage = + selectedSourceLanguage.langCode; + profile.userSettings.targetLanguage = + selectedTargetLanguage.langCode; + return profile; + }, + waitForDataInSync: true, + ); + }).then((_) { // if the profile update is successful, reset cached analytics // data, since analytics data corresponds to the user's L2 - pangeaController.myAnalytics.clearCache(); - pangeaController.analytics.clearCache(); + pangeaController.myAnalytics.dispose(); + pangeaController.analytics.dispose(); + + pangeaController.myAnalytics.initialize(); + pangeaController.analytics.initialize(); + Navigator.pop(context); }); } catch (err, s) { From e738fdbbb5fcff4e70cfc376297c506ed21d62c8 Mon Sep 17 00:00:00 2001 From: Krille Date: Thu, 15 Aug 2024 13:36:46 +0200 Subject: [PATCH 212/288] chore: Remove issue pr management workflow --- .github/workflows/issue_pr_management.yaml | 27 ---------------------- 1 file changed, 27 deletions(-) delete mode 100644 .github/workflows/issue_pr_management.yaml diff --git a/.github/workflows/issue_pr_management.yaml b/.github/workflows/issue_pr_management.yaml deleted file mode 100644 index bec4abca57..0000000000 --- a/.github/workflows/issue_pr_management.yaml +++ /dev/null @@ -1,27 +0,0 @@ -name: Close Inactive Issues And PRs -on: - schedule: - - cron: "30 1 * * *" - -jobs: - close-issues: - runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: write - steps: - - uses: actions/stale@v9 - with: - days-before-issue-stale: 120 - days-before-issue-close: 14 - stale-issue-label: "stale" - stale-issue-message: "This issue is stale because it has been open for 120 days with no activity." - close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." - stale-pr-message: "This pull request is stale because it has been open for 120 days with no activity." - close-pr-message: "This pull request was closed because it has been inactive for 14 days since being marked as stale." - days-before-pr-stale: 120 - days-before-pr-close: 14 - exempt-milestones: true - exempt-assignees: krille-chan - operations-per-run: 500 - repo-token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file From 0cb5dece72fdf9bb9018d19cd206fc1ee99b22e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Mon, 12 Aug 2024 18:33:05 +0000 Subject: [PATCH 213/288] Translated using Weblate (Estonian) Currently translated at 100.0% (658 of 658 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/ --- assets/l10n/intl_et.arb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_et.arb b/assets/l10n/intl_et.arb index 5c686459ed..c9ac697fcd 100644 --- a/assets/l10n/intl_et.arb +++ b/assets/l10n/intl_et.arb @@ -2789,5 +2789,9 @@ } }, "changelog": "Muudatuste logi", - "@changelog": {} + "@changelog": {}, + "sendCanceled": "Saatmine on katkestatud", + "@sendCanceled": {}, + "noChatsFoundHere": "Siin ei leidu veel ühtegi vestlust. Alusta uut vestlust klõpsides allpool asuvat nuppu. ⤵️", + "@noChatsFoundHere": {} } From 2c75c56e392e377dbf3af103db78038df0c10d44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jos=C3=A9=20m?= Date: Mon, 12 Aug 2024 12:16:20 +0000 Subject: [PATCH 214/288] Translated using Weblate (Galician) Currently translated at 100.0% (658 of 658 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/gl/ --- assets/l10n/intl_gl.arb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_gl.arb b/assets/l10n/intl_gl.arb index 4d2d6cfaa0..925f8bfb5b 100644 --- a/assets/l10n/intl_gl.arb +++ b/assets/l10n/intl_gl.arb @@ -2789,5 +2789,9 @@ "placeholders": { "version": {} } - } + }, + "sendCanceled": "Cancelouse o envío", + "@sendCanceled": {}, + "noChatsFoundHere": "Ningún chat por aquí. Comeza unha nova conversa con alguén premendo no botón de abaixo. ⤵️", + "@noChatsFoundHere": {} } From e05a4d7c3cccf96cf2757cadab2b77af4fbc4077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Mon, 12 Aug 2024 16:58:55 +0000 Subject: [PATCH 215/288] Translated using Weblate (Turkish) Currently translated at 100.0% (658 of 658 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/tr/ --- assets/l10n/intl_tr.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_tr.arb b/assets/l10n/intl_tr.arb index ec387220af..d508724f68 100644 --- a/assets/l10n/intl_tr.arb +++ b/assets/l10n/intl_tr.arb @@ -2791,5 +2791,7 @@ } }, "sendCanceled": "Gönderme iptal edildi", - "@sendCanceled": {} + "@sendCanceled": {}, + "noChatsFoundHere": "Burada henüz sohbet bulunamadı. Aşağıdaki düğmeyi kullanarak biriyle yeni bir sohbet başlatın. ⤵️", + "@noChatsFoundHere": {} } From eebdd8148d013cf282cc14135f7854f4a0af8df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E7=8E=8B=E5=8F=AB=E6=88=91=E6=9D=A5=E5=B7=A1?= =?UTF-8?q?=E5=B1=B1?= Date: Tue, 13 Aug 2024 06:37:48 +0000 Subject: [PATCH 216/288] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (658 of 658 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hans/ --- assets/l10n/intl_zh.arb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_zh.arb b/assets/l10n/intl_zh.arb index a188872b42..a252279478 100644 --- a/assets/l10n/intl_zh.arb +++ b/assets/l10n/intl_zh.arb @@ -2789,5 +2789,9 @@ "placeholders": { "version": {} } - } + }, + "sendCanceled": "发送被取消", + "@sendCanceled": {}, + "noChatsFoundHere": "此处尚未找到聊天。使用下方按钮 ⤵️ 开始和某人的新聊天", + "@noChatsFoundHere": {} } From c0e836418dd354fd9e99a461c657b9ceaa882799 Mon Sep 17 00:00:00 2001 From: xabirequejo Date: Tue, 13 Aug 2024 16:39:09 +0000 Subject: [PATCH 217/288] Translated using Weblate (Basque) Currently translated at 100.0% (658 of 658 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/ --- assets/l10n/intl_eu.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_eu.arb b/assets/l10n/intl_eu.arb index 39b93f6f1e..7b8aa8c301 100644 --- a/assets/l10n/intl_eu.arb +++ b/assets/l10n/intl_eu.arb @@ -2791,5 +2791,7 @@ "chatPermissionsDescription": "Definitu zer botere-maila behar den txat honetako ekintza jakinetarako. 0, 50 eta 100 botere-mailek erabiltzaileak, moderatzaileak eta administratzaileak ordezkatzen dituzte, baina edozein graduazio posible da.", "@chatPermissionsDescription": {}, "sendCanceled": "Bidalketa bertan behera utzi da", - "@sendCanceled": {} + "@sendCanceled": {}, + "noChatsFoundHere": "Ez da txatik aurkitu. Hasi norbaitekin txateatzen beheko botoia erabiliz. ⤵️", + "@noChatsFoundHere": {} } From 8009809fe27470b79d04142cb6f1a8a50be67ee5 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Wed, 14 Aug 2024 10:29:13 +0000 Subject: [PATCH 218/288] Translated using Weblate (Ukrainian) Currently translated at 100.0% (658 of 658 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/uk/ --- assets/l10n/intl_uk.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_uk.arb b/assets/l10n/intl_uk.arb index 7c39b63d5d..07c7131474 100644 --- a/assets/l10n/intl_uk.arb +++ b/assets/l10n/intl_uk.arb @@ -2791,5 +2791,7 @@ "unread": "Непрочитані", "@unread": {}, "sendCanceled": "Надсилання скасовано", - "@sendCanceled": {} + "@sendCanceled": {}, + "noChatsFoundHere": "Бесід ще немає. Розпочніть спілкування натиснувши кнопку нижче. ⤵️", + "@noChatsFoundHere": {} } From 7f3847f162a4dbbb2990d9738e09c64901ba1bf5 Mon Sep 17 00:00:00 2001 From: Rex_sa Date: Wed, 14 Aug 2024 17:15:42 +0000 Subject: [PATCH 219/288] Translated using Weblate (Arabic) Currently translated at 100.0% (658 of 658 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ar/ --- assets/l10n/intl_ar.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_ar.arb b/assets/l10n/intl_ar.arb index 59859a8a91..e9f73d8860 100644 --- a/assets/l10n/intl_ar.arb +++ b/assets/l10n/intl_ar.arb @@ -2791,5 +2791,7 @@ } }, "sendCanceled": "تم إلغاء الإرسال", - "@sendCanceled": {} + "@sendCanceled": {}, + "noChatsFoundHere": "لم يتم العثور على دردشات هنا حتى الآن. ابدأ محادثة جديدة مع شخص ما باستخدام الزر أدناه. ⤵️", + "@noChatsFoundHere": {} } From 7fa54b0a8f949b91f14cd7cb4c5c769e0e0463d8 Mon Sep 17 00:00:00 2001 From: xabirequejo Date: Thu, 15 Aug 2024 15:40:22 +0000 Subject: [PATCH 220/288] Translated using Weblate (Basque) Currently translated at 100.0% (658 of 658 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/ --- assets/l10n/intl_eu.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/l10n/intl_eu.arb b/assets/l10n/intl_eu.arb index 7b8aa8c301..2f377e98aa 100644 --- a/assets/l10n/intl_eu.arb +++ b/assets/l10n/intl_eu.arb @@ -2486,7 +2486,7 @@ "@thisDevice": {}, "decline": "Baztertu", "@decline": {}, - "databaseBuildErrorBody": "Ezin izan da SQlite datu-basea eraiki. Aplikazioa aurreko datu-basea erabiltzen saiatuko da oraingoz. Jakinarazi errorea garatzaileei {url} helbidean. Errorearen mezua ondorengoa da: {error}", + "databaseBuildErrorBody": "Ezin da SQlite datu-basea eraiki. Aplikazioa aurreko datu-basea erabiltzen saiatuko da oraingoz. Jakinarazi errorea garatzaileei {url} helbidean. Errorearen mezua ondorengoa da: {error}", "@databaseBuildErrorBody": { "type": "text", "placeholders": { From 249c6e0fa27f022976f04b2df255f1832c36613c Mon Sep 17 00:00:00 2001 From: krille-chan Date: Tue, 20 Aug 2024 19:56:33 +0200 Subject: [PATCH 221/288] build: Update video compress and other dependencies --- pubspec.lock | 40 ++++++++++++++++++++-------------------- pubspec.yaml | 2 +- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 2163a8d874..98aa4393ae 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1082,18 +1082,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -1194,10 +1194,10 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" matrix: dependency: "direct main" description: @@ -1210,10 +1210,10 @@ packages: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mgrs_dart: dependency: transitive description: @@ -1442,10 +1442,10 @@ packages: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" platform_detect: dependency: transitive description: @@ -1967,26 +1967,26 @@ packages: dependency: transitive description: name: test - sha256: "7ee446762c2c50b3bd4ea96fe13ffac69919352bd3b4b17bac3f3465edc58073" + sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e" url: "https://pub.dev" source: hosted - version: "1.25.2" + version: "1.25.7" test_api: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" test_core: dependency: transitive description: name: test_core - sha256: "2bc4b4ecddd75309300d8096f781c0e3280ca1ef85beda558d33fcbedc2eead4" + sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.4" timezone: dependency: transitive description: @@ -2223,10 +2223,10 @@ packages: dependency: "direct main" description: name: video_compress - sha256: "407693726e674a1e1958801deb2d9daf5a5297707ba6d03375007012dae7389a" + sha256: "5b42d89f3970c956bad7a86c29682b0892c11a4ddf95ae6e29897ee28788e377" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" video_player: dependency: "direct main" description: @@ -2279,10 +2279,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.4" wakelock_plus: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index d256a852f2..853df17441 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -89,7 +89,7 @@ dependencies: unifiedpush: ^5.0.1 universal_html: ^2.2.4 url_launcher: ^6.2.5 - video_compress: ^3.1.2 + video_compress: ^3.1.3 video_player: ^2.8.5 wakelock_plus: ^1.2.2 webrtc_interface: ^1.0.13 From 54040d841a1c0eaa68226b6a4670f6b41cf8c565 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 21 Aug 2024 14:01:24 -0400 Subject: [PATCH 222/288] add draft construct uses while using language assistance, added morphs to learning progress indicators --- assets/l10n/intl_en.arb | 5 +- lib/pages/chat/chat_view.dart | 2 +- .../controllers/choreographer.dart | 1 - .../controllers/it_controller.dart | 16 + lib/pangea/choreographer/widgets/it_bar.dart | 16 + .../controllers/my_analytics_controller.dart | 68 ++-- lib/pangea/enum/construct_type_enum.dart | 6 + lib/pangea/enum/progress_indicators_enum.dart | 12 +- .../pangea_message_event.dart | 10 +- .../models/analytics/constructs_model.dart | 2 +- lib/pangea/models/choreo_record.dart | 61 ---- .../models/representation_content_model.dart | 60 +++- .../widgets/animations/gain_points.dart | 19 +- .../learning_progress_indicators.dart | 9 + lib/pangea/widgets/igc/span_card.dart | 313 ++++++++++-------- 15 files changed, 327 insertions(+), 273 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 53f439316c..930cd20f74 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4017,7 +4017,7 @@ "conversationBotTextAdventureZone_title": "Text Adventure", "conversationBotTextAdventureZone_instructionLabel": "Game Master Instructions", "conversationBotTextAdventureZone_instructionPlaceholder": "Set game master instructions", - "conversationBotCustomZone_instructionSystemPromptEmptyError": "Missing game master instructions", + "conversationBotCustomZone_instructionSystemPromptEmptyError": "Missing game master instructions", "studentAnalyticsNotAvailable": "Student data not currently available", "roomDataMissing": "Some data may be missing from rooms in which you are not a member.", "updatePhoneOS": "You may need to update your device's OS version.", @@ -4125,5 +4125,6 @@ "wordsUsed": "Words Used", "errorTypes": "Error Types", "level": "Level", - "canceledSend": "Canceled send" + "canceledSend": "Canceled send", + "morphsUsed": "Morphs Used" } \ No newline at end of file diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 4a7d288d86..ea48c202d2 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -470,7 +470,7 @@ class ChatView extends StatelessWidget { Row( children: [ const PointsGainedAnimation( - color: Colors.blue, + gainColor: Colors.blue, ), ChatFloatingActionButton( controller: controller, diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index bafd1bbefd..1a11de0e3f 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -411,7 +411,6 @@ class Choreographer { choreoRecord = ChoreoRecord.newRecord; itController.clear(); igc.clear(); - pangeaController.myAnalytics.clearDraftConstructUses(roomId); // errorService.clear(); _resetDebounceTimer(); } diff --git a/lib/pangea/choreographer/controllers/it_controller.dart b/lib/pangea/choreographer/controllers/it_controller.dart index 3187d4a6a4..0067eee860 100644 --- a/lib/pangea/choreographer/controllers/it_controller.dart +++ b/lib/pangea/choreographer/controllers/it_controller.dart @@ -3,6 +3,8 @@ import 'dart:developer'; import 'package:fluffychat/pangea/choreographer/controllers/error_service.dart'; import 'package:fluffychat/pangea/constants/choreo_constants.dart'; +import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart'; +import 'package:fluffychat/pangea/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -285,6 +287,20 @@ class ITController { showChoiceFeedback = true; + // Get a list of the choices that the user did not click + final List? ignoredTokens = currentITStep?.continuances + .where((e) => !e.wasClicked) + .map((e) => e.tokens) + .expand((e) => e) + .toList(); + + // Save those choices' tokens to local construct analytics as ignored tokens + choreographer.pangeaController.myAnalytics.addDraftUses( + ignoredTokens ?? [], + choreographer.roomId, + ConstructUseTypeEnum.ignIt, + ); + Future.delayed( const Duration( milliseconds: ChoreoConstants.millisecondsToDisplayFeedback, diff --git a/lib/pangea/choreographer/widgets/it_bar.dart b/lib/pangea/choreographer/widgets/it_bar.dart index 536fd4610a..5d47cce7ed 100644 --- a/lib/pangea/choreographer/widgets/it_bar.dart +++ b/lib/pangea/choreographer/widgets/it_bar.dart @@ -7,7 +7,9 @@ import 'package:fluffychat/pangea/choreographer/widgets/it_bar_buttons.dart'; import 'package:fluffychat/pangea/choreographer/widgets/it_feedback_card.dart'; import 'package:fluffychat/pangea/choreographer/widgets/translation_finished_flow.dart'; import 'package:fluffychat/pangea/constants/choreo_constants.dart'; +import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; +import 'package:fluffychat/pangea/widgets/animations/gain_points.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -76,7 +78,12 @@ class ITBarState extends State { width: double.infinity, padding: const EdgeInsets.fromLTRB(0, 3, 3, 3), child: Stack( + alignment: Alignment.topCenter, children: [ + const Positioned( + top: 60, + child: PointsGainedAnimation(), + ), SingleChildScrollView( child: Column( children: [ @@ -334,6 +341,15 @@ class ITChoices extends StatelessWidget { continuance.feedbackText(context), ); } + if (!continuance.wasClicked) { + controller.choreographer.pangeaController.myAnalytics.addDraftUses( + continuance.tokens, + controller.choreographer.roomId, + continuance.level > 1 + ? ConstructUseTypeEnum.incIt + : ConstructUseTypeEnum.corIt, + ); + } controller.currentITStep!.continuances[index].wasClicked = true; controller.choreographer.setState(); } diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart index daefe1cd98..c24f3391bf 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/my_analytics_controller.dart @@ -112,7 +112,6 @@ class MyAnalyticsController extends BaseController { void onMessageSent(Map data) { // cancel the last timer that was set on message event and // reset it to fire after _minutesBeforeUpdate minutes - debugPrint("ONE MESSAGE SENT"); _updateTimer?.cancel(); _updateTimer = Timer(Duration(minutes: _minutesBeforeUpdate), () { debugPrint("timer fired, updating analytics"); @@ -138,12 +137,11 @@ class MyAnalyticsController extends BaseController { timeStamp: DateTime.now(), ); - final List constructs = []; + final List constructs = getDraftUses(roomID); if (eventType == EventTypes.Message) { final grammarConstructs = choreo?.grammarConstructUses(metadata: metadata); - final itConstructs = choreo?.itStepsToConstructUses(metadata: metadata); final vocabUses = tokensSent != null ? originalSent?.vocabUses( choreo: choreo, @@ -153,7 +151,6 @@ class MyAnalyticsController extends BaseController { : null; constructs.addAll([ ...(grammarConstructs ?? []), - ...(itConstructs ?? []), ...(vocabUses ?? []), ]); } @@ -171,35 +168,18 @@ class MyAnalyticsController extends BaseController { .filterConstructs(unfilteredConstructs: constructs) .then((filtered) { if (filtered.isEmpty) return; + filtered.addAll(getDraftUses(roomID)); final level = _pangeaController.analytics.level; addLocalMessage(eventID, filtered).then( - (_) => afterAddLocalMessages(level), + (_) { + clearDraftUses(roomID); + afterAddLocalMessages(level); + }, ); }); } - /// Called when the user selects a replacement during IGC - void onReplacementSelected( - List tokens, - String roomID, - bool isBestCorrection, - ) { - final useType = isBestCorrection - ? ConstructUseTypeEnum.corIGC - : ConstructUseTypeEnum.incIGC; - setDraftConstructUses(tokens, roomID, useType); - } - - /// Called when the user ignores a match during IGC - void onIgnoreMatch( - List tokens, - String roomID, - ) { - const useType = ConstructUseTypeEnum.ignIGC; - setDraftConstructUses(tokens, roomID, useType); - } - - void setDraftConstructUses( + void addDraftUses( List tokens, String roomID, ConstructUseTypeEnum useType, @@ -220,19 +200,41 @@ class MyAnalyticsController extends BaseController { ), ) .toList(); - addLocalMessage('draft$roomID', uses); + + final List morphs = tokens + .map((t) => t.morph.values) + .expand((m) => m) + .cast() + .toList(); + + uses.addAll( + morphs.map( + (morph) => OneConstructUse( + useType: useType, + lemma: morph, + constructType: ConstructTypeEnum.morph, + metadata: metadata, + ), + ), + ); + + final level = _pangeaController.analytics.level; + addLocalMessage('draft$roomID', uses).then( + (_) => afterAddLocalMessages(level), + ); } - void clearDraftConstructUses(String roomID) { + List getDraftUses(String roomID) { + final currentCache = _pangeaController.analytics.messagesSinceUpdate; + return currentCache['draft$roomID'] ?? []; + } + + void clearDraftUses(String roomID) { final currentCache = _pangeaController.analytics.messagesSinceUpdate; currentCache.remove('draft$roomID'); setMessagesSinceUpdate(currentCache); } - /// Called when the user selects a continuance during IT - /// TODO implement - void onSelectContinuance() {} - /// Add a list of construct uses for a new message to the local /// cache of recently sent messages Future addLocalMessage( diff --git a/lib/pangea/enum/construct_type_enum.dart b/lib/pangea/enum/construct_type_enum.dart index 7db7f9cd52..86c715fb6e 100644 --- a/lib/pangea/enum/construct_type_enum.dart +++ b/lib/pangea/enum/construct_type_enum.dart @@ -1,6 +1,7 @@ enum ConstructTypeEnum { grammar, vocab, + morph, } extension ConstructExtension on ConstructTypeEnum { @@ -10,6 +11,8 @@ extension ConstructExtension on ConstructTypeEnum { return 'grammar'; case ConstructTypeEnum.vocab: return 'vocab'; + case ConstructTypeEnum.morph: + return 'morph'; } } } @@ -23,6 +26,9 @@ class ConstructTypeUtil { case 'v': case 'vocab': return ConstructTypeEnum.vocab; + case 'm': + case 'morph': + return ConstructTypeEnum.morph; default: return ConstructTypeEnum.vocab; } diff --git a/lib/pangea/enum/progress_indicators_enum.dart b/lib/pangea/enum/progress_indicators_enum.dart index 39032433e8..aa38230e2a 100644 --- a/lib/pangea/enum/progress_indicators_enum.dart +++ b/lib/pangea/enum/progress_indicators_enum.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:material_symbols_icons/symbols.dart'; enum ProgressIndicatorEnum { level, wordsUsed, errorTypes, + morphsUsed, } extension ProgressIndicatorsExtension on ProgressIndicatorEnum { @@ -14,6 +16,8 @@ extension ProgressIndicatorsExtension on ProgressIndicatorEnum { return Icons.text_fields_outlined; case ProgressIndicatorEnum.errorTypes: return Icons.error_outline; + case ProgressIndicatorEnum.morphsUsed: + return Symbols.toys_and_games; case ProgressIndicatorEnum.level: return Icons.star; } @@ -36,8 +40,10 @@ extension ProgressIndicatorsExtension on ProgressIndicatorEnum { return Theme.of(context).brightness == Brightness.dark ? const Color.fromARGB(255, 250, 220, 129) : const Color.fromARGB(255, 255, 208, 67); - default: - return Theme.of(context).textTheme.bodyLarge!.color ?? Colors.blueGrey; + case ProgressIndicatorEnum.morphsUsed: + return Theme.of(context).brightness == Brightness.dark + ? const Color.fromARGB(255, 169, 183, 237) + : const Color.fromARGB(255, 38, 59, 141); } } @@ -49,6 +55,8 @@ extension ProgressIndicatorsExtension on ProgressIndicatorEnum { return L10n.of(context)!.errorTypes; case ProgressIndicatorEnum.level: return L10n.of(context)!.level; + case ProgressIndicatorEnum.morphsUsed: + return L10n.of(context)!.morphsUsed; } } } diff --git a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart index 06359a24b3..bfa7564cfd 100644 --- a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart +++ b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart @@ -665,12 +665,10 @@ class PangeaMessageEvent { l2Code == null ? [] : practiceActivitiesByLangCode(l2Code!); /// all construct uses for the message, including vocab and grammar - List get allConstructUses => - [..._grammarConstructUses, ..._vocabUses, ..._itStepsToConstructUses]; - - /// Returns a list of [OneConstructUse] from itSteps - List get _itStepsToConstructUses => - originalSent?.choreo?.itStepsToConstructUses(event: event) ?? []; + List get allConstructUses => [ + ..._grammarConstructUses, + ..._vocabUses, + ]; /// get construct uses of type vocab for the message List get _vocabUses { diff --git a/lib/pangea/models/analytics/constructs_model.dart b/lib/pangea/models/analytics/constructs_model.dart index 0e62419f02..e618c4307b 100644 --- a/lib/pangea/models/analytics/constructs_model.dart +++ b/lib/pangea/models/analytics/constructs_model.dart @@ -82,9 +82,9 @@ class OneConstructUse { OneConstructUse({ required this.useType, required this.lemma, - required this.form, required this.constructType, required this.metadata, + this.form, this.id, }); diff --git a/lib/pangea/models/choreo_record.dart b/lib/pangea/models/choreo_record.dart index 6fdde333a4..fe95dfc096 100644 --- a/lib/pangea/models/choreo_record.dart +++ b/lib/pangea/models/choreo_record.dart @@ -1,11 +1,9 @@ import 'dart:convert'; -import 'package:fluffychat/pangea/constants/choreo_constants.dart'; import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; import 'package:fluffychat/pangea/models/pangea_match_model.dart'; -import 'package:fluffychat/pangea/models/pangea_token_model.dart'; import 'package:matrix/matrix.dart'; import 'it_step.dart'; @@ -155,65 +153,6 @@ class ChoreoRecord { } return uses; } - - /// Returns a list of [OneConstructUse] from itSteps for which the continuance - /// was selected or ignored. Correct selections are considered in the tokens - /// flow. Once all continuances have lemmas, we can do both correct and incorrect - /// in this flow. It actually doesn't do anything at all right now, because the - /// choregrapher is not returning lemmas for continuances. This is a TODO. - /// So currently only the lemmas can be gotten from the tokens for choices that - /// are actually in the final message. - List itStepsToConstructUses({ - Event? event, - ConstructUseMetaData? metadata, - }) { - final List uses = []; - if (event == null && metadata == null) { - return uses; - } - - metadata ??= ConstructUseMetaData( - roomId: event!.roomId!, - eventId: event.eventId, - timeStamp: event.originServerTs, - ); - - for (final itStep in itSteps) { - for (final continuance in itStep.continuances) { - final List tokensToSave = - continuance.tokens.where((t) => t.lemma.saveVocab).toList(); - - if (finalMessage.contains(continuance.text)) { - continue; - } - if (continuance.wasClicked) { - //PTODO - account for end of flow score - if (continuance.level != ChoreoConstants.levelThresholdForGreen) { - for (final token in tokensToSave) { - uses.add( - token.lemma.toVocabUse( - ConstructUseTypeEnum.incIt, - metadata, - ), - ); - } - } - } else { - if (continuance.level != ChoreoConstants.levelThresholdForGreen) { - for (final token in tokensToSave) { - uses.add( - token.lemma.toVocabUse( - ConstructUseTypeEnum.ignIt, - metadata, - ), - ); - } - } - } - } - } - return uses; - } } /// A new ChoreoRecordStep is saved in the following cases: diff --git a/lib/pangea/models/representation_content_model.dart b/lib/pangea/models/representation_content_model.dart index 3600267f34..be68c11959 100644 --- a/lib/pangea/models/representation_content_model.dart +++ b/lib/pangea/models/representation_content_model.dart @@ -1,3 +1,4 @@ +import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; import 'package:fluffychat/pangea/models/choreo_record.dart'; @@ -118,8 +119,8 @@ class PangeaRepresentation { final tokensToSave = tokens.where((token) => token.lemma.saveVocab).toList(); for (final token in tokensToSave) { - uses.add( - getVocabUseForToken( + uses.addAll( + getUsesForToken( token, metadata, choreo: choreo, @@ -137,21 +138,38 @@ class PangeaRepresentation { /// If the [token] is in the [choreo.acceptedOrIgnoredMatch], it is considered to be a [ConstructUseTypeEnum.ga]. /// If the [token] is in the [choreo.acceptedOrIgnoredMatch.choices], it is considered to be a [ConstructUseTypeEnum.corIt]. /// If the [token] is not included in any choreoStep, it is considered to be a [ConstructUseTypeEnum.wa]. - OneConstructUse getVocabUseForToken( + List getUsesForToken( PangeaToken token, ConstructUseMetaData metadata, { ChoreoRecord? choreo, }) { + final List uses = []; final lemma = token.lemma; final content = token.text.content; + final morphs = token.morph.values.toList(); if (choreo == null) { final bool inUserL2 = langCode == MatrixState.pangeaController.languageController.activeL2Code(); - return lemma.toVocabUse( - inUserL2 ? ConstructUseTypeEnum.wa : ConstructUseTypeEnum.unk, - metadata, + final useType = + inUserL2 ? ConstructUseTypeEnum.wa : ConstructUseTypeEnum.unk; + uses.addAll( + morphs.map( + (morph) => OneConstructUse( + useType: useType, + lemma: morph, + constructType: ConstructTypeEnum.morph, + metadata: metadata, + ), + ), + ); + uses.add( + lemma.toVocabUse( + inUserL2 ? ConstructUseTypeEnum.wa : ConstructUseTypeEnum.unk, + metadata, + ), ); + return uses; } for (final step in choreo.choreoSteps) { @@ -174,10 +192,7 @@ class PangeaRepresentation { step.text.contains(choice.value), ); if (stepContainedToken) { - return lemma.toVocabUse( - ConstructUseTypeEnum.ga, - metadata, - ); + return []; } } @@ -185,16 +200,27 @@ class PangeaRepresentation { final bool pickedThroughIT = step.itStep!.chosenContinuance!.text.contains(content); if (pickedThroughIT) { - return lemma.toVocabUse( - ConstructUseTypeEnum.corIt, - metadata, - ); + return []; } } } - return lemma.toVocabUse( - ConstructUseTypeEnum.wa, - metadata, + + uses.addAll( + morphs.map( + (morph) => OneConstructUse( + useType: ConstructUseTypeEnum.wa, + lemma: morph, + constructType: ConstructTypeEnum.morph, + metadata: metadata, + ), + ), ); + uses.add( + lemma.toVocabUse( + ConstructUseTypeEnum.wa, + metadata, + ), + ); + return uses; } } diff --git a/lib/pangea/widgets/animations/gain_points.dart b/lib/pangea/widgets/animations/gain_points.dart index dfbee2931c..13ae324e71 100644 --- a/lib/pangea/widgets/animations/gain_points.dart +++ b/lib/pangea/widgets/animations/gain_points.dart @@ -6,8 +6,13 @@ import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; class PointsGainedAnimation extends StatefulWidget { - final Color? color; - const PointsGainedAnimation({super.key, this.color}); + final Color? gainColor; + final Color? loseColor; + const PointsGainedAnimation({ + super.key, + this.gainColor, + this.loseColor = Colors.red, + }); @override PointsGainedAnimationState createState() => PointsGainedAnimationState(); @@ -66,7 +71,7 @@ class PointsGainedAnimationState extends State void _showPointsGained(List constructs) { setState(() => _addedPoints = (_currentXP ?? 0) - (_prevXP ?? 0)); - if (_prevXP != _currentXP && !_controller.isAnimating) { + if (_prevXP != _currentXP) { _controller.reset(); _controller.forward(); } @@ -82,18 +87,20 @@ class PointsGainedAnimationState extends State Widget build(BuildContext context) { if (!animate) return const SizedBox(); + final textColor = _addedPoints! > 0 ? widget.gainColor : widget.loseColor; + return SlideTransition( position: _offsetAnimation, child: FadeTransition( opacity: _fadeAnimation, child: Text( - '+$_addedPoints', + '${_addedPoints! > 0 ? '+' : ''}$_addedPoints', style: BotStyle.text( context, big: true, - setColor: widget.color == null, + setColor: textColor == null, existingStyle: TextStyle( - color: widget.color, + color: textColor, ), ), ), diff --git a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart index bf8bf2f96f..f74217eb98 100644 --- a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart +++ b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart @@ -45,6 +45,9 @@ class LearningProgressIndicatorsState /// Grammar constructs model ConstructListModel? errors; + /// Morph constructs model + ConstructListModel? morphs; + bool loading = true; int get serverXP => _pangeaController.analytics.serverXP; @@ -78,6 +81,10 @@ class LearningProgressIndicatorsState type: ConstructTypeEnum.grammar, uses: constructs, ); + morphs = ConstructListModel( + type: ConstructTypeEnum.morph, + uses: constructs, + ); if (loading) loading = false; if (mounted) setState(() {}); @@ -90,6 +97,8 @@ class LearningProgressIndicatorsState return words?.lemmas.length; case ProgressIndicatorEnum.errorTypes: return errors?.lemmas.length; + case ProgressIndicatorEnum.morphsUsed: + return morphs?.lemmas.length; case ProgressIndicatorEnum.level: return level; } diff --git a/lib/pangea/widgets/igc/span_card.dart b/lib/pangea/widgets/igc/span_card.dart index a431f56663..5ddf1b0c5c 100644 --- a/lib/pangea/widgets/igc/span_card.dart +++ b/lib/pangea/widgets/igc/span_card.dart @@ -1,11 +1,14 @@ import 'dart:developer'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart'; import 'package:fluffychat/pangea/enum/span_data_type.dart'; +import 'package:fluffychat/pangea/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/models/span_data.dart'; import 'package:fluffychat/pangea/utils/bot_style.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/match_copy.dart'; +import 'package:fluffychat/pangea/widgets/animations/gain_points.dart'; import 'package:fluffychat/pangea/widgets/igc/card_error_widget.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -120,6 +123,16 @@ class SpanCardState extends State { Future onChoiceSelect(int index) async { selectedChoiceIndex = index; if (selectedChoice != null) { + if (!selectedChoice!.selected) { + MatrixState.pangeaController.myAnalytics.addDraftUses( + selectedChoice!.tokens, + widget.roomId, + selectedChoice!.isBestCorrection + ? ConstructUseTypeEnum.corIGC + : ConstructUseTypeEnum.incIGC, + ); + } + selectedChoice!.timestamp = DateTime.now(); selectedChoice!.selected = true; setState( @@ -130,41 +143,44 @@ class SpanCardState extends State { } } - void onReplaceSelected() { - if (selectedChoice != null) { - final tokens = widget.scm.choreographer.igc.igcTextData - ?.matchTokens(widget.scm.matchIndex) ?? - []; - MatrixState.pangeaController.myAnalytics.onReplacementSelected( - tokens, - widget.roomId, - selectedChoice!.isBestCorrection, - ); - } + /// Returns the list of choices that are not selected + List? get ignoredMatches => widget.scm.pangeaMatch?.match.choices + ?.where((choice) => !choice.selected) + .toList(); + + /// Returns the list of tokens from choices that are not selected + List? get ignoredTokens => ignoredMatches + ?.expand((choice) => choice.tokens) + .toList() + .cast(); + + /// Adds the ignored tokens to locally cached analytics + void addIgnoredTokenUses() { + MatrixState.pangeaController.myAnalytics.addDraftUses( + ignoredTokens ?? [], + widget.roomId, + ConstructUseTypeEnum.ignIGC, + ); + } + void onReplaceSelected() { + addIgnoredTokenUses(); widget.scm .onReplacementSelect( - matchIndex: widget.scm.matchIndex, - choiceIndex: selectedChoiceIndex!, - ) - .then((value) { - setState(() {}); - }); + matchIndex: widget.scm.matchIndex, + choiceIndex: selectedChoiceIndex!, + ) + .then((value) => setState(() {})); } void onIgnoreMatch() { MatrixState.pAnyState.closeOverlay(); + addIgnoredTokenUses(); + Future.delayed( Duration.zero, () { widget.scm.onIgnore(); - final tokens = widget.scm.choreographer.igc.igcTextData - ?.matchTokens(widget.scm.matchIndex) ?? - []; - MatrixState.pangeaController.myAnalytics.onIgnoreMatch( - tokens, - widget.roomId, - ); }, ); } @@ -205,142 +221,153 @@ class WordMatchContent extends StatelessWidget { final ScrollController scrollController = ScrollController(); try { - return Column( + return Stack( + alignment: Alignment.topCenter, children: [ - // if (!controller.widget.scm.pangeaMatch!.isITStart) - CardHeader( - text: controller.error?.toString() ?? matchCopy.title, - botExpression: controller.error == null - ? controller.currentExpression - : BotExpression.addled, + const Positioned( + top: 40, + child: PointsGainedAnimation(), ), - Expanded( - child: Scrollbar( - controller: scrollController, - thumbVisibility: true, - child: SingleChildScrollView( - controller: scrollController, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - // const SizedBox(height: 10.0), - // if (matchCopy.description != null) - // Padding( - // padding: const EdgeInsets.only(), - // child: Text( - // matchCopy.description!, - // style: BotStyle.text(context), - // ), - // ), - const SizedBox(height: 8), - if (!controller.widget.scm.pangeaMatch!.isITStart) - ChoicesArray( - originalSpan: - controller.widget.scm.pangeaMatch!.matchContent, - isLoading: controller.fetchingData, - choices: - controller.widget.scm.pangeaMatch!.match.choices - ?.map( - (e) => Choice( - text: e.value, - color: e.selected ? e.type.color : null, - isGold: e.type.name == 'bestCorrection', - ), - ) - .toList(), - onPressed: controller.onChoiceSelect, - uniqueKeyForLayerLink: (int index) => "wordMatch$index", - selectedChoiceIndex: controller.selectedChoiceIndex, - ), - const SizedBox(height: 12), - PromptAndFeedback(controller: controller), - ], - ), - ), - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + Column( children: [ - const SizedBox(width: 10), + // if (!controller.widget.scm.pangeaMatch!.isITStart) + CardHeader( + text: controller.error?.toString() ?? matchCopy.title, + botExpression: controller.error == null + ? controller.currentExpression + : BotExpression.addled, + ), Expanded( - child: Opacity( - opacity: 0.8, - child: TextButton( - style: ButtonStyle( - backgroundColor: WidgetStateProperty.all( - AppConfig.primaryColor.withOpacity(0.1), - ), - ), - onPressed: controller.onIgnoreMatch, - child: Center( - child: Text(L10n.of(context)!.ignoreInThisText), + child: Scrollbar( + controller: scrollController, + thumbVisibility: true, + child: SingleChildScrollView( + controller: scrollController, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // const SizedBox(height: 10.0), + // if (matchCopy.description != null) + // Padding( + // padding: const EdgeInsets.only(), + // child: Text( + // matchCopy.description!, + // style: BotStyle.text(context), + // ), + // ), + const SizedBox(height: 8), + if (!controller.widget.scm.pangeaMatch!.isITStart) + ChoicesArray( + originalSpan: + controller.widget.scm.pangeaMatch!.matchContent, + isLoading: controller.fetchingData, + choices: + controller.widget.scm.pangeaMatch!.match.choices + ?.map( + (e) => Choice( + text: e.value, + color: e.selected ? e.type.color : null, + isGold: e.type.name == 'bestCorrection', + ), + ) + .toList(), + onPressed: controller.onChoiceSelect, + uniqueKeyForLayerLink: (int index) => + "wordMatch$index", + selectedChoiceIndex: controller.selectedChoiceIndex, + ), + const SizedBox(height: 12), + PromptAndFeedback(controller: controller), + ], ), ), ), ), - const SizedBox(width: 10), - if (!controller.widget.scm.pangeaMatch!.isITStart) - Expanded( - child: Opacity( - opacity: controller.selectedChoiceIndex != null ? 1.0 : 0.5, - child: TextButton( - onPressed: controller.selectedChoiceIndex != null - ? controller.onReplaceSelected - : null, - style: ButtonStyle( - backgroundColor: WidgetStateProperty.all( - (controller.selectedChoice != null - ? controller.selectedChoice!.color - : AppConfig.primaryColor) - .withOpacity(0.2), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const SizedBox(width: 10), + Expanded( + child: Opacity( + opacity: 0.8, + child: TextButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + AppConfig.primaryColor.withOpacity(0.1), + ), + ), + onPressed: controller.onIgnoreMatch, + child: Center( + child: Text(L10n.of(context)!.ignoreInThisText), ), - // Outline if Replace button enabled - side: controller.selectedChoice != null - ? WidgetStateProperty.all( - BorderSide( - color: controller.selectedChoice!.color, - style: BorderStyle.solid, - width: 2.0, - ), - ) - : null, ), - child: Text(L10n.of(context)!.replace), ), ), - ), - const SizedBox(width: 10), - if (controller.widget.scm.pangeaMatch!.isITStart) - Expanded( - child: TextButton( - onPressed: () { - MatrixState.pAnyState.closeOverlay(); - Future.delayed( - Duration.zero, - () => controller.widget.scm.onITStart(), - ); - }, - style: ButtonStyle( - backgroundColor: WidgetStateProperty.all( - (AppConfig.primaryColor).withOpacity(0.1), + const SizedBox(width: 10), + if (!controller.widget.scm.pangeaMatch!.isITStart) + Expanded( + child: Opacity( + opacity: + controller.selectedChoiceIndex != null ? 1.0 : 0.5, + child: TextButton( + onPressed: controller.selectedChoiceIndex != null + ? controller.onReplaceSelected + : null, + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + (controller.selectedChoice != null + ? controller.selectedChoice!.color + : AppConfig.primaryColor) + .withOpacity(0.2), + ), + // Outline if Replace button enabled + side: controller.selectedChoice != null + ? WidgetStateProperty.all( + BorderSide( + color: controller.selectedChoice!.color, + style: BorderStyle.solid, + width: 2.0, + ), + ) + : null, + ), + child: Text(L10n.of(context)!.replace), + ), ), ), - child: Text(L10n.of(context)!.helpMeTranslate), - ), + const SizedBox(width: 10), + if (controller.widget.scm.pangeaMatch!.isITStart) + Expanded( + child: TextButton( + onPressed: () { + MatrixState.pAnyState.closeOverlay(); + Future.delayed( + Duration.zero, + () => controller.widget.scm.onITStart(), + ); + }, + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + (AppConfig.primaryColor).withOpacity(0.1), + ), + ), + child: Text(L10n.of(context)!.helpMeTranslate), + ), + ), + ], + ), + if (controller.widget.scm.pangeaMatch!.isITStart) + DontShowSwitchListTile( + controller: pangeaController, + onSwitch: (bool value) { + pangeaController.userController.updateProfile((profile) { + profile.userSettings.itAutoPlay = value; + return profile; + }); + }, ), ], ), - if (controller.widget.scm.pangeaMatch!.isITStart) - DontShowSwitchListTile( - controller: pangeaController, - onSwitch: (bool value) { - pangeaController.userController.updateProfile((profile) { - profile.userSettings.itAutoPlay = value; - return profile; - }); - }, - ), ], ); } on Exception catch (e) { From 4e2cd81095a30fbc3279e54fbca8700d195bb677 Mon Sep 17 00:00:00 2001 From: "lauren n. liberda" Date: Thu, 22 Aug 2024 16:52:09 +0200 Subject: [PATCH 223/288] chore: upgrade flutter to 3.24.1 --- .github/workflows/versions.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/versions.env b/.github/workflows/versions.env index 8bc2c377f1..a7af31db7e 100644 --- a/.github/workflows/versions.env +++ b/.github/workflows/versions.env @@ -1,2 +1,2 @@ -FLUTTER_VERSION=3.24.0 +FLUTTER_VERSION=3.24.1 JAVA_VERSION=17 From 05d595bb155685e919a62384f8bf3807d178ea87 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Aug 2024 20:52:16 +0000 Subject: [PATCH 224/288] build(deps): bump rexml from 3.3.3 to 3.3.6 in /ios Bumps [rexml](https://github.com/ruby/rexml) from 3.3.3 to 3.3.6. - [Release notes](https://github.com/ruby/rexml/releases) - [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md) - [Commits](https://github.com/ruby/rexml/compare/v3.3.3...v3.3.6) --- updated-dependencies: - dependency-name: rexml dependency-type: indirect ... Signed-off-by: dependabot[bot] --- ios/Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Gemfile.lock b/ios/Gemfile.lock index c2a9c1b968..3854e94090 100644 --- a/ios/Gemfile.lock +++ b/ios/Gemfile.lock @@ -156,7 +156,7 @@ GEM trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.3.3) + rexml (3.3.6) strscan rouge (2.0.7) ruby2_keywords (0.0.4) From 3ce03ecc2f4c56ea1e782cdb3cbc0aa426aaa87a Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 23 Aug 2024 14:19:22 -0400 Subject: [PATCH 225/288] adjust position of points gain animation over new messages --- lib/pages/chat/chat_view.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 689ac4e702..12437c8ac0 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -474,9 +474,12 @@ class ChatView extends StatelessWidget { ), Row( children: [ - const PointsGainedAnimation( - gainColor: Colors.blue, + PointsGainedAnimation( + gainColor: Theme.of(context) + .colorScheme + .onPrimary, ), + const SizedBox(width: 100), ChatFloatingActionButton( controller: controller, ), From 6d4efea3223c248608ba9c92ea8be504d396703b Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 23 Aug 2024 14:19:59 -0400 Subject: [PATCH 226/288] clear out analytics stream on dispose to prevent carry over across login sessions --- .../controllers/get_analytics_controller.dart | 4 ++- .../learning_progress_indicators.dart | 34 +++++++++++-------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/lib/pangea/controllers/get_analytics_controller.dart b/lib/pangea/controllers/get_analytics_controller.dart index dff3bee90a..e23b30d1e3 100644 --- a/lib/pangea/controllers/get_analytics_controller.dart +++ b/lib/pangea/controllers/get_analytics_controller.dart @@ -59,6 +59,8 @@ class GetAnalyticsController { _analyticsUpdateSubscription?.cancel(); _analyticsUpdateSubscription = null; _cache.clear(); + analyticsStream.add([]); + prevXP = null; } Future onAnalyticsUpdate(AnalyticsUpdateType type) async { @@ -77,7 +79,7 @@ class GetAnalyticsController { } // set the previous XP to the currentXP - if (analyticsStream.value != null) { + if (analyticsStream.value != null && analyticsStream.value!.isNotEmpty) { prevXP = calcXP(analyticsStream.value!); } diff --git a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart index f74217eb98..171f8ac643 100644 --- a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart +++ b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart @@ -7,7 +7,6 @@ import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; import 'package:fluffychat/pangea/enum/progress_indicators_enum.dart'; import 'package:fluffychat/pangea/models/analytics/construct_list_model.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; -import 'package:fluffychat/pangea/widgets/animations/gain_points.dart'; import 'package:fluffychat/pangea/widgets/animations/progress_bar/progress_bar.dart'; import 'package:fluffychat/pangea/widgets/animations/progress_bar/progress_bar_details.dart'; import 'package:fluffychat/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart'; @@ -50,9 +49,19 @@ class LearningProgressIndicatorsState bool loading = true; - int get serverXP => _pangeaController.analytics.serverXP; - int get totalXP => _pangeaController.analytics.currentXP; - int get level => _pangeaController.analytics.level; + // Some buggy stuff is happening with this data not being updated at login, so switching + // to stateful variables for now. Will switch this back later when I have more time to + // figure out why it's now working. + // int get serverXP => _pangeaController.analytics.serverXP; + // int get totalXP => _pangeaController.analytics.currentXP; + // int get level => _pangeaController.analytics.level; + List currentConstructs = []; + int get currentXP => _pangeaController.analytics.calcXP(currentConstructs); + int get localXP => _pangeaController.analytics.calcXP( + _pangeaController.analytics.locallyCachedConstructs, + ); + int get serverXP => currentXP - localXP; + int get level => currentXP ~/ AnalyticsConstants.xpPerLevel; @override void initState() { @@ -60,7 +69,8 @@ class LearningProgressIndicatorsState updateAnalyticsData( _pangeaController.analytics.analyticsStream.value ?? [], ); - _pangeaController.analytics.analyticsStream.stream + _analyticsUpdateSubscription = _pangeaController + .analytics.analyticsStream.stream .listen(updateAnalyticsData); } @@ -86,6 +96,7 @@ class LearningProgressIndicatorsState uses: constructs, ); + currentConstructs = constructs; if (loading) loading = false; if (mounted) setState(() {}); } @@ -105,11 +116,6 @@ class LearningProgressIndicatorsState } double get levelBarWidth => FluffyThemes.columnWidth - (32 * 2) - 25; - double get pointsBarWidth { - final percent = (totalXP % AnalyticsConstants.xpPerLevel) / - AnalyticsConstants.xpPerLevel; - return levelBarWidth * percent; - } Color levelColor(int level) { final colors = [ @@ -133,7 +139,7 @@ class LearningProgressIndicatorsState levelBars: [ LevelBarDetails( fillColor: const Color.fromARGB(255, 0, 190, 83), - currentPoints: totalXP, + currentPoints: currentXP, ), LevelBarDetails( fillColor: Theme.of(context).colorScheme.primary, @@ -171,9 +177,9 @@ class LearningProgressIndicatorsState return Stack( alignment: Alignment.topCenter, children: [ - const Positioned( - child: PointsGainedAnimation(), - ), + // const Positioned( + // child: PointsGainedAnimation(), + // ), Column( mainAxisSize: MainAxisSize.min, children: [ From 4d62039dd4bd181789485b31ca00fe6d9e3ef2c0 Mon Sep 17 00:00:00 2001 From: KaMeHb-UA Date: Fri, 23 Aug 2024 19:57:22 +0300 Subject: [PATCH 227/288] build: return `.gitkeep` file after removing assets to prevent Git see the deletion --- scripts/prepare-web.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/prepare-web.sh b/scripts/prepare-web.sh index 70b15a246a..c53136a37b 100755 --- a/scripts/prepare-web.sh +++ b/scripts/prepare-web.sh @@ -1,5 +1,7 @@ #!/bin/sh -ve rm -r assets/js/package +mkdir -p assets/js/package +touch assets/js/package/.gitkeep OLM_VERSION=$(cat pubspec.yaml | yq .dependencies.flutter_olm) DOWNLOAD_PATH="https://github.com/famedly/olm/releases/download/v$OLM_VERSION/olm.zip" From bd41aeb45ec48b94a5950542076820809a7cd419 Mon Sep 17 00:00:00 2001 From: KaMeHb-UA Date: Sat, 24 Aug 2024 12:21:51 +0300 Subject: [PATCH 228/288] fix: use raw `yq` query result (formatted one may result in `"d.d.d"` string on some systems) --- scripts/prepare-web.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/prepare-web.sh b/scripts/prepare-web.sh index c53136a37b..f55e5ed2de 100755 --- a/scripts/prepare-web.sh +++ b/scripts/prepare-web.sh @@ -3,7 +3,7 @@ rm -r assets/js/package mkdir -p assets/js/package touch assets/js/package/.gitkeep -OLM_VERSION=$(cat pubspec.yaml | yq .dependencies.flutter_olm) +OLM_VERSION=$(cat pubspec.yaml | yq -r .dependencies.flutter_olm) DOWNLOAD_PATH="https://github.com/famedly/olm/releases/download/v$OLM_VERSION/olm.zip" cd assets/js/ && curl -L $DOWNLOAD_PATH > olm.zip && cd ../../ From 75800ccdd5263707f73d8a2bc37f93b2a8b7ce31 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sun, 25 Aug 2024 08:59:35 +0200 Subject: [PATCH 229/288] chore: Follow up send file dialog fix --- lib/pages/chat/send_file_dialog.dart | 34 ++++++++++++++++------------ 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/lib/pages/chat/send_file_dialog.dart b/lib/pages/chat/send_file_dialog.dart index 79b4eed83e..edcb981f3d 100644 --- a/lib/pages/chat/send_file_dialog.dart +++ b/lib/pages/chat/send_file_dialog.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -33,6 +31,8 @@ class SendFileDialogState extends State { static const int minSizeToCompress = 20 * 1024; Future _send() async { + final scaffoldMessenger = ScaffoldMessenger.of(context); + final l10n = L10n.of(context)!; for (var file in widget.files) { MatrixImageFile? thumbnail; if (file is MatrixVideoFile && file.bytes.length > minSizeToCompress) { @@ -44,20 +44,24 @@ class SendFileDialogState extends State { }, ); } - try { - await widget.room.sendFileEvent( - file, - thumbnail: thumbnail, - shrinkImageMaxDimension: origImage ? null : 1600, - ); - } on IOException catch (_) { - } on FileTooBigMatrixException catch (_) { - } catch (e, s) { - if (mounted) { + widget.room + .sendFileEvent( + file, + thumbnail: thumbnail, + shrinkImageMaxDimension: origImage ? null : 1600, + ) + .catchError( + (e, s) { + if (e is FileTooBigMatrixException) { + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(l10n.fileIsTooBigForServer)), + ); + return null; + } ErrorReporter(context, 'Unable to send file').onErrorCallback(e, s); - } - rethrow; - } + return null; + }, + ); } Navigator.of(context, rootNavigator: false).pop(); From f17b09f56c20e2776daaa3cab1b7af9fa7ee240e Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sun, 25 Aug 2024 09:02:51 +0200 Subject: [PATCH 230/288] chore: Follow up set read marker --- lib/pages/chat/chat.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index a373b87eaa..7772e5f653 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -300,6 +300,9 @@ class ChatController extends State _showScrollUpMaterialBanner(readMarkerEventId); } + // Mark room as read on first visit if requirements are fulfilled + setReadMarker(); + if (!mounted) return; } catch (e, s) { ErrorReporter(context, 'Unable to load timeline').onErrorCallback(e, s); From dec588d0c0855a8c2a45d132e6f410f449b0a24c Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sat, 17 Aug 2024 12:16:55 +0200 Subject: [PATCH 231/288] refactor: Simplify login UX --- assets/l10n/intl_en.arb | 6 +- lib/config/routes.dart | 4 +- lib/config/themes.dart | 15 +- lib/pages/bootstrap/bootstrap_dialog.dart | 4 +- lib/pages/chat_list/chat_list_header.dart | 1 + lib/pages/chat_list/space_view.dart | 1 + .../homeserver_picker/homeserver_app_bar.dart | 109 ------- .../homeserver_bottom_sheet.dart | 53 ---- .../homeserver_picker/homeserver_picker.dart | 67 ++-- .../homeserver_picker_view.dart | 293 ++++++++---------- .../homeserver_picker/public_homeserver.dart | 73 ----- lib/pages/login/login_view.dart | 24 +- lib/widgets/layouts/login_scaffold.dart | 2 +- pubspec.lock | 4 +- pubspec.yaml | 2 +- 15 files changed, 172 insertions(+), 486 deletions(-) delete mode 100644 lib/pages/homeserver_picker/homeserver_app_bar.dart delete mode 100644 lib/pages/homeserver_picker/homeserver_bottom_sheet.dart delete mode 100644 lib/pages/homeserver_picker/public_homeserver.dart diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index aa2d054206..e9146f2cf5 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -2756,5 +2756,9 @@ } }, "changelog": "Changelog", - "sendCanceled": "Sending canceled" + "sendCanceled": "Sending canceled", + "loginWithMatrixId": "Login with Matrix-ID", + "discoverHomeservers": "Discover homeservers", + "whatIsAHomeserver": "What is a homeserver?", + "homeserverDescription": "All your data is stored on the homeserver, just like an email provider. You can choose which homeserver you want to use, while you can still communicate with everyone. Learn more at at https://matrix.org." } diff --git a/lib/config/routes.dart b/lib/config/routes.dart index d11cd56db6..b839dd3f30 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -62,7 +62,7 @@ abstract class AppRoutes { pageBuilder: (context, state) => defaultPageBuilder( context, state, - const HomeserverPicker(), + const HomeserverPicker(addMultiAccount: false), ), redirect: loggedInRedirect, routes: [ @@ -242,7 +242,7 @@ abstract class AppRoutes { pageBuilder: (context, state) => defaultPageBuilder( context, state, - const HomeserverPicker(), + const HomeserverPicker(addMultiAccount: true), ), routes: [ GoRoute( diff --git a/lib/config/themes.dart b/lib/config/themes.dart index f83b69a2b1..4fcff7b758 100644 --- a/lib/config/themes.dart +++ b/lib/config/themes.dart @@ -91,11 +91,10 @@ abstract class FluffyThemes { ), inputDecorationTheme: InputDecorationTheme( border: OutlineInputBorder( - borderSide: BorderSide.none, - borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), + borderRadius: BorderRadius.circular(AppConfig.borderRadius), ), contentPadding: const EdgeInsets.all(12), - filled: true, + filled: false, ), appBarTheme: AppBarTheme( toolbarHeight: FluffyThemes.isColumnMode(context) ? 72 : 56, @@ -114,13 +113,6 @@ abstract class FluffyThemes { systemNavigationBarColor: colorScheme.surface, ), ), - textButtonTheme: TextButtonThemeData( - style: TextButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), - ), - ), - ), outlinedButtonTheme: OutlinedButtonThemeData( style: OutlinedButton.styleFrom( side: BorderSide( @@ -145,9 +137,6 @@ abstract class FluffyThemes { elevation: 0, padding: const EdgeInsets.all(16), textStyle: const TextStyle(fontSize: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(AppConfig.borderRadius), - ), ), ), ); diff --git a/lib/pages/bootstrap/bootstrap_dialog.dart b/lib/pages/bootstrap/bootstrap_dialog.dart index 64e7febadb..b816d2fc13 100644 --- a/lib/pages/bootstrap/bootstrap_dialog.dart +++ b/lib/pages/bootstrap/bootstrap_dialog.dart @@ -264,7 +264,9 @@ class BootstrapDialogState extends State { hintStyle: TextStyle( fontFamily: theme.textTheme.bodyLarge?.fontFamily, ), - hintText: L10n.of(context)!.recoveryKey, + prefixIcon: const Icon(Icons.key_outlined), + labelText: L10n.of(context)!.recoveryKey, + hintText: 'Es** **** **** ****', errorText: _recoveryKeyInputError, errorMaxLines: 2, ), diff --git a/lib/pages/chat_list/chat_list_header.dart b/lib/pages/chat_list/chat_list_header.dart index 864a7a7f88..f429990428 100644 --- a/lib/pages/chat_list/chat_list_header.dart +++ b/lib/pages/chat_list/chat_list_header.dart @@ -54,6 +54,7 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget { globalSearch: globalSearch, ), decoration: InputDecoration( + filled: true, fillColor: theme.colorScheme.secondaryContainer, border: OutlineInputBorder( borderSide: BorderSide.none, diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index 0295d2b554..69d9c96969 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -361,6 +361,7 @@ class _SpaceViewState extends State { onChanged: (_) => setState(() {}), textInputAction: TextInputAction.search, decoration: InputDecoration( + filled: true, fillColor: theme.colorScheme.secondaryContainer, border: OutlineInputBorder( borderSide: BorderSide.none, diff --git a/lib/pages/homeserver_picker/homeserver_app_bar.dart b/lib/pages/homeserver_picker/homeserver_app_bar.dart deleted file mode 100644 index 3ba5064549..0000000000 --- a/lib/pages/homeserver_picker/homeserver_app_bar.dart +++ /dev/null @@ -1,109 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:flutter_typeahead/flutter_typeahead.dart'; - -import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/config/themes.dart'; -import 'package:fluffychat/pages/homeserver_picker/public_homeserver.dart'; -import 'package:fluffychat/utils/localized_exception_extension.dart'; -import 'homeserver_bottom_sheet.dart'; -import 'homeserver_picker.dart'; - -class HomeserverAppBar extends StatelessWidget { - final HomeserverPickerController controller; - - const HomeserverAppBar({super.key, required this.controller}); - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - - return TypeAheadField( - decorationBuilder: (context, child) => ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 256), - child: Material( - borderRadius: BorderRadius.circular(AppConfig.borderRadius), - elevation: theme.appBarTheme.scrolledUnderElevation ?? 4, - shadowColor: theme.appBarTheme.shadowColor ?? Colors.black, - child: child, - ), - ), - emptyBuilder: (context) => ListTile( - leading: const Icon(Icons.search_outlined), - title: Text(L10n.of(context)!.nothingFound), - ), - loadingBuilder: (context) => ListTile( - leading: const CircularProgressIndicator.adaptive(strokeWidth: 2), - title: Text(L10n.of(context)!.loadingPleaseWait), - ), - errorBuilder: (context, error) => ListTile( - leading: const Icon(Icons.error_outlined), - title: Text( - error.toLocalizedString(context), - ), - ), - itemBuilder: (context, homeserver) => ListTile( - title: Text(homeserver.name), - subtitle: homeserver.description == null - ? null - : Text(homeserver.description ?? ''), - trailing: IconButton( - icon: const Icon(Icons.info_outlined), - onPressed: () => showModalBottomSheet( - context: context, - builder: (_) => HomeserverBottomSheet( - homeserver: homeserver, - ), - ), - ), - ), - suggestionsCallback: (pattern) async { - pattern = pattern.toLowerCase().trim(); - final homeservers = await controller.loadHomeserverList(); - final matches = homeservers - .where( - (homeserver) => - homeserver.name.toLowerCase().contains(pattern) || - (homeserver.description?.toLowerCase().contains(pattern) ?? - false), - ) - .toList(); - if (pattern.contains('.') && - pattern.split('.').any((part) => part.isNotEmpty) && - !matches.any((homeserver) => homeserver.name == pattern)) { - matches.add(PublicHomeserver(name: pattern)); - } - return matches; - }, - onSelected: (suggestion) { - controller.homeserverController.text = suggestion.name; - controller.checkHomeserverAction(); - }, - controller: controller.homeserverController, - builder: (context, textEditingController, focusNode) => TextField( - enabled: !controller.isLoggingIn, - controller: textEditingController, - focusNode: focusNode, - decoration: InputDecoration( - prefixIcon: Navigator.of(context).canPop() - ? IconButton( - onPressed: Navigator.of(context).pop, - icon: const Icon(Icons.arrow_back), - ) - : null, - fillColor: FluffyThemes.isColumnMode(context) - ? theme.colorScheme.surface - // ignore: deprecated_member_use - : theme.colorScheme.surfaceVariant, - prefixText: '${L10n.of(context)!.homeserver}: ', - hintText: L10n.of(context)!.enterYourHomeserver, - suffixIcon: const Icon(Icons.search), - ), - textInputAction: TextInputAction.search, - onSubmitted: controller.checkHomeserverAction, - autocorrect: false, - ), - ); - } -} diff --git a/lib/pages/homeserver_picker/homeserver_bottom_sheet.dart b/lib/pages/homeserver_picker/homeserver_bottom_sheet.dart deleted file mode 100644 index 18256d6fbf..0000000000 --- a/lib/pages/homeserver_picker/homeserver_bottom_sheet.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:url_launcher/url_launcher_string.dart'; - -import 'package:fluffychat/pages/homeserver_picker/public_homeserver.dart'; - -class HomeserverBottomSheet extends StatelessWidget { - final PublicHomeserver homeserver; - const HomeserverBottomSheet({required this.homeserver, super.key}); - - @override - Widget build(BuildContext context) { - final description = homeserver.description; - final registration = homeserver.regLink; - final jurisdiction = homeserver.staffJur; - final homeserverSoftware = homeserver.software; - return Scaffold( - appBar: AppBar( - title: Text(homeserver.name), - ), - body: ListView( - children: [ - if (description != null && description.isNotEmpty) - ListTile( - leading: const Icon(Icons.info_outlined), - title: Text(description), - ), - if (jurisdiction != null && jurisdiction.isNotEmpty) - ListTile( - leading: const Icon(Icons.location_city_outlined), - title: Text(jurisdiction), - ), - if (homeserverSoftware != null && homeserverSoftware.isNotEmpty) - ListTile( - leading: const Icon(Icons.domain_outlined), - title: Text(homeserverSoftware), - ), - ListTile( - onTap: () => launchUrlString(homeserver.name), - leading: const Icon(Icons.link_outlined), - title: Text(homeserver.name), - ), - if (registration != null) - ListTile( - onTap: () => launchUrlString(registration), - leading: const Icon(Icons.person_add_outlined), - title: Text(registration), - ), - ], - ), - ); - } -} diff --git a/lib/pages/homeserver_picker/homeserver_picker.dart b/lib/pages/homeserver_picker/homeserver_picker.dart index 7ffe089a27..b2f782f34f 100644 --- a/lib/pages/homeserver_picker/homeserver_picker.dart +++ b/lib/pages/homeserver_picker/homeserver_picker.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -16,7 +15,6 @@ import 'package:universal_html/html.dart' as html; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/homeserver_picker/homeserver_picker_view.dart'; -import 'package:fluffychat/pages/homeserver_picker/public_homeserver.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/app_lock.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -26,7 +24,8 @@ import 'package:fluffychat/utils/tor_stub.dart' if (dart.library.html) 'package:tor_detector_web/tor_detector_web.dart'; class HomeserverPicker extends StatefulWidget { - const HomeserverPicker({super.key}); + final bool addMultiAccount; + const HomeserverPicker({required this.addMultiAccount, super.key}); @override HomeserverPickerController createState() => HomeserverPickerController(); @@ -64,6 +63,21 @@ class HomeserverPickerController extends State { String? _lastCheckedUrl; + Timer? _checkHomeserverCooldown; + + tryCheckHomeserverActionWithCooldown([_]) { + _checkHomeserverCooldown?.cancel(); + _checkHomeserverCooldown = Timer( + const Duration(milliseconds: 500), + checkHomeserverAction, + ); + } + + tryCheckHomeserverActionWithoutCooldown([_]) { + _checkHomeserverCooldown?.cancel(); + checkHomeserverAction(); + } + /// Starts an analysis of the given homeserver. It uses the current domain and /// makes sure that it is prefixed with https. Then it searches for the /// well-known information and forwards to the login page depending on the @@ -74,7 +88,7 @@ class HomeserverPickerController extends State { if (homeserverController.text == _lastCheckedUrl) return; _lastCheckedUrl = homeserverController.text; setState(() { - error = _rawLoginTypes = loginFlows = null; + error = loginFlows = null; isLoading = true; }); @@ -86,12 +100,6 @@ class HomeserverPickerController extends State { final client = Matrix.of(context).getLoginClient(); final (_, _, loginFlows) = await client.checkHomeserver(homeserver); this.loginFlows = loginFlows; - if (supportsSso) { - _rawLoginTypes = await client.request( - RequestType.GET, - '/client/v3/login', - ); - } } catch (e) { setState(() => error = (e).toLocalizedString(context)); } finally { @@ -113,9 +121,7 @@ class HomeserverPickerController extends State { bool get supportsPasswordLogin => _supportsFlow('m.login.password'); - Map? _rawLoginTypes; - - void ssoLoginAction(String? id) async { + void ssoLoginAction() async { final redirectUrl = kIsWeb ? Uri.parse(html.window.location.href) .resolveUri( @@ -127,7 +133,7 @@ class HomeserverPickerController extends State { : 'http://localhost:3001//login'; final url = Matrix.of(context).getLoginClient().homeserver!.replace( - path: '/_matrix/client/v3/login/sso/redirect${id == null ? '' : '/$id'}', + path: '/_matrix/client/v3/login/sso/redirect', queryParameters: {'redirectUrl': redirectUrl}, ); @@ -164,39 +170,6 @@ class HomeserverPickerController extends State { } } - List? get identityProviders { - final loginTypes = _rawLoginTypes; - if (loginTypes == null) return null; - final List? rawProviders = - loginTypes.tryGetList('flows')?.singleWhereOrNull( - (flow) => flow['type'] == AuthenticationTypes.sso, - )['identity_providers'] ?? - [ - {'id': null}, - ]; - if (rawProviders == null) return null; - final list = - rawProviders.map((json) => IdentityProvider.fromJson(json)).toList(); - if (PlatformInfos.isCupertinoStyle) { - list.sort((a, b) => a.brand == 'apple' ? -1 : 1); - } - return list; - } - - List? cachedHomeservers; - - Future> loadHomeserverList() async { - if (cachedHomeservers != null) return cachedHomeservers!; - final result = await Matrix.of(context) - .getLoginClient() - .httpClient - .get(AppConfig.homeserverList); - final resultJson = jsonDecode(result.body)['public_servers'] as List; - final homeserverList = - resultJson.map((json) => PublicHomeserver.fromJson(json)).toList(); - return cachedHomeservers = homeserverList; - } - void login() => context.push( '${GoRouter.of(context).routeInformationProvider.value.uri.path}/login', ); diff --git a/lib/pages/homeserver_picker/homeserver_picker_view.dart b/lib/pages/homeserver_picker/homeserver_picker_view.dart index dd6c50d044..3fd7bd24c3 100644 --- a/lib/pages/homeserver_picker/homeserver_picker_view.dart +++ b/lib/pages/homeserver_picker/homeserver_picker_view.dart @@ -1,15 +1,13 @@ import 'package:flutter/material.dart'; -import 'package:collection/collection.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:url_launcher/url_launcher_string.dart'; +import 'package:flutter_linkify/flutter_linkify.dart'; +import 'package:url_launcher/url_launcher.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/widgets/layouts/login_scaffold.dart'; import 'package:fluffychat/widgets/matrix.dart'; import '../../config/themes.dart'; -import '../../widgets/mxc_image.dart'; -import 'homeserver_app_bar.dart'; import 'homeserver_picker.dart'; class HomeserverPickerView extends StatelessWidget { @@ -21,22 +19,14 @@ class HomeserverPickerView extends StatelessWidget { Widget build(BuildContext context) { final theme = Theme.of(context); - final identityProviders = controller.identityProviders; - final errorText = controller.error; - final publicHomeserver = controller.cachedHomeservers?.singleWhereOrNull( - (homeserver) => - homeserver.name == - controller.homeserverController.text.trim().toLowerCase(), - ); - final regLink = publicHomeserver?.regLink; return LoginScaffold( enforceMobileMode: Matrix.of(context).client.isLogged(), - appBar: AppBar( - titleSpacing: 12, - automaticallyImplyLeading: false, - surfaceTintColor: theme.colorScheme.surface, - title: HomeserverAppBar(controller: controller), - ), + appBar: controller.widget.addMultiAccount + ? AppBar( + centerTitle: true, + title: Text(L10n.of(context)!.addAccount), + ) + : null, body: Column( children: [ // display a prominent banner to import session for TOR browser @@ -62,163 +52,132 @@ class HomeserverPickerView extends StatelessWidget { ), ), ), + Image.asset( + 'assets/banner_transparent.png', + ), Expanded( - child: controller.isLoading - ? const Center(child: CircularProgressIndicator.adaptive()) - : ListView( - children: [ - if (errorText != null) ...[ - const SizedBox(height: 12), - const Center( - child: Icon( - Icons.error_outline, - size: 48, - color: Colors.orange, - ), - ), - const SizedBox(height: 12), - Center( - child: Text( - errorText, - textAlign: TextAlign.center, - style: TextStyle( - color: theme.colorScheme.error, - fontSize: 18, - ), - ), - ), - Center( - child: Text( - L10n.of(context)! - .pleaseTryAgainLaterOrChooseDifferentServer, - textAlign: TextAlign.center, - style: TextStyle( - color: theme.colorScheme.error, - fontSize: 12, - ), - ), - ), - const SizedBox(height: 36), - ] else - Padding( - padding: const EdgeInsets.only( - top: 0.0, - right: 8.0, - left: 8.0, - bottom: 16.0, - ), - child: Image.asset( - 'assets/banner_transparent.png', - ), - ), - if (identityProviders != null) ...[ - ...identityProviders.map( - (provider) => _LoginButton( - icon: provider.icon == null - ? const Icon( - Icons.open_in_new_outlined, - size: 16, - ) - : Material( - borderRadius: BorderRadius.circular( - AppConfig.borderRadius, - ), - clipBehavior: Clip.hardEdge, - child: MxcImage( - placeholder: (_) => const Icon( - Icons.open_in_new_outlined, - size: 16, - ), - uri: Uri.parse(provider.icon!), - width: 24, - height: 24, - isThumbnail: false, - //isThumbnail: false, - ), + child: ListView( + padding: const EdgeInsets.only( + top: 16.0, + right: 8.0, + left: 8.0, + bottom: 16.0, + ), + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: TextField( + onChanged: controller.tryCheckHomeserverActionWithCooldown, + onEditingComplete: + controller.tryCheckHomeserverActionWithoutCooldown, + onSubmitted: + controller.tryCheckHomeserverActionWithoutCooldown, + onTap: controller.tryCheckHomeserverActionWithCooldown, + controller: controller.homeserverController, + decoration: InputDecoration( + prefixIcon: controller.isLoading + ? Container( + width: 16, + height: 16, + alignment: Alignment.center, + child: const SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator.adaptive( + strokeWidth: 2, + ), + ), + ) + : const Icon(Icons.search_outlined), + filled: false, + border: OutlineInputBorder( + borderRadius: + BorderRadius.circular(AppConfig.borderRadius), + ), + hintText: AppConfig.defaultHomeserver, + labelText: L10n.of(context)!.homeserver, + errorText: controller.error, + suffixIcon: IconButton( + onPressed: () { + showDialog( + context: context, + builder: (context) => AlertDialog.adaptive( + title: Text(L10n.of(context)!.whatIsAHomeserver), + content: Linkify( + text: L10n.of(context)!.homeserverDescription, + ), + actions: [ + TextButton( + onPressed: () => launchUrl( + Uri.https('servers.joinmatrix.org'), ), - label: L10n.of(context)!.signInWith( - provider.name ?? - provider.brand ?? - L10n.of(context)!.singlesignon, + child: Text( + L10n.of(context)!.discoverHomeservers, + ), + ), + TextButton( + onPressed: Navigator.of(context).pop, + child: Text(L10n.of(context)!.close), + ), + ], ), - onPressed: () => - controller.ssoLoginAction(provider.id), - ), - ), - ], - if (controller.supportsPasswordLogin) - _LoginButton( - onPressed: controller.login, - label: L10n.of(context)!.signInWithPassword, - icon: const Icon(Icons.lock_open_outlined, size: 16), - ), - if (regLink != null) - _LoginButton( - onPressed: () => launchUrlString(regLink), - icon: const Icon( - Icons.open_in_new_outlined, - size: 16, - ), - label: L10n.of(context)!.register, - ), - _LoginButton( - onPressed: controller.restoreBackup, - label: L10n.of(context)!.hydrate, - withBorder: false, + ); + }, + icon: const Icon(Icons.info_outlined), ), - const SizedBox(height: 16), - ], + ), ), - ), - ], - ), - ); - } -} - -class _LoginButton extends StatelessWidget { - final Widget? icon; - final String label; - final void Function() onPressed; - final bool withBorder; - - const _LoginButton({ - this.icon, - required this.label, - required this.onPressed, - this.withBorder = true, - }); - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - - final icon = this.icon; - return Container( - margin: const EdgeInsets.only(bottom: 12), - padding: const EdgeInsets.symmetric(horizontal: 16), - alignment: Alignment.center, - child: SizedBox( - width: double.infinity, - child: OutlinedButton.icon( - style: OutlinedButton.styleFrom( - side: FluffyThemes.isColumnMode(context) - ? BorderSide.none - : BorderSide( - color: theme.colorScheme.outlineVariant, - width: 1, + ), + if (controller.supportsPasswordLogin || controller.supportsSso) + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 8.0, + ), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: theme.colorScheme.primary, + foregroundColor: theme.colorScheme.onPrimary, + ), + onPressed: controller.isLoggingIn || controller.isLoading + ? null + : controller.supportsSso + ? controller.ssoLoginAction + : controller.login, + child: Text(L10n.of(context)!.connect), + ), + ), + if (controller.supportsPasswordLogin && controller.supportsSso) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: TextButton( + style: TextButton.styleFrom( + foregroundColor: theme.colorScheme.secondary, + textStyle: theme.textTheme.labelMedium, + ), + onPressed: controller.isLoggingIn || controller.isLoading + ? null + : controller.login, + child: Text(L10n.of(context)!.loginWithMatrixId), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: TextButton( + style: TextButton.styleFrom( + textStyle: theme.textTheme.labelMedium, + foregroundColor: theme.colorScheme.secondary, + ), + onPressed: controller.isLoggingIn || controller.isLoading + ? null + : controller.restoreBackup, + child: Text(L10n.of(context)!.hydrate), ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(99), + ), + ], ), - foregroundColor: theme.colorScheme.onSurface, - backgroundColor: - withBorder ? theme.colorScheme.surface : Colors.transparent, ), - onPressed: onPressed, - label: Text(label), - icon: icon ?? const SizedBox.shrink(), - ), + ], ), ); } diff --git a/lib/pages/homeserver_picker/public_homeserver.dart b/lib/pages/homeserver_picker/public_homeserver.dart deleted file mode 100644 index 4eef2aa56c..0000000000 --- a/lib/pages/homeserver_picker/public_homeserver.dart +++ /dev/null @@ -1,73 +0,0 @@ -class PublicHomeserver { - final String name; - final String? clientDomain; - final String? isp; - final String? staffJur; - final bool? usingVanillaReg; - final String? description; - final String? regMethod; - final String? regLink; - final String? software; - final String? version; - final bool? captcha; - final bool? email; - final List? languages; - final List? features; - final int? onlineStatus; - final String? serverDomain; - final int? verStatus; - final int? roomDirectory; - final bool? slidingSync; - final bool? ipv6; - - const PublicHomeserver({ - required this.name, - this.clientDomain, - this.isp, - this.staffJur, - this.usingVanillaReg, - this.description, - this.regMethod, - this.regLink, - this.software, - this.version, - this.captcha, - this.email, - this.languages, - this.features, - this.onlineStatus, - this.serverDomain, - this.verStatus, - this.roomDirectory, - this.slidingSync, - this.ipv6, - }); - - factory PublicHomeserver.fromJson(Map json) => - PublicHomeserver( - name: json['name'], - clientDomain: json['client_domain'], - isp: json['isp'], - staffJur: json['staff_jur'], - usingVanillaReg: json['using_vanilla_reg'], - description: json['description'], - regMethod: json['reg_method'], - regLink: json['reg_link'], - software: json['software'], - version: json['version'], - captcha: json['captcha'], - email: json['email'], - languages: json['languages'] == null - ? null - : List.from(json['languages']), - features: json['features'] == null - ? null - : List.from(json['features']), - onlineStatus: json['online_status'], - serverDomain: json['server_domain'], - verStatus: json['ver_status'], - roomDirectory: json['room_directory'], - slidingSync: json['sliding_sync'], - ipv6: json['ipv6'], - ); -} diff --git a/lib/pages/login/login_view.dart b/lib/pages/login/login_view.dart index 0358c44a62..a8ec3f502f 100644 --- a/lib/pages/login/login_view.dart +++ b/lib/pages/login/login_view.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/widgets/layouts/login_scaffold.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'login.dart'; @@ -24,11 +23,6 @@ class LoginView extends StatelessWidget { final title = L10n.of(context)!.logInTo(homeserver); final titleParts = title.split(homeserver); - final textFieldFillColor = FluffyThemes.isColumnMode(context) - ? theme.colorScheme.surface - // ignore: deprecated_member_use - : theme.colorScheme.surfaceVariant; - return LoginScaffold( enforceMobileMode: Matrix.of(context).client.isLogged(), appBar: AppBar( @@ -73,8 +67,8 @@ class LoginView extends StatelessWidget { prefixIcon: const Icon(Icons.account_box_outlined), errorText: controller.usernameError, errorStyle: const TextStyle(color: Colors.orange), - fillColor: textFieldFillColor, - hintText: L10n.of(context)!.emailOrUsername, + hintText: '@username:localpart', + labelText: L10n.of(context)!.emailOrUsername, ), ), ), @@ -94,7 +88,6 @@ class LoginView extends StatelessWidget { prefixIcon: const Icon(Icons.lock_outlined), errorText: controller.passwordError, errorStyle: const TextStyle(color: Colors.orange), - fillColor: textFieldFillColor, suffixIcon: IconButton( onPressed: controller.toggleShowPassword, icon: Icon( @@ -104,21 +97,21 @@ class LoginView extends StatelessWidget { color: Colors.black, ), ), - hintText: L10n.of(context)!.password, + hintText: '******', + labelText: L10n.of(context)!.password, ), ), ), const SizedBox(height: 16), Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: ElevatedButton.icon( + child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: theme.colorScheme.primary, foregroundColor: theme.colorScheme.onPrimary, ), onPressed: controller.loading ? null : controller.login, - icon: const Icon(Icons.login_outlined), - label: controller.loading + child: controller.loading ? const LinearProgressIndicator() : Text(L10n.of(context)!.login), ), @@ -126,15 +119,14 @@ class LoginView extends StatelessWidget { const SizedBox(height: 16), Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: TextButton.icon( + child: TextButton( onPressed: controller.loading ? () {} : controller.passwordForgotten, style: TextButton.styleFrom( foregroundColor: theme.colorScheme.error, ), - icon: const Icon(Icons.safety_check_outlined), - label: Text(L10n.of(context)!.passwordForgotten), + child: Text(L10n.of(context)!.passwordForgotten), ), ), const SizedBox(height: 16), diff --git a/lib/widgets/layouts/login_scaffold.dart b/lib/widgets/layouts/login_scaffold.dart index 7337e8ebd0..08ed8d75c8 100644 --- a/lib/widgets/layouts/login_scaffold.dart +++ b/lib/widgets/layouts/login_scaffold.dart @@ -78,7 +78,7 @@ class LoginScaffold extends StatelessWidget { child: ConstrainedBox( constraints: isMobileMode ? const BoxConstraints() - : const BoxConstraints(maxWidth: 480, maxHeight: 720), + : const BoxConstraints(maxWidth: 480, maxHeight: 640), child: BackdropFilter( filter: ImageFilter.blur( sigmaX: 10.0, diff --git a/pubspec.lock b/pubspec.lock index 98aa4393ae..ede4347e42 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2351,10 +2351,10 @@ packages: dependency: "direct overridden" description: name: win32 - sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" + sha256: "015002c060f1ae9f41a818f2d5640389cc05283e368be19dc8d77cecb43c40c9" url: "https://pub.dev" source: hosted - version: "5.5.0" + version: "5.5.3" win32_registry: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 853df17441..d338f9a75c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -160,4 +160,4 @@ dependency_overrides: git: url: https://github.com/TheOneWithTheBraid/keyboard_shortcuts.git ref: null-safety - win32: 5.5.0 + win32: 5.5.3 From 158a6855c3f6b6aa415241df8659f5d402fe4f39 Mon Sep 17 00:00:00 2001 From: Krille Date: Tue, 20 Aug 2024 09:27:00 +0200 Subject: [PATCH 232/288] feat: Use matrix authenticated media --- .../settings_emotes/settings_emotes.dart | 7 ++- .../client_download_content_extension.dart | 53 ++++++++++++++++++ lib/utils/push_helper.dart | 54 +++++++++--------- .../local_notifications_extension.dart | 55 +++++++------------ lib/widgets/mxc_image.dart | 48 +++------------- pubspec.lock | 27 ++++----- pubspec.yaml | 5 +- 7 files changed, 134 insertions(+), 115 deletions(-) create mode 100644 lib/utils/client_download_content_extension.dart diff --git a/lib/pages/settings_emotes/settings_emotes.dart b/lib/pages/settings_emotes/settings_emotes.dart index 2ef905b176..fbef5479c1 100644 --- a/lib/pages/settings_emotes/settings_emotes.dart +++ b/lib/pages/settings_emotes/settings_emotes.dart @@ -330,8 +330,11 @@ class EmotesSettingsController extends State { for (final entry in pack.images.entries) { final emote = entry.value; final name = entry.key; - final url = emote.url.getDownloadLink(client); - final response = await get(url); + final url = await emote.url.getDownloadUri(client); + final response = await get( + url, + headers: {'authorization': 'Bearer ${client.accessToken}'}, + ); archive.addFile( ArchiveFile( diff --git a/lib/utils/client_download_content_extension.dart b/lib/utils/client_download_content_extension.dart new file mode 100644 index 0000000000..9fff1eccd2 --- /dev/null +++ b/lib/utils/client_download_content_extension.dart @@ -0,0 +1,53 @@ +import 'dart:typed_data'; + +import 'package:matrix/matrix.dart'; + +extension ClientDownloadContentExtension on Client { + Future downloadMxcCached( + Uri mxc, { + num? width, + num? height, + bool isThumbnail = false, + bool? animated, + ThumbnailMethod? thumbnailMethod, + }) async { + // To stay compatible with previous storeKeys: + final cacheKey = isThumbnail + // ignore: deprecated_member_use + ? mxc.getThumbnail( + this, + width: width, + height: height, + animated: animated, + method: thumbnailMethod!, + ) + : mxc; + + final cachedData = await database?.getFile(cacheKey); + if (cachedData != null) return cachedData; + + final httpUri = isThumbnail + ? await mxc.getThumbnailUri( + this, + width: width, + height: height, + animated: animated, + method: thumbnailMethod, + ) + : await mxc.getDownloadUri(this); + + final response = await httpClient.get( + httpUri, + headers: + accessToken == null ? null : {'authorization': 'Bearer $accessToken'}, + ); + if (response.statusCode != 200) { + throw Exception(); + } + final remoteData = response.bodyBytes; + + await database?.storeFile(cacheKey, remoteData, 0); + + return remoteData; + } +} diff --git a/lib/utils/push_helper.dart b/lib/utils/push_helper.dart index edfdb49418..7a82b370f5 100644 --- a/lib/utils/push_helper.dart +++ b/lib/utils/push_helper.dart @@ -1,11 +1,9 @@ -import 'dart:io'; import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; -import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_shortcuts/flutter_shortcuts.dart'; @@ -13,6 +11,7 @@ import 'package:matrix/matrix.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/utils/client_download_content_extension.dart'; import 'package:fluffychat/utils/client_manager.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/platform_infos.dart'; @@ -177,28 +176,25 @@ Future _tryPushHelper( ); // The person object for the android message style notification - final avatar = event.room.avatar - ?.getThumbnail( - client, - width: 256, - height: 256, - ) - .toString(); + final avatar = event.room.avatar; final senderAvatar = event.room.isDirectChat ? avatar - : event.senderFromMemoryOrFallback.avatarUrl - ?.getThumbnail( - client, - width: 256, - height: 256, - ) - .toString(); - - File? roomAvatarFile, senderAvatarFile; + : event.senderFromMemoryOrFallback.avatarUrl; + + Uint8List? roomAvatarFile, senderAvatarFile; try { roomAvatarFile = avatar == null ? null - : await DefaultCacheManager().getSingleFile(avatar); + : await client + .downloadMxcCached( + avatar, + thumbnailMethod: ThumbnailMethod.scale, + width: 256, + height: 256, + animated: false, + isThumbnail: true, + ) + .timeout(const Duration(seconds: 3)); } catch (e, s) { Logs().e('Unable to get avatar picture', e, s); } @@ -207,7 +203,16 @@ Future _tryPushHelper( ? roomAvatarFile : senderAvatar == null ? null - : await DefaultCacheManager().getSingleFile(senderAvatar); + : await client + .downloadMxcCached( + senderAvatar, + thumbnailMethod: ThumbnailMethod.scale, + width: 256, + height: 256, + animated: false, + isThumbnail: true, + ) + .timeout(const Duration(seconds: 3)); } catch (e, s) { Logs().e('Unable to get avatar picture', e, s); } @@ -225,7 +230,7 @@ Future _tryPushHelper( name: event.senderFromMemoryOrFallback.calcDisplayname(), icon: senderAvatarFile == null ? null - : BitmapFilePathAndroidIcon(senderAvatarFile.path), + : ByteArrayAndroidIcon(senderAvatarFile), ), ); @@ -272,7 +277,7 @@ Future _tryPushHelper( name: event.senderFromMemoryOrFallback.calcDisplayname(), icon: roomAvatarFile == null ? null - : BitmapFilePathAndroidIcon(roomAvatarFile.path), + : ByteArrayAndroidIcon(roomAvatarFile), key: event.roomId, important: event.room.isFavourite, ), @@ -321,7 +326,7 @@ Future _setShortcut( Event event, L10n l10n, String title, - File? avatarFile, + Uint8List? avatarFile, ) async { final flutterShortcuts = FlutterShortcuts(); await flutterShortcuts.initialize(debug: !kReleaseMode); @@ -333,8 +338,7 @@ Future _setShortcut( conversationShortcut: true, icon: avatarFile == null ? null - : ShortcutMemoryIcon(jpegImage: await avatarFile.readAsBytes()) - .toString(), + : ShortcutMemoryIcon(jpegImage: avatarFile).toString(), shortcutIconAsset: avatarFile == null ? ShortcutIconAsset.androidAsset : ShortcutIconAsset.memoryAsset, diff --git a/lib/widgets/local_notifications_extension.dart b/lib/widgets/local_notifications_extension.dart index e3d9ff3c44..99fd00cf93 100644 --- a/lib/widgets/local_notifications_extension.dart +++ b/lib/widgets/local_notifications_extension.dart @@ -6,12 +6,11 @@ import 'package:flutter/material.dart'; import 'package:desktop_notifications/desktop_notifications.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:go_router/go_router.dart'; -import 'package:http/http.dart' as http; import 'package:matrix/matrix.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:universal_html/html.dart' as html; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/utils/client_download_content_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -48,50 +47,38 @@ extension LocalNotificationsExtension on MatrixState { hideEdit: true, removeMarkdown: true, ); - final icon = event.senderFromMemoryOrFallback.avatarUrl?.getThumbnail( - client, - width: 64, - height: 64, - method: ThumbnailMethod.crop, - ) ?? - room.avatar?.getThumbnail( - client, - width: 64, - height: 64, - method: ThumbnailMethod.crop, - ); + if (kIsWeb) { + final avatarUrl = event.senderFromMemoryOrFallback.avatarUrl; + + final iconBytes = avatarUrl == null + ? null + : await client.downloadMxcCached( + avatarUrl, + width: 64, + height: 64, + thumbnailMethod: ThumbnailMethod.crop, + isThumbnail: true, + animated: false, + ); + _audioPlayer.play(); + html.Notification( title, body: body, - icon: icon.toString(), + icon: iconBytes == null + ? null + : html.Url.createObjectUrl(html.Blob(iconBytes)), + tag: event.room.id, ); } else if (Platform.isLinux) { - final appIconUrl = room.avatar?.getThumbnail( - room.client, - width: 56, - height: 56, - ); - File? appIconFile; - if (appIconUrl != null) { - final tempDirectory = await getApplicationSupportDirectory(); - final avatarDirectory = - await Directory('${tempDirectory.path}/notiavatars/').create(); - appIconFile = File( - '${avatarDirectory.path}/${Uri.encodeComponent(appIconUrl.toString())}', - ); - if (await appIconFile.exists() == false) { - final response = await http.get(appIconUrl); - await appIconFile.writeAsBytes(response.bodyBytes); - } - } final notification = await linuxNotifications!.notify( title, body: body, replacesId: linuxNotificationIds[roomId] ?? 0, appName: AppConfig.applicationName, - appIcon: appIconFile?.path ?? '', + appIcon: 'fluffychat', actions: [ NotificationAction( DesktopNotificationActions.openChat.name, diff --git a/lib/widgets/mxc_image.dart b/lib/widgets/mxc_image.dart index 2126a8cbe6..d776cfc829 100644 --- a/lib/widgets/mxc_image.dart +++ b/lib/widgets/mxc_image.dart @@ -2,10 +2,10 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; -import 'package:http/http.dart' as http; import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/utils/client_download_content_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_file_extension.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -63,8 +63,6 @@ class _MxcImageState extends State { : _imageDataCache[cacheKey] = data; } - bool? _isCached; - Future _load() async { final client = widget.client ?? Matrix.of(context).client; final uri = widget.uri; @@ -77,45 +75,18 @@ class _MxcImageState extends State { final height = widget.height; final realHeight = height == null ? null : height * devicePixelRatio; - final httpUri = widget.isThumbnail - ? uri.getThumbnail( - client, - width: realWidth, - height: realHeight, - animated: widget.animated, - method: widget.thumbnailMethod, - ) - : uri.getDownloadLink(client); - - final storeKey = widget.isThumbnail ? httpUri : uri; - - if (_isCached == null) { - final cachedData = await client.database?.getFile(storeKey); - if (cachedData != null) { - if (!mounted) return; - setState(() { - _imageData = cachedData; - _isCached = true; - }); - return; - } - _isCached = false; - } - - final response = await http.get(httpUri); - if (response.statusCode != 200) { - if (response.statusCode == 404) { - return; - } - throw Exception(); - } - final remoteData = response.bodyBytes; - + final remoteData = await client.downloadMxcCached( + uri, + width: realWidth, + height: realHeight, + thumbnailMethod: widget.thumbnailMethod, + isThumbnail: widget.isThumbnail, + animated: widget.animated, + ); if (!mounted) return; setState(() { _imageData = remoteData; }); - await client.database?.storeFile(storeKey, remoteData, 0); } if (event != null) { @@ -179,7 +150,6 @@ class _MxcImageState extends State { filterQuality: widget.isThumbnail ? FilterQuality.low : FilterQuality.medium, errorBuilder: (context, __, ___) { - _isCached = false; _imageData = null; WidgetsBinding.instance.addPostFrameCallback(_tryLoad); return placeholder(context); diff --git a/pubspec.lock b/pubspec.lock index 98aa4393ae..2162bf86f4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -450,10 +450,10 @@ packages: dependency: "direct main" description: name: flutter_cache_manager - sha256: "395d6b7831f21f3b989ebedbb785545932adb9afe2622c1ffacf7f4b53a7e544" + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" url: "https://pub.dev" source: hosted - version: "3.3.2" + version: "3.4.1" flutter_driver: dependency: transitive description: flutter @@ -892,10 +892,10 @@ packages: dependency: "direct main" description: name: http - sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" http_multi_server: dependency: transitive description: @@ -1201,11 +1201,12 @@ packages: matrix: dependency: "direct main" description: - name: matrix - sha256: "4357245df2a64c435456d1faee55cb33a9fd30aa0df97aacd6abd52b68f70aa1" - url: "https://pub.dev" - source: hosted - version: "0.32.0" + path: "." + ref: HEAD + resolved-ref: "8f350760c4a418a1553030dc4b81408185e0fad5" + url: "https://github.com/famedly/matrix-dart-sdk.git" + source: git + version: "0.32.2" meta: dependency: transitive description: @@ -1338,10 +1339,10 @@ packages: dependency: "direct main" description: name: path_provider - sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" path_provider_android: dependency: transitive description: @@ -2279,10 +2280,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.4" + version: "14.2.5" wakelock_plus: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 853df17441..4ebb8a8dd8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,7 +29,7 @@ dependencies: flutter: sdk: flutter flutter_app_badger: ^1.5.0 - flutter_cache_manager: ^3.3.1 + flutter_cache_manager: ^3.4.1 flutter_foreground_task: ^6.1.3 flutter_highlighter: ^0.1.1 flutter_html: ^3.0.0-beta.2 @@ -63,7 +63,8 @@ dependencies: keyboard_shortcuts: ^0.1.4 latlong2: ^0.9.1 linkify: ^5.0.0 - matrix: ^0.32.0 + matrix: # Until 0.32.3 is released + git: https://github.com/famedly/matrix-dart-sdk.git native_imaging: ^0.1.1 opus_caf_converter_dart: ^1.0.1 package_info_plus: ^6.0.0 From 987f84f6b49eca370a2f825106547ace19cb1299 Mon Sep 17 00:00:00 2001 From: Krille-chan Date: Sun, 25 Aug 2024 17:32:11 +0200 Subject: [PATCH 233/288] Revert "fix: little issues in `prepare-web` script" --- scripts/prepare-web.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/prepare-web.sh b/scripts/prepare-web.sh index f55e5ed2de..70b15a246a 100755 --- a/scripts/prepare-web.sh +++ b/scripts/prepare-web.sh @@ -1,9 +1,7 @@ #!/bin/sh -ve rm -r assets/js/package -mkdir -p assets/js/package -touch assets/js/package/.gitkeep -OLM_VERSION=$(cat pubspec.yaml | yq -r .dependencies.flutter_olm) +OLM_VERSION=$(cat pubspec.yaml | yq .dependencies.flutter_olm) DOWNLOAD_PATH="https://github.com/famedly/olm/releases/download/v$OLM_VERSION/olm.zip" cd assets/js/ && curl -L $DOWNLOAD_PATH > olm.zip && cd ../../ From e2538816a35382d6b412d1ffee221939d32627dc Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sun, 25 Aug 2024 17:41:05 +0200 Subject: [PATCH 234/288] chore: Follow up homeserver picker page --- .../homeserver_picker/homeserver_picker.dart | 1 + .../homeserver_picker_view.dart | 20 ++++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/pages/homeserver_picker/homeserver_picker.dart b/lib/pages/homeserver_picker/homeserver_picker.dart index b2f782f34f..32f1356d74 100644 --- a/lib/pages/homeserver_picker/homeserver_picker.dart +++ b/lib/pages/homeserver_picker/homeserver_picker.dart @@ -75,6 +75,7 @@ class HomeserverPickerController extends State { tryCheckHomeserverActionWithoutCooldown([_]) { _checkHomeserverCooldown?.cancel(); + _lastCheckedUrl = null; checkHomeserverAction(); } diff --git a/lib/pages/homeserver_picker/homeserver_picker_view.dart b/lib/pages/homeserver_picker/homeserver_picker_view.dart index 3fd7bd24c3..f99f949b9d 100644 --- a/lib/pages/homeserver_picker/homeserver_picker_view.dart +++ b/lib/pages/homeserver_picker/homeserver_picker_view.dart @@ -27,7 +27,7 @@ class HomeserverPickerView extends StatelessWidget { title: Text(L10n.of(context)!.addAccount), ) : null, - body: Column( + body: ListView( children: [ // display a prominent banner to import session for TOR browser // users. This feature is just some UX sugar as TOR users are @@ -55,14 +55,16 @@ class HomeserverPickerView extends StatelessWidget { Image.asset( 'assets/banner_transparent.png', ), - Expanded( - child: ListView( - padding: const EdgeInsets.only( - top: 16.0, - right: 8.0, - left: 8.0, - bottom: 16.0, - ), + Padding( + padding: const EdgeInsets.only( + top: 16.0, + right: 8.0, + left: 8.0, + bottom: 16.0, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, children: [ Padding( padding: const EdgeInsets.all(16.0), From 0f16cdee88d373d74ed0b8fa88de0e40bbd1113d Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sun, 25 Aug 2024 17:47:23 +0200 Subject: [PATCH 235/288] chore: Better scroll up --- lib/pages/chat/chat.dart | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 7772e5f653..8dc0033aa2 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -285,13 +285,23 @@ class ChatController extends State await loadTimelineFuture; if (initialEventId != null) scrollToEventId(initialEventId); - final readMarkerEventIndex = readMarkerEventId.isEmpty + var readMarkerEventIndex = readMarkerEventId.isEmpty ? -1 : timeline!.events - .where((e) => e.isVisibleInGui) + .where((e) => e.isVisibleInGui || e.eventId == readMarkerEventId) .toList() .indexWhere((e) => e.eventId == readMarkerEventId); + // Read marker is existing but not found in first events. Try a single + // requestHistory call before opening timeline on event context: + if (readMarkerEventId.isNotEmpty && readMarkerEventIndex == -1) { + await timeline?.requestHistory(historyCount: _loadHistoryCount); + readMarkerEventIndex = timeline!.events + .where((e) => e.isVisibleInGui || e.eventId == readMarkerEventId) + .toList() + .indexWhere((e) => e.eventId == readMarkerEventId); + } + if (readMarkerEventIndex > 1) { Logs().v('Scroll up to visible event', readMarkerEventId); scrollToEventId(readMarkerEventId, highlightEvent: false); From 1227d762c35492663785b4852fcdefa54b0b7e83 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sun, 25 Aug 2024 18:09:54 +0200 Subject: [PATCH 236/288] chore: Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7132708af8..273594b59d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ FluffyChat v1.22.0 brings a new design for spaces, replaces the bottom navigation bar with filter chips and makes it finally possible to play ogg audio messages on iOS. A lot of other fixes and improvements have also been added to this release. +FluffyChat also now uses the new authenticated media endpoints if the server supports Matrix v1.11 or +mentions the msc with the key `org.matrix.msc3916.stable` in the `unstable_features`. + - build: (deps): bump docker/build-push-action from 5 to 6 (dependabot[bot]) - build(deps): bump rexml from 3.2.8 to 3.3.3 in /ios (dependabot[bot]) - build: Remove permissions for screensharing until it is fixed (Krille) @@ -24,6 +27,7 @@ FluffyChat v1.22.0 brings a new design for spaces, replaces the bottom navigatio - chore: Sligthly improve chat permissions page design (krille-chan) - design: Add snackbar with link to changelog on new version (Krille) - docs: Update privacy policy (krille-chan) +- feat: Support for matrix auth media endpoints - feat: Convert opus to aac on iOS before playing (Krille) - feat: New spaces and chat list design (krille-chan) - feat: Record voice message with opus/ogg if supported (Krille) From d366d2c2f262db9d7c75a11f419c9c92a1ec9951 Mon Sep 17 00:00:00 2001 From: tct123 Date: Sun, 25 Aug 2024 05:06:22 +0000 Subject: [PATCH 237/288] Translated using Weblate (German) Currently translated at 100.0% (658 of 658 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/de/ --- assets/l10n/intl_de.arb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_de.arb b/assets/l10n/intl_de.arb index cc05a4ddcc..f9321f156e 100644 --- a/assets/l10n/intl_de.arb +++ b/assets/l10n/intl_de.arb @@ -2789,5 +2789,9 @@ } }, "changelog": "Änderungsprotokoll", - "@changelog": {} + "@changelog": {}, + "sendCanceled": "Senden abgebrochen", + "@sendCanceled": {}, + "noChatsFoundHere": "Hier wurden noch keine Chats gefunden. Starten Sie einen neuen Chat mit jemandem, indem Sie die Schaltfläche unten verwenden. ⤵️", + "@noChatsFoundHere": {} } From aa3de2816be5a7b6953b9bac3c7e17765d846752 Mon Sep 17 00:00:00 2001 From: Krille Date: Tue, 27 Aug 2024 07:46:48 +0200 Subject: [PATCH 238/288] chore: Follow up design textfields --- assets/l10n/intl_en.arb | 3 +- lib/pages/chat_members/chat_members_view.dart | 11 ++++++ lib/pages/chat_search/chat_search_view.dart | 12 ++++++ .../homeserver_picker/homeserver_picker.dart | 19 ++++++++- .../homeserver_picker_view.dart | 2 + .../invitation_selection_view.dart | 11 ++++++ lib/pages/new_group/new_group_view.dart | 17 +++----- .../new_private_chat_view.dart | 10 +++++ lib/pages/new_space/new_space_view.dart | 15 ++----- .../settings_ignore_list_view.dart | 2 +- .../settings_password_view.dart | 39 +++++++++---------- .../settings_security_view.dart | 7 ++-- lib/utils/localized_exception_extension.dart | 9 ++++- 13 files changed, 105 insertions(+), 52 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index e9146f2cf5..8c0c677f95 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -2760,5 +2760,6 @@ "loginWithMatrixId": "Login with Matrix-ID", "discoverHomeservers": "Discover homeservers", "whatIsAHomeserver": "What is a homeserver?", - "homeserverDescription": "All your data is stored on the homeserver, just like an email provider. You can choose which homeserver you want to use, while you can still communicate with everyone. Learn more at at https://matrix.org." + "homeserverDescription": "All your data is stored on the homeserver, just like an email provider. You can choose which homeserver you want to use, while you can still communicate with everyone. Learn more at at https://matrix.org.", + "doesNotSeemToBeAValidHomeserver": "Doesn't seem to be a compatible homeserver. Wrong URL?" } diff --git a/lib/pages/chat_members/chat_members_view.dart b/lib/pages/chat_members/chat_members_view.dart index b699fcef08..daa681a52f 100644 --- a/lib/pages/chat_members/chat_members_view.dart +++ b/lib/pages/chat_members/chat_members_view.dart @@ -34,6 +34,7 @@ class ChatMembersView extends StatelessWidget { (room.summary.mInvitedMemberCount ?? 0); final error = controller.error; + final theme = Theme.of(context); return Scaffold( appBar: AppBar( @@ -90,6 +91,16 @@ class ChatMembersView extends StatelessWidget { controller: controller.filterController, onChanged: controller.setFilter, decoration: InputDecoration( + filled: true, + fillColor: theme.colorScheme.secondaryContainer, + border: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(99), + ), + hintStyle: TextStyle( + color: theme.colorScheme.onPrimaryContainer, + fontWeight: FontWeight.normal, + ), prefixIcon: const Icon(Icons.search_outlined), hintText: L10n.of(context)!.search, ), diff --git a/lib/pages/chat_search/chat_search_view.dart b/lib/pages/chat_search/chat_search_view.dart index e08f25a2b1..e29fa1d015 100644 --- a/lib/pages/chat_search/chat_search_view.dart +++ b/lib/pages/chat_search/chat_search_view.dart @@ -31,6 +31,8 @@ class ChatSearchView extends StatelessWidget { ); } + final theme = Theme.of(context); + return Scaffold( appBar: AppBar( leading: const Center(child: BackButton()), @@ -59,6 +61,16 @@ class ChatSearchView extends StatelessWidget { decoration: InputDecoration( hintText: L10n.of(context)!.search, suffixIcon: const Icon(Icons.search_outlined), + filled: true, + fillColor: theme.colorScheme.secondaryContainer, + border: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(99), + ), + hintStyle: TextStyle( + color: theme.colorScheme.onPrimaryContainer, + fontWeight: FontWeight.normal, + ), ), ), ), diff --git a/lib/pages/homeserver_picker/homeserver_picker.dart b/lib/pages/homeserver_picker/homeserver_picker.dart index 32f1356d74..955aa3c560 100644 --- a/lib/pages/homeserver_picker/homeserver_picker.dart +++ b/lib/pages/homeserver_picker/homeserver_picker.dart @@ -86,7 +86,17 @@ class HomeserverPickerController extends State { Future checkHomeserverAction([_]) async { homeserverController.text = homeserverController.text.trim().toLowerCase().replaceAll(' ', '-'); - if (homeserverController.text == _lastCheckedUrl) return; + + if (homeserverController.text.isEmpty) { + setState(() { + error = loginFlows = null; + isLoading = false; + Matrix.of(context).getLoginClient().homeserver = null; + }); + return; + } + if (_lastCheckedUrl == homeserverController.text) return; + _lastCheckedUrl = homeserverController.text; setState(() { error = loginFlows = null; @@ -102,7 +112,12 @@ class HomeserverPickerController extends State { final (_, _, loginFlows) = await client.checkHomeserver(homeserver); this.loginFlows = loginFlows; } catch (e) { - setState(() => error = (e).toLocalizedString(context)); + setState( + () => error = (e).toLocalizedString( + context, + ExceptionContext.checkHomeserver, + ), + ); } finally { if (mounted) { setState(() => isLoading = false); diff --git a/lib/pages/homeserver_picker/homeserver_picker_view.dart b/lib/pages/homeserver_picker/homeserver_picker_view.dart index f99f949b9d..f381cfd511 100644 --- a/lib/pages/homeserver_picker/homeserver_picker_view.dart +++ b/lib/pages/homeserver_picker/homeserver_picker_view.dart @@ -76,6 +76,8 @@ class HomeserverPickerView extends StatelessWidget { controller.tryCheckHomeserverActionWithoutCooldown, onTap: controller.tryCheckHomeserverActionWithCooldown, controller: controller.homeserverController, + autocorrect: false, + keyboardType: TextInputType.url, decoration: InputDecoration( prefixIcon: controller.isLoading ? Container( diff --git a/lib/pages/invitation_selection/invitation_selection_view.dart b/lib/pages/invitation_selection/invitation_selection_view.dart index 795334e04c..6ea03d8ed6 100644 --- a/lib/pages/invitation_selection/invitation_selection_view.dart +++ b/lib/pages/invitation_selection/invitation_selection_view.dart @@ -29,6 +29,7 @@ class InvitationSelectionView extends StatelessWidget { } final groupName = room.name.isEmpty ? L10n.of(context)!.group : room.name; + final theme = Theme.of(context); return Scaffold( appBar: AppBar( leading: const Center(child: BackButton()), @@ -44,6 +45,16 @@ class InvitationSelectionView extends StatelessWidget { child: TextField( textInputAction: TextInputAction.search, decoration: InputDecoration( + filled: true, + fillColor: theme.colorScheme.secondaryContainer, + border: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(99), + ), + hintStyle: TextStyle( + color: theme.colorScheme.onPrimaryContainer, + fontWeight: FontWeight.normal, + ), hintText: L10n.of(context)!.inviteContactToGroup(groupName), prefixIcon: controller.loading ? const Padding( diff --git a/lib/pages/new_group/new_group_view.dart b/lib/pages/new_group/new_group_view.dart index 0f86efb45b..54c9a12863 100644 --- a/lib/pages/new_group/new_group_view.dart +++ b/lib/pages/new_group/new_group_view.dart @@ -61,12 +61,13 @@ class NewGroupView extends StatelessWidget { readOnly: controller.loading, decoration: InputDecoration( prefixIcon: const Icon(Icons.people_outlined), - hintText: L10n.of(context)!.groupName, + labelText: L10n.of(context)!.groupName, ), ), ), const SizedBox(height: 16), SwitchListTile.adaptive( + contentPadding: const EdgeInsets.symmetric(horizontal: 32), secondary: const Icon(Icons.public_outlined), title: Text(L10n.of(context)!.groupIsPublic), value: controller.publicGroup, @@ -76,6 +77,8 @@ class NewGroupView extends StatelessWidget { duration: FluffyThemes.animationDuration, child: controller.publicGroup ? SwitchListTile.adaptive( + contentPadding: + const EdgeInsets.symmetric(horizontal: 32), secondary: const Icon(Icons.search_outlined), title: Text(L10n.of(context)!.groupCanBeFoundViaSearch), value: controller.groupCanBeFound, @@ -86,6 +89,7 @@ class NewGroupView extends StatelessWidget { : const SizedBox.shrink(), ), SwitchListTile.adaptive( + contentPadding: const EdgeInsets.symmetric(horizontal: 32), secondary: Icon( Icons.lock_outlined, color: theme.colorScheme.onSurface, @@ -108,16 +112,7 @@ class NewGroupView extends StatelessWidget { controller.loading ? null : controller.submitAction, child: controller.loading ? const LinearProgressIndicator() - : Row( - children: [ - Expanded( - child: Text( - L10n.of(context)!.createGroupAndInviteUsers, - ), - ), - Icon(Icons.adaptive.arrow_forward_outlined), - ], - ), + : Text(L10n.of(context)!.createGroupAndInviteUsers), ), ), ), diff --git a/lib/pages/new_private_chat/new_private_chat_view.dart b/lib/pages/new_private_chat/new_private_chat_view.dart index ceee0ef45e..f46e8a4c89 100644 --- a/lib/pages/new_private_chat/new_private_chat_view.dart +++ b/lib/pages/new_private_chat/new_private_chat_view.dart @@ -54,6 +54,16 @@ class NewPrivateChatView extends StatelessWidget { onChanged: controller.searchUsers, decoration: InputDecoration( hintText: L10n.of(context)!.searchForUsers, + filled: true, + fillColor: theme.colorScheme.secondaryContainer, + border: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(99), + ), + hintStyle: TextStyle( + color: theme.colorScheme.onPrimaryContainer, + fontWeight: FontWeight.normal, + ), prefixIcon: searchResponse == null ? const Icon(Icons.search_outlined) : FutureBuilder( diff --git a/lib/pages/new_space/new_space_view.dart b/lib/pages/new_space/new_space_view.dart index 2f46e5e739..ed60d670de 100644 --- a/lib/pages/new_space/new_space_view.dart +++ b/lib/pages/new_space/new_space_view.dart @@ -51,18 +51,20 @@ class NewSpaceView extends StatelessWidget { readOnly: controller.loading, decoration: InputDecoration( prefixIcon: const Icon(Icons.people_outlined), - hintText: L10n.of(context)!.spaceName, + labelText: L10n.of(context)!.spaceName, errorText: controller.nameError, ), ), ), const SizedBox(height: 16), SwitchListTile.adaptive( + contentPadding: const EdgeInsets.symmetric(horizontal: 32), title: Text(L10n.of(context)!.spaceIsPublic), value: controller.publicGroup, onChanged: controller.setPublicGroup, ), ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 32), trailing: const Padding( padding: EdgeInsets.symmetric(horizontal: 16.0), child: Icon(Icons.info_outlined), @@ -78,16 +80,7 @@ class NewSpaceView extends StatelessWidget { controller.loading ? null : controller.submitAction, child: controller.loading ? const LinearProgressIndicator() - : Row( - children: [ - Expanded( - child: Text( - L10n.of(context)!.createNewSpace, - ), - ), - Icon(Icons.adaptive.arrow_forward_outlined), - ], - ), + : Text(L10n.of(context)!.createNewSpace), ), ), ), diff --git a/lib/pages/settings_ignore_list/settings_ignore_list_view.dart b/lib/pages/settings_ignore_list/settings_ignore_list_view.dart index e66387503d..63466ddbb0 100644 --- a/lib/pages/settings_ignore_list/settings_ignore_list_view.dart +++ b/lib/pages/settings_ignore_list/settings_ignore_list_view.dart @@ -46,7 +46,7 @@ class SettingsIgnoreListView extends StatelessWidget { labelText: L10n.of(context)!.blockUsername, suffixIcon: IconButton( tooltip: L10n.of(context)!.block, - icon: const Icon(Icons.send_outlined), + icon: const Icon(Icons.add), onPressed: () => controller.ignoreUser(context), ), ), diff --git a/lib/pages/settings_password/settings_password_view.dart b/lib/pages/settings_password/settings_password_view.dart index ee80e93611..3e9b64f339 100644 --- a/lib/pages/settings_password/settings_password_view.dart +++ b/lib/pages/settings_password/settings_password_view.dart @@ -17,12 +17,6 @@ class SettingsPasswordView extends StatelessWidget { return Scaffold( appBar: AppBar( title: Text(L10n.of(context)!.changePassword), - actions: [ - TextButton( - child: Text(L10n.of(context)!.passwordRecoverySettings), - onPressed: () => context.go('/rooms/settings/security/3pid'), - ), - ], ), body: ListTileTheme( iconColor: theme.colorScheme.onSurface, @@ -31,13 +25,6 @@ class SettingsPasswordView extends StatelessWidget { padding: const EdgeInsets.all(16.0), child: Column( children: [ - Center( - child: Icon( - Icons.key_outlined, - color: theme.dividerColor, - size: 80, - ), - ), const SizedBox(height: 16), TextField( controller: controller.oldPasswordController, @@ -46,18 +33,22 @@ class SettingsPasswordView extends StatelessWidget { autofocus: true, readOnly: controller.loading, decoration: InputDecoration( - hintText: L10n.of(context)!.pleaseEnterYourCurrentPassword, + prefixIcon: const Icon(Icons.lock_outlined), + hintText: '********', + labelText: L10n.of(context)!.pleaseEnterYourCurrentPassword, errorText: controller.oldPasswordError, ), ), - const Divider(height: 32), + const Divider(height: 64), TextField( controller: controller.newPassword1Controller, obscureText: true, autocorrect: false, readOnly: controller.loading, decoration: InputDecoration( - hintText: L10n.of(context)!.newPassword, + prefixIcon: const Icon(Icons.lock_reset_outlined), + hintText: '********', + labelText: L10n.of(context)!.newPassword, errorText: controller.newPassword1Error, ), ), @@ -68,22 +59,28 @@ class SettingsPasswordView extends StatelessWidget { autocorrect: false, readOnly: controller.loading, decoration: InputDecoration( - hintText: L10n.of(context)!.repeatPassword, + prefixIcon: const Icon(Icons.repeat_outlined), + hintText: '********', + labelText: L10n.of(context)!.repeatPassword, errorText: controller.newPassword2Error, ), ), - const SizedBox(height: 16), + const SizedBox(height: 32), SizedBox( width: double.infinity, - child: ElevatedButton.icon( + child: ElevatedButton( onPressed: controller.loading ? null : controller.changePassword, - icon: const Icon(Icons.send_outlined), - label: controller.loading + child: controller.loading ? const LinearProgressIndicator() : Text(L10n.of(context)!.changePassword), ), ), + const SizedBox(height: 16), + TextButton( + child: Text(L10n.of(context)!.passwordRecoverySettings), + onPressed: () => context.go('/rooms/settings/security/3pid'), + ), ], ), ), diff --git a/lib/pages/settings_security/settings_security_view.dart b/lib/pages/settings_security/settings_security_view.dart index 76ea243a06..a805a6e335 100644 --- a/lib/pages/settings_security/settings_security_view.dart +++ b/lib/pages/settings_security/settings_security_view.dart @@ -87,9 +87,7 @@ class SettingsSecurityView extends StatelessWidget { onTap: controller.setAppLockAction, ), }, - Divider( - color: theme.dividerColor, - ), + Divider(color: theme.dividerColor), ListTile( title: Text( L10n.of(context)!.account, @@ -118,13 +116,14 @@ class SettingsSecurityView extends StatelessWidget { ), ListTile( iconColor: Colors.orange, - leading: const Icon(Icons.tap_and_play), + leading: const Icon(Icons.delete_sweep_outlined), title: Text( L10n.of(context)!.dehydrate, style: const TextStyle(color: Colors.orange), ), onTap: controller.dehydrateAction, ), + Divider(color: theme.dividerColor), ListTile( iconColor: Colors.red, leading: const Icon(Icons.delete_outlined), diff --git a/lib/utils/localized_exception_extension.dart b/lib/utils/localized_exception_extension.dart index f9473c3f55..0dfbe3dce5 100644 --- a/lib/utils/localized_exception_extension.dart +++ b/lib/utils/localized_exception_extension.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:http/http.dart'; import 'package:matrix/encryption.dart'; import 'package:matrix/matrix.dart'; @@ -69,9 +70,14 @@ extension LocalizedExceptionExtension on Object { } if (this is IOException || this is SocketException || - this is SyncConnectionException) { + this is SyncConnectionException || + this is ClientException) { return L10n.of(context)!.noConnectionToTheServer; } + if (this is FormatException && + exceptionContext == ExceptionContext.checkHomeserver) { + return L10n.of(context)!.doesNotSeemToBeAValidHomeserver; + } if (this is String) return toString(); if (this is UiaException) return toString(); Logs().w('Something went wrong: ', this); @@ -81,4 +87,5 @@ extension LocalizedExceptionExtension on Object { enum ExceptionContext { changePassword, + checkHomeserver, } From eae7db7d71c16935fab50e512f2c594cbfc96dd0 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Tue, 27 Aug 2024 17:22:01 +0200 Subject: [PATCH 239/288] chore: Follow up set read marker logic --- lib/pages/chat/chat.dart | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 8dc0033aa2..ebc60d6622 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -332,6 +332,7 @@ class ChatController extends State void updateView() { if (!mounted) return; + setReadMarker(); setState(() {}); } @@ -340,18 +341,10 @@ class ChatController extends State int? animateInEventIndex; void onInsert(int i) { - onChange(i); // setState will be called by updateView() anyway animateInEventIndex = i; } - void onChange(int i) { - if (timeline?.events[i].status == EventStatus.synced) { - final index = timeline!.events.firstIndexWhereNotError; - if (i == index) setReadMarker(eventId: timeline?.events[i].eventId); - } - } - Future _getTimeline({ String? eventContextId, }) async { @@ -367,7 +360,6 @@ class ChatController extends State onUpdate: updateView, eventContextId: eventContextId, onInsert: onInsert, - onChange: onChange, ); } catch (e, s) { Logs().w('Unable to load timeline on event ID $eventContextId', e, s); @@ -375,7 +367,6 @@ class ChatController extends State timeline = await room.getTimeline( onUpdate: updateView, onInsert: onInsert, - onChange: onChange, ); if (!mounted) return; if (e is TimeoutException || e is IOException) { @@ -1385,12 +1376,3 @@ class ChatController extends State } enum EmojiPickerType { reaction, keyboard } - -extension on List { - int get firstIndexWhereNotError { - if (isEmpty) return 0; - final index = indexWhere((event) => !event.status.isError); - if (index == -1) return length; - return index; - } -} From 3cb88f3d49086282b57bc725b8463dcfd52f8124 Mon Sep 17 00:00:00 2001 From: Krille Date: Wed, 28 Aug 2024 08:43:21 +0200 Subject: [PATCH 240/288] chore: Follow up login page --- .../homeserver_picker_view.dart | 234 +++++++++--------- lib/widgets/layouts/login_scaffold.dart | 8 +- 2 files changed, 118 insertions(+), 124 deletions(-) diff --git a/lib/pages/homeserver_picker/homeserver_picker_view.dart b/lib/pages/homeserver_picker/homeserver_picker_view.dart index f381cfd511..ff89bf4e13 100644 --- a/lib/pages/homeserver_picker/homeserver_picker_view.dart +++ b/lib/pages/homeserver_picker/homeserver_picker_view.dart @@ -27,7 +27,8 @@ class HomeserverPickerView extends StatelessWidget { title: Text(L10n.of(context)!.addAccount), ) : null, - body: ListView( + body: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // display a prominent banner to import session for TOR browser // users. This feature is just some UX sugar as TOR users are @@ -52,134 +53,125 @@ class HomeserverPickerView extends StatelessWidget { ), ), ), - Image.asset( - 'assets/banner_transparent.png', - ), - Padding( - padding: const EdgeInsets.only( - top: 16.0, - right: 8.0, - left: 8.0, - bottom: 16.0, + if (MediaQuery.of(context).size.height > 512) + ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height / 4, + ), + child: Image.asset( + 'assets/banner_transparent.png', + alignment: Alignment.center, + repeat: ImageRepeat.repeat, + ), ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.all(16.0), - child: TextField( - onChanged: controller.tryCheckHomeserverActionWithCooldown, - onEditingComplete: - controller.tryCheckHomeserverActionWithoutCooldown, - onSubmitted: - controller.tryCheckHomeserverActionWithoutCooldown, - onTap: controller.tryCheckHomeserverActionWithCooldown, - controller: controller.homeserverController, - autocorrect: false, - keyboardType: TextInputType.url, - decoration: InputDecoration( - prefixIcon: controller.isLoading - ? Container( - width: 16, - height: 16, - alignment: Alignment.center, - child: const SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator.adaptive( - strokeWidth: 2, - ), - ), - ) - : const Icon(Icons.search_outlined), - filled: false, - border: OutlineInputBorder( - borderRadius: - BorderRadius.circular(AppConfig.borderRadius), - ), - hintText: AppConfig.defaultHomeserver, - labelText: L10n.of(context)!.homeserver, - errorText: controller.error, - suffixIcon: IconButton( - onPressed: () { - showDialog( - context: context, - builder: (context) => AlertDialog.adaptive( - title: Text(L10n.of(context)!.whatIsAHomeserver), - content: Linkify( - text: L10n.of(context)!.homeserverDescription, - ), - actions: [ - TextButton( - onPressed: () => launchUrl( - Uri.https('servers.joinmatrix.org'), - ), - child: Text( - L10n.of(context)!.discoverHomeservers, - ), - ), - TextButton( - onPressed: Navigator.of(context).pop, - child: Text(L10n.of(context)!.close), - ), - ], + Padding( + padding: const EdgeInsets.all(32.0), + child: TextField( + onChanged: controller.tryCheckHomeserverActionWithCooldown, + onEditingComplete: + controller.tryCheckHomeserverActionWithoutCooldown, + onSubmitted: controller.tryCheckHomeserverActionWithoutCooldown, + onTap: controller.tryCheckHomeserverActionWithCooldown, + controller: controller.homeserverController, + autocorrect: false, + keyboardType: TextInputType.url, + decoration: InputDecoration( + prefixIcon: controller.isLoading + ? Container( + width: 16, + height: 16, + alignment: Alignment.center, + child: const SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator.adaptive( + strokeWidth: 2, + ), + ), + ) + : const Icon(Icons.search_outlined), + filled: false, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(AppConfig.borderRadius), + ), + hintText: AppConfig.defaultHomeserver, + labelText: L10n.of(context)!.homeserver, + errorText: controller.error, + suffixIcon: IconButton( + onPressed: () { + showDialog( + context: context, + builder: (context) => AlertDialog.adaptive( + title: Text(L10n.of(context)!.whatIsAHomeserver), + content: Linkify( + text: L10n.of(context)!.homeserverDescription, + ), + actions: [ + TextButton( + onPressed: () => launchUrl( + Uri.https('servers.joinmatrix.org'), + ), + child: Text( + L10n.of(context)!.discoverHomeservers, ), - ); - }, - icon: const Icon(Icons.info_outlined), + ), + TextButton( + onPressed: Navigator.of(context).pop, + child: Text(L10n.of(context)!.close), + ), + ], ), - ), + ); + }, + icon: const Icon(Icons.info_outlined), + ), + ), + ), + ), + if (MediaQuery.of(context).size.height > 512) const Spacer(), + ListView( + shrinkWrap: true, + padding: const EdgeInsets.symmetric( + horizontal: 32.0, + vertical: 32.0, + ), + children: [ + TextButton( + style: TextButton.styleFrom( + textStyle: theme.textTheme.labelMedium, + foregroundColor: theme.colorScheme.secondary, + ), + onPressed: controller.isLoggingIn || controller.isLoading + ? null + : controller.restoreBackup, + child: Text(L10n.of(context)!.hydrate), + ), + if (controller.supportsPasswordLogin && controller.supportsSso) + TextButton( + style: TextButton.styleFrom( + foregroundColor: theme.colorScheme.secondary, + textStyle: theme.textTheme.labelMedium, ), + onPressed: controller.isLoggingIn || controller.isLoading + ? null + : controller.login, + child: Text(L10n.of(context)!.loginWithMatrixId), ), - if (controller.supportsPasswordLogin || controller.supportsSso) - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 8.0, - ), - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: theme.colorScheme.primary, - foregroundColor: theme.colorScheme.onPrimary, - ), - onPressed: controller.isLoggingIn || controller.isLoading - ? null - : controller.supportsSso - ? controller.ssoLoginAction - : controller.login, - child: Text(L10n.of(context)!.connect), - ), + const SizedBox(height: 8.0), + if (controller.supportsPasswordLogin || controller.supportsSso) + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: theme.colorScheme.primary, + foregroundColor: theme.colorScheme.onPrimary, ), - if (controller.supportsPasswordLogin && controller.supportsSso) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: TextButton( - style: TextButton.styleFrom( - foregroundColor: theme.colorScheme.secondary, - textStyle: theme.textTheme.labelMedium, - ), - onPressed: controller.isLoggingIn || controller.isLoading - ? null + onPressed: controller.isLoggingIn || controller.isLoading + ? null + : controller.supportsSso + ? controller.ssoLoginAction : controller.login, - child: Text(L10n.of(context)!.loginWithMatrixId), - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: TextButton( - style: TextButton.styleFrom( - textStyle: theme.textTheme.labelMedium, - foregroundColor: theme.colorScheme.secondary, - ), - onPressed: controller.isLoggingIn || controller.isLoading - ? null - : controller.restoreBackup, - child: Text(L10n.of(context)!.hydrate), - ), + child: Text(L10n.of(context)!.next), ), - ], - ), + ], ), ], ), diff --git a/lib/widgets/layouts/login_scaffold.dart b/lib/widgets/layouts/login_scaffold.dart index 08ed8d75c8..8b9a13739c 100644 --- a/lib/widgets/layouts/login_scaffold.dart +++ b/lib/widgets/layouts/login_scaffold.dart @@ -41,15 +41,17 @@ class LoginScaffold extends StatelessWidget { actions: appBar?.actions, backgroundColor: isMobileMode ? null : Colors.transparent, ), - body: body, + body: SafeArea(child: body), backgroundColor: isMobileMode ? null : theme.colorScheme.surface.withOpacity(0.8), bottomNavigationBar: isMobileMode ? Material( elevation: 4, shadowColor: theme.colorScheme.onSurface, - child: const _PrivacyButtons( - mainAxisAlignment: MainAxisAlignment.center, + child: const SafeArea( + child: _PrivacyButtons( + mainAxisAlignment: MainAxisAlignment.center, + ), ), ) : null, From e38ccbebbe7ea6fb7c3fbc17c5375c22c40786ce Mon Sep 17 00:00:00 2001 From: Krille Date: Thu, 29 Aug 2024 07:52:30 +0200 Subject: [PATCH 241/288] build: Update matrix dart sdk --- pubspec.lock | 47 +++++++++++++++++++++++------------------------ pubspec.yaml | 3 +-- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 7e4ab160c2..89313580fb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1082,18 +1082,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.4" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.3" leak_tracker_testing: dependency: transitive description: @@ -1194,27 +1194,26 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.8.0" matrix: dependency: "direct main" description: - path: "." - ref: HEAD - resolved-ref: "8f350760c4a418a1553030dc4b81408185e0fad5" - url: "https://github.com/famedly/matrix-dart-sdk.git" - source: git - version: "0.32.2" + name: matrix + sha256: f47a751e01abe5a6aa17b21187724788c45e3700fbec14a04ac1e2d2097b9a81 + url: "https://pub.dev" + source: hosted + version: "0.32.4" meta: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.12.0" mgrs_dart: dependency: transitive description: @@ -1443,10 +1442,10 @@ packages: dependency: transitive description: name: platform - sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.4" platform_detect: dependency: transitive description: @@ -1968,26 +1967,26 @@ packages: dependency: transitive description: name: test - sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e" + sha256: "7ee446762c2c50b3bd4ea96fe13ffac69919352bd3b4b17bac3f3465edc58073" url: "https://pub.dev" source: hosted - version: "1.25.7" + version: "1.25.2" test_api: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.0" test_core: dependency: transitive description: name: test_core - sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696" + sha256: "2bc4b4ecddd75309300d8096f781c0e3280ca1ef85beda558d33fcbedc2eead4" url: "https://pub.dev" source: hosted - version: "0.6.4" + version: "0.6.0" timezone: dependency: transitive description: @@ -2280,10 +2279,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "14.2.1" wakelock_plus: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index bc76d406fd..ea6feab425 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -63,8 +63,7 @@ dependencies: keyboard_shortcuts: ^0.1.4 latlong2: ^0.9.1 linkify: ^5.0.0 - matrix: # Until 0.32.3 is released - git: https://github.com/famedly/matrix-dart-sdk.git + matrix: ^0.32.3 native_imaging: ^0.1.1 opus_caf_converter_dart: ^1.0.1 package_info_plus: ^6.0.0 From e6948ee0b4155750ee1f0af22c17a17c91aa3ea8 Mon Sep 17 00:00:00 2001 From: Krille Date: Thu, 29 Aug 2024 08:20:46 +0200 Subject: [PATCH 242/288] chore: Follow up add web notification icons with auth media --- .../local_notifications_extension.dart | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/lib/widgets/local_notifications_extension.dart b/lib/widgets/local_notifications_extension.dart index 99fd00cf93..0549663f81 100644 --- a/lib/widgets/local_notifications_extension.dart +++ b/lib/widgets/local_notifications_extension.dart @@ -50,26 +50,35 @@ extension LocalNotificationsExtension on MatrixState { if (kIsWeb) { final avatarUrl = event.senderFromMemoryOrFallback.avatarUrl; + Uri? thumbnailUri; - final iconBytes = avatarUrl == null - ? null - : await client.downloadMxcCached( - avatarUrl, - width: 64, - height: 64, - thumbnailMethod: ThumbnailMethod.crop, - isThumbnail: true, - animated: false, - ); + if (avatarUrl != null) { + const size = 64; + const thumbnailMethod = ThumbnailMethod.crop; + // Pre-cache so that we can later just set the thumbnail uri as icon: + await client.downloadMxcCached( + avatarUrl, + width: size, + height: size, + thumbnailMethod: thumbnailMethod, + isThumbnail: true, + ); + + thumbnailUri = + await event.senderFromMemoryOrFallback.avatarUrl?.getThumbnailUri( + client, + width: size, + height: size, + method: thumbnailMethod, + ); + } _audioPlayer.play(); html.Notification( title, body: body, - icon: iconBytes == null - ? null - : html.Url.createObjectUrl(html.Blob(iconBytes)), + icon: thumbnailUri?.toString(), tag: event.room.id, ); } else if (Platform.isLinux) { From ab0a5175f62f9a278f3a301bae7cfc7657aa9ea8 Mon Sep 17 00:00:00 2001 From: Rex_sa Date: Mon, 26 Aug 2024 20:52:54 +0000 Subject: [PATCH 243/288] Translated using Weblate (Arabic) Currently translated at 100.0% (662 of 662 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ar/ --- assets/l10n/intl_ar.arb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_ar.arb b/assets/l10n/intl_ar.arb index e9f73d8860..1786ae8dfa 100644 --- a/assets/l10n/intl_ar.arb +++ b/assets/l10n/intl_ar.arb @@ -2793,5 +2793,13 @@ "sendCanceled": "تم إلغاء الإرسال", "@sendCanceled": {}, "noChatsFoundHere": "لم يتم العثور على دردشات هنا حتى الآن. ابدأ محادثة جديدة مع شخص ما باستخدام الزر أدناه. ⤵️", - "@noChatsFoundHere": {} + "@noChatsFoundHere": {}, + "loginWithMatrixId": "تسجيل الدخول باستخدام معرف ماتريكس", + "@loginWithMatrixId": {}, + "discoverHomeservers": "اكتشف الخوادم المنزلية", + "@discoverHomeservers": {}, + "whatIsAHomeserver": "ما هو خادم المنزل ؟", + "@whatIsAHomeserver": {}, + "homeserverDescription": "يتم تخزين جميع بياناتك على خادم المنزل، تمامًا مثل مزود خدمة البريد الإلكتروني. يمكنك اختيار خادم البيت الذي تريد استخدامه، بينما لا يزال بإمكانك التواصل مع الجميع. اعرف المزيد على https://matrix.org.", + "@homeserverDescription": {} } From acd1540b90f18083f9fae665264b862377e72bb2 Mon Sep 17 00:00:00 2001 From: xabirequejo Date: Mon, 26 Aug 2024 15:45:26 +0000 Subject: [PATCH 244/288] Translated using Weblate (Basque) Currently translated at 100.0% (662 of 662 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/ --- assets/l10n/intl_eu.arb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_eu.arb b/assets/l10n/intl_eu.arb index 2f377e98aa..ab650efbd3 100644 --- a/assets/l10n/intl_eu.arb +++ b/assets/l10n/intl_eu.arb @@ -2793,5 +2793,13 @@ "sendCanceled": "Bidalketa bertan behera utzi da", "@sendCanceled": {}, "noChatsFoundHere": "Ez da txatik aurkitu. Hasi norbaitekin txateatzen beheko botoia erabiliz. ⤵️", - "@noChatsFoundHere": {} + "@noChatsFoundHere": {}, + "homeserverDescription": "Zerbitzariak datuak gordetzen ditu, ePosta hornitzaileek mezuak gordetzen dituzten bezala. Nahi duzun zerbitzaria aukeratu dezakezu eta, hala ere, besteetako edonorekin hitz egin. Ikasi gehiago https://matrix.org webgunean.", + "@homeserverDescription": {}, + "loginWithMatrixId": "Hasi saioa Matrix IDarekin", + "@loginWithMatrixId": {}, + "discoverHomeservers": "Arakatu zerbitzariak", + "@discoverHomeservers": {}, + "whatIsAHomeserver": "Zer da zerbitzari bat?", + "@whatIsAHomeserver": {} } From 9e59448a657ab2b79841c8d56be56f87e902edde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jos=C3=A9=20m?= Date: Mon, 26 Aug 2024 07:38:48 +0000 Subject: [PATCH 245/288] Translated using Weblate (Galician) Currently translated at 100.0% (662 of 662 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/gl/ --- assets/l10n/intl_gl.arb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_gl.arb b/assets/l10n/intl_gl.arb index 925f8bfb5b..0b5edd3d20 100644 --- a/assets/l10n/intl_gl.arb +++ b/assets/l10n/intl_gl.arb @@ -2793,5 +2793,13 @@ "sendCanceled": "Cancelouse o envío", "@sendCanceled": {}, "noChatsFoundHere": "Ningún chat por aquí. Comeza unha nova conversa con alguén premendo no botón de abaixo. ⤵️", - "@noChatsFoundHere": {} + "@noChatsFoundHere": {}, + "discoverHomeservers": "Atopar servidores", + "@discoverHomeservers": {}, + "whatIsAHomeserver": "Que é un servidor de inicio?", + "@whatIsAHomeserver": {}, + "loginWithMatrixId": "Acceder co ID-Matrix", + "@loginWithMatrixId": {}, + "homeserverDescription": "Todos os teus datos quedan gardados no servidor de inicio, igual que co teu provedor de correo electrónico. Podes elexir o servidor que queres usar e poderás comunicarte con todos os demais. Aprende máis en https://matrix.org.", + "@homeserverDescription": {} } From 73dbdd6f681fe1a78ff48830b15af4c16c22226f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Mon, 26 Aug 2024 16:55:02 +0000 Subject: [PATCH 246/288] Translated using Weblate (Turkish) Currently translated at 100.0% (662 of 662 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/tr/ --- assets/l10n/intl_tr.arb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_tr.arb b/assets/l10n/intl_tr.arb index d508724f68..1719c9733c 100644 --- a/assets/l10n/intl_tr.arb +++ b/assets/l10n/intl_tr.arb @@ -2793,5 +2793,13 @@ "sendCanceled": "Gönderme iptal edildi", "@sendCanceled": {}, "noChatsFoundHere": "Burada henüz sohbet bulunamadı. Aşağıdaki düğmeyi kullanarak biriyle yeni bir sohbet başlatın. ⤵️", - "@noChatsFoundHere": {} + "@noChatsFoundHere": {}, + "loginWithMatrixId": "Matrix kimliği ile oturum aç", + "@loginWithMatrixId": {}, + "discoverHomeservers": "Ana sunucuları keşfet", + "@discoverHomeservers": {}, + "whatIsAHomeserver": "Ana sunucu nedir?", + "@whatIsAHomeserver": {}, + "homeserverDescription": "Tüm verileriniz tıpkı bir e-posta sağlayıcısı gibi ana sunucuda saklanır. Hangi ana sunucuyu kullanmak istediğinizi seçebilir ve herkesle iletişim kurmaya devam edebilirsiniz. https://matrix.org adresinden daha fazla bilgi edinin.", + "@homeserverDescription": {} } From aef3f6d6f158a3b0b533e489934681097465b3c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E7=8E=8B=E5=8F=AB=E6=88=91=E6=9D=A5=E5=B7=A1?= =?UTF-8?q?=E5=B1=B1?= Date: Tue, 27 Aug 2024 01:28:45 +0000 Subject: [PATCH 247/288] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (662 of 662 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hans/ --- assets/l10n/intl_zh.arb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_zh.arb b/assets/l10n/intl_zh.arb index a252279478..620345df7f 100644 --- a/assets/l10n/intl_zh.arb +++ b/assets/l10n/intl_zh.arb @@ -2793,5 +2793,13 @@ "sendCanceled": "发送被取消", "@sendCanceled": {}, "noChatsFoundHere": "此处尚未找到聊天。使用下方按钮 ⤵️ 开始和某人的新聊天", - "@noChatsFoundHere": {} + "@noChatsFoundHere": {}, + "loginWithMatrixId": "使用 Matrix-ID 登录", + "@loginWithMatrixId": {}, + "discoverHomeservers": "发现主服务器", + "@discoverHomeservers": {}, + "whatIsAHomeserver": "什么是主服务器?", + "@whatIsAHomeserver": {}, + "homeserverDescription": "主服务器上就像电子邮件提供商,你的所有数据都存储在上面。你可以选择你想使用哪个主服务器。在 https://matrix.org 上了解更多信息。", + "@homeserverDescription": {} } From 2e22db2880cb699e4aab941612b524133868aa0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Tue, 27 Aug 2024 09:38:02 +0000 Subject: [PATCH 248/288] Translated using Weblate (Estonian) Currently translated at 99.6% (661 of 663 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/ --- assets/l10n/intl_et.arb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_et.arb b/assets/l10n/intl_et.arb index c9ac697fcd..f702beb5a3 100644 --- a/assets/l10n/intl_et.arb +++ b/assets/l10n/intl_et.arb @@ -2793,5 +2793,11 @@ "sendCanceled": "Saatmine on katkestatud", "@sendCanceled": {}, "noChatsFoundHere": "Siin ei leidu veel ühtegi vestlust. Alusta uut vestlust klõpsides allpool asuvat nuppu. ⤵️", - "@noChatsFoundHere": {} + "@noChatsFoundHere": {}, + "loginWithMatrixId": "Logi sisse Matrix-ID alusel", + "@loginWithMatrixId": {}, + "discoverHomeservers": "Leia koduservereid", + "@discoverHomeservers": {}, + "whatIsAHomeserver": "Mis on koduserver?", + "@whatIsAHomeserver": {} } From 68dc14066e887113a2e700d0272a37d7d05cacb6 Mon Sep 17 00:00:00 2001 From: DarkCoder15 Date: Tue, 27 Aug 2024 12:33:27 +0000 Subject: [PATCH 249/288] Translated using Weblate (Russian) Currently translated at 97.4% (646 of 663 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ru/ --- assets/l10n/intl_ru.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/l10n/intl_ru.arb b/assets/l10n/intl_ru.arb index ea5c7bbc1d..2526b66dbd 100644 --- a/assets/l10n/intl_ru.arb +++ b/assets/l10n/intl_ru.arb @@ -2232,7 +2232,7 @@ }, "commandHint_cuddle": "Отправить улыбку", "@commandHint_cuddle": {}, - "readUpToHere": "Дочитать до сюда", + "readUpToHere": "Непрочитанное", "@readUpToHere": {}, "commandHint_hug": "Отправить обнимашки", "@commandHint_hug": {}, From 3e5e101213e60dd8b9426ccf060c27778f6e45cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Tue, 27 Aug 2024 18:51:12 +0000 Subject: [PATCH 250/288] Translated using Weblate (Turkish) Currently translated at 100.0% (663 of 663 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/tr/ --- assets/l10n/intl_tr.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_tr.arb b/assets/l10n/intl_tr.arb index 1719c9733c..04e74ed261 100644 --- a/assets/l10n/intl_tr.arb +++ b/assets/l10n/intl_tr.arb @@ -2801,5 +2801,7 @@ "whatIsAHomeserver": "Ana sunucu nedir?", "@whatIsAHomeserver": {}, "homeserverDescription": "Tüm verileriniz tıpkı bir e-posta sağlayıcısı gibi ana sunucuda saklanır. Hangi ana sunucuyu kullanmak istediğinizi seçebilir ve herkesle iletişim kurmaya devam edebilirsiniz. https://matrix.org adresinden daha fazla bilgi edinin.", - "@homeserverDescription": {} + "@homeserverDescription": {}, + "doesNotSeemToBeAValidHomeserver": "Uyumlu bir ana sunucu gibi görünmüyor. Yanlış URL mi?", + "@doesNotSeemToBeAValidHomeserver": {} } From 0680bd8bae511f61d958bd5b6516ca01392e596a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E7=8E=8B=E5=8F=AB=E6=88=91=E6=9D=A5=E5=B7=A1?= =?UTF-8?q?=E5=B1=B1?= Date: Tue, 27 Aug 2024 23:24:00 +0000 Subject: [PATCH 251/288] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (663 of 663 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hans/ --- assets/l10n/intl_zh.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_zh.arb b/assets/l10n/intl_zh.arb index 620345df7f..e73944e6ac 100644 --- a/assets/l10n/intl_zh.arb +++ b/assets/l10n/intl_zh.arb @@ -2801,5 +2801,7 @@ "whatIsAHomeserver": "什么是主服务器?", "@whatIsAHomeserver": {}, "homeserverDescription": "主服务器上就像电子邮件提供商,你的所有数据都存储在上面。你可以选择你想使用哪个主服务器。在 https://matrix.org 上了解更多信息。", - "@homeserverDescription": {} + "@homeserverDescription": {}, + "doesNotSeemToBeAValidHomeserver": "似乎不是兼容的主服务器。URL 不正确?", + "@doesNotSeemToBeAValidHomeserver": {} } From 3e4f281b017ef59abb2dca4afe9f8d2957e9d8cd Mon Sep 17 00:00:00 2001 From: Rex_sa Date: Wed, 28 Aug 2024 11:36:03 +0000 Subject: [PATCH 252/288] Translated using Weblate (Arabic) Currently translated at 100.0% (663 of 663 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ar/ --- assets/l10n/intl_ar.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_ar.arb b/assets/l10n/intl_ar.arb index 1786ae8dfa..7adb678b10 100644 --- a/assets/l10n/intl_ar.arb +++ b/assets/l10n/intl_ar.arb @@ -2801,5 +2801,7 @@ "whatIsAHomeserver": "ما هو خادم المنزل ؟", "@whatIsAHomeserver": {}, "homeserverDescription": "يتم تخزين جميع بياناتك على خادم المنزل، تمامًا مثل مزود خدمة البريد الإلكتروني. يمكنك اختيار خادم البيت الذي تريد استخدامه، بينما لا يزال بإمكانك التواصل مع الجميع. اعرف المزيد على https://matrix.org.", - "@homeserverDescription": {} + "@homeserverDescription": {}, + "doesNotSeemToBeAValidHomeserver": "لا يبدو أنه خادم منزلي متوافق. عنوان URL غير صحيح ؟", + "@doesNotSeemToBeAValidHomeserver": {} } From 07abcf822e10d445a1c42b0ad12493cbf17f8f16 Mon Sep 17 00:00:00 2001 From: xabirequejo Date: Thu, 29 Aug 2024 09:17:04 +0000 Subject: [PATCH 253/288] Translated using Weblate (Basque) Currently translated at 100.0% (663 of 663 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/ --- assets/l10n/intl_eu.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_eu.arb b/assets/l10n/intl_eu.arb index ab650efbd3..f317b99dee 100644 --- a/assets/l10n/intl_eu.arb +++ b/assets/l10n/intl_eu.arb @@ -2801,5 +2801,7 @@ "discoverHomeservers": "Arakatu zerbitzariak", "@discoverHomeservers": {}, "whatIsAHomeserver": "Zer da zerbitzari bat?", - "@whatIsAHomeserver": {} + "@whatIsAHomeserver": {}, + "doesNotSeemToBeAValidHomeserver": "Ez dirudi zerbitzaria bateragarria denik. Zuzena da URLa?", + "@doesNotSeemToBeAValidHomeserver": {} } From 4e194548942e50bdc2da09b2b985f35dabb52266 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jos=C3=A9=20m?= Date: Thu, 29 Aug 2024 02:48:58 +0000 Subject: [PATCH 254/288] Translated using Weblate (Galician) Currently translated at 100.0% (663 of 663 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/gl/ --- assets/l10n/intl_gl.arb | 64 +++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/assets/l10n/intl_gl.arb b/assets/l10n/intl_gl.arb index 0b5edd3d20..a94ac1fda1 100644 --- a/assets/l10n/intl_gl.arb +++ b/assets/l10n/intl_gl.arb @@ -190,7 +190,7 @@ "description": {} } }, - "changedTheChatNameTo": "{username} mudou o nome da conversa a: '{chatname}'", + "changedTheChatNameTo": "{username} mudou o nome da charla a: '{chatname}'", "@changedTheChatNameTo": { "type": "text", "placeholders": { @@ -1931,7 +1931,7 @@ "@oneClientLoggedOut": {}, "link": "Ligazón", "@link": {}, - "yourChatBackupHasBeenSetUp": "Configurouse a copia de apoio do chat.", + "yourChatBackupHasBeenSetUp": "Configurouse a copia de apoio da charla.", "@yourChatBackupHasBeenSetUp": {}, "unverified": "Sen verificar", "@unverified": {}, @@ -1968,7 +1968,7 @@ "type": "text", "description": "Usage hint for the command /clearcache" }, - "commandHint_dm": "Iniciar un chat directo\nUsa --no-encryption para desactivar a cifraxe", + "commandHint_dm": "Iniciar unha charla directa\nUsa --no-encryption para desactivar a cifraxe", "@commandHint_dm": { "type": "text", "description": "Usage hint for the command /dm" @@ -2198,7 +2198,7 @@ "@callingAccount": {}, "callingAccountDetails": "Permítelle a FluffyChat usar a app marcador nativa de android.", "@callingAccountDetails": {}, - "appearOnTopDetails": "Permítelle á app aparecer por enrriba (non é preciso se xa configuraches FluffyChat como unha conta para chamadas)", + "appearOnTopDetails": "Permítelle á app aparecer por enriba (non é preciso se xa configuraches FluffyChat como unha conta para chamadas)", "@appearOnTopDetails": {}, "enterSpace": "Entrar no espazo", "@enterSpace": {}, @@ -2206,7 +2206,7 @@ "@enterRoom": {}, "allSpaces": "Todos os espazos", "@allSpaces": {}, - "screenSharingDetail": "Estás compartindo a túa pantalla en FluffyChat", + "screenSharingDetail": "Estás a compartir a túa pantalla en FluffyChat", "@screenSharingDetail": {}, "numChats": "{number} conversas", "@numChats": { @@ -2394,7 +2394,7 @@ "@pushNotificationsNotAvailable": {}, "makeAdminDescription": "Cando convirtas a esta usuaria en admin non poderás desfacer a acción xa que terá os mesmos permisos ca ti.", "@makeAdminDescription": {}, - "archiveRoomDescription": "Vaise mover o chat ao arquivo. Outras usuarias poderán ver que saíches da conversa.", + "archiveRoomDescription": "Vaise mover a charla ao arquivo. Outras usuarias poderán ver que saíches da conversa.", "@archiveRoomDescription": {}, "invalidInput": "Contido non válido!", "@invalidInput": {}, @@ -2413,7 +2413,7 @@ }, "learnMore": "Saber máis", "@learnMore": {}, - "roomUpgradeDescription": "Vaise recrear o chat coa nova versión da sala. Todas as participantes recibirán unha notificación para que cambien ao novo chat. Podes ler máis información acerca das versións das salas en https://spec.matrix.org/latest/rooms/", + "roomUpgradeDescription": "Vaise recrear a charla coa nova versión da sala. Todas as participantes recibirán unha notificación para que cambien á nova charla. Podes ler máis información acerca das versións das salas en https://spec.matrix.org/latest/rooms/", "@roomUpgradeDescription": {}, "pleaseEnterANumber": "Escribe un número maior de cero", "@pleaseEnterANumber": {}, @@ -2474,7 +2474,7 @@ "@select": {}, "pleaseChooseAStrongPassword": "Elixe un contrasinal forte", "@pleaseChooseAStrongPassword": {}, - "addChatOrSubSpace": "Engadir chat ou sub espazo", + "addChatOrSubSpace": "Engadir charla ou sub espazo", "@addChatOrSubSpace": {}, "leaveEmptyToClearStatus": "Deixa baleiro para limpar o teu estado.", "@leaveEmptyToClearStatus": {}, @@ -2609,7 +2609,7 @@ "@commandHint_ignore": {}, "commandHint_unignore": "Non ignorar o ID matrix indicado", "@commandHint_unignore": {}, - "unreadChatsInApp": "{appname}: {unread} chats sen ler", + "unreadChatsInApp": "{appname}: {unread} charlas sen ler", "@unreadChatsInApp": { "type": "text", "placeholders": { @@ -2621,7 +2621,7 @@ "@noDatabaseEncryption": {}, "accessAndVisibility": "Acceso e Visibilidade", "@accessAndVisibility": {}, - "accessAndVisibilityDescription": "Quen pode unirse a este chat de que xeito pode ser atopado.", + "accessAndVisibilityDescription": "Quen pode unirse a esta charla e de que xeito e como poden atopala.", "@accessAndVisibilityDescription": {}, "customEmojisAndStickers": "Emojis personais e adhesivos", "@customEmojisAndStickers": {}, @@ -2629,19 +2629,19 @@ "@calls": {}, "hideRedactedMessages": "Agochar mensaxes editadas", "@hideRedactedMessages": {}, - "hideRedactedMessagesBody": "Se alguén corrixe unha mensaxe entón esta xa non será visible no chat.", + "hideRedactedMessagesBody": "Se alguén corrixe unha mensaxe entón esta xa non será visible na charla.", "@hideRedactedMessagesBody": {}, "hideInvalidOrUnknownMessageFormats": "Agochar formatos de mensaxe non válidos ou descoñecidos", "@hideInvalidOrUnknownMessageFormats": {}, - "hideMemberChangesInPublicChats": "Agochar cambios dos membros nos chats públicos", + "hideMemberChangesInPublicChats": "Agochar cambios dos membros nas charlas públicas", "@hideMemberChangesInPublicChats": {}, "notifyMeFor": "Notificarme sobre", "@notifyMeFor": {}, - "hideMemberChangesInPublicChatsBody": "Non mostrar na cronoloxía se alguén se une ou deixa un chat público, para mellorar a lexibilidade.", + "hideMemberChangesInPublicChatsBody": "Non mostrar na cronoloxía se alguén se une ou deixa unha conversa pública, para mellorar a lexibilidade.", "@hideMemberChangesInPublicChatsBody": {}, "usersMustKnock": "As usuarias teñen que pedir entrar", "@usersMustKnock": {}, - "userWouldLikeToChangeTheChat": "{user} quere unirse ao chat.", + "userWouldLikeToChangeTheChat": "{user} quere unirse á charla.", "@userWouldLikeToChangeTheChat": { "placeholders": { "user": {} @@ -2649,7 +2649,7 @@ }, "knocking": "A solicitar", "@knocking": {}, - "chatCanBeDiscoveredViaSearchOnServer": "O chat pode ser atopado ao buscar en {server}", + "chatCanBeDiscoveredViaSearchOnServer": "A charla pode ser atopada ao buscar en {server}", "@chatCanBeDiscoveredViaSearchOnServer": { "type": "text", "placeholders": { @@ -2660,9 +2660,9 @@ "@createNewAddress": {}, "appLockDescription": "Bloquear a app cun código PIN cando non a uses", "@appLockDescription": {}, - "globalChatId": "ID Global do chat", + "globalChatId": "ID Global da charla", "@globalChatId": {}, - "customEmojisAndStickersBody": "Engade ou comparte emojis personais e adhesivos que poden usarse nos chats.", + "customEmojisAndStickersBody": "Engade ou comparte emojis personais e adhesivos que poden usarse nas charlas.", "@customEmojisAndStickersBody": {}, "overview": "Vista xeral", "@overview": {}, @@ -2679,7 +2679,7 @@ "type": "text", "count": {} }, - "publicChatAddresses": "Enderezos públicos do chat", + "publicChatAddresses": "Enderezos públicos da charla", "@publicChatAddresses": {}, "userRole": "Rol da usuaria", "@userRole": {}, @@ -2690,7 +2690,7 @@ "level": {} } }, - "searchIn": "Buscar no chat \"{chat}\"...", + "searchIn": "Buscar na charla \"{chat}\"...", "@searchIn": { "type": "text", "placeholders": { @@ -2713,11 +2713,11 @@ "@alwaysUse24HourFormat": { "description": "Set to true to always display time of day in 24 hour format." }, - "noMoreChatsFound": "Non se atopan máis chats…", + "noMoreChatsFound": "Non se atopan máis charlas…", "@noMoreChatsFound": {}, - "joinedChats": "Chats nos que participas", + "joinedChats": "Charlas nas que participas", "@joinedChats": {}, - "countChatsAndCountParticipants": "{chats} chats e {participants} participantes", + "countChatsAndCountParticipants": "{chats} charlas e {participants} participantes", "@countChatsAndCountParticipants": { "type": "text", "placeholders": { @@ -2759,19 +2759,19 @@ "level": {} } }, - "changeGeneralChatSettings": "Cambiar os axustes xerais do chat", + "changeGeneralChatSettings": "Cambiar os axustes xerais da charla", "@changeGeneralChatSettings": {}, - "inviteOtherUsers": "Convidar a outras usuarias a este chat", + "inviteOtherUsers": "Convidar a outras usuarias a esta charla", "@inviteOtherUsers": {}, - "changeTheChatPermissions": "Cambiar os permisos no chat", + "changeTheChatPermissions": "Cambiar os permisos na charla", "@changeTheChatPermissions": {}, - "changeTheVisibilityOfChatHistory": "Cambiar a visibilidade do historial do chat", + "changeTheVisibilityOfChatHistory": "Cambiar a visibilidade do historial da charla", "@changeTheVisibilityOfChatHistory": {}, - "changeTheCanonicalRoomAlias": "Cambiar o enderezo público principal do chat", + "changeTheCanonicalRoomAlias": "Cambiar o enderezo público principal da charla", "@changeTheCanonicalRoomAlias": {}, "sendRoomNotifications": "Enviar notificacións a @room", "@sendRoomNotifications": {}, - "changeTheDescriptionOfTheGroup": "Cambiar a descrición do chat", + "changeTheDescriptionOfTheGroup": "Cambiar a descrición da charla", "@changeTheDescriptionOfTheGroup": {}, "invitedBy": "📩 Convidada por {user}", "@invitedBy": { @@ -2781,7 +2781,7 @@ }, "changelog": "Novidades na versión", "@changelog": {}, - "chatPermissionsDescription": "Define que nivel de permisos son necesarios para realizar certas accións neste chat. Os niveis de permiso 0, 50 e 100 normalmente representan, usuarias, moderadoras e administradoras, pero son posibles outras escalas.", + "chatPermissionsDescription": "Define que nivel de permisos son necesarios para realizar certas accións nesta charla. Os niveis de permiso 0, 50 e 100 normalmente representan, usuarias, moderadoras e administradoras, pero son posibles outras escalas.", "@chatPermissionsDescription": {}, "updateInstalled": "🎉 Instalouse a actualización a {version}!", "@updateInstalled": { @@ -2792,7 +2792,7 @@ }, "sendCanceled": "Cancelouse o envío", "@sendCanceled": {}, - "noChatsFoundHere": "Ningún chat por aquí. Comeza unha nova conversa con alguén premendo no botón de abaixo. ⤵️", + "noChatsFoundHere": "Sen charlas por aquí. Comeza unha nova conversa con alguén premendo no botón de abaixo. ⤵️", "@noChatsFoundHere": {}, "discoverHomeservers": "Atopar servidores", "@discoverHomeservers": {}, @@ -2801,5 +2801,7 @@ "loginWithMatrixId": "Acceder co ID-Matrix", "@loginWithMatrixId": {}, "homeserverDescription": "Todos os teus datos quedan gardados no servidor de inicio, igual que co teu provedor de correo electrónico. Podes elexir o servidor que queres usar e poderás comunicarte con todos os demais. Aprende máis en https://matrix.org.", - "@homeserverDescription": {} + "@homeserverDescription": {}, + "doesNotSeemToBeAValidHomeserver": "Non semella ser un servidor de inicio compatible. É o URL correcto?", + "@doesNotSeemToBeAValidHomeserver": {} } From bbecaf4016d4af5d74d4874cfc4436ca60b97ccf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jos=C3=A9=20m?= Date: Fri, 30 Aug 2024 03:06:53 +0000 Subject: [PATCH 255/288] Translated using Weblate (Galician) Currently translated at 100.0% (663 of 663 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/gl/ --- assets/l10n/intl_gl.arb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/l10n/intl_gl.arb b/assets/l10n/intl_gl.arb index a94ac1fda1..55c4f87902 100644 --- a/assets/l10n/intl_gl.arb +++ b/assets/l10n/intl_gl.arb @@ -2141,7 +2141,7 @@ }, "unlockOldMessages": "Desbloquear mensaxes antigas", "@unlockOldMessages": {}, - "dehydrateTorLong": "Para usuarias de TOR, é recomendable exportar a sesión antes de pechar a ventál.", + "dehydrateTorLong": "Para usuarias de TOR, é recomendable exportar a sesión antes de pechar a xanela.", "@dehydrateTorLong": {}, "hydrateTor": "Usuarias TOR: Importar a sesión exportada", "@hydrateTor": {}, @@ -2530,7 +2530,7 @@ "@verifyOtherDevice": {}, "verifyOtherUser": "🔐 Verificar outra usuaria", "@verifyOtherUser": {}, - "verifyOtherDeviceDescription": "Ao verificar outro dispositivo estás compartindo as chaves, aumentando a túa seguridade 💪. Ao iniciar a verificación aparecerá unha ventá emerxente nos dous dispositivos. Nesa ventá verás varios emojis ou números que tes que comparar entre eles. O mellor xeito de facelo é ter os dous dispositivos contigo cando inicias o proceso de verificación. 🤳", + "verifyOtherDeviceDescription": "Ao verificar outro dispositivo estás compartindo as chaves, aumentando a túa seguridade 💪. Ao iniciar a verificación aparecerá unha xanela emerxente nos dous dispositivos. Nesa xanela verás varios emojis ou números que tes que comparar entre eles. O mellor xeito de facelo é ter os dous dispositivos contigo cando inicias o proceso de verificación. 🤳", "@verifyOtherDeviceDescription": {}, "canceledKeyVerification": "{sender} desbotou a verificación da chave", "@canceledKeyVerification": { @@ -2557,7 +2557,7 @@ "@sendTypingNotificationsDescription": {}, "formattedMessagesDescription": "Mostrar texto enriquecido nas mensaxes como letra grosa usando markdown.", "@formattedMessagesDescription": {}, - "verifyOtherUserDescription": "Se verificas a outra usuaria, podes ter a certeza de que sabes con quen estás a conversar. 💪\n\nAo iniciar a verificación, ti mais a outra usuaria veredes unha ventá emerxente na app onde aparecerán varios emojis ou números que teredes que comparar entre vós.\n\nO mellor xeito de facelo é en persoa o cunha chamada de vídeo. 👭", + "verifyOtherUserDescription": "Se verificas a outra usuaria, podes ter a certeza de que sabes con quen estás a conversar. 💪\n\nAo iniciar a verificación, ti mais a outra usuaria veredes unha xanela emerxente na app onde aparecerán varios emojis ou números que teredes que comparar entre vós.\n\nO mellor xeito de facelo é en persoa o cunha chamada de vídeo. 👭", "@verifyOtherUserDescription": {}, "requestedKeyVerification": "{sender} solicitou verificar a chave", "@requestedKeyVerification": { From 14a8e1a09c983784eb46c3ac63217e79b0f85741 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Fri, 30 Aug 2024 19:11:18 +0000 Subject: [PATCH 256/288] Translated using Weblate (Ukrainian) Currently translated at 100.0% (663 of 663 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/uk/ --- assets/l10n/intl_uk.arb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_uk.arb b/assets/l10n/intl_uk.arb index 07c7131474..519ce5cde1 100644 --- a/assets/l10n/intl_uk.arb +++ b/assets/l10n/intl_uk.arb @@ -2793,5 +2793,15 @@ "sendCanceled": "Надсилання скасовано", "@sendCanceled": {}, "noChatsFoundHere": "Бесід ще немає. Розпочніть спілкування натиснувши кнопку нижче. ⤵️", - "@noChatsFoundHere": {} + "@noChatsFoundHere": {}, + "loginWithMatrixId": "Увійти за допомогою Matrix-ID", + "@loginWithMatrixId": {}, + "discoverHomeservers": "Знайти домашні сервери", + "@discoverHomeservers": {}, + "whatIsAHomeserver": "Що таке домашній сервер?", + "@whatIsAHomeserver": {}, + "homeserverDescription": "Усі ваші дані зберігаються на домашньому сервері, так само як у постачальника послуг електронної пошти. Ви можете вибрати, який домашній сервер ви хочете використовувати, водночас ви можете спілкуватися з усіма. Докладніше на https://matrix.org.", + "@homeserverDescription": {}, + "doesNotSeemToBeAValidHomeserver": "Здається, це несумісний домашній сервер. Неправильна URL-адреса?", + "@doesNotSeemToBeAValidHomeserver": {} } From 3ce4df84251434b2ebc53ff41e44e410670872bb Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sun, 25 Aug 2024 08:50:28 +0200 Subject: [PATCH 257/288] build: Fix build linux after flutter 3.24.1 and add handy_window --- linux/flutter/generated_plugin_registrant.cc | 4 ++ linux/flutter/generated_plugins.cmake | 1 + linux/my_application.cc | 4 +- pubspec.lock | 44 ++++++++++++-------- pubspec.yaml | 1 + snap/snapcraft.yaml | 43 ++++--------------- 6 files changed, 43 insertions(+), 54 deletions(-) diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 18e739cdde..b5155de259 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) flutter_webrtc_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterWebRTCPlugin"); flutter_web_r_t_c_plugin_register_with_registrar(flutter_webrtc_registrar); + g_autoptr(FlPluginRegistrar) handy_window_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "HandyWindowPlugin"); + handy_window_plugin_register_with_registrar(handy_window_registrar); g_autoptr(FlPluginRegistrar) pasteboard_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "PasteboardPlugin"); pasteboard_plugin_register_with_registrar(pasteboard_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index b4a43e1b99..dab6fedfc5 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -9,6 +9,7 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_linux flutter_secure_storage_linux flutter_webrtc + handy_window pasteboard record_linux sqlcipher_flutter_libs diff --git a/linux/my_application.cc b/linux/my_application.cc index c185bcd785..7bddef81c0 100644 --- a/linux/my_application.cc +++ b/linux/my_application.cc @@ -61,17 +61,17 @@ static void my_application_activate(GApplication* application) { } gtk_window_set_default_size(window, 864, 680); - gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); FlView* view = fl_view_new(project); - gtk_widget_show(GTK_WIDGET(view)); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + gtk_widget_show(GTK_WIDGET(window)); + gtk_widget_show(GTK_WIDGET(view)); gtk_widget_grab_focus(GTK_WIDGET(view)); } diff --git a/pubspec.lock b/pubspec.lock index 89313580fb..348f73d541 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -848,6 +848,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + handy_window: + dependency: "direct main" + description: + name: handy_window + sha256: "56b813e58a68b0ee2ab22051400b8b1f1b5cfe88b8cd32288623defb3926245a" + url: "https://pub.dev" + source: hosted + version: "0.4.0" highlighter: dependency: transitive description: @@ -1082,18 +1090,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -1194,10 +1202,10 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" matrix: dependency: "direct main" description: @@ -1210,10 +1218,10 @@ packages: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mgrs_dart: dependency: transitive description: @@ -1442,10 +1450,10 @@ packages: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" platform_detect: dependency: transitive description: @@ -1967,26 +1975,26 @@ packages: dependency: transitive description: name: test - sha256: "7ee446762c2c50b3bd4ea96fe13ffac69919352bd3b4b17bac3f3465edc58073" + sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e" url: "https://pub.dev" source: hosted - version: "1.25.2" + version: "1.25.7" test_api: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" test_core: dependency: transitive description: name: test_core - sha256: "2bc4b4ecddd75309300d8096f781c0e3280ca1ef85beda558d33fcbedc2eead4" + sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.4" timezone: dependency: transitive description: @@ -2279,10 +2287,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.5" wakelock_plus: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index ea6feab425..cee3967501 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -52,6 +52,7 @@ dependencies: future_loading_dialog: ^0.3.0 geolocator: ^7.6.2 go_router: ^14.0.1 + handy_window: ^0.4.0 hive: ^2.2.3 hive_flutter: ^1.1.0 html: ^0.15.4 diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index a32ad5704c..63dfbb87da 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: fluffychat title: FluffyChat -base: core22 +base: core24 version: git license: AGPL-3.0 summary: The cutest messenger in the Matrix network @@ -44,9 +44,11 @@ description: | grade: stable confinement: strict -architectures: - - build-on: amd64 - - build-on: arm64 +platforms: + amd64: + build-on: amd64 + arm64: + build-on: arm64 parts: olm: @@ -64,40 +66,13 @@ parts: stage-snaps: - zenity-integration - flutter-git: - source: https://github.com/flutter/flutter.git - source-tag: 3.19.6 - source-depth: 1 - plugin: nil - override-build: | - mkdir -p $CRAFT_PART_INSTALL/usr/bin - mkdir -p $CRAFT_PART_INSTALL/usr/libexec - cp -r $CRAFT_PART_SRC $CRAFT_PART_INSTALL/usr/libexec/flutter - ln -s $CRAFT_PART_INSTALL/usr/libexec/flutter/bin/flutter $CRAFT_PART_INSTALL/usr/bin/flutter - ln -s $SNAPCRAFT_PART_INSTALL/usr/libexec/flutter/bin/dart $SNAPCRAFT_PART_INSTALL/usr/bin/dart - $CRAFT_PART_INSTALL/usr/bin/flutter doctor - build-packages: - - clang - - cmake - - curl - - libgtk-3-dev - - ninja-build - - unzip - - xz-utils - - zip - override-prime: '' - fluffychat: - after: [flutter-git] - plugin: nil + plugin: flutter source: . override-build: | # Workaround for Flutter build error: rm -rf build - - flutter build linux --release -v - mkdir -p $CRAFT_PART_INSTALL/bin/ - cp -r build/linux/*/release/bundle/* $CRAFT_PART_INSTALL/bin/ + craftctl default build-packages: - libjsoncpp-dev - curl @@ -114,7 +89,7 @@ slots: apps: fluffychat: - command: bin/fluffychat + command: fluffychat extensions: [ gnome ] plugs: - audio-playback From ca33a2dbdab8b5c1385c4a82fbbc2c64a72f3f13 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sat, 31 Aug 2024 15:00:33 +0200 Subject: [PATCH 258/288] build: Prepare flutter 3.24 --- pubspec.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.lock b/pubspec.lock index 348f73d541..baf63d620a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2290,7 +2290,7 @@ packages: sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "14.2.4" wakelock_plus: dependency: "direct main" description: From bff1f8c696e002f400f673a051da3f983c21d6c3 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sat, 31 Aug 2024 15:01:09 +0200 Subject: [PATCH 259/288] build: follow up vm service --- pubspec.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.lock b/pubspec.lock index baf63d620a..36ca6b556d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2287,7 +2287,7 @@ packages: dependency: transitive description: name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + sha256: "f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc" url: "https://pub.dev" source: hosted version: "14.2.4" From 3e06537d1b6fbdf31e5603d13f3fe7489676f74e Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sat, 31 Aug 2024 15:01:57 +0200 Subject: [PATCH 260/288] chore: Follow up vm service --- pubspec.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.lock b/pubspec.lock index 36ca6b556d..33068a8bf9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2287,7 +2287,7 @@ packages: dependency: transitive description: name: vm_service - sha256: "f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc" + sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc url: "https://pub.dev" source: hosted version: "14.2.4" From d70d8803c27d328d8e600b8991c3fd255ab6245d Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sun, 1 Sep 2024 09:16:37 +0200 Subject: [PATCH 261/288] build: Build linux with Flutter 3.24.1 --- .github/workflows/release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 1304de6307..204d1e0e04 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -110,7 +110,7 @@ jobs: - run: cat .github/workflows/versions.env >> $GITHUB_ENV - uses: subosito/flutter-action@v2 with: - flutter-version: 3.19.6 # Workaround for not working on 3.22 + flutter-version: ${{ env.FLUTTER_VERSION }} cache: true - name: Install dependencies run: sudo apt-get update && sudo apt-get install curl clang cmake ninja-build pkg-config libgtk-3-dev libblkid-dev liblzma-dev libjsoncpp-dev cmake-data libsecret-1-dev libsecret-1-0 librhash0 libssl-dev -y From eabd5b10aeec48709ef14eb1f3bab6671788e6e6 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 4 Sep 2024 16:06:57 -0400 Subject: [PATCH 262/288] re-enable recording on web --- lib/utils/platform_infos.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/utils/platform_infos.dart b/lib/utils/platform_infos.dart index b01498c888..83d4c9e745 100644 --- a/lib/utils/platform_infos.dart +++ b/lib/utils/platform_infos.dart @@ -29,7 +29,10 @@ abstract class PlatformInfos { static bool get usesTouchscreen => !isMobile; /// Web could also record in theory but currently only wav which is too large - static bool get platformCanRecord => (isMobile || isMacOS); + /// #Pangea + // static bool get platformCanRecord => (isMobile || isMacOS); + static bool get platformCanRecord => (isMobile || isMacOS || isWeb); + // Pangea# static String get clientName => '${AppConfig.applicationName} ${isWeb ? 'web' : Platform.operatingSystem}${kReleaseMode ? '' : 'Debug'}'; From 85b501e180ba0e51ef7a15e8721c3c3c51492e21 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 5 Sep 2024 10:11:09 -0400 Subject: [PATCH 263/288] re-enabled code related to recent updates to matrix SDK --- .../settings_emotes/settings_emotes.dart | 76 ++++++++++--------- .../client_download_content_extension.dart | 68 ++++++++--------- .../local_notifications_extension.dart | 17 ++--- 3 files changed, 80 insertions(+), 81 deletions(-) diff --git a/lib/pages/settings_emotes/settings_emotes.dart b/lib/pages/settings_emotes/settings_emotes.dart index 5f7ec49865..b90432f566 100644 --- a/lib/pages/settings_emotes/settings_emotes.dart +++ b/lib/pages/settings_emotes/settings_emotes.dart @@ -6,11 +6,14 @@ import 'package:archive/archive.dart' import 'package:collection/collection.dart'; import 'package:file_picker/file_picker.dart'; import 'package:fluffychat/utils/client_manager.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_file_extension.dart'; import 'package:fluffychat/widgets/app_lock.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:go_router/go_router.dart'; +import 'package:http/http.dart' hide Client; import 'package:matrix/matrix.dart'; import '../../widgets/matrix.dart'; @@ -315,42 +318,41 @@ class EmotesSettingsController extends State { } Future exportAsZip() async { - // TODO update matrix SDK to support this - // final client = Matrix.of(context).client; - - // await showFutureLoadingDialog( - // context: context, - // future: () async { - // final pack = _getPack(); - // final archive = Archive(); - // for (final entry in pack.images.entries) { - // final emote = entry.value; - // final name = entry.key; - // final url = await emote.url.getDownloadUri(client); - // final response = await get( - // url, - // headers: {'authorization': 'Bearer ${client.accessToken}'}, - // ); - - // archive.addFile( - // ArchiveFile( - // name, - // response.bodyBytes.length, - // response.bodyBytes, - // ), - // ); - // } - // final fileName = - // '${pack.pack.displayName ?? client.userID?.localpart ?? 'emotes'}.zip'; - // final output = ZipEncoder().encode(archive); - - // if (output == null) return; - - // MatrixFile( - // name: fileName, - // bytes: Uint8List.fromList(output), - // ).save(context); - // }, - // ); + final client = Matrix.of(context).client; + + await showFutureLoadingDialog( + context: context, + future: () async { + final pack = _getPack(); + final archive = Archive(); + for (final entry in pack.images.entries) { + final emote = entry.value; + final name = entry.key; + final url = await emote.url.getDownloadUri(client); + final response = await get( + url, + headers: {'authorization': 'Bearer ${client.accessToken}'}, + ); + + archive.addFile( + ArchiveFile( + name, + response.bodyBytes.length, + response.bodyBytes, + ), + ); + } + final fileName = + '${pack.pack.displayName ?? client.userID?.localpart ?? 'emotes'}.zip'; + final output = ZipEncoder().encode(archive); + + if (output == null) return; + + MatrixFile( + name: fileName, + bytes: Uint8List.fromList(output), + ).save(context); + }, + ); } } diff --git a/lib/utils/client_download_content_extension.dart b/lib/utils/client_download_content_extension.dart index ba7741e60a..9fff1eccd2 100644 --- a/lib/utils/client_download_content_extension.dart +++ b/lib/utils/client_download_content_extension.dart @@ -11,45 +11,43 @@ extension ClientDownloadContentExtension on Client { bool? animated, ThumbnailMethod? thumbnailMethod, }) async { - // // To stay compatible with previous storeKeys: - // final cacheKey = isThumbnail - // // ignore: deprecated_member_use - // ? mxc.getThumbnail( - // this, - // width: width, - // height: height, - // animated: animated, - // method: thumbnailMethod!, - // ) - // : mxc; + // To stay compatible with previous storeKeys: + final cacheKey = isThumbnail + // ignore: deprecated_member_use + ? mxc.getThumbnail( + this, + width: width, + height: height, + animated: animated, + method: thumbnailMethod!, + ) + : mxc; - // final cachedData = await database?.getFile(cacheKey); - // if (cachedData != null) return cachedData; + final cachedData = await database?.getFile(cacheKey); + if (cachedData != null) return cachedData; - // final httpUri = isThumbnail - // ? await mxc.getThumbnailUri( - // this, - // width: width, - // height: height, - // animated: animated, - // method: thumbnailMethod, - // ) - // : await mxc.getDownloadUri(this); + final httpUri = isThumbnail + ? await mxc.getThumbnailUri( + this, + width: width, + height: height, + animated: animated, + method: thumbnailMethod, + ) + : await mxc.getDownloadUri(this); - // final response = await httpClient.get( - // httpUri, - // headers: - // accessToken == null ? null : {'authorization': 'Bearer $accessToken'}, - // ); - // if (response.statusCode != 200) { - // throw Exception(); - // } - // final remoteData = response.bodyBytes; + final response = await httpClient.get( + httpUri, + headers: + accessToken == null ? null : {'authorization': 'Bearer $accessToken'}, + ); + if (response.statusCode != 200) { + throw Exception(); + } + final remoteData = response.bodyBytes; - // await database?.storeFile(cacheKey, remoteData, 0); + await database?.storeFile(cacheKey, remoteData, 0); - // return remoteData; - // TODO update matrix SDK to support this - return Uint8List.fromList([]); + return remoteData; } } diff --git a/lib/widgets/local_notifications_extension.dart b/lib/widgets/local_notifications_extension.dart index eeca03bde3..0953c1debf 100644 --- a/lib/widgets/local_notifications_extension.dart +++ b/lib/widgets/local_notifications_extension.dart @@ -62,14 +62,13 @@ extension LocalNotificationsExtension on MatrixState { isThumbnail: true, ); - // TODO replace after upgrading matrix SDK - // thumbnailUri = - // await event.senderFromMemoryOrFallback.avatarUrl?.getThumbnailUri( - // client, - // width: size, - // height: size, - // method: thumbnailMethod, - // ); + thumbnailUri = + await event.senderFromMemoryOrFallback.avatarUrl?.getThumbnailUri( + client, + width: size, + height: size, + method: thumbnailMethod, + ); } _audioPlayer.play(); @@ -77,7 +76,7 @@ extension LocalNotificationsExtension on MatrixState { html.Notification( title, body: body, - // icon: thumbnailUri?.toString(), + icon: thumbnailUri?.toString(), tag: event.room.id, ); } else if (Platform.isLinux) { From 9f9ea4786a3d3d734aefd823a6e3167cd2b72ba1 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 5 Sep 2024 10:14:03 -0400 Subject: [PATCH 264/288] replaced app ID in build.gradle --- android/app/build.gradle | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 42eda48e9c..7362248d26 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -41,7 +41,10 @@ android { } defaultConfig { - applicationId "chat.fluffy.fluffychat" + // #Pangea + // applicationId "chat.fluffy.fluffychat" + applicationId "com.talktolearn.chat" + // Pangea# minSdkVersion 21 targetSdkVersion 34 versionCode flutterVersionCode.toInteger() From 4a252729ffb238d0cb0e0d848ea99bc925fe2dda Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 5 Sep 2024 13:06:22 -0400 Subject: [PATCH 265/288] updated space view to auto-reload on space child update --- analysis_options.yaml | 1 - lib/pages/chat_list/chat_list_body.dart | 3 + lib/pages/chat_list/space_view.dart | 292 +++++++++++++++++++++--- 3 files changed, 269 insertions(+), 27 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index d74b363557..34a01078b4 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -9,7 +9,6 @@ linter: - prefer_final_in_for_each - sort_pub_dependencies - require_trailing_commas - - omit_local_variable_types analyzer: errors: diff --git a/lib/pages/chat_list/chat_list_body.dart b/lib/pages/chat_list/chat_list_body.dart index 1b46ec28dc..19e46b9aec 100644 --- a/lib/pages/chat_list/chat_list_body.dart +++ b/lib/pages/chat_list/chat_list_body.dart @@ -40,6 +40,9 @@ class ChatListViewBody extends StatelessWidget { controller.chatContextAction(room, context), activeChat: controller.activeChat, toParentSpace: controller.setActiveSpace, + // #Pangea + controller: controller, + // Pangea# ); } final spaces = client.rooms.where((r) => r.isSpace); diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index b6dd069424..3a36935763 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -1,8 +1,13 @@ +import 'dart:async'; + import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:collection/collection.dart'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pages/chat_list/chat_list.dart'; import 'package:fluffychat/pages/chat_list/chat_list_item.dart'; import 'package:fluffychat/pages/chat_list/search_title.dart'; +import 'package:fluffychat/pangea/constants/pangea_room_types.dart'; +import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/stream_extension.dart'; @@ -25,6 +30,9 @@ class SpaceView extends StatefulWidget { final void Function(Room room) onChatTab; final void Function(Room room, BuildContext context) onChatContext; final String? activeChat; + // #Pangea + final ChatListController controller; + // Pangea# const SpaceView({ required this.spaceId, @@ -33,6 +41,9 @@ class SpaceView extends StatefulWidget { required this.activeChat, required this.toParentSpace, required this.onChatContext, + // #Pangea + required this.controller, + // Pangea# super.key, }); @@ -41,7 +52,11 @@ class SpaceView extends StatefulWidget { } class _SpaceViewState extends State { - final List _discoveredChildren = []; + // #Pangea + // final List _discoveredChildren = []; + List? _discoveredChildren; + StreamSubscription? _roomSubscription; + // Pangea# final TextEditingController _filterController = TextEditingController(); String? _nextBatch; bool _noMoreRooms = false; @@ -49,11 +64,63 @@ class _SpaceViewState extends State { @override void initState() { - _loadHierarchy(); + // #Pangea + // loadHierarchy(); + + // If, on launch, this room has had updates to its children, + // ensure the hierarchy is properly reloaded + final bool hasUpdate = widget.controller.hasUpdates.contains( + widget.spaceId, + ); + + loadHierarchy(hasUpdate: hasUpdate).then( + // remove this space ID from the set of space IDs with updates + (_) => widget.controller.hasUpdates.remove( + widget.controller.activeSpaceId, + ), + ); + + // Listen for changes to the activeSpace's hierarchy, + // and reload the hierarchy when they come through + final client = Matrix.of(context).client; + _roomSubscription ??= client.onSync.stream + .where(hasHierarchyUpdate) + .listen((update) => loadHierarchy(hasUpdate: true)); + // Pangea# super.initState(); } - void _loadHierarchy() async { + // #Pangea + @override + void didUpdateWidget(covariant SpaceView oldWidget) { + // initState doesn't re-run when navigating between spaces + // via the navigation rail, so this accounts for that + super.didUpdateWidget(oldWidget); + if (oldWidget.spaceId != widget.spaceId) { + _discoveredChildren = null; + _nextBatch = null; + _noMoreRooms = false; + + loadHierarchy(hasUpdate: true).then( + // remove this space ID from the set of space IDs with updates + (_) { + if (widget.controller.hasUpdates.contains(widget.spaceId)) { + widget.controller.hasUpdates.remove( + widget.controller.activeSpaceId, + ); + } + }); + } + } + + @override + void dispose() { + _roomSubscription?.cancel(); + super.dispose(); + } + + Future loadHierarchy({hasUpdate = false}) async { + debugPrint("loading hierarchy. hasUpdate: $hasUpdate"); final room = Matrix.of(context).client.getRoomById(widget.spaceId); if (room == null) return; @@ -62,35 +129,129 @@ class _SpaceViewState extends State { }); try { - final hierarchy = await room.client.getSpaceHierarchy( - widget.spaceId, - suggestedOnly: false, - maxDepth: 2, - from: _nextBatch, - ); - if (!mounted) return; - setState(() { - _nextBatch = hierarchy.nextBatch; - if (hierarchy.nextBatch == null) { - _noMoreRooms = true; - } - _discoveredChildren.addAll( - hierarchy.rooms - .where((c) => room.client.getRoomById(c.roomId) == null), - ); - _isLoading = false; - }); + await _loadHierarchy(activeSpace: room, hasUpdate: hasUpdate); } catch (e, s) { Logs().w('Unable to load hierarchy', e, s); if (!mounted) return; ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text(e.toLocalizedString(context)))); + } finally { setState(() { _isLoading = false; }); } } + /// Internal logic of loadHierarchy. It will load the hierarchy of + /// the active space id (or specified spaceId). + Future _loadHierarchy({ + required Room activeSpace, + bool hasUpdate = false, + }) async { + // Load all of the space's state events. Space Child events + // are used to filtering out unsuggested, unjoined rooms. + await activeSpace.postLoad(); + + // The current number of rooms loaded for this space that are visible in the UI + final int prevLength = !hasUpdate ? (_discoveredChildren?.length ?? 0) : 0; + + // Failsafe to prevent too many calls to the server in a row + int callsToServer = 0; + + List? currentHierarchy = + _discoveredChildren == null || hasUpdate + ? null + : List.from(_discoveredChildren!); + String? currentNextBatch = hasUpdate ? null : _nextBatch; + + // Makes repeated calls to the server until 10 new visible rooms have + // been loaded, or there are no rooms left to load. Using a loop here, + // rather than one single call to the endpoint, because some spaces have + // so many invisible rooms (analytics rooms) that it might look like + // pressing the 'load more' button does nothing (Because the only rooms + // coming through from those calls are analytics rooms). + while (callsToServer < 5) { + // if this space has been loaded and there are no more rooms to load, break + if (currentHierarchy != null && currentNextBatch == null) { + break; + } + + // if this space has been loaded and 10 new rooms have been loaded, break + final int currentLength = currentHierarchy?.length ?? 0; + if (currentLength - prevLength >= 10) { + break; + } + + // make the call to the server + final response = await Matrix.of(context).client.getSpaceHierarchy( + widget.spaceId, + maxDepth: 1, + from: currentNextBatch, + limit: 100, + ); + callsToServer++; + + if (response.nextBatch == null) { + _noMoreRooms = true; + } + + // if rooms have earlier been loaded for this space, add those + // previously loaded rooms to the front of the response list + response.rooms.insertAll( + 0, + currentHierarchy ?? [], + ); + + // finally, set the response to the last response for this space + // and set the current next batch token + currentHierarchy = filterHierarchyResponse(activeSpace, response.rooms); + currentNextBatch = response.nextBatch; + } + + _discoveredChildren = currentHierarchy; + _discoveredChildren?.sort(sortSpaceChildren); + _nextBatch = currentNextBatch; + } + + // void _loadHierarchy() async { + // final room = Matrix.of(context).client.getRoomById(widget.spaceId); + // if (room == null) return; + + // setState(() { + // _isLoading = true; + // }); + + // try { + // final hierarchy = await room.client.getSpaceHierarchy( + // widget.spaceId, + // suggestedOnly: false, + // maxDepth: 2, + // from: _nextBatch, + // ); + // if (!mounted) return; + // setState(() { + // _nextBatch = hierarchy.nextBatch; + // if (hierarchy.nextBatch == null) { + // _noMoreRooms = true; + // } + // _discoveredChildren.addAll( + // hierarchy.rooms + // .where((c) => room.client.getRoomById(c.roomId) == null), + // ); + // _isLoading = false; + // }); + // } catch (e, s) { + // Logs().w('Unable to load hierarchy', e, s); + // if (!mounted) return; + // ScaffoldMessenger.of(context) + // .showSnackBar(SnackBar(content: Text(e.toLocalizedString(context)))); + // setState(() { + // _isLoading = false; + // }); + // } + // } + // Pangea# + void _joinChildRoom(SpaceRoomsChunk item) async { final client = Matrix.of(context).client; final space = client.getRoomById(widget.spaceId); @@ -109,7 +270,7 @@ class _SpaceViewState extends State { ); if (mounted && joined == true) { setState(() { - _discoveredChildren.remove(item); + _discoveredChildren?.remove(item); }); } } @@ -237,6 +398,81 @@ class _SpaceViewState extends State { if (result.error != null) return; } + // #Pangea + bool includeSpaceChild( + Room space, + SpaceRoomsChunk hierarchyMember, + ) { + if (!mounted) return false; + final bool isAnalyticsRoom = + hierarchyMember.roomType == PangeaRoomTypes.analytics; + + final bool isMember = [Membership.join, Membership.invite].contains( + Matrix.of(context).client.getRoomById(hierarchyMember.roomId)?.membership, + ); + + final bool isSuggested = + space.spaceChildSuggestionStatus[hierarchyMember.roomId] ?? true; + + return !isAnalyticsRoom && (isMember || isSuggested); + } + + List filterHierarchyResponse( + Room space, + List hierarchyResponse, + ) { + final List filteredChildren = []; + for (final child in hierarchyResponse) { + if (child.roomId == widget.spaceId || + Matrix.of(context).client.getRoomById(child.roomId) != null) { + continue; + } + + final isDuplicate = filteredChildren.any( + (filtered) => filtered.roomId == child.roomId, + ); + if (isDuplicate) continue; + + if (includeSpaceChild(space, child)) { + filteredChildren.add(child); + } + } + return filteredChildren; + } + + /// Used to filter out sync updates with hierarchy updates for the active + /// space so that the view can be auto-reloaded in the room subscription + bool hasHierarchyUpdate(SyncUpdate update) { + final joinTimeline = update.rooms?.join?[widget.spaceId]?.timeline; + final leaveTimeline = update.rooms?.leave?[widget.spaceId]?.timeline; + if (joinTimeline == null && leaveTimeline == null) return false; + final bool hasJoinUpdate = joinTimeline?.events?.any( + (event) => event.type == EventTypes.SpaceChild, + ) ?? + false; + final bool hasLeaveUpdate = leaveTimeline?.events?.any( + (event) => event.type == EventTypes.SpaceChild, + ) ?? + false; + return hasJoinUpdate || hasLeaveUpdate; + } + + int sortSpaceChildren( + SpaceRoomsChunk a, + SpaceRoomsChunk b, + ) { + final bool aIsSpace = a.roomType == 'm.space'; + final bool bIsSpace = b.roomType == 'm.space'; + + if (aIsSpace && !bIsSpace) { + return -1; + } else if (!aIsSpace && bIsSpace) { + return 1; + } + return 0; + } + // Pangea# + @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -328,6 +564,7 @@ class _SpaceViewState extends State { .where((s) => s.hasRoomUpdate) .rateLimit(const Duration(seconds: 1)), builder: (context, snapshot) { + debugPrint("build on room update"); final childrenIds = room.spaceChildren .map((c) => c.roomId) .whereType() @@ -335,6 +572,9 @@ class _SpaceViewState extends State { final joinedRooms = room.client.rooms .where((room) => childrenIds.remove(room.id)) + // #Pangea + .where((room) => !room.isAnalyticsRoom) + // Pangea# .toList(); final joinedParents = room.spaceParents @@ -480,7 +720,7 @@ class _SpaceViewState extends State { }, ), SliverList.builder( - itemCount: _discoveredChildren.length + 2, + itemCount: (_discoveredChildren?.length ?? 0) + 2, itemBuilder: (context, i) { if (i == 0) { return SearchTitle( @@ -489,7 +729,7 @@ class _SpaceViewState extends State { ); } i--; - if (i == _discoveredChildren.length) { + if (i == (_discoveredChildren?.length ?? 0)) { if (_noMoreRooms) { return Padding( padding: const EdgeInsets.all(12.0), @@ -507,7 +747,7 @@ class _SpaceViewState extends State { vertical: 2.0, ), child: TextButton( - onPressed: _isLoading ? null : _loadHierarchy, + onPressed: _isLoading ? null : loadHierarchy, child: _isLoading ? LinearProgressIndicator( borderRadius: BorderRadius.circular( @@ -518,7 +758,7 @@ class _SpaceViewState extends State { ), ); } - final item = _discoveredChildren[i]; + final item = _discoveredChildren![i]; final displayname = item.name ?? item.canonicalAlias ?? L10n.of(context)!.emptyChat; From 54618133526895160eda83f8bf9e03d403975f0d Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 5 Sep 2024 13:14:19 -0400 Subject: [PATCH 266/288] when navigating to space, go to the space details page --- lib/pages/chat_list/chat_list.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 2248abb90c..602821505c 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -118,6 +118,10 @@ class ChatListController extends State setState(() { _activeSpaceId = spaceId; }); + + // #Pangea + context.go('/rooms/$spaceId/details'); + // Pangea# } void clearActiveSpace() => setState(() { From 6b730e6d944d09fde6623d4e598afc62f28a777d Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 5 Sep 2024 14:27:37 -0400 Subject: [PATCH 267/288] revert change to iOS minimum deployment version, as sentry package requires a higher version --- ios/Podfile | 2 +- ios/Runner.xcodeproj/project.pbxproj | 6 +++--- ios/Runner/AppDelegate.swift | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ios/Podfile b/ios/Podfile index 1f9db6f6e3..fac4d8176f 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '12.1' +platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index d5da0966be..98f0adda22 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -475,7 +475,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.1; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -568,7 +568,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.1; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -617,7 +617,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.1; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 9413c69718..7f38bb254a 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import UIKit import Flutter -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, From af83ea1212260b6f1e2c204fabde70228c6e15a6 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 5 Sep 2024 14:28:01 -0400 Subject: [PATCH 268/288] only go to space details on navigate to space if in column mode --- lib/pages/chat_list/chat_list.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 602821505c..e02e32faf4 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/send_file_dialog.dart'; import 'package:fluffychat/pages/chat_list/chat_list_view.dart'; import 'package:fluffychat/pangea/constants/pangea_room_types.dart'; @@ -120,7 +121,9 @@ class ChatListController extends State }); // #Pangea - context.go('/rooms/$spaceId/details'); + if (FluffyThemes.isColumnMode(context)) { + context.go('/rooms/$spaceId/details'); + } // Pangea# } From 8605202c633ebd97f444b80bcade89f3a8be9bc9 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 5 Sep 2024 15:11:03 -0400 Subject: [PATCH 269/288] added back 'remove from space' option in chat list item dropdown --- lib/pages/chat_list/chat_list.dart | 282 +++++++++++++++++------------ 1 file changed, 167 insertions(+), 115 deletions(-) diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index e02e32faf4..3423c3612d 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -804,6 +804,26 @@ class ChatListController extends State ], ), ), + // #Pangea + // if the room has a parent for which the user has a high enough power level + // to set parent's space child events, show option to remove the room from the space + if (room.spaceParents.isNotEmpty && + room.pangeaSpaceParents.any( + (r) => r.canChangeStateEvent(EventTypes.SpaceChild), + ) && + activeSpaceId != null) + PopupMenuItem( + value: ChatContextAction.removeFromSpace, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.delete_sweep_outlined), + const SizedBox(width: 12), + Text(L10n.of(context)!.removeFromSpace), + ], + ), + ), + // Pangea# PopupMenuItem( value: ChatContextAction.leave, child: Row( @@ -885,9 +905,39 @@ class ChatListController extends State context: context, future: () => space.setSpaceChild(room.id), ); + // #Pangea + return; + case ChatContextAction.removeFromSpace: + await showFutureLoadingDialog( + context: context, + future: () async { + final futures = room.pangeaSpaceParents + .where((r) => r.canChangeStateEvent(EventTypes.SpaceChild)) + .map((space) => removeSpaceChild(space, room.id)); + await Future.wait(futures); + }, + ); + return; + // Pangea# } } + // #Pangea + // Remove a room from a space. Often, the user will have permission to set + // the SpaceChild event for the parent space, but not the SpaceParent event. + // This would cause a permissions error, but the child will still be removed + // via the SpaceChild event. If that's the case, silence the error. + Future removeSpaceChild(Room space, String roomId) async { + try { + await space.removeSpaceChild(roomId); + } catch (err) { + if ((err as MatrixException).error != MatrixError.M_FORBIDDEN) { + rethrow; + } + } + } + // Pangea# + void dismissStatusList() async { final result = await showOkCancelAlertDialog( title: L10n.of(context)!.hidePresences, @@ -1120,121 +1170,123 @@ enum ChatContextAction { mute, leave, addToSpace, + // #Pangea + removeFromSpace, + // Pangea# } - // TODO re-integrate this logic - // // #Pangea - // Future leaveAction() async { - // final onlyAdmin = await Matrix.of(context) - // .client - // .getRoomById(selectedRoomIds.first) - // ?.isOnlyAdmin() ?? - // false; - // final confirmed = await showOkCancelAlertDialog( - // useRootNavigator: false, - // context: context, - // title: L10n.of(context)!.areYouSure, - // okLabel: L10n.of(context)!.yes, - // cancelLabel: L10n.of(context)!.cancel, - // message: onlyAdmin && selectedRoomIds.length == 1 - // ? L10n.of(context)!.onlyAdminDescription - // : L10n.of(context)!.leaveRoomDescription, - // ) == - // OkCancelResult.ok; - // if (!confirmed) return; - // final leftActiveRoom = - // selectedRoomIds.contains(Matrix.of(context).activeRoomId); - // await showFutureLoadingDialog( - // context: context, - // future: () => _leaveSelectedRooms(onlyAdmin), - // ); - // if (leftActiveRoom) { - // context.go('/rooms'); - // } - // } - // // Pangea# - - // Future addToSpace() async { - // // #Pangea - // final firstSelectedRoom = - // Matrix.of(context).client.getRoomById(selectedRoomIds.toList().first); - // // Pangea# - // final selectedSpace = await showConfirmationDialog( - // context: context, - // title: L10n.of(context)!.addToSpace, - // // #Pangea - // // message: L10n.of(context)!.addToSpaceDescription, - // message: L10n.of(context)!.addSpaceToSpaceDescription, - // // Pangea# - // fullyCapitalizedForMaterial: false, - // actions: Matrix.of(context) - // .client - // .rooms - // .where( - // (r) => - // r.isSpace - // // #Pangea - // && - // selectedRoomIds - // .map((id) => Matrix.of(context).client.getRoomById(id)) - // // Only show non-recursion-causing spaces - // // Performs a few other checks as well - // .every((e) => r.canAddAsParentOf(e)), - // //Pangea# - // ) - // .map( - // (space) => AlertDialogAction( - // key: space.id, - // // #Pangea - // // label: space - // // .getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)), - // label: space.nameIncludingParents(context), - // // If user is not admin of space, button is grayed out - // textStyle: TextStyle( - // color: (firstSelectedRoom == null) - // ? Theme.of(context).colorScheme.outline - // : Theme.of(context).colorScheme.surfaceTint, - // ), - // // Pangea# - // ), - // ) - // .toList(), - // ); - // if (selectedSpace == null) return; - // final result = await showFutureLoadingDialog( - // context: context, - // future: () async { - // final space = Matrix.of(context).client.getRoomById(selectedSpace)!; - // // #Pangea - // if (firstSelectedRoom == null) { - // throw L10n.of(context)!.nonexistentSelection; - // } - - // if (space.canSendDefaultStates) { - // for (final roomId in selectedRoomIds) { - // await space.pangeaSetSpaceChild(roomId, suggested: true); - // } - // } - // // Pangea# - // }, - // ); - // if (result.error == null) { - // if (!mounted) return; - // ScaffoldMessenger.of(context).showSnackBar( - // SnackBar( - // // #Pangea - // // content: Text(L10n.of(context)!.chatHasBeenAddedToThisSpace), - // content: Text(L10n.of(context)!.roomAddedToSpace), - // // Pangea# - // ), - // ); - // } - - // // #Pangea - // // setState(() => selectedRoomIds.clear()); - // if (firstSelectedRoom != null) { - // toggleSelection(firstSelectedRoom.id); - // } - // // Pangea# - // } +// // #Pangea +// Future leaveAction() async { +// final onlyAdmin = await Matrix.of(context) +// .client +// .getRoomById(selectedRoomIds.first) +// ?.isOnlyAdmin() ?? +// false; +// final confirmed = await showOkCancelAlertDialog( +// useRootNavigator: false, +// context: context, +// title: L10n.of(context)!.areYouSure, +// okLabel: L10n.of(context)!.yes, +// cancelLabel: L10n.of(context)!.cancel, +// message: onlyAdmin && selectedRoomIds.length == 1 +// ? L10n.of(context)!.onlyAdminDescription +// : L10n.of(context)!.leaveRoomDescription, +// ) == +// OkCancelResult.ok; +// if (!confirmed) return; +// final leftActiveRoom = +// selectedRoomIds.contains(Matrix.of(context).activeRoomId); +// await showFutureLoadingDialog( +// context: context, +// future: () => _leaveSelectedRooms(onlyAdmin), +// ); +// if (leftActiveRoom) { +// context.go('/rooms'); +// } +// } +// // Pangea# + +// Future addToSpace() async { +// // #Pangea +// final firstSelectedRoom = +// Matrix.of(context).client.getRoomById(selectedRoomIds.toList().first); +// // Pangea# +// final selectedSpace = await showConfirmationDialog( +// context: context, +// title: L10n.of(context)!.addToSpace, +// // #Pangea +// // message: L10n.of(context)!.addToSpaceDescription, +// message: L10n.of(context)!.addSpaceToSpaceDescription, +// // Pangea# +// fullyCapitalizedForMaterial: false, +// actions: Matrix.of(context) +// .client +// .rooms +// .where( +// (r) => +// r.isSpace +// // #Pangea +// && +// selectedRoomIds +// .map((id) => Matrix.of(context).client.getRoomById(id)) +// // Only show non-recursion-causing spaces +// // Performs a few other checks as well +// .every((e) => r.canAddAsParentOf(e)), +// //Pangea# +// ) +// .map( +// (space) => AlertDialogAction( +// key: space.id, +// // #Pangea +// // label: space +// // .getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)), +// label: space.nameIncludingParents(context), +// // If user is not admin of space, button is grayed out +// textStyle: TextStyle( +// color: (firstSelectedRoom == null) +// ? Theme.of(context).colorScheme.outline +// : Theme.of(context).colorScheme.surfaceTint, +// ), +// // Pangea# +// ), +// ) +// .toList(), +// ); +// if (selectedSpace == null) return; +// final result = await showFutureLoadingDialog( +// context: context, +// future: () async { +// final space = Matrix.of(context).client.getRoomById(selectedSpace)!; +// // #Pangea +// if (firstSelectedRoom == null) { +// throw L10n.of(context)!.nonexistentSelection; +// } + +// if (space.canSendDefaultStates) { +// for (final roomId in selectedRoomIds) { +// await space.pangeaSetSpaceChild(roomId, suggested: true); +// } +// } +// // Pangea# +// }, +// ); +// if (result.error == null) { +// if (!mounted) return; +// ScaffoldMessenger.of(context).showSnackBar( +// SnackBar( +// // #Pangea +// // content: Text(L10n.of(context)!.chatHasBeenAddedToThisSpace), +// content: Text(L10n.of(context)!.roomAddedToSpace), +// // Pangea# +// ), +// ); +// } + +// // #Pangea +// // setState(() => selectedRoomIds.clear()); +// if (firstSelectedRoom != null) { +// toggleSelection(firstSelectedRoom.id); +// } +// // Pangea# +// } From 23a8e1f7eabcd11ea11f7f4be9c7b5d2292c8cff Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 5 Sep 2024 15:11:32 -0400 Subject: [PATCH 270/288] removes testing print statements --- lib/pages/chat_list/space_view.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index 3a36935763..79dce997fd 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -120,7 +120,6 @@ class _SpaceViewState extends State { } Future loadHierarchy({hasUpdate = false}) async { - debugPrint("loading hierarchy. hasUpdate: $hasUpdate"); final room = Matrix.of(context).client.getRoomById(widget.spaceId); if (room == null) return; @@ -564,7 +563,6 @@ class _SpaceViewState extends State { .where((s) => s.hasRoomUpdate) .rateLimit(const Duration(seconds: 1)), builder: (context, snapshot) { - debugPrint("build on room update"); final childrenIds = room.spaceChildren .map((c) => c.roomId) .whereType() From 9eb40e2b215c8daadc47516768caa539b1feec9d Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 5 Sep 2024 15:33:43 -0400 Subject: [PATCH 271/288] removed duplicate emoji picker widgets and fixed name of variable in chat controller to resolve late initialization error when sending an emoji reaction --- lib/pages/chat/chat.dart | 15 ++++---- lib/pages/chat/chat_emoji_picker.dart | 50 --------------------------- 2 files changed, 6 insertions(+), 59 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 09de93502e..b07e7cbad8 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -849,12 +849,9 @@ class ChatController extends State }); } - // #Pangea - // void hideEmojiPicker() { - void hideEmojiPicker({bool closeOverlay = false}) { - if (closeOverlay) { - MatrixState.pAnyState.closeOverlay(); - } + void hideEmojiPicker() { + // #Pangea + clearSelectedEvents(); // Pangea# setState(() => showEmojiPicker = false); } @@ -1221,7 +1218,7 @@ class ChatController extends State setState(() => showEmojiPicker = false); if (emoji == null) return; // make sure we don't send the same emoji twice - if (allReactionEvents.any( + if (_allReactionEvents.any( (e) => e.content.tryGetMap('m.relates_to')?['key'] == emoji.emoji, )) { return; @@ -1245,7 +1242,7 @@ class ChatController extends State ); } - late Iterable allReactionEvents; + late Iterable _allReactionEvents; void emojiPickerBackspace() { switch (emojiPickerType) { @@ -1266,7 +1263,7 @@ class ChatController extends State // #Pangea closeSelectionOverlay(); // Pangea# - allReactionEvents = allReactionEvents; + _allReactionEvents = allReactionEvents; emojiPickerType = EmojiPickerType.reaction; setState(() => showEmojiPicker = true); } diff --git a/lib/pages/chat/chat_emoji_picker.dart b/lib/pages/chat/chat_emoji_picker.dart index 011e3dc4b0..c016da6b99 100644 --- a/lib/pages/chat/chat_emoji_picker.dart +++ b/lib/pages/chat/chat_emoji_picker.dart @@ -82,56 +82,6 @@ class ChatEmojiPicker extends StatelessWidget { ], ), ), - Expanded( - child: TabBarView( - children: [ - EmojiPicker( - onEmojiSelected: controller.onEmojiSelected, - onBackspacePressed: controller.emojiPickerBackspace, - config: Config( - emojiViewConfig: EmojiViewConfig( - noRecents: const NoRecent(), - backgroundColor: Theme.of(context) - .colorScheme - .onInverseSurface, - ), - bottomActionBarConfig: const BottomActionBarConfig( - enabled: false, - ), - categoryViewConfig: CategoryViewConfig( - backspaceColor: theme.colorScheme.primary, - iconColor: - theme.colorScheme.primary.withOpacity(0.5), - iconColorSelected: theme.colorScheme.primary, - indicatorColor: theme.colorScheme.primary, - ), - skinToneConfig: SkinToneConfig( - dialogBackgroundColor: Color.lerp( - theme.colorScheme.surface, - theme.colorScheme.primaryContainer, - 0.75, - )!, - indicatorColor: theme.colorScheme.onSurface, - ), - ), - ), - StickerPickerDialog( - room: controller.room, - onSelected: (sticker) { - controller.room.sendEvent( - { - 'body': sticker.body, - 'info': sticker.info ?? {}, - 'url': sticker.url.toString(), - }, - type: EventTypes.Sticker, - ); - controller.hideEmojiPicker(); - }, - ), - ], - ), - ), // #Pangea Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), From 10c0cfef2a219c108c40e0d1ccacc75fd2e038c8 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 5 Sep 2024 15:56:57 -0400 Subject: [PATCH 272/288] changed homeserver picker view / functionality back to previous setup --- .../homeserver_picker/homeserver_picker.dart | 97 ++++-- .../homeserver_picker_view.dart | 283 +++++++++++------- 2 files changed, 254 insertions(+), 126 deletions(-) diff --git a/lib/pages/homeserver_picker/homeserver_picker.dart b/lib/pages/homeserver_picker/homeserver_picker.dart index 8854b1934e..41377d9af6 100644 --- a/lib/pages/homeserver_picker/homeserver_picker.dart +++ b/lib/pages/homeserver_picker/homeserver_picker.dart @@ -6,6 +6,7 @@ import 'package:file_picker/file_picker.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/homeserver_picker/homeserver_picker_view.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; +import 'package:fluffychat/pangea/utils/firebase_analytics.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/tor_stub.dart' if (dart.library.html) 'package:tor_detector_web/tor_detector_web.dart'; @@ -35,9 +36,11 @@ class HomeserverPickerController extends State { bool isLoading = false; bool isLoggingIn = false; - final TextEditingController homeserverController = TextEditingController( - text: AppConfig.defaultHomeserver, - ); + // #Pangea + // final TextEditingController homeserverController = TextEditingController( + // text: AppConfig.defaultHomeserver, + // ); + // Pangea# String? error; @@ -79,38 +82,59 @@ class HomeserverPickerController extends State { checkHomeserverAction(); } + // #Pangea + Map? _rawLoginTypes; + // Pangea# + /// Starts an analysis of the given homeserver. It uses the current domain and /// makes sure that it is prefixed with https. Then it searches for the /// well-known information and forwards to the login page depending on the /// login type. Future checkHomeserverAction([_]) async { - homeserverController.text = - homeserverController.text.trim().toLowerCase().replaceAll(' ', '-'); - - if (homeserverController.text.isEmpty) { - setState(() { - error = loginFlows = null; - isLoading = false; - Matrix.of(context).getLoginClient().homeserver = null; - }); - return; - } - if (_lastCheckedUrl == homeserverController.text) return; - - _lastCheckedUrl = homeserverController.text; + // #Pangea + // homeserverController.text = + // homeserverController.text.trim().toLowerCase().replaceAll(' ', '-'); + + // if (homeserverController.text.isEmpty) { + // setState(() { + // error = loginFlows = null; + // isLoading = false; + // Matrix.of(context).getLoginClient().homeserver = null; + // }); + // return; + // } + // if (_lastCheckedUrl == homeserverController.text) return; + + // _lastCheckedUrl = homeserverController.text; + _lastCheckedUrl = AppConfig.defaultHomeserver; + // Pangea# setState(() { error = loginFlows = null; isLoading = true; }); try { - var homeserver = Uri.parse(homeserverController.text); + // #Pangea + // var homeserver = Uri.parse(homeserverController.text); + // if (homeserver.scheme.isEmpty) { + // homeserver = Uri.https(homeserverController.text, ''); + // } + var homeserver = Uri.parse(AppConfig.defaultHomeserver); if (homeserver.scheme.isEmpty) { - homeserver = Uri.https(homeserverController.text, ''); + homeserver = Uri.https(AppConfig.defaultHomeserver, ''); } + // Pangea# final client = Matrix.of(context).getLoginClient(); final (_, _, loginFlows) = await client.checkHomeserver(homeserver); this.loginFlows = loginFlows; + // #Pangea + if (supportsSso) { + _rawLoginTypes = await client.request( + RequestType.GET, + '/client/v3/login', + ); + } + // Pangea# } catch (e) { setState( () => error = (e).toLocalizedString( @@ -137,7 +161,11 @@ class HomeserverPickerController extends State { bool get supportsPasswordLogin => _supportsFlow('m.login.password'); - void ssoLoginAction() async { + void ssoLoginAction( + // #Pangea + IdentityProvider provider, + // Pangea# + ) async { final redirectUrl = kIsWeb ? Uri.parse(html.window.location.href) .resolveUri( @@ -149,7 +177,11 @@ class HomeserverPickerController extends State { : 'http://localhost:3001//login'; final url = Matrix.of(context).getLoginClient().homeserver!.replace( - path: '/_matrix/client/v3/login/sso/redirect', + // #Pangea + // path: '/_matrix/client/v3/login/sso/redirect', + path: + '/_matrix/client/v3/login/sso/redirect${provider.id == null ? '' : '/${provider.id}'}', + // Pangea# queryParameters: {'redirectUrl': redirectUrl}, ); @@ -197,7 +229,7 @@ class HomeserverPickerController extends State { initialDeviceDisplayName: PlatformInfos.clientName, ); // #Pangea - // GoogleAnalytics.login(provider.name!, loginRes.userId); + GoogleAnalytics.login(provider.name!, loginRes.userId); // Pangea# } catch (e) { setState(() { @@ -258,6 +290,27 @@ class HomeserverPickerController extends State { } } } + + // #Pangea + List? get identityProviders { + final loginTypes = _rawLoginTypes; + if (loginTypes == null) return null; + final List? rawProviders = + loginTypes.tryGetList('flows')?.singleWhereOrNull( + (flow) => flow['type'] == AuthenticationTypes.sso, + )['identity_providers'] ?? + [ + {'id': null}, + ]; + if (rawProviders == null) return null; + final list = + rawProviders.map((json) => IdentityProvider.fromJson(json)).toList(); + if (PlatformInfos.isCupertinoStyle) { + list.sort((a, b) => a.brand == 'apple' ? -1 : 1); + } + return list; + } + // Pangea# } class IdentityProvider { diff --git a/lib/pages/homeserver_picker/homeserver_picker_view.dart b/lib/pages/homeserver_picker/homeserver_picker_view.dart index dd73b2c03f..1b0c7f59fa 100644 --- a/lib/pages/homeserver_picker/homeserver_picker_view.dart +++ b/lib/pages/homeserver_picker/homeserver_picker_view.dart @@ -1,9 +1,10 @@ import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pangea/pages/connect/p_sso_button.dart'; +import 'package:fluffychat/pangea/widgets/common/pangea_logo_svg.dart'; +import 'package:fluffychat/pangea/widgets/signup/signup_buttons.dart'; import 'package:fluffychat/widgets/layouts/login_scaffold.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:flutter_linkify/flutter_linkify.dart'; -import 'package:url_launcher/url_launcher.dart'; import 'homeserver_picker.dart'; @@ -70,116 +71,190 @@ class HomeserverPickerView extends StatelessWidget { // repeat: ImageRepeat.repeat, // ), // ), - // Pangea# - Padding( - padding: const EdgeInsets.all(32.0), - child: TextField( - onChanged: controller.tryCheckHomeserverActionWithCooldown, - onEditingComplete: - controller.tryCheckHomeserverActionWithoutCooldown, - onSubmitted: controller.tryCheckHomeserverActionWithoutCooldown, - onTap: controller.tryCheckHomeserverActionWithCooldown, - controller: controller.homeserverController, - autocorrect: false, - keyboardType: TextInputType.url, - decoration: InputDecoration( - prefixIcon: controller.isLoading - ? Container( - width: 16, - height: 16, - alignment: Alignment.center, - child: const SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator.adaptive( - strokeWidth: 2, + // Padding( + // padding: const EdgeInsets.all(32.0), + // child: TextField( + // onChanged: controller.tryCheckHomeserverActionWithCooldown, + // onEditingComplete: + // controller.tryCheckHomeserverActionWithoutCooldown, + // onSubmitted: controller.tryCheckHomeserverActionWithoutCooldown, + // onTap: controller.tryCheckHomeserverActionWithCooldown, + // controller: controller.homeserverController, + // autocorrect: false, + // keyboardType: TextInputType.url, + // decoration: InputDecoration( + // prefixIcon: controller.isLoading + // ? Container( + // width: 16, + // height: 16, + // alignment: Alignment.center, + // child: const SizedBox( + // width: 16, + // height: 16, + // child: CircularProgressIndicator.adaptive( + // strokeWidth: 2, + // ), + // ), + // ) + // : const Icon(Icons.search_outlined), + // filled: false, + // border: OutlineInputBorder( + // borderRadius: BorderRadius.circular(AppConfig.borderRadius), + // ), + // hintText: AppConfig.defaultHomeserver, + // labelText: L10n.of(context)!.homeserver, + // errorText: controller.error, + // suffixIcon: IconButton( + // onPressed: () { + // showDialog( + // context: context, + // builder: (context) => AlertDialog.adaptive( + // title: Text(L10n.of(context)!.whatIsAHomeserver), + // content: Linkify( + // text: L10n.of(context)!.homeserverDescription, + // ), + // actions: [ + // TextButton( + // onPressed: () => launchUrl( + // Uri.https('servers.joinmatrix.org'), + // ), + // child: Text( + // L10n.of(context)!.discoverHomeservers, + // ), + // ), + // TextButton( + // onPressed: Navigator.of(context).pop, + // child: Text(L10n.of(context)!.close), + // ), + // ], + // ), + // ); + // }, + // icon: const Icon(Icons.info_outlined), + // ), + // ), + // ), + // ), + // if (MediaQuery.of(context).size.height > 512) const Spacer(), + // ListView( + // shrinkWrap: true, + // padding: const EdgeInsets.symmetric( + // horizontal: 32.0, + // vertical: 32.0, + // ), + // children: [ + // TextButton( + // style: TextButton.styleFrom( + // textStyle: theme.textTheme.labelMedium, + // foregroundColor: theme.colorScheme.secondary, + // ), + // onPressed: controller.isLoggingIn || controller.isLoading + // ? null + // : controller.restoreBackup, + // child: Text(L10n.of(context)!.hydrate), + // ), + // if (controller.supportsPasswordLogin && controller.supportsSso) + // TextButton( + // style: TextButton.styleFrom( + // foregroundColor: theme.colorScheme.secondary, + // textStyle: theme.textTheme.labelMedium, + // ), + // onPressed: controller.isLoggingIn || controller.isLoading + // ? null + // : controller.login, + // child: Text(L10n.of(context)!.loginWithMatrixId), + // ), + // const SizedBox(height: 8.0), + // if (controller.supportsPasswordLogin || controller.supportsSso) + // ElevatedButton( + // style: ElevatedButton.styleFrom( + // backgroundColor: theme.colorScheme.primary, + // foregroundColor: theme.colorScheme.onPrimary, + // ), + // onPressed: controller.isLoggingIn || controller.isLoading + // ? null + // : controller.supportsSso + // ? controller.ssoLoginAction + // : controller.login, + // child: Text(L10n.of(context)!.next), + // ), + // ], + // ), + Expanded( + child: controller.isLoading + ? const Center( + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(Colors.black), + ), + ) + : ListView( + children: [ + if (controller.error != null) ...[ + const SizedBox(height: 12), + const Center( + child: Icon( + Icons.error_outline, + size: 48, + color: Colors.orange, ), ), - ) - : const Icon(Icons.search_outlined), - filled: false, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(AppConfig.borderRadius), - ), - hintText: AppConfig.defaultHomeserver, - labelText: L10n.of(context)!.homeserver, - errorText: controller.error, - suffixIcon: IconButton( - onPressed: () { - showDialog( - context: context, - builder: (context) => AlertDialog.adaptive( - title: Text(L10n.of(context)!.whatIsAHomeserver), - content: Linkify( - text: L10n.of(context)!.homeserverDescription, - ), - actions: [ - TextButton( - onPressed: () => launchUrl( - Uri.https('servers.joinmatrix.org'), + const SizedBox(height: 12), + Center( + child: Text( + controller.error!, + textAlign: TextAlign.center, + style: TextStyle( + color: Theme.of(context).colorScheme.error, + fontSize: 18, ), - child: Text( - L10n.of(context)!.discoverHomeservers, + ), + ), + const SizedBox(height: 36), + ] else + const SignupButtons(), + if (controller.identityProviders != null) ...[ + ...controller.identityProviders!.map( + (provider) => Padding( + padding: const EdgeInsets.all(12.0), + child: Hero( + tag: "ssobutton ${provider.id ?? provider.name}", + child: PangeaSsoButton( + identityProvider: provider, + onPressed: () => + controller.ssoLoginAction(provider), + ), ), ), - TextButton( - onPressed: Navigator.of(context).pop, - child: Text(L10n.of(context)!.close), + ), + if (controller.supportsPasswordLogin) + Padding( + padding: const EdgeInsets.all(12.0), + child: Hero( + tag: 'signinButton', + child: ElevatedButton( + onPressed: controller.login, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const PangeaLogoSvg(width: 20), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + ), + child: Text( + "${L10n.of(context)!.loginOrSignup} Pangea Chat", + ), + ), + ], + ), + ), + ), ), - ], - ), - ); - }, - icon: const Icon(Icons.info_outlined), - ), - ), - ), - ), - if (MediaQuery.of(context).size.height > 512) const Spacer(), - ListView( - shrinkWrap: true, - padding: const EdgeInsets.symmetric( - horizontal: 32.0, - vertical: 32.0, - ), - children: [ - TextButton( - style: TextButton.styleFrom( - textStyle: theme.textTheme.labelMedium, - foregroundColor: theme.colorScheme.secondary, - ), - onPressed: controller.isLoggingIn || controller.isLoading - ? null - : controller.restoreBackup, - child: Text(L10n.of(context)!.hydrate), - ), - if (controller.supportsPasswordLogin && controller.supportsSso) - TextButton( - style: TextButton.styleFrom( - foregroundColor: theme.colorScheme.secondary, - textStyle: theme.textTheme.labelMedium, - ), - onPressed: controller.isLoggingIn || controller.isLoading - ? null - : controller.login, - child: Text(L10n.of(context)!.loginWithMatrixId), - ), - const SizedBox(height: 8.0), - if (controller.supportsPasswordLogin || controller.supportsSso) - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: theme.colorScheme.primary, - foregroundColor: theme.colorScheme.onPrimary, + ], + ], ), - onPressed: controller.isLoggingIn || controller.isLoading - ? null - : controller.supportsSso - ? controller.ssoLoginAction - : controller.login, - child: Text(L10n.of(context)!.next), - ), - ], ), + // Pangea# ], ), ); From a367b756aff67f344995e7a595430897d2264bc6 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 6 Sep 2024 10:51:37 -0400 Subject: [PATCH 273/288] updated archive package --- pubspec.lock | 48 ++++++++++++++++++++---------------------------- pubspec.yaml | 2 +- 2 files changed, 21 insertions(+), 29 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index e9e47cfb8c..3ee3462336 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -61,10 +61,10 @@ packages: dependency: "direct main" description: name: archive - sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" + sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d url: "https://pub.dev" source: hosted - version: "3.4.10" + version: "3.6.1" args: dependency: transitive description: @@ -1305,18 +1305,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -1417,10 +1417,10 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" material_symbols_icons: dependency: "direct main" description: @@ -1442,10 +1442,10 @@ packages: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mgrs_dart: dependency: transitive description: @@ -1674,10 +1674,10 @@ packages: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" platform_detect: dependency: transitive description: @@ -1726,14 +1726,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.10.2" - pointycastle: - dependency: transitive - description: - name: pointycastle - sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" - url: "https://pub.dev" - source: hosted - version: "3.7.4" polylabel: dependency: transitive description: @@ -2319,26 +2311,26 @@ packages: dependency: transitive description: name: test - sha256: "7ee446762c2c50b3bd4ea96fe13ffac69919352bd3b4b17bac3f3465edc58073" + sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e" url: "https://pub.dev" source: hosted - version: "1.25.2" + version: "1.25.7" test_api: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" test_core: dependency: transitive description: name: test_core - sha256: "2bc4b4ecddd75309300d8096f781c0e3280ca1ef85beda558d33fcbedc2eead4" + sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.4" timezone: dependency: transitive description: @@ -2631,10 +2623,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.5" wakelock_plus: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 95ea003b32..73f5b8a9b4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,7 +14,7 @@ environment: dependencies: adaptive_dialog: ^2.1.0 animations: ^2.0.11 - archive: ^3.4.10 + archive: ^3.6.1 async: ^2.11.0 badges: ^3.1.2 blurhash_dart: ^1.2.1 From 0265019284c5ec6e189a7015f4e4404f055cb51d Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 6 Sep 2024 10:52:13 -0400 Subject: [PATCH 274/288] added 'categories' field to construct use model and started saving morph category --- .../controllers/my_analytics_controller.dart | 29 +++++++++---------- .../models/analytics/constructs_model.dart | 11 +++++-- .../models/representation_content_model.dart | 27 ++++++++--------- 3 files changed, 35 insertions(+), 32 deletions(-) diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart index c24f3391bf..15079a5155 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/my_analytics_controller.dart @@ -201,22 +201,19 @@ class MyAnalyticsController extends BaseController { ) .toList(); - final List morphs = tokens - .map((t) => t.morph.values) - .expand((m) => m) - .cast() - .toList(); - - uses.addAll( - morphs.map( - (morph) => OneConstructUse( - useType: useType, - lemma: morph, - constructType: ConstructTypeEnum.morph, - metadata: metadata, - ), - ), - ); + for (final token in tokens) { + for (final entry in token.morph.entries) { + uses.add( + OneConstructUse( + useType: useType, + lemma: entry.value, + categories: [entry.key], + constructType: ConstructTypeEnum.morph, + metadata: metadata, + ), + ); + } + } final level = _pangeaController.analytics.level; addLocalMessage('draft$roomID', uses).then( diff --git a/lib/pangea/models/analytics/constructs_model.dart b/lib/pangea/models/analytics/constructs_model.dart index 0454532287..fd9710a801 100644 --- a/lib/pangea/models/analytics/constructs_model.dart +++ b/lib/pangea/models/analytics/constructs_model.dart @@ -74,8 +74,9 @@ class ConstructAnalyticsModel { class OneConstructUse { String? lemma; - ConstructTypeEnum? constructType; String? form; + List categories; + ConstructTypeEnum? constructType; ConstructUseTypeEnum useType; String? id; ConstructUseMetaData metadata; @@ -85,6 +86,7 @@ class OneConstructUse { required this.lemma, required this.constructType, required this.metadata, + this.categories = const [], this.form, this.id, }); @@ -100,6 +102,9 @@ class OneConstructUse { ConstructUseTypeEnum.unk, lemma: json['lemma'], form: json['form'], + categories: json['categories'] != null + ? List.from(json['categories']) + : [], constructType: json['constructType'] != null ? ConstructTypeUtil.fromString(json['constructType']) : null, @@ -113,7 +118,7 @@ class OneConstructUse { } Map toJson([bool condensed = false]) { - final Map data = { + final Map data = { 'useType': useType.string, 'chatId': metadata.roomId, 'timeStamp': metadata.timeStamp.toIso8601String(), @@ -125,7 +130,7 @@ class OneConstructUse { data['constructType'] = constructType!.string; } if (id != null) data['id'] = id; - + data['categories'] = categories; return data; } diff --git a/lib/pangea/models/representation_content_model.dart b/lib/pangea/models/representation_content_model.dart index be68c11959..a318d18316 100644 --- a/lib/pangea/models/representation_content_model.dart +++ b/lib/pangea/models/representation_content_model.dart @@ -146,23 +146,23 @@ class PangeaRepresentation { final List uses = []; final lemma = token.lemma; final content = token.text.content; - final morphs = token.morph.values.toList(); if (choreo == null) { final bool inUserL2 = langCode == MatrixState.pangeaController.languageController.activeL2Code(); final useType = inUserL2 ? ConstructUseTypeEnum.wa : ConstructUseTypeEnum.unk; - uses.addAll( - morphs.map( - (morph) => OneConstructUse( + for (final entry in token.morph.entries) { + uses.add( + OneConstructUse( useType: useType, - lemma: morph, + lemma: entry.value, + categories: [entry.key], constructType: ConstructTypeEnum.morph, metadata: metadata, ), - ), - ); + ); + } uses.add( lemma.toVocabUse( inUserL2 ? ConstructUseTypeEnum.wa : ConstructUseTypeEnum.unk, @@ -205,16 +205,17 @@ class PangeaRepresentation { } } - uses.addAll( - morphs.map( - (morph) => OneConstructUse( + for (final entry in token.morph.entries) { + uses.add( + OneConstructUse( useType: ConstructUseTypeEnum.wa, - lemma: morph, + lemma: entry.value, + categories: [entry.key], constructType: ConstructTypeEnum.morph, metadata: metadata, ), - ), - ); + ); + } uses.add( lemma.toVocabUse( ConstructUseTypeEnum.wa, From 790223e68c3c01807c3231c33a03aaa6db51ea61 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 6 Sep 2024 12:17:18 -0400 Subject: [PATCH 275/288] added popup for analytics indicators to show xp per lemma --- assets/l10n/intl_en.arb | 3 +- lib/pangea/constants/analytics_constants.dart | 2 + lib/pangea/enum/construct_type_enum.dart | 13 ++++ lib/pangea/enum/progress_indicators_enum.dart | 17 ++--- .../analytics/construct_list_model.dart | 8 +++ .../analytics_summary/analytics_popup.dart | 67 +++++++++++++++++++ .../learning_progress_indicators.dart | 34 +++++++--- 7 files changed, 122 insertions(+), 22 deletions(-) create mode 100644 lib/pangea/widgets/chat_list/analytics_summary/analytics_popup.dart diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 5da8185d0e..32063d4131 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4134,5 +4134,6 @@ "canceledSend": "Canceled send", "morphsUsed": "Morphs Used", "translationChoicesBody": "Click and hold an option for a hint.", - "sendCanceled": "Sending canceled" + "sendCanceled": "Sending canceled", + "grammar": "Grammar" } \ No newline at end of file diff --git a/lib/pangea/constants/analytics_constants.dart b/lib/pangea/constants/analytics_constants.dart index 1608ef4225..fb7c356f8c 100644 --- a/lib/pangea/constants/analytics_constants.dart +++ b/lib/pangea/constants/analytics_constants.dart @@ -1,3 +1,5 @@ class AnalyticsConstants { static const int xpPerLevel = 2000; + static const int vocabUseMaxXP = 30; + static const int morphUseMaxXP = 500; } diff --git a/lib/pangea/enum/construct_type_enum.dart b/lib/pangea/enum/construct_type_enum.dart index 86c715fb6e..2b346b235c 100644 --- a/lib/pangea/enum/construct_type_enum.dart +++ b/lib/pangea/enum/construct_type_enum.dart @@ -1,3 +1,5 @@ +import 'package:fluffychat/pangea/constants/analytics_constants.dart'; + enum ConstructTypeEnum { grammar, vocab, @@ -15,6 +17,17 @@ extension ConstructExtension on ConstructTypeEnum { return 'morph'; } } + + int get maxXPPerLemma { + switch (this) { + case ConstructTypeEnum.grammar: + return 0; + case ConstructTypeEnum.vocab: + return AnalyticsConstants.vocabUseMaxXP; + case ConstructTypeEnum.morph: + return AnalyticsConstants.morphUseMaxXP; + } + } } class ConstructTypeUtil { diff --git a/lib/pangea/enum/progress_indicators_enum.dart b/lib/pangea/enum/progress_indicators_enum.dart index aa38230e2a..ae37df389b 100644 --- a/lib/pangea/enum/progress_indicators_enum.dart +++ b/lib/pangea/enum/progress_indicators_enum.dart @@ -5,7 +5,6 @@ import 'package:material_symbols_icons/symbols.dart'; enum ProgressIndicatorEnum { level, wordsUsed, - errorTypes, morphsUsed, } @@ -14,8 +13,6 @@ extension ProgressIndicatorsExtension on ProgressIndicatorEnum { switch (this) { case ProgressIndicatorEnum.wordsUsed: return Icons.text_fields_outlined; - case ProgressIndicatorEnum.errorTypes: - return Icons.error_outline; case ProgressIndicatorEnum.morphsUsed: return Symbols.toys_and_games; case ProgressIndicatorEnum.level: @@ -32,10 +29,10 @@ extension ProgressIndicatorsExtension on ProgressIndicatorEnum { return Theme.of(context).brightness == Brightness.dark ? const Color.fromARGB(255, 169, 183, 237) : const Color.fromARGB(255, 38, 59, 141); - case ProgressIndicatorEnum.errorTypes: - return Theme.of(context).brightness == Brightness.dark - ? const Color.fromARGB(255, 212, 144, 216) - : const Color.fromARGB(255, 163, 39, 169); + // case ProgressIndicatorEnum.errorTypes: + // return Theme.of(context).brightness == Brightness.dark + // ? const Color.fromARGB(255, 212, 144, 216) + // : const Color.fromARGB(255, 163, 39, 169); case ProgressIndicatorEnum.level: return Theme.of(context).brightness == Brightness.dark ? const Color.fromARGB(255, 250, 220, 129) @@ -50,13 +47,11 @@ extension ProgressIndicatorsExtension on ProgressIndicatorEnum { String tooltip(BuildContext context) { switch (this) { case ProgressIndicatorEnum.wordsUsed: - return L10n.of(context)!.wordsUsed; - case ProgressIndicatorEnum.errorTypes: - return L10n.of(context)!.errorTypes; + return L10n.of(context)!.vocab; case ProgressIndicatorEnum.level: return L10n.of(context)!.level; case ProgressIndicatorEnum.morphsUsed: - return L10n.of(context)!.morphsUsed; + return L10n.of(context)!.grammar; } } } diff --git a/lib/pangea/models/analytics/construct_list_model.dart b/lib/pangea/models/analytics/construct_list_model.dart index 8d8aeaff35..661fa9d0b1 100644 --- a/lib/pangea/models/analytics/construct_list_model.dart +++ b/lib/pangea/models/analytics/construct_list_model.dart @@ -117,6 +117,14 @@ class ConstructUses { required this.constructType, required this.lemma, }); + + // Total points for all uses of this lemma + int get points { + return uses.fold( + 0, + (total, use) => total + use.useType.pointValue, + ); + } } /// One lemma, a use type, and a list of uses diff --git a/lib/pangea/widgets/chat_list/analytics_summary/analytics_popup.dart b/lib/pangea/widgets/chat_list/analytics_summary/analytics_popup.dart new file mode 100644 index 0000000000..59859bb7b8 --- /dev/null +++ b/lib/pangea/widgets/chat_list/analytics_summary/analytics_popup.dart @@ -0,0 +1,67 @@ +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; +import 'package:fluffychat/pangea/enum/progress_indicators_enum.dart'; +import 'package:fluffychat/pangea/models/analytics/construct_list_model.dart'; +import 'package:flutter/material.dart'; + +class AnalyticsPopup extends StatelessWidget { + final ProgressIndicatorEnum indicator; + final ConstructListModel constructsModel; + + const AnalyticsPopup({ + required this.indicator, + required this.constructsModel, + super.key, + }); + + @override + Widget build(BuildContext context) { + return Dialog( + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 400, + maxHeight: 600, + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(20.0), + child: Scaffold( + appBar: AppBar( + title: Text(indicator.tooltip(context)), + leading: IconButton( + icon: const Icon(Icons.close), + onPressed: Navigator.of(context).pop, + ), + ), + body: Padding( + padding: const EdgeInsets.symmetric(vertical: 20), + child: ListView.builder( + itemCount: constructsModel.constructs.length, + itemBuilder: (context, index) { + return Tooltip( + message: + "${constructsModel.constructs[index].points} / ${constructsModel.type.maxXPPerLemma}", + child: ListTile( + onTap: () {}, + title: Text(constructsModel.constructs[index].lemma), + subtitle: LinearProgressIndicator( + value: constructsModel.constructs[index].points / + constructsModel.type.maxXPPerLemma, + minHeight: 20, + borderRadius: const BorderRadius.all( + Radius.circular(AppConfig.borderRadius), + ), + color: indicator.color(context), + ), + contentPadding: + const EdgeInsets.symmetric(horizontal: 20), + ), + ); + }, + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart index 171f8ac643..39865aee95 100644 --- a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart +++ b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart @@ -9,6 +9,7 @@ import 'package:fluffychat/pangea/models/analytics/construct_list_model.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; import 'package:fluffychat/pangea/widgets/animations/progress_bar/progress_bar.dart'; import 'package:fluffychat/pangea/widgets/animations/progress_bar/progress_bar_details.dart'; +import 'package:fluffychat/pangea/widgets/chat_list/analytics_summary/analytics_popup.dart'; import 'package:fluffychat/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -41,9 +42,6 @@ class LearningProgressIndicatorsState /// Vocabulary constructs model ConstructListModel? words; - /// Grammar constructs model - ConstructListModel? errors; - /// Morph constructs model ConstructListModel? morphs; @@ -87,10 +85,6 @@ class LearningProgressIndicatorsState type: ConstructTypeEnum.vocab, uses: constructs, ); - errors = ConstructListModel( - type: ConstructTypeEnum.grammar, - uses: constructs, - ); morphs = ConstructListModel( type: ConstructTypeEnum.morph, uses: constructs, @@ -101,13 +95,23 @@ class LearningProgressIndicatorsState if (mounted) setState(() {}); } + /// Get the number of points for a given progress indicator + ConstructListModel? getConstructsModel(ProgressIndicatorEnum indicator) { + switch (indicator) { + case ProgressIndicatorEnum.wordsUsed: + return words; + case ProgressIndicatorEnum.morphsUsed: + return morphs; + default: + return null; + } + } + /// Get the number of points for a given progress indicator int? getProgressPoints(ProgressIndicatorEnum indicator) { switch (indicator) { case ProgressIndicatorEnum.wordsUsed: return words?.lemmas.length; - case ProgressIndicatorEnum.errorTypes: - return errors?.lemmas.length; case ProgressIndicatorEnum.morphsUsed: return morphs?.lemmas.length; case ProgressIndicatorEnum.level: @@ -215,7 +219,17 @@ class LearningProgressIndicatorsState .map( (indicator) => ProgressIndicatorBadge( points: getProgressPoints(indicator), - onTap: () {}, + onTap: () { + final model = getConstructsModel(indicator); + if (model == null) return; + showDialog( + context: context, + builder: (c) => AnalyticsPopup( + indicator: indicator, + constructsModel: model, + ), + ); + }, progressIndicator: indicator, loading: loading, ), From 1df329d92427469e95fcf66a8db266a6094e4ca1 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 6 Sep 2024 16:11:49 -0400 Subject: [PATCH 276/288] filter out lemmas with saveVocab set to false --- .../controllers/my_analytics_controller.dart | 1 + .../models/representation_content_model.dart | 29 +++++++++++-------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart index 15079a5155..d92c2e48d0 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/my_analytics_controller.dart @@ -190,6 +190,7 @@ class MyAnalyticsController extends BaseController { ); final uses = tokens + .where((token) => token.lemma.saveVocab) .map( (token) => OneConstructUse( useType: useType, diff --git a/lib/pangea/models/representation_content_model.dart b/lib/pangea/models/representation_content_model.dart index a318d18316..9fb96f1e91 100644 --- a/lib/pangea/models/representation_content_model.dart +++ b/lib/pangea/models/representation_content_model.dart @@ -163,12 +163,15 @@ class PangeaRepresentation { ), ); } - uses.add( - lemma.toVocabUse( - inUserL2 ? ConstructUseTypeEnum.wa : ConstructUseTypeEnum.unk, - metadata, - ), - ); + + if (lemma.saveVocab) { + uses.add( + lemma.toVocabUse( + inUserL2 ? ConstructUseTypeEnum.wa : ConstructUseTypeEnum.unk, + metadata, + ), + ); + } return uses; } @@ -216,12 +219,14 @@ class PangeaRepresentation { ), ); } - uses.add( - lemma.toVocabUse( - ConstructUseTypeEnum.wa, - metadata, - ), - ); + if (lemma.saveVocab) { + uses.add( + lemma.toVocabUse( + ConstructUseTypeEnum.wa, + metadata, + ), + ); + } return uses; } } From 6687889d585770b1f0dfea0d2b0114c135b7c1df Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 6 Sep 2024 16:31:27 -0400 Subject: [PATCH 277/288] added learning analytics view to chat list appbar --- lib/pages/chat_list/chat_list_body.dart | 4 ---- lib/pages/chat_list/chat_list_header.dart | 10 ++++++++-- lib/pages/chat_list/space_view.dart | 11 ----------- 3 files changed, 8 insertions(+), 17 deletions(-) diff --git a/lib/pages/chat_list/chat_list_body.dart b/lib/pages/chat_list/chat_list_body.dart index 1993a97a6d..b9b388da98 100644 --- a/lib/pages/chat_list/chat_list_body.dart +++ b/lib/pages/chat_list/chat_list_body.dart @@ -3,7 +3,6 @@ import 'package:fluffychat/pages/chat_list/chat_list.dart'; import 'package:fluffychat/pages/chat_list/search_title.dart'; import 'package:fluffychat/pages/chat_list/space_view.dart'; import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart'; -import 'package:fluffychat/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart'; import 'package:fluffychat/pangea/widgets/chat_list/chat_list_body_text.dart'; import 'package:fluffychat/pangea/widgets/chat_list/chat_list_header_wrapper.dart'; import 'package:fluffychat/pangea/widgets/chat_list/chat_list_item_wrapper.dart'; @@ -165,9 +164,6 @@ class ChatListViewBody extends StatelessWidget { title: L10n.of(context)!.chats, icon: const Icon(Icons.forum_outlined), ), - // #Pangea - const LearningProgressIndicators(), - // Pangea# if (client.prevBatch != null && rooms.isEmpty && !controller.isSearchMode) ...[ diff --git a/lib/pages/chat_list/chat_list_header.dart b/lib/pages/chat_list/chat_list_header.dart index a45751201f..3ab5bc1bc5 100644 --- a/lib/pages/chat_list/chat_list_header.dart +++ b/lib/pages/chat_list/chat_list_header.dart @@ -2,6 +2,7 @@ import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat_list/chat_list.dart'; import 'package:fluffychat/pages/chat_list/client_chooser_button.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; +import 'package:fluffychat/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -22,7 +23,7 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget { return SliverAppBar( floating: true, - toolbarHeight: 72, + toolbarHeight: 175, pinned: FluffyThemes.isColumnMode(context) || selectMode != SelectMode.normal, scrolledUnderElevation: selectMode == SelectMode.normal ? 0 : null, @@ -55,7 +56,12 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget { key: const ValueKey(SelectMode.select), ) // #Pangea - : ClientChooserButton(controller), + : Column( + children: [ + ClientChooserButton(controller), + const LearningProgressIndicators(), + ], + ), ), // : TextField( // controller: controller.searchController, diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index ebf343bbf6..a90c0d0946 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -11,7 +11,6 @@ import 'package:fluffychat/pangea/constants/pangea_room_types.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; import 'package:fluffychat/pangea/utils/chat_list_handle_space_tap.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; -import 'package:fluffychat/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart'; import 'package:fluffychat/pangea/widgets/chat_list/chat_list_header_wrapper.dart'; import 'package:fluffychat/pangea/widgets/chat_list/chat_list_item_wrapper.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; @@ -734,11 +733,6 @@ class _SpaceViewState extends State { // #Pangea // ChatListHeader(controller: widget.controller), ChatListHeaderWrapper(controller: widget.controller), - SliverList( - delegate: SliverChildListDelegate( - [const LearningProgressIndicators()], - ), - ), // Pangea# SliverList( delegate: SliverChildBuilderDelegate( @@ -825,11 +819,6 @@ class _SpaceViewState extends State { controller: widget.controller, globalSearch: false, ), - SliverList( - delegate: SliverChildListDelegate( - [const LearningProgressIndicators()], - ), - ), // Pangea# SliverAppBar( automaticallyImplyLeading: false, From f016d6e24b9297412ced3b514b4bdf9900c87349 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 9 Sep 2024 11:20:32 -0400 Subject: [PATCH 278/288] when adding a room to a space via the chat list item popup, remove it from any other parent spaces it was previously in --- lib/pages/chat_list/chat_list.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 3423c3612d..065f9c9e8d 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -903,7 +903,10 @@ class ChatListController extends State if (space == null) return; await showFutureLoadingDialog( context: context, - future: () => space.setSpaceChild(room.id), + // #Pangea + // future: () => space.setSpaceChild(room.id), + future: () => space.pangeaSetSpaceChild(room.id), + // Pangea# ); // #Pangea return; From 3a31e28838d76ecfe323de33ab148ac6f22d4395 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 10 Sep 2024 13:00:25 -0400 Subject: [PATCH 279/288] decreased font size of level indicator --- .../analytics_summary/learning_progress_indicators.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart index 39865aee95..eb0b0cb629 100644 --- a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart +++ b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart @@ -173,7 +173,7 @@ class LearningProgressIndicatorsState child: Center( child: Text( "$level", - style: const TextStyle(color: Colors.white), + style: const TextStyle(color: Colors.white, fontSize: 16), ), ), ); From c2466f7aec7a03b9312f7215fd906f6a4c760a05 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 11 Sep 2024 09:20:12 -0400 Subject: [PATCH 280/288] added 'no data' warning to analytics popup if there's no data --- .../analytics_summary/analytics_popup.dart | 53 +++++++++++-------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/lib/pangea/widgets/chat_list/analytics_summary/analytics_popup.dart b/lib/pangea/widgets/chat_list/analytics_summary/analytics_popup.dart index 59859bb7b8..5378796af0 100644 --- a/lib/pangea/widgets/chat_list/analytics_summary/analytics_popup.dart +++ b/lib/pangea/widgets/chat_list/analytics_summary/analytics_popup.dart @@ -3,6 +3,7 @@ import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; import 'package:fluffychat/pangea/enum/progress_indicators_enum.dart'; import 'package:fluffychat/pangea/models/analytics/construct_list_model.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; class AnalyticsPopup extends StatelessWidget { final ProgressIndicatorEnum indicator; @@ -34,30 +35,36 @@ class AnalyticsPopup extends StatelessWidget { ), body: Padding( padding: const EdgeInsets.symmetric(vertical: 20), - child: ListView.builder( - itemCount: constructsModel.constructs.length, - itemBuilder: (context, index) { - return Tooltip( - message: - "${constructsModel.constructs[index].points} / ${constructsModel.type.maxXPPerLemma}", - child: ListTile( - onTap: () {}, - title: Text(constructsModel.constructs[index].lemma), - subtitle: LinearProgressIndicator( - value: constructsModel.constructs[index].points / - constructsModel.type.maxXPPerLemma, - minHeight: 20, - borderRadius: const BorderRadius.all( - Radius.circular(AppConfig.borderRadius), - ), - color: indicator.color(context), - ), - contentPadding: - const EdgeInsets.symmetric(horizontal: 20), + child: constructsModel.constructs.isEmpty + ? Center( + child: Text(L10n.of(context)!.noDataFound), + ) + : ListView.builder( + itemCount: constructsModel.constructs.length, + itemBuilder: (context, index) { + return Tooltip( + message: + "${constructsModel.constructs[index].points} / ${constructsModel.type.maxXPPerLemma}", + child: ListTile( + onTap: () {}, + title: Text( + constructsModel.constructs[index].lemma, + ), + subtitle: LinearProgressIndicator( + value: constructsModel.constructs[index].points / + constructsModel.type.maxXPPerLemma, + minHeight: 20, + borderRadius: const BorderRadius.all( + Radius.circular(AppConfig.borderRadius), + ), + color: indicator.color(context), + ), + contentPadding: + const EdgeInsets.symmetric(horizontal: 20), + ), + ); + }, ), - ); - }, - ), ), ), ), From b63ae8c43d873148e7215eb16ba3eb36ff032d8a Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 11 Sep 2024 10:27:03 -0400 Subject: [PATCH 281/288] updated copy to always call chat 'chats' (insteads of groups) --- assets/l10n/intl_en.arb | 8 ++++++-- lib/pages/chat_list/chat_list.dart | 5 ++++- lib/pages/chat_list/chat_list_item.dart | 5 ++++- lib/pages/chat_list/space_view.dart | 15 ++++++++++++--- .../invitation_selection.dart | 5 ++++- lib/pages/new_group/new_group_view.dart | 10 ++++++++-- .../new_private_chat/new_private_chat_view.dart | 5 ++++- .../class_invitation_selection.dart | 5 ++++- 8 files changed, 46 insertions(+), 12 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 9bd5cdd2ec..667d218737 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -3903,7 +3903,7 @@ "define": "Define", "listen": "Listen", "addConversationBot": "Enable Conversation Bot", - "addConversationBotDesc": "Add a bot to this group chat", + "addConversationBotDesc": "Add a bot to this chat", "convoBotSettingsTitle": "Conversation Bot Settings", "convoBotSettingsDescription": "Edit conversation topic and difficulty", "enterAConversationTopic": "Enter a conversation topic", @@ -4195,5 +4195,9 @@ "discoverHomeservers": "Discover homeservers", "whatIsAHomeserver": "What is a homeserver?", "homeserverDescription": "All your data is stored on the homeserver, just like an email provider. You can choose which homeserver you want to use, while you can still communicate with everyone. Learn more at at https://matrix.org.", - "doesNotSeemToBeAValidHomeserver": "Doesn't seem to be a compatible homeserver. Wrong URL?" + "doesNotSeemToBeAValidHomeserver": "Doesn't seem to be a compatible homeserver. Wrong URL?", + "grammar": "Grammar", + "contactHasBeenInvitedToTheChat": "Contact has been invited to the chat", + "inviteChat": "📨 Invite chat", + "chatName": "Chat name" } \ No newline at end of file diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 065f9c9e8d..5a84541d82 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -138,8 +138,11 @@ class ChatListController extends State final inviteAction = await showModalActionSheet( context: context, message: room.isDirectChat + // #Pangea ? L10n.of(context)!.invitePrivateChat - : L10n.of(context)!.inviteGroupChat, + // : L10n.of(context)!.inviteGroupChat, + : L10n.of(context)!.inviteChat, + // Pangea# title: room.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)), actions: [ SheetAction( diff --git a/lib/pages/chat_list/chat_list_item.dart b/lib/pages/chat_list/chat_list_item.dart index 5cae0c8245..9cbe6af82b 100644 --- a/lib/pages/chat_list/chat_list_item.dart +++ b/lib/pages/chat_list/chat_list_item.dart @@ -328,7 +328,10 @@ class ChatListItem extends StatelessWidget { room.membership == Membership.invite ? isDirectChat ? L10n.of(context)!.invitePrivateChat - : L10n.of(context)!.inviteGroupChat + // #Pangea + // : L10n.of(context)!.inviteGroupChat + : L10n.of(context)!.inviteChat + // Pangea# : snapshot.data ?? L10n.of(context)!.emptyChat, softWrap: false, diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index 79dce997fd..589c0341c2 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -319,7 +319,10 @@ class _SpaceViewState extends State { ), AlertDialogAction( key: AddRoomType.chat, - label: L10n.of(context)!.createGroup, + // #Pangea + // label: L10n.of(context)!.createGroup, + label: L10n.of(context)!.createChat, + // Pangea# ), ], ); @@ -329,12 +332,18 @@ class _SpaceViewState extends State { context: context, title: roomType == AddRoomType.subspace ? L10n.of(context)!.createNewSpace - : L10n.of(context)!.createGroup, + // #Pangea + // : L10n.of(context)!.createGroup, + : L10n.of(context)!.createChat, + // Pangea# textFields: [ DialogTextField( hintText: roomType == AddRoomType.subspace ? L10n.of(context)!.spaceName - : L10n.of(context)!.groupName, + // #Pangea + // : L10n.of(context)!.groupName, + : L10n.of(context)!.chatName, + // Pangea# minLines: 1, maxLines: 1, maxLength: 64, diff --git a/lib/pages/invitation_selection/invitation_selection.dart b/lib/pages/invitation_selection/invitation_selection.dart index e59e038198..761f43271d 100644 --- a/lib/pages/invitation_selection/invitation_selection.dart +++ b/lib/pages/invitation_selection/invitation_selection.dart @@ -182,7 +182,10 @@ class InvitationSelectionController extends State { if (success.error == null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text(L10n.of(context)!.contactHasBeenInvitedToTheGroup), + // #Pangea + // content: Text(L10n.of(context)!.contactHasBeenInvitedToTheGroup), + content: Text(L10n.of(context)!.contactHasBeenInvitedToTheChat), + // Pangea# ), ); } diff --git a/lib/pages/new_group/new_group_view.dart b/lib/pages/new_group/new_group_view.dart index 52ca198f02..6dfcec2129 100644 --- a/lib/pages/new_group/new_group_view.dart +++ b/lib/pages/new_group/new_group_view.dart @@ -27,7 +27,10 @@ class NewGroupView extends StatelessWidget { onPressed: controller.loading ? null : Navigator.of(context).pop, ), ), - title: Text(L10n.of(context)!.createGroup), + // #Pangea + // title: Text(L10n.of(context)!.createGroup), + title: Text(L10n.of(context)!.createChat), + // Pangea# ), // #Pangea floatingActionButton: FloatingActionButton.extended( @@ -74,7 +77,10 @@ class NewGroupView extends StatelessWidget { readOnly: controller.loading, decoration: InputDecoration( prefixIcon: const Icon(Icons.people_outlined), - labelText: L10n.of(context)!.groupName, + // #Pangea + // labelText: L10n.of(context)!.groupName, + labelText: L10n.of(context)!.chatName, + // Pangea# ), ), ), diff --git a/lib/pages/new_private_chat/new_private_chat_view.dart b/lib/pages/new_private_chat/new_private_chat_view.dart index 0a7c3de4f9..024e52bc71 100644 --- a/lib/pages/new_private_chat/new_private_chat_view.dart +++ b/lib/pages/new_private_chat/new_private_chat_view.dart @@ -140,7 +140,10 @@ class NewPrivateChatView extends StatelessWidget { foregroundColor: theme.colorScheme.onTertiaryContainer, child: const Icon(Icons.group_add_outlined), ), - title: Text(L10n.of(context)!.createGroup), + // #Pangea + // title: Text(L10n.of(context)!.createGroup), + title: Text(L10n.of(context)!.createChat), + // Pangea# onTap: () => context.go('/rooms/newgroup'), ), if (PlatformInfos.isMobile) diff --git a/lib/pangea/pages/class_invitation_selection/class_invitation_selection.dart b/lib/pangea/pages/class_invitation_selection/class_invitation_selection.dart index 0164f7b84e..8289d357f0 100644 --- a/lib/pangea/pages/class_invitation_selection/class_invitation_selection.dart +++ b/lib/pangea/pages/class_invitation_selection/class_invitation_selection.dart @@ -53,7 +53,10 @@ class ClassInvitationSelectionController if (success.error == null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text(L10n.of(context)!.contactHasBeenInvitedToTheGroup), + // #Pangea + // content: Text(L10n.of(context)!.contactHasBeenInvitedToTheGroup), + content: Text(L10n.of(context)!.contactHasBeenInvitedToTheChat), + // Pangea# ), ); } From 925a03bd3579a9e4674c8ecffa058be8da08514d Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 11 Sep 2024 11:23:42 -0400 Subject: [PATCH 282/288] switched from parent/child space copy to sub space/main space --- assets/l10n/intl_en.arb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 1f46aab2a3..ae3f83ad26 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4113,11 +4113,11 @@ "placeholders": {} }, "addChatToSpaceDesc": "Adding a chat to a space will make the chat appear within the space for students and give them access.", - "addSpaceToSpaceDesc": "Adding a space to another space will make the child space appear within the parent space for students and give them access.", + "addSpaceToSpaceDesc": "Adding a sub space to space will make the sub space appear in the main space's chat list.", "spaceAnalytics": "Space Analytics", "changeAnalyticsLanguage": "Change Analytics Language", "suggestToSpace": "Suggest this space", - "suggestToSpaceDesc": "Suggested spaces will appear in the chat lists for their parent spaces", + "suggestToSpaceDesc": "Suggested sub spaces will appear in their main space's chat list", "practice": "Practice", "noLanguagesSet": "No languages set", "noActivitiesFound": "No practice activities found for this message", From 397f32fcbc67173357d1d5688506ed048be8b2de Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 12 Sep 2024 09:34:40 -0400 Subject: [PATCH 283/288] don't show toolbar is message is redacted --- lib/pages/chat/chat.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 57de6370df..b35845bbf8 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -1619,9 +1619,10 @@ class ChatController extends State Event? prevEvent, }) { if (![ - MessageTypes.Text, - MessageTypes.Audio, - ].contains(pangeaMessageEvent.event.messageType)) { + MessageTypes.Text, + MessageTypes.Audio, + ].contains(pangeaMessageEvent.event.messageType) || + pangeaMessageEvent.event.redacted) { return; } From 666cbfd2f3e29cb3f4991b99125ea7541db73cf7 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 12 Sep 2024 10:04:10 -0400 Subject: [PATCH 284/288] updated copy to say highlight instead of double click when defining a word or phrase --- assets/l10n/intl_en.arb | 2 +- assets/l10n/intl_es.arb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 20b80f59dc..6312008d60 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -3968,7 +3968,7 @@ "seeOptions": "See options", "continuedWithoutSubscription": "Continue without subscribing", "trialPeriodExpired": "Your trial period has expired", - "selectToDefine": "Double click a word to see its definition!", + "selectToDefine": "Highlight a word or phrase to see its definition!", "translations": "translations", "messageAudio": "message audio", "definitions": "definitions", diff --git a/assets/l10n/intl_es.arb b/assets/l10n/intl_es.arb index 0cebc36aaf..fb9c91df6b 100644 --- a/assets/l10n/intl_es.arb +++ b/assets/l10n/intl_es.arb @@ -4505,7 +4505,7 @@ "age": {} } }, - "selectToDefine": "Haga doble clic en una palabra para ver su definición.", + "selectToDefine": "¡Resalta una palabra o frase para ver su definición!", "kickBotWarning": "Patear Pangea Bot eliminará el bot de conversación de este chat.", "activateTrial": "Activar prueba gratuita", "refresh": "Actualizar", From 78214b743386cbd71f7e3f40c70cd340b44be51e Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 12 Sep 2024 10:05:00 -0400 Subject: [PATCH 285/288] removed clearing of errorService in it_controller clear function - this was causing errors that happened on send to not be shown --- lib/pangea/choreographer/controllers/it_controller.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pangea/choreographer/controllers/it_controller.dart b/lib/pangea/choreographer/controllers/it_controller.dart index 0bb82aa630..c3ce495754 100644 --- a/lib/pangea/choreographer/controllers/it_controller.dart +++ b/lib/pangea/choreographer/controllers/it_controller.dart @@ -52,7 +52,6 @@ class ITController { payLoadIds = []; choreographer.altTranslator.clear(); - choreographer.errorService.resetError(); choreographer.choreoMode = ChoreoMode.igc; choreographer.setState(); } From f7fdffaaa4514a44eae99a18173e7b90b05e5080 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 12 Sep 2024 11:49:35 -0400 Subject: [PATCH 286/288] disable suggestions in input bar --- lib/pages/chat/input_bar.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pages/chat/input_bar.dart b/lib/pages/chat/input_bar.dart index d845c8a962..c0939f2783 100644 --- a/lib/pages/chat/input_bar.dart +++ b/lib/pages/chat/input_bar.dart @@ -477,6 +477,7 @@ class InputBar extends StatelessWidget { key: controller?.choreographer.inputLayerLinkAndKey.key, // builder: (context, controller, focusNode) => TextField( builder: (context, _, focusNode) => TextField( + enableSuggestions: false, // Pangea# controller: controller, focusNode: focusNode, From 3a7d46fc184222e1a327e9b5c608eb60f6b93b92 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 12 Sep 2024 15:30:54 -0400 Subject: [PATCH 287/288] add blue effect to overlat background, made toolbar backdrop slightly darker --- lib/pages/chat/chat.dart | 2 +- lib/pangea/utils/overlay.dart | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index b35845bbf8..12fc40af11 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -1660,7 +1660,7 @@ class ChatController extends State context: context, child: overlayEntry, transformTargetId: "", - backgroundColor: const Color.fromRGBO(0, 0, 0, 1).withAlpha(100), + backgroundColor: const Color.fromRGBO(0, 0, 0, 1).withAlpha(150), closePrevOverlay: MatrixState.pangeaController.subscriptionController.isSubscribed, position: OverlayPositionEnum.centered, diff --git a/lib/pangea/utils/overlay.dart b/lib/pangea/utils/overlay.dart index c83c39af43..c49b1250e7 100644 --- a/lib/pangea/utils/overlay.dart +++ b/lib/pangea/utils/overlay.dart @@ -1,5 +1,6 @@ import 'dart:developer'; import 'dart:math'; +import 'dart:ui'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/utils/any_state_holder.dart'; @@ -229,10 +230,13 @@ class TransparentBackdrop extends StatelessWidget { } MatrixState.pAnyState.closeOverlay(); }, - child: Container( - height: double.infinity, - width: double.infinity, - color: Colors.transparent, + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 2.5, sigmaY: 2.5), + child: Container( + height: double.infinity, + width: double.infinity, + color: Colors.transparent, + ), ), ), ); From 39396aba23b24497495aec70c7bf83754121eb1e Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 12 Sep 2024 16:38:55 -0400 Subject: [PATCH 288/288] removed duplicate 'new space' option in main menu --- lib/pages/chat_list/client_chooser_button.dart | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lib/pages/chat_list/client_chooser_button.dart b/lib/pages/chat_list/client_chooser_button.dart index 73ade85ead..0a406fa2cd 100644 --- a/lib/pages/chat_list/client_chooser_button.dart +++ b/lib/pages/chat_list/client_chooser_button.dart @@ -69,16 +69,6 @@ class ClientChooserButton extends StatelessWidget { // ], // ), // ), - PopupMenuItem( - value: SettingsAction.newClass, - child: Row( - children: [ - const Icon(Icons.school), - const SizedBox(width: 18), - Expanded(child: Text(L10n.of(context)!.createNewSpace)), - ], - ), - ), // PopupMenuItem( // value: SettingsAction.newGroup, // child: Row(