diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d93197fe..a2dccfb2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -172,12 +172,10 @@ upload_to_fdroid_repo: - chmod 700 ~/.ssh - ssh-keyscan -t rsa fdroid.nordgedanken.dev >> ~/.ssh/known_hosts script: - - mkdir -p upload - - cp build/android/* upload/ - cd build/android/ - - export UPDATE_VERSION=$(pcregrep -o1 'version:\\s([0-9]*\\.[0-9]*\\.[0-9]*)\\+[0-9]*' pubspec.yaml) && mv app-release.apk "${UPDATE_VERSION}.apk" + - export UPDATE_VERSION=$(pcregrep -o1 'version:\\s([0-9]*\\.[0-9]*\\.[0-9]*)\\+[0-9]*' ../../pubspec.yaml) && mv app-release.apk "${UPDATE_VERSION}.apk" - rsync -rav -e ssh ./ fluffy@fdroid.nordgedanken.dev:/fdroid/repo - - ssh fluffy@fdroid.nordgedanken.dev "cd fdroid && mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc && fdroid update" + - ssh fluffy@fdroid.nordgedanken.dev "cd fdroid && fdroid update" needs: ["build_android_apk"] only: - tags @@ -216,6 +214,29 @@ build_linux: - build/linux/release/bundle/ only: - main + +snap:edge: + stage: publish + image: "cibuilds/snapcraft:core18" + only: + - main + script: + ## Manually install the flutter-dev snap, so we can use the flutter extension + - 'curl -L $(curl -H "X-Ubuntu-Series: 16" "https://api.snapcraft.io/api/v1/snaps/details/flutter?channel=latest/stable" | jq ".download_url" -r) --output flutter.snap' + - sudo mkdir -p /snap/flutter + - sudo unsquashfs -d /snap/flutter/current flutter.snap + - rm -f flutter.snap + - sudo ln -sf /snap/flutter/current/flutter.sh /snap/bin/flutter + - sudo ln -sf /snap/flutter/current/env.sh /snap/bin/env.sh + - snapcraft + - echo $SNAPCRAFT_LOGIN_FILE | base64 --decode --ignore-garbage > snapcraft.login + - snapcraft login --with snapcraft.login + - snapcraft push --release=edge *.snap + - snapcraft logout + artifacts: + paths: + - './*.snap' + when: on_success snap:publish: stage: publish @@ -234,3 +255,35 @@ snap:publish: when: on_success expire_in: 1 week needs: [] + +update-dependencies: + stage: coverage + needs: [] + tags: + - docker + only: + - schedules + variables: + HOST: ${CI_PROJECT_URL} + UPDATE_BRANCH: ci-bot/dependency-updates + PRIVATE_TOKEN: ${GITLAB_API_TOKEN} + before_script: + - eval $(ssh-agent -s) + - echo "$SSH_PRIVATE_BOT_KEY" | tr -d '\r' | ssh-add - > /dev/null + + - mkdir -p ~/.ssh + - chmod 700 ~/.ssh + + - ssh-keyscan "https://gitlab.com" >> ~/.ssh/known_hosts + - chmod 644 ~/.ssh/known_hosts + + - git config --global user.email "bot@fluffy.chat" + - git config --global user.name "Dependency Update Bot" + - sudo apt-get update && sudo apt-get install -y curl + script: + - flutter pub get + - flutter pub pub run dapackages:dapackages.dart ./pubspec.yaml + - flutter pub get + - git remote set-url --push origin git@gitlab.com:$CI_PROJECT_PATH + - 'git diff --exit-code || (git checkout -B ${UPDATE_BRANCH} && git add . && git commit -m "chore: Update dependencies" && git push -f origin ${UPDATE_BRANCH} && ./scripts/open-mr.sh)' + diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d111481..f7fac2e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,16 @@ -# Version 0.20.0 - 2020-??-?? +# Version 0.21.0 - 2020-10-28 +### Features +- New user viewer +- Add code syntax highlighting in messages +- Updated translations: Thanks to all helpers +### Changes +- Stories feature removed +### Fixes +- Fixes sentry +- Fixes Android download +- Minor fixes + +# Version 0.20.0 - 2020-10-23 ### Features - Added translations: Arabic - Add ability to enable / disable emotes globally @@ -18,6 +30,7 @@ - Show device name in account information correctly - Fix tapping on aliases / room pills not always working - Link clicking in web not always working +- Return message input field to previous state after editing message - Thanks @inexcode # Version 0.19.0 - 2020-09-21 ### Features diff --git a/README.md b/README.md index 092146db..d7342ebc 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@
- Open FluffyChat in the browser - Join the community - Follow me on Mastodon - Translate FluffyChat - Translate the website - FAQ - Website - Download latest APK - Famedly Matrix SDK + Open FluffyChat in the browser - Join the community - Follow me on Mastodon - Translate FluffyChat - Translate the website - Website - Download latest APK - Famedly Matrix SDK



@@ -57,11 +57,6 @@ cd fluffychat-flutter sudo apt install ninja-build ``` -* Outcomment the Google Services plugin at the end of the file `android/app/build.gradle`: -``` -// apply plugin: "com.google.gms.google-services" -``` - * Build with: `flutter build apk` ### iOS / iPadOS diff --git a/android/app/build.gradle b/android/app/build.gradle index 36caa499..9b211682 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -32,7 +32,7 @@ if (keystorePropertiesFile.exists()) { } android { - compileSdkVersion 28 + compileSdkVersion 30 sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -44,8 +44,8 @@ android { defaultConfig { applicationId "chat.fluffy.fluffychat" - minSdkVersion 18 - targetSdkVersion 28 + minSdkVersion 21 + targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -87,4 +87,4 @@ dependencies { implementation "net.zetetic:android-database-sqlcipher:4.4.0" // needed for moor_ffi w/ sqlcipher } -apply plugin: "com.google.gms.google-services" +apply plugin: 'com.google.gms.google-services' \ No newline at end of file diff --git a/assets/logo.svg b/assets/logo.svg index 93d52318..a3a95164 100644 --- a/assets/logo.svg +++ b/assets/logo.svg @@ -1,48 +1,43 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/lib/components/avatar.dart b/lib/components/avatar.dart index c4b8e16b..0a35c1cd 100644 --- a/lib/components/avatar.dart +++ b/lib/components/avatar.dart @@ -45,10 +45,12 @@ class Avatar extends StatelessWidget { ), ); final noPic = mxContent == null || mxContent.toString().isEmpty; + final borderRadius = BorderRadius.circular(size / 2); return InkWell( onTap: onTap, + borderRadius: borderRadius, child: ClipRRect( - borderRadius: BorderRadius.circular(size / 2), + borderRadius: borderRadius, child: Container( width: size, height: size, @@ -68,6 +70,11 @@ class Avatar extends StatelessWidget { textWidget, ], ), + errorWidget: (c, s, d) => Stack( + children: [ + textWidget, + ], + ), ), ), ), diff --git a/lib/components/dialogs/send_file_dialog.dart b/lib/components/dialogs/send_file_dialog.dart index 1ad33ef7..061c0b75 100644 --- a/lib/components/dialogs/send_file_dialog.dart +++ b/lib/components/dialogs/send_file_dialog.dart @@ -19,7 +19,7 @@ class SendFileDialog extends StatefulWidget { class _SendFileDialogState extends State { bool origImage = false; - + bool _isSending = false; Future _send() async { var file = widget.file; if (file is MatrixImageFile && !origImage) { @@ -82,10 +82,16 @@ class _SendFileDialogState extends State { ), FlatButton( child: Text(L10n.of(context).send), - onPressed: () async { - await SimpleDialogs(context).tryRequestWithLoadingDialog(_send()); - await Navigator.of(context).pop(); - }, + onPressed: _isSending + ? null + : () async { + setState(() { + _isSending = true; + }); + await SimpleDialogs(context) + .tryRequestWithLoadingDialog(_send()); + await Navigator.of(context).pop(); + }, ), ], ); diff --git a/lib/components/html_message.dart b/lib/components/html_message.dart index df508a41..a67377a0 100644 --- a/lib/components/html_message.dart +++ b/lib/components/html_message.dart @@ -33,6 +33,8 @@ class HtmlMessage extends StatelessWidget { // there is no need to pre-validate the html, as we validate it while rendering + final matrix = Matrix.of(context); + final themeData = Theme.of(context); return Html( data: renderHtml, @@ -50,12 +52,18 @@ class HtmlMessage extends StatelessWidget { getMxcUrl: (String mxc, double width, double height) { final ratio = MediaQuery.of(context).devicePixelRatio; return Uri.parse(mxc)?.getThumbnail( - Matrix.of(context).client, + matrix.client, width: (width ?? 800) * ratio, height: (height ?? 800) * ratio, method: ThumbnailMethod.scale, ); }, + setCodeLanguage: (String key, String value) async { + await matrix.store.setItem('code_language.$key', value); + }, + getCodeLanguage: (String key) async { + return await matrix.store.getItem('code_language.$key'); + }, getPillInfo: (String identifier) async { if (room == null) { return null; diff --git a/lib/components/list_items/participant_list_item.dart b/lib/components/list_items/participant_list_item.dart index 5258f2c4..278fb842 100644 --- a/lib/components/list_items/participant_list_item.dart +++ b/lib/components/list_items/participant_list_item.dart @@ -1,66 +1,15 @@ import 'package:famedlysdk/famedlysdk.dart'; -import 'package:fluffychat/components/dialogs/simple_dialogs.dart'; -import 'package:fluffychat/utils/app_route.dart'; -import 'package:fluffychat/views/chat.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import '../avatar.dart'; -import '../matrix.dart'; +import '../user_bottom_sheet.dart'; class ParticipantListItem extends StatelessWidget { final User user; const ParticipantListItem(this.user); - void participantAction(BuildContext context, String action) async { - switch (action) { - case 'ban': - if (await SimpleDialogs(context).askConfirmation()) { - await SimpleDialogs(context).tryRequestWithLoadingDialog(user.ban()); - } - break; - case 'unban': - if (await SimpleDialogs(context).askConfirmation()) { - await SimpleDialogs(context) - .tryRequestWithLoadingDialog(user.unban()); - } - break; - case 'kick': - if (await SimpleDialogs(context).askConfirmation()) { - await SimpleDialogs(context).tryRequestWithLoadingDialog(user.kick()); - } - break; - case 'admin': - if (await SimpleDialogs(context).askConfirmation()) { - await SimpleDialogs(context) - .tryRequestWithLoadingDialog(user.setPower(100)); - } - break; - case 'moderator': - if (await SimpleDialogs(context).askConfirmation()) { - await SimpleDialogs(context) - .tryRequestWithLoadingDialog(user.setPower(50)); - } - break; - case 'user': - if (await SimpleDialogs(context).askConfirmation()) { - await SimpleDialogs(context) - .tryRequestWithLoadingDialog(user.setPower(0)); - } - break; - case 'message': - final roomId = await user.startDirectChat(); - await Navigator.of(context).pushAndRemoveUntil( - AppRoute.defaultRoute( - context, - ChatView(roomId), - ), - (Route r) => r.isFirst); - break; - } - } - @override Widget build(BuildContext context) { var membershipBatch = { @@ -74,87 +23,43 @@ class ParticipantListItem extends StatelessWidget { : user.powerLevel >= 50 ? L10n.of(context).moderator : ''; - var items = >[]; - if (user.id != Matrix.of(context).client.userID) { - items.add( - PopupMenuItem( - child: Text(L10n.of(context).sendAMessage), value: 'message'), - ); - } - if (user.canChangePowerLevel && - user.room.ownPowerLevel == 100 && - user.powerLevel != 100) { - items.add( - PopupMenuItem( - child: Text(L10n.of(context).makeAnAdmin), value: 'admin'), - ); - } - if (user.canChangePowerLevel && - user.room.ownPowerLevel >= 50 && - user.powerLevel != 50) { - items.add( - PopupMenuItem( - child: Text(L10n.of(context).makeAModerator), value: 'moderator'), - ); - } - if (user.canChangePowerLevel && user.powerLevel != 0) { - items.add( - PopupMenuItem( - child: Text(L10n.of(context).revokeAllPermissions), value: 'user'), - ); - } - if (user.canKick) { - items.add( - PopupMenuItem( - child: Text(L10n.of(context).kickFromChat), value: 'kick'), - ); - } - if (user.canBan && user.membership != Membership.ban) { - items.add( - PopupMenuItem(child: Text(L10n.of(context).banFromChat), value: 'ban'), - ); - } else if (user.canBan && user.membership == Membership.ban) { - items.add( - PopupMenuItem( - child: Text(L10n.of(context).removeExile), value: 'unban'), - ); - } - return PopupMenuButton( - onSelected: (action) => participantAction(context, action), - itemBuilder: (c) => items, - child: ListTile( - title: Row( - children: [ - Text(user.calcDisplayname()), - permissionBatch.isEmpty - ? Container() - : Container( - padding: EdgeInsets.all(4), - margin: EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration( - color: Theme.of(context).secondaryHeaderColor, - borderRadius: BorderRadius.circular(8), - ), - child: Center(child: Text(permissionBatch)), - ), - membershipBatch[user.membership].isEmpty - ? Container() - : Container( - padding: EdgeInsets.all(4), - margin: EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration( - color: Theme.of(context).secondaryHeaderColor, - borderRadius: BorderRadius.circular(8), - ), - child: - Center(child: Text(membershipBatch[user.membership])), - ), - ], + return ListTile( + onTap: () => showModalBottomSheet( + context: context, + builder: (context) => UserBottomSheet( + user: user, ), - subtitle: Text(user.id), - leading: Avatar(user.avatarUrl, user.calcDisplayname()), ), + title: Row( + children: [ + Text(user.calcDisplayname()), + permissionBatch.isEmpty + ? Container() + : Container( + padding: EdgeInsets.all(4), + margin: EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + color: Theme.of(context).secondaryHeaderColor, + borderRadius: BorderRadius.circular(8), + ), + child: Center(child: Text(permissionBatch)), + ), + membershipBatch[user.membership].isEmpty + ? Container() + : Container( + padding: EdgeInsets.all(4), + margin: EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + color: Theme.of(context).secondaryHeaderColor, + borderRadius: BorderRadius.circular(8), + ), + child: Center(child: Text(membershipBatch[user.membership])), + ), + ], + ), + subtitle: Text(user.id), + leading: Avatar(user.avatarUrl, user.calcDisplayname()), ); } } diff --git a/lib/components/list_items/status_list_item.dart b/lib/components/list_items/status_list_item.dart deleted file mode 100644 index 1d5f2725..00000000 --- a/lib/components/list_items/status_list_item.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'package:famedlysdk/famedlysdk.dart'; -import 'package:fluffychat/utils/user_status.dart'; -import 'package:fluffychat/views/status_view.dart'; -import 'package:flutter/material.dart'; -import '../avatar.dart'; -import '../matrix.dart'; - -class StatusListItem extends StatelessWidget { - final UserStatus status; - - const StatusListItem(this.status, {Key key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final client = Matrix.of(context).client; - return FutureBuilder( - future: client.getProfileFromUserId(status.userId), - builder: (context, snapshot) { - final profile = - snapshot.data ?? Profile(status.userId.localpart, null); - return InkWell( - borderRadius: BorderRadius.circular(8), - onTap: () => Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => StatusView( - status: status, - avatarUrl: profile.avatarUrl, - displayname: profile.displayname, - ), - ), - ), - child: Container( - width: 76, - child: Column( - children: [ - SizedBox(height: 10), - Container( - child: Stack( - children: [ - Avatar(profile.avatarUrl, profile.displayname), - Positioned( - bottom: 0, - right: 0, - child: Container( - width: 10, - height: 10, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: Colors.green, - ), - ), - ), - ], - ), - decoration: BoxDecoration( - border: Border.all( - width: 1, - color: Theme.of(context).primaryColor, - ), - borderRadius: BorderRadius.circular(80), - ), - padding: EdgeInsets.all(2), - ), - Padding( - padding: - const EdgeInsets.only(left: 6.0, top: 0.0, right: 6.0), - child: Text( - profile.displayname.trim().split(' ').first, - overflow: TextOverflow.clip, - maxLines: 1, - style: TextStyle( - color: Theme.of(context).textTheme.bodyText2.color, - fontSize: 13, - ), - ), - ), - ], - ), - ), - ); - }); - } -} diff --git a/lib/components/matrix.dart b/lib/components/matrix.dart index f626e5e8..6e57617d 100644 --- a/lib/components/matrix.dart +++ b/lib/components/matrix.dart @@ -7,7 +7,7 @@ import 'package:fluffychat/components/dialogs/simple_dialogs.dart'; import 'package:fluffychat/utils/firebase_controller.dart'; import 'package:fluffychat/utils/matrix_locals.dart'; import 'package:fluffychat/utils/platform_infos.dart'; -import 'package:fluffychat/utils/user_status.dart'; +import 'package:fluffychat/utils/sentry_controller.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -18,11 +18,9 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:dbus/dbus.dart'; import 'package:desktop_notifications/desktop_notifications.dart';*/ -import '../main.dart'; import '../utils/app_route.dart'; import '../utils/beautify_string_extension.dart'; import '../utils/famedlysdk_store.dart'; -import '../utils/presence_extension.dart'; import '../views/key_verification.dart'; import '../utils/platform_infos.dart'; import 'avatar.dart'; @@ -81,8 +79,7 @@ class MatrixState extends State { void clean() async { if (!kIsWeb) return; - final storage = await getLocalStorage(); - await storage.deleteItem(widget.clientName); + await store.deleteItem(widget.clientName); } void _initWithStore() async { @@ -92,7 +89,6 @@ class MatrixState extends State { await client.connect(); final firstLoginState = await initLoginState; if (firstLoginState == LoginState.logged) { - _cleanUpUserStatus(userStatuses); if (PlatformInfos.isMobile) { await FirebaseController.setupFirebase( this, @@ -102,7 +98,7 @@ class MatrixState extends State { } } catch (e, s) { client.onLoginStateChanged.sink.addError(e, s); - captureException(e, s); + SentryController.captureException(e, s); rethrow; } } @@ -124,7 +120,6 @@ class MatrixState extends State { StreamSubscription onNotification; StreamSubscription onFocusSub; StreamSubscription onBlurSub; - StreamSubscription onPresenceSub; void onJitsiCall(EventUpdate eventUpdate) { final event = Event.fromJson( @@ -247,12 +242,9 @@ class MatrixState extends State { importantStateEvents: { 'im.ponies.room_emotes', // we want emotes to work properly }); - onPresenceSub ??= client.onPresence.stream - .where((p) => p.isUserStatus) - .listen(_storeUserStatus); onJitsiCallSub ??= client.onEvent.stream .where((e) => - e.type == 'timeline' && + e.type == EventUpdateType.timeline && e.eventType == 'm.room.message' && e.content['content']['msgtype'] == Matrix.callNamespace && e.content['sender'] != client.userID) @@ -321,7 +313,7 @@ class MatrixState extends State { html.Notification.requestPermission(); onNotification ??= client.onEvent.stream .where((e) => - e.type == 'timeline' && + e.type == EventUpdateType.timeline && [EventTypes.Message, EventTypes.Sticker, EventTypes.Encrypted] .contains(e.eventType) && e.content['sender'] != client.userID) @@ -331,64 +323,11 @@ class MatrixState extends State { super.initState(); } - List get userStatuses { - try { - return (client.accountData[userStatusesType].content['user_statuses'] - as List) - .map((json) => UserStatus.fromJson(json)) - .toList(); - } catch (_) {} - return []; - } - - void _storeUserStatus(Presence presence) { - final tmpUserStatuses = List.from(userStatuses); - final currentStatusIndex = - userStatuses.indexWhere((u) => u.userId == presence.senderId); - final newUserStatus = UserStatus() - ..receivedAt = DateTime.now().millisecondsSinceEpoch - ..statusMsg = presence.presence.statusMsg - ..userId = presence.senderId; - if (currentStatusIndex == -1) { - tmpUserStatuses.add(newUserStatus); - } else if (tmpUserStatuses[currentStatusIndex].statusMsg != - presence.presence.statusMsg) { - if (presence.presence.statusMsg.trim().isEmpty) { - tmpUserStatuses.removeAt(currentStatusIndex); - } else { - tmpUserStatuses[currentStatusIndex] = newUserStatus; - } - } else { - return; - } - _cleanUpUserStatus(tmpUserStatuses); - } - - void _cleanUpUserStatus(List tmpUserStatuses) { - final now = DateTime.now().millisecondsSinceEpoch; - tmpUserStatuses - .removeWhere((u) => (now - u.receivedAt) > (1000 * 60 * 60 * 24)); - tmpUserStatuses.sort((a, b) => b.receivedAt.compareTo(a.receivedAt)); - if (tmpUserStatuses.length > 40) { - tmpUserStatuses.removeRange(40, tmpUserStatuses.length); - } - if (tmpUserStatuses != userStatuses) { - client.setAccountData( - client.userID, - userStatusesType, - { - 'user_statuses': tmpUserStatuses.map((i) => i.toJson()).toList(), - }, - ); - } - } - @override void dispose() { onRoomKeyRequestSub?.cancel(); onKeyVerificationRequestSub?.cancel(); onJitsiCallSub?.cancel(); - onPresenceSub?.cancel(); onNotification?.cancel(); onFocusSub?.cancel(); onBlurSub?.cancel(); diff --git a/lib/components/theme_switcher.dart b/lib/components/theme_switcher.dart index a212b821..dafdd4cc 100644 --- a/lib/components/theme_switcher.dart +++ b/lib/components/theme_switcher.dart @@ -175,7 +175,7 @@ class ThemeSwitcherWidgetState extends State { BuildContext context; Future loadSelection(MatrixState matrix) async { - String item = await matrix.store.getItem('theme') ?? 'system'; + var item = await matrix.store.getItem('theme') ?? 'system'; selectedTheme = Themes.values.firstWhere( (e) => e.toString() == 'Themes.' + item, orElse: () => Themes.system); diff --git a/lib/components/user_bottom_sheet.dart b/lib/components/user_bottom_sheet.dart new file mode 100644 index 00000000..4757f3f4 --- /dev/null +++ b/lib/components/user_bottom_sheet.dart @@ -0,0 +1,188 @@ +import 'dart:math'; + +import 'package:famedlysdk/famedlysdk.dart'; +import 'package:fluffychat/components/adaptive_page_layout.dart'; +import 'package:fluffychat/utils/app_route.dart'; +import 'package:fluffychat/utils/fluffy_share.dart'; +import 'package:fluffychat/views/chat.dart'; +import 'package:flutter/material.dart'; +import 'content_banner.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +import '../utils/presence_extension.dart'; +import 'dialogs/simple_dialogs.dart'; +import 'matrix.dart'; + +class UserBottomSheet extends StatelessWidget { + final User user; + final Function onMention; + + const UserBottomSheet({Key key, @required this.user, this.onMention}) + : super(key: key); + + void participantAction(BuildContext context, String action) async { + switch (action) { + case 'mention': + Navigator.of(context).pop(); + onMention(); + break; + case 'ban': + if (await SimpleDialogs(context).askConfirmation()) { + await SimpleDialogs(context).tryRequestWithLoadingDialog(user.ban()); + } + break; + case 'unban': + if (await SimpleDialogs(context).askConfirmation()) { + await SimpleDialogs(context) + .tryRequestWithLoadingDialog(user.unban()); + } + break; + case 'kick': + if (await SimpleDialogs(context).askConfirmation()) { + await SimpleDialogs(context).tryRequestWithLoadingDialog(user.kick()); + } + break; + case 'admin': + if (await SimpleDialogs(context).askConfirmation()) { + await SimpleDialogs(context) + .tryRequestWithLoadingDialog(user.setPower(100)); + } + break; + case 'moderator': + if (await SimpleDialogs(context).askConfirmation()) { + await SimpleDialogs(context) + .tryRequestWithLoadingDialog(user.setPower(50)); + } + break; + case 'user': + if (await SimpleDialogs(context).askConfirmation()) { + await SimpleDialogs(context) + .tryRequestWithLoadingDialog(user.setPower(0)); + } + break; + case 'message': + final roomId = await user.startDirectChat(); + await Navigator.of(context).pushAndRemoveUntil( + AppRoute.defaultRoute( + context, + ChatView(roomId), + ), + (Route r) => r.isFirst); + break; + } + } + + @override + Widget build(BuildContext context) { + final presence = Matrix.of(context).client.presences[user.id]; + var items = >[]; + + if (onMention != null) { + items.add( + PopupMenuItem(child: Text(L10n.of(context).mention), value: 'mention'), + ); + } + if (user.id != Matrix.of(context).client.userID) { + items.add( + PopupMenuItem( + child: Text(L10n.of(context).sendAMessage), value: 'message'), + ); + } + if (user.canChangePowerLevel && + user.room.ownPowerLevel == 100 && + user.powerLevel != 100) { + items.add( + PopupMenuItem( + child: Text(L10n.of(context).makeAnAdmin), value: 'admin'), + ); + } + if (user.canChangePowerLevel && + user.room.ownPowerLevel >= 50 && + user.powerLevel != 50) { + items.add( + PopupMenuItem( + child: Text(L10n.of(context).makeAModerator), value: 'moderator'), + ); + } + if (user.canChangePowerLevel && user.powerLevel != 0) { + items.add( + PopupMenuItem( + child: Text(L10n.of(context).revokeAllPermissions), value: 'user'), + ); + } + if (user.canKick) { + items.add( + PopupMenuItem( + child: Text(L10n.of(context).kickFromChat), value: 'kick'), + ); + } + if (user.canBan && user.membership != Membership.ban) { + items.add( + PopupMenuItem(child: Text(L10n.of(context).banFromChat), value: 'ban'), + ); + } else if (user.canBan && user.membership == Membership.ban) { + items.add( + PopupMenuItem( + child: Text(L10n.of(context).removeExile), value: 'unban'), + ); + } + return Center( + child: Container( + width: min(MediaQuery.of(context).size.width, + AdaptivePageLayout.defaultMinWidth * 1.5), + child: SafeArea( + child: Material( + elevation: 4, + child: Scaffold( + extendBodyBehindAppBar: true, + appBar: AppBar( + elevation: 0, + backgroundColor: + Theme.of(context).scaffoldBackgroundColor.withOpacity(0.5), + leading: IconButton( + icon: Icon(Icons.arrow_downward_outlined), + onPressed: Navigator.of(context).pop, + ), + title: Text(user.calcDisplayname()), + actions: [ + if (user.id != Matrix.of(context).client.userID) + PopupMenuButton( + itemBuilder: (_) => items, + onSelected: (action) => + participantAction(context, action), + ), + ], + ), + body: Column( + children: [ + Expanded( + child: ContentBanner( + user.avatarUrl, + defaultIcon: Icons.person_outline, + ), + ), + ListTile( + title: Text(L10n.of(context).username), + subtitle: Text(user.id), + trailing: Icon(Icons.share), + onTap: () => FluffyShare.share(user.id, context), + ), + if (presence != null) + ListTile( + title: Text(presence.getLocalizedStatusMessage(context)), + subtitle: + Text(presence.getLocalizedLastActiveAgo(context)), + trailing: Icon(Icons.circle, + color: presence.presence.currentlyActive ?? false + ? Colors.green + : Colors.grey), + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index 8c50c09b..a0c98cec 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -6,4 +6,6 @@ abstract class AppConfig { 'https://gitlab.com/ChristianPauly/fluffychat-flutter'; static const String supportUrl = 'https://gitlab.com/ChristianPauly/fluffychat-flutter/issues'; + static const String sentryDsn = + 'https://8591d0d863b646feb4f3dda7e5dcab38@o256755.ingest.sentry.io/5243143'; } diff --git a/lib/l10n/intl_ca.arb b/lib/l10n/intl_ca.arb new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/lib/l10n/intl_ca.arb @@ -0,0 +1 @@ +{} diff --git a/lib/l10n/intl_cs.arb b/lib/l10n/intl_cs.arb index e08169d5..80bfb588 100644 --- a/lib/l10n/intl_cs.arb +++ b/lib/l10n/intl_cs.arb @@ -87,22 +87,22 @@ "type": "text", "placeholders": {} }, - "askSSSSCache": "Prosím zadajte vaší prístupovu frázI k \"bezpečému úložišti\" anebo \"klíč na obnovu\" pro uložení klíčů.", + "askSSSSCache": "Prosím zadajte vaší přístupovu frázi k „bezpečému úložišti“ anebo „klíč na obnovu“ pro uložení klíčů.", "@askSSSSCache": { "type": "text", "placeholders": {} }, - "askSSSSSign": "Pro ověření této osoby, zadejte prosím přístupovou frází k “bezpečnému úložišti” anebo “klíč pro obnovu”.", + "askSSSSSign": "Pro ověření této osoby, zadejte prosím přístupovou frázi k „bezpečnému úložišti“ anebo „klíč pro obnovu“.", "@askSSSSSign": { "type": "text", "placeholders": {} }, - "askSSSSVerify": "Zadejte prosím vaší přístupovou frází k “bezpečnému úložišti” anebo “klíč pro obnovu” pro ověření vaší relace.", + "askSSSSVerify": "Zadejte prosím vaší přístupovou frází k „bezpečnému úložišti“ anebo „klíč pro obnovu“ pro ověření vaší relace.", "@askSSSSVerify": { "type": "text", "placeholders": {} }, - "askVerificationRequest": "Přijmout žádost o ověření od (username)?", + "askVerificationRequest": "Přijmout žádost o ověření od {username}?", "@askVerificationRequest": { "type": "text", "placeholders": { @@ -549,7 +549,7 @@ "type": "text", "placeholders": {} }, - "enableEncryptionWarning": "Šifrování jiš nebude možné vypnout. Jste si tím jisti?", + "enableEncryptionWarning": "Šifrování již nebude možné vypnout. Jste si tím jisti?", "@enableEncryptionWarning": { "type": "text", "placeholders": {} @@ -948,7 +948,7 @@ "type": "text", "placeholders": {} }, - "noGoogleServicesWarning": "Vypadá to, že váš telefon nemá nainstalovány google services. Dobré rozhodnutí pro vaši bezpečnost! Pro příjem notifikací doporučujeme použít miocroG: https://microg.org/", + "noGoogleServicesWarning": "Vypadá to, že váš telefon nemá nainstalovány google services. Dobré rozhodnutí pro vaši bezpečnost! Pro příjem notifikací doporučujeme použít microG: https://microg.org/", "@noGoogleServicesWarning": { "type": "text", "placeholders": {} @@ -1653,7 +1653,7 @@ "type": "text", "placeholders": {} }, - "you": "Ty", + "you": "Vy", "@you": { "type": "text", "placeholders": {} @@ -1702,5 +1702,45 @@ "@deactivateAccountWarning": { "type": "text", "placeholders": {} + }, + "privacy": "Soukromí", + "@privacy": { + "type": "text", + "placeholders": {} + }, + "unavailable": "Nedostupní", + "@unavailable": { + "type": "text", + "placeholders": {} + }, + "online": "Připojeni", + "@online": { + "type": "text", + "placeholders": {} + }, + "offline": "Odpojeni", + "@offline": { + "type": "text", + "placeholders": {} + }, + "mention": "Zmiň", + "@mention": { + "type": "text", + "placeholders": {} + }, + "enableEmotesGlobally": "Aktivuj balíček emotikon všude", + "@enableEmotesGlobally": { + "type": "text", + "placeholders": {} + }, + "emotePacks": "Balíček emotikon pro místnost", + "@emotePacks": { + "type": "text", + "placeholders": {} + }, + "changeDeviceName": "Změnit název zařízení", + "@changeDeviceName": { + "type": "text", + "placeholders": {} } } diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index 93dc0da8..402097e8 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -744,7 +744,7 @@ "type": "text", "placeholders": {} }, - "isTyping": "schreibt...", + "isTyping": "schreibt …", "@isTyping": { "type": "text", "placeholders": {} @@ -836,12 +836,12 @@ "count": {} } }, - "loadingPleaseWait": "Lade... Bitte warten", + "loadingPleaseWait": "Ladevorgang … Bitte warten.", "@loadingPleaseWait": { "type": "text", "placeholders": {} }, - "loadMore": "Lade mehr...", + "loadMore": "Mehr laden …", "@loadMore": { "type": "text", "placeholders": {} @@ -953,7 +953,7 @@ "type": "text", "placeholders": {} }, - "noRoomsFound": "Keine Räume gefunden...", + "noRoomsFound": "Keine Räume gefunden …", "@noRoomsFound": { "type": "text", "placeholders": {} @@ -985,7 +985,7 @@ "type": "text", "placeholders": {} }, - "oopsSomethingWentWrong": "Hoppla! Da ist etwas schief gelaufen ...", + "oopsSomethingWentWrong": "Hoppla! Etwas ist schief gelaufen …", "@oopsSomethingWentWrong": { "type": "text", "placeholders": {} @@ -1485,7 +1485,7 @@ "type": "text", "placeholders": {} }, - "userAndOthersAreTyping": "{username} und {count} andere schreiben...", + "userAndOthersAreTyping": "{username} und {count} andere schreiben …", "@userAndOthersAreTyping": { "type": "text", "placeholders": { @@ -1493,7 +1493,7 @@ "count": {} } }, - "userAndUserAreTyping": "{username} und {username2} schreiben...", + "userAndUserAreTyping": "{username} und {username2} schreiben …", "@userAndUserAreTyping": { "type": "text", "placeholders": { @@ -1501,7 +1501,7 @@ "username2": {} } }, - "userIsTyping": "{username} schreibt ...", + "userIsTyping": "{username} schreibt …", "@userIsTyping": { "type": "text", "placeholders": { @@ -1598,7 +1598,7 @@ "type": "text", "placeholders": {} }, - "waitingPartnerNumbers": "Warte darauf, dass der Partner die Zahlen annimmt...", + "waitingPartnerNumbers": "Warten, dass der Partner die Zahlen annimmt …", "@waitingPartnerNumbers": { "type": "text", "placeholders": {} @@ -1618,7 +1618,7 @@ "type": "text", "placeholders": {} }, - "welcomeText": "Herzlich willkommen beim knuffigsten Instant Messenger im Matrix-Netwerk.", + "welcomeText": "Herzlich willkommen beim knuffigsten Instant Messenger im Matrix-Netzwerk.", "@welcomeText": { "type": "text", "placeholders": {} @@ -1628,7 +1628,7 @@ "type": "text", "placeholders": {} }, - "writeAMessage": "Schreibe eine Nachricht ...", + "writeAMessage": "Schreibe eine Nachricht …", "@writeAMessage": { "type": "text", "placeholders": {} @@ -1722,5 +1722,25 @@ "@changeDeviceName": { "type": "text", "placeholders": {} + }, + "unavailable": "Nicht Verfügbar", + "@unavailable": { + "type": "text", + "placeholders": {} + }, + "offline": "Offline", + "@offline": { + "type": "text", + "placeholders": {} + }, + "online": "Online", + "@online": { + "type": "text", + "placeholders": {} + }, + "mention": "Erwähnen", + "@mention": { + "type": "text", + "placeholders": {} } } diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 35ceddd6..94237671 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -194,7 +194,7 @@ "username": {} } }, - "changedTheDisplaynameTo": "{username} changed the displayname to: {displayname}", + "changedTheDisplaynameTo": "{username} changed the displayname to: '{displayname}'", "@changedTheDisplaynameTo": { "type": "text", "placeholders": { @@ -759,7 +759,7 @@ "type": "text", "placeholders": {} }, - "isTyping": "is typing...", + "isTyping": "is typing…", "@isTyping": { "type": "text", "placeholders": {} @@ -851,12 +851,12 @@ "type": "text", "placeholders": {} }, - "loadingPleaseWait": "Loading... Please wait", + "loadingPleaseWait": "Loading… Please wait.", "@loadingPleaseWait": { "type": "text", "placeholders": {} }, - "loadMore": "Load more...", + "loadMore": "Load more…", "@loadMore": { "type": "text", "placeholders": {} @@ -895,6 +895,11 @@ "type": "text", "placeholders": {} }, + "mention": "Mention", + "@mention": { + "type": "text", + "placeholders": {} + }, "messageWillBeRemovedWarning": "Message will be removed for all participants", "@messageWillBeRemovedWarning": { "type": "text", @@ -965,7 +970,7 @@ "type": "text", "placeholders": {} }, - "noRoomsFound": "No rooms found...", + "noRoomsFound": "No rooms found…", "@noRoomsFound": { "type": "text", "placeholders": {} @@ -992,12 +997,27 @@ "type": "text", "placeholders": {} }, + "online": "Online", + "@online": { + "type": "text", + "placeholders": {} + }, + "offline": "Offline", + "@offline": { + "type": "text", + "placeholders": {} + }, + "unavailable": "Unavailable", + "@unavailable": { + "type": "text", + "placeholders": {} + }, "onlineKeyBackupEnabled": "Online Key Backup is enabled", "@onlineKeyBackupEnabled": { "type": "text", "placeholders": {} }, - "oopsSomethingWentWrong": "Oops something went wrong...", + "oopsSomethingWentWrong": "Oops, something went wrong…", "@oopsSomethingWentWrong": { "type": "text", "placeholders": {} @@ -1542,7 +1562,7 @@ "unreadChats": {} } }, - "userAndOthersAreTyping": "{username} and {count} others are typing...", + "userAndOthersAreTyping": "{username} and {count} others are typing…", "@userAndOthersAreTyping": { "type": "text", "placeholders": { @@ -1550,7 +1570,7 @@ "count": {} } }, - "userAndUserAreTyping": "{username} and {username2} are typing...", + "userAndUserAreTyping": "{username} and {username2} are typing…", "@userAndUserAreTyping": { "type": "text", "placeholders": { @@ -1563,7 +1583,7 @@ "type": "text", "placeholders": {} }, - "userIsTyping": "{username} is typing...", + "userIsTyping": "{username} is typing…", "@userIsTyping": { "type": "text", "placeholders": { @@ -1638,17 +1658,17 @@ "type": "text", "placeholders": {} }, - "waitingPartnerAcceptRequest": "Waiting for partner to accept the request...", + "waitingPartnerAcceptRequest": "Waiting for partner to accept the request…", "@waitingPartnerAcceptRequest": { "type": "text", "placeholders": {} }, - "waitingPartnerEmoji": "Waiting for partner to accept the emoji...", + "waitingPartnerEmoji": "Waiting for partner to accept the emoji…", "@waitingPartnerEmoji": { "type": "text", "placeholders": {} }, - "waitingPartnerNumbers": "Waiting for partner to accept the numbers...", + "waitingPartnerNumbers": "Waiting for partner to accept the numbers…", "@waitingPartnerNumbers": { "type": "text", "placeholders": {} @@ -1673,7 +1693,7 @@ "type": "text", "placeholders": {} }, - "welcomeText": "Welcome to the cutest instant messenger in the matrix network.", + "welcomeText": "Welcome to the cutest instant messenger in the Matrix network.", "@welcomeText": { "type": "text", "placeholders": {} @@ -1683,7 +1703,7 @@ "type": "text", "placeholders": {} }, - "writeAMessage": "Write a message...", + "writeAMessage": "Write a message…", "@writeAMessage": { "type": "text", "placeholders": {} @@ -1723,4 +1743,4 @@ "type": "text", "placeholders": {} } -} \ No newline at end of file +} diff --git a/lib/l10n/intl_eo.arb b/lib/l10n/intl_eo.arb new file mode 100644 index 00000000..ef24c3fa --- /dev/null +++ b/lib/l10n/intl_eo.arb @@ -0,0 +1,1744 @@ +{ + "participatingUserDevices": "Partoprenantaj aparatoj de uzanto", + "@participatingUserDevices": { + "type": "text", + "placeholders": {} + }, + "optionalGroupName": "(Malnepra) Nomo de grupo", + "@optionalGroupName": { + "type": "text", + "placeholders": {} + }, + "openCamera": "Malfermi fotilon", + "@openCamera": { + "type": "text", + "placeholders": {} + }, + "unavailable": "Nedisponeble", + "@unavailable": { + "type": "text", + "placeholders": {} + }, + "offline": "Eksterrete", + "@offline": { + "type": "text", + "placeholders": {} + }, + "online": "Enrete", + "@online": { + "type": "text", + "placeholders": {} + }, + "numberSelected": "{number} elektitaj", + "@numberSelected": { + "type": "text", + "placeholders": { + "number": {} + } + }, + "noPermission": "Neniu permeso", + "@noPermission": { + "type": "text", + "placeholders": {} + }, + "noEmotesFound": "Neniuj mienetoj troviĝis. 😕", + "@noEmotesFound": { + "type": "text", + "placeholders": {} + }, + "noCrossSignBootstrap": "FluffyChat ankoraŭ ne subtenas ŝaltadon de delegaj subskriboj. Bonvolu ŝalti ilin per Riot (Element).", + "@noCrossSignBootstrap": { + "type": "text", + "placeholders": {} + }, + "needPantalaimonWarning": "Bonvolu scii, ke vi ankoraŭ bezonas la programon Pantalaimon por uzi tutvojan ĉifradon.", + "@needPantalaimonWarning": { + "type": "text", + "placeholders": {} + }, + "messageWillBeRemovedWarning": "Mesaĝo foriĝos por ĉiuj partoprenantoj", + "@messageWillBeRemovedWarning": { + "type": "text", + "placeholders": {} + }, + "mention": "Mencii", + "@mention": { + "type": "text", + "placeholders": {} + }, + "makeAnAdmin": "Igi administranto", + "@makeAnAdmin": { + "type": "text", + "placeholders": {} + }, + "makeAModerator": "Igi reguligisto", + "@makeAModerator": { + "type": "text", + "placeholders": {} + }, + "loadCountMoreParticipants": "Enlegi {count} pliajn partoprenantojn", + "@loadCountMoreParticipants": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "loadMore": "Enlegi pli…", + "@loadMore": { + "type": "text", + "placeholders": {} + }, + "lastSeenIp": "Lastafoje vidita IP-adreso", + "@lastSeenIp": { + "type": "text", + "placeholders": {} + }, + "lastActiveAgo": "Lastafoje aktiva: {localizedTimeShort}", + "@lastActiveAgo": { + "type": "text", + "placeholders": { + "localizedTimeShort": {} + } + }, + "invitedUsersOnly": "Nur invititoj", + "@invitedUsersOnly": { + "type": "text", + "placeholders": {} + }, + "ignoreListDescription": "Vi povas malatenti uzantojn, kiuj vin ĝenas. Vi ne povos ricevi mesaĝojn nek invitojn al ĉambroj de la uzantoj sur via listo de malatentatoj.", + "@ignoreListDescription": { + "type": "text", + "placeholders": {} + }, + "ignoreUsername": "Malatenti uzantonomon", + "@ignoreUsername": { + "type": "text", + "placeholders": {} + }, + "ignoredUsers": "Malatentitaj uzantoj", + "@ignoredUsers": { + "type": "text", + "placeholders": {} + }, + "enableEmotesGlobally": "Ŝalti mienetaron ĉie", + "@enableEmotesGlobally": { + "type": "text", + "placeholders": {} + }, + "emotePacks": "Mienetaroj por la ĉambro", + "@emotePacks": { + "type": "text", + "placeholders": {} + }, + "emoteInvalid": "Nevalida mallongigo de mieneto!", + "@emoteInvalid": { + "type": "text", + "placeholders": {} + }, + "emoteExists": "Mieneto jam ekzistas!", + "@emoteExists": { + "type": "text", + "placeholders": {} + }, + "emoteWarnNeedToPick": "Vi devas elekti mallongigon de mieneto kaj bildon!", + "@emoteWarnNeedToPick": { + "type": "text", + "placeholders": {} + }, + "emoteShortcode": "Mallongigo de mieneto", + "@emoteShortcode": { + "type": "text", + "placeholders": {} + }, + "emoteSettings": "Agordoj pri mienetoj", + "@emoteSettings": { + "type": "text", + "placeholders": {} + }, + "guestsAreForbidden": "Gastoj estas malpermesitaj", + "@guestsAreForbidden": { + "type": "text", + "placeholders": {} + }, + "discardPicture": "Forĵeti bildon", + "@discardPicture": { + "type": "text", + "placeholders": {} + }, + "authentication": "Aŭtentikigo", + "@authentication": { + "type": "text", + "placeholders": {} + }, + "alias": "kromnomo", + "@alias": { + "type": "text", + "placeholders": {} + }, + "yourOwnUsername": "Via propra uzantonomo", + "@yourOwnUsername": { + "type": "text", + "placeholders": {} + }, + "youHaveBeenBannedFromThisChat": "Vi estas forbarita de ĉi tiu babilo", + "@youHaveBeenBannedFromThisChat": { + "type": "text", + "placeholders": {} + }, + "youCannotInviteYourself": "Vi ne povas inviti vin mem", + "@youCannotInviteYourself": { + "type": "text", + "placeholders": {} + }, + "youAreNoLongerParticipatingInThisChat": "Vi ne plu partoprenas ĉi tiun babilon", + "@youAreNoLongerParticipatingInThisChat": { + "type": "text", + "placeholders": {} + }, + "youAreInvitedToThisChat": "Vi estas invitita al ĉi tiu babilo", + "@youAreInvitedToThisChat": { + "type": "text", + "placeholders": {} + }, + "you": "Vi", + "@you": { + "type": "text", + "placeholders": {} + }, + "yes": "Jes", + "@yes": { + "type": "text", + "placeholders": {} + }, + "writeAMessage": "Skribi mesaĝon…", + "@writeAMessage": { + "type": "text", + "placeholders": {} + }, + "whoIsAllowedToJoinThisGroup": "Kiu rajtas aliĝi al ĉi tiu grupo", + "@whoIsAllowedToJoinThisGroup": { + "type": "text", + "placeholders": {} + }, + "wednesday": "Merkredo", + "@wednesday": { + "type": "text", + "placeholders": {} + }, + "warningEncryptionInBeta": "Tutvoja ĉifrado estas ankoraŭ beta-versia! Uzu je via propra risko!", + "@warningEncryptionInBeta": { + "type": "text", + "placeholders": {} + }, + "wallpaper": "Fonbildo", + "@wallpaper": { + "type": "text", + "placeholders": {} + }, + "warning": "Averto!", + "@warning": { + "type": "text", + "placeholders": {} + }, + "voiceMessage": "Voĉmesaĝo", + "@voiceMessage": { + "type": "text", + "placeholders": {} + }, + "videoCall": "Vidvoko", + "@videoCall": { + "type": "text", + "placeholders": {} + }, + "verifyUser": "Kontroli uzanton", + "@verifyUser": { + "type": "text", + "placeholders": {} + }, + "verifyTitle": "Kontrolante alian konton", + "@verifyTitle": { + "type": "text", + "placeholders": {} + }, + "verifySuccess": "Vi sukcese kontrolis!", + "@verifySuccess": { + "type": "text", + "placeholders": {} + }, + "verifyStart": "Komenci kontrolon", + "@verifyStart": { + "type": "text", + "placeholders": {} + }, + "verifiedSession": "Sukcese kontrolis salutaĵon!", + "@verifiedSession": { + "type": "text", + "placeholders": {} + }, + "verifyManual": "Kontroli permane", + "@verifyManual": { + "type": "text", + "placeholders": {} + }, + "verify": "Kontroli", + "@verify": { + "type": "text", + "placeholders": {} + }, + "userSentUnknownEvent": "{username} sendis eventon de speco {type}", + "@userSentUnknownEvent": { + "type": "text", + "placeholders": { + "username": {}, + "type": {} + } + }, + "userIsTyping": "{username} tajpas…", + "@userIsTyping": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "username": "Uzantonomo", + "@username": { + "type": "text", + "placeholders": {} + }, + "userAndUserAreTyping": "{username} kaj {username2} tajpas…", + "@userAndUserAreTyping": { + "type": "text", + "placeholders": { + "username": {}, + "username2": {} + } + }, + "unknownEvent": "Nekonata evento «{type}»", + "@unknownEvent": { + "type": "text", + "placeholders": { + "type": {} + } + }, + "unknownSessionVerify": "Nekonata salutaĵo; bonvolu kontroli", + "@unknownSessionVerify": { + "type": "text", + "placeholders": {} + }, + "unknownEncryptionAlgorithm": "Nekonata ĉifra algoritmo", + "@unknownEncryptionAlgorithm": { + "type": "text", + "placeholders": {} + }, + "unknownDevice": "Nekonata aparato", + "@unknownDevice": { + "type": "text", + "placeholders": {} + }, + "unmuteChat": "Malsilentigi babilon", + "@unmuteChat": { + "type": "text", + "placeholders": {} + }, + "unblockDevice": "Malbloki aparaton", + "@unblockDevice": { + "type": "text", + "placeholders": {} + }, + "unbannedUser": "{username} malforbaris uzanton {targetName}", + "@unbannedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "tuesday": "Mardo", + "@tuesday": { + "type": "text", + "placeholders": {} + }, + "tryToSendAgain": "Reprovi sendi", + "@tryToSendAgain": { + "type": "text", + "placeholders": {} + }, + "title": "FluffyChat", + "@title": { + "description": "Title for the application", + "type": "text", + "placeholders": {} + }, + "thursday": "Ĵaŭdo", + "@thursday": { + "type": "text", + "placeholders": {} + }, + "theyMatch": "Ili akordas", + "@theyMatch": { + "type": "text", + "placeholders": {} + }, + "theyDontMatch": "Ili ne akordas", + "@theyDontMatch": { + "type": "text", + "placeholders": {} + }, + "tapToShowMenu": "Tuŝetu por montri menuon", + "@tapToShowMenu": { + "type": "text", + "placeholders": {} + }, + "donate": "Donaci", + "@donate": { + "type": "text", + "placeholders": {} + }, + "sunday": "Dimanĉo", + "@sunday": { + "type": "text", + "placeholders": {} + }, + "submit": "Sendi", + "@submit": { + "type": "text", + "placeholders": {} + }, + "startYourFirstChat": "Komencu vian unuan babilon :-)", + "@startYourFirstChat": { + "type": "text", + "placeholders": {} + }, + "statusExampleMessage": "Kiel vi fartas?", + "@statusExampleMessage": { + "type": "text", + "placeholders": {} + }, + "startedACall": "{senderName} komencis vokon", + "@startedACall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "signUp": "Registriĝi", + "@signUp": { + "type": "text", + "placeholders": {} + }, + "setInvitationLink": "Agordi invitan ligilon", + "@setInvitationLink": { + "type": "text", + "placeholders": {} + }, + "setGroupDescription": "Agordi priskribon de grupo", + "@setGroupDescription": { + "type": "text", + "placeholders": {} + }, + "setAProfilePicture": "Agordi profilbildon", + "@setAProfilePicture": { + "type": "text", + "placeholders": {} + }, + "sessionVerified": "Salutaĵo estas kontrolita", + "@sessionVerified": { + "type": "text", + "placeholders": {} + }, + "sentAVideo": "{username} sendis filmon", + "@sentAVideo": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentASticker": "{username} sendis glumarkon", + "@sentASticker": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAPicture": "{username} sendis bildon", + "@sentAPicture": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAFile": "{username} sendis dosieron", + "@sentAFile": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sendVideo": "Sendi filmon", + "@sendVideo": { + "type": "text", + "placeholders": {} + }, + "sendOriginal": "Sendi originalon", + "@sendOriginal": { + "type": "text", + "placeholders": {} + }, + "sendImage": "Sendi bildon", + "@sendImage": { + "type": "text", + "placeholders": {} + }, + "sendFile": "Sendi dosieron", + "@sendFile": { + "type": "text", + "placeholders": {} + }, + "sendAMessage": "Sendi mesaĝon", + "@sendAMessage": { + "type": "text", + "placeholders": {} + }, + "send": "Sendi", + "@send": { + "type": "text", + "placeholders": {} + }, + "no": "Ne", + "@no": { + "type": "text", + "placeholders": {} + }, + "changesHaveBeenSaved": "Ŝanĝoj konserviĝis", + "@changesHaveBeenSaved": { + "type": "text", + "placeholders": {} + }, + "sentryInfo": "Informoj pri via privateco: https://sentry.io/security/", + "@sentryInfo": { + "type": "text", + "placeholders": {} + }, + "lastSeenLongTimeAgo": "Vidita antaŭ longe", + "@lastSeenLongTimeAgo": { + "type": "text", + "placeholders": {} + }, + "searchForAChat": "Serĉi babilon", + "@searchForAChat": { + "type": "text", + "placeholders": {} + }, + "saturday": "Sabato", + "@saturday": { + "type": "text", + "placeholders": {} + }, + "roomHasBeenUpgraded": "Ĉambro gradaltiĝis", + "@roomHasBeenUpgraded": { + "type": "text", + "placeholders": {} + }, + "requestToReadOlderMessages": "Peti legi pli malnovajn mesaĝojn", + "@requestToReadOlderMessages": { + "type": "text", + "placeholders": {} + }, + "requestPermission": "Peti permeson", + "@requestPermission": { + "type": "text", + "placeholders": {} + }, + "reply": "Respondi", + "@reply": { + "type": "text", + "placeholders": {} + }, + "removeMessage": "Forigi mesaĝon", + "@removeMessage": { + "type": "text", + "placeholders": {} + }, + "remove": "Forigi", + "@remove": { + "type": "text", + "placeholders": {} + }, + "revokeAllPermissions": "Nuligi ĉiujn permesojn", + "@revokeAllPermissions": { + "type": "text", + "placeholders": {} + }, + "removeDevice": "Forigi aparaton", + "@removeDevice": { + "type": "text", + "placeholders": {} + }, + "removedBy": "Forigita de {username}", + "@removedBy": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "redactedAnEvent": "{username} redaktis eventon", + "@redactedAnEvent": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "rejoin": "Ree aliĝi", + "@rejoin": { + "type": "text", + "placeholders": {} + }, + "publicRooms": "Publikaj ĉambroj", + "@publicRooms": { + "type": "text", + "placeholders": {} + }, + "privacy": "Privateco", + "@privacy": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourUsername": "Bonvolu enigi vian uzantonomon", + "@pleaseEnterYourUsername": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourPassword": "Bonvolu enigi vian pasvorton", + "@pleaseEnterYourPassword": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterAMatrixIdentifier": "Bonvolu enigi identigilon de Matrix", + "@pleaseEnterAMatrixIdentifier": { + "type": "text", + "placeholders": {} + }, + "pleaseChooseAUsername": "Bonvolu elekti uzantonomon", + "@pleaseChooseAUsername": { + "type": "text", + "placeholders": {} + }, + "passwordHasBeenChanged": "Pasvorto ŝanĝiĝis", + "@passwordHasBeenChanged": { + "type": "text", + "placeholders": {} + }, + "password": "Pasvorto", + "@password": { + "type": "text", + "placeholders": {} + }, + "passphraseOrKey": "pasfrazo aŭ rehava ŝlosilo", + "@passphraseOrKey": { + "type": "text", + "placeholders": {} + }, + "openAppToReadMessages": "Malfermu la aplikaĵon por legi mesaĝojn", + "@openAppToReadMessages": { + "type": "text", + "placeholders": {} + }, + "oopsSomethingWentWrong": "Oj! Io misokazis…", + "@oopsSomethingWentWrong": { + "type": "text", + "placeholders": {} + }, + "ok": "bone", + "@ok": { + "type": "text", + "placeholders": {} + }, + "noRoomsFound": "Neniuj ĉambroj troviĝis…", + "@noRoomsFound": { + "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 programaron microG: https://microg.org/", + "@noGoogleServicesWarning": { + "type": "text", + "placeholders": {} + }, + "newVerificationRequest": "Nova kontrolpeto!", + "@newVerificationRequest": { + "type": "text", + "placeholders": {} + }, + "newPrivateChat": "Nova privata babilo", + "@newPrivateChat": { + "type": "text", + "placeholders": {} + }, + "newMessageInFluffyChat": "Nova mesaĝo en FluffyChat", + "@newMessageInFluffyChat": { + "type": "text", + "placeholders": {} + }, + "muteChat": "Silentigi babilon", + "@muteChat": { + "type": "text", + "placeholders": {} + }, + "monday": "Lundo", + "@monday": { + "type": "text", + "placeholders": {} + }, + "moderator": "Reguligisto", + "@moderator": { + "type": "text", + "placeholders": {} + }, + "makeSureTheIdentifierIsValid": "Certigu, ke la identigilo estas valida", + "@makeSureTheIdentifierIsValid": { + "type": "text", + "placeholders": {} + }, + "logInTo": "Saluti servilon {homeserver}", + "@logInTo": { + "type": "text", + "placeholders": { + "homeserver": {} + } + }, + "login": "Saluti", + "@login": { + "type": "text", + "placeholders": {} + }, + "loadingPleaseWait": "Enlegante… bonvolu atendi", + "@loadingPleaseWait": { + "type": "text", + "placeholders": {} + }, + "license": "Permesilo", + "@license": { + "type": "text", + "placeholders": {} + }, + "userLeftTheChat": "{username} foriris de la babilo", + "@userLeftTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "logout": "Adiaŭi", + "@logout": { + "type": "text", + "placeholders": {} + }, + "leftTheChat": "Foriris de la ĉambro", + "@leftTheChat": { + "type": "text", + "placeholders": {} + }, + "leave": "Foriri", + "@leave": { + "type": "text", + "placeholders": {} + }, + "kickFromChat": "Forpeli de babilo", + "@kickFromChat": { + "type": "text", + "placeholders": {} + }, + "kickedAndBanned": "{username} forpelis kaj forbaris uzanton {targetName}", + "@kickedAndBanned": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "kicked": "{username} forpelis uzanton {targetName}", + "@kicked": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "keysMissing": "Ŝlosiloj mankas", + "@keysMissing": { + "type": "text", + "placeholders": {} + }, + "keysCached": "Ŝlosiloj estas kaŝmemoritaj", + "@keysCached": { + "type": "text", + "placeholders": {} + }, + "joinRoom": "Aliĝi al ĉambro", + "@joinRoom": { + "type": "text", + "placeholders": {} + }, + "joinedTheChat": "{username} aliĝis al la babilo", + "@joinedTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "isTyping": "tajpas…", + "@isTyping": { + "type": "text", + "placeholders": {} + }, + "invitedUser": "{username} invitis uzanton {targetName}", + "@invitedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "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": {} + } + }, + "inviteContactToGroup": "Inviti kontakton al {groupName}", + "@inviteContactToGroup": { + "type": "text", + "placeholders": { + "groupName": {} + } + }, + "inviteContact": "Inviti kontakton", + "@inviteContact": { + "type": "text", + "placeholders": {} + }, + "incorrectPassphraseOrKey": "Neĝusta pasfrazo aŭ rehava ŝlosilo", + "@incorrectPassphraseOrKey": { + "type": "text", + "placeholders": {} + }, + "identity": "Identeco", + "@identity": { + "type": "text", + "placeholders": {} + }, + "id": "Identigilo", + "@id": { + "type": "text", + "placeholders": {} + }, + "homeserverIsNotCompatible": "Hejmservilo ne estas interkonforma", + "@homeserverIsNotCompatible": { + "type": "text", + "placeholders": {} + }, + "help": "Helpo", + "@help": { + "type": "text", + "placeholders": {} + }, + "hasWithdrawnTheInvitationFor": "{username} nuligis la inviton por {targetName}", + "@hasWithdrawnTheInvitationFor": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "guestsCanJoin": "Gastoj povas aliĝi", + "@guestsCanJoin": { + "type": "text", + "placeholders": {} + }, + "groupWith": "Grupo kun {displayname}", + "@groupWith": { + "type": "text", + "placeholders": { + "displayname": {} + } + }, + "groupIsPublic": "Grupo estas publika", + "@groupIsPublic": { + "type": "text", + "placeholders": {} + }, + "groupDescriptionHasBeenChanged": "Priskribo de grupo ŝanĝiĝis", + "@groupDescriptionHasBeenChanged": { + "type": "text", + "placeholders": {} + }, + "groupDescription": "Priskribo de grupo", + "@groupDescription": { + "type": "text", + "placeholders": {} + }, + "group": "Grupo", + "@group": { + "type": "text", + "placeholders": {} + }, + "fromTheInvitation": "Ekde la invito", + "@fromTheInvitation": { + "type": "text", + "placeholders": {} + }, + "fromJoining": "Ekde aliĝo", + "@fromJoining": { + "type": "text", + "placeholders": {} + }, + "friday": "Vendredo", + "@friday": { + "type": "text", + "placeholders": {} + }, + "forward": "Plusendi", + "@forward": { + "type": "text", + "placeholders": {} + }, + "fluffychat": "FluffyChat", + "@fluffychat": { + "type": "text", + "placeholders": {} + }, + "fileSize": "Grandeco de dosiero", + "@fileSize": { + "type": "text", + "placeholders": {} + }, + "fileName": "Dosiernomo", + "@fileName": { + "type": "text", + "placeholders": {} + }, + "enterYourHomeserver": "Enigu vian hejmservilon", + "@enterYourHomeserver": { + "type": "text", + "placeholders": {} + }, + "enterAUsername": "Enigu uzantonomon", + "@enterAUsername": { + "type": "text", + "placeholders": {} + }, + "enterAGroupName": "Enigu nomon de grupo", + "@enterAGroupName": { + "type": "text", + "placeholders": {} + }, + "endedTheCall": "{senderName} finis la vokon", + "@endedTheCall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "encryptionNotEnabled": "Ĉifrado ne estas ŝaltita", + "@encryptionNotEnabled": { + "type": "text", + "placeholders": {} + }, + "encryptionAlgorithm": "Ĉifra algoritmo", + "@encryptionAlgorithm": { + "type": "text", + "placeholders": {} + }, + "encryption": "Ĉifrado", + "@encryption": { + "type": "text", + "placeholders": {} + }, + "enableEncryptionWarning": "Vi ne povos malŝalti la ĉifradon. Ĉu vi certas?", + "@enableEncryptionWarning": { + "type": "text", + "placeholders": {} + }, + "emptyChat": "Malplena babilo", + "@emptyChat": { + "type": "text", + "placeholders": {} + }, + "editDisplayname": "Redakti prezentan nomon", + "@editDisplayname": { + "type": "text", + "placeholders": {} + }, + "downloadFile": "Elŝuti dosieron", + "@downloadFile": { + "type": "text", + "placeholders": {} + }, + "displaynameHasBeenChanged": "Prezenta nomo ŝanĝiĝis", + "@displaynameHasBeenChanged": { + "type": "text", + "placeholders": {} + }, + "devices": "Aparatoj", + "@devices": { + "type": "text", + "placeholders": {} + }, + "device": "Aparato", + "@device": { + "type": "text", + "placeholders": {} + }, + "deny": "Malakcepti", + "@deny": { + "type": "text", + "placeholders": {} + }, + "deleteMessage": "Forigi mesaĝon", + "@deleteMessage": { + "type": "text", + "placeholders": {} + }, + "deleteAccount": "Forigi konton", + "@deleteAccount": { + "type": "text", + "placeholders": {} + }, + "deactivateAccountWarning": "Ĉi tio malaktivigos vian konton de uzanto. Ne eblas tion malfari! Ĉu certe vi certas?", + "@deactivateAccountWarning": { + "type": "text", + "placeholders": {} + }, + "delete": "Forigi", + "@delete": { + "type": "text", + "placeholders": {} + }, + "dateAndTimeOfDay": "{date}, {timeOfDay}", + "@dateAndTimeOfDay": { + "type": "text", + "placeholders": { + "date": {}, + "timeOfDay": {} + } + }, + "crossSigningEnabled": "Delegaj subskriboj estas ŝaltitaj", + "@crossSigningEnabled": { + "type": "text", + "placeholders": {} + }, + "crossSigningDisabled": "Delegaj subskriboj estas malŝaltitaj", + "@crossSigningDisabled": { + "type": "text", + "placeholders": {} + }, + "createdTheChat": "{username} kreis la babilon", + "@createdTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "createAccountNow": "Krei konton nun", + "@createAccountNow": { + "type": "text", + "placeholders": {} + }, + "create": "Krei", + "@create": { + "type": "text", + "placeholders": {} + }, + "countParticipants": "{count} partoprenantoj", + "@countParticipants": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "couldNotSetDisplayname": "Ne povis agordi prezentan nomon", + "@couldNotSetDisplayname": { + "type": "text", + "placeholders": {} + }, + "couldNotSetAvatar": "Ne povis agordi profilbildon", + "@couldNotSetAvatar": { + "type": "text", + "placeholders": {} + }, + "couldNotDecryptMessage": "Ne povis malĉifri mesaĝon: {error}", + "@couldNotDecryptMessage": { + "type": "text", + "placeholders": { + "error": {} + } + }, + "copy": "Kopii", + "@copy": { + "type": "text", + "placeholders": {} + }, + "copiedToClipboard": "Kopiite al tondujo", + "@copiedToClipboard": { + "type": "text", + "placeholders": {} + }, + "contentViewer": "Vidilo de enhavo", + "@contentViewer": { + "type": "text", + "placeholders": {} + }, + "contactHasBeenInvitedToTheGroup": "Kontakto invitiĝis al la grupo", + "@contactHasBeenInvitedToTheGroup": { + "type": "text", + "placeholders": {} + }, + "connectionAttemptFailed": "Malsukcesis provo konektiĝi", + "@connectionAttemptFailed": { + "type": "text", + "placeholders": {} + }, + "connect": "Konektiĝi", + "@connect": { + "type": "text", + "placeholders": {} + }, + "confirm": "Konfirmi", + "@confirm": { + "type": "text", + "placeholders": {} + }, + "compareNumbersMatch": "Komparu kaj certigu, ke la jenaj numeroj samas en ambaŭ aparatoj:", + "@compareNumbersMatch": { + "type": "text", + "placeholders": {} + }, + "compareEmojiMatch": "Komparu kaj certigu, ke la jenaj bildosignoj samas en ambaŭ aparatoj:", + "@compareEmojiMatch": { + "type": "text", + "placeholders": {} + }, + "close": "Fermi", + "@close": { + "type": "text", + "placeholders": {} + }, + "chooseAUsername": "Elektu uzantonomon", + "@chooseAUsername": { + "type": "text", + "placeholders": {} + }, + "chooseAStrongPassword": "Elektu fortan pasvorton", + "@chooseAStrongPassword": { + "type": "text", + "placeholders": {} + }, + "chatDetails": "Detaloj pri babilo", + "@chatDetails": { + "type": "text", + "placeholders": {} + }, + "chat": "Babilo", + "@chat": { + "type": "text", + "placeholders": {} + }, + "channelCorruptedDecryptError": "La ĉifrado estas difektita", + "@channelCorruptedDecryptError": { + "type": "text", + "placeholders": {} + }, + "changeTheServer": "Ŝanĝi la servilon", + "@changeTheServer": { + "type": "text", + "placeholders": {} + }, + "changeWallpaper": "Ŝanĝi fonbildon", + "@changeWallpaper": { + "type": "text", + "placeholders": {} + }, + "changeTheNameOfTheGroup": "Ŝanĝi nomon de la grupo", + "@changeTheNameOfTheGroup": { + "type": "text", + "placeholders": {} + }, + "changelog": "Protokolo de ŝanĝoj", + "@changelog": { + "type": "text", + "placeholders": {} + }, + "changedTheRoomInvitationLink": "{username} ŝanĝis la invitan ligilon", + "@changedTheRoomInvitationLink": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheRoomAliases": "{username} ŝanĝis la kromnomojn de la ĉambro", + "@changedTheRoomAliases": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheProfileAvatar": "{username} ŝanĝis sian profilbildon", + "@changedTheProfileAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheJoinRulesTo": "{username} ŝanĝis regulojn pri aliĝado al: {joinRules}", + "@changedTheJoinRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "joinRules": {} + } + }, + "changedTheJoinRules": "{username} ŝanĝis regulojn pri aliĝado", + "@changedTheJoinRules": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheHistoryVisibilityTo": "{username} ŝanĝis videblecon de la historio al: {rules}", + "@changedTheHistoryVisibilityTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "changedTheHistoryVisibility": "{username} ŝanĝis videblecon de la historio", + "@changedTheHistoryVisibility": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheGuestAccessRulesTo": "{username} ŝanĝis regulojn pri aliro de gastoj al: {rules}", + "@changedTheGuestAccessRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "changedTheGuestAccessRules": "{username} ŝanĝis regulojn pri aliro de gastoj", + "@changedTheGuestAccessRules": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changeTheHomeserver": "Ŝanĝi hejmservilon", + "@changeTheHomeserver": { + "type": "text", + "placeholders": {} + }, + "changedTheDisplaynameTo": "{username} ŝanĝis la prezentan nomon al: {username}", + "@changedTheDisplaynameTo": { + "type": "text", + "placeholders": { + "username": {}, + "displayname": {} + } + }, + "changedTheChatPermissions": "{username} ŝanĝis permesojn pri la babilo", + "@changedTheChatPermissions": { + "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": {} + } + }, + "changedTheChatAvatar": "{username} ŝanĝis bildon de la babilo", + "@changedTheChatAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changeDeviceName": "Ŝanĝi nomon de aparato", + "@changeDeviceName": { + "type": "text", + "placeholders": {} + }, + "cancel": "Nuligi", + "@cancel": { + "type": "text", + "placeholders": {} + }, + "cachedKeys": "Sukcese kaŝmemoris ŝlosilojn!", + "@cachedKeys": { + "type": "text", + "placeholders": {} + }, + "byDefaultYouWillBeConnectedTo": "Implice vi konektiĝos al {homeserver}", + "@byDefaultYouWillBeConnectedTo": { + "type": "text", + "placeholders": { + "homeserver": {} + } + }, + "blockDevice": "Bloki aparaton", + "@blockDevice": { + "type": "text", + "placeholders": {} + }, + "bannedUser": "{username} forbaris uzanton {targetName}", + "@bannedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "banned": "Forbarita", + "@banned": { + "type": "text", + "placeholders": {} + }, + "banFromChat": "Forbari de babilo", + "@banFromChat": { + "type": "text", + "placeholders": {} + }, + "askSSSSVerify": "Bonvolu enigi pasfrazon de via sekura deponejo aŭ vian rehavan ŝlosilon por kontroli vian salutaĵon.", + "@askSSSSVerify": { + "type": "text", + "placeholders": {} + }, + "askSSSSCache": "Bonvolu enigi pasfrazon de via sekura deponejo aŭ rehavan ŝlosilon por kaŝmemori la ŝlosilojn.", + "@askSSSSCache": { + "type": "text", + "placeholders": {} + }, + "areGuestsAllowedToJoin": "Ĉu gastoj rajtas aliĝi", + "@areGuestsAllowedToJoin": { + "type": "text", + "placeholders": {} + }, + "anyoneCanJoin": "Ĉiu ajn povas aliĝi", + "@anyoneCanJoin": { + "type": "text", + "placeholders": {} + }, + "avatarHasBeenChanged": "Profilbildo ŝanĝiĝis", + "@avatarHasBeenChanged": { + "type": "text", + "placeholders": {} + }, + "askVerificationRequest": "Ĉu akcepti ĉi tiun kontrolpeton de {username}?", + "@askVerificationRequest": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "areYouSure": "Ĉu vi certas?", + "@areYouSure": { + "type": "text", + "placeholders": {} + }, + "answeredTheCall": "{senderName} respondis la vokon", + "@answeredTheCall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "alreadyHaveAnAccount": "Ĉu vi jam havas konton?", + "@alreadyHaveAnAccount": { + "type": "text", + "placeholders": {} + }, + "admin": "Administranto", + "@admin": { + "type": "text", + "placeholders": {} + }, + "addGroupDescription": "Aldoni priskribon de grupo", + "@addGroupDescription": { + "type": "text", + "placeholders": {} + }, + "activatedEndToEndEncryption": "{username} aktivigis tutvojan ĉifradon", + "@activatedEndToEndEncryption": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "accountInformation": "Informoj pri konto", + "@accountInformation": { + "type": "text", + "placeholders": {} + }, + "account": "Konto", + "@account": { + "type": "text", + "placeholders": {} + }, + "acceptedTheInvitation": "{username} akceptis la inviton", + "@acceptedTheInvitation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "accept": "Akcepti", + "@accept": { + "type": "text", + "placeholders": {} + }, + "about": "Prio", + "@about": { + "type": "text", + "placeholders": {} + }, + "thisRoomHasBeenArchived": "Ĉi tiu ĉambro arĥiviĝis.", + "@thisRoomHasBeenArchived": { + "type": "text", + "placeholders": {} + }, + "archivedRoom": "Arĥivita ĉambro", + "@archivedRoom": { + "type": "text", + "placeholders": {} + }, + "sharedTheLocation": "{username} konigis sian lokon", + "@sharedTheLocation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "isDeviceKeyCorrect": "Ĉu la jena identigilo de aparato estas ĝusta?", + "@isDeviceKeyCorrect": { + "type": "text", + "placeholders": {} + }, + "currentlyActive": "Nun aktiva", + "@currentlyActive": { + "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": {} + }, + "share": "Konigi", + "@share": { + "type": "text", + "placeholders": {} + }, + "none": "Neniu", + "@none": { + "type": "text", + "placeholders": {} + }, + "editJitsiInstance": "Redakti provizanton de Jitsi", + "@editJitsiInstance": { + "type": "text", + "placeholders": {} + }, + "invited": "Invitita", + "@invited": { + "type": "text", + "placeholders": {} + }, + "renderRichContent": "Bildigi riĉforman enhavon de mesaĝoj", + "@renderRichContent": { + "type": "text", + "placeholders": {} + }, + "onlineKeyBackupEnabled": "Enreta savkopiado de ŝlosiloj estas ŝaltita", + "@onlineKeyBackupEnabled": { + "type": "text", + "placeholders": {} + }, + "onlineKeyBackupDisabled": "Enreta savkopiado de ŝlosiloj estas malŝaltita", + "@onlineKeyBackupDisabled": { + "type": "text", + "placeholders": {} + }, + "notSupportedInWeb": "Nesubtenata de la TTT-versio", + "@notSupportedInWeb": { + "type": "text", + "placeholders": {} + }, + "noMegolmBootstrap": "FluffyChat ankoraŭ ne subtenas ŝaltadon de Enreta savkopiado de ŝlosiloj. Bonvolu ŝalti ĝin per Riot (Element).", + "@noMegolmBootstrap": { + "type": "text", + "placeholders": {} + }, + "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": {} + } + }, + "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": {} + }, + "waitingPartnerAcceptRequest": "Atendante konfirmon de peto de la kunulo…", + "@waitingPartnerAcceptRequest": { + "type": "text", + "placeholders": {} + }, + "visibilityOfTheChatHistory": "Videbleco de historio de la babilo", + "@visibilityOfTheChatHistory": { + "type": "text", + "placeholders": {} + }, + "visibleForEveryone": "Videbla al ĉiuj", + "@visibleForEveryone": { + "type": "text", + "placeholders": {} + }, + "visibleForAllParticipants": "Videbla al ĉiuj partoprenantoj", + "@visibleForAllParticipants": { + "type": "text", + "placeholders": {} + }, + "userAndOthersAreTyping": "{username} kaj {count} aliaj tajpas…", + "@userAndOthersAreTyping": { + "type": "text", + "placeholders": { + "username": {}, + "count": {} + } + }, + "unreadMessagesInChats": "{unreadEvents} nelegitaj mesaĝoj en {unreadChats} babiloj", + "@unreadMessagesInChats": { + "type": "text", + "placeholders": { + "unreadEvents": {}, + "unreadChats": {} + } + }, + "unreadMessages": "{unreadEvents} nelegitaj mesaĝoj", + "@unreadMessages": { + "type": "text", + "placeholders": { + "unreadEvents": {} + } + }, + "unreadChats": "{unreadCount} nelegitaj babiloj", + "@unreadChats": { + "type": "text", + "placeholders": { + "unreadCount": {} + } + }, + "unpin": "Malfiksi", + "@unpin": { + "type": "text", + "placeholders": {} + }, + "timeOfDay": "{hours24}:{minutes}", + "@timeOfDay": { + "type": "text", + "placeholders": { + "hours12": {}, + "hours24": {}, + "minutes": {}, + "suffix": {} + } + }, + "sourceCode": "Fontkodo", + "@sourceCode": { + "type": "text", + "placeholders": {} + }, + "useAmoledTheme": "Ĉu uzi kolorojn adaptitajn por AMOLED?", + "@useAmoledTheme": { + "type": "text", + "placeholders": {} + }, + "darkTheme": "Malhela", + "@darkTheme": { + "type": "text", + "placeholders": {} + }, + "lightTheme": "Hela", + "@lightTheme": { + "type": "text", + "placeholders": {} + }, + "systemTheme": "Sistema", + "@systemTheme": { + "type": "text", + "placeholders": {} + }, + "changeTheme": "Ŝanĝu la haŭton", + "@changeTheme": { + "type": "text", + "placeholders": {} + }, + "skip": "Preterpasi", + "@skip": { + "type": "text", + "placeholders": {} + }, + "sentCallInformations": "{senderName} sendis informojn pri voko", + "@sentCallInformations": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "sentAnAudio": "{username} sendis sondosieron", + "@sentAnAudio": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sendAudio": "Sendi sondosieron", + "@sendAudio": { + "type": "text", + "placeholders": {} + }, + "seenByUserAndCountOthers": "Vidita de {username} kaj {count} aliaj", + "@seenByUserAndCountOthers": { + "type": "text", + "placeholders": { + "username": {}, + "count": {} + } + }, + "seenByUserAndUser": "Vidita de {username} kaj {username2}", + "@seenByUserAndUser": { + "type": "text", + "placeholders": { + "username": {}, + "username2": {} + } + }, + "seenByUser": "Vidita de {username}", + "@seenByUser": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sendBugReports": "Permesi raportadon de eraroj per sentry.io", + "@sendBugReports": { + "type": "text", + "placeholders": {} + }, + "removeExile": "Malforbari", + "@removeExile": { + "type": "text", + "placeholders": {} + }, + "removeAllOtherDevices": "Forigi ĉiujn aliajn aparatojn", + "@removeAllOtherDevices": { + "type": "text", + "placeholders": {} + }, + "rejectedTheInvitation": "{username} rifuzis la inviton", + "@rejectedTheInvitation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "recording": "Registrante", + "@recording": { + "type": "text", + "placeholders": {} + }, + "reject": "Rifuzi", + "@reject": { + "type": "text", + "placeholders": {} + }, + "play": "Ludi {fileName}", + "@play": { + "type": "text", + "placeholders": { + "fileName": {} + } + }, + "pin": "Fiksi", + "@pin": { + "type": "text", + "placeholders": {} + }, + "pickImage": "Elekti bildon", + "@pickImage": { + "type": "text", + "placeholders": {} + }, + "archive": "Arĥivo", + "@archive": { + "type": "text", + "placeholders": {} + }, + "settings": "Agordoj", + "@settings": { + "type": "text", + "placeholders": {} + }, + "end2endEncryptionSettings": "Agordoj pri tutvoja ĉifrado", + "@end2endEncryptionSettings": { + "type": "text", + "placeholders": {} + }, + "createNewGroup": "Krei novan grupon", + "@createNewGroup": { + "type": "text", + "placeholders": {} + }, + "setStatus": "Agordi staton", + "@setStatus": { + "type": "text", + "placeholders": {} + }, + "welcomeText": "Bonvenu al la plej ĉarma tujmesaĝilo en la reto de Matrix.", + "@welcomeText": { + "type": "text", + "placeholders": {} + } +} diff --git a/lib/l10n/intl_et.arb b/lib/l10n/intl_et.arb index 53f0b4c2..f05cab50 100644 --- a/lib/l10n/intl_et.arb +++ b/lib/l10n/intl_et.arb @@ -1722,5 +1722,25 @@ "@changeDeviceName": { "type": "text", "placeholders": {} + }, + "mention": "Märgi ära", + "@mention": { + "type": "text", + "placeholders": {} + }, + "offline": "Väljas", + "@offline": { + "type": "text", + "placeholders": {} + }, + "unavailable": "Eemal", + "@unavailable": { + "type": "text", + "placeholders": {} + }, + "online": "Saadaval", + "@online": { + "type": "text", + "placeholders": {} } } diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index caf51426..0b51d554 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -1722,5 +1722,25 @@ "@changeDeviceName": { "type": "text", "placeholders": {} + }, + "unavailable": "Indisponible", + "@unavailable": { + "type": "text", + "placeholders": {} + }, + "offline": "Hors ligne", + "@offline": { + "type": "text", + "placeholders": {} + }, + "online": "En ligne", + "@online": { + "type": "text", + "placeholders": {} + }, + "mention": "Mention", + "@mention": { + "type": "text", + "placeholders": {} } } diff --git a/lib/l10n/intl_gl.arb b/lib/l10n/intl_gl.arb index 313197db..832a11e1 100644 --- a/lib/l10n/intl_gl.arb +++ b/lib/l10n/intl_gl.arb @@ -769,7 +769,7 @@ "type": "text", "placeholders": {} }, - "isTyping": "está escribindo...", + "isTyping": "está escribindo…", "@isTyping": { "type": "text", "placeholders": {} @@ -861,12 +861,12 @@ "count": {} } }, - "loadingPleaseWait": "Cargando... Agarda", + "loadingPleaseWait": "Cargando... Agarda.", "@loadingPleaseWait": { "type": "text", "placeholders": {} }, - "loadMore": "Cargar máis...", + "loadMore": "Cargar máis…", "@loadMore": { "type": "text", "placeholders": {} @@ -978,7 +978,7 @@ "type": "text", "placeholders": {} }, - "noRoomsFound": "Non se atoparon salas...", + "noRoomsFound": "Non se atoparon salas…", "@noRoomsFound": { "type": "text", "placeholders": {} @@ -1010,7 +1010,7 @@ "type": "text", "placeholders": {} }, - "oopsSomethingWentWrong": "Ooooi, algo fallou...", + "oopsSomethingWentWrong": "Ooooi, algo fallou…", "@oopsSomethingWentWrong": { "type": "text", "placeholders": {} @@ -1515,7 +1515,7 @@ "type": "text", "placeholders": {} }, - "userAndOthersAreTyping": "{username} e {count} máis están escribindo...", + "userAndOthersAreTyping": "{username} e {count} máis están escribindo…", "@userAndOthersAreTyping": { "type": "text", "placeholders": { @@ -1523,7 +1523,7 @@ "count": {} } }, - "userAndUserAreTyping": "{username} e {username2} están escribindo...", + "userAndUserAreTyping": "{username} e {username2} están escribindo…", "@userAndUserAreTyping": { "type": "text", "placeholders": { @@ -1531,7 +1531,7 @@ "username2": {} } }, - "userIsTyping": "{username} está escribindo...", + "userIsTyping": "{username} está escribindo…", "@userIsTyping": { "type": "text", "placeholders": { @@ -1628,7 +1628,7 @@ "type": "text", "placeholders": {} }, - "waitingPartnerNumbers": "Agardando a que a outra parte acepte os números...", + "waitingPartnerNumbers": "Agardando a que a outra parte acepte os números…", "@waitingPartnerNumbers": { "type": "text", "placeholders": {} @@ -1653,7 +1653,7 @@ "type": "text", "placeholders": {} }, - "welcomeText": "Benvida á mensaxería instantánea más cuquiña da rede matrix.", + "welcomeText": "Benvida á mensaxería instantánea más cuquiña da rede Matrix.", "@welcomeText": { "type": "text", "placeholders": {} @@ -1663,7 +1663,7 @@ "type": "text", "placeholders": {} }, - "writeAMessage": "Escribe unha mensaxe...", + "writeAMessage": "Escribe unha mensaxe…", "@writeAMessage": { "type": "text", "placeholders": {} @@ -1722,5 +1722,25 @@ "@changeDeviceName": { "type": "text", "placeholders": {} + }, + "unavailable": "Non dispoñible", + "@unavailable": { + "type": "text", + "placeholders": {} + }, + "offline": "Desconectada", + "@offline": { + "type": "text", + "placeholders": {} + }, + "online": "En liña", + "@online": { + "type": "text", + "placeholders": {} + }, + "mention": "Mención", + "@mention": { + "type": "text", + "placeholders": {} } } diff --git a/lib/l10n/intl_hr.arb b/lib/l10n/intl_hr.arb index d8d098c3..17ff57da 100644 --- a/lib/l10n/intl_hr.arb +++ b/lib/l10n/intl_hr.arb @@ -861,12 +861,12 @@ "count": {} } }, - "loadingPleaseWait": "Učitava se … Pričekaj", + "loadingPleaseWait": "Učitava se … Pričekaj.", "@loadingPleaseWait": { "type": "text", "placeholders": {} }, - "loadMore": "Učitaj više …", + "loadMore": "Učitaj još …", "@loadMore": { "type": "text", "placeholders": {} @@ -923,7 +923,7 @@ "type": "text", "placeholders": {} }, - "needPantalaimonWarning": "Za sada trebaš Pantalaimon za obostrano šifriranje.", + "needPantalaimonWarning": "Za trenutačno korištenje obostranog šifriranja trebaš Pantalaimon.", "@needPantalaimonWarning": { "type": "text", "placeholders": {} @@ -948,7 +948,7 @@ "type": "text", "placeholders": {} }, - "noCrossSignBootstrap": "Fluffychat trenutačno ne podržava unakrsno potpisivanje. Aktiviraj je u Riot.", + "noCrossSignBootstrap": "Fluffychat trenutačno ne podržava mogućnost unakrsnog potpisivanja. Aktiviraj je u Riotu.", "@noCrossSignBootstrap": { "type": "text", "placeholders": {} @@ -963,7 +963,7 @@ "type": "text", "placeholders": {} }, - "noMegolmBootstrap": "Fluffychat trenutačno ne podržava aktiviranje online sigurnosnu kopiju ključeva. Aktiviraj je u Riot.", + "noMegolmBootstrap": "Fluffychat trenutačno ne podržava mogućnost aktiviranja internetskog ključa sigurnosnih kopija. Aktiviraj je u Riotu.", "@noMegolmBootstrap": { "type": "text", "placeholders": {} @@ -1000,12 +1000,12 @@ "type": "text", "placeholders": {} }, - "onlineKeyBackupDisabled": "Online sigurnosna kopija ključeva je deaktivirana", + "onlineKeyBackupDisabled": "Internetski ključ sigurnosnih kopija je deaktiviran", "@onlineKeyBackupDisabled": { "type": "text", "placeholders": {} }, - "onlineKeyBackupEnabled": "Online sigurnosna kopija ključeva je aktivirana", + "onlineKeyBackupEnabled": "Internetski ključ sigurnosnih kopija je aktiviran", "@onlineKeyBackupEnabled": { "type": "text", "placeholders": {} @@ -1628,7 +1628,7 @@ "type": "text", "placeholders": {} }, - "waitingPartnerNumbers": "Čekanje na partnera, da prihvati brojeve …", + "waitingPartnerNumbers": "Čeka se, da partner prihvati brojeve …", "@waitingPartnerNumbers": { "type": "text", "placeholders": {} @@ -1653,7 +1653,7 @@ "type": "text", "placeholders": {} }, - "welcomeText": "Lijep pozdrav u najslađi program za čavrljanje u mreži matrix.", + "welcomeText": "Lijep pozdrav u najslađem programu za čavrljanje u mreži Matrix.", "@welcomeText": { "type": "text", "placeholders": {} @@ -1722,5 +1722,25 @@ "@changeDeviceName": { "type": "text", "placeholders": {} + }, + "offline": "Nepovezano s internetom", + "@offline": { + "type": "text", + "placeholders": {} + }, + "online": "Povezano s internetom", + "@online": { + "type": "text", + "placeholders": {} + }, + "unavailable": "Nedostupno", + "@unavailable": { + "type": "text", + "placeholders": {} + }, + "mention": "Spominjanje", + "@mention": { + "type": "text", + "placeholders": {} } } diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index 089134ff..3ef6ff31 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -224,7 +224,7 @@ "day": {} } }, - "dateWithoutYear": "{month}-{day}", + "dateWithoutYear": "{day}/{month}", "@dateWithoutYear": { "type": "text", "placeholders": { @@ -445,19 +445,19 @@ "rules": {} } }, - "changedTheChatAvatar": "{username} ha cambiato avatar", + "changedTheChatAvatar": "{username} ha cambiato l'avatar della chat", "@changedTheChatAvatar": { "type": "text", "placeholders": { "username": {} } }, - "askSSSSSign": "Per entrare con l'altro utente, per favore inserisci la tua passphrase o recovery key.", + "askSSSSSign": "Per far accedere l'altra persona, per favore inserisci la tua frase segreta o chiave di recupero.", "@askSSSSSign": { "type": "text", "placeholders": {} }, - "askSSSSCache": "Per favore inserisci la tua passphrase o recovery key per la cache delle chiavi.", + "askSSSSCache": "Per favore inserisci la tua frase segrata o chiave di recuparo per mettere in cache le chiavi.", "@askSSSSCache": { "type": "text", "placeholders": {} @@ -578,5 +578,165 @@ "@deny": { "type": "text", "placeholders": {} + }, + "crossSigningDisabled": "La firma incrociata è disabilitata", + "@crossSigningDisabled": { + "type": "text", + "placeholders": {} + }, + "inviteText": "{username} ti ha invitato/a a FluffyChat.\n1. Installa FluffyChat: https://fluffychat.im\n2. Iscriviti o accedi\n3. Apri il collegamento di invito: {link}", + "@inviteText": { + "type": "text", + "placeholders": { + "username": {}, + "link": {} + } + }, + "invited": "Invitato/a", + "@invited": { + "type": "text", + "placeholders": {} + }, + "inviteContactToGroup": "Invita un contatto a {groupName}", + "@inviteContactToGroup": { + "type": "text", + "placeholders": { + "groupName": {} + } + }, + "inviteContact": "Invita contatto", + "@inviteContact": { + "type": "text", + "placeholders": {} + }, + "incorrectPassphraseOrKey": "Frase segrata o chiave di ripristino errate", + "@incorrectPassphraseOrKey": { + "type": "text", + "placeholders": {} + }, + "ignoreListDescription": "Puoi ignorare gli utenti che ti stanno disturbando. Non sarai in grado di ricevere messaggi o inviti a stanze virtuali dagli utenti nel tuo elenco personale da ignorare.", + "@ignoreListDescription": { + "type": "text", + "placeholders": {} + }, + "ignoreUsername": "Ignora il nome utente", + "@ignoreUsername": { + "type": "text", + "placeholders": {} + }, + "ignoredUsers": "Utenti ignorati", + "@ignoredUsers": { + "type": "text", + "placeholders": {} + }, + "identity": "Identità", + "@identity": { + "type": "text", + "placeholders": {} + }, + "id": "ID", + "@id": { + "type": "text", + "placeholders": {} + }, + "homeserverIsNotCompatible": "Il server principale non è compatibile", + "@homeserverIsNotCompatible": { + "type": "text", + "placeholders": {} + }, + "help": "Aiuto", + "@help": { + "type": "text", + "placeholders": {} + }, + "hasWithdrawnTheInvitationFor": "{username} ha ritirato l'invito per {targetName}", + "@hasWithdrawnTheInvitationFor": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + }, + "guestsCanJoin": "Gli ospiti possono partecipare", + "@guestsCanJoin": { + "type": "text", + "placeholders": {} + }, + "guestsAreForbidden": "Gli ospiti sono vietati", + "@guestsAreForbidden": { + "type": "text", + "placeholders": {} + }, + "groupWith": "Gruppo con {displayname}", + "@groupWith": { + "type": "text", + "placeholders": { + "displayname": {} + } + }, + "groupIsPublic": "Il gruppo è pubblico", + "@groupIsPublic": { + "type": "text", + "placeholders": {} + }, + "groupDescriptionHasBeenChanged": "La descrizione del gruppo è stata modificata", + "@groupDescriptionHasBeenChanged": { + "type": "text", + "placeholders": {} + }, + "groupDescription": "Descrizione del gruppo", + "@groupDescription": { + "type": "text", + "placeholders": {} + }, + "group": "Gruppo", + "@group": { + "type": "text", + "placeholders": {} + }, + "fromTheInvitation": "Dall'invito", + "@fromTheInvitation": { + "type": "text", + "placeholders": {} + }, + "fromJoining": "Dall'adesione", + "@fromJoining": { + "type": "text", + "placeholders": {} + }, + "friday": "venerdì", + "@friday": { + "type": "text", + "placeholders": {} + }, + "forward": "Inoltra", + "@forward": { + "type": "text", + "placeholders": {} + }, + "fluffychat": "FluffyChat", + "@fluffychat": { + "type": "text", + "placeholders": {} + }, + "fileSize": "Dimensione del file", + "@fileSize": { + "type": "text", + "placeholders": {} + }, + "fileName": "Nome del file", + "@fileName": { + "type": "text", + "placeholders": {} + }, + "enterYourHomeserver": "Inserisci il tuo server principale", + "@enterYourHomeserver": { + "type": "text", + "placeholders": {} + }, + "crossSigningEnabled": "La firma incrociata è abilitata", + "@crossSigningEnabled": { + "type": "text", + "placeholders": {} } } diff --git a/lib/l10n/intl_nb.arb b/lib/l10n/intl_nb.arb new file mode 100644 index 00000000..6f7feb08 --- /dev/null +++ b/lib/l10n/intl_nb.arb @@ -0,0 +1,1105 @@ +{ + "yourOwnUsername": "Ditt eget brukernavn", + "@yourOwnUsername": { + "type": "text", + "placeholders": {} + }, + "youHaveBeenBannedFromThisChat": "Du har blitt bannlyst fra denne sludringen", + "@youHaveBeenBannedFromThisChat": { + "type": "text", + "placeholders": {} + }, + "youAreInvitedToThisChat": "Du er invitert til denne sludringen", + "@youAreInvitedToThisChat": { + "type": "text", + "placeholders": {} + }, + "you": "Deg", + "@you": { + "type": "text", + "placeholders": {} + }, + "unpin": "Løsne", + "@unpin": { + "type": "text", + "placeholders": {} + }, + "unknownEncryptionAlgorithm": "Ukjent krypteringsalgoritme", + "@unknownEncryptionAlgorithm": { + "type": "text", + "placeholders": {} + }, + "unknownDevice": "Ukjent enhet", + "@unknownDevice": { + "type": "text", + "placeholders": {} + }, + "unmuteChat": "Opphev forstumming av sludring", + "@unmuteChat": { + "type": "text", + "placeholders": {} + }, + "tuesday": "Tirsdag", + "@tuesday": { + "type": "text", + "placeholders": {} + }, + "tryToSendAgain": "Prøv å sende igjen", + "@tryToSendAgain": { + "type": "text", + "placeholders": {} + }, + "title": "FluffyChat", + "@title": { + "description": "Title for the application", + "type": "text", + "placeholders": {} + }, + "thursday": "Torsdag", + "@thursday": { + "type": "text", + "placeholders": {} + }, + "tapToShowMenu": "Trykk for å vise meny", + "@tapToShowMenu": { + "type": "text", + "placeholders": {} + }, + "donate": "Doner", + "@donate": { + "type": "text", + "placeholders": {} + }, + "sunday": "Søndag", + "@sunday": { + "type": "text", + "placeholders": {} + }, + "submit": "Send inn", + "@submit": { + "type": "text", + "placeholders": {} + }, + "sourceCode": "Kildekode", + "@sourceCode": { + "type": "text", + "placeholders": {} + }, + "darkTheme": "Mørk", + "@darkTheme": { + "type": "text", + "placeholders": {} + }, + "lightTheme": "Lys", + "@lightTheme": { + "type": "text", + "placeholders": {} + }, + "statusExampleMessage": "Hvordan har du det i dag?", + "@statusExampleMessage": { + "type": "text", + "placeholders": {} + }, + "systemTheme": "System", + "@systemTheme": { + "type": "text", + "placeholders": {} + }, + "changeTheme": "Endre din stil", + "@changeTheme": { + "type": "text", + "placeholders": {} + }, + "skip": "Hopp over", + "@skip": { + "type": "text", + "placeholders": {} + }, + "signUp": "Registrer deg", + "@signUp": { + "type": "text", + "placeholders": {} + }, + "settings": "Innstilinger", + "@settings": { + "type": "text", + "placeholders": {} + }, + "setStatus": "Angi status", + "@setStatus": { + "type": "text", + "placeholders": {} + }, + "setGroupDescription": "Sett gruppebeskrivelse", + "@setGroupDescription": { + "type": "text", + "placeholders": {} + }, + "setAProfilePicture": "Sett et profilbilde", + "@setAProfilePicture": { + "type": "text", + "placeholders": {} + }, + "sentAVideo": "{username} sendte en video", + "@sentAVideo": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAPicture": "{username} sendte et bilde", + "@sentAPicture": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAnAudio": "{username} sendte lyd", + "@sentAnAudio": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sentAFile": "{username} sendte en fil", + "@sentAFile": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "sendVideo": "Send video", + "@sendVideo": { + "type": "text", + "placeholders": {} + }, + "sendOriginal": "Send original", + "@sendOriginal": { + "type": "text", + "placeholders": {} + }, + "sendImage": "Send bilde", + "@sendImage": { + "type": "text", + "placeholders": {} + }, + "sendFile": "Send fil", + "@sendFile": { + "type": "text", + "placeholders": {} + }, + "sendAudio": "Send lyd", + "@sendAudio": { + "type": "text", + "placeholders": {} + }, + "sendAMessage": "Send en melding", + "@sendAMessage": { + "type": "text", + "placeholders": {} + }, + "send": "Send", + "@send": { + "type": "text", + "placeholders": {} + }, + "seenByUserAndUser": "Sett av {username} og {username2}", + "@seenByUserAndUser": { + "type": "text", + "placeholders": { + "username": {}, + "username2": {} + } + }, + "seenByUser": "Sett av {username}", + "@seenByUser": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "no": "Nei", + "@no": { + "type": "text", + "placeholders": {} + }, + "lastSeenLongTimeAgo": "Sett for lenge siden", + "@lastSeenLongTimeAgo": { + "type": "text", + "placeholders": {} + }, + "searchForAChat": "Søk etter en sludring", + "@searchForAChat": { + "type": "text", + "placeholders": {} + }, + "share": "Del", + "@share": { + "type": "text", + "placeholders": {} + }, + "saturday": "Lørdag", + "@saturday": { + "type": "text", + "placeholders": {} + }, + "requestPermission": "Forespør tilgang", + "@requestPermission": { + "type": "text", + "placeholders": {} + }, + "reply": "Svar", + "@reply": { + "type": "text", + "placeholders": {} + }, + "removeMessage": "Fjern melding", + "@removeMessage": { + "type": "text", + "placeholders": {} + }, + "revokeAllPermissions": "Trekk tilbake alle tilganger", + "@revokeAllPermissions": { + "type": "text", + "placeholders": {} + }, + "remove": "Fjern", + "@remove": { + "type": "text", + "placeholders": {} + }, + "removeDevice": "Fjern enhet", + "@removeDevice": { + "type": "text", + "placeholders": {} + }, + "removedBy": "Fjernet av {username}", + "@removedBy": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "removeAllOtherDevices": "Fjern alle andre enheter", + "@removeAllOtherDevices": { + "type": "text", + "placeholders": {} + }, + "publicRooms": "Offentlige rom", + "@publicRooms": { + "type": "text", + "placeholders": {} + }, + "privacy": "Personvern", + "@privacy": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourUsername": "Skriv inn brukernavnet ditt", + "@pleaseEnterYourUsername": { + "type": "text", + "placeholders": {} + }, + "pleaseEnterYourPassword": "Skriv inn passordet ditt", + "@pleaseEnterYourPassword": { + "type": "text", + "placeholders": {} + }, + "pleaseChooseAUsername": "Velg et brukernavn", + "@pleaseChooseAUsername": { + "type": "text", + "placeholders": {} + }, + "play": "Spill av {fileName}", + "@play": { + "type": "text", + "placeholders": { + "fileName": {} + } + }, + "pin": "Fest", + "@pin": { + "type": "text", + "placeholders": {} + }, + "pickImage": "Velg bilde", + "@pickImage": { + "type": "text", + "placeholders": {} + }, + "passwordHasBeenChanged": "Passord endret", + "@passwordHasBeenChanged": { + "type": "text", + "placeholders": {} + }, + "password": "Passord", + "@password": { + "type": "text", + "placeholders": {} + }, + "optionalGroupName": "Gruppenavn (valgfritt)", + "@optionalGroupName": { + "type": "text", + "placeholders": {} + }, + "openCamera": "Åpne kamera", + "@openCamera": { + "type": "text", + "placeholders": {} + }, + "unavailable": "Utilgjengelig", + "@unavailable": { + "type": "text", + "placeholders": {} + }, + "offline": "Frakoblet", + "@offline": { + "type": "text", + "placeholders": {} + }, + "online": "Pålogget", + "@online": { + "type": "text", + "placeholders": {} + }, + "ok": "OK", + "@ok": { + "type": "text", + "placeholders": {} + }, + "login": "Logg inn", + "@login": { + "type": "text", + "placeholders": {} + }, + "license": "Lisens", + "@license": { + "type": "text", + "placeholders": {} + }, + "userLeftTheChat": "{username} har forlatt sludringen", + "@userLeftTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "logout": "Logg ut", + "@logout": { + "type": "text", + "placeholders": {} + }, + "leftTheChat": "Forlat sludringen", + "@leftTheChat": { + "type": "text", + "placeholders": {} + }, + "leave": "Forlat", + "@leave": { + "type": "text", + "placeholders": {} + }, + "joinRoom": "Ta del i rom", + "@joinRoom": { + "type": "text", + "placeholders": {} + }, + "joinedTheChat": "{username} tok del i sludringen", + "@joinedTheChat": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "editJitsiInstance": "Skriv inn Jitsi-instans", + "@editJitsiInstance": { + "type": "text", + "placeholders": {} + }, + "invitedUsersOnly": "Kun inviterte brukere", + "@invitedUsersOnly": { + "type": "text", + "placeholders": {} + }, + "invited": "Invitert", + "@invited": { + "type": "text", + "placeholders": {} + }, + "inviteContact": "Inviter kontakt", + "@inviteContact": { + "type": "text", + "placeholders": {} + }, + "ignoreUsername": "Ignorer brukernavn", + "@ignoreUsername": { + "type": "text", + "placeholders": {} + }, + "ignoredUsers": "Ignorerte brukere", + "@ignoredUsers": { + "type": "text", + "placeholders": {} + }, + "identity": "Identitet", + "@identity": { + "type": "text", + "placeholders": {} + }, + "id": "ID", + "@id": { + "type": "text", + "placeholders": {} + }, + "help": "Hjelp", + "@help": { + "type": "text", + "placeholders": {} + }, + "guestsAreForbidden": "Gjester forbudt", + "@guestsAreForbidden": { + "type": "text", + "placeholders": {} + }, + "guestsCanJoin": "Gjester kan ta del", + "@guestsCanJoin": { + "type": "text", + "placeholders": {} + }, + "groupIsPublic": "Gruppen er offentlig", + "@groupIsPublic": { + "type": "text", + "placeholders": {} + }, + "groupDescription": "Gruppebeskrivelse", + "@groupDescription": { + "type": "text", + "placeholders": {} + }, + "group": "Gruppe", + "@group": { + "type": "text", + "placeholders": {} + }, + "fromTheInvitation": "Fra invitasjonen", + "@fromTheInvitation": { + "type": "text", + "placeholders": {} + }, + "friday": "Fredag", + "@friday": { + "type": "text", + "placeholders": {} + }, + "forward": "Videre", + "@forward": { + "type": "text", + "placeholders": {} + }, + "fluffychat": "FluffyChat", + "@fluffychat": { + "type": "text", + "placeholders": {} + }, + "fileSize": "Filstørrelse", + "@fileSize": { + "type": "text", + "placeholders": {} + }, + "fileName": "Filnavn", + "@fileName": { + "type": "text", + "placeholders": {} + }, + "enterYourHomeserver": "Skriv inn din hjemmetjener", + "@enterYourHomeserver": { + "type": "text", + "placeholders": {} + }, + "enterAUsername": "Skriv inn et brukernavn", + "@enterAUsername": { + "type": "text", + "placeholders": {} + }, + "enterAGroupName": "Skriv inn et gruppenavn", + "@enterAGroupName": { + "type": "text", + "placeholders": {} + }, + "end2endEncryptionSettings": "Ende-til-ende -krypteringsinnstillinger", + "@end2endEncryptionSettings": { + "type": "text", + "placeholders": {} + }, + "encryptionAlgorithm": "Krypteringsalgoritme", + "@encryptionAlgorithm": { + "type": "text", + "placeholders": {} + }, + "encryption": "Kryptering", + "@encryption": { + "type": "text", + "placeholders": {} + }, + "editDisplayname": "Rediger visningsnavn", + "@editDisplayname": { + "type": "text", + "placeholders": {} + }, + "downloadFile": "Last ned fil", + "@downloadFile": { + "type": "text", + "placeholders": {} + }, + "devices": "Enheter", + "@devices": { + "type": "text", + "placeholders": {} + }, + "device": "Enhet", + "@device": { + "type": "text", + "placeholders": {} + }, + "deny": "Nekt", + "@deny": { + "type": "text", + "placeholders": {} + }, + "deleteMessage": "Slett melding", + "@deleteMessage": { + "type": "text", + "placeholders": {} + }, + "deleteAccount": "Slett konto", + "@deleteAccount": { + "type": "text", + "placeholders": {} + }, + "delete": "Slett", + "@delete": { + "type": "text", + "placeholders": {} + }, + "createAccountNow": "Opprett konto nå", + "@createAccountNow": { + "type": "text", + "placeholders": {} + }, + "create": "Opprett", + "@create": { + "type": "text", + "placeholders": {} + }, + "countParticipants": "{count} deltagere", + "@countParticipants": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "couldNotSetDisplayname": "Kunne ikke sette visningsnavn", + "@couldNotSetDisplayname": { + "type": "text", + "placeholders": {} + }, + "couldNotDecryptMessage": "Kunne ikke dekryptere melding: {error}", + "@couldNotDecryptMessage": { + "type": "text", + "placeholders": { + "error": {} + } + }, + "copy": "Kopier", + "@copy": { + "type": "text", + "placeholders": {} + }, + "copiedToClipboard": "Kopiert til utklippstavle", + "@copiedToClipboard": { + "type": "text", + "placeholders": {} + }, + "contentViewer": "Innholdsviser", + "@contentViewer": { + "type": "text", + "placeholders": {} + }, + "connect": "Koble til", + "@connect": { + "type": "text", + "placeholders": {} + }, + "confirm": "Bekreft", + "@confirm": { + "type": "text", + "placeholders": {} + }, + "close": "Lukk", + "@close": { + "type": "text", + "placeholders": {} + }, + "chooseAUsername": "Velg et brukernavn", + "@chooseAUsername": { + "type": "text", + "placeholders": {} + }, + "chooseAStrongPassword": "Velg et sterkt passord", + "@chooseAStrongPassword": { + "type": "text", + "placeholders": {} + }, + "changeTheServer": "Endre tjeneren", + "@changeTheServer": { + "type": "text", + "placeholders": {} + }, + "changeWallpaper": "Endre bakgrunnsbilde", + "@changeWallpaper": { + "type": "text", + "placeholders": {} + }, + "changeTheNameOfTheGroup": "Endre gruppens navn", + "@changeTheNameOfTheGroup": { + "type": "text", + "placeholders": {} + }, + "changelog": "Endringslogg", + "@changelog": { + "type": "text", + "placeholders": {} + }, + "changedTheGuestAccessRules": "{username} endret gjestetilgangsreglene", + "@changedTheGuestAccessRules": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changeTheHomeserver": "Endre hjemmetjener", + "@changeTheHomeserver": { + "type": "text", + "placeholders": {} + }, + "areYouSure": "Er du sikker?", + "@areYouSure": { + "type": "text", + "placeholders": {} + }, + "areGuestsAllowedToJoin": "Skal gjester tillates å ta del", + "@areGuestsAllowedToJoin": { + "type": "text", + "placeholders": {} + }, + "archivedRoom": "Arkivert rom", + "@archivedRoom": { + "type": "text", + "placeholders": {} + }, + "anyoneCanJoin": "Hvem som helst kan delta", + "@anyoneCanJoin": { + "type": "text", + "placeholders": {} + }, + "answeredTheCall": "{senderName} besvarte anropet", + "@answeredTheCall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "alreadyHaveAnAccount": "Har du allerede en konto?", + "@alreadyHaveAnAccount": { + "type": "text", + "placeholders": {} + }, + "alias": "alias", + "@alias": { + "type": "text", + "placeholders": {} + }, + "admin": "Administrator", + "@admin": { + "type": "text", + "placeholders": {} + }, + "addGroupDescription": "Legg til gruppebeskrivelse", + "@addGroupDescription": { + "type": "text", + "placeholders": {} + }, + "activatedEndToEndEncryption": "{username} skrudde på ende-til-ende -kryptering", + "@activatedEndToEndEncryption": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "accountInformation": "Kontoinfo", + "@accountInformation": { + "type": "text", + "placeholders": {} + }, + "account": "Konto", + "@account": { + "type": "text", + "placeholders": {} + }, + "acceptedTheInvitation": "{username} godtok invitasjonen", + "@acceptedTheInvitation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "accept": "Godta", + "@accept": { + "type": "text", + "placeholders": {} + }, + "about": "Om", + "@about": { + "type": "text", + "placeholders": {} + }, + "youCannotInviteYourself": "Du kan ikke invitere deg selv", + "@youCannotInviteYourself": { + "type": "text", + "placeholders": {} + }, + "youAreNoLongerParticipatingInThisChat": "Du deltar ikke lenger i denne sludringen", + "@youAreNoLongerParticipatingInThisChat": { + "type": "text", + "placeholders": {} + }, + "wednesday": "Onsdag", + "@wednesday": { + "type": "text", + "placeholders": {} + }, + "wallpaper": "Bakgrunnsbilde", + "@wallpaper": { + "type": "text", + "placeholders": {} + }, + "warning": "Advarsel!", + "@warning": { + "type": "text", + "placeholders": {} + }, + "visibleForEveryone": "Synlig for alle", + "@visibleForEveryone": { + "type": "text", + "placeholders": {} + }, + "visibleForAllParticipants": "Synlig for alle deltagere", + "@visibleForAllParticipants": { + "type": "text", + "placeholders": {} + }, + "videoCall": "Videosamtale", + "@videoCall": { + "type": "text", + "placeholders": {} + }, + "verifyUser": "Bekreft bruker", + "@verifyUser": { + "type": "text", + "placeholders": {} + }, + "verifyStart": "Start bekreftelse", + "@verifyStart": { + "type": "text", + "placeholders": {} + }, + "verifyManual": "Bekreft manuelt", + "@verifyManual": { + "type": "text", + "placeholders": {} + }, + "verify": "Bekreft", + "@verify": { + "type": "text", + "placeholders": {} + }, + "userSentUnknownEvent": "{username} sendte en {type}-hendelse", + "@userSentUnknownEvent": { + "type": "text", + "placeholders": { + "username": {}, + "type": {} + } + }, + "userIsTyping": "{username} skriver…", + "@userIsTyping": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "username": "Brukernavn", + "@username": { + "type": "text", + "placeholders": {} + }, + "userAndUserAreTyping": "{username} og {username2} skriver…", + "@userAndUserAreTyping": { + "type": "text", + "placeholders": { + "username": {}, + "username2": {} + } + }, + "userAndOthersAreTyping": "{username} og {count} andre skriver…", + "@userAndOthersAreTyping": { + "type": "text", + "placeholders": { + "username": {}, + "count": {} + } + }, + "unreadMessagesInChats": "{unreadEvents} uleste meldinger i {unreadChats} sludringer", + "@unreadMessagesInChats": { + "type": "text", + "placeholders": { + "unreadEvents": {}, + "unreadChats": {} + } + }, + "unreadMessages": "{unreadEvents} uleste meldinger", + "@unreadMessages": { + "type": "text", + "placeholders": { + "unreadEvents": {} + } + }, + "unreadChats": "{unreadCount} uleste sludringer", + "@unreadChats": { + "type": "text", + "placeholders": { + "unreadCount": {} + } + }, + "newVerificationRequest": "Ny bekreftelsesforespørsel.", + "@newVerificationRequest": { + "type": "text", + "placeholders": {} + }, + "newPrivateChat": "Ny privat sludring", + "@newPrivateChat": { + "type": "text", + "placeholders": {} + }, + "newMessageInFluffyChat": "Ny melding i FluffyChat", + "@newMessageInFluffyChat": { + "type": "text", + "placeholders": {} + }, + "muteChat": "Forstum sludring", + "@muteChat": { + "type": "text", + "placeholders": {} + }, + "monday": "Mandag", + "@monday": { + "type": "text", + "placeholders": {} + }, + "logInTo": "Logg inn på {homeserver}", + "@logInTo": { + "type": "text", + "placeholders": { + "homeserver": {} + } + }, + "loadCountMoreParticipants": "Last inn {count} deltagere til", + "@loadCountMoreParticipants": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "loadMore": "Last inn mer…", + "@loadMore": { + "type": "text", + "placeholders": {} + }, + "loadingPleaseWait": "Laster inn… Vent.", + "@loadingPleaseWait": { + "type": "text", + "placeholders": {} + }, + "changedTheRoomInvitationLink": "{username} endret invitasjonslenken", + "@changedTheRoomInvitationLink": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheRoomAliases": "{username} endret rom-aliasene", + "@changedTheRoomAliases": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheJoinRulesTo": "{username} endret tilgangsreglene til: {joinRules}", + "@changedTheJoinRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "joinRules": {} + } + }, + "changedTheJoinRules": "{username} endret tilgangsreglene", + "@changedTheJoinRules": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheGuestAccessRulesTo": "{username} endret gjestetilgangsregler til: {rules}", + "@changedTheGuestAccessRulesTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "encryptionNotEnabled": "Kryptering er ikke påskrudd", + "@encryptionNotEnabled": { + "type": "text", + "placeholders": {} + }, + "discardPicture": "Forkast bilde", + "@discardPicture": { + "type": "text", + "placeholders": {} + }, + "dateWithYear": "{day} {month} {year}", + "@dateWithYear": { + "type": "text", + "placeholders": { + "year": {}, + "month": {}, + "day": {} + } + }, + "dateWithoutYear": "{day} {month}", + "@dateWithoutYear": { + "type": "text", + "placeholders": { + "month": {}, + "day": {} + } + }, + "dateAndTimeOfDay": "{timeOfDay}, {date}", + "@dateAndTimeOfDay": { + "type": "text", + "placeholders": { + "date": {}, + "timeOfDay": {} + } + }, + "createNewGroup": "Opprett ny gruppe", + "@createNewGroup": { + "type": "text", + "placeholders": {} + }, + "couldNotSetAvatar": "Kunne ikke sette avatar", + "@couldNotSetAvatar": { + "type": "text", + "placeholders": {} + }, + "changedTheProfileAvatar": "{username} endret avataren sin", + "@changedTheProfileAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheHistoryVisibilityTo": "{username} endret historikksynlighet til: {rules}", + "@changedTheHistoryVisibilityTo": { + "type": "text", + "placeholders": { + "username": {}, + "rules": {} + } + }, + "changedTheHistoryVisibility": "{username} endret historikksynlighet", + "@changedTheHistoryVisibility": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheDisplaynameTo": "{username} endret visningsnavn til: {displayname}", + "@changedTheDisplaynameTo": { + "type": "text", + "placeholders": { + "username": {}, + "displayname": {} + } + }, + "changedTheChatPermissions": "{username} endret sludretilgangene", + "@changedTheChatPermissions": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changedTheChatDescriptionTo": "{username} endret sludrebeskrivelse til: «{description}»", + "@changedTheChatDescriptionTo": { + "type": "text", + "placeholders": { + "username": {}, + "description": {} + } + }, + "changedTheChatNameTo": "{username} endret sludringsnavn til: «{chatname}»", + "@changedTheChatNameTo": { + "type": "text", + "placeholders": { + "username": {}, + "chatname": {} + } + }, + "changedTheChatAvatar": "{username} endret sludreavatar", + "@changedTheChatAvatar": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "changeDeviceName": "Endre enhetsnavn", + "@changeDeviceName": { + "type": "text", + "placeholders": {} + }, + "cancel": "Avbryt", + "@cancel": { + "type": "text", + "placeholders": {} + }, + "byDefaultYouWillBeConnectedTo": "Som forvalg vil du bli tilkoblet via {homeserver}", + "@byDefaultYouWillBeConnectedTo": { + "type": "text", + "placeholders": { + "homeserver": {} + } + }, + "blockDevice": "Blokker enhet", + "@blockDevice": { + "type": "text", + "placeholders": {} + }, + "bannedUser": "{username} bannlyste {targetName}", + "@bannedUser": { + "type": "text", + "placeholders": { + "username": {}, + "targetName": {} + } + } +} diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index d8960c14..a8881c39 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -759,7 +759,7 @@ "type": "text", "placeholders": {} }, - "isTyping": "Печатает...", + "isTyping": "Печатает…", "@isTyping": { "type": "text", "placeholders": {} @@ -851,12 +851,12 @@ "count": {} } }, - "loadingPleaseWait": "Пожалуйста, подождите...", + "loadingPleaseWait": "Пожалуйста, подождите…", "@loadingPleaseWait": { "type": "text", "placeholders": {} }, - "loadMore": "Загрузить больше...", + "loadMore": "Загрузить больше…", "@loadMore": { "type": "text", "placeholders": {} @@ -968,7 +968,7 @@ "type": "text", "placeholders": {} }, - "noRoomsFound": "Комнаты не найдены...", + "noRoomsFound": "Комнаты не найдены…", "@noRoomsFound": { "type": "text", "placeholders": {} @@ -1000,7 +1000,7 @@ "type": "text", "placeholders": {} }, - "oopsSomethingWentWrong": "Упс! Что-то пошло не так...", + "oopsSomethingWentWrong": "Упс! Что-то пошло не так…", "@oopsSomethingWentWrong": { "type": "text", "placeholders": {} @@ -1500,7 +1500,7 @@ "type": "text", "placeholders": {} }, - "userAndOthersAreTyping": "{username} и {count} других участников печатают...", + "userAndOthersAreTyping": "{username} и {count} других участников печатают…", "@userAndOthersAreTyping": { "type": "text", "placeholders": { @@ -1508,7 +1508,7 @@ "count": {} } }, - "userAndUserAreTyping": "{username} и {username2} печатают...", + "userAndUserAreTyping": "{username} и {username2} печатают…", "@userAndUserAreTyping": { "type": "text", "placeholders": { @@ -1516,7 +1516,7 @@ "username2": {} } }, - "userIsTyping": "{username} печатает...", + "userIsTyping": "{username} печатает…", "@userIsTyping": { "type": "text", "placeholders": { @@ -1613,7 +1613,7 @@ "type": "text", "placeholders": {} }, - "waitingPartnerNumbers": "В ожидании партнёра, чтобы принять числа...", + "waitingPartnerNumbers": "В ожидании партнёра, чтобы принять числа…", "@waitingPartnerNumbers": { "type": "text", "placeholders": {} @@ -1643,7 +1643,7 @@ "type": "text", "placeholders": {} }, - "writeAMessage": "Напишите сообщение...", + "writeAMessage": "Напишите сообщение…", "@writeAMessage": { "type": "text", "placeholders": {} @@ -1698,7 +1698,7 @@ "type": "text", "placeholders": {} }, - "deactivateAccountWarning": "Это деактивирует вашу учётную запись пользователя. Это не может быть отменено! Вы уверены?", + "deactivateAccountWarning": "Это деактивирует вашу учётную запись пользователя. Данное действие не может быть отменено! Вы уверены?", "@deactivateAccountWarning": { "type": "text", "placeholders": {} @@ -1708,12 +1708,12 @@ "type": "text", "placeholders": {} }, - "enableEmotesGlobally": "Включить набор эмоджи глобально", + "enableEmotesGlobally": "Включить набор эмодзи глобально", "@enableEmotesGlobally": { "type": "text", "placeholders": {} }, - "emotePacks": "Наборы эмоджи для комнаты", + "emotePacks": "Наборы эмодзи для комнаты", "@emotePacks": { "type": "text", "placeholders": {} @@ -1722,5 +1722,25 @@ "@changeDeviceName": { "type": "text", "placeholders": {} + }, + "mention": "Упомянуть", + "@mention": { + "type": "text", + "placeholders": {} + }, + "offline": "Не в сети", + "@offline": { + "type": "text", + "placeholders": {} + }, + "online": "В сети", + "@online": { + "type": "text", + "placeholders": {} + }, + "unavailable": "Недоступен", + "@unavailable": { + "type": "text", + "placeholders": {} } } diff --git a/lib/l10n/intl_tr.arb b/lib/l10n/intl_tr.arb index c767ae9a..59163039 100644 --- a/lib/l10n/intl_tr.arb +++ b/lib/l10n/intl_tr.arb @@ -137,7 +137,7 @@ "targetName": {} } }, - "blockDevice": "Cihazı Engelle", + "blockDevice": "Aygıtı Engelle", "@blockDevice": { "type": "text", "placeholders": {} @@ -323,12 +323,12 @@ "type": "text", "placeholders": {} }, - "compareEmojiMatch": "Karşılaştırın ve aşağıdaki emojilerin diğer cihazdakilerle eşleştiğinden emin olun:", + "compareEmojiMatch": "Karşılaştırın ve aşağıdaki emojilerin diğer aygıttaki emojilerle eşleştiğinden emin olun:", "@compareEmojiMatch": { "type": "text", "placeholders": {} }, - "compareNumbersMatch": "Karşılaştırın ve aşağıdaki numaraların diğer cihazdakilerle eşleştiğinden emin olun:", + "compareNumbersMatch": "Karşılaştırın ve aşağıdaki numaraların diğer aygıttaki numaralarla eşleştiğinden emin olun:", "@compareNumbersMatch": { "type": "text", "placeholders": {} @@ -474,12 +474,12 @@ "type": "text", "placeholders": {} }, - "device": "Cihaz", + "device": "Aygıt", "@device": { "type": "text", "placeholders": {} }, - "devices": "Cihazlar", + "devices": "Aygıtlar", "@devices": { "type": "text", "placeholders": {} @@ -727,7 +727,7 @@ "link": {} } }, - "isDeviceKeyCorrect": "Aşağıdaki cihaz anahtarı doğru mu?", + "isDeviceKeyCorrect": "Aşağıdaki aygıt anahtarı doğru mu?", "@isDeviceKeyCorrect": { "type": "text", "placeholders": {} @@ -983,7 +983,7 @@ "type": "text", "placeholders": {} }, - "participatingUserDevices": "Katılan kullanıcı cihazları", + "participatingUserDevices": "Katılan kullanıcı aygıtları", "@participatingUserDevices": { "type": "text", "placeholders": {} @@ -1069,7 +1069,7 @@ "type": "text", "placeholders": {} }, - "removeAllOtherDevices": "Diğer tüm cihazları kaldır", + "removeAllOtherDevices": "Diğer tüm aygıtları kaldır", "@removeAllOtherDevices": { "type": "text", "placeholders": {} @@ -1081,7 +1081,7 @@ "username": {} } }, - "removeDevice": "Cihazı kaldır", + "removeDevice": "Aygıtı kaldır", "@removeDevice": { "type": "text", "placeholders": {} @@ -1355,12 +1355,12 @@ "targetName": {} } }, - "unblockDevice": "Cihazın Engellemesini Kaldır", + "unblockDevice": "Aygıtın Engellemesini Kaldır", "@unblockDevice": { "type": "text", "placeholders": {} }, - "unknownDevice": "Bilinmeyen cihaz", + "unknownDevice": "Bilinmeyen aygıt", "@unknownDevice": { "type": "text", "placeholders": {} @@ -1718,9 +1718,29 @@ "type": "text", "placeholders": {} }, - "changeDeviceName": "Cihaz adını değiştir", + "changeDeviceName": "Aygıt adını değiştir", "@changeDeviceName": { "type": "text", "placeholders": {} + }, + "mention": "Bahset", + "@mention": { + "type": "text", + "placeholders": {} + }, + "unavailable": "Yok", + "@unavailable": { + "type": "text", + "placeholders": {} + }, + "offline": "Çevrim dışı", + "@offline": { + "type": "text", + "placeholders": {} + }, + "online": "Çevrim içi", + "@online": { + "type": "text", + "placeholders": {} } } diff --git a/lib/l10n/intl_vi.arb b/lib/l10n/intl_vi.arb new file mode 100644 index 00000000..a8a59ba1 --- /dev/null +++ b/lib/l10n/intl_vi.arb @@ -0,0 +1,98 @@ +{ + "blockDevice": "Thiết bị bị chặn", + "@blockDevice": { + "type": "text", + "placeholders": {} + }, + "askSSSSCache": "Vui lòng nhập cụm mật khẩu hoặc khóa khôi phục để lưu khóa vào bộ nhớ cache.", + "@askSSSSCache": { + "type": "text", + "placeholders": {} + }, + "areYouSure": "Bạn chắc chứ?", + "@areYouSure": { + "type": "text", + "placeholders": {} + }, + "areGuestsAllowedToJoin": "Khách vãng lai có được tham gia không", + "@areGuestsAllowedToJoin": { + "type": "text", + "placeholders": {} + }, + "archivedRoom": "Phòng hội thảo đã lưu trữ", + "@archivedRoom": { + "type": "text", + "placeholders": {} + }, + "archive": "Lưu trữ", + "@archive": { + "type": "text", + "placeholders": {} + }, + "anyoneCanJoin": "Mọi người đều có thể gia nhập", + "@anyoneCanJoin": { + "type": "text", + "placeholders": {} + }, + "answeredTheCall": "{senderName} đã trả lời cuộc gọi", + "@answeredTheCall": { + "type": "text", + "placeholders": { + "senderName": {} + } + }, + "alreadyHaveAnAccount": "Bạn đã có tài khoản?", + "@alreadyHaveAnAccount": { + "type": "text", + "placeholders": {} + }, + "alias": "bí danh", + "@alias": { + "type": "text", + "placeholders": {} + }, + "admin": "Quản trị viên", + "@admin": { + "type": "text", + "placeholders": {} + }, + "addGroupDescription": "Thêm mô tả cho nhóm", + "@addGroupDescription": { + "type": "text", + "placeholders": {} + }, + "activatedEndToEndEncryption": "{username} đã kích hoạt mã hóa đầu cuối 2 chiều", + "@activatedEndToEndEncryption": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "accountInformation": "Thông tin tài khoản", + "@accountInformation": { + "type": "text", + "placeholders": {} + }, + "account": "Tài khoản", + "@account": { + "type": "text", + "placeholders": {} + }, + "acceptedTheInvitation": "{username} đã đồng ý lời mời", + "@acceptedTheInvitation": { + "type": "text", + "placeholders": { + "username": {} + } + }, + "accept": "Đồng ý", + "@accept": { + "type": "text", + "placeholders": {} + }, + "about": "Giới thiệu", + "@about": { + "type": "text", + "placeholders": {} + } +} diff --git a/lib/main.dart b/lib/main.dart index a08f0015..bba5897c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,39 +3,26 @@ import 'dart:io'; import 'package:bot_toast/bot_toast.dart'; import 'package:famedlysdk/famedlysdk.dart'; +import 'package:fluffychat/utils/sentry_controller.dart'; import 'package:fluffychat/views/homeserver_picker.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:sentry/sentry.dart'; import 'package:universal_html/prefer_universal/html.dart' as html; import 'components/matrix.dart'; import 'components/theme_switcher.dart'; -import 'utils/famedlysdk_store.dart'; import 'views/chat_list.dart'; -final sentry = SentryClient(dsn: '8591d0d863b646feb4f3dda7e5dcab38'); - -void captureException(error, stackTrace) async { - debugPrint(error.toString()); - debugPrint(stackTrace.toString()); - final storage = await getLocalStorage(); - if (storage.getItem('sentry') == true) { - await sentry.captureException( - exception: error, - stackTrace: stackTrace, - ); - } -} - void main() { SystemChrome.setSystemUIOverlayStyle( SystemUiOverlayStyle(statusBarColor: Colors.transparent)); + FlutterError.onError = (FlutterErrorDetails details) => + Zone.current.handleUncaughtError(details.exception, details.stack); runZonedGuarded( () => runApp(App()), - captureException, + SentryController.captureException, ); } diff --git a/lib/utils/famedlysdk_store.dart b/lib/utils/famedlysdk_store.dart index 25acf110..34232e59 100644 --- a/lib/utils/famedlysdk_store.dart +++ b/lib/utils/famedlysdk_store.dart @@ -1,27 +1,13 @@ -import 'dart:convert'; - import 'package:famedlysdk/famedlysdk.dart'; import 'package:fluffychat/utils/platform_infos.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:localstorage/localstorage.dart'; import 'package:path_provider/path_provider.dart'; import 'dart:async'; import 'dart:core'; import './database/shared.dart'; -import 'package:olm/olm.dart' as olm; // needed for migration import 'package:random_string/random_string.dart'; -Future getLocalStorage() async { - final directory = PlatformInfos.isBetaDesktop - ? await getApplicationSupportDirectory() - : (PlatformInfos.isWeb ? null : await getApplicationDocumentsDirectory()); - final localStorage = LocalStorage('LocalStorage', directory?.path); - await localStorage.ready; - return localStorage; -} - Future getDatabase(Client client) async { while (_generateDatabaseLock) { await Future.delayed(Duration(milliseconds: 50)); @@ -31,9 +17,9 @@ Future getDatabase(Client client) async { if (_db != null) return _db; final store = Store(); var password = await store.getItem('database-password'); - var needMigration = false; + var newPassword = false; if (password == null || password.isEmpty) { - needMigration = true; + newPassword = true; password = randomString(255); } _db = await constructDb( @@ -41,11 +27,7 @@ Future getDatabase(Client client) async { filename: 'moor.sqlite', password: password, ); - // Check if database is open: - debugPrint((await _db.customSelect('SELECT 1').get()).toString()); - if (needMigration) { - debugPrint('[Moor] Start migration'); - await migrate(client.clientName, _db, store); + if (newPassword) { await store.setItem('database-password', password); } return _db; @@ -57,239 +39,54 @@ Future getDatabase(Client client) async { Database _db; bool _generateDatabaseLock = false; -Future migrate(String clientName, Database db, Store store) async { - debugPrint('[Store] attempting old migration to moor...'); - final oldKeys = await store.getAllItems(); - if (oldKeys == null || oldKeys.isEmpty) { - debugPrint('[Store] empty store!'); - return; // we are done! - } - final credentialsStr = oldKeys[clientName]; - if (credentialsStr == null || credentialsStr.isEmpty) { - debugPrint('[Store] no credentials found!'); - return; // no credentials - } - final Map credentials = json.decode(credentialsStr); - if (!credentials.containsKey('homeserver') || - !credentials.containsKey('token') || - !credentials.containsKey('userID')) { - debugPrint('[Store] invalid credentials!'); - return; // invalid old store, we are done, too! - } - var clientId = 0; - final oldClient = await db.getClient(clientName); - if (oldClient == null) { - clientId = await db.insertClient( - clientName, - credentials['homeserver'], - credentials['token'], - credentials['userID'], - credentials['deviceID'], - credentials['deviceName'], - null, - credentials['olmAccount'], - ); - } else { - clientId = oldClient.clientId; - await db.updateClient( - credentials['homeserver'], - credentials['token'], - credentials['userID'], - credentials['deviceID'], - credentials['deviceName'], - null, - credentials['olmAccount'], - clientId, - ); - } - await db.clearCache(clientId); - debugPrint('[Store] Inserted/updated client, clientId = ${clientId}'); - await db.transaction(() async { - // alright, we stored / updated the client and have the account ID, time to import everything else! - // user_device_keys and user_device_keys_key - debugPrint('[Store] Migrating user device keys...'); - final deviceKeysListString = oldKeys['${clientName}.user_device_keys']; - if (deviceKeysListString != null && deviceKeysListString.isNotEmpty) { - Map rawUserDeviceKeys = - json.decode(deviceKeysListString); - for (final entry in rawUserDeviceKeys.entries) { - final map = entry.value; - await db.storeUserDeviceKeysInfo( - clientId, map['user_id'], map['outdated']); - for (final rawKey in map['device_keys'].entries) { - final jsonVaue = rawKey.value; - await db.storeUserDeviceKey( - clientId, - jsonVaue['user_id'], - jsonVaue['device_id'], - json.encode(jsonVaue), - jsonVaue['verified'], - jsonVaue['blocked']); - } - } - } - for (final entry in oldKeys.entries) { - final key = entry.key; - final value = entry.value; - if (value == null || value.isEmpty) { - continue; - } - // olm_sessions - final olmSessionsMatch = - RegExp(r'^\/clients\/([^\/]+)\/olm-sessions$').firstMatch(key); - if (olmSessionsMatch != null) { - if (olmSessionsMatch[1] != credentials['deviceID']) { - continue; - } - debugPrint('[Store] migrating olm sessions...'); - final identityKey = json.decode(value); - for (final olmKey in identityKey.entries) { - final identKey = olmKey.key; - final sessions = olmKey.value; - for (final pickle in sessions) { - var sess = olm.Session(); - sess.unpickle(credentials['userID'], pickle); - await db.storeOlmSession( - clientId, identKey, sess.session_id(), pickle, null); - sess?.free(); - } - } - } - // outbound_group_sessions - final outboundGroupSessionsMatch = RegExp( - r'^\/clients\/([^\/]+)\/rooms\/([^\/]+)\/outbound_group_session$') - .firstMatch(key); - if (outboundGroupSessionsMatch != null) { - if (outboundGroupSessionsMatch[1] != credentials['deviceID']) { - continue; - } - final pickle = value; - final roomId = outboundGroupSessionsMatch[2]; - debugPrint( - '[Store] Migrating outbound group sessions for room ${roomId}...'); - final devicesString = oldKeys[ - '/clients/${outboundGroupSessionsMatch[1]}/rooms/${roomId}/outbound_group_session_devices']; - var devices = []; - if (devicesString != null) { - devices = List.from(json.decode(devicesString)); - } - await db.storeOutboundGroupSession( - clientId, - roomId, - pickle, - json.encode(devices), - DateTime.now().millisecondsSinceEpoch, - 0, - ); - } - // session_keys - final sessionKeysMatch = - RegExp(r'^\/clients\/([^\/]+)\/rooms\/([^\/]+)\/session_keys$') - .firstMatch(key); - if (sessionKeysMatch != null) { - if (sessionKeysMatch[1] != credentials['deviceID']) { - continue; - } - final roomId = sessionKeysMatch[2]; - debugPrint('[Store] Migrating session keys for room ${roomId}...'); - final map = json.decode(value); - for (final entry in map.entries) { - await db.storeInboundGroupSession( - clientId, - roomId, - entry.key, - entry.value['inboundGroupSession'], - json.encode(entry.value['content']), - json.encode(entry.value['indexes']), - null, - null); - } - } - } - }); -} - -// see https://github.com/mogol/flutter_secure_storage/issues/161#issuecomment-704578453 -class AsyncMutex { - Completer _completer; - - Future lock() async { - while (_completer != null) { - await _completer.future; - } - - _completer = Completer(); - } - - void unlock() { - assert(_completer != null); - final completer = _completer; - _completer = null; - completer.complete(); - } -} - class Store { - final LocalStorage storage; + LocalStorage storage; final FlutterSecureStorage secureStorage; - static final _mutex = AsyncMutex(); Store() - : storage = LocalStorage('LocalStorage'), - secureStorage = PlatformInfos.isMobile ? FlutterSecureStorage() : null; + : secureStorage = PlatformInfos.isMobile ? FlutterSecureStorage() : null; - Future getItem(String key) async { - if (!PlatformInfos.isMobile) { + Future _setupLocalStorage() async { + if (storage == null) { + final directory = PlatformInfos.isBetaDesktop + ? await getApplicationSupportDirectory() + : (PlatformInfos.isWeb + ? null + : await getApplicationDocumentsDirectory()); + storage = LocalStorage('LocalStorage', directory?.path); await storage.ready; + } + } + + Future getItem(String key) async { + if (!PlatformInfos.isMobile) { + await _setupLocalStorage(); try { - return await storage.getItem(key); + return await storage.getItem(key)?.toString(); } catch (_) { return null; } } try { - await _mutex.lock(); return await secureStorage.read(key: key); } catch (_) { return null; - } finally { - _mutex.unlock(); } } Future setItem(String key, String value) async { if (!PlatformInfos.isMobile) { - await storage.ready; + await _setupLocalStorage(); return await storage.setItem(key, value); } - if (value == null) { - return await secureStorage.delete(key: key); - } else { - try { - await _mutex.lock(); - return await secureStorage.write(key: key, value: value); - } finally { - _mutex.unlock(); - } - } + return await secureStorage.write(key: key, value: value); } - Future> getAllItems() async { + Future deleteItem(String key) async { if (!PlatformInfos.isMobile) { - try { - final rawStorage = await getLocalstorage('LocalStorage'); - return json.decode(rawStorage); - } catch (_) { - return {}; - } - } - try { - await _mutex.lock(); - return await secureStorage.readAll(); - } catch (_) { - return {}; - } finally { - _mutex.unlock(); + await _setupLocalStorage(); + return await storage.deleteItem(key); } + return await secureStorage.delete(key: key); } } diff --git a/lib/utils/firebase_controller.dart b/lib/utils/firebase_controller.dart index 9c20f03e..02c12467 100644 --- a/lib/utils/firebase_controller.dart +++ b/lib/utils/firebase_controller.dart @@ -58,7 +58,7 @@ abstract class FirebaseController { } final pushers = await client.requestPushers().catchError((e) { debugPrint('[Push] Unable to request pushers: ${e.toString()}'); - return []; + return []; }); final currentPushers = pushers.where((pusher) => pusher.pushkey == token); if (currentPushers.length == 1 && @@ -133,7 +133,9 @@ abstract class FirebaseController { return null; }); var initializationSettings = InitializationSettings( - initializationSettingsAndroid, initializationSettingsIOS); + android: initializationSettingsAndroid, + iOS: initializationSettingsIOS, + ); await _flutterLocalNotificationsPlugin.initialize(initializationSettings, onSelectNotification: goToRoom); @@ -264,12 +266,14 @@ abstract class FirebaseController { ) ], ), - importance: Importance.Max, - priority: Priority.High, + importance: Importance.max, + priority: Priority.high, ticker: i18n.newMessageInFluffyChat); var iOSPlatformChannelSpecifics = IOSNotificationDetails(); var platformChannelSpecifics = NotificationDetails( - androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics); + android: androidPlatformChannelSpecifics, + iOS: iOSPlatformChannelSpecifics, + ); await _flutterLocalNotificationsPlugin.show( 0, room.getLocalizedDisplayname(MatrixLocals(i18n)), @@ -299,7 +303,9 @@ abstract class FirebaseController { AndroidInitializationSettings('notifications_icon'); var initializationSettingsIOS = IOSInitializationSettings(); var initializationSettings = InitializationSettings( - initializationSettingsAndroid, initializationSettingsIOS); + android: initializationSettingsAndroid, + iOS: initializationSettingsIOS, + ); await flutterLocalNotificationsPlugin.initialize(initializationSettings); // FIXME unable to init without context currently https://github.com/flutter/flutter/issues/67092 @@ -322,10 +328,12 @@ abstract class FirebaseController { // Display notification var androidPlatformChannelSpecifics = AndroidNotificationDetails( CHANNEL_ID, CHANNEL_NAME, CHANNEL_DESCRIPTION, - importance: Importance.Max, priority: Priority.High); + importance: Importance.max, priority: Priority.high); var iOSPlatformChannelSpecifics = IOSNotificationDetails(); var platformChannelSpecifics = NotificationDetails( - androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics); + android: androidPlatformChannelSpecifics, + iOS: iOSPlatformChannelSpecifics, + ); final title = l10n.unreadChats(unread.toString()); await flutterLocalNotificationsPlugin.show( 1, title, l10n.openAppToReadMessages, platformChannelSpecifics, diff --git a/lib/utils/fluffy_share.dart b/lib/utils/fluffy_share.dart new file mode 100644 index 00000000..88409a3d --- /dev/null +++ b/lib/utils/fluffy_share.dart @@ -0,0 +1,19 @@ +import 'package:bot_toast/bot_toast.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:share/share.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +abstract class FluffyShare { + static Future share(String text, BuildContext context) async { + if (PlatformInfos.isMobile) { + return Share.share(text); + } + await Clipboard.setData( + ClipboardData(text: text), + ); + BotToast.showText(text: L10n.of(context).copiedToClipboard); + return; + } +} diff --git a/lib/utils/matrix_file_extension.dart b/lib/utils/matrix_file_extension.dart index 2f1dc447..d7daef3d 100644 --- a/lib/utils/matrix_file_extension.dart +++ b/lib/utils/matrix_file_extension.dart @@ -1,11 +1,14 @@ import 'dart:io'; import 'package:famedlysdk/famedlysdk.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; import 'package:flutter/foundation.dart'; import 'package:open_file/open_file.dart'; import 'package:path_provider/path_provider.dart'; import 'package:universal_html/prefer_universal/html.dart' as html; import 'package:mime_type/mime_type.dart'; +import 'package:downloads_path_provider_28/downloads_path_provider_28.dart'; +import 'package:permission_handler/permission_handler.dart'; extension MatrixFileExtension on MatrixFile { void open() async { @@ -24,9 +27,12 @@ extension MatrixFileExtension on MatrixFile { element.click(); element.remove(); } else { - final downloadsDir = Platform.isAndroid - ? (await getExternalStorageDirectory()) - : (await getApplicationDocumentsDirectory()); + if (!(await Permission.storage.request()).isGranted) return; + final downloadsDir = PlatformInfos.isDesktop + ? (await getDownloadsDirectory()) + : Platform.isAndroid + ? (await DownloadsPathProvider.downloadsDirectory) + : (await getApplicationDocumentsDirectory()); final file = File(downloadsDir.path + '/' + name.split('/').last); file.writeAsBytesSync(bytes); diff --git a/lib/utils/platform_infos.dart b/lib/utils/platform_infos.dart index 5473a8cf..3bc4898b 100644 --- a/lib/utils/platform_infos.dart +++ b/lib/utils/platform_infos.dart @@ -11,5 +11,8 @@ abstract class PlatformInfos { static bool get isBetaDesktop => !kIsWeb && (Platform.isWindows || Platform.isLinux); + static bool get isDesktop => + !kIsWeb && (Platform.isLinux || Platform.isWindows || Platform.isMacOS); + static bool get usesTouchscreen => !isMobile; } diff --git a/lib/utils/presence_extension.dart b/lib/utils/presence_extension.dart index bfbd0325..50203f87 100644 --- a/lib/utils/presence_extension.dart +++ b/lib/utils/presence_extension.dart @@ -4,21 +4,37 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'date_time_extension.dart'; +extension on PresenceType { + String getLocalized(BuildContext context) { + switch (this) { + case PresenceType.online: + return L10n.of(context).online; + case PresenceType.unavailable: + return L10n.of(context).unavailable; + case PresenceType.offline: + default: + return L10n.of(context).offline; + } + } +} + extension PresenceExtension on Presence { - bool get isUserStatus => presence?.statusMsg?.isNotEmpty ?? false; + String getLocalizedLastActiveAgo(BuildContext context) { + if (presence.lastActiveAgo != null && presence.lastActiveAgo != 0) { + return L10n.of(context).lastActiveAgo(DateTime.fromMillisecondsSinceEpoch( + DateTime.now().millisecondsSinceEpoch - presence.lastActiveAgo) + .localizedTimeShort(context)); + } + return L10n.of(context).lastSeenLongTimeAgo; + } String getLocalizedStatusMessage(BuildContext context) { if (presence.statusMsg?.isNotEmpty ?? false) { return presence.statusMsg; } - if (presence.lastActiveAgo != null ?? presence.lastActiveAgo != 0) { - return L10n.of(context).lastActiveAgo( - DateTime.fromMillisecondsSinceEpoch(presence.lastActiveAgo) - .localizedTimeShort(context)); - } - if (presence.currentlyActive) { + if (presence.currentlyActive ?? false) { return L10n.of(context).currentlyActive; } - return L10n.of(context).lastSeenLongTimeAgo; + return presence.presence.getLocalized(context); } } diff --git a/lib/utils/sentry_controller.dart b/lib/utils/sentry_controller.dart index 970a4193..8cebc6c1 100644 --- a/lib/utils/sentry_controller.dart +++ b/lib/utils/sentry_controller.dart @@ -1,7 +1,10 @@ import 'package:bot_toast/bot_toast.dart'; import 'package:fluffychat/components/dialogs/simple_dialogs.dart'; +import 'package:fluffychat/config/app_config.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:sentry/sentry.dart'; import 'famedlysdk_store.dart'; @@ -13,14 +16,27 @@ abstract class SentryController { confirmText: L10n.of(context).ok, cancelText: L10n.of(context).no, ); - final storage = await getLocalStorage(); - await storage.setItem('sentry', enableSentry); + final storage = Store(); + await storage.setItem('sentry', enableSentry.toString()); BotToast.showText(text: L10n.of(context).changesHaveBeenSaved); return; } static Future getSentryStatus() async { - final storage = await getLocalStorage(); - return storage.getItem('sentry') as bool; + final storage = Store(); + return await storage.getItem('sentry') == 'true'; + } + + static final sentry = SentryClient(dsn: AppConfig.sentryDsn); + + static void captureException(error, stackTrace) async { + debugPrint(error.toString()); + debugPrint(stackTrace.toString()); + if (!kDebugMode && await getSentryStatus()) { + await sentry.captureException( + exception: error, + stackTrace: stackTrace, + ); + } } } diff --git a/lib/utils/user_status.dart b/lib/utils/user_status.dart deleted file mode 100644 index dadfb3fd..00000000 --- a/lib/utils/user_status.dart +++ /dev/null @@ -1,21 +0,0 @@ -class UserStatus { - String statusMsg; - String userId; - int receivedAt; - - UserStatus(); - - UserStatus.fromJson(Map json) { - statusMsg = json['status_msg']; - userId = json['user_id']; - receivedAt = json['received_at']; - } - - Map toJson() { - final data = {}; - data['status_msg'] = statusMsg; - data['user_id'] = userId; - data['received_at'] = receivedAt; - return data; - } -} diff --git a/lib/views/chat.dart b/lib/views/chat.dart index 50509a3a..49aa2d1f 100644 --- a/lib/views/chat.dart +++ b/lib/views/chat.dart @@ -15,6 +15,7 @@ import 'package:fluffychat/components/encryption_button.dart'; import 'package:fluffychat/components/list_items/message.dart'; import 'package:fluffychat/components/matrix.dart'; import 'package:fluffychat/components/reply_content.dart'; +import 'package:fluffychat/components/user_bottom_sheet.dart'; import 'package:fluffychat/config/app_emojis.dart'; import 'package:fluffychat/utils/app_route.dart'; import 'package:fluffychat/utils/matrix_locals.dart'; @@ -98,6 +99,8 @@ class _ChatState extends State<_Chat> { String inputText = ''; + String pendingText = ''; + bool get _canLoadMore => timeline.events.last.type != EventTypes.RoomCreate; void requestHistory() async { @@ -202,12 +205,13 @@ class _ChatState extends State<_Chat> { if (sendController.text.isEmpty) return; room.sendTextEvent(sendController.text, inReplyTo: replyEvent, editEventId: editEvent?.eventId); - sendController.text = ''; + sendController.text = pendingText; setState(() { - inputText = ''; + inputText = pendingText; replyEvent = null; editEvent = null; + pendingText = ''; }); } @@ -473,16 +477,22 @@ class _ChatState extends State<_Chat> { return ListTile( leading: Avatar(room.avatar, room.displayname), contentPadding: EdgeInsets.zero, - onTap: room.isDirectChat && room.directChatPresence == null - ? null - : room.isDirectChat - ? null - : () => Navigator.of(context).push( - AppRoute.defaultRoute( - context, - ChatDetails(room), - ), - ), + onTap: room.isDirectChat + ? () => showModalBottomSheet( + context: context, + builder: (context) => UserBottomSheet( + user: room + .getUserByMXIDSync(room.directChatMatrixID), + onMention: () => sendController.text += + ' ${room.directChatMatrixID}', + ), + ) + : () => Navigator.of(context).push( + AppRoute.defaultRoute( + context, + ChatDetails(room), + ), + ), title: Text( room.getLocalizedDisplayname( MatrixLocals(L10n.of(context))), @@ -522,8 +532,9 @@ class _ChatState extends State<_Chat> { icon: Icon(Icons.edit), onPressed: () { setState(() { + pendingText = sendController.text; editEvent = selectedEvents.first; - sendController.text = editEvent + inputText = sendController.text = editEvent .getDisplayEvent(timeline) .getLocalizedBody(MatrixLocals(L10n.of(context)), withSenderNamePrefix: false, hideReply: true); @@ -566,465 +577,486 @@ class _ChatState extends State<_Chat> { width: double.infinity, fit: BoxFit.cover, ), - Column( - children: [ - ConnectionStatusHeader(), - Expanded( - child: FutureBuilder( - future: getTimeline(context), - builder: (BuildContext context, snapshot) { - if (!snapshot.hasData) { - return Center( - child: CircularProgressIndicator(), - ); - } + SafeArea( + child: Column( + children: [ + ConnectionStatusHeader(), + Expanded( + child: FutureBuilder( + future: getTimeline(context), + builder: (BuildContext context, snapshot) { + if (!snapshot.hasData) { + return Center( + child: CircularProgressIndicator(), + ); + } - if (room.notificationCount != null && - room.notificationCount > 0 && - timeline != null && - timeline.events.isNotEmpty && - Matrix.of(context).webHasFocus) { - room.sendReadReceipt(timeline.events.first.eventId); - } + if (room.notificationCount != null && + room.notificationCount > 0 && + timeline != null && + timeline.events.isNotEmpty && + Matrix.of(context).webHasFocus) { + room.sendReadReceipt(timeline.events.first.eventId); + } - final filteredEvents = getFilteredEvents(); + final filteredEvents = getFilteredEvents(); - return ListView.builder( - padding: EdgeInsets.symmetric( - horizontal: max( - 0, - (MediaQuery.of(context).size.width - - AdaptivePageLayout.defaultMinWidth * - 3.5) / - 2), - ), - reverse: true, - itemCount: filteredEvents.length + 2, - controller: _scrollController, - itemBuilder: (BuildContext context, int i) { - return i == filteredEvents.length + 1 - ? _loadingHistory - ? Container( - height: 50, - alignment: Alignment.center, - padding: EdgeInsets.all(8), - child: CircularProgressIndicator(), - ) - : _canLoadMore - ? FlatButton( + return ListView.builder( + padding: EdgeInsets.symmetric( + horizontal: max( + 0, + (MediaQuery.of(context).size.width - + AdaptivePageLayout.defaultMinWidth * + 3.5) / + 2), + ), + reverse: true, + itemCount: filteredEvents.length + 2, + controller: _scrollController, + itemBuilder: (BuildContext context, int i) { + return i == filteredEvents.length + 1 + ? _loadingHistory + ? Container( + height: 50, + alignment: Alignment.center, + padding: EdgeInsets.all(8), + child: CircularProgressIndicator(), + ) + : _canLoadMore + ? FlatButton( + child: Text( + L10n.of(context).loadMore, + style: TextStyle( + color: Theme.of(context) + .primaryColor, + fontWeight: FontWeight.bold, + decoration: + TextDecoration.underline, + ), + ), + onPressed: requestHistory, + ) + : Container() + : i == 0 + ? AnimatedContainer( + height: seenByText.isEmpty ? 0 : 24, + duration: seenByText.isEmpty + ? Duration(milliseconds: 0) + : Duration(milliseconds: 300), + alignment: + filteredEvents.first.senderId == + client.userID + ? Alignment.topRight + : Alignment.topLeft, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 4), + decoration: BoxDecoration( + color: Theme.of(context) + .scaffoldBackgroundColor + .withOpacity(0.8), + borderRadius: + BorderRadius.circular(4), + ), child: Text( - L10n.of(context).loadMore, + seenByText, + maxLines: 1, + overflow: TextOverflow.ellipsis, style: TextStyle( color: Theme.of(context) .primaryColor, - fontWeight: FontWeight.bold, - decoration: - TextDecoration.underline, ), ), - onPressed: requestHistory, - ) - : Container() - : i == 0 - ? AnimatedContainer( - height: seenByText.isEmpty ? 0 : 24, - duration: seenByText.isEmpty - ? Duration(milliseconds: 0) - : Duration(milliseconds: 300), - alignment: - filteredEvents.first.senderId == - client.userID - ? Alignment.topRight - : Alignment.topLeft, - child: Container( - padding: - EdgeInsets.symmetric(horizontal: 4), - decoration: BoxDecoration( - color: Theme.of(context) - .scaffoldBackgroundColor - .withOpacity(0.8), - borderRadius: - BorderRadius.circular(4), ), - child: Text( - seenByText, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: - Theme.of(context).primaryColor, + padding: EdgeInsets.only( + left: 8, + right: 8, + bottom: 8, + ), + ) + : AutoScrollTag( + key: ValueKey(i - 1), + index: i - 1, + controller: _scrollController, + child: Swipeable( + key: ValueKey( + filteredEvents[i - 1].eventId), + background: Padding( + padding: EdgeInsets.symmetric( + horizontal: 12.0), + child: Center( + child: Icon(Icons.reply), + ), ), - ), - ), - padding: EdgeInsets.only( - left: 8, - right: 8, - bottom: 8, - ), - ) - : AutoScrollTag( - key: ValueKey(i - 1), - index: i - 1, - controller: _scrollController, - child: Swipeable( - key: ValueKey( - filteredEvents[i - 1].eventId), - background: Padding( - padding: EdgeInsets.symmetric( - horizontal: 12.0), - child: Center( - child: Icon(Icons.reply), - ), - ), - direction: SwipeDirection.endToStart, - onSwipe: (direction) => replyAction( - replyTo: filteredEvents[i - 1]), - child: Message(filteredEvents[i - 1], - onAvatarTab: (Event event) { - sendController.text += - ' ${event.senderId}'; - }, - onSelect: (Event event) { - if (!event.redacted) { - if (selectedEvents - .contains(event)) { - setState( - () => selectedEvents - .remove(event), - ); - } else { - setState( - () => selectedEvents - .add(event), + direction: SwipeDirection.endToStart, + onSwipe: (direction) => replyAction( + replyTo: filteredEvents[i - 1]), + child: Message(filteredEvents[i - 1], + onAvatarTab: (Event event) => + showModalBottomSheet( + context: context, + builder: (context) => + UserBottomSheet( + user: event.sender, + onMention: () => + sendController.text += + ' ${event.senderId}', + ), + ), + onSelect: (Event event) { + if (!event.redacted) { + if (selectedEvents + .contains(event)) { + setState( + () => selectedEvents + .remove(event), + ); + } else { + setState( + () => selectedEvents + .add(event), + ); + } + selectedEvents.sort( + (a, b) => a.originServerTs + .compareTo( + b.originServerTs), ); } - selectedEvents.sort( - (a, b) => a.originServerTs - .compareTo( - b.originServerTs), - ); - } - }, - scrollToEventId: (String eventId) => - _scrollToEventId(eventId, - context: context), - longPressSelect: - selectedEvents.isEmpty, - selected: selectedEvents.contains( - filteredEvents[i - 1]), - timeline: timeline, - nextEvent: i >= 2 - ? filteredEvents[i - 2] - : null), - ), - ); - }); - }, - ), - ), - AnimatedContainer( - duration: Duration(milliseconds: 300), - height: (editEvent == null && - replyEvent == null && - selectedEvents.length == 1) - ? 56 - : 0, - child: Material( - color: Theme.of(context).secondaryHeaderColor, - child: Builder(builder: (context) { - if (!(editEvent == null && - replyEvent == null && - selectedEvents.length == 1)) { - return Container(); - } - var emojis = List.from(AppEmojis.emojis); - final allReactionEvents = selectedEvents.first - .aggregatedEvents(timeline, RelationshipTypes.Reaction) - ?.where((event) => - event.senderId == event.room.client.userID && - event.type == 'm.reaction'); - - allReactionEvents.forEach((event) { - try { - emojis.remove(event.content['m.relates_to']['key']); - } catch (_) {} - }); - return ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: emojis.length, - itemBuilder: (c, i) => InkWell( - borderRadius: BorderRadius.circular(8), - onTap: () { - SimpleDialogs(context).tryRequestWithLoadingDialog( - room.sendReaction( - selectedEvents.first.eventId, - emojis[i], - ), - ); - setState(() => selectedEvents.clear()); - }, - child: Container( - width: 56, - height: 56, - alignment: Alignment.center, - child: Text( - emojis[i], - style: TextStyle(fontSize: 30), - ), - ), - ), - ); - }), - ), - ), - AnimatedContainer( - duration: Duration(milliseconds: 300), - height: editEvent != null || replyEvent != null ? 56 : 0, - child: Material( - color: Theme.of(context).secondaryHeaderColor, - child: Row( - children: [ - IconButton( - icon: Icon(Icons.close), - onPressed: () => setState(() { - replyEvent = null; - editEvent = null; - }), - ), - Expanded( - child: replyEvent != null - ? ReplyContent(replyEvent, timeline: timeline) - : _EditContent( - editEvent?.getDisplayEvent(timeline)), - ), - ], + }, + scrollToEventId: + (String eventId) => + _scrollToEventId(eventId, + context: context), + longPressSelect: + selectedEvents.isEmpty, + selected: selectedEvents.contains( + filteredEvents[i - 1]), + timeline: timeline, + nextEvent: i >= 2 + ? filteredEvents[i - 2] + : null), + ), + ); + }); + }, ), ), - ), - Divider( - height: 1, - color: Theme.of(context).secondaryHeaderColor, - thickness: 1, - ), - room.canSendDefaultMessages && room.membership == Membership.join - ? Container( - decoration: BoxDecoration( - color: Theme.of(context).backgroundColor, - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: selectMode - ? [ - Container( - height: 56, - child: FlatButton( - onPressed: () => - forwardEventsAction(context), - child: Row( - children: [ - Icon(Icons.keyboard_arrow_left), - Text(L10n.of(context).forward), - ], - ), - ), - ), - selectedEvents.length == 1 - ? selectedEvents.first - .getDisplayEvent(timeline) - .status > - 0 - ? Container( - height: 56, - child: FlatButton( - onPressed: () => replyAction(), - child: Row( - children: [ - Text(L10n.of(context).reply), - Icon(Icons - .keyboard_arrow_right), - ], - ), - ), - ) - : Container( - height: 56, - child: FlatButton( - onPressed: () => - sendAgainAction(timeline), - child: Row( - children: [ - Text(L10n.of(context) - .tryToSendAgain), - SizedBox(width: 4), - Icon(Icons.send, size: 16), - ], - ), - ), - ) - : Container(), - ] - : [ - if (inputText.isEmpty) + AnimatedContainer( + duration: Duration(milliseconds: 300), + height: (editEvent == null && + replyEvent == null && + selectedEvents.length == 1) + ? 56 + : 0, + child: Material( + color: Theme.of(context).secondaryHeaderColor, + child: Builder(builder: (context) { + if (!(editEvent == null && + replyEvent == null && + selectedEvents.length == 1)) { + return Container(); + } + var emojis = List.from(AppEmojis.emojis); + final allReactionEvents = selectedEvents.first + .aggregatedEvents( + timeline, RelationshipTypes.Reaction) + ?.where((event) => + event.senderId == event.room.client.userID && + event.type == 'm.reaction'); + + allReactionEvents.forEach((event) { + try { + emojis.remove(event.content['m.relates_to']['key']); + } catch (_) {} + }); + return ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: emojis.length, + itemBuilder: (c, i) => InkWell( + borderRadius: BorderRadius.circular(8), + onTap: () { + SimpleDialogs(context).tryRequestWithLoadingDialog( + room.sendReaction( + selectedEvents.first.eventId, + emojis[i], + ), + ); + setState(() => selectedEvents.clear()); + }, + child: Container( + width: 56, + height: 56, + alignment: Alignment.center, + child: Text( + emojis[i], + style: TextStyle(fontSize: 30), + ), + ), + ), + ); + }), + ), + ), + AnimatedContainer( + duration: Duration(milliseconds: 300), + height: editEvent != null || replyEvent != null ? 56 : 0, + child: Material( + color: Theme.of(context).secondaryHeaderColor, + child: Row( + children: [ + IconButton( + icon: Icon(Icons.close), + onPressed: () => setState(() { + if (editEvent != null) { + inputText = sendController.text = pendingText; + pendingText = ''; + } + replyEvent = null; + editEvent = null; + }), + ), + Expanded( + child: replyEvent != null + ? ReplyContent(replyEvent, timeline: timeline) + : _EditContent( + editEvent?.getDisplayEvent(timeline)), + ), + ], + ), + ), + ), + Divider( + height: 1, + color: Theme.of(context).secondaryHeaderColor, + thickness: 1, + ), + room.canSendDefaultMessages && + room.membership == Membership.join + ? Container( + decoration: BoxDecoration( + color: Theme.of(context).backgroundColor, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: selectMode + ? [ Container( height: 56, - alignment: Alignment.center, - child: PopupMenuButton( - icon: Icon(Icons.add), - onSelected: (String choice) async { - if (choice == 'file') { - sendFileAction(context); - } else if (choice == 'image') { - sendImageAction(context); - } - if (choice == 'camera') { - openCameraAction(context); - } - if (choice == 'voice') { - voiceMessageAction(context); - } - }, - itemBuilder: (BuildContext context) => - >[ - PopupMenuItem( - value: 'file', - child: ListTile( - leading: CircleAvatar( - backgroundColor: Colors.green, - foregroundColor: Colors.white, - child: Icon(Icons.attachment), - ), - title: - Text(L10n.of(context).sendFile), - contentPadding: EdgeInsets.all(0), - ), - ), - PopupMenuItem( - value: 'image', - child: ListTile( - leading: CircleAvatar( - backgroundColor: Colors.blue, - foregroundColor: Colors.white, - child: Icon(Icons.image), - ), - title: Text( - L10n.of(context).sendImage), - contentPadding: EdgeInsets.all(0), - ), - ), - if (PlatformInfos.isMobile) + child: FlatButton( + onPressed: () => + forwardEventsAction(context), + child: Row( + children: [ + Icon(Icons.keyboard_arrow_left), + Text(L10n.of(context).forward), + ], + ), + ), + ), + selectedEvents.length == 1 + ? selectedEvents.first + .getDisplayEvent(timeline) + .status > + 0 + ? Container( + height: 56, + child: FlatButton( + onPressed: () => replyAction(), + child: Row( + children: [ + Text( + L10n.of(context).reply), + Icon(Icons + .keyboard_arrow_right), + ], + ), + ), + ) + : Container( + height: 56, + child: FlatButton( + onPressed: () => + sendAgainAction(timeline), + child: Row( + children: [ + Text(L10n.of(context) + .tryToSendAgain), + SizedBox(width: 4), + Icon(Icons.send, size: 16), + ], + ), + ), + ) + : Container(), + ] + : [ + if (inputText.isEmpty) + Container( + height: 56, + alignment: Alignment.center, + child: PopupMenuButton( + icon: Icon(Icons.add), + onSelected: (String choice) async { + if (choice == 'file') { + sendFileAction(context); + } else if (choice == 'image') { + sendImageAction(context); + } + if (choice == 'camera') { + openCameraAction(context); + } + if (choice == 'voice') { + voiceMessageAction(context); + } + }, + itemBuilder: (BuildContext context) => + >[ PopupMenuItem( - value: 'camera', + value: 'file', child: ListTile( leading: CircleAvatar( - backgroundColor: Colors.purple, + backgroundColor: Colors.green, foregroundColor: Colors.white, - child: Icon(Icons.camera_alt), + child: Icon(Icons.attachment), ), title: Text( - L10n.of(context).openCamera), + L10n.of(context).sendFile), contentPadding: EdgeInsets.all(0), ), ), - if (PlatformInfos.isMobile) PopupMenuItem( - value: 'voice', + value: 'image', child: ListTile( leading: CircleAvatar( - backgroundColor: Colors.red, + backgroundColor: Colors.blue, foregroundColor: Colors.white, - child: Icon(Icons.mic), + child: Icon(Icons.image), ), - title: Text(L10n.of(context) - .voiceMessage), + title: Text( + L10n.of(context).sendImage), contentPadding: EdgeInsets.all(0), ), ), - ], - ), - ), - Container( - height: 56, - alignment: Alignment.center, - child: EncryptionButton(room), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 4.0), - child: InputBar( - room: room, - minLines: 1, - maxLines: kIsWeb ? 1 : 8, - autofocus: !PlatformInfos.isMobile, - keyboardType: !PlatformInfos.isMobile - ? TextInputType.text - : TextInputType.multiline, - onSubmitted: (String text) { - send(); - FocusScope.of(context) - .requestFocus(inputFocus); - }, - focusNode: inputFocus, - controller: sendController, - decoration: InputDecoration( - hintText: - L10n.of(context).writeAMessage, - hintMaxLines: 1, - border: InputBorder.none, + if (PlatformInfos.isMobile) + PopupMenuItem( + value: 'camera', + child: ListTile( + leading: CircleAvatar( + backgroundColor: + Colors.purple, + foregroundColor: Colors.white, + child: Icon(Icons.camera_alt), + ), + title: Text(L10n.of(context) + .openCamera), + contentPadding: + EdgeInsets.all(0), + ), + ), + if (PlatformInfos.isMobile) + PopupMenuItem( + value: 'voice', + child: ListTile( + leading: CircleAvatar( + backgroundColor: Colors.red, + foregroundColor: Colors.white, + child: Icon(Icons.mic), + ), + title: Text(L10n.of(context) + .voiceMessage), + contentPadding: + EdgeInsets.all(0), + ), + ), + ], ), - onChanged: (String text) { - typingCoolDown?.cancel(); - typingCoolDown = - Timer(Duration(seconds: 2), () { - typingCoolDown = null; - currentlyTyping = false; - room.sendTypingInfo(false); - }); - typingTimeout ??= - Timer(Duration(seconds: 30), () { - typingTimeout = null; - currentlyTyping = false; - }); - if (!currentlyTyping) { - currentlyTyping = true; - room.sendTypingInfo(true, - timeout: Duration(seconds: 30) - .inMilliseconds); - } - // Workaround for a current desktop bug - if (!PlatformInfos.isBetaDesktop) { - setState(() => inputText = text); - } - }, ), - ), - ), - if (PlatformInfos.isMobile && inputText.isEmpty) Container( height: 56, alignment: Alignment.center, - child: IconButton( - icon: Icon(Icons.mic), - onPressed: () => - voiceMessageAction(context), + child: EncryptionButton(room), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 4.0), + child: InputBar( + room: room, + minLines: 1, + maxLines: kIsWeb ? 1 : 8, + autofocus: !PlatformInfos.isMobile, + keyboardType: !PlatformInfos.isMobile + ? TextInputType.text + : TextInputType.multiline, + onSubmitted: (String text) { + send(); + FocusScope.of(context) + .requestFocus(inputFocus); + }, + focusNode: inputFocus, + controller: sendController, + decoration: InputDecoration( + hintText: + L10n.of(context).writeAMessage, + hintMaxLines: 1, + border: InputBorder.none, + ), + onChanged: (String text) { + typingCoolDown?.cancel(); + typingCoolDown = + Timer(Duration(seconds: 2), () { + typingCoolDown = null; + currentlyTyping = false; + room.sendTypingInfo(false); + }); + typingTimeout ??= + Timer(Duration(seconds: 30), () { + typingTimeout = null; + currentlyTyping = false; + }); + if (!currentlyTyping) { + currentlyTyping = true; + room.sendTypingInfo(true, + timeout: Duration(seconds: 30) + .inMilliseconds); + } + // Workaround for a current desktop bug + if (!PlatformInfos.isBetaDesktop) { + setState(() => inputText = text); + } + }, + ), ), ), - if (!PlatformInfos.isMobile || - inputText.isNotEmpty) - Container( - height: 56, - alignment: Alignment.center, - child: IconButton( - icon: Icon(Icons.send), - onPressed: () => send(), + if (PlatformInfos.isMobile && + inputText.isEmpty) + Container( + height: 56, + alignment: Alignment.center, + child: IconButton( + icon: Icon(Icons.mic), + onPressed: () => + voiceMessageAction(context), + ), ), - ), - ], - ), - ) - : Container(), - ], + if (!PlatformInfos.isMobile || + inputText.isNotEmpty) + Container( + height: 56, + alignment: Alignment.center, + child: IconButton( + icon: Icon(Icons.send), + onPressed: () => send(), + ), + ), + ], + ), + ) + : Container(), + ], + ), ), ], ), diff --git a/lib/views/chat_list.dart b/lib/views/chat_list.dart index d56c3d43..e1f63af0 100644 --- a/lib/views/chat_list.dart +++ b/lib/views/chat_list.dart @@ -3,18 +3,15 @@ import 'dart:io'; import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/matrix_api.dart'; -import 'package:fluffychat/components/avatar.dart'; import 'package:fluffychat/components/connection_status_header.dart'; import 'package:fluffychat/components/dialogs/simple_dialogs.dart'; -import 'package:fluffychat/components/list_items/status_list_item.dart'; import 'package:fluffychat/components/list_items/public_room_list_item.dart'; +import 'package:fluffychat/utils/fluffy_share.dart'; import 'package:fluffychat/utils/platform_infos.dart'; -import 'package:fluffychat/views/status_view.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:receive_sharing_intent/receive_sharing_intent.dart'; -import 'package:share/share.dart'; import '../components/adaptive_page_layout.dart'; import '../components/list_items/chat_list_item.dart'; @@ -198,29 +195,23 @@ class _ChatListState extends State { ); } - void _setStatus(BuildContext context, {bool fromDrawer = false}) async { - if (fromDrawer) Navigator.of(context).pop(); - final ownProfile = await SimpleDialogs(context) - .tryRequestWithLoadingDialog(Matrix.of(context).client.ownProfile); - String composeText; - if (Matrix.of(context).shareContent != null && - Matrix.of(context).shareContent['msgtype'] == 'm.text') { - composeText = Matrix.of(context).shareContent['body']; - Matrix.of(context).shareContent = null; - } - if (ownProfile is Profile) { - await Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => StatusView( - composeMode: true, - avatarUrl: ownProfile.avatarUrl, - displayname: ownProfile.displayname ?? - Matrix.of(context).client.userID.localpart, - composeText: composeText, - ), - ), - ); - } + void _setStatus(BuildContext context) async { + Navigator.of(context).pop(); + final statusMsg = await SimpleDialogs(context).enterText( + titleText: L10n.of(context).setStatus, + labelText: L10n.of(context).setStatus, + hintText: L10n.of(context).statusExampleMessage, + multiLine: true, + ); + if (statusMsg?.isEmpty ?? true) return; + final client = Matrix.of(context).client; + await SimpleDialogs(context).tryRequestWithLoadingDialog( + client.sendPresence( + client.userID, + PresenceType.online, + statusMsg: statusMsg, + ), + ); return; } @@ -302,8 +293,7 @@ class _ChatListState extends State { ListTile( leading: Icon(Icons.edit), title: Text(L10n.of(context).setStatus), - onTap: () => - _setStatus(context, fromDrawer: true), + onTap: () => _setStatus(context), ), Divider(height: 1), ListTile( @@ -338,9 +328,11 @@ class _ChatListState extends State { title: Text(L10n.of(context).inviteContact), onTap: () { Navigator.of(context).pop(); - Share.share(L10n.of(context).inviteText( - Matrix.of(context).client.userID, - 'https://matrix.to/#/${Matrix.of(context).client.userID}')); + FluffyShare.share( + L10n.of(context).inviteText( + Matrix.of(context).client.userID, + 'https://matrix.to/#/${Matrix.of(context).client.userID}'), + context); }, ), ], @@ -422,31 +414,14 @@ class _ChatListState extends State { ), floatingActionButton: AdaptivePageLayout.columnMode(context) ? null - : Column( - mainAxisSize: MainAxisSize.min, - children: [ - FloatingActionButton( - heroTag: null, - child: Icon( - Icons.edit, - color: Theme.of(context).primaryColor, - ), - elevation: 1, - backgroundColor: - Theme.of(context).secondaryHeaderColor, - onPressed: () => _setStatus(context), - ), - SizedBox(height: 16.0), - FloatingActionButton( - child: Icon(Icons.add), - backgroundColor: Theme.of(context).primaryColor, - onPressed: () => Navigator.of(context) - .pushAndRemoveUntil( - AppRoute.defaultRoute( - context, NewPrivateChatView()), - (r) => r.isFirst), - ), - ], + : FloatingActionButton( + child: Icon(Icons.add), + backgroundColor: Theme.of(context).primaryColor, + onPressed: () => Navigator.of(context) + .pushAndRemoveUntil( + AppRoute.defaultRoute( + context, NewPrivateChatView()), + (r) => r.isFirst), ), body: Column( children: [ @@ -506,94 +481,28 @@ class _ChatListState extends State { final totalCount = rooms.length + publicRoomsCount; return ListView.separated( - controller: _scrollController, - separatorBuilder: (BuildContext context, - int i) => - i == totalCount - publicRoomsCount - ? ListTile( - title: Text( - L10n.of(context) - .publicRooms + - ':', - style: TextStyle( - fontWeight: - FontWeight.bold, - color: Theme.of(context) - .primaryColor, - ), + controller: _scrollController, + separatorBuilder: (BuildContext context, + int i) => + i == totalCount - publicRoomsCount + ? ListTile( + title: Text( + L10n.of(context) + .publicRooms + + ':', + style: TextStyle( + fontWeight: + FontWeight.bold, + color: Theme.of(context) + .primaryColor, ), - ) - : Container(), - itemCount: totalCount + 1, - itemBuilder: - (BuildContext context, int i) { - if (i == 0) { - final displayPresences = - selectMode != SelectMode.share; - final displayShareStatus = - selectMode == - SelectMode.share && - Matrix.of(context) - .shareContent[ - 'msgtype'] == - 'm.text'; - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - AnimatedContainer( - duration: Duration( - milliseconds: 300), - height: displayPresences - ? 78 - : displayShareStatus - ? 56 - : 0, - child: displayPresences - ? ListView.builder( - scrollDirection: - Axis.horizontal, - itemCount: - Matrix.of(context) - .userStatuses - .length, - itemBuilder: (BuildContext - context, - int i) => - StatusListItem(Matrix - .of(context) - .userStatuses[i]), - ) - : displayShareStatus - ? ListTile( - leading: - CircleAvatar( - radius: Avatar - .defaultSize / - 2, - backgroundColor: - Theme.of( - context) - .secondaryHeaderColor, - child: Icon( - Icons.edit, - color: Theme.of( - context) - .primaryColor, - ), - ), - title: Text(L10n.of( - context) - .setStatus), - onTap: () => - _setStatus( - context)) - : null, - ), - ], - ); - } - i--; - return i < rooms.length + ), + ) + : Container(), + itemCount: totalCount, + itemBuilder: (BuildContext context, + int i) => + i < rooms.length ? ChatListItem( rooms[i], selected: _selectedRoomIds @@ -614,8 +523,9 @@ class _ChatListState extends State { ) : PublicRoomListItem( publicRoomsResponse - .chunk[i - rooms.length]); - }); + .chunk[i - rooms.length], + ), + ); } else { return Center( child: CircularProgressIndicator(), diff --git a/lib/views/homeserver_picker.dart b/lib/views/homeserver_picker.dart index a7d7b436..8d6fbcfe 100644 --- a/lib/views/homeserver_picker.dart +++ b/lib/views/homeserver_picker.dart @@ -1,5 +1,6 @@ import 'dart:math'; +import 'package:famedlysdk/famedlysdk.dart'; import 'package:fluffychat/components/dialogs/simple_dialogs.dart'; import 'package:fluffychat/components/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; @@ -31,12 +32,17 @@ class HomeserverPicker extends StatelessWidget { } final success = await SimpleDialogs(context).tryRequestWithLoadingDialog( - Matrix.of(context).client.checkServer(homeserver)); - if (success != false) { + checkHomeserver(homeserver, Matrix.of(context).client)); + if (success == true) { await Navigator.of(context).push(AppRoute(SignUp())); } } + Future checkHomeserver(dynamic homeserver, Client client) async { + await client.checkHomeserver(homeserver); + return true; + } + @override Widget build(BuildContext context) { return Scaffold( diff --git a/lib/views/login.dart b/lib/views/login.dart index d7d327a1..14e3e954 100644 --- a/lib/views/login.dart +++ b/lib/views/login.dart @@ -94,7 +94,7 @@ class _LoginState extends State { if ((newDomain?.isNotEmpty ?? false) && newDomain != Matrix.of(context).client.homeserver.toString()) { await SimpleDialogs(context).tryRequestWithErrorToast( - Matrix.of(context).client.checkServer(newDomain)); + Matrix.of(context).client.checkHomeserver(newDomain)); setState(() => usernameError = null); } } catch (e) { diff --git a/lib/views/new_private_chat.dart b/lib/views/new_private_chat.dart index 46e9fd3f..66b9da42 100644 --- a/lib/views/new_private_chat.dart +++ b/lib/views/new_private_chat.dart @@ -7,9 +7,9 @@ import 'package:fluffychat/components/avatar.dart'; import 'package:fluffychat/components/dialogs/simple_dialogs.dart'; import 'package:fluffychat/components/matrix.dart'; import 'package:fluffychat/utils/app_route.dart'; +import 'package:fluffychat/utils/fluffy_share.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:share/share.dart'; import 'chat.dart'; import 'chat_list.dart'; @@ -204,9 +204,10 @@ class _NewPrivateChatState extends State<_NewPrivateChat> { Icons.share, size: 16, ), - onTap: () => Share.share(L10n.of(context).inviteText( - Matrix.of(context).client.userID, - 'https://matrix.to/#/${Matrix.of(context).client.userID}')), + onTap: () => FluffyShare.share( + L10n.of(context).inviteText(Matrix.of(context).client.userID, + 'https://matrix.to/#/${Matrix.of(context).client.userID}'), + context), title: Text( '${L10n.of(context).yourOwnUsername}:', style: TextStyle( diff --git a/lib/views/settings.dart b/lib/views/settings.dart index 8896a39e..bd4d4a54 100644 --- a/lib/views/settings.dart +++ b/lib/views/settings.dart @@ -185,7 +185,7 @@ class _SettingsState extends State { void deleteWallpaperAction(BuildContext context) async { Matrix.of(context).wallpaper = null; - await Matrix.of(context).store.setItem('chat.fluffy.wallpaper', null); + await Matrix.of(context).store.deleteItem('chat.fluffy.wallpaper'); setState(() => null); } diff --git a/lib/views/status_view.dart b/lib/views/status_view.dart deleted file mode 100644 index 0ea3ba25..00000000 --- a/lib/views/status_view.dart +++ /dev/null @@ -1,186 +0,0 @@ -import 'package:famedlysdk/famedlysdk.dart'; -import 'package:fluffychat/components/avatar.dart'; -import 'package:fluffychat/components/dialogs/simple_dialogs.dart'; -import 'package:fluffychat/components/matrix.dart'; -import 'package:fluffychat/utils/url_launcher.dart'; -import 'package:fluffychat/utils/user_status.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:fluffychat/utils/app_route.dart'; -import 'package:fluffychat/utils/string_color.dart'; -import 'package:flutter/material.dart'; -import 'package:matrix_link_text/link_text.dart'; - -import 'chat.dart'; - -class StatusView extends StatelessWidget { - final Uri avatarUrl; - final String displayname; - final UserStatus status; - final bool composeMode; - final String composeText; - final TextEditingController _composeController; - - StatusView({ - this.composeMode = false, - this.status, - this.avatarUrl, - this.displayname, - this.composeText, - Key key, - }) : _composeController = TextEditingController(text: composeText), - super(key: key); - - void _sendMessageAction(BuildContext context) async { - final roomId = await User( - status.userId, - room: Room(id: '', client: Matrix.of(context).client), - ).startDirectChat(); - await Navigator.of(context).pushAndRemoveUntil( - AppRoute.defaultRoute( - context, - ChatView(roomId), - ), - (Route r) => r.isFirst); - } - - void _setStatusAction(BuildContext context) async { - if (_composeController.text.isEmpty) return; - await SimpleDialogs(context).tryRequestWithLoadingDialog( - Matrix.of(context).client.sendPresence( - Matrix.of(context).client.userID, PresenceType.online, - statusMsg: _composeController.text), - ); - await Navigator.of(context).popUntil((Route r) => r.isFirst); - } - - void _removeStatusAction(BuildContext context) async { - final success = await SimpleDialogs(context).tryRequestWithLoadingDialog( - Matrix.of(context).client.sendPresence( - Matrix.of(context).client.userID, - PresenceType.online, - statusMsg: - ' ', // Send this empty String make sure that all other devices will get an update - ), - ); - if (success == false) return; - await Navigator.of(context).popUntil((Route r) => r.isFirst); - } - - @override - Widget build(BuildContext context) { - if (composeMode == false && status == null) { - throw ('If composeMode is null then the presence must be not null!'); - } - final padding = const EdgeInsets.only( - top: 16.0, - right: 16.0, - left: 16.0, - bottom: 64.0, - ); - return Scaffold( - backgroundColor: displayname.color, - extendBody: true, - appBar: AppBar( - titleSpacing: 0.0, - brightness: Brightness.dark, - leading: IconButton( - icon: Icon( - Icons.close, - color: Colors.white, - ), - onPressed: Navigator.of(context).pop, - ), - backgroundColor: Colors.transparent, - elevation: 1, - title: ListTile( - contentPadding: EdgeInsets.zero, - leading: Avatar(avatarUrl, displayname), - title: Text( - displayname, - style: TextStyle(color: Colors.white), - ), - subtitle: Text( - status?.userId ?? Matrix.of(context).client.userID, - maxLines: 1, - style: TextStyle(color: Colors.white), - ), - ), - actions: - !composeMode && status.userId == Matrix.of(context).client.userID - ? [ - IconButton( - icon: Icon(Icons.archive), - onPressed: () => _removeStatusAction(context), - color: Colors.white, - ), - ] - : null, - ), - body: Container( - alignment: Alignment.center, - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - displayname.color, - Theme.of(context).primaryColor, - displayname.color, - ], - ), - ), - child: composeMode - ? Padding( - padding: padding, - child: TextField( - controller: _composeController, - autofocus: true, - minLines: 1, - maxLines: 20, - style: TextStyle( - fontSize: 30, - color: Colors.white, - ), - textAlign: TextAlign.center, - decoration: InputDecoration( - border: InputBorder.none, - ), - ), - ) - : ListView( - shrinkWrap: true, - padding: padding, - children: [ - LinkText( - text: status.statusMsg, - textAlign: TextAlign.center, - textStyle: TextStyle( - fontSize: 30, - color: Colors.white, - ), - linkStyle: TextStyle( - fontSize: 30, - color: Colors.white70, - decoration: TextDecoration.underline, - ), - onLinkTap: (url) => UrlLauncher(context, url).launchUrl(), - ), - ], - ), - ), - floatingActionButton: - !composeMode && status.userId == Matrix.of(context).client.userID - ? null - : FloatingActionButton.extended( - backgroundColor: Theme.of(context).primaryColor, - icon: Icon(composeMode ? Icons.edit : Icons.message_outlined), - label: Text(composeMode - ? L10n.of(context).setStatus - : L10n.of(context).sendAMessage), - onPressed: () => composeMode - ? _setStatusAction(context) - : _sendMessageAction(context), - ), - ); - } -} diff --git a/linux/main.cc b/linux/main.cc index 058e6178..e7c5c543 100644 --- a/linux/main.cc +++ b/linux/main.cc @@ -1,10 +1,6 @@ #include "my_application.h" int main(int argc, char** argv) { - // Only X11 is currently supported. - // Wayland support is being developed: https://github.com/flutter/flutter/issues/57932. - gdk_set_allowed_backends("x11"); - g_autoptr(MyApplication) app = my_application_new(); return g_application_run(G_APPLICATION(app), argc, argv); } diff --git a/pubspec.lock b/pubspec.lock index 043f3e03..b7474137 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -168,7 +168,14 @@ packages: name: cupertino_icons url: "https://pub.dartlang.org" source: hosted - version: "0.1.3" + version: "1.0.0" + dapackages: + dependency: "direct dev" + description: + name: dapackages + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" dart_style: dependency: transitive description: @@ -183,6 +190,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.0.3" + downloads_path_provider_28: + dependency: "direct main" + description: + name: downloads_path_provider_28 + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0" encrypt: dependency: transitive description: @@ -201,8 +215,8 @@ packages: dependency: "direct main" description: path: "." - ref: be6824b7465b2bda7e5b769254be5cddd207b479 - resolved-ref: be6824b7465b2bda7e5b769254be5cddd207b479 + ref: "955fb747c29eab76b17eb9a13ebc15026e917fb8" + resolved-ref: "955fb747c29eab76b17eb9a13ebc15026e917fb8" url: "https://gitlab.com/famedly/famedlysdk.git" source: git version: "0.0.1" @@ -226,7 +240,7 @@ packages: name: file_chooser url: "https://pub.dartlang.org" source: hosted - version: "0.1.5" + version: "0.1.6" file_picker: dependency: transitive description: @@ -261,7 +275,7 @@ packages: name: firebase_core url: "https://pub.dartlang.org" source: hosted - version: "0.5.0" + version: "0.5.0+1" firebase_core_platform_interface: dependency: transitive description: @@ -282,7 +296,7 @@ packages: name: firebase_messaging url: "https://pub.dartlang.org" source: hosted - version: "7.0.2" + version: "7.0.3" flutter: dependency: "direct main" description: flutter @@ -302,6 +316,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0" + flutter_highlight: + dependency: transitive + description: + name: flutter_highlight + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.0" flutter_keyboard_visibility: dependency: transitive description: @@ -322,14 +343,14 @@ packages: name: flutter_local_notifications url: "https://pub.dartlang.org" source: hosted - version: "1.4.4+4" + version: "3.0.1" flutter_local_notifications_platform_interface: dependency: transitive description: name: flutter_local_notifications_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "2.0.0" flutter_localizations: dependency: "direct main" description: flutter @@ -341,7 +362,7 @@ packages: name: flutter_matrix_html url: "https://pub.dartlang.org" source: hosted - version: "0.1.9" + version: "0.1.10" flutter_olm: dependency: "direct main" description: @@ -362,7 +383,7 @@ packages: name: flutter_secure_storage url: "https://pub.dartlang.org" source: hosted - version: "3.3.4" + version: "3.3.5" flutter_slidable: dependency: "direct main" description: @@ -401,6 +422,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.0" + highlight: + dependency: transitive + description: + name: highlight + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.0" html: dependency: transitive description: @@ -449,7 +477,7 @@ packages: name: image_picker url: "https://pub.dartlang.org" source: hosted - version: "0.6.7+11" + version: "0.6.7+12" image_picker_platform_interface: dependency: transitive description: @@ -498,7 +526,7 @@ packages: name: localstorage url: "https://pub.dartlang.org" source: hosted - version: "3.0.2+5" + version: "3.0.3+6" logging: dependency: transitive description: @@ -533,7 +561,7 @@ packages: name: matrix_link_text url: "https://pub.dartlang.org" source: hosted - version: "0.3.1" + version: "0.3.2" meta: dependency: transitive description: @@ -561,7 +589,7 @@ packages: name: moor url: "https://pub.dartlang.org" source: hosted - version: "3.3.1" + version: "3.4.0" native_imaging: dependency: "direct main" description: @@ -612,7 +640,7 @@ packages: name: open_file url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.0.3" package_config: dependency: transitive description: @@ -647,7 +675,7 @@ packages: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "1.6.21" + version: "1.6.22" path_provider_linux: dependency: transitive description: @@ -683,6 +711,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.10.0-nullsafety.1" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.1+1" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" petitparser: dependency: transitive description: @@ -787,7 +829,7 @@ packages: name: share url: "https://pub.dartlang.org" source: hosted - version: "0.6.5+2" + version: "0.6.5+4" shelf: dependency: transitive description: @@ -848,7 +890,7 @@ packages: name: sqflite url: "https://pub.dartlang.org" source: hosted - version: "1.3.1+1" + version: "1.3.2" sqflite_common: dependency: transitive description: @@ -862,7 +904,7 @@ packages: name: sqlite3 url: "https://pub.dartlang.org" source: hosted - version: "0.1.5" + version: "0.1.7" sqlite3_flutter_libs: dependency: "direct main" description: @@ -933,6 +975,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.3.12-nullsafety.5" + timezone: + dependency: transitive + description: + name: timezone + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.9" typed_data: dependency: transitive description: @@ -967,7 +1016,7 @@ packages: name: url_launcher url: "https://pub.dartlang.org" source: hosted - version: "5.7.2" + version: "5.7.8" url_launcher_linux: dependency: transitive description: @@ -988,14 +1037,14 @@ packages: name: url_launcher_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.8" + version: "1.0.9" url_launcher_web: dependency: transitive description: name: url_launcher_web url: "https://pub.dartlang.org" source: hosted - version: "0.1.4+1" + version: "0.1.5" url_launcher_windows: dependency: transitive description: @@ -1058,7 +1107,7 @@ packages: name: webview_flutter url: "https://pub.dartlang.org" source: hosted - version: "0.3.24" + version: "1.0.5" win32: dependency: transitive description: @@ -1095,5 +1144,5 @@ packages: source: hosted version: "0.1.2" sdks: - dart: ">=2.10.0-110 <=2.11.0-161.0.dev" - flutter: ">=1.20.0 <2.0.0" + dart: ">=2.10.2 <2.11.0" + flutter: ">=1.22.2 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 094f0f98..98d6aaf8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ description: Chat with your friends. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.20.0+47 +version: 0.21.1+48 environment: sdk: ">=2.6.0 <3.0.0" @@ -22,48 +22,50 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^0.1.2 + cupertino_icons: ^1.0.0 famedlysdk: git: url: https://gitlab.com/famedly/famedlysdk.git - ref: be6824b7465b2bda7e5b769254be5cddd207b479 + ref: 955fb747c29eab76b17eb9a13ebc15026e917fb8 - localstorage: ^3.0.1+4 - file_picker_cross: ^4.2.2 - image_picker: ^0.6.7+11 - url_launcher: ^5.7.2 + localstorage: ^3.0.3+6 + file_picker_cross: 4.2.2 + image_picker: ^0.6.7+12 + url_launcher: ^5.7.8 cached_network_image: ^2.3.3 - firebase_messaging: ^7.0.2 - flutter_local_notifications: ^1.4.3 + firebase_messaging: ^7.0.3 + flutter_local_notifications: ^3.0.1 # desktop_notifications: ^0.0.0-dev.4 // Currently blocked by: https://github.com/canonical/desktop_notifications.dart/issues/5 - matrix_link_text: ^0.3.1 - path_provider: ^1.6.21 - webview_flutter: ^0.3.19+9 - share: ^0.6.3+5 - flutter_secure_storage: ^3.3.4 - http: ^0.12.0+4 - universal_html: ^1.1.12 - receive_sharing_intent: ^1.3.3 - flutter_slidable: ^0.5.4 + matrix_link_text: ^0.3.2 + path_provider: ^1.6.22 + downloads_path_provider_28: ^0.1.0 + permission_handler: ^5.0.1+1 + webview_flutter: ^1.0.5 + share: ^0.6.5+4 + flutter_secure_storage: ^3.3.5 + http: ^0.12.2 + universal_html: ^1.2.3 + receive_sharing_intent: ^1.4.1 + flutter_slidable: ^0.5.7 photo_view: ^0.10.2 - flutter_sound: ^2.1.1 - open_file: ^3.0.1 - mime_type: ^0.3.0 - bot_toast: ^3.0.0 - flutter_matrix_html: ^0.1.9 - moor: ^3.3.1 + flutter_sound: 2.1.1 + open_file: ^3.0.3 + mime_type: ^0.3.2 + bot_toast: ^3.0.4 + flutter_matrix_html: ^0.1.10 + moor: ^3.4.0 sqlite3_flutter_libs: ^0.2.0 - sqlite3: ^0.1.4 - random_string: ^2.0.1 - flutter_typeahead: ^1.8.1 + sqlite3: ^0.1.7 + random_string: ^2.1.0 + flutter_typeahead: ^1.8.8 flutter_olm: ^1.0.1 intl: ^0.16.1 - intl_translation: ^0.17.9 + intl_translation: ^0.17.10+1 circular_check_box: ^1.0.4 flutter_localizations: sdk: flutter - sqflite: ^1.1.7 # Still used to obtain the database location + sqflite: ^1.3.2 # Still used to obtain the database location native_imaging: git: url: https://gitlab.com/famedly/libraries/native_imaging.git @@ -78,7 +80,8 @@ dev_dependencies: sdk: flutter flutter_launcher_icons: "^0.7.4" - pedantic: ^1.9.0 + pedantic: ^1.9.2 + dapackages: ^1.3.0 flutter_icons: android: "launcher_icon" diff --git a/scripts/open-mr.sh b/scripts/open-mr.sh new file mode 100755 index 00000000..d2d4b7d3 --- /dev/null +++ b/scripts/open-mr.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +# source: https://about.gitlab.com/blog/2017/09/05/how-to-automatically-create-a-new-mr-on-gitlab-with-gitlab-ci/ + +# Extract the host where the server is running, and add the URL to the APIs +[[ $HOST =~ ^https?://[^/]+ ]] && HOST="${BASH_REMATCH[0]}/api/v4/projects/" + +# Look which is the default branch +TARGET_BRANCH=`curl --silent "${HOST}${CI_PROJECT_ID}" --header "PRIVATE-TOKEN:${PRIVATE_TOKEN}" | python3 -c "import sys, json; print(json.load(sys.stdin)['default_branch'])"`; + +# The description of our new MR, we want to remove the branch after the MR has +# been closed +BODY="{ + \"id\": ${CI_PROJECT_ID}, + \"source_branch\": \"${UPDATE_BRANCH}\", + \"target_branch\": \"${TARGET_BRANCH}\", + \"remove_source_branch\": true, + \"title\": \"chore: automated dependency update\" +}"; + +# Require a list of all the merge request and take a look if there is already +# one with the same source branch +LISTMR=`curl --silent "${HOST}${CI_PROJECT_ID}/merge_requests?state=opened" --header "PRIVATE-TOKEN:${PRIVATE_TOKEN}"`; +COUNTBRANCHES=`echo ${LISTMR} | grep -o "\"source_branch\":\"${UPDATE_BRANCH}\"" | wc -l`; + +# No MR found, let's create a new one +if [ ${COUNTBRANCHES} -eq "0" ]; then + curl -X POST "${HOST}${CI_PROJECT_ID}/merge_requests" \ + --header "PRIVATE-TOKEN:${PRIVATE_TOKEN}" \ + --header "Content-Type: application/json" \ + --data "${BODY}"; + + echo "Opened a new dependency update MR." + exit; +fi + +echo "No new merge request opened."; diff --git a/snap/gui/fluffychat.desktop b/snap/gui/fluffychat.desktop new file mode 100755 index 00000000..88614875 --- /dev/null +++ b/snap/gui/fluffychat.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=FluffyChat +GenericName=Matrix Client +Comment=Chat with your friends +Exec=fluffychat +Icon=${SNAP}/meta/gui/fluffychat.png +Terminal=false +Type=Application +Categories=Network;Chat;InstantMessaging; diff --git a/snap/gui/fluffychat.png b/snap/gui/fluffychat.png new file mode 100644 index 00000000..7a46e62c Binary files /dev/null and b/snap/gui/fluffychat.png differ diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 26ac6640..3b5bd17c 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: fluffychat base: core18 -version: script +version: git summary: Open. Nonprofit. Cute ♥ description: | FluffyChat - Chat with your friends @@ -23,28 +23,63 @@ description: | Microblog: https://metalhead.club/@krille grade: devel +confinement: strict icon: assets/logo.png -confinement: devmode parts: - olm: # FIXME + olm: + plugin: cmake source: https://gitlab.matrix.org/matrix-org/olm.git source-type: git source-tag: 3.2.1 - plugin: cmake build-packages: - - build-essential - override-build: | - cd /root/parts/olm/src - cmake . -Bbuild - cmake --build build + - g++ fluffychat: - after: [olm] - plugin: dump - source: ./build/linux/release/bundle + plugin: flutter + source: . + flutter-target: lib/main.dart stage-packages: - libsqlite3-dev - + - libatk-bridge2.0-0 + - libatk1.0-0 + - libatspi2.0-0 + - libcairo-gobject2 + - libcairo2 + - libdatrie1 + - libegl1 + - libepoxy0 + - libfontconfig1 + - libfreetype6 + - libgdk-pixbuf2.0-0 + - libglvnd0 + - libgraphite2-3 + - libgtk-3-0 + - libharfbuzz0b + - libpango-1.0-0 + - libpangocairo-1.0-0 + - libpangoft2-1.0-0 + - libpixman-1-0 + - libpng16-16 + - libthai0 + - libwayland-client0 + - libwayland-cursor0 + - libwayland-egl1 + - libx11-6 + - libxau6 + - libxcb-render0 + - libxcb-shm0 + - libxcb1 + - libxcomposite1 + - libxcursor1 + - libxdamage1 + - libxdmcp6 + - libxext6 + - libxfixes3 + - libxi6 + - libxinerama1 + - libxkbcommon0 + - libxrandr2 + - libxrender1 slots: dbus-svc: interface: dbus @@ -55,7 +90,7 @@ apps: fluffychat: command: fluffychat extensions: - - gnome-3-28 + - flutter-dev plugs: - network - home