diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 3c08604..286f91d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -152,12 +152,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
@@ -196,6 +194,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
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3d11148..f7fac2e 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 5c3a6c5..843eb45 100644
--- a/README.md
+++ b/README.md
@@ -54,11 +54,6 @@ cd FurryChat
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 3d72600..b6b03c4 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 "dev.inex.furrychat"
- 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 93d5231..a3a9516 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 9e51f4f..cef8855 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 90cd3fc..28d1505 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 df508a4..a67377a 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 e890f8f..907ed5b 100644
--- a/lib/components/list_items/participant_list_item.dart
+++ b/lib/components/list_items/participant_list_item.dart
@@ -7,60 +7,13 @@ import '../../views/chat.dart';
import '../avatar.dart';
import '../dialogs/simple_dialogs.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 +27,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 5c8f1b9..0000000
--- a/lib/components/list_items/status_list_item.dart
+++ /dev/null
@@ -1,84 +0,0 @@
-import 'package:famedlysdk/famedlysdk.dart';
-import 'package:flutter/material.dart';
-
-import '../../utils/user_status.dart';
-import '../../views/status_view.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 241e072..c22fa16 100644
--- a/lib/components/matrix.dart
+++ b/lib/components/matrix.dart
@@ -82,8 +82,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 {
@@ -93,7 +92,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,
@@ -124,7 +122,6 @@ class MatrixState extends State {
StreamSubscription onNotification;
StreamSubscription onFocusSub;
StreamSubscription onBlurSub;
- StreamSubscription onPresenceSub;
void onJitsiCall(EventUpdate eventUpdate) {
final event = Event.fromJson(
@@ -247,12 +244,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)
@@ -331,7 +325,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)
@@ -341,64 +335,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 5fecdde..967abf0 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 0000000..22095ef
--- /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
+ ? Colors.green
+ : Colors.grey),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb
index eeb353c..5636cc4 100644
--- a/lib/l10n/intl_en.arb
+++ b/lib/l10n/intl_en.arb
@@ -920,6 +920,11 @@
"type": "text",
"placeholders": {}
},
+ "mention": "Mention",
+ "@mention": {
+ "type": "text",
+ "placeholders": {}
+ },
"messageWillBeRemovedWarning": "Message will be removed for all participants",
"@messageWillBeRemovedWarning": {
"type": "text",
@@ -1017,6 +1022,21 @@
"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",
diff --git a/lib/l10n/intl_eo.arb b/lib/l10n/intl_eo.arb
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/lib/l10n/intl_eo.arb
@@ -0,0 +1 @@
+{}
diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb
index c5cbc6f..089134f 100644
--- a/lib/l10n/intl_it.arb
+++ b/lib/l10n/intl_it.arb
@@ -461,5 +461,122 @@
"@askSSSSCache": {
"type": "text",
"placeholders": {}
+ },
+ "enterAUsername": "Inserisci un username",
+ "@enterAUsername": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "enterAGroupName": "Inserisci un nome del gruppo",
+ "@enterAGroupName": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "endedTheCall": "{senderName} è entrato in chiamata",
+ "@endedTheCall": {
+ "type": "text",
+ "placeholders": {
+ "senderName": {}
+ }
+ },
+ "end2endEncryptionSettings": "Impostazioni crittografia end-to-end",
+ "@end2endEncryptionSettings": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "encryptionNotEnabled": "Crittografia non abilitata",
+ "@encryptionNotEnabled": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "encryptionAlgorithm": "Algoritmo crittografia",
+ "@encryptionAlgorithm": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "encryption": "Crittografia",
+ "@encryption": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "enableEncryptionWarning": "Non potrai disabilitare la crittografia in futuro. Sei sicuro?",
+ "@enableEncryptionWarning": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "enableEmotesGlobally": "Abilita i pacchetti emotes globalmente",
+ "@enableEmotesGlobally": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "emptyChat": "Chat vuota",
+ "@emptyChat": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "emotePacks": "Pacchetti emotes della stanza",
+ "@emotePacks": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "emoteInvalid": "Shortcode emote invalido!",
+ "@emoteInvalid": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "emoteExists": "L'emote già esiste!",
+ "@emoteExists": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "emoteWarnNeedToPick": "Devi scegliere uno shortcode emote e aggiungere un immagine!",
+ "@emoteWarnNeedToPick": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "emoteShortcode": "Shortcode Emotes",
+ "@emoteShortcode": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "emoteSettings": "Impostazioni Emotes",
+ "@emoteSettings": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "editDisplayname": "Modifica nominativo",
+ "@editDisplayname": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "downloadFile": "Scarica file",
+ "@downloadFile": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "displaynameHasBeenChanged": "Il nominativo è stato cambiato",
+ "@displaynameHasBeenChanged": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "discardPicture": "Rimuovi immagine",
+ "@discardPicture": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "devices": "Dispositivi",
+ "@devices": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "device": "Dispositivo",
+ "@device": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "deny": "Declina",
+ "@deny": {
+ "type": "text",
+ "placeholders": {}
}
}
diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb
index 3bae65a..305877d 100644
--- a/lib/l10n/intl_ru.arb
+++ b/lib/l10n/intl_ru.arb
@@ -1733,7 +1733,7 @@
"type": "text",
"placeholders": {}
},
- "deactivateAccountWarning": "Это деактивирует вашу учётную запись пользователя. Это не может быть отменено! Вы уверены?",
+ "deactivateAccountWarning": "Это деактивирует вашу учётную запись пользователя. Данное действие не может быть отменено! Вы уверены?",
"@deactivateAccountWarning": {
"type": "text",
"placeholders": {}
@@ -1743,12 +1743,12 @@
"type": "text",
"placeholders": {}
},
- "enableEmotesGlobally": "Включить набор эмоджи глобально",
+ "enableEmotesGlobally": "Включить набор эмодзи глобально",
"@enableEmotesGlobally": {
"type": "text",
"placeholders": {}
},
- "emotePacks": "Наборы эмоджи для комнаты",
+ "emotePacks": "Наборы эмодзи для комнаты",
"@emotePacks": {
"type": "text",
"placeholders": {}
diff --git a/lib/l10n/intl_tr.arb b/lib/l10n/intl_tr.arb
index b9fd174..92ba185 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,7 +1718,7 @@
"type": "text",
"placeholders": {}
},
- "changeDeviceName": "Cihaz adını değiştir",
+ "changeDeviceName": "Aygıt adını değiştir",
"@changeDeviceName": {
"type": "text",
"placeholders": {}
diff --git a/lib/l10n/intl_vi.arb b/lib/l10n/intl_vi.arb
new file mode 100644
index 0000000..a8a59ba
--- /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/utils/famedlysdk_store.dart b/lib/utils/famedlysdk_store.dart
index f828e4c..9e671be 100644
--- a/lib/utils/famedlysdk_store.dart
+++ b/lib/utils/famedlysdk_store.dart
@@ -1,27 +1,12 @@
-import 'dart:async';
-import 'dart:convert';
-import 'dart:core';
-
-import 'package:famedlysdk/famedlysdk.dart';
-import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart';
+import '../famedlysdk.dart';
+import './platform_infos.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:localstorage/localstorage.dart';
-import 'package:olm/olm.dart' as olm; // needed for migration
import 'package:path_provider/path_provider.dart';
-import 'package:random_string/random_string.dart';
-
+import 'dart:async';
+import 'dart:core';
import './database/shared.dart';
-import 'platform_infos.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;
-}
+import 'package:random_string/random_string.dart';
Future getDatabase(Client client) async {
while (_generateDatabaseLock) {
@@ -32,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(
@@ -42,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;
@@ -58,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