Merge branch 'krille/refactor-loading-dialogs' into 'main'

chore: Switch to adaptive dialogs

See merge request ChristianPauly/fluffychat-flutter!267
This commit is contained in:
Christian Pauly 2020-11-14 09:42:40 +00:00
commit 5a897739e4
20 changed files with 361 additions and 363 deletions

View File

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/utils/app_route.dart'; import 'package:fluffychat/utils/app_route.dart';
import 'package:fluffychat/views/chat_details.dart'; import 'package:fluffychat/views/chat_details.dart';
@ -84,8 +85,11 @@ class _ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
onSelected: (String choice) async { onSelected: (String choice) async {
switch (choice) { switch (choice) {
case 'leave': case 'leave':
var confirmed = await SimpleDialogs(context).askConfirmation(); var confirmed = await showOkCancelAlertDialog(
if (confirmed) { context: context,
title: L10n.of(context).areYouSure,
);
if (confirmed == OkCancelResult.ok) {
final success = await SimpleDialogs(context) final success = await SimpleDialogs(context)
.tryRequestWithLoadingDialog(widget.room.leave()); .tryRequestWithLoadingDialog(widget.room.leave());
if (success != false) { if (success != false) {

View File

@ -2,140 +2,12 @@ import 'package:flushbar/flushbar_helper.dart';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix_link_text/link_text.dart';
class SimpleDialogs { class SimpleDialogs {
final BuildContext context; final BuildContext context;
const SimpleDialogs(this.context); const SimpleDialogs(this.context);
Future<String> enterText(
{String titleText,
String confirmText,
String cancelText,
String hintText,
String labelText,
String prefixText,
String suffixText,
bool password = false,
bool multiLine = false,
TextInputType keyboardType}) async {
var textEditingController = TextEditingController();
final controller = textEditingController;
String input;
await showDialog(
context: context,
builder: (c) => AlertDialog(
title: Text(titleText ?? 'Please enter a text'),
content: TextField(
controller: controller,
autofocus: true,
autocorrect: false,
onSubmitted: (s) {
input = s;
Navigator.of(c).pop();
},
minLines: multiLine ? 3 : 1,
maxLines: multiLine ? 3 : 1,
obscureText: password,
textInputAction: multiLine ? TextInputAction.newline : null,
keyboardType: keyboardType,
decoration: InputDecoration(
hintText: hintText,
labelText: labelText,
prefixText: prefixText,
suffixText: suffixText,
prefixStyle: TextStyle(color: Theme.of(context).primaryColor),
suffixStyle: TextStyle(color: Theme.of(context).primaryColor),
border: OutlineInputBorder(),
),
),
actions: <Widget>[
FlatButton(
child: Text(
cancelText?.toUpperCase() ??
L10n.of(context).close.toUpperCase(),
style: TextStyle(color: Colors.blueGrey)),
onPressed: () => Navigator.of(c).pop(),
),
FlatButton(
child: Text(
confirmText?.toUpperCase() ??
L10n.of(context).confirm.toUpperCase(),
),
onPressed: () {
input = controller.text;
Navigator.of(c).pop();
},
),
],
),
);
return input;
}
Future<bool> askConfirmation({
String titleText,
String contentText,
String confirmText,
String cancelText,
bool dangerous = false,
}) async {
var confirmed = false;
await showDialog(
context: context,
builder: (c) => AlertDialog(
title: Text(titleText ?? L10n.of(context).areYouSure),
content: contentText != null ? LinkText(text: contentText) : null,
actions: <Widget>[
FlatButton(
child: Text(
cancelText?.toUpperCase() ??
L10n.of(context).close.toUpperCase(),
style: TextStyle(color: Colors.blueGrey)),
onPressed: () => Navigator.of(c).pop(),
),
FlatButton(
child: Text(
confirmText?.toUpperCase() ??
L10n.of(context).confirm.toUpperCase(),
style: TextStyle(color: dangerous ? Colors.red : null),
),
onPressed: () {
confirmed = true;
Navigator.of(c).pop();
},
),
],
),
);
return confirmed;
}
Future<void> inform({
String titleText,
String contentText,
String okText,
}) async {
await showDialog(
context: context,
builder: (c) => AlertDialog(
title: titleText != null ? Text(titleText) : null,
content: contentText != null ? Text(contentText) : null,
actions: <Widget>[
FlatButton(
child: Text(
okText ?? L10n.of(context).ok.toUpperCase(),
),
onPressed: () {
Navigator.of(c).pop();
},
),
],
),
);
}
Future<dynamic> tryRequestWithLoadingDialog(Future<dynamic> request, Future<dynamic> tryRequestWithLoadingDialog(Future<dynamic> request,
{Function(MatrixException) onAdditionalAuth}) async { {Function(MatrixException) onAdditionalAuth}) async {
var completed = false; var completed = false;
@ -178,15 +50,4 @@ class SimpleDialogs {
return false; return false;
} }
} }
void showLoadingDialog(BuildContext context) async {
await showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) => AlertDialog(
title: Text(L10n.of(context).loadingPleaseWait),
content: LinearProgressIndicator(),
),
);
}
} }

View File

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:flushbar/flushbar_helper.dart'; import 'package:flushbar/flushbar_helper.dart';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/utils/app_route.dart'; import 'package:fluffychat/utils/app_route.dart';
@ -36,14 +37,15 @@ class _EncryptionButtonState extends State<EncryptionButton> {
.show(context); .show(context);
return; return;
} }
if (await SimpleDialogs(context).askConfirmation( if (await showOkCancelAlertDialog(
titleText: L10n.of(context).enableEncryptionWarning, context: context,
contentText: widget.room.client.encryptionEnabled title: L10n.of(context).enableEncryptionWarning,
message: widget.room.client.encryptionEnabled
? L10n.of(context).warningEncryptionInBeta ? L10n.of(context).warningEncryptionInBeta
: L10n.of(context).needPantalaimonWarning, : L10n.of(context).needPantalaimonWarning,
confirmText: L10n.of(context).yes, okLabel: L10n.of(context).yes,
) == ) ==
true) { OkCancelResult.ok) {
await SimpleDialogs(context).tryRequestWithLoadingDialog( await SimpleDialogs(context).tryRequestWithLoadingDialog(
widget.room.enableEncryption(), widget.room.enableEncryption(),
); );

View File

@ -1,3 +1,4 @@
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:flushbar/flushbar_helper.dart'; import 'package:flushbar/flushbar_helper.dart';
import 'package:circular_check_box/circular_check_box.dart'; import 'package:circular_check_box/circular_check_box.dart';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
@ -117,8 +118,11 @@ class ChatListItem extends StatelessWidget {
} }
return success; return success;
} }
final confirmed = await SimpleDialogs(context).askConfirmation(); final confirmed = await showOkCancelAlertDialog(
if (!confirmed) return; context: context,
title: L10n.of(context).areYouSure,
);
if (confirmed == OkCancelResult.cancel) return;
await SimpleDialogs(context).tryRequestWithLoadingDialog(room.leave()); await SimpleDialogs(context).tryRequestWithLoadingDialog(room.leave());
return; return;
} }

View File

@ -1,9 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:famedlysdk/encryption.dart'; import 'package:famedlysdk/encryption.dart';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/components/dialogs/simple_dialogs.dart';
import 'package:fluffychat/utils/firebase_controller.dart'; import 'package:fluffychat/utils/firebase_controller.dart';
import 'package:fluffychat/utils/matrix_locals.dart'; import 'package:fluffychat/utils/matrix_locals.dart';
import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/platform_infos.dart';
@ -258,13 +258,15 @@ class MatrixState extends State<Matrix> {
return; // ignore share requests by others return; // ignore share requests by others
} }
final sender = room.getUserByMXIDSync(request.sender); final sender = room.getUserByMXIDSync(request.sender);
if (await SimpleDialogs(context).askConfirmation( if (await showOkCancelAlertDialog(
titleText: L10n.of(context).requestToReadOlderMessages, context: context,
contentText: title: L10n.of(context).requestToReadOlderMessages,
message:
'${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, okLabel: L10n.of(context).verify,
cancelText: L10n.of(context).deny, cancelLabel: L10n.of(context).deny,
)) { ) ==
OkCancelResult.ok) {
await request.forwardKey(); await request.forwardKey();
} }
}); });
@ -279,10 +281,12 @@ class MatrixState extends State<Matrix> {
} }
hidPopup = true; hidPopup = true;
}; };
if (await SimpleDialogs(context).askConfirmation( if (await showOkCancelAlertDialog(
titleText: L10n.of(context).newVerificationRequest, context: context,
contentText: L10n.of(context).askVerificationRequest(request.userId), title: L10n.of(context).newVerificationRequest,
)) { message: L10n.of(context).askVerificationRequest(request.userId),
) ==
OkCancelResult.ok) {
request.onUpdate = null; request.onUpdate = null;
hidPopup = true; hidPopup = true;
await request.acceptVerification(); await request.acceptVerification();

View File

@ -1,5 +1,6 @@
import 'dart:math'; import 'dart:math';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/components/adaptive_page_layout.dart'; import 'package:fluffychat/components/adaptive_page_layout.dart';
import 'package:fluffychat/utils/app_route.dart'; import 'package:fluffychat/utils/app_route.dart';
@ -23,41 +24,45 @@ class UserBottomSheet extends StatelessWidget {
: super(key: key); : super(key: key);
void participantAction(BuildContext context, String action) async { void participantAction(BuildContext context, String action) async {
final Function _askConfirmation = () async =>
(await showOkCancelAlertDialog(
context: context, title: L10n.of(context).areYouSure) ==
OkCancelResult.ok);
switch (action) { switch (action) {
case 'mention': case 'mention':
Navigator.of(context).pop(); Navigator.of(context).pop();
onMention(); onMention();
break; break;
case 'ban': case 'ban':
if (await SimpleDialogs(context).askConfirmation()) { if (await _askConfirmation()) {
await SimpleDialogs(context).tryRequestWithLoadingDialog(user.ban()); await SimpleDialogs(context).tryRequestWithLoadingDialog(user.ban());
} }
break; break;
case 'unban': case 'unban':
if (await SimpleDialogs(context).askConfirmation()) { if (await _askConfirmation()) {
await SimpleDialogs(context) await SimpleDialogs(context)
.tryRequestWithLoadingDialog(user.unban()); .tryRequestWithLoadingDialog(user.unban());
} }
break; break;
case 'kick': case 'kick':
if (await SimpleDialogs(context).askConfirmation()) { if (await _askConfirmation()) {
await SimpleDialogs(context).tryRequestWithLoadingDialog(user.kick()); await SimpleDialogs(context).tryRequestWithLoadingDialog(user.kick());
} }
break; break;
case 'admin': case 'admin':
if (await SimpleDialogs(context).askConfirmation()) { if (await _askConfirmation()) {
await SimpleDialogs(context) await SimpleDialogs(context)
.tryRequestWithLoadingDialog(user.setPower(100)); .tryRequestWithLoadingDialog(user.setPower(100));
} }
break; break;
case 'moderator': case 'moderator':
if (await SimpleDialogs(context).askConfirmation()) { if (await _askConfirmation()) {
await SimpleDialogs(context) await SimpleDialogs(context)
.tryRequestWithLoadingDialog(user.setPower(50)); .tryRequestWithLoadingDialog(user.setPower(50));
} }
break; break;
case 'user': case 'user':
if (await SimpleDialogs(context).askConfirmation()) { if (await _askConfirmation()) {
await SimpleDialogs(context) await SimpleDialogs(context)
.tryRequestWithLoadingDialog(user.setPower(0)); .tryRequestWithLoadingDialog(user.setPower(0));
} }

View File

@ -283,6 +283,11 @@
"type": "text", "type": "text",
"placeholders": {} "placeholders": {}
}, },
"changePassword": "Change password",
"@changePassword": {
"type": "text",
"placeholders": {}
},
"changeWallpaper": "Change wallpaper", "changeWallpaper": "Change wallpaper",
"@changeWallpaper": { "@changeWallpaper": {
"type": "text", "type": "text",

View File

@ -1,5 +1,5 @@
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:flushbar/flushbar_helper.dart'; import 'package:flushbar/flushbar_helper.dart';
import 'package:fluffychat/components/dialogs/simple_dialogs.dart';
import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/app_config.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -11,16 +11,18 @@ import '../config/setting_keys.dart';
abstract class SentryController { abstract class SentryController {
static Future<void> toggleSentryAction(BuildContext context) async { static Future<void> toggleSentryAction(BuildContext context) async {
final enableSentry = await SimpleDialogs(context).askConfirmation( final enableSentry = await showOkCancelAlertDialog(
titleText: L10n.of(context).sendBugReports, context: context,
contentText: L10n.of(context).sentryInfo, title: L10n.of(context).sendBugReports,
confirmText: L10n.of(context).ok, message: L10n.of(context).sentryInfo,
cancelText: L10n.of(context).no, okLabel: L10n.of(context).ok,
); cancelLabel: L10n.of(context).no,
) ==
OkCancelResult.ok;
final storage = Store(); final storage = Store();
await storage.setItem(SettingKeys.sentry, enableSentry.toString()); await storage.setItem(SettingKeys.sentry, enableSentry.toString());
await FlushbarHelper.createSuccess( // ignore: unawaited_futures
message: L10n.of(context).changesHaveBeenSaved) FlushbarHelper.createSuccess(message: L10n.of(context).changesHaveBeenSaved)
.show(context); .show(context);
return; return;
} }

View File

@ -1,3 +1,4 @@
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/components/dialogs/simple_dialogs.dart'; import 'package:fluffychat/components/dialogs/simple_dialogs.dart';
import 'package:fluffychat/components/matrix.dart'; import 'package:fluffychat/components/matrix.dart';
@ -79,8 +80,11 @@ class UrlLauncher {
if (roomIdOrAlias[0] == '!') { if (roomIdOrAlias[0] == '!') {
roomId = roomIdOrAlias; roomId = roomIdOrAlias;
} }
if (await SimpleDialogs(context) if (await showOkCancelAlertDialog(
.askConfirmation(titleText: 'Join room $roomIdOrAlias')) { context: context,
title: 'Join room $roomIdOrAlias',
) ==
OkCancelResult.ok) {
final response = final response =
await SimpleDialogs(context).tryRequestWithLoadingDialog( await SimpleDialogs(context).tryRequestWithLoadingDialog(
matrix.client.joinRoomOrAlias( matrix.client.joinRoomOrAlias(
@ -114,8 +118,11 @@ class UrlLauncher {
return; return;
} }
if (await SimpleDialogs(context) if (await showOkCancelAlertDialog(
.askConfirmation(titleText: 'Message user $identifier')) { context: context,
title: 'Message user $identifier',
) ==
OkCancelResult.ok) {
roomId = await SimpleDialogs(context) roomId = await SimpleDialogs(context)
.tryRequestWithLoadingDialog(user.startDirectChat()); .tryRequestWithLoadingDialog(user.startDirectChat());
Navigator.of(context).pop(); Navigator.of(context).pop();

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:file_picker_cross/file_picker_cross.dart'; import 'package:file_picker_cross/file_picker_cross.dart';
@ -303,10 +304,12 @@ class _ChatState extends State<_Chat> {
} }
void redactEventsAction(BuildContext context) async { void redactEventsAction(BuildContext context) async {
var confirmed = await SimpleDialogs(context).askConfirmation( var confirmed = await showOkCancelAlertDialog(
titleText: L10n.of(context).messageWillBeRemovedWarning, context: context,
confirmText: L10n.of(context).remove, title: L10n.of(context).messageWillBeRemovedWarning,
); okLabel: L10n.of(context).remove,
) ==
OkCancelResult.ok;
if (!confirmed) return; if (!confirmed) return;
for (var event in selectedEvents) { for (var event in selectedEvents) {
await SimpleDialogs(context).tryRequestWithLoadingDialog( await SimpleDialogs(context).tryRequestWithLoadingDialog(
@ -363,10 +366,7 @@ class _ChatState extends State<_Chat> {
if (eventIndex == -1) { if (eventIndex == -1) {
// event id not found...maybe we can fetch it? // event id not found...maybe we can fetch it?
// the try...finally is here to start and close the loading dialog reliably // the try...finally is here to start and close the loading dialog reliably
try { final task = Future.microtask(() async {
if (context != null) {
SimpleDialogs(context).showLoadingDialog(context);
}
// okay, we first have to fetch if the event is in the room // okay, we first have to fetch if the event is in the room
try { try {
final event = await timeline.getEventById(eventId); final event = await timeline.getEventById(eventId);
@ -399,10 +399,11 @@ class _ChatState extends State<_Chat> {
eventIndex = eventIndex =
getFilteredEvents().indexWhere((e) => e.eventId == eventId); getFilteredEvents().indexWhere((e) => e.eventId == eventId);
} }
} finally { });
if (context != null) { if (context != null) {
Navigator.of(context)?.pop(); await SimpleDialogs(context).tryRequestWithLoadingDialog(task);
} } else {
await task;
} }
} }
await _scrollController.scrollToIndex(eventIndex, await _scrollController.scrollToIndex(eventIndex,

View File

@ -1,3 +1,4 @@
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:flushbar/flushbar_helper.dart'; import 'package:flushbar/flushbar_helper.dart';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:famedlysdk/matrix_api.dart'; import 'package:famedlysdk/matrix_api.dart';
@ -36,16 +37,22 @@ class ChatDetails extends StatefulWidget {
class _ChatDetailsState extends State<ChatDetails> { class _ChatDetailsState extends State<ChatDetails> {
List<User> members; List<User> members;
void setDisplaynameAction(BuildContext context) async { void setDisplaynameAction(BuildContext context) async {
var enterText = SimpleDialogs(context).enterText( final input = await showTextInputDialog(
titleText: L10n.of(context).changeTheNameOfTheGroup, context: context,
labelText: L10n.of(context).changeTheNameOfTheGroup, title: L10n.of(context).changeTheNameOfTheGroup,
hintText: textFields: [
widget.room.getLocalizedDisplayname(MatrixLocals(L10n.of(context))), DialogTextField(
initialText: widget.room.getLocalizedDisplayname(
MatrixLocals(
L10n.of(context),
),
),
)
],
); );
final displayname = await enterText; if (input == null) return;
if (displayname == null) return;
final success = await SimpleDialogs(context).tryRequestWithLoadingDialog( final success = await SimpleDialogs(context).tryRequestWithLoadingDialog(
widget.room.setName(displayname), widget.room.setName(input.single),
); );
if (success != false) { if (success != false) {
await FlushbarHelper.createSuccess( await FlushbarHelper.createSuccess(
@ -55,16 +62,19 @@ class _ChatDetailsState extends State<ChatDetails> {
} }
void setCanonicalAliasAction(context) async { void setCanonicalAliasAction(context) async {
final s = await SimpleDialogs(context).enterText( final input = await showTextInputDialog(
titleText: L10n.of(context).setInvitationLink, context: context,
labelText: L10n.of(context).setInvitationLink, title: L10n.of(context).setInvitationLink,
hintText: L10n.of(context).alias.toLowerCase(), textFields: [
prefixText: '#', DialogTextField(
suffixText: ':' + widget.room.client.userID.domain, hintText: '#localpart:domain',
initialText: L10n.of(context).alias.toLowerCase(),
)
],
); );
if (s == null) return; if (input == null) return;
final domain = widget.room.client.userID.domain; final domain = widget.room.client.userID.domain;
final canonicalAlias = '%23' + s + '%3A' + domain; final canonicalAlias = '%23' + input.single + '%3A' + domain;
final aliasEvent = widget.room.getState('m.room.aliases', domain); final aliasEvent = widget.room.getState('m.room.aliases', domain);
final aliases = final aliases =
aliasEvent != null ? aliasEvent.content['aliases'] ?? [] : []; aliasEvent != null ? aliasEvent.content['aliases'] ?? [] : [];
@ -84,23 +94,25 @@ class _ChatDetailsState extends State<ChatDetails> {
} }
await SimpleDialogs(context).tryRequestWithLoadingDialog( await SimpleDialogs(context).tryRequestWithLoadingDialog(
widget.room.client.sendState(widget.room.id, 'm.room.canonical_alias', { widget.room.client.sendState(widget.room.id, 'm.room.canonical_alias', {
'alias': '#$s:$domain', 'alias': input.single,
}), }),
); );
} }
void setTopicAction(BuildContext context) async { void setTopicAction(BuildContext context) async {
final displayname = await SimpleDialogs(context).enterText( final input = await showTextInputDialog(
titleText: L10n.of(context).setGroupDescription, context: context,
labelText: L10n.of(context).setGroupDescription, title: L10n.of(context).setGroupDescription,
hintText: (widget.room.topic?.isNotEmpty ?? false) textFields: [
? widget.room.topic DialogTextField(
: L10n.of(context).addGroupDescription, hintText: L10n.of(context).setGroupDescription,
multiLine: true, initialText: widget.room.topic,
)
],
); );
if (displayname == null) return; if (input == null) return;
final success = await SimpleDialogs(context).tryRequestWithLoadingDialog( final success = await SimpleDialogs(context).tryRequestWithLoadingDialog(
widget.room.setDescription(displayname), widget.room.setDescription(input.single),
); );
if (success != false) { if (success != false) {
await FlushbarHelper.createSuccess( await FlushbarHelper.createSuccess(

View File

@ -1,3 +1,4 @@
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:famedlysdk/encryption.dart'; import 'package:famedlysdk/encryption.dart';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/components/adaptive_page_layout.dart'; import 'package:fluffychat/components/adaptive_page_layout.dart';
@ -8,8 +9,6 @@ import 'package:fluffychat/views/chat_list.dart';
import 'package:flushbar/flushbar_helper.dart'; import 'package:flushbar/flushbar_helper.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import '../components/dialogs/simple_dialogs.dart';
import '../utils/app_route.dart'; import '../utils/app_route.dart';
import 'key_verification.dart'; import 'key_verification.dart';
@ -64,10 +63,12 @@ class _ChatEncryptionSettingsState extends State<ChatEncryptionSettings> {
); );
break; break;
case 'verify_manual': case 'verify_manual':
if (await SimpleDialogs(context).askConfirmation( if (await showOkCancelAlertDialog(
titleText: L10n.of(context).isDeviceKeyCorrect, context: context,
contentText: key.ed25519Key.beautified, title: L10n.of(context).isDeviceKeyCorrect,
)) { message: key.ed25519Key.beautified,
) ==
OkCancelResult.ok) {
await unblock(); await unblock();
await key.setVerified(true); await key.setVerified(true);
setState(() => null); setState(() => null);

View File

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:famedlysdk/matrix_api.dart'; import 'package:famedlysdk/matrix_api.dart';
import 'package:fluffychat/components/connection_status_header.dart'; import 'package:fluffychat/components/connection_status_header.dart';
@ -197,19 +198,22 @@ class _ChatListState extends State<ChatList> {
void _setStatus(BuildContext context) async { void _setStatus(BuildContext context) async {
Navigator.of(context).pop(); Navigator.of(context).pop();
final statusMsg = await SimpleDialogs(context).enterText( final input = await showTextInputDialog(
titleText: L10n.of(context).setStatus, title: L10n.of(context).setStatus,
labelText: L10n.of(context).setStatus, context: context,
textFields: [
DialogTextField(
hintText: L10n.of(context).statusExampleMessage, hintText: L10n.of(context).statusExampleMessage,
multiLine: true, )
],
); );
if (statusMsg?.isEmpty ?? true) return; if (input == null || input.single.isEmpty) return;
final client = Matrix.of(context).client; final client = Matrix.of(context).client;
await SimpleDialogs(context).tryRequestWithLoadingDialog( await SimpleDialogs(context).tryRequestWithLoadingDialog(
client.sendPresence( client.sendPresence(
client.userID, client.userID,
PresenceType.online, PresenceType.online,
statusMsg: statusMsg, statusMsg: input.single,
), ),
); );
return; return;
@ -242,7 +246,11 @@ class _ChatListState extends State<ChatList> {
} }
Future<void> _archiveAction(BuildContext context) async { Future<void> _archiveAction(BuildContext context) async {
final confirmed = await SimpleDialogs(context).askConfirmation(); final confirmed = await showOkCancelAlertDialog(
context: context,
title: L10n.of(context).areYouSure,
) ==
OkCancelResult.ok;
if (!confirmed) return; if (!confirmed) return;
await SimpleDialogs(context) await SimpleDialogs(context)
.tryRequestWithLoadingDialog(_archiveSelectedRooms(context)); .tryRequestWithLoadingDialog(_archiveSelectedRooms(context));

View File

@ -1,5 +1,6 @@
import 'dart:math'; import 'dart:math';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/components/dialogs/simple_dialogs.dart'; import 'package:fluffychat/components/dialogs/simple_dialogs.dart';
import 'package:fluffychat/components/matrix.dart'; import 'package:fluffychat/components/matrix.dart';
@ -13,13 +14,17 @@ import 'package:url_launcher/url_launcher.dart';
class HomeserverPicker extends StatelessWidget { class HomeserverPicker extends StatelessWidget {
Future<void> _setHomeserverAction(BuildContext context) async { Future<void> _setHomeserverAction(BuildContext context) async {
final homeserver = await SimpleDialogs(context).enterText( final homeserver = await showTextInputDialog(
titleText: L10n.of(context).enterYourHomeserver, title: L10n.of(context).enterYourHomeserver,
context: context,
textFields: [
DialogTextField(
hintText: AppConfig.defaultHomeserver, hintText: AppConfig.defaultHomeserver,
prefixText: 'https://', )
keyboardType: TextInputType.url); ],
if (homeserver?.isEmpty ?? true) return; );
_checkHomeserverAction(homeserver, context); if (homeserver?.single?.isEmpty ?? true) return;
_checkHomeserverAction(homeserver.single, context);
} }
void _checkHomeserverAction(String homeserver, BuildContext context) async { void _checkHomeserverAction(String homeserver, BuildContext context) async {

View File

@ -1,3 +1,4 @@
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:famedlysdk/encryption.dart'; import 'package:famedlysdk/encryption.dart';
import 'package:famedlysdk/matrix_api.dart'; import 'package:famedlysdk/matrix_api.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -77,7 +78,8 @@ class _KeyVerificationPageState extends State<KeyVerificationPage> {
if (input == null) { if (input == null) {
return; return;
} }
SimpleDialogs(context).showLoadingDialog(context); final valid = await SimpleDialogs(context)
.tryRequestWithLoadingDialog(Future.microtask(() async {
// make sure the loading spinner shows before we test the keys // make sure the loading spinner shows before we test the keys
await Future.delayed(Duration(milliseconds: 100)); await Future.delayed(Duration(milliseconds: 100));
var valid = false; var valid = false;
@ -92,10 +94,12 @@ class _KeyVerificationPageState extends State<KeyVerificationPage> {
valid = false; valid = false;
} }
} }
await Navigator.of(context)?.pop(); return valid;
if (!valid) { }));
await SimpleDialogs(context).inform( if (valid == false) {
contentText: L10n.of(context).incorrectPassphraseOrKey, await showOkAlertDialog(
context: context,
message: L10n.of(context).incorrectPassphraseOrKey,
); );
} }
}; };

View File

@ -1,5 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:flushbar/flushbar_helper.dart'; import 'package:flushbar/flushbar_helper.dart';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:file_picker_cross/file_picker_cross.dart'; import 'package:file_picker_cross/file_picker_cross.dart';
@ -52,7 +53,11 @@ class _SettingsState extends State<Settings> {
bool megolmBackupCached; bool megolmBackupCached;
void logoutAction(BuildContext context) async { void logoutAction(BuildContext context) async {
if (await SimpleDialogs(context).askConfirmation() == false) { if (await showOkCancelAlertDialog(
context: context,
title: L10n.of(context).areYouSure,
) ==
OkCancelResult.cancel) {
return; return;
} }
var matrix = Matrix.of(context); var matrix = Matrix.of(context);
@ -61,20 +66,25 @@ class _SettingsState extends State<Settings> {
} }
void _changePasswordAccountAction(BuildContext context) async { void _changePasswordAccountAction(BuildContext context) async {
final oldPassword = await SimpleDialogs(context).enterText( final input = await showTextInputDialog(
password: true, context: context,
titleText: L10n.of(context).pleaseEnterYourPassword, title: L10n.of(context).changePassword,
textFields: [
DialogTextField(
hintText: L10n.of(context).pleaseEnterYourPassword,
obscureText: true,
),
DialogTextField(
hintText: L10n.of(context).chooseAStrongPassword,
obscureText: true,
),
],
); );
if (oldPassword == null) return; if (input == null) return;
final newPassword = await SimpleDialogs(context).enterText(
password: true,
titleText: L10n.of(context).chooseAStrongPassword,
);
if (newPassword == null) return;
await SimpleDialogs(context).tryRequestWithLoadingDialog( await SimpleDialogs(context).tryRequestWithLoadingDialog(
Matrix.of(context) Matrix.of(context)
.client .client
.changePassword(newPassword, oldPassword: oldPassword), .changePassword(input.last, oldPassword: input.first),
); );
await FlushbarHelper.createSuccess( await FlushbarHelper.createSuccess(
message: L10n.of(context).passwordHasBeenChanged) message: L10n.of(context).passwordHasBeenChanged)
@ -82,39 +92,44 @@ class _SettingsState extends State<Settings> {
} }
void _deleteAccountAction(BuildContext context) async { void _deleteAccountAction(BuildContext context) async {
if (await SimpleDialogs(context).askConfirmation( if (await showOkCancelAlertDialog(
titleText: L10n.of(context).warning, context: context,
contentText: L10n.of(context).deactivateAccountWarning, title: L10n.of(context).warning,
dangerous: true, message: L10n.of(context).deactivateAccountWarning,
) == ) ==
false) { OkCancelResult.cancel) {
return; return;
} }
if (await SimpleDialogs(context).askConfirmation(dangerous: true) == if (await showOkCancelAlertDialog(
false) { context: context, title: L10n.of(context).areYouSure) ==
OkCancelResult.cancel) {
return; return;
} }
final password = await SimpleDialogs(context).enterText( final input = await showTextInputDialog(
password: true, context: context,
titleText: L10n.of(context).pleaseEnterYourPassword, title: L10n.of(context).pleaseEnterYourPassword,
textFields: [DialogTextField(obscureText: true, hintText: '******')],
); );
if (password == null) return; if (input == null) return;
await SimpleDialogs(context).tryRequestWithLoadingDialog( await SimpleDialogs(context).tryRequestWithLoadingDialog(
Matrix.of(context).client.deactivateAccount(auth: { Matrix.of(context).client.deactivateAccount(auth: {
'type': 'm.login.password', 'type': 'm.login.password',
'user': Matrix.of(context).client.userID, 'user': Matrix.of(context).client.userID,
'password': password, 'password': input.single,
}), }),
); );
} }
void setJitsiInstanceAction(BuildContext context) async { void setJitsiInstanceAction(BuildContext context) async {
var jitsi = await SimpleDialogs(context).enterText( var input = await showTextInputDialog(
titleText: L10n.of(context).editJitsiInstance, context: context,
hintText: Matrix.of(context).jitsiInstance, title: L10n.of(context).editJitsiInstance,
labelText: L10n.of(context).editJitsiInstance, textFields: [
DialogTextField(initialText: Matrix.of(context).jitsiInstance),
],
); );
if (jitsi == null) return; if (input == null) return;
var jitsi = input.single;
if (!jitsi.endsWith('/')) { if (!jitsi.endsWith('/')) {
jitsi += '/'; jitsi += '/';
} }
@ -124,16 +139,20 @@ class _SettingsState extends State<Settings> {
} }
void setDisplaynameAction(BuildContext context) async { void setDisplaynameAction(BuildContext context) async {
final displayname = await SimpleDialogs(context).enterText( final input = await showTextInputDialog(
titleText: L10n.of(context).editDisplayname, context: context,
hintText: title: L10n.of(context).editDisplayname,
profile?.displayname ?? Matrix.of(context).client.userID.localpart, textFields: [
labelText: L10n.of(context).enterAUsername, DialogTextField(
initialText: profile?.displayname ??
Matrix.of(context).client.userID.localpart,
)
],
); );
if (displayname == null) return; if (input == null) return;
final matrix = Matrix.of(context); final matrix = Matrix.of(context);
final success = await SimpleDialogs(context).tryRequestWithLoadingDialog( final success = await SimpleDialogs(context).tryRequestWithLoadingDialog(
matrix.client.setDisplayname(matrix.client.userID, displayname), matrix.client.setDisplayname(matrix.client.userID, input.single),
); );
if (success != false) { if (success != false) {
setState(() { setState(() {
@ -195,24 +214,28 @@ class _SettingsState extends State<Settings> {
Future<void> requestSSSSCache(BuildContext context) async { Future<void> requestSSSSCache(BuildContext context) async {
final handle = Matrix.of(context).client.encryption.ssss.open(); final handle = Matrix.of(context).client.encryption.ssss.open();
final str = await SimpleDialogs(context).enterText( final input = await showTextInputDialog(
titleText: L10n.of(context).askSSSSCache, context: context,
hintText: L10n.of(context).passphraseOrKey, title: L10n.of(context).askSSSSCache,
password: true, textFields: [
DialogTextField(
hintText: L10n.of(context).passphraseOrKey, obscureText: true)
],
); );
if (str != null) { if (input != null) {
SimpleDialogs(context).showLoadingDialog(context); final valid = await SimpleDialogs(context)
.tryRequestWithLoadingDialog(Future.microtask(() async {
// make sure the loading spinner shows before we test the keys // make sure the loading spinner shows before we test the keys
await Future.delayed(Duration(milliseconds: 100)); await Future.delayed(Duration(milliseconds: 100));
var valid = false; var valid = false;
try { try {
handle.unlock(recoveryKey: str); handle.unlock(recoveryKey: input.single);
valid = true; valid = true;
} catch (e, s) { } catch (e, s) {
debugPrint('Couldn\'t use recovery key: ' + e.toString()); debugPrint('Couldn\'t use recovery key: ' + e.toString());
debugPrint(s.toString()); debugPrint(s.toString());
try { try {
handle.unlock(passphrase: str); handle.unlock(passphrase: input.single);
valid = true; valid = true;
} catch (e, s) { } catch (e, s) {
debugPrint('Couldn\'t use recovery passphrase: ' + e.toString()); debugPrint('Couldn\'t use recovery passphrase: ' + e.toString());
@ -220,11 +243,14 @@ class _SettingsState extends State<Settings> {
valid = false; valid = false;
} }
} }
await Navigator.of(context)?.pop(); return valid;
if (valid) { }));
if (valid == true) {
await handle.maybeCacheAll(); await handle.maybeCacheAll();
await SimpleDialogs(context).inform( await showOkAlertDialog(
contentText: L10n.of(context).cachedKeys, context: context,
message: L10n.of(context).cachedKeys,
); );
setState(() { setState(() {
crossSigningCachedFuture = null; crossSigningCachedFuture = null;
@ -233,8 +259,9 @@ class _SettingsState extends State<Settings> {
megolmBackupCached = null; megolmBackupCached = null;
}); });
} else { } else {
await SimpleDialogs(context).inform( await showOkAlertDialog(
contentText: L10n.of(context).incorrectPassphraseOrKey, context: context,
message: L10n.of(context).incorrectPassphraseOrKey,
); );
} }
} }
@ -452,7 +479,7 @@ class _SettingsState extends State<Settings> {
ListTile( ListTile(
trailing: Icon(Icons.vpn_key), trailing: Icon(Icons.vpn_key),
title: Text( title: Text(
'Change password', L10n.of(context).changePassword,
), ),
onTap: () => _changePasswordAccountAction(context), onTap: () => _changePasswordAccountAction(context),
), ),
@ -497,39 +524,48 @@ class _SettingsState extends State<Settings> {
: null, : null,
onTap: () async { onTap: () async {
if (!client.encryption.crossSigning.enabled) { if (!client.encryption.crossSigning.enabled) {
await SimpleDialogs(context).inform( await showOkAlertDialog(
contentText: L10n.of(context).noCrossSignBootstrap, context: context,
message: L10n.of(context).noCrossSignBootstrap,
); );
return; return;
} }
if (client.isUnknownSession) { if (client.isUnknownSession) {
final str = await SimpleDialogs(context).enterText( final input = await showTextInputDialog(
titleText: L10n.of(context).askSSSSVerify, context: context,
title: L10n.of(context).askSSSSVerify,
textFields: [
DialogTextField(
hintText: L10n.of(context).passphraseOrKey, hintText: L10n.of(context).passphraseOrKey,
password: true, obscureText: true)
],
); );
if (str != null) { if (input != null) {
SimpleDialogs(context).showLoadingDialog(context); final valid = await SimpleDialogs(context)
.tryRequestWithLoadingDialog(Future.microtask(() async {
// make sure the loading spinner shows before we test the keys // make sure the loading spinner shows before we test the keys
await Future.delayed(Duration(milliseconds: 100)); await Future.delayed(Duration(milliseconds: 100));
var valid = false; var valid = false;
try { try {
await client.encryption.crossSigning await client.encryption.crossSigning
.selfSign(recoveryKey: str); .selfSign(recoveryKey: input.single);
valid = true; valid = true;
} catch (_) { } catch (_) {
try { try {
await client.encryption.crossSigning await client.encryption.crossSigning
.selfSign(passphrase: str); .selfSign(passphrase: input.single);
valid = true; valid = true;
} catch (_) { } catch (_) {
valid = false; valid = false;
} }
} }
await Navigator.of(context)?.pop(); return valid;
if (valid) { }));
await SimpleDialogs(context).inform(
contentText: L10n.of(context).verifiedSession, if (valid == true) {
await showOkAlertDialog(
context: context,
message: L10n.of(context).verifiedSession,
); );
setState(() { setState(() {
crossSigningCachedFuture = null; crossSigningCachedFuture = null;
@ -538,8 +574,9 @@ class _SettingsState extends State<Settings> {
megolmBackupCached = null; megolmBackupCached = null;
}); });
} else { } else {
await SimpleDialogs(context).inform( await showOkAlertDialog(
contentText: L10n.of(context).incorrectPassphraseOrKey, context: context,
message: L10n.of(context).incorrectPassphraseOrKey,
); );
} }
} }
@ -563,8 +600,9 @@ class _SettingsState extends State<Settings> {
: null, : null,
onTap: () async { onTap: () async {
if (!client.encryption.keyManager.enabled) { if (!client.encryption.keyManager.enabled) {
await SimpleDialogs(context).inform( await showOkAlertDialog(
contentText: L10n.of(context).noMegolmBootstrap, context: context,
message: L10n.of(context).noMegolmBootstrap,
); );
return; return;
} }

View File

@ -1,3 +1,4 @@
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/components/dialogs/simple_dialogs.dart'; import 'package:fluffychat/components/dialogs/simple_dialogs.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -35,37 +36,51 @@ class DevicesSettingsState extends State<DevicesSettings> {
void reload() => setState(() => devices = null); void reload() => setState(() => devices = null);
void _removeDevicesAction(BuildContext context, List<Device> devices) async { void _removeDevicesAction(BuildContext context, List<Device> devices) async {
if (await SimpleDialogs(context).askConfirmation() == false) return; if (await showOkCancelAlertDialog(
context: context,
title: L10n.of(context).areYouSure,
) ==
OkCancelResult.cancel) return;
var matrix = Matrix.of(context); var matrix = Matrix.of(context);
var deviceIds = <String>[]; var deviceIds = <String>[];
for (var userDevice in devices) { for (var userDevice in devices) {
deviceIds.add(userDevice.deviceId); deviceIds.add(userDevice.deviceId);
} }
final password = await SimpleDialogs(context).enterText( final password = await showTextInputDialog(
titleText: L10n.of(context).pleaseEnterYourPassword, title: L10n.of(context).pleaseEnterYourPassword,
labelText: L10n.of(context).pleaseEnterYourPassword, context: context,
textFields: [
DialogTextField(
hintText: '******', hintText: '******',
password: true); obscureText: true,
)
],
);
if (password == null) return; if (password == null) return;
final success = await SimpleDialogs(context).tryRequestWithLoadingDialog( final success = await SimpleDialogs(context).tryRequestWithLoadingDialog(
matrix.client.deleteDevices(deviceIds, matrix.client.deleteDevices(deviceIds,
auth: matrix.getAuthByPassword(password))); auth: matrix.getAuthByPassword(password.single)));
if (success != false) { if (success != false) {
reload(); reload();
} }
} }
void _renameDeviceAction(BuildContext context, Device device) async { void _renameDeviceAction(BuildContext context, Device device) async {
final displayName = await SimpleDialogs(context).enterText( final displayName = await showTextInputDialog(
context: context,
title: L10n.of(context).changeDeviceName,
textFields: [
DialogTextField(
hintText: device.displayName, hintText: device.displayName,
labelText: L10n.of(context).changeDeviceName, )
],
); );
if (displayName == null) return; if (displayName == null) return;
final success = await SimpleDialogs(context).tryRequestWithLoadingDialog( final success = await SimpleDialogs(context).tryRequestWithLoadingDialog(
Matrix.of(context) Matrix.of(context)
.client .client
.setDeviceMetadata(device.deviceId, displayName: displayName), .setDeviceMetadata(device.deviceId, displayName: displayName.single),
); );
if (success != false) { if (success != false) {
reload(); reload();

View File

@ -1,3 +1,4 @@
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:flushbar/flushbar_helper.dart'; import 'package:flushbar/flushbar_helper.dart';
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
@ -247,9 +248,9 @@ class _EmotesSettingsState extends State<EmotesSettings> {
newEmoteController.text.isEmpty || newEmoteController.text.isEmpty ||
newMxcController.text == null || newMxcController.text == null ||
newMxcController.text.isEmpty) { newMxcController.text.isEmpty) {
await SimpleDialogs(context).inform( await showOkAlertDialog(
contentText: context: context,
L10n.of(context).emoteWarnNeedToPick); message: L10n.of(context).emoteWarnNeedToPick);
return; return;
} }
final emoteCode = ':${newEmoteController.text}:'; final emoteCode = ':${newEmoteController.text}:';
@ -257,13 +258,15 @@ class _EmotesSettingsState extends State<EmotesSettings> {
if (emotes.indexWhere((e) => if (emotes.indexWhere((e) =>
e.emote == emoteCode && e.mxc != mxc) != e.emote == emoteCode && e.mxc != mxc) !=
-1) { -1) {
await SimpleDialogs(context).inform( await showOkAlertDialog(
contentText: L10n.of(context).emoteExists); context: context,
message: L10n.of(context).emoteExists);
return; return;
} }
if (!RegExp(r'^:[-\w]+:$').hasMatch(emoteCode)) { if (!RegExp(r'^:[-\w]+:$').hasMatch(emoteCode)) {
await SimpleDialogs(context).inform( await showOkAlertDialog(
contentText: L10n.of(context).emoteInvalid); context: context,
message: L10n.of(context).emoteInvalid);
return; return;
} }
emotes.add(_EmoteEntry(emote: emoteCode, mxc: mxc)); emotes.add(_EmoteEntry(emote: emoteCode, mxc: mxc));
@ -357,16 +360,18 @@ class _EmotesSettingsState extends State<EmotesSettings> {
e.mxc != emote.mxc) != e.mxc != emote.mxc) !=
-1) { -1) {
controller.text = emote.emoteClean; controller.text = emote.emoteClean;
SimpleDialogs(context).inform( showOkAlertDialog(
contentText: context: context,
message:
L10n.of(context).emoteExists); L10n.of(context).emoteExists);
return; return;
} }
if (!RegExp(r'^:[-\w]+:$') if (!RegExp(r'^:[-\w]+:$')
.hasMatch(emoteCode)) { .hasMatch(emoteCode)) {
controller.text = emote.emoteClean; controller.text = emote.emoteClean;
SimpleDialogs(context).inform( showOkAlertDialog(
contentText: context: context,
message:
L10n.of(context).emoteInvalid); L10n.of(context).emoteInvalid);
return; return;
} }

View File

@ -8,6 +8,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "7.0.0" version: "7.0.0"
adaptive_dialog:
dependency: "direct main"
description:
name: adaptive_dialog
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.0+1"
analyzer: analyzer:
dependency: transitive dependency: transitive
description: description:
@ -15,6 +22,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.39.17" version: "0.39.17"
animations:
dependency: transitive
description:
name: animations
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.2"
ansicolor: ansicolor:
dependency: transitive dependency: transitive
description: description:

View File

@ -49,6 +49,7 @@ dependencies:
open_file: ^3.0.3 open_file: ^3.0.3
mime_type: ^0.3.2 mime_type: ^0.3.2
flushbar: ^1.10.4 flushbar: ^1.10.4
adaptive_dialog: ^0.9.0+1
flutter_matrix_html: ^0.1.10 flutter_matrix_html: ^0.1.10
moor: ^3.4.0 moor: ^3.4.0
sqlite3_flutter_libs: ^0.2.0 sqlite3_flutter_libs: ^0.2.0