Soru/moor

This commit is contained in:
Sorunome 2020-05-13 13:58:59 +00:00 committed by Christian Pauly
commit f594c7005d
55 changed files with 1034 additions and 1133 deletions

View file

@ -32,7 +32,7 @@ class _AudioPlayerState extends State<AudioPlayer> {
StreamSubscription soundSubscription;
Uint8List audioFile;
String statusText = "00:00";
String statusText = '00:00';
double currentPosition = 0;
double maxPosition = 0;
@ -45,7 +45,7 @@ class _AudioPlayerState extends State<AudioPlayer> {
super.dispose();
}
_downloadAction() async {
Future<void> _downloadAction() async {
if (status != AudioPlayerStatus.NOT_DOWNLOADED) return;
setState(() => status = AudioPlayerStatus.DOWNLOADING);
final matrixFile = await SimpleDialogs(context)
@ -57,7 +57,7 @@ class _AudioPlayerState extends State<AudioPlayer> {
_playAction();
}
_playAction() async {
void _playAction() async {
if (AudioPlayer.currentId != widget.event.eventId) {
if (AudioPlayer.currentId != null) {
if (flutterSound.audioState != t_AUDIO_STATE.IS_STOPPED) {
@ -84,16 +84,16 @@ class _AudioPlayerState extends State<AudioPlayer> {
soundSubscription ??= flutterSound.onPlayerStateChanged.listen((e) {
if (AudioPlayer.currentId != widget.event.eventId) {
soundSubscription?.cancel()?.then((f) => soundSubscription = null);
this.setState(() {
setState(() {
currentPosition = 0;
statusText = "00:00";
statusText = '00:00';
});
AudioPlayer.currentId = null;
} else if (e != null) {
DateTime date =
var date =
DateTime.fromMillisecondsSinceEpoch(e.currentPosition.toInt());
String txt = DateFormat('mm:ss', 'en_US').format(date);
this.setState(() {
var txt = DateFormat('mm:ss', 'en_US').format(date);
setState(() {
maxPosition = e.duration;
currentPosition = e.currentPosition;
statusText = txt;

View file

@ -23,13 +23,14 @@ class Avatar extends StatelessWidget {
@override
Widget build(BuildContext context) {
final String src = mxContent?.getThumbnail(
var thumbnail = mxContent?.getThumbnail(
Matrix.of(context).client,
width: size * MediaQuery.of(context).devicePixelRatio,
height: size * MediaQuery.of(context).devicePixelRatio,
method: ThumbnailMethod.scale,
);
String fallbackLetters = "@";
final src = thumbnail;
var fallbackLetters = '@';
if ((name?.length ?? 0) >= 2) {
fallbackLetters = name.substring(0, 2);
} else if ((name?.length ?? 0) == 1) {

View file

@ -48,26 +48,26 @@ class _ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
.client
.onUserEvent
.stream
.where((u) => u.type == 'account_data' && u.eventType == "m.push_rules")
.where((u) => u.type == 'account_data' && u.eventType == 'm.push_rules')
.listen(
(u) => setState(() => null),
);
List<PopupMenuEntry<String>> items = <PopupMenuEntry<String>>[
var items = <PopupMenuEntry<String>>[
widget.room.pushRuleState == PushRuleState.notify
? PopupMenuItem<String>(
value: "mute",
value: 'mute',
child: Text(L10n.of(context).muteChat),
)
: PopupMenuItem<String>(
value: "unmute",
value: 'unmute',
child: Text(L10n.of(context).unmuteChat),
),
PopupMenuItem<String>(
value: "call",
value: 'call',
child: Text(L10n.of(context).videoCall),
),
PopupMenuItem<String>(
value: "leave",
value: 'leave',
child: Text(L10n.of(context).leave),
),
];
@ -75,7 +75,7 @@ class _ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
items.insert(
0,
PopupMenuItem<String>(
value: "details",
value: 'details',
child: Text(L10n.of(context).chatDetails),
),
);
@ -83,8 +83,8 @@ class _ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
return PopupMenuButton(
onSelected: (String choice) async {
switch (choice) {
case "leave":
bool confirmed = await SimpleDialogs(context).askConfirmation();
case 'leave':
var confirmed = await SimpleDialogs(context).askConfirmation();
if (confirmed) {
final success = await SimpleDialogs(context)
.tryRequestWithLoadingDialog(widget.room.leave());
@ -95,18 +95,18 @@ class _ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
}
}
break;
case "mute":
case 'mute':
await SimpleDialogs(context).tryRequestWithLoadingDialog(
widget.room.setPushRuleState(PushRuleState.mentions_only));
break;
case "unmute":
case 'unmute':
await SimpleDialogs(context).tryRequestWithLoadingDialog(
widget.room.setPushRuleState(PushRuleState.notify));
break;
case "call":
case 'call':
startCallAction(context);
break;
case "details":
case 'details':
await Navigator.of(context).push(
AppRoute.defaultRoute(
context,

View file

@ -23,9 +23,9 @@ class ContentBanner extends StatelessWidget {
@override
Widget build(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
final int bannerSize =
final bannerSize =
(mediaQuery.size.width * mediaQuery.devicePixelRatio).toInt();
final String src = mxContent?.getThumbnail(
final src = mxContent?.getThumbnail(
Matrix.of(context).client,
width: bannerSize,
height: bannerSize,
@ -60,7 +60,7 @@ class ContentBanner extends StatelessWidget {
: Icon(defaultIcon, size: 300),
),
),
if (this.onEdit != null)
if (onEdit != null)
Container(
margin: EdgeInsets.all(8),
alignment: Alignment.bottomRight,

View file

@ -16,7 +16,7 @@ class RecordingDialog extends StatefulWidget {
class _RecordingDialogState extends State<RecordingDialog> {
FlutterSound flutterSound = FlutterSound();
String time = "00:00:00";
String time = '00:00:00';
StreamSubscription _recorderSubscription;
@ -28,7 +28,7 @@ class _RecordingDialogState extends State<RecordingDialog> {
codec: t_CODEC.CODEC_AAC,
);
_recorderSubscription = flutterSound.onRecorderStateChanged.listen((e) {
DateTime date =
var date =
DateTime.fromMillisecondsSinceEpoch(e.currentPosition.toInt());
setState(() => time = DateFormat('mm:ss:SS', 'en_US').format(date));
});
@ -67,7 +67,7 @@ class _RecordingDialogState extends State<RecordingDialog> {
SizedBox(width: 8),
Expanded(
child: Text(
"${L10n.of(context).recording}: $time",
'${L10n.of(context).recording}: $time',
style: TextStyle(
fontSize: 18,
),
@ -95,7 +95,7 @@ class _RecordingDialogState extends State<RecordingDialog> {
),
onPressed: () async {
await _recorderSubscription?.cancel();
final String result = await flutterSound.stopRecorder();
final result = await flutterSound.stopRecorder();
if (widget.onFinished != null) {
widget.onFinished(result);
}

View file

@ -19,7 +19,8 @@ class SimpleDialogs {
bool password = false,
bool multiLine = false,
}) async {
final TextEditingController controller = TextEditingController();
var textEditingController = TextEditingController();
final controller = textEditingController;
String input;
await showDialog(
context: context,
@ -77,7 +78,7 @@ class SimpleDialogs {
String confirmText,
String cancelText,
}) async {
bool confirmed = false;
var confirmed = false;
await showDialog(
context: context,
builder: (c) => AlertDialog(
@ -157,8 +158,8 @@ class SimpleDialogs {
}
}
showLoadingDialog(BuildContext context) {
showDialog(
void showLoadingDialog(BuildContext context) async {
await showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) => AlertDialog(

View file

@ -67,7 +67,8 @@ class _EncryptionButtonState extends State<EncryptionButton> {
builder: (BuildContext context, snapshot) {
Color color;
if (widget.room.encrypted && snapshot.hasData) {
final List<DeviceKeys> deviceKeysList = snapshot.data;
var data = snapshot.data;
final deviceKeysList = data;
color = Colors.orange;
if (deviceKeysList.indexWhere((DeviceKeys deviceKeys) =>
deviceKeys.verified == false &&

View file

@ -15,7 +15,7 @@ class ImageBubble extends StatefulWidget {
}
class _ImageBubbleState extends State<ImageBubble> {
static Map<String, MatrixFile> _matrixFileMap = {};
static final Map<String, MatrixFile> _matrixFileMap = {};
MatrixFile get _file => _matrixFileMap[widget.event.eventId];
set _file(MatrixFile file) {
_matrixFileMap[widget.event.eventId] = file;
@ -65,7 +65,7 @@ class _ImageBubbleState extends State<ImageBubble> {
}
_getFile().then((MatrixFile file) {
setState(() => _file = file);
}, onError: (error) {
}, onError: (error, stacktrace) {
setState(() => _error = error);
});
return Center(

View file

@ -71,11 +71,11 @@ class ChatListItem extends StatelessWidget {
if (room.membership == Membership.join) {
if (Matrix.of(context).shareContent != null) {
if (Matrix.of(context).shareContent["msgtype"] ==
"chat.fluffy.shared_file") {
if (Matrix.of(context).shareContent['msgtype'] ==
'chat.fluffy.shared_file') {
await SimpleDialogs(context).tryRequestWithErrorToast(
room.sendFileEvent(
Matrix.of(context).shareContent["file"],
Matrix.of(context).shareContent['file'],
),
);
} else {
@ -98,11 +98,11 @@ class ChatListItem extends StatelessWidget {
final success = await SimpleDialogs(context)
.tryRequestWithLoadingDialog(room.forget());
if (success != false) {
if (this.onForget != null) this.onForget();
if (onForget != null) onForget();
}
return success;
}
final bool confirmed = await SimpleDialogs(context).askConfirmation();
final confirmed = await SimpleDialogs(context).askConfirmation();
if (!confirmed) {
return false;
}
@ -217,7 +217,7 @@ class ChatListItem extends StatelessWidget {
),
),
)
: Text(" "),
: Text(' '),
],
),
onTap: () => clickAction(context),

View file

@ -37,23 +37,23 @@ class Message extends StatelessWidget {
return StateMessage(event);
}
Client client = Matrix.of(context).client;
final bool ownMessage = event.senderId == client.userID;
Alignment alignment = ownMessage ? Alignment.topRight : Alignment.topLeft;
Color color = Theme.of(context).secondaryHeaderColor;
final bool sameSender = nextEvent != null &&
var client = Matrix.of(context).client;
final ownMessage = event.senderId == client.userID;
var alignment = ownMessage ? Alignment.topRight : Alignment.topLeft;
var color = Theme.of(context).secondaryHeaderColor;
final sameSender = nextEvent != null &&
[EventTypes.Message, EventTypes.Sticker].contains(nextEvent.type)
? nextEvent.sender.id == event.sender.id
: false;
BubbleNip nip = sameSender
var nip = sameSender
? BubbleNip.no
: ownMessage ? BubbleNip.rightBottom : BubbleNip.leftBottom;
Color textColor = ownMessage
var textColor = ownMessage
? Colors.white
: Theme.of(context).brightness == Brightness.dark
? Colors.white
: Colors.black;
MainAxisAlignment rowMainAxisAlignment =
var rowMainAxisAlignment =
ownMessage ? MainAxisAlignment.end : MainAxisAlignment.start;
if (event.showThumbnail) {
@ -65,7 +65,7 @@ class Message extends StatelessWidget {
: Theme.of(context).primaryColor;
}
List<Widget> rowChildren = [
var rowChildren = <Widget>[
Expanded(
child: Bubble(
elevation: 0,
@ -84,14 +84,14 @@ class Message extends StatelessWidget {
FutureBuilder<Event>(
future: event.getReplyEvent(timeline),
builder: (BuildContext context, snapshot) {
final Event replyEvent = snapshot.hasData
final replyEvent = snapshot.hasData
? snapshot.data
: Event(
eventId: event.content['m.relates_to']
['m.in_reply_to']['event_id'],
content: {"msgtype": "m.text", "body": "..."},
content: {'msgtype': 'm.text', 'body': '...'},
senderId: event.senderId,
typeKey: "m.room.message",
typeKey: 'm.room.message',
room: event.room,
roomId: event.roomId,
status: 1,
@ -110,7 +110,7 @@ class Message extends StatelessWidget {
),
if (event.type == EventTypes.Encrypted &&
event.messageType == MessageTypes.BadEncrypted &&
event.content["body"] == DecryptError.UNKNOWN_SESSION)
event.content['body'] == DecryptError.UNKNOWN_SESSION)
RaisedButton(
color: color.withAlpha(100),
child: Text(
@ -146,7 +146,7 @@ class Message extends StatelessWidget {
),
),
];
final Widget avatarOrSizedBox = sameSender
final avatarOrSizedBox = sameSender
? SizedBox(width: Avatar.defaultSize)
: Avatar(
event.sender.avatarUrl,
@ -193,8 +193,8 @@ class _MetaRow extends StatelessWidget {
@override
Widget build(BuildContext context) {
final String displayname = event.sender.calcDisplayname();
final bool showDisplayname =
final displayname = event.sender.calcDisplayname();
final showDisplayname =
!ownMessage && event.senderId != event.room.directChatMatrixID;
return Row(
mainAxisSize: MainAxisSize.min,

View file

@ -13,44 +13,44 @@ class ParticipantListItem extends StatelessWidget {
const ParticipantListItem(this.user);
participantAction(BuildContext context, String action) async {
void participantAction(BuildContext context, String action) async {
switch (action) {
case "ban":
case 'ban':
if (await SimpleDialogs(context).askConfirmation()) {
await SimpleDialogs(context).tryRequestWithLoadingDialog(user.ban());
}
break;
case "unban":
case 'unban':
if (await SimpleDialogs(context).askConfirmation()) {
await SimpleDialogs(context)
.tryRequestWithLoadingDialog(user.unban());
}
break;
case "kick":
case 'kick':
if (await SimpleDialogs(context).askConfirmation()) {
await SimpleDialogs(context).tryRequestWithLoadingDialog(user.kick());
}
break;
case "admin":
case 'admin':
if (await SimpleDialogs(context).askConfirmation()) {
await SimpleDialogs(context)
.tryRequestWithLoadingDialog(user.setPower(100));
}
break;
case "moderator":
case 'moderator':
if (await SimpleDialogs(context).askConfirmation()) {
await SimpleDialogs(context)
.tryRequestWithLoadingDialog(user.setPower(50));
}
break;
case "user":
case 'user':
if (await SimpleDialogs(context).askConfirmation()) {
await SimpleDialogs(context)
.tryRequestWithLoadingDialog(user.setPower(0));
}
break;
case "message":
final String roomId = await user.startDirectChat();
case 'message':
final roomId = await user.startDirectChat();
await Navigator.of(context).pushAndRemoveUntil(
AppRoute.defaultRoute(
context,
@ -63,21 +63,21 @@ class ParticipantListItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
Map<Membership, String> membershipBatch = {
Membership.join: "",
var membershipBatch = <Membership, String>{
Membership.join: '',
Membership.ban: L10n.of(context).banned,
Membership.invite: L10n.of(context).invited,
Membership.leave: L10n.of(context).leftTheChat,
};
final String permissionBatch = user.powerLevel == 100
final permissionBatch = user.powerLevel == 100
? L10n.of(context).admin
: user.powerLevel >= 50 ? L10n.of(context).moderator : "";
List<PopupMenuEntry<String>> items = <PopupMenuEntry<String>>[];
: user.powerLevel >= 50 ? L10n.of(context).moderator : '';
var items = <PopupMenuEntry<String>>[];
if (user.id != Matrix.of(context).client.userID) {
items.add(
PopupMenuItem(
child: Text(L10n.of(context).sendAMessage), value: "message"),
child: Text(L10n.of(context).sendAMessage), value: 'message'),
);
}
if (user.canChangePowerLevel &&
@ -85,7 +85,7 @@ class ParticipantListItem extends StatelessWidget {
user.powerLevel != 100) {
items.add(
PopupMenuItem(
child: Text(L10n.of(context).makeAnAdmin), value: "admin"),
child: Text(L10n.of(context).makeAnAdmin), value: 'admin'),
);
}
if (user.canChangePowerLevel &&
@ -93,29 +93,29 @@ class ParticipantListItem extends StatelessWidget {
user.powerLevel != 50) {
items.add(
PopupMenuItem(
child: Text(L10n.of(context).makeAModerator), value: "moderator"),
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"),
child: Text(L10n.of(context).revokeAllPermissions), value: 'user'),
);
}
if (user.canKick) {
items.add(
PopupMenuItem(
child: Text(L10n.of(context).kickFromChat), value: "kick"),
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"),
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"),
child: Text(L10n.of(context).removeExile), value: 'unban'),
);
}
return PopupMenuButton(

View file

@ -13,7 +13,7 @@ class PresenceListItem extends StatelessWidget {
const PresenceListItem(this.presence);
static Map<String, Profile> _presences = {};
static final Map<String, Profile> _presences = {};
Future<Profile> _requestProfile(BuildContext context) async {
_presences[presence.sender] ??=
@ -28,7 +28,7 @@ class PresenceListItem extends StatelessWidget {
builder: (context, snapshot) {
if (!snapshot.hasData) return Container();
Uri avatarUrl;
String displayname = presence.sender.localpart;
var displayname = presence.sender.localpart;
if (snapshot.hasData) {
avatarUrl = snapshot.data.avatarUrl;
displayname = snapshot.data.displayname;
@ -64,7 +64,7 @@ class PresenceListItem extends StatelessWidget {
FlatButton(
child: Text(L10n.of(context).sendAMessage),
onPressed: () async {
final String roomId = await User(
final roomId = await User(
presence.sender,
room: Room(id: '', client: Matrix.of(context).client),
).startDirectChat();

View file

@ -27,7 +27,7 @@ class PublicRoomListItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
final bool hasTopic =
final hasTopic =
publicRoomEntry.topic != null && publicRoomEntry.topic.isNotEmpty;
return ListTile(
leading: Avatar(
@ -36,13 +36,13 @@ class PublicRoomListItem extends StatelessWidget {
: Uri.parse(publicRoomEntry.avatarUrl),
publicRoomEntry.name),
title: Text(hasTopic
? "${publicRoomEntry.name} (${publicRoomEntry.numJoinedMembers})"
? '${publicRoomEntry.name} (${publicRoomEntry.numJoinedMembers})'
: publicRoomEntry.name),
subtitle: Text(
hasTopic
? publicRoomEntry.topic
: L10n.of(context).countParticipants(
publicRoomEntry.numJoinedMembers?.toString() ?? "0"),
publicRoomEntry.numJoinedMembers?.toString() ?? '0'),
maxLines: 1,
),
onTap: () => joinAction(context),

View file

@ -24,14 +24,17 @@ class Matrix extends StatefulWidget {
final Client client;
Matrix({this.child, this.clientName, this.client, Key key}) : super(key: key);
final Store store;
Matrix({this.child, this.clientName, this.client, this.store, Key key})
: super(key: key);
@override
MatrixState createState() => MatrixState();
/// Returns the (nearest) Client instance of your application.
static MatrixState of(BuildContext context) {
MatrixState newState =
var newState =
(context.dependOnInheritedWidgetOfExactType<_InheritedMatrix>()).data;
newState.context = FirebaseController.context = context;
return newState;
@ -40,6 +43,8 @@ class Matrix extends StatefulWidget {
class MatrixState extends State<Matrix> {
Client client;
Store store;
@override
BuildContext context;
Map<String, dynamic> get shareContent => _shareContent;
@ -62,16 +67,15 @@ class MatrixState extends State<Matrix> {
void clean() async {
if (!kIsWeb) return;
final LocalStorage storage = LocalStorage('LocalStorage');
final storage = LocalStorage('LocalStorage');
await storage.ready;
await storage.deleteItem(widget.clientName);
}
void _initWithStore() async {
Future<LoginState> initLoginState = client.onLoginStateChanged.stream.first;
client.storeAPI = kIsWeb ? Store(client) : ExtendedStore(client);
debugPrint(
"[Store] Store is extended: ${client.storeAPI.extended.toString()}");
var initLoginState = client.onLoginStateChanged.stream.first;
client.database = await getDatabase(client, store);
client.connect();
if (await initLoginState == LoginState.logged && !kIsWeb) {
await FirebaseController.setupFirebase(
client,
@ -81,14 +85,14 @@ class MatrixState extends State<Matrix> {
}
Map<String, dynamic> getAuthByPassword(String password, String session) => {
"type": "m.login.password",
"identifier": {
"type": "m.id.user",
"user": client.userID,
'type': 'm.login.password',
'identifier': {
'type': 'm.id.user',
'user': client.userID,
},
"user": client.userID,
"password": password,
"session": session,
'user': client.userID,
'password': password,
'session': session,
};
StreamSubscription onRoomKeyRequestSub;
@ -151,8 +155,9 @@ class MatrixState extends State<Matrix> {
@override
void initState() {
store = widget.store ?? Store();
if (widget.client == null) {
debugPrint("[Matrix] Init matrix client");
debugPrint('[Matrix] Init matrix client');
client = Client(widget.clientName, debug: false);
onJitsiCallSub ??= client.onEvent.stream
.where((e) =>
@ -163,12 +168,12 @@ class MatrixState extends State<Matrix> {
.listen(onJitsiCall);
onRoomKeyRequestSub ??=
client.onRoomKeyRequest.stream.listen((RoomKeyRequest request) async {
final Room room = request.room;
final User sender = room.getUserByMXIDSync(request.sender);
final room = request.room;
final sender = room.getUserByMXIDSync(request.sender);
if (await SimpleDialogs(context).askConfirmation(
titleText: L10n.of(context).requestToReadOlderMessages,
contentText:
"${sender.id}\n\n${L10n.of(context).device}:\n${request.requestingDevice.deviceId}\n\n${L10n.of(context).identity}:\n${request.requestingDevice.curve25519Key.beautified}",
'${sender.id}\n\n${L10n.of(context).device}:\n${request.requestingDevice.deviceId}\n\n${L10n.of(context).identity}:\n${request.requestingDevice.curve25519Key.beautified}',
confirmText: L10n.of(context).verify,
cancelText: L10n.of(context).deny,
)) {
@ -178,20 +183,21 @@ class MatrixState extends State<Matrix> {
_initWithStore();
} else {
client = widget.client;
client.connect();
}
if (client.storeAPI != null) {
client.storeAPI
.getItem("chat.fluffy.jitsi_instance")
if (store != null) {
store
.getItem('chat.fluffy.jitsi_instance')
.then((final instance) => jitsiInstance = instance ?? jitsiInstance);
client.storeAPI.getItem("chat.fluffy.wallpaper").then((final path) async {
store.getItem('chat.fluffy.wallpaper').then((final path) async {
if (path == null) return;
final file = File(path);
if (await file.exists()) {
wallpaper = file;
}
});
client.storeAPI.getItem("chat.fluffy.renderHtml").then((final render) async {
renderHtml = render == "1";
store.getItem('chat.fluffy.renderHtml').then((final render) async {
renderHtml = render == '1';
});
}
super.initState();
@ -221,12 +227,11 @@ class _InheritedMatrix extends InheritedWidget {
@override
bool updateShouldNotify(_InheritedMatrix old) {
bool update = old.data.client.accessToken != this.data.client.accessToken ||
old.data.client.userID != this.data.client.userID ||
old.data.client.matrixVersions != this.data.client.matrixVersions ||
old.data.client.deviceID != this.data.client.deviceID ||
old.data.client.deviceName != this.data.client.deviceName ||
old.data.client.homeserver != this.data.client.homeserver;
var update = old.data.client.accessToken != data.client.accessToken ||
old.data.client.userID != data.client.userID ||
old.data.client.deviceID != data.client.deviceID ||
old.data.client.deviceName != data.client.deviceName ||
old.data.client.homeserver != data.client.homeserver;
return update;
}
}

View file

@ -40,14 +40,13 @@ class MessageContent extends StatelessWidget {
case MessageTypes.Text:
case MessageTypes.Notice:
case MessageTypes.Emote:
if (
Matrix.of(context).renderHtml && !event.redacted &&
event.content['format'] == 'org.matrix.custom.html' &&
event.content['formatted_body'] is String
) {
if (Matrix.of(context).renderHtml &&
!event.redacted &&
event.content['format'] == 'org.matrix.custom.html' &&
event.content['formatted_body'] is String) {
String html = event.content['formatted_body'];
if (event.messageType == MessageTypes.Emote) {
html = "* $html";
html = '* $html';
}
return HtmlMessage(
html: html,

View file

@ -36,9 +36,9 @@ class MessageDownloadContent extends StatelessWidget {
matrixFile.open();
}),
Text(
"- " +
(event.content.containsKey("filename")
? event.content["filename"]
'- ' +
(event.content.containsKey('filename')
? event.content['filename']
: event.body),
style: TextStyle(
color: textColor,
@ -47,7 +47,7 @@ class MessageDownloadContent extends StatelessWidget {
),
if (event.sizeString != null)
Text(
"- " + event.sizeString,
'- ' + event.sizeString,
style: TextStyle(
color: textColor,
fontWeight: FontWeight.bold,

View file

@ -15,15 +15,17 @@ class ReplyContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
Widget replyBody;
if (
replyEvent != null && Matrix.of(context).renderHtml &&
[EventTypes.Message, EventTypes.Encrypted].contains(replyEvent.type) &&
[MessageTypes.Text, MessageTypes.Notice, MessageTypes.Emote].contains(replyEvent.messageType) &&
!replyEvent.redacted && replyEvent.content['format'] == 'org.matrix.custom.html' && replyEvent.content['formatted_body'] is String
) {
if (replyEvent != null &&
Matrix.of(context).renderHtml &&
[EventTypes.Message, EventTypes.Encrypted].contains(replyEvent.type) &&
[MessageTypes.Text, MessageTypes.Notice, MessageTypes.Emote]
.contains(replyEvent.messageType) &&
!replyEvent.redacted &&
replyEvent.content['format'] == 'org.matrix.custom.html' &&
replyEvent.content['formatted_body'] is String) {
String html = replyEvent.content['formatted_body'];
if (replyEvent.messageType == MessageTypes.Emote) {
html = "* $html";
html = '* $html';
}
replyBody = HtmlMessage(
html: html,
@ -39,7 +41,7 @@ class ReplyContent extends StatelessWidget {
withSenderNamePrefix: false,
hideReply: true,
) ??
"",
'',
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: TextStyle(
@ -62,7 +64,7 @@ class ReplyContent extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
(replyEvent?.sender?.calcDisplayname() ?? "") + ":",
(replyEvent?.sender?.calcDisplayname() ?? '') + ':',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(

View file

@ -15,9 +15,8 @@ class ThemesSettingsState extends State<ThemesSettings> {
@override
Widget build(BuildContext context) {
final MatrixState matrix = Matrix.of(context);
final ThemeSwitcherWidgetState themeEngine =
ThemeSwitcherWidget.of(context);
final matrix = Matrix.of(context);
final themeEngine = ThemeSwitcherWidget.of(context);
_selectedTheme = themeEngine.selectedTheme;
_amoledEnabled = themeEngine.amoledEnabled;

View file

@ -147,7 +147,7 @@ class ThemeSwitcher extends InheritedWidget {
class ThemeSwitcherWidget extends StatefulWidget {
final Widget child;
ThemeSwitcherWidget({Key key, this.child})
ThemeSwitcherWidget({Key key, @required this.child})
: assert(child != null),
super(key: key);
@ -156,7 +156,7 @@ class ThemeSwitcherWidget extends StatefulWidget {
/// Returns the (nearest) Client instance of your application.
static ThemeSwitcherWidgetState of(BuildContext context) {
ThemeSwitcherWidgetState newState =
var newState =
(context.dependOnInheritedWidgetOfExactType<ThemeSwitcher>()).data;
newState.context = context;
return newState;
@ -167,17 +167,17 @@ class ThemeSwitcherWidgetState extends State<ThemeSwitcherWidget> {
ThemeData themeData;
Themes selectedTheme;
bool amoledEnabled;
@override
BuildContext context;
Future loadSelection(MatrixState matrix) async {
String item = await matrix.client.storeAPI.getItem("theme") ?? "light";
String item = await matrix.store.getItem('theme') ?? 'light';
selectedTheme =
Themes.values.firstWhere((e) => e.toString() == 'Themes.' + item);
amoledEnabled =
(await matrix.client.storeAPI.getItem("amoled_enabled") ?? "false")
.toLowerCase() ==
'true';
amoledEnabled = (await matrix.store.getItem('amoled_enabled') ?? 'false')
.toLowerCase() ==
'true';
switchTheme(matrix, selectedTheme, amoledEnabled);
return;
@ -199,7 +199,7 @@ class ThemeSwitcherWidgetState extends State<ThemeSwitcherWidget> {
break;
case Themes.system:
// This needs to be a low level call as we don't have a MaterialApp yet
Brightness brightness =
var brightness =
MediaQueryData.fromWindow(WidgetsBinding.instance.window)
.platformBrightness;
if (brightness == Brightness.dark) {
@ -224,16 +224,15 @@ class ThemeSwitcherWidgetState extends State<ThemeSwitcherWidget> {
}
Future saveThemeValue(MatrixState matrix, Themes value) async {
await matrix.client.storeAPI
.setItem("theme", value.toString().split('.').last);
await matrix.store.setItem('theme', value.toString().split('.').last);
}
Future saveAmoledEnabledValue(MatrixState matrix, bool value) async {
await matrix.client.storeAPI.setItem("amoled_enabled", value.toString());
await matrix.store.setItem('amoled_enabled', value.toString());
}
void setup() async {
final MatrixState matrix = Matrix.of(context);
final matrix = Matrix.of(context);
await loadSelection(matrix);
if (selectedTheme == null) {
@ -271,9 +270,8 @@ class ThemeSwitcherWidgetState extends State<ThemeSwitcherWidget> {
Widget build(BuildContext context) {
if (themeData == null) {
// This needs to be a low level call as we don't have a MaterialApp yet
Brightness brightness =
MediaQueryData.fromWindow(WidgetsBinding.instance.window)
.platformBrightness;
var brightness = MediaQueryData.fromWindow(WidgetsBinding.instance.window)
.platformBrightness;
if (brightness == Brightness.dark) {
themeData = darkTheme;
} else {

View file

@ -21,11 +21,11 @@ void main() {
}
class App extends StatelessWidget {
final String platform = kIsWeb ? "Web" : Platform.operatingSystem;
final String platform = kIsWeb ? 'Web' : Platform.operatingSystem;
@override
Widget build(BuildContext context) {
return Matrix(
clientName: "FluffyChat $platform",
clientName: 'FluffyChat $platform',
child: Builder(
builder: (BuildContext context) => ThemeSwitcherWidget(
child: Builder(
@ -47,7 +47,7 @@ class App extends StatelessWidget {
const Locale('pl'), // Polish
],
locale: kIsWeb
? Locale(html.window.navigator.language.split("-").first)
? Locale(html.window.navigator.language.split('-').first)
: null,
home: FutureBuilder<LoginState>(
future:

View file

@ -1,13 +1,13 @@
extension BeautifyStringExtension on String {
String get beautified {
String beautifiedStr = "";
for (int i = 0; i < this.length; i++) {
beautifiedStr += this.substring(i, i + 1);
var beautifiedStr = '';
for (var i = 0; i < length; i++) {
beautifiedStr += substring(i, i + 1);
if (i % 4 == 3) {
beautifiedStr += " ";
beautifiedStr += ' ';
}
if (i % 16 == 15) {
beautifiedStr += "\n";
beautifiedStr += '\n';
}
}
return beautifiedStr;

View file

@ -0,0 +1,12 @@
import 'package:famedlysdk/famedlysdk.dart';
import 'package:encrypted_moor/encrypted_moor.dart';
import 'package:flutter/material.dart';
Database constructDb({bool logStatements = false, String filename = 'database.sqlite', String password = ''}) {
debugPrint('[Moor] using encrypted moor');
return Database(EncryptedExecutor(path: filename, password: password, logStatements: logStatements));
}
Future<String> getLocalstorage(String key) async {
return null;
}

View file

@ -0,0 +1,3 @@
export 'unsupported.dart'
if (dart.library.html) 'web.dart'
if (dart.library.io) 'mobile.dart';

View file

@ -0,0 +1,9 @@
import 'package:famedlysdk/famedlysdk.dart';
Database constructDb({bool logStatements = false, String filename = 'database.sqlite', String password = ''}) {
throw 'Platform not supported';
}
Future<String> getLocalstorage(String key) async {
return null;
}

View file

@ -0,0 +1,13 @@
import 'package:famedlysdk/famedlysdk.dart';
import 'package:moor/moor_web.dart';
import 'package:flutter/material.dart';
import 'dart:html';
Database constructDb({bool logStatements = false, String filename = 'database.sqlite', String password = ''}) {
debugPrint('[Moor] Using moor web');
return Database(WebDatabase.withStorage(MoorWebStorage.indexedDbIfSupported(filename), logStatements: logStatements));
}
Future<String> getLocalstorage(String key) async {
return await window.localStorage[key];
}

View file

@ -3,20 +3,20 @@ import 'package:flutter/material.dart';
/// Provides extra functionality for formatting the time.
extension DateTimeExtension on DateTime {
operator <(DateTime other) {
return this.millisecondsSinceEpoch < other.millisecondsSinceEpoch;
bool operator <(DateTime other) {
return millisecondsSinceEpoch < other.millisecondsSinceEpoch;
}
operator >(DateTime other) {
return this.millisecondsSinceEpoch > other.millisecondsSinceEpoch;
bool operator >(DateTime other) {
return millisecondsSinceEpoch > other.millisecondsSinceEpoch;
}
operator >=(DateTime other) {
return this.millisecondsSinceEpoch >= other.millisecondsSinceEpoch;
bool operator >=(DateTime other) {
return millisecondsSinceEpoch >= other.millisecondsSinceEpoch;
}
operator <=(DateTime other) {
return this.millisecondsSinceEpoch <= other.millisecondsSinceEpoch;
bool operator <=(DateTime other) {
return millisecondsSinceEpoch <= other.millisecondsSinceEpoch;
}
/// Two message events can belong to the same environment. That means that they
@ -34,28 +34,28 @@ extension DateTimeExtension on DateTime {
/// Returns a simple time String.
/// TODO: Add localization
String localizedTimeOfDay(BuildContext context) {
return L10n.of(context).timeOfDay(_z(this.hour % 12), _z(this.hour),
_z(this.minute), this.hour > 11 ? 'pm' : 'am');
return L10n.of(context).timeOfDay(
_z(hour % 12), _z(hour), _z(minute), hour > 11 ? 'pm' : 'am');
}
/// Returns [localizedTimeOfDay()] if the ChatTime is today, the name of the week
/// day if the ChatTime is this week and a date string else.
String localizedTimeShort(BuildContext context) {
DateTime now = DateTime.now();
var now = DateTime.now();
bool sameYear = now.year == this.year;
var sameYear = now.year == year;
bool sameDay = sameYear && now.month == this.month && now.day == this.day;
var sameDay = sameYear && now.month == month && now.day == day;
bool sameWeek = sameYear &&
var sameWeek = sameYear &&
!sameDay &&
now.millisecondsSinceEpoch - this.millisecondsSinceEpoch <
now.millisecondsSinceEpoch - millisecondsSinceEpoch <
1000 * 60 * 60 * 24 * 7;
if (sameDay) {
return localizedTimeOfDay(context);
} else if (sameWeek) {
switch (this.weekday) {
switch (weekday) {
case 1:
return L10n.of(context).monday;
case 2:
@ -73,29 +73,26 @@ extension DateTimeExtension on DateTime {
}
} else if (sameYear) {
return L10n.of(context).dateWithoutYear(
this.month.toString().padLeft(2, '0'),
this.day.toString().padLeft(2, '0'));
month.toString().padLeft(2, '0'), day.toString().padLeft(2, '0'));
}
return L10n.of(context).dateWithYear(
this.year.toString(),
this.month.toString().padLeft(2, '0'),
this.day.toString().padLeft(2, '0'));
return L10n.of(context).dateWithYear(year.toString(),
month.toString().padLeft(2, '0'), day.toString().padLeft(2, '0'));
}
/// If the DateTime is today, this returns [localizedTimeOfDay()], if not it also
/// shows the date.
/// TODO: Add localization
String localizedTime(BuildContext context) {
DateTime now = DateTime.now();
var now = DateTime.now();
bool sameYear = now.year == this.year;
var sameYear = now.year == year;
bool sameDay = sameYear && now.month == this.month && now.day == this.day;
var sameDay = sameYear && now.month == month && now.day == day;
if (sameDay) return localizedTimeOfDay(context);
return L10n.of(context).dateAndTimeOfDay(
localizedTimeShort(context), localizedTimeOfDay(context));
}
static String _z(int i) => i < 10 ? "0${i.toString()}" : i.toString();
static String _z(int i) => i < 10 ? '0${i.toString()}' : i.toString();
}

View file

@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
extension LocalizedBody on Event {
IconData get statusIcon {
switch (this.status) {
switch (status) {
case -1:
return Icons.error_outline;
case 0:
@ -22,21 +22,21 @@ extension LocalizedBody on Event {
[MessageTypes.Image, MessageTypes.Sticker].contains(messageType) &&
(kIsWeb ||
(content['info'] is Map &&
content['info']['size'] < room.client.store.maxFileSize));
content['info']['size'] < room.client.database.maxFileSize));
String get sizeString {
if (content["info"] is Map<String, dynamic> &&
content["info"].containsKey("size")) {
num size = content["info"]["size"];
if (content['info'] is Map<String, dynamic> &&
content['info'].containsKey('size')) {
num size = content['info']['size'];
if (size < 1000000) {
size = size / 1000;
return "${size.toString()}kb";
return '${size.toString()}kb';
} else if (size < 1000000000) {
size = size / 1000000;
return "${size.toString()}mb";
return '${size.toString()}mb';
} else {
size = size / 1000000000;
return "${size.toString()}gb";
return '${size.toString()}gb';
}
} else {
return null;

View file

@ -1,25 +1,186 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:famedlysdk/famedlysdk.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 'dart:async';
import 'dart:core';
import 'package:path/path.dart' as p;
import 'package:sqflite/sqflite.dart';
import './database/shared.dart';
import 'package:olm/olm.dart' as olm; // needed for migration
import 'package:random_string/random_string.dart';
class Store extends StoreAPI {
final Client client;
Future<Database> getDatabase(Client client, Store store) async {
var password = await store.getItem('database-password');
var needMigration = false;
if (password == null || password.isEmpty) {
needMigration = true;
password = randomString(255);
}
final db = constructDb(
logStatements: false,
filename: 'moor.sqlite',
password: password,
);
if (needMigration) {
await migrate(client.clientName, db, store);
await store.setItem('database-password', password);
}
return db;
}
Future<void> 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<String, dynamic> 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<String, dynamic> 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);
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 = <String>[];
if (devicesString != null) {
devices = List<String>.from(json.decode(devicesString));
}
await db.storeOutboundGroupSession(
clientId, roomId, pickle, json.encode(devices));
}
// 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']));
}
}
}
});
}
class Store {
final LocalStorage storage;
final FlutterSecureStorage secureStorage;
Store(this.client)
Store()
: storage = LocalStorage('LocalStorage'),
secureStorage = kIsWeb ? null : FlutterSecureStorage() {
_init();
}
secureStorage = kIsWeb ? null : FlutterSecureStorage();
Future<dynamic> getItem(String key) async {
if (kIsWeb) {
@ -49,587 +210,19 @@ class Store extends StoreAPI {
}
}
Future<Map<String, DeviceKeysList>> getUserDeviceKeys() async {
final deviceKeysListString = await getItem(_UserDeviceKeysKey);
if (deviceKeysListString == null) return {};
Map<String, dynamic> rawUserDeviceKeys = json.decode(deviceKeysListString);
Map<String, DeviceKeysList> userDeviceKeys = {};
for (final entry in rawUserDeviceKeys.entries) {
userDeviceKeys[entry.key] = DeviceKeysList.fromJson(entry.value);
Future<Map<String, dynamic>> getAllItems() async {
if (kIsWeb) {
try {
final rawStorage = await getLocalstorage('LocalStorage');
return json.decode(rawStorage);
} catch (_) {
return {};
}
}
return userDeviceKeys;
}
Future<void> storeUserDeviceKeys(
Map<String, DeviceKeysList> userDeviceKeys) async {
await setItem(_UserDeviceKeysKey, json.encode(userDeviceKeys));
}
String get _UserDeviceKeysKey => "${client.clientName}.user_device_keys";
_init() async {
final credentialsStr = await getItem(client.clientName);
if (credentialsStr == null || credentialsStr.isEmpty) {
client.onLoginStateChanged.add(LoginState.loggedOut);
return;
try {
return await secureStorage.readAll();
} catch (_) {
return {};
}
debugPrint("[Matrix] Restoring account credentials");
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(
newDeviceID: credentials["deviceID"],
newDeviceName: credentials["deviceName"],
newHomeserver: credentials["homeserver"],
newMatrixVersions: List<String>.from(credentials["matrixVersions"] ?? []),
newToken: credentials["token"],
newUserID: credentials["userID"],
newPrevBatch: kIsWeb
? null
: (credentials["prev_batch"]?.isEmpty ?? true)
? null
: credentials["prev_batch"],
newOlmAccount: credentials["olmAccount"],
);
}
Future<void> storeClient() async {
final Map<String, dynamic> credentials = {
"deviceID": client.deviceID,
"deviceName": client.deviceName,
"homeserver": client.homeserver,
"matrixVersions": client.matrixVersions,
"token": client.accessToken,
"userID": client.userID,
"olmAccount": client.pickledOlmAccount,
};
await setItem(client.clientName, json.encode(credentials));
return;
}
Future<void> clear() => kIsWeb ? storage.clear() : secureStorage.deleteAll();
}
/// Responsible to store all data persistent and to query objects from the
/// database.
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
final bool extended = true;
ExtendedStore(Client client) : super(client);
Database _db;
var txn;
/// SQLite database for all persistent data. It is recommended to extend this
/// SDK instead of writing direct queries to the database.
//Database get db => _db;
@override
_init() async {
// Open the database and migrate if necessary.
var databasePath = await getDatabasesPath();
String path = p.join(databasePath, "FluffyMatrix.db");
_db = await openDatabase(path, version: 20,
onCreate: (Database db, int version) async {
await createTables(db);
}, onUpgrade: (Database db, int oldVersion, int newVersion) async {
debugPrint(
"[Store] Migrate database from version $oldVersion to $newVersion");
if (oldVersion >= 18 && newVersion <= 20) {
await createTables(db);
} else if (oldVersion != newVersion) {
// Look for an old entry in an old clients library
List<Map> list = [];
try {
list = await db.rawQuery(
"SELECT * FROM Clients WHERE client=?", [client.clientName]);
} catch (_) {
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) {
debugPrint("[Store] Found old client from deprecated store");
var clientList = list[0];
_db = db;
client.connect(
newToken: clientList["token"],
newHomeserver: clientList["homeserver"],
newUserID: clientList["matrix_id"],
newDeviceID: clientList["device_id"],
newDeviceName: clientList["device_name"],
newMatrixVersions:
clientList["matrix_versions"].toString().split(","),
newPrevBatch: null,
);
await db.execute("DROP TABLE IF EXISTS Clients");
debugPrint(
"[Store] Restore client credentials from deprecated database of ${client.userID}");
}
} else {
client.onLoginStateChanged.add(LoginState.loggedOut);
}
return;
});
// Mark all pending events as failed.
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();
}
Future<void> setRoomPrevBatch(String roomId, String prevBatch) async {
await txn.rawUpdate(
"UPDATE Rooms SET prev_batch=? WHERE room_id=?", [roomId, prevBatch]);
return;
}
Future<void> createTables(Database db) async {
schemes.forEach((String name, String scheme) async {
await db.execute(scheme);
});
}
/// Clears all tables from the database.
Future<void> clear() async {
schemes.forEach((String name, String scheme) async {
await _db.rawDelete("DELETE FROM $name");
});
await super.clear();
return;
}
Future<void> transaction(Function queries) async {
return _db.transaction((txnObj) async {
txn = txnObj.batch();
queries();
await txn.commit(noResult: true);
});
}
/// Will be automatically called on every synchronisation.
Future<void> storePrevBatch(String prevBatch) async {
final credentialsStr = await getItem(client.clientName);
if (credentialsStr == null) return;
final Map<String, dynamic> credentials = json.decode(credentialsStr);
credentials["prev_batch"] = prevBatch;
await setItem(client.clientName, json.encode(credentials));
}
Future<void> storeRoomPrevBatch(Room room) async {
await _db.rawUpdate("UPDATE Rooms SET prev_batch=? WHERE room_id=?",
[room.prev_batch, room.id]);
return null;
}
/// Stores a RoomUpdate object in the database. Must be called inside of
/// [transaction].
Future<void> storeRoomUpdate(RoomUpdate roomUpdate) {
if (txn == null) return null;
// Insert the chat into the database if not exists
if (roomUpdate.membership != Membership.leave) {
txn.rawInsert(
"INSERT OR IGNORE INTO Rooms " + "VALUES(?, ?, 0, 0, '', 0, 0, '') ",
[roomUpdate.id, roomUpdate.membership.toString().split('.').last]);
} else {
txn.rawDelete("DELETE FROM Rooms WHERE room_id=? ", [roomUpdate.id]);
return null;
}
// Update the notification counts and the limited timeline boolean and the summary
String updateQuery =
"UPDATE Rooms SET highlight_count=?, notification_count=?, membership=?";
List<dynamic> updateArgs = [
roomUpdate.highlight_count,
roomUpdate.notification_count,
roomUpdate.membership.toString().split('.').last
];
if (roomUpdate.summary?.mJoinedMemberCount != null) {
updateQuery += ", joined_member_count=?";
updateArgs.add(roomUpdate.summary.mJoinedMemberCount);
}
if (roomUpdate.summary?.mInvitedMemberCount != null) {
updateQuery += ", invited_member_count=?";
updateArgs.add(roomUpdate.summary.mInvitedMemberCount);
}
if (roomUpdate.summary?.mHeroes != null) {
updateQuery += ", heroes=?";
updateArgs.add(roomUpdate.summary.mHeroes.join(","));
}
updateQuery += " WHERE room_id=?";
updateArgs.add(roomUpdate.id);
txn.rawUpdate(updateQuery, updateArgs);
// Is the timeline limited? Then all previous messages should be
// removed from the database!
if (roomUpdate.limitedTimeline) {
txn.rawDelete("DELETE FROM Events WHERE room_id=?", [roomUpdate.id]);
txn.rawUpdate("UPDATE Rooms SET prev_batch=? WHERE room_id=?",
[roomUpdate.prev_batch, roomUpdate.id]);
}
return null;
}
/// Stores an UserUpdate object in the database. Must be called inside of
/// [transaction].
Future<void> storeUserEventUpdate(UserUpdate userUpdate) {
if (txn == null) return null;
if (userUpdate.type == "account_data") {
txn.rawInsert("INSERT OR REPLACE INTO AccountData VALUES(?, ?)", [
userUpdate.eventType,
json.encode(userUpdate.content["content"]),
]);
} else if (userUpdate.type == "presence") {
txn.rawInsert("INSERT OR REPLACE INTO Presences VALUES(?, ?, ?)", [
userUpdate.eventType,
userUpdate.content["sender"],
json.encode(userUpdate.content["content"]),
]);
}
return null;
}
Future<dynamic> redactMessage(EventUpdate eventUpdate) async {
List<Map<String, dynamic>> res = await _db.rawQuery(
"SELECT * FROM Events WHERE event_id=?",
[eventUpdate.content["redacts"]]);
if (res.length == 1) {
Event event = Event.fromJson(res[0], null);
event.setRedactionEvent(Event.fromJson(eventUpdate.content, null));
final int changes1 = await _db.rawUpdate(
"UPDATE Events SET unsigned=?, content=?, prev_content=? WHERE event_id=?",
[
json.encode(event.unsigned ?? ""),
json.encode(event.content ?? ""),
json.encode(event.prevContent ?? ""),
event.eventId,
],
);
final int changes2 = await _db.rawUpdate(
"UPDATE RoomStates SET unsigned=?, content=?, prev_content=? WHERE event_id=?",
[
json.encode(event.unsigned ?? ""),
json.encode(event.content ?? ""),
json.encode(event.prevContent ?? ""),
event.eventId,
],
);
if (changes1 == 1 && changes2 == 1) return true;
}
return false;
}
/// Stores an EventUpdate object in the database. Must be called inside of
/// [transaction].
Future<void> storeEventUpdate(EventUpdate eventUpdate) {
if (txn == null || eventUpdate.type == "ephemeral") return null;
Map<String, dynamic> eventContent = eventUpdate.content;
String type = eventUpdate.type;
String chatId = eventUpdate.roomID;
// Get the state_key for m.room.member events
String stateKey = "";
if (eventContent["state_key"] is String) {
stateKey = eventContent["state_key"];
}
if (eventUpdate.eventType == "m.room.redaction") {
redactMessage(eventUpdate);
}
if (type == "timeline" || type == "history") {
// calculate the status
num status = 2;
if (eventContent["status"] is num) status = eventContent["status"];
// Save the event in the database
if ((status == 1 || status == -1) &&
eventContent["unsigned"] is Map<String, dynamic> &&
eventContent["unsigned"]["transaction_id"] is String) {
txn.rawUpdate(
"UPDATE Events SET status=?, event_id=? WHERE event_id=?", [
status,
eventContent["event_id"],
eventContent["unsigned"]["transaction_id"]
]);
} else {
txn.rawInsert(
"INSERT OR REPLACE INTO Events VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
[
eventContent["event_id"],
chatId,
eventContent["origin_server_ts"],
eventContent["sender"],
eventContent["type"],
json.encode(eventContent["unsigned"] ?? ""),
json.encode(eventContent["content"]),
json.encode(eventContent["prevContent"]),
eventContent["state_key"],
status
]);
}
// Is there a transaction id? Then delete the event with this id.
if (status != -1 &&
eventUpdate.content.containsKey("unsigned") &&
eventUpdate.content["unsigned"]["transaction_id"] is String) {
txn.rawDelete("DELETE FROM Events WHERE event_id=?",
[eventUpdate.content["unsigned"]["transaction_id"]]);
}
}
if (type == "history") return null;
if (type != "account_data") {
final String now = DateTime.now().millisecondsSinceEpoch.toString();
txn.rawInsert(
"INSERT OR REPLACE INTO RoomStates VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)",
[
eventContent["event_id"] ?? now,
chatId,
eventContent["origin_server_ts"] ?? now,
eventContent["sender"],
stateKey,
json.encode(eventContent["unsigned"] ?? ""),
json.encode(eventContent["prev_content"] ?? ""),
eventContent["type"],
json.encode(eventContent["content"]),
]);
} else if (type == "account_data") {
txn.rawInsert("INSERT OR REPLACE INTO RoomAccountData VALUES(?, ?, ?)", [
eventContent["type"],
chatId,
json.encode(eventContent["content"]),
]);
}
return null;
}
/// Returns a User object by a given Matrix ID and a Room.
Future<User> getUser({String matrixID, Room room}) async {
List<Map<String, dynamic>> res = await _db.rawQuery(
"SELECT * FROM RoomStates WHERE state_key=? AND room_id=?",
[matrixID, room.id]);
if (res.length != 1) return null;
return Event.fromJson(res[0], room).asUser;
}
/// Returns a list of events for the given room and sets all participants.
Future<List<Event>> getEventList(Room room) async {
List<Map<String, dynamic>> eventRes = await _db.rawQuery(
"SELECT * " +
" FROM Events " +
" WHERE room_id=?" +
" GROUP BY event_id " +
" ORDER BY origin_server_ts DESC",
[room.id]);
List<Event> eventList = [];
for (num i = 0; i < eventRes.length; i++) {
eventList.add(Event.fromJson(eventRes[i], room));
}
return eventList;
}
/// Returns all rooms, the client is participating. Excludes left rooms.
Future<List<Room>> getRoomList({bool onlyLeft = false}) async {
List<Map<String, dynamic>> res = await _db.rawQuery("SELECT * " +
" FROM Rooms" +
" WHERE membership" +
(onlyLeft ? "=" : "!=") +
"'leave' " +
" GROUP BY room_id ");
List<Map<String, dynamic>> resStates = await _db.rawQuery("SELECT * FROM RoomStates WHERE type IS NOT NULL");
List<Map<String, dynamic>> resAccountData = await _db.rawQuery("SELECT * FROM RoomAccountData");
List<Room> roomList = [];
for (num i = 0; i < res.length; i++) {
Room room = await Room.getRoomFromTableRow(
res[i],
client,
states: Future.value(resStates.where((r) => r["room_id"] == res[i]["room_id"]).toList()),
roomAccountData: Future.value(resAccountData.where((r) => r["room_id"] == res[i]["room_id"]).toList()),
);
roomList.add(room);
}
return roomList;
}
Future<List<Map<String, dynamic>>> getStatesFromRoomId(String id) async {
return _db.rawQuery(
"SELECT * FROM RoomStates WHERE room_id=? AND type IS NOT NULL", [id]);
}
Future<List<Map<String, dynamic>>> getAccountDataFromRoomId(String id) async {
return _db.rawQuery("SELECT * FROM RoomAccountData WHERE room_id=?", [id]);
}
Future<void> resetNotificationCount(String roomID) async {
await _db.rawDelete(
"UPDATE Rooms SET notification_count=0, highlight_count=0 WHERE room_id=?",
[roomID]);
return;
}
Future<void> forgetRoom(String roomID) async {
await _db.rawDelete("DELETE FROM Rooms WHERE room_id=?", [roomID]);
await _db.rawDelete("DELETE FROM Events WHERE room_id=?", [roomID]);
await _db.rawDelete("DELETE FROM RoomStates WHERE room_id=?", [roomID]);
await _db
.rawDelete("DELETE FROM RoomAccountData WHERE room_id=?", [roomID]);
return;
}
/// Searches for the event in the store.
Future<Event> getEventById(String eventID, Room room) async {
List<Map<String, dynamic>> res = await _db.rawQuery(
"SELECT * FROM Events WHERE event_id=? AND room_id=?",
[eventID, room.id]);
if (res.isEmpty) return null;
return Event.fromJson(res[0], room);
}
Future<Map<String, AccountData>> getAccountData() async {
Map<String, AccountData> newAccountData = {};
List<Map<String, dynamic>> rawAccountData =
await _db.rawQuery("SELECT * FROM AccountData");
for (int i = 0; i < rawAccountData.length; i++) {
newAccountData[rawAccountData[i]["type"]] =
AccountData.fromJson(rawAccountData[i]);
}
return newAccountData;
}
Future<Map<String, Presence>> getPresences() async {
Map<String, Presence> newPresences = {};
List<Map<String, dynamic>> rawPresences =
await _db.rawQuery("SELECT * FROM Presences");
for (int i = 0; i < rawPresences.length; i++) {
Map<String, dynamic> rawPresence = {
"sender": rawPresences[i]["sender"],
"content": json.decode(rawPresences[i]["content"]),
};
newPresences[rawPresences[i]["sender"]] = Presence.fromJson(rawPresence);
}
return newPresences;
}
Future removeEvent(String eventId) async {
assert(eventId != "");
await _db.rawDelete("DELETE FROM Events WHERE event_id=?", [eventId]);
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 = {
/// The database scheme for the Room class.
'Rooms': 'CREATE TABLE IF NOT EXISTS Rooms(' +
'room_id TEXT PRIMARY KEY, ' +
'membership TEXT, ' +
'highlight_count INTEGER, ' +
'notification_count INTEGER, ' +
'prev_batch TEXT, ' +
'joined_member_count INTEGER, ' +
'invited_member_count INTEGER, ' +
'heroes TEXT, ' +
'UNIQUE(room_id))',
/// The database scheme for the TimelineEvent class.
'Events': 'CREATE TABLE IF NOT EXISTS Events(' +
'event_id TEXT PRIMARY KEY, ' +
'room_id TEXT, ' +
'origin_server_ts INTEGER, ' +
'sender TEXT, ' +
'type TEXT, ' +
'unsigned TEXT, ' +
'content TEXT, ' +
'prev_content TEXT, ' +
'state_key TEXT, ' +
"status INTEGER, " +
'UNIQUE(event_id))',
/// The database scheme for room states.
'RoomStates': 'CREATE TABLE IF NOT EXISTS RoomStates(' +
'event_id TEXT PRIMARY KEY, ' +
'room_id TEXT, ' +
'origin_server_ts INTEGER, ' +
'sender TEXT, ' +
'state_key TEXT, ' +
'unsigned TEXT, ' +
'prev_content TEXT, ' +
'type TEXT, ' +
'content TEXT, ' +
'UNIQUE(room_id,state_key,type))',
/// The database scheme for room states.
'AccountData': 'CREATE TABLE IF NOT EXISTS AccountData(' +
'type TEXT PRIMARY KEY, ' +
'content TEXT, ' +
'UNIQUE(type))',
/// The database scheme for room states.
'RoomAccountData': 'CREATE TABLE IF NOT EXISTS RoomAccountData(' +
'type TEXT, ' +
'room_id TEXT, ' +
'content TEXT, ' +
'UNIQUE(type,room_id))',
/// The database scheme for room states.
'Presences': 'CREATE TABLE IF NOT EXISTS Presences(' +
'type TEXT PRIMARY KEY, ' +
'sender TEXT, ' +
'content TEXT, ' +
'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))',
};
@override
int get maxFileSize => 1 * 1024 * 1024;
}

View file

@ -15,9 +15,9 @@ import 'package:famedlysdk/famedlysdk.dart';
import 'famedlysdk_store.dart';
abstract class FirebaseController {
static FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
static FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
static final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
static final FlutterLocalNotificationsPlugin
_flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
static BuildContext context;
static const String CHANNEL_ID = 'fluffychat_push';
static const String CHANNEL_NAME = 'FluffyChat push channel';
@ -52,7 +52,7 @@ abstract class FirebaseController {
currentPushers.first.lang == 'en' &&
currentPushers.first.data.url == GATEWAY_URL &&
currentPushers.first.data.format == PUSHER_FORMAT) {
debugPrint("[Push] Pusher already set");
debugPrint('[Push] Pusher already set');
} else {
if (currentPushers.isNotEmpty) {
for (final currentPusher in currentPushers) {
@ -66,16 +66,16 @@ abstract class FirebaseController {
currentPusher.data.url,
append: true,
);
debugPrint("[Push] Remove legacy pusher for this device");
debugPrint('[Push] Remove legacy pusher for this device');
}
}
await client.setPushers(
token,
"http",
'http',
APP_ID,
clientName,
client.deviceName,
"en",
'en',
GATEWAY_URL,
append: false,
format: PUSHER_FORMAT,
@ -88,9 +88,9 @@ abstract class FirebaseController {
if (message is String) {
roomId = message;
} else if (message is Map) {
roomId = (message["data"] ?? message)["room_id"];
roomId = (message['data'] ?? message)['room_id'];
}
if (roomId?.isEmpty ?? true) throw ("Bad roomId");
if (roomId?.isEmpty ?? true) throw ('Bad roomId');
await Navigator.of(context).pushAndRemoveUntil(
AppRoute.defaultRoute(
context,
@ -98,7 +98,7 @@ abstract class FirebaseController {
),
(r) => r.isFirst);
} catch (_) {
BotToast.showText(text: "Failed to open chat...");
BotToast.showText(text: 'Failed to open chat...');
debugPrint(_);
}
};
@ -121,16 +121,16 @@ abstract class FirebaseController {
onResume: goToRoom,
onLaunch: goToRoom,
);
debugPrint("[Push] Firebase initialized");
debugPrint('[Push] Firebase initialized');
return;
}
static Future<dynamic> _onMessage(Map<String, dynamic> message) async {
try {
final data = message['data'] ?? message;
final String roomId = data["room_id"];
final String eventId = data["event_id"];
final int unread = json.decode(data["counts"])["unread"];
final String roomId = data['room_id'];
final String eventId = data['event_id'];
final int unread = json.decode(data['counts'])['unread'];
if ((roomId?.isEmpty ?? true) ||
(eventId?.isEmpty ?? true) ||
unread == 0) {
@ -148,10 +148,12 @@ abstract class FirebaseController {
if (context != null) {
client = Matrix.of(context).client;
} else {
final platform = kIsWeb ? "Web" : Platform.operatingSystem;
final clientName = "FluffyChat $platform";
final platform = kIsWeb ? 'Web' : Platform.operatingSystem;
final clientName = 'FluffyChat $platform';
client = Client(clientName, debug: false);
client.storeAPI = ExtendedStore(client);
final store = Store();
client.database = await getDatabase(client, store);
client.connect();
await client.onLoginStateChanged.stream
.firstWhere((l) => l == LoginState.logged)
.timeout(
@ -160,7 +162,7 @@ abstract class FirebaseController {
}
// Get the room
Room room = client.getRoomById(roomId);
var room = client.getRoomById(roomId);
if (room == null) {
await client.onRoomUpdate.stream
.where((u) => u.id == roomId)
@ -171,10 +173,10 @@ abstract class FirebaseController {
}
// Get the event
Event event = await client.store.getEventById(eventId, room);
var event = await client.database.getEventById(client.id, eventId, room);
if (event == null) {
final EventUpdate eventUpdate = await client.onEvent.stream
.where((u) => u.content["event_id"] == eventId)
final eventUpdate = await client.onEvent.stream
.where((u) => u.content['event_id'] == eventId)
.first
.timeout(Duration(seconds: 5));
event = Event.fromJson(eventUpdate.content, room);
@ -182,18 +184,18 @@ abstract class FirebaseController {
}
// Count all unread events
int unreadEvents = 0;
var unreadEvents = 0;
client.rooms
.forEach((Room room) => unreadEvents += room.notificationCount);
// Calculate title
final String title = unread > 1
final title = unread > 1
? i18n.unreadMessagesInChats(
unreadEvents.toString(), unread.toString())
: i18n.unreadMessages(unreadEvents.toString());
// Calculate the body
final String body = event.getLocalizedBody(
final body = event.getLocalizedBody(
i18n,
withSenderNamePrefix: true,
hideReply: true,
@ -238,7 +240,7 @@ abstract class FirebaseController {
0, room.getLocalizedDisplayname(i18n), body, platformChannelSpecifics,
payload: roomId);
} catch (exception) {
debugPrint("[Push] Error while processing notification: " +
debugPrint('[Push] Error while processing notification: ' +
exception.toString());
await _showDefaultNotification(message);
}
@ -248,8 +250,7 @@ abstract class FirebaseController {
static Future<dynamic> _showDefaultNotification(
Map<String, dynamic> message) async {
try {
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
var flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
// Init notifications framework
var initializationSettingsAndroid =
AndroidInitializationSettings('notifications_icon');
@ -261,10 +262,10 @@ abstract class FirebaseController {
// Notification data and matrix data
Map<dynamic, dynamic> data = message['data'] ?? message;
String eventID = data["event_id"];
String roomID = data["room_id"];
final int unread = data.containsKey("counts")
? json.decode(data["counts"])["unread"]
String eventID = data['event_id'];
String roomID = data['room_id'];
final int unread = data.containsKey('counts')
? json.decode(data['counts'])['unread']
: 1;
await flutterLocalNotificationsPlugin.cancelAll();
if (unread == 0 || roomID == null || eventID == null) {
@ -278,12 +279,12 @@ abstract class FirebaseController {
var iOSPlatformChannelSpecifics = IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails(
androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
final String title = l10n.unreadChats(unread.toString());
final title = l10n.unreadChats(unread.toString());
await flutterLocalNotificationsPlugin.show(
1, title, l10n.openAppToReadMessages, platformChannelSpecifics,
payload: roomID);
} catch (exception) {
debugPrint("[Push] Error while processing background notification: " +
debugPrint('[Push] Error while processing background notification: ' +
exception.toString());
}
return Future<void>.value();
@ -291,10 +292,10 @@ abstract class FirebaseController {
static Future<String> downloadAndSaveAvatar(Uri content, Client client,
{int width, int height}) async {
final bool thumbnail = width == null && height == null ? false : true;
final String tempDirectory = (await getTemporaryDirectory()).path;
final String prefix = thumbnail ? "thumbnail" : "";
File file =
final thumbnail = width == null && height == null ? false : true;
final tempDirectory = (await getTemporaryDirectory()).path;
final prefix = thumbnail ? 'thumbnail' : '';
var file =
File('$tempDirectory/${prefix}_${content.toString().split("/").last}');
if (!file.existsSync()) {
@ -315,7 +316,7 @@ abstract class FirebaseController {
IosNotificationSettings(sound: true, badge: true, alert: true));
_firebaseMessaging.onIosSettingsRegistered
.listen((IosNotificationSettings settings) {
debugPrint("Settings registered: $settings");
debugPrint('Settings registered: $settings');
});
}
}

View file

@ -15,8 +15,8 @@ extension MatrixFileExtension on MatrixFile {
var element = html.document.createElement('a');
element.setAttribute(
'href', html.Url.createObjectUrlFromBlob(html.Blob([bytes])));
element.setAttribute('target', "_blank");
element.setAttribute('rel', "noopener");
element.setAttribute('target', '_blank');
element.setAttribute('rel', 'noopener');
element.setAttribute('download', fileName);
element.setAttribute('type', mimeType);
element.style.display = 'none';
@ -24,8 +24,8 @@ extension MatrixFileExtension on MatrixFile {
element.click();
element.remove();
} else {
Directory tempDir = await getTemporaryDirectory();
final file = File(tempDir.path + "/" + path.split("/").last);
var tempDir = await getTemporaryDirectory();
final file = File(tempDir.path + '/' + path.split('/').last);
file.writeAsBytesSync(bytes);
await OpenFile.open(file.path);
}

View file

@ -2,9 +2,9 @@ import 'package:flutter/material.dart';
extension StringColor on String {
Color get color {
double number = 0.0;
for (var i = 0; i < this.length; i++) {
number += this.codeUnitAt(i);
var number = 0.0;
for (var i = 0; i < length; i++) {
number += codeUnitAt(i);
}
number = (number % 10) * 25.5;
return HSLColor.fromAHSL(1, number, 1, 0.35).toColor();

View file

@ -12,7 +12,7 @@ class UrlLauncher {
const UrlLauncher(this.context, this.url);
void launchUrl() {
if (url.startsWith("https://matrix.to/#/")) {
if (url.startsWith('https://matrix.to/#/')) {
return openMatrixToUrl();
}
launch(url);
@ -20,8 +20,8 @@ class UrlLauncher {
void openMatrixToUrl() async {
final matrix = Matrix.of(context);
final String identifier = url.replaceAll("https://matrix.to/#/", "");
if (identifier.substring(0, 1) == "#") {
final identifier = url.replaceAll('https://matrix.to/#/', '');
if (identifier.substring(0, 1) == '#') {
final response = await SimpleDialogs(context).tryRequestWithLoadingDialog(
matrix.client.joinRoomById(
Uri.encodeComponent(identifier),
@ -30,13 +30,13 @@ class UrlLauncher {
if (response == false) return;
await Navigator.pushAndRemoveUntil(
context,
AppRoute.defaultRoute(context, ChatView(response["room_id"])),
AppRoute.defaultRoute(context, ChatView(response['room_id'])),
(r) => r.isFirst,
);
} else if (identifier.substring(0, 1) == "@") {
final User user = User(
} else if (identifier.substring(0, 1) == '@') {
final user = User(
identifier,
room: Room(id: "", client: matrix.client),
room: Room(id: '', client: matrix.client),
);
final String roomID = await SimpleDialogs(context)
.tryRequestWithLoadingDialog(user.startDirectChat());

View file

@ -1,4 +1,3 @@
import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/components/adaptive_page_layout.dart';
import 'package:fluffychat/components/matrix.dart';
import 'package:fluffychat/l10n/l10n.dart';
@ -21,7 +20,7 @@ class AppInfoView extends StatelessWidget {
class AppInfo extends StatelessWidget {
@override
Widget build(BuildContext context) {
Client client = Matrix.of(context).client;
var client = Matrix.of(context).client;
return Scaffold(
appBar: AppBar(
title: Text(L10n.of(context).accountInformations),
@ -29,43 +28,39 @@ class AppInfo extends StatelessWidget {
body: ListView(
children: <Widget>[
ListTile(
title: Text(L10n.of(context).yourOwnUsername + ":"),
title: Text(L10n.of(context).yourOwnUsername + ':'),
subtitle: Text(client.userID),
),
ListTile(
title: Text("Homeserver:"),
title: Text('Homeserver:'),
subtitle: Text(client.homeserver),
),
ListTile(
title: Text("Supported versions:"),
subtitle: Text(client.matrixVersions.toString()),
),
ListTile(
title: Text("Device name:"),
title: Text('Device name:'),
subtitle: Text(client.deviceName),
),
ListTile(
title: Text("Device ID:"),
title: Text('Device ID:'),
subtitle: Text(client.deviceID),
),
ListTile(
title: Text("Encryption enabled:"),
title: Text('Encryption enabled:'),
subtitle: Text(client.encryptionEnabled.toString()),
),
if (client.encryptionEnabled)
Column(
children: <Widget>[
ListTile(
title: Text("Your public fingerprint key:"),
title: Text('Your public fingerprint key:'),
subtitle: Text(client.fingerprintKey.beautified),
),
ListTile(
title: Text("Your public identity key:"),
title: Text('Your public identity key:'),
subtitle: Text(client.identityKey.beautified),
),
ListTile(
title: Text("LibOlm version:"),
subtitle: Text(olm.get_library_version().join(".")),
title: Text('LibOlm version:'),
subtitle: Text(olm.get_library_version().join('.')),
),
],
),

View file

@ -44,7 +44,7 @@ class _ArchiveState extends State<Archive> {
),
secondScaffold: Scaffold(
body: Center(
child: Image.asset("assets/logo.png", width: 100, height: 100),
child: Image.asset('assets/logo.png', width: 100, height: 100),
),
),
primaryPage: FocusPage.FIRST,

View file

@ -14,8 +14,9 @@ class AuthWebView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final String url = Matrix.of(context).client.homeserver +
"/_matrix/client/r0/auth/$authType/fallback/web?session=$session";
final url =
'/_matrix/client/r0/auth/$authType/fallback/web?session=$session' +
Matrix.of(context).client.homeserver;
if (kIsWeb) launch(url);
return Scaffold(
appBar: AppBar(

View file

@ -55,7 +55,7 @@ class _ChatState extends State<_Chat> {
MatrixState matrix;
String seenByText = "";
String seenByText = '';
final ScrollController _scrollController = ScrollController();
@ -77,15 +77,19 @@ class _ChatState extends State<_Chat> {
final int _loadHistoryCount = 100;
String inputText = "";
String inputText = '';
bool get _canLoadMore => timeline.events.last.type != EventTypes.RoomCreate;
void requestHistory() async {
if (_canLoadMore) {
setState(() => this._loadingHistory = true);
await timeline.requestHistory(historyCount: _loadHistoryCount);
if (mounted) setState(() => this._loadingHistory = false);
setState(() => _loadingHistory = true);
try {
await timeline.requestHistory(historyCount: _loadHistoryCount);
} catch (e) {
debugPrint('Error loading history: ' + e.toString());
}
if (mounted) setState(() => _loadingHistory = false);
}
}
@ -114,9 +118,9 @@ class _ChatState extends State<_Chat> {
void updateView() {
if (!mounted) return;
String seenByText = "";
var seenByText = '';
if (timeline.events.isNotEmpty) {
List lastReceipts = List.from(timeline.events.first.receipts);
var lastReceipts = List.from(timeline.events.first.receipts);
lastReceipts.removeWhere((r) =>
r.user.id == room.client.userID ||
r.user.id == timeline.events.first.senderId);
@ -147,7 +151,7 @@ class _ChatState extends State<_Chat> {
unawaited(room.sendReadReceipt(timeline.events.first.eventId));
}
if (timeline.events.length < _loadHistoryCount) {
this.requestHistory();
requestHistory();
}
}
updateView();
@ -158,7 +162,7 @@ class _ChatState extends State<_Chat> {
void dispose() {
timeline?.cancelSubscriptions();
timeline = null;
matrix.activeRoomId = "";
matrix.activeRoomId = '';
super.dispose();
}
@ -167,12 +171,12 @@ class _ChatState extends State<_Chat> {
void send() {
if (sendController.text.isEmpty) return;
room.sendTextEvent(sendController.text, inReplyTo: replyEvent);
sendController.text = "";
sendController.text = '';
if (replyEvent != null) {
setState(() => replyEvent = null);
}
setState(() => inputText = "");
setState(() => inputText = '');
}
void sendFileAction(BuildContext context) async {
@ -180,7 +184,7 @@ class _ChatState extends State<_Chat> {
BotToast.showText(text: L10n.of(context).notSupportedInWeb);
return;
}
File file = await FilePicker.getFile();
var file = await FilePicker.getFile();
if (file == null) return;
await SimpleDialogs(context).tryRequestWithLoadingDialog(
room.sendFileEvent(
@ -194,7 +198,7 @@ class _ChatState extends State<_Chat> {
BotToast.showText(text: L10n.of(context).notSupportedInWeb);
return;
}
File file = await ImagePicker.pickImage(
var file = await ImagePicker.pickImage(
source: ImageSource.gallery,
imageQuality: 50,
maxWidth: 1600,
@ -212,7 +216,7 @@ class _ChatState extends State<_Chat> {
BotToast.showText(text: L10n.of(context).notSupportedInWeb);
return;
}
File file = await ImagePicker.pickImage(
var file = await ImagePicker.pickImage(
source: ImageSource.camera,
imageQuality: 50,
maxWidth: 1600,
@ -233,7 +237,7 @@ class _ChatState extends State<_Chat> {
onFinished: (r) => result = r,
));
if (result == null) return;
final File audioFile = File(result);
final audioFile = File(result);
await SimpleDialogs(context).tryRequestWithLoadingDialog(
room.sendAudioEvent(
MatrixFile(bytes: audioFile.readAsBytesSync(), path: audioFile.path),
@ -242,12 +246,12 @@ class _ChatState extends State<_Chat> {
}
String _getSelectedEventString(BuildContext context) {
String copyString = "";
var copyString = '';
if (selectedEvents.length == 1) {
return selectedEvents.first.getLocalizedBody(L10n.of(context));
}
for (Event event in selectedEvents) {
if (copyString.isNotEmpty) copyString += "\n\n";
for (var event in selectedEvents) {
if (copyString.isNotEmpty) copyString += '\n\n';
copyString +=
event.getLocalizedBody(L10n.of(context), withSenderNamePrefix: true);
}
@ -260,12 +264,12 @@ class _ChatState extends State<_Chat> {
}
void redactEventsAction(BuildContext context) async {
bool confirmed = await SimpleDialogs(context).askConfirmation(
var confirmed = await SimpleDialogs(context).askConfirmation(
titleText: L10n.of(context).messageWillBeRemovedWarning,
confirmText: L10n.of(context).remove,
);
if (!confirmed) return;
for (Event event in selectedEvents) {
for (var event in selectedEvents) {
await SimpleDialogs(context).tryRequestWithLoadingDialog(
event.status > 0 ? event.redact() : event.remove());
}
@ -273,7 +277,7 @@ class _ChatState extends State<_Chat> {
}
bool get canRedactSelectedEvents {
for (Event event in selectedEvents) {
for (var event in selectedEvents) {
if (event.canRedact == false) return false;
}
return true;
@ -284,8 +288,8 @@ class _ChatState extends State<_Chat> {
Matrix.of(context).shareContent = selectedEvents.first.content;
} else {
Matrix.of(context).shareContent = {
"msgtype": "m.text",
"body": _getSelectedEventString(context),
'msgtype': 'm.text',
'body': _getSelectedEventString(context),
};
}
setState(() => selectedEvents.clear());
@ -308,7 +312,7 @@ class _ChatState extends State<_Chat> {
@override
Widget build(BuildContext context) {
matrix = Matrix.of(context);
Client client = matrix.client;
var client = matrix.client;
room ??= client.getRoomById(widget.id);
if (room == null) {
return Scaffold(
@ -326,8 +330,8 @@ class _ChatState extends State<_Chat> {
SimpleDialogs(context).tryRequestWithLoadingDialog(room.join());
}
String typingText = "";
List<User> typingUsers = room.typingUsers;
var typingText = '';
var typingUsers = room.typingUsers;
typingUsers.removeWhere((User u) => u.id == client.userID);
if (typingUsers.length == 1) {
@ -616,22 +620,22 @@ class _ChatState extends State<_Chat> {
PopupMenuButton<String>(
icon: Icon(Icons.add),
onSelected: (String choice) async {
if (choice == "file") {
if (choice == 'file') {
sendFileAction(context);
} else if (choice == "image") {
} else if (choice == 'image') {
sendImageAction(context);
}
if (choice == "camera") {
if (choice == 'camera') {
openCameraAction(context);
}
if (choice == "voice") {
if (choice == 'voice') {
voiceMessageAction(context);
}
},
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<String>>[
PopupMenuItem<String>(
value: "file",
value: 'file',
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.green,
@ -644,7 +648,7 @@ class _ChatState extends State<_Chat> {
),
),
PopupMenuItem<String>(
value: "image",
value: 'image',
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.blue,
@ -657,7 +661,7 @@ class _ChatState extends State<_Chat> {
),
),
PopupMenuItem<String>(
value: "camera",
value: 'camera',
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.purple,
@ -670,7 +674,7 @@ class _ChatState extends State<_Chat> {
),
),
PopupMenuItem<String>(
value: "voice",
value: 'voice',
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.red,
@ -708,20 +712,20 @@ class _ChatState extends State<_Chat> {
border: InputBorder.none,
),
onChanged: (String text) {
this.typingCoolDown?.cancel();
this.typingCoolDown =
typingCoolDown?.cancel();
typingCoolDown =
Timer(Duration(seconds: 2), () {
this.typingCoolDown = null;
this.currentlyTyping = false;
typingCoolDown = null;
currentlyTyping = false;
room.sendTypingInfo(false);
});
this.typingTimeout ??=
typingTimeout ??=
Timer(Duration(seconds: 30), () {
this.typingTimeout = null;
this.currentlyTyping = false;
typingTimeout = null;
currentlyTyping = false;
});
if (!this.currentlyTyping) {
this.currentlyTyping = true;
if (!currentlyTyping) {
currentlyTyping = true;
room.sendTypingInfo(true,
timeout: Duration(seconds: 30)
.inMilliseconds);

View file

@ -1,5 +1,3 @@
import 'dart:io';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/components/adaptive_page_layout.dart';
import 'package:fluffychat/components/chat_settings_popup_menu.dart';
@ -30,11 +28,12 @@ class ChatDetails extends StatefulWidget {
class _ChatDetailsState extends State<ChatDetails> {
List<User> members;
void setDisplaynameAction(BuildContext context) async {
final String displayname = await SimpleDialogs(context).enterText(
var enterText = SimpleDialogs(context).enterText(
titleText: L10n.of(context).changeTheNameOfTheGroup,
labelText: L10n.of(context).changeTheNameOfTheGroup,
hintText: widget.room.getLocalizedDisplayname(L10n.of(context)),
);
final displayname = await enterText;
if (displayname == null) return;
final success = await SimpleDialogs(context).tryRequestWithLoadingDialog(
widget.room.setName(displayname),
@ -45,26 +44,26 @@ class _ChatDetailsState extends State<ChatDetails> {
}
void setCanonicalAliasAction(context) async {
final String s = await SimpleDialogs(context).enterText(
final s = await SimpleDialogs(context).enterText(
titleText: L10n.of(context).setInvitationLink,
labelText: L10n.of(context).setInvitationLink,
hintText: L10n.of(context).alias.toLowerCase(),
prefixText: "#",
suffixText: ":" + widget.room.client.userID.domain,
prefixText: '#',
suffixText: ':' + widget.room.client.userID.domain,
);
if (s == null) return;
final String domain = widget.room.client.userID.domain;
final String canonicalAlias = "%23" + s + "%3A" + domain;
final Event aliasEvent = widget.room.getState("m.room.aliases", domain);
final List aliases =
aliasEvent != null ? aliasEvent.content["aliases"] ?? [] : [];
final domain = widget.room.client.userID.domain;
final canonicalAlias = '%23' + s + '%3A' + domain;
final aliasEvent = widget.room.getState('m.room.aliases', domain);
final aliases =
aliasEvent != null ? aliasEvent.content['aliases'] ?? [] : [];
if (aliases.indexWhere((s) => s == canonicalAlias) == -1) {
List<String> newAliases = List.from(aliases);
var newAliases = List<String>.from(aliases);
newAliases.add(canonicalAlias);
final response = await SimpleDialogs(context).tryRequestWithLoadingDialog(
widget.room.client.jsonRequest(
type: HTTPType.GET,
action: "/client/r0/directory/room/$canonicalAlias",
action: '/client/r0/directory/room/$canonicalAlias',
),
);
if (response == false) {
@ -72,8 +71,8 @@ class _ChatDetailsState extends State<ChatDetails> {
await SimpleDialogs(context).tryRequestWithLoadingDialog(
widget.room.client.jsonRequest(
type: HTTPType.PUT,
action: "/client/r0/directory/room/$canonicalAlias",
data: {"room_id": widget.room.id}),
action: '/client/r0/directory/room/$canonicalAlias',
data: {'room_id': widget.room.id}),
);
if (success == false) return;
}
@ -82,13 +81,13 @@ class _ChatDetailsState extends State<ChatDetails> {
widget.room.client.jsonRequest(
type: HTTPType.PUT,
action:
"/client/r0/rooms/${widget.room.id}/state/m.room.canonical_alias",
data: {"alias": "#$s:$domain"}),
'/client/r0/rooms/${widget.room.id}/state/m.room.canonical_alias',
data: {'alias': '#$s:$domain'}),
);
}
void setTopicAction(BuildContext context) async {
final String displayname = await SimpleDialogs(context).enterText(
final displayname = await SimpleDialogs(context).enterText(
titleText: L10n.of(context).setGroupDescription,
labelText: L10n.of(context).setGroupDescription,
hintText: (widget.room.topic?.isNotEmpty ?? false)
@ -106,7 +105,7 @@ class _ChatDetailsState extends State<ChatDetails> {
}
void setAvatarAction(BuildContext context) async {
final File tempFile = await ImagePicker.pickImage(
final tempFile = await ImagePicker.pickImage(
source: ImageSource.gallery,
imageQuality: 50,
maxWidth: 1600,
@ -145,9 +144,9 @@ class _ChatDetailsState extends State<ChatDetails> {
}
members ??= widget.room.getParticipants();
members.removeWhere((u) => u.membership == Membership.leave);
final int actualMembersCount =
final actualMembersCount =
widget.room.mInvitedMemberCount + widget.room.mJoinedMemberCount;
final bool canRequestMoreMembers = members.length < actualMembersCount;
final canRequestMoreMembers = members.length < actualMembersCount;
return AdaptivePageLayout(
primaryPage: FocusPage.SECOND,
firstScaffold: ChatList(
@ -189,7 +188,7 @@ class _ChatDetailsState extends State<ChatDetails> {
backgroundColor: Theme.of(context).appBarTheme.color,
flexibleSpace: FlexibleSpaceBar(
background: ContentBanner(widget.room.avatar,
onEdit: widget.room.canSendEvent("m.room.avatar") &&
onEdit: widget.room.canSendEvent('m.room.avatar') &&
!kIsWeb
? () => setAvatarAction(context)
: null),
@ -204,7 +203,7 @@ class _ChatDetailsState extends State<ChatDetails> {
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
ListTile(
leading: widget.room.canSendEvent("m.room.topic")
leading: widget.room.canSendEvent('m.room.topic')
? CircleAvatar(
backgroundColor: Theme.of(context)
.scaffoldBackgroundColor,
@ -213,7 +212,7 @@ class _ChatDetailsState extends State<ChatDetails> {
)
: null,
title: Text(
"${L10n.of(context).groupDescription}:",
'${L10n.of(context).groupDescription}:',
style: TextStyle(
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold)),
@ -230,7 +229,7 @@ class _ChatDetailsState extends State<ChatDetails> {
.color,
),
),
onTap: widget.room.canSendEvent("m.room.topic")
onTap: widget.room.canSendEvent('m.room.topic')
? () => setTopicAction(context)
: null,
),
@ -244,7 +243,7 @@ class _ChatDetailsState extends State<ChatDetails> {
),
),
),
if (widget.room.canSendEvent("m.room.name"))
if (widget.room.canSendEvent('m.room.name'))
ListTile(
leading: CircleAvatar(
backgroundColor:
@ -259,7 +258,7 @@ class _ChatDetailsState extends State<ChatDetails> {
onTap: () => setDisplaynameAction(context),
),
if (widget.room
.canSendEvent("m.room.canonical_alias") &&
.canSendEvent('m.room.canonical_alias') &&
widget.room.joinRules == JoinRules.public)
ListTile(
leading: CircleAvatar(

View file

@ -50,14 +50,14 @@ class _ChatEncryptionSettingsState extends State<ChatEncryptionSettings> {
if (snapshot.hasError) {
return Center(
child: Text(L10n.of(context).oopsSomethingWentWrong +
": " +
': ' +
snapshot.error.toString()),
);
}
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
}
final List<DeviceKeys> deviceKeys = snapshot.data;
final deviceKeys = snapshot.data;
return ListView.separated(
separatorBuilder: (BuildContext context, int i) =>
Divider(height: 1),
@ -96,7 +96,7 @@ class _ChatEncryptionSettingsState extends State<ChatEncryptionSettings> {
),
subtitle: Text(
deviceKeys[i]
.keys["ed25519:${deviceKeys[i].deviceId}"]
.keys['ed25519:${deviceKeys[i].deviceId}']
.beautified,
style: TextStyle(
color:

View file

@ -35,7 +35,7 @@ class ChatListView extends StatelessWidget {
firstScaffold: ChatList(),
secondScaffold: Scaffold(
body: Center(
child: Image.asset("assets/logo.png", width: 100, height: 100),
child: Image.asset('assets/logo.png', width: 100, height: 100),
),
),
);
@ -62,7 +62,7 @@ class _ChatListState extends State<ChatList> {
final ScrollController _scrollController = ScrollController();
Future<void> waitForFirstSync(BuildContext context) async {
Client client = Matrix.of(context).client;
var client = Matrix.of(context).client;
if (client.prevBatch?.isEmpty ?? true) {
await client.onFirstSync.stream.first;
}
@ -106,7 +106,7 @@ class _ChatListState extends State<ChatList> {
publicRoomsResponse = newPublicRoomsResponse;
if (searchController.text.isNotEmpty &&
searchController.text.isValidMatrixId &&
searchController.text.sigil == "#") {
searchController.text.sigil == '#') {
publicRoomsResponse.publicRooms.add(
PublicRoomEntry(
aliases: [searchController.text],
@ -134,11 +134,11 @@ class _ChatListState extends State<ChatList> {
if (Navigator.of(context).canPop()) {
Navigator.of(context).popUntil((r) => r.isFirst);
}
final File file = File(files.first.path);
final file = File(files.first.path);
Matrix.of(context).shareContent = {
"msgtype": "chat.fluffy.shared_file",
"file": MatrixFile(
'msgtype': 'chat.fluffy.shared_file',
'file': MatrixFile(
bytes: file.readAsBytesSync(),
path: file.path,
),
@ -150,13 +150,13 @@ class _ChatListState extends State<ChatList> {
if (Navigator.of(context).canPop()) {
Navigator.of(context).popUntil((r) => r.isFirst);
}
if (text.startsWith("https://matrix.to/#/")) {
if (text.startsWith('https://matrix.to/#/')) {
UrlLauncher(context, text).openMatrixToUrl();
return;
}
Matrix.of(context).shareContent = {
"msgtype": "m.text",
"body": text,
'msgtype': 'm.text',
'body': text,
};
}
@ -204,8 +204,8 @@ class _ChatListState extends State<ChatList> {
action:
'/client/r0/presence/${Matrix.of(context).client.userID}/status',
data: {
"presence": "online",
"status_msg": status,
'presence': 'online',
'status_msg': status,
},
),
);
@ -288,7 +288,7 @@ class _ChatListState extends State<ChatList> {
Navigator.of(context).pop();
Share.share(L10n.of(context).inviteText(
Matrix.of(context).client.userID,
"https://matrix.to/#/${Matrix.of(context).client.userID}"));
'https://matrix.to/#/${Matrix.of(context).client.userID}'));
},
),
],
@ -381,13 +381,13 @@ class _ChatListState extends State<ChatList> {
future: waitForFirstSync(context),
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
List<Room> rooms = List<Room>.from(
var rooms = List<Room>.from(
Matrix.of(context).client.rooms);
rooms.removeWhere((Room room) =>
searchMode &&
!room.displayname.toLowerCase().contains(
searchController.text.toLowerCase() ??
""));
''));
if (rooms.isEmpty &&
(!searchMode ||
publicRoomsResponse == null)) {
@ -410,10 +410,10 @@ class _ChatListState extends State<ChatList> {
),
);
}
final int publicRoomsCount =
final publicRoomsCount =
(publicRoomsResponse?.publicRooms?.length ??
0);
final int totalCount =
final totalCount =
rooms.length + publicRoomsCount;
return ListView.separated(
controller: _scrollController,

View file

@ -8,7 +8,7 @@ import 'package:fluffychat/views/sign_up.dart';
import 'package:flutter/material.dart';
class HomeserverPicker extends StatelessWidget {
_setHomeserverAction(BuildContext context) async {
Future<void> _setHomeserverAction(BuildContext context) async {
final homeserver = await SimpleDialogs(context).enterText(
titleText: L10n.of(context).enterYourHomeserver,
hintText: Matrix.defaultHomeserver,
@ -17,7 +17,7 @@ class HomeserverPicker extends StatelessWidget {
_checkHomeserverAction(homeserver, context);
}
_checkHomeserverAction(String homeserver, BuildContext context) async {
void _checkHomeserverAction(String homeserver, BuildContext context) async {
if (!homeserver.startsWith('https://')) {
homeserver = 'https://$homeserver';
}
@ -40,7 +40,7 @@ class HomeserverPicker extends StatelessWidget {
children: <Widget>[
Hero(
tag: 'loginBanner',
child: Image.asset("assets/fluffychat-banner.png"),
child: Image.asset('assets/fluffychat-banner.png'),
),
Padding(
padding: const EdgeInsets.all(16.0),

View file

@ -27,17 +27,18 @@ class _InvitationSelectionState extends State<InvitationSelection> {
Timer coolDown;
Future<List<User>> getContacts(BuildContext context) async {
final Client client = Matrix.of(context).client;
List<User> participants = await widget.room.requestParticipants();
var client2 = Matrix.of(context).client;
final client = client2;
var participants = await widget.room.requestParticipants();
participants.removeWhere(
(u) => ![Membership.join, Membership.invite].contains(u.membership),
);
List<User> contacts = [];
Map<String, bool> userMap = {};
for (int i = 0; i < client.rooms.length; i++) {
List<User> roomUsers = client.rooms[i].getParticipants();
var contacts = <User>[];
var userMap = <String, bool>{};
for (var i = 0; i < client.rooms.length; i++) {
var roomUsers = client.rooms[i].getParticipants();
for (int j = 0; j < roomUsers.length; j++) {
for (var j = 0; j < roomUsers.length; j++) {
if (userMap[roomUsers[j].id] != true &&
participants.indexWhere((u) => u.id == roomUsers[j].id) == -1) {
contacts.add(roomUsers[j]);
@ -81,41 +82,41 @@ class _InvitationSelectionState extends State<InvitationSelection> {
if (currentSearchTerm.isEmpty) return;
if (loading) return;
setState(() => loading = true);
final MatrixState matrix = Matrix.of(context);
final matrix = Matrix.of(context);
final response = await SimpleDialogs(context).tryRequestWithErrorToast(
matrix.client.jsonRequest(
type: HTTPType.POST,
action: "/client/r0/user_directory/search",
action: '/client/r0/user_directory/search',
data: {
"search_term": text,
"limit": 10,
'search_term': text,
'limit': 10,
}),
);
setState(() => loading = false);
if (response == false ||
!(response is Map) ||
(response["results"] == null)) return;
(response['results'] == null)) return;
setState(() {
foundProfiles = List<Map<String, dynamic>>.from(response["results"]);
if ("@$text".isValidMatrixId &&
foundProfiles = List<Map<String, dynamic>>.from(response['results']);
if ('@$text'.isValidMatrixId &&
foundProfiles
.indexWhere((profile) => "@$text" == profile["user_id"]) ==
.indexWhere((profile) => '@$text' == profile['user_id']) ==
-1) {
setState(() => foundProfiles = [
{"user_id": "@$text"}
{'user_id': '@$text'}
]);
}
foundProfiles.removeWhere((profile) =>
widget.room
.getParticipants()
.indexWhere((u) => u.id == profile["user_id"]) !=
.indexWhere((u) => u.id == profile['user_id']) !=
-1);
});
}
@override
Widget build(BuildContext context) {
final String groupName = widget.room.name?.isEmpty ?? false
final groupName = widget.room.name?.isEmpty ?? false
? L10n.of(context).group
: widget.room.name;
return AdaptivePageLayout(
@ -138,7 +139,7 @@ class _InvitationSelectionState extends State<InvitationSelection> {
onSubmitted: (String text) => searchUser(context, text),
decoration: InputDecoration(
border: OutlineInputBorder(),
prefixText: "@",
prefixText: '@',
hintText: L10n.of(context).username,
labelText: L10n.of(context).inviteContactToGroup(groupName),
suffixIcon: loading
@ -159,19 +160,19 @@ class _InvitationSelectionState extends State<InvitationSelection> {
itemCount: foundProfiles.length,
itemBuilder: (BuildContext context, int i) => ListTile(
leading: Avatar(
foundProfiles[i]["avatar_url"] == null
foundProfiles[i]['avatar_url'] == null
? null
: Uri.parse(foundProfiles[i]["avatar_url"]),
foundProfiles[i]["display_name"] ??
foundProfiles[i]["user_id"],
: Uri.parse(foundProfiles[i]['avatar_url']),
foundProfiles[i]['display_name'] ??
foundProfiles[i]['user_id'],
),
title: Text(
foundProfiles[i]["display_name"] ??
(foundProfiles[i]["user_id"] as String).localpart,
foundProfiles[i]['display_name'] ??
(foundProfiles[i]['user_id'] as String).localpart,
),
subtitle: Text(foundProfiles[i]["user_id"]),
subtitle: Text(foundProfiles[i]['user_id']),
onTap: () =>
inviteAction(context, foundProfiles[i]["user_id"]),
inviteAction(context, foundProfiles[i]['user_id']),
),
)
: FutureBuilder<List<User>>(
@ -182,7 +183,7 @@ class _InvitationSelectionState extends State<InvitationSelection> {
child: CircularProgressIndicator(),
);
}
List<User> contacts = snapshot.data;
var contacts = snapshot.data;
return ListView.builder(
itemCount: contacts.length,
itemBuilder: (BuildContext context, int i) => ListTile(

View file

@ -24,7 +24,7 @@ class _LoginState extends State<Login> {
bool showPassword = false;
void login(BuildContext context) async {
MatrixState matrix = Matrix.of(context);
var matrix = Matrix.of(context);
if (usernameController.text.isEmpty) {
setState(() => usernameError = L10n.of(context).pleaseEnterYourUsername);
} else {
@ -101,7 +101,7 @@ class _LoginState extends State<Login> {
controller: usernameController,
decoration: InputDecoration(
hintText:
"@${L10n.of(context).username.toLowerCase()}:domain",
'@${L10n.of(context).username.toLowerCase()}:domain',
errorText: usernameError,
labelText: L10n.of(context).username),
),
@ -120,7 +120,7 @@ class _LoginState extends State<Login> {
obscureText: !showPassword,
onSubmitted: (t) => login(context),
decoration: InputDecoration(
hintText: "****",
hintText: '****',
errorText: passwordError,
suffixIcon: IconButton(
icon: Icon(showPassword

View file

@ -31,18 +31,18 @@ class _NewGroupState extends State<_NewGroup> {
bool publicGroup = false;
void submitAction(BuildContext context) async {
final MatrixState matrix = Matrix.of(context);
Map<String, dynamic> params = {};
final matrix = Matrix.of(context);
var params = <String, dynamic>{};
if (publicGroup) {
params["preset"] = "public_chat";
params["visibility"] = "public";
params['preset'] = 'public_chat';
params['visibility'] = 'public';
if (controller.text.isNotEmpty) {
params["room_alias_name"] = controller.text;
params['room_alias_name'] = controller.text;
}
} else {
params["preset"] = "private_chat";
params['preset'] = 'private_chat';
}
if (controller.text.isNotEmpty) params["name"] = controller.text;
if (controller.text.isNotEmpty) params['name'] = controller.text;
final String roomID =
await SimpleDialogs(context).tryRequestWithLoadingDialog(
matrix.client.createRoom(params: params),
@ -99,7 +99,7 @@ class _NewGroupState extends State<_NewGroup> {
onChanged: (bool b) => setState(() => publicGroup = b),
),
Expanded(
child: Image.asset("assets/new_group_wallpaper.png"),
child: Image.asset('assets/new_group_wallpaper.png'),
),
],
),

View file

@ -37,23 +37,23 @@ class _NewPrivateChatState extends State<_NewPrivateChat> {
List<Map<String, dynamic>> foundProfiles = [];
Timer coolDown;
Map<String, dynamic> get foundProfile => foundProfiles.firstWhere(
(user) => user["user_id"] == "@$currentSearchTerm",
(user) => user['user_id'] == '@$currentSearchTerm',
orElse: () => null);
bool get correctMxId =>
foundProfiles
.indexWhere((user) => user["user_id"] == "@$currentSearchTerm") !=
.indexWhere((user) => user['user_id'] == '@$currentSearchTerm') !=
-1;
void submitAction(BuildContext context) async {
if (controller.text.isEmpty) return;
if (!_formKey.currentState.validate()) return;
final MatrixState matrix = Matrix.of(context);
final matrix = Matrix.of(context);
if ("@" + controller.text.trim() == matrix.client.userID) return;
if ('@' + controller.text.trim() == matrix.client.userID) return;
final User user = User(
"@" + controller.text.trim(),
room: Room(id: "", client: matrix.client),
final user = User(
'@' + controller.text.trim(),
room: Room(id: '', client: matrix.client),
);
final String roomID = await SimpleDialogs(context)
.tryRequestWithLoadingDialog(user.startDirectChat());
@ -87,22 +87,22 @@ class _NewPrivateChatState extends State<_NewPrivateChat> {
if (currentSearchTerm.isEmpty) return;
if (loading) return;
setState(() => loading = true);
final MatrixState matrix = Matrix.of(context);
final matrix = Matrix.of(context);
final response = await SimpleDialogs(context).tryRequestWithErrorToast(
matrix.client.jsonRequest(
type: HTTPType.POST,
action: "/client/r0/user_directory/search",
action: '/client/r0/user_directory/search',
data: {
"search_term": text,
"limit": 10,
'search_term': text,
'limit': 10,
}),
);
setState(() => loading = false);
if (response == false ||
!(response is Map) ||
(response["results"]?.isEmpty ?? true)) return;
(response['results']?.isEmpty ?? true)) return;
setState(() {
foundProfiles = List<Map<String, dynamic>>.from(response["results"]);
foundProfiles = List<Map<String, dynamic>>.from(response['results']);
});
}
@ -131,15 +131,15 @@ class _NewPrivateChatState extends State<_NewPrivateChat> {
if (value.isEmpty) {
return L10n.of(context).pleaseEnterAMatrixIdentifier;
}
final MatrixState matrix = Matrix.of(context);
String mxid = "@" + controller.text.trim();
final matrix = Matrix.of(context);
var mxid = '@' + controller.text.trim();
if (mxid == matrix.client.userID) {
return L10n.of(context).youCannotInviteYourself;
}
if (!mxid.contains("@")) {
if (!mxid.contains('@')) {
return L10n.of(context).makeSureTheIdentifierIsValid;
}
if (!mxid.contains(":")) {
if (!mxid.contains(':')) {
return L10n.of(context).makeSureTheIdentifierIsValid;
}
return null;
@ -158,17 +158,17 @@ class _NewPrivateChatState extends State<_NewPrivateChat> {
? Padding(
padding: const EdgeInsets.all(8.0),
child: Avatar(
foundProfile["avatar_url"] == null
foundProfile['avatar_url'] == null
? null
: Uri.parse(foundProfile["avatar_url"]),
foundProfile["display_name"] ??
foundProfile["user_id"],
: Uri.parse(foundProfile['avatar_url']),
foundProfile['display_name'] ??
foundProfile['user_id'],
size: 12,
),
)
: Icon(Icons.account_circle),
prefixText: "@",
hintText: "${L10n.of(context).username.toLowerCase()}",
prefixText: '@',
hintText: '${L10n.of(context).username.toLowerCase()}',
),
),
),
@ -179,29 +179,29 @@ class _NewPrivateChatState extends State<_NewPrivateChat> {
child: ListView.builder(
itemCount: foundProfiles.length,
itemBuilder: (BuildContext context, int i) {
Map<String, dynamic> foundProfile = foundProfiles[i];
var foundProfile = foundProfiles[i];
return ListTile(
onTap: () {
setState(() {
controller.text = currentSearchTerm =
foundProfile["user_id"].substring(1);
foundProfile['user_id'].substring(1);
});
},
leading: Avatar(
foundProfile["avatar_url"] == null
foundProfile['avatar_url'] == null
? null
: Uri.parse(foundProfile["avatar_url"]),
foundProfile["display_name"] ?? foundProfile["user_id"],
: Uri.parse(foundProfile['avatar_url']),
foundProfile['display_name'] ?? foundProfile['user_id'],
//size: 24,
),
title: Text(
foundProfile["display_name"] ??
(foundProfile["user_id"] as String).localpart,
foundProfile['display_name'] ??
(foundProfile['user_id'] as String).localpart,
style: TextStyle(),
maxLines: 1,
),
subtitle: Text(
foundProfile["user_id"],
foundProfile['user_id'],
maxLines: 1,
style: TextStyle(
fontSize: 12,
@ -219,9 +219,9 @@ class _NewPrivateChatState extends State<_NewPrivateChat> {
),
onTap: () => Share.share(L10n.of(context).inviteText(
Matrix.of(context).client.userID,
"https://matrix.to/#/${Matrix.of(context).client.userID}")),
'https://matrix.to/#/${Matrix.of(context).client.userID}')),
title: Text(
"${L10n.of(context).yourOwnUsername}:",
'${L10n.of(context).yourOwnUsername}:',
style: TextStyle(
fontStyle: FontStyle.italic,
),
@ -237,7 +237,7 @@ class _NewPrivateChatState extends State<_NewPrivateChat> {
Divider(height: 1),
if (foundProfiles.isEmpty || correctMxId)
Expanded(
child: Image.asset("assets/private_chat_wallpaper.png"),
child: Image.asset('assets/private_chat_wallpaper.png'),
),
],
),

View file

@ -1,5 +1,3 @@
import 'dart:io';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/components/settings_themes.dart';
import 'package:fluffychat/views/settings_devices.dart';
@ -42,7 +40,7 @@ class _SettingsState extends State<Settings> {
if (await SimpleDialogs(context).askConfirmation() == false) {
return;
}
MatrixState matrix = Matrix.of(context);
var matrix = Matrix.of(context);
await SimpleDialogs(context)
.tryRequestWithLoadingDialog(matrix.client.logout());
}
@ -57,20 +55,20 @@ class _SettingsState extends State<Settings> {
if (!jitsi.endsWith('/')) {
jitsi += '/';
}
final MatrixState matrix = Matrix.of(context);
await matrix.client.storeAPI.setItem('chat.fluffy.jitsi_instance', jitsi);
final matrix = Matrix.of(context);
await matrix.store.setItem('chat.fluffy.jitsi_instance', jitsi);
matrix.jitsiInstance = jitsi;
}
void setDisplaynameAction(BuildContext context) async {
final String displayname = await SimpleDialogs(context).enterText(
final displayname = await SimpleDialogs(context).enterText(
titleText: L10n.of(context).editDisplayname,
hintText:
profile?.displayname ?? Matrix.of(context).client.userID.localpart,
labelText: L10n.of(context).enterAUsername,
);
if (displayname == null) return;
final MatrixState matrix = Matrix.of(context);
final matrix = Matrix.of(context);
final success = await SimpleDialogs(context).tryRequestWithLoadingDialog(
matrix.client.setDisplayname(displayname),
);
@ -83,13 +81,13 @@ class _SettingsState extends State<Settings> {
}
void setAvatarAction(BuildContext context) async {
final File tempFile = await ImagePicker.pickImage(
final tempFile = await ImagePicker.pickImage(
source: ImageSource.gallery,
imageQuality: 50,
maxWidth: 1600,
maxHeight: 1600);
if (tempFile == null) return;
final MatrixState matrix = Matrix.of(context);
final matrix = Matrix.of(context);
final success = await SimpleDialogs(context).tryRequestWithLoadingDialog(
matrix.client.setAvatar(
MatrixFile(
@ -111,24 +109,20 @@ class _SettingsState extends State<Settings> {
if (wallpaper == null) return;
Matrix.of(context).wallpaper = wallpaper;
await Matrix.of(context)
.client
.storeAPI
.setItem("chat.fluffy.wallpaper", wallpaper.path);
.store
.setItem('chat.fluffy.wallpaper', wallpaper.path);
setState(() => null);
}
void deleteWallpaperAction(BuildContext context) async {
Matrix.of(context).wallpaper = null;
await Matrix.of(context)
.client
.storeAPI
.setItem("chat.fluffy.wallpaper", null);
await Matrix.of(context).store.setItem('chat.fluffy.wallpaper', null);
setState(() => null);
}
@override
Widget build(BuildContext context) {
final Client client = Matrix.of(context).client;
final client = Matrix.of(context).client;
profileFuture ??= client.ownProfile;
profileFuture.then((p) {
if (mounted) setState(() => profile = p);
@ -174,8 +168,9 @@ class _SettingsState extends State<Settings> {
),
),
ThemesSettings(),
if (!kIsWeb && client.storeAPI != null) Divider(thickness: 1),
if (!kIsWeb && client.storeAPI != null)
if (!kIsWeb && Matrix.of(context).store != null)
Divider(thickness: 1),
if (!kIsWeb && Matrix.of(context).store != null)
ListTile(
title: Text(
L10n.of(context).wallpaper,
@ -198,7 +193,7 @@ class _SettingsState extends State<Settings> {
),
onTap: () => deleteWallpaperAction(context),
),
if (!kIsWeb && client.storeAPI != null)
if (!kIsWeb && Matrix.of(context).store != null)
Builder(builder: (context) {
return ListTile(
title: Text(L10n.of(context).changeWallpaper),
@ -223,8 +218,9 @@ class _SettingsState extends State<Settings> {
activeColor: Theme.of(context).primaryColor,
onChanged: (bool newValue) async {
Matrix.of(context).renderHtml = newValue;
await client.storeAPI
.setItem("chat.fluffy.renderHtml", newValue ? "1" : "0");
await Matrix.of(context)
.store
.setItem('chat.fluffy.renderHtml', newValue ? '1' : '0');
setState(() => null);
},
),
@ -300,19 +296,19 @@ class _SettingsState extends State<Settings> {
trailing: Icon(Icons.help),
title: Text(L10n.of(context).help),
onTap: () => launch(
"https://gitlab.com/ChristianPauly/fluffychat-flutter/issues"),
'https://gitlab.com/ChristianPauly/fluffychat-flutter/issues'),
),
ListTile(
trailing: Icon(Icons.link),
title: Text(L10n.of(context).license),
onTap: () => launch(
"https://gitlab.com/ChristianPauly/fluffychat-flutter/raw/master/LICENSE"),
'https://gitlab.com/ChristianPauly/fluffychat-flutter/raw/master/LICENSE'),
),
ListTile(
trailing: Icon(Icons.code),
title: Text(L10n.of(context).sourceCode),
onTap: () => launch(
"https://gitlab.com/ChristianPauly/fluffychat-flutter"),
'https://gitlab.com/ChristianPauly/fluffychat-flutter'),
),
],
),

View file

@ -37,18 +37,18 @@ class DevicesSettingsState extends State<DevicesSettings> {
void _removeDevicesAction(
BuildContext context, List<UserDevice> devices) async {
if (await SimpleDialogs(context).askConfirmation() == false) return;
MatrixState matrix = Matrix.of(context);
List<String> deviceIds = [];
for (UserDevice userDevice in devices) {
var matrix = Matrix.of(context);
var deviceIds = <String>[];
for (var userDevice in devices) {
deviceIds.add(userDevice.deviceId);
}
final success = await SimpleDialogs(context)
.tryRequestWithLoadingDialog(matrix.client.deleteDevices(deviceIds),
onAdditionalAuth: (MatrixException exception) async {
final String password = await SimpleDialogs(context).enterText(
final password = await SimpleDialogs(context).enterText(
titleText: L10n.of(context).pleaseEnterYourPassword,
labelText: L10n.of(context).pleaseEnterYourPassword,
hintText: "******",
hintText: '******',
password: true);
if (password == null) return;
await matrix.client.deleteDevices(deviceIds,
@ -83,9 +83,8 @@ class DevicesSettingsState extends State<DevicesSettings> {
}
Function isOwnDevice = (UserDevice userDevice) =>
userDevice.deviceId == Matrix.of(context).client.deviceID;
final List<UserDevice> devices = List<UserDevice>.from(this.devices);
UserDevice thisDevice =
devices.firstWhere(isOwnDevice, orElse: () => null);
final devices = List<UserDevice>.from(this.devices);
var thisDevice = devices.firstWhere(isOwnDevice, orElse: () => null);
devices.removeWhere(isOwnDevice);
devices.sort((a, b) => b.lastSeenTs.compareTo(a.lastSeenTs));
return Column(
@ -145,13 +144,13 @@ class UserDeviceListItem extends StatelessWidget {
Widget build(BuildContext context) {
return PopupMenuButton(
onSelected: (String action) {
if (action == "remove" && this.remove != null) {
if (action == 'remove' && remove != null) {
remove(userDevice);
}
},
itemBuilder: (BuildContext context) => [
PopupMenuItem<String>(
value: "remove",
value: 'remove',
child: Text(L10n.of(context).removeDevice,
style: TextStyle(color: Colors.red)),
),
@ -175,8 +174,8 @@ class UserDeviceListItem extends StatelessWidget {
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("${L10n.of(context).id}: ${userDevice.deviceId}"),
Text("${L10n.of(context).lastSeenIp}: ${userDevice.lastSeenIp}"),
Text('${L10n.of(context).id}: ${userDevice.deviceId}'),
Text('${L10n.of(context).lastSeenIp}: ${userDevice.lastSeenIp}'),
],
),
),

View file

@ -1,5 +1,3 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_advanced_networkimage/provider.dart';
@ -55,7 +53,7 @@ class _EmotesSettingsState extends State<EmotesSettings> {
if (readonly) {
return;
}
debugPrint("Saving....");
debugPrint('Saving....');
final client = Matrix.of(context).client;
// be sure to preserve any data not in "short"
Map<String, dynamic> content;
@ -95,7 +93,7 @@ class _EmotesSettingsState extends State<EmotesSettings> {
@override
Widget build(BuildContext context) {
Client client = Matrix.of(context).client;
var client = Matrix.of(context).client;
if (emotes == null) {
emotes = <_EmoteEntry>[];
Map<String, dynamic> emoteSource;
@ -173,7 +171,7 @@ class _EmotesSettingsState extends State<EmotesSettings> {
size: 32.0,
),
onTap: () async {
debugPrint("blah");
debugPrint('blah');
if (newEmoteController.text == null ||
newEmoteController.text.isEmpty ||
newMxcController.text == null ||
@ -374,7 +372,7 @@ class _EmoteImagePickerState extends State<_EmoteImagePicker> {
BotToast.showText(text: L10n.of(context).notSupportedInWeb);
return;
}
File file = await ImagePicker.pickImage(
var file = await ImagePicker.pickImage(
source: ImageSource.gallery,
imageQuality: 50,
maxWidth: 128,

View file

@ -23,7 +23,7 @@ class _SignUpState extends State<SignUp> {
File avatar;
void setAvatarAction() async {
File file = await ImagePicker.pickImage(
var file = await ImagePicker.pickImage(
source: ImageSource.gallery,
maxHeight: 512,
maxWidth: 512,
@ -33,7 +33,7 @@ class _SignUpState extends State<SignUp> {
}
void signUpAction(BuildContext context) async {
MatrixState matrix = Matrix.of(context);
var matrix = Matrix.of(context);
if (usernameController.text.isEmpty) {
setState(() => usernameError = L10n.of(context).pleaseChooseAUsername);
} else {
@ -45,8 +45,8 @@ class _SignUpState extends State<SignUp> {
}
setState(() => loading = true);
final String preferredUsername =
usernameController.text.toLowerCase().replaceAll(" ", "-");
final preferredUsername =
usernameController.text.toLowerCase().replaceAll(' ', '-');
try {
await matrix.client.usernameAvailable(preferredUsername);
@ -83,7 +83,7 @@ class _SignUpState extends State<SignUp> {
children: <Widget>[
Hero(
tag: 'loginBanner',
child: Image.asset("assets/fluffychat-banner.png"),
child: Image.asset('assets/fluffychat-banner.png'),
),
ListTile(
leading: CircleAvatar(

View file

@ -27,7 +27,7 @@ class _SignUpPasswordState extends State<SignUpPassword> {
bool showPassword = true;
void _signUpAction(BuildContext context, {Map<String, dynamic> auth}) async {
MatrixState matrix = Matrix.of(context);
var matrix = Matrix.of(context);
if (passwordController.text.isEmpty) {
setState(() => passwordError = L10n.of(context).pleaseEnterYourPassword);
} else {
@ -40,8 +40,7 @@ class _SignUpPasswordState extends State<SignUpPassword> {
try {
setState(() => loading = true);
Future<LoginState> waitForLogin =
matrix.client.onLoginStateChanged.stream.first;
var waitForLogin = matrix.client.onLoginStateChanged.stream.first;
await matrix.client.register(
username: widget.username,
password: passwordController.text,
@ -51,21 +50,20 @@ class _SignUpPasswordState extends State<SignUpPassword> {
await waitForLogin;
} on MatrixException catch (exception) {
if (exception.requireAdditionalAuthentication) {
final List<String> stages = exception.authenticationFlows
.firstWhere((a) => !a.stages.contains("m.login.email.identity"))
final stages = exception.authenticationFlows
.firstWhere((a) => !a.stages.contains('m.login.email.identity'))
.stages;
final String currentStage =
exception.completedAuthenticationFlows == null
? stages.first
: stages.firstWhere((stage) =>
!exception.completedAuthenticationFlows.contains(stage) ??
true);
final currentStage = exception.completedAuthenticationFlows == null
? stages.first
: stages.firstWhere((stage) =>
!exception.completedAuthenticationFlows.contains(stage) ??
true);
if (currentStage == "m.login.dummy") {
if (currentStage == 'm.login.dummy') {
_signUpAction(context, auth: {
"type": currentStage,
"session": exception.session,
'type': currentStage,
'session': exception.session,
});
} else {
await Navigator.of(context).push(
@ -75,7 +73,7 @@ class _SignUpPasswordState extends State<SignUpPassword> {
currentStage,
exception.session,
() => _signUpAction(context, auth: {
"session": exception.session,
'session': exception.session,
}),
),
),
@ -141,7 +139,7 @@ class _SignUpPasswordState extends State<SignUpPassword> {
autocorrect: false,
onSubmitted: (t) => _signUpAction(context),
decoration: InputDecoration(
hintText: "****",
hintText: '****',
errorText: passwordError,
suffixIcon: IconButton(
icon: Icon(