Version 0.10.0

This commit is contained in:
Christian Pauly 2020-03-29 12:06:25 +02:00
parent 5367d9d037
commit a79e8b1d8d
16 changed files with 313 additions and 126 deletions

View File

@ -2,6 +2,12 @@
### New features ### New features
- Voice messages - Voice messages
- New message bubble design - New message bubble design
### Changes:
- Use SnackBars instead of Toasts
### Fixes:
- Minor fixes in the SDK
- Loading dialog when sending files is displayed too long
- Fixed device settings list
# Version 0.9.0 - 2020-03-13 # Version 0.9.0 - 2020-03-13
### New features ### New features

View File

@ -5,7 +5,6 @@ import 'package:fluffychat/i18n/i18n.dart';
import 'package:fluffychat/utils/app_route.dart'; import 'package:fluffychat/utils/app_route.dart';
import 'package:fluffychat/views/chat_encryption_settings.dart'; import 'package:fluffychat/views/chat_encryption_settings.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:toast/toast.dart';
import 'dialogs/simple_dialogs.dart'; import 'dialogs/simple_dialogs.dart';
import 'matrix.dart'; import 'matrix.dart';
@ -22,8 +21,11 @@ class _EncryptionButtonState extends State<EncryptionButton> {
void _enableEncryptionAction() async { void _enableEncryptionAction() async {
if (widget.room.encrypted) { if (widget.room.encrypted) {
Toast.show(I18n.of(context).warningEncryptionInBeta, context, Scaffold.of(context).showSnackBar(
duration: 5); SnackBar(
content: Text(I18n.of(context).warningEncryptionInBeta),
),
);
await Navigator.of(context).push( await Navigator.of(context).push(
AppRoute.defaultRoute( AppRoute.defaultRoute(
context, context,
@ -33,7 +35,13 @@ class _EncryptionButtonState extends State<EncryptionButton> {
return; return;
} }
if (!widget.room.client.encryptionEnabled) { if (!widget.room.client.encryptionEnabled) {
Toast.show(I18n.of(context).needPantalaimonWarning, context, duration: 8); Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(
I18n.of(context).needPantalaimonWarning,
),
),
);
return; return;
} }
if (await SimpleDialogs(context).askConfirmation( if (await SimpleDialogs(context).askConfirmation(

View File

@ -3,7 +3,6 @@ import 'package:fluffychat/views/chat.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:pedantic/pedantic.dart'; import 'package:pedantic/pedantic.dart';
import 'package:toast/toast.dart';
import '../../i18n/i18n.dart'; import '../../i18n/i18n.dart';
import '../../utils/app_route.dart'; import '../../utils/app_route.dart';
@ -32,8 +31,13 @@ class ChatListItem extends StatelessWidget {
} }
if (room.membership == Membership.ban) { if (room.membership == Membership.ban) {
Toast.show(I18n.of(context).youHaveBeenBannedFromThisChat, context, Scaffold.of(context).showSnackBar(
duration: 5); SnackBar(
content: Text(
I18n.of(context).youHaveBeenBannedFromThisChat,
),
),
);
return; return;
} }
@ -139,19 +143,26 @@ class ChatListItem extends StatelessWidget {
leading: Avatar(room.avatar, room.displayname), leading: Avatar(room.avatar, room.displayname),
title: Row( title: Row(
children: <Widget>[ children: <Widget>[
Text( Expanded(
room.getLocalizedDisplayname(context), child: Row(
maxLines: 1, mainAxisSize: MainAxisSize.min,
overflow: TextOverflow.ellipsis, children: <Widget>[
), Text(
SizedBox(width: 4), room.getLocalizedDisplayname(context),
room.pushRuleState == PushRuleState.notify maxLines: 1,
? Container() overflow: TextOverflow.ellipsis,
: Icon(
Icons.notifications_off,
color: Colors.grey[400],
size: 16,
), ),
SizedBox(width: 4),
room.pushRuleState == PushRuleState.notify
? Container()
: Icon(
Icons.notifications_off,
color: Colors.grey[400],
size: 16,
),
],
),
),
Spacer(), Spacer(),
Text( Text(
room.timeCreated.localizedTimeShort(context), room.timeCreated.localizedTimeShort(context),

View File

@ -10,7 +10,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:localstorage/localstorage.dart'; import 'package:localstorage/localstorage.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:toast/toast.dart';
import '../i18n/i18n.dart'; import '../i18n/i18n.dart';
import '../utils/app_route.dart'; import '../utils/app_route.dart';
@ -80,17 +79,21 @@ class MatrixState extends State<Matrix> {
onAdditionalAuth != null) { onAdditionalAuth != null) {
return await tryRequestWithErrorToast(onAdditionalAuth(exception)); return await tryRequestWithErrorToast(onAdditionalAuth(exception));
} else { } else {
Toast.show( Scaffold.of(context).showSnackBar(
exception.errorMessage, SnackBar(
context, content: Text(
duration: Toast.LENGTH_LONG, exception.errorMessage,
),
),
); );
} }
} catch (exception) { } catch (exception) {
Toast.show( Scaffold.of(context).showSnackBar(
exception.toString(), SnackBar(
context, content: Text(
duration: Toast.LENGTH_LONG, exception.toString(),
),
),
); );
return false; return false;
} }
@ -141,10 +144,12 @@ class MatrixState extends State<Matrix> {
final String token = await _firebaseMessaging.getToken(); final String token = await _firebaseMessaging.getToken();
if (token?.isEmpty ?? true) { if (token?.isEmpty ?? true) {
return Toast.show( return Scaffold.of(context).showSnackBar(
I18n.of(context).noGoogleServicesWarning, SnackBar(
context, content: Text(
duration: 10, I18n.of(context).noGoogleServicesWarning,
),
),
); );
} }
await client.setPushers( await client.setPushers(
@ -175,7 +180,13 @@ class MatrixState extends State<Matrix> {
), ),
(r) => r.isFirst); (r) => r.isFirst);
} catch (_) { } catch (_) {
Toast.show("Failed to open chat...", context); Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(
"Failed to open chat...",
),
),
);
debugPrint(_); debugPrint(_);
} }
}; };

View File

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:bubble/bubble.dart'; import 'package:bubble/bubble.dart';
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
@ -8,7 +10,9 @@ import 'package:fluffychat/views/image_viewer.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:link_text/link_text.dart'; import 'package:link_text/link_text.dart';
import 'package:path_provider/path_provider.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:open_file/open_file.dart';
import 'matrix.dart'; import 'matrix.dart';
@ -20,11 +24,21 @@ class MessageContent extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var messageType = event.messageType;
if (event.room.encrypted &&
[
MessageTypes.Image,
MessageTypes.Sticker,
MessageTypes.Audio,
MessageTypes.Video,
].contains(messageType)) {
messageType = MessageTypes.File;
}
switch (event.type) { switch (event.type) {
case EventTypes.Message: case EventTypes.Message:
case EventTypes.Encrypted: case EventTypes.Encrypted:
case EventTypes.Sticker: case EventTypes.Sticker:
switch (event.messageType) { switch (messageType) {
case MessageTypes.Image: case MessageTypes.Image:
case MessageTypes.Sticker: case MessageTypes.Sticker:
final int size = 400; final int size = 400;
@ -72,19 +86,41 @@ class MessageContent extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
RaisedButton( RaisedButton(
color: Colors.blueGrey, color: Colors.blueGrey,
child: Text( child: Text(
I18n.of(context).downloadFile, I18n.of(context).downloadFile,
overflow: TextOverflow.fade, overflow: TextOverflow.fade,
softWrap: false, softWrap: false,
maxLines: 1, maxLines: 1,
style: TextStyle(color: Colors.white), style: TextStyle(color: Colors.white),
), ),
onPressed: () => launch( onPressed: () async {
MxContent(event.content["url"]) if (kIsWeb) {
.getDownloadLink(event.room.client), if (event.room.encrypted) {
), Scaffold.of(context).showSnackBar(
), SnackBar(
content:
Text(I18n.of(context).notSupportedInWeb),
),
);
}
await launch(
MxContent(event.content["url"])
.getDownloadLink(event.room.client),
);
return;
}
final matrixFile = await Matrix.of(context)
.tryRequestWithLoadingDialog(
event.downloadAndDecryptAttachment(),
);
Directory tempDir = await getTemporaryDirectory();
final file = File(tempDir.path +
"/" +
matrixFile.path.split("/").last);
file.writeAsBytesSync(matrixFile.bytes);
await OpenFile.open(file.path);
}),
Text( Text(
"- " + "- " +
(event.content.containsKey("filename") (event.content.containsKey("filename")

View File

@ -16,6 +16,9 @@ final ThemeData lightTheme = ThemeData(
backgroundColor: Colors.white, backgroundColor: Colors.white,
secondaryHeaderColor: Color(0xFFECECF2), secondaryHeaderColor: Color(0xFFECECF2),
scaffoldBackgroundColor: Colors.white, scaffoldBackgroundColor: Colors.white,
snackBarTheme: SnackBarThemeData(
behavior: kIsWeb ? SnackBarBehavior.floating : SnackBarBehavior.fixed,
),
dialogTheme: DialogTheme( dialogTheme: DialogTheme(
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0), borderRadius: BorderRadius.circular(8.0),
@ -47,6 +50,9 @@ final ThemeData darkTheme = ThemeData.dark().copyWith(
scaffoldBackgroundColor: Color(0xff121212), scaffoldBackgroundColor: Color(0xff121212),
accentColor: Color(0xFFF5B4D2), accentColor: Color(0xFFF5B4D2),
secondaryHeaderColor: Color(0xff1D1D1D), secondaryHeaderColor: Color(0xff1D1D1D),
snackBarTheme: SnackBarThemeData(
behavior: kIsWeb ? SnackBarBehavior.floating : SnackBarBehavior.fixed,
),
dialogTheme: DialogTheme( dialogTheme: DialogTheme(
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0), borderRadius: BorderRadius.circular(8.0),
@ -78,6 +84,9 @@ final ThemeData amoledTheme = ThemeData.dark().copyWith(
scaffoldBackgroundColor: Colors.black, scaffoldBackgroundColor: Colors.black,
accentColor: Color(0xFFF5B4D2), accentColor: Color(0xFFF5B4D2),
secondaryHeaderColor: Color(0xff1D1D1D), secondaryHeaderColor: Color(0xff1D1D1D),
snackBarTheme: SnackBarThemeData(
behavior: kIsWeb ? SnackBarBehavior.floating : SnackBarBehavior.fixed,
),
dialogTheme: DialogTheme( dialogTheme: DialogTheme(
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0), borderRadius: BorderRadius.circular(8.0),

View File

@ -1,4 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:typed_data';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -29,7 +30,11 @@ class Store extends StoreAPI {
return null; return null;
} }
} }
return await secureStorage.read(key: key); try {
return await secureStorage.read(key: key);
} catch (_) {
return null;
}
} }
Future<void> setItem(String key, String value) async { Future<void> setItem(String key, String value) async {
@ -71,12 +76,18 @@ class Store extends StoreAPI {
} }
debugPrint("[Matrix] Restoring account credentials"); debugPrint("[Matrix] Restoring account credentials");
final Map<String, dynamic> credentials = json.decode(credentialsStr); final Map<String, dynamic> credentials = json.decode(credentialsStr);
if (credentials["homeserver"] == null ||
credentials["token"] == null ||
credentials["userID"] == null) {
client.onLoginStateChanged.add(LoginState.loggedOut);
return;
}
client.connect( client.connect(
newDeviceID: credentials["deviceID"], newDeviceID: credentials["deviceID"],
newDeviceName: credentials["deviceName"], newDeviceName: credentials["deviceName"],
newHomeserver: credentials["homeserver"], newHomeserver: credentials["homeserver"],
newLazyLoadMembers: credentials["lazyLoadMembers"], newLazyLoadMembers: credentials["lazyLoadMembers"],
newMatrixVersions: List<String>.from(credentials["matrixVersions"]), newMatrixVersions: List<String>.from(credentials["matrixVersions"] ?? []),
newToken: credentials["token"], newToken: credentials["token"],
newUserID: credentials["userID"], newUserID: credentials["userID"],
newPrevBatch: kIsWeb newPrevBatch: kIsWeb
@ -109,6 +120,10 @@ class Store extends StoreAPI {
/// Responsible to store all data persistent and to query objects from the /// Responsible to store all data persistent and to query objects from the
/// database. /// database.
class ExtendedStore extends Store implements ExtendedStoreAPI { class ExtendedStore extends Store implements ExtendedStoreAPI {
/// The maximum time that files are allowed to stay in the
/// store. By default this is are 30 days.
static const int MAX_FILE_STORING_TIME = 1 * 30 * 24 * 60 * 60 * 1000;
@override @override
final bool extended = true; final bool extended = true;
@ -126,21 +141,29 @@ class ExtendedStore extends Store implements ExtendedStoreAPI {
// Open the database and migrate if necessary. // Open the database and migrate if necessary.
var databasePath = await getDatabasesPath(); var databasePath = await getDatabasesPath();
String path = p.join(databasePath, "FluffyMatrix.db"); String path = p.join(databasePath, "FluffyMatrix.db");
_db = await openDatabase(path, version: 16, _db = await openDatabase(path, version: 20,
onCreate: (Database db, int version) async { onCreate: (Database db, int version) async {
await createTables(db); await createTables(db);
}, onUpgrade: (Database db, int oldVersion, int newVersion) async { }, onUpgrade: (Database db, int oldVersion, int newVersion) async {
debugPrint( debugPrint(
"[Store] Migrate database from version $oldVersion to $newVersion"); "[Store] Migrate database from version $oldVersion to $newVersion");
if (oldVersion != newVersion) { if (oldVersion >= 18 && newVersion <= 20) {
await createTables(db);
} else if (oldVersion != newVersion) {
// Look for an old entry in an old clients library // Look for an old entry in an old clients library
List<Map> list = []; List<Map> list = [];
try { try {
list = await db.rawQuery( list = await db.rawQuery(
"SELECT * FROM Clients WHERE client=?", [client.clientName]); "SELECT * FROM Clients WHERE client=?", [client.clientName]);
} on DatabaseException catch (_) {} catch (_) { } catch (_) {
rethrow; list = [];
} }
client.prevBatch = null;
await this.storePrevBatch(null);
schemes.forEach((String name, String scheme) async {
await db.execute("DROP TABLE IF EXISTS $name");
});
await createTables(db);
if (list.length == 1) { if (list.length == 1) {
debugPrint("[Store] Found old client from deprecated store"); debugPrint("[Store] Found old client from deprecated store");
@ -158,22 +181,26 @@ class ExtendedStore extends Store implements ExtendedStoreAPI {
newPrevBatch: null, newPrevBatch: null,
); );
await db.execute("DROP TABLE IF EXISTS Clients"); await db.execute("DROP TABLE IF EXISTS Clients");
if (client.debug) { debugPrint(
debugPrint( "[Store] Restore client credentials from deprecated database of ${client.userID}");
"[Store] Restore client credentials from deprecated database of ${client.userID}");
}
schemes.forEach((String name, String scheme) async {
await db.execute("DROP TABLE IF EXISTS $name");
});
await createTables(db);
} }
} else { } else {
client.onLoginStateChanged.add(LoginState.loggedOut); client.onLoginStateChanged.add(LoginState.loggedOut);
} }
return;
}); });
// Mark all pending events as failed. // Mark all pending events as failed.
await _db.rawUpdate("UPDATE Events SET status=-1 WHERE status=0"); await _db.rawUpdate("UPDATE Events SET status=-1 WHERE status=0");
// Delete all stored files which are older than [MAX_FILE_STORING_TIME]
final int currentDeadline = DateTime.now().millisecondsSinceEpoch -
ExtendedStore.MAX_FILE_STORING_TIME;
await _db.rawDelete(
"DELETE From Files WHERE saved_at<?",
[currentDeadline],
);
super._init(); super._init();
} }
@ -515,6 +542,23 @@ class ExtendedStore extends Store implements ExtendedStoreAPI {
return; return;
} }
Future<void> storeFile(Uint8List bytes, String mxcUri) async {
await _db.rawInsert(
"INSERT OR REPLACE INTO Files VALUES(?, ?, ?)",
[mxcUri, bytes, DateTime.now().millisecondsSinceEpoch],
);
return;
}
Future<Uint8List> getFile(String mxcUri) async {
List<Map<String, dynamic>> res = await _db.rawQuery(
"SELECT * FROM Files WHERE mxc_uri=?",
[mxcUri],
);
if (res.isEmpty) return null;
return res.first["bytes"];
}
static final Map<String, String> schemes = { static final Map<String, String> schemes = {
/// The database scheme for the Room class. /// The database scheme for the Room class.
'Rooms': 'CREATE TABLE IF NOT EXISTS Rooms(' + 'Rooms': 'CREATE TABLE IF NOT EXISTS Rooms(' +
@ -574,5 +618,12 @@ class ExtendedStore extends Store implements ExtendedStoreAPI {
'sender TEXT, ' + 'sender TEXT, ' +
'content TEXT, ' + 'content TEXT, ' +
'UNIQUE(sender))', 'UNIQUE(sender))',
/// The database scheme for room states.
'Files': 'CREATE TABLE IF NOT EXISTS Files(' +
'mxc_uri TEXT PRIMARY KEY, ' +
'bytes BLOB, ' +
'saved_at INTEGER, ' +
'UNIQUE(mxc_uri))',
}; };
} }

View File

@ -19,7 +19,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:toast/toast.dart';
import 'package:pedantic/pedantic.dart'; import 'package:pedantic/pedantic.dart';
import 'chat_list.dart'; import 'chat_list.dart';
@ -178,7 +177,14 @@ class _ChatState extends State<_Chat> {
void sendFileAction(BuildContext context) async { void sendFileAction(BuildContext context) async {
if (kIsWeb) { if (kIsWeb) {
return Toast.show(I18n.of(context).notSupportedInWeb, context); Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(
I18n.of(context).notSupportedInWeb,
),
),
);
return;
} }
File file = await FilePicker.getFile(); File file = await FilePicker.getFile();
if (file == null) return; if (file == null) return;
@ -191,7 +197,14 @@ class _ChatState extends State<_Chat> {
void sendImageAction(BuildContext context) async { void sendImageAction(BuildContext context) async {
if (kIsWeb) { if (kIsWeb) {
return Toast.show(I18n.of(context).notSupportedInWeb, context); Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(
I18n.of(context).notSupportedInWeb,
),
),
);
return;
} }
File file = await ImagePicker.pickImage( File file = await ImagePicker.pickImage(
source: ImageSource.gallery, source: ImageSource.gallery,
@ -208,7 +221,14 @@ class _ChatState extends State<_Chat> {
void openCameraAction(BuildContext context) async { void openCameraAction(BuildContext context) async {
if (kIsWeb) { if (kIsWeb) {
return Toast.show(I18n.of(context).notSupportedInWeb, context); Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(
I18n.of(context).notSupportedInWeb,
),
),
);
return;
} }
File file = await ImagePicker.pickImage( File file = await ImagePicker.pickImage(
source: ImageSource.camera, source: ImageSource.camera,

View File

@ -19,7 +19,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:link_text/link_text.dart'; import 'package:link_text/link_text.dart';
import 'package:toast/toast.dart';
class ChatDetails extends StatefulWidget { class ChatDetails extends StatefulWidget {
final Room room; final Room room;
@ -44,10 +43,12 @@ class _ChatDetailsState extends State<ChatDetails> {
widget.room.setName(displayname), widget.room.setName(displayname),
); );
if (success != false) { if (success != false) {
Toast.show( Scaffold.of(context).showSnackBar(
I18n.of(context).displaynameHasBeenChanged, SnackBar(
context, content: Text(
duration: Toast.LENGTH_LONG, I18n.of(context).displaynameHasBeenChanged,
),
),
); );
} }
} }
@ -109,10 +110,12 @@ class _ChatDetailsState extends State<ChatDetails> {
widget.room.setDescription(displayname), widget.room.setDescription(displayname),
); );
if (success != false) { if (success != false) {
Toast.show( Scaffold.of(context).showSnackBar(
I18n.of(context).groupDescriptionHasBeenChanged, SnackBar(
context, content: Text(
duration: Toast.LENGTH_LONG, I18n.of(context).groupDescriptionHasBeenChanged,
),
),
); );
} }
} }
@ -134,10 +137,12 @@ class _ChatDetailsState extends State<ChatDetails> {
), ),
); );
if (success != false) { if (success != false) {
Toast.show( Scaffold.of(context).showSnackBar(
I18n.of(context).avatarHasBeenChanged, SnackBar(
context, content: Text(
duration: Toast.LENGTH_LONG, I18n.of(context).avatarHasBeenChanged,
),
),
); );
} }
} }
@ -195,8 +200,13 @@ class _ChatDetailsState extends State<ChatDetails> {
Clipboard.setData( Clipboard.setData(
ClipboardData(text: widget.room.canonicalAlias), ClipboardData(text: widget.room.canonicalAlias),
); );
Toast.show(I18n.of(context).copiedToClipboard, context, Scaffold.of(context).showSnackBar(
duration: 5); SnackBar(
content: Text(
I18n.of(context).copiedToClipboard,
),
),
);
}, },
), ),
ChatSettingsPopupMenu(widget.room, false) ChatSettingsPopupMenu(widget.room, false)

View File

@ -6,7 +6,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_speed_dial/flutter_speed_dial.dart'; import 'package:flutter_speed_dial/flutter_speed_dial.dart';
import 'package:toast/toast.dart';
import 'package:uni_links/uni_links.dart'; import 'package:uni_links/uni_links.dart';
import '../components/dialogs/simple_dialogs.dart'; import '../components/dialogs/simple_dialogs.dart';
@ -125,10 +124,13 @@ class _ChatListState extends State<ChatList> {
debugPrint("initUniLinks failed during platform exception"); debugPrint("initUniLinks failed during platform exception");
} }
}, },
onError: (error) => Toast.show( onError: (error) => Scaffold.of(context).showSnackBar(
I18n.of(context).oopsSomethingWentWrong + " " + error.toString(), SnackBar(
context, content: Text(
duration: 5), I18n.of(context).oopsSomethingWentWrong + " " + error.toString(),
),
),
),
); );
} }

View File

@ -6,7 +6,6 @@ import 'package:fluffychat/components/avatar.dart';
import 'package:fluffychat/components/matrix.dart'; import 'package:fluffychat/components/matrix.dart';
import 'package:fluffychat/i18n/i18n.dart'; import 'package:fluffychat/i18n/i18n.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:toast/toast.dart';
import 'chat_list.dart'; import 'chat_list.dart';
@ -52,10 +51,12 @@ class _InvitationSelectionState extends State<InvitationSelection> {
widget.room.invite(id), widget.room.invite(id),
); );
if (success != false) { if (success != false) {
Toast.show( Scaffold.of(context).showSnackBar(
I18n.of(context).contactHasBeenInvitedToTheGroup, SnackBar(
context, content: Text(
duration: Toast.LENGTH_LONG, I18n.of(context).contactHasBeenInvitedToTheGroup,
),
),
); );
} }
} }

View File

@ -5,7 +5,6 @@ import 'package:fluffychat/components/settings_themes.dart';
import 'package:fluffychat/views/settings_devices.dart'; import 'package:fluffychat/views/settings_devices.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:toast/toast.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'app_info.dart'; import 'app_info.dart';
@ -86,11 +85,6 @@ class _SettingsState extends State<Settings> {
), ),
); );
if (success != false) { if (success != false) {
Toast.show(
I18n.of(context).avatarHasBeenChanged,
context,
duration: Toast.LENGTH_LONG,
);
setState(() { setState(() {
profileFuture = null; profileFuture = null;
profile = null; profile = null;
@ -194,25 +188,19 @@ class _SettingsState extends State<Settings> {
), ),
), ),
ListTile( ListTile(
leading: Icon(Icons.help), trailing: Icon(Icons.help),
title: Text(I18n.of(context).help), title: Text(I18n.of(context).help),
onTap: () => launch( onTap: () => launch(
"https://gitlab.com/ChristianPauly/fluffychat-flutter/issues"), "https://gitlab.com/ChristianPauly/fluffychat-flutter/issues"),
), ),
ListTile( ListTile(
leading: Icon(Icons.list), trailing: Icon(Icons.link),
title: Text(I18n.of(context).changelog),
onTap: () => launch(
"https://gitlab.com/ChristianPauly/fluffychat-flutter/blob/master/CHANGELOG.md"),
),
ListTile(
leading: Icon(Icons.link),
title: Text(I18n.of(context).license), title: Text(I18n.of(context).license),
onTap: () => launch( onTap: () => launch(
"https://gitlab.com/ChristianPauly/fluffychat-flutter/raw/master/LICENSE"), "https://gitlab.com/ChristianPauly/fluffychat-flutter/raw/master/LICENSE"),
), ),
ListTile( ListTile(
leading: Icon(Icons.code), trailing: Icon(Icons.code),
title: Text(I18n.of(context).sourceCode), title: Text(I18n.of(context).sourceCode),
onTap: () => launch( onTap: () => launch(
"https://gitlab.com/ChristianPauly/fluffychat-flutter"), "https://gitlab.com/ChristianPauly/fluffychat-flutter"),

View File

@ -87,6 +87,7 @@ class DevicesSettingsState extends State<DevicesSettings> {
UserDevice thisDevice = UserDevice thisDevice =
devices.firstWhere(isOwnDevice, orElse: () => null); devices.firstWhere(isOwnDevice, orElse: () => null);
devices.removeWhere(isOwnDevice); devices.removeWhere(isOwnDevice);
devices.sort((a, b) => b.lastSeenTs.compareTo(a.lastSeenTs));
return Column( return Column(
children: <Widget>[ children: <Widget>[
if (thisDevice != null) if (thisDevice != null)
@ -159,9 +160,15 @@ class UserDeviceListItem extends StatelessWidget {
contentPadding: EdgeInsets.all(16.0), contentPadding: EdgeInsets.all(16.0),
title: Row( title: Row(
children: <Widget>[ children: <Widget>[
Text((userDevice.displayName?.isNotEmpty ?? false) Expanded(
? userDevice.displayName child: Text(
: I18n.of(context).unknownDevice), (userDevice.displayName?.isNotEmpty ?? false)
? userDevice.displayName
: I18n.of(context).unknownDevice,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
Spacer(), Spacer(),
Text(userDevice.lastSeenTs.localizedTimeShort(context)), Text(userDevice.lastSeenTs.localizedTimeShort(context)),
], ],

View File

@ -7,7 +7,6 @@ import 'package:fluffychat/i18n/i18n.dart';
import 'package:fluffychat/utils/app_route.dart'; import 'package:fluffychat/utils/app_route.dart';
import 'package:fluffychat/views/auth_web_view.dart'; import 'package:fluffychat/views/auth_web_view.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:toast/toast.dart';
import 'chat_list.dart'; import 'chat_list.dart';
@ -96,7 +95,13 @@ class _SignUpPasswordState extends State<SignUpPassword> {
try { try {
await matrix.client.setDisplayname(widget.displayname); await matrix.client.setDisplayname(widget.displayname);
} catch (exception) { } catch (exception) {
Toast.show(I18n.of(context).couldNotSetDisplayname, context, duration: 5); Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(
I18n.of(context).couldNotSetDisplayname,
),
),
);
} }
if (widget.avatar != null) { if (widget.avatar != null) {
try { try {
@ -107,7 +112,13 @@ class _SignUpPasswordState extends State<SignUpPassword> {
), ),
); );
} catch (exception) { } catch (exception) {
Toast.show(I18n.of(context).couldNotSetAvatar, context, duration: 5); Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(
I18n.of(context).couldNotSetAvatar,
),
),
);
} }
} }
await Navigator.of(context).pushAndRemoveUntil( await Navigator.of(context).pushAndRemoveUntil(

View File

@ -124,8 +124,8 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: eb84c2a0856af99527f00697cbed4eb67a47887d ref: eb66ec79d431c73d5d752cb237b94da56b92f10f
resolved-ref: eb84c2a0856af99527f00697cbed4eb67a47887d resolved-ref: eb66ec79d431c73d5d752cb237b94da56b92f10f
url: "https://gitlab.com/famedly/famedlysdk.git" url: "https://gitlab.com/famedly/famedlysdk.git"
source: git source: git
version: "0.0.1" version: "0.0.1"
@ -331,6 +331,15 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.6" version: "0.12.6"
matrix_file_e2ee:
dependency: transitive
description:
path: "."
ref: HEAD
resolved-ref: b043fcc29031979dc65e5b08e10ebb9b8d2fae30
url: "https://gitlab.com/famedly/libraries/matrix_file_e2ee.git"
source: git
version: "1.0.2"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@ -384,11 +393,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
path: "." path: "."
ref: bbc7ce10a52be5d5c10d2eb6c3591aade71356e2 ref: "1.x.y"
resolved-ref: bbc7ce10a52be5d5c10d2eb6c3591aade71356e2 resolved-ref: "79868b06b3ea156f90b73abafb3bbf3ac4114cc6"
url: "https://gitlab.com/famedly/libraries/dart-olm.git" url: "https://gitlab.com/famedly/libraries/dart-olm.git"
source: git source: git
version: "0.0.0" version: "1.0.0"
open_file:
dependency: "direct main"
description:
name: open_file
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
package_config: package_config:
dependency: transitive dependency: transitive
description: description:
@ -466,6 +482,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.1" version: "1.0.1"
pointycastle:
dependency: transitive
description:
name: pointycastle
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
pool: pool:
dependency: transitive dependency: transitive
description: description:
@ -611,13 +634,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.15" version: "0.2.15"
toast:
dependency: "direct main"
description:
name: toast
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.5"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:

View File

@ -11,7 +11,7 @@ description: Chat with your friends.
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.9.0+28 version: 0.10.0+29
environment: environment:
sdk: ">=2.6.0 <3.0.0" sdk: ">=2.6.0 <3.0.0"
@ -27,11 +27,10 @@ dependencies:
famedlysdk: famedlysdk:
git: git:
url: https://gitlab.com/famedly/famedlysdk.git url: https://gitlab.com/famedly/famedlysdk.git
ref: eb84c2a0856af99527f00697cbed4eb67a47887d ref: eb66ec79d431c73d5d752cb237b94da56b92f10f
localstorage: ^3.0.1+4 localstorage: ^3.0.1+4
bubble: ^1.1.9+1 bubble: ^1.1.9+1
toast: ^0.1.5
file_picker: ^1.4.3+2 file_picker: ^1.4.3+2
image_picker: ^0.6.2+3 image_picker: ^0.6.2+3
flutter_speed_dial: ^1.2.5 flutter_speed_dial: ^1.2.5
@ -53,6 +52,7 @@ dependencies:
flutter_slidable: ^0.5.4 flutter_slidable: ^0.5.4
photo_view: ^0.9.2 photo_view: ^0.9.2
flutter_sound: ^2.1.1 flutter_sound: ^2.1.1
open_file: ^3.0.1
intl: ^0.16.0 intl: ^0.16.0
intl_translation: ^0.17.9 intl_translation: ^0.17.9