Merge branch 'krille/migrate-to-nullsafety' into 'main'

refactor: Migrate to null safety

See merge request famedly/fluffychat!701
This commit is contained in:
Krille Fear 2022-02-01 07:58:27 +00:00
commit ff6f67e1b2
164 changed files with 1761 additions and 1905 deletions

View File

@ -1,5 +1,3 @@
//@dart=2.12
import 'dart:ui'; import 'dart:ui';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';

View File

@ -1,5 +1,3 @@
//@dart=2.12
abstract class AppEmojis { abstract class AppEmojis {
static const List<String> emojis = [ static const List<String> emojis = [
'👍', '👍',

View File

@ -1,5 +1,3 @@
//@dart=2.12
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:vrouter/vrouter.dart'; import 'package:vrouter/vrouter.dart';

View File

@ -1,5 +1,3 @@
//@dart=2.12
abstract class SettingKeys { abstract class SettingKeys {
static const String jitsiInstance = 'chat.fluffy.jitsi_instance'; static const String jitsiInstance = 'chat.fluffy.jitsi_instance';
static const String wallpaper = 'chat.fluffy.wallpaper'; static const String wallpaper = 'chat.fluffy.wallpaper';

View File

@ -1,5 +1,3 @@
//@dart=2.12
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';

View File

@ -1,5 +1,3 @@
// @dart=2.11
import 'dart:async'; import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -67,14 +65,14 @@ void main() async {
} }
class FluffyChatApp extends StatefulWidget { class FluffyChatApp extends StatefulWidget {
final Widget testWidget; final Widget? testWidget;
final List<Client> clients; final List<Client> clients;
final Map<String, String> queryParameters; final Map<String, String>? queryParameters;
const FluffyChatApp({ const FluffyChatApp({
Key key, Key? key,
this.testWidget, this.testWidget,
@required this.clients, required this.clients,
this.queryParameters, this.queryParameters,
}) : super(key: key); }) : super(key: key);
@ -88,9 +86,9 @@ class FluffyChatApp extends StatefulWidget {
} }
class _FluffyChatAppState extends State<FluffyChatApp> { class _FluffyChatAppState extends State<FluffyChatApp> {
GlobalKey<VRouterState> _router; GlobalKey<VRouterState>? _router;
bool columnMode; bool? columnMode;
String _initialUrl; String? _initialUrl;
@override @override
void initState() { void initState() {
@ -133,15 +131,12 @@ class _FluffyChatAppState extends State<FluffyChatApp> {
localizationsDelegates: L10n.localizationsDelegates, localizationsDelegates: L10n.localizationsDelegates,
supportedLocales: L10n.supportedLocales, supportedLocales: L10n.supportedLocales,
initialUrl: _initialUrl ?? '/', initialUrl: _initialUrl ?? '/',
locale: kIsWeb
? Locale(html.window.navigator.language.split('-').first)
: null,
routes: AppRoutes(columnMode ?? false).routes, routes: AppRoutes(columnMode ?? false).routes,
builder: (context, child) { builder: (context, child) {
LoadingDialog.defaultTitle = L10n.of(context).loadingPleaseWait; LoadingDialog.defaultTitle = L10n.of(context)!.loadingPleaseWait;
LoadingDialog.defaultBackLabel = L10n.of(context).close; LoadingDialog.defaultBackLabel = L10n.of(context)!.close;
LoadingDialog.defaultOnError = LoadingDialog.defaultOnError =
(e) => (e as Object).toLocalizedString(context); (e) => (e as Object?)!.toLocalizedString(context);
WidgetsBinding.instance?.addPostFrameCallback((_) { WidgetsBinding.instance?.addPostFrameCallback((_) {
SystemChrome.setSystemUIOverlayStyle( SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle( SystemUiOverlayStyle(

View File

@ -1,5 +1,3 @@
//@dart=2.12
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View File

@ -1,5 +1,3 @@
//@dart=2.12
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';

View File

@ -1,5 +1,3 @@
//@dart=2.12
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:adaptive_dialog/adaptive_dialog.dart';

View File

@ -1,5 +1,3 @@
//@dart=2.12
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:adaptive_dialog/adaptive_dialog.dart';

View File

@ -1,5 +1,3 @@
//@dart=2.12
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';

View File

@ -18,12 +18,12 @@ class BootstrapDialog extends StatefulWidget {
final bool wipe; final bool wipe;
final Client client; final Client client;
const BootstrapDialog({ const BootstrapDialog({
Key key, Key? key,
this.wipe = false, this.wipe = false,
@required this.client, required this.client,
}) : super(key: key); }) : super(key: key);
Future<bool> show(BuildContext context) => PlatformInfos.isCupertinoStyle Future<bool?> show(BuildContext context) => PlatformInfos.isCupertinoStyle
? showCupertinoDialog( ? showCupertinoDialog(
context: context, context: context,
builder: (context) => this, builder: (context) => this,
@ -45,18 +45,18 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
final TextEditingController _recoveryKeyTextEditingController = final TextEditingController _recoveryKeyTextEditingController =
TextEditingController(); TextEditingController();
Bootstrap bootstrap; late Bootstrap bootstrap;
String _recoveryKeyInputError; String? _recoveryKeyInputError;
bool _recoveryKeyInputLoading = false; bool _recoveryKeyInputLoading = false;
String titleText; String? titleText;
bool _recoveryKeyStored = false; bool _recoveryKeyStored = false;
bool _recoveryKeyCopied = false; bool _recoveryKeyCopied = false;
bool _wipe; bool? _wipe;
@override @override
void initState() { void initState() {
@ -68,8 +68,8 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
_wipe = wipe; _wipe = wipe;
titleText = null; titleText = null;
_recoveryKeyStored = false; _recoveryKeyStored = false;
bootstrap = widget.client.encryption bootstrap =
.bootstrap(onUpdate: () => setState(() => null)); widget.client.encryption!.bootstrap(onUpdate: () => setState(() {}));
} }
@override @override
@ -79,12 +79,12 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
Widget body = PlatformInfos.isCupertinoStyle Widget body = PlatformInfos.isCupertinoStyle
? const CupertinoActivityIndicator() ? const CupertinoActivityIndicator()
: const LinearProgressIndicator(); : const LinearProgressIndicator();
titleText = L10n.of(context).loadingPleaseWait; titleText = L10n.of(context)!.loadingPleaseWait;
if (bootstrap.newSsssKey?.recoveryKey != null && if (bootstrap.newSsssKey?.recoveryKey != null &&
_recoveryKeyStored == false) { _recoveryKeyStored == false) {
final key = bootstrap.newSsssKey.recoveryKey; final key = bootstrap.newSsssKey!.recoveryKey;
titleText = L10n.of(context).securityKey; titleText = L10n.of(context)!.securityKey;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
centerTitle: true, centerTitle: true,
@ -92,7 +92,7 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
icon: const Icon(Icons.close), icon: const Icon(Icons.close),
onPressed: Navigator.of(context).pop, onPressed: Navigator.of(context).pop,
), ),
title: Text(L10n.of(context).securityKey), title: Text(L10n.of(context)!.securityKey),
), ),
body: Center( body: Center(
child: ConstrainedBox( child: ConstrainedBox(
@ -102,7 +102,7 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
children: [ children: [
Text( Text(
L10n.of(context).chatBackupDescription, L10n.of(context)!.chatBackupDescription,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle( style: const TextStyle(
fontSize: 16, fontSize: 16,
@ -119,9 +119,9 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
const SizedBox(height: 16), const SizedBox(height: 16),
ElevatedButton.icon( ElevatedButton.icon(
icon: const Icon(Icons.save_alt_outlined), icon: const Icon(Icons.save_alt_outlined),
label: Text(L10n.of(context).saveTheSecurityKeyNow), label: Text(L10n.of(context)!.saveTheSecurityKeyNow),
onPressed: () { onPressed: () {
Share.share(key); Share.share(key!);
setState(() => _recoveryKeyCopied = true); setState(() => _recoveryKeyCopied = true);
}, },
), ),
@ -132,7 +132,7 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
onPrimary: Theme.of(context).primaryColor, onPrimary: Theme.of(context).primaryColor,
), ),
icon: const Icon(Icons.check_outlined), icon: const Icon(Icons.check_outlined),
label: Text(L10n.of(context).next), label: Text(L10n.of(context)!.next),
onPressed: _recoveryKeyCopied onPressed: _recoveryKeyCopied
? () => setState(() => _recoveryKeyStored = true) ? () => setState(() => _recoveryKeyStored = true)
: null, : null,
@ -147,27 +147,27 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
case BootstrapState.loading: case BootstrapState.loading:
break; break;
case BootstrapState.askWipeSsss: case BootstrapState.askWipeSsss:
WidgetsBinding.instance.addPostFrameCallback( WidgetsBinding.instance!.addPostFrameCallback(
(_) => bootstrap.wipeSsss(_wipe), (_) => bootstrap.wipeSsss(_wipe!),
); );
break; break;
case BootstrapState.askBadSsss: case BootstrapState.askBadSsss:
WidgetsBinding.instance.addPostFrameCallback( WidgetsBinding.instance!.addPostFrameCallback(
(_) => bootstrap.ignoreBadSecrets(true), (_) => bootstrap.ignoreBadSecrets(true),
); );
break; break;
case BootstrapState.askUseExistingSsss: case BootstrapState.askUseExistingSsss:
WidgetsBinding.instance.addPostFrameCallback( WidgetsBinding.instance!.addPostFrameCallback(
(_) => bootstrap.useExistingSsss(!_wipe), (_) => bootstrap.useExistingSsss(!_wipe!),
); );
break; break;
case BootstrapState.askUnlockSsss: case BootstrapState.askUnlockSsss:
WidgetsBinding.instance.addPostFrameCallback( WidgetsBinding.instance!.addPostFrameCallback(
(_) => bootstrap.unlockedSsss(), (_) => bootstrap.unlockedSsss(),
); );
break; break;
case BootstrapState.askNewSsss: case BootstrapState.askNewSsss:
WidgetsBinding.instance.addPostFrameCallback( WidgetsBinding.instance!.addPostFrameCallback(
(_) => bootstrap.newSsss(), (_) => bootstrap.newSsss(),
); );
break; break;
@ -180,7 +180,7 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
icon: const Icon(Icons.close), icon: const Icon(Icons.close),
onPressed: Navigator.of(context).pop, onPressed: Navigator.of(context).pop,
), ),
title: Text(L10n.of(context).pleaseEnterSecurityKey), title: Text(L10n.of(context)!.pleaseEnterSecurityKey),
), ),
body: Center( body: Center(
child: ConstrainedBox( child: ConstrainedBox(
@ -190,7 +190,7 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
children: [ children: [
Text( Text(
L10n.of(context).pleaseEnterSecurityKeyDescription, L10n.of(context)!.pleaseEnterSecurityKeyDescription,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle( style: const TextStyle(
fontSize: 16, fontSize: 16,
@ -209,7 +209,7 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
controller: _recoveryKeyTextEditingController, controller: _recoveryKeyTextEditingController,
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Abc123 Def456', hintText: 'Abc123 Def456',
labelText: L10n.of(context).securityKey, labelText: L10n.of(context)!.securityKey,
errorText: _recoveryKeyInputError, errorText: _recoveryKeyInputError,
), ),
), ),
@ -218,7 +218,7 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
icon: _recoveryKeyInputLoading icon: _recoveryKeyInputLoading
? const CircularProgressIndicator.adaptive() ? const CircularProgressIndicator.adaptive()
: const Icon(Icons.lock_open_outlined), : const Icon(Icons.lock_open_outlined),
label: Text(L10n.of(context).unlockChatBackup), label: Text(L10n.of(context)!.unlockChatBackup),
onPressed: _recoveryKeyInputLoading onPressed: _recoveryKeyInputLoading
? null ? null
: () async { : () async {
@ -229,11 +229,12 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
try { try {
final key = final key =
_recoveryKeyTextEditingController.text; _recoveryKeyTextEditingController.text;
await bootstrap.newSsssKey.unlock( await bootstrap.newSsssKey!.unlock(
keyOrPassphrase: key, keyOrPassphrase: key,
); );
Logs().d('SSSS unlocked'); Logs().d('SSSS unlocked');
await bootstrap.client.encryption.crossSigning await bootstrap
.client.encryption!.crossSigning
.selfSign( .selfSign(
keyOrPassphrase: key, keyOrPassphrase: key,
); );
@ -242,7 +243,7 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
} catch (e, s) { } catch (e, s) {
Logs().w('Unable to unlock SSSS', e, s); Logs().w('Unable to unlock SSSS', e, s);
setState(() => _recoveryKeyInputError = setState(() => _recoveryKeyInputError =
L10n.of(context).oopsSomethingWentWrong); L10n.of(context)!.oopsSomethingWentWrong);
} finally { } finally {
setState( setState(
() => _recoveryKeyInputLoading = false); () => _recoveryKeyInputLoading = false);
@ -253,7 +254,7 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
const Expanded(child: Divider()), const Expanded(child: Divider()),
Padding( Padding(
padding: const EdgeInsets.all(12.0), padding: const EdgeInsets.all(12.0),
child: Text(L10n.of(context).or), child: Text(L10n.of(context)!.or),
), ),
const Expanded(child: Divider()), const Expanded(child: Divider()),
]), ]),
@ -264,18 +265,18 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
onPrimary: Theme.of(context).primaryColor, onPrimary: Theme.of(context).primaryColor,
), ),
icon: const Icon(Icons.cast_connected_outlined), icon: const Icon(Icons.cast_connected_outlined),
label: Text(L10n.of(context).transferFromAnotherDevice), label: Text(L10n.of(context)!.transferFromAnotherDevice),
onPressed: _recoveryKeyInputLoading onPressed: _recoveryKeyInputLoading
? null ? null
: () async { : () async {
final req = await showFutureLoadingDialog( final req = await showFutureLoadingDialog(
context: context, context: context,
future: () => widget future: () => widget.client
.client.userDeviceKeys[widget.client.userID] .userDeviceKeys[widget.client.userID!]!
.startVerification(), .startVerification(),
); );
if (req.error != null) return; if (req.error != null) return;
await KeyVerificationDialog(request: req.result) await KeyVerificationDialog(request: req.result!)
.show(context); .show(context);
Navigator.of(context, rootNavigator: false).pop(); Navigator.of(context, rootNavigator: false).pop();
}, },
@ -287,7 +288,7 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
onPrimary: Colors.red, onPrimary: Colors.red,
), ),
icon: const Icon(Icons.delete_outlined), icon: const Icon(Icons.delete_outlined),
label: Text(L10n.of(context).securityKeyLost), label: Text(L10n.of(context)!.securityKeyLost),
onPressed: _recoveryKeyInputLoading onPressed: _recoveryKeyInputLoading
? null ? null
: () async { : () async {
@ -295,10 +296,10 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
await showOkCancelAlertDialog( await showOkCancelAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).securityKeyLost, title: L10n.of(context)!.securityKeyLost,
message: L10n.of(context).wipeChatBackup, message: L10n.of(context)!.wipeChatBackup,
okLabel: L10n.of(context).ok, okLabel: L10n.of(context)!.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context)!.cancel,
isDestructiveAction: true, isDestructiveAction: true,
)) { )) {
setState(() => _createBootstrap(true)); setState(() => _createBootstrap(true));
@ -311,12 +312,12 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
), ),
); );
case BootstrapState.askWipeCrossSigning: case BootstrapState.askWipeCrossSigning:
WidgetsBinding.instance.addPostFrameCallback( WidgetsBinding.instance!.addPostFrameCallback(
(_) => bootstrap.wipeCrossSigning(_wipe), (_) => bootstrap.wipeCrossSigning(_wipe!),
); );
break; break;
case BootstrapState.askSetupCrossSigning: case BootstrapState.askSetupCrossSigning:
WidgetsBinding.instance.addPostFrameCallback( WidgetsBinding.instance!.addPostFrameCallback(
(_) => bootstrap.askSetupCrossSigning( (_) => bootstrap.askSetupCrossSigning(
setupMasterKey: true, setupMasterKey: true,
setupSelfSigningKey: true, setupSelfSigningKey: true,
@ -325,36 +326,36 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
); );
break; break;
case BootstrapState.askWipeOnlineKeyBackup: case BootstrapState.askWipeOnlineKeyBackup:
WidgetsBinding.instance.addPostFrameCallback( WidgetsBinding.instance!.addPostFrameCallback(
(_) => bootstrap.wipeOnlineKeyBackup(_wipe), (_) => bootstrap.wipeOnlineKeyBackup(_wipe!),
); );
break; break;
case BootstrapState.askSetupOnlineKeyBackup: case BootstrapState.askSetupOnlineKeyBackup:
WidgetsBinding.instance.addPostFrameCallback( WidgetsBinding.instance!.addPostFrameCallback(
(_) => bootstrap.askSetupOnlineKeyBackup(true), (_) => bootstrap.askSetupOnlineKeyBackup(true),
); );
break; break;
case BootstrapState.error: case BootstrapState.error:
titleText = L10n.of(context).oopsSomethingWentWrong; titleText = L10n.of(context)!.oopsSomethingWentWrong;
body = const Icon(Icons.error_outline, color: Colors.red, size: 40); body = const Icon(Icons.error_outline, color: Colors.red, size: 40);
buttons.add(AdaptiveFlatButton( buttons.add(AdaptiveFlatButton(
label: L10n.of(context).close, label: L10n.of(context)!.close,
onPressed: () => onPressed: () =>
Navigator.of(context, rootNavigator: false).pop<bool>(false), Navigator.of(context, rootNavigator: false).pop<bool>(false),
)); ));
break; break;
case BootstrapState.done: case BootstrapState.done:
titleText = L10n.of(context).everythingReady; titleText = L10n.of(context)!.everythingReady;
body = Column( body = Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Image.asset('assets/backup.png', fit: BoxFit.contain), Image.asset('assets/backup.png', fit: BoxFit.contain),
Text(L10n.of(context).yourChatBackupHasBeenSetUp), Text(L10n.of(context)!.yourChatBackupHasBeenSetUp),
], ],
); );
buttons.add(AdaptiveFlatButton( buttons.add(AdaptiveFlatButton(
label: L10n.of(context).close, label: L10n.of(context)!.close,
onPressed: () => onPressed: () =>
Navigator.of(context, rootNavigator: false).pop<bool>(false), Navigator.of(context, rootNavigator: false).pop<bool>(false),
)); ));
@ -362,7 +363,7 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
} }
} }
final title = Text(titleText); final title = Text(titleText!);
if (PlatformInfos.isCupertinoStyle) { if (PlatformInfos.isCupertinoStyle) {
return CupertinoAlertDialog( return CupertinoAlertDialog(
title: title, title: title,

View File

@ -34,31 +34,31 @@ import 'send_location_dialog.dart';
import 'sticker_picker_dialog.dart'; import 'sticker_picker_dialog.dart';
class Chat extends StatefulWidget { class Chat extends StatefulWidget {
final Widget sideView; final Widget? sideView;
const Chat({Key key, this.sideView}) : super(key: key); const Chat({Key? key, this.sideView}) : super(key: key);
@override @override
ChatController createState() => ChatController(); ChatController createState() => ChatController();
} }
class ChatController extends State<Chat> { class ChatController extends State<Chat> {
Room room; Room? room;
Client sendingClient; Client? sendingClient;
Timeline timeline; Timeline? timeline;
MatrixState matrix; MatrixState? matrix;
String get roomId => context.vRouter.pathParameters['roomid']; String? get roomId => context.vRouter.pathParameters['roomid'];
final AutoScrollController scrollController = AutoScrollController(); final AutoScrollController scrollController = AutoScrollController();
FocusNode inputFocus = FocusNode(); FocusNode inputFocus = FocusNode();
Timer typingCoolDown; Timer? typingCoolDown;
Timer typingTimeout; Timer? typingTimeout;
bool currentlyTyping = false; bool currentlyTyping = false;
bool dragging = false; bool dragging = false;
@ -76,7 +76,7 @@ class ChatController extends State<Chat> {
bytes: bytes, bytes: bytes,
name: xfile.name, name: xfile.name,
).detectFileType, ).detectFileType,
room: room, room: room!,
), ),
); );
} }
@ -96,13 +96,13 @@ class ChatController extends State<Chat> {
List<Event> selectedEvents = []; List<Event> selectedEvents = [];
List<Event> filteredEvents; late List<Event> filteredEvents;
final Set<String> unfolded = {}; final Set<String> unfolded = {};
Event replyEvent; Event? replyEvent;
Event editEvent; Event? editEvent;
bool showScrollDownButton = false; bool showScrollDownButton = false;
@ -115,8 +115,8 @@ class ChatController extends State<Chat> {
String pendingText = ''; String pendingText = '';
bool get canLoadMore => bool get canLoadMore =>
timeline.events.isEmpty || timeline!.events.isEmpty ||
timeline.events.last.type != EventTypes.RoomCreate; timeline!.events.last.type != EventTypes.RoomCreate;
bool showEmojiPicker = false; bool showEmojiPicker = false;
@ -126,7 +126,7 @@ class ChatController extends State<Chat> {
final success = await showFutureLoadingDialog( final success = await showFutureLoadingDialog(
context: context, context: context,
future: () => room.sendEvent({ future: () => room!.sendEvent({
'msgtype': Matrix.callNamespace, 'msgtype': Matrix.callNamespace,
'body': url, 'body': url,
})); }));
@ -137,12 +137,12 @@ class ChatController extends State<Chat> {
void requestHistory() async { void requestHistory() async {
if (canLoadMore) { if (canLoadMore) {
try { try {
await timeline.requestHistory(historyCount: _loadHistoryCount); await timeline!.requestHistory(historyCount: _loadHistoryCount);
} catch (err) { } catch (err) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text( content: Text(
(err as Object).toLocalizedString(context), (err).toLocalizedString(context),
), ),
), ),
); );
@ -157,8 +157,8 @@ class ChatController extends State<Chat> {
} }
if (scrollController.position.pixels == if (scrollController.position.pixels ==
scrollController.position.maxScrollExtent && scrollController.position.maxScrollExtent &&
timeline.events.isNotEmpty && timeline!.events.isNotEmpty &&
timeline.events[timeline.events.length - 1].type != timeline!.events[timeline!.events.length - 1].type !=
EventTypes.RoomCreate) { EventTypes.RoomCreate) {
requestHistory(); requestHistory();
} }
@ -180,7 +180,7 @@ class ChatController extends State<Chat> {
if (!mounted) return; if (!mounted) return;
setState( setState(
() { () {
filteredEvents = timeline.getFilteredEvents(unfolded: unfolded); filteredEvents = timeline!.getFilteredEvents(unfolded: unfolded);
}, },
); );
} }
@ -192,22 +192,22 @@ class ChatController extends State<Chat> {
unfolded.add(filteredEvents[i].eventId); unfolded.add(filteredEvents[i].eventId);
i++; i++;
} }
filteredEvents = timeline.getFilteredEvents(unfolded: unfolded); filteredEvents = timeline!.getFilteredEvents(unfolded: unfolded);
}); });
} }
Future<bool> getTimeline() async { Future<bool> getTimeline() async {
if (timeline == null) { if (timeline == null) {
timeline = await room.getTimeline(onUpdate: updateView); timeline = await room!.getTimeline(onUpdate: updateView);
if (timeline.events.isNotEmpty) { if (timeline!.events.isNotEmpty) {
// ignore: unawaited_futures // ignore: unawaited_futures
if (room.markedUnread) room.markUnread(false); if (room!.markedUnread) room!.markUnread(false);
} }
// when the scroll controller is attached we want to scroll to an event id, if specified // when the scroll controller is attached we want to scroll to an event id, if specified
// and update the scroll controller...which will trigger a request history, if the // and update the scroll controller...which will trigger a request history, if the
// "load more" button is visible on the screen // "load more" button is visible on the screen
SchedulerBinding.instance.addPostFrameCallback((_) async { SchedulerBinding.instance!.addPostFrameCallback((_) async {
if (mounted) { if (mounted) {
final event = VRouter.of(context).queryParameters['event']; final event = VRouter.of(context).queryParameters['event'];
if (event != null) { if (event != null) {
@ -217,16 +217,15 @@ class ChatController extends State<Chat> {
} }
}); });
} }
filteredEvents = timeline.getFilteredEvents(unfolded: unfolded); filteredEvents = timeline!.getFilteredEvents(unfolded: unfolded);
timeline.requestKeys(); timeline!.requestKeys();
if (room.notificationCount != null && if (room!.notificationCount > 0 &&
room.notificationCount > 0 &&
timeline != null && timeline != null &&
timeline.events.isNotEmpty && timeline!.events.isNotEmpty &&
Matrix.of(context).webHasFocus) { Matrix.of(context).webHasFocus) {
// ignore: unawaited_futures // ignore: unawaited_futures
timeline.setReadMarker(); timeline!.setReadMarker();
room.client.updateIosBadge(); room!.client.updateIosBadge();
} }
return true; return true;
} }
@ -240,13 +239,13 @@ class ChatController extends State<Chat> {
TextEditingController sendController = TextEditingController(); TextEditingController sendController = TextEditingController();
void setSendingClient(Client c) { void setSendingClient(Client? c) {
// first cancle typing with the old sending client // first cancle typing with the old sending client
if (currentlyTyping) { if (currentlyTyping) {
// no need to have the setting typing to false be blocking // no need to have the setting typing to false be blocking
typingCoolDown?.cancel(); typingCoolDown?.cancel();
typingCoolDown = null; typingCoolDown = null;
room.setTyping(false); room!.setTyping(false);
currentlyTyping = false; currentlyTyping = false;
} }
// then set the new sending client // then set the new sending client
@ -263,22 +262,22 @@ class ChatController extends State<Chat> {
final commandMatch = RegExp(r'^\/(\w+)').firstMatch(sendController.text); final commandMatch = RegExp(r'^\/(\w+)').firstMatch(sendController.text);
if (commandMatch != null && if (commandMatch != null &&
!room.client.commands.keys.contains(commandMatch[1].toLowerCase())) { !room!.client.commands.keys.contains(commandMatch[1]!.toLowerCase())) {
final l10n = L10n.of(context); final l10n = L10n.of(context)!;
final dialogResult = await showOkCancelAlertDialog( final dialogResult = await showOkCancelAlertDialog(
context: context, context: context,
useRootNavigator: false, useRootNavigator: false,
title: l10n.commandInvalid, title: l10n.commandInvalid,
message: l10n.commandMissing(commandMatch[0]), message: l10n.commandMissing(commandMatch[0]!),
okLabel: l10n.sendAsText, okLabel: l10n.sendAsText,
cancelLabel: l10n.cancel, cancelLabel: l10n.cancel,
); );
if (dialogResult == null || dialogResult == OkCancelResult.cancel) return; if (dialogResult == OkCancelResult.cancel) return;
parseCommands = false; parseCommands = false;
} }
// ignore: unawaited_futures // ignore: unawaited_futures
room.sendTextEvent(sendController.text, room!.sendTextEvent(sendController.text,
inReplyTo: replyEvent, inReplyTo: replyEvent,
editEventId: editEvent?.eventId, editEventId: editEvent?.eventId,
parseCommands: parseCommands); parseCommands: parseCommands);
@ -298,16 +297,16 @@ class ChatController extends State<Chat> {
void sendFileAction() async { void sendFileAction() async {
final result = final result =
await FilePickerCross.importFromStorage(type: FileTypeCross.any); await FilePickerCross.importFromStorage(type: FileTypeCross.any);
if (result == null) return; if (result.fileName == null) return;
await showDialog( await showDialog(
context: context, context: context,
useRootNavigator: false, useRootNavigator: false,
builder: (c) => SendFileDialog( builder: (c) => SendFileDialog(
file: MatrixFile( file: MatrixFile(
bytes: result.toUint8List(), bytes: result.toUint8List(),
name: result.fileName, name: result.fileName!,
).detectFileType, ).detectFileType,
room: room, room: room!,
), ),
); );
} }
@ -315,16 +314,16 @@ class ChatController extends State<Chat> {
void sendImageAction() async { void sendImageAction() async {
final result = final result =
await FilePickerCross.importFromStorage(type: FileTypeCross.image); await FilePickerCross.importFromStorage(type: FileTypeCross.image);
if (result == null) return; if (result.fileName == null) return;
await showDialog( await showDialog(
context: context, context: context,
useRootNavigator: false, useRootNavigator: false,
builder: (c) => SendFileDialog( builder: (c) => SendFileDialog(
file: MatrixImageFile( file: MatrixImageFile(
bytes: result.toUint8List(), bytes: result.toUint8List(),
name: result.fileName, name: result.fileName!,
), ),
room: room, room: room!,
), ),
); );
} }
@ -343,7 +342,7 @@ class ChatController extends State<Chat> {
bytes: bytes, bytes: bytes,
name: file.path, name: file.path,
), ),
room: room, room: room!,
), ),
); );
} }
@ -362,7 +361,7 @@ class ChatController extends State<Chat> {
bytes: bytes, bytes: bytes,
name: file.path, name: file.path,
), ),
room: room, room: room!,
), ),
); );
} }
@ -371,7 +370,7 @@ class ChatController extends State<Chat> {
final sticker = await showModalBottomSheet<ImagePackImageContent>( final sticker = await showModalBottomSheet<ImagePackImageContent>(
context: context, context: context,
useRootNavigator: false, useRootNavigator: false,
builder: (c) => StickerPickerDialog(room: room), builder: (c) => StickerPickerDialog(room: room!),
); );
if (sticker == null) return; if (sticker == null) return;
final eventContent = <String, dynamic>{ final eventContent = <String, dynamic>{
@ -382,7 +381,7 @@ class ChatController extends State<Chat> {
// send the sticker // send the sticker
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => room.sendEvent( future: () => room!.sendEvent(
eventContent, eventContent,
type: EventTypes.Sticker, type: EventTypes.Sticker,
), ),
@ -405,7 +404,7 @@ class ChatController extends State<Chat> {
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => future: () =>
room.sendFileEvent(file, inReplyTo: replyEvent, extraContent: { room!.sendFileEvent(file, inReplyTo: replyEvent, extraContent: {
'info': { 'info': {
...file.info, ...file.info,
'duration': result.duration, 'duration': result.duration,
@ -426,7 +425,7 @@ class ChatController extends State<Chat> {
await showDialog( await showDialog(
context: context, context: context,
useRootNavigator: false, useRootNavigator: false,
builder: (c) => SendLocationDialog(room: room), builder: (c) => SendLocationDialog(room: room!),
); );
} }
@ -434,13 +433,13 @@ class ChatController extends State<Chat> {
var copyString = ''; var copyString = '';
if (selectedEvents.length == 1) { if (selectedEvents.length == 1) {
return selectedEvents.first return selectedEvents.first
.getDisplayEvent(timeline) .getDisplayEvent(timeline!)
.getLocalizedBody(MatrixLocals(L10n.of(context))); .getLocalizedBody(MatrixLocals(L10n.of(context)!));
} }
for (final event in selectedEvents) { for (final event in selectedEvents) {
if (copyString.isNotEmpty) copyString += '\n\n'; if (copyString.isNotEmpty) copyString += '\n\n';
copyString += event.getDisplayEvent(timeline).getLocalizedBody( copyString += event.getDisplayEvent(timeline!).getLocalizedBody(
MatrixLocals(L10n.of(context)), MatrixLocals(L10n.of(context)!),
withSenderNamePrefix: true); withSenderNamePrefix: true);
} }
return copyString; return copyString;
@ -458,37 +457,37 @@ class ChatController extends State<Chat> {
final event = selectedEvents.single; final event = selectedEvents.single;
final score = await showConfirmationDialog<int>( final score = await showConfirmationDialog<int>(
context: context, context: context,
title: L10n.of(context).reportMessage, title: L10n.of(context)!.reportMessage,
message: L10n.of(context).howOffensiveIsThisContent, message: L10n.of(context)!.howOffensiveIsThisContent,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context)!.cancel,
okLabel: L10n.of(context).ok, okLabel: L10n.of(context)!.ok,
actions: [ actions: [
AlertDialogAction( AlertDialogAction(
key: -100, key: -100,
label: L10n.of(context).extremeOffensive, label: L10n.of(context)!.extremeOffensive,
), ),
AlertDialogAction( AlertDialogAction(
key: -50, key: -50,
label: L10n.of(context).offensive, label: L10n.of(context)!.offensive,
), ),
AlertDialogAction( AlertDialogAction(
key: 0, key: 0,
label: L10n.of(context).inoffensive, label: L10n.of(context)!.inoffensive,
), ),
]); ]);
if (score == null) return; if (score == null) return;
final reason = await showTextInputDialog( final reason = await showTextInputDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).whyDoYouWantToReportThis, title: L10n.of(context)!.whyDoYouWantToReportThis,
okLabel: L10n.of(context).ok, okLabel: L10n.of(context)!.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context)!.cancel,
textFields: [DialogTextField(hintText: L10n.of(context).reason)]); textFields: [DialogTextField(hintText: L10n.of(context)!.reason)]);
if (reason == null || reason.single.isEmpty) return; if (reason == null || reason.single.isEmpty) return;
final result = await showFutureLoadingDialog( final result = await showFutureLoadingDialog(
context: context, context: context,
future: () => Matrix.of(context).client.reportContent( future: () => Matrix.of(context).client.reportContent(
event.roomId, event.roomId!,
event.eventId, event.eventId,
reason: reason.single, reason: reason.single,
score: score, score: score,
@ -500,16 +499,16 @@ class ChatController extends State<Chat> {
selectedEvents.clear(); selectedEvents.clear();
}); });
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(L10n.of(context).contentHasBeenReported))); SnackBar(content: Text(L10n.of(context)!.contentHasBeenReported)));
} }
void redactEventsAction() async { void redactEventsAction() async {
final confirmed = await showOkCancelAlertDialog( final confirmed = await showOkCancelAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).messageWillBeRemovedWarning, title: L10n.of(context)!.messageWillBeRemovedWarning,
okLabel: L10n.of(context).remove, okLabel: L10n.of(context)!.remove,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context)!.cancel,
) == ) ==
OkCancelResult.ok; OkCancelResult.ok;
if (!confirmed) return; if (!confirmed) return;
@ -522,12 +521,12 @@ class ChatController extends State<Chat> {
await event.redactEvent(); await event.redactEvent();
} else { } else {
final client = currentRoomBundle.firstWhere( final client = currentRoomBundle.firstWhere(
(cl) => selectedEvents.first.senderId == cl.userID, (cl) => selectedEvents.first.senderId == cl!.userID,
orElse: () => null); orElse: () => null);
if (client == null) { if (client == null) {
return; return;
} }
final room = client.getRoomById(roomId); final room = client.getRoomById(roomId!)!;
await Event.fromJson(event.toJson(), room).redactEvent(); await Event.fromJson(event.toJson(), room).redactEvent();
} }
} else { } else {
@ -541,17 +540,17 @@ class ChatController extends State<Chat> {
}); });
} }
List<Client> get currentRoomBundle { List<Client?> get currentRoomBundle {
final clients = matrix.currentBundle; final clients = matrix!.currentBundle!;
clients.removeWhere((c) => c.getRoomById(roomId) == null); clients.removeWhere((c) => c!.getRoomById(roomId!) == null);
return clients; return clients;
} }
bool get canRedactSelectedEvents { bool get canRedactSelectedEvents {
final clients = matrix.currentBundle; final clients = matrix!.currentBundle;
for (final event in selectedEvents) { for (final event in selectedEvents) {
if (event.canRedact == false && if (event.canRedact == false &&
!(clients.any((cl) => event.senderId == cl.userID))) return false; !(clients!.any((cl) => event.senderId == cl!.userID))) return false;
} }
return true; return true;
} }
@ -561,7 +560,7 @@ class ChatController extends State<Chat> {
return false; return false;
} }
return currentRoomBundle return currentRoomBundle
.any((cl) => selectedEvents.first.senderId == cl.userID); .any((cl) => selectedEvents.first.senderId == cl!.userID);
} }
void forwardEventsAction() async { void forwardEventsAction() async {
@ -583,7 +582,7 @@ class ChatController extends State<Chat> {
event.sendAgain(); event.sendAgain();
} }
final allEditEvents = event final allEditEvents = event
.aggregatedEvents(timeline, RelationshipTypes.edit) .aggregatedEvents(timeline!, RelationshipTypes.edit)
.where((e) => e.status.isError); .where((e) => e.status.isError);
for (final e in allEditEvents) { for (final e in allEditEvents) {
e.sendAgain(); e.sendAgain();
@ -591,7 +590,7 @@ class ChatController extends State<Chat> {
setState(() => selectedEvents.clear()); setState(() => selectedEvents.clear());
} }
void replyAction({Event replyTo}) { void replyAction({Event? replyTo}) {
setState(() { setState(() {
replyEvent = replyTo ?? selectedEvents.first; replyEvent = replyTo ?? selectedEvents.first;
selectedEvents.clear(); selectedEvents.clear();
@ -609,7 +608,7 @@ class ChatController extends State<Chat> {
future: () async { future: () async {
// 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);
if (event == null) { if (event == null) {
// event is null...meaning something is off // event is null...meaning something is off
return; return;
@ -628,7 +627,7 @@ class ChatController extends State<Chat> {
return; return;
} }
try { try {
await timeline.requestHistory(historyCount: _loadHistoryCount); await timeline!.requestHistory(historyCount: _loadHistoryCount);
} catch (err) { } catch (err) {
if (err is TimeoutException) { if (err is TimeoutException) {
// loading the history timed out...so let's do nothing // loading the history timed out...so let's do nothing
@ -662,7 +661,7 @@ class ChatController extends State<Chat> {
return sendEmojiAction(emoji.emoji); return sendEmojiAction(emoji.emoji);
} }
Iterable<Event> _allReactionEvents; late Iterable<Event> _allReactionEvents;
void cancelEmojiPicker() => setState(() => showEmojiPicker = false); void cancelEmojiPicker() => setState(() => showEmojiPicker = false);
@ -671,13 +670,13 @@ class ChatController extends State<Chat> {
setState(() => showEmojiPicker = true); setState(() => showEmojiPicker = true);
} }
void sendEmojiAction(String emoji) async { void sendEmojiAction(String? emoji) async {
final events = List<Event>.from(selectedEvents); final events = List<Event>.from(selectedEvents);
setState(() => selectedEvents.clear()); setState(() => selectedEvents.clear());
for (final event in events) { for (final event in events) {
await room.sendReaction( await room!.sendReaction(
event.eventId, event.eventId,
emoji, emoji!,
); );
} }
} }
@ -695,7 +694,7 @@ class ChatController extends State<Chat> {
void editSelectedEventAction() { void editSelectedEventAction() {
final client = currentRoomBundle.firstWhere( final client = currentRoomBundle.firstWhere(
(cl) => selectedEvents.first.senderId == cl.userID, (cl) => selectedEvents.first.senderId == cl!.userID,
orElse: () => null); orElse: () => null);
if (client == null) { if (client == null) {
return; return;
@ -704,9 +703,9 @@ class ChatController extends State<Chat> {
setState(() { setState(() {
pendingText = sendController.text; pendingText = sendController.text;
editEvent = selectedEvents.first; editEvent = selectedEvents.first;
inputText = sendController.text = editEvent inputText = sendController.text = editEvent!
.getDisplayEvent(timeline) .getDisplayEvent(timeline!)
.getLocalizedBody(MatrixLocals(L10n.of(context)), .getLocalizedBody(MatrixLocals(L10n.of(context)!),
withSenderNamePrefix: false, hideReply: true); withSenderNamePrefix: false, hideReply: true);
selectedEvents.clear(); selectedEvents.clear();
}); });
@ -718,29 +717,29 @@ class ChatController extends State<Chat> {
await showOkCancelAlertDialog( await showOkCancelAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).goToTheNewRoom, title: L10n.of(context)!.goToTheNewRoom,
message: room message: room!
.getState(EventTypes.RoomTombstone) .getState(EventTypes.RoomTombstone)!
.parsedTombstoneContent .parsedTombstoneContent
.body, .body,
okLabel: L10n.of(context).ok, okLabel: L10n.of(context)!.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context)!.cancel,
)) { )) {
return; return;
} }
final result = await showFutureLoadingDialog( final result = await showFutureLoadingDialog(
context: context, context: context,
future: () => room.client.joinRoom(room future: () => room!.client.joinRoom(room!
.getState(EventTypes.RoomTombstone) .getState(EventTypes.RoomTombstone)!
.parsedTombstoneContent .parsedTombstoneContent
.replacementRoom), .replacementRoom),
); );
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: room.leave, future: room!.leave,
); );
if (result.error == null) { if (result.error == null) {
VRouter.of(context).toSegments(['rooms', result.result]); VRouter.of(context).toSegments(['rooms', result.result!]);
} }
} }
@ -761,12 +760,12 @@ class ChatController extends State<Chat> {
} }
} }
int findChildIndexCallback(Key key, Map<String, int> thisEventsKeyMap) { int? findChildIndexCallback(Key key, Map<String, int> thisEventsKeyMap) {
// this method is called very often. As such, it has to be optimized for speed. // this method is called very often. As such, it has to be optimized for speed.
if (key is! ValueKey) { if (key is! ValueKey) {
return null; return null;
} }
final eventId = (key as ValueKey).value; final eventId = key.value;
if (eventId is! String) { if (eventId is! String) {
return null; return null;
} }
@ -809,11 +808,11 @@ class ChatController extends State<Chat> {
} }
void onInputBarChanged(String text) { void onInputBarChanged(String text) {
if (text.endsWith(' ') && matrix.hasComplexBundles) { if (text.endsWith(' ') && matrix!.hasComplexBundles) {
final clients = currentRoomBundle; final clients = currentRoomBundle;
for (final client in clients) { for (final client in clients) {
final prefix = client.sendPrefix; final prefix = client!.sendPrefix;
if ((prefix?.isNotEmpty ?? false) && if ((prefix.isNotEmpty) &&
text.toLowerCase() == '${prefix.toLowerCase()} ') { text.toLowerCase() == '${prefix.toLowerCase()} ') {
setSendingClient(client); setSendingClient(client);
setState(() { setState(() {
@ -828,7 +827,7 @@ class ChatController extends State<Chat> {
typingCoolDown = Timer(const Duration(seconds: 2), () { typingCoolDown = Timer(const Duration(seconds: 2), () {
typingCoolDown = null; typingCoolDown = null;
currentlyTyping = false; currentlyTyping = false;
room.setTyping(false); room!.setTyping(false);
}); });
typingTimeout ??= Timer(const Duration(seconds: 30), () { typingTimeout ??= Timer(const Duration(seconds: 30), () {
typingTimeout = null; typingTimeout = null;
@ -836,12 +835,13 @@ class ChatController extends State<Chat> {
}); });
if (!currentlyTyping) { if (!currentlyTyping) {
currentlyTyping = true; currentlyTyping = true;
room.setTyping(true, timeout: const Duration(seconds: 30).inMilliseconds); room!
.setTyping(true, timeout: const Duration(seconds: 30).inMilliseconds);
} }
setState(() => inputText = text); setState(() => inputText = text);
} }
void showEventInfo([Event event]) => void showEventInfo([Event? event]) =>
(event ?? selectedEvents.single).showInfoDialog(context); (event ?? selectedEvents.single).showInfoDialog(context);
void cancelReplyEventAction() => setState(() { void cancelReplyEventAction() => setState(() {

View File

@ -1,5 +1,3 @@
//@dart=2.12
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';
@ -19,41 +17,42 @@ class ChatAppBarTitle extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final room = controller.room;
if (room == null) {
return Container();
}
if (controller.selectedEvents.isNotEmpty) { if (controller.selectedEvents.isNotEmpty) {
return Text(controller.selectedEvents.length.toString()); return Text(controller.selectedEvents.length.toString());
} }
final directChatMatrixID = controller.room.directChatMatrixID; final directChatMatrixID = room.directChatMatrixID;
return ListTile( return ListTile(
leading: Avatar( leading: Avatar(
mxContent: controller.room.avatar, mxContent: room.avatar,
name: controller.room.displayname, name: room.displayname,
), ),
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
onTap: directChatMatrixID != null onTap: directChatMatrixID != null
? () => showModalBottomSheet( ? () => showModalBottomSheet(
context: context, context: context,
builder: (c) => UserBottomSheet( builder: (c) => UserBottomSheet(
user: controller.room.getUserByMXIDSync(directChatMatrixID), user: room.getUserByMXIDSync(directChatMatrixID),
outerContext: context, outerContext: context,
onMention: () => controller.sendController.text += onMention: () => controller.sendController.text +=
'${controller.room.getUserByMXIDSync(directChatMatrixID).mention} ', '${room.getUserByMXIDSync(directChatMatrixID).mention} ',
), ),
) )
: () => VRouter.of(context) : () => VRouter.of(context).toSegments(['rooms', room.id, 'details']),
.toSegments(['rooms', controller.room.id, 'details']), title: Text(room.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)),
title: Text(
controller.room
.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)),
maxLines: 1), maxLines: 1),
subtitle: StreamBuilder<Object>( subtitle: StreamBuilder<Object>(
stream: Matrix.of(context) stream: Matrix.of(context)
.client .client
.onPresence .onPresence
.stream .stream
.where((p) => p.senderId == controller.room.directChatMatrixID) .where((p) => p.senderId == room.directChatMatrixID)
.rateLimit(const Duration(seconds: 1)), .rateLimit(const Duration(seconds: 1)),
builder: (context, snapshot) => Text( builder: (context, snapshot) => Text(
controller.room.getLocalizedStatus(context), room.getLocalizedStatus(context),
maxLines: 1, maxLines: 1,
//overflow: TextOverflow.ellipsis, //overflow: TextOverflow.ellipsis,
), ),

View File

@ -1,5 +1,3 @@
//@dart=2.12
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart'; import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';

View File

@ -13,7 +13,7 @@ import 'input_bar.dart';
class ChatInputRow extends StatelessWidget { class ChatInputRow extends StatelessWidget {
final ChatController controller; final ChatController controller;
const ChatInputRow(this.controller, {Key key}) : super(key: key); const ChatInputRow(this.controller, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -30,14 +30,14 @@ class ChatInputRow extends StatelessWidget {
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
const Icon(Icons.keyboard_arrow_left_outlined), const Icon(Icons.keyboard_arrow_left_outlined),
Text(L10n.of(context).forward), Text(L10n.of(context)!.forward),
], ],
), ),
), ),
), ),
controller.selectedEvents.length == 1 controller.selectedEvents.length == 1
? controller.selectedEvents.first ? controller.selectedEvents.first
.getDisplayEvent(controller.timeline) .getDisplayEvent(controller.timeline!)
.status .status
.isSent .isSent
? SizedBox( ? SizedBox(
@ -46,7 +46,7 @@ class ChatInputRow extends StatelessWidget {
onPressed: controller.replyAction, onPressed: controller.replyAction,
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
Text(L10n.of(context).reply), Text(L10n.of(context)!.reply),
const Icon(Icons.keyboard_arrow_right), const Icon(Icons.keyboard_arrow_right),
], ],
), ),
@ -58,7 +58,7 @@ class ChatInputRow extends StatelessWidget {
onPressed: controller.sendAgainAction, onPressed: controller.sendAgainAction,
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
Text(L10n.of(context).tryToSendAgain), Text(L10n.of(context)!.tryToSendAgain),
const SizedBox(width: 4), const SizedBox(width: 4),
const Icon(Icons.send_outlined, size: 16), const Icon(Icons.send_outlined, size: 16),
], ],
@ -88,7 +88,7 @@ class ChatInputRow extends StatelessWidget {
foregroundColor: Colors.white, foregroundColor: Colors.white,
child: Icon(Icons.video_call_outlined), child: Icon(Icons.video_call_outlined),
), ),
title: Text(L10n.of(context).videoCall), title: Text(L10n.of(context)!.videoCall),
contentPadding: const EdgeInsets.all(0), contentPadding: const EdgeInsets.all(0),
), ),
), ),
@ -100,7 +100,7 @@ class ChatInputRow extends StatelessWidget {
foregroundColor: Colors.white, foregroundColor: Colors.white,
child: Icon(Icons.attachment_outlined), child: Icon(Icons.attachment_outlined),
), ),
title: Text(L10n.of(context).sendFile), title: Text(L10n.of(context)!.sendFile),
contentPadding: const EdgeInsets.all(0), contentPadding: const EdgeInsets.all(0),
), ),
), ),
@ -112,7 +112,7 @@ class ChatInputRow extends StatelessWidget {
foregroundColor: Colors.white, foregroundColor: Colors.white,
child: Icon(Icons.image_outlined), child: Icon(Icons.image_outlined),
), ),
title: Text(L10n.of(context).sendImage), title: Text(L10n.of(context)!.sendImage),
contentPadding: const EdgeInsets.all(0), contentPadding: const EdgeInsets.all(0),
), ),
), ),
@ -125,7 +125,7 @@ class ChatInputRow extends StatelessWidget {
foregroundColor: Colors.white, foregroundColor: Colors.white,
child: Icon(Icons.camera_alt_outlined), child: Icon(Icons.camera_alt_outlined),
), ),
title: Text(L10n.of(context).openCamera), title: Text(L10n.of(context)!.openCamera),
contentPadding: const EdgeInsets.all(0), contentPadding: const EdgeInsets.all(0),
), ),
), ),
@ -138,11 +138,11 @@ class ChatInputRow extends StatelessWidget {
foregroundColor: Colors.white, foregroundColor: Colors.white,
child: Icon(Icons.videocam_outlined), child: Icon(Icons.videocam_outlined),
), ),
title: Text(L10n.of(context).openVideoCamera), title: Text(L10n.of(context)!.openVideoCamera),
contentPadding: const EdgeInsets.all(0), contentPadding: const EdgeInsets.all(0),
), ),
), ),
if (controller.room if (controller.room!
.getImagePacks(ImagePackUsage.sticker) .getImagePacks(ImagePackUsage.sticker)
.isNotEmpty) .isNotEmpty)
PopupMenuItem<String>( PopupMenuItem<String>(
@ -153,7 +153,7 @@ class ChatInputRow extends StatelessWidget {
foregroundColor: Colors.white, foregroundColor: Colors.white,
child: Icon(Icons.emoji_emotions_outlined), child: Icon(Icons.emoji_emotions_outlined),
), ),
title: Text(L10n.of(context).sendSticker), title: Text(L10n.of(context)!.sendSticker),
contentPadding: const EdgeInsets.all(0), contentPadding: const EdgeInsets.all(0),
), ),
), ),
@ -166,7 +166,7 @@ class ChatInputRow extends StatelessWidget {
foregroundColor: Colors.white, foregroundColor: Colors.white,
child: Icon(Icons.gps_fixed_outlined), child: Icon(Icons.gps_fixed_outlined),
), ),
title: Text(L10n.of(context).shareLocation), title: Text(L10n.of(context)!.shareLocation),
contentPadding: const EdgeInsets.all(0), contentPadding: const EdgeInsets.all(0),
), ),
), ),
@ -176,11 +176,11 @@ class ChatInputRow extends StatelessWidget {
Container( Container(
height: 56, height: 56,
alignment: Alignment.center, alignment: Alignment.center,
child: EncryptionButton(controller.room), child: EncryptionButton(controller.room!),
), ),
if (controller.matrix.isMultiAccount && if (controller.matrix!.isMultiAccount &&
controller.matrix.hasComplexBundles && controller.matrix!.hasComplexBundles &&
controller.matrix.currentBundle.length > 1) controller.matrix!.currentBundle!.length > 1)
Container( Container(
height: 56, height: 56,
alignment: Alignment.center, alignment: Alignment.center,
@ -190,7 +190,7 @@ class ChatInputRow extends StatelessWidget {
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0), padding: const EdgeInsets.symmetric(vertical: 4.0),
child: InputBar( child: InputBar(
room: controller.room, room: controller.room!,
minLines: 1, minLines: 1,
maxLines: 8, maxLines: 8,
autofocus: !PlatformInfos.isMobile, autofocus: !PlatformInfos.isMobile,
@ -201,7 +201,7 @@ class ChatInputRow extends StatelessWidget {
focusNode: controller.inputFocus, focusNode: controller.inputFocus,
controller: controller.sendController, controller: controller.sendController,
decoration: InputDecoration( decoration: InputDecoration(
hintText: L10n.of(context).writeAMessage, hintText: L10n.of(context)!.writeAMessage,
hintMaxLines: 1, hintMaxLines: 1,
border: InputBorder.none, border: InputBorder.none,
enabledBorder: InputBorder.none, enabledBorder: InputBorder.none,
@ -216,7 +216,7 @@ class ChatInputRow extends StatelessWidget {
height: 56, height: 56,
alignment: Alignment.center, alignment: Alignment.center,
child: IconButton( child: IconButton(
tooltip: L10n.of(context).voiceMessage, tooltip: L10n.of(context)!.voiceMessage,
icon: const Icon(Icons.mic_none_outlined), icon: const Icon(Icons.mic_none_outlined),
onPressed: controller.voiceMessageAction, onPressed: controller.voiceMessageAction,
), ),
@ -228,7 +228,7 @@ class ChatInputRow extends StatelessWidget {
child: IconButton( child: IconButton(
icon: const Icon(Icons.send_outlined), icon: const Icon(Icons.send_outlined),
onPressed: controller.send, onPressed: controller.send,
tooltip: L10n.of(context).send, tooltip: L10n.of(context)!.send,
), ),
), ),
], ],
@ -239,11 +239,11 @@ class ChatInputRow extends StatelessWidget {
class _ChatAccountPicker extends StatelessWidget { class _ChatAccountPicker extends StatelessWidget {
final ChatController controller; final ChatController controller;
const _ChatAccountPicker(this.controller, {Key key}) : super(key: key); const _ChatAccountPicker(this.controller, {Key? key}) : super(key: key);
void _popupMenuButtonSelected(String mxid) { void _popupMenuButtonSelected(String mxid) {
final client = controller.matrix.currentBundle final client = controller.matrix!.currentBundle!
.firstWhere((cl) => cl.userID == mxid, orElse: () => null); .firstWhere((cl) => cl!.userID == mxid, orElse: () => null);
if (client == null) { if (client == null) {
Logs().w('Attempted to switch to a non-existing client $mxid'); Logs().w('Attempted to switch to a non-existing client $mxid');
return; return;
@ -258,23 +258,23 @@ class _ChatAccountPicker extends StatelessWidget {
return Padding( return Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: FutureBuilder<Profile>( child: FutureBuilder<Profile>(
future: controller.sendingClient.ownProfile, future: controller.sendingClient!.ownProfile,
builder: (context, snapshot) => PopupMenuButton<String>( builder: (context, snapshot) => PopupMenuButton<String>(
onSelected: _popupMenuButtonSelected, onSelected: _popupMenuButtonSelected,
itemBuilder: (BuildContext context) => clients itemBuilder: (BuildContext context) => clients
.map((client) => PopupMenuItem<String>( .map((client) => PopupMenuItem<String>(
value: client.userID, value: client!.userID,
child: FutureBuilder<Profile>( child: FutureBuilder<Profile>(
future: client.ownProfile, future: client.ownProfile,
builder: (context, snapshot) => ListTile( builder: (context, snapshot) => ListTile(
leading: Avatar( leading: Avatar(
mxContent: snapshot.data?.avatarUrl, mxContent: snapshot.data?.avatarUrl,
name: snapshot.data?.displayName ?? name: snapshot.data?.displayName ??
client.userID.localpart, client.userID!.localpart,
size: 20, size: 20,
), ),
title: title:
Text(snapshot.data?.displayName ?? client.userID), Text(snapshot.data?.displayName ?? client.userID!),
contentPadding: const EdgeInsets.all(0), contentPadding: const EdgeInsets.all(0),
), ),
), ),
@ -283,7 +283,7 @@ class _ChatAccountPicker extends StatelessWidget {
child: Avatar( child: Avatar(
mxContent: snapshot.data?.avatarUrl, mxContent: snapshot.data?.avatarUrl,
name: snapshot.data?.displayName ?? name: snapshot.data?.displayName ??
controller.matrix.client.userID.localpart, controller.matrix!.client.userID!.localpart,
size: 20, size: 20,
), ),
), ),

View File

@ -34,31 +34,31 @@ enum _EventContextAction { info, report }
class ChatView extends StatelessWidget { class ChatView extends StatelessWidget {
final ChatController controller; final ChatController controller;
const ChatView(this.controller, {Key key}) : super(key: key); const ChatView(this.controller, {Key? key}) : super(key: key);
List<Widget> _appBarActions(BuildContext context) => controller.selectMode List<Widget> _appBarActions(BuildContext context) => controller.selectMode
? [ ? [
if (controller.canEditSelectedEvents) if (controller.canEditSelectedEvents)
IconButton( IconButton(
icon: const Icon(Icons.edit_outlined), icon: const Icon(Icons.edit_outlined),
tooltip: L10n.of(context).edit, tooltip: L10n.of(context)!.edit,
onPressed: controller.editSelectedEventAction, onPressed: controller.editSelectedEventAction,
), ),
IconButton( IconButton(
icon: const Icon(Icons.copy_outlined), icon: const Icon(Icons.copy_outlined),
tooltip: L10n.of(context).copy, tooltip: L10n.of(context)!.copy,
onPressed: controller.copyEventsAction, onPressed: controller.copyEventsAction,
), ),
if (controller.canSaveSelectedEvent) if (controller.canSaveSelectedEvent)
IconButton( IconButton(
icon: Icon(Icons.adaptive.share), icon: Icon(Icons.adaptive.share),
tooltip: L10n.of(context).share, tooltip: L10n.of(context)!.share,
onPressed: controller.saveSelectedEvent, onPressed: controller.saveSelectedEvent,
), ),
if (controller.canRedactSelectedEvents) if (controller.canRedactSelectedEvents)
IconButton( IconButton(
icon: const Icon(Icons.delete_outlined), icon: const Icon(Icons.delete_outlined),
tooltip: L10n.of(context).redactMessage, tooltip: L10n.of(context)!.redactMessage,
onPressed: controller.redactEventsAction, onPressed: controller.redactEventsAction,
), ),
if (controller.selectedEvents.length == 1) if (controller.selectedEvents.length == 1)
@ -82,7 +82,7 @@ class ChatView extends StatelessWidget {
children: [ children: [
const Icon(Icons.info_outlined), const Icon(Icons.info_outlined),
const SizedBox(width: 12), const SizedBox(width: 12),
Text(L10n.of(context).messageInfo), Text(L10n.of(context)!.messageInfo),
], ],
), ),
), ),
@ -96,7 +96,7 @@ class ChatView extends StatelessWidget {
color: Colors.red, color: Colors.red,
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
Text(L10n.of(context).reportMessage), Text(L10n.of(context)!.reportMessage),
], ],
), ),
), ),
@ -104,29 +104,30 @@ class ChatView extends StatelessWidget {
), ),
] ]
: [ : [
ChatSettingsPopupMenu(controller.room, !controller.room.isDirectChat), ChatSettingsPopupMenu(
controller.room!, !controller.room!.isDirectChat),
]; ];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
controller.matrix ??= Matrix.of(context); controller.matrix ??= Matrix.of(context);
final client = controller.matrix.client; final client = controller.matrix!.client;
controller.sendingClient ??= client; controller.sendingClient ??= client;
controller.room = controller.sendingClient.getRoomById(controller.roomId); controller.room = controller.sendingClient!.getRoomById(controller.roomId!);
if (controller.room == null) { if (controller.room == null) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(L10n.of(context).oopsSomethingWentWrong), title: Text(L10n.of(context)!.oopsSomethingWentWrong),
), ),
body: Center( body: Center(
child: Text(L10n.of(context).youAreNoLongerParticipatingInThisChat), child: Text(L10n.of(context)!.youAreNoLongerParticipatingInThisChat),
), ),
); );
} }
if (controller.room.membership == Membership.invite) { if (controller.room!.membership == Membership.invite) {
showFutureLoadingDialog( showFutureLoadingDialog(
context: context, future: () => controller.room.join()); context: context, future: () => controller.room!.join());
} }
final bottomSheetPadding = FluffyThemes.isColumnMode(context) ? 16.0 : 8.0; final bottomSheetPadding = FluffyThemes.isColumnMode(context) ? 16.0 : 8.0;
final horizontalPadding = FluffyThemes.isColumnMode(context) ? 8.0 : 0.0; final horizontalPadding = FluffyThemes.isColumnMode(context) ? 8.0 : 0.0;
@ -139,7 +140,7 @@ class ChatView extends StatelessWidget {
} }
}, },
child: StreamBuilder( child: StreamBuilder(
stream: controller.room.onUpdate.stream stream: controller.room!.onUpdate.stream
.rateLimit(const Duration(milliseconds: 250)), .rateLimit(const Duration(milliseconds: 250)),
builder: (context, snapshot) => Scaffold( builder: (context, snapshot) => Scaffold(
appBar: AppBar( appBar: AppBar(
@ -152,10 +153,10 @@ class ChatView extends StatelessWidget {
? IconButton( ? IconButton(
icon: const Icon(Icons.close), icon: const Icon(Icons.close),
onPressed: controller.clearSelectedEvents, onPressed: controller.clearSelectedEvents,
tooltip: L10n.of(context).close, tooltip: L10n.of(context)!.close,
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
) )
: UnreadBadgeBackButton(roomId: controller.roomId), : UnreadBadgeBackButton(roomId: controller.roomId!),
titleSpacing: 0, titleSpacing: 0,
title: ChatAppBarTitle(controller), title: ChatAppBarTitle(controller),
actions: _appBarActions(context), actions: _appBarActions(context),
@ -167,7 +168,7 @@ class ChatView extends StatelessWidget {
child: FloatingActionButton( child: FloatingActionButton(
onPressed: controller.scrollDown, onPressed: controller.scrollDown,
foregroundColor: foregroundColor:
Theme.of(context).textTheme.bodyText2.color, Theme.of(context).textTheme.bodyText2!.color,
backgroundColor: Theme.of(context).scaffoldBackgroundColor, backgroundColor: Theme.of(context).scaffoldBackgroundColor,
mini: true, mini: true,
child: Icon(Icons.arrow_downward_outlined, child: Icon(Icons.arrow_downward_outlined,
@ -184,7 +185,7 @@ class ChatView extends StatelessWidget {
children: <Widget>[ children: <Widget>[
if (Matrix.of(context).wallpaper != null) if (Matrix.of(context).wallpaper != null)
Image.file( Image.file(
Matrix.of(context).wallpaper, Matrix.of(context).wallpaper!,
width: double.infinity, width: double.infinity,
height: double.infinity, height: double.infinity,
fit: BoxFit.cover, fit: BoxFit.cover,
@ -240,7 +241,7 @@ class ChatView extends StatelessWidget {
controller.filteredEvents.length + controller.filteredEvents.length +
1 1
? controller ? controller
.timeline.isRequestingHistory .timeline!.isRequestingHistory
? const Center( ? const Center(
child: child:
CircularProgressIndicator CircularProgressIndicator
@ -259,7 +260,7 @@ class ChatView extends StatelessWidget {
onPressed: controller onPressed: controller
.requestHistory, .requestHistory,
child: Text( child: Text(
L10n.of(context) L10n.of(context)!
.loadMore), .loadMore),
), ),
) )
@ -337,7 +338,7 @@ class ChatView extends StatelessWidget {
selected: controller selected: controller
.selectedEvents .selectedEvents
.any((e) => e.eventId == controller.filteredEvents[i - 1].eventId), .any((e) => e.eventId == controller.filteredEvents[i - 1].eventId),
timeline: controller.timeline, timeline: controller.timeline!,
nextEvent: i < controller.filteredEvents.length ? controller.filteredEvents[i] : null), nextEvent: i < controller.filteredEvents.length ? controller.filteredEvents[i] : null),
), ),
); );
@ -352,8 +353,8 @@ class ChatView extends StatelessWidget {
}, },
)), )),
), ),
if (controller.room.canSendDefaultMessages && if (controller.room!.canSendDefaultMessages &&
controller.room.membership == Membership.join) controller.room!.membership == Membership.join)
Container( Container(
margin: EdgeInsets.only( margin: EdgeInsets.only(
bottom: bottomSheetPadding, bottom: bottomSheetPadding,

View File

@ -1,4 +1,3 @@
//@dart=2.12
// This file is auto-generated using scripts/generate_command_hints_glue.sh. // This file is auto-generated using scripts/generate_command_hints_glue.sh.
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';

View File

@ -12,13 +12,13 @@ import '../../widgets/matrix.dart';
class EncryptionButton extends StatefulWidget { class EncryptionButton extends StatefulWidget {
final Room room; final Room room;
const EncryptionButton(this.room, {Key key}) : super(key: key); const EncryptionButton(this.room, {Key? key}) : super(key: key);
@override @override
_EncryptionButtonState createState() => _EncryptionButtonState(); _EncryptionButtonState createState() => _EncryptionButtonState();
} }
class _EncryptionButtonState extends State<EncryptionButton> { class _EncryptionButtonState extends State<EncryptionButton> {
StreamSubscription _onSyncSub; StreamSubscription? _onSyncSub;
void _enableEncryptionAction() async { void _enableEncryptionAction() async {
if (widget.room.encrypted) { if (widget.room.encrypted) {
@ -29,20 +29,20 @@ class _EncryptionButtonState extends State<EncryptionButton> {
await showOkAlertDialog( await showOkAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
okLabel: L10n.of(context).ok, okLabel: L10n.of(context)!.ok,
message: L10n.of(context).noEncryptionForPublicRooms, message: L10n.of(context)!.noEncryptionForPublicRooms,
); );
return; return;
} }
if (await showOkCancelAlertDialog( if (await showOkCancelAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).enableEncryption, title: L10n.of(context)!.enableEncryption,
message: widget.room.client.encryptionEnabled message: widget.room.client.encryptionEnabled
? L10n.of(context).enableEncryptionWarning ? L10n.of(context)!.enableEncryptionWarning
: L10n.of(context).needPantalaimonWarning, : L10n.of(context)!.needPantalaimonWarning,
okLabel: L10n.of(context).yes, okLabel: L10n.of(context)!.yes,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context)!.cancel,
) == ) ==
OkCancelResult.ok) { OkCancelResult.ok) {
await showFutureLoadingDialog( await showFutureLoadingDialog(
@ -50,7 +50,7 @@ class _EncryptionButtonState extends State<EncryptionButton> {
future: () => widget.room.enableEncryption(), future: () => widget.room.enableEncryption(),
); );
// we want to enable the lock icon // we want to enable the lock icon
setState(() => null); setState(() {});
} }
} }
@ -68,22 +68,22 @@ class _EncryptionButtonState extends State<EncryptionButton> {
.onSync .onSync
.stream .stream
.where((s) => s.deviceLists != null) .where((s) => s.deviceLists != null)
.listen((s) => setState(() => null)); .listen((s) => setState(() {}));
} }
return FutureBuilder<List<User>>( return FutureBuilder<List<User>>(
future: future:
widget.room.encrypted ? widget.room.requestParticipants() : null, widget.room.encrypted ? widget.room.requestParticipants() : null,
builder: (BuildContext context, snapshot) { builder: (BuildContext context, snapshot) {
Color color; Color? color;
if (widget.room.encrypted && snapshot.hasData) { if (widget.room.encrypted && snapshot.hasData) {
final users = snapshot.data; final users = snapshot.data!;
users.removeWhere((u) => users.removeWhere((u) =>
!{Membership.invite, Membership.join}.contains(u.membership) || !{Membership.invite, Membership.join}.contains(u.membership) ||
!widget.room.client.userDeviceKeys.containsKey(u.id)); !widget.room.client.userDeviceKeys.containsKey(u.id));
var allUsersValid = true; var allUsersValid = true;
var oneUserInvalid = false; var oneUserInvalid = false;
for (final u in users) { for (final u in users) {
final status = widget.room.client.userDeviceKeys[u.id].verified; final status = widget.room.client.userDeviceKeys[u.id]!.verified;
if (status != UserVerifiedStatus.verified) { if (status != UserVerifiedStatus.verified) {
allUsersValid = false; allUsersValid = false;
} }
@ -99,8 +99,8 @@ class _EncryptionButtonState extends State<EncryptionButton> {
} }
return IconButton( return IconButton(
tooltip: widget.room.encrypted tooltip: widget.room.encrypted
? L10n.of(context).encrypted ? L10n.of(context)!.encrypted
: L10n.of(context).encryptionNotEnabled, : L10n.of(context)!.encryptionNotEnabled,
icon: Icon( icon: Icon(
widget.room.encrypted widget.room.encrypted
? Icons.lock_outlined ? Icons.lock_outlined

View File

@ -13,7 +13,7 @@ extension EventInfoDialogExtension on Event {
void showInfoDialog(BuildContext context) => showModalBottomSheet( void showInfoDialog(BuildContext context) => showModalBottomSheet(
context: context, context: context,
builder: (context) => builder: (context) =>
EventInfoDialog(l10n: L10n.of(context), event: this), EventInfoDialog(l10n: L10n.of(context)!, event: this),
); );
} }
@ -21,9 +21,9 @@ class EventInfoDialog extends StatelessWidget {
final Event event; final Event event;
final L10n l10n; final L10n l10n;
const EventInfoDialog({ const EventInfoDialog({
@required this.event, required this.event,
@required this.l10n, required this.l10n,
Key key, Key? key,
}) : super(key: key); }) : super(key: key);
String get prettyJson { String get prettyJson {
@ -37,11 +37,11 @@ class EventInfoDialog extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(L10n.of(context).messageInfo), title: Text(L10n.of(context)!.messageInfo),
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_downward_outlined), icon: const Icon(Icons.arrow_downward_outlined),
onPressed: Navigator.of(context, rootNavigator: false).pop, onPressed: Navigator.of(context, rootNavigator: false).pop,
tooltip: L10n.of(context).close, tooltip: L10n.of(context)!.close,
), ),
), ),
body: ListView( body: ListView(
@ -51,19 +51,19 @@ class EventInfoDialog extends StatelessWidget {
mxContent: event.sender.avatarUrl, mxContent: event.sender.avatarUrl,
name: event.sender.calcDisplayname(), name: event.sender.calcDisplayname(),
), ),
title: Text(L10n.of(context).sender), title: Text(L10n.of(context)!.sender),
subtitle: subtitle:
Text('${event.sender.calcDisplayname()} [${event.senderId}]'), Text('${event.sender.calcDisplayname()} [${event.senderId}]'),
), ),
ListTile( ListTile(
title: Text(L10n.of(context).time), title: Text(L10n.of(context)!.time),
subtitle: Text(event.originServerTs.localizedTime(context)), subtitle: Text(event.originServerTs.localizedTime(context)),
), ),
ListTile( ListTile(
title: Text(L10n.of(context).messageType), title: Text(L10n.of(context)!.messageType),
subtitle: Text(event.humanreadableType), subtitle: Text(event.humanreadableType),
), ),
ListTile(title: Text('${L10n.of(context).sourceCode}:')), ListTile(title: Text('${L10n.of(context)!.sourceCode}:')),
Padding( Padding(
padding: const EdgeInsets.all(12.0), padding: const EdgeInsets.all(12.0),
child: Material( child: Material(

View File

@ -1,5 +1,3 @@
//@dart=2.12
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';

View File

@ -13,17 +13,17 @@ import '../../../utils/url_launcher.dart';
class HtmlMessage extends StatelessWidget { class HtmlMessage extends StatelessWidget {
final String html; final String html;
final int maxLines; final int? maxLines;
final Room room; final Room room;
final TextStyle defaultTextStyle; final TextStyle? defaultTextStyle;
final TextStyle linkStyle; final TextStyle? linkStyle;
final double emoteSize; final double? emoteSize;
const HtmlMessage({ const HtmlMessage({
Key key, Key? key,
this.html, required this.html,
this.maxLines, this.maxLines,
this.room, required this.room,
this.defaultTextStyle, this.defaultTextStyle,
this.linkStyle, this.linkStyle,
this.emoteSize, this.emoteSize,
@ -52,7 +52,7 @@ class HtmlMessage extends StatelessWidget {
defaultTextStyle: defaultTextStyle, defaultTextStyle: defaultTextStyle,
emoteSize: emoteSize, emoteSize: emoteSize,
linkStyle: linkStyle ?? linkStyle: linkStyle ??
themeData.textTheme.bodyText2.copyWith( themeData.textTheme.bodyText2!.copyWith(
color: themeData.colorScheme.secondary, color: themeData.colorScheme.secondary,
decoration: TextDecoration.underline, decoration: TextDecoration.underline,
), ),
@ -60,11 +60,11 @@ class HtmlMessage extends StatelessWidget {
maxLines: maxLines, maxLines: maxLines,
onLinkTap: (url) => UrlLauncher(context, url).launchUrl(), onLinkTap: (url) => UrlLauncher(context, url).launchUrl(),
onPillTap: (url) => UrlLauncher(context, url).launchUrl(), onPillTap: (url) => UrlLauncher(context, url).launchUrl(),
getMxcUrl: (String mxc, double width, double height, getMxcUrl: (String mxc, double? width, double? height,
{bool animated = false}) { {bool? animated = false}) {
final ratio = MediaQuery.of(context).devicePixelRatio; final ratio = MediaQuery.of(context).devicePixelRatio;
return Uri.parse(mxc) return Uri.parse(mxc)
?.getThumbnail( .getThumbnail(
matrix.client, matrix.client,
width: (width ?? 800) * ratio, width: (width ?? 800) * ratio,
height: (height ?? 800) * ratio, height: (height ?? 800) * ratio,
@ -92,9 +92,6 @@ class HtmlMessage extends StatelessWidget {
return await matrix.store.getItem('${SettingKeys.codeLanguage}.$key'); return await matrix.store.getItem('${SettingKeys.codeLanguage}.$key');
}, },
getPillInfo: (String url) async { getPillInfo: (String url) async {
if (room == null) {
return null;
}
final identityParts = url.parseIdentifierIntoParts(); final identityParts = url.parseIdentifierIntoParts();
final identifier = identityParts?.primaryIdentifier; final identifier = identityParts?.primaryIdentifier;
if (identifier == null) { if (identifier == null) {
@ -108,14 +105,11 @@ class HtmlMessage extends StatelessWidget {
} }
// there might still be a profile... // there might still be a profile...
final profile = await room.client.getProfileFromUserId(identifier); final profile = await room.client.getProfileFromUserId(identifier);
if (profile != null) {
return { return {
'displayname': profile.displayName, 'displayname': profile.displayName,
'avatar_url': profile.avatarUrl.toString(), 'avatar_url': profile.avatarUrl.toString(),
}; };
} }
return null;
}
if (identifier.sigil == '#') { if (identifier.sigil == '#') {
// we have an alias pill // we have an alias pill
for (final r in room.client.rooms) { for (final r in room.client.rooms) {
@ -128,7 +122,7 @@ class HtmlMessage extends StatelessWidget {
// we have a room! // we have a room!
return { return {
'displayname': 'displayname':
r.getLocalizedDisplayname(MatrixLocals(L10n.of(context))), r.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)),
'avatar_url': r.getState('m.room.avatar')?.content['url'], 'avatar_url': r.getState('m.room.avatar')?.content['url'],
}; };
} }
@ -143,12 +137,12 @@ class HtmlMessage extends StatelessWidget {
} }
return { return {
'displayname': 'displayname':
r.getLocalizedDisplayname(MatrixLocals(L10n.of(context))), r.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)),
'avatar_url': r.getState('m.room.avatar')?.content['url'], 'avatar_url': r.getState('m.room.avatar')?.content['url'],
}; };
} }
return null; return null;
}, } as Future<Map<String, dynamic>> Function(String)?,
); );
} }
} }

View File

@ -19,13 +19,13 @@ class ImageBubble extends StatefulWidget {
final bool tapToView; final bool tapToView;
final BoxFit fit; final BoxFit fit;
final bool maxSize; final bool maxSize;
final Color backgroundColor; final Color? backgroundColor;
final bool thumbnailOnly; final bool thumbnailOnly;
final bool animated; final bool animated;
final double width; final double width;
final double height; final double height;
final void Function() onLoaded; final void Function()? onLoaded;
final void Function() onTap; final void Function()? onTap;
const ImageBubble( const ImageBubble(
this.event, { this.event, {
@ -39,7 +39,7 @@ class ImageBubble extends StatefulWidget {
this.height = 300, this.height = 300,
this.animated = false, this.animated = false,
this.onTap, this.onTap,
Key key, Key? key,
}) : super(key: key); }) : super(key: key);
@override @override
@ -48,17 +48,17 @@ class ImageBubble extends StatefulWidget {
class _ImageBubbleState extends State<ImageBubble> { class _ImageBubbleState extends State<ImageBubble> {
// for plaintext: holds the http URL for the thumbnail // for plaintext: holds the http URL for the thumbnail
String thumbnailUrl; String? thumbnailUrl;
// for plaintext. holds the http URL for the thumbnial, without the animated flag // for plaintext. holds the http URL for the thumbnial, without the animated flag
String thumbnailUrlNoAnimated; String? thumbnailUrlNoAnimated;
// for plaintext: holds the http URL of the original // for plaintext: holds the http URL of the original
String attachmentUrl; String? attachmentUrl;
MatrixFile _file; MatrixFile? _file;
MatrixFile _thumbnail; MatrixFile? _thumbnail;
bool _requestedThumbnailOnFailure = false; bool _requestedThumbnailOnFailure = false;
// In case we have animated = false, this will hold the first frame so that we make // In case we have animated = false, this will hold the first frame so that we make
// sure that things are never animated // sure that things are never animated
Widget _firstFrame; Widget? _firstFrame;
// the mimetypes that we know how to render, from the flutter Image widget // the mimetypes that we know how to render, from the flutter Image widget
final _knownMimetypes = <String>{ final _knownMimetypes = <String>{
@ -82,8 +82,8 @@ class _ImageBubbleState extends State<ImageBubble> {
? widget.event.thumbnailMimetype.toLowerCase() ? widget.event.thumbnailMimetype.toLowerCase()
: widget.event.attachmentMimetype.toLowerCase(); : widget.event.attachmentMimetype.toLowerCase();
MatrixFile get _displayFile => _file ?? _thumbnail; MatrixFile? get _displayFile => _file ?? _thumbnail;
String get displayUrl => widget.thumbnailOnly ? thumbnailUrl : attachmentUrl; String? get displayUrl => widget.thumbnailOnly ? thumbnailUrl : attachmentUrl;
dynamic _error; dynamic _error;
@ -91,14 +91,14 @@ class _ImageBubbleState extends State<ImageBubble> {
try { try {
final res = await widget.event final res = await widget.event
.downloadAndDecryptAttachmentCached(getThumbnail: getThumbnail); .downloadAndDecryptAttachmentCached(getThumbnail: getThumbnail);
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance!.addPostFrameCallback((_) {
if (getThumbnail) { if (getThumbnail) {
if (mounted) { if (mounted) {
setState(() => _thumbnail = res); setState(() => _thumbnail = res);
} }
} else { } else {
if (widget.onLoaded != null) { if (widget.onLoaded != null) {
widget.onLoaded(); widget.onLoaded!();
} }
if (mounted) { if (mounted) {
setState(() => _file = res); setState(() => _file = res);
@ -106,7 +106,7 @@ class _ImageBubbleState extends State<ImageBubble> {
} }
}); });
} catch (err) { } catch (err) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance!.addPostFrameCallback((_) {
if (mounted) { if (mounted) {
setState(() => _error = err); setState(() => _error = err);
} }
@ -114,7 +114,7 @@ class _ImageBubbleState extends State<ImageBubble> {
} }
} }
Widget frameBuilder(_, Widget child, int frame, __) { Widget frameBuilder(_, Widget child, int? frame, __) {
// as servers might return animated gifs as thumbnails and we want them to *not* play // as servers might return animated gifs as thumbnails and we want them to *not* play
// animated, we'll have to store the first frame in a variable and display that instead // animated, we'll have to store the first frame in a variable and display that instead
if (widget.animated) { if (widget.animated) {
@ -135,7 +135,9 @@ class _ImageBubbleState extends State<ImageBubble> {
key: ValueKey(key), key: ValueKey(key),
fit: widget.fit, fit: widget.fit,
), ),
network: (String url) => SvgPicture.network( network: (String? url) => url == null
? Container()
: SvgPicture.network(
url, url,
key: ValueKey(url), key: ValueKey(url),
placeholderBuilder: (context) => getPlaceholderWidget(), placeholderBuilder: (context) => getPlaceholderWidget(),
@ -151,7 +153,9 @@ class _ImageBubbleState extends State<ImageBubble> {
getErrorWidget(context, error), getErrorWidget(context, error),
animate: widget.animated, animate: widget.animated,
), ),
network: (String url) => Lottie.network( network: (String? url) => url == null
? Container()
: Lottie.network(
url, url,
key: ValueKey(url), key: ValueKey(url),
fit: widget.fit, fit: widget.fit,
@ -203,7 +207,7 @@ class _ImageBubbleState extends State<ImageBubble> {
OutlinedButton.icon( OutlinedButton.icon(
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
backgroundColor: Theme.of(context).scaffoldBackgroundColor, backgroundColor: Theme.of(context).scaffoldBackgroundColor,
primary: Theme.of(context).textTheme.bodyText1.color, primary: Theme.of(context).textTheme.bodyText1!.color,
), ),
icon: const Icon(Icons.download_outlined), icon: const Icon(Icons.download_outlined),
onPressed: () => widget.event.saveFile(context), onPressed: () => widget.event.saveFile(context),
@ -215,7 +219,7 @@ class _ImageBubbleState extends State<ImageBubble> {
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
if (widget.event.sizeString != null) Text(widget.event.sizeString), if (widget.event.sizeString != null) Text(widget.event.sizeString!),
const SizedBox(height: 8), const SizedBox(height: 8),
Text((error ?? _error).toString()), Text((error ?? _error).toString()),
], ],
@ -223,8 +227,8 @@ class _ImageBubbleState extends State<ImageBubble> {
); );
} }
Widget getPlaceholderWidget({Widget child}) { Widget getPlaceholderWidget({Widget? child}) {
Widget blurhash; Widget? blurhash;
if (widget.event.infoMap['xyz.amorgan.blurhash'] is String) { if (widget.event.infoMap['xyz.amorgan.blurhash'] is String) {
final ratio = final ratio =
widget.event.infoMap['w'] is int && widget.event.infoMap['h'] is int widget.event.infoMap['w'] is int && widget.event.infoMap['h'] is int
@ -265,16 +269,16 @@ class _ImageBubbleState extends State<ImageBubble> {
: widget.event.thumbnailMxcUrl.toString(); : widget.event.thumbnailMxcUrl.toString();
final mimetype = getMimetype(!isOriginal); final mimetype = getMimetype(!isOriginal);
if (_contentRenderers.containsKey(mimetype)) { if (_contentRenderers.containsKey(mimetype)) {
return _contentRenderers[mimetype].memory(_displayFile.bytes, key); return _contentRenderers[mimetype]!.memory!(_displayFile!.bytes, key);
} else { } else {
return Image.memory( return Image.memory(
_displayFile.bytes, _displayFile!.bytes,
key: ValueKey(key), key: ValueKey(key),
fit: widget.fit, fit: widget.fit,
errorBuilder: (context, error, stacktrace) { errorBuilder: (context, error, stacktrace) {
if (widget.event.hasThumbnail && !_requestedThumbnailOnFailure) { if (widget.event.hasThumbnail && !_requestedThumbnailOnFailure) {
_requestedThumbnailOnFailure = true; _requestedThumbnailOnFailure = true;
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance!.addPostFrameCallback((_) {
setState(() { setState(() {
_file = null; _file = null;
_requestFile(getThumbnail: true); _requestFile(getThumbnail: true);
@ -299,12 +303,12 @@ class _ImageBubbleState extends State<ImageBubble> {
final mimetype = getMimetype(_requestedThumbnailOnFailure); final mimetype = getMimetype(_requestedThumbnailOnFailure);
if (displayUrl == attachmentUrl && if (displayUrl == attachmentUrl &&
_contentRenderers.containsKey(mimetype)) { _contentRenderers.containsKey(mimetype)) {
return _contentRenderers[mimetype].network(displayUrl); return _contentRenderers[mimetype]!.network!(displayUrl);
} else { } else {
return CachedNetworkImage( return CachedNetworkImage(
// as we change the url on-error we need a key so that the widget actually updates // as we change the url on-error we need a key so that the widget actually updates
key: ValueKey(displayUrl), key: ValueKey(displayUrl),
imageUrl: displayUrl, imageUrl: displayUrl!,
placeholder: (context, url) { placeholder: (context, url) {
if (!widget.thumbnailOnly && if (!widget.thumbnailOnly &&
displayUrl != thumbnailUrl && displayUrl != thumbnailUrl &&
@ -313,7 +317,7 @@ class _ImageBubbleState extends State<ImageBubble> {
return FutureBuilder<bool>( return FutureBuilder<bool>(
future: (() async { future: (() async {
return await DefaultCacheManager() return await DefaultCacheManager()
.getFileFromCache(thumbnailUrl) != .getFileFromCache(thumbnailUrl!) !=
null; null;
})(), })(),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) { builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
@ -321,8 +325,8 @@ class _ImageBubbleState extends State<ImageBubble> {
return getPlaceholderWidget(); return getPlaceholderWidget();
} }
final effectiveUrl = snapshot.data == true final effectiveUrl = snapshot.data == true
? thumbnailUrl ? thumbnailUrl!
: thumbnailUrlNoAnimated; : thumbnailUrlNoAnimated!;
return CachedNetworkImage( return CachedNetworkImage(
key: ValueKey(effectiveUrl), key: ValueKey(effectiveUrl),
imageUrl: effectiveUrl, imageUrl: effectiveUrl,
@ -348,7 +352,7 @@ class _ImageBubbleState extends State<ImageBubble> {
// the image failed to load but the event has a thumbnail attached....so we can // the image failed to load but the event has a thumbnail attached....so we can
// try to load this one! // try to load this one!
_requestedThumbnailOnFailure = true; _requestedThumbnailOnFailure = true;
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance!.addPostFrameCallback((_) {
setState(() { setState(() {
thumbnailUrl = widget.event thumbnailUrl = widget.event
.getAttachmentUrl( .getAttachmentUrl(
@ -382,11 +386,11 @@ class _ImageBubbleState extends State<ImageBubble> {
OutlinedButton( OutlinedButton(
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
backgroundColor: Theme.of(context).scaffoldBackgroundColor, backgroundColor: Theme.of(context).scaffoldBackgroundColor,
primary: Theme.of(context).textTheme.bodyText1.color, primary: Theme.of(context).textTheme.bodyText1!.color,
), ),
onPressed: () => onTap(context), onPressed: () => onTap(context),
child: Text( child: Text(
L10n.of(context).tapToShowImage, L10n.of(context)!.tapToShowImage,
overflow: TextOverflow.fade, overflow: TextOverflow.fade,
softWrap: false, softWrap: false,
maxLines: 1, maxLines: 1,
@ -394,7 +398,7 @@ class _ImageBubbleState extends State<ImageBubble> {
), ),
if (widget.event.sizeString != null) ...[ if (widget.event.sizeString != null) ...[
const SizedBox(height: 8), const SizedBox(height: 8),
Text(widget.event.sizeString), Text(widget.event.sizeString!),
] ]
], ],
)); ));
@ -451,7 +455,7 @@ class _ImageBubbleState extends State<ImageBubble> {
void onTap(BuildContext context) { void onTap(BuildContext context) {
if (widget.onTap != null) { if (widget.onTap != null) {
widget.onTap(); widget.onTap!();
return; return;
} }
if (!widget.tapToView) return; if (!widget.tapToView) return;
@ -476,8 +480,8 @@ class _ImageBubbleState extends State<ImageBubble> {
} }
class _ImageBubbleContentRenderer { class _ImageBubbleContentRenderer {
final Widget Function(Uint8List, String) memory; final Widget Function(Uint8List, String)? memory;
final Widget Function(String) network; final Widget Function(String?)? network;
_ImageBubbleContentRenderer({this.memory, this.network}); _ImageBubbleContentRenderer({this.memory, this.network});
} }

View File

@ -11,13 +11,13 @@ class MapBubble extends StatelessWidget {
final double height; final double height;
final double radius; final double radius;
const MapBubble({ const MapBubble({
this.latitude, required this.latitude,
this.longitude, required this.longitude,
this.zoom = 14.0, this.zoom = 14.0,
this.width = 400, this.width = 400,
this.height = 400, this.height = 400,
this.radius = 10.0, this.radius = 10.0,
Key key, Key? key,
}) : super(key: key); }) : super(key: key);
@override @override

View File

@ -16,14 +16,14 @@ import 'verification_request_content.dart';
class Message extends StatelessWidget { class Message extends StatelessWidget {
final Event event; final Event event;
final Event nextEvent; final Event? nextEvent;
final void Function(Event) onSelect; final void Function(Event)? onSelect;
final void Function(Event) onAvatarTab; final void Function(Event)? onAvatarTab;
final void Function(Event) onInfoTab; final void Function(Event)? onInfoTab;
final void Function(String) scrollToEventId; final void Function(String)? scrollToEventId;
final void Function(String) unfold; final void Function(String) unfold;
final bool longPressSelect; final bool? longPressSelect;
final bool selected; final bool? selected;
final Timeline timeline; final Timeline timeline;
const Message(this.event, const Message(this.event,
@ -33,10 +33,10 @@ class Message extends StatelessWidget {
this.onInfoTab, this.onInfoTab,
this.onAvatarTab, this.onAvatarTab,
this.scrollToEventId, this.scrollToEventId,
@required this.unfold, required this.unfold,
this.selected, this.selected,
this.timeline, required this.timeline,
Key key}) Key? key})
: super(key: key); : super(key: key);
/// Indicates wheither the user may use a mouse instead /// Indicates wheither the user may use a mouse instead
@ -61,14 +61,14 @@ class Message extends StatelessWidget {
var color = Theme.of(context).appBarTheme.backgroundColor; var color = Theme.of(context).appBarTheme.backgroundColor;
final displayTime = event.type == EventTypes.RoomCreate || final displayTime = event.type == EventTypes.RoomCreate ||
nextEvent == null || nextEvent == null ||
!event.originServerTs.sameEnvironment(nextEvent.originServerTs); !event.originServerTs.sameEnvironment(nextEvent!.originServerTs);
final sameSender = nextEvent != null && final sameSender = nextEvent != null &&
[ [
EventTypes.Message, EventTypes.Message,
EventTypes.Sticker, EventTypes.Sticker,
EventTypes.Encrypted, EventTypes.Encrypted,
].contains(nextEvent.type) ].contains(nextEvent!.type)
? nextEvent.sender.id == event.sender.id && !displayTime ? nextEvent!.sender.id == event.sender.id && !displayTime
: false; : false;
final textColor = ownMessage final textColor = ownMessage
? Colors.white ? Colors.white
@ -119,7 +119,7 @@ class Message extends StatelessWidget {
: Avatar( : Avatar(
mxContent: event.sender.avatarUrl, mxContent: event.sender.avatarUrl,
name: event.sender.calcDisplayname(), name: event.sender.calcDisplayname(),
onTap: () => onAvatarTab(event), onTap: () => onAvatarTab!(event),
), ),
Expanded( Expanded(
child: Column( child: Column(
@ -152,10 +152,11 @@ class Message extends StatelessWidget {
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
child: InkWell( child: InkWell(
onHover: (b) => useMouse = true, onHover: (b) => useMouse = true,
onTap: !useMouse && longPressSelect onTap: !useMouse && longPressSelect!
? () => null ? () {}
: () => onSelect(event), : () => onSelect!(event),
onLongPress: !longPressSelect ? null : () => onSelect(event), onLongPress:
!longPressSelect! ? null : () => onSelect!(event),
borderRadius: borderRadius, borderRadius: borderRadius,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -175,13 +176,13 @@ class Message extends StatelessWidget {
children: <Widget>[ children: <Widget>[
if (event.relationshipType == if (event.relationshipType ==
RelationshipTypes.reply) RelationshipTypes.reply)
FutureBuilder<Event>( FutureBuilder<Event?>(
future: event.getReplyEvent(timeline), future: event.getReplyEvent(timeline),
builder: (BuildContext context, snapshot) { builder: (BuildContext context, snapshot) {
final replyEvent = snapshot.hasData final replyEvent = snapshot.hasData
? snapshot.data ? snapshot.data!
: Event( : Event(
eventId: event.relationshipEventId, eventId: event.relationshipEventId!,
content: { content: {
'msgtype': 'm.text', 'msgtype': 'm.text',
'body': '...' 'body': '...'
@ -195,7 +196,7 @@ class Message extends StatelessWidget {
return InkWell( return InkWell(
onTap: () { onTap: () {
if (scrollToEventId != null) { if (scrollToEventId != null) {
scrollToEventId(replyEvent.eventId); scrollToEventId!(replyEvent.eventId);
} }
}, },
child: AbsorbPointer( child: AbsorbPointer(
@ -300,7 +301,7 @@ class Message extends StatelessWidget {
return Center( return Center(
child: Container( child: Container(
color: selected color: selected!
? Theme.of(context).primaryColor.withAlpha(100) ? Theme.of(context).primaryColor.withAlpha(100)
: Theme.of(context).primaryColor.withAlpha(0), : Theme.of(context).primaryColor.withAlpha(0),
constraints: constraints:

View File

@ -22,10 +22,10 @@ import 'sticker.dart';
class MessageContent extends StatelessWidget { class MessageContent extends StatelessWidget {
final Event event; final Event event;
final Color textColor; final Color? textColor;
final void Function(Event) onInfoTab; final void Function(Event)? onInfoTab;
const MessageContent(this.event, {this.onInfoTab, Key key, this.textColor}) const MessageContent(this.event, {this.onInfoTab, Key? key, this.textColor})
: super(key: key); : super(key: key);
void _verifyOrRequestKey(BuildContext context) async { void _verifyOrRequestKey(BuildContext context) async {
@ -33,15 +33,15 @@ class MessageContent extends StatelessWidget {
ScaffoldMessenger.of(context).showSnackBar(SnackBar( ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text( content: Text(
event.type == EventTypes.Encrypted event.type == EventTypes.Encrypted
? L10n.of(context).needPantalaimonWarning ? L10n.of(context)!.needPantalaimonWarning
: event.getLocalizedBody( : event.getLocalizedBody(
MatrixLocals(L10n.of(context)), MatrixLocals(L10n.of(context)!),
), ),
))); )));
return; return;
} }
final client = Matrix.of(context).client; final client = Matrix.of(context).client;
if (client.isUnknownSession && client.encryption.crossSigning.enabled) { if (client.isUnknownSession && client.encryption!.crossSigning.enabled) {
await BootstrapDialog( await BootstrapDialog(
client: Matrix.of(context).client, client: Matrix.of(context).client,
).show(context); ).show(context);
@ -55,7 +55,7 @@ class MessageContent extends StatelessWidget {
); );
if (success.error == null) { if (success.error == null) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar( ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(L10n.of(context).requestToReadOlderMessages))); content: Text(L10n.of(context)!.requestToReadOlderMessages)));
} }
} }
} }
@ -83,17 +83,17 @@ class MessageContent extends StatelessWidget {
if (PlatformInfos.isMobile) { if (PlatformInfos.isMobile) {
return AudioPlayerWidget( return AudioPlayerWidget(
event, event,
color: textColor, color: textColor!,
); );
} }
return MessageDownloadContent(event, textColor); return MessageDownloadContent(event, textColor!);
case MessageTypes.Video: case MessageTypes.Video:
if (PlatformInfos.isMobile || PlatformInfos.isWeb) { if (PlatformInfos.isMobile || PlatformInfos.isWeb) {
return EventVideoPlayer(event); return EventVideoPlayer(event);
} }
return MessageDownloadContent(event, textColor); return MessageDownloadContent(event, textColor!);
case MessageTypes.File: case MessageTypes.File:
return MessageDownloadContent(event, textColor); return MessageDownloadContent(event, textColor!);
case MessageTypes.Text: case MessageTypes.Text:
case MessageTypes.Notice: case MessageTypes.Notice:
@ -115,7 +115,7 @@ class MessageContent extends StatelessWidget {
fontSize: bigEmotes ? fontSize * 3 : fontSize, fontSize: bigEmotes ? fontSize * 3 : fontSize,
), ),
linkStyle: TextStyle( linkStyle: TextStyle(
color: textColor.withAlpha(150), color: textColor!.withAlpha(150),
fontSize: bigEmotes ? fontSize * 3 : fontSize, fontSize: bigEmotes ? fontSize * 3 : fontSize,
decoration: TextDecoration.underline, decoration: TextDecoration.underline,
), ),
@ -131,14 +131,12 @@ class MessageContent extends StatelessWidget {
textColor: buttonTextColor, textColor: buttonTextColor,
onPressed: () => _verifyOrRequestKey(context), onPressed: () => _verifyOrRequestKey(context),
icon: const Icon(Icons.lock_outline), icon: const Icon(Icons.lock_outline),
label: L10n.of(context).encrypted, label: L10n.of(context)!.encrypted,
); );
case MessageTypes.Location: case MessageTypes.Location:
final geoUri = final geoUri =
Uri.tryParse(event.content.tryGet<String>('geo_uri')); Uri.tryParse(event.content.tryGet<String>('geo_uri')!);
if (geoUri != null && if (geoUri != null && geoUri.scheme == 'geo') {
geoUri.scheme == 'geo' &&
geoUri.path != null) {
final latlong = geoUri.path final latlong = geoUri.path
.split(';') .split(';')
.first .first
@ -152,8 +150,8 @@ class MessageContent extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
MapBubble( MapBubble(
latitude: latlong.first, latitude: latlong.first!,
longitude: latlong.last, longitude: latlong.last!,
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
OutlinedButton.icon( OutlinedButton.icon(
@ -161,7 +159,7 @@ class MessageContent extends StatelessWidget {
onPressed: onPressed:
UrlLauncher(context, geoUri.toString()).launchUrl, UrlLauncher(context, geoUri.toString()).launchUrl,
label: Text( label: Text(
L10n.of(context).openInMaps, L10n.of(context)!.openInMaps,
style: TextStyle(color: textColor), style: TextStyle(color: textColor),
), ),
), ),
@ -177,24 +175,24 @@ class MessageContent extends StatelessWidget {
return _ButtonContent( return _ButtonContent(
onPressed: () => launch(event.body), onPressed: () => launch(event.body),
icon: const Icon(Icons.phone_outlined, color: Colors.green), icon: const Icon(Icons.phone_outlined, color: Colors.green),
label: L10n.of(context).videoCall, label: L10n.of(context)!.videoCall,
textColor: buttonTextColor, textColor: buttonTextColor,
); );
} }
if (event.redacted) { if (event.redacted) {
return _ButtonContent( return _ButtonContent(
label: L10n.of(context) label: L10n.of(context)!
.redactedAnEvent(event.sender.calcDisplayname()), .redactedAnEvent(event.sender.calcDisplayname()),
icon: const Icon(Icons.delete_outlined), icon: const Icon(Icons.delete_outlined),
textColor: buttonTextColor, textColor: buttonTextColor,
onPressed: () => onInfoTab(event), onPressed: () => onInfoTab!(event),
); );
} }
final bigEmotes = event.onlyEmotes && final bigEmotes = event.onlyEmotes &&
event.numberEmotes > 0 && event.numberEmotes > 0 &&
event.numberEmotes <= 10; event.numberEmotes <= 10;
return LinkText( return LinkText(
text: event.getLocalizedBody(MatrixLocals(L10n.of(context)), text: event.getLocalizedBody(MatrixLocals(L10n.of(context)!),
hideReply: true), hideReply: true),
textStyle: TextStyle( textStyle: TextStyle(
color: textColor, color: textColor,
@ -202,24 +200,22 @@ class MessageContent extends StatelessWidget {
decoration: event.redacted ? TextDecoration.lineThrough : null, decoration: event.redacted ? TextDecoration.lineThrough : null,
), ),
linkStyle: TextStyle( linkStyle: TextStyle(
color: textColor.withAlpha(150), color: textColor!.withAlpha(150),
fontSize: bigEmotes ? fontSize * 3 : fontSize, fontSize: bigEmotes ? fontSize * 3 : fontSize,
decoration: TextDecoration.underline, decoration: TextDecoration.underline,
), ),
onLinkTap: (url) => UrlLauncher(context, url).launchUrl(), onLinkTap: (url) => UrlLauncher(context, url).launchUrl(),
); );
} }
break;
default: default:
return _ButtonContent( return _ButtonContent(
label: L10n.of(context) label: L10n.of(context)!
.userSentUnknownEvent(event.sender.calcDisplayname(), event.type), .userSentUnknownEvent(event.sender.calcDisplayname(), event.type),
icon: const Icon(Icons.info_outlined), icon: const Icon(Icons.info_outlined),
textColor: buttonTextColor, textColor: buttonTextColor,
onPressed: () => onInfoTab(event), onPressed: () => onInfoTab!(event),
); );
} }
return Container(); // else flutter analyze complains
} }
} }
@ -227,14 +223,14 @@ class _ButtonContent extends StatelessWidget {
final void Function() onPressed; final void Function() onPressed;
final String label; final String label;
final Icon icon; final Icon icon;
final Color textColor; final Color? textColor;
const _ButtonContent({ const _ButtonContent({
@required this.label, required this.label,
@required this.icon, required this.icon,
@required this.textColor, required this.textColor,
@required this.onPressed, required this.onPressed,
Key key, Key? key,
}) : super(key: key); }) : super(key: key);
@override @override

View File

@ -1,5 +1,3 @@
//@dart=2.12
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';

View File

@ -2,6 +2,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:collection/collection.dart' show IterableExtension;
import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
@ -14,14 +15,14 @@ class MessageReactions extends StatelessWidget {
final Event event; final Event event;
final Timeline timeline; final Timeline timeline;
const MessageReactions(this.event, this.timeline, {Key key}) const MessageReactions(this.event, this.timeline, {Key? key})
: super(key: key); : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final allReactionEvents = final allReactionEvents =
event.aggregatedEvents(timeline, RelationshipTypes.reaction); event.aggregatedEvents(timeline, RelationshipTypes.reaction);
final reactionMap = <String, _ReactionEntry>{}; final reactionMap = <String?, _ReactionEntry>{};
final client = Matrix.of(context).client; final client = Matrix.of(context).client;
for (final e in allReactionEvents) { for (final e in allReactionEvents) {
@ -35,9 +36,9 @@ class MessageReactions extends StatelessWidget {
reactors: [], reactors: [],
); );
} }
reactionMap[key].count++; reactionMap[key]!.count++;
reactionMap[key].reactors.add(e.sender); reactionMap[key]!.reactors!.add(e.sender);
reactionMap[key].reacted |= e.senderId == e.room.client.userID; reactionMap[key]!.reacted |= e.senderId == e.room.client.userID;
} }
} }
@ -52,11 +53,9 @@ class MessageReactions extends StatelessWidget {
reacted: r.reacted, reacted: r.reacted,
onTap: () { onTap: () {
if (r.reacted) { if (r.reacted) {
final evt = allReactionEvents.firstWhere( final evt = allReactionEvents.firstWhereOrNull((e) =>
(e) =>
e.senderId == e.room.client.userID && e.senderId == e.room.client.userID &&
e.content['m.relates_to']['key'] == r.key, e.content['m.relates_to']['key'] == r.key);
orElse: () => null);
if (evt != null) { if (evt != null) {
showFutureLoadingDialog( showFutureLoadingDialog(
context: context, context: context,
@ -67,7 +66,7 @@ class MessageReactions extends StatelessWidget {
showFutureLoadingDialog( showFutureLoadingDialog(
context: context, context: context,
future: () => future: () =>
event.room.sendReaction(event.eventId, r.key)); event.room.sendReaction(event.eventId, r.key!));
} }
}, },
onLongPress: () async => await _AdaptableReactorsDialog( onLongPress: () async => await _AdaptableReactorsDialog(
@ -91,11 +90,11 @@ class MessageReactions extends StatelessWidget {
} }
class _Reaction extends StatelessWidget { class _Reaction extends StatelessWidget {
final String reactionKey; final String? reactionKey;
final int count; final int? count;
final bool reacted; final bool? reacted;
final void Function() onTap; final void Function()? onTap;
final void Function() onLongPress; final void Function()? onLongPress;
const _Reaction({ const _Reaction({
this.reactionKey, this.reactionKey,
@ -113,11 +112,11 @@ class _Reaction extends StatelessWidget {
final color = Theme.of(context).scaffoldBackgroundColor; final color = Theme.of(context).scaffoldBackgroundColor;
final fontSize = DefaultTextStyle.of(context).style.fontSize; final fontSize = DefaultTextStyle.of(context).style.fontSize;
Widget content; Widget content;
if (reactionKey.startsWith('mxc://')) { if (reactionKey!.startsWith('mxc://')) {
final src = Uri.parse(reactionKey)?.getThumbnail( final src = Uri.parse(reactionKey!).getThumbnail(
Matrix.of(context).client, Matrix.of(context).client,
width: 9999, width: 9999,
height: fontSize * MediaQuery.of(context).devicePixelRatio, height: fontSize! * MediaQuery.of(context).devicePixelRatio,
method: ThumbnailMethod.scale, method: ThumbnailMethod.scale,
); );
content = Row( content = Row(
@ -136,7 +135,7 @@ class _Reaction extends StatelessWidget {
], ],
); );
} else { } else {
var renderKey = Characters(reactionKey); var renderKey = Characters(reactionKey!);
if (renderKey.length > 10) { if (renderKey.length > 10) {
renderKey = renderKey.getRange(0, 9) + Characters(''); renderKey = renderKey.getRange(0, 9) + Characters('');
} }
@ -147,13 +146,13 @@ class _Reaction extends StatelessWidget {
)); ));
} }
return InkWell( return InkWell(
onTap: () => onTap != null ? onTap() : null, onTap: () => onTap != null ? onTap!() : null,
onLongPress: () => onLongPress != null ? onLongPress() : null, onLongPress: () => onLongPress != null ? onLongPress!() : null,
borderRadius: BorderRadius.circular(AppConfig.borderRadius), borderRadius: BorderRadius.circular(AppConfig.borderRadius),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: color, color: color,
border: reacted border: reacted!
? Border.all( ? Border.all(
width: 1, width: 1,
color: Theme.of(context).primaryColor, color: Theme.of(context).primaryColor,
@ -169,25 +168,30 @@ class _Reaction extends StatelessWidget {
} }
class _ReactionEntry { class _ReactionEntry {
String key; String? key;
int count; int count;
bool reacted; bool reacted;
List<User> reactors; List<User>? reactors;
_ReactionEntry({this.key, this.count, this.reacted, this.reactors}); _ReactionEntry({
this.key,
required this.count,
required this.reacted,
this.reactors,
});
} }
class _AdaptableReactorsDialog extends StatelessWidget { class _AdaptableReactorsDialog extends StatelessWidget {
final Client client; final Client? client;
final _ReactionEntry reactionEntry; final _ReactionEntry? reactionEntry;
const _AdaptableReactorsDialog({ const _AdaptableReactorsDialog({
Key key, Key? key,
this.client, this.client,
this.reactionEntry, this.reactionEntry,
}) : super(key: key); }) : super(key: key);
Future<bool> show(BuildContext context) => PlatformInfos.isCupertinoStyle Future<bool?> show(BuildContext context) => PlatformInfos.isCupertinoStyle
? showCupertinoDialog( ? showCupertinoDialog(
context: context, context: context,
builder: (context) => this, builder: (context) => this,
@ -209,20 +213,20 @@ class _AdaptableReactorsDialog extends StatelessWidget {
runSpacing: 4.0, runSpacing: 4.0,
alignment: WrapAlignment.center, alignment: WrapAlignment.center,
children: <Widget>[ children: <Widget>[
for (var reactor in reactionEntry.reactors) for (var reactor in reactionEntry!.reactors!)
Chip( Chip(
avatar: Avatar( avatar: Avatar(
mxContent: reactor.avatarUrl, mxContent: reactor.avatarUrl,
name: reactor.displayName, name: reactor.displayName,
client: client, client: client,
), ),
label: Text(reactor.displayName), label: Text(reactor.displayName!),
), ),
], ],
), ),
); );
final title = Center(child: Text(reactionEntry.key)); final title = Center(child: Text(reactionEntry!.key!));
return PlatformInfos.isCupertinoStyle return PlatformInfos.isCupertinoStyle
? CupertinoAlertDialog( ? CupertinoAlertDialog(

View File

@ -10,21 +10,23 @@ import 'html_message.dart';
class ReplyContent extends StatelessWidget { class ReplyContent extends StatelessWidget {
final Event replyEvent; final Event replyEvent;
final bool lightText; final bool lightText;
final Timeline timeline; final Timeline? timeline;
const ReplyContent(this.replyEvent, const ReplyContent(
{this.lightText = false, Key key, this.timeline}) this.replyEvent, {
: super(key: key); this.lightText = false,
Key? key,
this.timeline,
}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget replyBody; Widget replyBody;
final displayEvent = replyEvent != null && timeline != null final timeline = this.timeline;
? replyEvent.getDisplayEvent(timeline) final displayEvent =
: replyEvent; timeline != null ? replyEvent.getDisplayEvent(timeline) : replyEvent;
final fontSize = AppConfig.messageFontSize * AppConfig.fontSizeFactor; final fontSize = AppConfig.messageFontSize * AppConfig.fontSizeFactor;
if (displayEvent != null && if (AppConfig.renderHtml &&
AppConfig.renderHtml &&
[EventTypes.Message, EventTypes.Encrypted] [EventTypes.Message, EventTypes.Encrypted]
.contains(displayEvent.type) && .contains(displayEvent.type) &&
[MessageTypes.Text, MessageTypes.Notice, MessageTypes.Emote] [MessageTypes.Text, MessageTypes.Notice, MessageTypes.Emote]
@ -32,16 +34,16 @@ class ReplyContent extends StatelessWidget {
!displayEvent.redacted && !displayEvent.redacted &&
displayEvent.content['format'] == 'org.matrix.custom.html' && displayEvent.content['format'] == 'org.matrix.custom.html' &&
displayEvent.content['formatted_body'] is String) { displayEvent.content['formatted_body'] is String) {
String html = displayEvent.content['formatted_body']; String? html = displayEvent.content['formatted_body'];
if (displayEvent.messageType == MessageTypes.Emote) { if (displayEvent.messageType == MessageTypes.Emote) {
html = '* $html'; html = '* $html';
} }
replyBody = HtmlMessage( replyBody = HtmlMessage(
html: html, html: html!,
defaultTextStyle: TextStyle( defaultTextStyle: TextStyle(
color: lightText color: lightText
? Colors.white ? Colors.white
: Theme.of(context).textTheme.bodyText2.color, : Theme.of(context).textTheme.bodyText2!.color,
fontSize: fontSize, fontSize: fontSize,
), ),
maxLines: 1, maxLines: 1,
@ -50,18 +52,17 @@ class ReplyContent extends StatelessWidget {
); );
} else { } else {
replyBody = Text( replyBody = Text(
displayEvent?.getLocalizedBody( displayEvent.getLocalizedBody(
MatrixLocals(L10n.of(context)), MatrixLocals(L10n.of(context)!),
withSenderNamePrefix: false, withSenderNamePrefix: false,
hideReply: true, hideReply: true,
) ?? ),
'',
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
maxLines: 1, maxLines: 1,
style: TextStyle( style: TextStyle(
color: lightText color: lightText
? Colors.white ? Colors.white
: Theme.of(context).textTheme.bodyText2.color, : Theme.of(context).textTheme.bodyText2!.color,
fontSize: fontSize, fontSize: fontSize,
), ),
); );
@ -81,7 +82,7 @@ class ReplyContent extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Text( Text(
(displayEvent?.sender?.calcDisplayname() ?? '') + ':', displayEvent.sender.calcDisplayname() + ':',
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(

View File

@ -9,16 +9,16 @@ import '../../../config/app_config.dart';
class StateMessage extends StatelessWidget { class StateMessage extends StatelessWidget {
final Event event; final Event event;
final void Function(String) unfold; final void Function(String) unfold;
const StateMessage(this.event, {@required this.unfold, Key key}) const StateMessage(this.event, {required this.unfold, Key? key})
: super(key: key); : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (event.unsigned['im.fluffychat.collapsed_state_event'] == true) { if (event.unsigned!['im.fluffychat.collapsed_state_event'] == true) {
return Container(); return Container();
} }
final int counter = final int counter =
event.unsigned['im.fluffychat.collapsed_state_event_count'] ?? 0; event.unsigned!['im.fluffychat.collapsed_state_event_count'] ?? 0;
return Padding( return Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 8.0, horizontal: 8.0,
@ -40,18 +40,18 @@ class StateMessage extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
event.getLocalizedBody(MatrixLocals(L10n.of(context))), event.getLocalizedBody(MatrixLocals(L10n.of(context)!)),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 14 * AppConfig.fontSizeFactor, fontSize: 14 * AppConfig.fontSizeFactor,
color: Theme.of(context).textTheme.bodyText2.color, color: Theme.of(context).textTheme.bodyText2!.color,
decoration: decoration:
event.redacted ? TextDecoration.lineThrough : null, event.redacted ? TextDecoration.lineThrough : null,
), ),
), ),
if (counter != 0) if (counter != 0)
Text( Text(
L10n.of(context).moreEvents(counter), L10n.of(context)!.moreEvents(counter),
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 14 * AppConfig.fontSizeFactor, fontSize: 14 * AppConfig.fontSizeFactor,

View File

@ -10,14 +10,14 @@ import 'image_bubble.dart';
class Sticker extends StatefulWidget { class Sticker extends StatefulWidget {
final Event event; final Event event;
const Sticker(this.event, {Key key}) : super(key: key); const Sticker(this.event, {Key? key}) : super(key: key);
@override @override
_StickerState createState() => _StickerState(); _StickerState createState() => _StickerState();
} }
class _StickerState extends State<Sticker> { class _StickerState extends State<Sticker> {
bool animated; bool? animated;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -31,7 +31,7 @@ class _StickerState extends State<Sticker> {
showOkAlertDialog( showOkAlertDialog(
context: context, context: context,
message: widget.event.body, message: widget.event.body,
okLabel: L10n.of(context).ok, okLabel: L10n.of(context)!.ok,
); );
}, },
animated: animated ?? AppConfig.autoplayImages, animated: animated ?? AppConfig.autoplayImages,

View File

@ -9,7 +9,8 @@ class VerificationRequestContent extends StatelessWidget {
final Event event; final Event event;
final Timeline timeline; final Timeline timeline;
const VerificationRequestContent({this.event, this.timeline, Key key}) const VerificationRequestContent(
{required this.event, required this.timeline, Key? key})
: super(key: key); : super(key: key);
@override @override
@ -50,10 +51,10 @@ class VerificationRequestContent extends StatelessWidget {
Text(canceled Text(canceled
? 'Error ${cancel.first.content.tryGet<String>('code')}: ${cancel.first.content.tryGet<String>('reason')}' ? 'Error ${cancel.first.content.tryGet<String>('code')}: ${cancel.first.content.tryGet<String>('reason')}'
: (fullyDone : (fullyDone
? L10n.of(context).verifySuccess ? L10n.of(context)!.verifySuccess
: (started : (started
? L10n.of(context).loadingPleaseWait ? L10n.of(context)!.loadingPleaseWait
: L10n.of(context).newVerificationRequest))) : L10n.of(context)!.newVerificationRequest)))
], ],
), ),
), ),

View File

@ -1,5 +1,3 @@
//@dart=2.12
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';

View File

@ -15,19 +15,19 @@ import 'command_hints.dart';
class InputBar extends StatelessWidget { class InputBar extends StatelessWidget {
final Room room; final Room room;
final int minLines; final int? minLines;
final int maxLines; final int? maxLines;
final TextInputType keyboardType; final TextInputType? keyboardType;
final TextInputAction textInputAction; final TextInputAction? textInputAction;
final ValueChanged<String> onSubmitted; final ValueChanged<String>? onSubmitted;
final FocusNode focusNode; final FocusNode? focusNode;
final TextEditingController controller; final TextEditingController? controller;
final InputDecoration decoration; final InputDecoration? decoration;
final ValueChanged<String> onChanged; final ValueChanged<String>? onChanged;
final bool autofocus; final bool? autofocus;
const InputBar({ const InputBar({
this.room, required this.room,
this.minLines, this.minLines,
this.maxLines, this.maxLines,
this.keyboardType, this.keyboardType,
@ -38,22 +38,23 @@ class InputBar extends StatelessWidget {
this.onChanged, this.onChanged,
this.autofocus, this.autofocus,
this.textInputAction, this.textInputAction,
Key key, Key? key,
}) : super(key: key); }) : super(key: key);
List<Map<String, String>> getSuggestions(String text) { List<Map<String, String?>> getSuggestions(String text) {
if (controller.selection.baseOffset != controller.selection.extentOffset || if (controller!.selection.baseOffset !=
controller.selection.baseOffset < 0) { controller!.selection.extentOffset ||
controller!.selection.baseOffset < 0) {
return []; // no entries if there is selected text return []; // no entries if there is selected text
} }
final searchText = final searchText =
controller.text.substring(0, controller.selection.baseOffset); controller!.text.substring(0, controller!.selection.baseOffset);
final ret = <Map<String, String>>[]; final List<Map<String, String?>> ret = <Map<String, String>>[];
const maxResults = 30; const maxResults = 30;
final commandMatch = RegExp(r'^\/([\w]*)$').firstMatch(searchText); final commandMatch = RegExp(r'^\/([\w]*)$').firstMatch(searchText);
if (commandMatch != null) { if (commandMatch != null) {
final commandSearch = commandMatch[1].toLowerCase(); final commandSearch = commandMatch[1]!.toLowerCase();
for (final command in room.client.commands.keys) { for (final command in room.client.commands.keys) {
if (command.contains(commandSearch)) { if (command.contains(commandSearch)) {
ret.add({ ret.add({
@ -69,7 +70,7 @@ class InputBar extends StatelessWidget {
RegExp(r'(?:\s|^):(?:([-\w]+)~)?([-\w]+)$').firstMatch(searchText); RegExp(r'(?:\s|^):(?:([-\w]+)~)?([-\w]+)$').firstMatch(searchText);
if (emojiMatch != null) { if (emojiMatch != null) {
final packSearch = emojiMatch[1]; final packSearch = emojiMatch[1];
final emoteSearch = emojiMatch[2].toLowerCase(); final emoteSearch = emojiMatch[2]!.toLowerCase();
final emotePacks = room.getImagePacks(ImagePackUsage.emoticon); final emotePacks = room.getImagePacks(ImagePackUsage.emoticon);
if (packSearch == null || packSearch.isEmpty) { if (packSearch == null || packSearch.isEmpty) {
for (final pack in emotePacks.entries) { for (final pack in emotePacks.entries) {
@ -93,16 +94,16 @@ class InputBar extends StatelessWidget {
} }
} }
} else if (emotePacks[packSearch] != null) { } else if (emotePacks[packSearch] != null) {
for (final emote in emotePacks[packSearch].images.entries) { for (final emote in emotePacks[packSearch]!.images.entries) {
if (emote.key.toLowerCase().contains(emoteSearch)) { if (emote.key.toLowerCase().contains(emoteSearch)) {
ret.add({ ret.add({
'type': 'emote', 'type': 'emote',
'name': emote.key, 'name': emote.key,
'pack': packSearch, 'pack': packSearch,
'pack_avatar_url': 'pack_avatar_url':
emotePacks[packSearch].pack.avatarUrl?.toString(), emotePacks[packSearch]!.pack.avatarUrl?.toString(),
'pack_display_name': 'pack_display_name':
emotePacks[packSearch].pack.displayName ?? packSearch, emotePacks[packSearch]!.pack.displayName ?? packSearch,
'mxc': emote.value.url.toString(), 'mxc': emote.value.url.toString(),
}); });
} }
@ -114,11 +115,11 @@ class InputBar extends StatelessWidget {
} }
final userMatch = RegExp(r'(?:\s|^)@([-\w]+)$').firstMatch(searchText); final userMatch = RegExp(r'(?:\s|^)@([-\w]+)$').firstMatch(searchText);
if (userMatch != null) { if (userMatch != null) {
final userSearch = userMatch[1].toLowerCase(); final userSearch = userMatch[1]!.toLowerCase();
for (final user in room.getParticipants()) { for (final user in room.getParticipants()) {
if ((user.displayName != null && if ((user.displayName != null &&
(user.displayName.toLowerCase().contains(userSearch) || (user.displayName!.toLowerCase().contains(userSearch) ||
slugify(user.displayName.toLowerCase()) slugify(user.displayName!.toLowerCase())
.contains(userSearch))) || .contains(userSearch))) ||
user.id.split(':')[0].toLowerCase().contains(userSearch)) { user.id.split(':')[0].toLowerCase().contains(userSearch)) {
ret.add({ ret.add({
@ -136,7 +137,7 @@ class InputBar extends StatelessWidget {
} }
final roomMatch = RegExp(r'(?:\s|^)#([-\w]+)$').firstMatch(searchText); final roomMatch = RegExp(r'(?:\s|^)#([-\w]+)$').firstMatch(searchText);
if (roomMatch != null) { if (roomMatch != null) {
final roomSearch = roomMatch[1].toLowerCase(); final roomSearch = roomMatch[1]!.toLowerCase();
for (final r in room.client.rooms) { for (final r in room.client.rooms) {
if (r.getState(EventTypes.RoomTombstone) != null) { if (r.getState(EventTypes.RoomTombstone) != null) {
continue; // we don't care about tombstoned rooms continue; // we don't care about tombstoned rooms
@ -155,12 +156,10 @@ class InputBar extends StatelessWidget {
.split(':')[0] .split(':')[0]
.toLowerCase() .toLowerCase()
.contains(roomSearch))))) || .contains(roomSearch))))) ||
(r.name != null && r.name.toLowerCase().contains(roomSearch))) { (r.name.toLowerCase().contains(roomSearch))) {
ret.add({ ret.add({
'type': 'room', 'type': 'room',
'mxid': (r.canonicalAlias != null && r.canonicalAlias.isNotEmpty) 'mxid': (r.canonicalAlias.isNotEmpty) ? r.canonicalAlias : r.id,
? r.canonicalAlias
: r.id,
'displayname': r.displayname, 'displayname': r.displayname,
'avatar_url': r.avatar?.toString(), 'avatar_url': r.avatar?.toString(),
}); });
@ -175,14 +174,14 @@ class InputBar extends StatelessWidget {
Widget buildSuggestion( Widget buildSuggestion(
BuildContext context, BuildContext context,
Map<String, String> suggestion, Map<String, String?> suggestion,
Client client, Client? client,
) { ) {
const size = 30.0; const size = 30.0;
const padding = EdgeInsets.all(4.0); const padding = EdgeInsets.all(4.0);
if (suggestion['type'] == 'command') { if (suggestion['type'] == 'command') {
final command = suggestion['name']; final command = suggestion['name']!;
final hint = commandHint(L10n.of(context), command); final hint = commandHint(L10n.of(context)!, command);
return Tooltip( return Tooltip(
message: hint, message: hint,
waitDuration: const Duration(days: 1), // don't show on hover waitDuration: const Duration(days: 1), // don't show on hover
@ -206,7 +205,7 @@ class InputBar extends StatelessWidget {
} }
if (suggestion['type'] == 'emote') { if (suggestion['type'] == 'emote') {
final ratio = MediaQuery.of(context).devicePixelRatio; final ratio = MediaQuery.of(context).devicePixelRatio;
final url = Uri.parse(suggestion['mxc'] ?? '')?.getThumbnail( final url = Uri.parse(suggestion['mxc'] ?? '').getThumbnail(
room.client, room.client,
width: size * ratio, width: size * ratio,
height: size * ratio, height: size * ratio,
@ -224,7 +223,7 @@ class InputBar extends StatelessWidget {
height: size, height: size,
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
Text(suggestion['name']), Text(suggestion['name']!),
Expanded( Expanded(
child: Align( child: Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
@ -239,7 +238,7 @@ class InputBar extends StatelessWidget {
size: size * 0.9, size: size * 0.9,
client: client, client: client,
) )
: Text(suggestion['pack_display_name']), : Text(suggestion['pack_display_name']!),
), ),
), ),
), ),
@ -262,7 +261,7 @@ class InputBar extends StatelessWidget {
client: client, client: client,
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
Text(suggestion['displayname'] ?? suggestion['mxid']), Text(suggestion['displayname'] ?? suggestion['mxid']!),
], ],
), ),
); );
@ -270,16 +269,16 @@ class InputBar extends StatelessWidget {
return Container(); return Container();
} }
void insertSuggestion(_, Map<String, String> suggestion) { void insertSuggestion(_, Map<String, String?> suggestion) {
final replaceText = final replaceText =
controller.text.substring(0, controller.selection.baseOffset); controller!.text.substring(0, controller!.selection.baseOffset);
var startText = ''; var startText = '';
final afterText = replaceText == controller.text final afterText = replaceText == controller!.text
? '' ? ''
: controller.text.substring(controller.selection.baseOffset + 1); : controller!.text.substring(controller!.selection.baseOffset + 1);
var insertText = ''; var insertText = '';
if (suggestion['type'] == 'command') { if (suggestion['type'] == 'command') {
insertText = suggestion['name'] + ' '; insertText = suggestion['name']! + ' ';
startText = replaceText.replaceAllMapped( startText = replaceText.replaceAllMapped(
RegExp(r'^(\/[\w]*)$'), RegExp(r'^(\/[\w]*)$'),
(Match m) => '/' + insertText, (Match m) => '/' + insertText,
@ -304,29 +303,29 @@ class InputBar extends StatelessWidget {
break; break;
} }
} }
insertText = ':${isUnique ? '' : insertPack + '~'}$insertEmote: '; insertText = ':${isUnique ? '' : insertPack! + '~'}$insertEmote: ';
startText = replaceText.replaceAllMapped( startText = replaceText.replaceAllMapped(
RegExp(r'(\s|^)(:(?:[-\w]+~)?[-\w]+)$'), RegExp(r'(\s|^)(:(?:[-\w]+~)?[-\w]+)$'),
(Match m) => '${m[1]}$insertText', (Match m) => '${m[1]}$insertText',
); );
} }
if (suggestion['type'] == 'user') { if (suggestion['type'] == 'user') {
insertText = suggestion['mention'] + ' '; insertText = suggestion['mention']! + ' ';
startText = replaceText.replaceAllMapped( startText = replaceText.replaceAllMapped(
RegExp(r'(\s|^)(@[-\w]+)$'), RegExp(r'(\s|^)(@[-\w]+)$'),
(Match m) => '${m[1]}$insertText', (Match m) => '${m[1]}$insertText',
); );
} }
if (suggestion['type'] == 'room') { if (suggestion['type'] == 'room') {
insertText = suggestion['mxid'] + ' '; insertText = suggestion['mxid']! + ' ';
startText = replaceText.replaceAllMapped( startText = replaceText.replaceAllMapped(
RegExp(r'(\s|^)(#[-\w]+)$'), RegExp(r'(\s|^)(#[-\w]+)$'),
(Match m) => '${m[1]}$insertText', (Match m) => '${m[1]}$insertText',
); );
} }
if (insertText.isNotEmpty && startText.isNotEmpty) { if (insertText.isNotEmpty && startText.isNotEmpty) {
controller.text = startText + afterText; controller!.text = startText + afterText;
controller.selection = TextSelection( controller!.selection = TextSelection(
baseOffset: startText.length, baseOffset: startText.length,
extentOffset: startText.length, extentOffset: startText.length,
); );
@ -351,13 +350,13 @@ class InputBar extends StatelessWidget {
? {} ? {}
: { : {
NewLineIntent: CallbackAction(onInvoke: (i) { NewLineIntent: CallbackAction(onInvoke: (i) {
final val = controller.value; final val = controller!.value;
final selection = val.selection.start; final selection = val.selection.start;
final messageWithoutNewLine = final messageWithoutNewLine =
controller.text.substring(0, val.selection.start) + controller!.text.substring(0, val.selection.start) +
'\n' + '\n' +
controller.text.substring(val.selection.end); controller!.text.substring(val.selection.end);
controller.value = TextEditingValue( controller!.value = TextEditingValue(
text: messageWithoutNewLine, text: messageWithoutNewLine,
selection: TextSelection.fromPosition( selection: TextSelection.fromPosition(
TextPosition(offset: selection + 1), TextPosition(offset: selection + 1),
@ -366,11 +365,11 @@ class InputBar extends StatelessWidget {
return null; return null;
}), }),
SubmitLineIntent: CallbackAction(onInvoke: (i) { SubmitLineIntent: CallbackAction(onInvoke: (i) {
onSubmitted(controller.text); onSubmitted!(controller!.text);
return null; return null;
}), }),
}, },
child: TypeAheadField<Map<String, String>>( child: TypeAheadField<Map<String, String?>>(
direction: AxisDirection.up, direction: AxisDirection.up,
hideOnEmpty: true, hideOnEmpty: true,
hideOnLoading: true, hideOnLoading: true,
@ -381,31 +380,31 @@ class InputBar extends StatelessWidget {
textFieldConfiguration: TextFieldConfiguration( textFieldConfiguration: TextFieldConfiguration(
minLines: minLines, minLines: minLines,
maxLines: maxLines, maxLines: maxLines,
keyboardType: keyboardType, keyboardType: keyboardType!,
textInputAction: textInputAction, textInputAction: textInputAction,
autofocus: autofocus, autofocus: autofocus!,
onSubmitted: (text) { onSubmitted: (text) {
// fix for library for now // fix for library for now
// it sets the types for the callback incorrectly // it sets the types for the callback incorrectly
onSubmitted(text); onSubmitted!(text);
}, },
//focusNode: focusNode, //focusNode: focusNode,
controller: controller, controller: controller,
decoration: decoration, decoration: decoration!,
focusNode: focusNode, focusNode: focusNode,
onChanged: (text) { onChanged: (text) {
// fix for the library for now // fix for the library for now
// it sets the types for the callback incorrectly // it sets the types for the callback incorrectly
onChanged(text); onChanged!(text);
}, },
textCapitalization: TextCapitalization.sentences, textCapitalization: TextCapitalization.sentences,
), ),
suggestionsCallback: getSuggestions, suggestionsCallback: getSuggestions,
itemBuilder: (c, s) => itemBuilder: (c, s) =>
buildSuggestion(c, s, Matrix.of(context).client), buildSuggestion(c, s, Matrix.of(context).client),
onSuggestionSelected: (Map<String, String> suggestion) => onSuggestionSelected: (Map<String, String?> suggestion) =>
insertSuggestion(context, suggestion), insertSuggestion(context, suggestion),
errorBuilder: (BuildContext context, Object error) => Container(), errorBuilder: (BuildContext context, Object? error) => Container(),
loadingBuilder: (BuildContext context) => loadingBuilder: (BuildContext context) =>
Container(), // fix loading briefly flickering a dark box Container(), // fix loading briefly flickering a dark box
noItemsFoundBuilder: (BuildContext context) => noItemsFoundBuilder: (BuildContext context) =>

View File

@ -8,14 +8,14 @@ import 'package:fluffychat/pages/chat/chat.dart';
class ReactionsPicker extends StatelessWidget { class ReactionsPicker extends StatelessWidget {
final ChatController controller; final ChatController controller;
const ReactionsPicker(this.controller, {Key key}) : super(key: key); const ReactionsPicker(this.controller, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (controller.showEmojiPicker) return Container(); if (controller.showEmojiPicker) return Container();
final display = controller.editEvent == null && final display = controller.editEvent == null &&
controller.replyEvent == null && controller.replyEvent == null &&
controller.room.canSendDefaultMessages && controller.room!.canSendDefaultMessages &&
controller.selectedEvents.isNotEmpty; controller.selectedEvents.isNotEmpty;
return AnimatedContainer( return AnimatedContainer(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
@ -28,8 +28,9 @@ class ReactionsPicker extends StatelessWidget {
} }
final emojis = List<String>.from(AppEmojis.emojis); final emojis = List<String>.from(AppEmojis.emojis);
final allReactionEvents = controller.selectedEvents.first final allReactionEvents = controller.selectedEvents.first
.aggregatedEvents(controller.timeline, RelationshipTypes.reaction) .aggregatedEvents(
?.where((event) => controller.timeline!, RelationshipTypes.reaction)
.where((event) =>
event.senderId == event.room.client.userID && event.senderId == event.room.client.userID &&
event.type == 'm.reaction'); event.type == 'm.reaction');

View File

@ -1,5 +1,3 @@
//@dart=2.12
import 'dart:async'; import 'dart:async';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';

View File

@ -9,7 +9,7 @@ import 'events/reply_content.dart';
class ReplyDisplay extends StatelessWidget { class ReplyDisplay extends StatelessWidget {
final ChatController controller; final ChatController controller;
const ReplyDisplay(this.controller, {Key key}) : super(key: key); const ReplyDisplay(this.controller, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -23,16 +23,16 @@ class ReplyDisplay extends StatelessWidget {
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
IconButton( IconButton(
tooltip: L10n.of(context).close, tooltip: L10n.of(context)!.close,
icon: const Icon(Icons.close), icon: const Icon(Icons.close),
onPressed: controller.cancelReplyEventAction, onPressed: controller.cancelReplyEventAction,
), ),
Expanded( Expanded(
child: controller.replyEvent != null child: controller.replyEvent != null
? ReplyContent(controller.replyEvent, ? ReplyContent(controller.replyEvent!,
timeline: controller.timeline) timeline: controller.timeline!)
: _EditContent(controller.editEvent : _EditContent(controller.editEvent
?.getDisplayEvent(controller.timeline)), ?.getDisplayEvent(controller.timeline!)),
), ),
], ],
), ),
@ -42,7 +42,7 @@ class ReplyDisplay extends StatelessWidget {
} }
class _EditContent extends StatelessWidget { class _EditContent extends StatelessWidget {
final Event event; final Event? event;
const _EditContent(this.event); const _EditContent(this.event);
@ -60,7 +60,7 @@ class _EditContent extends StatelessWidget {
Container(width: 15.0), Container(width: 15.0),
Text( Text(
event?.getLocalizedBody( event?.getLocalizedBody(
MatrixLocals(L10n.of(context)), MatrixLocals(L10n.of(context)!),
withSenderNamePrefix: false, withSenderNamePrefix: false,
hideReply: true, hideReply: true,
) ?? ) ??
@ -68,7 +68,7 @@ class _EditContent extends StatelessWidget {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
maxLines: 1, maxLines: 1,
style: TextStyle( style: TextStyle(
color: Theme.of(context).textTheme.bodyText2.color, color: Theme.of(context).textTheme.bodyText2!.color,
), ),
), ),
], ],

View File

@ -8,12 +8,12 @@ import 'package:fluffychat/widgets/matrix.dart';
class SeenByRow extends StatelessWidget { class SeenByRow extends StatelessWidget {
final ChatController controller; final ChatController controller;
const SeenByRow(this.controller, {Key key}) : super(key: key); const SeenByRow(this.controller, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final seenByUsers = controller.room.getSeenByUsers( final seenByUsers = controller.room!.getSeenByUsers(
controller.timeline, controller.timeline!,
controller.filteredEvents, controller.filteredEvents,
controller.unfolded, controller.unfolded,
); );

View File

@ -1,5 +1,3 @@
//@dart=2.12
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';

View File

@ -15,8 +15,8 @@ class SendLocationDialog extends StatefulWidget {
final Room room; final Room room;
const SendLocationDialog({ const SendLocationDialog({
this.room, required this.room,
Key key, Key? key,
}) : super(key: key); }) : super(key: key);
@override @override
@ -27,8 +27,8 @@ class _SendLocationDialogState extends State<SendLocationDialog> {
bool disabled = false; bool disabled = false;
bool denied = false; bool denied = false;
bool isSending = false; bool isSending = false;
Position position; Position? position;
Error error; Object? error;
@override @override
void initState() { void initState() {
@ -75,9 +75,9 @@ class _SendLocationDialogState extends State<SendLocationDialog> {
void sendAction() async { void sendAction() async {
setState(() => isSending = true); setState(() => isSending = true);
final body = final body =
'https://www.openstreetmap.org/?mlat=${position.latitude}&mlon=${position.longitude}#map=16/${position.latitude}/${position.longitude}'; 'https://www.openstreetmap.org/?mlat=${position!.latitude}&mlon=${position!.longitude}#map=16/${position!.latitude}/${position!.longitude}';
final uri = final uri =
'geo:${position.latitude},${position.longitude};u=${position.accuracy}'; 'geo:${position!.latitude},${position!.longitude};u=${position!.accuracy}';
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => widget.room.sendLocation(body, uri), future: () => widget.room.sendLocation(body, uri),
@ -90,16 +90,16 @@ class _SendLocationDialogState extends State<SendLocationDialog> {
Widget contentWidget; Widget contentWidget;
if (position != null) { if (position != null) {
contentWidget = MapBubble( contentWidget = MapBubble(
latitude: position.latitude, latitude: position!.latitude,
longitude: position.longitude, longitude: position!.longitude,
); );
} else if (disabled) { } else if (disabled) {
contentWidget = Text(L10n.of(context).locationDisabledNotice); contentWidget = Text(L10n.of(context)!.locationDisabledNotice);
} else if (denied) { } else if (denied) {
contentWidget = Text(L10n.of(context).locationPermissionDeniedNotice); contentWidget = Text(L10n.of(context)!.locationPermissionDeniedNotice);
} else if (error != null) { } else if (error != null) {
contentWidget = contentWidget =
Text(L10n.of(context).errorObtainingLocation(error.toString())); Text(L10n.of(context)!.errorObtainingLocation(error.toString()));
} else { } else {
contentWidget = Row( contentWidget = Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -107,38 +107,38 @@ class _SendLocationDialogState extends State<SendLocationDialog> {
children: [ children: [
const CupertinoActivityIndicator(), const CupertinoActivityIndicator(),
const SizedBox(width: 12), const SizedBox(width: 12),
Text(L10n.of(context).obtainingLocation), Text(L10n.of(context)!.obtainingLocation),
], ],
); );
} }
if (PlatformInfos.isCupertinoStyle) { if (PlatformInfos.isCupertinoStyle) {
return CupertinoAlertDialog( return CupertinoAlertDialog(
title: Text(L10n.of(context).shareLocation), title: Text(L10n.of(context)!.shareLocation),
content: contentWidget, content: contentWidget,
actions: [ actions: [
CupertinoDialogAction( CupertinoDialogAction(
onPressed: Navigator.of(context, rootNavigator: false).pop, onPressed: Navigator.of(context, rootNavigator: false).pop,
child: Text(L10n.of(context).cancel), child: Text(L10n.of(context)!.cancel),
), ),
CupertinoDialogAction( CupertinoDialogAction(
onPressed: isSending ? null : sendAction, onPressed: isSending ? null : sendAction,
child: Text(L10n.of(context).send), child: Text(L10n.of(context)!.send),
), ),
], ],
); );
} }
return AlertDialog( return AlertDialog(
title: Text(L10n.of(context).shareLocation), title: Text(L10n.of(context)!.shareLocation),
content: contentWidget, content: contentWidget,
actions: [ actions: [
TextButton( TextButton(
onPressed: Navigator.of(context, rootNavigator: false).pop, onPressed: Navigator.of(context, rootNavigator: false).pop,
child: Text(L10n.of(context).cancel), child: Text(L10n.of(context)!.cancel),
), ),
if (position != null) if (position != null)
TextButton( TextButton(
onPressed: isSending ? null : sendAction, onPressed: isSending ? null : sendAction,
child: Text(L10n.of(context).send), child: Text(L10n.of(context)!.send),
), ),
], ],
); );

View File

@ -10,14 +10,14 @@ import 'events/image_bubble.dart';
class StickerPickerDialog extends StatefulWidget { class StickerPickerDialog extends StatefulWidget {
final Room room; final Room room;
const StickerPickerDialog({this.room, Key key}) : super(key: key); const StickerPickerDialog({required this.room, Key? key}) : super(key: key);
@override @override
StickerPickerDialogState createState() => StickerPickerDialogState(); StickerPickerDialogState createState() => StickerPickerDialogState();
} }
class StickerPickerDialogState extends State<StickerPickerDialog> { class StickerPickerDialogState extends State<StickerPickerDialog> {
String searchFilter; String? searchFilter;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -26,14 +26,14 @@ class StickerPickerDialogState extends State<StickerPickerDialog> {
// ignore: prefer_function_declarations_over_variables // ignore: prefer_function_declarations_over_variables
final _packBuilder = (BuildContext context, int packIndex) { final _packBuilder = (BuildContext context, int packIndex) {
final pack = stickerPacks[packSlugs[packIndex]]; final pack = stickerPacks[packSlugs[packIndex]]!;
final filteredImagePackImageEntried = pack.images.entries.toList(); final filteredImagePackImageEntried = pack.images.entries.toList();
if (searchFilter?.isNotEmpty ?? false) { if (searchFilter?.isNotEmpty ?? false) {
filteredImagePackImageEntried.removeWhere((e) => filteredImagePackImageEntried.removeWhere((e) =>
!(e.key.toLowerCase().contains(searchFilter.toLowerCase()) || !(e.key.toLowerCase().contains(searchFilter!.toLowerCase()) ||
(e.value.body (e.value.body
?.toLowerCase() ?.toLowerCase()
?.contains(searchFilter.toLowerCase()) ?? .contains(searchFilter!.toLowerCase()) ??
false))); false)));
} }
final imageKeys = final imageKeys =
@ -62,7 +62,7 @@ class StickerPickerDialogState extends State<StickerPickerDialog> {
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int imageIndex) { itemBuilder: (BuildContext context, int imageIndex) {
final image = pack.images[imageKeys[imageIndex]]; final image = pack.images[imageKeys[imageIndex]]!;
final fakeEvent = Event.fromJson(<String, dynamic>{ final fakeEvent = Event.fromJson(<String, dynamic>{
'type': EventTypes.Sticker, 'type': EventTypes.Sticker,
'content': <String, dynamic>{ 'content': <String, dynamic>{
@ -116,7 +116,7 @@ class StickerPickerDialogState extends State<StickerPickerDialog> {
), ),
title: DefaultAppBarSearchField( title: DefaultAppBarSearchField(
autofocus: false, autofocus: false,
hintText: L10n.of(context).search, hintText: L10n.of(context)!.search,
suffix: const Icon(Icons.search_outlined), suffix: const Icon(Icons.search_outlined),
onChanged: (s) => setState(() => searchFilter = s), onChanged: (s) => setState(() => searchFilter = s),
), ),

View File

@ -7,11 +7,11 @@ import 'chat.dart';
class TombstoneDisplay extends StatelessWidget { class TombstoneDisplay extends StatelessWidget {
final ChatController controller; final ChatController controller;
const TombstoneDisplay(this.controller, {Key key}) : super(key: key); const TombstoneDisplay(this.controller, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (controller.room.getState(EventTypes.RoomTombstone) == null) { if (controller.room!.getState(EventTypes.RoomTombstone) == null) {
return Container(); return Container();
} }
return SizedBox( return SizedBox(
@ -26,14 +26,14 @@ class TombstoneDisplay extends StatelessWidget {
child: const Icon(Icons.upgrade_outlined), child: const Icon(Icons.upgrade_outlined),
), ),
title: Text( title: Text(
controller.room controller.room!
.getState(EventTypes.RoomTombstone) .getState(EventTypes.RoomTombstone)!
.parsedTombstoneContent .parsedTombstoneContent
.body, .body,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
subtitle: Text(L10n.of(context).goToTheNewRoom), subtitle: Text(L10n.of(context)!.goToTheNewRoom),
onTap: controller.goToNewRoomAction, onTap: controller.goToNewRoomAction,
), ),
), ),

View File

@ -8,11 +8,11 @@ import 'package:fluffychat/widgets/matrix.dart';
class TypingIndicators extends StatelessWidget { class TypingIndicators extends StatelessWidget {
final ChatController controller; final ChatController controller;
const TypingIndicators(this.controller, {Key key}) : super(key: key); const TypingIndicators(this.controller, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final typingUsers = controller.room.typingUsers final typingUsers = controller.room!.typingUsers
..removeWhere((u) => u.stateKey == Matrix.of(context).client.userID); ..removeWhere((u) => u.stateKey == Matrix.of(context).client.userID);
const topPadding = 20.0; const topPadding = 20.0;
const bottomPadding = 4.0; const bottomPadding = 4.0;

View File

@ -18,34 +18,34 @@ import 'package:fluffychat/widgets/matrix.dart';
enum AliasActions { copy, delete, setCanonical } enum AliasActions { copy, delete, setCanonical }
class ChatDetails extends StatefulWidget { class ChatDetails extends StatefulWidget {
const ChatDetails({Key key}) : super(key: key); const ChatDetails({Key? key}) : super(key: key);
@override @override
ChatDetailsController createState() => ChatDetailsController(); ChatDetailsController createState() => ChatDetailsController();
} }
class ChatDetailsController extends State<ChatDetails> { class ChatDetailsController extends State<ChatDetails> {
List<User> members; List<User>? members;
bool displaySettings = false; bool displaySettings = false;
void toggleDisplaySettings() => void toggleDisplaySettings() =>
setState(() => displaySettings = !displaySettings); setState(() => displaySettings = !displaySettings);
String get roomId => VRouter.of(context).pathParameters['roomid']; String? get roomId => VRouter.of(context).pathParameters['roomid'];
void setDisplaynameAction() async { void setDisplaynameAction() async {
final room = Matrix.of(context).client.getRoomById(roomId); final room = Matrix.of(context).client.getRoomById(roomId!)!;
final input = await showTextInputDialog( final input = await showTextInputDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).changeTheNameOfTheGroup, title: L10n.of(context)!.changeTheNameOfTheGroup,
okLabel: L10n.of(context).ok, okLabel: L10n.of(context)!.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context)!.cancel,
textFields: [ textFields: [
DialogTextField( DialogTextField(
initialText: room.getLocalizedDisplayname( initialText: room.getLocalizedDisplayname(
MatrixLocals( MatrixLocals(
L10n.of(context), L10n.of(context)!,
), ),
), ),
) )
@ -58,12 +58,12 @@ class ChatDetailsController extends State<ChatDetails> {
); );
if (success.error == null) { if (success.error == null) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(L10n.of(context).displaynameHasBeenChanged))); SnackBar(content: Text(L10n.of(context)!.displaynameHasBeenChanged)));
} }
} }
void editAliases() async { void editAliases() async {
final room = Matrix.of(context).client.getRoomById(roomId); final room = Matrix.of(context).client.getRoomById(roomId!);
// The current endpoint doesnt seem to be implemented in Synapse. This may // The current endpoint doesnt seem to be implemented in Synapse. This may
// change in the future and then we just need to switch to this api call: // change in the future and then we just need to switch to this api call:
@ -76,7 +76,7 @@ class ChatDetailsController extends State<ChatDetails> {
// While this is not working we use the unstable api: // While this is not working we use the unstable api:
final aliases = await showFutureLoadingDialog( final aliases = await showFutureLoadingDialog(
context: context, context: context,
future: () => room.client future: () => room!.client
.request( .request(
RequestType.GET, RequestType.GET,
'/client/unstable/org.matrix.msc2432/rooms/${Uri.encodeComponent(room.id)}/aliases', '/client/unstable/org.matrix.msc2432/rooms/${Uri.encodeComponent(room.id)}/aliases',
@ -86,21 +86,21 @@ class ChatDetailsController extends State<ChatDetails> {
// Switch to the stable api once it is implemented. // Switch to the stable api once it is implemented.
if (aliases.error != null) return; if (aliases.error != null) return;
final adminMode = room.canSendEvent('m.room.canonical_alias'); final adminMode = room!.canSendEvent('m.room.canonical_alias');
if (aliases.result.isEmpty && (room.canonicalAlias?.isNotEmpty ?? false)) { if (aliases.result!.isEmpty && (room.canonicalAlias.isNotEmpty)) {
aliases.result.add(room.canonicalAlias); aliases.result!.add(room.canonicalAlias);
} }
if (aliases.result.isEmpty && adminMode) { if (aliases.result!.isEmpty && adminMode) {
return setAliasAction(); return setAliasAction();
} }
final select = await showConfirmationDialog( final select = await showConfirmationDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).editRoomAliases, title: L10n.of(context)!.editRoomAliases,
actions: [ actions: [
if (adminMode) if (adminMode)
AlertDialogAction(label: L10n.of(context).create, key: 'new'), AlertDialogAction(label: L10n.of(context)!.create, key: 'new'),
...aliases.result ...aliases.result!
.map((alias) => AlertDialogAction(key: alias, label: alias)) .map((alias) => AlertDialogAction(key: alias, label: alias))
.toList(), .toList(),
], ],
@ -114,29 +114,30 @@ class ChatDetailsController extends State<ChatDetails> {
title: select, title: select,
actions: [ actions: [
AlertDialogAction( AlertDialogAction(
label: L10n.of(context).copyToClipboard, label: L10n.of(context)!.copyToClipboard,
key: AliasActions.copy, key: AliasActions.copy,
isDefaultAction: true, isDefaultAction: true,
), ),
if (adminMode) ...{ if (adminMode) ...{
AlertDialogAction( AlertDialogAction(
label: L10n.of(context).setAsCanonicalAlias, label: L10n.of(context)!.setAsCanonicalAlias,
key: AliasActions.setCanonical, key: AliasActions.setCanonical,
isDestructiveAction: true, isDestructiveAction: true,
), ),
AlertDialogAction( AlertDialogAction(
label: L10n.of(context).delete, label: L10n.of(context)!.delete,
key: AliasActions.delete, key: AliasActions.delete,
isDestructiveAction: true, isDestructiveAction: true,
), ),
}, },
], ],
); );
if (option == null) return;
switch (option) { switch (option) {
case AliasActions.copy: case AliasActions.copy:
await Clipboard.setData(ClipboardData(text: select)); await Clipboard.setData(ClipboardData(text: select));
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(L10n.of(context).copiedToClipboard)), SnackBar(content: Text(L10n.of(context)!.copiedToClipboard)),
); );
break; break;
case AliasActions.delete: case AliasActions.delete:
@ -162,21 +163,21 @@ class ChatDetailsController extends State<ChatDetails> {
} }
void setAliasAction() async { void setAliasAction() async {
final room = Matrix.of(context).client.getRoomById(roomId); final room = Matrix.of(context).client.getRoomById(roomId!)!;
final domain = room.client.userID.domain; final domain = room.client.userID!.domain;
final input = await showTextInputDialog( final input = await showTextInputDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).setInvitationLink, title: L10n.of(context)!.setInvitationLink,
okLabel: L10n.of(context).ok, okLabel: L10n.of(context)!.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context)!.cancel,
textFields: [ textFields: [
DialogTextField( DialogTextField(
prefixText: '#', prefixText: '#',
suffixText: domain, suffixText: domain,
hintText: L10n.of(context).alias, hintText: L10n.of(context)!.alias,
initialText: room.canonicalAlias?.localpart, initialText: room.canonicalAlias.localpart,
) )
], ],
); );
@ -184,21 +185,21 @@ class ChatDetailsController extends State<ChatDetails> {
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => future: () =>
room.client.setRoomAlias('#' + input.single + ':' + domain, room.id), room.client.setRoomAlias('#' + input.single + ':' + domain!, room.id),
); );
} }
void setTopicAction() async { void setTopicAction() async {
final room = Matrix.of(context).client.getRoomById(roomId); final room = Matrix.of(context).client.getRoomById(roomId!)!;
final input = await showTextInputDialog( final input = await showTextInputDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).setGroupDescription, title: L10n.of(context)!.setGroupDescription,
okLabel: L10n.of(context).ok, okLabel: L10n.of(context)!.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context)!.cancel,
textFields: [ textFields: [
DialogTextField( DialogTextField(
hintText: L10n.of(context).setGroupDescription, hintText: L10n.of(context)!.setGroupDescription,
initialText: room.topic, initialText: room.topic,
minLines: 1, minLines: 1,
maxLines: 4, maxLines: 4,
@ -212,7 +213,7 @@ class ChatDetailsController extends State<ChatDetails> {
); );
if (success.error == null) { if (success.error == null) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar( ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(L10n.of(context).groupDescriptionHasBeenChanged))); content: Text(L10n.of(context)!.groupDescriptionHasBeenChanged)));
} }
} }
@ -220,7 +221,7 @@ class ChatDetailsController extends State<ChatDetails> {
context: context, context: context,
future: () => Matrix.of(context) future: () => Matrix.of(context)
.client .client
.getRoomById(roomId) .getRoomById(roomId!)!
.setGuestAccess(guestAccess), .setGuestAccess(guestAccess),
); );
@ -229,7 +230,7 @@ class ChatDetailsController extends State<ChatDetails> {
context: context, context: context,
future: () => Matrix.of(context) future: () => Matrix.of(context)
.client .client
.getRoomById(roomId) .getRoomById(roomId!)!
.setHistoryVisibility(historyVisibility), .setHistoryVisibility(historyVisibility),
); );
@ -237,12 +238,12 @@ class ChatDetailsController extends State<ChatDetails> {
context: context, context: context,
future: () => Matrix.of(context) future: () => Matrix.of(context)
.client .client
.getRoomById(roomId) .getRoomById(roomId!)!
.setJoinRules(joinRule), .setJoinRules(joinRule),
); );
void goToEmoteSettings() async { void goToEmoteSettings() async {
final room = Matrix.of(context).client.getRoomById(roomId); final room = Matrix.of(context).client.getRoomById(roomId!)!;
// okay, we need to test if there are any emote state events other than the default one // okay, we need to test if there are any emote state events other than the default one
// if so, we need to be directed to a selection screen for which pack we want to look at // if so, we need to be directed to a selection screen for which pack we want to look at
// otherwise, we just open the normal one. // otherwise, we just open the normal one.
@ -256,24 +257,24 @@ class ChatDetailsController extends State<ChatDetails> {
} }
void setAvatarAction() async { void setAvatarAction() async {
final room = Matrix.of(context).client.getRoomById(roomId); final room = Matrix.of(context).client.getRoomById(roomId!);
final actions = [ final actions = [
if (PlatformInfos.isMobile) if (PlatformInfos.isMobile)
SheetAction( SheetAction(
key: AvatarAction.camera, key: AvatarAction.camera,
label: L10n.of(context).openCamera, label: L10n.of(context)!.openCamera,
isDefaultAction: true, isDefaultAction: true,
icon: Icons.camera_alt_outlined, icon: Icons.camera_alt_outlined,
), ),
SheetAction( SheetAction(
key: AvatarAction.file, key: AvatarAction.file,
label: L10n.of(context).openGallery, label: L10n.of(context)!.openGallery,
icon: Icons.photo_outlined, icon: Icons.photo_outlined,
), ),
if (room?.avatar != null) if (room?.avatar != null)
SheetAction( SheetAction(
key: AvatarAction.remove, key: AvatarAction.remove,
label: L10n.of(context).delete, label: L10n.of(context)!.delete,
isDestructiveAction: true, isDestructiveAction: true,
icon: Icons.delete_outlined, icon: Icons.delete_outlined,
), ),
@ -282,14 +283,14 @@ class ChatDetailsController extends State<ChatDetails> {
? actions.single ? actions.single
: await showModalActionSheet<AvatarAction>( : await showModalActionSheet<AvatarAction>(
context: context, context: context,
title: L10n.of(context).editRoomAvatar, title: L10n.of(context)!.editRoomAvatar,
actions: actions, actions: actions,
); );
if (action == null) return; if (action == null) return;
if (action == AvatarAction.remove) { if (action == AvatarAction.remove) {
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => room.setAvatar(null), future: () => room!.setAvatar(null),
); );
return; return;
} }
@ -309,22 +310,22 @@ class ChatDetailsController extends State<ChatDetails> {
} else { } else {
final result = final result =
await FilePickerCross.importFromStorage(type: FileTypeCross.image); await FilePickerCross.importFromStorage(type: FileTypeCross.image);
if (result == null) return; if (result.fileName == null) return;
file = MatrixFile( file = MatrixFile(
bytes: result.toUint8List(), bytes: result.toUint8List(),
name: result.fileName, name: result.fileName!,
); );
} }
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => room.setAvatar(file), future: () => room!.setAvatar(file),
); );
} }
void requestMoreMembersAction() async { void requestMoreMembersAction() async {
final room = Matrix.of(context).client.getRoomById(roomId); final room = Matrix.of(context).client.getRoomById(roomId!);
final participants = await showFutureLoadingDialog( final participants = await showFutureLoadingDialog(
context: context, future: () => room.requestParticipants()); context: context, future: () => room!.requestParticipants());
if (participants.error == null) { if (participants.error == null) {
setState(() => members = participants.result); setState(() => members = participants.result);
} }
@ -334,7 +335,8 @@ class ChatDetailsController extends State<ChatDetails> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
members ??= Matrix.of(context).client.getRoomById(roomId).getParticipants(); members ??=
Matrix.of(context).client.getRoomById(roomId!)!.getParticipants();
return SizedBox( return SizedBox(
width: fixedWidth, width: fixedWidth,
child: ChatDetailsView(this), child: ChatDetailsView(this),

View File

@ -20,28 +20,28 @@ import '../../utils/url_launcher.dart';
class ChatDetailsView extends StatelessWidget { class ChatDetailsView extends StatelessWidget {
final ChatDetailsController controller; final ChatDetailsController controller;
const ChatDetailsView(this.controller, {Key key}) : super(key: key); const ChatDetailsView(this.controller, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final room = Matrix.of(context).client.getRoomById(controller.roomId); final room = Matrix.of(context).client.getRoomById(controller.roomId!);
if (room == null) { if (room == null) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(L10n.of(context).oopsSomethingWentWrong), title: Text(L10n.of(context)!.oopsSomethingWentWrong),
), ),
body: Center( body: Center(
child: Text(L10n.of(context).youAreNoLongerParticipatingInThisChat), child: Text(L10n.of(context)!.youAreNoLongerParticipatingInThisChat),
), ),
); );
} }
controller.members.removeWhere((u) => u.membership == Membership.leave); controller.members!.removeWhere((u) => u.membership == Membership.leave);
final actualMembersCount = (room.summary?.mInvitedMemberCount ?? 0) + final actualMembersCount = (room.summary.mInvitedMemberCount ?? 0) +
(room.summary?.mJoinedMemberCount ?? 0); (room.summary.mJoinedMemberCount ?? 0);
final canRequestMoreMembers = final canRequestMoreMembers =
controller.members.length < actualMembersCount; controller.members!.length < actualMembersCount;
final iconColor = Theme.of(context).textTheme.bodyText1.color; final iconColor = Theme.of(context).textTheme.bodyText1!.color;
return StreamBuilder( return StreamBuilder(
stream: room.onUpdate.stream, stream: room.onUpdate.stream,
builder: (context, snapshot) { builder: (context, snapshot) {
@ -56,16 +56,16 @@ class ChatDetailsView extends StatelessWidget {
VRouter.of(context).path.startsWith('/spaces/') VRouter.of(context).path.startsWith('/spaces/')
? VRouter.of(context).pop() ? VRouter.of(context).pop()
: VRouter.of(context) : VRouter.of(context)
.toSegments(['rooms', controller.roomId]), .toSegments(['rooms', controller.roomId!]),
), ),
elevation: Theme.of(context).appBarTheme.elevation, elevation: Theme.of(context).appBarTheme.elevation,
expandedHeight: 300.0, expandedHeight: 300.0,
floating: true, floating: true,
pinned: true, pinned: true,
actions: <Widget>[ actions: <Widget>[
if (room.canonicalAlias?.isNotEmpty ?? false) if (room.canonicalAlias.isNotEmpty)
IconButton( IconButton(
tooltip: L10n.of(context).share, tooltip: L10n.of(context)!.share,
icon: Icon(Icons.adaptive.share_outlined), icon: Icon(Icons.adaptive.share_outlined),
onPressed: () => FluffyShare.share( onPressed: () => FluffyShare.share(
AppConfig.inviteLinkPrefix + room.canonicalAlias, AppConfig.inviteLinkPrefix + room.canonicalAlias,
@ -75,16 +75,17 @@ class ChatDetailsView extends StatelessWidget {
], ],
title: Text( title: Text(
room.getLocalizedDisplayname( room.getLocalizedDisplayname(
MatrixLocals(L10n.of(context))), MatrixLocals(L10n.of(context)!)),
style: TextStyle( style: TextStyle(
color: Theme.of(context) color: Theme.of(context)
.appBarTheme .appBarTheme
.titleTextStyle .titleTextStyle!
.color)), .color)),
backgroundColor: backgroundColor:
Theme.of(context).appBarTheme.backgroundColor, Theme.of(context).appBarTheme.backgroundColor,
flexibleSpace: FlexibleSpaceBar( flexibleSpace: FlexibleSpaceBar(
background: ContentBanner(room.avatar, background: ContentBanner(
mxContent: room.avatar,
onEdit: room.canSendEvent('m.room.avatar') onEdit: room.canSendEvent('m.room.avatar')
? controller.setAvatarAction ? controller.setAvatarAction
: null), : null),
@ -93,7 +94,7 @@ class ChatDetailsView extends StatelessWidget {
], ],
body: MaxWidthBody( body: MaxWidthBody(
child: ListView.builder( child: ListView.builder(
itemCount: controller.members.length + itemCount: controller.members!.length +
1 + 1 +
(canRequestMoreMembers ? 1 : 0), (canRequestMoreMembers ? 1 : 0),
itemBuilder: (BuildContext context, int i) => i == 0 itemBuilder: (BuildContext context, int i) => i == 0
@ -111,15 +112,15 @@ class ChatDetailsView extends StatelessWidget {
) )
: null, : null,
title: Text( title: Text(
'${L10n.of(context).groupDescription}:', '${L10n.of(context)!.groupDescription}:',
style: TextStyle( style: TextStyle(
color: Theme.of(context) color: Theme.of(context)
.colorScheme .colorScheme
.secondary, .secondary,
fontWeight: FontWeight.bold)), fontWeight: FontWeight.bold)),
subtitle: LinkText( subtitle: LinkText(
text: room.topic?.isEmpty ?? true text: room.topic.isEmpty
? L10n.of(context).addGroupDescription ? L10n.of(context)!.addGroupDescription
: room.topic, : room.topic,
linkStyle: linkStyle:
const TextStyle(color: Colors.blueAccent), const TextStyle(color: Colors.blueAccent),
@ -127,7 +128,7 @@ class ChatDetailsView extends StatelessWidget {
fontSize: 14, fontSize: 14,
color: Theme.of(context) color: Theme.of(context)
.textTheme .textTheme
.bodyText2 .bodyText2!
.color, .color,
), ),
onLinkTap: (url) => onLinkTap: (url) =>
@ -141,7 +142,7 @@ class ChatDetailsView extends StatelessWidget {
const Divider(height: 1), const Divider(height: 1),
ListTile( ListTile(
title: Text( title: Text(
L10n.of(context).settings, L10n.of(context)!.settings,
style: TextStyle( style: TextStyle(
color: color:
Theme.of(context).colorScheme.secondary, Theme.of(context).colorScheme.secondary,
@ -163,10 +164,10 @@ class ChatDetailsView extends StatelessWidget {
child: const Icon( child: const Icon(
Icons.people_outline_outlined), Icons.people_outline_outlined),
), ),
title: Text( title: Text(L10n.of(context)!
L10n.of(context).changeTheNameOfTheGroup), .changeTheNameOfTheGroup),
subtitle: Text(room.getLocalizedDisplayname( subtitle: Text(room.getLocalizedDisplayname(
MatrixLocals(L10n.of(context)))), MatrixLocals(L10n.of(context)!))),
onTap: controller.setDisplaynameAction, onTap: controller.setDisplaynameAction,
), ),
if (room.joinRules == JoinRules.public) if (room.joinRules == JoinRules.public)
@ -178,11 +179,12 @@ class ChatDetailsView extends StatelessWidget {
child: const Icon(Icons.link_outlined), child: const Icon(Icons.link_outlined),
), ),
onTap: controller.editAliases, onTap: controller.editAliases,
title: Text(L10n.of(context).editRoomAliases), title:
Text(L10n.of(context)!.editRoomAliases),
subtitle: Text( subtitle: Text(
(room.canonicalAlias?.isNotEmpty ?? false) (room.canonicalAlias.isNotEmpty)
? room.canonicalAlias ? room.canonicalAlias
: L10n.of(context).none), : L10n.of(context)!.none),
), ),
ListTile( ListTile(
leading: CircleAvatar( leading: CircleAvatar(
@ -192,9 +194,9 @@ class ChatDetailsView extends StatelessWidget {
child: const Icon( child: const Icon(
Icons.insert_emoticon_outlined), Icons.insert_emoticon_outlined),
), ),
title: Text(L10n.of(context).emoteSettings), title: Text(L10n.of(context)!.emoteSettings),
subtitle: subtitle:
Text(L10n.of(context).setCustomEmotes), Text(L10n.of(context)!.setCustomEmotes),
onTap: controller.goToEmoteSettings, onTap: controller.goToEmoteSettings,
), ),
PopupMenuButton( PopupMenuButton(
@ -206,14 +208,14 @@ class ChatDetailsView extends StatelessWidget {
value: JoinRules.public, value: JoinRules.public,
child: Text(JoinRules.public child: Text(JoinRules.public
.getLocalizedString( .getLocalizedString(
MatrixLocals(L10n.of(context)))), MatrixLocals(L10n.of(context)!))),
), ),
if (room.canChangeJoinRules) if (room.canChangeJoinRules)
PopupMenuItem<JoinRules>( PopupMenuItem<JoinRules>(
value: JoinRules.invite, value: JoinRules.invite,
child: Text(JoinRules.invite child: Text(JoinRules.invite
.getLocalizedString( .getLocalizedString(
MatrixLocals(L10n.of(context)))), MatrixLocals(L10n.of(context)!))),
), ),
], ],
child: ListTile( child: ListTile(
@ -222,11 +224,11 @@ class ChatDetailsView extends StatelessWidget {
.scaffoldBackgroundColor, .scaffoldBackgroundColor,
foregroundColor: iconColor, foregroundColor: iconColor,
child: const Icon(Icons.shield_outlined)), child: const Icon(Icons.shield_outlined)),
title: Text(L10n.of(context) title: Text(L10n.of(context)!
.whoIsAllowedToJoinThisGroup), .whoIsAllowedToJoinThisGroup),
subtitle: Text( subtitle: Text(
room.joinRules.getLocalizedString( room.joinRules!.getLocalizedString(
MatrixLocals(L10n.of(context))), MatrixLocals(L10n.of(context)!)),
), ),
), ),
), ),
@ -240,21 +242,21 @@ class ChatDetailsView extends StatelessWidget {
value: HistoryVisibility.invited, value: HistoryVisibility.invited,
child: Text(HistoryVisibility.invited child: Text(HistoryVisibility.invited
.getLocalizedString( .getLocalizedString(
MatrixLocals(L10n.of(context)))), MatrixLocals(L10n.of(context)!))),
), ),
if (room.canChangeHistoryVisibility) if (room.canChangeHistoryVisibility)
PopupMenuItem<HistoryVisibility>( PopupMenuItem<HistoryVisibility>(
value: HistoryVisibility.joined, value: HistoryVisibility.joined,
child: Text(HistoryVisibility.joined child: Text(HistoryVisibility.joined
.getLocalizedString( .getLocalizedString(
MatrixLocals(L10n.of(context)))), MatrixLocals(L10n.of(context)!))),
), ),
if (room.canChangeHistoryVisibility) if (room.canChangeHistoryVisibility)
PopupMenuItem<HistoryVisibility>( PopupMenuItem<HistoryVisibility>(
value: HistoryVisibility.shared, value: HistoryVisibility.shared,
child: Text(HistoryVisibility.shared child: Text(HistoryVisibility.shared
.getLocalizedString( .getLocalizedString(
MatrixLocals(L10n.of(context)))), MatrixLocals(L10n.of(context)!))),
), ),
if (room.canChangeHistoryVisibility) if (room.canChangeHistoryVisibility)
PopupMenuItem<HistoryVisibility>( PopupMenuItem<HistoryVisibility>(
@ -262,7 +264,7 @@ class ChatDetailsView extends StatelessWidget {
child: Text(HistoryVisibility child: Text(HistoryVisibility
.worldReadable .worldReadable
.getLocalizedString( .getLocalizedString(
MatrixLocals(L10n.of(context)))), MatrixLocals(L10n.of(context)!))),
), ),
], ],
child: ListTile( child: ListTile(
@ -273,12 +275,11 @@ class ChatDetailsView extends StatelessWidget {
child: child:
const Icon(Icons.visibility_outlined), const Icon(Icons.visibility_outlined),
), ),
title: Text(L10n.of(context) title: Text(L10n.of(context)!
.visibilityOfTheChatHistory), .visibilityOfTheChatHistory),
subtitle: Text( subtitle: Text(
room.historyVisibility.getLocalizedString( room.historyVisibility!.getLocalizedString(
MatrixLocals(L10n.of(context))) ?? MatrixLocals(L10n.of(context)!)),
'',
), ),
), ),
), ),
@ -293,7 +294,7 @@ class ChatDetailsView extends StatelessWidget {
child: Text( child: Text(
GuestAccess.canJoin GuestAccess.canJoin
.getLocalizedString(MatrixLocals( .getLocalizedString(MatrixLocals(
L10n.of(context))), L10n.of(context)!)),
), ),
), ),
if (room.canChangeGuestAccess) if (room.canChangeGuestAccess)
@ -302,7 +303,7 @@ class ChatDetailsView extends StatelessWidget {
child: Text( child: Text(
GuestAccess.forbidden GuestAccess.forbidden
.getLocalizedString(MatrixLocals( .getLocalizedString(MatrixLocals(
L10n.of(context))), L10n.of(context)!)),
), ),
), ),
], ],
@ -314,19 +315,19 @@ class ChatDetailsView extends StatelessWidget {
child: const Icon( child: const Icon(
Icons.person_add_alt_1_outlined), Icons.person_add_alt_1_outlined),
), ),
title: Text(L10n.of(context) title: Text(L10n.of(context)!
.areGuestsAllowedToJoin), .areGuestsAllowedToJoin),
subtitle: Text( subtitle: Text(
room.guestAccess.getLocalizedString( room.guestAccess.getLocalizedString(
MatrixLocals(L10n.of(context))), MatrixLocals(L10n.of(context)!)),
), ),
), ),
), ),
ListTile( ListTile(
title: title:
Text(L10n.of(context).editChatPermissions), Text(L10n.of(context)!.editChatPermissions),
subtitle: Text( subtitle: Text(
L10n.of(context).whoCanPerformWhichAction), L10n.of(context)!.whoCanPerformWhichAction),
leading: CircleAvatar( leading: CircleAvatar(
backgroundColor: backgroundColor:
Theme.of(context).scaffoldBackgroundColor, Theme.of(context).scaffoldBackgroundColor,
@ -342,9 +343,9 @@ class ChatDetailsView extends StatelessWidget {
ListTile( ListTile(
title: Text( title: Text(
actualMembersCount > 1 actualMembersCount > 1
? L10n.of(context).countParticipants( ? L10n.of(context)!.countParticipants(
actualMembersCount.toString()) actualMembersCount.toString())
: L10n.of(context).emptyChat, : L10n.of(context)!.emptyChat,
style: TextStyle( style: TextStyle(
color: color:
Theme.of(context).colorScheme.secondary, Theme.of(context).colorScheme.secondary,
@ -354,7 +355,8 @@ class ChatDetailsView extends StatelessWidget {
), ),
room.canInvite room.canInvite
? ListTile( ? ListTile(
title: Text(L10n.of(context).inviteContact), title:
Text(L10n.of(context)!.inviteContact),
leading: CircleAvatar( leading: CircleAvatar(
backgroundColor: backgroundColor:
Theme.of(context).primaryColor, Theme.of(context).primaryColor,
@ -368,13 +370,13 @@ class ChatDetailsView extends StatelessWidget {
: Container(), : Container(),
], ],
) )
: i < controller.members.length + 1 : i < controller.members!.length + 1
? ParticipantListItem(controller.members[i - 1]) ? ParticipantListItem(controller.members![i - 1])
: ListTile( : ListTile(
title: Text(L10n.of(context) title: Text(L10n.of(context)!
.loadCountMoreParticipants( .loadCountMoreParticipants(
(actualMembersCount - (actualMembersCount -
controller.members.length) controller.members!.length)
.toString())), .toString())),
leading: CircleAvatar( leading: CircleAvatar(
backgroundColor: backgroundColor:

View File

@ -9,20 +9,20 @@ import '../user_bottom_sheet/user_bottom_sheet.dart';
class ParticipantListItem extends StatelessWidget { class ParticipantListItem extends StatelessWidget {
final User user; final User user;
const ParticipantListItem(this.user, {Key key}) : super(key: key); const ParticipantListItem(this.user, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final membershipBatch = <Membership, String>{ final membershipBatch = <Membership, String>{
Membership.join: '', Membership.join: '',
Membership.ban: L10n.of(context).banned, Membership.ban: L10n.of(context)!.banned,
Membership.invite: L10n.of(context).invited, Membership.invite: L10n.of(context)!.invited,
Membership.leave: L10n.of(context).leftTheChat, Membership.leave: L10n.of(context)!.leftTheChat,
}; };
final permissionBatch = user.powerLevel == 100 final permissionBatch = user.powerLevel == 100
? L10n.of(context).admin ? L10n.of(context)!.admin
: user.powerLevel >= 50 : user.powerLevel >= 50
? L10n.of(context).moderator ? L10n.of(context)!.moderator
: ''; : '';
return Opacity( return Opacity(
@ -49,7 +49,7 @@ class ParticipantListItem extends StatelessWidget {
), ),
child: Center(child: Text(permissionBatch)), child: Center(child: Text(permissionBatch)),
), ),
membershipBatch[user.membership].isEmpty membershipBatch[user.membership]!.isEmpty
? Container() ? Container()
: Container( : Container(
padding: const EdgeInsets.all(4), padding: const EdgeInsets.all(4),
@ -59,7 +59,7 @@ class ParticipantListItem extends StatelessWidget {
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: child:
Center(child: Text(membershipBatch[user.membership])), Center(child: Text(membershipBatch[user.membership]!)),
), ),
], ],
), ),

View File

@ -9,7 +9,7 @@ import 'package:fluffychat/widgets/matrix.dart';
import '../key_verification/key_verification_dialog.dart'; import '../key_verification/key_verification_dialog.dart';
class ChatEncryptionSettings extends StatefulWidget { class ChatEncryptionSettings extends StatefulWidget {
const ChatEncryptionSettings({Key key}) : super(key: key); const ChatEncryptionSettings({Key? key}) : super(key: key);
@override @override
ChatEncryptionSettingsController createState() => ChatEncryptionSettingsController createState() =>
@ -17,7 +17,7 @@ class ChatEncryptionSettings extends StatefulWidget {
} }
class ChatEncryptionSettingsController extends State<ChatEncryptionSettings> { class ChatEncryptionSettingsController extends State<ChatEncryptionSettings> {
String get roomId => VRouter.of(context).pathParameters['roomid']; String? get roomId => VRouter.of(context).pathParameters['roomid'];
Future<void> unblock(DeviceKeys key) async { Future<void> unblock(DeviceKeys key) async {
if (key.blocked) { if (key.blocked) {
@ -27,14 +27,14 @@ class ChatEncryptionSettingsController extends State<ChatEncryptionSettings> {
Future<void> onSelected( Future<void> onSelected(
BuildContext context, String action, DeviceKeys key) async { BuildContext context, String action, DeviceKeys key) async {
final room = Matrix.of(context).client.getRoomById(roomId); final room = Matrix.of(context).client.getRoomById(roomId!);
switch (action) { switch (action) {
case 'verify': case 'verify':
await unblock(key); await unblock(key);
final req = key.startVerification(); final req = key.startVerification();
req.onUpdate = () { req.onUpdate = () {
if (req.state == KeyVerificationState.done) { if (req.state == KeyVerificationState.done) {
setState(() => null); setState(() {});
} }
}; };
await KeyVerificationDialog(request: req).show(context); await KeyVerificationDialog(request: req).show(context);
@ -42,10 +42,10 @@ class ChatEncryptionSettingsController extends State<ChatEncryptionSettings> {
case 'verify_user': case 'verify_user':
await unblock(key); await unblock(key);
final req = final req =
await room.client.userDeviceKeys[key.userId].startVerification(); await room!.client.userDeviceKeys[key.userId]!.startVerification();
req.onUpdate = () { req.onUpdate = () {
if (req.state == KeyVerificationState.done) { if (req.state == KeyVerificationState.done) {
setState(() => null); setState(() {});
} }
}; };
await KeyVerificationDialog(request: req).show(context); await KeyVerificationDialog(request: req).show(context);
@ -55,11 +55,11 @@ class ChatEncryptionSettingsController extends State<ChatEncryptionSettings> {
await key.setVerified(false); await key.setVerified(false);
} }
await key.setBlocked(true); await key.setBlocked(true);
setState(() => null); setState(() {});
break; break;
case 'unblock': case 'unblock':
await unblock(key); await unblock(key);
setState(() => null); setState(() {});
break; break;
} }
} }

View File

@ -13,21 +13,21 @@ import '../../utils/matrix_sdk_extensions.dart/device_extension.dart';
class ChatEncryptionSettingsView extends StatelessWidget { class ChatEncryptionSettingsView extends StatelessWidget {
final ChatEncryptionSettingsController controller; final ChatEncryptionSettingsController controller;
const ChatEncryptionSettingsView(this.controller, {Key key}) const ChatEncryptionSettingsView(this.controller, {Key? key})
: super(key: key); : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final room = Matrix.of(context).client.getRoomById(controller.roomId); final room = Matrix.of(context).client.getRoomById(controller.roomId!)!;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.close_outlined), icon: const Icon(Icons.close_outlined),
onPressed: () => onPressed: () =>
VRouter.of(context).toSegments(['rooms', controller.roomId]), VRouter.of(context).toSegments(['rooms', controller.roomId!]),
), ),
title: Text(L10n.of(context).tapOnDeviceToVerify), title: Text(L10n.of(context)!.tapOnDeviceToVerify),
elevation: 0, elevation: 0,
), ),
body: MaxWidthBody( body: MaxWidthBody(
@ -36,7 +36,7 @@ class ChatEncryptionSettingsView extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
ListTile( ListTile(
title: Text(L10n.of(context).deviceVerifyDescription), title: Text(L10n.of(context)!.deviceVerifyDescription),
leading: CircleAvatar( leading: CircleAvatar(
backgroundColor: Theme.of(context).secondaryHeaderColor, backgroundColor: Theme.of(context).secondaryHeaderColor,
foregroundColor: Theme.of(context).colorScheme.secondary, foregroundColor: Theme.of(context).colorScheme.secondary,
@ -52,7 +52,7 @@ class ChatEncryptionSettingsView extends StatelessWidget {
builder: (BuildContext context, snapshot) { builder: (BuildContext context, snapshot) {
if (snapshot.hasError) { if (snapshot.hasError) {
return Center( return Center(
child: Text(L10n.of(context).oopsSomethingWentWrong + child: Text(L10n.of(context)!.oopsSomethingWentWrong +
': ' + ': ' +
snapshot.error.toString()), snapshot.error.toString()),
); );
@ -62,7 +62,7 @@ class ChatEncryptionSettingsView extends StatelessWidget {
child: CircularProgressIndicator.adaptive( child: CircularProgressIndicator.adaptive(
strokeWidth: 2)); strokeWidth: 2));
} }
final deviceKeys = snapshot.data; final deviceKeys = snapshot.data!;
return ListView.builder( return ListView.builder(
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
@ -75,18 +75,18 @@ class ChatEncryptionSettingsView extends StatelessWidget {
deviceKeys[i - 1].userId) ...{ deviceKeys[i - 1].userId) ...{
const Divider(height: 1, thickness: 1), const Divider(height: 1, thickness: 1),
PopupMenuButton( PopupMenuButton(
onSelected: (action) => controller.onSelected( onSelected: (dynamic action) => controller
context, action, deviceKeys[i]), .onSelected(context, action, deviceKeys[i]),
itemBuilder: (c) { itemBuilder: (c) {
final items = <PopupMenuEntry<String>>[]; final items = <PopupMenuEntry<String>>[];
if (room if (room
.client .client
.userDeviceKeys[deviceKeys[i].userId] .userDeviceKeys[deviceKeys[i].userId]!
.verified == .verified ==
UserVerifiedStatus.unknown) { UserVerifiedStatus.unknown) {
items.add(PopupMenuItem( items.add(PopupMenuItem(
value: 'verify_user', value: 'verify_user',
child: Text(L10n.of(context).verifyUser), child: Text(L10n.of(context)!.verifyUser),
)); ));
} }
return items; return items;
@ -114,8 +114,8 @@ class ChatEncryptionSettingsView extends StatelessWidget {
), ),
}, },
PopupMenuButton( PopupMenuButton(
onSelected: (action) => controller.onSelected( onSelected: (dynamic action) => controller
context, action, deviceKeys[i]), .onSelected(context, action, deviceKeys[i]),
itemBuilder: (c) { itemBuilder: (c) {
final items = <PopupMenuEntry<String>>[]; final items = <PopupMenuEntry<String>>[];
if (deviceKeys[i].blocked || if (deviceKeys[i].blocked ||
@ -125,19 +125,20 @@ class ChatEncryptionSettingsView extends StatelessWidget {
room.client.userID room.client.userID
? 'verify' ? 'verify'
: 'verify_user', : 'verify_user',
child: Text(L10n.of(context).verifyStart), child: Text(L10n.of(context)!.verifyStart),
)); ));
} }
if (deviceKeys[i].blocked) { if (deviceKeys[i].blocked) {
items.add(PopupMenuItem( items.add(PopupMenuItem(
value: 'unblock', value: 'unblock',
child: Text(L10n.of(context).unblockDevice), child:
Text(L10n.of(context)!.unblockDevice),
)); ));
} }
if (!deviceKeys[i].blocked) { if (!deviceKeys[i].blocked) {
items.add(PopupMenuItem( items.add(PopupMenuItem(
value: 'block', value: 'block',
child: Text(L10n.of(context).blockDevice), child: Text(L10n.of(context)!.blockDevice),
)); ));
} }
return items; return items;
@ -156,17 +157,17 @@ class ChatEncryptionSettingsView extends StatelessWidget {
subtitle: Row( subtitle: Row(
children: [ children: [
Text( Text(
deviceKeys[i].deviceId, deviceKeys[i].deviceId!,
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.w300), fontWeight: FontWeight.w300),
), ),
const Spacer(), const Spacer(),
Text( Text(
deviceKeys[i].blocked deviceKeys[i].blocked
? L10n.of(context).blocked ? L10n.of(context)!.blocked
: deviceKeys[i].verified : deviceKeys[i].verified
? L10n.of(context).verified ? L10n.of(context)!.verified
: L10n.of(context).unverified, : L10n.of(context)!.unverified,
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: deviceKeys[i].color, color: deviceKeys[i].color,

View File

@ -34,25 +34,28 @@ enum PopupMenuAction {
} }
class ChatList extends StatefulWidget { class ChatList extends StatefulWidget {
const ChatList({Key key}) : super(key: key); const ChatList({Key? key}) : super(key: key);
@override @override
ChatListController createState() => ChatListController(); ChatListController createState() => ChatListController();
} }
class ChatListController extends State<ChatList> { class ChatListController extends State<ChatList> {
StreamSubscription _intentDataStreamSubscription; StreamSubscription? _intentDataStreamSubscription;
StreamSubscription _intentFileStreamSubscription; StreamSubscription? _intentFileStreamSubscription;
StreamSubscription _intentUriStreamSubscription; StreamSubscription? _intentUriStreamSubscription;
String _activeSpaceId; String? _activeSpaceId;
String get activeSpaceId => String? get activeSpaceId {
Matrix.of(context).client.getRoomById(_activeSpaceId) == null final id = _activeSpaceId;
return id != null && Matrix.of(context).client.getRoomById(id) == null
? null ? null
: _activeSpaceId; : _activeSpaceId;
}
final ScrollController scrollController = ScrollController(); final ScrollController scrollController = ScrollController();
bool scrolledToTop = true; bool scrolledToTop = true;
@ -69,12 +72,12 @@ class ChatListController extends State<ChatList> {
} }
} }
void setActiveSpaceId(BuildContext context, String spaceId) { void setActiveSpaceId(BuildContext context, String? spaceId) {
setState(() => _activeSpaceId = spaceId); setState(() => _activeSpaceId = spaceId);
} }
void editSpace(BuildContext context, String spaceId) async { void editSpace(BuildContext context, String spaceId) async {
await Matrix.of(context).client.getRoomById(spaceId).postLoad(); await Matrix.of(context).client.getRoomById(spaceId)!.postLoad();
VRouter.of(context).toSegments(['spaces', spaceId]); VRouter.of(context).toSegments(['spaces', spaceId]);
} }
@ -82,7 +85,7 @@ class ChatListController extends State<ChatList> {
Matrix.of(context).client.rooms.where((r) => r.isSpace).toList(); Matrix.of(context).client.rooms.where((r) => r.isSpace).toList();
final selectedRoomIds = <String>{}; final selectedRoomIds = <String>{};
bool crossSigningCached; bool? crossSigningCached;
bool showChatBackupBanner = false; bool showChatBackupBanner = false;
void firstRunBootstrapAction() async { void firstRunBootstrapAction() async {
@ -96,7 +99,7 @@ class ChatListController extends State<ChatList> {
VRouter.of(context).to('/rooms'); VRouter.of(context).to('/rooms');
} }
String get activeChat => VRouter.of(context).pathParameters['roomid']; String? get activeChat => VRouter.of(context).pathParameters['roomid'];
SelectMode get selectMode => Matrix.of(context).shareContent != null SelectMode get selectMode => Matrix.of(context).shareContent != null
? SelectMode.share ? SelectMode.share
@ -105,7 +108,7 @@ class ChatListController extends State<ChatList> {
: SelectMode.select; : SelectMode.select;
void _processIncomingSharedFiles(List<SharedMediaFile> files) { void _processIncomingSharedFiles(List<SharedMediaFile> files) {
if (files?.isEmpty ?? true) return; if (files.isEmpty) return;
VRouter.of(context).to('/rooms'); VRouter.of(context).to('/rooms');
final file = File(files.first.path); final file = File(files.first.path);
@ -118,7 +121,7 @@ class ChatListController extends State<ChatList> {
}; };
} }
void _processIncomingSharedText(String text) { void _processIncomingSharedText(String? text) {
if (text == null) return; if (text == null) return;
VRouter.of(context).to('/rooms'); VRouter.of(context).to('/rooms');
if (text.toLowerCase().startsWith(AppConfig.deepLinkPrefix) || if (text.toLowerCase().startsWith(AppConfig.deepLinkPrefix) ||
@ -133,10 +136,10 @@ class ChatListController extends State<ChatList> {
}; };
} }
void _processIncomingUris(String text) async { void _processIncomingUris(String? text) async {
if (text == null) return; if (text == null) return;
VRouter.of(context).to('/rooms'); VRouter.of(context).to('/rooms');
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance!.addPostFrameCallback((_) {
UrlLauncher(context, text).openMatrixToUrl(); UrlLauncher(context, text).openMatrixToUrl();
}); });
} }
@ -180,10 +183,10 @@ class ChatListController extends State<ChatList> {
await Matrix.of(context).client.accountDataLoading; await Matrix.of(context).client.accountDataLoading;
await Matrix.of(context).client.userDeviceKeysLoading; await Matrix.of(context).client.userDeviceKeysLoading;
final crossSigning = final crossSigning =
await Matrix.of(context).client.encryption?.crossSigning?.isCached() ?? await Matrix.of(context).client.encryption?.crossSigning.isCached() ??
false; false;
final needsBootstrap = final needsBootstrap =
Matrix.of(context).client.encryption?.crossSigning?.enabled == false || Matrix.of(context).client.encryption?.crossSigning.enabled == false ||
crossSigning == false; crossSigning == false;
final isUnknownSession = Matrix.of(context).client.isUnknownSession; final isUnknownSession = Matrix.of(context).client.isUnknownSession;
if (needsBootstrap || isUnknownSession) { if (needsBootstrap || isUnknownSession) {
@ -206,23 +209,21 @@ class ChatListController extends State<ChatList> {
if (room.isSpace && room.membership == Membership.join && !room.isUnread) { if (room.isSpace && room.membership == Membership.join && !room.isUnread) {
return false; return false;
} }
if (room.getState(EventTypes.RoomCreate)?.content?.tryGet<String>('type') == if (room.getState(EventTypes.RoomCreate)?.content.tryGet<String>('type') ==
ClientStoriesExtension.storiesRoomType) { ClientStoriesExtension.storiesRoomType) {
return false; return false;
} }
if (activeSpaceId != null) { if (activeSpaceId != null) {
final space = Matrix.of(context).client.getRoomById(activeSpaceId); final space = Matrix.of(context).client.getRoomById(activeSpaceId!)!;
if (space.spaceChildren?.any((child) => child.roomId == room.id) ?? if (space.spaceChildren.any((child) => child.roomId == room.id)) {
false) {
return true; return true;
} }
if (room.spaceParents?.any((parent) => parent.roomId == activeSpaceId) ?? if (room.spaceParents.any((parent) => parent.roomId == activeSpaceId)) {
false) {
return true; return true;
} }
if (room.isDirectChat && if (room.isDirectChat &&
room.summary?.mHeroes != null && room.summary.mHeroes != null &&
room.summary.mHeroes.any((userId) { room.summary.mHeroes!.any((userId) {
final user = space.getState(EventTypes.RoomMember, userId)?.asUser; final user = space.getState(EventTypes.RoomMember, userId)?.asUser;
return user != null && user.membership == Membership.join; return user != null && user.membership == Membership.join;
})) { })) {
@ -246,9 +247,9 @@ class ChatListController extends State<ChatList> {
final markUnread = anySelectedRoomNotMarkedUnread; final markUnread = anySelectedRoomNotMarkedUnread;
final client = Matrix.of(context).client; final client = Matrix.of(context).client;
for (final roomId in selectedRoomIds) { for (final roomId in selectedRoomIds) {
final room = client.getRoomById(roomId); final room = client.getRoomById(roomId)!;
if (room.markedUnread == markUnread) continue; if (room.markedUnread == markUnread) continue;
await client.getRoomById(roomId).markUnread(markUnread); await client.getRoomById(roomId)!.markUnread(markUnread);
} }
}, },
); );
@ -262,9 +263,9 @@ class ChatListController extends State<ChatList> {
final makeFavorite = anySelectedRoomNotFavorite; final makeFavorite = anySelectedRoomNotFavorite;
final client = Matrix.of(context).client; final client = Matrix.of(context).client;
for (final roomId in selectedRoomIds) { for (final roomId in selectedRoomIds) {
final room = client.getRoomById(roomId); final room = client.getRoomById(roomId)!;
if (room.isFavourite == makeFavorite) continue; if (room.isFavourite == makeFavorite) continue;
await client.getRoomById(roomId).setFavourite(makeFavorite); await client.getRoomById(roomId)!.setFavourite(makeFavorite);
} }
}, },
); );
@ -280,9 +281,9 @@ class ChatListController extends State<ChatList> {
: PushRuleState.notify; : PushRuleState.notify;
final client = Matrix.of(context).client; final client = Matrix.of(context).client;
for (final roomId in selectedRoomIds) { for (final roomId in selectedRoomIds) {
final room = client.getRoomById(roomId); final room = client.getRoomById(roomId)!;
if (room.pushRuleState == newState) continue; if (room.pushRuleState == newState) continue;
await client.getRoomById(roomId).setPushRuleState(newState); await client.getRoomById(roomId)!.setPushRuleState(newState);
} }
}, },
); );
@ -293,9 +294,9 @@ class ChatListController extends State<ChatList> {
final confirmed = await showOkCancelAlertDialog( final confirmed = await showOkCancelAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).areYouSure, title: L10n.of(context)!.areYouSure,
okLabel: L10n.of(context).yes, okLabel: L10n.of(context)!.yes,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context)!.cancel,
) == ) ==
OkCancelResult.ok; OkCancelResult.ok;
if (!confirmed) return; if (!confirmed) return;
@ -303,26 +304,26 @@ class ChatListController extends State<ChatList> {
context: context, context: context,
future: () => _archiveSelectedRooms(), future: () => _archiveSelectedRooms(),
); );
setState(() => null); setState(() {});
} }
void setStatus() async { void setStatus() async {
final input = await showTextInputDialog( final input = await showTextInputDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).setStatus, title: L10n.of(context)!.setStatus,
okLabel: L10n.of(context).ok, okLabel: L10n.of(context)!.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context)!.cancel,
textFields: [ textFields: [
DialogTextField( DialogTextField(
hintText: L10n.of(context).statusExampleMessage, hintText: L10n.of(context)!.statusExampleMessage,
), ),
]); ]);
if (input == null) return; if (input == null) return;
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => Matrix.of(context).client.setPresence( future: () => Matrix.of(context).client.setPresence(
Matrix.of(context).client.userID, Matrix.of(context).client.userID!,
PresenceType.online, PresenceType.online,
statusMsg: input.single, statusMsg: input.single,
), ),
@ -339,7 +340,7 @@ class ChatListController extends State<ChatList> {
break; break;
case PopupMenuAction.invite: case PopupMenuAction.invite:
FluffyShare.share( FluffyShare.share(
L10n.of(context).inviteText(Matrix.of(context).client.userID, L10n.of(context)!.inviteText(Matrix.of(context).client.userID!,
'https://matrix.to/#/${Matrix.of(context).client.userID}?client=im.fluffychat'), 'https://matrix.to/#/${Matrix.of(context).client.userID}?client=im.fluffychat'),
context); context);
break; break;
@ -360,7 +361,7 @@ class ChatListController extends State<ChatList> {
while (selectedRoomIds.isNotEmpty) { while (selectedRoomIds.isNotEmpty) {
final roomId = selectedRoomIds.first; final roomId = selectedRoomIds.first;
try { try {
await client.getRoomById(roomId).leave(); await client.getRoomById(roomId)!.leave();
} finally { } finally {
toggleSelection(roomId); toggleSelection(roomId);
} }
@ -371,36 +372,36 @@ class ChatListController extends State<ChatList> {
if (activeSpaceId != null) { if (activeSpaceId != null) {
final consent = await showOkCancelAlertDialog( final consent = await showOkCancelAlertDialog(
context: context, context: context,
title: L10n.of(context).removeFromSpace, title: L10n.of(context)!.removeFromSpace,
message: L10n.of(context).removeFromSpaceDescription, message: L10n.of(context)!.removeFromSpaceDescription,
okLabel: L10n.of(context).remove, okLabel: L10n.of(context)!.remove,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context)!.cancel,
isDestructiveAction: true, isDestructiveAction: true,
fullyCapitalizedForMaterial: false, fullyCapitalizedForMaterial: false,
); );
if (consent != OkCancelResult.ok) return; if (consent != OkCancelResult.ok) return;
final space = Matrix.of(context).client.getRoomById(activeSpaceId); final space = Matrix.of(context).client.getRoomById(activeSpaceId!);
final result = await showFutureLoadingDialog( final result = await showFutureLoadingDialog(
context: context, context: context,
future: () async { future: () async {
for (final roomId in selectedRoomIds) { for (final roomId in selectedRoomIds) {
await space.removeSpaceChild(roomId); await space!.removeSpaceChild(roomId);
} }
}, },
); );
if (result.error == null) { if (result.error == null) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(L10n.of(context).chatHasBeenRemovedFromThisSpace), content: Text(L10n.of(context)!.chatHasBeenRemovedFromThisSpace),
), ),
); );
} }
} else { } else {
final selectedSpace = await showConfirmationDialog<String>( final selectedSpace = await showConfirmationDialog<String>(
context: context, context: context,
title: L10n.of(context).addToSpace, title: L10n.of(context)!.addToSpace,
message: L10n.of(context).addToSpaceDescription, message: L10n.of(context)!.addToSpaceDescription,
fullyCapitalizedForMaterial: false, fullyCapitalizedForMaterial: false,
actions: Matrix.of(context) actions: Matrix.of(context)
.client .client
@ -417,7 +418,7 @@ class ChatListController extends State<ChatList> {
final result = await showFutureLoadingDialog( final result = await showFutureLoadingDialog(
context: context, context: context,
future: () async { future: () async {
final space = Matrix.of(context).client.getRoomById(selectedSpace); final space = Matrix.of(context).client.getRoomById(selectedSpace)!;
if (space.canSendDefaultStates) { if (space.canSendDefaultStates) {
for (final roomId in selectedRoomIds) { for (final roomId in selectedRoomIds) {
await space.setSpaceChild(roomId); await space.setSpaceChild(roomId);
@ -428,7 +429,7 @@ class ChatListController extends State<ChatList> {
if (result.error == null) { if (result.error == null) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(L10n.of(context).chatHasBeenAddedToThisSpace), content: Text(L10n.of(context)!.chatHasBeenAddedToThisSpace),
), ),
); );
} }
@ -437,13 +438,13 @@ class ChatListController extends State<ChatList> {
} }
bool get anySelectedRoomNotMarkedUnread => selectedRoomIds.any( bool get anySelectedRoomNotMarkedUnread => selectedRoomIds.any(
(roomId) => !Matrix.of(context).client.getRoomById(roomId).markedUnread); (roomId) => !Matrix.of(context).client.getRoomById(roomId)!.markedUnread);
bool get anySelectedRoomNotFavorite => selectedRoomIds.any( bool get anySelectedRoomNotFavorite => selectedRoomIds.any(
(roomId) => !Matrix.of(context).client.getRoomById(roomId).isFavourite); (roomId) => !Matrix.of(context).client.getRoomById(roomId)!.isFavourite);
bool get anySelectedRoomNotMuted => selectedRoomIds.any((roomId) => bool get anySelectedRoomNotMuted => selectedRoomIds.any((roomId) =>
Matrix.of(context).client.getRoomById(roomId).pushRuleState == Matrix.of(context).client.getRoomById(roomId)!.pushRuleState ==
PushRuleState.notify); PushRuleState.notify);
bool waitForFirstSync = false; bool waitForFirstSync = false;
@ -457,10 +458,10 @@ class ChatListController extends State<ChatList> {
} }
// Load space members to display DM rooms // Load space members to display DM rooms
if (activeSpaceId != null) { if (activeSpaceId != null) {
final space = client.getRoomById(activeSpaceId); final space = client.getRoomById(activeSpaceId!)!;
final localMembers = space.getParticipants().length; final localMembers = space.getParticipants().length;
final actualMembersCount = (space.summary?.mInvitedMemberCount ?? 0) + final actualMembersCount = (space.summary.mInvitedMemberCount ?? 0) +
(space.summary?.mJoinedMemberCount ?? 0); (space.summary.mJoinedMemberCount ?? 0);
if (localMembers < actualMembersCount) { if (localMembers < actualMembersCount) {
await space.requestParticipants(); await space.requestParticipants();
} }
@ -468,7 +469,7 @@ class ChatListController extends State<ChatList> {
setState(() { setState(() {
waitForFirstSync = true; waitForFirstSync = true;
}); });
WidgetsBinding.instance.addPostFrameCallback((_) => checkBootstrap()); WidgetsBinding.instance!.addPostFrameCallback((_) => checkBootstrap());
return; return;
} }
@ -481,7 +482,6 @@ class ChatListController extends State<ChatList> {
} }
void setActiveClient(Client client) { void setActiveClient(Client client) {
if (client == null) return;
VRouter.of(context).to('/rooms'); VRouter.of(context).to('/rooms');
setState(() { setState(() {
_activeSpaceId = null; _activeSpaceId = null;
@ -498,30 +498,30 @@ class ChatListController extends State<ChatList> {
selectedRoomIds.clear(); selectedRoomIds.clear();
Matrix.of(context).activeBundle = bundle; Matrix.of(context).activeBundle = bundle;
if (!Matrix.of(context) if (!Matrix.of(context)
.currentBundle .currentBundle!
.any((client) => client == Matrix.of(context).client)) { .any((client) => client == Matrix.of(context).client)) {
Matrix.of(context) Matrix.of(context)
.setActiveClient(Matrix.of(context).currentBundle.first); .setActiveClient(Matrix.of(context).currentBundle!.first);
} }
}); });
} }
void editBundlesForAccount(String userId, String activeBundle) async { void editBundlesForAccount(String? userId, String? activeBundle) async {
final client = Matrix.of(context) final client = Matrix.of(context)
.widget .widget
.clients[Matrix.of(context).getClientIndexByMatrixId(userId)]; .clients[Matrix.of(context).getClientIndexByMatrixId(userId!)];
final action = await showConfirmationDialog<EditBundleAction>( final action = await showConfirmationDialog<EditBundleAction>(
context: context, context: context,
title: L10n.of(context).editBundlesForAccount, title: L10n.of(context)!.editBundlesForAccount,
actions: [ actions: [
AlertDialogAction( AlertDialogAction(
key: EditBundleAction.addToBundle, key: EditBundleAction.addToBundle,
label: L10n.of(context).addToBundle, label: L10n.of(context)!.addToBundle,
), ),
if (activeBundle != client.userID) if (activeBundle != client.userID)
AlertDialogAction( AlertDialogAction(
key: EditBundleAction.removeFromBundle, key: EditBundleAction.removeFromBundle,
label: L10n.of(context).removeFromBundle, label: L10n.of(context)!.removeFromBundle,
), ),
], ],
); );
@ -530,9 +530,9 @@ class ChatListController extends State<ChatList> {
case EditBundleAction.addToBundle: case EditBundleAction.addToBundle:
final bundle = await showTextInputDialog( final bundle = await showTextInputDialog(
context: context, context: context,
title: L10n.of(context).bundleName, title: L10n.of(context)!.bundleName,
textFields: [ textFields: [
DialogTextField(hintText: L10n.of(context).bundleName) DialogTextField(hintText: L10n.of(context)!.bundleName)
]); ]);
if (bundle == null || bundle.isEmpty || bundle.single.isEmpty) return; if (bundle == null || bundle.isEmpty || bundle.single.isEmpty) return;
await showFutureLoadingDialog( await showFutureLoadingDialog(
@ -543,7 +543,7 @@ class ChatListController extends State<ChatList> {
case EditBundleAction.removeFromBundle: case EditBundleAction.removeFromBundle:
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => client.removeFromAccountBundle(activeBundle), future: () => client.removeFromAccountBundle(activeBundle!),
); );
} }
} }
@ -552,7 +552,7 @@ class ChatListController extends State<ChatList> {
Matrix.of(context).hasComplexBundles && Matrix.of(context).hasComplexBundles &&
Matrix.of(context).accountBundles.keys.length > 1; Matrix.of(context).accountBundles.keys.length > 1;
String get secureActiveBundle { String? get secureActiveBundle {
if (Matrix.of(context).activeBundle == null || if (Matrix.of(context).activeBundle == null ||
!Matrix.of(context) !Matrix.of(context)
.accountBundles .accountBundles
@ -564,7 +564,7 @@ class ChatListController extends State<ChatList> {
} }
void resetActiveBundle() { void resetActiveBundle() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) { WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
setState(() { setState(() {
Matrix.of(context).activeBundle = null; Matrix.of(context).activeBundle = null;
}); });

View File

@ -21,9 +21,9 @@ class ChatListItem extends StatelessWidget {
final Room room; final Room room;
final bool activeChat; final bool activeChat;
final bool selected; final bool selected;
final Function onForget; final Function? onForget;
final Function onTap; final Function? onTap;
final Function onLongPress; final Function? onLongPress;
const ChatListItem( const ChatListItem(
this.room, { this.room, {
@ -32,11 +32,11 @@ class ChatListItem extends StatelessWidget {
this.onTap, this.onTap,
this.onLongPress, this.onLongPress,
this.onForget, this.onForget,
Key key, Key? key,
}) : super(key: key); }) : super(key: key);
dynamic clickAction(BuildContext context) async { dynamic clickAction(BuildContext context) async {
if (onTap != null) return onTap(); if (onTap != null) return onTap!();
if (!activeChat) { if (!activeChat) {
if (room.membership == Membership.invite && if (room.membership == Membership.invite &&
(await showFutureLoadingDialog( (await showFutureLoadingDialog(
@ -57,7 +57,7 @@ class ChatListItem extends StatelessWidget {
if (room.membership == Membership.ban) { if (room.membership == Membership.ban) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(L10n.of(context).youHaveBeenBannedFromThisChat), content: Text(L10n.of(context)!.youHaveBeenBannedFromThisChat),
), ),
); );
return; return;
@ -66,15 +66,15 @@ class ChatListItem extends StatelessWidget {
if (room.membership == Membership.leave) { if (room.membership == Membership.leave) {
final action = await showModalActionSheet<ArchivedRoomAction>( final action = await showModalActionSheet<ArchivedRoomAction>(
context: context, context: context,
title: L10n.of(context).archivedRoom, title: L10n.of(context)!.archivedRoom,
message: L10n.of(context).thisRoomHasBeenArchived, message: L10n.of(context)!.thisRoomHasBeenArchived,
actions: [ actions: [
SheetAction( SheetAction(
label: L10n.of(context).rejoin, label: L10n.of(context)!.rejoin,
key: ArchivedRoomAction.rejoin, key: ArchivedRoomAction.rejoin,
), ),
SheetAction( SheetAction(
label: L10n.of(context).delete, label: L10n.of(context)!.delete,
key: ArchivedRoomAction.delete, key: ArchivedRoomAction.delete,
isDestructiveAction: true, isDestructiveAction: true,
), ),
@ -97,18 +97,18 @@ class ChatListItem extends StatelessWidget {
if (room.membership == Membership.join) { if (room.membership == Membership.join) {
if (Matrix.of(context).shareContent != null) { if (Matrix.of(context).shareContent != null) {
if (Matrix.of(context).shareContent['msgtype'] == if (Matrix.of(context).shareContent!['msgtype'] ==
'chat.fluffy.shared_file') { 'chat.fluffy.shared_file') {
await showDialog( await showDialog(
context: context, context: context,
useRootNavigator: false, useRootNavigator: false,
builder: (c) => SendFileDialog( builder: (c) => SendFileDialog(
file: Matrix.of(context).shareContent['file'], file: Matrix.of(context).shareContent!['file'],
room: room, room: room,
), ),
); );
} else { } else {
unawaited(room.sendEvent(Matrix.of(context).shareContent)); unawaited(room.sendEvent(Matrix.of(context).shareContent!));
} }
Matrix.of(context).shareContent = null; Matrix.of(context).shareContent = null;
} }
@ -125,16 +125,16 @@ class ChatListItem extends StatelessWidget {
future: () => room.forget(), future: () => room.forget(),
); );
if (success.error == null) { if (success.error == null) {
if (onForget != null) onForget(); if (onForget != null) onForget!();
} }
return success; return;
} }
final confirmed = await showOkCancelAlertDialog( final confirmed = await showOkCancelAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).areYouSure, title: L10n.of(context)!.areYouSure,
okLabel: L10n.of(context).yes, okLabel: L10n.of(context)!.yes,
cancelLabel: L10n.of(context).no, cancelLabel: L10n.of(context)!.no,
); );
if (confirmed == OkCancelResult.cancel) return; if (confirmed == OkCancelResult.cancel) return;
await showFutureLoadingDialog( await showFutureLoadingDialog(
@ -160,7 +160,7 @@ class ChatListItem extends StatelessWidget {
selectedTileColor: selected selectedTileColor: selected
? Theme.of(context).primaryColor.withAlpha(100) ? Theme.of(context).primaryColor.withAlpha(100)
: Theme.of(context).secondaryHeaderColor, : Theme.of(context).secondaryHeaderColor,
onLongPress: onLongPress, onLongPress: onLongPress as void Function()?,
leading: selected leading: selected
? SizedBox( ? SizedBox(
width: Avatar.defaultSize, width: Avatar.defaultSize,
@ -174,13 +174,13 @@ class ChatListItem extends StatelessWidget {
: Avatar( : Avatar(
mxContent: room.avatar, mxContent: room.avatar,
name: room.displayname, name: room.displayname,
onTap: onLongPress, onTap: onLongPress as void Function()?,
), ),
title: Row( title: Row(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: Text( child: Text(
room.getLocalizedDisplayname(MatrixLocals(L10n.of(context))), room.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
softWrap: false, softWrap: false,
@ -188,7 +188,7 @@ class ChatListItem extends StatelessWidget {
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: unread color: unread
? Theme.of(context).colorScheme.secondary ? Theme.of(context).colorScheme.secondary
: Theme.of(context).textTheme.bodyText1.color, : Theme.of(context).textTheme.bodyText1!.color,
), ),
), ),
), ),
@ -218,7 +218,7 @@ class ChatListItem extends StatelessWidget {
fontSize: 13, fontSize: 13,
color: unread color: unread
? Theme.of(context).colorScheme.secondary ? Theme.of(context).colorScheme.secondary
: Theme.of(context).textTheme.bodyText2.color, : Theme.of(context).textTheme.bodyText2!.color,
), ),
), ),
), ),
@ -229,7 +229,7 @@ class ChatListItem extends StatelessWidget {
children: <Widget>[ children: <Widget>[
if (typingText.isEmpty && if (typingText.isEmpty &&
ownMessage && ownMessage &&
room.lastEvent.status.isSending) ...[ room.lastEvent!.status.isSending) ...[
const SizedBox( const SizedBox(
width: 16, width: 16,
height: 16, height: 16,
@ -261,9 +261,9 @@ class ChatListItem extends StatelessWidget {
) )
: Text( : Text(
room.membership == Membership.invite room.membership == Membership.invite
? L10n.of(context).youAreInvitedToThisChat ? L10n.of(context)!.youAreInvitedToThisChat
: room.lastEvent?.getLocalizedBody( : room.lastEvent?.getLocalizedBody(
MatrixLocals(L10n.of(context)), MatrixLocals(L10n.of(context)!),
hideReply: true, hideReply: true,
hideEdit: true, hideEdit: true,
plaintextBody: true, plaintextBody: true,
@ -271,14 +271,14 @@ class ChatListItem extends StatelessWidget {
room.directChatMatrixID != room.directChatMatrixID !=
room.lastEvent?.senderId, room.lastEvent?.senderId,
) ?? ) ??
L10n.of(context).emptyChat, L10n.of(context)!.emptyChat,
softWrap: false, softWrap: false,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
color: unread color: unread
? Theme.of(context).colorScheme.secondary ? Theme.of(context).colorScheme.secondary
: Theme.of(context).textTheme.bodyText2.color, : Theme.of(context).textTheme.bodyText2!.color,
decoration: room.lastEvent?.redacted == true decoration: room.lastEvent?.redacted == true
? TextDecoration.lineThrough ? TextDecoration.lineThrough
: null, : null,

View File

@ -21,11 +21,11 @@ import '../../widgets/matrix.dart';
class ChatListView extends StatelessWidget { class ChatListView extends StatelessWidget {
final ChatListController controller; final ChatListController controller;
const ChatListView(this.controller, {Key key}) : super(key: key); const ChatListView(this.controller, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return StreamBuilder<Object>( return StreamBuilder<Object?>(
stream: Matrix.of(context).onShareContentChanged.stream, stream: Matrix.of(context).onShareContentChanged.stream,
builder: (_, __) { builder: (_, __) {
final selectMode = controller.selectMode; final selectMode = controller.selectMode;
@ -48,7 +48,7 @@ class ChatListView extends StatelessWidget {
? ClientChooserButton(controller) ? ClientChooserButton(controller)
: null : null
: IconButton( : IconButton(
tooltip: L10n.of(context).cancel, tooltip: L10n.of(context)!.cancel,
icon: const Icon(Icons.close_outlined), icon: const Icon(Icons.close_outlined),
onPressed: controller.cancelAction, onPressed: controller.cancelAction,
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
@ -60,12 +60,12 @@ class ChatListView extends StatelessWidget {
? [ ? [
if (controller.spaces.isNotEmpty) if (controller.spaces.isNotEmpty)
IconButton( IconButton(
tooltip: L10n.of(context).addToSpace, tooltip: L10n.of(context)!.addToSpace,
icon: const Icon(Icons.group_work_outlined), icon: const Icon(Icons.group_work_outlined),
onPressed: controller.addOrRemoveToSpace, onPressed: controller.addOrRemoveToSpace,
), ),
IconButton( IconButton(
tooltip: L10n.of(context).toggleUnread, tooltip: L10n.of(context)!.toggleUnread,
icon: Icon( icon: Icon(
controller.anySelectedRoomNotMarkedUnread controller.anySelectedRoomNotMarkedUnread
? Icons.mark_chat_read_outlined ? Icons.mark_chat_read_outlined
@ -73,7 +73,7 @@ class ChatListView extends StatelessWidget {
onPressed: controller.toggleUnread, onPressed: controller.toggleUnread,
), ),
IconButton( IconButton(
tooltip: L10n.of(context).toggleFavorite, tooltip: L10n.of(context)!.toggleFavorite,
icon: Icon(controller.anySelectedRoomNotFavorite icon: Icon(controller.anySelectedRoomNotFavorite
? Icons.push_pin_outlined ? Icons.push_pin_outlined
: Icons.push_pin), : Icons.push_pin),
@ -83,19 +83,19 @@ class ChatListView extends StatelessWidget {
icon: Icon(controller.anySelectedRoomNotMuted icon: Icon(controller.anySelectedRoomNotMuted
? Icons.notifications_off_outlined ? Icons.notifications_off_outlined
: Icons.notifications_outlined), : Icons.notifications_outlined),
tooltip: L10n.of(context).toggleMuted, tooltip: L10n.of(context)!.toggleMuted,
onPressed: controller.toggleMuted, onPressed: controller.toggleMuted,
), ),
IconButton( IconButton(
icon: const Icon(Icons.delete_outlined), icon: const Icon(Icons.delete_outlined),
tooltip: L10n.of(context).archive, tooltip: L10n.of(context)!.archive,
onPressed: controller.archiveAction, onPressed: controller.archiveAction,
), ),
] ]
: [ : [
IconButton( IconButton(
icon: const Icon(Icons.search_outlined), icon: const Icon(Icons.search_outlined),
tooltip: L10n.of(context).search, tooltip: L10n.of(context)!.search,
onPressed: () => onPressed: () =>
VRouter.of(context).to('/search'), VRouter.of(context).to('/search'),
), ),
@ -109,7 +109,7 @@ class ChatListView extends StatelessWidget {
children: [ children: [
const Icon(Icons.edit_outlined), const Icon(Icons.edit_outlined),
const SizedBox(width: 12), const SizedBox(width: 12),
Text(L10n.of(context).setStatus), Text(L10n.of(context)!.setStatus),
], ],
), ),
), ),
@ -120,7 +120,7 @@ class ChatListView extends StatelessWidget {
children: [ children: [
const Icon(Icons.group_add_outlined), const Icon(Icons.group_add_outlined),
const SizedBox(width: 12), const SizedBox(width: 12),
Text(L10n.of(context).createNewGroup), Text(L10n.of(context)!.createNewGroup),
], ],
), ),
), ),
@ -131,7 +131,7 @@ class ChatListView extends StatelessWidget {
children: [ children: [
const Icon(Icons.group_work_outlined), const Icon(Icons.group_work_outlined),
const SizedBox(width: 12), const SizedBox(width: 12),
Text(L10n.of(context).createNewSpace), Text(L10n.of(context)!.createNewSpace),
], ],
), ),
), ),
@ -142,7 +142,7 @@ class ChatListView extends StatelessWidget {
children: [ children: [
const Icon(Icons.share_outlined), const Icon(Icons.share_outlined),
const SizedBox(width: 12), const SizedBox(width: 12),
Text(L10n.of(context).inviteContact), Text(L10n.of(context)!.inviteContact),
], ],
), ),
), ),
@ -153,7 +153,7 @@ class ChatListView extends StatelessWidget {
children: [ children: [
const Icon(Icons.archive_outlined), const Icon(Icons.archive_outlined),
const SizedBox(width: 12), const SizedBox(width: 12),
Text(L10n.of(context).archive), Text(L10n.of(context)!.archive),
], ],
), ),
), ),
@ -164,7 +164,7 @@ class ChatListView extends StatelessWidget {
children: [ children: [
const Icon(Icons.settings_outlined), const Icon(Icons.settings_outlined),
const SizedBox(width: 12), const SizedBox(width: 12),
Text(L10n.of(context).settings), Text(L10n.of(context)!.settings),
], ],
), ),
), ),
@ -172,14 +172,14 @@ class ChatListView extends StatelessWidget {
), ),
], ],
title: Text(selectMode == SelectMode.share title: Text(selectMode == SelectMode.share
? L10n.of(context).share ? L10n.of(context)!.share
: selectMode == SelectMode.select : selectMode == SelectMode.select
? controller.selectedRoomIds.length.toString() ? controller.selectedRoomIds.length.toString()
: controller.activeSpaceId == null : controller.activeSpaceId == null
? AppConfig.applicationName ? AppConfig.applicationName
: Matrix.of(context) : Matrix.of(context)
.client .client
.getRoomById(controller.activeSpaceId) .getRoomById(controller.activeSpaceId!)!
.displayname), .displayname),
), ),
body: Column(children: [ body: Column(children: [
@ -197,7 +197,7 @@ class ChatListView extends StatelessWidget {
fit: BoxFit.contain, fit: BoxFit.contain,
width: 44, width: 44,
), ),
title: Text(L10n.of(context).setupChatBackupNow), title: Text(L10n.of(context)!.setupChatBackupNow),
trailing: const Icon(Icons.chevron_right_outlined), trailing: const Icon(Icons.chevron_right_outlined),
onTap: controller.firstRunBootstrapAction, onTap: controller.firstRunBootstrapAction,
), ),
@ -211,7 +211,7 @@ class ChatListView extends StatelessWidget {
onPressed: () => onPressed: () =>
VRouter.of(context).to('/newprivatechat'), VRouter.of(context).to('/newprivatechat'),
icon: const Icon(CupertinoIcons.chat_bubble), icon: const Icon(CupertinoIcons.chat_bubble),
label: Text(L10n.of(context).newChat), label: Text(L10n.of(context)!.newChat),
) )
: null, : null,
bottomNavigationBar: Column( bottomNavigationBar: Column(
@ -232,7 +232,7 @@ class ChatListView extends StatelessWidget {
class _ChatListViewBody extends StatefulWidget { class _ChatListViewBody extends StatefulWidget {
final ChatListController controller; final ChatListController controller;
const _ChatListViewBody(this.controller, {Key key}) : super(key: key); const _ChatListViewBody(this.controller, {Key? key}) : super(key: key);
@override @override
State<_ChatListViewBody> createState() => _ChatListViewBodyState(); State<_ChatListViewBody> createState() => _ChatListViewBodyState();
@ -240,12 +240,12 @@ class _ChatListViewBody extends StatefulWidget {
class _ChatListViewBodyState extends State<_ChatListViewBody> { class _ChatListViewBodyState extends State<_ChatListViewBody> {
// the matrix sync stream // the matrix sync stream
StreamSubscription _subscription; late StreamSubscription _subscription;
StreamSubscription _clientSubscription; late StreamSubscription _clientSubscription;
// used to check the animation direction // used to check the animation direction
String _lastUserId; String? _lastUserId;
String _lastSpaceId; String? _lastSpaceId;
@override @override
void initState() { void initState() {
@ -285,7 +285,7 @@ class _ChatListViewBodyState extends State<_ChatListViewBody> {
), ),
Center( Center(
child: Text( child: Text(
L10n.of(context).startYourFirstChat, L10n.of(context)!.startYourFirstChat,
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: const TextStyle( style: const TextStyle(
color: Colors.grey, color: Colors.grey,
@ -324,9 +324,9 @@ class _ChatListViewBodyState extends State<_ChatListViewBody> {
} else { } else {
const dummyChatCount = 8; const dummyChatCount = 8;
final titleColor = final titleColor =
Theme.of(context).textTheme.bodyText1.color.withAlpha(100); Theme.of(context).textTheme.bodyText1!.color!.withAlpha(100);
final subtitleColor = final subtitleColor =
Theme.of(context).textTheme.bodyText1.color.withAlpha(50); Theme.of(context).textTheme.bodyText1!.color!.withAlpha(50);
child = ListView.builder( child = ListView.builder(
itemCount: dummyChatCount, itemCount: dummyChatCount,
itemBuilder: (context, i) => Opacity( itemBuilder: (context, i) => Opacity(
@ -336,7 +336,7 @@ class _ChatListViewBodyState extends State<_ChatListViewBody> {
backgroundColor: titleColor, backgroundColor: titleColor,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 1, strokeWidth: 1,
color: Theme.of(context).textTheme.bodyText1.color, color: Theme.of(context).textTheme.bodyText1!.color,
), ),
), ),
title: Row( title: Row(
@ -417,11 +417,11 @@ class _ChatListViewBodyState extends State<_ChatListViewBody> {
final newClient = Matrix.of(context).client; final newClient = Matrix.of(context).client;
if (_lastUserId != newClient.userID) { if (_lastUserId != newClient.userID) {
reversed = Matrix.of(context) reversed = Matrix.of(context)
.currentBundle .currentBundle!
.indexWhere((element) => element.userID == _lastUserId) < .indexWhere((element) => element!.userID == _lastUserId) <
Matrix.of(context) Matrix.of(context)
.currentBundle .currentBundle!
.indexWhere((element) => element.userID == newClient.userID); .indexWhere((element) => element!.userID == newClient.userID);
} }
// otherwise, the space changed... // otherwise, the space changed...
else { else {

View File

@ -8,20 +8,20 @@ import 'chat_list.dart';
class ClientChooserButton extends StatelessWidget { class ClientChooserButton extends StatelessWidget {
final ChatListController controller; final ChatListController controller;
const ClientChooserButton(this.controller, {Key key}) : super(key: key); const ClientChooserButton(this.controller, {Key? key}) : super(key: key);
List<PopupMenuEntry<Object>> _bundleMenuItems(BuildContext context) { List<PopupMenuEntry<Object>> _bundleMenuItems(BuildContext context) {
final matrix = Matrix.of(context); final matrix = Matrix.of(context);
final bundles = matrix.accountBundles.keys.toList() final bundles = matrix.accountBundles.keys.toList()
..sort((a, b) => a.isValidMatrixId == b.isValidMatrixId ..sort((a, b) => a!.isValidMatrixId == b!.isValidMatrixId
? 0 ? 0
: a.isValidMatrixId && !b.isValidMatrixId : a.isValidMatrixId && !b.isValidMatrixId
? -1 ? -1
: 1); : 1);
return <PopupMenuEntry<Object>>[ return <PopupMenuEntry<Object>>[
for (final bundle in bundles) ...[ for (final bundle in bundles) ...[
if (matrix.accountBundles[bundle].length != 1 || if (matrix.accountBundles[bundle]!.length != 1 ||
matrix.accountBundles[bundle].single.userID != bundle) matrix.accountBundles[bundle]!.single!.userID != bundle)
PopupMenuItem( PopupMenuItem(
value: null, value: null,
child: Column( child: Column(
@ -29,9 +29,9 @@ class ClientChooserButton extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
bundle, bundle!,
style: TextStyle( style: TextStyle(
color: Theme.of(context).textTheme.subtitle1.color, color: Theme.of(context).textTheme.subtitle1!.color,
fontSize: 14, fontSize: 14,
), ),
), ),
@ -39,25 +39,26 @@ class ClientChooserButton extends StatelessWidget {
], ],
), ),
), ),
...matrix.accountBundles[bundle] ...matrix.accountBundles[bundle]!
.map( .map(
(client) => PopupMenuItem( (client) => PopupMenuItem(
value: client, value: client,
child: FutureBuilder<Profile>( child: FutureBuilder<Profile>(
future: client.ownProfile, future: client!.ownProfile,
builder: (context, snapshot) => Row( builder: (context, snapshot) => Row(
children: [ children: [
Avatar( Avatar(
mxContent: snapshot.data?.avatarUrl, mxContent: snapshot.data?.avatarUrl,
name: snapshot.data?.displayName ?? name: snapshot.data?.displayName ??
client.userID.localpart, client.userID!.localpart,
size: 28, size: 28,
fontSize: 12, fontSize: 12,
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: Text( child: Text(
snapshot.data?.displayName ?? client.userID.localpart, snapshot.data?.displayName ??
client.userID!.localpart!,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
@ -86,7 +87,7 @@ class ClientChooserButton extends StatelessWidget {
builder: (context, snapshot) => PopupMenuButton<Object>( builder: (context, snapshot) => PopupMenuButton<Object>(
child: Avatar( child: Avatar(
mxContent: snapshot.data?.avatarUrl, mxContent: snapshot.data?.avatarUrl,
name: snapshot.data?.displayName ?? matrix.client.userID.localpart, name: snapshot.data?.displayName ?? matrix.client.userID!.localpart,
size: 28, size: 28,
fontSize: 12, fontSize: 12,
), ),

View File

@ -10,7 +10,7 @@ import 'package:fluffychat/widgets/matrix.dart';
class SpacesBottomBar extends StatelessWidget { class SpacesBottomBar extends StatelessWidget {
final ChatListController controller; final ChatListController controller;
const SpacesBottomBar(this.controller, {Key key}) : super(key: key); const SpacesBottomBar(this.controller, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -25,8 +25,9 @@ class SpacesBottomBar extends StatelessWidget {
child: SafeArea( child: SafeArea(
child: StreamBuilder<Object>( child: StreamBuilder<Object>(
stream: Matrix.of(context).client.onSync.stream.where((sync) => stream: Matrix.of(context).client.onSync.stream.where((sync) =>
(sync.rooms?.join?.values?.any((r) => (sync.rooms?.join?.values.any((r) =>
r.state?.any((s) => s.type.startsWith('m.space'))) ?? r.state?.any((s) => s.type.startsWith('m.space')) ??
false) ??
false) || false) ||
(sync.rooms?.leave?.isNotEmpty ?? false)), (sync.rooms?.leave?.isNotEmpty ?? false)),
builder: (context, snapshot) { builder: (context, snapshot) {
@ -48,7 +49,7 @@ class SpacesBottomBar extends StatelessWidget {
icon: const Icon(CupertinoIcons.chat_bubble_2), icon: const Icon(CupertinoIcons.chat_bubble_2),
activeIcon: activeIcon:
const Icon(CupertinoIcons.chat_bubble_2_fill), const Icon(CupertinoIcons.chat_bubble_2_fill),
title: Text(L10n.of(context).allChats), title: Text(L10n.of(context)!.allChats),
), ),
...controller.spaces ...controller.spaces
.map((space) => SalomonBottomBarItem( .map((space) => SalomonBottomBarItem(

View File

@ -1,5 +1,3 @@
//@dart=2.12
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:adaptive_dialog/adaptive_dialog.dart';

View File

@ -13,7 +13,7 @@ import 'package:fluffychat/widgets/matrix.dart';
import 'package:fluffychat/widgets/permission_slider_dialog.dart'; import 'package:fluffychat/widgets/permission_slider_dialog.dart';
class ChatPermissionsSettings extends StatefulWidget { class ChatPermissionsSettings extends StatefulWidget {
const ChatPermissionsSettings({Key key}) : super(key: key); const ChatPermissionsSettings({Key? key}) : super(key: key);
@override @override
ChatPermissionsSettingsController createState() => ChatPermissionsSettingsController createState() =>
@ -21,13 +21,13 @@ class ChatPermissionsSettings extends StatefulWidget {
} }
class ChatPermissionsSettingsController extends State<ChatPermissionsSettings> { class ChatPermissionsSettingsController extends State<ChatPermissionsSettings> {
String get roomId => VRouter.of(context).pathParameters['roomid']; String? get roomId => VRouter.of(context).pathParameters['roomid'];
void editPowerLevel(BuildContext context, String key, int currentLevel, void editPowerLevel(BuildContext context, String key, int currentLevel,
{String category}) async { {String? category}) async {
final room = Matrix.of(context).client.getRoomById(roomId); final room = Matrix.of(context).client.getRoomById(roomId!)!;
if (!room.canSendEvent(EventTypes.RoomPowerLevels)) { if (!room.canSendEvent(EventTypes.RoomPowerLevels)) {
ScaffoldMessenger.of(context) ScaffoldMessenger.of(context).showSnackBar(
.showSnackBar(SnackBar(content: Text(L10n.of(context).noPermission))); SnackBar(content: Text(L10n.of(context)!.noPermission)));
return; return;
} }
final newLevel = final newLevel =
@ -35,7 +35,7 @@ class ChatPermissionsSettingsController extends State<ChatPermissionsSettings> {
.show(context); .show(context);
if (newLevel == null) return; if (newLevel == null) return;
final content = Map<String, dynamic>.from( final content = Map<String, dynamic>.from(
room.getState(EventTypes.RoomPowerLevels).content); room.getState(EventTypes.RoomPowerLevels)!.content);
if (category != null) { if (category != null) {
if (!content.containsKey(category)) { if (!content.containsKey(category)) {
content[category] = <String, dynamic>{}; content[category] = <String, dynamic>{};
@ -58,20 +58,20 @@ class ChatPermissionsSettingsController extends State<ChatPermissionsSettings> {
Stream get onChanged => Matrix.of(context).client.onSync.stream.where( Stream get onChanged => Matrix.of(context).client.onSync.stream.where(
(e) => (e) =>
(e?.rooms?.join?.containsKey(roomId) ?? false) && (e.rooms?.join?.containsKey(roomId) ?? false) &&
(e.rooms.join[roomId]?.timeline?.events (e.rooms!.join![roomId!]?.timeline?.events
?.any((s) => s.type == EventTypes.RoomPowerLevels) ?? ?.any((s) => s.type == EventTypes.RoomPowerLevels) ??
false), false),
); );
void updateRoomAction(Capabilities capabilities) async { void updateRoomAction(Capabilities capabilities) async {
final room = Matrix.of(context).client.getRoomById(roomId); final room = Matrix.of(context).client.getRoomById(roomId!)!;
final String roomVersion = final String roomVersion =
room.getState(EventTypes.RoomCreate).content['room_version'] ?? '1'; room.getState(EventTypes.RoomCreate)!.content['room_version'] ?? '1';
final newVersion = await showConfirmationDialog<String>( final newVersion = await showConfirmationDialog<String>(
context: context, context: context,
title: L10n.of(context).replaceRoomWithNewerVersion, title: L10n.of(context)!.replaceRoomWithNewerVersion,
actions: capabilities.mRoomVersions.available.entries actions: capabilities.mRoomVersions!.available.entries
.where((r) => r.key != roomVersion) .where((r) => r.key != roomVersion)
.map((version) => AlertDialogAction( .map((version) => AlertDialogAction(
key: version.key, key: version.key,
@ -84,15 +84,15 @@ class ChatPermissionsSettingsController extends State<ChatPermissionsSettings> {
await showOkCancelAlertDialog( await showOkCancelAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
okLabel: L10n.of(context).yes, okLabel: L10n.of(context)!.yes,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context)!.cancel,
title: L10n.of(context).areYouSure, title: L10n.of(context)!.areYouSure,
)) { )) {
return; return;
} }
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => room.client.upgradeRoom(roomId, newVersion), future: () => room.client.upgradeRoom(roomId!, newVersion),
).then((_) => VRouter.of(context).pop()); ).then((_) => VRouter.of(context).pop());
} }

View File

@ -12,7 +12,7 @@ import 'package:fluffychat/widgets/matrix.dart';
class ChatPermissionsSettingsView extends StatelessWidget { class ChatPermissionsSettingsView extends StatelessWidget {
final ChatPermissionsSettingsController controller; final ChatPermissionsSettingsController controller;
const ChatPermissionsSettingsView(this.controller, {Key key}) const ChatPermissionsSettingsView(this.controller, {Key? key})
: super(key: key); : super(key: key);
@override @override
@ -24,19 +24,24 @@ class ChatPermissionsSettingsView extends StatelessWidget {
: IconButton( : IconButton(
icon: const Icon(Icons.close_outlined), icon: const Icon(Icons.close_outlined),
onPressed: () => VRouter.of(context) onPressed: () => VRouter.of(context)
.toSegments(['rooms', controller.roomId]), .toSegments(['rooms', controller.roomId!]),
), ),
title: Text(L10n.of(context).editChatPermissions), title: Text(L10n.of(context)!.editChatPermissions),
), ),
body: MaxWidthBody( body: MaxWidthBody(
withScrolling: true, withScrolling: true,
child: StreamBuilder( child: StreamBuilder(
stream: controller.onChanged, stream: controller.onChanged,
builder: (context, _) { builder: (context, _) {
final room = final roomId = controller.roomId;
Matrix.of(context).client.getRoomById(controller.roomId); final room = roomId == null
? null
: Matrix.of(context).client.getRoomById(roomId);
if (room == null) {
return Center(child: Text(L10n.of(context)!.noRoomsFound));
}
final powerLevelsContent = Map<String, dynamic>.from( final powerLevelsContent = Map<String, dynamic>.from(
room.getState(EventTypes.RoomPowerLevels).content); room.getState(EventTypes.RoomPowerLevels)!.content);
final powerLevels = Map<String, dynamic>.from(powerLevelsContent) final powerLevels = Map<String, dynamic>.from(powerLevelsContent)
..removeWhere((k, v) => v is! int); ..removeWhere((k, v) => v is! int);
final eventsPowerLevels = final eventsPowerLevels =
@ -57,7 +62,7 @@ class ChatPermissionsSettingsView extends StatelessWidget {
const Divider(thickness: 1), const Divider(thickness: 1),
ListTile( ListTile(
title: Text( title: Text(
L10n.of(context).notifications, L10n.of(context)!.notifications,
style: TextStyle( style: TextStyle(
color: Theme.of(context).primaryColor, color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -82,14 +87,13 @@ class ChatPermissionsSettingsView extends StatelessWidget {
const Divider(thickness: 1), const Divider(thickness: 1),
ListTile( ListTile(
title: Text( title: Text(
L10n.of(context).configureChat, L10n.of(context)!.configureChat,
style: TextStyle( style: TextStyle(
color: Theme.of(context).primaryColor, color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
), ),
if (eventsPowerLevels != null)
for (var entry in eventsPowerLevels.entries) for (var entry in eventsPowerLevels.entries)
PermissionsListTile( PermissionsListTile(
permissionKey: entry.key, permissionKey: entry.key,
@ -110,15 +114,15 @@ class ChatPermissionsSettingsView extends StatelessWidget {
strokeWidth: 2)); strokeWidth: 2));
} }
final String roomVersion = room final String roomVersion = room
.getState(EventTypes.RoomCreate) .getState(EventTypes.RoomCreate)!
.content['room_version'] ?? .content['room_version'] ??
'1'; '1';
return ListTile( return ListTile(
title: Text( title: Text(
'${L10n.of(context).roomVersion}: $roomVersion'), '${L10n.of(context)!.roomVersion}: $roomVersion'),
onTap: () => onTap: () =>
controller.updateRoomAction(snapshot.data), controller.updateRoomAction(snapshot.data!),
); );
}, },
), ),

View File

@ -6,13 +6,13 @@ import 'package:matrix/matrix.dart';
class PermissionsListTile extends StatelessWidget { class PermissionsListTile extends StatelessWidget {
final String permissionKey; final String permissionKey;
final int permission; final int permission;
final String category; final String? category;
final void Function() onTap; final void Function()? onTap;
const PermissionsListTile({ const PermissionsListTile({
Key key, Key? key,
@required this.permissionKey, required this.permissionKey,
@required this.permission, required this.permission,
this.category, this.category,
this.onTap, this.onTap,
}) : super(key: key); }) : super(key: key);
@ -21,43 +21,43 @@ class PermissionsListTile extends StatelessWidget {
if (category == null) { if (category == null) {
switch (permissionKey) { switch (permissionKey) {
case 'users_default': case 'users_default':
return L10n.of(context).defaultPermissionLevel; return L10n.of(context)!.defaultPermissionLevel;
case 'events_default': case 'events_default':
return L10n.of(context).sendMessages; return L10n.of(context)!.sendMessages;
case 'state_default': case 'state_default':
return L10n.of(context).configureChat; return L10n.of(context)!.configureChat;
case 'ban': case 'ban':
return L10n.of(context).banFromChat; return L10n.of(context)!.banFromChat;
case 'kick': case 'kick':
return L10n.of(context).kickFromChat; return L10n.of(context)!.kickFromChat;
case 'redact': case 'redact':
return L10n.of(context).deleteMessage; return L10n.of(context)!.deleteMessage;
case 'invite': case 'invite':
return L10n.of(context).inviteContact; return L10n.of(context)!.inviteContact;
} }
} else if (category == 'notifications') { } else if (category == 'notifications') {
switch (permissionKey) { switch (permissionKey) {
case 'rooms': case 'rooms':
return L10n.of(context).notifications; return L10n.of(context)!.notifications;
} }
} else if (category == 'events') { } else if (category == 'events') {
switch (permissionKey) { switch (permissionKey) {
case EventTypes.RoomName: case EventTypes.RoomName:
return L10n.of(context).changeTheNameOfTheGroup; return L10n.of(context)!.changeTheNameOfTheGroup;
case EventTypes.RoomPowerLevels: case EventTypes.RoomPowerLevels:
return L10n.of(context).editChatPermissions; return L10n.of(context)!.editChatPermissions;
case EventTypes.HistoryVisibility: case EventTypes.HistoryVisibility:
return L10n.of(context).visibilityOfTheChatHistory; return L10n.of(context)!.visibilityOfTheChatHistory;
case EventTypes.RoomCanonicalAlias: case EventTypes.RoomCanonicalAlias:
return L10n.of(context).setInvitationLink; return L10n.of(context)!.setInvitationLink;
case EventTypes.RoomAvatar: case EventTypes.RoomAvatar:
return L10n.of(context).editRoomAvatar; return L10n.of(context)!.editRoomAvatar;
case EventTypes.RoomTombstone: case EventTypes.RoomTombstone:
return L10n.of(context).replaceRoomWithNewerVersion; return L10n.of(context)!.replaceRoomWithNewerVersion;
case EventTypes.Encryption: case EventTypes.Encryption:
return L10n.of(context).enableEncryption; return L10n.of(context)!.enableEncryption;
case 'm.room.server_acl': case 'm.room.server_acl':
return L10n.of(context).editBlockedServers; return L10n.of(context)!.editBlockedServers;
} }
} }
return permissionKey; return permissionKey;
@ -96,9 +96,9 @@ class PermissionsListTile extends StatelessWidget {
extension on int { extension on int {
String toLocalizedPowerLevelString(BuildContext context) { String toLocalizedPowerLevelString(BuildContext context) {
return this == 100 return this == 100
? L10n.of(context).admin ? L10n.of(context)!.admin
: this >= 50 : this >= 50
? L10n.of(context).moderator ? L10n.of(context)!.moderator
: L10n.of(context).participant; : L10n.of(context)!.participant;
} }
} }

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:collection/collection.dart' show IterableExtension;
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:matrix/encryption/utils/key_verification.dart'; import 'package:matrix/encryption/utils/key_verification.dart';
@ -11,14 +12,14 @@ import 'package:fluffychat/pages/key_verification/key_verification_dialog.dart';
import '../../widgets/matrix.dart'; import '../../widgets/matrix.dart';
class DevicesSettings extends StatefulWidget { class DevicesSettings extends StatefulWidget {
const DevicesSettings({Key key}) : super(key: key); const DevicesSettings({Key? key}) : super(key: key);
@override @override
DevicesSettingsController createState() => DevicesSettingsController(); DevicesSettingsController createState() => DevicesSettingsController();
} }
class DevicesSettingsController extends State<DevicesSettings> { class DevicesSettingsController extends State<DevicesSettings> {
List<Device> devices; List<Device>? devices;
Future<bool> loadUserDevices(BuildContext context) async { Future<bool> loadUserDevices(BuildContext context) async {
if (devices != null) return true; if (devices != null) return true;
devices = await Matrix.of(context).client.getDevices(); devices = await Matrix.of(context).client.getDevices();
@ -28,15 +29,15 @@ class DevicesSettingsController extends State<DevicesSettings> {
void reload() => setState(() => devices = null); void reload() => setState(() => devices = null);
bool loadingDeletingDevices = false; bool loadingDeletingDevices = false;
String errorDeletingDevices; String? errorDeletingDevices;
void removeDevicesAction(List<Device> devices) async { void removeDevicesAction(List<Device> devices) async {
if (await showOkCancelAlertDialog( if (await showOkCancelAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).areYouSure, title: L10n.of(context)!.areYouSure,
okLabel: L10n.of(context).yes, okLabel: L10n.of(context)!.yes,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context)!.cancel,
) == ) ==
OkCancelResult.cancel) return; OkCancelResult.cancel) return;
final matrix = Matrix.of(context); final matrix = Matrix.of(context);
@ -69,9 +70,9 @@ class DevicesSettingsController extends State<DevicesSettings> {
final displayName = await showTextInputDialog( final displayName = await showTextInputDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).changeDeviceName, title: L10n.of(context)!.changeDeviceName,
okLabel: L10n.of(context).ok, okLabel: L10n.of(context)!.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context)!.cancel,
textFields: [ textFields: [
DialogTextField( DialogTextField(
hintText: device.displayName, hintText: device.displayName,
@ -93,13 +94,13 @@ class DevicesSettingsController extends State<DevicesSettings> {
void verifyDeviceAction(Device device) async { void verifyDeviceAction(Device device) async {
final req = Matrix.of(context) final req = Matrix.of(context)
.client .client
.userDeviceKeys[Matrix.of(context).client.userID] .userDeviceKeys[Matrix.of(context).client.userID!]!
.deviceKeys[device.deviceId] .deviceKeys[device.deviceId]!
.startVerification(); .startVerification();
req.onUpdate = () { req.onUpdate = () {
if ({KeyVerificationState.error, KeyVerificationState.done} if ({KeyVerificationState.error, KeyVerificationState.done}
.contains(req.state)) { .contains(req.state)) {
setState(() => null); setState(() {});
} }
}; };
await KeyVerificationDialog(request: req).show(context); await KeyVerificationDialog(request: req).show(context);
@ -108,33 +109,32 @@ class DevicesSettingsController extends State<DevicesSettings> {
void blockDeviceAction(Device device) async { void blockDeviceAction(Device device) async {
final key = Matrix.of(context) final key = Matrix.of(context)
.client .client
.userDeviceKeys[Matrix.of(context).client.userID] .userDeviceKeys[Matrix.of(context).client.userID!]!
.deviceKeys[device.deviceId]; .deviceKeys[device.deviceId]!;
if (key.directVerified) { if (key.directVerified) {
await key.setVerified(false); await key.setVerified(false);
} }
await key.setBlocked(true); await key.setBlocked(true);
setState(() => null); setState(() {});
} }
void unblockDeviceAction(Device device) async { void unblockDeviceAction(Device device) async {
final key = Matrix.of(context) final key = Matrix.of(context)
.client .client
.userDeviceKeys[Matrix.of(context).client.userID] .userDeviceKeys[Matrix.of(context).client.userID!]!
.deviceKeys[device.deviceId]; .deviceKeys[device.deviceId]!;
await key.setBlocked(false); await key.setBlocked(false);
setState(() => null); setState(() {});
} }
bool _isOwnDevice(Device userDevice) => bool _isOwnDevice(Device userDevice) =>
userDevice.deviceId == Matrix.of(context).client.deviceID; userDevice.deviceId == Matrix.of(context).client.deviceID;
Device get thisDevice => devices.firstWhere( Device? get thisDevice => devices!.firstWhereOrNull(
_isOwnDevice, _isOwnDevice,
orElse: () => null,
); );
List<Device> get notThisDevice => List<Device>.from(devices) List<Device> get notThisDevice => List<Device>.from(devices!)
..removeWhere(_isOwnDevice) ..removeWhere(_isOwnDevice)
..sort((a, b) => (b.lastSeenTs ?? 0).compareTo(a.lastSeenTs ?? 0)); ..sort((a, b) => (b.lastSeenTs ?? 0).compareTo(a.lastSeenTs ?? 0));

View File

@ -9,14 +9,14 @@ import 'user_device_list_item.dart';
class DevicesSettingsView extends StatelessWidget { class DevicesSettingsView extends StatelessWidget {
final DevicesSettingsController controller; final DevicesSettingsController controller;
const DevicesSettingsView(this.controller, {Key key}) : super(key: key); const DevicesSettingsView(this.controller, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
leading: const BackButton(), leading: const BackButton(),
title: Text(L10n.of(context).devices), title: Text(L10n.of(context)!.devices),
), ),
body: MaxWidthBody( body: MaxWidthBody(
child: FutureBuilder<bool>( child: FutureBuilder<bool>(
@ -41,7 +41,7 @@ class DevicesSettingsView extends StatelessWidget {
children: <Widget>[ children: <Widget>[
if (controller.thisDevice != null) if (controller.thisDevice != null)
UserDeviceListItem( UserDeviceListItem(
controller.thisDevice, controller.thisDevice!,
rename: controller.renameDeviceAction, rename: controller.renameDeviceAction,
remove: (d) => controller.removeDevicesAction([d]), remove: (d) => controller.removeDevicesAction([d]),
verify: controller.verifyDeviceAction, verify: controller.verifyDeviceAction,
@ -53,7 +53,7 @@ class DevicesSettingsView extends StatelessWidget {
ListTile( ListTile(
title: Text( title: Text(
controller.errorDeletingDevices ?? controller.errorDeletingDevices ??
L10n.of(context).removeAllOtherDevices, L10n.of(context)!.removeAllOtherDevices,
style: const TextStyle(color: Colors.red), style: const TextStyle(color: Colors.red),
), ),
trailing: controller.loadingDeletingDevices trailing: controller.loadingDeletingDevices

View File

@ -1,5 +1,3 @@
//@dart=2.12
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:adaptive_dialog/adaptive_dialog.dart';

View File

@ -21,7 +21,7 @@ import '../../main.dart';
import '../../utils/localized_exception_extension.dart'; import '../../utils/localized_exception_extension.dart';
class HomeserverPicker extends StatefulWidget { class HomeserverPicker extends StatefulWidget {
const HomeserverPicker({Key key}) : super(key: key); const HomeserverPicker({Key? key}) : super(key: key);
@override @override
HomeserverPickerController createState() => HomeserverPickerController(); HomeserverPickerController createState() => HomeserverPickerController();
@ -32,9 +32,9 @@ class HomeserverPickerController extends State<HomeserverPicker> {
String domain = AppConfig.defaultHomeserver; String domain = AppConfig.defaultHomeserver;
final TextEditingController homeserverController = final TextEditingController homeserverController =
TextEditingController(text: AppConfig.defaultHomeserver); TextEditingController(text: AppConfig.defaultHomeserver);
StreamSubscription _intentDataStreamSubscription; StreamSubscription? _intentDataStreamSubscription;
String error; String? error;
Timer _coolDown; Timer? _coolDown;
void setDomain(String domain) { void setDomain(String domain) {
this.domain = domain; this.domain = domain;
@ -46,7 +46,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
} }
void _loginWithToken(String token) { void _loginWithToken(String token) {
if (token?.isEmpty ?? true) return; if (token.isEmpty) return;
showFutureLoadingDialog( showFutureLoadingDialog(
context: context, context: context,
@ -66,7 +66,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
); );
} }
void _processIncomingUris(String text) async { void _processIncomingUris(String? text) async {
if (text == null || !text.startsWith(AppConfig.appOpenUrlScheme)) return; if (text == null || !text.startsWith(AppConfig.appOpenUrlScheme)) return;
await browser?.close(); await browser?.close();
VRouter.of(context).to('/home'); VRouter.of(context).to('/home');
@ -89,8 +89,8 @@ class HomeserverPickerController extends State<HomeserverPicker> {
super.initState(); super.initState();
_initReceiveUri(); _initReceiveUri();
if (kIsWeb) { if (kIsWeb) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance!.addPostFrameCallback((_) {
final token = Matrix.of(context).widget.queryParameters['loginToken']; final token = Matrix.of(context).widget.queryParameters!['loginToken'];
if (token != null) _loginWithToken(token); if (token != null) _loginWithToken(token);
}); });
} }
@ -103,7 +103,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
_intentDataStreamSubscription?.cancel(); _intentDataStreamSubscription?.cancel();
} }
String _lastCheckedHomeserver; String? _lastCheckedHomeserver;
/// Starts an analysis of the given homeserver. It uses the current domain and /// Starts an analysis of the given homeserver. It uses the current domain and
/// makes sure that it is prefixed with https. Then it searches for the /// makes sure that it is prefixed with https. Then it searches for the
@ -112,7 +112,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
Future<void> checkHomeserverAction() async { Future<void> checkHomeserverAction() async {
_coolDown?.cancel(); _coolDown?.cancel();
if (_lastCheckedHomeserver == domain) return; if (_lastCheckedHomeserver == domain) return;
if (domain.isEmpty) throw L10n.of(context).changeTheHomeserver; if (domain.isEmpty) throw L10n.of(context)!.changeTheHomeserver;
var homeserver = domain; var homeserver = domain;
if (!homeserver.startsWith('https://')) { if (!homeserver.startsWith('https://')) {
@ -129,7 +129,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
await Matrix.of(context).getLoginClient().checkHomeserver(homeserver); await Matrix.of(context).getLoginClient().checkHomeserver(homeserver);
var jitsi = wellKnown?.additionalProperties var jitsi = wellKnown?.additionalProperties
?.tryGet<Map<String, dynamic>>('im.vector.riot.jitsi') .tryGet<Map<String, dynamic>>('im.vector.riot.jitsi')
?.tryGet<String>('preferredDomain'); ?.tryGet<String>('preferredDomain');
if (jitsi != null) { if (jitsi != null) {
if (!jitsi.endsWith('/')) { if (!jitsi.endsWith('/')) {
@ -150,10 +150,10 @@ class HomeserverPickerController extends State<HomeserverPicker> {
await Matrix.of(context).getLoginClient().register(); await Matrix.of(context).getLoginClient().register();
registrationSupported = true; registrationSupported = true;
} on MatrixException catch (e) { } on MatrixException catch (e) {
registrationSupported = e.requireAdditionalAuthentication ?? false; registrationSupported = e.requireAdditionalAuthentication;
} }
} catch (e) { } catch (e) {
setState(() => error = (e as Object).toLocalizedString(context)); setState(() => error = (e).toLocalizedString(context));
} finally { } finally {
_lastCheckedHomeserver = domain; _lastCheckedHomeserver = domain;
if (mounted) { if (mounted) {
@ -162,12 +162,12 @@ class HomeserverPickerController extends State<HomeserverPicker> {
} }
} }
Map<String, dynamic> _rawLoginTypes; Map<String, dynamic>? _rawLoginTypes;
bool registrationSupported; bool? registrationSupported;
List<IdentityProvider> get identityProviders { List<IdentityProvider> get identityProviders {
if (!ssoLoginSupported) return []; if (!ssoLoginSupported) return [];
final rawProviders = _rawLoginTypes.tryGetList('flows').singleWhere( final rawProviders = _rawLoginTypes!.tryGetList('flows')!.singleWhere(
(flow) => (flow) =>
flow['type'] == AuthenticationTypes.sso)['identity_providers']; flow['type'] == AuthenticationTypes.sso)['identity_providers'];
final list = (rawProviders as List) final list = (rawProviders as List)
@ -184,8 +184,8 @@ class HomeserverPickerController extends State<HomeserverPicker> {
.client .client
.supportedLoginTypes .supportedLoginTypes
.contains(AuthenticationTypes.password) && .contains(AuthenticationTypes.password) &&
_rawLoginTypes _rawLoginTypes!
.tryGetList('flows') .tryGetList('flows')!
.any((flow) => flow['type'] == AuthenticationTypes.password); .any((flow) => flow['type'] == AuthenticationTypes.password);
bool get ssoLoginSupported => bool get ssoLoginSupported =>
@ -193,11 +193,11 @@ class HomeserverPickerController extends State<HomeserverPicker> {
.client .client
.supportedLoginTypes .supportedLoginTypes
.contains(AuthenticationTypes.sso) && .contains(AuthenticationTypes.sso) &&
_rawLoginTypes _rawLoginTypes!
.tryGetList('flows') .tryGetList('flows')!
.any((flow) => flow['type'] == AuthenticationTypes.sso); .any((flow) => flow['type'] == AuthenticationTypes.sso);
ChromeSafariBrowser browser; ChromeSafariBrowser? browser;
static const String ssoHomeserverKey = 'sso-homeserver'; static const String ssoHomeserverKey = 'sso-homeserver';
@ -215,7 +215,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
'${Matrix.of(context).getLoginClient().homeserver?.toString()}/_matrix/client/r0/login/sso/redirect/${Uri.encodeComponent(id)}?redirectUrl=${Uri.encodeQueryComponent(redirectUrl)}'; '${Matrix.of(context).getLoginClient().homeserver?.toString()}/_matrix/client/r0/login/sso/redirect/${Uri.encodeComponent(id)}?redirectUrl=${Uri.encodeQueryComponent(redirectUrl)}';
if (PlatformInfos.isMobile) { if (PlatformInfos.isMobile) {
browser ??= ChromeSafariBrowser(); browser ??= ChromeSafariBrowser();
browser.open(url: Uri.parse(url)); browser!.open(url: Uri.parse(url));
} else { } else {
launch(redirectUrl); launch(redirectUrl);
} }
@ -234,10 +234,10 @@ class HomeserverPickerController extends State<HomeserverPicker> {
} }
class IdentityProvider { class IdentityProvider {
final String id; final String? id;
final String name; final String? name;
final String icon; final String? icon;
final String brand; final String? brand;
IdentityProvider({this.id, this.name, this.icon, this.brand}); IdentityProvider({this.id, this.name, this.icon, this.brand});

View File

@ -17,7 +17,7 @@ import 'homeserver_picker.dart';
class HomeserverPickerView extends StatelessWidget { class HomeserverPickerView extends StatelessWidget {
final HomeserverPickerController controller; final HomeserverPickerController controller;
const HomeserverPickerView(this.controller, {Key key}) : super(key: key); const HomeserverPickerView(this.controller, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -27,7 +27,7 @@ class HomeserverPickerView extends StatelessWidget {
titleSpacing: 8, titleSpacing: 8,
title: DefaultAppBarSearchField( title: DefaultAppBarSearchField(
prefixText: 'https://', prefixText: 'https://',
hintText: L10n.of(context).enterYourHomeserver, hintText: L10n.of(context)!.enterYourHomeserver,
searchController: controller.homeserverController, searchController: controller.homeserverController,
suffix: const Icon(Icons.edit_outlined), suffix: const Icon(Icons.edit_outlined),
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
@ -36,7 +36,7 @@ class HomeserverPickerView extends StatelessWidget {
onSubmit: (_) => controller.checkHomeserverAction(), onSubmit: (_) => controller.checkHomeserverAction(),
unfocusOnClear: false, unfocusOnClear: false,
autocorrect: false, autocorrect: false,
labelText: L10n.of(context).homeserver, labelText: L10n.of(context)!.homeserver,
), ),
elevation: 0, elevation: 0,
), ),
@ -54,7 +54,7 @@ class HomeserverPickerView extends StatelessWidget {
child: Padding( child: Padding(
padding: const EdgeInsets.all(12.0), padding: const EdgeInsets.all(12.0),
child: Text( child: Text(
controller.error, controller.error!,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
@ -77,7 +77,7 @@ class HomeserverPickerView extends StatelessWidget {
const Expanded(child: Divider()), const Expanded(child: Divider()),
Padding( Padding(
padding: const EdgeInsets.all(12.0), padding: const EdgeInsets.all(12.0),
child: Text(L10n.of(context).loginWithOneClick), child: Text(L10n.of(context)!.loginWithOneClick),
), ),
const Expanded(child: Divider()), const Expanded(child: Divider()),
]), ]),
@ -88,20 +88,20 @@ class HomeserverPickerView extends StatelessWidget {
in controller.identityProviders) in controller.identityProviders)
_SsoButton( _SsoButton(
onPressed: () => onPressed: () =>
controller.ssoLoginAction(identityProvider.id), controller.ssoLoginAction(identityProvider.id!),
identityProvider: identityProvider, identityProvider: identityProvider,
), ),
}, },
].toList(), ].toList(),
), ),
if (controller.ssoLoginSupported && if (controller.ssoLoginSupported &&
(controller.registrationSupported || (controller.registrationSupported! ||
controller.passwordLoginSupported)) controller.passwordLoginSupported))
Row(children: [ Row(children: [
const Expanded(child: Divider()), const Expanded(child: Divider()),
Padding( Padding(
padding: const EdgeInsets.all(12.0), padding: const EdgeInsets.all(12.0),
child: Text(L10n.of(context).or), child: Text(L10n.of(context)!.or),
), ),
const Expanded(child: Divider()), const Expanded(child: Divider()),
]), ]),
@ -111,22 +111,22 @@ class HomeserverPickerView extends StatelessWidget {
onPressed: () => VRouter.of(context).to('login'), onPressed: () => VRouter.of(context).to('login'),
icon: Icon( icon: Icon(
CupertinoIcons.lock_open_fill, CupertinoIcons.lock_open_fill,
color: Theme.of(context).textTheme.bodyText1.color, color: Theme.of(context).textTheme.bodyText1!.color,
), ),
labelText: L10n.of(context).login, labelText: L10n.of(context)!.login,
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
], ],
if (controller.registrationSupported) if (controller.registrationSupported!)
Center( Center(
child: _LoginButton( child: _LoginButton(
onPressed: controller.signUpAction, onPressed: controller.signUpAction,
icon: Icon( icon: Icon(
CupertinoIcons.person_add, CupertinoIcons.person_add,
color: Theme.of(context).textTheme.bodyText1.color, color: Theme.of(context).textTheme.bodyText1!.color,
), ),
labelText: L10n.of(context).register, labelText: L10n.of(context)!.register,
), ),
), ),
], ],
@ -142,7 +142,7 @@ class HomeserverPickerView extends StatelessWidget {
TextButton( TextButton(
onPressed: () => launch(AppConfig.privacyUrl), onPressed: () => launch(AppConfig.privacyUrl),
child: Text( child: Text(
L10n.of(context).privacy, L10n.of(context)!.privacy,
style: const TextStyle( style: const TextStyle(
decoration: TextDecoration.underline, decoration: TextDecoration.underline,
color: Colors.blueGrey, color: Colors.blueGrey,
@ -152,7 +152,7 @@ class HomeserverPickerView extends StatelessWidget {
TextButton( TextButton(
onPressed: () => PlatformInfos.showDialog(context), onPressed: () => PlatformInfos.showDialog(context),
child: Text( child: Text(
L10n.of(context).about, L10n.of(context)!.about,
style: const TextStyle( style: const TextStyle(
decoration: TextDecoration.underline, decoration: TextDecoration.underline,
color: Colors.blueGrey, color: Colors.blueGrey,
@ -169,10 +169,10 @@ class HomeserverPickerView extends StatelessWidget {
class _SsoButton extends StatelessWidget { class _SsoButton extends StatelessWidget {
final IdentityProvider identityProvider; final IdentityProvider identityProvider;
final void Function() onPressed; final void Function()? onPressed;
const _SsoButton({ const _SsoButton({
Key key, Key? key,
@required this.identityProvider, required this.identityProvider,
this.onPressed, this.onPressed,
}) : super(key: key); }) : super(key: key);
@ -196,7 +196,7 @@ class _SsoButton extends StatelessWidget {
child: identityProvider.icon == null child: identityProvider.icon == null
? const Icon(Icons.web_outlined) ? const Icon(Icons.web_outlined)
: CachedNetworkImage( : CachedNetworkImage(
imageUrl: Uri.parse(identityProvider.icon) imageUrl: Uri.parse(identityProvider.icon!)
.getDownloadLink( .getDownloadLink(
Matrix.of(context).getLoginClient()) Matrix.of(context).getLoginClient())
.toString(), .toString(),
@ -209,11 +209,11 @@ class _SsoButton extends StatelessWidget {
Text( Text(
identityProvider.name ?? identityProvider.name ??
identityProvider.brand ?? identityProvider.brand ??
L10n.of(context).singlesignon, L10n.of(context)!.singlesignon,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Theme.of(context).textTheme.subtitle2.color, color: Theme.of(context).textTheme.subtitle2!.color,
), ),
), ),
], ],
@ -224,11 +224,11 @@ class _SsoButton extends StatelessWidget {
} }
class _LoginButton extends StatelessWidget { class _LoginButton extends StatelessWidget {
final String labelText; final String? labelText;
final Widget icon; final Widget? icon;
final void Function() onPressed; final void Function()? onPressed;
const _LoginButton({ const _LoginButton({
Key key, Key? key,
this.labelText, this.labelText,
this.icon, this.icon,
this.onPressed, this.onPressed,
@ -246,11 +246,11 @@ class _LoginButton extends StatelessWidget {
), ),
), ),
onPressed: onPressed, onPressed: onPressed,
icon: icon, icon: icon!,
label: Text( label: Text(
labelText, labelText!,
style: TextStyle( style: TextStyle(
color: Theme.of(context).textTheme.bodyText1.color, color: Theme.of(context).textTheme.bodyText1!.color,
), ),
), ),
); );

View File

@ -10,9 +10,9 @@ import '../../utils/matrix_sdk_extensions.dart/event_extension.dart';
class ImageViewer extends StatefulWidget { class ImageViewer extends StatefulWidget {
final Event event; final Event event;
final void Function() onLoaded; final void Function()? onLoaded;
const ImageViewer(this.event, {Key key, this.onLoaded}) : super(key: key); const ImageViewer(this.event, {Key? key, this.onLoaded}) : super(key: key);
@override @override
ImageViewerController createState() => ImageViewerController(); ImageViewerController createState() => ImageViewerController();

View File

@ -8,7 +8,7 @@ import 'image_viewer.dart';
class ImageViewerView extends StatelessWidget { class ImageViewerView extends StatelessWidget {
final ImageViewerController controller; final ImageViewerController controller;
const ImageViewerView(this.controller, {Key key}) : super(key: key); const ImageViewerView(this.controller, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -21,7 +21,7 @@ class ImageViewerView extends StatelessWidget {
icon: const Icon(Icons.close), icon: const Icon(Icons.close),
onPressed: Navigator.of(context).pop, onPressed: Navigator.of(context).pop,
color: Colors.white, color: Colors.white,
tooltip: L10n.of(context).close, tooltip: L10n.of(context)!.close,
), ),
backgroundColor: const Color(0x44000000), backgroundColor: const Color(0x44000000),
actions: [ actions: [
@ -29,13 +29,13 @@ class ImageViewerView extends StatelessWidget {
icon: const Icon(Icons.reply_outlined), icon: const Icon(Icons.reply_outlined),
onPressed: controller.forwardAction, onPressed: controller.forwardAction,
color: Colors.white, color: Colors.white,
tooltip: L10n.of(context).share, tooltip: L10n.of(context)!.share,
), ),
IconButton( IconButton(
icon: const Icon(Icons.download_outlined), icon: const Icon(Icons.download_outlined),
onPressed: controller.saveFileAction, onPressed: controller.saveFileAction,
color: Colors.white, color: Colors.white,
tooltip: L10n.of(context).downloadFile, tooltip: L10n.of(context)!.downloadFile,
), ),
], ],
), ),

View File

@ -12,7 +12,7 @@ import 'package:fluffychat/widgets/matrix.dart';
import '../../utils/localized_exception_extension.dart'; import '../../utils/localized_exception_extension.dart';
class InvitationSelection extends StatefulWidget { class InvitationSelection extends StatefulWidget {
const InvitationSelection({Key key}) : super(key: key); const InvitationSelection({Key? key}) : super(key: key);
@override @override
InvitationSelectionController createState() => InvitationSelectionController createState() =>
@ -21,16 +21,16 @@ class InvitationSelection extends StatefulWidget {
class InvitationSelectionController extends State<InvitationSelection> { class InvitationSelectionController extends State<InvitationSelection> {
TextEditingController controller = TextEditingController(); TextEditingController controller = TextEditingController();
String currentSearchTerm; late String currentSearchTerm;
bool loading = false; bool loading = false;
List<Profile> foundProfiles = []; List<Profile> foundProfiles = [];
Timer coolDown; Timer? coolDown;
String get roomId => VRouter.of(context).pathParameters['roomid']; String? get roomId => VRouter.of(context).pathParameters['roomid'];
Future<List<User>> getContacts(BuildContext context) async { Future<List<User>> getContacts(BuildContext context) async {
final client = Matrix.of(context).client; final client = Matrix.of(context).client;
final room = client.getRoomById(roomId); final room = client.getRoomById(roomId!)!;
final participants = await room.requestParticipants(); final participants = await room.requestParticipants();
participants.removeWhere( participants.removeWhere(
(u) => ![Membership.join, Membership.invite].contains(u.membership), (u) => ![Membership.join, Membership.invite].contains(u.membership),
@ -38,7 +38,7 @@ class InvitationSelectionController extends State<InvitationSelection> {
final participantsIds = participants.map((p) => p.stateKey).toList(); final participantsIds = participants.map((p) => p.stateKey).toList();
final contacts = client.rooms final contacts = client.rooms
.where((r) => r.isDirectChat) .where((r) => r.isDirectChat)
.map((r) => r.getUserByMXIDSync(r.directChatMatrixID)) .map((r) => r.getUserByMXIDSync(r.directChatMatrixID!))
.toList() .toList()
..removeWhere((u) => participantsIds.contains(u.stateKey)); ..removeWhere((u) => participantsIds.contains(u.stateKey));
contacts.sort( contacts.sort(
@ -50,14 +50,14 @@ class InvitationSelectionController extends State<InvitationSelection> {
} }
void inviteAction(BuildContext context, String id) async { void inviteAction(BuildContext context, String id) async {
final room = Matrix.of(context).client.getRoomById(roomId); final room = Matrix.of(context).client.getRoomById(roomId!);
final success = await showFutureLoadingDialog( final success = await showFutureLoadingDialog(
context: context, context: context,
future: () => room.invite(id), future: () => room!.invite(id),
); );
if (success.error == null) { if (success.error == null) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar( ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(L10n.of(context).contactHasBeenInvitedToTheGroup))); content: Text(L10n.of(context)!.contactHasBeenInvitedToTheGroup)));
} }
} }
@ -84,7 +84,7 @@ class InvitationSelectionController extends State<InvitationSelection> {
response = await matrix.client.searchUserDirectory(text, limit: 10); response = await matrix.client.searchUserDirectory(text, limit: 10);
} catch (e) { } catch (e) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text((e as Object).toLocalizedString(context)))); SnackBar(content: Text((e).toLocalizedString(context))));
return; return;
} finally { } finally {
setState(() => loading = false); setState(() => loading = false);
@ -99,7 +99,7 @@ class InvitationSelectionController extends State<InvitationSelection> {
} }
final participants = Matrix.of(context) final participants = Matrix.of(context)
.client .client
.getRoomById(roomId) .getRoomById(roomId!)!
.getParticipants() .getParticipants()
.where((user) => .where((user) =>
[Membership.join, Membership.invite].contains(user.membership)) [Membership.join, Membership.invite].contains(user.membership))

View File

@ -13,13 +13,12 @@ import 'package:fluffychat/widgets/matrix.dart';
class InvitationSelectionView extends StatelessWidget { class InvitationSelectionView extends StatelessWidget {
final InvitationSelectionController controller; final InvitationSelectionController controller;
const InvitationSelectionView(this.controller, {Key key}) : super(key: key); const InvitationSelectionView(this.controller, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final room = Matrix.of(context).client.getRoomById(controller.roomId); final room = Matrix.of(context).client.getRoomById(controller.roomId!)!;
final groupName = final groupName = room.name.isEmpty ? L10n.of(context)!.group : room.name;
room.name?.isEmpty ?? false ? L10n.of(context).group : room.name;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
leading: VRouter.of(context).path.startsWith('/spaces/') leading: VRouter.of(context).path.startsWith('/spaces/')
@ -27,12 +26,12 @@ class InvitationSelectionView extends StatelessWidget {
: IconButton( : IconButton(
icon: const Icon(Icons.close_outlined), icon: const Icon(Icons.close_outlined),
onPressed: () => VRouter.of(context) onPressed: () => VRouter.of(context)
.toSegments(['rooms', controller.roomId]), .toSegments(['rooms', controller.roomId!]),
), ),
titleSpacing: 0, titleSpacing: 0,
title: DefaultAppBarSearchField( title: DefaultAppBarSearchField(
autofocus: true, autofocus: true,
hintText: L10n.of(context).inviteContactToGroup(groupName), hintText: L10n.of(context)!.inviteContactToGroup(groupName),
onChanged: controller.searchUserWithCoolDown, onChanged: controller.searchUserWithCoolDown,
), ),
), ),
@ -51,7 +50,7 @@ class InvitationSelectionView extends StatelessWidget {
), ),
title: Text( title: Text(
controller.foundProfiles[i].displayName ?? controller.foundProfiles[i].displayName ??
controller.foundProfiles[i].userId.localpart, controller.foundProfiles[i].userId.localpart!,
), ),
subtitle: Text(controller.foundProfiles[i].userId), subtitle: Text(controller.foundProfiles[i].userId),
onTap: () => controller.inviteAction( onTap: () => controller.inviteAction(
@ -66,7 +65,7 @@ class InvitationSelectionView extends StatelessWidget {
child: CircularProgressIndicator.adaptive(strokeWidth: 2), child: CircularProgressIndicator.adaptive(strokeWidth: 2),
); );
} }
final contacts = snapshot.data; final contacts = snapshot.data!;
return ListView.builder( return ListView.builder(
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true, shrinkWrap: true,

View File

@ -34,8 +34,8 @@ class KeyVerificationDialog extends StatefulWidget {
final KeyVerification request; final KeyVerification request;
const KeyVerificationDialog({ const KeyVerificationDialog({
Key key, Key? key,
this.request, required this.request,
}) : super(key: key); }) : super(key: key);
@override @override
@ -43,25 +43,23 @@ class KeyVerificationDialog extends StatefulWidget {
} }
class _KeyVerificationPageState extends State<KeyVerificationDialog> { class _KeyVerificationPageState extends State<KeyVerificationDialog> {
void Function() originalOnUpdate; void Function()? originalOnUpdate;
List<dynamic> sasEmoji; late final List<dynamic> sasEmoji;
@override @override
void initState() { void initState() {
originalOnUpdate = widget.request.onUpdate; originalOnUpdate = widget.request.onUpdate;
widget.request.onUpdate = () { widget.request.onUpdate = () {
if (originalOnUpdate != null) { originalOnUpdate?.call();
originalOnUpdate(); setState(() {});
}
setState(() => null);
}; };
widget.request.client.getProfileFromUserId(widget.request.userId).then((p) { widget.request.client.getProfileFromUserId(widget.request.userId).then((p) {
profile = p; profile = p;
setState(() => null); setState(() {});
}); });
rootBundle.loadString('assets/sas-emoji.json').then((e) { rootBundle.loadString('assets/sas-emoji.json').then((e) {
sasEmoji = json.decode(e); sasEmoji = json.decode(e);
setState(() => null); setState(() {});
}); });
super.initState(); super.initState();
} }
@ -77,12 +75,11 @@ class _KeyVerificationPageState extends State<KeyVerificationDialog> {
super.dispose(); super.dispose();
} }
Profile profile; Profile? profile;
Future<void> checkInput(String input) async { Future<void> checkInput(String input) async {
if (input == null || input.isEmpty) { if (input.isEmpty) return;
return;
}
final valid = await showFutureLoadingDialog( final valid = await showFutureLoadingDialog(
context: context, context: context,
future: () async { future: () async {
@ -101,24 +98,24 @@ class _KeyVerificationPageState extends State<KeyVerificationDialog> {
await showOkAlertDialog( await showOkAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
message: L10n.of(context).incorrectPassphraseOrKey, message: L10n.of(context)!.incorrectPassphraseOrKey,
); );
} }
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
User user; User? user;
final directChatId = final directChatId =
widget.request.client.getDirectChatFromUserId(widget.request.userId); widget.request.client.getDirectChatFromUserId(widget.request.userId);
if (directChatId != null) { if (directChatId != null) {
user = widget.request.client user = widget.request.client
.getRoomById(directChatId) .getRoomById(directChatId)!
?.getUserByMXIDSync(widget.request.userId); .getUserByMXIDSync(widget.request.userId);
} }
final displayName = final displayName =
user?.calcDisplayname() ?? widget.request.userId.localpart; user?.calcDisplayname() ?? widget.request.userId.localpart!;
var title = Text(L10n.of(context).verifyTitle); var title = Text(L10n.of(context)!.verifyTitle);
Widget body; Widget body;
final buttons = <Widget>[]; final buttons = <Widget>[];
switch (widget.request.state) { switch (widget.request.state) {
@ -131,7 +128,7 @@ class _KeyVerificationPageState extends State<KeyVerificationDialog> {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Text(L10n.of(context).askSSSSSign, Text(L10n.of(context)!.askSSSSSign,
style: const TextStyle(fontSize: 20)), style: const TextStyle(fontSize: 20)),
Container(height: 10), Container(height: 10),
TextField( TextField(
@ -146,7 +143,7 @@ class _KeyVerificationPageState extends State<KeyVerificationDialog> {
maxLines: 1, maxLines: 1,
obscureText: true, obscureText: true,
decoration: InputDecoration( decoration: InputDecoration(
hintText: L10n.of(context).passphraseOrKey, hintText: L10n.of(context)!.passphraseOrKey,
prefixStyle: TextStyle(color: Theme.of(context).primaryColor), prefixStyle: TextStyle(color: Theme.of(context).primaryColor),
suffixStyle: TextStyle(color: Theme.of(context).primaryColor), suffixStyle: TextStyle(color: Theme.of(context).primaryColor),
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
@ -156,16 +153,16 @@ class _KeyVerificationPageState extends State<KeyVerificationDialog> {
), ),
); );
buttons.add(AdaptiveFlatButton( buttons.add(AdaptiveFlatButton(
label: L10n.of(context).submit, label: L10n.of(context)!.submit,
onPressed: () => checkInput(textEditingController.text), onPressed: () => checkInput(textEditingController.text),
)); ));
buttons.add(AdaptiveFlatButton( buttons.add(AdaptiveFlatButton(
label: L10n.of(context).skip, label: L10n.of(context)!.skip,
onPressed: () => widget.request.openSSSS(skip: true), onPressed: () => widget.request.openSSSS(skip: true),
)); ));
break; break;
case KeyVerificationState.askAccept: case KeyVerificationState.askAccept:
title = Text(L10n.of(context).newVerificationRequest); title = Text(L10n.of(context)!.newVerificationRequest);
body = Column( body = Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@ -199,19 +196,19 @@ class _KeyVerificationPageState extends State<KeyVerificationDialog> {
Image.asset('assets/verification.png', fit: BoxFit.contain), Image.asset('assets/verification.png', fit: BoxFit.contain),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
L10n.of(context).askVerificationRequest(displayName), L10n.of(context)!.askVerificationRequest(displayName),
) )
], ],
); );
buttons.add(AdaptiveFlatButton( buttons.add(AdaptiveFlatButton(
label: L10n.of(context).reject, label: L10n.of(context)!.reject,
textColor: Colors.red, textColor: Colors.red,
onPressed: () => widget.request onPressed: () => widget.request
.rejectVerification() .rejectVerification()
.then((_) => Navigator.of(context, rootNavigator: false).pop()), .then((_) => Navigator.of(context, rootNavigator: false).pop()),
)); ));
buttons.add(AdaptiveFlatButton( buttons.add(AdaptiveFlatButton(
label: L10n.of(context).accept, label: L10n.of(context)!.accept,
onPressed: () => widget.request.acceptVerification(), onPressed: () => widget.request.acceptVerification(),
)); ));
break; break;
@ -224,22 +221,22 @@ class _KeyVerificationPageState extends State<KeyVerificationDialog> {
const CircularProgressIndicator.adaptive(strokeWidth: 2), const CircularProgressIndicator.adaptive(strokeWidth: 2),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
L10n.of(context).waitingPartnerAcceptRequest, L10n.of(context)!.waitingPartnerAcceptRequest,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
], ],
); );
final key = widget.request.client.userDeviceKeys[widget.request.userId] final key = widget.request.client.userDeviceKeys[widget.request.userId]
.deviceKeys[widget.request.deviceId]; ?.deviceKeys[widget.request.deviceId];
if (key != null) { if (key != null) {
buttons.add(AdaptiveFlatButton( buttons.add(AdaptiveFlatButton(
label: L10n.of(context).verifyManual, label: L10n.of(context)!.verifyManual,
onPressed: () async { onPressed: () async {
final result = await showOkCancelAlertDialog( final result = await showOkCancelAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).verifyManual, title: L10n.of(context)!.verifyManual,
message: key.ed25519Key.beautified, message: key.ed25519Key?.beautified ?? 'Key not found',
); );
if (result == OkCancelResult.ok) { if (result == OkCancelResult.ok) {
await key.setVerified(true); await key.setVerified(true);
@ -257,14 +254,14 @@ class _KeyVerificationPageState extends State<KeyVerificationDialog> {
// view for if "emoji" is a present sasType or not? // view for if "emoji" is a present sasType or not?
String compareText; String compareText;
if (widget.request.sasTypes.contains('emoji')) { if (widget.request.sasTypes.contains('emoji')) {
compareText = L10n.of(context).compareEmojiMatch; compareText = L10n.of(context)!.compareEmojiMatch;
compareWidget = TextSpan( compareWidget = TextSpan(
children: widget.request.sasEmojis children: widget.request.sasEmojis
.map((e) => WidgetSpan(child: _Emoji(e, sasEmoji))) .map((e) => WidgetSpan(child: _Emoji(e, sasEmoji)))
.toList(), .toList(),
); );
} else { } else {
compareText = L10n.of(context).compareNumbersMatch; compareText = L10n.of(context)!.compareNumbersMatch;
final numbers = widget.request.sasNumbers; final numbers = widget.request.sasNumbers;
final numbstr = '${numbers[0]}-${numbers[1]}-${numbers[2]}'; final numbstr = '${numbers[0]}-${numbers[1]}-${numbers[2]}';
compareWidget = compareWidget =
@ -289,18 +286,18 @@ class _KeyVerificationPageState extends State<KeyVerificationDialog> {
); );
buttons.add(AdaptiveFlatButton( buttons.add(AdaptiveFlatButton(
textColor: Colors.red, textColor: Colors.red,
label: L10n.of(context).theyDontMatch, label: L10n.of(context)!.theyDontMatch,
onPressed: () => widget.request.rejectSas(), onPressed: () => widget.request.rejectSas(),
)); ));
buttons.add(AdaptiveFlatButton( buttons.add(AdaptiveFlatButton(
label: L10n.of(context).theyMatch, label: L10n.of(context)!.theyMatch,
onPressed: () => widget.request.acceptSas(), onPressed: () => widget.request.acceptSas(),
)); ));
break; break;
case KeyVerificationState.waitingSas: case KeyVerificationState.waitingSas:
final acceptText = widget.request.sasTypes.contains('emoji') final acceptText = widget.request.sasTypes.contains('emoji')
? L10n.of(context).waitingPartnerEmoji ? L10n.of(context)!.waitingPartnerEmoji
: L10n.of(context).waitingPartnerNumbers; : L10n.of(context)!.waitingPartnerNumbers;
body = Column( body = Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
@ -321,13 +318,13 @@ class _KeyVerificationPageState extends State<KeyVerificationDialog> {
color: Colors.green, size: 200.0), color: Colors.green, size: 200.0),
const SizedBox(height: 10), const SizedBox(height: 10),
Text( Text(
L10n.of(context).verifySuccess, L10n.of(context)!.verifySuccess,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
], ],
); );
buttons.add(AdaptiveFlatButton( buttons.add(AdaptiveFlatButton(
label: L10n.of(context).close, label: L10n.of(context)!.close,
onPressed: () => Navigator.of(context, rootNavigator: false).pop(), onPressed: () => Navigator.of(context, rootNavigator: false).pop(),
)); ));
break; break;
@ -344,12 +341,11 @@ class _KeyVerificationPageState extends State<KeyVerificationDialog> {
], ],
); );
buttons.add(AdaptiveFlatButton( buttons.add(AdaptiveFlatButton(
label: L10n.of(context).close, label: L10n.of(context)!.close,
onPressed: () => Navigator.of(context, rootNavigator: false).pop(), onPressed: () => Navigator.of(context, rootNavigator: false).pop(),
)); ));
break; break;
} }
body ??= Text('ERROR: Unknown state ' + widget.request.state.toString());
final content = SingleChildScrollView( final content = SingleChildScrollView(
scrollDirection: Axis.vertical, scrollDirection: Axis.vertical,
child: Column( child: Column(
@ -377,16 +373,17 @@ class _KeyVerificationPageState extends State<KeyVerificationDialog> {
class _Emoji extends StatelessWidget { class _Emoji extends StatelessWidget {
final KeyVerificationEmoji emoji; final KeyVerificationEmoji emoji;
final List<dynamic> sasEmoji; final List<dynamic>? sasEmoji;
const _Emoji(this.emoji, this.sasEmoji); const _Emoji(this.emoji, this.sasEmoji);
String getLocalizedName() { String getLocalizedName() {
final sasEmoji = this.sasEmoji;
if (sasEmoji == null) { if (sasEmoji == null) {
// asset is still being loaded // asset is still being loaded
return emoji.name; return emoji.name;
} }
final translations = Map<String, String>.from( final translations = Map<String, String?>.from(
sasEmoji[emoji.number]['translated_descriptions']); sasEmoji[emoji.number]['translated_descriptions']);
translations['en'] = emoji.name; translations['en'] = emoji.name;
for (final locale in window.locales) { for (final locale in window.locales) {
@ -398,7 +395,7 @@ class _Emoji extends StatelessWidget {
if (haveLanguage == wantLanguage && if (haveLanguage == wantLanguage &&
(Set.from(haveLocaleParts)..removeAll(wantLocaleParts)).isEmpty && (Set.from(haveLocaleParts)..removeAll(wantLocaleParts)).isEmpty &&
(translations[haveLocale]?.isNotEmpty ?? false)) { (translations[haveLocale]?.isNotEmpty ?? false)) {
return translations[haveLocale]; return translations[haveLocale]!;
} }
} }
} }

View File

@ -15,7 +15,7 @@ import '../../utils/platform_infos.dart';
import 'login_view.dart'; import 'login_view.dart';
class Login extends StatefulWidget { class Login extends StatefulWidget {
const Login({Key key}) : super(key: key); const Login({Key? key}) : super(key: key);
@override @override
LoginController createState() => LoginController(); LoginController createState() => LoginController();
@ -24,8 +24,8 @@ class Login extends StatefulWidget {
class LoginController extends State<Login> { class LoginController extends State<Login> {
final TextEditingController usernameController = TextEditingController(); final TextEditingController usernameController = TextEditingController();
final TextEditingController passwordController = TextEditingController(); final TextEditingController passwordController = TextEditingController();
String usernameError; String? usernameError;
String passwordError; String? passwordError;
bool loading = false; bool loading = false;
bool showPassword = false; bool showPassword = false;
@ -34,12 +34,12 @@ class LoginController extends State<Login> {
void login([_]) async { void login([_]) async {
final matrix = Matrix.of(context); final matrix = Matrix.of(context);
if (usernameController.text.isEmpty) { if (usernameController.text.isEmpty) {
setState(() => usernameError = L10n.of(context).pleaseEnterYourUsername); setState(() => usernameError = L10n.of(context)!.pleaseEnterYourUsername);
} else { } else {
setState(() => usernameError = null); setState(() => usernameError = null);
} }
if (passwordController.text.isEmpty) { if (passwordController.text.isEmpty) {
setState(() => passwordError = L10n.of(context).pleaseEnterYourPassword); setState(() => passwordError = L10n.of(context)!.pleaseEnterYourPassword);
} else { } else {
setState(() => passwordError = null); setState(() => passwordError = null);
} }
@ -85,7 +85,7 @@ class LoginController extends State<Login> {
if (mounted) setState(() => loading = false); if (mounted) setState(() => loading = false);
} }
Timer _coolDown; Timer? _coolDown;
void checkWellKnownWithCoolDown(String userId) async { void checkWellKnownWithCoolDown(String userId) async {
_coolDown?.cancel(); _coolDown?.cancel();
@ -100,14 +100,13 @@ class LoginController extends State<Login> {
if (!userId.isValidMatrixId) return; if (!userId.isValidMatrixId) return;
try { try {
final oldHomeserver = Matrix.of(context).getLoginClient().homeserver; final oldHomeserver = Matrix.of(context).getLoginClient().homeserver;
var newDomain = Uri.https(userId.domain, ''); var newDomain = Uri.https(userId.domain!, '');
Matrix.of(context).getLoginClient().homeserver = newDomain; Matrix.of(context).getLoginClient().homeserver = newDomain;
DiscoveryInformation wellKnownInformation; DiscoveryInformation? wellKnownInformation;
try { try {
wellKnownInformation = wellKnownInformation =
await Matrix.of(context).getLoginClient().getWellknown(); await Matrix.of(context).getLoginClient().getWellknown();
if (wellKnownInformation.mHomeserver?.baseUrl?.toString()?.isNotEmpty ?? if (wellKnownInformation.mHomeserver.baseUrl.toString().isNotEmpty) {
false) {
newDomain = wellKnownInformation.mHomeserver.baseUrl; newDomain = wellKnownInformation.mHomeserver.baseUrl;
} }
} catch (_) { } catch (_) {
@ -130,9 +129,10 @@ class LoginController extends State<Login> {
final dialogResult = await showOkCancelAlertDialog( final dialogResult = await showOkCancelAlertDialog(
context: context, context: context,
useRootNavigator: false, useRootNavigator: false,
message: L10n.of(context).noMatrixServer(newDomain, oldHomeserver), message:
okLabel: L10n.of(context).ok, L10n.of(context)!.noMatrixServer(newDomain, oldHomeserver!),
cancelLabel: L10n.of(context).cancel, okLabel: L10n.of(context)!.ok,
cancelLabel: L10n.of(context)!.cancel,
); );
if (dialogResult == OkCancelResult.ok) { if (dialogResult == OkCancelResult.ok) {
setState(() => usernameError = null); setState(() => usernameError = null);
@ -142,7 +142,7 @@ class LoginController extends State<Login> {
} }
} }
var jitsi = wellKnownInformation?.additionalProperties var jitsi = wellKnownInformation?.additionalProperties
?.tryGet<Map<String, dynamic>>('im.vector.riot.jitsi') .tryGet<Map<String, dynamic>>('im.vector.riot.jitsi')
?.tryGet<String>('preferredDomain'); ?.tryGet<String>('preferredDomain');
if (jitsi != null) { if (jitsi != null) {
if (!jitsi.endsWith('/')) { if (!jitsi.endsWith('/')) {
@ -168,12 +168,12 @@ class LoginController extends State<Login> {
final input = await showTextInputDialog( final input = await showTextInputDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).enterAnEmailAddress, title: L10n.of(context)!.enterAnEmailAddress,
okLabel: L10n.of(context).ok, okLabel: L10n.of(context)!.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context)!.cancel,
textFields: [ textFields: [
DialogTextField( DialogTextField(
hintText: L10n.of(context).enterAnEmailAddress, hintText: L10n.of(context)!.enterAnEmailAddress,
keyboardType: TextInputType.emailAddress, keyboardType: TextInputType.emailAddress,
), ),
], ],
@ -194,17 +194,17 @@ class LoginController extends State<Login> {
final ok = await showOkAlertDialog( final ok = await showOkAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).weSentYouAnEmail, title: L10n.of(context)!.weSentYouAnEmail,
message: L10n.of(context).pleaseClickOnLink, message: L10n.of(context)!.pleaseClickOnLink,
okLabel: L10n.of(context).iHaveClickedOnLink, okLabel: L10n.of(context)!.iHaveClickedOnLink,
); );
if (ok == null) return; if (ok != OkCancelResult.ok) return;
final password = await showTextInputDialog( final password = await showTextInputDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).chooseAStrongPassword, title: L10n.of(context)!.chooseAStrongPassword,
okLabel: L10n.of(context).ok, okLabel: L10n.of(context)!.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context)!.cancel,
textFields: [ textFields: [
const DialogTextField( const DialogTextField(
hintText: '******', hintText: '******',
@ -222,7 +222,7 @@ class LoginController extends State<Login> {
auth: AuthenticationThreePidCreds( auth: AuthenticationThreePidCreds(
type: AuthenticationTypes.emailIdentity, type: AuthenticationTypes.emailIdentity,
threepidCreds: ThreepidCreds( threepidCreds: ThreepidCreds(
sid: response.result.sid, sid: response.result!.sid,
clientSecret: clientSecret, clientSecret: clientSecret,
), ),
), ),
@ -230,7 +230,7 @@ class LoginController extends State<Login> {
); );
if (success.error == null) { if (success.error == null) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(L10n.of(context).passwordHasBeenChanged))); SnackBar(content: Text(L10n.of(context)!.passwordHasBeenChanged)));
} }
} }

View File

@ -9,7 +9,7 @@ import 'login.dart';
class LoginView extends StatelessWidget { class LoginView extends StatelessWidget {
final LoginController controller; final LoginController controller;
const LoginView(this.controller, {Key key}) : super(key: key); const LoginView(this.controller, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -19,7 +19,7 @@ class LoginView extends StatelessWidget {
leading: controller.loading ? Container() : const BackButton(), leading: controller.loading ? Container() : const BackButton(),
elevation: 0, elevation: 0,
title: Text( title: Text(
L10n.of(context).logInTo(Matrix.of(context) L10n.of(context)!.logInTo(Matrix.of(context)
.getLoginClient() .getLoginClient()
.homeserver .homeserver
.toString() .toString()
@ -42,9 +42,9 @@ class LoginView extends StatelessWidget {
controller.loading ? null : [AutofillHints.username], controller.loading ? null : [AutofillHints.username],
decoration: InputDecoration( decoration: InputDecoration(
prefixIcon: const Icon(Icons.account_box_outlined), prefixIcon: const Icon(Icons.account_box_outlined),
hintText: L10n.of(context).username, hintText: L10n.of(context)!.username,
errorText: controller.usernameError, errorText: controller.usernameError,
labelText: L10n.of(context).username), labelText: L10n.of(context)!.username),
), ),
), ),
Padding( Padding(
@ -62,13 +62,13 @@ class LoginView extends StatelessWidget {
hintText: '****', hintText: '****',
errorText: controller.passwordError, errorText: controller.passwordError,
suffixIcon: IconButton( suffixIcon: IconButton(
tooltip: L10n.of(context).showPassword, tooltip: L10n.of(context)!.showPassword,
icon: Icon(controller.showPassword icon: Icon(controller.showPassword
? Icons.visibility_off_outlined ? Icons.visibility_off_outlined
: Icons.visibility_outlined), : Icons.visibility_outlined),
onPressed: controller.toggleShowPassword, onPressed: controller.toggleShowPassword,
), ),
labelText: L10n.of(context).password, labelText: L10n.of(context)!.password,
), ),
), ),
), ),
@ -83,7 +83,7 @@ class LoginView extends StatelessWidget {
: () => controller.login(context), : () => controller.login(context),
child: controller.loading child: controller.loading
? const LinearProgressIndicator() ? const LinearProgressIndicator()
: Text(L10n.of(context).login), : Text(L10n.of(context)!.login),
), ),
), ),
), ),
@ -91,7 +91,7 @@ class LoginView extends StatelessWidget {
child: TextButton( child: TextButton(
onPressed: controller.passwordForgotten, onPressed: controller.passwordForgotten,
child: Text( child: Text(
L10n.of(context).passwordForgotten, L10n.of(context)!.passwordForgotten,
style: const TextStyle( style: const TextStyle(
color: Colors.blue, color: Colors.blue,
decoration: TextDecoration.underline, decoration: TextDecoration.underline,

View File

@ -8,7 +8,7 @@ import 'package:fluffychat/pages/new_group/new_group_view.dart';
import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/matrix.dart';
class NewGroup extends StatefulWidget { class NewGroup extends StatefulWidget {
const NewGroup({Key key}) : super(key: key); const NewGroup({Key? key}) : super(key: key);
@override @override
NewGroupController createState() => NewGroupController(); NewGroupController createState() => NewGroupController();
@ -35,7 +35,7 @@ class NewGroupController extends State<NewGroup> {
}, },
); );
if (roomID.error == null) { if (roomID.error == null) {
VRouter.of(context).toSegments(['rooms', roomID.result, 'invite']); VRouter.of(context).toSegments(['rooms', roomID.result!, 'invite']);
} }
} }

View File

@ -8,13 +8,13 @@ import 'package:fluffychat/widgets/layouts/max_width_body.dart';
class NewGroupView extends StatelessWidget { class NewGroupView extends StatelessWidget {
final NewGroupController controller; final NewGroupController controller;
const NewGroupView(this.controller, {Key key}) : super(key: key); const NewGroupView(this.controller, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(L10n.of(context).createNewGroup), title: Text(L10n.of(context)!.createNewGroup),
), ),
body: MaxWidthBody( body: MaxWidthBody(
child: Column( child: Column(
@ -29,13 +29,13 @@ class NewGroupView extends StatelessWidget {
textInputAction: TextInputAction.go, textInputAction: TextInputAction.go,
onSubmitted: controller.submitAction, onSubmitted: controller.submitAction,
decoration: InputDecoration( decoration: InputDecoration(
labelText: L10n.of(context).optionalGroupName, labelText: L10n.of(context)!.optionalGroupName,
prefixIcon: const Icon(Icons.people_outlined), prefixIcon: const Icon(Icons.people_outlined),
hintText: L10n.of(context).enterAGroupName), hintText: L10n.of(context)!.enterAGroupName),
), ),
), ),
SwitchListTile.adaptive( SwitchListTile.adaptive(
title: Text(L10n.of(context).groupIsPublic), title: Text(L10n.of(context)!.groupIsPublic),
value: controller.publicGroup, value: controller.publicGroup,
onChanged: controller.setPublicGroup, onChanged: controller.setPublicGroup,
), ),

View File

@ -11,7 +11,7 @@ import 'package:fluffychat/utils/url_launcher.dart';
import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/matrix.dart';
class NewPrivateChat extends StatefulWidget { class NewPrivateChat extends StatefulWidget {
const NewPrivateChat({Key key}) : super(key: key); const NewPrivateChat({Key? key}) : super(key: key);
@override @override
NewPrivateChatController createState() => NewPrivateChatController(); NewPrivateChatController createState() => NewPrivateChatController();
@ -48,20 +48,20 @@ class NewPrivateChatController extends State<NewPrivateChat> {
void submitAction([_]) async { void submitAction([_]) async {
controller.text = controller.text.trim(); controller.text = controller.text.trim();
if (!formKey.currentState.validate()) return; if (!formKey.currentState!.validate()) return;
UrlLauncher(context, '$prefix${controller.text}').openMatrixToUrl(); UrlLauncher(context, '$prefix${controller.text}').openMatrixToUrl();
} }
String validateForm(String value) { String? validateForm(String? value) {
if (value.isEmpty) { if (value!.isEmpty) {
return L10n.of(context).pleaseEnterAMatrixIdentifier; return L10n.of(context)!.pleaseEnterAMatrixIdentifier;
} }
if (!controller.text.isValidMatrixId || if (!controller.text.isValidMatrixId ||
!supportedSigils.contains(controller.text.sigil)) { !supportedSigils.contains(controller.text.sigil)) {
return L10n.of(context).makeSureTheIdentifierIsValid; return L10n.of(context)!.makeSureTheIdentifierIsValid;
} }
if (controller.text == Matrix.of(context).client.userID) { if (controller.text == Matrix.of(context).client.userID) {
return L10n.of(context).youCannotInviteYourself; return L10n.of(context)!.youCannotInviteYourself;
} }
return null; return null;
} }

View File

@ -15,7 +15,7 @@ import 'package:fluffychat/widgets/matrix.dart';
class NewPrivateChatView extends StatelessWidget { class NewPrivateChatView extends StatelessWidget {
final NewPrivateChatController controller; final NewPrivateChatController controller;
const NewPrivateChatView(this.controller, {Key key}) : super(key: key); const NewPrivateChatView(this.controller, {Key? key}) : super(key: key);
static const double _qrCodePadding = 8; static const double _qrCodePadding = 8;
@ -24,13 +24,13 @@ class NewPrivateChatView extends StatelessWidget {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
leading: const BackButton(), leading: const BackButton(),
title: Text(L10n.of(context).newChat), title: Text(L10n.of(context)!.newChat),
backgroundColor: Theme.of(context).scaffoldBackgroundColor, backgroundColor: Theme.of(context).scaffoldBackgroundColor,
actions: [ actions: [
TextButton( TextButton(
onPressed: () => VRouter.of(context).to('/newgroup'), onPressed: () => VRouter.of(context).to('/newgroup'),
child: Text( child: Text(
L10n.of(context).createNewGroup, L10n.of(context)!.createNewGroup,
style: TextStyle(color: Theme.of(context).colorScheme.secondary), style: TextStyle(color: Theme.of(context).colorScheme.secondary),
), ),
) )
@ -67,7 +67,7 @@ class NewPrivateChatView extends StatelessWidget {
), ),
), ),
ListTile( ListTile(
subtitle: Text(L10n.of(context).createNewChatExplaination), subtitle: Text(L10n.of(context)!.createNewChatExplaination),
), ),
Padding( Padding(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
@ -81,7 +81,7 @@ class NewPrivateChatView extends StatelessWidget {
onFieldSubmitted: controller.submitAction, onFieldSubmitted: controller.submitAction,
validator: controller.validateForm, validator: controller.validateForm,
decoration: InputDecoration( decoration: InputDecoration(
labelText: L10n.of(context).typeInInviteLinkManually, labelText: L10n.of(context)!.typeInInviteLinkManually,
hintText: '@username', hintText: '@username',
prefixText: 'matrix.to/#/', prefixText: 'matrix.to/#/',
suffixIcon: IconButton( suffixIcon: IconButton(
@ -105,7 +105,7 @@ class NewPrivateChatView extends StatelessWidget {
floatingActionButton: PlatformInfos.isMobile && !controller.hideFab floatingActionButton: PlatformInfos.isMobile && !controller.hideFab
? FloatingActionButton.extended( ? FloatingActionButton.extended(
onPressed: controller.openScannerAction, onPressed: controller.openScannerAction,
label: Text(L10n.of(context).scanQrCode), label: Text(L10n.of(context)!.scanQrCode),
icon: const Icon(Icons.camera_alt_outlined), icon: const Icon(Icons.camera_alt_outlined),
) )
: null, : null,

View File

@ -9,7 +9,7 @@ import 'package:qr_code_scanner/qr_code_scanner.dart';
import 'package:fluffychat/utils/url_launcher.dart'; import 'package:fluffychat/utils/url_launcher.dart';
class QrScannerModal extends StatefulWidget { class QrScannerModal extends StatefulWidget {
const QrScannerModal({Key key}) : super(key: key); const QrScannerModal({Key? key}) : super(key: key);
@override @override
_QrScannerModalState createState() => _QrScannerModalState(); _QrScannerModalState createState() => _QrScannerModalState();
@ -17,15 +17,15 @@ class QrScannerModal extends StatefulWidget {
class _QrScannerModalState extends State<QrScannerModal> { class _QrScannerModalState extends State<QrScannerModal> {
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
QRViewController controller; QRViewController? controller;
@override @override
void reassemble() { void reassemble() {
super.reassemble(); super.reassemble();
if (Platform.isAndroid) { if (Platform.isAndroid) {
controller.pauseCamera(); controller!.pauseCamera();
} else if (Platform.isIOS) { } else if (Platform.isIOS) {
controller.resumeCamera(); controller!.resumeCamera();
} }
} }
@ -36,9 +36,9 @@ class _QrScannerModalState extends State<QrScannerModal> {
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.close_outlined), icon: const Icon(Icons.close_outlined),
onPressed: Navigator.of(context).pop, onPressed: Navigator.of(context).pop,
tooltip: L10n.of(context).close, tooltip: L10n.of(context)!.close,
), ),
title: Text(L10n.of(context).scanQrCode), title: Text(L10n.of(context)!.scanQrCode),
), ),
body: Stack( body: Stack(
children: [ children: [
@ -59,7 +59,7 @@ class _QrScannerModalState extends State<QrScannerModal> {
void _onQRViewCreated(QRViewController controller) { void _onQRViewCreated(QRViewController controller) {
this.controller = controller; this.controller = controller;
StreamSubscription sub; late StreamSubscription sub;
sub = controller.scannedDataStream.listen((scanData) { sub = controller.scannedDataStream.listen((scanData) {
sub.cancel(); sub.cancel();
Navigator.of(context).pop(); Navigator.of(context).pop();

View File

@ -9,7 +9,7 @@ import 'package:fluffychat/pages/new_space/new_space_view.dart';
import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/matrix.dart';
class NewSpace extends StatefulWidget { class NewSpace extends StatefulWidget {
const NewSpace({Key key}) : super(key: key); const NewSpace({Key? key}) : super(key: key);
@override @override
NewSpaceController createState() => NewSpaceController(); NewSpaceController createState() => NewSpaceController();
@ -38,7 +38,7 @@ class NewSpaceController extends State<NewSpace> {
), ),
); );
if (roomID.error == null) { if (roomID.error == null) {
VRouter.of(context).toSegments(['rooms', roomID.result, 'details']); VRouter.of(context).toSegments(['rooms', roomID.result!, 'details']);
} }
} }

View File

@ -8,13 +8,13 @@ import 'new_space.dart';
class NewSpaceView extends StatelessWidget { class NewSpaceView extends StatelessWidget {
final NewSpaceController controller; final NewSpaceController controller;
const NewSpaceView(this.controller, {Key key}) : super(key: key); const NewSpaceView(this.controller, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(L10n.of(context).createNewSpace), title: Text(L10n.of(context)!.createNewSpace),
), ),
body: MaxWidthBody( body: MaxWidthBody(
child: Column( child: Column(
@ -29,13 +29,13 @@ class NewSpaceView extends StatelessWidget {
textInputAction: TextInputAction.go, textInputAction: TextInputAction.go,
onSubmitted: controller.submitAction, onSubmitted: controller.submitAction,
decoration: InputDecoration( decoration: InputDecoration(
labelText: L10n.of(context).spaceName, labelText: L10n.of(context)!.spaceName,
prefixIcon: const Icon(Icons.people_outlined), prefixIcon: const Icon(Icons.people_outlined),
hintText: L10n.of(context).enterASpacepName), hintText: L10n.of(context)!.enterASpacepName),
), ),
), ),
SwitchListTile.adaptive( SwitchListTile.adaptive(
title: Text(L10n.of(context).spaceIsPublic), title: Text(L10n.of(context)!.spaceIsPublic),
value: controller.publicGroup, value: controller.publicGroup,
onChanged: controller.setPublicGroup, onChanged: controller.setPublicGroup,
), ),

View File

@ -1,5 +1,3 @@
//@dart=2.12
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -114,7 +112,7 @@ class SearchController extends State<Search> {
super.initState(); super.initState();
WidgetsBinding.instance?.addPostFrameCallback((_) async { WidgetsBinding.instance?.addPostFrameCallback((_) async {
controller.text = VRouter.of(context).queryParameters['query'] ?? ''; controller.text = VRouter.of(context).queryParameters['query'] ?? '';
final server = await Store().getItem(_serverStoreNamespace) as String?; final server = await Store().getItem(_serverStoreNamespace);
if (server?.isNotEmpty ?? false) { if (server?.isNotEmpty ?? false) {
this.server = server; this.server = server;
} }

View File

@ -17,12 +17,12 @@ import 'search.dart';
class SearchView extends StatelessWidget { class SearchView extends StatelessWidget {
final SearchController controller; final SearchController controller;
const SearchView(this.controller, {Key key}) : super(key: key); const SearchView(this.controller, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final server = controller.genericSearchTerm?.isValidMatrixId ?? false final server = controller.genericSearchTerm?.isValidMatrixId ?? false
? controller.genericSearchTerm.domain ? controller.genericSearchTerm!.domain
: controller.server; : controller.server;
if (controller.lastServer != server) { if (controller.lastServer != server) {
controller.lastServer = server; controller.lastServer = server;
@ -44,15 +44,22 @@ class SearchView extends StatelessWidget {
'chunk': [], 'chunk': [],
}); });
}).then((QueryPublicRoomsResponse res) { }).then((QueryPublicRoomsResponse res) {
if (controller.genericSearchTerm != null && final genericSearchTerm = controller.genericSearchTerm;
if (genericSearchTerm != null &&
!res.chunk.any((room) => !res.chunk.any((room) =>
(room.aliases?.contains(controller.genericSearchTerm) ?? false) || (room.aliases?.contains(controller.genericSearchTerm) ?? false) ||
room.canonicalAlias == controller.genericSearchTerm)) { room.canonicalAlias == controller.genericSearchTerm)) {
// we have to tack on the original alias // we have to tack on the original alias
res.chunk.add(PublicRoomsChunk.fromJson(<String, dynamic>{ res.chunk.add(
'aliases': [controller.genericSearchTerm], PublicRoomsChunk(
'name': controller.genericSearchTerm, aliases: [genericSearchTerm],
})); name: genericSearchTerm,
numJoinedMembers: 0,
roomId: '!unknown',
worldReadable: true,
guestCanJoin: true,
),
);
} }
return res; return res;
}); });
@ -68,15 +75,14 @@ class SearchView extends StatelessWidget {
const tabCount = 3; const tabCount = 3;
return DefaultTabController( return DefaultTabController(
length: tabCount, length: tabCount,
initialIndex: initialIndex: controller.controller.text.startsWith('#') ? 0 : 1,
controller.controller.text?.startsWith('#') ?? false ? 0 : 1,
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
leading: const BackButton(), leading: const BackButton(),
titleSpacing: 0, titleSpacing: 0,
title: DefaultAppBarSearchField( title: DefaultAppBarSearchField(
autofocus: true, autofocus: true,
hintText: L10n.of(context).search, hintText: L10n.of(context)!.search,
searchController: controller.controller, searchController: controller.controller,
suffix: const Icon(Icons.search_outlined), suffix: const Icon(Icons.search_outlined),
onChanged: controller.search, onChanged: controller.search,
@ -84,16 +90,16 @@ class SearchView extends StatelessWidget {
bottom: TabBar( bottom: TabBar(
indicatorColor: Theme.of(context).colorScheme.secondary, indicatorColor: Theme.of(context).colorScheme.secondary,
labelColor: Theme.of(context).colorScheme.secondary, labelColor: Theme.of(context).colorScheme.secondary,
unselectedLabelColor: Theme.of(context).textTheme.bodyText1.color, unselectedLabelColor: Theme.of(context).textTheme.bodyText1!.color,
labelStyle: const TextStyle(fontSize: 16), labelStyle: const TextStyle(fontSize: 16),
labelPadding: const EdgeInsets.symmetric( labelPadding: const EdgeInsets.symmetric(
horizontal: 8, horizontal: 8,
vertical: 0, vertical: 0,
), ),
tabs: [ tabs: [
Tab(child: Text(L10n.of(context).discover, maxLines: 1)), Tab(child: Text(L10n.of(context)!.discover, maxLines: 1)),
Tab(child: Text(L10n.of(context).chats, maxLines: 1)), Tab(child: Text(L10n.of(context)!.chats, maxLines: 1)),
Tab(child: Text(L10n.of(context).people, maxLines: 1)), Tab(child: Text(L10n.of(context)!.people, maxLines: 1)),
], ],
), ),
), ),
@ -111,7 +117,7 @@ class SearchView extends StatelessWidget {
backgroundColor: Theme.of(context).secondaryHeaderColor, backgroundColor: Theme.of(context).secondaryHeaderColor,
child: const Icon(Icons.edit_outlined), child: const Icon(Icons.edit_outlined),
), ),
title: Text(L10n.of(context).changeTheServer), title: Text(L10n.of(context)!.changeTheServer),
onTap: controller.setServer, onTap: controller.setServer,
), ),
FutureBuilder<QueryPublicRoomsResponse>( FutureBuilder<QueryPublicRoomsResponse>(
@ -130,7 +136,7 @@ class SearchView extends StatelessWidget {
), ),
Center( Center(
child: Text( child: Text(
snapshot.error.toLocalizedString(context), snapshot.error!.toLocalizedString(context),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle( style: const TextStyle(
color: Colors.grey, color: Colors.grey,
@ -146,7 +152,7 @@ class SearchView extends StatelessWidget {
child: CircularProgressIndicator.adaptive( child: CircularProgressIndicator.adaptive(
strokeWidth: 2)); strokeWidth: 2));
} }
final publicRoomsResponse = snapshot.data; final publicRoomsResponse = snapshot.data!;
if (publicRoomsResponse.chunk.isEmpty) { if (publicRoomsResponse.chunk.isEmpty) {
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -159,7 +165,7 @@ class SearchView extends StatelessWidget {
), ),
Center( Center(
child: Text( child: Text(
L10n.of(context).noPublicRoomsFound, L10n.of(context)!.noPublicRoomsFound,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle( style: const TextStyle(
color: Colors.grey, color: Colors.grey,
@ -201,7 +207,7 @@ class SearchView extends StatelessWidget {
name: publicRoomsResponse.chunk[i].name, name: publicRoomsResponse.chunk[i].name,
), ),
Text( Text(
publicRoomsResponse.chunk[i].name, publicRoomsResponse.chunk[i].name!,
style: const TextStyle( style: const TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -210,17 +216,16 @@ class SearchView extends StatelessWidget {
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
Text( Text(
L10n.of(context).countParticipants( L10n.of(context)!.countParticipants(
publicRoomsResponse publicRoomsResponse
.chunk[i].numJoinedMembers ?? .chunk[i].numJoinedMembers),
0),
style: const TextStyle(fontSize: 10.5), style: const TextStyle(fontSize: 10.5),
maxLines: 1, maxLines: 1,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
Text( Text(
publicRoomsResponse.chunk[i].topic ?? publicRoomsResponse.chunk[i].topic ??
L10n.of(context).noDescription, L10n.of(context)!.noDescription,
maxLines: 4, maxLines: 4,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
@ -261,7 +266,7 @@ class SearchView extends StatelessWidget {
); );
if (roomID.error == null) { if (roomID.error == null) {
VRouter.of(context) VRouter.of(context)
.toSegments(['rooms', roomID.result]); .toSegments(['rooms', roomID.result!]);
} }
}, },
leading: Avatar( leading: Avatar(
@ -271,7 +276,7 @@ class SearchView extends StatelessWidget {
), ),
title: Text( title: Text(
foundProfile.displayName ?? foundProfile.displayName ??
foundProfile.userId.localpart, foundProfile.userId.localpart!,
style: const TextStyle(), style: const TextStyle(),
maxLines: 1, maxLines: 1,
), ),

View File

@ -14,19 +14,19 @@ import '../../widgets/matrix.dart';
import 'settings_view.dart'; import 'settings_view.dart';
class Settings extends StatefulWidget { class Settings extends StatefulWidget {
const Settings({Key key}) : super(key: key); const Settings({Key? key}) : super(key: key);
@override @override
SettingsController createState() => SettingsController(); SettingsController createState() => SettingsController();
} }
class SettingsController extends State<Settings> { class SettingsController extends State<Settings> {
Future<bool> crossSigningCachedFuture; Future<bool>? crossSigningCachedFuture;
bool crossSigningCached; bool? crossSigningCached;
Future<bool> megolmBackupCachedFuture; Future<bool>? megolmBackupCachedFuture;
bool megolmBackupCached; bool? megolmBackupCached;
Future<dynamic> profileFuture; Future<dynamic>? profileFuture;
Profile profile; Profile? profile;
bool profileUpdated = false; bool profileUpdated = false;
void updateProfile() => setState(() { void updateProfile() => setState(() {
@ -39,19 +39,19 @@ class SettingsController extends State<Settings> {
if (PlatformInfos.isMobile) if (PlatformInfos.isMobile)
SheetAction( SheetAction(
key: AvatarAction.camera, key: AvatarAction.camera,
label: L10n.of(context).openCamera, label: L10n.of(context)!.openCamera,
isDefaultAction: true, isDefaultAction: true,
icon: Icons.camera_alt_outlined, icon: Icons.camera_alt_outlined,
), ),
SheetAction( SheetAction(
key: AvatarAction.file, key: AvatarAction.file,
label: L10n.of(context).openGallery, label: L10n.of(context)!.openGallery,
icon: Icons.photo_outlined, icon: Icons.photo_outlined,
), ),
if (profile?.avatarUrl != null) if (profile?.avatarUrl != null)
SheetAction( SheetAction(
key: AvatarAction.remove, key: AvatarAction.remove,
label: L10n.of(context).removeYourAvatar, label: L10n.of(context)!.removeYourAvatar,
isDestructiveAction: true, isDestructiveAction: true,
icon: Icons.delete_outlined, icon: Icons.delete_outlined,
), ),
@ -60,7 +60,7 @@ class SettingsController extends State<Settings> {
? actions.single ? actions.single
: await showModalActionSheet<AvatarAction>( : await showModalActionSheet<AvatarAction>(
context: context, context: context,
title: L10n.of(context).changeYourAvatar, title: L10n.of(context)!.changeYourAvatar,
actions: actions, actions: actions,
); );
if (action == null) return; if (action == null) return;
@ -91,10 +91,10 @@ class SettingsController extends State<Settings> {
} else { } else {
final result = final result =
await FilePickerCross.importFromStorage(type: FileTypeCross.image); await FilePickerCross.importFromStorage(type: FileTypeCross.image);
if (result == null) return; if (result.fileName == null) return;
file = MatrixFile( file = MatrixFile(
bytes: result.toUint8List(), bytes: result.toUint8List(),
name: result.fileName, name: result.fileName!,
); );
} }
final success = await showFutureLoadingDialog( final success = await showFutureLoadingDialog(
@ -111,7 +111,7 @@ class SettingsController extends State<Settings> {
final client = Matrix.of(context).client; final client = Matrix.of(context).client;
profileFuture ??= client profileFuture ??= client
.getProfileFromUserId( .getProfileFromUserId(
client.userID, client.userID!,
cache: !profileUpdated, cache: !profileUpdated,
getFromRooms: !profileUpdated, getFromRooms: !profileUpdated,
) )
@ -121,12 +121,12 @@ class SettingsController extends State<Settings> {
}); });
if (client.encryption != null) { if (client.encryption != null) {
crossSigningCachedFuture ??= crossSigningCachedFuture ??=
client.encryption?.crossSigning?.isCached()?.then((c) { client.encryption?.crossSigning.isCached().then((c) {
if (mounted) setState(() => crossSigningCached = c); if (mounted) setState(() => crossSigningCached = c);
return c; return c;
}); });
megolmBackupCachedFuture ??= megolmBackupCachedFuture ??=
client.encryption?.keyManager?.isCached()?.then((c) { client.encryption?.keyManager.isCached().then((c) {
if (mounted) setState(() => megolmBackupCached = c); if (mounted) setState(() => megolmBackupCached = c);
return c; return c;
}); });

View File

@ -13,7 +13,7 @@ import 'settings.dart';
class SettingsView extends StatelessWidget { class SettingsView extends StatelessWidget {
final SettingsController controller; final SettingsController controller;
const SettingsView(this.controller, {Key key}) : super(key: key); const SettingsView(this.controller, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -25,11 +25,11 @@ class SettingsView extends StatelessWidget {
expandedHeight: 300.0, expandedHeight: 300.0,
floating: true, floating: true,
pinned: true, pinned: true,
title: Text(L10n.of(context).settings), title: Text(L10n.of(context)!.settings),
backgroundColor: Theme.of(context).appBarTheme.backgroundColor, backgroundColor: Theme.of(context).appBarTheme.backgroundColor,
flexibleSpace: FlexibleSpaceBar( flexibleSpace: FlexibleSpaceBar(
background: ContentBanner( background: ContentBanner(
controller.profile?.avatarUrl, mxContent: controller.profile?.avatarUrl,
onEdit: controller.setAvatarAction, onEdit: controller.setAvatarAction,
defaultIcon: Icons.person_outline_outlined, defaultIcon: Icons.person_outline_outlined,
), ),
@ -37,54 +37,54 @@ class SettingsView extends StatelessWidget {
), ),
], ],
body: ListTileTheme( body: ListTileTheme(
iconColor: Theme.of(context).textTheme.bodyText1.color, iconColor: Theme.of(context).textTheme.bodyText1!.color,
child: ListView( child: ListView(
children: <Widget>[ children: <Widget>[
ListTile( ListTile(
leading: const Icon(Icons.format_paint_outlined), leading: const Icon(Icons.format_paint_outlined),
title: Text(L10n.of(context).changeTheme), title: Text(L10n.of(context)!.changeTheme),
onTap: () => VRouter.of(context).to('/settings/style'), onTap: () => VRouter.of(context).to('/settings/style'),
), ),
const Divider(thickness: 1), const Divider(thickness: 1),
ListTile( ListTile(
leading: const Icon(Icons.notifications_outlined), leading: const Icon(Icons.notifications_outlined),
title: Text(L10n.of(context).notifications), title: Text(L10n.of(context)!.notifications),
onTap: () => VRouter.of(context).to('/settings/notifications'), onTap: () => VRouter.of(context).to('/settings/notifications'),
), ),
ListTile( ListTile(
leading: const Icon(Icons.devices_outlined), leading: const Icon(Icons.devices_outlined),
title: Text(L10n.of(context).devices), title: Text(L10n.of(context)!.devices),
onTap: () => VRouter.of(context).to('/settings/devices'), onTap: () => VRouter.of(context).to('/settings/devices'),
), ),
ListTile( ListTile(
leading: const Icon(Icons.chat_bubble_outline_outlined), leading: const Icon(Icons.chat_bubble_outline_outlined),
title: Text(L10n.of(context).chat), title: Text(L10n.of(context)!.chat),
onTap: () => VRouter.of(context).to('/settings/chat'), onTap: () => VRouter.of(context).to('/settings/chat'),
), ),
ListTile( ListTile(
leading: const Icon(Icons.account_circle_outlined), leading: const Icon(Icons.account_circle_outlined),
title: Text(L10n.of(context).account), title: Text(L10n.of(context)!.account),
onTap: () => VRouter.of(context).to('/settings/account'), onTap: () => VRouter.of(context).to('/settings/account'),
), ),
ListTile( ListTile(
leading: const Icon(Icons.shield_outlined), leading: const Icon(Icons.shield_outlined),
title: Text(L10n.of(context).security), title: Text(L10n.of(context)!.security),
onTap: () => VRouter.of(context).to('/settings/security'), onTap: () => VRouter.of(context).to('/settings/security'),
), ),
const Divider(thickness: 1), const Divider(thickness: 1),
ListTile( ListTile(
leading: const Icon(Icons.help_outline_outlined), leading: const Icon(Icons.help_outline_outlined),
title: Text(L10n.of(context).help), title: Text(L10n.of(context)!.help),
onTap: () => launch(AppConfig.supportUrl), onTap: () => launch(AppConfig.supportUrl),
), ),
ListTile( ListTile(
leading: const Icon(Icons.shield_sharp), leading: const Icon(Icons.shield_sharp),
title: Text(L10n.of(context).privacy), title: Text(L10n.of(context)!.privacy),
onTap: () => launch(AppConfig.privacyUrl), onTap: () => launch(AppConfig.privacyUrl),
), ),
ListTile( ListTile(
leading: const Icon(Icons.info_outline_rounded), leading: const Icon(Icons.info_outline_rounded),
title: Text(L10n.of(context).about), title: Text(L10n.of(context)!.about),
onTap: () => PlatformInfos.showDialog(context), onTap: () => PlatformInfos.showDialog(context),
), ),
], ],

View File

@ -11,7 +11,7 @@ import 'settings_3pid_view.dart';
class Settings3Pid extends StatefulWidget { class Settings3Pid extends StatefulWidget {
static int sendAttempt = 0; static int sendAttempt = 0;
const Settings3Pid({Key key}) : super(key: key); const Settings3Pid({Key? key}) : super(key: key);
@override @override
Settings3PidController createState() => Settings3PidController(); Settings3PidController createState() => Settings3PidController();
@ -22,12 +22,12 @@ class Settings3PidController extends State<Settings3Pid> {
final input = await showTextInputDialog( final input = await showTextInputDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).enterAnEmailAddress, title: L10n.of(context)!.enterAnEmailAddress,
okLabel: L10n.of(context).ok, okLabel: L10n.of(context)!.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context)!.cancel,
textFields: [ textFields: [
DialogTextField( DialogTextField(
hintText: L10n.of(context).enterAnEmailAddress, hintText: L10n.of(context)!.enterAnEmailAddress,
keyboardType: TextInputType.emailAddress, keyboardType: TextInputType.emailAddress,
), ),
], ],
@ -46,17 +46,17 @@ class Settings3PidController extends State<Settings3Pid> {
final ok = await showOkAlertDialog( final ok = await showOkAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).weSentYouAnEmail, title: L10n.of(context)!.weSentYouAnEmail,
message: L10n.of(context).pleaseClickOnLink, message: L10n.of(context)!.pleaseClickOnLink,
okLabel: L10n.of(context).iHaveClickedOnLink, okLabel: L10n.of(context)!.iHaveClickedOnLink,
); );
if (ok == null) return; if (ok != OkCancelResult.ok) return;
final success = await showFutureLoadingDialog( final success = await showFutureLoadingDialog(
context: context, context: context,
future: () => Matrix.of(context).client.uiaRequestBackground( future: () => Matrix.of(context).client.uiaRequestBackground(
(auth) => Matrix.of(context).client.add3PID( (auth) => Matrix.of(context).client.add3PID(
clientSecret, clientSecret,
response.result.sid, response.result!.sid,
auth: auth, auth: auth,
), ),
), ),
@ -65,15 +65,15 @@ class Settings3PidController extends State<Settings3Pid> {
setState(() => request = null); setState(() => request = null);
} }
Future<List<ThirdPartyIdentifier>> request; Future<List<ThirdPartyIdentifier>?>? request;
void delete3Pid(ThirdPartyIdentifier identifier) async { void delete3Pid(ThirdPartyIdentifier identifier) async {
if (await showOkCancelAlertDialog( if (await showOkCancelAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).areYouSure, title: L10n.of(context)!.areYouSure,
okLabel: L10n.of(context).yes, okLabel: L10n.of(context)!.yes,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context)!.cancel,
) != ) !=
OkCancelResult.ok) { OkCancelResult.ok) {
return; return;

View File

@ -10,7 +10,7 @@ import 'package:fluffychat/widgets/matrix.dart';
class Settings3PidView extends StatelessWidget { class Settings3PidView extends StatelessWidget {
final Settings3PidController controller; final Settings3PidController controller;
const Settings3PidView(this.controller, {Key key}) : super(key: key); const Settings3PidView(this.controller, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -18,20 +18,20 @@ class Settings3PidView extends StatelessWidget {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
leading: const BackButton(), leading: const BackButton(),
title: Text(L10n.of(context).passwordRecovery), title: Text(L10n.of(context)!.passwordRecovery),
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Icons.add_outlined), icon: const Icon(Icons.add_outlined),
onPressed: controller.add3PidAction, onPressed: controller.add3PidAction,
tooltip: L10n.of(context).addEmail, tooltip: L10n.of(context)!.addEmail,
) )
], ],
), ),
body: MaxWidthBody( body: MaxWidthBody(
child: FutureBuilder<List<ThirdPartyIdentifier>>( child: FutureBuilder<List<ThirdPartyIdentifier>?>(
future: controller.request, future: controller.request,
builder: (BuildContext context, builder: (BuildContext context,
AsyncSnapshot<List<ThirdPartyIdentifier>> snapshot) { AsyncSnapshot<List<ThirdPartyIdentifier>?> snapshot) {
if (snapshot.hasError) { if (snapshot.hasError) {
return Center( return Center(
child: Text( child: Text(
@ -44,7 +44,7 @@ class Settings3PidView extends StatelessWidget {
return const Center( return const Center(
child: CircularProgressIndicator.adaptive(strokeWidth: 2)); child: CircularProgressIndicator.adaptive(strokeWidth: 2));
} }
final identifier = snapshot.data; final identifier = snapshot.data!;
return Column( return Column(
children: [ children: [
ListTile( ListTile(
@ -60,8 +60,8 @@ class Settings3PidView extends StatelessWidget {
), ),
title: Text( title: Text(
identifier.isEmpty identifier.isEmpty
? L10n.of(context).noPasswordRecoveryDescription ? L10n.of(context)!.noPasswordRecoveryDescription
: L10n.of(context) : L10n.of(context)!
.withTheseAddressesRecoveryDescription, .withTheseAddressesRecoveryDescription,
), ),
), ),
@ -77,7 +77,7 @@ class Settings3PidView extends StatelessWidget {
child: Icon(identifier[i].iconData)), child: Icon(identifier[i].iconData)),
title: Text(identifier[i].address), title: Text(identifier[i].address),
trailing: IconButton( trailing: IconButton(
tooltip: L10n.of(context).delete, tooltip: L10n.of(context)!.delete,
icon: const Icon(Icons.delete_forever_outlined), icon: const Icon(Icons.delete_forever_outlined),
color: Colors.red, color: Colors.red,
onPressed: () => controller.delete3Pid(identifier[i]), onPressed: () => controller.delete3Pid(identifier[i]),
@ -102,6 +102,5 @@ extension on ThirdPartyIdentifier {
case ThirdPartyIdentifierMedium.msisdn: case ThirdPartyIdentifierMedium.msisdn:
return Icons.phone_android_outlined; return Icons.phone_android_outlined;
} }
return Icons.device_unknown_outlined;
} }
} }

View File

@ -10,15 +10,15 @@ import 'package:fluffychat/pages/settings_account/settings_account_view.dart';
import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/matrix.dart';
class SettingsAccount extends StatefulWidget { class SettingsAccount extends StatefulWidget {
const SettingsAccount({Key key}) : super(key: key); const SettingsAccount({Key? key}) : super(key: key);
@override @override
SettingsAccountController createState() => SettingsAccountController(); SettingsAccountController createState() => SettingsAccountController();
} }
class SettingsAccountController extends State<SettingsAccount> { class SettingsAccountController extends State<SettingsAccount> {
Future<dynamic> profileFuture; Future<dynamic>? profileFuture;
Profile profile; Profile? profile;
bool profileUpdated = false; bool profileUpdated = false;
void updateProfile() => setState(() { void updateProfile() => setState(() {
@ -30,13 +30,13 @@ class SettingsAccountController extends State<SettingsAccount> {
final input = await showTextInputDialog( final input = await showTextInputDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).editDisplayname, title: L10n.of(context)!.editDisplayname,
okLabel: L10n.of(context).ok, okLabel: L10n.of(context)!.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context)!.cancel,
textFields: [ textFields: [
DialogTextField( DialogTextField(
initialText: profile?.displayName ?? initialText: profile?.displayName ??
Matrix.of(context).client.userID.localpart, Matrix.of(context).client.userID!.localpart,
) )
], ],
); );
@ -45,7 +45,7 @@ class SettingsAccountController extends State<SettingsAccount> {
final success = await showFutureLoadingDialog( final success = await showFutureLoadingDialog(
context: context, context: context,
future: () => future: () =>
matrix.client.setDisplayName(matrix.client.userID, input.single), matrix.client.setDisplayName(matrix.client.userID!, input.single),
); );
if (success.error == null) { if (success.error == null) {
updateProfile(); updateProfile();
@ -56,9 +56,9 @@ class SettingsAccountController extends State<SettingsAccount> {
if (await showOkCancelAlertDialog( if (await showOkCancelAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).areYouSureYouWantToLogout, title: L10n.of(context)!.areYouSureYouWantToLogout,
okLabel: L10n.of(context).yes, okLabel: L10n.of(context)!.yes,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context)!.cancel,
) == ) ==
OkCancelResult.cancel) { OkCancelResult.cancel) {
return; return;
@ -74,10 +74,10 @@ class SettingsAccountController extends State<SettingsAccount> {
if (await showOkCancelAlertDialog( if (await showOkCancelAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).warning, title: L10n.of(context)!.warning,
message: L10n.of(context).deactivateAccountWarning, message: L10n.of(context)!.deactivateAccountWarning,
okLabel: L10n.of(context).ok, okLabel: L10n.of(context)!.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context)!.cancel,
) == ) ==
OkCancelResult.cancel) { OkCancelResult.cancel) {
return; return;
@ -85,9 +85,9 @@ class SettingsAccountController extends State<SettingsAccount> {
if (await showOkCancelAlertDialog( if (await showOkCancelAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).areYouSure, title: L10n.of(context)!.areYouSure,
okLabel: L10n.of(context).yes, okLabel: L10n.of(context)!.yes,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context)!.cancel,
) == ) ==
OkCancelResult.cancel) { OkCancelResult.cancel) {
return; return;
@ -95,9 +95,9 @@ class SettingsAccountController extends State<SettingsAccount> {
final input = await showTextInputDialog( final input = await showTextInputDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).pleaseEnterYourPassword, title: L10n.of(context)!.pleaseEnterYourPassword,
okLabel: L10n.of(context).ok, okLabel: L10n.of(context)!.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context)!.cancel,
textFields: [ textFields: [
const DialogTextField( const DialogTextField(
obscureText: true, obscureText: true,
@ -114,7 +114,7 @@ class SettingsAccountController extends State<SettingsAccount> {
auth: AuthenticationPassword( auth: AuthenticationPassword(
password: input.single, password: input.single,
identifier: AuthenticationUserIdentifier( identifier: AuthenticationUserIdentifier(
user: Matrix.of(context).client.userID), user: Matrix.of(context).client.userID!),
), ),
), ),
); );
@ -127,7 +127,7 @@ class SettingsAccountController extends State<SettingsAccount> {
final client = Matrix.of(context).client; final client = Matrix.of(context).client;
profileFuture ??= client profileFuture ??= client
.getProfileFromUserId( .getProfileFromUserId(
client.userID, client.userID!,
cache: !profileUpdated, cache: !profileUpdated,
getFromRooms: !profileUpdated, getFromRooms: !profileUpdated,
) )

View File

@ -10,51 +10,51 @@ import 'settings_account.dart';
class SettingsAccountView extends StatelessWidget { class SettingsAccountView extends StatelessWidget {
final SettingsAccountController controller; final SettingsAccountController controller;
const SettingsAccountView(this.controller, {Key key}) : super(key: key); const SettingsAccountView(this.controller, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: Text(L10n.of(context).account)), appBar: AppBar(title: Text(L10n.of(context)!.account)),
body: ListTileTheme( body: ListTileTheme(
iconColor: Theme.of(context).textTheme.bodyText1.color, iconColor: Theme.of(context).textTheme.bodyText1!.color,
child: MaxWidthBody( child: MaxWidthBody(
withScrolling: true, withScrolling: true,
child: Column( child: Column(
children: [ children: [
ListTile( ListTile(
title: Text(L10n.of(context).yourUserId), title: Text(L10n.of(context)!.yourUserId),
subtitle: Text(Matrix.of(context).client.userID), subtitle: Text(Matrix.of(context).client.userID!),
trailing: const Icon(Icons.copy_outlined), trailing: const Icon(Icons.copy_outlined),
onTap: () => FluffyShare.share( onTap: () => FluffyShare.share(
Matrix.of(context).client.userID, Matrix.of(context).client.userID!,
context, context,
), ),
), ),
ListTile( ListTile(
trailing: const Icon(Icons.edit_outlined), trailing: const Icon(Icons.edit_outlined),
title: Text(L10n.of(context).editDisplayname), title: Text(L10n.of(context)!.editDisplayname),
subtitle: Text(controller.profile?.displayName ?? subtitle: Text(controller.profile?.displayName ??
Matrix.of(context).client.userID.localpart), Matrix.of(context).client.userID!.localpart!),
onTap: controller.setDisplaynameAction, onTap: controller.setDisplaynameAction,
), ),
const Divider(height: 1), const Divider(height: 1),
ListTile( ListTile(
trailing: const Icon(Icons.person_add_outlined), trailing: const Icon(Icons.person_add_outlined),
title: Text(L10n.of(context).addAccount), title: Text(L10n.of(context)!.addAccount),
subtitle: Text(L10n.of(context).enableMultiAccounts), subtitle: Text(L10n.of(context)!.enableMultiAccounts),
onTap: controller.addAccountAction, onTap: controller.addAccountAction,
), ),
ListTile( ListTile(
trailing: const Icon(Icons.exit_to_app_outlined), trailing: const Icon(Icons.exit_to_app_outlined),
title: Text(L10n.of(context).logout), title: Text(L10n.of(context)!.logout),
onTap: controller.logoutAction, onTap: controller.logoutAction,
), ),
const Divider(height: 1), const Divider(height: 1),
ListTile( ListTile(
trailing: const Icon(Icons.delete_outlined), trailing: const Icon(Icons.delete_outlined),
title: Text( title: Text(
L10n.of(context).deleteAccount, L10n.of(context)!.deleteAccount,
style: const TextStyle(color: Colors.red), style: const TextStyle(color: Colors.red),
), ),
onTap: controller.deleteAccountAction, onTap: controller.deleteAccountAction,

View File

@ -9,7 +9,7 @@ import 'package:fluffychat/widgets/matrix.dart';
import 'settings_chat_view.dart'; import 'settings_chat_view.dart';
class SettingsChat extends StatefulWidget { class SettingsChat extends StatefulWidget {
const SettingsChat({Key key}) : super(key: key); const SettingsChat({Key? key}) : super(key: key);
@override @override
SettingsChatController createState() => SettingsChatController(); SettingsChatController createState() => SettingsChatController();
@ -21,9 +21,9 @@ class SettingsChatController extends State<SettingsChat> {
final input = await showTextInputDialog( final input = await showTextInputDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).editJitsiInstance, title: L10n.of(context)!.editJitsiInstance,
okLabel: L10n.of(context).ok, okLabel: L10n.of(context)!.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context)!.cancel,
textFields: [ textFields: [
DialogTextField( DialogTextField(
initialText: AppConfig.jitsiInstance.replaceFirst(prefix, ''), initialText: AppConfig.jitsiInstance.replaceFirst(prefix, ''),

View File

@ -12,52 +12,52 @@ import 'settings_chat.dart';
class SettingsChatView extends StatelessWidget { class SettingsChatView extends StatelessWidget {
final SettingsChatController controller; final SettingsChatController controller;
const SettingsChatView(this.controller, {Key key}) : super(key: key); const SettingsChatView(this.controller, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: Text(L10n.of(context).chat)), appBar: AppBar(title: Text(L10n.of(context)!.chat)),
body: ListTileTheme( body: ListTileTheme(
iconColor: Theme.of(context).textTheme.bodyText1.color, iconColor: Theme.of(context).textTheme.bodyText1!.color,
child: MaxWidthBody( child: MaxWidthBody(
withScrolling: true, withScrolling: true,
child: Column( child: Column(
children: [ children: [
SettingsSwitchListTile.adaptive( SettingsSwitchListTile.adaptive(
title: L10n.of(context).renderRichContent, title: L10n.of(context)!.renderRichContent,
onChanged: (b) => AppConfig.renderHtml = b, onChanged: (b) => AppConfig.renderHtml = b,
storeKey: SettingKeys.renderHtml, storeKey: SettingKeys.renderHtml,
defaultValue: AppConfig.renderHtml, defaultValue: AppConfig.renderHtml,
), ),
SettingsSwitchListTile.adaptive( SettingsSwitchListTile.adaptive(
title: L10n.of(context).hideRedactedEvents, title: L10n.of(context)!.hideRedactedEvents,
onChanged: (b) => AppConfig.hideRedactedEvents = b, onChanged: (b) => AppConfig.hideRedactedEvents = b,
storeKey: SettingKeys.hideRedactedEvents, storeKey: SettingKeys.hideRedactedEvents,
defaultValue: AppConfig.hideRedactedEvents, defaultValue: AppConfig.hideRedactedEvents,
), ),
SettingsSwitchListTile.adaptive( SettingsSwitchListTile.adaptive(
title: L10n.of(context).hideUnknownEvents, title: L10n.of(context)!.hideUnknownEvents,
onChanged: (b) => AppConfig.hideUnknownEvents = b, onChanged: (b) => AppConfig.hideUnknownEvents = b,
storeKey: SettingKeys.hideUnknownEvents, storeKey: SettingKeys.hideUnknownEvents,
defaultValue: AppConfig.hideUnknownEvents, defaultValue: AppConfig.hideUnknownEvents,
), ),
SettingsSwitchListTile.adaptive( SettingsSwitchListTile.adaptive(
title: L10n.of(context).autoplayImages, title: L10n.of(context)!.autoplayImages,
onChanged: (b) => AppConfig.autoplayImages = b, onChanged: (b) => AppConfig.autoplayImages = b,
storeKey: SettingKeys.autoplayImages, storeKey: SettingKeys.autoplayImages,
defaultValue: AppConfig.autoplayImages, defaultValue: AppConfig.autoplayImages,
), ),
if (PlatformInfos.isMobile) if (PlatformInfos.isMobile)
SettingsSwitchListTile.adaptive( SettingsSwitchListTile.adaptive(
title: L10n.of(context).sendOnEnter, title: L10n.of(context)!.sendOnEnter,
onChanged: (b) => AppConfig.sendOnEnter = b, onChanged: (b) => AppConfig.sendOnEnter = b,
storeKey: SettingKeys.sendOnEnter, storeKey: SettingKeys.sendOnEnter,
defaultValue: AppConfig.sendOnEnter, defaultValue: AppConfig.sendOnEnter,
), ),
const Divider(height: 1), const Divider(height: 1),
ListTile( ListTile(
title: Text(L10n.of(context).emoteSettings), title: Text(L10n.of(context)!.emoteSettings),
onTap: () => VRouter.of(context).to('emotes'), onTap: () => VRouter.of(context).to('emotes'),
trailing: const Padding( trailing: const Padding(
padding: EdgeInsets.all(16.0), padding: EdgeInsets.all(16.0),
@ -69,7 +69,7 @@ class SettingsChatView extends StatelessWidget {
padding: EdgeInsets.all(16.0), padding: EdgeInsets.all(16.0),
child: Icon(Icons.phone_outlined), child: Icon(Icons.phone_outlined),
), ),
title: Text(L10n.of(context).editJitsiInstance), title: Text(L10n.of(context)!.editJitsiInstance),
subtitle: Text(AppConfig.jitsiInstance), subtitle: Text(AppConfig.jitsiInstance),
onTap: controller.setJitsiInstanceAction, onTap: controller.setJitsiInstanceAction,
), ),

View File

@ -12,27 +12,27 @@ import '../../widgets/matrix.dart';
import 'settings_emotes_view.dart'; import 'settings_emotes_view.dart';
class EmotesSettings extends StatefulWidget { class EmotesSettings extends StatefulWidget {
const EmotesSettings({Key key}) : super(key: key); const EmotesSettings({Key? key}) : super(key: key);
@override @override
EmotesSettingsController createState() => EmotesSettingsController(); EmotesSettingsController createState() => EmotesSettingsController();
} }
class EmotesSettingsController extends State<EmotesSettings> { class EmotesSettingsController extends State<EmotesSettings> {
String get roomId => VRouter.of(context).pathParameters['roomid']; String? get roomId => VRouter.of(context).pathParameters['roomid'];
Room get room => Room? get room =>
roomId != null ? Matrix.of(context).client.getRoomById(roomId) : null; roomId != null ? Matrix.of(context).client.getRoomById(roomId!) : null;
String get stateKey => VRouter.of(context).pathParameters['state_key']; String? get stateKey => VRouter.of(context).pathParameters['state_key'];
bool showSave = false; bool showSave = false;
TextEditingController newImageCodeController = TextEditingController(); TextEditingController newImageCodeController = TextEditingController();
ValueNotifier<ImagePackImageContent> newImageController = ValueNotifier<ImagePackImageContent?> newImageController =
ValueNotifier<ImagePackImageContent>(null); ValueNotifier<ImagePackImageContent?>(null);
ImagePackContent _getPack() { ImagePackContent _getPack() {
final client = Matrix.of(context).client; final client = Matrix.of(context).client;
final event = (room != null final event = (room != null
? room.getState('im.ponies.room_emotes', stateKey ?? '') ? room!.getState('im.ponies.room_emotes', stateKey ?? '')
: client.accountData['im.ponies.user_emotes']) ?? : client.accountData['im.ponies.user_emotes']) ??
BasicEvent.fromJson(<String, dynamic>{ BasicEvent.fromJson(<String, dynamic>{
'type': 'm.dummy', 'type': 'm.dummy',
@ -42,8 +42,8 @@ class EmotesSettingsController extends State<EmotesSettings> {
return BasicEvent.fromJson(event.toJson()).parsedImagePackContent; return BasicEvent.fromJson(event.toJson()).parsedImagePackContent;
} }
ImagePackContent _pack; ImagePackContent? _pack;
ImagePackContent get pack { ImagePackContent? get pack {
if (_pack != null) { if (_pack != null) {
return _pack; return _pack;
} }
@ -60,13 +60,13 @@ class EmotesSettingsController extends State<EmotesSettings> {
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => client.setRoomStateWithKey( future: () => client.setRoomStateWithKey(
room.id, 'im.ponies.room_emotes', stateKey ?? '', pack.toJson()), room!.id, 'im.ponies.room_emotes', stateKey ?? '', pack!.toJson()),
); );
} else { } else {
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => client.setAccountData( future: () => client.setAccountData(
client.userID, 'im.ponies.user_emotes', pack.toJson()), client.userID!, 'im.ponies.user_emotes', pack!.toJson()),
); );
} }
} }
@ -82,26 +82,26 @@ class EmotesSettingsController extends State<EmotesSettings> {
if (content['rooms'] is! Map) { if (content['rooms'] is! Map) {
content['rooms'] = <String, dynamic>{}; content['rooms'] = <String, dynamic>{};
} }
if (content['rooms'][room.id] is! Map) { if (content['rooms'][room!.id] is! Map) {
content['rooms'][room.id] = <String, dynamic>{}; content['rooms'][room!.id] = <String, dynamic>{};
} }
if (content['rooms'][room.id][stateKey ?? ''] is! Map) { if (content['rooms'][room!.id][stateKey ?? ''] is! Map) {
content['rooms'][room.id][stateKey ?? ''] = <String, dynamic>{}; content['rooms'][room!.id][stateKey ?? ''] = <String, dynamic>{};
} }
} else if (content['rooms'] is Map && content['rooms'][room.id] is Map) { } else if (content['rooms'] is Map && content['rooms'][room!.id] is Map) {
content['rooms'][room.id].remove(stateKey ?? ''); content['rooms'][room!.id].remove(stateKey ?? '');
} }
// and save // and save
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => client.setAccountData( future: () => client.setAccountData(
client.userID, 'im.ponies.emote_rooms', content), client.userID!, 'im.ponies.emote_rooms', content),
); );
setState(() => null); setState(() {});
} }
void removeImageAction(String oldImageCode) => setState(() { void removeImageAction(String oldImageCode) => setState(() {
pack.images.remove(oldImageCode); pack!.images.remove(oldImageCode);
showSave = true; showSave = true;
}); });
@ -111,13 +111,13 @@ class EmotesSettingsController extends State<EmotesSettings> {
ImagePackImageContent image, ImagePackImageContent image,
TextEditingController controller, TextEditingController controller,
) { ) {
if (pack.images.keys.any((k) => k == imageCode && k != oldImageCode)) { if (pack!.images.keys.any((k) => k == imageCode && k != oldImageCode)) {
controller.text = oldImageCode; controller.text = oldImageCode;
showOkAlertDialog( showOkAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
message: L10n.of(context).emoteExists, message: L10n.of(context)!.emoteExists,
okLabel: L10n.of(context).ok, okLabel: L10n.of(context)!.ok,
); );
return; return;
} }
@ -126,29 +126,29 @@ class EmotesSettingsController extends State<EmotesSettings> {
showOkAlertDialog( showOkAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
message: L10n.of(context).emoteInvalid, message: L10n.of(context)!.emoteInvalid,
okLabel: L10n.of(context).ok, okLabel: L10n.of(context)!.ok,
); );
return; return;
} }
setState(() { setState(() {
pack.images[imageCode] = image; pack!.images[imageCode] = image;
pack.images.remove(oldImageCode); pack!.images.remove(oldImageCode);
showSave = true; showSave = true;
}); });
} }
bool isGloballyActive(Client client) => bool isGloballyActive(Client? client) =>
room != null && room != null &&
client.accountData['im.ponies.emote_rooms']?.content is Map && client!.accountData['im.ponies.emote_rooms']?.content is Map &&
client.accountData['im.ponies.emote_rooms'].content['rooms'] is Map && client.accountData['im.ponies.emote_rooms']!.content['rooms'] is Map &&
client.accountData['im.ponies.emote_rooms'].content['rooms'][room.id] client.accountData['im.ponies.emote_rooms']!.content['rooms'][room!.id]
is Map && is Map &&
client.accountData['im.ponies.emote_rooms'].content['rooms'][room.id] client.accountData['im.ponies.emote_rooms']!.content['rooms'][room!.id]
[stateKey ?? ''] is Map; [stateKey ?? ''] is Map;
bool get readonly => bool get readonly =>
room == null ? false : !(room.canSendEvent('im.ponies.room_emotes')); room == null ? false : !(room!.canSendEvent('im.ponies.room_emotes'));
void saveAction() async { void saveAction() async {
await _save(context); await _save(context);
@ -158,24 +158,23 @@ class EmotesSettingsController extends State<EmotesSettings> {
} }
void addImageAction() async { void addImageAction() async {
if (newImageCodeController.text == null || if (newImageCodeController.text.isEmpty ||
newImageCodeController.text.isEmpty ||
newImageController.value == null) { newImageController.value == null) {
await showOkAlertDialog( await showOkAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
message: L10n.of(context).emoteWarnNeedToPick, message: L10n.of(context)!.emoteWarnNeedToPick,
okLabel: L10n.of(context).ok, okLabel: L10n.of(context)!.ok,
); );
return; return;
} }
final imageCode = newImageCodeController.text; final imageCode = newImageCodeController.text;
if (pack.images.containsKey(imageCode)) { if (pack!.images.containsKey(imageCode)) {
await showOkAlertDialog( await showOkAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
message: L10n.of(context).emoteExists, message: L10n.of(context)!.emoteExists,
okLabel: L10n.of(context).ok, okLabel: L10n.of(context)!.ok,
); );
return; return;
} }
@ -183,12 +182,12 @@ class EmotesSettingsController extends State<EmotesSettings> {
await showOkAlertDialog( await showOkAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
message: L10n.of(context).emoteInvalid, message: L10n.of(context)!.emoteInvalid,
okLabel: L10n.of(context).ok, okLabel: L10n.of(context)!.ok,
); );
return; return;
} }
pack.images[imageCode] = newImageController.value; pack!.images[imageCode] = newImageController.value!;
await _save(context); await _save(context);
setState(() { setState(() {
newImageCodeController.text = ''; newImageCodeController.text = '';
@ -198,13 +197,13 @@ class EmotesSettingsController extends State<EmotesSettings> {
} }
void imagePickerAction( void imagePickerAction(
ValueNotifier<ImagePackImageContent> controller) async { ValueNotifier<ImagePackImageContent?> controller) async {
final result = final result =
await FilePickerCross.importFromStorage(type: FileTypeCross.image); await FilePickerCross.importFromStorage(type: FileTypeCross.image);
if (result == null) return; if (result.fileName == null) return;
var file = MatrixImageFile( var file = MatrixImageFile(
bytes: result.toUint8List(), bytes: result.toUint8List(),
name: result.fileName, name: result.fileName!,
); );
try { try {
file = await file.resizeImage(calcBlurhash: false); file = await file.resizeImage(calcBlurhash: false);

View File

@ -13,16 +13,16 @@ import 'settings_emotes.dart';
class EmotesSettingsView extends StatelessWidget { class EmotesSettingsView extends StatelessWidget {
final EmotesSettingsController controller; final EmotesSettingsController controller;
const EmotesSettingsView(this.controller, {Key key}) : super(key: key); const EmotesSettingsView(this.controller, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final client = Matrix.of(context).client; final client = Matrix.of(context).client;
final imageKeys = controller.pack.images.keys.toList(); final imageKeys = controller.pack!.images.keys.toList();
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
leading: const BackButton(), leading: const BackButton(),
title: Text(L10n.of(context).emoteSettings), title: Text(L10n.of(context)!.emoteSettings),
), ),
floatingActionButton: controller.showSave floatingActionButton: controller.showSave
? FloatingActionButton( ? FloatingActionButton(
@ -53,7 +53,7 @@ class EmotesSettingsView extends StatelessWidget {
minLines: 1, minLines: 1,
maxLines: 1, maxLines: 1,
decoration: InputDecoration( decoration: InputDecoration(
hintText: L10n.of(context).emoteShortcode, hintText: L10n.of(context)!.emoteShortcode,
prefixText: ': ', prefixText: ': ',
suffixText: ':', suffixText: ':',
prefixStyle: TextStyle( prefixStyle: TextStyle(
@ -84,7 +84,7 @@ class EmotesSettingsView extends StatelessWidget {
), ),
if (controller.room != null) if (controller.room != null)
SwitchListTile.adaptive( SwitchListTile.adaptive(
title: Text(L10n.of(context).enableEmotesGlobally), title: Text(L10n.of(context)!.enableEmotesGlobally),
value: controller.isGloballyActive(client), value: controller.isGloballyActive(client),
onChanged: controller.setIsGloballyActive, onChanged: controller.setIsGloballyActive,
), ),
@ -100,7 +100,7 @@ class EmotesSettingsView extends StatelessWidget {
child: Padding( child: Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Text( child: Text(
L10n.of(context).noEmotesFound, L10n.of(context)!.noEmotesFound,
style: const TextStyle(fontSize: 20), style: const TextStyle(fontSize: 20),
), ),
), ),
@ -114,7 +114,7 @@ class EmotesSettingsView extends StatelessWidget {
return Container(height: 70); return Container(height: 70);
} }
final imageCode = imageKeys[i]; final imageCode = imageKeys[i];
final image = controller.pack.images[imageCode]; final image = controller.pack!.images[imageCode]!;
final textEditingController = TextEditingController(); final textEditingController = TextEditingController();
textEditingController.text = imageCode; textEditingController.text = imageCode;
final useShortCuts = final useShortCuts =
@ -158,7 +158,7 @@ class EmotesSettingsView extends StatelessWidget {
minLines: 1, minLines: 1,
maxLines: 1, maxLines: 1,
decoration: InputDecoration( decoration: InputDecoration(
hintText: L10n.of(context).emoteShortcode, hintText: L10n.of(context)!.emoteShortcode,
prefixText: ': ', prefixText: ': ',
suffixText: ':', suffixText: ':',
prefixStyle: TextStyle( prefixStyle: TextStyle(
@ -217,7 +217,7 @@ class _EmoteImage extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
const size = 38.0; const size = 38.0;
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio; final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
final url = mxc?.getThumbnail( final url = mxc.getThumbnail(
Matrix.of(context).client, Matrix.of(context).client,
width: size * devicePixelRatio, width: size * devicePixelRatio,
height: size * devicePixelRatio, height: size * devicePixelRatio,
@ -233,11 +233,11 @@ class _EmoteImage extends StatelessWidget {
} }
class _ImagePicker extends StatefulWidget { class _ImagePicker extends StatefulWidget {
final ValueNotifier<ImagePackImageContent> controller; final ValueNotifier<ImagePackImageContent?> controller;
final void Function(ValueNotifier<ImagePackImageContent>) onPressed; final void Function(ValueNotifier<ImagePackImageContent?>) onPressed;
const _ImagePicker({@required this.controller, @required this.onPressed}); const _ImagePicker({required this.controller, required this.onPressed});
@override @override
_ImagePickerState createState() => _ImagePickerState(); _ImagePickerState createState() => _ImagePickerState();
@ -249,10 +249,10 @@ class _ImagePickerState extends State<_ImagePicker> {
if (widget.controller.value == null) { if (widget.controller.value == null) {
return ElevatedButton( return ElevatedButton(
onPressed: () => widget.onPressed(widget.controller), onPressed: () => widget.onPressed(widget.controller),
child: Text(L10n.of(context).pickImage), child: Text(L10n.of(context)!.pickImage),
); );
} else { } else {
return _EmoteImage(widget.controller.value.url); return _EmoteImage(widget.controller.value!.url);
} }
} }
} }

View File

@ -6,9 +6,9 @@ import '../../widgets/matrix.dart';
import 'settings_ignore_list_view.dart'; import 'settings_ignore_list_view.dart';
class SettingsIgnoreList extends StatefulWidget { class SettingsIgnoreList extends StatefulWidget {
final String initialUserId; final String? initialUserId;
const SettingsIgnoreList({Key key, this.initialUserId}) : super(key: key); const SettingsIgnoreList({Key? key, this.initialUserId}) : super(key: key);
@override @override
SettingsIgnoreListController createState() => SettingsIgnoreListController(); SettingsIgnoreListController createState() => SettingsIgnoreListController();
@ -21,7 +21,7 @@ class SettingsIgnoreListController extends State<SettingsIgnoreList> {
void initState() { void initState() {
super.initState(); super.initState();
if (widget.initialUserId != null) { if (widget.initialUserId != null) {
controller.text = widget.initialUserId.replaceAll('@', ''); controller.text = widget.initialUserId!.replaceAll('@', '');
} }
} }

View File

@ -12,7 +12,7 @@ import 'settings_ignore_list.dart';
class SettingsIgnoreListView extends StatelessWidget { class SettingsIgnoreListView extends StatelessWidget {
final SettingsIgnoreListController controller; final SettingsIgnoreListController controller;
const SettingsIgnoreListView(this.controller, {Key key}) : super(key: key); const SettingsIgnoreListView(this.controller, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -20,7 +20,7 @@ class SettingsIgnoreListView extends StatelessWidget {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
leading: const BackButton(), leading: const BackButton(),
title: Text(L10n.of(context).ignoredUsers), title: Text(L10n.of(context)!.ignoredUsers),
), ),
body: MaxWidthBody( body: MaxWidthBody(
child: Column( child: Column(
@ -39,9 +39,9 @@ class SettingsIgnoreListView extends StatelessWidget {
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
hintText: 'bad_guy:domain.abc', hintText: 'bad_guy:domain.abc',
prefixText: '@', prefixText: '@',
labelText: L10n.of(context).ignoreUsername, labelText: L10n.of(context)!.ignoreUsername,
suffixIcon: IconButton( suffixIcon: IconButton(
tooltip: L10n.of(context).ignore, tooltip: L10n.of(context)!.ignore,
icon: const Icon(Icons.done_outlined), icon: const Icon(Icons.done_outlined),
onPressed: () => controller.ignoreUser(context), onPressed: () => controller.ignoreUser(context),
), ),
@ -49,7 +49,7 @@ class SettingsIgnoreListView extends StatelessWidget {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
L10n.of(context).ignoreListDescription, L10n.of(context)!.ignoreListDescription,
style: const TextStyle(color: Colors.orange), style: const TextStyle(color: Colors.orange),
), ),
], ],
@ -74,7 +74,7 @@ class SettingsIgnoreListView extends StatelessWidget {
title: Text( title: Text(
s.data?.displayName ?? client.ignoredUsers[i]), s.data?.displayName ?? client.ignoredUsers[i]),
trailing: IconButton( trailing: IconButton(
tooltip: L10n.of(context).delete, tooltip: L10n.of(context)!.delete,
icon: const Icon(Icons.delete_forever_outlined), icon: const Icon(Icons.delete_forever_outlined),
onPressed: () => showFutureLoadingDialog( onPressed: () => showFutureLoadingDialog(
context: context, context: context,

View File

@ -5,7 +5,7 @@ import 'package:vrouter/vrouter.dart';
import 'settings_multiple_emotes_view.dart'; import 'settings_multiple_emotes_view.dart';
class MultipleEmotesSettings extends StatefulWidget { class MultipleEmotesSettings extends StatefulWidget {
const MultipleEmotesSettings({Key key}) : super(key: key); const MultipleEmotesSettings({Key? key}) : super(key: key);
@override @override
MultipleEmotesSettingsController createState() => MultipleEmotesSettingsController createState() =>
@ -13,7 +13,7 @@ class MultipleEmotesSettings extends StatefulWidget {
} }
class MultipleEmotesSettingsController extends State<MultipleEmotesSettings> { class MultipleEmotesSettingsController extends State<MultipleEmotesSettings> {
String get roomId => VRouter.of(context).pathParameters['roomid']; String? get roomId => VRouter.of(context).pathParameters['roomid'];
@override @override
Widget build(BuildContext context) => MultipleEmotesSettingsView(this); Widget build(BuildContext context) => MultipleEmotesSettingsView(this);
} }

View File

@ -10,21 +10,21 @@ import 'package:fluffychat/widgets/matrix.dart';
class MultipleEmotesSettingsView extends StatelessWidget { class MultipleEmotesSettingsView extends StatelessWidget {
final MultipleEmotesSettingsController controller; final MultipleEmotesSettingsController controller;
const MultipleEmotesSettingsView(this.controller, {Key key}) const MultipleEmotesSettingsView(this.controller, {Key? key})
: super(key: key); : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final room = Matrix.of(context).client.getRoomById(controller.roomId); final room = Matrix.of(context).client.getRoomById(controller.roomId!)!;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
leading: const BackButton(), leading: const BackButton(),
title: Text(L10n.of(context).emotePacks), title: Text(L10n.of(context)!.emotePacks),
), ),
body: StreamBuilder( body: StreamBuilder(
stream: room.onUpdate.stream, stream: room.onUpdate.stream,
builder: (context, snapshot) { builder: (context, snapshot) {
final packs = final Map<String, Event?> packs =
room.states['im.ponies.room_emotes'] ?? <String, Event>{}; room.states['im.ponies.room_emotes'] ?? <String, Event>{};
if (!packs.containsKey('')) { if (!packs.containsKey('')) {
packs[''] = null; packs[''] = null;
@ -36,7 +36,8 @@ class MultipleEmotesSettingsView extends StatelessWidget {
itemCount: keys.length, itemCount: keys.length,
itemBuilder: (BuildContext context, int i) { itemBuilder: (BuildContext context, int i) {
final event = packs[keys[i]]; final event = packs[keys[i]];
var packName = keys[i].isNotEmpty ? keys[i] : 'Default Pack'; String? packName =
keys[i].isNotEmpty ? keys[i] : 'Default Pack';
if (event != null && event.content['pack'] is Map) { if (event != null && event.content['pack'] is Map) {
if (event.content['pack']['displayname'] is String) { if (event.content['pack']['displayname'] is String) {
packName = event.content['pack']['displayname']; packName = event.content['pack']['displayname'];
@ -45,7 +46,7 @@ class MultipleEmotesSettingsView extends StatelessWidget {
} }
} }
return ListTile( return ListTile(
title: Text(packName), title: Text(packName!),
onTap: () async { onTap: () async {
VRouter.of(context).toSegments( VRouter.of(context).toSegments(
['rooms', room.id, 'details', 'emotes', keys[i]]); ['rooms', room.id, 'details', 'emotes', keys[i]]);

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:collection/collection.dart' show IterableExtension;
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart';
@ -19,38 +20,38 @@ class NotificationSettingsItem {
NotificationSettingsItem( NotificationSettingsItem(
PushRuleKind.underride, PushRuleKind.underride,
'.m.rule.room_one_to_one', '.m.rule.room_one_to_one',
(c) => L10n.of(c).directChats, (c) => L10n.of(c)!.directChats,
), ),
NotificationSettingsItem( NotificationSettingsItem(
PushRuleKind.override, PushRuleKind.override,
'.m.rule.contains_display_name', '.m.rule.contains_display_name',
(c) => L10n.of(c).containsDisplayName, (c) => L10n.of(c)!.containsDisplayName,
), ),
NotificationSettingsItem( NotificationSettingsItem(
PushRuleKind.content, PushRuleKind.content,
'.m.rule.contains_user_name', '.m.rule.contains_user_name',
(c) => L10n.of(c).containsUserName, (c) => L10n.of(c)!.containsUserName,
), ),
NotificationSettingsItem( NotificationSettingsItem(
PushRuleKind.override, PushRuleKind.override,
'.m.rule.invite_for_me', '.m.rule.invite_for_me',
(c) => L10n.of(c).inviteForMe, (c) => L10n.of(c)!.inviteForMe,
), ),
NotificationSettingsItem( NotificationSettingsItem(
PushRuleKind.override, PushRuleKind.override,
'.m.rule.member_event', '.m.rule.member_event',
(c) => L10n.of(c).memberChanges, (c) => L10n.of(c)!.memberChanges,
), ),
NotificationSettingsItem( NotificationSettingsItem(
PushRuleKind.override, PushRuleKind.override,
'.m.rule.suppress_notices', '.m.rule.suppress_notices',
(c) => L10n.of(c).botMessages, (c) => L10n.of(c)!.botMessages,
), ),
]; ];
} }
class SettingsNotifications extends StatefulWidget { class SettingsNotifications extends StatefulWidget {
const SettingsNotifications({Key key}) : super(key: key); const SettingsNotifications({Key? key}) : super(key: key);
@override @override
SettingsNotificationsController createState() => SettingsNotificationsController createState() =>
@ -71,31 +72,30 @@ class SettingsNotificationsController extends State<SettingsNotifications> {
return NotificationSetting.open(); return NotificationSetting.open();
} }
bool getNotificationSetting(NotificationSettingsItem item) { bool? getNotificationSetting(NotificationSettingsItem item) {
final pushRules = Matrix.of(context).client.globalPushRules; final pushRules = Matrix.of(context).client.globalPushRules;
switch (item.type) { switch (item.type) {
case PushRuleKind.content: case PushRuleKind.content:
return pushRules.content return pushRules!.content
?.singleWhere((r) => r.ruleId == item.key, orElse: () => null) ?.singleWhereOrNull((r) => r.ruleId == item.key)
?.enabled; ?.enabled;
case PushRuleKind.override: case PushRuleKind.override:
return pushRules.override return pushRules!.override
?.singleWhere((r) => r.ruleId == item.key, orElse: () => null) ?.singleWhereOrNull((r) => r.ruleId == item.key)
?.enabled; ?.enabled;
case PushRuleKind.room: case PushRuleKind.room:
return pushRules.room return pushRules!.room
?.singleWhere((r) => r.ruleId == item.key, orElse: () => null) ?.singleWhereOrNull((r) => r.ruleId == item.key)
?.enabled; ?.enabled;
case PushRuleKind.sender: case PushRuleKind.sender:
return pushRules.sender return pushRules!.sender
?.singleWhere((r) => r.ruleId == item.key, orElse: () => null) ?.singleWhereOrNull((r) => r.ruleId == item.key)
?.enabled; ?.enabled;
case PushRuleKind.underride: case PushRuleKind.underride:
return pushRules.underride return pushRules!.underride
?.singleWhere((r) => r.ruleId == item.key, orElse: () => null) ?.singleWhereOrNull((r) => r.ruleId == item.key)
?.enabled; ?.enabled;
} }
return false;
} }
void setNotificationSetting(NotificationSettingsItem item, bool enabled) { void setNotificationSetting(NotificationSettingsItem item, bool enabled) {

View File

@ -15,14 +15,15 @@ import 'settings_notifications.dart';
class SettingsNotificationsView extends StatelessWidget { class SettingsNotificationsView extends StatelessWidget {
final SettingsNotificationsController controller; final SettingsNotificationsController controller;
const SettingsNotificationsView(this.controller, {Key key}) : super(key: key); const SettingsNotificationsView(this.controller, {Key? key})
: super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
leading: const BackButton(), leading: const BackButton(),
title: Text(L10n.of(context).notifications), title: Text(L10n.of(context)!.notifications),
), ),
body: MaxWidthBody( body: MaxWidthBody(
withScrolling: true, withScrolling: true,
@ -38,7 +39,7 @@ class SettingsNotificationsView extends StatelessWidget {
SwitchListTile.adaptive( SwitchListTile.adaptive(
value: !Matrix.of(context).client.allPushNotificationsMuted, value: !Matrix.of(context).client.allPushNotificationsMuted,
title: Text( title: Text(
L10n.of(context).notificationsEnabledForThisAccount), L10n.of(context)!.notificationsEnabledForThisAccount),
onChanged: (_) => showFutureLoadingDialog( onChanged: (_) => showFutureLoadingDialog(
context: context, context: context,
future: () => future: () =>
@ -52,7 +53,7 @@ class SettingsNotificationsView extends StatelessWidget {
if (!Matrix.of(context).client.allPushNotificationsMuted) ...{ if (!Matrix.of(context).client.allPushNotificationsMuted) ...{
if (!kIsWeb && Platform.isAndroid) if (!kIsWeb && Platform.isAndroid)
ListTile( ListTile(
title: Text(L10n.of(context).soundVibrationLedColor), title: Text(L10n.of(context)!.soundVibrationLedColor),
trailing: CircleAvatar( trailing: CircleAvatar(
backgroundColor: backgroundColor:
Theme.of(context).scaffoldBackgroundColor, Theme.of(context).scaffoldBackgroundColor,
@ -64,7 +65,7 @@ class SettingsNotificationsView extends StatelessWidget {
const Divider(thickness: 1), const Divider(thickness: 1),
ListTile( ListTile(
title: Text( title: Text(
L10n.of(context).pushRules, L10n.of(context)!.pushRules,
style: TextStyle( style: TextStyle(
color: Theme.of(context).colorScheme.secondary, color: Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -82,20 +83,20 @@ class SettingsNotificationsView extends StatelessWidget {
const Divider(thickness: 1), const Divider(thickness: 1),
ListTile( ListTile(
title: Text( title: Text(
L10n.of(context).devices, L10n.of(context)!.devices,
style: TextStyle( style: TextStyle(
color: Theme.of(context).colorScheme.secondary, color: Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
), ),
FutureBuilder<List<Pusher>>( FutureBuilder<List<Pusher>?>(
future: Matrix.of(context).client.getPushers(), future: Matrix.of(context).client.getPushers(),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasError) { if (snapshot.hasError) {
Center( Center(
child: Text( child: Text(
snapshot.error.toLocalizedString(context), snapshot.error!.toLocalizedString(context),
), ),
); );
} }

View File

@ -12,7 +12,7 @@ import '../bootstrap/bootstrap_dialog.dart';
import 'settings_security_view.dart'; import 'settings_security_view.dart';
class SettingsSecurity extends StatefulWidget { class SettingsSecurity extends StatefulWidget {
const SettingsSecurity({Key key}) : super(key: key); const SettingsSecurity({Key? key}) : super(key: key);
@override @override
SettingsSecurityController createState() => SettingsSecurityController(); SettingsSecurityController createState() => SettingsSecurityController();
@ -23,18 +23,18 @@ class SettingsSecurityController extends State<SettingsSecurity> {
final input = await showTextInputDialog( final input = await showTextInputDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).changePassword, title: L10n.of(context)!.changePassword,
okLabel: L10n.of(context).ok, okLabel: L10n.of(context)!.ok,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context)!.cancel,
textFields: [ textFields: [
DialogTextField( DialogTextField(
hintText: L10n.of(context).pleaseEnterYourPassword, hintText: L10n.of(context)!.pleaseEnterYourPassword,
obscureText: true, obscureText: true,
minLines: 1, minLines: 1,
maxLines: 1, maxLines: 1,
), ),
DialogTextField( DialogTextField(
hintText: L10n.of(context).chooseAStrongPassword, hintText: L10n.of(context)!.chooseAStrongPassword,
obscureText: true, obscureText: true,
minLines: 1, minLines: 1,
maxLines: 1, maxLines: 1,
@ -50,7 +50,7 @@ class SettingsSecurityController extends State<SettingsSecurity> {
); );
if (success.error == null) { if (success.error == null) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(L10n.of(context).passwordHasBeenChanged))); SnackBar(content: Text(L10n.of(context)!.passwordHasBeenChanged)));
} }
} }
@ -58,21 +58,22 @@ class SettingsSecurityController extends State<SettingsSecurity> {
final currentLock = final currentLock =
await const FlutterSecureStorage().read(key: SettingKeys.appLockKey); await const FlutterSecureStorage().read(key: SettingKeys.appLockKey);
if (currentLock?.isNotEmpty ?? false) { if (currentLock?.isNotEmpty ?? false) {
await AppLock.of(context).showLockScreen(); await AppLock.of(context)!.showLockScreen();
} }
final newLock = await showTextInputDialog( final newLock = await showTextInputDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).pleaseChooseAPasscode, title: L10n.of(context)!.pleaseChooseAPasscode,
message: L10n.of(context).pleaseEnter4Digits, message: L10n.of(context)!.pleaseEnter4Digits,
cancelLabel: L10n.of(context).cancel, cancelLabel: L10n.of(context)!.cancel,
textFields: [ textFields: [
DialogTextField( DialogTextField(
validator: (text) { validator: (text) {
if (text.isEmpty || (text.length == 4 && int.tryParse(text) >= 0)) { if (text!.isEmpty ||
(text.length == 4 && int.tryParse(text)! >= 0)) {
return null; return null;
} }
return L10n.of(context).pleaseEnter4Digits; return L10n.of(context)!.pleaseEnter4Digits;
}, },
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
obscureText: true, obscureText: true,
@ -85,9 +86,9 @@ class SettingsSecurityController extends State<SettingsSecurity> {
await const FlutterSecureStorage() await const FlutterSecureStorage()
.write(key: SettingKeys.appLockKey, value: newLock.single); .write(key: SettingKeys.appLockKey, value: newLock.single);
if (newLock.single.isEmpty) { if (newLock.single.isEmpty) {
AppLock.of(context).disable(); AppLock.of(context)!.disable();
} else { } else {
AppLock.of(context).enable(); AppLock.of(context)!.enable();
} }
} }
} }

View File

@ -12,38 +12,38 @@ import 'settings_security.dart';
class SettingsSecurityView extends StatelessWidget { class SettingsSecurityView extends StatelessWidget {
final SettingsSecurityController controller; final SettingsSecurityController controller;
const SettingsSecurityView(this.controller, {Key key}) : super(key: key); const SettingsSecurityView(this.controller, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: Text(L10n.of(context).security)), appBar: AppBar(title: Text(L10n.of(context)!.security)),
body: ListTileTheme( body: ListTileTheme(
iconColor: Theme.of(context).textTheme.bodyText1.color, iconColor: Theme.of(context).textTheme.bodyText1!.color,
child: MaxWidthBody( child: MaxWidthBody(
withScrolling: true, withScrolling: true,
child: Column( child: Column(
children: [ children: [
ListTile( ListTile(
trailing: const Icon(Icons.panorama_fish_eye), trailing: const Icon(Icons.panorama_fish_eye),
title: Text(L10n.of(context).whoCanSeeMyStories), title: Text(L10n.of(context)!.whoCanSeeMyStories),
onTap: () => VRouter.of(context).to('stories'), onTap: () => VRouter.of(context).to('stories'),
), ),
ListTile( ListTile(
trailing: const Icon(Icons.close), trailing: const Icon(Icons.close),
title: Text(L10n.of(context).ignoredUsers), title: Text(L10n.of(context)!.ignoredUsers),
onTap: () => VRouter.of(context).to('ignorelist'), onTap: () => VRouter.of(context).to('ignorelist'),
), ),
ListTile( ListTile(
trailing: const Icon(Icons.vpn_key_outlined), trailing: const Icon(Icons.vpn_key_outlined),
title: Text( title: Text(
L10n.of(context).changePassword, L10n.of(context)!.changePassword,
), ),
onTap: controller.changePasswordAccountAction, onTap: controller.changePasswordAccountAction,
), ),
ListTile( ListTile(
trailing: const Icon(Icons.mail_outlined), trailing: const Icon(Icons.mail_outlined),
title: Text(L10n.of(context).passwordRecovery), title: Text(L10n.of(context)!.passwordRecovery),
onTap: () => VRouter.of(context).to('3pid'), onTap: () => VRouter.of(context).to('3pid'),
), ),
if (Matrix.of(context).client.encryption != null) ...{ if (Matrix.of(context).client.encryption != null) ...{
@ -51,30 +51,30 @@ class SettingsSecurityView extends StatelessWidget {
if (PlatformInfos.isMobile) if (PlatformInfos.isMobile)
ListTile( ListTile(
trailing: const Icon(Icons.lock_outlined), trailing: const Icon(Icons.lock_outlined),
title: Text(L10n.of(context).appLock), title: Text(L10n.of(context)!.appLock),
onTap: controller.setAppLockAction, onTap: controller.setAppLockAction,
), ),
ListTile( ListTile(
title: Text(L10n.of(context).yourPublicKey), title: Text(L10n.of(context)!.yourPublicKey),
onTap: () => showOkAlertDialog( onTap: () => showOkAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).yourPublicKey, title: L10n.of(context)!.yourPublicKey,
message: message:
Matrix.of(context).client.fingerprintKey.beautified, Matrix.of(context).client.fingerprintKey.beautified,
okLabel: L10n.of(context).ok, okLabel: L10n.of(context)!.ok,
), ),
trailing: const Icon(Icons.vpn_key_outlined), trailing: const Icon(Icons.vpn_key_outlined),
), ),
if (!Matrix.of(context).client.encryption.crossSigning.enabled) if (!Matrix.of(context).client.encryption!.crossSigning.enabled)
ListTile( ListTile(
title: Text(L10n.of(context).crossSigningEnabled), title: Text(L10n.of(context)!.crossSigningEnabled),
trailing: const Icon(Icons.error, color: Colors.red), trailing: const Icon(Icons.error, color: Colors.red),
onTap: () => controller.showBootstrapDialog(context), onTap: () => controller.showBootstrapDialog(context),
), ),
if (!Matrix.of(context).client.encryption.keyManager.enabled) if (!Matrix.of(context).client.encryption!.keyManager.enabled)
ListTile( ListTile(
title: Text(L10n.of(context).onlineKeyBackupEnabled), title: Text(L10n.of(context)!.onlineKeyBackupEnabled),
trailing: const Icon(Icons.error, color: Colors.red), trailing: const Icon(Icons.error, color: Colors.red),
onTap: () => controller.showBootstrapDialog(context), onTap: () => controller.showBootstrapDialog(context),
), ),
@ -88,19 +88,19 @@ class SettingsSecurityView extends StatelessWidget {
future: () async { future: () async {
return (await Matrix.of(context) return (await Matrix.of(context)
.client .client
.encryption .encryption!
.keyManager .keyManager
.isCached()) && .isCached()) &&
(await Matrix.of(context) (await Matrix.of(context)
.client .client
.encryption .encryption!
.crossSigning .crossSigning
.isCached()); .isCached());
}(), }(),
builder: (context, snapshot) => snapshot.data == true builder: (context, snapshot) => snapshot.data == true
? Container() ? Container()
: ListTile( : ListTile(
title: Text(L10n.of(context).keysCached), title: Text(L10n.of(context)!.keysCached),
trailing: const Icon(Icons.error, color: Colors.red), trailing: const Icon(Icons.error, color: Colors.red),
onTap: () => controller.showBootstrapDialog(context), onTap: () => controller.showBootstrapDialog(context),
), ),

View File

@ -1,5 +1,3 @@
//@dart=2.12
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart';

View File

@ -1,5 +1,3 @@
//@dart=2.12
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';

View File

@ -1,5 +1,3 @@
//@dart=2.12
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View File

@ -1,5 +1,3 @@
//@dart=2.12
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:adaptive_theme/adaptive_theme.dart'; import 'package:adaptive_theme/adaptive_theme.dart';
@ -19,6 +17,7 @@ class SettingsStyleView extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
controller.currentTheme ??= AdaptiveTheme.of(context).mode; controller.currentTheme ??= AdaptiveTheme.of(context).mode;
const colorPickerSize = 32.0; const colorPickerSize = 32.0;
final wallpaper = Matrix.of(context).wallpaper;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
leading: const BackButton(), leading: const BackButton(),
@ -86,10 +85,10 @@ class SettingsStyleView extends StatelessWidget {
), ),
), ),
), ),
if (Matrix.of(context).wallpaper != null) if (wallpaper != null)
ListTile( ListTile(
title: Image.file( title: Image.file(
Matrix.of(context).wallpaper, wallpaper,
height: 38, height: 38,
fit: BoxFit.cover, fit: BoxFit.cover,
), ),

Some files were not shown because too many files have changed in this diff Show More