mirror of
				https://gitlab.com/famedly/fluffychat.git
				synced 2025-11-04 14:27:23 +01:00 
			
		
		
		
	Merge branch 'krille/migrate-to-nullsafety' into 'main'
refactor: Migrate to null safety See merge request famedly/fluffychat!701
This commit is contained in:
		
						commit
						ff6f67e1b2
					
				@ -1,5 +1,3 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'dart:ui';
 | 
			
		||||
 | 
			
		||||
import 'package:matrix/matrix.dart';
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
abstract class AppEmojis {
 | 
			
		||||
  static const List<String> emojis = [
 | 
			
		||||
    '👍',
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:vrouter/vrouter.dart';
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
abstract class SettingKeys {
 | 
			
		||||
  static const String jitsiInstance = 'chat.fluffy.jitsi_instance';
 | 
			
		||||
  static const String wallpaper = 'chat.fluffy.wallpaper';
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/cupertino.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter/services.dart';
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
// @dart=2.11
 | 
			
		||||
 | 
			
		||||
import 'dart:async';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/foundation.dart';
 | 
			
		||||
@ -67,14 +65,14 @@ void main() async {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class FluffyChatApp extends StatefulWidget {
 | 
			
		||||
  final Widget testWidget;
 | 
			
		||||
  final Widget? testWidget;
 | 
			
		||||
  final List<Client> clients;
 | 
			
		||||
  final Map<String, String> queryParameters;
 | 
			
		||||
  final Map<String, String>? queryParameters;
 | 
			
		||||
 | 
			
		||||
  const FluffyChatApp({
 | 
			
		||||
    Key key,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    this.testWidget,
 | 
			
		||||
    @required this.clients,
 | 
			
		||||
    required this.clients,
 | 
			
		||||
    this.queryParameters,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
@ -88,9 +86,9 @@ class FluffyChatApp extends StatefulWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _FluffyChatAppState extends State<FluffyChatApp> {
 | 
			
		||||
  GlobalKey<VRouterState> _router;
 | 
			
		||||
  bool columnMode;
 | 
			
		||||
  String _initialUrl;
 | 
			
		||||
  GlobalKey<VRouterState>? _router;
 | 
			
		||||
  bool? columnMode;
 | 
			
		||||
  String? _initialUrl;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
@ -133,15 +131,12 @@ class _FluffyChatAppState extends State<FluffyChatApp> {
 | 
			
		||||
            localizationsDelegates: L10n.localizationsDelegates,
 | 
			
		||||
            supportedLocales: L10n.supportedLocales,
 | 
			
		||||
            initialUrl: _initialUrl ?? '/',
 | 
			
		||||
            locale: kIsWeb
 | 
			
		||||
                ? Locale(html.window.navigator.language.split('-').first)
 | 
			
		||||
                : null,
 | 
			
		||||
            routes: AppRoutes(columnMode ?? false).routes,
 | 
			
		||||
            builder: (context, child) {
 | 
			
		||||
              LoadingDialog.defaultTitle = L10n.of(context).loadingPleaseWait;
 | 
			
		||||
              LoadingDialog.defaultBackLabel = L10n.of(context).close;
 | 
			
		||||
              LoadingDialog.defaultTitle = L10n.of(context)!.loadingPleaseWait;
 | 
			
		||||
              LoadingDialog.defaultBackLabel = L10n.of(context)!.close;
 | 
			
		||||
              LoadingDialog.defaultOnError =
 | 
			
		||||
                  (e) => (e as Object).toLocalizedString(context);
 | 
			
		||||
                  (e) => (e as Object?)!.toLocalizedString(context);
 | 
			
		||||
              WidgetsBinding.instance?.addPostFrameCallback((_) {
 | 
			
		||||
                SystemChrome.setSystemUIOverlayStyle(
 | 
			
		||||
                  SystemUiOverlayStyle(
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'dart:io';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter/services.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
 | 
			
		||||
@ -18,12 +18,12 @@ class BootstrapDialog extends StatefulWidget {
 | 
			
		||||
  final bool wipe;
 | 
			
		||||
  final Client client;
 | 
			
		||||
  const BootstrapDialog({
 | 
			
		||||
    Key key,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    this.wipe = false,
 | 
			
		||||
    @required this.client,
 | 
			
		||||
    required this.client,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  Future<bool> show(BuildContext context) => PlatformInfos.isCupertinoStyle
 | 
			
		||||
  Future<bool?> show(BuildContext context) => PlatformInfos.isCupertinoStyle
 | 
			
		||||
      ? showCupertinoDialog(
 | 
			
		||||
          context: context,
 | 
			
		||||
          builder: (context) => this,
 | 
			
		||||
@ -45,18 +45,18 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
 | 
			
		||||
  final TextEditingController _recoveryKeyTextEditingController =
 | 
			
		||||
      TextEditingController();
 | 
			
		||||
 | 
			
		||||
  Bootstrap bootstrap;
 | 
			
		||||
  late Bootstrap bootstrap;
 | 
			
		||||
 | 
			
		||||
  String _recoveryKeyInputError;
 | 
			
		||||
  String? _recoveryKeyInputError;
 | 
			
		||||
 | 
			
		||||
  bool _recoveryKeyInputLoading = false;
 | 
			
		||||
 | 
			
		||||
  String titleText;
 | 
			
		||||
  String? titleText;
 | 
			
		||||
 | 
			
		||||
  bool _recoveryKeyStored = false;
 | 
			
		||||
  bool _recoveryKeyCopied = false;
 | 
			
		||||
 | 
			
		||||
  bool _wipe;
 | 
			
		||||
  bool? _wipe;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
@ -68,8 +68,8 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
 | 
			
		||||
    _wipe = wipe;
 | 
			
		||||
    titleText = null;
 | 
			
		||||
    _recoveryKeyStored = false;
 | 
			
		||||
    bootstrap = widget.client.encryption
 | 
			
		||||
        .bootstrap(onUpdate: () => setState(() => null));
 | 
			
		||||
    bootstrap =
 | 
			
		||||
        widget.client.encryption!.bootstrap(onUpdate: () => setState(() {}));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@ -79,12 +79,12 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
 | 
			
		||||
    Widget body = PlatformInfos.isCupertinoStyle
 | 
			
		||||
        ? const CupertinoActivityIndicator()
 | 
			
		||||
        : const LinearProgressIndicator();
 | 
			
		||||
    titleText = L10n.of(context).loadingPleaseWait;
 | 
			
		||||
    titleText = L10n.of(context)!.loadingPleaseWait;
 | 
			
		||||
 | 
			
		||||
    if (bootstrap.newSsssKey?.recoveryKey != null &&
 | 
			
		||||
        _recoveryKeyStored == false) {
 | 
			
		||||
      final key = bootstrap.newSsssKey.recoveryKey;
 | 
			
		||||
      titleText = L10n.of(context).securityKey;
 | 
			
		||||
      final key = bootstrap.newSsssKey!.recoveryKey;
 | 
			
		||||
      titleText = L10n.of(context)!.securityKey;
 | 
			
		||||
      return Scaffold(
 | 
			
		||||
        appBar: AppBar(
 | 
			
		||||
          centerTitle: true,
 | 
			
		||||
@ -92,7 +92,7 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
 | 
			
		||||
            icon: const Icon(Icons.close),
 | 
			
		||||
            onPressed: Navigator.of(context).pop,
 | 
			
		||||
          ),
 | 
			
		||||
          title: Text(L10n.of(context).securityKey),
 | 
			
		||||
          title: Text(L10n.of(context)!.securityKey),
 | 
			
		||||
        ),
 | 
			
		||||
        body: Center(
 | 
			
		||||
          child: ConstrainedBox(
 | 
			
		||||
@ -102,7 +102,7 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
 | 
			
		||||
              padding: const EdgeInsets.all(16.0),
 | 
			
		||||
              children: [
 | 
			
		||||
                Text(
 | 
			
		||||
                  L10n.of(context).chatBackupDescription,
 | 
			
		||||
                  L10n.of(context)!.chatBackupDescription,
 | 
			
		||||
                  textAlign: TextAlign.center,
 | 
			
		||||
                  style: const TextStyle(
 | 
			
		||||
                    fontSize: 16,
 | 
			
		||||
@ -119,9 +119,9 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
 | 
			
		||||
                const SizedBox(height: 16),
 | 
			
		||||
                ElevatedButton.icon(
 | 
			
		||||
                  icon: const Icon(Icons.save_alt_outlined),
 | 
			
		||||
                  label: Text(L10n.of(context).saveTheSecurityKeyNow),
 | 
			
		||||
                  label: Text(L10n.of(context)!.saveTheSecurityKeyNow),
 | 
			
		||||
                  onPressed: () {
 | 
			
		||||
                    Share.share(key);
 | 
			
		||||
                    Share.share(key!);
 | 
			
		||||
                    setState(() => _recoveryKeyCopied = true);
 | 
			
		||||
                  },
 | 
			
		||||
                ),
 | 
			
		||||
@ -132,7 +132,7 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
 | 
			
		||||
                    onPrimary: Theme.of(context).primaryColor,
 | 
			
		||||
                  ),
 | 
			
		||||
                  icon: const Icon(Icons.check_outlined),
 | 
			
		||||
                  label: Text(L10n.of(context).next),
 | 
			
		||||
                  label: Text(L10n.of(context)!.next),
 | 
			
		||||
                  onPressed: _recoveryKeyCopied
 | 
			
		||||
                      ? () => setState(() => _recoveryKeyStored = true)
 | 
			
		||||
                      : null,
 | 
			
		||||
@ -147,27 +147,27 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
 | 
			
		||||
        case BootstrapState.loading:
 | 
			
		||||
          break;
 | 
			
		||||
        case BootstrapState.askWipeSsss:
 | 
			
		||||
          WidgetsBinding.instance.addPostFrameCallback(
 | 
			
		||||
            (_) => bootstrap.wipeSsss(_wipe),
 | 
			
		||||
          WidgetsBinding.instance!.addPostFrameCallback(
 | 
			
		||||
            (_) => bootstrap.wipeSsss(_wipe!),
 | 
			
		||||
          );
 | 
			
		||||
          break;
 | 
			
		||||
        case BootstrapState.askBadSsss:
 | 
			
		||||
          WidgetsBinding.instance.addPostFrameCallback(
 | 
			
		||||
          WidgetsBinding.instance!.addPostFrameCallback(
 | 
			
		||||
            (_) => bootstrap.ignoreBadSecrets(true),
 | 
			
		||||
          );
 | 
			
		||||
          break;
 | 
			
		||||
        case BootstrapState.askUseExistingSsss:
 | 
			
		||||
          WidgetsBinding.instance.addPostFrameCallback(
 | 
			
		||||
            (_) => bootstrap.useExistingSsss(!_wipe),
 | 
			
		||||
          WidgetsBinding.instance!.addPostFrameCallback(
 | 
			
		||||
            (_) => bootstrap.useExistingSsss(!_wipe!),
 | 
			
		||||
          );
 | 
			
		||||
          break;
 | 
			
		||||
        case BootstrapState.askUnlockSsss:
 | 
			
		||||
          WidgetsBinding.instance.addPostFrameCallback(
 | 
			
		||||
          WidgetsBinding.instance!.addPostFrameCallback(
 | 
			
		||||
            (_) => bootstrap.unlockedSsss(),
 | 
			
		||||
          );
 | 
			
		||||
          break;
 | 
			
		||||
        case BootstrapState.askNewSsss:
 | 
			
		||||
          WidgetsBinding.instance.addPostFrameCallback(
 | 
			
		||||
          WidgetsBinding.instance!.addPostFrameCallback(
 | 
			
		||||
            (_) => bootstrap.newSsss(),
 | 
			
		||||
          );
 | 
			
		||||
          break;
 | 
			
		||||
@ -180,7 +180,7 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
 | 
			
		||||
                icon: const Icon(Icons.close),
 | 
			
		||||
                onPressed: Navigator.of(context).pop,
 | 
			
		||||
              ),
 | 
			
		||||
              title: Text(L10n.of(context).pleaseEnterSecurityKey),
 | 
			
		||||
              title: Text(L10n.of(context)!.pleaseEnterSecurityKey),
 | 
			
		||||
            ),
 | 
			
		||||
            body: Center(
 | 
			
		||||
              child: ConstrainedBox(
 | 
			
		||||
@ -190,7 +190,7 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
 | 
			
		||||
                  padding: const EdgeInsets.all(16.0),
 | 
			
		||||
                  children: [
 | 
			
		||||
                    Text(
 | 
			
		||||
                      L10n.of(context).pleaseEnterSecurityKeyDescription,
 | 
			
		||||
                      L10n.of(context)!.pleaseEnterSecurityKeyDescription,
 | 
			
		||||
                      textAlign: TextAlign.center,
 | 
			
		||||
                      style: const TextStyle(
 | 
			
		||||
                        fontSize: 16,
 | 
			
		||||
@ -209,7 +209,7 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
 | 
			
		||||
                      controller: _recoveryKeyTextEditingController,
 | 
			
		||||
                      decoration: InputDecoration(
 | 
			
		||||
                        hintText: 'Abc123 Def456',
 | 
			
		||||
                        labelText: L10n.of(context).securityKey,
 | 
			
		||||
                        labelText: L10n.of(context)!.securityKey,
 | 
			
		||||
                        errorText: _recoveryKeyInputError,
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
@ -218,7 +218,7 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
 | 
			
		||||
                        icon: _recoveryKeyInputLoading
 | 
			
		||||
                            ? const CircularProgressIndicator.adaptive()
 | 
			
		||||
                            : const Icon(Icons.lock_open_outlined),
 | 
			
		||||
                        label: Text(L10n.of(context).unlockChatBackup),
 | 
			
		||||
                        label: Text(L10n.of(context)!.unlockChatBackup),
 | 
			
		||||
                        onPressed: _recoveryKeyInputLoading
 | 
			
		||||
                            ? null
 | 
			
		||||
                            : () async {
 | 
			
		||||
@ -229,11 +229,12 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
 | 
			
		||||
                                try {
 | 
			
		||||
                                  final key =
 | 
			
		||||
                                      _recoveryKeyTextEditingController.text;
 | 
			
		||||
                                  await bootstrap.newSsssKey.unlock(
 | 
			
		||||
                                  await bootstrap.newSsssKey!.unlock(
 | 
			
		||||
                                    keyOrPassphrase: key,
 | 
			
		||||
                                  );
 | 
			
		||||
                                  Logs().d('SSSS unlocked');
 | 
			
		||||
                                  await bootstrap.client.encryption.crossSigning
 | 
			
		||||
                                  await bootstrap
 | 
			
		||||
                                      .client.encryption!.crossSigning
 | 
			
		||||
                                      .selfSign(
 | 
			
		||||
                                    keyOrPassphrase: key,
 | 
			
		||||
                                  );
 | 
			
		||||
@ -242,7 +243,7 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
 | 
			
		||||
                                } catch (e, s) {
 | 
			
		||||
                                  Logs().w('Unable to unlock SSSS', e, s);
 | 
			
		||||
                                  setState(() => _recoveryKeyInputError =
 | 
			
		||||
                                      L10n.of(context).oopsSomethingWentWrong);
 | 
			
		||||
                                      L10n.of(context)!.oopsSomethingWentWrong);
 | 
			
		||||
                                } finally {
 | 
			
		||||
                                  setState(
 | 
			
		||||
                                      () => _recoveryKeyInputLoading = false);
 | 
			
		||||
@ -253,7 +254,7 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
 | 
			
		||||
                      const Expanded(child: Divider()),
 | 
			
		||||
                      Padding(
 | 
			
		||||
                        padding: const EdgeInsets.all(12.0),
 | 
			
		||||
                        child: Text(L10n.of(context).or),
 | 
			
		||||
                        child: Text(L10n.of(context)!.or),
 | 
			
		||||
                      ),
 | 
			
		||||
                      const Expanded(child: Divider()),
 | 
			
		||||
                    ]),
 | 
			
		||||
@ -264,18 +265,18 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
 | 
			
		||||
                        onPrimary: Theme.of(context).primaryColor,
 | 
			
		||||
                      ),
 | 
			
		||||
                      icon: const Icon(Icons.cast_connected_outlined),
 | 
			
		||||
                      label: Text(L10n.of(context).transferFromAnotherDevice),
 | 
			
		||||
                      label: Text(L10n.of(context)!.transferFromAnotherDevice),
 | 
			
		||||
                      onPressed: _recoveryKeyInputLoading
 | 
			
		||||
                          ? null
 | 
			
		||||
                          : () async {
 | 
			
		||||
                              final req = await showFutureLoadingDialog(
 | 
			
		||||
                                context: context,
 | 
			
		||||
                                future: () => widget
 | 
			
		||||
                                    .client.userDeviceKeys[widget.client.userID]
 | 
			
		||||
                                future: () => widget.client
 | 
			
		||||
                                    .userDeviceKeys[widget.client.userID!]!
 | 
			
		||||
                                    .startVerification(),
 | 
			
		||||
                              );
 | 
			
		||||
                              if (req.error != null) return;
 | 
			
		||||
                              await KeyVerificationDialog(request: req.result)
 | 
			
		||||
                              await KeyVerificationDialog(request: req.result!)
 | 
			
		||||
                                  .show(context);
 | 
			
		||||
                              Navigator.of(context, rootNavigator: false).pop();
 | 
			
		||||
                            },
 | 
			
		||||
@ -287,7 +288,7 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
 | 
			
		||||
                        onPrimary: Colors.red,
 | 
			
		||||
                      ),
 | 
			
		||||
                      icon: const Icon(Icons.delete_outlined),
 | 
			
		||||
                      label: Text(L10n.of(context).securityKeyLost),
 | 
			
		||||
                      label: Text(L10n.of(context)!.securityKeyLost),
 | 
			
		||||
                      onPressed: _recoveryKeyInputLoading
 | 
			
		||||
                          ? null
 | 
			
		||||
                          : () async {
 | 
			
		||||
@ -295,10 +296,10 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
 | 
			
		||||
                                  await showOkCancelAlertDialog(
 | 
			
		||||
                                    useRootNavigator: false,
 | 
			
		||||
                                    context: context,
 | 
			
		||||
                                    title: L10n.of(context).securityKeyLost,
 | 
			
		||||
                                    message: L10n.of(context).wipeChatBackup,
 | 
			
		||||
                                    okLabel: L10n.of(context).ok,
 | 
			
		||||
                                    cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
                                    title: L10n.of(context)!.securityKeyLost,
 | 
			
		||||
                                    message: L10n.of(context)!.wipeChatBackup,
 | 
			
		||||
                                    okLabel: L10n.of(context)!.ok,
 | 
			
		||||
                                    cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
                                    isDestructiveAction: true,
 | 
			
		||||
                                  )) {
 | 
			
		||||
                                setState(() => _createBootstrap(true));
 | 
			
		||||
@ -311,12 +312,12 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
 | 
			
		||||
            ),
 | 
			
		||||
          );
 | 
			
		||||
        case BootstrapState.askWipeCrossSigning:
 | 
			
		||||
          WidgetsBinding.instance.addPostFrameCallback(
 | 
			
		||||
            (_) => bootstrap.wipeCrossSigning(_wipe),
 | 
			
		||||
          WidgetsBinding.instance!.addPostFrameCallback(
 | 
			
		||||
            (_) => bootstrap.wipeCrossSigning(_wipe!),
 | 
			
		||||
          );
 | 
			
		||||
          break;
 | 
			
		||||
        case BootstrapState.askSetupCrossSigning:
 | 
			
		||||
          WidgetsBinding.instance.addPostFrameCallback(
 | 
			
		||||
          WidgetsBinding.instance!.addPostFrameCallback(
 | 
			
		||||
            (_) => bootstrap.askSetupCrossSigning(
 | 
			
		||||
              setupMasterKey: true,
 | 
			
		||||
              setupSelfSigningKey: true,
 | 
			
		||||
@ -325,36 +326,36 @@ class _BootstrapDialogState extends State<BootstrapDialog> {
 | 
			
		||||
          );
 | 
			
		||||
          break;
 | 
			
		||||
        case BootstrapState.askWipeOnlineKeyBackup:
 | 
			
		||||
          WidgetsBinding.instance.addPostFrameCallback(
 | 
			
		||||
            (_) => bootstrap.wipeOnlineKeyBackup(_wipe),
 | 
			
		||||
          WidgetsBinding.instance!.addPostFrameCallback(
 | 
			
		||||
            (_) => bootstrap.wipeOnlineKeyBackup(_wipe!),
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
          break;
 | 
			
		||||
        case BootstrapState.askSetupOnlineKeyBackup:
 | 
			
		||||
          WidgetsBinding.instance.addPostFrameCallback(
 | 
			
		||||
          WidgetsBinding.instance!.addPostFrameCallback(
 | 
			
		||||
            (_) => bootstrap.askSetupOnlineKeyBackup(true),
 | 
			
		||||
          );
 | 
			
		||||
          break;
 | 
			
		||||
        case BootstrapState.error:
 | 
			
		||||
          titleText = L10n.of(context).oopsSomethingWentWrong;
 | 
			
		||||
          titleText = L10n.of(context)!.oopsSomethingWentWrong;
 | 
			
		||||
          body = const Icon(Icons.error_outline, color: Colors.red, size: 40);
 | 
			
		||||
          buttons.add(AdaptiveFlatButton(
 | 
			
		||||
            label: L10n.of(context).close,
 | 
			
		||||
            label: L10n.of(context)!.close,
 | 
			
		||||
            onPressed: () =>
 | 
			
		||||
                Navigator.of(context, rootNavigator: false).pop<bool>(false),
 | 
			
		||||
          ));
 | 
			
		||||
          break;
 | 
			
		||||
        case BootstrapState.done:
 | 
			
		||||
          titleText = L10n.of(context).everythingReady;
 | 
			
		||||
          titleText = L10n.of(context)!.everythingReady;
 | 
			
		||||
          body = Column(
 | 
			
		||||
            mainAxisSize: MainAxisSize.min,
 | 
			
		||||
            children: [
 | 
			
		||||
              Image.asset('assets/backup.png', fit: BoxFit.contain),
 | 
			
		||||
              Text(L10n.of(context).yourChatBackupHasBeenSetUp),
 | 
			
		||||
              Text(L10n.of(context)!.yourChatBackupHasBeenSetUp),
 | 
			
		||||
            ],
 | 
			
		||||
          );
 | 
			
		||||
          buttons.add(AdaptiveFlatButton(
 | 
			
		||||
            label: L10n.of(context).close,
 | 
			
		||||
            label: L10n.of(context)!.close,
 | 
			
		||||
            onPressed: () =>
 | 
			
		||||
                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) {
 | 
			
		||||
      return CupertinoAlertDialog(
 | 
			
		||||
        title: title,
 | 
			
		||||
 | 
			
		||||
@ -34,31 +34,31 @@ import 'send_location_dialog.dart';
 | 
			
		||||
import 'sticker_picker_dialog.dart';
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
  ChatController createState() => ChatController();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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();
 | 
			
		||||
 | 
			
		||||
  FocusNode inputFocus = FocusNode();
 | 
			
		||||
 | 
			
		||||
  Timer typingCoolDown;
 | 
			
		||||
  Timer typingTimeout;
 | 
			
		||||
  Timer? typingCoolDown;
 | 
			
		||||
  Timer? typingTimeout;
 | 
			
		||||
  bool currentlyTyping = false;
 | 
			
		||||
  bool dragging = false;
 | 
			
		||||
 | 
			
		||||
@ -76,7 +76,7 @@ class ChatController extends State<Chat> {
 | 
			
		||||
            bytes: bytes,
 | 
			
		||||
            name: xfile.name,
 | 
			
		||||
          ).detectFileType,
 | 
			
		||||
          room: room,
 | 
			
		||||
          room: room!,
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
@ -96,13 +96,13 @@ class ChatController extends State<Chat> {
 | 
			
		||||
 | 
			
		||||
  List<Event> selectedEvents = [];
 | 
			
		||||
 | 
			
		||||
  List<Event> filteredEvents;
 | 
			
		||||
  late List<Event> filteredEvents;
 | 
			
		||||
 | 
			
		||||
  final Set<String> unfolded = {};
 | 
			
		||||
 | 
			
		||||
  Event replyEvent;
 | 
			
		||||
  Event? replyEvent;
 | 
			
		||||
 | 
			
		||||
  Event editEvent;
 | 
			
		||||
  Event? editEvent;
 | 
			
		||||
 | 
			
		||||
  bool showScrollDownButton = false;
 | 
			
		||||
 | 
			
		||||
@ -115,8 +115,8 @@ class ChatController extends State<Chat> {
 | 
			
		||||
  String pendingText = '';
 | 
			
		||||
 | 
			
		||||
  bool get canLoadMore =>
 | 
			
		||||
      timeline.events.isEmpty ||
 | 
			
		||||
      timeline.events.last.type != EventTypes.RoomCreate;
 | 
			
		||||
      timeline!.events.isEmpty ||
 | 
			
		||||
      timeline!.events.last.type != EventTypes.RoomCreate;
 | 
			
		||||
 | 
			
		||||
  bool showEmojiPicker = false;
 | 
			
		||||
 | 
			
		||||
@ -126,7 +126,7 @@ class ChatController extends State<Chat> {
 | 
			
		||||
 | 
			
		||||
    final success = await showFutureLoadingDialog(
 | 
			
		||||
        context: context,
 | 
			
		||||
        future: () => room.sendEvent({
 | 
			
		||||
        future: () => room!.sendEvent({
 | 
			
		||||
              'msgtype': Matrix.callNamespace,
 | 
			
		||||
              'body': url,
 | 
			
		||||
            }));
 | 
			
		||||
@ -137,12 +137,12 @@ class ChatController extends State<Chat> {
 | 
			
		||||
  void requestHistory() async {
 | 
			
		||||
    if (canLoadMore) {
 | 
			
		||||
      try {
 | 
			
		||||
        await timeline.requestHistory(historyCount: _loadHistoryCount);
 | 
			
		||||
        await timeline!.requestHistory(historyCount: _loadHistoryCount);
 | 
			
		||||
      } catch (err) {
 | 
			
		||||
        ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
          SnackBar(
 | 
			
		||||
            content: Text(
 | 
			
		||||
              (err as Object).toLocalizedString(context),
 | 
			
		||||
              (err).toLocalizedString(context),
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        );
 | 
			
		||||
@ -157,8 +157,8 @@ class ChatController extends State<Chat> {
 | 
			
		||||
    }
 | 
			
		||||
    if (scrollController.position.pixels ==
 | 
			
		||||
            scrollController.position.maxScrollExtent &&
 | 
			
		||||
        timeline.events.isNotEmpty &&
 | 
			
		||||
        timeline.events[timeline.events.length - 1].type !=
 | 
			
		||||
        timeline!.events.isNotEmpty &&
 | 
			
		||||
        timeline!.events[timeline!.events.length - 1].type !=
 | 
			
		||||
            EventTypes.RoomCreate) {
 | 
			
		||||
      requestHistory();
 | 
			
		||||
    }
 | 
			
		||||
@ -180,7 +180,7 @@ class ChatController extends State<Chat> {
 | 
			
		||||
    if (!mounted) return;
 | 
			
		||||
    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);
 | 
			
		||||
        i++;
 | 
			
		||||
      }
 | 
			
		||||
      filteredEvents = timeline.getFilteredEvents(unfolded: unfolded);
 | 
			
		||||
      filteredEvents = timeline!.getFilteredEvents(unfolded: unfolded);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<bool> getTimeline() async {
 | 
			
		||||
    if (timeline == null) {
 | 
			
		||||
      timeline = await room.getTimeline(onUpdate: updateView);
 | 
			
		||||
      if (timeline.events.isNotEmpty) {
 | 
			
		||||
      timeline = await room!.getTimeline(onUpdate: updateView);
 | 
			
		||||
      if (timeline!.events.isNotEmpty) {
 | 
			
		||||
        // 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
 | 
			
		||||
      // and update the scroll controller...which will trigger a request history, if the
 | 
			
		||||
      // "load more" button is visible on the screen
 | 
			
		||||
      SchedulerBinding.instance.addPostFrameCallback((_) async {
 | 
			
		||||
      SchedulerBinding.instance!.addPostFrameCallback((_) async {
 | 
			
		||||
        if (mounted) {
 | 
			
		||||
          final event = VRouter.of(context).queryParameters['event'];
 | 
			
		||||
          if (event != null) {
 | 
			
		||||
@ -217,16 +217,15 @@ class ChatController extends State<Chat> {
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    filteredEvents = timeline.getFilteredEvents(unfolded: unfolded);
 | 
			
		||||
    timeline.requestKeys();
 | 
			
		||||
    if (room.notificationCount != null &&
 | 
			
		||||
        room.notificationCount > 0 &&
 | 
			
		||||
    filteredEvents = timeline!.getFilteredEvents(unfolded: unfolded);
 | 
			
		||||
    timeline!.requestKeys();
 | 
			
		||||
    if (room!.notificationCount > 0 &&
 | 
			
		||||
        timeline != null &&
 | 
			
		||||
        timeline.events.isNotEmpty &&
 | 
			
		||||
        timeline!.events.isNotEmpty &&
 | 
			
		||||
        Matrix.of(context).webHasFocus) {
 | 
			
		||||
      // ignore: unawaited_futures
 | 
			
		||||
      timeline.setReadMarker();
 | 
			
		||||
      room.client.updateIosBadge();
 | 
			
		||||
      timeline!.setReadMarker();
 | 
			
		||||
      room!.client.updateIosBadge();
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
@ -240,13 +239,13 @@ class ChatController extends State<Chat> {
 | 
			
		||||
 | 
			
		||||
  TextEditingController sendController = TextEditingController();
 | 
			
		||||
 | 
			
		||||
  void setSendingClient(Client c) {
 | 
			
		||||
  void setSendingClient(Client? c) {
 | 
			
		||||
    // first cancle typing with the old sending client
 | 
			
		||||
    if (currentlyTyping) {
 | 
			
		||||
      // no need to have the setting typing to false be blocking
 | 
			
		||||
      typingCoolDown?.cancel();
 | 
			
		||||
      typingCoolDown = null;
 | 
			
		||||
      room.setTyping(false);
 | 
			
		||||
      room!.setTyping(false);
 | 
			
		||||
      currentlyTyping = false;
 | 
			
		||||
    }
 | 
			
		||||
    // then set the new sending client
 | 
			
		||||
@ -263,22 +262,22 @@ class ChatController extends State<Chat> {
 | 
			
		||||
 | 
			
		||||
    final commandMatch = RegExp(r'^\/(\w+)').firstMatch(sendController.text);
 | 
			
		||||
    if (commandMatch != null &&
 | 
			
		||||
        !room.client.commands.keys.contains(commandMatch[1].toLowerCase())) {
 | 
			
		||||
      final l10n = L10n.of(context);
 | 
			
		||||
        !room!.client.commands.keys.contains(commandMatch[1]!.toLowerCase())) {
 | 
			
		||||
      final l10n = L10n.of(context)!;
 | 
			
		||||
      final dialogResult = await showOkCancelAlertDialog(
 | 
			
		||||
        context: context,
 | 
			
		||||
        useRootNavigator: false,
 | 
			
		||||
        title: l10n.commandInvalid,
 | 
			
		||||
        message: l10n.commandMissing(commandMatch[0]),
 | 
			
		||||
        message: l10n.commandMissing(commandMatch[0]!),
 | 
			
		||||
        okLabel: l10n.sendAsText,
 | 
			
		||||
        cancelLabel: l10n.cancel,
 | 
			
		||||
      );
 | 
			
		||||
      if (dialogResult == null || dialogResult == OkCancelResult.cancel) return;
 | 
			
		||||
      if (dialogResult == OkCancelResult.cancel) return;
 | 
			
		||||
      parseCommands = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ignore: unawaited_futures
 | 
			
		||||
    room.sendTextEvent(sendController.text,
 | 
			
		||||
    room!.sendTextEvent(sendController.text,
 | 
			
		||||
        inReplyTo: replyEvent,
 | 
			
		||||
        editEventId: editEvent?.eventId,
 | 
			
		||||
        parseCommands: parseCommands);
 | 
			
		||||
@ -298,16 +297,16 @@ class ChatController extends State<Chat> {
 | 
			
		||||
  void sendFileAction() async {
 | 
			
		||||
    final result =
 | 
			
		||||
        await FilePickerCross.importFromStorage(type: FileTypeCross.any);
 | 
			
		||||
    if (result == null) return;
 | 
			
		||||
    if (result.fileName == null) return;
 | 
			
		||||
    await showDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      useRootNavigator: false,
 | 
			
		||||
      builder: (c) => SendFileDialog(
 | 
			
		||||
        file: MatrixFile(
 | 
			
		||||
          bytes: result.toUint8List(),
 | 
			
		||||
          name: result.fileName,
 | 
			
		||||
          name: result.fileName!,
 | 
			
		||||
        ).detectFileType,
 | 
			
		||||
        room: room,
 | 
			
		||||
        room: room!,
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
@ -315,16 +314,16 @@ class ChatController extends State<Chat> {
 | 
			
		||||
  void sendImageAction() async {
 | 
			
		||||
    final result =
 | 
			
		||||
        await FilePickerCross.importFromStorage(type: FileTypeCross.image);
 | 
			
		||||
    if (result == null) return;
 | 
			
		||||
    if (result.fileName == null) return;
 | 
			
		||||
    await showDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      useRootNavigator: false,
 | 
			
		||||
      builder: (c) => SendFileDialog(
 | 
			
		||||
        file: MatrixImageFile(
 | 
			
		||||
          bytes: result.toUint8List(),
 | 
			
		||||
          name: result.fileName,
 | 
			
		||||
          name: result.fileName!,
 | 
			
		||||
        ),
 | 
			
		||||
        room: room,
 | 
			
		||||
        room: room!,
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
@ -343,7 +342,7 @@ class ChatController extends State<Chat> {
 | 
			
		||||
          bytes: bytes,
 | 
			
		||||
          name: file.path,
 | 
			
		||||
        ),
 | 
			
		||||
        room: room,
 | 
			
		||||
        room: room!,
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
@ -362,7 +361,7 @@ class ChatController extends State<Chat> {
 | 
			
		||||
          bytes: bytes,
 | 
			
		||||
          name: file.path,
 | 
			
		||||
        ),
 | 
			
		||||
        room: room,
 | 
			
		||||
        room: room!,
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
@ -371,7 +370,7 @@ class ChatController extends State<Chat> {
 | 
			
		||||
    final sticker = await showModalBottomSheet<ImagePackImageContent>(
 | 
			
		||||
      context: context,
 | 
			
		||||
      useRootNavigator: false,
 | 
			
		||||
      builder: (c) => StickerPickerDialog(room: room),
 | 
			
		||||
      builder: (c) => StickerPickerDialog(room: room!),
 | 
			
		||||
    );
 | 
			
		||||
    if (sticker == null) return;
 | 
			
		||||
    final eventContent = <String, dynamic>{
 | 
			
		||||
@ -382,7 +381,7 @@ class ChatController extends State<Chat> {
 | 
			
		||||
    // send the sticker
 | 
			
		||||
    await showFutureLoadingDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      future: () => room.sendEvent(
 | 
			
		||||
      future: () => room!.sendEvent(
 | 
			
		||||
        eventContent,
 | 
			
		||||
        type: EventTypes.Sticker,
 | 
			
		||||
      ),
 | 
			
		||||
@ -405,7 +404,7 @@ class ChatController extends State<Chat> {
 | 
			
		||||
    await showFutureLoadingDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      future: () =>
 | 
			
		||||
          room.sendFileEvent(file, inReplyTo: replyEvent, extraContent: {
 | 
			
		||||
          room!.sendFileEvent(file, inReplyTo: replyEvent, extraContent: {
 | 
			
		||||
        'info': {
 | 
			
		||||
          ...file.info,
 | 
			
		||||
          'duration': result.duration,
 | 
			
		||||
@ -426,7 +425,7 @@ class ChatController extends State<Chat> {
 | 
			
		||||
    await showDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      useRootNavigator: false,
 | 
			
		||||
      builder: (c) => SendLocationDialog(room: room),
 | 
			
		||||
      builder: (c) => SendLocationDialog(room: room!),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -434,13 +433,13 @@ class ChatController extends State<Chat> {
 | 
			
		||||
    var copyString = '';
 | 
			
		||||
    if (selectedEvents.length == 1) {
 | 
			
		||||
      return selectedEvents.first
 | 
			
		||||
          .getDisplayEvent(timeline)
 | 
			
		||||
          .getLocalizedBody(MatrixLocals(L10n.of(context)));
 | 
			
		||||
          .getDisplayEvent(timeline!)
 | 
			
		||||
          .getLocalizedBody(MatrixLocals(L10n.of(context)!));
 | 
			
		||||
    }
 | 
			
		||||
    for (final event in selectedEvents) {
 | 
			
		||||
      if (copyString.isNotEmpty) copyString += '\n\n';
 | 
			
		||||
      copyString += event.getDisplayEvent(timeline).getLocalizedBody(
 | 
			
		||||
          MatrixLocals(L10n.of(context)),
 | 
			
		||||
      copyString += event.getDisplayEvent(timeline!).getLocalizedBody(
 | 
			
		||||
          MatrixLocals(L10n.of(context)!),
 | 
			
		||||
          withSenderNamePrefix: true);
 | 
			
		||||
    }
 | 
			
		||||
    return copyString;
 | 
			
		||||
@ -458,37 +457,37 @@ class ChatController extends State<Chat> {
 | 
			
		||||
    final event = selectedEvents.single;
 | 
			
		||||
    final score = await showConfirmationDialog<int>(
 | 
			
		||||
        context: context,
 | 
			
		||||
        title: L10n.of(context).reportMessage,
 | 
			
		||||
        message: L10n.of(context).howOffensiveIsThisContent,
 | 
			
		||||
        cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
        okLabel: L10n.of(context).ok,
 | 
			
		||||
        title: L10n.of(context)!.reportMessage,
 | 
			
		||||
        message: L10n.of(context)!.howOffensiveIsThisContent,
 | 
			
		||||
        cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
        okLabel: L10n.of(context)!.ok,
 | 
			
		||||
        actions: [
 | 
			
		||||
          AlertDialogAction(
 | 
			
		||||
            key: -100,
 | 
			
		||||
            label: L10n.of(context).extremeOffensive,
 | 
			
		||||
            label: L10n.of(context)!.extremeOffensive,
 | 
			
		||||
          ),
 | 
			
		||||
          AlertDialogAction(
 | 
			
		||||
            key: -50,
 | 
			
		||||
            label: L10n.of(context).offensive,
 | 
			
		||||
            label: L10n.of(context)!.offensive,
 | 
			
		||||
          ),
 | 
			
		||||
          AlertDialogAction(
 | 
			
		||||
            key: 0,
 | 
			
		||||
            label: L10n.of(context).inoffensive,
 | 
			
		||||
            label: L10n.of(context)!.inoffensive,
 | 
			
		||||
          ),
 | 
			
		||||
        ]);
 | 
			
		||||
    if (score == null) return;
 | 
			
		||||
    final reason = await showTextInputDialog(
 | 
			
		||||
        useRootNavigator: false,
 | 
			
		||||
        context: context,
 | 
			
		||||
        title: L10n.of(context).whyDoYouWantToReportThis,
 | 
			
		||||
        okLabel: L10n.of(context).ok,
 | 
			
		||||
        cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
        textFields: [DialogTextField(hintText: L10n.of(context).reason)]);
 | 
			
		||||
        title: L10n.of(context)!.whyDoYouWantToReportThis,
 | 
			
		||||
        okLabel: L10n.of(context)!.ok,
 | 
			
		||||
        cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
        textFields: [DialogTextField(hintText: L10n.of(context)!.reason)]);
 | 
			
		||||
    if (reason == null || reason.single.isEmpty) return;
 | 
			
		||||
    final result = await showFutureLoadingDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      future: () => Matrix.of(context).client.reportContent(
 | 
			
		||||
            event.roomId,
 | 
			
		||||
            event.roomId!,
 | 
			
		||||
            event.eventId,
 | 
			
		||||
            reason: reason.single,
 | 
			
		||||
            score: score,
 | 
			
		||||
@ -500,16 +499,16 @@ class ChatController extends State<Chat> {
 | 
			
		||||
      selectedEvents.clear();
 | 
			
		||||
    });
 | 
			
		||||
    ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
        SnackBar(content: Text(L10n.of(context).contentHasBeenReported)));
 | 
			
		||||
        SnackBar(content: Text(L10n.of(context)!.contentHasBeenReported)));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void redactEventsAction() async {
 | 
			
		||||
    final confirmed = await showOkCancelAlertDialog(
 | 
			
		||||
          useRootNavigator: false,
 | 
			
		||||
          context: context,
 | 
			
		||||
          title: L10n.of(context).messageWillBeRemovedWarning,
 | 
			
		||||
          okLabel: L10n.of(context).remove,
 | 
			
		||||
          cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
          title: L10n.of(context)!.messageWillBeRemovedWarning,
 | 
			
		||||
          okLabel: L10n.of(context)!.remove,
 | 
			
		||||
          cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
        ) ==
 | 
			
		||||
        OkCancelResult.ok;
 | 
			
		||||
    if (!confirmed) return;
 | 
			
		||||
@ -522,12 +521,12 @@ class ChatController extends State<Chat> {
 | 
			
		||||
                await event.redactEvent();
 | 
			
		||||
              } else {
 | 
			
		||||
                final client = currentRoomBundle.firstWhere(
 | 
			
		||||
                    (cl) => selectedEvents.first.senderId == cl.userID,
 | 
			
		||||
                    (cl) => selectedEvents.first.senderId == cl!.userID,
 | 
			
		||||
                    orElse: () => null);
 | 
			
		||||
                if (client == null) {
 | 
			
		||||
                  return;
 | 
			
		||||
                }
 | 
			
		||||
                final room = client.getRoomById(roomId);
 | 
			
		||||
                final room = client.getRoomById(roomId!)!;
 | 
			
		||||
                await Event.fromJson(event.toJson(), room).redactEvent();
 | 
			
		||||
              }
 | 
			
		||||
            } else {
 | 
			
		||||
@ -541,17 +540,17 @@ class ChatController extends State<Chat> {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  List<Client> get currentRoomBundle {
 | 
			
		||||
    final clients = matrix.currentBundle;
 | 
			
		||||
    clients.removeWhere((c) => c.getRoomById(roomId) == null);
 | 
			
		||||
  List<Client?> get currentRoomBundle {
 | 
			
		||||
    final clients = matrix!.currentBundle!;
 | 
			
		||||
    clients.removeWhere((c) => c!.getRoomById(roomId!) == null);
 | 
			
		||||
    return clients;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool get canRedactSelectedEvents {
 | 
			
		||||
    final clients = matrix.currentBundle;
 | 
			
		||||
    final clients = matrix!.currentBundle;
 | 
			
		||||
    for (final event in selectedEvents) {
 | 
			
		||||
      if (event.canRedact == false &&
 | 
			
		||||
          !(clients.any((cl) => event.senderId == cl.userID))) return false;
 | 
			
		||||
          !(clients!.any((cl) => event.senderId == cl!.userID))) return false;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
@ -561,7 +560,7 @@ class ChatController extends State<Chat> {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    return currentRoomBundle
 | 
			
		||||
        .any((cl) => selectedEvents.first.senderId == cl.userID);
 | 
			
		||||
        .any((cl) => selectedEvents.first.senderId == cl!.userID);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void forwardEventsAction() async {
 | 
			
		||||
@ -583,7 +582,7 @@ class ChatController extends State<Chat> {
 | 
			
		||||
      event.sendAgain();
 | 
			
		||||
    }
 | 
			
		||||
    final allEditEvents = event
 | 
			
		||||
        .aggregatedEvents(timeline, RelationshipTypes.edit)
 | 
			
		||||
        .aggregatedEvents(timeline!, RelationshipTypes.edit)
 | 
			
		||||
        .where((e) => e.status.isError);
 | 
			
		||||
    for (final e in allEditEvents) {
 | 
			
		||||
      e.sendAgain();
 | 
			
		||||
@ -591,7 +590,7 @@ class ChatController extends State<Chat> {
 | 
			
		||||
    setState(() => selectedEvents.clear());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void replyAction({Event replyTo}) {
 | 
			
		||||
  void replyAction({Event? replyTo}) {
 | 
			
		||||
    setState(() {
 | 
			
		||||
      replyEvent = replyTo ?? selectedEvents.first;
 | 
			
		||||
      selectedEvents.clear();
 | 
			
		||||
@ -609,7 +608,7 @@ class ChatController extends State<Chat> {
 | 
			
		||||
          future: () async {
 | 
			
		||||
            // okay, we first have to fetch if the event is in the room
 | 
			
		||||
            try {
 | 
			
		||||
              final event = await timeline.getEventById(eventId);
 | 
			
		||||
              final event = await timeline!.getEventById(eventId);
 | 
			
		||||
              if (event == null) {
 | 
			
		||||
                // event is null...meaning something is off
 | 
			
		||||
                return;
 | 
			
		||||
@ -628,7 +627,7 @@ class ChatController extends State<Chat> {
 | 
			
		||||
                return;
 | 
			
		||||
              }
 | 
			
		||||
              try {
 | 
			
		||||
                await timeline.requestHistory(historyCount: _loadHistoryCount);
 | 
			
		||||
                await timeline!.requestHistory(historyCount: _loadHistoryCount);
 | 
			
		||||
              } catch (err) {
 | 
			
		||||
                if (err is TimeoutException) {
 | 
			
		||||
                  // loading the history timed out...so let's do nothing
 | 
			
		||||
@ -662,7 +661,7 @@ class ChatController extends State<Chat> {
 | 
			
		||||
    return sendEmojiAction(emoji.emoji);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Iterable<Event> _allReactionEvents;
 | 
			
		||||
  late Iterable<Event> _allReactionEvents;
 | 
			
		||||
 | 
			
		||||
  void cancelEmojiPicker() => setState(() => showEmojiPicker = false);
 | 
			
		||||
 | 
			
		||||
@ -671,13 +670,13 @@ class ChatController extends State<Chat> {
 | 
			
		||||
    setState(() => showEmojiPicker = true);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void sendEmojiAction(String emoji) async {
 | 
			
		||||
  void sendEmojiAction(String? emoji) async {
 | 
			
		||||
    final events = List<Event>.from(selectedEvents);
 | 
			
		||||
    setState(() => selectedEvents.clear());
 | 
			
		||||
    for (final event in events) {
 | 
			
		||||
      await room.sendReaction(
 | 
			
		||||
      await room!.sendReaction(
 | 
			
		||||
        event.eventId,
 | 
			
		||||
        emoji,
 | 
			
		||||
        emoji!,
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -695,7 +694,7 @@ class ChatController extends State<Chat> {
 | 
			
		||||
 | 
			
		||||
  void editSelectedEventAction() {
 | 
			
		||||
    final client = currentRoomBundle.firstWhere(
 | 
			
		||||
        (cl) => selectedEvents.first.senderId == cl.userID,
 | 
			
		||||
        (cl) => selectedEvents.first.senderId == cl!.userID,
 | 
			
		||||
        orElse: () => null);
 | 
			
		||||
    if (client == null) {
 | 
			
		||||
      return;
 | 
			
		||||
@ -704,9 +703,9 @@ class ChatController extends State<Chat> {
 | 
			
		||||
    setState(() {
 | 
			
		||||
      pendingText = sendController.text;
 | 
			
		||||
      editEvent = selectedEvents.first;
 | 
			
		||||
      inputText = sendController.text = editEvent
 | 
			
		||||
          .getDisplayEvent(timeline)
 | 
			
		||||
          .getLocalizedBody(MatrixLocals(L10n.of(context)),
 | 
			
		||||
      inputText = sendController.text = editEvent!
 | 
			
		||||
          .getDisplayEvent(timeline!)
 | 
			
		||||
          .getLocalizedBody(MatrixLocals(L10n.of(context)!),
 | 
			
		||||
              withSenderNamePrefix: false, hideReply: true);
 | 
			
		||||
      selectedEvents.clear();
 | 
			
		||||
    });
 | 
			
		||||
@ -718,29 +717,29 @@ class ChatController extends State<Chat> {
 | 
			
		||||
        await showOkCancelAlertDialog(
 | 
			
		||||
          useRootNavigator: false,
 | 
			
		||||
          context: context,
 | 
			
		||||
          title: L10n.of(context).goToTheNewRoom,
 | 
			
		||||
          message: room
 | 
			
		||||
              .getState(EventTypes.RoomTombstone)
 | 
			
		||||
          title: L10n.of(context)!.goToTheNewRoom,
 | 
			
		||||
          message: room!
 | 
			
		||||
              .getState(EventTypes.RoomTombstone)!
 | 
			
		||||
              .parsedTombstoneContent
 | 
			
		||||
              .body,
 | 
			
		||||
          okLabel: L10n.of(context).ok,
 | 
			
		||||
          cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
          okLabel: L10n.of(context)!.ok,
 | 
			
		||||
          cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
        )) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    final result = await showFutureLoadingDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      future: () => room.client.joinRoom(room
 | 
			
		||||
          .getState(EventTypes.RoomTombstone)
 | 
			
		||||
      future: () => room!.client.joinRoom(room!
 | 
			
		||||
          .getState(EventTypes.RoomTombstone)!
 | 
			
		||||
          .parsedTombstoneContent
 | 
			
		||||
          .replacementRoom),
 | 
			
		||||
    );
 | 
			
		||||
    await showFutureLoadingDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      future: room.leave,
 | 
			
		||||
      future: room!.leave,
 | 
			
		||||
    );
 | 
			
		||||
    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.
 | 
			
		||||
    if (key is! ValueKey) {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
    final eventId = (key as ValueKey).value;
 | 
			
		||||
    final eventId = key.value;
 | 
			
		||||
    if (eventId is! String) {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
@ -809,11 +808,11 @@ class ChatController extends State<Chat> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void onInputBarChanged(String text) {
 | 
			
		||||
    if (text.endsWith(' ') && matrix.hasComplexBundles) {
 | 
			
		||||
    if (text.endsWith(' ') && matrix!.hasComplexBundles) {
 | 
			
		||||
      final clients = currentRoomBundle;
 | 
			
		||||
      for (final client in clients) {
 | 
			
		||||
        final prefix = client.sendPrefix;
 | 
			
		||||
        if ((prefix?.isNotEmpty ?? false) &&
 | 
			
		||||
        final prefix = client!.sendPrefix;
 | 
			
		||||
        if ((prefix.isNotEmpty) &&
 | 
			
		||||
            text.toLowerCase() == '${prefix.toLowerCase()} ') {
 | 
			
		||||
          setSendingClient(client);
 | 
			
		||||
          setState(() {
 | 
			
		||||
@ -828,7 +827,7 @@ class ChatController extends State<Chat> {
 | 
			
		||||
    typingCoolDown = Timer(const Duration(seconds: 2), () {
 | 
			
		||||
      typingCoolDown = null;
 | 
			
		||||
      currentlyTyping = false;
 | 
			
		||||
      room.setTyping(false);
 | 
			
		||||
      room!.setTyping(false);
 | 
			
		||||
    });
 | 
			
		||||
    typingTimeout ??= Timer(const Duration(seconds: 30), () {
 | 
			
		||||
      typingTimeout = null;
 | 
			
		||||
@ -836,12 +835,13 @@ class ChatController extends State<Chat> {
 | 
			
		||||
    });
 | 
			
		||||
    if (!currentlyTyping) {
 | 
			
		||||
      currentlyTyping = true;
 | 
			
		||||
      room.setTyping(true, timeout: const Duration(seconds: 30).inMilliseconds);
 | 
			
		||||
      room!
 | 
			
		||||
          .setTyping(true, timeout: const Duration(seconds: 30).inMilliseconds);
 | 
			
		||||
    }
 | 
			
		||||
    setState(() => inputText = text);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void showEventInfo([Event event]) =>
 | 
			
		||||
  void showEventInfo([Event? event]) =>
 | 
			
		||||
      (event ?? selectedEvents.single).showInfoDialog(context);
 | 
			
		||||
 | 
			
		||||
  void cancelReplyEventAction() => setState(() {
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
@ -19,41 +17,42 @@ class ChatAppBarTitle extends StatelessWidget {
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final room = controller.room;
 | 
			
		||||
    if (room == null) {
 | 
			
		||||
      return Container();
 | 
			
		||||
    }
 | 
			
		||||
    if (controller.selectedEvents.isNotEmpty) {
 | 
			
		||||
      return Text(controller.selectedEvents.length.toString());
 | 
			
		||||
    }
 | 
			
		||||
    final directChatMatrixID = controller.room.directChatMatrixID;
 | 
			
		||||
    final directChatMatrixID = room.directChatMatrixID;
 | 
			
		||||
    return ListTile(
 | 
			
		||||
      leading: Avatar(
 | 
			
		||||
        mxContent: controller.room.avatar,
 | 
			
		||||
        name: controller.room.displayname,
 | 
			
		||||
        mxContent: room.avatar,
 | 
			
		||||
        name: room.displayname,
 | 
			
		||||
      ),
 | 
			
		||||
      contentPadding: EdgeInsets.zero,
 | 
			
		||||
      onTap: directChatMatrixID != null
 | 
			
		||||
          ? () => showModalBottomSheet(
 | 
			
		||||
                context: context,
 | 
			
		||||
                builder: (c) => UserBottomSheet(
 | 
			
		||||
                  user: controller.room.getUserByMXIDSync(directChatMatrixID),
 | 
			
		||||
                  user: room.getUserByMXIDSync(directChatMatrixID),
 | 
			
		||||
                  outerContext: context,
 | 
			
		||||
                  onMention: () => controller.sendController.text +=
 | 
			
		||||
                      '${controller.room.getUserByMXIDSync(directChatMatrixID).mention} ',
 | 
			
		||||
                      '${room.getUserByMXIDSync(directChatMatrixID).mention} ',
 | 
			
		||||
                ),
 | 
			
		||||
              )
 | 
			
		||||
          : () => VRouter.of(context)
 | 
			
		||||
              .toSegments(['rooms', controller.room.id, 'details']),
 | 
			
		||||
      title: Text(
 | 
			
		||||
          controller.room
 | 
			
		||||
              .getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)),
 | 
			
		||||
          : () => VRouter.of(context).toSegments(['rooms', room.id, 'details']),
 | 
			
		||||
      title: Text(room.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)),
 | 
			
		||||
          maxLines: 1),
 | 
			
		||||
      subtitle: StreamBuilder<Object>(
 | 
			
		||||
        stream: Matrix.of(context)
 | 
			
		||||
            .client
 | 
			
		||||
            .onPresence
 | 
			
		||||
            .stream
 | 
			
		||||
            .where((p) => p.senderId == controller.room.directChatMatrixID)
 | 
			
		||||
            .where((p) => p.senderId == room.directChatMatrixID)
 | 
			
		||||
            .rateLimit(const Duration(seconds: 1)),
 | 
			
		||||
        builder: (context, snapshot) => Text(
 | 
			
		||||
          controller.room.getLocalizedStatus(context),
 | 
			
		||||
          room.getLocalizedStatus(context),
 | 
			
		||||
          maxLines: 1,
 | 
			
		||||
          //overflow: TextOverflow.ellipsis,
 | 
			
		||||
        ),
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,7 @@ import 'input_bar.dart';
 | 
			
		||||
 | 
			
		||||
class ChatInputRow extends StatelessWidget {
 | 
			
		||||
  final ChatController controller;
 | 
			
		||||
  const ChatInputRow(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const ChatInputRow(this.controller, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
@ -30,14 +30,14 @@ class ChatInputRow extends StatelessWidget {
 | 
			
		||||
                  child: Row(
 | 
			
		||||
                    children: <Widget>[
 | 
			
		||||
                      const Icon(Icons.keyboard_arrow_left_outlined),
 | 
			
		||||
                      Text(L10n.of(context).forward),
 | 
			
		||||
                      Text(L10n.of(context)!.forward),
 | 
			
		||||
                    ],
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
              controller.selectedEvents.length == 1
 | 
			
		||||
                  ? controller.selectedEvents.first
 | 
			
		||||
                          .getDisplayEvent(controller.timeline)
 | 
			
		||||
                          .getDisplayEvent(controller.timeline!)
 | 
			
		||||
                          .status
 | 
			
		||||
                          .isSent
 | 
			
		||||
                      ? SizedBox(
 | 
			
		||||
@ -46,7 +46,7 @@ class ChatInputRow extends StatelessWidget {
 | 
			
		||||
                            onPressed: controller.replyAction,
 | 
			
		||||
                            child: Row(
 | 
			
		||||
                              children: <Widget>[
 | 
			
		||||
                                Text(L10n.of(context).reply),
 | 
			
		||||
                                Text(L10n.of(context)!.reply),
 | 
			
		||||
                                const Icon(Icons.keyboard_arrow_right),
 | 
			
		||||
                              ],
 | 
			
		||||
                            ),
 | 
			
		||||
@ -58,7 +58,7 @@ class ChatInputRow extends StatelessWidget {
 | 
			
		||||
                            onPressed: controller.sendAgainAction,
 | 
			
		||||
                            child: Row(
 | 
			
		||||
                              children: <Widget>[
 | 
			
		||||
                                Text(L10n.of(context).tryToSendAgain),
 | 
			
		||||
                                Text(L10n.of(context)!.tryToSendAgain),
 | 
			
		||||
                                const SizedBox(width: 4),
 | 
			
		||||
                                const Icon(Icons.send_outlined, size: 16),
 | 
			
		||||
                              ],
 | 
			
		||||
@ -88,7 +88,7 @@ class ChatInputRow extends StatelessWidget {
 | 
			
		||||
                          foregroundColor: Colors.white,
 | 
			
		||||
                          child: Icon(Icons.video_call_outlined),
 | 
			
		||||
                        ),
 | 
			
		||||
                        title: Text(L10n.of(context).videoCall),
 | 
			
		||||
                        title: Text(L10n.of(context)!.videoCall),
 | 
			
		||||
                        contentPadding: const EdgeInsets.all(0),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
@ -100,7 +100,7 @@ class ChatInputRow extends StatelessWidget {
 | 
			
		||||
                          foregroundColor: Colors.white,
 | 
			
		||||
                          child: Icon(Icons.attachment_outlined),
 | 
			
		||||
                        ),
 | 
			
		||||
                        title: Text(L10n.of(context).sendFile),
 | 
			
		||||
                        title: Text(L10n.of(context)!.sendFile),
 | 
			
		||||
                        contentPadding: const EdgeInsets.all(0),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
@ -112,7 +112,7 @@ class ChatInputRow extends StatelessWidget {
 | 
			
		||||
                          foregroundColor: Colors.white,
 | 
			
		||||
                          child: Icon(Icons.image_outlined),
 | 
			
		||||
                        ),
 | 
			
		||||
                        title: Text(L10n.of(context).sendImage),
 | 
			
		||||
                        title: Text(L10n.of(context)!.sendImage),
 | 
			
		||||
                        contentPadding: const EdgeInsets.all(0),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
@ -125,7 +125,7 @@ class ChatInputRow extends StatelessWidget {
 | 
			
		||||
                            foregroundColor: Colors.white,
 | 
			
		||||
                            child: Icon(Icons.camera_alt_outlined),
 | 
			
		||||
                          ),
 | 
			
		||||
                          title: Text(L10n.of(context).openCamera),
 | 
			
		||||
                          title: Text(L10n.of(context)!.openCamera),
 | 
			
		||||
                          contentPadding: const EdgeInsets.all(0),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
@ -138,11 +138,11 @@ class ChatInputRow extends StatelessWidget {
 | 
			
		||||
                            foregroundColor: Colors.white,
 | 
			
		||||
                            child: Icon(Icons.videocam_outlined),
 | 
			
		||||
                          ),
 | 
			
		||||
                          title: Text(L10n.of(context).openVideoCamera),
 | 
			
		||||
                          title: Text(L10n.of(context)!.openVideoCamera),
 | 
			
		||||
                          contentPadding: const EdgeInsets.all(0),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
                    if (controller.room
 | 
			
		||||
                    if (controller.room!
 | 
			
		||||
                        .getImagePacks(ImagePackUsage.sticker)
 | 
			
		||||
                        .isNotEmpty)
 | 
			
		||||
                      PopupMenuItem<String>(
 | 
			
		||||
@ -153,7 +153,7 @@ class ChatInputRow extends StatelessWidget {
 | 
			
		||||
                            foregroundColor: Colors.white,
 | 
			
		||||
                            child: Icon(Icons.emoji_emotions_outlined),
 | 
			
		||||
                          ),
 | 
			
		||||
                          title: Text(L10n.of(context).sendSticker),
 | 
			
		||||
                          title: Text(L10n.of(context)!.sendSticker),
 | 
			
		||||
                          contentPadding: const EdgeInsets.all(0),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
@ -166,7 +166,7 @@ class ChatInputRow extends StatelessWidget {
 | 
			
		||||
                            foregroundColor: Colors.white,
 | 
			
		||||
                            child: Icon(Icons.gps_fixed_outlined),
 | 
			
		||||
                          ),
 | 
			
		||||
                          title: Text(L10n.of(context).shareLocation),
 | 
			
		||||
                          title: Text(L10n.of(context)!.shareLocation),
 | 
			
		||||
                          contentPadding: const EdgeInsets.all(0),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
@ -176,11 +176,11 @@ class ChatInputRow extends StatelessWidget {
 | 
			
		||||
              Container(
 | 
			
		||||
                height: 56,
 | 
			
		||||
                alignment: Alignment.center,
 | 
			
		||||
                child: EncryptionButton(controller.room),
 | 
			
		||||
                child: EncryptionButton(controller.room!),
 | 
			
		||||
              ),
 | 
			
		||||
              if (controller.matrix.isMultiAccount &&
 | 
			
		||||
                  controller.matrix.hasComplexBundles &&
 | 
			
		||||
                  controller.matrix.currentBundle.length > 1)
 | 
			
		||||
              if (controller.matrix!.isMultiAccount &&
 | 
			
		||||
                  controller.matrix!.hasComplexBundles &&
 | 
			
		||||
                  controller.matrix!.currentBundle!.length > 1)
 | 
			
		||||
                Container(
 | 
			
		||||
                  height: 56,
 | 
			
		||||
                  alignment: Alignment.center,
 | 
			
		||||
@ -190,7 +190,7 @@ class ChatInputRow extends StatelessWidget {
 | 
			
		||||
                child: Padding(
 | 
			
		||||
                  padding: const EdgeInsets.symmetric(vertical: 4.0),
 | 
			
		||||
                  child: InputBar(
 | 
			
		||||
                    room: controller.room,
 | 
			
		||||
                    room: controller.room!,
 | 
			
		||||
                    minLines: 1,
 | 
			
		||||
                    maxLines: 8,
 | 
			
		||||
                    autofocus: !PlatformInfos.isMobile,
 | 
			
		||||
@ -201,7 +201,7 @@ class ChatInputRow extends StatelessWidget {
 | 
			
		||||
                    focusNode: controller.inputFocus,
 | 
			
		||||
                    controller: controller.sendController,
 | 
			
		||||
                    decoration: InputDecoration(
 | 
			
		||||
                      hintText: L10n.of(context).writeAMessage,
 | 
			
		||||
                      hintText: L10n.of(context)!.writeAMessage,
 | 
			
		||||
                      hintMaxLines: 1,
 | 
			
		||||
                      border: InputBorder.none,
 | 
			
		||||
                      enabledBorder: InputBorder.none,
 | 
			
		||||
@ -216,7 +216,7 @@ class ChatInputRow extends StatelessWidget {
 | 
			
		||||
                  height: 56,
 | 
			
		||||
                  alignment: Alignment.center,
 | 
			
		||||
                  child: IconButton(
 | 
			
		||||
                    tooltip: L10n.of(context).voiceMessage,
 | 
			
		||||
                    tooltip: L10n.of(context)!.voiceMessage,
 | 
			
		||||
                    icon: const Icon(Icons.mic_none_outlined),
 | 
			
		||||
                    onPressed: controller.voiceMessageAction,
 | 
			
		||||
                  ),
 | 
			
		||||
@ -228,7 +228,7 @@ class ChatInputRow extends StatelessWidget {
 | 
			
		||||
                  child: IconButton(
 | 
			
		||||
                    icon: const Icon(Icons.send_outlined),
 | 
			
		||||
                    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 {
 | 
			
		||||
  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) {
 | 
			
		||||
    final client = controller.matrix.currentBundle
 | 
			
		||||
        .firstWhere((cl) => cl.userID == mxid, orElse: () => null);
 | 
			
		||||
    final client = controller.matrix!.currentBundle!
 | 
			
		||||
        .firstWhere((cl) => cl!.userID == mxid, orElse: () => null);
 | 
			
		||||
    if (client == null) {
 | 
			
		||||
      Logs().w('Attempted to switch to a non-existing client $mxid');
 | 
			
		||||
      return;
 | 
			
		||||
@ -258,23 +258,23 @@ class _ChatAccountPicker extends StatelessWidget {
 | 
			
		||||
    return Padding(
 | 
			
		||||
      padding: const EdgeInsets.all(8.0),
 | 
			
		||||
      child: FutureBuilder<Profile>(
 | 
			
		||||
        future: controller.sendingClient.ownProfile,
 | 
			
		||||
        future: controller.sendingClient!.ownProfile,
 | 
			
		||||
        builder: (context, snapshot) => PopupMenuButton<String>(
 | 
			
		||||
          onSelected: _popupMenuButtonSelected,
 | 
			
		||||
          itemBuilder: (BuildContext context) => clients
 | 
			
		||||
              .map((client) => PopupMenuItem<String>(
 | 
			
		||||
                    value: client.userID,
 | 
			
		||||
                    value: client!.userID,
 | 
			
		||||
                    child: FutureBuilder<Profile>(
 | 
			
		||||
                      future: client.ownProfile,
 | 
			
		||||
                      builder: (context, snapshot) => ListTile(
 | 
			
		||||
                        leading: Avatar(
 | 
			
		||||
                          mxContent: snapshot.data?.avatarUrl,
 | 
			
		||||
                          name: snapshot.data?.displayName ??
 | 
			
		||||
                              client.userID.localpart,
 | 
			
		||||
                              client.userID!.localpart,
 | 
			
		||||
                          size: 20,
 | 
			
		||||
                        ),
 | 
			
		||||
                        title:
 | 
			
		||||
                            Text(snapshot.data?.displayName ?? client.userID),
 | 
			
		||||
                            Text(snapshot.data?.displayName ?? client.userID!),
 | 
			
		||||
                        contentPadding: const EdgeInsets.all(0),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
@ -283,7 +283,7 @@ class _ChatAccountPicker extends StatelessWidget {
 | 
			
		||||
          child: Avatar(
 | 
			
		||||
            mxContent: snapshot.data?.avatarUrl,
 | 
			
		||||
            name: snapshot.data?.displayName ??
 | 
			
		||||
                controller.matrix.client.userID.localpart,
 | 
			
		||||
                controller.matrix!.client.userID!.localpart,
 | 
			
		||||
            size: 20,
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
 | 
			
		||||
@ -34,31 +34,31 @@ enum _EventContextAction { info, report }
 | 
			
		||||
class ChatView extends StatelessWidget {
 | 
			
		||||
  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
 | 
			
		||||
      ? [
 | 
			
		||||
          if (controller.canEditSelectedEvents)
 | 
			
		||||
            IconButton(
 | 
			
		||||
              icon: const Icon(Icons.edit_outlined),
 | 
			
		||||
              tooltip: L10n.of(context).edit,
 | 
			
		||||
              tooltip: L10n.of(context)!.edit,
 | 
			
		||||
              onPressed: controller.editSelectedEventAction,
 | 
			
		||||
            ),
 | 
			
		||||
          IconButton(
 | 
			
		||||
            icon: const Icon(Icons.copy_outlined),
 | 
			
		||||
            tooltip: L10n.of(context).copy,
 | 
			
		||||
            tooltip: L10n.of(context)!.copy,
 | 
			
		||||
            onPressed: controller.copyEventsAction,
 | 
			
		||||
          ),
 | 
			
		||||
          if (controller.canSaveSelectedEvent)
 | 
			
		||||
            IconButton(
 | 
			
		||||
              icon: Icon(Icons.adaptive.share),
 | 
			
		||||
              tooltip: L10n.of(context).share,
 | 
			
		||||
              tooltip: L10n.of(context)!.share,
 | 
			
		||||
              onPressed: controller.saveSelectedEvent,
 | 
			
		||||
            ),
 | 
			
		||||
          if (controller.canRedactSelectedEvents)
 | 
			
		||||
            IconButton(
 | 
			
		||||
              icon: const Icon(Icons.delete_outlined),
 | 
			
		||||
              tooltip: L10n.of(context).redactMessage,
 | 
			
		||||
              tooltip: L10n.of(context)!.redactMessage,
 | 
			
		||||
              onPressed: controller.redactEventsAction,
 | 
			
		||||
            ),
 | 
			
		||||
          if (controller.selectedEvents.length == 1)
 | 
			
		||||
@ -82,7 +82,7 @@ class ChatView extends StatelessWidget {
 | 
			
		||||
                    children: [
 | 
			
		||||
                      const Icon(Icons.info_outlined),
 | 
			
		||||
                      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,
 | 
			
		||||
                      ),
 | 
			
		||||
                      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
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    controller.matrix ??= Matrix.of(context);
 | 
			
		||||
    final client = controller.matrix.client;
 | 
			
		||||
    final client = controller.matrix!.client;
 | 
			
		||||
    controller.sendingClient ??= client;
 | 
			
		||||
    controller.room = controller.sendingClient.getRoomById(controller.roomId);
 | 
			
		||||
    controller.room = controller.sendingClient!.getRoomById(controller.roomId!);
 | 
			
		||||
    if (controller.room == null) {
 | 
			
		||||
      return Scaffold(
 | 
			
		||||
        appBar: AppBar(
 | 
			
		||||
          title: Text(L10n.of(context).oopsSomethingWentWrong),
 | 
			
		||||
          title: Text(L10n.of(context)!.oopsSomethingWentWrong),
 | 
			
		||||
        ),
 | 
			
		||||
        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(
 | 
			
		||||
          context: context, future: () => controller.room.join());
 | 
			
		||||
          context: context, future: () => controller.room!.join());
 | 
			
		||||
    }
 | 
			
		||||
    final bottomSheetPadding = FluffyThemes.isColumnMode(context) ? 16.0 : 8.0;
 | 
			
		||||
    final horizontalPadding = FluffyThemes.isColumnMode(context) ? 8.0 : 0.0;
 | 
			
		||||
@ -139,7 +140,7 @@ class ChatView extends StatelessWidget {
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      child: StreamBuilder(
 | 
			
		||||
        stream: controller.room.onUpdate.stream
 | 
			
		||||
        stream: controller.room!.onUpdate.stream
 | 
			
		||||
            .rateLimit(const Duration(milliseconds: 250)),
 | 
			
		||||
        builder: (context, snapshot) => Scaffold(
 | 
			
		||||
          appBar: AppBar(
 | 
			
		||||
@ -152,10 +153,10 @@ class ChatView extends StatelessWidget {
 | 
			
		||||
                ? IconButton(
 | 
			
		||||
                    icon: const Icon(Icons.close),
 | 
			
		||||
                    onPressed: controller.clearSelectedEvents,
 | 
			
		||||
                    tooltip: L10n.of(context).close,
 | 
			
		||||
                    tooltip: L10n.of(context)!.close,
 | 
			
		||||
                    color: Theme.of(context).colorScheme.primary,
 | 
			
		||||
                  )
 | 
			
		||||
                : UnreadBadgeBackButton(roomId: controller.roomId),
 | 
			
		||||
                : UnreadBadgeBackButton(roomId: controller.roomId!),
 | 
			
		||||
            titleSpacing: 0,
 | 
			
		||||
            title: ChatAppBarTitle(controller),
 | 
			
		||||
            actions: _appBarActions(context),
 | 
			
		||||
@ -167,7 +168,7 @@ class ChatView extends StatelessWidget {
 | 
			
		||||
                  child: FloatingActionButton(
 | 
			
		||||
                    onPressed: controller.scrollDown,
 | 
			
		||||
                    foregroundColor:
 | 
			
		||||
                        Theme.of(context).textTheme.bodyText2.color,
 | 
			
		||||
                        Theme.of(context).textTheme.bodyText2!.color,
 | 
			
		||||
                    backgroundColor: Theme.of(context).scaffoldBackgroundColor,
 | 
			
		||||
                    mini: true,
 | 
			
		||||
                    child: Icon(Icons.arrow_downward_outlined,
 | 
			
		||||
@ -184,7 +185,7 @@ class ChatView extends StatelessWidget {
 | 
			
		||||
              children: <Widget>[
 | 
			
		||||
                if (Matrix.of(context).wallpaper != null)
 | 
			
		||||
                  Image.file(
 | 
			
		||||
                    Matrix.of(context).wallpaper,
 | 
			
		||||
                    Matrix.of(context).wallpaper!,
 | 
			
		||||
                    width: double.infinity,
 | 
			
		||||
                    height: double.infinity,
 | 
			
		||||
                    fit: BoxFit.cover,
 | 
			
		||||
@ -240,7 +241,7 @@ class ChatView extends StatelessWidget {
 | 
			
		||||
                                              controller.filteredEvents.length +
 | 
			
		||||
                                                  1
 | 
			
		||||
                                          ? controller
 | 
			
		||||
                                                  .timeline.isRequestingHistory
 | 
			
		||||
                                                  .timeline!.isRequestingHistory
 | 
			
		||||
                                              ? const Center(
 | 
			
		||||
                                                  child:
 | 
			
		||||
                                                      CircularProgressIndicator
 | 
			
		||||
@ -259,7 +260,7 @@ class ChatView extends StatelessWidget {
 | 
			
		||||
                                                        onPressed: controller
 | 
			
		||||
                                                            .requestHistory,
 | 
			
		||||
                                                        child: Text(
 | 
			
		||||
                                                            L10n.of(context)
 | 
			
		||||
                                                            L10n.of(context)!
 | 
			
		||||
                                                                .loadMore),
 | 
			
		||||
                                                      ),
 | 
			
		||||
                                                    )
 | 
			
		||||
@ -337,7 +338,7 @@ class ChatView extends StatelessWidget {
 | 
			
		||||
                                                        selected: controller
 | 
			
		||||
                                                            .selectedEvents
 | 
			
		||||
                                                            .any((e) => e.eventId == controller.filteredEvents[i - 1].eventId),
 | 
			
		||||
                                                        timeline: controller.timeline,
 | 
			
		||||
                                                        timeline: controller.timeline!,
 | 
			
		||||
                                                        nextEvent: i < controller.filteredEvents.length ? controller.filteredEvents[i] : null),
 | 
			
		||||
                                                  ),
 | 
			
		||||
                                                );
 | 
			
		||||
@ -352,8 +353,8 @@ class ChatView extends StatelessWidget {
 | 
			
		||||
                              },
 | 
			
		||||
                            )),
 | 
			
		||||
                      ),
 | 
			
		||||
                      if (controller.room.canSendDefaultMessages &&
 | 
			
		||||
                          controller.room.membership == Membership.join)
 | 
			
		||||
                      if (controller.room!.canSendDefaultMessages &&
 | 
			
		||||
                          controller.room!.membership == Membership.join)
 | 
			
		||||
                        Container(
 | 
			
		||||
                          margin: EdgeInsets.only(
 | 
			
		||||
                            bottom: bottomSheetPadding,
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,3 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
// This file is auto-generated using scripts/generate_command_hints_glue.sh.
 | 
			
		||||
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
 | 
			
		||||
@ -12,13 +12,13 @@ import '../../widgets/matrix.dart';
 | 
			
		||||
 | 
			
		||||
class EncryptionButton extends StatefulWidget {
 | 
			
		||||
  final Room room;
 | 
			
		||||
  const EncryptionButton(this.room, {Key key}) : super(key: key);
 | 
			
		||||
  const EncryptionButton(this.room, {Key? key}) : super(key: key);
 | 
			
		||||
  @override
 | 
			
		||||
  _EncryptionButtonState createState() => _EncryptionButtonState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _EncryptionButtonState extends State<EncryptionButton> {
 | 
			
		||||
  StreamSubscription _onSyncSub;
 | 
			
		||||
  StreamSubscription? _onSyncSub;
 | 
			
		||||
 | 
			
		||||
  void _enableEncryptionAction() async {
 | 
			
		||||
    if (widget.room.encrypted) {
 | 
			
		||||
@ -29,20 +29,20 @@ class _EncryptionButtonState extends State<EncryptionButton> {
 | 
			
		||||
      await showOkAlertDialog(
 | 
			
		||||
        useRootNavigator: false,
 | 
			
		||||
        context: context,
 | 
			
		||||
        okLabel: L10n.of(context).ok,
 | 
			
		||||
        message: L10n.of(context).noEncryptionForPublicRooms,
 | 
			
		||||
        okLabel: L10n.of(context)!.ok,
 | 
			
		||||
        message: L10n.of(context)!.noEncryptionForPublicRooms,
 | 
			
		||||
      );
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (await showOkCancelAlertDialog(
 | 
			
		||||
          useRootNavigator: false,
 | 
			
		||||
          context: context,
 | 
			
		||||
          title: L10n.of(context).enableEncryption,
 | 
			
		||||
          title: L10n.of(context)!.enableEncryption,
 | 
			
		||||
          message: widget.room.client.encryptionEnabled
 | 
			
		||||
              ? L10n.of(context).enableEncryptionWarning
 | 
			
		||||
              : L10n.of(context).needPantalaimonWarning,
 | 
			
		||||
          okLabel: L10n.of(context).yes,
 | 
			
		||||
          cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
              ? L10n.of(context)!.enableEncryptionWarning
 | 
			
		||||
              : L10n.of(context)!.needPantalaimonWarning,
 | 
			
		||||
          okLabel: L10n.of(context)!.yes,
 | 
			
		||||
          cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
        ) ==
 | 
			
		||||
        OkCancelResult.ok) {
 | 
			
		||||
      await showFutureLoadingDialog(
 | 
			
		||||
@ -50,7 +50,7 @@ class _EncryptionButtonState extends State<EncryptionButton> {
 | 
			
		||||
        future: () => widget.room.enableEncryption(),
 | 
			
		||||
      );
 | 
			
		||||
      // we want to enable the lock icon
 | 
			
		||||
      setState(() => null);
 | 
			
		||||
      setState(() {});
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -68,22 +68,22 @@ class _EncryptionButtonState extends State<EncryptionButton> {
 | 
			
		||||
          .onSync
 | 
			
		||||
          .stream
 | 
			
		||||
          .where((s) => s.deviceLists != null)
 | 
			
		||||
          .listen((s) => setState(() => null));
 | 
			
		||||
          .listen((s) => setState(() {}));
 | 
			
		||||
    }
 | 
			
		||||
    return FutureBuilder<List<User>>(
 | 
			
		||||
        future:
 | 
			
		||||
            widget.room.encrypted ? widget.room.requestParticipants() : null,
 | 
			
		||||
        builder: (BuildContext context, snapshot) {
 | 
			
		||||
          Color color;
 | 
			
		||||
          Color? color;
 | 
			
		||||
          if (widget.room.encrypted && snapshot.hasData) {
 | 
			
		||||
            final users = snapshot.data;
 | 
			
		||||
            final users = snapshot.data!;
 | 
			
		||||
            users.removeWhere((u) =>
 | 
			
		||||
                !{Membership.invite, Membership.join}.contains(u.membership) ||
 | 
			
		||||
                !widget.room.client.userDeviceKeys.containsKey(u.id));
 | 
			
		||||
            var allUsersValid = true;
 | 
			
		||||
            var oneUserInvalid = false;
 | 
			
		||||
            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) {
 | 
			
		||||
                allUsersValid = false;
 | 
			
		||||
              }
 | 
			
		||||
@ -99,8 +99,8 @@ class _EncryptionButtonState extends State<EncryptionButton> {
 | 
			
		||||
          }
 | 
			
		||||
          return IconButton(
 | 
			
		||||
            tooltip: widget.room.encrypted
 | 
			
		||||
                ? L10n.of(context).encrypted
 | 
			
		||||
                : L10n.of(context).encryptionNotEnabled,
 | 
			
		||||
                ? L10n.of(context)!.encrypted
 | 
			
		||||
                : L10n.of(context)!.encryptionNotEnabled,
 | 
			
		||||
            icon: Icon(
 | 
			
		||||
                widget.room.encrypted
 | 
			
		||||
                    ? Icons.lock_outlined
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,7 @@ extension EventInfoDialogExtension on Event {
 | 
			
		||||
  void showInfoDialog(BuildContext context) => showModalBottomSheet(
 | 
			
		||||
        context: 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 L10n l10n;
 | 
			
		||||
  const EventInfoDialog({
 | 
			
		||||
    @required this.event,
 | 
			
		||||
    @required this.l10n,
 | 
			
		||||
    Key key,
 | 
			
		||||
    required this.event,
 | 
			
		||||
    required this.l10n,
 | 
			
		||||
    Key? key,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  String get prettyJson {
 | 
			
		||||
@ -37,11 +37,11 @@ class EventInfoDialog extends StatelessWidget {
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        title: Text(L10n.of(context).messageInfo),
 | 
			
		||||
        title: Text(L10n.of(context)!.messageInfo),
 | 
			
		||||
        leading: IconButton(
 | 
			
		||||
          icon: const Icon(Icons.arrow_downward_outlined),
 | 
			
		||||
          onPressed: Navigator.of(context, rootNavigator: false).pop,
 | 
			
		||||
          tooltip: L10n.of(context).close,
 | 
			
		||||
          tooltip: L10n.of(context)!.close,
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
      body: ListView(
 | 
			
		||||
@ -51,19 +51,19 @@ class EventInfoDialog extends StatelessWidget {
 | 
			
		||||
              mxContent: event.sender.avatarUrl,
 | 
			
		||||
              name: event.sender.calcDisplayname(),
 | 
			
		||||
            ),
 | 
			
		||||
            title: Text(L10n.of(context).sender),
 | 
			
		||||
            title: Text(L10n.of(context)!.sender),
 | 
			
		||||
            subtitle:
 | 
			
		||||
                Text('${event.sender.calcDisplayname()} [${event.senderId}]'),
 | 
			
		||||
          ),
 | 
			
		||||
          ListTile(
 | 
			
		||||
            title: Text(L10n.of(context).time),
 | 
			
		||||
            title: Text(L10n.of(context)!.time),
 | 
			
		||||
            subtitle: Text(event.originServerTs.localizedTime(context)),
 | 
			
		||||
          ),
 | 
			
		||||
          ListTile(
 | 
			
		||||
            title: Text(L10n.of(context).messageType),
 | 
			
		||||
            title: Text(L10n.of(context)!.messageType),
 | 
			
		||||
            subtitle: Text(event.humanreadableType),
 | 
			
		||||
          ),
 | 
			
		||||
          ListTile(title: Text('${L10n.of(context).sourceCode}:')),
 | 
			
		||||
          ListTile(title: Text('${L10n.of(context)!.sourceCode}:')),
 | 
			
		||||
          Padding(
 | 
			
		||||
            padding: const EdgeInsets.all(12.0),
 | 
			
		||||
            child: Material(
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'dart:async';
 | 
			
		||||
import 'dart:io';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -13,17 +13,17 @@ import '../../../utils/url_launcher.dart';
 | 
			
		||||
 | 
			
		||||
class HtmlMessage extends StatelessWidget {
 | 
			
		||||
  final String html;
 | 
			
		||||
  final int maxLines;
 | 
			
		||||
  final int? maxLines;
 | 
			
		||||
  final Room room;
 | 
			
		||||
  final TextStyle defaultTextStyle;
 | 
			
		||||
  final TextStyle linkStyle;
 | 
			
		||||
  final double emoteSize;
 | 
			
		||||
  final TextStyle? defaultTextStyle;
 | 
			
		||||
  final TextStyle? linkStyle;
 | 
			
		||||
  final double? emoteSize;
 | 
			
		||||
 | 
			
		||||
  const HtmlMessage({
 | 
			
		||||
    Key key,
 | 
			
		||||
    this.html,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.html,
 | 
			
		||||
    this.maxLines,
 | 
			
		||||
    this.room,
 | 
			
		||||
    required this.room,
 | 
			
		||||
    this.defaultTextStyle,
 | 
			
		||||
    this.linkStyle,
 | 
			
		||||
    this.emoteSize,
 | 
			
		||||
@ -52,7 +52,7 @@ class HtmlMessage extends StatelessWidget {
 | 
			
		||||
      defaultTextStyle: defaultTextStyle,
 | 
			
		||||
      emoteSize: emoteSize,
 | 
			
		||||
      linkStyle: linkStyle ??
 | 
			
		||||
          themeData.textTheme.bodyText2.copyWith(
 | 
			
		||||
          themeData.textTheme.bodyText2!.copyWith(
 | 
			
		||||
            color: themeData.colorScheme.secondary,
 | 
			
		||||
            decoration: TextDecoration.underline,
 | 
			
		||||
          ),
 | 
			
		||||
@ -60,11 +60,11 @@ class HtmlMessage extends StatelessWidget {
 | 
			
		||||
      maxLines: maxLines,
 | 
			
		||||
      onLinkTap: (url) => UrlLauncher(context, url).launchUrl(),
 | 
			
		||||
      onPillTap: (url) => UrlLauncher(context, url).launchUrl(),
 | 
			
		||||
      getMxcUrl: (String mxc, double width, double height,
 | 
			
		||||
          {bool animated = false}) {
 | 
			
		||||
      getMxcUrl: (String mxc, double? width, double? height,
 | 
			
		||||
          {bool? animated = false}) {
 | 
			
		||||
        final ratio = MediaQuery.of(context).devicePixelRatio;
 | 
			
		||||
        return Uri.parse(mxc)
 | 
			
		||||
            ?.getThumbnail(
 | 
			
		||||
            .getThumbnail(
 | 
			
		||||
              matrix.client,
 | 
			
		||||
              width: (width ?? 800) * ratio,
 | 
			
		||||
              height: (height ?? 800) * ratio,
 | 
			
		||||
@ -92,9 +92,6 @@ class HtmlMessage extends StatelessWidget {
 | 
			
		||||
        return await matrix.store.getItem('${SettingKeys.codeLanguage}.$key');
 | 
			
		||||
      },
 | 
			
		||||
      getPillInfo: (String url) async {
 | 
			
		||||
        if (room == null) {
 | 
			
		||||
          return null;
 | 
			
		||||
        }
 | 
			
		||||
        final identityParts = url.parseIdentifierIntoParts();
 | 
			
		||||
        final identifier = identityParts?.primaryIdentifier;
 | 
			
		||||
        if (identifier == null) {
 | 
			
		||||
@ -108,13 +105,10 @@ class HtmlMessage extends StatelessWidget {
 | 
			
		||||
          }
 | 
			
		||||
          // there might still be a profile...
 | 
			
		||||
          final profile = await room.client.getProfileFromUserId(identifier);
 | 
			
		||||
          if (profile != null) {
 | 
			
		||||
            return {
 | 
			
		||||
              'displayname': profile.displayName,
 | 
			
		||||
              'avatar_url': profile.avatarUrl.toString(),
 | 
			
		||||
            };
 | 
			
		||||
          }
 | 
			
		||||
          return null;
 | 
			
		||||
          return {
 | 
			
		||||
            'displayname': profile.displayName,
 | 
			
		||||
            'avatar_url': profile.avatarUrl.toString(),
 | 
			
		||||
          };
 | 
			
		||||
        }
 | 
			
		||||
        if (identifier.sigil == '#') {
 | 
			
		||||
          // we have an alias pill
 | 
			
		||||
@ -128,7 +122,7 @@ class HtmlMessage extends StatelessWidget {
 | 
			
		||||
              // we have a room!
 | 
			
		||||
              return {
 | 
			
		||||
                'displayname':
 | 
			
		||||
                    r.getLocalizedDisplayname(MatrixLocals(L10n.of(context))),
 | 
			
		||||
                    r.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)),
 | 
			
		||||
                'avatar_url': r.getState('m.room.avatar')?.content['url'],
 | 
			
		||||
              };
 | 
			
		||||
            }
 | 
			
		||||
@ -143,12 +137,12 @@ class HtmlMessage extends StatelessWidget {
 | 
			
		||||
          }
 | 
			
		||||
          return {
 | 
			
		||||
            'displayname':
 | 
			
		||||
                r.getLocalizedDisplayname(MatrixLocals(L10n.of(context))),
 | 
			
		||||
                r.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)),
 | 
			
		||||
            'avatar_url': r.getState('m.room.avatar')?.content['url'],
 | 
			
		||||
          };
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
      },
 | 
			
		||||
      } as Future<Map<String, dynamic>> Function(String)?,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -19,13 +19,13 @@ class ImageBubble extends StatefulWidget {
 | 
			
		||||
  final bool tapToView;
 | 
			
		||||
  final BoxFit fit;
 | 
			
		||||
  final bool maxSize;
 | 
			
		||||
  final Color backgroundColor;
 | 
			
		||||
  final Color? backgroundColor;
 | 
			
		||||
  final bool thumbnailOnly;
 | 
			
		||||
  final bool animated;
 | 
			
		||||
  final double width;
 | 
			
		||||
  final double height;
 | 
			
		||||
  final void Function() onLoaded;
 | 
			
		||||
  final void Function() onTap;
 | 
			
		||||
  final void Function()? onLoaded;
 | 
			
		||||
  final void Function()? onTap;
 | 
			
		||||
 | 
			
		||||
  const ImageBubble(
 | 
			
		||||
    this.event, {
 | 
			
		||||
@ -39,7 +39,7 @@ class ImageBubble extends StatefulWidget {
 | 
			
		||||
    this.height = 300,
 | 
			
		||||
    this.animated = false,
 | 
			
		||||
    this.onTap,
 | 
			
		||||
    Key key,
 | 
			
		||||
    Key? key,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@ -48,17 +48,17 @@ class ImageBubble extends StatefulWidget {
 | 
			
		||||
 | 
			
		||||
class _ImageBubbleState extends State<ImageBubble> {
 | 
			
		||||
  // 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
 | 
			
		||||
  String thumbnailUrlNoAnimated;
 | 
			
		||||
  String? thumbnailUrlNoAnimated;
 | 
			
		||||
  // for plaintext: holds the http URL of the original
 | 
			
		||||
  String attachmentUrl;
 | 
			
		||||
  MatrixFile _file;
 | 
			
		||||
  MatrixFile _thumbnail;
 | 
			
		||||
  String? attachmentUrl;
 | 
			
		||||
  MatrixFile? _file;
 | 
			
		||||
  MatrixFile? _thumbnail;
 | 
			
		||||
  bool _requestedThumbnailOnFailure = false;
 | 
			
		||||
  // In case we have animated = false, this will hold the first frame so that we make
 | 
			
		||||
  // sure that things are never animated
 | 
			
		||||
  Widget _firstFrame;
 | 
			
		||||
  Widget? _firstFrame;
 | 
			
		||||
 | 
			
		||||
  // the mimetypes that we know how to render, from the flutter Image widget
 | 
			
		||||
  final _knownMimetypes = <String>{
 | 
			
		||||
@ -82,8 +82,8 @@ class _ImageBubbleState extends State<ImageBubble> {
 | 
			
		||||
      ? widget.event.thumbnailMimetype.toLowerCase()
 | 
			
		||||
      : widget.event.attachmentMimetype.toLowerCase();
 | 
			
		||||
 | 
			
		||||
  MatrixFile get _displayFile => _file ?? _thumbnail;
 | 
			
		||||
  String get displayUrl => widget.thumbnailOnly ? thumbnailUrl : attachmentUrl;
 | 
			
		||||
  MatrixFile? get _displayFile => _file ?? _thumbnail;
 | 
			
		||||
  String? get displayUrl => widget.thumbnailOnly ? thumbnailUrl : attachmentUrl;
 | 
			
		||||
 | 
			
		||||
  dynamic _error;
 | 
			
		||||
 | 
			
		||||
@ -91,14 +91,14 @@ class _ImageBubbleState extends State<ImageBubble> {
 | 
			
		||||
    try {
 | 
			
		||||
      final res = await widget.event
 | 
			
		||||
          .downloadAndDecryptAttachmentCached(getThumbnail: getThumbnail);
 | 
			
		||||
      WidgetsBinding.instance.addPostFrameCallback((_) {
 | 
			
		||||
      WidgetsBinding.instance!.addPostFrameCallback((_) {
 | 
			
		||||
        if (getThumbnail) {
 | 
			
		||||
          if (mounted) {
 | 
			
		||||
            setState(() => _thumbnail = res);
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          if (widget.onLoaded != null) {
 | 
			
		||||
            widget.onLoaded();
 | 
			
		||||
            widget.onLoaded!();
 | 
			
		||||
          }
 | 
			
		||||
          if (mounted) {
 | 
			
		||||
            setState(() => _file = res);
 | 
			
		||||
@ -106,7 +106,7 @@ class _ImageBubbleState extends State<ImageBubble> {
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      WidgetsBinding.instance.addPostFrameCallback((_) {
 | 
			
		||||
      WidgetsBinding.instance!.addPostFrameCallback((_) {
 | 
			
		||||
        if (mounted) {
 | 
			
		||||
          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
 | 
			
		||||
    // animated, we'll have to store the first frame in a variable and display that instead
 | 
			
		||||
    if (widget.animated) {
 | 
			
		||||
@ -135,12 +135,14 @@ class _ImageBubbleState extends State<ImageBubble> {
 | 
			
		||||
        key: ValueKey(key),
 | 
			
		||||
        fit: widget.fit,
 | 
			
		||||
      ),
 | 
			
		||||
      network: (String url) => SvgPicture.network(
 | 
			
		||||
        url,
 | 
			
		||||
        key: ValueKey(url),
 | 
			
		||||
        placeholderBuilder: (context) => getPlaceholderWidget(),
 | 
			
		||||
        fit: widget.fit,
 | 
			
		||||
      ),
 | 
			
		||||
      network: (String? url) => url == null
 | 
			
		||||
          ? Container()
 | 
			
		||||
          : SvgPicture.network(
 | 
			
		||||
              url,
 | 
			
		||||
              key: ValueKey(url),
 | 
			
		||||
              placeholderBuilder: (context) => getPlaceholderWidget(),
 | 
			
		||||
              fit: widget.fit,
 | 
			
		||||
            ),
 | 
			
		||||
    );
 | 
			
		||||
    _contentRenderers['image/lottie+json'] = _ImageBubbleContentRenderer(
 | 
			
		||||
      memory: (Uint8List bytes, String key) => Lottie.memory(
 | 
			
		||||
@ -151,14 +153,16 @@ class _ImageBubbleState extends State<ImageBubble> {
 | 
			
		||||
            getErrorWidget(context, error),
 | 
			
		||||
        animate: widget.animated,
 | 
			
		||||
      ),
 | 
			
		||||
      network: (String url) => Lottie.network(
 | 
			
		||||
        url,
 | 
			
		||||
        key: ValueKey(url),
 | 
			
		||||
        fit: widget.fit,
 | 
			
		||||
        errorBuilder: (context, error, stacktrace) =>
 | 
			
		||||
            getErrorWidget(context, error),
 | 
			
		||||
        animate: widget.animated,
 | 
			
		||||
      ),
 | 
			
		||||
      network: (String? url) => url == null
 | 
			
		||||
          ? Container()
 | 
			
		||||
          : Lottie.network(
 | 
			
		||||
              url,
 | 
			
		||||
              key: ValueKey(url),
 | 
			
		||||
              fit: widget.fit,
 | 
			
		||||
              errorBuilder: (context, error, stacktrace) =>
 | 
			
		||||
                  getErrorWidget(context, error),
 | 
			
		||||
              animate: widget.animated,
 | 
			
		||||
            ),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // add all the custom content renderer mimetypes to the known mimetypes set
 | 
			
		||||
@ -203,7 +207,7 @@ class _ImageBubbleState extends State<ImageBubble> {
 | 
			
		||||
          OutlinedButton.icon(
 | 
			
		||||
            style: OutlinedButton.styleFrom(
 | 
			
		||||
              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),
 | 
			
		||||
            onPressed: () => widget.event.saveFile(context),
 | 
			
		||||
@ -215,7 +219,7 @@ class _ImageBubbleState extends State<ImageBubble> {
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
          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),
 | 
			
		||||
          Text((error ?? _error).toString()),
 | 
			
		||||
        ],
 | 
			
		||||
@ -223,8 +227,8 @@ class _ImageBubbleState extends State<ImageBubble> {
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget getPlaceholderWidget({Widget child}) {
 | 
			
		||||
    Widget blurhash;
 | 
			
		||||
  Widget getPlaceholderWidget({Widget? child}) {
 | 
			
		||||
    Widget? blurhash;
 | 
			
		||||
    if (widget.event.infoMap['xyz.amorgan.blurhash'] is String) {
 | 
			
		||||
      final ratio =
 | 
			
		||||
          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();
 | 
			
		||||
    final mimetype = getMimetype(!isOriginal);
 | 
			
		||||
    if (_contentRenderers.containsKey(mimetype)) {
 | 
			
		||||
      return _contentRenderers[mimetype].memory(_displayFile.bytes, key);
 | 
			
		||||
      return _contentRenderers[mimetype]!.memory!(_displayFile!.bytes, key);
 | 
			
		||||
    } else {
 | 
			
		||||
      return Image.memory(
 | 
			
		||||
        _displayFile.bytes,
 | 
			
		||||
        _displayFile!.bytes,
 | 
			
		||||
        key: ValueKey(key),
 | 
			
		||||
        fit: widget.fit,
 | 
			
		||||
        errorBuilder: (context, error, stacktrace) {
 | 
			
		||||
          if (widget.event.hasThumbnail && !_requestedThumbnailOnFailure) {
 | 
			
		||||
            _requestedThumbnailOnFailure = true;
 | 
			
		||||
            WidgetsBinding.instance.addPostFrameCallback((_) {
 | 
			
		||||
            WidgetsBinding.instance!.addPostFrameCallback((_) {
 | 
			
		||||
              setState(() {
 | 
			
		||||
                _file = null;
 | 
			
		||||
                _requestFile(getThumbnail: true);
 | 
			
		||||
@ -299,12 +303,12 @@ class _ImageBubbleState extends State<ImageBubble> {
 | 
			
		||||
    final mimetype = getMimetype(_requestedThumbnailOnFailure);
 | 
			
		||||
    if (displayUrl == attachmentUrl &&
 | 
			
		||||
        _contentRenderers.containsKey(mimetype)) {
 | 
			
		||||
      return _contentRenderers[mimetype].network(displayUrl);
 | 
			
		||||
      return _contentRenderers[mimetype]!.network!(displayUrl);
 | 
			
		||||
    } else {
 | 
			
		||||
      return CachedNetworkImage(
 | 
			
		||||
        // as we change the url on-error we need a key so that the widget actually updates
 | 
			
		||||
        key: ValueKey(displayUrl),
 | 
			
		||||
        imageUrl: displayUrl,
 | 
			
		||||
        imageUrl: displayUrl!,
 | 
			
		||||
        placeholder: (context, url) {
 | 
			
		||||
          if (!widget.thumbnailOnly &&
 | 
			
		||||
              displayUrl != thumbnailUrl &&
 | 
			
		||||
@ -313,7 +317,7 @@ class _ImageBubbleState extends State<ImageBubble> {
 | 
			
		||||
            return FutureBuilder<bool>(
 | 
			
		||||
              future: (() async {
 | 
			
		||||
                return await DefaultCacheManager()
 | 
			
		||||
                        .getFileFromCache(thumbnailUrl) !=
 | 
			
		||||
                        .getFileFromCache(thumbnailUrl!) !=
 | 
			
		||||
                    null;
 | 
			
		||||
              })(),
 | 
			
		||||
              builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
 | 
			
		||||
@ -321,8 +325,8 @@ class _ImageBubbleState extends State<ImageBubble> {
 | 
			
		||||
                  return getPlaceholderWidget();
 | 
			
		||||
                }
 | 
			
		||||
                final effectiveUrl = snapshot.data == true
 | 
			
		||||
                    ? thumbnailUrl
 | 
			
		||||
                    : thumbnailUrlNoAnimated;
 | 
			
		||||
                    ? thumbnailUrl!
 | 
			
		||||
                    : thumbnailUrlNoAnimated!;
 | 
			
		||||
                return CachedNetworkImage(
 | 
			
		||||
                  key: ValueKey(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
 | 
			
		||||
            // try to load this one!
 | 
			
		||||
            _requestedThumbnailOnFailure = true;
 | 
			
		||||
            WidgetsBinding.instance.addPostFrameCallback((_) {
 | 
			
		||||
            WidgetsBinding.instance!.addPostFrameCallback((_) {
 | 
			
		||||
              setState(() {
 | 
			
		||||
                thumbnailUrl = widget.event
 | 
			
		||||
                    .getAttachmentUrl(
 | 
			
		||||
@ -382,11 +386,11 @@ class _ImageBubbleState extends State<ImageBubble> {
 | 
			
		||||
                OutlinedButton(
 | 
			
		||||
                  style: OutlinedButton.styleFrom(
 | 
			
		||||
                    backgroundColor: Theme.of(context).scaffoldBackgroundColor,
 | 
			
		||||
                    primary: Theme.of(context).textTheme.bodyText1.color,
 | 
			
		||||
                    primary: Theme.of(context).textTheme.bodyText1!.color,
 | 
			
		||||
                  ),
 | 
			
		||||
                  onPressed: () => onTap(context),
 | 
			
		||||
                  child: Text(
 | 
			
		||||
                    L10n.of(context).tapToShowImage,
 | 
			
		||||
                    L10n.of(context)!.tapToShowImage,
 | 
			
		||||
                    overflow: TextOverflow.fade,
 | 
			
		||||
                    softWrap: false,
 | 
			
		||||
                    maxLines: 1,
 | 
			
		||||
@ -394,7 +398,7 @@ class _ImageBubbleState extends State<ImageBubble> {
 | 
			
		||||
                ),
 | 
			
		||||
                if (widget.event.sizeString != null) ...[
 | 
			
		||||
                  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) {
 | 
			
		||||
    if (widget.onTap != null) {
 | 
			
		||||
      widget.onTap();
 | 
			
		||||
      widget.onTap!();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (!widget.tapToView) return;
 | 
			
		||||
@ -476,8 +480,8 @@ class _ImageBubbleState extends State<ImageBubble> {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _ImageBubbleContentRenderer {
 | 
			
		||||
  final Widget Function(Uint8List, String) memory;
 | 
			
		||||
  final Widget Function(String) network;
 | 
			
		||||
  final Widget Function(Uint8List, String)? memory;
 | 
			
		||||
  final Widget Function(String?)? network;
 | 
			
		||||
 | 
			
		||||
  _ImageBubbleContentRenderer({this.memory, this.network});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -11,13 +11,13 @@ class MapBubble extends StatelessWidget {
 | 
			
		||||
  final double height;
 | 
			
		||||
  final double radius;
 | 
			
		||||
  const MapBubble({
 | 
			
		||||
    this.latitude,
 | 
			
		||||
    this.longitude,
 | 
			
		||||
    required this.latitude,
 | 
			
		||||
    required this.longitude,
 | 
			
		||||
    this.zoom = 14.0,
 | 
			
		||||
    this.width = 400,
 | 
			
		||||
    this.height = 400,
 | 
			
		||||
    this.radius = 10.0,
 | 
			
		||||
    Key key,
 | 
			
		||||
    Key? key,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
 | 
			
		||||
@ -16,14 +16,14 @@ import 'verification_request_content.dart';
 | 
			
		||||
 | 
			
		||||
class Message extends StatelessWidget {
 | 
			
		||||
  final Event event;
 | 
			
		||||
  final Event nextEvent;
 | 
			
		||||
  final void Function(Event) onSelect;
 | 
			
		||||
  final void Function(Event) onAvatarTab;
 | 
			
		||||
  final void Function(Event) onInfoTab;
 | 
			
		||||
  final void Function(String) scrollToEventId;
 | 
			
		||||
  final Event? nextEvent;
 | 
			
		||||
  final void Function(Event)? onSelect;
 | 
			
		||||
  final void Function(Event)? onAvatarTab;
 | 
			
		||||
  final void Function(Event)? onInfoTab;
 | 
			
		||||
  final void Function(String)? scrollToEventId;
 | 
			
		||||
  final void Function(String) unfold;
 | 
			
		||||
  final bool longPressSelect;
 | 
			
		||||
  final bool selected;
 | 
			
		||||
  final bool? longPressSelect;
 | 
			
		||||
  final bool? selected;
 | 
			
		||||
  final Timeline timeline;
 | 
			
		||||
 | 
			
		||||
  const Message(this.event,
 | 
			
		||||
@ -33,10 +33,10 @@ class Message extends StatelessWidget {
 | 
			
		||||
      this.onInfoTab,
 | 
			
		||||
      this.onAvatarTab,
 | 
			
		||||
      this.scrollToEventId,
 | 
			
		||||
      @required this.unfold,
 | 
			
		||||
      required this.unfold,
 | 
			
		||||
      this.selected,
 | 
			
		||||
      this.timeline,
 | 
			
		||||
      Key key})
 | 
			
		||||
      required this.timeline,
 | 
			
		||||
      Key? key})
 | 
			
		||||
      : super(key: key);
 | 
			
		||||
 | 
			
		||||
  /// Indicates wheither the user may use a mouse instead
 | 
			
		||||
@ -61,14 +61,14 @@ class Message extends StatelessWidget {
 | 
			
		||||
    var color = Theme.of(context).appBarTheme.backgroundColor;
 | 
			
		||||
    final displayTime = event.type == EventTypes.RoomCreate ||
 | 
			
		||||
        nextEvent == null ||
 | 
			
		||||
        !event.originServerTs.sameEnvironment(nextEvent.originServerTs);
 | 
			
		||||
        !event.originServerTs.sameEnvironment(nextEvent!.originServerTs);
 | 
			
		||||
    final sameSender = nextEvent != null &&
 | 
			
		||||
            [
 | 
			
		||||
              EventTypes.Message,
 | 
			
		||||
              EventTypes.Sticker,
 | 
			
		||||
              EventTypes.Encrypted,
 | 
			
		||||
            ].contains(nextEvent.type)
 | 
			
		||||
        ? nextEvent.sender.id == event.sender.id && !displayTime
 | 
			
		||||
            ].contains(nextEvent!.type)
 | 
			
		||||
        ? nextEvent!.sender.id == event.sender.id && !displayTime
 | 
			
		||||
        : false;
 | 
			
		||||
    final textColor = ownMessage
 | 
			
		||||
        ? Colors.white
 | 
			
		||||
@ -119,7 +119,7 @@ class Message extends StatelessWidget {
 | 
			
		||||
          : Avatar(
 | 
			
		||||
              mxContent: event.sender.avatarUrl,
 | 
			
		||||
              name: event.sender.calcDisplayname(),
 | 
			
		||||
              onTap: () => onAvatarTab(event),
 | 
			
		||||
              onTap: () => onAvatarTab!(event),
 | 
			
		||||
            ),
 | 
			
		||||
      Expanded(
 | 
			
		||||
        child: Column(
 | 
			
		||||
@ -152,10 +152,11 @@ class Message extends StatelessWidget {
 | 
			
		||||
                clipBehavior: Clip.antiAlias,
 | 
			
		||||
                child: InkWell(
 | 
			
		||||
                  onHover: (b) => useMouse = true,
 | 
			
		||||
                  onTap: !useMouse && longPressSelect
 | 
			
		||||
                      ? () => null
 | 
			
		||||
                      : () => onSelect(event),
 | 
			
		||||
                  onLongPress: !longPressSelect ? null : () => onSelect(event),
 | 
			
		||||
                  onTap: !useMouse && longPressSelect!
 | 
			
		||||
                      ? () {}
 | 
			
		||||
                      : () => onSelect!(event),
 | 
			
		||||
                  onLongPress:
 | 
			
		||||
                      !longPressSelect! ? null : () => onSelect!(event),
 | 
			
		||||
                  borderRadius: borderRadius,
 | 
			
		||||
                  child: Container(
 | 
			
		||||
                    decoration: BoxDecoration(
 | 
			
		||||
@ -175,13 +176,13 @@ class Message extends StatelessWidget {
 | 
			
		||||
                          children: <Widget>[
 | 
			
		||||
                            if (event.relationshipType ==
 | 
			
		||||
                                RelationshipTypes.reply)
 | 
			
		||||
                              FutureBuilder<Event>(
 | 
			
		||||
                              FutureBuilder<Event?>(
 | 
			
		||||
                                future: event.getReplyEvent(timeline),
 | 
			
		||||
                                builder: (BuildContext context, snapshot) {
 | 
			
		||||
                                  final replyEvent = snapshot.hasData
 | 
			
		||||
                                      ? snapshot.data
 | 
			
		||||
                                      ? snapshot.data!
 | 
			
		||||
                                      : Event(
 | 
			
		||||
                                          eventId: event.relationshipEventId,
 | 
			
		||||
                                          eventId: event.relationshipEventId!,
 | 
			
		||||
                                          content: {
 | 
			
		||||
                                            'msgtype': 'm.text',
 | 
			
		||||
                                            'body': '...'
 | 
			
		||||
@ -195,7 +196,7 @@ class Message extends StatelessWidget {
 | 
			
		||||
                                  return InkWell(
 | 
			
		||||
                                    onTap: () {
 | 
			
		||||
                                      if (scrollToEventId != null) {
 | 
			
		||||
                                        scrollToEventId(replyEvent.eventId);
 | 
			
		||||
                                        scrollToEventId!(replyEvent.eventId);
 | 
			
		||||
                                      }
 | 
			
		||||
                                    },
 | 
			
		||||
                                    child: AbsorbPointer(
 | 
			
		||||
@ -300,7 +301,7 @@ class Message extends StatelessWidget {
 | 
			
		||||
 | 
			
		||||
    return Center(
 | 
			
		||||
      child: Container(
 | 
			
		||||
        color: selected
 | 
			
		||||
        color: selected!
 | 
			
		||||
            ? Theme.of(context).primaryColor.withAlpha(100)
 | 
			
		||||
            : Theme.of(context).primaryColor.withAlpha(0),
 | 
			
		||||
        constraints:
 | 
			
		||||
 | 
			
		||||
@ -22,10 +22,10 @@ import 'sticker.dart';
 | 
			
		||||
 | 
			
		||||
class MessageContent extends StatelessWidget {
 | 
			
		||||
  final Event event;
 | 
			
		||||
  final Color textColor;
 | 
			
		||||
  final void Function(Event) onInfoTab;
 | 
			
		||||
  final Color? textColor;
 | 
			
		||||
  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);
 | 
			
		||||
 | 
			
		||||
  void _verifyOrRequestKey(BuildContext context) async {
 | 
			
		||||
@ -33,15 +33,15 @@ class MessageContent extends StatelessWidget {
 | 
			
		||||
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(
 | 
			
		||||
          content: Text(
 | 
			
		||||
        event.type == EventTypes.Encrypted
 | 
			
		||||
            ? L10n.of(context).needPantalaimonWarning
 | 
			
		||||
            ? L10n.of(context)!.needPantalaimonWarning
 | 
			
		||||
            : event.getLocalizedBody(
 | 
			
		||||
                MatrixLocals(L10n.of(context)),
 | 
			
		||||
                MatrixLocals(L10n.of(context)!),
 | 
			
		||||
              ),
 | 
			
		||||
      )));
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    final client = Matrix.of(context).client;
 | 
			
		||||
    if (client.isUnknownSession && client.encryption.crossSigning.enabled) {
 | 
			
		||||
    if (client.isUnknownSession && client.encryption!.crossSigning.enabled) {
 | 
			
		||||
      await BootstrapDialog(
 | 
			
		||||
        client: Matrix.of(context).client,
 | 
			
		||||
      ).show(context);
 | 
			
		||||
@ -55,7 +55,7 @@ class MessageContent extends StatelessWidget {
 | 
			
		||||
      );
 | 
			
		||||
      if (success.error == null) {
 | 
			
		||||
        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) {
 | 
			
		||||
              return AudioPlayerWidget(
 | 
			
		||||
                event,
 | 
			
		||||
                color: textColor,
 | 
			
		||||
                color: textColor!,
 | 
			
		||||
              );
 | 
			
		||||
            }
 | 
			
		||||
            return MessageDownloadContent(event, textColor);
 | 
			
		||||
            return MessageDownloadContent(event, textColor!);
 | 
			
		||||
          case MessageTypes.Video:
 | 
			
		||||
            if (PlatformInfos.isMobile || PlatformInfos.isWeb) {
 | 
			
		||||
              return EventVideoPlayer(event);
 | 
			
		||||
            }
 | 
			
		||||
            return MessageDownloadContent(event, textColor);
 | 
			
		||||
            return MessageDownloadContent(event, textColor!);
 | 
			
		||||
          case MessageTypes.File:
 | 
			
		||||
            return MessageDownloadContent(event, textColor);
 | 
			
		||||
            return MessageDownloadContent(event, textColor!);
 | 
			
		||||
 | 
			
		||||
          case MessageTypes.Text:
 | 
			
		||||
          case MessageTypes.Notice:
 | 
			
		||||
@ -115,7 +115,7 @@ class MessageContent extends StatelessWidget {
 | 
			
		||||
                  fontSize: bigEmotes ? fontSize * 3 : fontSize,
 | 
			
		||||
                ),
 | 
			
		||||
                linkStyle: TextStyle(
 | 
			
		||||
                  color: textColor.withAlpha(150),
 | 
			
		||||
                  color: textColor!.withAlpha(150),
 | 
			
		||||
                  fontSize: bigEmotes ? fontSize * 3 : fontSize,
 | 
			
		||||
                  decoration: TextDecoration.underline,
 | 
			
		||||
                ),
 | 
			
		||||
@ -131,14 +131,12 @@ class MessageContent extends StatelessWidget {
 | 
			
		||||
              textColor: buttonTextColor,
 | 
			
		||||
              onPressed: () => _verifyOrRequestKey(context),
 | 
			
		||||
              icon: const Icon(Icons.lock_outline),
 | 
			
		||||
              label: L10n.of(context).encrypted,
 | 
			
		||||
              label: L10n.of(context)!.encrypted,
 | 
			
		||||
            );
 | 
			
		||||
          case MessageTypes.Location:
 | 
			
		||||
            final geoUri =
 | 
			
		||||
                Uri.tryParse(event.content.tryGet<String>('geo_uri'));
 | 
			
		||||
            if (geoUri != null &&
 | 
			
		||||
                geoUri.scheme == 'geo' &&
 | 
			
		||||
                geoUri.path != null) {
 | 
			
		||||
                Uri.tryParse(event.content.tryGet<String>('geo_uri')!);
 | 
			
		||||
            if (geoUri != null && geoUri.scheme == 'geo') {
 | 
			
		||||
              final latlong = geoUri.path
 | 
			
		||||
                  .split(';')
 | 
			
		||||
                  .first
 | 
			
		||||
@ -152,8 +150,8 @@ class MessageContent extends StatelessWidget {
 | 
			
		||||
                  mainAxisSize: MainAxisSize.min,
 | 
			
		||||
                  children: [
 | 
			
		||||
                    MapBubble(
 | 
			
		||||
                      latitude: latlong.first,
 | 
			
		||||
                      longitude: latlong.last,
 | 
			
		||||
                      latitude: latlong.first!,
 | 
			
		||||
                      longitude: latlong.last!,
 | 
			
		||||
                    ),
 | 
			
		||||
                    const SizedBox(height: 6),
 | 
			
		||||
                    OutlinedButton.icon(
 | 
			
		||||
@ -161,7 +159,7 @@ class MessageContent extends StatelessWidget {
 | 
			
		||||
                      onPressed:
 | 
			
		||||
                          UrlLauncher(context, geoUri.toString()).launchUrl,
 | 
			
		||||
                      label: Text(
 | 
			
		||||
                        L10n.of(context).openInMaps,
 | 
			
		||||
                        L10n.of(context)!.openInMaps,
 | 
			
		||||
                        style: TextStyle(color: textColor),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
@ -177,24 +175,24 @@ class MessageContent extends StatelessWidget {
 | 
			
		||||
              return _ButtonContent(
 | 
			
		||||
                onPressed: () => launch(event.body),
 | 
			
		||||
                icon: const Icon(Icons.phone_outlined, color: Colors.green),
 | 
			
		||||
                label: L10n.of(context).videoCall,
 | 
			
		||||
                label: L10n.of(context)!.videoCall,
 | 
			
		||||
                textColor: buttonTextColor,
 | 
			
		||||
              );
 | 
			
		||||
            }
 | 
			
		||||
            if (event.redacted) {
 | 
			
		||||
              return _ButtonContent(
 | 
			
		||||
                label: L10n.of(context)
 | 
			
		||||
                label: L10n.of(context)!
 | 
			
		||||
                    .redactedAnEvent(event.sender.calcDisplayname()),
 | 
			
		||||
                icon: const Icon(Icons.delete_outlined),
 | 
			
		||||
                textColor: buttonTextColor,
 | 
			
		||||
                onPressed: () => onInfoTab(event),
 | 
			
		||||
                onPressed: () => onInfoTab!(event),
 | 
			
		||||
              );
 | 
			
		||||
            }
 | 
			
		||||
            final bigEmotes = event.onlyEmotes &&
 | 
			
		||||
                event.numberEmotes > 0 &&
 | 
			
		||||
                event.numberEmotes <= 10;
 | 
			
		||||
            return LinkText(
 | 
			
		||||
              text: event.getLocalizedBody(MatrixLocals(L10n.of(context)),
 | 
			
		||||
              text: event.getLocalizedBody(MatrixLocals(L10n.of(context)!),
 | 
			
		||||
                  hideReply: true),
 | 
			
		||||
              textStyle: TextStyle(
 | 
			
		||||
                color: textColor,
 | 
			
		||||
@ -202,24 +200,22 @@ class MessageContent extends StatelessWidget {
 | 
			
		||||
                decoration: event.redacted ? TextDecoration.lineThrough : null,
 | 
			
		||||
              ),
 | 
			
		||||
              linkStyle: TextStyle(
 | 
			
		||||
                color: textColor.withAlpha(150),
 | 
			
		||||
                color: textColor!.withAlpha(150),
 | 
			
		||||
                fontSize: bigEmotes ? fontSize * 3 : fontSize,
 | 
			
		||||
                decoration: TextDecoration.underline,
 | 
			
		||||
              ),
 | 
			
		||||
              onLinkTap: (url) => UrlLauncher(context, url).launchUrl(),
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        return _ButtonContent(
 | 
			
		||||
          label: L10n.of(context)
 | 
			
		||||
          label: L10n.of(context)!
 | 
			
		||||
              .userSentUnknownEvent(event.sender.calcDisplayname(), event.type),
 | 
			
		||||
          icon: const Icon(Icons.info_outlined),
 | 
			
		||||
          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 String label;
 | 
			
		||||
  final Icon icon;
 | 
			
		||||
  final Color textColor;
 | 
			
		||||
  final Color? textColor;
 | 
			
		||||
 | 
			
		||||
  const _ButtonContent({
 | 
			
		||||
    @required this.label,
 | 
			
		||||
    @required this.icon,
 | 
			
		||||
    @required this.textColor,
 | 
			
		||||
    @required this.onPressed,
 | 
			
		||||
    Key key,
 | 
			
		||||
    required this.label,
 | 
			
		||||
    required this.icon,
 | 
			
		||||
    required this.textColor,
 | 
			
		||||
    required this.onPressed,
 | 
			
		||||
    Key? key,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:matrix/matrix.dart';
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ import 'package:flutter/cupertino.dart';
 | 
			
		||||
import 'package:flutter/material.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:matrix/matrix.dart';
 | 
			
		||||
 | 
			
		||||
@ -14,14 +15,14 @@ class MessageReactions extends StatelessWidget {
 | 
			
		||||
  final Event event;
 | 
			
		||||
  final Timeline timeline;
 | 
			
		||||
 | 
			
		||||
  const MessageReactions(this.event, this.timeline, {Key key})
 | 
			
		||||
  const MessageReactions(this.event, this.timeline, {Key? key})
 | 
			
		||||
      : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final allReactionEvents =
 | 
			
		||||
        event.aggregatedEvents(timeline, RelationshipTypes.reaction);
 | 
			
		||||
    final reactionMap = <String, _ReactionEntry>{};
 | 
			
		||||
    final reactionMap = <String?, _ReactionEntry>{};
 | 
			
		||||
    final client = Matrix.of(context).client;
 | 
			
		||||
 | 
			
		||||
    for (final e in allReactionEvents) {
 | 
			
		||||
@ -35,9 +36,9 @@ class MessageReactions extends StatelessWidget {
 | 
			
		||||
            reactors: [],
 | 
			
		||||
          );
 | 
			
		||||
        }
 | 
			
		||||
        reactionMap[key].count++;
 | 
			
		||||
        reactionMap[key].reactors.add(e.sender);
 | 
			
		||||
        reactionMap[key].reacted |= e.senderId == e.room.client.userID;
 | 
			
		||||
        reactionMap[key]!.count++;
 | 
			
		||||
        reactionMap[key]!.reactors!.add(e.sender);
 | 
			
		||||
        reactionMap[key]!.reacted |= e.senderId == e.room.client.userID;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -52,11 +53,9 @@ class MessageReactions extends StatelessWidget {
 | 
			
		||||
              reacted: r.reacted,
 | 
			
		||||
              onTap: () {
 | 
			
		||||
                if (r.reacted) {
 | 
			
		||||
                  final evt = allReactionEvents.firstWhere(
 | 
			
		||||
                      (e) =>
 | 
			
		||||
                          e.senderId == e.room.client.userID &&
 | 
			
		||||
                          e.content['m.relates_to']['key'] == r.key,
 | 
			
		||||
                      orElse: () => null);
 | 
			
		||||
                  final evt = allReactionEvents.firstWhereOrNull((e) =>
 | 
			
		||||
                      e.senderId == e.room.client.userID &&
 | 
			
		||||
                      e.content['m.relates_to']['key'] == r.key);
 | 
			
		||||
                  if (evt != null) {
 | 
			
		||||
                    showFutureLoadingDialog(
 | 
			
		||||
                      context: context,
 | 
			
		||||
@ -67,7 +66,7 @@ class MessageReactions extends StatelessWidget {
 | 
			
		||||
                  showFutureLoadingDialog(
 | 
			
		||||
                      context: context,
 | 
			
		||||
                      future: () =>
 | 
			
		||||
                          event.room.sendReaction(event.eventId, r.key));
 | 
			
		||||
                          event.room.sendReaction(event.eventId, r.key!));
 | 
			
		||||
                }
 | 
			
		||||
              },
 | 
			
		||||
              onLongPress: () async => await _AdaptableReactorsDialog(
 | 
			
		||||
@ -91,11 +90,11 @@ class MessageReactions extends StatelessWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _Reaction extends StatelessWidget {
 | 
			
		||||
  final String reactionKey;
 | 
			
		||||
  final int count;
 | 
			
		||||
  final bool reacted;
 | 
			
		||||
  final void Function() onTap;
 | 
			
		||||
  final void Function() onLongPress;
 | 
			
		||||
  final String? reactionKey;
 | 
			
		||||
  final int? count;
 | 
			
		||||
  final bool? reacted;
 | 
			
		||||
  final void Function()? onTap;
 | 
			
		||||
  final void Function()? onLongPress;
 | 
			
		||||
 | 
			
		||||
  const _Reaction({
 | 
			
		||||
    this.reactionKey,
 | 
			
		||||
@ -113,11 +112,11 @@ class _Reaction extends StatelessWidget {
 | 
			
		||||
    final color = Theme.of(context).scaffoldBackgroundColor;
 | 
			
		||||
    final fontSize = DefaultTextStyle.of(context).style.fontSize;
 | 
			
		||||
    Widget content;
 | 
			
		||||
    if (reactionKey.startsWith('mxc://')) {
 | 
			
		||||
      final src = Uri.parse(reactionKey)?.getThumbnail(
 | 
			
		||||
    if (reactionKey!.startsWith('mxc://')) {
 | 
			
		||||
      final src = Uri.parse(reactionKey!).getThumbnail(
 | 
			
		||||
        Matrix.of(context).client,
 | 
			
		||||
        width: 9999,
 | 
			
		||||
        height: fontSize * MediaQuery.of(context).devicePixelRatio,
 | 
			
		||||
        height: fontSize! * MediaQuery.of(context).devicePixelRatio,
 | 
			
		||||
        method: ThumbnailMethod.scale,
 | 
			
		||||
      );
 | 
			
		||||
      content = Row(
 | 
			
		||||
@ -136,7 +135,7 @@ class _Reaction extends StatelessWidget {
 | 
			
		||||
        ],
 | 
			
		||||
      );
 | 
			
		||||
    } else {
 | 
			
		||||
      var renderKey = Characters(reactionKey);
 | 
			
		||||
      var renderKey = Characters(reactionKey!);
 | 
			
		||||
      if (renderKey.length > 10) {
 | 
			
		||||
        renderKey = renderKey.getRange(0, 9) + Characters('…');
 | 
			
		||||
      }
 | 
			
		||||
@ -147,13 +146,13 @@ class _Reaction extends StatelessWidget {
 | 
			
		||||
          ));
 | 
			
		||||
    }
 | 
			
		||||
    return InkWell(
 | 
			
		||||
      onTap: () => onTap != null ? onTap() : null,
 | 
			
		||||
      onLongPress: () => onLongPress != null ? onLongPress() : null,
 | 
			
		||||
      onTap: () => onTap != null ? onTap!() : null,
 | 
			
		||||
      onLongPress: () => onLongPress != null ? onLongPress!() : null,
 | 
			
		||||
      borderRadius: BorderRadius.circular(AppConfig.borderRadius),
 | 
			
		||||
      child: Container(
 | 
			
		||||
        decoration: BoxDecoration(
 | 
			
		||||
          color: color,
 | 
			
		||||
          border: reacted
 | 
			
		||||
          border: reacted!
 | 
			
		||||
              ? Border.all(
 | 
			
		||||
                  width: 1,
 | 
			
		||||
                  color: Theme.of(context).primaryColor,
 | 
			
		||||
@ -169,25 +168,30 @@ class _Reaction extends StatelessWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _ReactionEntry {
 | 
			
		||||
  String key;
 | 
			
		||||
  String? key;
 | 
			
		||||
  int count;
 | 
			
		||||
  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 {
 | 
			
		||||
  final Client client;
 | 
			
		||||
  final _ReactionEntry reactionEntry;
 | 
			
		||||
  final Client? client;
 | 
			
		||||
  final _ReactionEntry? reactionEntry;
 | 
			
		||||
 | 
			
		||||
  const _AdaptableReactorsDialog({
 | 
			
		||||
    Key key,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    this.client,
 | 
			
		||||
    this.reactionEntry,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  Future<bool> show(BuildContext context) => PlatformInfos.isCupertinoStyle
 | 
			
		||||
  Future<bool?> show(BuildContext context) => PlatformInfos.isCupertinoStyle
 | 
			
		||||
      ? showCupertinoDialog(
 | 
			
		||||
          context: context,
 | 
			
		||||
          builder: (context) => this,
 | 
			
		||||
@ -209,20 +213,20 @@ class _AdaptableReactorsDialog extends StatelessWidget {
 | 
			
		||||
        runSpacing: 4.0,
 | 
			
		||||
        alignment: WrapAlignment.center,
 | 
			
		||||
        children: <Widget>[
 | 
			
		||||
          for (var reactor in reactionEntry.reactors)
 | 
			
		||||
          for (var reactor in reactionEntry!.reactors!)
 | 
			
		||||
            Chip(
 | 
			
		||||
              avatar: Avatar(
 | 
			
		||||
                mxContent: reactor.avatarUrl,
 | 
			
		||||
                name: reactor.displayName,
 | 
			
		||||
                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
 | 
			
		||||
        ? CupertinoAlertDialog(
 | 
			
		||||
 | 
			
		||||
@ -10,21 +10,23 @@ import 'html_message.dart';
 | 
			
		||||
class ReplyContent extends StatelessWidget {
 | 
			
		||||
  final Event replyEvent;
 | 
			
		||||
  final bool lightText;
 | 
			
		||||
  final Timeline timeline;
 | 
			
		||||
  final Timeline? timeline;
 | 
			
		||||
 | 
			
		||||
  const ReplyContent(this.replyEvent,
 | 
			
		||||
      {this.lightText = false, Key key, this.timeline})
 | 
			
		||||
      : super(key: key);
 | 
			
		||||
  const ReplyContent(
 | 
			
		||||
    this.replyEvent, {
 | 
			
		||||
    this.lightText = false,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    this.timeline,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    Widget replyBody;
 | 
			
		||||
    final displayEvent = replyEvent != null && timeline != null
 | 
			
		||||
        ? replyEvent.getDisplayEvent(timeline)
 | 
			
		||||
        : replyEvent;
 | 
			
		||||
    final timeline = this.timeline;
 | 
			
		||||
    final displayEvent =
 | 
			
		||||
        timeline != null ? replyEvent.getDisplayEvent(timeline) : replyEvent;
 | 
			
		||||
    final fontSize = AppConfig.messageFontSize * AppConfig.fontSizeFactor;
 | 
			
		||||
    if (displayEvent != null &&
 | 
			
		||||
        AppConfig.renderHtml &&
 | 
			
		||||
    if (AppConfig.renderHtml &&
 | 
			
		||||
        [EventTypes.Message, EventTypes.Encrypted]
 | 
			
		||||
            .contains(displayEvent.type) &&
 | 
			
		||||
        [MessageTypes.Text, MessageTypes.Notice, MessageTypes.Emote]
 | 
			
		||||
@ -32,16 +34,16 @@ class ReplyContent extends StatelessWidget {
 | 
			
		||||
        !displayEvent.redacted &&
 | 
			
		||||
        displayEvent.content['format'] == 'org.matrix.custom.html' &&
 | 
			
		||||
        displayEvent.content['formatted_body'] is String) {
 | 
			
		||||
      String html = displayEvent.content['formatted_body'];
 | 
			
		||||
      String? html = displayEvent.content['formatted_body'];
 | 
			
		||||
      if (displayEvent.messageType == MessageTypes.Emote) {
 | 
			
		||||
        html = '* $html';
 | 
			
		||||
      }
 | 
			
		||||
      replyBody = HtmlMessage(
 | 
			
		||||
        html: html,
 | 
			
		||||
        html: html!,
 | 
			
		||||
        defaultTextStyle: TextStyle(
 | 
			
		||||
          color: lightText
 | 
			
		||||
              ? Colors.white
 | 
			
		||||
              : Theme.of(context).textTheme.bodyText2.color,
 | 
			
		||||
              : Theme.of(context).textTheme.bodyText2!.color,
 | 
			
		||||
          fontSize: fontSize,
 | 
			
		||||
        ),
 | 
			
		||||
        maxLines: 1,
 | 
			
		||||
@ -50,18 +52,17 @@ class ReplyContent extends StatelessWidget {
 | 
			
		||||
      );
 | 
			
		||||
    } else {
 | 
			
		||||
      replyBody = Text(
 | 
			
		||||
        displayEvent?.getLocalizedBody(
 | 
			
		||||
              MatrixLocals(L10n.of(context)),
 | 
			
		||||
              withSenderNamePrefix: false,
 | 
			
		||||
              hideReply: true,
 | 
			
		||||
            ) ??
 | 
			
		||||
            '',
 | 
			
		||||
        displayEvent.getLocalizedBody(
 | 
			
		||||
          MatrixLocals(L10n.of(context)!),
 | 
			
		||||
          withSenderNamePrefix: false,
 | 
			
		||||
          hideReply: true,
 | 
			
		||||
        ),
 | 
			
		||||
        overflow: TextOverflow.ellipsis,
 | 
			
		||||
        maxLines: 1,
 | 
			
		||||
        style: TextStyle(
 | 
			
		||||
          color: lightText
 | 
			
		||||
              ? Colors.white
 | 
			
		||||
              : Theme.of(context).textTheme.bodyText2.color,
 | 
			
		||||
              : Theme.of(context).textTheme.bodyText2!.color,
 | 
			
		||||
          fontSize: fontSize,
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
@ -81,7 +82,7 @@ class ReplyContent extends StatelessWidget {
 | 
			
		||||
            mainAxisAlignment: MainAxisAlignment.center,
 | 
			
		||||
            children: <Widget>[
 | 
			
		||||
              Text(
 | 
			
		||||
                (displayEvent?.sender?.calcDisplayname() ?? '') + ':',
 | 
			
		||||
                displayEvent.sender.calcDisplayname() + ':',
 | 
			
		||||
                maxLines: 1,
 | 
			
		||||
                overflow: TextOverflow.ellipsis,
 | 
			
		||||
                style: TextStyle(
 | 
			
		||||
 | 
			
		||||
@ -9,16 +9,16 @@ import '../../../config/app_config.dart';
 | 
			
		||||
class StateMessage extends StatelessWidget {
 | 
			
		||||
  final Event event;
 | 
			
		||||
  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);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    if (event.unsigned['im.fluffychat.collapsed_state_event'] == true) {
 | 
			
		||||
    if (event.unsigned!['im.fluffychat.collapsed_state_event'] == true) {
 | 
			
		||||
      return Container();
 | 
			
		||||
    }
 | 
			
		||||
    final int counter =
 | 
			
		||||
        event.unsigned['im.fluffychat.collapsed_state_event_count'] ?? 0;
 | 
			
		||||
        event.unsigned!['im.fluffychat.collapsed_state_event_count'] ?? 0;
 | 
			
		||||
    return Padding(
 | 
			
		||||
      padding: const EdgeInsets.symmetric(
 | 
			
		||||
        horizontal: 8.0,
 | 
			
		||||
@ -40,18 +40,18 @@ class StateMessage extends StatelessWidget {
 | 
			
		||||
              mainAxisSize: MainAxisSize.min,
 | 
			
		||||
              children: [
 | 
			
		||||
                Text(
 | 
			
		||||
                  event.getLocalizedBody(MatrixLocals(L10n.of(context))),
 | 
			
		||||
                  event.getLocalizedBody(MatrixLocals(L10n.of(context)!)),
 | 
			
		||||
                  textAlign: TextAlign.center,
 | 
			
		||||
                  style: TextStyle(
 | 
			
		||||
                    fontSize: 14 * AppConfig.fontSizeFactor,
 | 
			
		||||
                    color: Theme.of(context).textTheme.bodyText2.color,
 | 
			
		||||
                    color: Theme.of(context).textTheme.bodyText2!.color,
 | 
			
		||||
                    decoration:
 | 
			
		||||
                        event.redacted ? TextDecoration.lineThrough : null,
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
                if (counter != 0)
 | 
			
		||||
                  Text(
 | 
			
		||||
                    L10n.of(context).moreEvents(counter),
 | 
			
		||||
                    L10n.of(context)!.moreEvents(counter),
 | 
			
		||||
                    style: TextStyle(
 | 
			
		||||
                      fontWeight: FontWeight.bold,
 | 
			
		||||
                      fontSize: 14 * AppConfig.fontSizeFactor,
 | 
			
		||||
 | 
			
		||||
@ -10,14 +10,14 @@ import 'image_bubble.dart';
 | 
			
		||||
class Sticker extends StatefulWidget {
 | 
			
		||||
  final Event event;
 | 
			
		||||
 | 
			
		||||
  const Sticker(this.event, {Key key}) : super(key: key);
 | 
			
		||||
  const Sticker(this.event, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  _StickerState createState() => _StickerState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _StickerState extends State<Sticker> {
 | 
			
		||||
  bool animated;
 | 
			
		||||
  bool? animated;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
@ -31,7 +31,7 @@ class _StickerState extends State<Sticker> {
 | 
			
		||||
        showOkAlertDialog(
 | 
			
		||||
          context: context,
 | 
			
		||||
          message: widget.event.body,
 | 
			
		||||
          okLabel: L10n.of(context).ok,
 | 
			
		||||
          okLabel: L10n.of(context)!.ok,
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
      animated: animated ?? AppConfig.autoplayImages,
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,8 @@ class VerificationRequestContent extends StatelessWidget {
 | 
			
		||||
  final Event event;
 | 
			
		||||
  final Timeline timeline;
 | 
			
		||||
 | 
			
		||||
  const VerificationRequestContent({this.event, this.timeline, Key key})
 | 
			
		||||
  const VerificationRequestContent(
 | 
			
		||||
      {required this.event, required this.timeline, Key? key})
 | 
			
		||||
      : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@ -50,10 +51,10 @@ class VerificationRequestContent extends StatelessWidget {
 | 
			
		||||
              Text(canceled
 | 
			
		||||
                  ? 'Error ${cancel.first.content.tryGet<String>('code')}: ${cancel.first.content.tryGet<String>('reason')}'
 | 
			
		||||
                  : (fullyDone
 | 
			
		||||
                      ? L10n.of(context).verifySuccess
 | 
			
		||||
                      ? L10n.of(context)!.verifySuccess
 | 
			
		||||
                      : (started
 | 
			
		||||
                          ? L10n.of(context).loadingPleaseWait
 | 
			
		||||
                          : L10n.of(context).newVerificationRequest)))
 | 
			
		||||
                          ? L10n.of(context)!.loadingPleaseWait
 | 
			
		||||
                          : L10n.of(context)!.newVerificationRequest)))
 | 
			
		||||
            ],
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'dart:io';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/foundation.dart';
 | 
			
		||||
 | 
			
		||||
@ -15,19 +15,19 @@ import 'command_hints.dart';
 | 
			
		||||
 | 
			
		||||
class InputBar extends StatelessWidget {
 | 
			
		||||
  final Room room;
 | 
			
		||||
  final int minLines;
 | 
			
		||||
  final int maxLines;
 | 
			
		||||
  final TextInputType keyboardType;
 | 
			
		||||
  final TextInputAction textInputAction;
 | 
			
		||||
  final ValueChanged<String> onSubmitted;
 | 
			
		||||
  final FocusNode focusNode;
 | 
			
		||||
  final TextEditingController controller;
 | 
			
		||||
  final InputDecoration decoration;
 | 
			
		||||
  final ValueChanged<String> onChanged;
 | 
			
		||||
  final bool autofocus;
 | 
			
		||||
  final int? minLines;
 | 
			
		||||
  final int? maxLines;
 | 
			
		||||
  final TextInputType? keyboardType;
 | 
			
		||||
  final TextInputAction? textInputAction;
 | 
			
		||||
  final ValueChanged<String>? onSubmitted;
 | 
			
		||||
  final FocusNode? focusNode;
 | 
			
		||||
  final TextEditingController? controller;
 | 
			
		||||
  final InputDecoration? decoration;
 | 
			
		||||
  final ValueChanged<String>? onChanged;
 | 
			
		||||
  final bool? autofocus;
 | 
			
		||||
 | 
			
		||||
  const InputBar({
 | 
			
		||||
    this.room,
 | 
			
		||||
    required this.room,
 | 
			
		||||
    this.minLines,
 | 
			
		||||
    this.maxLines,
 | 
			
		||||
    this.keyboardType,
 | 
			
		||||
@ -38,22 +38,23 @@ class InputBar extends StatelessWidget {
 | 
			
		||||
    this.onChanged,
 | 
			
		||||
    this.autofocus,
 | 
			
		||||
    this.textInputAction,
 | 
			
		||||
    Key key,
 | 
			
		||||
    Key? key,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  List<Map<String, String>> getSuggestions(String text) {
 | 
			
		||||
    if (controller.selection.baseOffset != controller.selection.extentOffset ||
 | 
			
		||||
        controller.selection.baseOffset < 0) {
 | 
			
		||||
  List<Map<String, String?>> getSuggestions(String text) {
 | 
			
		||||
    if (controller!.selection.baseOffset !=
 | 
			
		||||
            controller!.selection.extentOffset ||
 | 
			
		||||
        controller!.selection.baseOffset < 0) {
 | 
			
		||||
      return []; // no entries if there is selected text
 | 
			
		||||
    }
 | 
			
		||||
    final searchText =
 | 
			
		||||
        controller.text.substring(0, controller.selection.baseOffset);
 | 
			
		||||
    final ret = <Map<String, String>>[];
 | 
			
		||||
        controller!.text.substring(0, controller!.selection.baseOffset);
 | 
			
		||||
    final List<Map<String, String?>> ret = <Map<String, String>>[];
 | 
			
		||||
    const maxResults = 30;
 | 
			
		||||
 | 
			
		||||
    final commandMatch = RegExp(r'^\/([\w]*)$').firstMatch(searchText);
 | 
			
		||||
    if (commandMatch != null) {
 | 
			
		||||
      final commandSearch = commandMatch[1].toLowerCase();
 | 
			
		||||
      final commandSearch = commandMatch[1]!.toLowerCase();
 | 
			
		||||
      for (final command in room.client.commands.keys) {
 | 
			
		||||
        if (command.contains(commandSearch)) {
 | 
			
		||||
          ret.add({
 | 
			
		||||
@ -69,7 +70,7 @@ class InputBar extends StatelessWidget {
 | 
			
		||||
        RegExp(r'(?:\s|^):(?:([-\w]+)~)?([-\w]+)$').firstMatch(searchText);
 | 
			
		||||
    if (emojiMatch != null) {
 | 
			
		||||
      final packSearch = emojiMatch[1];
 | 
			
		||||
      final emoteSearch = emojiMatch[2].toLowerCase();
 | 
			
		||||
      final emoteSearch = emojiMatch[2]!.toLowerCase();
 | 
			
		||||
      final emotePacks = room.getImagePacks(ImagePackUsage.emoticon);
 | 
			
		||||
      if (packSearch == null || packSearch.isEmpty) {
 | 
			
		||||
        for (final pack in emotePacks.entries) {
 | 
			
		||||
@ -93,16 +94,16 @@ class InputBar extends StatelessWidget {
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      } 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)) {
 | 
			
		||||
            ret.add({
 | 
			
		||||
              'type': 'emote',
 | 
			
		||||
              'name': emote.key,
 | 
			
		||||
              'pack': packSearch,
 | 
			
		||||
              'pack_avatar_url':
 | 
			
		||||
                  emotePacks[packSearch].pack.avatarUrl?.toString(),
 | 
			
		||||
                  emotePacks[packSearch]!.pack.avatarUrl?.toString(),
 | 
			
		||||
              'pack_display_name':
 | 
			
		||||
                  emotePacks[packSearch].pack.displayName ?? packSearch,
 | 
			
		||||
                  emotePacks[packSearch]!.pack.displayName ?? packSearch,
 | 
			
		||||
              'mxc': emote.value.url.toString(),
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
@ -114,11 +115,11 @@ class InputBar extends StatelessWidget {
 | 
			
		||||
    }
 | 
			
		||||
    final userMatch = RegExp(r'(?:\s|^)@([-\w]+)$').firstMatch(searchText);
 | 
			
		||||
    if (userMatch != null) {
 | 
			
		||||
      final userSearch = userMatch[1].toLowerCase();
 | 
			
		||||
      final userSearch = userMatch[1]!.toLowerCase();
 | 
			
		||||
      for (final user in room.getParticipants()) {
 | 
			
		||||
        if ((user.displayName != null &&
 | 
			
		||||
                (user.displayName.toLowerCase().contains(userSearch) ||
 | 
			
		||||
                    slugify(user.displayName.toLowerCase())
 | 
			
		||||
                (user.displayName!.toLowerCase().contains(userSearch) ||
 | 
			
		||||
                    slugify(user.displayName!.toLowerCase())
 | 
			
		||||
                        .contains(userSearch))) ||
 | 
			
		||||
            user.id.split(':')[0].toLowerCase().contains(userSearch)) {
 | 
			
		||||
          ret.add({
 | 
			
		||||
@ -136,7 +137,7 @@ class InputBar extends StatelessWidget {
 | 
			
		||||
    }
 | 
			
		||||
    final roomMatch = RegExp(r'(?:\s|^)#([-\w]+)$').firstMatch(searchText);
 | 
			
		||||
    if (roomMatch != null) {
 | 
			
		||||
      final roomSearch = roomMatch[1].toLowerCase();
 | 
			
		||||
      final roomSearch = roomMatch[1]!.toLowerCase();
 | 
			
		||||
      for (final r in room.client.rooms) {
 | 
			
		||||
        if (r.getState(EventTypes.RoomTombstone) != null) {
 | 
			
		||||
          continue; // we don't care about tombstoned rooms
 | 
			
		||||
@ -155,12 +156,10 @@ class InputBar extends StatelessWidget {
 | 
			
		||||
                                .split(':')[0]
 | 
			
		||||
                                .toLowerCase()
 | 
			
		||||
                                .contains(roomSearch))))) ||
 | 
			
		||||
            (r.name != null && r.name.toLowerCase().contains(roomSearch))) {
 | 
			
		||||
            (r.name.toLowerCase().contains(roomSearch))) {
 | 
			
		||||
          ret.add({
 | 
			
		||||
            'type': 'room',
 | 
			
		||||
            'mxid': (r.canonicalAlias != null && r.canonicalAlias.isNotEmpty)
 | 
			
		||||
                ? r.canonicalAlias
 | 
			
		||||
                : r.id,
 | 
			
		||||
            'mxid': (r.canonicalAlias.isNotEmpty) ? r.canonicalAlias : r.id,
 | 
			
		||||
            'displayname': r.displayname,
 | 
			
		||||
            'avatar_url': r.avatar?.toString(),
 | 
			
		||||
          });
 | 
			
		||||
@ -175,14 +174,14 @@ class InputBar extends StatelessWidget {
 | 
			
		||||
 | 
			
		||||
  Widget buildSuggestion(
 | 
			
		||||
    BuildContext context,
 | 
			
		||||
    Map<String, String> suggestion,
 | 
			
		||||
    Client client,
 | 
			
		||||
    Map<String, String?> suggestion,
 | 
			
		||||
    Client? client,
 | 
			
		||||
  ) {
 | 
			
		||||
    const size = 30.0;
 | 
			
		||||
    const padding = EdgeInsets.all(4.0);
 | 
			
		||||
    if (suggestion['type'] == 'command') {
 | 
			
		||||
      final command = suggestion['name'];
 | 
			
		||||
      final hint = commandHint(L10n.of(context), command);
 | 
			
		||||
      final command = suggestion['name']!;
 | 
			
		||||
      final hint = commandHint(L10n.of(context)!, command);
 | 
			
		||||
      return Tooltip(
 | 
			
		||||
        message: hint,
 | 
			
		||||
        waitDuration: const Duration(days: 1), // don't show on hover
 | 
			
		||||
@ -206,7 +205,7 @@ class InputBar extends StatelessWidget {
 | 
			
		||||
    }
 | 
			
		||||
    if (suggestion['type'] == 'emote') {
 | 
			
		||||
      final ratio = MediaQuery.of(context).devicePixelRatio;
 | 
			
		||||
      final url = Uri.parse(suggestion['mxc'] ?? '')?.getThumbnail(
 | 
			
		||||
      final url = Uri.parse(suggestion['mxc'] ?? '').getThumbnail(
 | 
			
		||||
        room.client,
 | 
			
		||||
        width: size * ratio,
 | 
			
		||||
        height: size * ratio,
 | 
			
		||||
@ -224,7 +223,7 @@ class InputBar extends StatelessWidget {
 | 
			
		||||
              height: size,
 | 
			
		||||
            ),
 | 
			
		||||
            const SizedBox(width: 6),
 | 
			
		||||
            Text(suggestion['name']),
 | 
			
		||||
            Text(suggestion['name']!),
 | 
			
		||||
            Expanded(
 | 
			
		||||
              child: Align(
 | 
			
		||||
                alignment: Alignment.centerRight,
 | 
			
		||||
@ -239,7 +238,7 @@ class InputBar extends StatelessWidget {
 | 
			
		||||
                          size: size * 0.9,
 | 
			
		||||
                          client: client,
 | 
			
		||||
                        )
 | 
			
		||||
                      : Text(suggestion['pack_display_name']),
 | 
			
		||||
                      : Text(suggestion['pack_display_name']!),
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
@ -262,7 +261,7 @@ class InputBar extends StatelessWidget {
 | 
			
		||||
              client: client,
 | 
			
		||||
            ),
 | 
			
		||||
            const SizedBox(width: 6),
 | 
			
		||||
            Text(suggestion['displayname'] ?? suggestion['mxid']),
 | 
			
		||||
            Text(suggestion['displayname'] ?? suggestion['mxid']!),
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
@ -270,16 +269,16 @@ class InputBar extends StatelessWidget {
 | 
			
		||||
    return Container();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void insertSuggestion(_, Map<String, String> suggestion) {
 | 
			
		||||
  void insertSuggestion(_, Map<String, String?> suggestion) {
 | 
			
		||||
    final replaceText =
 | 
			
		||||
        controller.text.substring(0, controller.selection.baseOffset);
 | 
			
		||||
        controller!.text.substring(0, controller!.selection.baseOffset);
 | 
			
		||||
    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 = '';
 | 
			
		||||
    if (suggestion['type'] == 'command') {
 | 
			
		||||
      insertText = suggestion['name'] + ' ';
 | 
			
		||||
      insertText = suggestion['name']! + ' ';
 | 
			
		||||
      startText = replaceText.replaceAllMapped(
 | 
			
		||||
        RegExp(r'^(\/[\w]*)$'),
 | 
			
		||||
        (Match m) => '/' + insertText,
 | 
			
		||||
@ -304,29 +303,29 @@ class InputBar extends StatelessWidget {
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      insertText = ':${isUnique ? '' : insertPack + '~'}$insertEmote: ';
 | 
			
		||||
      insertText = ':${isUnique ? '' : insertPack! + '~'}$insertEmote: ';
 | 
			
		||||
      startText = replaceText.replaceAllMapped(
 | 
			
		||||
        RegExp(r'(\s|^)(:(?:[-\w]+~)?[-\w]+)$'),
 | 
			
		||||
        (Match m) => '${m[1]}$insertText',
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    if (suggestion['type'] == 'user') {
 | 
			
		||||
      insertText = suggestion['mention'] + ' ';
 | 
			
		||||
      insertText = suggestion['mention']! + ' ';
 | 
			
		||||
      startText = replaceText.replaceAllMapped(
 | 
			
		||||
        RegExp(r'(\s|^)(@[-\w]+)$'),
 | 
			
		||||
        (Match m) => '${m[1]}$insertText',
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    if (suggestion['type'] == 'room') {
 | 
			
		||||
      insertText = suggestion['mxid'] + ' ';
 | 
			
		||||
      insertText = suggestion['mxid']! + ' ';
 | 
			
		||||
      startText = replaceText.replaceAllMapped(
 | 
			
		||||
        RegExp(r'(\s|^)(#[-\w]+)$'),
 | 
			
		||||
        (Match m) => '${m[1]}$insertText',
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    if (insertText.isNotEmpty && startText.isNotEmpty) {
 | 
			
		||||
      controller.text = startText + afterText;
 | 
			
		||||
      controller.selection = TextSelection(
 | 
			
		||||
      controller!.text = startText + afterText;
 | 
			
		||||
      controller!.selection = TextSelection(
 | 
			
		||||
        baseOffset: startText.length,
 | 
			
		||||
        extentOffset: startText.length,
 | 
			
		||||
      );
 | 
			
		||||
@ -351,13 +350,13 @@ class InputBar extends StatelessWidget {
 | 
			
		||||
            ? {}
 | 
			
		||||
            : {
 | 
			
		||||
                NewLineIntent: CallbackAction(onInvoke: (i) {
 | 
			
		||||
                  final val = controller.value;
 | 
			
		||||
                  final val = controller!.value;
 | 
			
		||||
                  final selection = val.selection.start;
 | 
			
		||||
                  final messageWithoutNewLine =
 | 
			
		||||
                      controller.text.substring(0, val.selection.start) +
 | 
			
		||||
                      controller!.text.substring(0, val.selection.start) +
 | 
			
		||||
                          '\n' +
 | 
			
		||||
                          controller.text.substring(val.selection.end);
 | 
			
		||||
                  controller.value = TextEditingValue(
 | 
			
		||||
                          controller!.text.substring(val.selection.end);
 | 
			
		||||
                  controller!.value = TextEditingValue(
 | 
			
		||||
                    text: messageWithoutNewLine,
 | 
			
		||||
                    selection: TextSelection.fromPosition(
 | 
			
		||||
                      TextPosition(offset: selection + 1),
 | 
			
		||||
@ -366,11 +365,11 @@ class InputBar extends StatelessWidget {
 | 
			
		||||
                  return null;
 | 
			
		||||
                }),
 | 
			
		||||
                SubmitLineIntent: CallbackAction(onInvoke: (i) {
 | 
			
		||||
                  onSubmitted(controller.text);
 | 
			
		||||
                  onSubmitted!(controller!.text);
 | 
			
		||||
                  return null;
 | 
			
		||||
                }),
 | 
			
		||||
              },
 | 
			
		||||
        child: TypeAheadField<Map<String, String>>(
 | 
			
		||||
        child: TypeAheadField<Map<String, String?>>(
 | 
			
		||||
          direction: AxisDirection.up,
 | 
			
		||||
          hideOnEmpty: true,
 | 
			
		||||
          hideOnLoading: true,
 | 
			
		||||
@ -381,31 +380,31 @@ class InputBar extends StatelessWidget {
 | 
			
		||||
          textFieldConfiguration: TextFieldConfiguration(
 | 
			
		||||
            minLines: minLines,
 | 
			
		||||
            maxLines: maxLines,
 | 
			
		||||
            keyboardType: keyboardType,
 | 
			
		||||
            keyboardType: keyboardType!,
 | 
			
		||||
            textInputAction: textInputAction,
 | 
			
		||||
            autofocus: autofocus,
 | 
			
		||||
            autofocus: autofocus!,
 | 
			
		||||
            onSubmitted: (text) {
 | 
			
		||||
              // fix for library for now
 | 
			
		||||
              // it sets the types for the callback incorrectly
 | 
			
		||||
              onSubmitted(text);
 | 
			
		||||
              onSubmitted!(text);
 | 
			
		||||
            },
 | 
			
		||||
            //focusNode: focusNode,
 | 
			
		||||
            controller: controller,
 | 
			
		||||
            decoration: decoration,
 | 
			
		||||
            decoration: decoration!,
 | 
			
		||||
            focusNode: focusNode,
 | 
			
		||||
            onChanged: (text) {
 | 
			
		||||
              // fix for the library for now
 | 
			
		||||
              // it sets the types for the callback incorrectly
 | 
			
		||||
              onChanged(text);
 | 
			
		||||
              onChanged!(text);
 | 
			
		||||
            },
 | 
			
		||||
            textCapitalization: TextCapitalization.sentences,
 | 
			
		||||
          ),
 | 
			
		||||
          suggestionsCallback: getSuggestions,
 | 
			
		||||
          itemBuilder: (c, s) =>
 | 
			
		||||
              buildSuggestion(c, s, Matrix.of(context).client),
 | 
			
		||||
          onSuggestionSelected: (Map<String, String> suggestion) =>
 | 
			
		||||
          onSuggestionSelected: (Map<String, String?> suggestion) =>
 | 
			
		||||
              insertSuggestion(context, suggestion),
 | 
			
		||||
          errorBuilder: (BuildContext context, Object error) => Container(),
 | 
			
		||||
          errorBuilder: (BuildContext context, Object? error) => Container(),
 | 
			
		||||
          loadingBuilder: (BuildContext context) =>
 | 
			
		||||
              Container(), // fix loading briefly flickering a dark box
 | 
			
		||||
          noItemsFoundBuilder: (BuildContext context) =>
 | 
			
		||||
 | 
			
		||||
@ -8,14 +8,14 @@ import 'package:fluffychat/pages/chat/chat.dart';
 | 
			
		||||
 | 
			
		||||
class ReactionsPicker extends StatelessWidget {
 | 
			
		||||
  final ChatController controller;
 | 
			
		||||
  const ReactionsPicker(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const ReactionsPicker(this.controller, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    if (controller.showEmojiPicker) return Container();
 | 
			
		||||
    final display = controller.editEvent == null &&
 | 
			
		||||
        controller.replyEvent == null &&
 | 
			
		||||
        controller.room.canSendDefaultMessages &&
 | 
			
		||||
        controller.room!.canSendDefaultMessages &&
 | 
			
		||||
        controller.selectedEvents.isNotEmpty;
 | 
			
		||||
    return AnimatedContainer(
 | 
			
		||||
      duration: const Duration(milliseconds: 300),
 | 
			
		||||
@ -28,8 +28,9 @@ class ReactionsPicker extends StatelessWidget {
 | 
			
		||||
          }
 | 
			
		||||
          final emojis = List<String>.from(AppEmojis.emojis);
 | 
			
		||||
          final allReactionEvents = controller.selectedEvents.first
 | 
			
		||||
              .aggregatedEvents(controller.timeline, RelationshipTypes.reaction)
 | 
			
		||||
              ?.where((event) =>
 | 
			
		||||
              .aggregatedEvents(
 | 
			
		||||
                  controller.timeline!, RelationshipTypes.reaction)
 | 
			
		||||
              .where((event) =>
 | 
			
		||||
                  event.senderId == event.room.client.userID &&
 | 
			
		||||
                  event.type == 'm.reaction');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'dart:async';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/cupertino.dart';
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@ import 'events/reply_content.dart';
 | 
			
		||||
 | 
			
		||||
class ReplyDisplay extends StatelessWidget {
 | 
			
		||||
  final ChatController controller;
 | 
			
		||||
  const ReplyDisplay(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const ReplyDisplay(this.controller, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
@ -23,16 +23,16 @@ class ReplyDisplay extends StatelessWidget {
 | 
			
		||||
        child: Row(
 | 
			
		||||
          children: <Widget>[
 | 
			
		||||
            IconButton(
 | 
			
		||||
              tooltip: L10n.of(context).close,
 | 
			
		||||
              tooltip: L10n.of(context)!.close,
 | 
			
		||||
              icon: const Icon(Icons.close),
 | 
			
		||||
              onPressed: controller.cancelReplyEventAction,
 | 
			
		||||
            ),
 | 
			
		||||
            Expanded(
 | 
			
		||||
              child: controller.replyEvent != null
 | 
			
		||||
                  ? ReplyContent(controller.replyEvent,
 | 
			
		||||
                      timeline: controller.timeline)
 | 
			
		||||
                  ? ReplyContent(controller.replyEvent!,
 | 
			
		||||
                      timeline: controller.timeline!)
 | 
			
		||||
                  : _EditContent(controller.editEvent
 | 
			
		||||
                      ?.getDisplayEvent(controller.timeline)),
 | 
			
		||||
                      ?.getDisplayEvent(controller.timeline!)),
 | 
			
		||||
            ),
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
@ -42,7 +42,7 @@ class ReplyDisplay extends StatelessWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _EditContent extends StatelessWidget {
 | 
			
		||||
  final Event event;
 | 
			
		||||
  final Event? event;
 | 
			
		||||
 | 
			
		||||
  const _EditContent(this.event);
 | 
			
		||||
 | 
			
		||||
@ -60,7 +60,7 @@ class _EditContent extends StatelessWidget {
 | 
			
		||||
        Container(width: 15.0),
 | 
			
		||||
        Text(
 | 
			
		||||
          event?.getLocalizedBody(
 | 
			
		||||
                MatrixLocals(L10n.of(context)),
 | 
			
		||||
                MatrixLocals(L10n.of(context)!),
 | 
			
		||||
                withSenderNamePrefix: false,
 | 
			
		||||
                hideReply: true,
 | 
			
		||||
              ) ??
 | 
			
		||||
@ -68,7 +68,7 @@ class _EditContent extends StatelessWidget {
 | 
			
		||||
          overflow: TextOverflow.ellipsis,
 | 
			
		||||
          maxLines: 1,
 | 
			
		||||
          style: TextStyle(
 | 
			
		||||
            color: Theme.of(context).textTheme.bodyText2.color,
 | 
			
		||||
            color: Theme.of(context).textTheme.bodyText2!.color,
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ],
 | 
			
		||||
 | 
			
		||||
@ -8,12 +8,12 @@ import 'package:fluffychat/widgets/matrix.dart';
 | 
			
		||||
 | 
			
		||||
class SeenByRow extends StatelessWidget {
 | 
			
		||||
  final ChatController controller;
 | 
			
		||||
  const SeenByRow(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const SeenByRow(this.controller, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final seenByUsers = controller.room.getSeenByUsers(
 | 
			
		||||
      controller.timeline,
 | 
			
		||||
    final seenByUsers = controller.room!.getSeenByUsers(
 | 
			
		||||
      controller.timeline!,
 | 
			
		||||
      controller.filteredEvents,
 | 
			
		||||
      controller.unfolded,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
 | 
			
		||||
@ -15,8 +15,8 @@ class SendLocationDialog extends StatefulWidget {
 | 
			
		||||
  final Room room;
 | 
			
		||||
 | 
			
		||||
  const SendLocationDialog({
 | 
			
		||||
    this.room,
 | 
			
		||||
    Key key,
 | 
			
		||||
    required this.room,
 | 
			
		||||
    Key? key,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@ -27,8 +27,8 @@ class _SendLocationDialogState extends State<SendLocationDialog> {
 | 
			
		||||
  bool disabled = false;
 | 
			
		||||
  bool denied = false;
 | 
			
		||||
  bool isSending = false;
 | 
			
		||||
  Position position;
 | 
			
		||||
  Error error;
 | 
			
		||||
  Position? position;
 | 
			
		||||
  Object? error;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
@ -75,9 +75,9 @@ class _SendLocationDialogState extends State<SendLocationDialog> {
 | 
			
		||||
  void sendAction() async {
 | 
			
		||||
    setState(() => isSending = true);
 | 
			
		||||
    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 =
 | 
			
		||||
        'geo:${position.latitude},${position.longitude};u=${position.accuracy}';
 | 
			
		||||
        'geo:${position!.latitude},${position!.longitude};u=${position!.accuracy}';
 | 
			
		||||
    await showFutureLoadingDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      future: () => widget.room.sendLocation(body, uri),
 | 
			
		||||
@ -90,16 +90,16 @@ class _SendLocationDialogState extends State<SendLocationDialog> {
 | 
			
		||||
    Widget contentWidget;
 | 
			
		||||
    if (position != null) {
 | 
			
		||||
      contentWidget = MapBubble(
 | 
			
		||||
        latitude: position.latitude,
 | 
			
		||||
        longitude: position.longitude,
 | 
			
		||||
        latitude: position!.latitude,
 | 
			
		||||
        longitude: position!.longitude,
 | 
			
		||||
      );
 | 
			
		||||
    } else if (disabled) {
 | 
			
		||||
      contentWidget = Text(L10n.of(context).locationDisabledNotice);
 | 
			
		||||
      contentWidget = Text(L10n.of(context)!.locationDisabledNotice);
 | 
			
		||||
    } else if (denied) {
 | 
			
		||||
      contentWidget = Text(L10n.of(context).locationPermissionDeniedNotice);
 | 
			
		||||
      contentWidget = Text(L10n.of(context)!.locationPermissionDeniedNotice);
 | 
			
		||||
    } else if (error != null) {
 | 
			
		||||
      contentWidget =
 | 
			
		||||
          Text(L10n.of(context).errorObtainingLocation(error.toString()));
 | 
			
		||||
          Text(L10n.of(context)!.errorObtainingLocation(error.toString()));
 | 
			
		||||
    } else {
 | 
			
		||||
      contentWidget = Row(
 | 
			
		||||
        mainAxisSize: MainAxisSize.min,
 | 
			
		||||
@ -107,38 +107,38 @@ class _SendLocationDialogState extends State<SendLocationDialog> {
 | 
			
		||||
        children: [
 | 
			
		||||
          const CupertinoActivityIndicator(),
 | 
			
		||||
          const SizedBox(width: 12),
 | 
			
		||||
          Text(L10n.of(context).obtainingLocation),
 | 
			
		||||
          Text(L10n.of(context)!.obtainingLocation),
 | 
			
		||||
        ],
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    if (PlatformInfos.isCupertinoStyle) {
 | 
			
		||||
      return CupertinoAlertDialog(
 | 
			
		||||
        title: Text(L10n.of(context).shareLocation),
 | 
			
		||||
        title: Text(L10n.of(context)!.shareLocation),
 | 
			
		||||
        content: contentWidget,
 | 
			
		||||
        actions: [
 | 
			
		||||
          CupertinoDialogAction(
 | 
			
		||||
            onPressed: Navigator.of(context, rootNavigator: false).pop,
 | 
			
		||||
            child: Text(L10n.of(context).cancel),
 | 
			
		||||
            child: Text(L10n.of(context)!.cancel),
 | 
			
		||||
          ),
 | 
			
		||||
          CupertinoDialogAction(
 | 
			
		||||
            onPressed: isSending ? null : sendAction,
 | 
			
		||||
            child: Text(L10n.of(context).send),
 | 
			
		||||
            child: Text(L10n.of(context)!.send),
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    return AlertDialog(
 | 
			
		||||
      title: Text(L10n.of(context).shareLocation),
 | 
			
		||||
      title: Text(L10n.of(context)!.shareLocation),
 | 
			
		||||
      content: contentWidget,
 | 
			
		||||
      actions: [
 | 
			
		||||
        TextButton(
 | 
			
		||||
          onPressed: Navigator.of(context, rootNavigator: false).pop,
 | 
			
		||||
          child: Text(L10n.of(context).cancel),
 | 
			
		||||
          child: Text(L10n.of(context)!.cancel),
 | 
			
		||||
        ),
 | 
			
		||||
        if (position != null)
 | 
			
		||||
          TextButton(
 | 
			
		||||
            onPressed: isSending ? null : sendAction,
 | 
			
		||||
            child: Text(L10n.of(context).send),
 | 
			
		||||
            child: Text(L10n.of(context)!.send),
 | 
			
		||||
          ),
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@ -10,14 +10,14 @@ import 'events/image_bubble.dart';
 | 
			
		||||
class StickerPickerDialog extends StatefulWidget {
 | 
			
		||||
  final Room room;
 | 
			
		||||
 | 
			
		||||
  const StickerPickerDialog({this.room, Key key}) : super(key: key);
 | 
			
		||||
  const StickerPickerDialog({required this.room, Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  StickerPickerDialogState createState() => StickerPickerDialogState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class StickerPickerDialogState extends State<StickerPickerDialog> {
 | 
			
		||||
  String searchFilter;
 | 
			
		||||
  String? searchFilter;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
@ -26,14 +26,14 @@ class StickerPickerDialogState extends State<StickerPickerDialog> {
 | 
			
		||||
 | 
			
		||||
    // ignore: prefer_function_declarations_over_variables
 | 
			
		||||
    final _packBuilder = (BuildContext context, int packIndex) {
 | 
			
		||||
      final pack = stickerPacks[packSlugs[packIndex]];
 | 
			
		||||
      final pack = stickerPacks[packSlugs[packIndex]]!;
 | 
			
		||||
      final filteredImagePackImageEntried = pack.images.entries.toList();
 | 
			
		||||
      if (searchFilter?.isNotEmpty ?? false) {
 | 
			
		||||
        filteredImagePackImageEntried.removeWhere((e) =>
 | 
			
		||||
            !(e.key.toLowerCase().contains(searchFilter.toLowerCase()) ||
 | 
			
		||||
            !(e.key.toLowerCase().contains(searchFilter!.toLowerCase()) ||
 | 
			
		||||
                (e.value.body
 | 
			
		||||
                        ?.toLowerCase()
 | 
			
		||||
                        ?.contains(searchFilter.toLowerCase()) ??
 | 
			
		||||
                        .contains(searchFilter!.toLowerCase()) ??
 | 
			
		||||
                    false)));
 | 
			
		||||
      }
 | 
			
		||||
      final imageKeys =
 | 
			
		||||
@ -62,7 +62,7 @@ class StickerPickerDialogState extends State<StickerPickerDialog> {
 | 
			
		||||
            shrinkWrap: true,
 | 
			
		||||
            physics: const NeverScrollableScrollPhysics(),
 | 
			
		||||
            itemBuilder: (BuildContext context, int imageIndex) {
 | 
			
		||||
              final image = pack.images[imageKeys[imageIndex]];
 | 
			
		||||
              final image = pack.images[imageKeys[imageIndex]]!;
 | 
			
		||||
              final fakeEvent = Event.fromJson(<String, dynamic>{
 | 
			
		||||
                'type': EventTypes.Sticker,
 | 
			
		||||
                'content': <String, dynamic>{
 | 
			
		||||
@ -116,7 +116,7 @@ class StickerPickerDialogState extends State<StickerPickerDialog> {
 | 
			
		||||
              ),
 | 
			
		||||
              title: DefaultAppBarSearchField(
 | 
			
		||||
                autofocus: false,
 | 
			
		||||
                hintText: L10n.of(context).search,
 | 
			
		||||
                hintText: L10n.of(context)!.search,
 | 
			
		||||
                suffix: const Icon(Icons.search_outlined),
 | 
			
		||||
                onChanged: (s) => setState(() => searchFilter = s),
 | 
			
		||||
              ),
 | 
			
		||||
 | 
			
		||||
@ -7,11 +7,11 @@ import 'chat.dart';
 | 
			
		||||
 | 
			
		||||
class TombstoneDisplay extends StatelessWidget {
 | 
			
		||||
  final ChatController controller;
 | 
			
		||||
  const TombstoneDisplay(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const TombstoneDisplay(this.controller, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    if (controller.room.getState(EventTypes.RoomTombstone) == null) {
 | 
			
		||||
    if (controller.room!.getState(EventTypes.RoomTombstone) == null) {
 | 
			
		||||
      return Container();
 | 
			
		||||
    }
 | 
			
		||||
    return SizedBox(
 | 
			
		||||
@ -26,14 +26,14 @@ class TombstoneDisplay extends StatelessWidget {
 | 
			
		||||
            child: const Icon(Icons.upgrade_outlined),
 | 
			
		||||
          ),
 | 
			
		||||
          title: Text(
 | 
			
		||||
            controller.room
 | 
			
		||||
                .getState(EventTypes.RoomTombstone)
 | 
			
		||||
            controller.room!
 | 
			
		||||
                .getState(EventTypes.RoomTombstone)!
 | 
			
		||||
                .parsedTombstoneContent
 | 
			
		||||
                .body,
 | 
			
		||||
            maxLines: 1,
 | 
			
		||||
            overflow: TextOverflow.ellipsis,
 | 
			
		||||
          ),
 | 
			
		||||
          subtitle: Text(L10n.of(context).goToTheNewRoom),
 | 
			
		||||
          subtitle: Text(L10n.of(context)!.goToTheNewRoom),
 | 
			
		||||
          onTap: controller.goToNewRoomAction,
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
 | 
			
		||||
@ -8,11 +8,11 @@ import 'package:fluffychat/widgets/matrix.dart';
 | 
			
		||||
 | 
			
		||||
class TypingIndicators extends StatelessWidget {
 | 
			
		||||
  final ChatController controller;
 | 
			
		||||
  const TypingIndicators(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const TypingIndicators(this.controller, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final typingUsers = controller.room.typingUsers
 | 
			
		||||
    final typingUsers = controller.room!.typingUsers
 | 
			
		||||
      ..removeWhere((u) => u.stateKey == Matrix.of(context).client.userID);
 | 
			
		||||
    const topPadding = 20.0;
 | 
			
		||||
    const bottomPadding = 4.0;
 | 
			
		||||
 | 
			
		||||
@ -18,34 +18,34 @@ import 'package:fluffychat/widgets/matrix.dart';
 | 
			
		||||
enum AliasActions { copy, delete, setCanonical }
 | 
			
		||||
 | 
			
		||||
class ChatDetails extends StatefulWidget {
 | 
			
		||||
  const ChatDetails({Key key}) : super(key: key);
 | 
			
		||||
  const ChatDetails({Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  ChatDetailsController createState() => ChatDetailsController();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ChatDetailsController extends State<ChatDetails> {
 | 
			
		||||
  List<User> members;
 | 
			
		||||
  List<User>? members;
 | 
			
		||||
  bool displaySettings = false;
 | 
			
		||||
 | 
			
		||||
  void toggleDisplaySettings() =>
 | 
			
		||||
      setState(() => displaySettings = !displaySettings);
 | 
			
		||||
 | 
			
		||||
  String get roomId => VRouter.of(context).pathParameters['roomid'];
 | 
			
		||||
  String? get roomId => VRouter.of(context).pathParameters['roomid'];
 | 
			
		||||
 | 
			
		||||
  void setDisplaynameAction() async {
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(roomId);
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(roomId!)!;
 | 
			
		||||
    final input = await showTextInputDialog(
 | 
			
		||||
      useRootNavigator: false,
 | 
			
		||||
      context: context,
 | 
			
		||||
      title: L10n.of(context).changeTheNameOfTheGroup,
 | 
			
		||||
      okLabel: L10n.of(context).ok,
 | 
			
		||||
      cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
      title: L10n.of(context)!.changeTheNameOfTheGroup,
 | 
			
		||||
      okLabel: L10n.of(context)!.ok,
 | 
			
		||||
      cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
      textFields: [
 | 
			
		||||
        DialogTextField(
 | 
			
		||||
          initialText: room.getLocalizedDisplayname(
 | 
			
		||||
            MatrixLocals(
 | 
			
		||||
              L10n.of(context),
 | 
			
		||||
              L10n.of(context)!,
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        )
 | 
			
		||||
@ -58,12 +58,12 @@ class ChatDetailsController extends State<ChatDetails> {
 | 
			
		||||
    );
 | 
			
		||||
    if (success.error == null) {
 | 
			
		||||
      ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
          SnackBar(content: Text(L10n.of(context).displaynameHasBeenChanged)));
 | 
			
		||||
          SnackBar(content: Text(L10n.of(context)!.displaynameHasBeenChanged)));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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
 | 
			
		||||
    // 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:
 | 
			
		||||
    final aliases = await showFutureLoadingDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      future: () => room.client
 | 
			
		||||
      future: () => room!.client
 | 
			
		||||
          .request(
 | 
			
		||||
            RequestType.GET,
 | 
			
		||||
            '/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.
 | 
			
		||||
 | 
			
		||||
    if (aliases.error != null) return;
 | 
			
		||||
    final adminMode = room.canSendEvent('m.room.canonical_alias');
 | 
			
		||||
    if (aliases.result.isEmpty && (room.canonicalAlias?.isNotEmpty ?? false)) {
 | 
			
		||||
      aliases.result.add(room.canonicalAlias);
 | 
			
		||||
    final adminMode = room!.canSendEvent('m.room.canonical_alias');
 | 
			
		||||
    if (aliases.result!.isEmpty && (room.canonicalAlias.isNotEmpty)) {
 | 
			
		||||
      aliases.result!.add(room.canonicalAlias);
 | 
			
		||||
    }
 | 
			
		||||
    if (aliases.result.isEmpty && adminMode) {
 | 
			
		||||
    if (aliases.result!.isEmpty && adminMode) {
 | 
			
		||||
      return setAliasAction();
 | 
			
		||||
    }
 | 
			
		||||
    final select = await showConfirmationDialog(
 | 
			
		||||
      useRootNavigator: false,
 | 
			
		||||
      context: context,
 | 
			
		||||
      title: L10n.of(context).editRoomAliases,
 | 
			
		||||
      title: L10n.of(context)!.editRoomAliases,
 | 
			
		||||
      actions: [
 | 
			
		||||
        if (adminMode)
 | 
			
		||||
          AlertDialogAction(label: L10n.of(context).create, key: 'new'),
 | 
			
		||||
        ...aliases.result
 | 
			
		||||
          AlertDialogAction(label: L10n.of(context)!.create, key: 'new'),
 | 
			
		||||
        ...aliases.result!
 | 
			
		||||
            .map((alias) => AlertDialogAction(key: alias, label: alias))
 | 
			
		||||
            .toList(),
 | 
			
		||||
      ],
 | 
			
		||||
@ -114,29 +114,30 @@ class ChatDetailsController extends State<ChatDetails> {
 | 
			
		||||
      title: select,
 | 
			
		||||
      actions: [
 | 
			
		||||
        AlertDialogAction(
 | 
			
		||||
          label: L10n.of(context).copyToClipboard,
 | 
			
		||||
          label: L10n.of(context)!.copyToClipboard,
 | 
			
		||||
          key: AliasActions.copy,
 | 
			
		||||
          isDefaultAction: true,
 | 
			
		||||
        ),
 | 
			
		||||
        if (adminMode) ...{
 | 
			
		||||
          AlertDialogAction(
 | 
			
		||||
            label: L10n.of(context).setAsCanonicalAlias,
 | 
			
		||||
            label: L10n.of(context)!.setAsCanonicalAlias,
 | 
			
		||||
            key: AliasActions.setCanonical,
 | 
			
		||||
            isDestructiveAction: true,
 | 
			
		||||
          ),
 | 
			
		||||
          AlertDialogAction(
 | 
			
		||||
            label: L10n.of(context).delete,
 | 
			
		||||
            label: L10n.of(context)!.delete,
 | 
			
		||||
            key: AliasActions.delete,
 | 
			
		||||
            isDestructiveAction: true,
 | 
			
		||||
          ),
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
    if (option == null) return;
 | 
			
		||||
    switch (option) {
 | 
			
		||||
      case AliasActions.copy:
 | 
			
		||||
        await Clipboard.setData(ClipboardData(text: select));
 | 
			
		||||
        ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
          SnackBar(content: Text(L10n.of(context).copiedToClipboard)),
 | 
			
		||||
          SnackBar(content: Text(L10n.of(context)!.copiedToClipboard)),
 | 
			
		||||
        );
 | 
			
		||||
        break;
 | 
			
		||||
      case AliasActions.delete:
 | 
			
		||||
@ -162,21 +163,21 @@ class ChatDetailsController extends State<ChatDetails> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setAliasAction() async {
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(roomId);
 | 
			
		||||
    final domain = room.client.userID.domain;
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(roomId!)!;
 | 
			
		||||
    final domain = room.client.userID!.domain;
 | 
			
		||||
 | 
			
		||||
    final input = await showTextInputDialog(
 | 
			
		||||
      useRootNavigator: false,
 | 
			
		||||
      context: context,
 | 
			
		||||
      title: L10n.of(context).setInvitationLink,
 | 
			
		||||
      okLabel: L10n.of(context).ok,
 | 
			
		||||
      cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
      title: L10n.of(context)!.setInvitationLink,
 | 
			
		||||
      okLabel: L10n.of(context)!.ok,
 | 
			
		||||
      cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
      textFields: [
 | 
			
		||||
        DialogTextField(
 | 
			
		||||
          prefixText: '#',
 | 
			
		||||
          suffixText: domain,
 | 
			
		||||
          hintText: L10n.of(context).alias,
 | 
			
		||||
          initialText: room.canonicalAlias?.localpart,
 | 
			
		||||
          hintText: L10n.of(context)!.alias,
 | 
			
		||||
          initialText: room.canonicalAlias.localpart,
 | 
			
		||||
        )
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
@ -184,21 +185,21 @@ class ChatDetailsController extends State<ChatDetails> {
 | 
			
		||||
    await showFutureLoadingDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      future: () =>
 | 
			
		||||
          room.client.setRoomAlias('#' + input.single + ':' + domain, room.id),
 | 
			
		||||
          room.client.setRoomAlias('#' + input.single + ':' + domain!, room.id),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setTopicAction() async {
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(roomId);
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(roomId!)!;
 | 
			
		||||
    final input = await showTextInputDialog(
 | 
			
		||||
      useRootNavigator: false,
 | 
			
		||||
      context: context,
 | 
			
		||||
      title: L10n.of(context).setGroupDescription,
 | 
			
		||||
      okLabel: L10n.of(context).ok,
 | 
			
		||||
      cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
      title: L10n.of(context)!.setGroupDescription,
 | 
			
		||||
      okLabel: L10n.of(context)!.ok,
 | 
			
		||||
      cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
      textFields: [
 | 
			
		||||
        DialogTextField(
 | 
			
		||||
          hintText: L10n.of(context).setGroupDescription,
 | 
			
		||||
          hintText: L10n.of(context)!.setGroupDescription,
 | 
			
		||||
          initialText: room.topic,
 | 
			
		||||
          minLines: 1,
 | 
			
		||||
          maxLines: 4,
 | 
			
		||||
@ -212,7 +213,7 @@ class ChatDetailsController extends State<ChatDetails> {
 | 
			
		||||
    );
 | 
			
		||||
    if (success.error == null) {
 | 
			
		||||
      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,
 | 
			
		||||
        future: () => Matrix.of(context)
 | 
			
		||||
            .client
 | 
			
		||||
            .getRoomById(roomId)
 | 
			
		||||
            .getRoomById(roomId!)!
 | 
			
		||||
            .setGuestAccess(guestAccess),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
@ -229,7 +230,7 @@ class ChatDetailsController extends State<ChatDetails> {
 | 
			
		||||
        context: context,
 | 
			
		||||
        future: () => Matrix.of(context)
 | 
			
		||||
            .client
 | 
			
		||||
            .getRoomById(roomId)
 | 
			
		||||
            .getRoomById(roomId!)!
 | 
			
		||||
            .setHistoryVisibility(historyVisibility),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
@ -237,12 +238,12 @@ class ChatDetailsController extends State<ChatDetails> {
 | 
			
		||||
        context: context,
 | 
			
		||||
        future: () => Matrix.of(context)
 | 
			
		||||
            .client
 | 
			
		||||
            .getRoomById(roomId)
 | 
			
		||||
            .getRoomById(roomId!)!
 | 
			
		||||
            .setJoinRules(joinRule),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
  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
 | 
			
		||||
    // 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.
 | 
			
		||||
@ -256,24 +257,24 @@ class ChatDetailsController extends State<ChatDetails> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setAvatarAction() async {
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(roomId);
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(roomId!);
 | 
			
		||||
    final actions = [
 | 
			
		||||
      if (PlatformInfos.isMobile)
 | 
			
		||||
        SheetAction(
 | 
			
		||||
          key: AvatarAction.camera,
 | 
			
		||||
          label: L10n.of(context).openCamera,
 | 
			
		||||
          label: L10n.of(context)!.openCamera,
 | 
			
		||||
          isDefaultAction: true,
 | 
			
		||||
          icon: Icons.camera_alt_outlined,
 | 
			
		||||
        ),
 | 
			
		||||
      SheetAction(
 | 
			
		||||
        key: AvatarAction.file,
 | 
			
		||||
        label: L10n.of(context).openGallery,
 | 
			
		||||
        label: L10n.of(context)!.openGallery,
 | 
			
		||||
        icon: Icons.photo_outlined,
 | 
			
		||||
      ),
 | 
			
		||||
      if (room?.avatar != null)
 | 
			
		||||
        SheetAction(
 | 
			
		||||
          key: AvatarAction.remove,
 | 
			
		||||
          label: L10n.of(context).delete,
 | 
			
		||||
          label: L10n.of(context)!.delete,
 | 
			
		||||
          isDestructiveAction: true,
 | 
			
		||||
          icon: Icons.delete_outlined,
 | 
			
		||||
        ),
 | 
			
		||||
@ -282,14 +283,14 @@ class ChatDetailsController extends State<ChatDetails> {
 | 
			
		||||
        ? actions.single
 | 
			
		||||
        : await showModalActionSheet<AvatarAction>(
 | 
			
		||||
            context: context,
 | 
			
		||||
            title: L10n.of(context).editRoomAvatar,
 | 
			
		||||
            title: L10n.of(context)!.editRoomAvatar,
 | 
			
		||||
            actions: actions,
 | 
			
		||||
          );
 | 
			
		||||
    if (action == null) return;
 | 
			
		||||
    if (action == AvatarAction.remove) {
 | 
			
		||||
      await showFutureLoadingDialog(
 | 
			
		||||
        context: context,
 | 
			
		||||
        future: () => room.setAvatar(null),
 | 
			
		||||
        future: () => room!.setAvatar(null),
 | 
			
		||||
      );
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
@ -309,22 +310,22 @@ class ChatDetailsController extends State<ChatDetails> {
 | 
			
		||||
    } else {
 | 
			
		||||
      final result =
 | 
			
		||||
          await FilePickerCross.importFromStorage(type: FileTypeCross.image);
 | 
			
		||||
      if (result == null) return;
 | 
			
		||||
      if (result.fileName == null) return;
 | 
			
		||||
      file = MatrixFile(
 | 
			
		||||
        bytes: result.toUint8List(),
 | 
			
		||||
        name: result.fileName,
 | 
			
		||||
        name: result.fileName!,
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    await showFutureLoadingDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      future: () => room.setAvatar(file),
 | 
			
		||||
      future: () => room!.setAvatar(file),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void requestMoreMembersAction() async {
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(roomId);
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(roomId!);
 | 
			
		||||
    final participants = await showFutureLoadingDialog(
 | 
			
		||||
        context: context, future: () => room.requestParticipants());
 | 
			
		||||
        context: context, future: () => room!.requestParticipants());
 | 
			
		||||
    if (participants.error == null) {
 | 
			
		||||
      setState(() => members = participants.result);
 | 
			
		||||
    }
 | 
			
		||||
@ -334,7 +335,8 @@ class ChatDetailsController extends State<ChatDetails> {
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    members ??= Matrix.of(context).client.getRoomById(roomId).getParticipants();
 | 
			
		||||
    members ??=
 | 
			
		||||
        Matrix.of(context).client.getRoomById(roomId!)!.getParticipants();
 | 
			
		||||
    return SizedBox(
 | 
			
		||||
      width: fixedWidth,
 | 
			
		||||
      child: ChatDetailsView(this),
 | 
			
		||||
 | 
			
		||||
@ -20,28 +20,28 @@ import '../../utils/url_launcher.dart';
 | 
			
		||||
class ChatDetailsView extends StatelessWidget {
 | 
			
		||||
  final ChatDetailsController controller;
 | 
			
		||||
 | 
			
		||||
  const ChatDetailsView(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const ChatDetailsView(this.controller, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  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) {
 | 
			
		||||
      return Scaffold(
 | 
			
		||||
        appBar: AppBar(
 | 
			
		||||
          title: Text(L10n.of(context).oopsSomethingWentWrong),
 | 
			
		||||
          title: Text(L10n.of(context)!.oopsSomethingWentWrong),
 | 
			
		||||
        ),
 | 
			
		||||
        body: Center(
 | 
			
		||||
          child: Text(L10n.of(context).youAreNoLongerParticipatingInThisChat),
 | 
			
		||||
          child: Text(L10n.of(context)!.youAreNoLongerParticipatingInThisChat),
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    controller.members.removeWhere((u) => u.membership == Membership.leave);
 | 
			
		||||
    final actualMembersCount = (room.summary?.mInvitedMemberCount ?? 0) +
 | 
			
		||||
        (room.summary?.mJoinedMemberCount ?? 0);
 | 
			
		||||
    controller.members!.removeWhere((u) => u.membership == Membership.leave);
 | 
			
		||||
    final actualMembersCount = (room.summary.mInvitedMemberCount ?? 0) +
 | 
			
		||||
        (room.summary.mJoinedMemberCount ?? 0);
 | 
			
		||||
    final canRequestMoreMembers =
 | 
			
		||||
        controller.members.length < actualMembersCount;
 | 
			
		||||
    final iconColor = Theme.of(context).textTheme.bodyText1.color;
 | 
			
		||||
        controller.members!.length < actualMembersCount;
 | 
			
		||||
    final iconColor = Theme.of(context).textTheme.bodyText1!.color;
 | 
			
		||||
    return StreamBuilder(
 | 
			
		||||
        stream: room.onUpdate.stream,
 | 
			
		||||
        builder: (context, snapshot) {
 | 
			
		||||
@ -56,16 +56,16 @@ class ChatDetailsView extends StatelessWidget {
 | 
			
		||||
                        VRouter.of(context).path.startsWith('/spaces/')
 | 
			
		||||
                            ? VRouter.of(context).pop()
 | 
			
		||||
                            : VRouter.of(context)
 | 
			
		||||
                                .toSegments(['rooms', controller.roomId]),
 | 
			
		||||
                                .toSegments(['rooms', controller.roomId!]),
 | 
			
		||||
                  ),
 | 
			
		||||
                  elevation: Theme.of(context).appBarTheme.elevation,
 | 
			
		||||
                  expandedHeight: 300.0,
 | 
			
		||||
                  floating: true,
 | 
			
		||||
                  pinned: true,
 | 
			
		||||
                  actions: <Widget>[
 | 
			
		||||
                    if (room.canonicalAlias?.isNotEmpty ?? false)
 | 
			
		||||
                    if (room.canonicalAlias.isNotEmpty)
 | 
			
		||||
                      IconButton(
 | 
			
		||||
                        tooltip: L10n.of(context).share,
 | 
			
		||||
                        tooltip: L10n.of(context)!.share,
 | 
			
		||||
                        icon: Icon(Icons.adaptive.share_outlined),
 | 
			
		||||
                        onPressed: () => FluffyShare.share(
 | 
			
		||||
                            AppConfig.inviteLinkPrefix + room.canonicalAlias,
 | 
			
		||||
@ -75,16 +75,17 @@ class ChatDetailsView extends StatelessWidget {
 | 
			
		||||
                  ],
 | 
			
		||||
                  title: Text(
 | 
			
		||||
                      room.getLocalizedDisplayname(
 | 
			
		||||
                          MatrixLocals(L10n.of(context))),
 | 
			
		||||
                          MatrixLocals(L10n.of(context)!)),
 | 
			
		||||
                      style: TextStyle(
 | 
			
		||||
                          color: Theme.of(context)
 | 
			
		||||
                              .appBarTheme
 | 
			
		||||
                              .titleTextStyle
 | 
			
		||||
                              .titleTextStyle!
 | 
			
		||||
                              .color)),
 | 
			
		||||
                  backgroundColor:
 | 
			
		||||
                      Theme.of(context).appBarTheme.backgroundColor,
 | 
			
		||||
                  flexibleSpace: FlexibleSpaceBar(
 | 
			
		||||
                    background: ContentBanner(room.avatar,
 | 
			
		||||
                    background: ContentBanner(
 | 
			
		||||
                        mxContent: room.avatar,
 | 
			
		||||
                        onEdit: room.canSendEvent('m.room.avatar')
 | 
			
		||||
                            ? controller.setAvatarAction
 | 
			
		||||
                            : null),
 | 
			
		||||
@ -93,7 +94,7 @@ class ChatDetailsView extends StatelessWidget {
 | 
			
		||||
              ],
 | 
			
		||||
              body: MaxWidthBody(
 | 
			
		||||
                child: ListView.builder(
 | 
			
		||||
                  itemCount: controller.members.length +
 | 
			
		||||
                  itemCount: controller.members!.length +
 | 
			
		||||
                      1 +
 | 
			
		||||
                      (canRequestMoreMembers ? 1 : 0),
 | 
			
		||||
                  itemBuilder: (BuildContext context, int i) => i == 0
 | 
			
		||||
@ -111,15 +112,15 @@ class ChatDetailsView extends StatelessWidget {
 | 
			
		||||
                                    )
 | 
			
		||||
                                  : null,
 | 
			
		||||
                              title: Text(
 | 
			
		||||
                                  '${L10n.of(context).groupDescription}:',
 | 
			
		||||
                                  '${L10n.of(context)!.groupDescription}:',
 | 
			
		||||
                                  style: TextStyle(
 | 
			
		||||
                                      color: Theme.of(context)
 | 
			
		||||
                                          .colorScheme
 | 
			
		||||
                                          .secondary,
 | 
			
		||||
                                      fontWeight: FontWeight.bold)),
 | 
			
		||||
                              subtitle: LinkText(
 | 
			
		||||
                                text: room.topic?.isEmpty ?? true
 | 
			
		||||
                                    ? L10n.of(context).addGroupDescription
 | 
			
		||||
                                text: room.topic.isEmpty
 | 
			
		||||
                                    ? L10n.of(context)!.addGroupDescription
 | 
			
		||||
                                    : room.topic,
 | 
			
		||||
                                linkStyle:
 | 
			
		||||
                                    const TextStyle(color: Colors.blueAccent),
 | 
			
		||||
@ -127,7 +128,7 @@ class ChatDetailsView extends StatelessWidget {
 | 
			
		||||
                                  fontSize: 14,
 | 
			
		||||
                                  color: Theme.of(context)
 | 
			
		||||
                                      .textTheme
 | 
			
		||||
                                      .bodyText2
 | 
			
		||||
                                      .bodyText2!
 | 
			
		||||
                                      .color,
 | 
			
		||||
                                ),
 | 
			
		||||
                                onLinkTap: (url) =>
 | 
			
		||||
@ -141,7 +142,7 @@ class ChatDetailsView extends StatelessWidget {
 | 
			
		||||
                            const Divider(height: 1),
 | 
			
		||||
                            ListTile(
 | 
			
		||||
                              title: Text(
 | 
			
		||||
                                L10n.of(context).settings,
 | 
			
		||||
                                L10n.of(context)!.settings,
 | 
			
		||||
                                style: TextStyle(
 | 
			
		||||
                                  color:
 | 
			
		||||
                                      Theme.of(context).colorScheme.secondary,
 | 
			
		||||
@ -163,10 +164,10 @@ class ChatDetailsView extends StatelessWidget {
 | 
			
		||||
                                    child: const Icon(
 | 
			
		||||
                                        Icons.people_outline_outlined),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                  title: Text(
 | 
			
		||||
                                      L10n.of(context).changeTheNameOfTheGroup),
 | 
			
		||||
                                  title: Text(L10n.of(context)!
 | 
			
		||||
                                      .changeTheNameOfTheGroup),
 | 
			
		||||
                                  subtitle: Text(room.getLocalizedDisplayname(
 | 
			
		||||
                                      MatrixLocals(L10n.of(context)))),
 | 
			
		||||
                                      MatrixLocals(L10n.of(context)!))),
 | 
			
		||||
                                  onTap: controller.setDisplaynameAction,
 | 
			
		||||
                                ),
 | 
			
		||||
                              if (room.joinRules == JoinRules.public)
 | 
			
		||||
@ -178,11 +179,12 @@ class ChatDetailsView extends StatelessWidget {
 | 
			
		||||
                                    child: const Icon(Icons.link_outlined),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                  onTap: controller.editAliases,
 | 
			
		||||
                                  title: Text(L10n.of(context).editRoomAliases),
 | 
			
		||||
                                  title:
 | 
			
		||||
                                      Text(L10n.of(context)!.editRoomAliases),
 | 
			
		||||
                                  subtitle: Text(
 | 
			
		||||
                                      (room.canonicalAlias?.isNotEmpty ?? false)
 | 
			
		||||
                                      (room.canonicalAlias.isNotEmpty)
 | 
			
		||||
                                          ? room.canonicalAlias
 | 
			
		||||
                                          : L10n.of(context).none),
 | 
			
		||||
                                          : L10n.of(context)!.none),
 | 
			
		||||
                                ),
 | 
			
		||||
                              ListTile(
 | 
			
		||||
                                leading: CircleAvatar(
 | 
			
		||||
@ -192,9 +194,9 @@ class ChatDetailsView extends StatelessWidget {
 | 
			
		||||
                                  child: const Icon(
 | 
			
		||||
                                      Icons.insert_emoticon_outlined),
 | 
			
		||||
                                ),
 | 
			
		||||
                                title: Text(L10n.of(context).emoteSettings),
 | 
			
		||||
                                title: Text(L10n.of(context)!.emoteSettings),
 | 
			
		||||
                                subtitle:
 | 
			
		||||
                                    Text(L10n.of(context).setCustomEmotes),
 | 
			
		||||
                                    Text(L10n.of(context)!.setCustomEmotes),
 | 
			
		||||
                                onTap: controller.goToEmoteSettings,
 | 
			
		||||
                              ),
 | 
			
		||||
                              PopupMenuButton(
 | 
			
		||||
@ -206,14 +208,14 @@ class ChatDetailsView extends StatelessWidget {
 | 
			
		||||
                                      value: JoinRules.public,
 | 
			
		||||
                                      child: Text(JoinRules.public
 | 
			
		||||
                                          .getLocalizedString(
 | 
			
		||||
                                              MatrixLocals(L10n.of(context)))),
 | 
			
		||||
                                              MatrixLocals(L10n.of(context)!))),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                  if (room.canChangeJoinRules)
 | 
			
		||||
                                    PopupMenuItem<JoinRules>(
 | 
			
		||||
                                      value: JoinRules.invite,
 | 
			
		||||
                                      child: Text(JoinRules.invite
 | 
			
		||||
                                          .getLocalizedString(
 | 
			
		||||
                                              MatrixLocals(L10n.of(context)))),
 | 
			
		||||
                                              MatrixLocals(L10n.of(context)!))),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                ],
 | 
			
		||||
                                child: ListTile(
 | 
			
		||||
@ -222,11 +224,11 @@ class ChatDetailsView extends StatelessWidget {
 | 
			
		||||
                                          .scaffoldBackgroundColor,
 | 
			
		||||
                                      foregroundColor: iconColor,
 | 
			
		||||
                                      child: const Icon(Icons.shield_outlined)),
 | 
			
		||||
                                  title: Text(L10n.of(context)
 | 
			
		||||
                                  title: Text(L10n.of(context)!
 | 
			
		||||
                                      .whoIsAllowedToJoinThisGroup),
 | 
			
		||||
                                  subtitle: Text(
 | 
			
		||||
                                    room.joinRules.getLocalizedString(
 | 
			
		||||
                                        MatrixLocals(L10n.of(context))),
 | 
			
		||||
                                    room.joinRules!.getLocalizedString(
 | 
			
		||||
                                        MatrixLocals(L10n.of(context)!)),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
@ -240,21 +242,21 @@ class ChatDetailsView extends StatelessWidget {
 | 
			
		||||
                                      value: HistoryVisibility.invited,
 | 
			
		||||
                                      child: Text(HistoryVisibility.invited
 | 
			
		||||
                                          .getLocalizedString(
 | 
			
		||||
                                              MatrixLocals(L10n.of(context)))),
 | 
			
		||||
                                              MatrixLocals(L10n.of(context)!))),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                  if (room.canChangeHistoryVisibility)
 | 
			
		||||
                                    PopupMenuItem<HistoryVisibility>(
 | 
			
		||||
                                      value: HistoryVisibility.joined,
 | 
			
		||||
                                      child: Text(HistoryVisibility.joined
 | 
			
		||||
                                          .getLocalizedString(
 | 
			
		||||
                                              MatrixLocals(L10n.of(context)))),
 | 
			
		||||
                                              MatrixLocals(L10n.of(context)!))),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                  if (room.canChangeHistoryVisibility)
 | 
			
		||||
                                    PopupMenuItem<HistoryVisibility>(
 | 
			
		||||
                                      value: HistoryVisibility.shared,
 | 
			
		||||
                                      child: Text(HistoryVisibility.shared
 | 
			
		||||
                                          .getLocalizedString(
 | 
			
		||||
                                              MatrixLocals(L10n.of(context)))),
 | 
			
		||||
                                              MatrixLocals(L10n.of(context)!))),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                  if (room.canChangeHistoryVisibility)
 | 
			
		||||
                                    PopupMenuItem<HistoryVisibility>(
 | 
			
		||||
@ -262,7 +264,7 @@ class ChatDetailsView extends StatelessWidget {
 | 
			
		||||
                                      child: Text(HistoryVisibility
 | 
			
		||||
                                          .worldReadable
 | 
			
		||||
                                          .getLocalizedString(
 | 
			
		||||
                                              MatrixLocals(L10n.of(context)))),
 | 
			
		||||
                                              MatrixLocals(L10n.of(context)!))),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                ],
 | 
			
		||||
                                child: ListTile(
 | 
			
		||||
@ -273,12 +275,11 @@ class ChatDetailsView extends StatelessWidget {
 | 
			
		||||
                                    child:
 | 
			
		||||
                                        const Icon(Icons.visibility_outlined),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                  title: Text(L10n.of(context)
 | 
			
		||||
                                  title: Text(L10n.of(context)!
 | 
			
		||||
                                      .visibilityOfTheChatHistory),
 | 
			
		||||
                                  subtitle: Text(
 | 
			
		||||
                                    room.historyVisibility.getLocalizedString(
 | 
			
		||||
                                            MatrixLocals(L10n.of(context))) ??
 | 
			
		||||
                                        '',
 | 
			
		||||
                                    room.historyVisibility!.getLocalizedString(
 | 
			
		||||
                                        MatrixLocals(L10n.of(context)!)),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
@ -293,7 +294,7 @@ class ChatDetailsView extends StatelessWidget {
 | 
			
		||||
                                        child: Text(
 | 
			
		||||
                                          GuestAccess.canJoin
 | 
			
		||||
                                              .getLocalizedString(MatrixLocals(
 | 
			
		||||
                                                  L10n.of(context))),
 | 
			
		||||
                                                  L10n.of(context)!)),
 | 
			
		||||
                                        ),
 | 
			
		||||
                                      ),
 | 
			
		||||
                                    if (room.canChangeGuestAccess)
 | 
			
		||||
@ -302,7 +303,7 @@ class ChatDetailsView extends StatelessWidget {
 | 
			
		||||
                                        child: Text(
 | 
			
		||||
                                          GuestAccess.forbidden
 | 
			
		||||
                                              .getLocalizedString(MatrixLocals(
 | 
			
		||||
                                                  L10n.of(context))),
 | 
			
		||||
                                                  L10n.of(context)!)),
 | 
			
		||||
                                        ),
 | 
			
		||||
                                      ),
 | 
			
		||||
                                  ],
 | 
			
		||||
@ -314,19 +315,19 @@ class ChatDetailsView extends StatelessWidget {
 | 
			
		||||
                                      child: const Icon(
 | 
			
		||||
                                          Icons.person_add_alt_1_outlined),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                    title: Text(L10n.of(context)
 | 
			
		||||
                                    title: Text(L10n.of(context)!
 | 
			
		||||
                                        .areGuestsAllowedToJoin),
 | 
			
		||||
                                    subtitle: Text(
 | 
			
		||||
                                      room.guestAccess.getLocalizedString(
 | 
			
		||||
                                          MatrixLocals(L10n.of(context))),
 | 
			
		||||
                                          MatrixLocals(L10n.of(context)!)),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                ),
 | 
			
		||||
                              ListTile(
 | 
			
		||||
                                title:
 | 
			
		||||
                                    Text(L10n.of(context).editChatPermissions),
 | 
			
		||||
                                    Text(L10n.of(context)!.editChatPermissions),
 | 
			
		||||
                                subtitle: Text(
 | 
			
		||||
                                    L10n.of(context).whoCanPerformWhichAction),
 | 
			
		||||
                                    L10n.of(context)!.whoCanPerformWhichAction),
 | 
			
		||||
                                leading: CircleAvatar(
 | 
			
		||||
                                  backgroundColor:
 | 
			
		||||
                                      Theme.of(context).scaffoldBackgroundColor,
 | 
			
		||||
@ -342,9 +343,9 @@ class ChatDetailsView extends StatelessWidget {
 | 
			
		||||
                            ListTile(
 | 
			
		||||
                              title: Text(
 | 
			
		||||
                                actualMembersCount > 1
 | 
			
		||||
                                    ? L10n.of(context).countParticipants(
 | 
			
		||||
                                    ? L10n.of(context)!.countParticipants(
 | 
			
		||||
                                        actualMembersCount.toString())
 | 
			
		||||
                                    : L10n.of(context).emptyChat,
 | 
			
		||||
                                    : L10n.of(context)!.emptyChat,
 | 
			
		||||
                                style: TextStyle(
 | 
			
		||||
                                  color:
 | 
			
		||||
                                      Theme.of(context).colorScheme.secondary,
 | 
			
		||||
@ -354,7 +355,8 @@ class ChatDetailsView extends StatelessWidget {
 | 
			
		||||
                            ),
 | 
			
		||||
                            room.canInvite
 | 
			
		||||
                                ? ListTile(
 | 
			
		||||
                                    title: Text(L10n.of(context).inviteContact),
 | 
			
		||||
                                    title:
 | 
			
		||||
                                        Text(L10n.of(context)!.inviteContact),
 | 
			
		||||
                                    leading: CircleAvatar(
 | 
			
		||||
                                      backgroundColor:
 | 
			
		||||
                                          Theme.of(context).primaryColor,
 | 
			
		||||
@ -368,13 +370,13 @@ class ChatDetailsView extends StatelessWidget {
 | 
			
		||||
                                : Container(),
 | 
			
		||||
                          ],
 | 
			
		||||
                        )
 | 
			
		||||
                      : i < controller.members.length + 1
 | 
			
		||||
                          ? ParticipantListItem(controller.members[i - 1])
 | 
			
		||||
                      : i < controller.members!.length + 1
 | 
			
		||||
                          ? ParticipantListItem(controller.members![i - 1])
 | 
			
		||||
                          : ListTile(
 | 
			
		||||
                              title: Text(L10n.of(context)
 | 
			
		||||
                              title: Text(L10n.of(context)!
 | 
			
		||||
                                  .loadCountMoreParticipants(
 | 
			
		||||
                                      (actualMembersCount -
 | 
			
		||||
                                              controller.members.length)
 | 
			
		||||
                                              controller.members!.length)
 | 
			
		||||
                                          .toString())),
 | 
			
		||||
                              leading: CircleAvatar(
 | 
			
		||||
                                backgroundColor:
 | 
			
		||||
 | 
			
		||||
@ -9,20 +9,20 @@ import '../user_bottom_sheet/user_bottom_sheet.dart';
 | 
			
		||||
class ParticipantListItem extends StatelessWidget {
 | 
			
		||||
  final User user;
 | 
			
		||||
 | 
			
		||||
  const ParticipantListItem(this.user, {Key key}) : super(key: key);
 | 
			
		||||
  const ParticipantListItem(this.user, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final membershipBatch = <Membership, String>{
 | 
			
		||||
      Membership.join: '',
 | 
			
		||||
      Membership.ban: L10n.of(context).banned,
 | 
			
		||||
      Membership.invite: L10n.of(context).invited,
 | 
			
		||||
      Membership.leave: L10n.of(context).leftTheChat,
 | 
			
		||||
      Membership.ban: L10n.of(context)!.banned,
 | 
			
		||||
      Membership.invite: L10n.of(context)!.invited,
 | 
			
		||||
      Membership.leave: L10n.of(context)!.leftTheChat,
 | 
			
		||||
    };
 | 
			
		||||
    final permissionBatch = user.powerLevel == 100
 | 
			
		||||
        ? L10n.of(context).admin
 | 
			
		||||
        ? L10n.of(context)!.admin
 | 
			
		||||
        : user.powerLevel >= 50
 | 
			
		||||
            ? L10n.of(context).moderator
 | 
			
		||||
            ? L10n.of(context)!.moderator
 | 
			
		||||
            : '';
 | 
			
		||||
 | 
			
		||||
    return Opacity(
 | 
			
		||||
@ -49,7 +49,7 @@ class ParticipantListItem extends StatelessWidget {
 | 
			
		||||
                    ),
 | 
			
		||||
                    child: Center(child: Text(permissionBatch)),
 | 
			
		||||
                  ),
 | 
			
		||||
            membershipBatch[user.membership].isEmpty
 | 
			
		||||
            membershipBatch[user.membership]!.isEmpty
 | 
			
		||||
                ? Container()
 | 
			
		||||
                : Container(
 | 
			
		||||
                    padding: const EdgeInsets.all(4),
 | 
			
		||||
@ -59,7 +59,7 @@ class ParticipantListItem extends StatelessWidget {
 | 
			
		||||
                      borderRadius: BorderRadius.circular(8),
 | 
			
		||||
                    ),
 | 
			
		||||
                    child:
 | 
			
		||||
                        Center(child: Text(membershipBatch[user.membership])),
 | 
			
		||||
                        Center(child: Text(membershipBatch[user.membership]!)),
 | 
			
		||||
                  ),
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@ import 'package:fluffychat/widgets/matrix.dart';
 | 
			
		||||
import '../key_verification/key_verification_dialog.dart';
 | 
			
		||||
 | 
			
		||||
class ChatEncryptionSettings extends StatefulWidget {
 | 
			
		||||
  const ChatEncryptionSettings({Key key}) : super(key: key);
 | 
			
		||||
  const ChatEncryptionSettings({Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  ChatEncryptionSettingsController createState() =>
 | 
			
		||||
@ -17,7 +17,7 @@ class ChatEncryptionSettings extends StatefulWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
    if (key.blocked) {
 | 
			
		||||
@ -27,14 +27,14 @@ class ChatEncryptionSettingsController extends State<ChatEncryptionSettings> {
 | 
			
		||||
 | 
			
		||||
  Future<void> onSelected(
 | 
			
		||||
      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) {
 | 
			
		||||
      case 'verify':
 | 
			
		||||
        await unblock(key);
 | 
			
		||||
        final req = key.startVerification();
 | 
			
		||||
        req.onUpdate = () {
 | 
			
		||||
          if (req.state == KeyVerificationState.done) {
 | 
			
		||||
            setState(() => null);
 | 
			
		||||
            setState(() {});
 | 
			
		||||
          }
 | 
			
		||||
        };
 | 
			
		||||
        await KeyVerificationDialog(request: req).show(context);
 | 
			
		||||
@ -42,10 +42,10 @@ class ChatEncryptionSettingsController extends State<ChatEncryptionSettings> {
 | 
			
		||||
      case 'verify_user':
 | 
			
		||||
        await unblock(key);
 | 
			
		||||
        final req =
 | 
			
		||||
            await room.client.userDeviceKeys[key.userId].startVerification();
 | 
			
		||||
            await room!.client.userDeviceKeys[key.userId]!.startVerification();
 | 
			
		||||
        req.onUpdate = () {
 | 
			
		||||
          if (req.state == KeyVerificationState.done) {
 | 
			
		||||
            setState(() => null);
 | 
			
		||||
            setState(() {});
 | 
			
		||||
          }
 | 
			
		||||
        };
 | 
			
		||||
        await KeyVerificationDialog(request: req).show(context);
 | 
			
		||||
@ -55,11 +55,11 @@ class ChatEncryptionSettingsController extends State<ChatEncryptionSettings> {
 | 
			
		||||
          await key.setVerified(false);
 | 
			
		||||
        }
 | 
			
		||||
        await key.setBlocked(true);
 | 
			
		||||
        setState(() => null);
 | 
			
		||||
        setState(() {});
 | 
			
		||||
        break;
 | 
			
		||||
      case 'unblock':
 | 
			
		||||
        await unblock(key);
 | 
			
		||||
        setState(() => null);
 | 
			
		||||
        setState(() {});
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -13,21 +13,21 @@ import '../../utils/matrix_sdk_extensions.dart/device_extension.dart';
 | 
			
		||||
class ChatEncryptionSettingsView extends StatelessWidget {
 | 
			
		||||
  final ChatEncryptionSettingsController controller;
 | 
			
		||||
 | 
			
		||||
  const ChatEncryptionSettingsView(this.controller, {Key key})
 | 
			
		||||
  const ChatEncryptionSettingsView(this.controller, {Key? key})
 | 
			
		||||
      : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(controller.roomId);
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(controller.roomId!)!;
 | 
			
		||||
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        leading: IconButton(
 | 
			
		||||
          icon: const Icon(Icons.close_outlined),
 | 
			
		||||
          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,
 | 
			
		||||
      ),
 | 
			
		||||
      body: MaxWidthBody(
 | 
			
		||||
@ -36,7 +36,7 @@ class ChatEncryptionSettingsView extends StatelessWidget {
 | 
			
		||||
          mainAxisSize: MainAxisSize.min,
 | 
			
		||||
          children: [
 | 
			
		||||
            ListTile(
 | 
			
		||||
              title: Text(L10n.of(context).deviceVerifyDescription),
 | 
			
		||||
              title: Text(L10n.of(context)!.deviceVerifyDescription),
 | 
			
		||||
              leading: CircleAvatar(
 | 
			
		||||
                backgroundColor: Theme.of(context).secondaryHeaderColor,
 | 
			
		||||
                foregroundColor: Theme.of(context).colorScheme.secondary,
 | 
			
		||||
@ -52,7 +52,7 @@ class ChatEncryptionSettingsView extends StatelessWidget {
 | 
			
		||||
                    builder: (BuildContext context, snapshot) {
 | 
			
		||||
                      if (snapshot.hasError) {
 | 
			
		||||
                        return Center(
 | 
			
		||||
                          child: Text(L10n.of(context).oopsSomethingWentWrong +
 | 
			
		||||
                          child: Text(L10n.of(context)!.oopsSomethingWentWrong +
 | 
			
		||||
                              ': ' +
 | 
			
		||||
                              snapshot.error.toString()),
 | 
			
		||||
                        );
 | 
			
		||||
@ -62,7 +62,7 @@ class ChatEncryptionSettingsView extends StatelessWidget {
 | 
			
		||||
                            child: CircularProgressIndicator.adaptive(
 | 
			
		||||
                                strokeWidth: 2));
 | 
			
		||||
                      }
 | 
			
		||||
                      final deviceKeys = snapshot.data;
 | 
			
		||||
                      final deviceKeys = snapshot.data!;
 | 
			
		||||
                      return ListView.builder(
 | 
			
		||||
                        shrinkWrap: true,
 | 
			
		||||
                        physics: const NeverScrollableScrollPhysics(),
 | 
			
		||||
@ -75,18 +75,18 @@ class ChatEncryptionSettingsView extends StatelessWidget {
 | 
			
		||||
                                    deviceKeys[i - 1].userId) ...{
 | 
			
		||||
                              const Divider(height: 1, thickness: 1),
 | 
			
		||||
                              PopupMenuButton(
 | 
			
		||||
                                onSelected: (action) => controller.onSelected(
 | 
			
		||||
                                    context, action, deviceKeys[i]),
 | 
			
		||||
                                onSelected: (dynamic action) => controller
 | 
			
		||||
                                    .onSelected(context, action, deviceKeys[i]),
 | 
			
		||||
                                itemBuilder: (c) {
 | 
			
		||||
                                  final items = <PopupMenuEntry<String>>[];
 | 
			
		||||
                                  if (room
 | 
			
		||||
                                          .client
 | 
			
		||||
                                          .userDeviceKeys[deviceKeys[i].userId]
 | 
			
		||||
                                          .userDeviceKeys[deviceKeys[i].userId]!
 | 
			
		||||
                                          .verified ==
 | 
			
		||||
                                      UserVerifiedStatus.unknown) {
 | 
			
		||||
                                    items.add(PopupMenuItem(
 | 
			
		||||
                                      value: 'verify_user',
 | 
			
		||||
                                      child: Text(L10n.of(context).verifyUser),
 | 
			
		||||
                                      child: Text(L10n.of(context)!.verifyUser),
 | 
			
		||||
                                    ));
 | 
			
		||||
                                  }
 | 
			
		||||
                                  return items;
 | 
			
		||||
@ -114,8 +114,8 @@ class ChatEncryptionSettingsView extends StatelessWidget {
 | 
			
		||||
                              ),
 | 
			
		||||
                            },
 | 
			
		||||
                            PopupMenuButton(
 | 
			
		||||
                              onSelected: (action) => controller.onSelected(
 | 
			
		||||
                                  context, action, deviceKeys[i]),
 | 
			
		||||
                              onSelected: (dynamic action) => controller
 | 
			
		||||
                                  .onSelected(context, action, deviceKeys[i]),
 | 
			
		||||
                              itemBuilder: (c) {
 | 
			
		||||
                                final items = <PopupMenuEntry<String>>[];
 | 
			
		||||
                                if (deviceKeys[i].blocked ||
 | 
			
		||||
@ -125,19 +125,20 @@ class ChatEncryptionSettingsView extends StatelessWidget {
 | 
			
		||||
                                            room.client.userID
 | 
			
		||||
                                        ? 'verify'
 | 
			
		||||
                                        : 'verify_user',
 | 
			
		||||
                                    child: Text(L10n.of(context).verifyStart),
 | 
			
		||||
                                    child: Text(L10n.of(context)!.verifyStart),
 | 
			
		||||
                                  ));
 | 
			
		||||
                                }
 | 
			
		||||
                                if (deviceKeys[i].blocked) {
 | 
			
		||||
                                  items.add(PopupMenuItem(
 | 
			
		||||
                                    value: 'unblock',
 | 
			
		||||
                                    child: Text(L10n.of(context).unblockDevice),
 | 
			
		||||
                                    child:
 | 
			
		||||
                                        Text(L10n.of(context)!.unblockDevice),
 | 
			
		||||
                                  ));
 | 
			
		||||
                                }
 | 
			
		||||
                                if (!deviceKeys[i].blocked) {
 | 
			
		||||
                                  items.add(PopupMenuItem(
 | 
			
		||||
                                    value: 'block',
 | 
			
		||||
                                    child: Text(L10n.of(context).blockDevice),
 | 
			
		||||
                                    child: Text(L10n.of(context)!.blockDevice),
 | 
			
		||||
                                  ));
 | 
			
		||||
                                }
 | 
			
		||||
                                return items;
 | 
			
		||||
@ -156,17 +157,17 @@ class ChatEncryptionSettingsView extends StatelessWidget {
 | 
			
		||||
                                subtitle: Row(
 | 
			
		||||
                                  children: [
 | 
			
		||||
                                    Text(
 | 
			
		||||
                                      deviceKeys[i].deviceId,
 | 
			
		||||
                                      deviceKeys[i].deviceId!,
 | 
			
		||||
                                      style: const TextStyle(
 | 
			
		||||
                                          fontWeight: FontWeight.w300),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                    const Spacer(),
 | 
			
		||||
                                    Text(
 | 
			
		||||
                                      deviceKeys[i].blocked
 | 
			
		||||
                                          ? L10n.of(context).blocked
 | 
			
		||||
                                          ? L10n.of(context)!.blocked
 | 
			
		||||
                                          : deviceKeys[i].verified
 | 
			
		||||
                                              ? L10n.of(context).verified
 | 
			
		||||
                                              : L10n.of(context).unverified,
 | 
			
		||||
                                              ? L10n.of(context)!.verified
 | 
			
		||||
                                              : L10n.of(context)!.unverified,
 | 
			
		||||
                                      style: TextStyle(
 | 
			
		||||
                                        fontSize: 14,
 | 
			
		||||
                                        color: deviceKeys[i].color,
 | 
			
		||||
 | 
			
		||||
@ -34,25 +34,28 @@ enum PopupMenuAction {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ChatList extends StatefulWidget {
 | 
			
		||||
  const ChatList({Key key}) : super(key: key);
 | 
			
		||||
  const ChatList({Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  ChatListController createState() => ChatListController();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ChatListController extends State<ChatList> {
 | 
			
		||||
  StreamSubscription _intentDataStreamSubscription;
 | 
			
		||||
  StreamSubscription? _intentDataStreamSubscription;
 | 
			
		||||
 | 
			
		||||
  StreamSubscription _intentFileStreamSubscription;
 | 
			
		||||
  StreamSubscription? _intentFileStreamSubscription;
 | 
			
		||||
 | 
			
		||||
  StreamSubscription _intentUriStreamSubscription;
 | 
			
		||||
  StreamSubscription? _intentUriStreamSubscription;
 | 
			
		||||
 | 
			
		||||
  String _activeSpaceId;
 | 
			
		||||
  String? _activeSpaceId;
 | 
			
		||||
 | 
			
		||||
  String? get activeSpaceId {
 | 
			
		||||
    final id = _activeSpaceId;
 | 
			
		||||
    return id != null && Matrix.of(context).client.getRoomById(id) == null
 | 
			
		||||
        ? null
 | 
			
		||||
        : _activeSpaceId;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  String get activeSpaceId =>
 | 
			
		||||
      Matrix.of(context).client.getRoomById(_activeSpaceId) == null
 | 
			
		||||
          ? null
 | 
			
		||||
          : _activeSpaceId;
 | 
			
		||||
  final ScrollController scrollController = ScrollController();
 | 
			
		||||
  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);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -82,7 +85,7 @@ class ChatListController extends State<ChatList> {
 | 
			
		||||
      Matrix.of(context).client.rooms.where((r) => r.isSpace).toList();
 | 
			
		||||
 | 
			
		||||
  final selectedRoomIds = <String>{};
 | 
			
		||||
  bool crossSigningCached;
 | 
			
		||||
  bool? crossSigningCached;
 | 
			
		||||
  bool showChatBackupBanner = false;
 | 
			
		||||
 | 
			
		||||
  void firstRunBootstrapAction() async {
 | 
			
		||||
@ -96,7 +99,7 @@ class ChatListController extends State<ChatList> {
 | 
			
		||||
    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.share
 | 
			
		||||
@ -105,7 +108,7 @@ class ChatListController extends State<ChatList> {
 | 
			
		||||
          : SelectMode.select;
 | 
			
		||||
 | 
			
		||||
  void _processIncomingSharedFiles(List<SharedMediaFile> files) {
 | 
			
		||||
    if (files?.isEmpty ?? true) return;
 | 
			
		||||
    if (files.isEmpty) return;
 | 
			
		||||
    VRouter.of(context).to('/rooms');
 | 
			
		||||
    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;
 | 
			
		||||
    VRouter.of(context).to('/rooms');
 | 
			
		||||
    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;
 | 
			
		||||
    VRouter.of(context).to('/rooms');
 | 
			
		||||
    WidgetsBinding.instance.addPostFrameCallback((_) {
 | 
			
		||||
    WidgetsBinding.instance!.addPostFrameCallback((_) {
 | 
			
		||||
      UrlLauncher(context, text).openMatrixToUrl();
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
@ -180,10 +183,10 @@ class ChatListController extends State<ChatList> {
 | 
			
		||||
    await Matrix.of(context).client.accountDataLoading;
 | 
			
		||||
    await Matrix.of(context).client.userDeviceKeysLoading;
 | 
			
		||||
    final crossSigning =
 | 
			
		||||
        await Matrix.of(context).client.encryption?.crossSigning?.isCached() ??
 | 
			
		||||
        await Matrix.of(context).client.encryption?.crossSigning.isCached() ??
 | 
			
		||||
            false;
 | 
			
		||||
    final needsBootstrap =
 | 
			
		||||
        Matrix.of(context).client.encryption?.crossSigning?.enabled == false ||
 | 
			
		||||
        Matrix.of(context).client.encryption?.crossSigning.enabled == false ||
 | 
			
		||||
            crossSigning == false;
 | 
			
		||||
    final isUnknownSession = Matrix.of(context).client.isUnknownSession;
 | 
			
		||||
    if (needsBootstrap || isUnknownSession) {
 | 
			
		||||
@ -206,23 +209,21 @@ class ChatListController extends State<ChatList> {
 | 
			
		||||
    if (room.isSpace && room.membership == Membership.join && !room.isUnread) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    if (room.getState(EventTypes.RoomCreate)?.content?.tryGet<String>('type') ==
 | 
			
		||||
    if (room.getState(EventTypes.RoomCreate)?.content.tryGet<String>('type') ==
 | 
			
		||||
        ClientStoriesExtension.storiesRoomType) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    if (activeSpaceId != null) {
 | 
			
		||||
      final space = Matrix.of(context).client.getRoomById(activeSpaceId);
 | 
			
		||||
      if (space.spaceChildren?.any((child) => child.roomId == room.id) ??
 | 
			
		||||
          false) {
 | 
			
		||||
      final space = Matrix.of(context).client.getRoomById(activeSpaceId!)!;
 | 
			
		||||
      if (space.spaceChildren.any((child) => child.roomId == room.id)) {
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
      if (room.spaceParents?.any((parent) => parent.roomId == activeSpaceId) ??
 | 
			
		||||
          false) {
 | 
			
		||||
      if (room.spaceParents.any((parent) => parent.roomId == activeSpaceId)) {
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
      if (room.isDirectChat &&
 | 
			
		||||
          room.summary?.mHeroes != null &&
 | 
			
		||||
          room.summary.mHeroes.any((userId) {
 | 
			
		||||
          room.summary.mHeroes != null &&
 | 
			
		||||
          room.summary.mHeroes!.any((userId) {
 | 
			
		||||
            final user = space.getState(EventTypes.RoomMember, userId)?.asUser;
 | 
			
		||||
            return user != null && user.membership == Membership.join;
 | 
			
		||||
          })) {
 | 
			
		||||
@ -246,9 +247,9 @@ class ChatListController extends State<ChatList> {
 | 
			
		||||
        final markUnread = anySelectedRoomNotMarkedUnread;
 | 
			
		||||
        final client = Matrix.of(context).client;
 | 
			
		||||
        for (final roomId in selectedRoomIds) {
 | 
			
		||||
          final room = client.getRoomById(roomId);
 | 
			
		||||
          final room = client.getRoomById(roomId)!;
 | 
			
		||||
          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 client = Matrix.of(context).client;
 | 
			
		||||
        for (final roomId in selectedRoomIds) {
 | 
			
		||||
          final room = client.getRoomById(roomId);
 | 
			
		||||
          final room = client.getRoomById(roomId)!;
 | 
			
		||||
          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;
 | 
			
		||||
        final client = Matrix.of(context).client;
 | 
			
		||||
        for (final roomId in selectedRoomIds) {
 | 
			
		||||
          final room = client.getRoomById(roomId);
 | 
			
		||||
          final room = client.getRoomById(roomId)!;
 | 
			
		||||
          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(
 | 
			
		||||
          useRootNavigator: false,
 | 
			
		||||
          context: context,
 | 
			
		||||
          title: L10n.of(context).areYouSure,
 | 
			
		||||
          okLabel: L10n.of(context).yes,
 | 
			
		||||
          cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
          title: L10n.of(context)!.areYouSure,
 | 
			
		||||
          okLabel: L10n.of(context)!.yes,
 | 
			
		||||
          cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
        ) ==
 | 
			
		||||
        OkCancelResult.ok;
 | 
			
		||||
    if (!confirmed) return;
 | 
			
		||||
@ -303,26 +304,26 @@ class ChatListController extends State<ChatList> {
 | 
			
		||||
      context: context,
 | 
			
		||||
      future: () => _archiveSelectedRooms(),
 | 
			
		||||
    );
 | 
			
		||||
    setState(() => null);
 | 
			
		||||
    setState(() {});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setStatus() async {
 | 
			
		||||
    final input = await showTextInputDialog(
 | 
			
		||||
        useRootNavigator: false,
 | 
			
		||||
        context: context,
 | 
			
		||||
        title: L10n.of(context).setStatus,
 | 
			
		||||
        okLabel: L10n.of(context).ok,
 | 
			
		||||
        cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
        title: L10n.of(context)!.setStatus,
 | 
			
		||||
        okLabel: L10n.of(context)!.ok,
 | 
			
		||||
        cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
        textFields: [
 | 
			
		||||
          DialogTextField(
 | 
			
		||||
            hintText: L10n.of(context).statusExampleMessage,
 | 
			
		||||
            hintText: L10n.of(context)!.statusExampleMessage,
 | 
			
		||||
          ),
 | 
			
		||||
        ]);
 | 
			
		||||
    if (input == null) return;
 | 
			
		||||
    await showFutureLoadingDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      future: () => Matrix.of(context).client.setPresence(
 | 
			
		||||
            Matrix.of(context).client.userID,
 | 
			
		||||
            Matrix.of(context).client.userID!,
 | 
			
		||||
            PresenceType.online,
 | 
			
		||||
            statusMsg: input.single,
 | 
			
		||||
          ),
 | 
			
		||||
@ -339,7 +340,7 @@ class ChatListController extends State<ChatList> {
 | 
			
		||||
        break;
 | 
			
		||||
      case PopupMenuAction.invite:
 | 
			
		||||
        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'),
 | 
			
		||||
            context);
 | 
			
		||||
        break;
 | 
			
		||||
@ -360,7 +361,7 @@ class ChatListController extends State<ChatList> {
 | 
			
		||||
    while (selectedRoomIds.isNotEmpty) {
 | 
			
		||||
      final roomId = selectedRoomIds.first;
 | 
			
		||||
      try {
 | 
			
		||||
        await client.getRoomById(roomId).leave();
 | 
			
		||||
        await client.getRoomById(roomId)!.leave();
 | 
			
		||||
      } finally {
 | 
			
		||||
        toggleSelection(roomId);
 | 
			
		||||
      }
 | 
			
		||||
@ -371,36 +372,36 @@ class ChatListController extends State<ChatList> {
 | 
			
		||||
    if (activeSpaceId != null) {
 | 
			
		||||
      final consent = await showOkCancelAlertDialog(
 | 
			
		||||
        context: context,
 | 
			
		||||
        title: L10n.of(context).removeFromSpace,
 | 
			
		||||
        message: L10n.of(context).removeFromSpaceDescription,
 | 
			
		||||
        okLabel: L10n.of(context).remove,
 | 
			
		||||
        cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
        title: L10n.of(context)!.removeFromSpace,
 | 
			
		||||
        message: L10n.of(context)!.removeFromSpaceDescription,
 | 
			
		||||
        okLabel: L10n.of(context)!.remove,
 | 
			
		||||
        cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
        isDestructiveAction: true,
 | 
			
		||||
        fullyCapitalizedForMaterial: false,
 | 
			
		||||
      );
 | 
			
		||||
      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(
 | 
			
		||||
        context: context,
 | 
			
		||||
        future: () async {
 | 
			
		||||
          for (final roomId in selectedRoomIds) {
 | 
			
		||||
            await space.removeSpaceChild(roomId);
 | 
			
		||||
            await space!.removeSpaceChild(roomId);
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
      );
 | 
			
		||||
      if (result.error == null) {
 | 
			
		||||
        ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
          SnackBar(
 | 
			
		||||
            content: Text(L10n.of(context).chatHasBeenRemovedFromThisSpace),
 | 
			
		||||
            content: Text(L10n.of(context)!.chatHasBeenRemovedFromThisSpace),
 | 
			
		||||
          ),
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      final selectedSpace = await showConfirmationDialog<String>(
 | 
			
		||||
          context: context,
 | 
			
		||||
          title: L10n.of(context).addToSpace,
 | 
			
		||||
          message: L10n.of(context).addToSpaceDescription,
 | 
			
		||||
          title: L10n.of(context)!.addToSpace,
 | 
			
		||||
          message: L10n.of(context)!.addToSpaceDescription,
 | 
			
		||||
          fullyCapitalizedForMaterial: false,
 | 
			
		||||
          actions: Matrix.of(context)
 | 
			
		||||
              .client
 | 
			
		||||
@ -417,7 +418,7 @@ class ChatListController extends State<ChatList> {
 | 
			
		||||
      final result = await showFutureLoadingDialog(
 | 
			
		||||
        context: context,
 | 
			
		||||
        future: () async {
 | 
			
		||||
          final space = Matrix.of(context).client.getRoomById(selectedSpace);
 | 
			
		||||
          final space = Matrix.of(context).client.getRoomById(selectedSpace)!;
 | 
			
		||||
          if (space.canSendDefaultStates) {
 | 
			
		||||
            for (final roomId in selectedRoomIds) {
 | 
			
		||||
              await space.setSpaceChild(roomId);
 | 
			
		||||
@ -428,7 +429,7 @@ class ChatListController extends State<ChatList> {
 | 
			
		||||
      if (result.error == null) {
 | 
			
		||||
        ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
          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(
 | 
			
		||||
      (roomId) => !Matrix.of(context).client.getRoomById(roomId).markedUnread);
 | 
			
		||||
      (roomId) => !Matrix.of(context).client.getRoomById(roomId)!.markedUnread);
 | 
			
		||||
 | 
			
		||||
  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) =>
 | 
			
		||||
      Matrix.of(context).client.getRoomById(roomId).pushRuleState ==
 | 
			
		||||
      Matrix.of(context).client.getRoomById(roomId)!.pushRuleState ==
 | 
			
		||||
      PushRuleState.notify);
 | 
			
		||||
 | 
			
		||||
  bool waitForFirstSync = false;
 | 
			
		||||
@ -457,10 +458,10 @@ class ChatListController extends State<ChatList> {
 | 
			
		||||
    }
 | 
			
		||||
    // Load space members to display DM rooms
 | 
			
		||||
    if (activeSpaceId != null) {
 | 
			
		||||
      final space = client.getRoomById(activeSpaceId);
 | 
			
		||||
      final space = client.getRoomById(activeSpaceId!)!;
 | 
			
		||||
      final localMembers = space.getParticipants().length;
 | 
			
		||||
      final actualMembersCount = (space.summary?.mInvitedMemberCount ?? 0) +
 | 
			
		||||
          (space.summary?.mJoinedMemberCount ?? 0);
 | 
			
		||||
      final actualMembersCount = (space.summary.mInvitedMemberCount ?? 0) +
 | 
			
		||||
          (space.summary.mJoinedMemberCount ?? 0);
 | 
			
		||||
      if (localMembers < actualMembersCount) {
 | 
			
		||||
        await space.requestParticipants();
 | 
			
		||||
      }
 | 
			
		||||
@ -468,7 +469,7 @@ class ChatListController extends State<ChatList> {
 | 
			
		||||
    setState(() {
 | 
			
		||||
      waitForFirstSync = true;
 | 
			
		||||
    });
 | 
			
		||||
    WidgetsBinding.instance.addPostFrameCallback((_) => checkBootstrap());
 | 
			
		||||
    WidgetsBinding.instance!.addPostFrameCallback((_) => checkBootstrap());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -481,7 +482,6 @@ class ChatListController extends State<ChatList> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setActiveClient(Client client) {
 | 
			
		||||
    if (client == null) return;
 | 
			
		||||
    VRouter.of(context).to('/rooms');
 | 
			
		||||
    setState(() {
 | 
			
		||||
      _activeSpaceId = null;
 | 
			
		||||
@ -498,30 +498,30 @@ class ChatListController extends State<ChatList> {
 | 
			
		||||
      selectedRoomIds.clear();
 | 
			
		||||
      Matrix.of(context).activeBundle = bundle;
 | 
			
		||||
      if (!Matrix.of(context)
 | 
			
		||||
          .currentBundle
 | 
			
		||||
          .currentBundle!
 | 
			
		||||
          .any((client) => client == Matrix.of(context).client)) {
 | 
			
		||||
        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)
 | 
			
		||||
        .widget
 | 
			
		||||
        .clients[Matrix.of(context).getClientIndexByMatrixId(userId)];
 | 
			
		||||
        .clients[Matrix.of(context).getClientIndexByMatrixId(userId!)];
 | 
			
		||||
    final action = await showConfirmationDialog<EditBundleAction>(
 | 
			
		||||
      context: context,
 | 
			
		||||
      title: L10n.of(context).editBundlesForAccount,
 | 
			
		||||
      title: L10n.of(context)!.editBundlesForAccount,
 | 
			
		||||
      actions: [
 | 
			
		||||
        AlertDialogAction(
 | 
			
		||||
          key: EditBundleAction.addToBundle,
 | 
			
		||||
          label: L10n.of(context).addToBundle,
 | 
			
		||||
          label: L10n.of(context)!.addToBundle,
 | 
			
		||||
        ),
 | 
			
		||||
        if (activeBundle != client.userID)
 | 
			
		||||
          AlertDialogAction(
 | 
			
		||||
            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:
 | 
			
		||||
        final bundle = await showTextInputDialog(
 | 
			
		||||
            context: context,
 | 
			
		||||
            title: L10n.of(context).bundleName,
 | 
			
		||||
            title: L10n.of(context)!.bundleName,
 | 
			
		||||
            textFields: [
 | 
			
		||||
              DialogTextField(hintText: L10n.of(context).bundleName)
 | 
			
		||||
              DialogTextField(hintText: L10n.of(context)!.bundleName)
 | 
			
		||||
            ]);
 | 
			
		||||
        if (bundle == null || bundle.isEmpty || bundle.single.isEmpty) return;
 | 
			
		||||
        await showFutureLoadingDialog(
 | 
			
		||||
@ -543,7 +543,7 @@ class ChatListController extends State<ChatList> {
 | 
			
		||||
      case EditBundleAction.removeFromBundle:
 | 
			
		||||
        await showFutureLoadingDialog(
 | 
			
		||||
          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).accountBundles.keys.length > 1;
 | 
			
		||||
 | 
			
		||||
  String get secureActiveBundle {
 | 
			
		||||
  String? get secureActiveBundle {
 | 
			
		||||
    if (Matrix.of(context).activeBundle == null ||
 | 
			
		||||
        !Matrix.of(context)
 | 
			
		||||
            .accountBundles
 | 
			
		||||
@ -564,7 +564,7 @@ class ChatListController extends State<ChatList> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void resetActiveBundle() {
 | 
			
		||||
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
 | 
			
		||||
    WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        Matrix.of(context).activeBundle = null;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
@ -21,9 +21,9 @@ class ChatListItem extends StatelessWidget {
 | 
			
		||||
  final Room room;
 | 
			
		||||
  final bool activeChat;
 | 
			
		||||
  final bool selected;
 | 
			
		||||
  final Function onForget;
 | 
			
		||||
  final Function onTap;
 | 
			
		||||
  final Function onLongPress;
 | 
			
		||||
  final Function? onForget;
 | 
			
		||||
  final Function? onTap;
 | 
			
		||||
  final Function? onLongPress;
 | 
			
		||||
 | 
			
		||||
  const ChatListItem(
 | 
			
		||||
    this.room, {
 | 
			
		||||
@ -32,11 +32,11 @@ class ChatListItem extends StatelessWidget {
 | 
			
		||||
    this.onTap,
 | 
			
		||||
    this.onLongPress,
 | 
			
		||||
    this.onForget,
 | 
			
		||||
    Key key,
 | 
			
		||||
    Key? key,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  dynamic clickAction(BuildContext context) async {
 | 
			
		||||
    if (onTap != null) return onTap();
 | 
			
		||||
    if (onTap != null) return onTap!();
 | 
			
		||||
    if (!activeChat) {
 | 
			
		||||
      if (room.membership == Membership.invite &&
 | 
			
		||||
          (await showFutureLoadingDialog(
 | 
			
		||||
@ -57,7 +57,7 @@ class ChatListItem extends StatelessWidget {
 | 
			
		||||
      if (room.membership == Membership.ban) {
 | 
			
		||||
        ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
          SnackBar(
 | 
			
		||||
            content: Text(L10n.of(context).youHaveBeenBannedFromThisChat),
 | 
			
		||||
            content: Text(L10n.of(context)!.youHaveBeenBannedFromThisChat),
 | 
			
		||||
          ),
 | 
			
		||||
        );
 | 
			
		||||
        return;
 | 
			
		||||
@ -66,15 +66,15 @@ class ChatListItem extends StatelessWidget {
 | 
			
		||||
      if (room.membership == Membership.leave) {
 | 
			
		||||
        final action = await showModalActionSheet<ArchivedRoomAction>(
 | 
			
		||||
          context: context,
 | 
			
		||||
          title: L10n.of(context).archivedRoom,
 | 
			
		||||
          message: L10n.of(context).thisRoomHasBeenArchived,
 | 
			
		||||
          title: L10n.of(context)!.archivedRoom,
 | 
			
		||||
          message: L10n.of(context)!.thisRoomHasBeenArchived,
 | 
			
		||||
          actions: [
 | 
			
		||||
            SheetAction(
 | 
			
		||||
              label: L10n.of(context).rejoin,
 | 
			
		||||
              label: L10n.of(context)!.rejoin,
 | 
			
		||||
              key: ArchivedRoomAction.rejoin,
 | 
			
		||||
            ),
 | 
			
		||||
            SheetAction(
 | 
			
		||||
              label: L10n.of(context).delete,
 | 
			
		||||
              label: L10n.of(context)!.delete,
 | 
			
		||||
              key: ArchivedRoomAction.delete,
 | 
			
		||||
              isDestructiveAction: true,
 | 
			
		||||
            ),
 | 
			
		||||
@ -97,18 +97,18 @@ class ChatListItem extends StatelessWidget {
 | 
			
		||||
 | 
			
		||||
      if (room.membership == Membership.join) {
 | 
			
		||||
        if (Matrix.of(context).shareContent != null) {
 | 
			
		||||
          if (Matrix.of(context).shareContent['msgtype'] ==
 | 
			
		||||
          if (Matrix.of(context).shareContent!['msgtype'] ==
 | 
			
		||||
              'chat.fluffy.shared_file') {
 | 
			
		||||
            await showDialog(
 | 
			
		||||
              context: context,
 | 
			
		||||
              useRootNavigator: false,
 | 
			
		||||
              builder: (c) => SendFileDialog(
 | 
			
		||||
                file: Matrix.of(context).shareContent['file'],
 | 
			
		||||
                file: Matrix.of(context).shareContent!['file'],
 | 
			
		||||
                room: room,
 | 
			
		||||
              ),
 | 
			
		||||
            );
 | 
			
		||||
          } else {
 | 
			
		||||
            unawaited(room.sendEvent(Matrix.of(context).shareContent));
 | 
			
		||||
            unawaited(room.sendEvent(Matrix.of(context).shareContent!));
 | 
			
		||||
          }
 | 
			
		||||
          Matrix.of(context).shareContent = null;
 | 
			
		||||
        }
 | 
			
		||||
@ -125,16 +125,16 @@ class ChatListItem extends StatelessWidget {
 | 
			
		||||
          future: () => room.forget(),
 | 
			
		||||
        );
 | 
			
		||||
        if (success.error == null) {
 | 
			
		||||
          if (onForget != null) onForget();
 | 
			
		||||
          if (onForget != null) onForget!();
 | 
			
		||||
        }
 | 
			
		||||
        return success;
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      final confirmed = await showOkCancelAlertDialog(
 | 
			
		||||
        useRootNavigator: false,
 | 
			
		||||
        context: context,
 | 
			
		||||
        title: L10n.of(context).areYouSure,
 | 
			
		||||
        okLabel: L10n.of(context).yes,
 | 
			
		||||
        cancelLabel: L10n.of(context).no,
 | 
			
		||||
        title: L10n.of(context)!.areYouSure,
 | 
			
		||||
        okLabel: L10n.of(context)!.yes,
 | 
			
		||||
        cancelLabel: L10n.of(context)!.no,
 | 
			
		||||
      );
 | 
			
		||||
      if (confirmed == OkCancelResult.cancel) return;
 | 
			
		||||
      await showFutureLoadingDialog(
 | 
			
		||||
@ -160,7 +160,7 @@ class ChatListItem extends StatelessWidget {
 | 
			
		||||
      selectedTileColor: selected
 | 
			
		||||
          ? Theme.of(context).primaryColor.withAlpha(100)
 | 
			
		||||
          : Theme.of(context).secondaryHeaderColor,
 | 
			
		||||
      onLongPress: onLongPress,
 | 
			
		||||
      onLongPress: onLongPress as void Function()?,
 | 
			
		||||
      leading: selected
 | 
			
		||||
          ? SizedBox(
 | 
			
		||||
              width: Avatar.defaultSize,
 | 
			
		||||
@ -174,13 +174,13 @@ class ChatListItem extends StatelessWidget {
 | 
			
		||||
          : Avatar(
 | 
			
		||||
              mxContent: room.avatar,
 | 
			
		||||
              name: room.displayname,
 | 
			
		||||
              onTap: onLongPress,
 | 
			
		||||
              onTap: onLongPress as void Function()?,
 | 
			
		||||
            ),
 | 
			
		||||
      title: Row(
 | 
			
		||||
        children: <Widget>[
 | 
			
		||||
          Expanded(
 | 
			
		||||
            child: Text(
 | 
			
		||||
              room.getLocalizedDisplayname(MatrixLocals(L10n.of(context))),
 | 
			
		||||
              room.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)),
 | 
			
		||||
              maxLines: 1,
 | 
			
		||||
              overflow: TextOverflow.ellipsis,
 | 
			
		||||
              softWrap: false,
 | 
			
		||||
@ -188,7 +188,7 @@ class ChatListItem extends StatelessWidget {
 | 
			
		||||
                fontWeight: FontWeight.bold,
 | 
			
		||||
                color: unread
 | 
			
		||||
                    ? 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,
 | 
			
		||||
                color: unread
 | 
			
		||||
                    ? 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>[
 | 
			
		||||
          if (typingText.isEmpty &&
 | 
			
		||||
              ownMessage &&
 | 
			
		||||
              room.lastEvent.status.isSending) ...[
 | 
			
		||||
              room.lastEvent!.status.isSending) ...[
 | 
			
		||||
            const SizedBox(
 | 
			
		||||
              width: 16,
 | 
			
		||||
              height: 16,
 | 
			
		||||
@ -261,9 +261,9 @@ class ChatListItem extends StatelessWidget {
 | 
			
		||||
                  )
 | 
			
		||||
                : Text(
 | 
			
		||||
                    room.membership == Membership.invite
 | 
			
		||||
                        ? L10n.of(context).youAreInvitedToThisChat
 | 
			
		||||
                        ? L10n.of(context)!.youAreInvitedToThisChat
 | 
			
		||||
                        : room.lastEvent?.getLocalizedBody(
 | 
			
		||||
                              MatrixLocals(L10n.of(context)),
 | 
			
		||||
                              MatrixLocals(L10n.of(context)!),
 | 
			
		||||
                              hideReply: true,
 | 
			
		||||
                              hideEdit: true,
 | 
			
		||||
                              plaintextBody: true,
 | 
			
		||||
@ -271,14 +271,14 @@ class ChatListItem extends StatelessWidget {
 | 
			
		||||
                                  room.directChatMatrixID !=
 | 
			
		||||
                                      room.lastEvent?.senderId,
 | 
			
		||||
                            ) ??
 | 
			
		||||
                            L10n.of(context).emptyChat,
 | 
			
		||||
                            L10n.of(context)!.emptyChat,
 | 
			
		||||
                    softWrap: false,
 | 
			
		||||
                    maxLines: 1,
 | 
			
		||||
                    overflow: TextOverflow.ellipsis,
 | 
			
		||||
                    style: TextStyle(
 | 
			
		||||
                      color: unread
 | 
			
		||||
                          ? Theme.of(context).colorScheme.secondary
 | 
			
		||||
                          : Theme.of(context).textTheme.bodyText2.color,
 | 
			
		||||
                          : Theme.of(context).textTheme.bodyText2!.color,
 | 
			
		||||
                      decoration: room.lastEvent?.redacted == true
 | 
			
		||||
                          ? TextDecoration.lineThrough
 | 
			
		||||
                          : null,
 | 
			
		||||
 | 
			
		||||
@ -21,11 +21,11 @@ import '../../widgets/matrix.dart';
 | 
			
		||||
class ChatListView extends StatelessWidget {
 | 
			
		||||
  final ChatListController controller;
 | 
			
		||||
 | 
			
		||||
  const ChatListView(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const ChatListView(this.controller, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return StreamBuilder<Object>(
 | 
			
		||||
    return StreamBuilder<Object?>(
 | 
			
		||||
        stream: Matrix.of(context).onShareContentChanged.stream,
 | 
			
		||||
        builder: (_, __) {
 | 
			
		||||
          final selectMode = controller.selectMode;
 | 
			
		||||
@ -48,7 +48,7 @@ class ChatListView extends StatelessWidget {
 | 
			
		||||
                        ? ClientChooserButton(controller)
 | 
			
		||||
                        : null
 | 
			
		||||
                    : IconButton(
 | 
			
		||||
                        tooltip: L10n.of(context).cancel,
 | 
			
		||||
                        tooltip: L10n.of(context)!.cancel,
 | 
			
		||||
                        icon: const Icon(Icons.close_outlined),
 | 
			
		||||
                        onPressed: controller.cancelAction,
 | 
			
		||||
                        color: Theme.of(context).colorScheme.primary,
 | 
			
		||||
@ -60,12 +60,12 @@ class ChatListView extends StatelessWidget {
 | 
			
		||||
                        ? [
 | 
			
		||||
                            if (controller.spaces.isNotEmpty)
 | 
			
		||||
                              IconButton(
 | 
			
		||||
                                tooltip: L10n.of(context).addToSpace,
 | 
			
		||||
                                tooltip: L10n.of(context)!.addToSpace,
 | 
			
		||||
                                icon: const Icon(Icons.group_work_outlined),
 | 
			
		||||
                                onPressed: controller.addOrRemoveToSpace,
 | 
			
		||||
                              ),
 | 
			
		||||
                            IconButton(
 | 
			
		||||
                              tooltip: L10n.of(context).toggleUnread,
 | 
			
		||||
                              tooltip: L10n.of(context)!.toggleUnread,
 | 
			
		||||
                              icon: Icon(
 | 
			
		||||
                                  controller.anySelectedRoomNotMarkedUnread
 | 
			
		||||
                                      ? Icons.mark_chat_read_outlined
 | 
			
		||||
@ -73,7 +73,7 @@ class ChatListView extends StatelessWidget {
 | 
			
		||||
                              onPressed: controller.toggleUnread,
 | 
			
		||||
                            ),
 | 
			
		||||
                            IconButton(
 | 
			
		||||
                              tooltip: L10n.of(context).toggleFavorite,
 | 
			
		||||
                              tooltip: L10n.of(context)!.toggleFavorite,
 | 
			
		||||
                              icon: Icon(controller.anySelectedRoomNotFavorite
 | 
			
		||||
                                  ? Icons.push_pin_outlined
 | 
			
		||||
                                  : Icons.push_pin),
 | 
			
		||||
@ -83,19 +83,19 @@ class ChatListView extends StatelessWidget {
 | 
			
		||||
                              icon: Icon(controller.anySelectedRoomNotMuted
 | 
			
		||||
                                  ? Icons.notifications_off_outlined
 | 
			
		||||
                                  : Icons.notifications_outlined),
 | 
			
		||||
                              tooltip: L10n.of(context).toggleMuted,
 | 
			
		||||
                              tooltip: L10n.of(context)!.toggleMuted,
 | 
			
		||||
                              onPressed: controller.toggleMuted,
 | 
			
		||||
                            ),
 | 
			
		||||
                            IconButton(
 | 
			
		||||
                              icon: const Icon(Icons.delete_outlined),
 | 
			
		||||
                              tooltip: L10n.of(context).archive,
 | 
			
		||||
                              tooltip: L10n.of(context)!.archive,
 | 
			
		||||
                              onPressed: controller.archiveAction,
 | 
			
		||||
                            ),
 | 
			
		||||
                          ]
 | 
			
		||||
                        : [
 | 
			
		||||
                            IconButton(
 | 
			
		||||
                              icon: const Icon(Icons.search_outlined),
 | 
			
		||||
                              tooltip: L10n.of(context).search,
 | 
			
		||||
                              tooltip: L10n.of(context)!.search,
 | 
			
		||||
                              onPressed: () =>
 | 
			
		||||
                                  VRouter.of(context).to('/search'),
 | 
			
		||||
                            ),
 | 
			
		||||
@ -109,7 +109,7 @@ class ChatListView extends StatelessWidget {
 | 
			
		||||
                                    children: [
 | 
			
		||||
                                      const Icon(Icons.edit_outlined),
 | 
			
		||||
                                      const SizedBox(width: 12),
 | 
			
		||||
                                      Text(L10n.of(context).setStatus),
 | 
			
		||||
                                      Text(L10n.of(context)!.setStatus),
 | 
			
		||||
                                    ],
 | 
			
		||||
                                  ),
 | 
			
		||||
                                ),
 | 
			
		||||
@ -120,7 +120,7 @@ class ChatListView extends StatelessWidget {
 | 
			
		||||
                                    children: [
 | 
			
		||||
                                      const Icon(Icons.group_add_outlined),
 | 
			
		||||
                                      const SizedBox(width: 12),
 | 
			
		||||
                                      Text(L10n.of(context).createNewGroup),
 | 
			
		||||
                                      Text(L10n.of(context)!.createNewGroup),
 | 
			
		||||
                                    ],
 | 
			
		||||
                                  ),
 | 
			
		||||
                                ),
 | 
			
		||||
@ -131,7 +131,7 @@ class ChatListView extends StatelessWidget {
 | 
			
		||||
                                    children: [
 | 
			
		||||
                                      const Icon(Icons.group_work_outlined),
 | 
			
		||||
                                      const SizedBox(width: 12),
 | 
			
		||||
                                      Text(L10n.of(context).createNewSpace),
 | 
			
		||||
                                      Text(L10n.of(context)!.createNewSpace),
 | 
			
		||||
                                    ],
 | 
			
		||||
                                  ),
 | 
			
		||||
                                ),
 | 
			
		||||
@ -142,7 +142,7 @@ class ChatListView extends StatelessWidget {
 | 
			
		||||
                                    children: [
 | 
			
		||||
                                      const Icon(Icons.share_outlined),
 | 
			
		||||
                                      const SizedBox(width: 12),
 | 
			
		||||
                                      Text(L10n.of(context).inviteContact),
 | 
			
		||||
                                      Text(L10n.of(context)!.inviteContact),
 | 
			
		||||
                                    ],
 | 
			
		||||
                                  ),
 | 
			
		||||
                                ),
 | 
			
		||||
@ -153,7 +153,7 @@ class ChatListView extends StatelessWidget {
 | 
			
		||||
                                    children: [
 | 
			
		||||
                                      const Icon(Icons.archive_outlined),
 | 
			
		||||
                                      const SizedBox(width: 12),
 | 
			
		||||
                                      Text(L10n.of(context).archive),
 | 
			
		||||
                                      Text(L10n.of(context)!.archive),
 | 
			
		||||
                                    ],
 | 
			
		||||
                                  ),
 | 
			
		||||
                                ),
 | 
			
		||||
@ -164,7 +164,7 @@ class ChatListView extends StatelessWidget {
 | 
			
		||||
                                    children: [
 | 
			
		||||
                                      const Icon(Icons.settings_outlined),
 | 
			
		||||
                                      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
 | 
			
		||||
                    ? L10n.of(context).share
 | 
			
		||||
                    ? L10n.of(context)!.share
 | 
			
		||||
                    : selectMode == SelectMode.select
 | 
			
		||||
                        ? controller.selectedRoomIds.length.toString()
 | 
			
		||||
                        : controller.activeSpaceId == null
 | 
			
		||||
                            ? AppConfig.applicationName
 | 
			
		||||
                            : Matrix.of(context)
 | 
			
		||||
                                .client
 | 
			
		||||
                                .getRoomById(controller.activeSpaceId)
 | 
			
		||||
                                .getRoomById(controller.activeSpaceId!)!
 | 
			
		||||
                                .displayname),
 | 
			
		||||
              ),
 | 
			
		||||
              body: Column(children: [
 | 
			
		||||
@ -197,7 +197,7 @@ class ChatListView extends StatelessWidget {
 | 
			
		||||
                        fit: BoxFit.contain,
 | 
			
		||||
                        width: 44,
 | 
			
		||||
                      ),
 | 
			
		||||
                      title: Text(L10n.of(context).setupChatBackupNow),
 | 
			
		||||
                      title: Text(L10n.of(context)!.setupChatBackupNow),
 | 
			
		||||
                      trailing: const Icon(Icons.chevron_right_outlined),
 | 
			
		||||
                      onTap: controller.firstRunBootstrapAction,
 | 
			
		||||
                    ),
 | 
			
		||||
@ -211,7 +211,7 @@ class ChatListView extends StatelessWidget {
 | 
			
		||||
                      onPressed: () =>
 | 
			
		||||
                          VRouter.of(context).to('/newprivatechat'),
 | 
			
		||||
                      icon: const Icon(CupertinoIcons.chat_bubble),
 | 
			
		||||
                      label: Text(L10n.of(context).newChat),
 | 
			
		||||
                      label: Text(L10n.of(context)!.newChat),
 | 
			
		||||
                    )
 | 
			
		||||
                  : null,
 | 
			
		||||
              bottomNavigationBar: Column(
 | 
			
		||||
@ -232,7 +232,7 @@ class ChatListView extends StatelessWidget {
 | 
			
		||||
class _ChatListViewBody extends StatefulWidget {
 | 
			
		||||
  final ChatListController controller;
 | 
			
		||||
 | 
			
		||||
  const _ChatListViewBody(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const _ChatListViewBody(this.controller, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  State<_ChatListViewBody> createState() => _ChatListViewBodyState();
 | 
			
		||||
@ -240,12 +240,12 @@ class _ChatListViewBody extends StatefulWidget {
 | 
			
		||||
 | 
			
		||||
class _ChatListViewBodyState extends State<_ChatListViewBody> {
 | 
			
		||||
  // the matrix sync stream
 | 
			
		||||
  StreamSubscription _subscription;
 | 
			
		||||
  StreamSubscription _clientSubscription;
 | 
			
		||||
  late StreamSubscription _subscription;
 | 
			
		||||
  late StreamSubscription _clientSubscription;
 | 
			
		||||
 | 
			
		||||
  // used to check the animation direction
 | 
			
		||||
  String _lastUserId;
 | 
			
		||||
  String _lastSpaceId;
 | 
			
		||||
  String? _lastUserId;
 | 
			
		||||
  String? _lastSpaceId;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
@ -285,7 +285,7 @@ class _ChatListViewBodyState extends State<_ChatListViewBody> {
 | 
			
		||||
            ),
 | 
			
		||||
            Center(
 | 
			
		||||
              child: Text(
 | 
			
		||||
                L10n.of(context).startYourFirstChat,
 | 
			
		||||
                L10n.of(context)!.startYourFirstChat,
 | 
			
		||||
                textAlign: TextAlign.start,
 | 
			
		||||
                style: const TextStyle(
 | 
			
		||||
                  color: Colors.grey,
 | 
			
		||||
@ -324,9 +324,9 @@ class _ChatListViewBodyState extends State<_ChatListViewBody> {
 | 
			
		||||
    } else {
 | 
			
		||||
      const dummyChatCount = 8;
 | 
			
		||||
      final titleColor =
 | 
			
		||||
          Theme.of(context).textTheme.bodyText1.color.withAlpha(100);
 | 
			
		||||
          Theme.of(context).textTheme.bodyText1!.color!.withAlpha(100);
 | 
			
		||||
      final subtitleColor =
 | 
			
		||||
          Theme.of(context).textTheme.bodyText1.color.withAlpha(50);
 | 
			
		||||
          Theme.of(context).textTheme.bodyText1!.color!.withAlpha(50);
 | 
			
		||||
      child = ListView.builder(
 | 
			
		||||
        itemCount: dummyChatCount,
 | 
			
		||||
        itemBuilder: (context, i) => Opacity(
 | 
			
		||||
@ -336,7 +336,7 @@ class _ChatListViewBodyState extends State<_ChatListViewBody> {
 | 
			
		||||
              backgroundColor: titleColor,
 | 
			
		||||
              child: CircularProgressIndicator(
 | 
			
		||||
                strokeWidth: 1,
 | 
			
		||||
                color: Theme.of(context).textTheme.bodyText1.color,
 | 
			
		||||
                color: Theme.of(context).textTheme.bodyText1!.color,
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
            title: Row(
 | 
			
		||||
@ -417,11 +417,11 @@ class _ChatListViewBodyState extends State<_ChatListViewBody> {
 | 
			
		||||
    final newClient = Matrix.of(context).client;
 | 
			
		||||
    if (_lastUserId != newClient.userID) {
 | 
			
		||||
      reversed = Matrix.of(context)
 | 
			
		||||
              .currentBundle
 | 
			
		||||
              .indexWhere((element) => element.userID == _lastUserId) <
 | 
			
		||||
              .currentBundle!
 | 
			
		||||
              .indexWhere((element) => element!.userID == _lastUserId) <
 | 
			
		||||
          Matrix.of(context)
 | 
			
		||||
              .currentBundle
 | 
			
		||||
              .indexWhere((element) => element.userID == newClient.userID);
 | 
			
		||||
              .currentBundle!
 | 
			
		||||
              .indexWhere((element) => element!.userID == newClient.userID);
 | 
			
		||||
    }
 | 
			
		||||
    // otherwise, the space changed...
 | 
			
		||||
    else {
 | 
			
		||||
 | 
			
		||||
@ -8,20 +8,20 @@ import 'chat_list.dart';
 | 
			
		||||
 | 
			
		||||
class ClientChooserButton extends StatelessWidget {
 | 
			
		||||
  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) {
 | 
			
		||||
    final matrix = Matrix.of(context);
 | 
			
		||||
    final bundles = matrix.accountBundles.keys.toList()
 | 
			
		||||
      ..sort((a, b) => a.isValidMatrixId == b.isValidMatrixId
 | 
			
		||||
      ..sort((a, b) => a!.isValidMatrixId == b!.isValidMatrixId
 | 
			
		||||
          ? 0
 | 
			
		||||
          : a.isValidMatrixId && !b.isValidMatrixId
 | 
			
		||||
              ? -1
 | 
			
		||||
              : 1);
 | 
			
		||||
    return <PopupMenuEntry<Object>>[
 | 
			
		||||
      for (final bundle in bundles) ...[
 | 
			
		||||
        if (matrix.accountBundles[bundle].length != 1 ||
 | 
			
		||||
            matrix.accountBundles[bundle].single.userID != bundle)
 | 
			
		||||
        if (matrix.accountBundles[bundle]!.length != 1 ||
 | 
			
		||||
            matrix.accountBundles[bundle]!.single!.userID != bundle)
 | 
			
		||||
          PopupMenuItem(
 | 
			
		||||
            value: null,
 | 
			
		||||
            child: Column(
 | 
			
		||||
@ -29,9 +29,9 @@ class ClientChooserButton extends StatelessWidget {
 | 
			
		||||
              mainAxisSize: MainAxisSize.min,
 | 
			
		||||
              children: [
 | 
			
		||||
                Text(
 | 
			
		||||
                  bundle,
 | 
			
		||||
                  bundle!,
 | 
			
		||||
                  style: TextStyle(
 | 
			
		||||
                    color: Theme.of(context).textTheme.subtitle1.color,
 | 
			
		||||
                    color: Theme.of(context).textTheme.subtitle1!.color,
 | 
			
		||||
                    fontSize: 14,
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
@ -39,25 +39,26 @@ class ClientChooserButton extends StatelessWidget {
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ...matrix.accountBundles[bundle]
 | 
			
		||||
        ...matrix.accountBundles[bundle]!
 | 
			
		||||
            .map(
 | 
			
		||||
              (client) => PopupMenuItem(
 | 
			
		||||
                value: client,
 | 
			
		||||
                child: FutureBuilder<Profile>(
 | 
			
		||||
                  future: client.ownProfile,
 | 
			
		||||
                  future: client!.ownProfile,
 | 
			
		||||
                  builder: (context, snapshot) => Row(
 | 
			
		||||
                    children: [
 | 
			
		||||
                      Avatar(
 | 
			
		||||
                        mxContent: snapshot.data?.avatarUrl,
 | 
			
		||||
                        name: snapshot.data?.displayName ??
 | 
			
		||||
                            client.userID.localpart,
 | 
			
		||||
                            client.userID!.localpart,
 | 
			
		||||
                        size: 28,
 | 
			
		||||
                        fontSize: 12,
 | 
			
		||||
                      ),
 | 
			
		||||
                      const SizedBox(width: 12),
 | 
			
		||||
                      Expanded(
 | 
			
		||||
                        child: Text(
 | 
			
		||||
                          snapshot.data?.displayName ?? client.userID.localpart,
 | 
			
		||||
                          snapshot.data?.displayName ??
 | 
			
		||||
                              client.userID!.localpart!,
 | 
			
		||||
                          overflow: TextOverflow.ellipsis,
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
@ -86,7 +87,7 @@ class ClientChooserButton extends StatelessWidget {
 | 
			
		||||
        builder: (context, snapshot) => PopupMenuButton<Object>(
 | 
			
		||||
          child: Avatar(
 | 
			
		||||
            mxContent: snapshot.data?.avatarUrl,
 | 
			
		||||
            name: snapshot.data?.displayName ?? matrix.client.userID.localpart,
 | 
			
		||||
            name: snapshot.data?.displayName ?? matrix.client.userID!.localpart,
 | 
			
		||||
            size: 28,
 | 
			
		||||
            fontSize: 12,
 | 
			
		||||
          ),
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,7 @@ import 'package:fluffychat/widgets/matrix.dart';
 | 
			
		||||
 | 
			
		||||
class SpacesBottomBar extends StatelessWidget {
 | 
			
		||||
  final ChatListController controller;
 | 
			
		||||
  const SpacesBottomBar(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const SpacesBottomBar(this.controller, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
@ -25,8 +25,9 @@ class SpacesBottomBar extends StatelessWidget {
 | 
			
		||||
      child: SafeArea(
 | 
			
		||||
        child: StreamBuilder<Object>(
 | 
			
		||||
            stream: Matrix.of(context).client.onSync.stream.where((sync) =>
 | 
			
		||||
                (sync.rooms?.join?.values?.any((r) =>
 | 
			
		||||
                        r.state?.any((s) => s.type.startsWith('m.space'))) ??
 | 
			
		||||
                (sync.rooms?.join?.values.any((r) =>
 | 
			
		||||
                        r.state?.any((s) => s.type.startsWith('m.space')) ??
 | 
			
		||||
                        false) ??
 | 
			
		||||
                    false) ||
 | 
			
		||||
                (sync.rooms?.leave?.isNotEmpty ?? false)),
 | 
			
		||||
            builder: (context, snapshot) {
 | 
			
		||||
@ -48,7 +49,7 @@ class SpacesBottomBar extends StatelessWidget {
 | 
			
		||||
                        icon: const Icon(CupertinoIcons.chat_bubble_2),
 | 
			
		||||
                        activeIcon:
 | 
			
		||||
                            const Icon(CupertinoIcons.chat_bubble_2_fill),
 | 
			
		||||
                        title: Text(L10n.of(context).allChats),
 | 
			
		||||
                        title: Text(L10n.of(context)!.allChats),
 | 
			
		||||
                      ),
 | 
			
		||||
                      ...controller.spaces
 | 
			
		||||
                          .map((space) => SalomonBottomBarItem(
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,7 @@ import 'package:fluffychat/widgets/matrix.dart';
 | 
			
		||||
import 'package:fluffychat/widgets/permission_slider_dialog.dart';
 | 
			
		||||
 | 
			
		||||
class ChatPermissionsSettings extends StatefulWidget {
 | 
			
		||||
  const ChatPermissionsSettings({Key key}) : super(key: key);
 | 
			
		||||
  const ChatPermissionsSettings({Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  ChatPermissionsSettingsController createState() =>
 | 
			
		||||
@ -21,13 +21,13 @@ class ChatPermissionsSettings extends StatefulWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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,
 | 
			
		||||
      {String category}) async {
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(roomId);
 | 
			
		||||
      {String? category}) async {
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(roomId!)!;
 | 
			
		||||
    if (!room.canSendEvent(EventTypes.RoomPowerLevels)) {
 | 
			
		||||
      ScaffoldMessenger.of(context)
 | 
			
		||||
          .showSnackBar(SnackBar(content: Text(L10n.of(context).noPermission)));
 | 
			
		||||
      ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
          SnackBar(content: Text(L10n.of(context)!.noPermission)));
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    final newLevel =
 | 
			
		||||
@ -35,7 +35,7 @@ class ChatPermissionsSettingsController extends State<ChatPermissionsSettings> {
 | 
			
		||||
            .show(context);
 | 
			
		||||
    if (newLevel == null) return;
 | 
			
		||||
    final content = Map<String, dynamic>.from(
 | 
			
		||||
        room.getState(EventTypes.RoomPowerLevels).content);
 | 
			
		||||
        room.getState(EventTypes.RoomPowerLevels)!.content);
 | 
			
		||||
    if (category != null) {
 | 
			
		||||
      if (!content.containsKey(category)) {
 | 
			
		||||
        content[category] = <String, dynamic>{};
 | 
			
		||||
@ -58,20 +58,20 @@ class ChatPermissionsSettingsController extends State<ChatPermissionsSettings> {
 | 
			
		||||
 | 
			
		||||
  Stream get onChanged => Matrix.of(context).client.onSync.stream.where(
 | 
			
		||||
        (e) =>
 | 
			
		||||
            (e?.rooms?.join?.containsKey(roomId) ?? false) &&
 | 
			
		||||
            (e.rooms.join[roomId]?.timeline?.events
 | 
			
		||||
            (e.rooms?.join?.containsKey(roomId) ?? false) &&
 | 
			
		||||
            (e.rooms!.join![roomId!]?.timeline?.events
 | 
			
		||||
                    ?.any((s) => s.type == EventTypes.RoomPowerLevels) ??
 | 
			
		||||
                false),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
  void updateRoomAction(Capabilities capabilities) async {
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(roomId);
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(roomId!)!;
 | 
			
		||||
    final String roomVersion =
 | 
			
		||||
        room.getState(EventTypes.RoomCreate).content['room_version'] ?? '1';
 | 
			
		||||
        room.getState(EventTypes.RoomCreate)!.content['room_version'] ?? '1';
 | 
			
		||||
    final newVersion = await showConfirmationDialog<String>(
 | 
			
		||||
      context: context,
 | 
			
		||||
      title: L10n.of(context).replaceRoomWithNewerVersion,
 | 
			
		||||
      actions: capabilities.mRoomVersions.available.entries
 | 
			
		||||
      title: L10n.of(context)!.replaceRoomWithNewerVersion,
 | 
			
		||||
      actions: capabilities.mRoomVersions!.available.entries
 | 
			
		||||
          .where((r) => r.key != roomVersion)
 | 
			
		||||
          .map((version) => AlertDialogAction(
 | 
			
		||||
              key: version.key,
 | 
			
		||||
@ -84,15 +84,15 @@ class ChatPermissionsSettingsController extends State<ChatPermissionsSettings> {
 | 
			
		||||
            await showOkCancelAlertDialog(
 | 
			
		||||
              useRootNavigator: false,
 | 
			
		||||
              context: context,
 | 
			
		||||
              okLabel: L10n.of(context).yes,
 | 
			
		||||
              cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
              title: L10n.of(context).areYouSure,
 | 
			
		||||
              okLabel: L10n.of(context)!.yes,
 | 
			
		||||
              cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
              title: L10n.of(context)!.areYouSure,
 | 
			
		||||
            )) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    await showFutureLoadingDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      future: () => room.client.upgradeRoom(roomId, newVersion),
 | 
			
		||||
      future: () => room.client.upgradeRoom(roomId!, newVersion),
 | 
			
		||||
    ).then((_) => VRouter.of(context).pop());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,7 @@ import 'package:fluffychat/widgets/matrix.dart';
 | 
			
		||||
class ChatPermissionsSettingsView extends StatelessWidget {
 | 
			
		||||
  final ChatPermissionsSettingsController controller;
 | 
			
		||||
 | 
			
		||||
  const ChatPermissionsSettingsView(this.controller, {Key key})
 | 
			
		||||
  const ChatPermissionsSettingsView(this.controller, {Key? key})
 | 
			
		||||
      : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@ -24,19 +24,24 @@ class ChatPermissionsSettingsView extends StatelessWidget {
 | 
			
		||||
            : IconButton(
 | 
			
		||||
                icon: const Icon(Icons.close_outlined),
 | 
			
		||||
                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(
 | 
			
		||||
        withScrolling: true,
 | 
			
		||||
        child: StreamBuilder(
 | 
			
		||||
          stream: controller.onChanged,
 | 
			
		||||
          builder: (context, _) {
 | 
			
		||||
            final room =
 | 
			
		||||
                Matrix.of(context).client.getRoomById(controller.roomId);
 | 
			
		||||
            final roomId = 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(
 | 
			
		||||
                room.getState(EventTypes.RoomPowerLevels).content);
 | 
			
		||||
                room.getState(EventTypes.RoomPowerLevels)!.content);
 | 
			
		||||
            final powerLevels = Map<String, dynamic>.from(powerLevelsContent)
 | 
			
		||||
              ..removeWhere((k, v) => v is! int);
 | 
			
		||||
            final eventsPowerLevels =
 | 
			
		||||
@ -57,7 +62,7 @@ class ChatPermissionsSettingsView extends StatelessWidget {
 | 
			
		||||
                    const Divider(thickness: 1),
 | 
			
		||||
                    ListTile(
 | 
			
		||||
                      title: Text(
 | 
			
		||||
                        L10n.of(context).notifications,
 | 
			
		||||
                        L10n.of(context)!.notifications,
 | 
			
		||||
                        style: TextStyle(
 | 
			
		||||
                          color: Theme.of(context).primaryColor,
 | 
			
		||||
                          fontWeight: FontWeight.bold,
 | 
			
		||||
@ -82,23 +87,22 @@ class ChatPermissionsSettingsView extends StatelessWidget {
 | 
			
		||||
                    const Divider(thickness: 1),
 | 
			
		||||
                    ListTile(
 | 
			
		||||
                      title: Text(
 | 
			
		||||
                        L10n.of(context).configureChat,
 | 
			
		||||
                        L10n.of(context)!.configureChat,
 | 
			
		||||
                        style: TextStyle(
 | 
			
		||||
                          color: Theme.of(context).primaryColor,
 | 
			
		||||
                          fontWeight: FontWeight.bold,
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                    if (eventsPowerLevels != null)
 | 
			
		||||
                      for (var entry in eventsPowerLevels.entries)
 | 
			
		||||
                        PermissionsListTile(
 | 
			
		||||
                          permissionKey: entry.key,
 | 
			
		||||
                          category: 'events',
 | 
			
		||||
                          permission: entry.value,
 | 
			
		||||
                          onTap: () => controller.editPowerLevel(
 | 
			
		||||
                              context, entry.key, entry.value,
 | 
			
		||||
                              category: 'events'),
 | 
			
		||||
                        ),
 | 
			
		||||
                    for (var entry in eventsPowerLevels.entries)
 | 
			
		||||
                      PermissionsListTile(
 | 
			
		||||
                        permissionKey: entry.key,
 | 
			
		||||
                        category: 'events',
 | 
			
		||||
                        permission: entry.value,
 | 
			
		||||
                        onTap: () => controller.editPowerLevel(
 | 
			
		||||
                            context, entry.key, entry.value,
 | 
			
		||||
                            category: 'events'),
 | 
			
		||||
                      ),
 | 
			
		||||
                    if (room.canSendEvent(EventTypes.RoomTombstone)) ...{
 | 
			
		||||
                      const Divider(thickness: 1),
 | 
			
		||||
                      FutureBuilder<Capabilities>(
 | 
			
		||||
@ -110,15 +114,15 @@ class ChatPermissionsSettingsView extends StatelessWidget {
 | 
			
		||||
                                    strokeWidth: 2));
 | 
			
		||||
                          }
 | 
			
		||||
                          final String roomVersion = room
 | 
			
		||||
                                  .getState(EventTypes.RoomCreate)
 | 
			
		||||
                                  .getState(EventTypes.RoomCreate)!
 | 
			
		||||
                                  .content['room_version'] ??
 | 
			
		||||
                              '1';
 | 
			
		||||
 | 
			
		||||
                          return ListTile(
 | 
			
		||||
                            title: Text(
 | 
			
		||||
                                '${L10n.of(context).roomVersion}: $roomVersion'),
 | 
			
		||||
                                '${L10n.of(context)!.roomVersion}: $roomVersion'),
 | 
			
		||||
                            onTap: () =>
 | 
			
		||||
                                controller.updateRoomAction(snapshot.data),
 | 
			
		||||
                                controller.updateRoomAction(snapshot.data!),
 | 
			
		||||
                          );
 | 
			
		||||
                        },
 | 
			
		||||
                      ),
 | 
			
		||||
 | 
			
		||||
@ -6,13 +6,13 @@ import 'package:matrix/matrix.dart';
 | 
			
		||||
class PermissionsListTile extends StatelessWidget {
 | 
			
		||||
  final String permissionKey;
 | 
			
		||||
  final int permission;
 | 
			
		||||
  final String category;
 | 
			
		||||
  final void Function() onTap;
 | 
			
		||||
  final String? category;
 | 
			
		||||
  final void Function()? onTap;
 | 
			
		||||
 | 
			
		||||
  const PermissionsListTile({
 | 
			
		||||
    Key key,
 | 
			
		||||
    @required this.permissionKey,
 | 
			
		||||
    @required this.permission,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.permissionKey,
 | 
			
		||||
    required this.permission,
 | 
			
		||||
    this.category,
 | 
			
		||||
    this.onTap,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
@ -21,43 +21,43 @@ class PermissionsListTile extends StatelessWidget {
 | 
			
		||||
    if (category == null) {
 | 
			
		||||
      switch (permissionKey) {
 | 
			
		||||
        case 'users_default':
 | 
			
		||||
          return L10n.of(context).defaultPermissionLevel;
 | 
			
		||||
          return L10n.of(context)!.defaultPermissionLevel;
 | 
			
		||||
        case 'events_default':
 | 
			
		||||
          return L10n.of(context).sendMessages;
 | 
			
		||||
          return L10n.of(context)!.sendMessages;
 | 
			
		||||
        case 'state_default':
 | 
			
		||||
          return L10n.of(context).configureChat;
 | 
			
		||||
          return L10n.of(context)!.configureChat;
 | 
			
		||||
        case 'ban':
 | 
			
		||||
          return L10n.of(context).banFromChat;
 | 
			
		||||
          return L10n.of(context)!.banFromChat;
 | 
			
		||||
        case 'kick':
 | 
			
		||||
          return L10n.of(context).kickFromChat;
 | 
			
		||||
          return L10n.of(context)!.kickFromChat;
 | 
			
		||||
        case 'redact':
 | 
			
		||||
          return L10n.of(context).deleteMessage;
 | 
			
		||||
          return L10n.of(context)!.deleteMessage;
 | 
			
		||||
        case 'invite':
 | 
			
		||||
          return L10n.of(context).inviteContact;
 | 
			
		||||
          return L10n.of(context)!.inviteContact;
 | 
			
		||||
      }
 | 
			
		||||
    } else if (category == 'notifications') {
 | 
			
		||||
      switch (permissionKey) {
 | 
			
		||||
        case 'rooms':
 | 
			
		||||
          return L10n.of(context).notifications;
 | 
			
		||||
          return L10n.of(context)!.notifications;
 | 
			
		||||
      }
 | 
			
		||||
    } else if (category == 'events') {
 | 
			
		||||
      switch (permissionKey) {
 | 
			
		||||
        case EventTypes.RoomName:
 | 
			
		||||
          return L10n.of(context).changeTheNameOfTheGroup;
 | 
			
		||||
          return L10n.of(context)!.changeTheNameOfTheGroup;
 | 
			
		||||
        case EventTypes.RoomPowerLevels:
 | 
			
		||||
          return L10n.of(context).editChatPermissions;
 | 
			
		||||
          return L10n.of(context)!.editChatPermissions;
 | 
			
		||||
        case EventTypes.HistoryVisibility:
 | 
			
		||||
          return L10n.of(context).visibilityOfTheChatHistory;
 | 
			
		||||
          return L10n.of(context)!.visibilityOfTheChatHistory;
 | 
			
		||||
        case EventTypes.RoomCanonicalAlias:
 | 
			
		||||
          return L10n.of(context).setInvitationLink;
 | 
			
		||||
          return L10n.of(context)!.setInvitationLink;
 | 
			
		||||
        case EventTypes.RoomAvatar:
 | 
			
		||||
          return L10n.of(context).editRoomAvatar;
 | 
			
		||||
          return L10n.of(context)!.editRoomAvatar;
 | 
			
		||||
        case EventTypes.RoomTombstone:
 | 
			
		||||
          return L10n.of(context).replaceRoomWithNewerVersion;
 | 
			
		||||
          return L10n.of(context)!.replaceRoomWithNewerVersion;
 | 
			
		||||
        case EventTypes.Encryption:
 | 
			
		||||
          return L10n.of(context).enableEncryption;
 | 
			
		||||
          return L10n.of(context)!.enableEncryption;
 | 
			
		||||
        case 'm.room.server_acl':
 | 
			
		||||
          return L10n.of(context).editBlockedServers;
 | 
			
		||||
          return L10n.of(context)!.editBlockedServers;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return permissionKey;
 | 
			
		||||
@ -96,9 +96,9 @@ class PermissionsListTile extends StatelessWidget {
 | 
			
		||||
extension on int {
 | 
			
		||||
  String toLocalizedPowerLevelString(BuildContext context) {
 | 
			
		||||
    return this == 100
 | 
			
		||||
        ? L10n.of(context).admin
 | 
			
		||||
        ? L10n.of(context)!.admin
 | 
			
		||||
        : this >= 50
 | 
			
		||||
            ? L10n.of(context).moderator
 | 
			
		||||
            : L10n.of(context).participant;
 | 
			
		||||
            ? L10n.of(context)!.moderator
 | 
			
		||||
            : L10n.of(context)!.participant;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
import 'package:flutter/material.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:future_loading_dialog/future_loading_dialog.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';
 | 
			
		||||
 | 
			
		||||
class DevicesSettings extends StatefulWidget {
 | 
			
		||||
  const DevicesSettings({Key key}) : super(key: key);
 | 
			
		||||
  const DevicesSettings({Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  DevicesSettingsController createState() => DevicesSettingsController();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class DevicesSettingsController extends State<DevicesSettings> {
 | 
			
		||||
  List<Device> devices;
 | 
			
		||||
  List<Device>? devices;
 | 
			
		||||
  Future<bool> loadUserDevices(BuildContext context) async {
 | 
			
		||||
    if (devices != null) return true;
 | 
			
		||||
    devices = await Matrix.of(context).client.getDevices();
 | 
			
		||||
@ -28,15 +29,15 @@ class DevicesSettingsController extends State<DevicesSettings> {
 | 
			
		||||
  void reload() => setState(() => devices = null);
 | 
			
		||||
 | 
			
		||||
  bool loadingDeletingDevices = false;
 | 
			
		||||
  String errorDeletingDevices;
 | 
			
		||||
  String? errorDeletingDevices;
 | 
			
		||||
 | 
			
		||||
  void removeDevicesAction(List<Device> devices) async {
 | 
			
		||||
    if (await showOkCancelAlertDialog(
 | 
			
		||||
          useRootNavigator: false,
 | 
			
		||||
          context: context,
 | 
			
		||||
          title: L10n.of(context).areYouSure,
 | 
			
		||||
          okLabel: L10n.of(context).yes,
 | 
			
		||||
          cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
          title: L10n.of(context)!.areYouSure,
 | 
			
		||||
          okLabel: L10n.of(context)!.yes,
 | 
			
		||||
          cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
        ) ==
 | 
			
		||||
        OkCancelResult.cancel) return;
 | 
			
		||||
    final matrix = Matrix.of(context);
 | 
			
		||||
@ -69,9 +70,9 @@ class DevicesSettingsController extends State<DevicesSettings> {
 | 
			
		||||
    final displayName = await showTextInputDialog(
 | 
			
		||||
      useRootNavigator: false,
 | 
			
		||||
      context: context,
 | 
			
		||||
      title: L10n.of(context).changeDeviceName,
 | 
			
		||||
      okLabel: L10n.of(context).ok,
 | 
			
		||||
      cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
      title: L10n.of(context)!.changeDeviceName,
 | 
			
		||||
      okLabel: L10n.of(context)!.ok,
 | 
			
		||||
      cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
      textFields: [
 | 
			
		||||
        DialogTextField(
 | 
			
		||||
          hintText: device.displayName,
 | 
			
		||||
@ -93,13 +94,13 @@ class DevicesSettingsController extends State<DevicesSettings> {
 | 
			
		||||
  void verifyDeviceAction(Device device) async {
 | 
			
		||||
    final req = Matrix.of(context)
 | 
			
		||||
        .client
 | 
			
		||||
        .userDeviceKeys[Matrix.of(context).client.userID]
 | 
			
		||||
        .deviceKeys[device.deviceId]
 | 
			
		||||
        .userDeviceKeys[Matrix.of(context).client.userID!]!
 | 
			
		||||
        .deviceKeys[device.deviceId]!
 | 
			
		||||
        .startVerification();
 | 
			
		||||
    req.onUpdate = () {
 | 
			
		||||
      if ({KeyVerificationState.error, KeyVerificationState.done}
 | 
			
		||||
          .contains(req.state)) {
 | 
			
		||||
        setState(() => null);
 | 
			
		||||
        setState(() {});
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
    await KeyVerificationDialog(request: req).show(context);
 | 
			
		||||
@ -108,33 +109,32 @@ class DevicesSettingsController extends State<DevicesSettings> {
 | 
			
		||||
  void blockDeviceAction(Device device) async {
 | 
			
		||||
    final key = Matrix.of(context)
 | 
			
		||||
        .client
 | 
			
		||||
        .userDeviceKeys[Matrix.of(context).client.userID]
 | 
			
		||||
        .deviceKeys[device.deviceId];
 | 
			
		||||
        .userDeviceKeys[Matrix.of(context).client.userID!]!
 | 
			
		||||
        .deviceKeys[device.deviceId]!;
 | 
			
		||||
    if (key.directVerified) {
 | 
			
		||||
      await key.setVerified(false);
 | 
			
		||||
    }
 | 
			
		||||
    await key.setBlocked(true);
 | 
			
		||||
    setState(() => null);
 | 
			
		||||
    setState(() {});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void unblockDeviceAction(Device device) async {
 | 
			
		||||
    final key = Matrix.of(context)
 | 
			
		||||
        .client
 | 
			
		||||
        .userDeviceKeys[Matrix.of(context).client.userID]
 | 
			
		||||
        .deviceKeys[device.deviceId];
 | 
			
		||||
        .userDeviceKeys[Matrix.of(context).client.userID!]!
 | 
			
		||||
        .deviceKeys[device.deviceId]!;
 | 
			
		||||
    await key.setBlocked(false);
 | 
			
		||||
    setState(() => null);
 | 
			
		||||
    setState(() {});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool _isOwnDevice(Device userDevice) =>
 | 
			
		||||
      userDevice.deviceId == Matrix.of(context).client.deviceID;
 | 
			
		||||
 | 
			
		||||
  Device get thisDevice => devices.firstWhere(
 | 
			
		||||
  Device? get thisDevice => devices!.firstWhereOrNull(
 | 
			
		||||
        _isOwnDevice,
 | 
			
		||||
        orElse: () => null,
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
  List<Device> get notThisDevice => List<Device>.from(devices)
 | 
			
		||||
  List<Device> get notThisDevice => List<Device>.from(devices!)
 | 
			
		||||
    ..removeWhere(_isOwnDevice)
 | 
			
		||||
    ..sort((a, b) => (b.lastSeenTs ?? 0).compareTo(a.lastSeenTs ?? 0));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -9,14 +9,14 @@ import 'user_device_list_item.dart';
 | 
			
		||||
class DevicesSettingsView extends StatelessWidget {
 | 
			
		||||
  final DevicesSettingsController controller;
 | 
			
		||||
 | 
			
		||||
  const DevicesSettingsView(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const DevicesSettingsView(this.controller, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        leading: const BackButton(),
 | 
			
		||||
        title: Text(L10n.of(context).devices),
 | 
			
		||||
        title: Text(L10n.of(context)!.devices),
 | 
			
		||||
      ),
 | 
			
		||||
      body: MaxWidthBody(
 | 
			
		||||
        child: FutureBuilder<bool>(
 | 
			
		||||
@ -41,7 +41,7 @@ class DevicesSettingsView extends StatelessWidget {
 | 
			
		||||
              children: <Widget>[
 | 
			
		||||
                if (controller.thisDevice != null)
 | 
			
		||||
                  UserDeviceListItem(
 | 
			
		||||
                    controller.thisDevice,
 | 
			
		||||
                    controller.thisDevice!,
 | 
			
		||||
                    rename: controller.renameDeviceAction,
 | 
			
		||||
                    remove: (d) => controller.removeDevicesAction([d]),
 | 
			
		||||
                    verify: controller.verifyDeviceAction,
 | 
			
		||||
@ -53,7 +53,7 @@ class DevicesSettingsView extends StatelessWidget {
 | 
			
		||||
                  ListTile(
 | 
			
		||||
                    title: Text(
 | 
			
		||||
                      controller.errorDeletingDevices ??
 | 
			
		||||
                          L10n.of(context).removeAllOtherDevices,
 | 
			
		||||
                          L10n.of(context)!.removeAllOtherDevices,
 | 
			
		||||
                      style: const TextStyle(color: Colors.red),
 | 
			
		||||
                    ),
 | 
			
		||||
                    trailing: controller.loadingDeletingDevices
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,7 @@ import '../../main.dart';
 | 
			
		||||
import '../../utils/localized_exception_extension.dart';
 | 
			
		||||
 | 
			
		||||
class HomeserverPicker extends StatefulWidget {
 | 
			
		||||
  const HomeserverPicker({Key key}) : super(key: key);
 | 
			
		||||
  const HomeserverPicker({Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  HomeserverPickerController createState() => HomeserverPickerController();
 | 
			
		||||
@ -32,9 +32,9 @@ class HomeserverPickerController extends State<HomeserverPicker> {
 | 
			
		||||
  String domain = AppConfig.defaultHomeserver;
 | 
			
		||||
  final TextEditingController homeserverController =
 | 
			
		||||
      TextEditingController(text: AppConfig.defaultHomeserver);
 | 
			
		||||
  StreamSubscription _intentDataStreamSubscription;
 | 
			
		||||
  String error;
 | 
			
		||||
  Timer _coolDown;
 | 
			
		||||
  StreamSubscription? _intentDataStreamSubscription;
 | 
			
		||||
  String? error;
 | 
			
		||||
  Timer? _coolDown;
 | 
			
		||||
 | 
			
		||||
  void setDomain(String domain) {
 | 
			
		||||
    this.domain = domain;
 | 
			
		||||
@ -46,7 +46,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _loginWithToken(String token) {
 | 
			
		||||
    if (token?.isEmpty ?? true) return;
 | 
			
		||||
    if (token.isEmpty) return;
 | 
			
		||||
 | 
			
		||||
    showFutureLoadingDialog(
 | 
			
		||||
      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;
 | 
			
		||||
    await browser?.close();
 | 
			
		||||
    VRouter.of(context).to('/home');
 | 
			
		||||
@ -89,8 +89,8 @@ class HomeserverPickerController extends State<HomeserverPicker> {
 | 
			
		||||
    super.initState();
 | 
			
		||||
    _initReceiveUri();
 | 
			
		||||
    if (kIsWeb) {
 | 
			
		||||
      WidgetsBinding.instance.addPostFrameCallback((_) {
 | 
			
		||||
        final token = Matrix.of(context).widget.queryParameters['loginToken'];
 | 
			
		||||
      WidgetsBinding.instance!.addPostFrameCallback((_) {
 | 
			
		||||
        final token = Matrix.of(context).widget.queryParameters!['loginToken'];
 | 
			
		||||
        if (token != null) _loginWithToken(token);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
@ -103,7 +103,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
 | 
			
		||||
    _intentDataStreamSubscription?.cancel();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  String _lastCheckedHomeserver;
 | 
			
		||||
  String? _lastCheckedHomeserver;
 | 
			
		||||
 | 
			
		||||
  /// 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
 | 
			
		||||
@ -112,7 +112,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
 | 
			
		||||
  Future<void> checkHomeserverAction() async {
 | 
			
		||||
    _coolDown?.cancel();
 | 
			
		||||
    if (_lastCheckedHomeserver == domain) return;
 | 
			
		||||
    if (domain.isEmpty) throw L10n.of(context).changeTheHomeserver;
 | 
			
		||||
    if (domain.isEmpty) throw L10n.of(context)!.changeTheHomeserver;
 | 
			
		||||
    var homeserver = domain;
 | 
			
		||||
 | 
			
		||||
    if (!homeserver.startsWith('https://')) {
 | 
			
		||||
@ -129,7 +129,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
 | 
			
		||||
          await Matrix.of(context).getLoginClient().checkHomeserver(homeserver);
 | 
			
		||||
 | 
			
		||||
      var jitsi = wellKnown?.additionalProperties
 | 
			
		||||
          ?.tryGet<Map<String, dynamic>>('im.vector.riot.jitsi')
 | 
			
		||||
          .tryGet<Map<String, dynamic>>('im.vector.riot.jitsi')
 | 
			
		||||
          ?.tryGet<String>('preferredDomain');
 | 
			
		||||
      if (jitsi != null) {
 | 
			
		||||
        if (!jitsi.endsWith('/')) {
 | 
			
		||||
@ -150,10 +150,10 @@ class HomeserverPickerController extends State<HomeserverPicker> {
 | 
			
		||||
        await Matrix.of(context).getLoginClient().register();
 | 
			
		||||
        registrationSupported = true;
 | 
			
		||||
      } on MatrixException catch (e) {
 | 
			
		||||
        registrationSupported = e.requireAdditionalAuthentication ?? false;
 | 
			
		||||
        registrationSupported = e.requireAdditionalAuthentication;
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      setState(() => error = (e as Object).toLocalizedString(context));
 | 
			
		||||
      setState(() => error = (e).toLocalizedString(context));
 | 
			
		||||
    } finally {
 | 
			
		||||
      _lastCheckedHomeserver = domain;
 | 
			
		||||
      if (mounted) {
 | 
			
		||||
@ -162,12 +162,12 @@ class HomeserverPickerController extends State<HomeserverPicker> {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> _rawLoginTypes;
 | 
			
		||||
  bool registrationSupported;
 | 
			
		||||
  Map<String, dynamic>? _rawLoginTypes;
 | 
			
		||||
  bool? registrationSupported;
 | 
			
		||||
 | 
			
		||||
  List<IdentityProvider> get identityProviders {
 | 
			
		||||
    if (!ssoLoginSupported) return [];
 | 
			
		||||
    final rawProviders = _rawLoginTypes.tryGetList('flows').singleWhere(
 | 
			
		||||
    final rawProviders = _rawLoginTypes!.tryGetList('flows')!.singleWhere(
 | 
			
		||||
        (flow) =>
 | 
			
		||||
            flow['type'] == AuthenticationTypes.sso)['identity_providers'];
 | 
			
		||||
    final list = (rawProviders as List)
 | 
			
		||||
@ -184,8 +184,8 @@ class HomeserverPickerController extends State<HomeserverPicker> {
 | 
			
		||||
          .client
 | 
			
		||||
          .supportedLoginTypes
 | 
			
		||||
          .contains(AuthenticationTypes.password) &&
 | 
			
		||||
      _rawLoginTypes
 | 
			
		||||
          .tryGetList('flows')
 | 
			
		||||
      _rawLoginTypes!
 | 
			
		||||
          .tryGetList('flows')!
 | 
			
		||||
          .any((flow) => flow['type'] == AuthenticationTypes.password);
 | 
			
		||||
 | 
			
		||||
  bool get ssoLoginSupported =>
 | 
			
		||||
@ -193,11 +193,11 @@ class HomeserverPickerController extends State<HomeserverPicker> {
 | 
			
		||||
          .client
 | 
			
		||||
          .supportedLoginTypes
 | 
			
		||||
          .contains(AuthenticationTypes.sso) &&
 | 
			
		||||
      _rawLoginTypes
 | 
			
		||||
          .tryGetList('flows')
 | 
			
		||||
      _rawLoginTypes!
 | 
			
		||||
          .tryGetList('flows')!
 | 
			
		||||
          .any((flow) => flow['type'] == AuthenticationTypes.sso);
 | 
			
		||||
 | 
			
		||||
  ChromeSafariBrowser browser;
 | 
			
		||||
  ChromeSafariBrowser? browser;
 | 
			
		||||
 | 
			
		||||
  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)}';
 | 
			
		||||
    if (PlatformInfos.isMobile) {
 | 
			
		||||
      browser ??= ChromeSafariBrowser();
 | 
			
		||||
      browser.open(url: Uri.parse(url));
 | 
			
		||||
      browser!.open(url: Uri.parse(url));
 | 
			
		||||
    } else {
 | 
			
		||||
      launch(redirectUrl);
 | 
			
		||||
    }
 | 
			
		||||
@ -234,10 +234,10 @@ class HomeserverPickerController extends State<HomeserverPicker> {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class IdentityProvider {
 | 
			
		||||
  final String id;
 | 
			
		||||
  final String name;
 | 
			
		||||
  final String icon;
 | 
			
		||||
  final String brand;
 | 
			
		||||
  final String? id;
 | 
			
		||||
  final String? name;
 | 
			
		||||
  final String? icon;
 | 
			
		||||
  final String? brand;
 | 
			
		||||
 | 
			
		||||
  IdentityProvider({this.id, this.name, this.icon, this.brand});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,7 @@ import 'homeserver_picker.dart';
 | 
			
		||||
class HomeserverPickerView extends StatelessWidget {
 | 
			
		||||
  final HomeserverPickerController controller;
 | 
			
		||||
 | 
			
		||||
  const HomeserverPickerView(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const HomeserverPickerView(this.controller, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
@ -27,7 +27,7 @@ class HomeserverPickerView extends StatelessWidget {
 | 
			
		||||
          titleSpacing: 8,
 | 
			
		||||
          title: DefaultAppBarSearchField(
 | 
			
		||||
            prefixText: 'https://',
 | 
			
		||||
            hintText: L10n.of(context).enterYourHomeserver,
 | 
			
		||||
            hintText: L10n.of(context)!.enterYourHomeserver,
 | 
			
		||||
            searchController: controller.homeserverController,
 | 
			
		||||
            suffix: const Icon(Icons.edit_outlined),
 | 
			
		||||
            padding: EdgeInsets.zero,
 | 
			
		||||
@ -36,7 +36,7 @@ class HomeserverPickerView extends StatelessWidget {
 | 
			
		||||
            onSubmit: (_) => controller.checkHomeserverAction(),
 | 
			
		||||
            unfocusOnClear: false,
 | 
			
		||||
            autocorrect: false,
 | 
			
		||||
            labelText: L10n.of(context).homeserver,
 | 
			
		||||
            labelText: L10n.of(context)!.homeserver,
 | 
			
		||||
          ),
 | 
			
		||||
          elevation: 0,
 | 
			
		||||
        ),
 | 
			
		||||
@ -54,7 +54,7 @@ class HomeserverPickerView extends StatelessWidget {
 | 
			
		||||
              child: Padding(
 | 
			
		||||
                padding: const EdgeInsets.all(12.0),
 | 
			
		||||
                child: Text(
 | 
			
		||||
                  controller.error,
 | 
			
		||||
                  controller.error!,
 | 
			
		||||
                  textAlign: TextAlign.center,
 | 
			
		||||
                  style: TextStyle(
 | 
			
		||||
                    fontSize: 18,
 | 
			
		||||
@ -77,7 +77,7 @@ class HomeserverPickerView extends StatelessWidget {
 | 
			
		||||
                      const Expanded(child: Divider()),
 | 
			
		||||
                      Padding(
 | 
			
		||||
                        padding: const EdgeInsets.all(12.0),
 | 
			
		||||
                        child: Text(L10n.of(context).loginWithOneClick),
 | 
			
		||||
                        child: Text(L10n.of(context)!.loginWithOneClick),
 | 
			
		||||
                      ),
 | 
			
		||||
                      const Expanded(child: Divider()),
 | 
			
		||||
                    ]),
 | 
			
		||||
@ -88,20 +88,20 @@ class HomeserverPickerView extends StatelessWidget {
 | 
			
		||||
                            in controller.identityProviders)
 | 
			
		||||
                          _SsoButton(
 | 
			
		||||
                            onPressed: () =>
 | 
			
		||||
                                controller.ssoLoginAction(identityProvider.id),
 | 
			
		||||
                                controller.ssoLoginAction(identityProvider.id!),
 | 
			
		||||
                            identityProvider: identityProvider,
 | 
			
		||||
                          ),
 | 
			
		||||
                      },
 | 
			
		||||
                    ].toList(),
 | 
			
		||||
                  ),
 | 
			
		||||
                  if (controller.ssoLoginSupported &&
 | 
			
		||||
                      (controller.registrationSupported ||
 | 
			
		||||
                      (controller.registrationSupported! ||
 | 
			
		||||
                          controller.passwordLoginSupported))
 | 
			
		||||
                    Row(children: [
 | 
			
		||||
                      const Expanded(child: Divider()),
 | 
			
		||||
                      Padding(
 | 
			
		||||
                        padding: const EdgeInsets.all(12.0),
 | 
			
		||||
                        child: Text(L10n.of(context).or),
 | 
			
		||||
                        child: Text(L10n.of(context)!.or),
 | 
			
		||||
                      ),
 | 
			
		||||
                      const Expanded(child: Divider()),
 | 
			
		||||
                    ]),
 | 
			
		||||
@ -111,22 +111,22 @@ class HomeserverPickerView extends StatelessWidget {
 | 
			
		||||
                        onPressed: () => VRouter.of(context).to('login'),
 | 
			
		||||
                        icon: Icon(
 | 
			
		||||
                          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),
 | 
			
		||||
                  ],
 | 
			
		||||
                  if (controller.registrationSupported)
 | 
			
		||||
                  if (controller.registrationSupported!)
 | 
			
		||||
                    Center(
 | 
			
		||||
                      child: _LoginButton(
 | 
			
		||||
                        onPressed: controller.signUpAction,
 | 
			
		||||
                        icon: Icon(
 | 
			
		||||
                          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(
 | 
			
		||||
                onPressed: () => launch(AppConfig.privacyUrl),
 | 
			
		||||
                child: Text(
 | 
			
		||||
                  L10n.of(context).privacy,
 | 
			
		||||
                  L10n.of(context)!.privacy,
 | 
			
		||||
                  style: const TextStyle(
 | 
			
		||||
                    decoration: TextDecoration.underline,
 | 
			
		||||
                    color: Colors.blueGrey,
 | 
			
		||||
@ -152,7 +152,7 @@ class HomeserverPickerView extends StatelessWidget {
 | 
			
		||||
              TextButton(
 | 
			
		||||
                onPressed: () => PlatformInfos.showDialog(context),
 | 
			
		||||
                child: Text(
 | 
			
		||||
                  L10n.of(context).about,
 | 
			
		||||
                  L10n.of(context)!.about,
 | 
			
		||||
                  style: const TextStyle(
 | 
			
		||||
                    decoration: TextDecoration.underline,
 | 
			
		||||
                    color: Colors.blueGrey,
 | 
			
		||||
@ -169,10 +169,10 @@ class HomeserverPickerView extends StatelessWidget {
 | 
			
		||||
 | 
			
		||||
class _SsoButton extends StatelessWidget {
 | 
			
		||||
  final IdentityProvider identityProvider;
 | 
			
		||||
  final void Function() onPressed;
 | 
			
		||||
  final void Function()? onPressed;
 | 
			
		||||
  const _SsoButton({
 | 
			
		||||
    Key key,
 | 
			
		||||
    @required this.identityProvider,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.identityProvider,
 | 
			
		||||
    this.onPressed,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
@ -196,7 +196,7 @@ class _SsoButton extends StatelessWidget {
 | 
			
		||||
                child: identityProvider.icon == null
 | 
			
		||||
                    ? const Icon(Icons.web_outlined)
 | 
			
		||||
                    : CachedNetworkImage(
 | 
			
		||||
                        imageUrl: Uri.parse(identityProvider.icon)
 | 
			
		||||
                        imageUrl: Uri.parse(identityProvider.icon!)
 | 
			
		||||
                            .getDownloadLink(
 | 
			
		||||
                                Matrix.of(context).getLoginClient())
 | 
			
		||||
                            .toString(),
 | 
			
		||||
@ -209,11 +209,11 @@ class _SsoButton extends StatelessWidget {
 | 
			
		||||
            Text(
 | 
			
		||||
              identityProvider.name ??
 | 
			
		||||
                  identityProvider.brand ??
 | 
			
		||||
                  L10n.of(context).singlesignon,
 | 
			
		||||
                  L10n.of(context)!.singlesignon,
 | 
			
		||||
              style: TextStyle(
 | 
			
		||||
                fontSize: 12,
 | 
			
		||||
                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 {
 | 
			
		||||
  final String labelText;
 | 
			
		||||
  final Widget icon;
 | 
			
		||||
  final void Function() onPressed;
 | 
			
		||||
  final String? labelText;
 | 
			
		||||
  final Widget? icon;
 | 
			
		||||
  final void Function()? onPressed;
 | 
			
		||||
  const _LoginButton({
 | 
			
		||||
    Key key,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    this.labelText,
 | 
			
		||||
    this.icon,
 | 
			
		||||
    this.onPressed,
 | 
			
		||||
@ -246,11 +246,11 @@ class _LoginButton extends StatelessWidget {
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
      onPressed: onPressed,
 | 
			
		||||
      icon: icon,
 | 
			
		||||
      icon: icon!,
 | 
			
		||||
      label: Text(
 | 
			
		||||
        labelText,
 | 
			
		||||
        labelText!,
 | 
			
		||||
        style: TextStyle(
 | 
			
		||||
          color: Theme.of(context).textTheme.bodyText1.color,
 | 
			
		||||
          color: Theme.of(context).textTheme.bodyText1!.color,
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@ -10,9 +10,9 @@ import '../../utils/matrix_sdk_extensions.dart/event_extension.dart';
 | 
			
		||||
 | 
			
		||||
class ImageViewer extends StatefulWidget {
 | 
			
		||||
  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
 | 
			
		||||
  ImageViewerController createState() => ImageViewerController();
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,7 @@ import 'image_viewer.dart';
 | 
			
		||||
class ImageViewerView extends StatelessWidget {
 | 
			
		||||
  final ImageViewerController controller;
 | 
			
		||||
 | 
			
		||||
  const ImageViewerView(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const ImageViewerView(this.controller, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
@ -21,7 +21,7 @@ class ImageViewerView extends StatelessWidget {
 | 
			
		||||
          icon: const Icon(Icons.close),
 | 
			
		||||
          onPressed: Navigator.of(context).pop,
 | 
			
		||||
          color: Colors.white,
 | 
			
		||||
          tooltip: L10n.of(context).close,
 | 
			
		||||
          tooltip: L10n.of(context)!.close,
 | 
			
		||||
        ),
 | 
			
		||||
        backgroundColor: const Color(0x44000000),
 | 
			
		||||
        actions: [
 | 
			
		||||
@ -29,13 +29,13 @@ class ImageViewerView extends StatelessWidget {
 | 
			
		||||
            icon: const Icon(Icons.reply_outlined),
 | 
			
		||||
            onPressed: controller.forwardAction,
 | 
			
		||||
            color: Colors.white,
 | 
			
		||||
            tooltip: L10n.of(context).share,
 | 
			
		||||
            tooltip: L10n.of(context)!.share,
 | 
			
		||||
          ),
 | 
			
		||||
          IconButton(
 | 
			
		||||
            icon: const Icon(Icons.download_outlined),
 | 
			
		||||
            onPressed: controller.saveFileAction,
 | 
			
		||||
            color: Colors.white,
 | 
			
		||||
            tooltip: L10n.of(context).downloadFile,
 | 
			
		||||
            tooltip: L10n.of(context)!.downloadFile,
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,7 @@ import 'package:fluffychat/widgets/matrix.dart';
 | 
			
		||||
import '../../utils/localized_exception_extension.dart';
 | 
			
		||||
 | 
			
		||||
class InvitationSelection extends StatefulWidget {
 | 
			
		||||
  const InvitationSelection({Key key}) : super(key: key);
 | 
			
		||||
  const InvitationSelection({Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  InvitationSelectionController createState() =>
 | 
			
		||||
@ -21,16 +21,16 @@ class InvitationSelection extends StatefulWidget {
 | 
			
		||||
 | 
			
		||||
class InvitationSelectionController extends State<InvitationSelection> {
 | 
			
		||||
  TextEditingController controller = TextEditingController();
 | 
			
		||||
  String currentSearchTerm;
 | 
			
		||||
  late String currentSearchTerm;
 | 
			
		||||
  bool loading = false;
 | 
			
		||||
  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 {
 | 
			
		||||
    final client = Matrix.of(context).client;
 | 
			
		||||
    final room = client.getRoomById(roomId);
 | 
			
		||||
    final room = client.getRoomById(roomId!)!;
 | 
			
		||||
    final participants = await room.requestParticipants();
 | 
			
		||||
    participants.removeWhere(
 | 
			
		||||
      (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 contacts = client.rooms
 | 
			
		||||
        .where((r) => r.isDirectChat)
 | 
			
		||||
        .map((r) => r.getUserByMXIDSync(r.directChatMatrixID))
 | 
			
		||||
        .map((r) => r.getUserByMXIDSync(r.directChatMatrixID!))
 | 
			
		||||
        .toList()
 | 
			
		||||
      ..removeWhere((u) => participantsIds.contains(u.stateKey));
 | 
			
		||||
    contacts.sort(
 | 
			
		||||
@ -50,14 +50,14 @@ class InvitationSelectionController extends State<InvitationSelection> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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(
 | 
			
		||||
      context: context,
 | 
			
		||||
      future: () => room.invite(id),
 | 
			
		||||
      future: () => room!.invite(id),
 | 
			
		||||
    );
 | 
			
		||||
    if (success.error == null) {
 | 
			
		||||
      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);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
          SnackBar(content: Text((e as Object).toLocalizedString(context))));
 | 
			
		||||
          SnackBar(content: Text((e).toLocalizedString(context))));
 | 
			
		||||
      return;
 | 
			
		||||
    } finally {
 | 
			
		||||
      setState(() => loading = false);
 | 
			
		||||
@ -99,7 +99,7 @@ class InvitationSelectionController extends State<InvitationSelection> {
 | 
			
		||||
      }
 | 
			
		||||
      final participants = Matrix.of(context)
 | 
			
		||||
          .client
 | 
			
		||||
          .getRoomById(roomId)
 | 
			
		||||
          .getRoomById(roomId!)!
 | 
			
		||||
          .getParticipants()
 | 
			
		||||
          .where((user) =>
 | 
			
		||||
              [Membership.join, Membership.invite].contains(user.membership))
 | 
			
		||||
 | 
			
		||||
@ -13,13 +13,12 @@ import 'package:fluffychat/widgets/matrix.dart';
 | 
			
		||||
class InvitationSelectionView extends StatelessWidget {
 | 
			
		||||
  final InvitationSelectionController controller;
 | 
			
		||||
 | 
			
		||||
  const InvitationSelectionView(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const InvitationSelectionView(this.controller, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(controller.roomId);
 | 
			
		||||
    final groupName =
 | 
			
		||||
        room.name?.isEmpty ?? false ? L10n.of(context).group : room.name;
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(controller.roomId!)!;
 | 
			
		||||
    final groupName = room.name.isEmpty ? L10n.of(context)!.group : room.name;
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        leading: VRouter.of(context).path.startsWith('/spaces/')
 | 
			
		||||
@ -27,12 +26,12 @@ class InvitationSelectionView extends StatelessWidget {
 | 
			
		||||
            : IconButton(
 | 
			
		||||
                icon: const Icon(Icons.close_outlined),
 | 
			
		||||
                onPressed: () => VRouter.of(context)
 | 
			
		||||
                    .toSegments(['rooms', controller.roomId]),
 | 
			
		||||
                    .toSegments(['rooms', controller.roomId!]),
 | 
			
		||||
              ),
 | 
			
		||||
        titleSpacing: 0,
 | 
			
		||||
        title: DefaultAppBarSearchField(
 | 
			
		||||
          autofocus: true,
 | 
			
		||||
          hintText: L10n.of(context).inviteContactToGroup(groupName),
 | 
			
		||||
          hintText: L10n.of(context)!.inviteContactToGroup(groupName),
 | 
			
		||||
          onChanged: controller.searchUserWithCoolDown,
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
@ -51,7 +50,7 @@ class InvitationSelectionView extends StatelessWidget {
 | 
			
		||||
                  ),
 | 
			
		||||
                  title: Text(
 | 
			
		||||
                    controller.foundProfiles[i].displayName ??
 | 
			
		||||
                        controller.foundProfiles[i].userId.localpart,
 | 
			
		||||
                        controller.foundProfiles[i].userId.localpart!,
 | 
			
		||||
                  ),
 | 
			
		||||
                  subtitle: Text(controller.foundProfiles[i].userId),
 | 
			
		||||
                  onTap: () => controller.inviteAction(
 | 
			
		||||
@ -66,7 +65,7 @@ class InvitationSelectionView extends StatelessWidget {
 | 
			
		||||
                      child: CircularProgressIndicator.adaptive(strokeWidth: 2),
 | 
			
		||||
                    );
 | 
			
		||||
                  }
 | 
			
		||||
                  final contacts = snapshot.data;
 | 
			
		||||
                  final contacts = snapshot.data!;
 | 
			
		||||
                  return ListView.builder(
 | 
			
		||||
                    physics: const NeverScrollableScrollPhysics(),
 | 
			
		||||
                    shrinkWrap: true,
 | 
			
		||||
 | 
			
		||||
@ -34,8 +34,8 @@ class KeyVerificationDialog extends StatefulWidget {
 | 
			
		||||
  final KeyVerification request;
 | 
			
		||||
 | 
			
		||||
  const KeyVerificationDialog({
 | 
			
		||||
    Key key,
 | 
			
		||||
    this.request,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.request,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@ -43,25 +43,23 @@ class KeyVerificationDialog extends StatefulWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _KeyVerificationPageState extends State<KeyVerificationDialog> {
 | 
			
		||||
  void Function() originalOnUpdate;
 | 
			
		||||
  List<dynamic> sasEmoji;
 | 
			
		||||
  void Function()? originalOnUpdate;
 | 
			
		||||
  late final List<dynamic> sasEmoji;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    originalOnUpdate = widget.request.onUpdate;
 | 
			
		||||
    widget.request.onUpdate = () {
 | 
			
		||||
      if (originalOnUpdate != null) {
 | 
			
		||||
        originalOnUpdate();
 | 
			
		||||
      }
 | 
			
		||||
      setState(() => null);
 | 
			
		||||
      originalOnUpdate?.call();
 | 
			
		||||
      setState(() {});
 | 
			
		||||
    };
 | 
			
		||||
    widget.request.client.getProfileFromUserId(widget.request.userId).then((p) {
 | 
			
		||||
      profile = p;
 | 
			
		||||
      setState(() => null);
 | 
			
		||||
      setState(() {});
 | 
			
		||||
    });
 | 
			
		||||
    rootBundle.loadString('assets/sas-emoji.json').then((e) {
 | 
			
		||||
      sasEmoji = json.decode(e);
 | 
			
		||||
      setState(() => null);
 | 
			
		||||
      setState(() {});
 | 
			
		||||
    });
 | 
			
		||||
    super.initState();
 | 
			
		||||
  }
 | 
			
		||||
@ -77,12 +75,11 @@ class _KeyVerificationPageState extends State<KeyVerificationDialog> {
 | 
			
		||||
    super.dispose();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Profile profile;
 | 
			
		||||
  Profile? profile;
 | 
			
		||||
 | 
			
		||||
  Future<void> checkInput(String input) async {
 | 
			
		||||
    if (input == null || input.isEmpty) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (input.isEmpty) return;
 | 
			
		||||
 | 
			
		||||
    final valid = await showFutureLoadingDialog(
 | 
			
		||||
        context: context,
 | 
			
		||||
        future: () async {
 | 
			
		||||
@ -101,24 +98,24 @@ class _KeyVerificationPageState extends State<KeyVerificationDialog> {
 | 
			
		||||
      await showOkAlertDialog(
 | 
			
		||||
        useRootNavigator: false,
 | 
			
		||||
        context: context,
 | 
			
		||||
        message: L10n.of(context).incorrectPassphraseOrKey,
 | 
			
		||||
        message: L10n.of(context)!.incorrectPassphraseOrKey,
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    User user;
 | 
			
		||||
    User? user;
 | 
			
		||||
    final directChatId =
 | 
			
		||||
        widget.request.client.getDirectChatFromUserId(widget.request.userId);
 | 
			
		||||
    if (directChatId != null) {
 | 
			
		||||
      user = widget.request.client
 | 
			
		||||
          .getRoomById(directChatId)
 | 
			
		||||
          ?.getUserByMXIDSync(widget.request.userId);
 | 
			
		||||
          .getRoomById(directChatId)!
 | 
			
		||||
          .getUserByMXIDSync(widget.request.userId);
 | 
			
		||||
    }
 | 
			
		||||
    final displayName =
 | 
			
		||||
        user?.calcDisplayname() ?? widget.request.userId.localpart;
 | 
			
		||||
    var title = Text(L10n.of(context).verifyTitle);
 | 
			
		||||
        user?.calcDisplayname() ?? widget.request.userId.localpart!;
 | 
			
		||||
    var title = Text(L10n.of(context)!.verifyTitle);
 | 
			
		||||
    Widget body;
 | 
			
		||||
    final buttons = <Widget>[];
 | 
			
		||||
    switch (widget.request.state) {
 | 
			
		||||
@ -131,7 +128,7 @@ class _KeyVerificationPageState extends State<KeyVerificationDialog> {
 | 
			
		||||
          child: Column(
 | 
			
		||||
            mainAxisSize: MainAxisSize.min,
 | 
			
		||||
            children: <Widget>[
 | 
			
		||||
              Text(L10n.of(context).askSSSSSign,
 | 
			
		||||
              Text(L10n.of(context)!.askSSSSSign,
 | 
			
		||||
                  style: const TextStyle(fontSize: 20)),
 | 
			
		||||
              Container(height: 10),
 | 
			
		||||
              TextField(
 | 
			
		||||
@ -146,7 +143,7 @@ class _KeyVerificationPageState extends State<KeyVerificationDialog> {
 | 
			
		||||
                maxLines: 1,
 | 
			
		||||
                obscureText: true,
 | 
			
		||||
                decoration: InputDecoration(
 | 
			
		||||
                  hintText: L10n.of(context).passphraseOrKey,
 | 
			
		||||
                  hintText: L10n.of(context)!.passphraseOrKey,
 | 
			
		||||
                  prefixStyle: TextStyle(color: Theme.of(context).primaryColor),
 | 
			
		||||
                  suffixStyle: TextStyle(color: Theme.of(context).primaryColor),
 | 
			
		||||
                  border: const OutlineInputBorder(),
 | 
			
		||||
@ -156,16 +153,16 @@ class _KeyVerificationPageState extends State<KeyVerificationDialog> {
 | 
			
		||||
          ),
 | 
			
		||||
        );
 | 
			
		||||
        buttons.add(AdaptiveFlatButton(
 | 
			
		||||
          label: L10n.of(context).submit,
 | 
			
		||||
          label: L10n.of(context)!.submit,
 | 
			
		||||
          onPressed: () => checkInput(textEditingController.text),
 | 
			
		||||
        ));
 | 
			
		||||
        buttons.add(AdaptiveFlatButton(
 | 
			
		||||
          label: L10n.of(context).skip,
 | 
			
		||||
          label: L10n.of(context)!.skip,
 | 
			
		||||
          onPressed: () => widget.request.openSSSS(skip: true),
 | 
			
		||||
        ));
 | 
			
		||||
        break;
 | 
			
		||||
      case KeyVerificationState.askAccept:
 | 
			
		||||
        title = Text(L10n.of(context).newVerificationRequest);
 | 
			
		||||
        title = Text(L10n.of(context)!.newVerificationRequest);
 | 
			
		||||
        body = Column(
 | 
			
		||||
          mainAxisSize: MainAxisSize.min,
 | 
			
		||||
          children: [
 | 
			
		||||
@ -199,19 +196,19 @@ class _KeyVerificationPageState extends State<KeyVerificationDialog> {
 | 
			
		||||
            Image.asset('assets/verification.png', fit: BoxFit.contain),
 | 
			
		||||
            const SizedBox(height: 16),
 | 
			
		||||
            Text(
 | 
			
		||||
              L10n.of(context).askVerificationRequest(displayName),
 | 
			
		||||
              L10n.of(context)!.askVerificationRequest(displayName),
 | 
			
		||||
            )
 | 
			
		||||
          ],
 | 
			
		||||
        );
 | 
			
		||||
        buttons.add(AdaptiveFlatButton(
 | 
			
		||||
          label: L10n.of(context).reject,
 | 
			
		||||
          label: L10n.of(context)!.reject,
 | 
			
		||||
          textColor: Colors.red,
 | 
			
		||||
          onPressed: () => widget.request
 | 
			
		||||
              .rejectVerification()
 | 
			
		||||
              .then((_) => Navigator.of(context, rootNavigator: false).pop()),
 | 
			
		||||
        ));
 | 
			
		||||
        buttons.add(AdaptiveFlatButton(
 | 
			
		||||
          label: L10n.of(context).accept,
 | 
			
		||||
          label: L10n.of(context)!.accept,
 | 
			
		||||
          onPressed: () => widget.request.acceptVerification(),
 | 
			
		||||
        ));
 | 
			
		||||
        break;
 | 
			
		||||
@ -224,22 +221,22 @@ class _KeyVerificationPageState extends State<KeyVerificationDialog> {
 | 
			
		||||
            const CircularProgressIndicator.adaptive(strokeWidth: 2),
 | 
			
		||||
            const SizedBox(height: 16),
 | 
			
		||||
            Text(
 | 
			
		||||
              L10n.of(context).waitingPartnerAcceptRequest,
 | 
			
		||||
              L10n.of(context)!.waitingPartnerAcceptRequest,
 | 
			
		||||
              textAlign: TextAlign.center,
 | 
			
		||||
            ),
 | 
			
		||||
          ],
 | 
			
		||||
        );
 | 
			
		||||
        final key = widget.request.client.userDeviceKeys[widget.request.userId]
 | 
			
		||||
            .deviceKeys[widget.request.deviceId];
 | 
			
		||||
            ?.deviceKeys[widget.request.deviceId];
 | 
			
		||||
        if (key != null) {
 | 
			
		||||
          buttons.add(AdaptiveFlatButton(
 | 
			
		||||
            label: L10n.of(context).verifyManual,
 | 
			
		||||
            label: L10n.of(context)!.verifyManual,
 | 
			
		||||
            onPressed: () async {
 | 
			
		||||
              final result = await showOkCancelAlertDialog(
 | 
			
		||||
                useRootNavigator: false,
 | 
			
		||||
                context: context,
 | 
			
		||||
                title: L10n.of(context).verifyManual,
 | 
			
		||||
                message: key.ed25519Key.beautified,
 | 
			
		||||
                title: L10n.of(context)!.verifyManual,
 | 
			
		||||
                message: key.ed25519Key?.beautified ?? 'Key not found',
 | 
			
		||||
              );
 | 
			
		||||
              if (result == OkCancelResult.ok) {
 | 
			
		||||
                await key.setVerified(true);
 | 
			
		||||
@ -257,14 +254,14 @@ class _KeyVerificationPageState extends State<KeyVerificationDialog> {
 | 
			
		||||
        // view for if "emoji" is a present sasType or not?
 | 
			
		||||
        String compareText;
 | 
			
		||||
        if (widget.request.sasTypes.contains('emoji')) {
 | 
			
		||||
          compareText = L10n.of(context).compareEmojiMatch;
 | 
			
		||||
          compareText = L10n.of(context)!.compareEmojiMatch;
 | 
			
		||||
          compareWidget = TextSpan(
 | 
			
		||||
            children: widget.request.sasEmojis
 | 
			
		||||
                .map((e) => WidgetSpan(child: _Emoji(e, sasEmoji)))
 | 
			
		||||
                .toList(),
 | 
			
		||||
          );
 | 
			
		||||
        } else {
 | 
			
		||||
          compareText = L10n.of(context).compareNumbersMatch;
 | 
			
		||||
          compareText = L10n.of(context)!.compareNumbersMatch;
 | 
			
		||||
          final numbers = widget.request.sasNumbers;
 | 
			
		||||
          final numbstr = '${numbers[0]}-${numbers[1]}-${numbers[2]}';
 | 
			
		||||
          compareWidget =
 | 
			
		||||
@ -289,18 +286,18 @@ class _KeyVerificationPageState extends State<KeyVerificationDialog> {
 | 
			
		||||
        );
 | 
			
		||||
        buttons.add(AdaptiveFlatButton(
 | 
			
		||||
          textColor: Colors.red,
 | 
			
		||||
          label: L10n.of(context).theyDontMatch,
 | 
			
		||||
          label: L10n.of(context)!.theyDontMatch,
 | 
			
		||||
          onPressed: () => widget.request.rejectSas(),
 | 
			
		||||
        ));
 | 
			
		||||
        buttons.add(AdaptiveFlatButton(
 | 
			
		||||
          label: L10n.of(context).theyMatch,
 | 
			
		||||
          label: L10n.of(context)!.theyMatch,
 | 
			
		||||
          onPressed: () => widget.request.acceptSas(),
 | 
			
		||||
        ));
 | 
			
		||||
        break;
 | 
			
		||||
      case KeyVerificationState.waitingSas:
 | 
			
		||||
        final acceptText = widget.request.sasTypes.contains('emoji')
 | 
			
		||||
            ? L10n.of(context).waitingPartnerEmoji
 | 
			
		||||
            : L10n.of(context).waitingPartnerNumbers;
 | 
			
		||||
            ? L10n.of(context)!.waitingPartnerEmoji
 | 
			
		||||
            : L10n.of(context)!.waitingPartnerNumbers;
 | 
			
		||||
        body = Column(
 | 
			
		||||
          mainAxisSize: MainAxisSize.min,
 | 
			
		||||
          children: <Widget>[
 | 
			
		||||
@ -321,13 +318,13 @@ class _KeyVerificationPageState extends State<KeyVerificationDialog> {
 | 
			
		||||
                color: Colors.green, size: 200.0),
 | 
			
		||||
            const SizedBox(height: 10),
 | 
			
		||||
            Text(
 | 
			
		||||
              L10n.of(context).verifySuccess,
 | 
			
		||||
              L10n.of(context)!.verifySuccess,
 | 
			
		||||
              textAlign: TextAlign.center,
 | 
			
		||||
            ),
 | 
			
		||||
          ],
 | 
			
		||||
        );
 | 
			
		||||
        buttons.add(AdaptiveFlatButton(
 | 
			
		||||
          label: L10n.of(context).close,
 | 
			
		||||
          label: L10n.of(context)!.close,
 | 
			
		||||
          onPressed: () => Navigator.of(context, rootNavigator: false).pop(),
 | 
			
		||||
        ));
 | 
			
		||||
        break;
 | 
			
		||||
@ -344,12 +341,11 @@ class _KeyVerificationPageState extends State<KeyVerificationDialog> {
 | 
			
		||||
          ],
 | 
			
		||||
        );
 | 
			
		||||
        buttons.add(AdaptiveFlatButton(
 | 
			
		||||
          label: L10n.of(context).close,
 | 
			
		||||
          label: L10n.of(context)!.close,
 | 
			
		||||
          onPressed: () => Navigator.of(context, rootNavigator: false).pop(),
 | 
			
		||||
        ));
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    body ??= Text('ERROR: Unknown state ' + widget.request.state.toString());
 | 
			
		||||
    final content = SingleChildScrollView(
 | 
			
		||||
      scrollDirection: Axis.vertical,
 | 
			
		||||
      child: Column(
 | 
			
		||||
@ -377,16 +373,17 @@ class _KeyVerificationPageState extends State<KeyVerificationDialog> {
 | 
			
		||||
 | 
			
		||||
class _Emoji extends StatelessWidget {
 | 
			
		||||
  final KeyVerificationEmoji emoji;
 | 
			
		||||
  final List<dynamic> sasEmoji;
 | 
			
		||||
  final List<dynamic>? sasEmoji;
 | 
			
		||||
 | 
			
		||||
  const _Emoji(this.emoji, this.sasEmoji);
 | 
			
		||||
 | 
			
		||||
  String getLocalizedName() {
 | 
			
		||||
    final sasEmoji = this.sasEmoji;
 | 
			
		||||
    if (sasEmoji == null) {
 | 
			
		||||
      // asset is still being loaded
 | 
			
		||||
      return emoji.name;
 | 
			
		||||
    }
 | 
			
		||||
    final translations = Map<String, String>.from(
 | 
			
		||||
    final translations = Map<String, String?>.from(
 | 
			
		||||
        sasEmoji[emoji.number]['translated_descriptions']);
 | 
			
		||||
    translations['en'] = emoji.name;
 | 
			
		||||
    for (final locale in window.locales) {
 | 
			
		||||
@ -398,7 +395,7 @@ class _Emoji extends StatelessWidget {
 | 
			
		||||
        if (haveLanguage == wantLanguage &&
 | 
			
		||||
            (Set.from(haveLocaleParts)..removeAll(wantLocaleParts)).isEmpty &&
 | 
			
		||||
            (translations[haveLocale]?.isNotEmpty ?? false)) {
 | 
			
		||||
          return translations[haveLocale];
 | 
			
		||||
          return translations[haveLocale]!;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,7 @@ import '../../utils/platform_infos.dart';
 | 
			
		||||
import 'login_view.dart';
 | 
			
		||||
 | 
			
		||||
class Login extends StatefulWidget {
 | 
			
		||||
  const Login({Key key}) : super(key: key);
 | 
			
		||||
  const Login({Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  LoginController createState() => LoginController();
 | 
			
		||||
@ -24,8 +24,8 @@ class Login extends StatefulWidget {
 | 
			
		||||
class LoginController extends State<Login> {
 | 
			
		||||
  final TextEditingController usernameController = TextEditingController();
 | 
			
		||||
  final TextEditingController passwordController = TextEditingController();
 | 
			
		||||
  String usernameError;
 | 
			
		||||
  String passwordError;
 | 
			
		||||
  String? usernameError;
 | 
			
		||||
  String? passwordError;
 | 
			
		||||
  bool loading = false;
 | 
			
		||||
  bool showPassword = false;
 | 
			
		||||
 | 
			
		||||
@ -34,12 +34,12 @@ class LoginController extends State<Login> {
 | 
			
		||||
  void login([_]) async {
 | 
			
		||||
    final matrix = Matrix.of(context);
 | 
			
		||||
    if (usernameController.text.isEmpty) {
 | 
			
		||||
      setState(() => usernameError = L10n.of(context).pleaseEnterYourUsername);
 | 
			
		||||
      setState(() => usernameError = L10n.of(context)!.pleaseEnterYourUsername);
 | 
			
		||||
    } else {
 | 
			
		||||
      setState(() => usernameError = null);
 | 
			
		||||
    }
 | 
			
		||||
    if (passwordController.text.isEmpty) {
 | 
			
		||||
      setState(() => passwordError = L10n.of(context).pleaseEnterYourPassword);
 | 
			
		||||
      setState(() => passwordError = L10n.of(context)!.pleaseEnterYourPassword);
 | 
			
		||||
    } else {
 | 
			
		||||
      setState(() => passwordError = null);
 | 
			
		||||
    }
 | 
			
		||||
@ -85,7 +85,7 @@ class LoginController extends State<Login> {
 | 
			
		||||
    if (mounted) setState(() => loading = false);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Timer _coolDown;
 | 
			
		||||
  Timer? _coolDown;
 | 
			
		||||
 | 
			
		||||
  void checkWellKnownWithCoolDown(String userId) async {
 | 
			
		||||
    _coolDown?.cancel();
 | 
			
		||||
@ -100,14 +100,13 @@ class LoginController extends State<Login> {
 | 
			
		||||
    if (!userId.isValidMatrixId) return;
 | 
			
		||||
    try {
 | 
			
		||||
      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;
 | 
			
		||||
      DiscoveryInformation wellKnownInformation;
 | 
			
		||||
      DiscoveryInformation? wellKnownInformation;
 | 
			
		||||
      try {
 | 
			
		||||
        wellKnownInformation =
 | 
			
		||||
            await Matrix.of(context).getLoginClient().getWellknown();
 | 
			
		||||
        if (wellKnownInformation.mHomeserver?.baseUrl?.toString()?.isNotEmpty ??
 | 
			
		||||
            false) {
 | 
			
		||||
        if (wellKnownInformation.mHomeserver.baseUrl.toString().isNotEmpty) {
 | 
			
		||||
          newDomain = wellKnownInformation.mHomeserver.baseUrl;
 | 
			
		||||
        }
 | 
			
		||||
      } catch (_) {
 | 
			
		||||
@ -130,9 +129,10 @@ class LoginController extends State<Login> {
 | 
			
		||||
          final dialogResult = await showOkCancelAlertDialog(
 | 
			
		||||
            context: context,
 | 
			
		||||
            useRootNavigator: false,
 | 
			
		||||
            message: L10n.of(context).noMatrixServer(newDomain, oldHomeserver),
 | 
			
		||||
            okLabel: L10n.of(context).ok,
 | 
			
		||||
            cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
            message:
 | 
			
		||||
                L10n.of(context)!.noMatrixServer(newDomain, oldHomeserver!),
 | 
			
		||||
            okLabel: L10n.of(context)!.ok,
 | 
			
		||||
            cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
          );
 | 
			
		||||
          if (dialogResult == OkCancelResult.ok) {
 | 
			
		||||
            setState(() => usernameError = null);
 | 
			
		||||
@ -142,7 +142,7 @@ class LoginController extends State<Login> {
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        var jitsi = wellKnownInformation?.additionalProperties
 | 
			
		||||
            ?.tryGet<Map<String, dynamic>>('im.vector.riot.jitsi')
 | 
			
		||||
            .tryGet<Map<String, dynamic>>('im.vector.riot.jitsi')
 | 
			
		||||
            ?.tryGet<String>('preferredDomain');
 | 
			
		||||
        if (jitsi != null) {
 | 
			
		||||
          if (!jitsi.endsWith('/')) {
 | 
			
		||||
@ -168,12 +168,12 @@ class LoginController extends State<Login> {
 | 
			
		||||
    final input = await showTextInputDialog(
 | 
			
		||||
      useRootNavigator: false,
 | 
			
		||||
      context: context,
 | 
			
		||||
      title: L10n.of(context).enterAnEmailAddress,
 | 
			
		||||
      okLabel: L10n.of(context).ok,
 | 
			
		||||
      cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
      title: L10n.of(context)!.enterAnEmailAddress,
 | 
			
		||||
      okLabel: L10n.of(context)!.ok,
 | 
			
		||||
      cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
      textFields: [
 | 
			
		||||
        DialogTextField(
 | 
			
		||||
          hintText: L10n.of(context).enterAnEmailAddress,
 | 
			
		||||
          hintText: L10n.of(context)!.enterAnEmailAddress,
 | 
			
		||||
          keyboardType: TextInputType.emailAddress,
 | 
			
		||||
        ),
 | 
			
		||||
      ],
 | 
			
		||||
@ -194,17 +194,17 @@ class LoginController extends State<Login> {
 | 
			
		||||
    final ok = await showOkAlertDialog(
 | 
			
		||||
      useRootNavigator: false,
 | 
			
		||||
      context: context,
 | 
			
		||||
      title: L10n.of(context).weSentYouAnEmail,
 | 
			
		||||
      message: L10n.of(context).pleaseClickOnLink,
 | 
			
		||||
      okLabel: L10n.of(context).iHaveClickedOnLink,
 | 
			
		||||
      title: L10n.of(context)!.weSentYouAnEmail,
 | 
			
		||||
      message: L10n.of(context)!.pleaseClickOnLink,
 | 
			
		||||
      okLabel: L10n.of(context)!.iHaveClickedOnLink,
 | 
			
		||||
    );
 | 
			
		||||
    if (ok == null) return;
 | 
			
		||||
    if (ok != OkCancelResult.ok) return;
 | 
			
		||||
    final password = await showTextInputDialog(
 | 
			
		||||
      useRootNavigator: false,
 | 
			
		||||
      context: context,
 | 
			
		||||
      title: L10n.of(context).chooseAStrongPassword,
 | 
			
		||||
      okLabel: L10n.of(context).ok,
 | 
			
		||||
      cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
      title: L10n.of(context)!.chooseAStrongPassword,
 | 
			
		||||
      okLabel: L10n.of(context)!.ok,
 | 
			
		||||
      cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
      textFields: [
 | 
			
		||||
        const DialogTextField(
 | 
			
		||||
          hintText: '******',
 | 
			
		||||
@ -222,7 +222,7 @@ class LoginController extends State<Login> {
 | 
			
		||||
            auth: AuthenticationThreePidCreds(
 | 
			
		||||
              type: AuthenticationTypes.emailIdentity,
 | 
			
		||||
              threepidCreds: ThreepidCreds(
 | 
			
		||||
                sid: response.result.sid,
 | 
			
		||||
                sid: response.result!.sid,
 | 
			
		||||
                clientSecret: clientSecret,
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
@ -230,7 +230,7 @@ class LoginController extends State<Login> {
 | 
			
		||||
    );
 | 
			
		||||
    if (success.error == null) {
 | 
			
		||||
      ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
          SnackBar(content: Text(L10n.of(context).passwordHasBeenChanged)));
 | 
			
		||||
          SnackBar(content: Text(L10n.of(context)!.passwordHasBeenChanged)));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@ import 'login.dart';
 | 
			
		||||
class LoginView extends StatelessWidget {
 | 
			
		||||
  final LoginController controller;
 | 
			
		||||
 | 
			
		||||
  const LoginView(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const LoginView(this.controller, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
@ -19,7 +19,7 @@ class LoginView extends StatelessWidget {
 | 
			
		||||
          leading: controller.loading ? Container() : const BackButton(),
 | 
			
		||||
          elevation: 0,
 | 
			
		||||
          title: Text(
 | 
			
		||||
            L10n.of(context).logInTo(Matrix.of(context)
 | 
			
		||||
            L10n.of(context)!.logInTo(Matrix.of(context)
 | 
			
		||||
                .getLoginClient()
 | 
			
		||||
                .homeserver
 | 
			
		||||
                .toString()
 | 
			
		||||
@ -42,9 +42,9 @@ class LoginView extends StatelessWidget {
 | 
			
		||||
                        controller.loading ? null : [AutofillHints.username],
 | 
			
		||||
                    decoration: InputDecoration(
 | 
			
		||||
                        prefixIcon: const Icon(Icons.account_box_outlined),
 | 
			
		||||
                        hintText: L10n.of(context).username,
 | 
			
		||||
                        hintText: L10n.of(context)!.username,
 | 
			
		||||
                        errorText: controller.usernameError,
 | 
			
		||||
                        labelText: L10n.of(context).username),
 | 
			
		||||
                        labelText: L10n.of(context)!.username),
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
                Padding(
 | 
			
		||||
@ -62,13 +62,13 @@ class LoginView extends StatelessWidget {
 | 
			
		||||
                      hintText: '****',
 | 
			
		||||
                      errorText: controller.passwordError,
 | 
			
		||||
                      suffixIcon: IconButton(
 | 
			
		||||
                        tooltip: L10n.of(context).showPassword,
 | 
			
		||||
                        tooltip: L10n.of(context)!.showPassword,
 | 
			
		||||
                        icon: Icon(controller.showPassword
 | 
			
		||||
                            ? Icons.visibility_off_outlined
 | 
			
		||||
                            : Icons.visibility_outlined),
 | 
			
		||||
                        onPressed: controller.toggleShowPassword,
 | 
			
		||||
                      ),
 | 
			
		||||
                      labelText: L10n.of(context).password,
 | 
			
		||||
                      labelText: L10n.of(context)!.password,
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
@ -83,7 +83,7 @@ class LoginView extends StatelessWidget {
 | 
			
		||||
                          : () => controller.login(context),
 | 
			
		||||
                      child: controller.loading
 | 
			
		||||
                          ? const LinearProgressIndicator()
 | 
			
		||||
                          : Text(L10n.of(context).login),
 | 
			
		||||
                          : Text(L10n.of(context)!.login),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
@ -91,7 +91,7 @@ class LoginView extends StatelessWidget {
 | 
			
		||||
                  child: TextButton(
 | 
			
		||||
                    onPressed: controller.passwordForgotten,
 | 
			
		||||
                    child: Text(
 | 
			
		||||
                      L10n.of(context).passwordForgotten,
 | 
			
		||||
                      L10n.of(context)!.passwordForgotten,
 | 
			
		||||
                      style: const TextStyle(
 | 
			
		||||
                        color: Colors.blue,
 | 
			
		||||
                        decoration: TextDecoration.underline,
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,7 @@ import 'package:fluffychat/pages/new_group/new_group_view.dart';
 | 
			
		||||
import 'package:fluffychat/widgets/matrix.dart';
 | 
			
		||||
 | 
			
		||||
class NewGroup extends StatefulWidget {
 | 
			
		||||
  const NewGroup({Key key}) : super(key: key);
 | 
			
		||||
  const NewGroup({Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  NewGroupController createState() => NewGroupController();
 | 
			
		||||
@ -35,7 +35,7 @@ class NewGroupController extends State<NewGroup> {
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
    if (roomID.error == null) {
 | 
			
		||||
      VRouter.of(context).toSegments(['rooms', roomID.result, 'invite']);
 | 
			
		||||
      VRouter.of(context).toSegments(['rooms', roomID.result!, 'invite']);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -8,13 +8,13 @@ import 'package:fluffychat/widgets/layouts/max_width_body.dart';
 | 
			
		||||
class NewGroupView extends StatelessWidget {
 | 
			
		||||
  final NewGroupController controller;
 | 
			
		||||
 | 
			
		||||
  const NewGroupView(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const NewGroupView(this.controller, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        title: Text(L10n.of(context).createNewGroup),
 | 
			
		||||
        title: Text(L10n.of(context)!.createNewGroup),
 | 
			
		||||
      ),
 | 
			
		||||
      body: MaxWidthBody(
 | 
			
		||||
        child: Column(
 | 
			
		||||
@ -29,13 +29,13 @@ class NewGroupView extends StatelessWidget {
 | 
			
		||||
                textInputAction: TextInputAction.go,
 | 
			
		||||
                onSubmitted: controller.submitAction,
 | 
			
		||||
                decoration: InputDecoration(
 | 
			
		||||
                    labelText: L10n.of(context).optionalGroupName,
 | 
			
		||||
                    labelText: L10n.of(context)!.optionalGroupName,
 | 
			
		||||
                    prefixIcon: const Icon(Icons.people_outlined),
 | 
			
		||||
                    hintText: L10n.of(context).enterAGroupName),
 | 
			
		||||
                    hintText: L10n.of(context)!.enterAGroupName),
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
            SwitchListTile.adaptive(
 | 
			
		||||
              title: Text(L10n.of(context).groupIsPublic),
 | 
			
		||||
              title: Text(L10n.of(context)!.groupIsPublic),
 | 
			
		||||
              value: controller.publicGroup,
 | 
			
		||||
              onChanged: controller.setPublicGroup,
 | 
			
		||||
            ),
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ import 'package:fluffychat/utils/url_launcher.dart';
 | 
			
		||||
import 'package:fluffychat/widgets/matrix.dart';
 | 
			
		||||
 | 
			
		||||
class NewPrivateChat extends StatefulWidget {
 | 
			
		||||
  const NewPrivateChat({Key key}) : super(key: key);
 | 
			
		||||
  const NewPrivateChat({Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  NewPrivateChatController createState() => NewPrivateChatController();
 | 
			
		||||
@ -48,20 +48,20 @@ class NewPrivateChatController extends State<NewPrivateChat> {
 | 
			
		||||
 | 
			
		||||
  void submitAction([_]) async {
 | 
			
		||||
    controller.text = controller.text.trim();
 | 
			
		||||
    if (!formKey.currentState.validate()) return;
 | 
			
		||||
    if (!formKey.currentState!.validate()) return;
 | 
			
		||||
    UrlLauncher(context, '$prefix${controller.text}').openMatrixToUrl();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  String validateForm(String value) {
 | 
			
		||||
    if (value.isEmpty) {
 | 
			
		||||
      return L10n.of(context).pleaseEnterAMatrixIdentifier;
 | 
			
		||||
  String? validateForm(String? value) {
 | 
			
		||||
    if (value!.isEmpty) {
 | 
			
		||||
      return L10n.of(context)!.pleaseEnterAMatrixIdentifier;
 | 
			
		||||
    }
 | 
			
		||||
    if (!controller.text.isValidMatrixId ||
 | 
			
		||||
        !supportedSigils.contains(controller.text.sigil)) {
 | 
			
		||||
      return L10n.of(context).makeSureTheIdentifierIsValid;
 | 
			
		||||
      return L10n.of(context)!.makeSureTheIdentifierIsValid;
 | 
			
		||||
    }
 | 
			
		||||
    if (controller.text == Matrix.of(context).client.userID) {
 | 
			
		||||
      return L10n.of(context).youCannotInviteYourself;
 | 
			
		||||
      return L10n.of(context)!.youCannotInviteYourself;
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,7 @@ import 'package:fluffychat/widgets/matrix.dart';
 | 
			
		||||
class NewPrivateChatView extends StatelessWidget {
 | 
			
		||||
  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;
 | 
			
		||||
 | 
			
		||||
@ -24,13 +24,13 @@ class NewPrivateChatView extends StatelessWidget {
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        leading: const BackButton(),
 | 
			
		||||
        title: Text(L10n.of(context).newChat),
 | 
			
		||||
        title: Text(L10n.of(context)!.newChat),
 | 
			
		||||
        backgroundColor: Theme.of(context).scaffoldBackgroundColor,
 | 
			
		||||
        actions: [
 | 
			
		||||
          TextButton(
 | 
			
		||||
            onPressed: () => VRouter.of(context).to('/newgroup'),
 | 
			
		||||
            child: Text(
 | 
			
		||||
              L10n.of(context).createNewGroup,
 | 
			
		||||
              L10n.of(context)!.createNewGroup,
 | 
			
		||||
              style: TextStyle(color: Theme.of(context).colorScheme.secondary),
 | 
			
		||||
            ),
 | 
			
		||||
          )
 | 
			
		||||
@ -67,7 +67,7 @@ class NewPrivateChatView extends StatelessWidget {
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
            ListTile(
 | 
			
		||||
              subtitle: Text(L10n.of(context).createNewChatExplaination),
 | 
			
		||||
              subtitle: Text(L10n.of(context)!.createNewChatExplaination),
 | 
			
		||||
            ),
 | 
			
		||||
            Padding(
 | 
			
		||||
              padding: const EdgeInsets.all(12),
 | 
			
		||||
@ -81,7 +81,7 @@ class NewPrivateChatView extends StatelessWidget {
 | 
			
		||||
                  onFieldSubmitted: controller.submitAction,
 | 
			
		||||
                  validator: controller.validateForm,
 | 
			
		||||
                  decoration: InputDecoration(
 | 
			
		||||
                    labelText: L10n.of(context).typeInInviteLinkManually,
 | 
			
		||||
                    labelText: L10n.of(context)!.typeInInviteLinkManually,
 | 
			
		||||
                    hintText: '@username',
 | 
			
		||||
                    prefixText: 'matrix.to/#/',
 | 
			
		||||
                    suffixIcon: IconButton(
 | 
			
		||||
@ -105,7 +105,7 @@ class NewPrivateChatView extends StatelessWidget {
 | 
			
		||||
      floatingActionButton: PlatformInfos.isMobile && !controller.hideFab
 | 
			
		||||
          ? FloatingActionButton.extended(
 | 
			
		||||
              onPressed: controller.openScannerAction,
 | 
			
		||||
              label: Text(L10n.of(context).scanQrCode),
 | 
			
		||||
              label: Text(L10n.of(context)!.scanQrCode),
 | 
			
		||||
              icon: const Icon(Icons.camera_alt_outlined),
 | 
			
		||||
            )
 | 
			
		||||
          : null,
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@ import 'package:qr_code_scanner/qr_code_scanner.dart';
 | 
			
		||||
import 'package:fluffychat/utils/url_launcher.dart';
 | 
			
		||||
 | 
			
		||||
class QrScannerModal extends StatefulWidget {
 | 
			
		||||
  const QrScannerModal({Key key}) : super(key: key);
 | 
			
		||||
  const QrScannerModal({Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  _QrScannerModalState createState() => _QrScannerModalState();
 | 
			
		||||
@ -17,15 +17,15 @@ class QrScannerModal extends StatefulWidget {
 | 
			
		||||
 | 
			
		||||
class _QrScannerModalState extends State<QrScannerModal> {
 | 
			
		||||
  final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
 | 
			
		||||
  QRViewController controller;
 | 
			
		||||
  QRViewController? controller;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void reassemble() {
 | 
			
		||||
    super.reassemble();
 | 
			
		||||
    if (Platform.isAndroid) {
 | 
			
		||||
      controller.pauseCamera();
 | 
			
		||||
      controller!.pauseCamera();
 | 
			
		||||
    } else if (Platform.isIOS) {
 | 
			
		||||
      controller.resumeCamera();
 | 
			
		||||
      controller!.resumeCamera();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -36,9 +36,9 @@ class _QrScannerModalState extends State<QrScannerModal> {
 | 
			
		||||
        leading: IconButton(
 | 
			
		||||
          icon: const Icon(Icons.close_outlined),
 | 
			
		||||
          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(
 | 
			
		||||
        children: [
 | 
			
		||||
@ -59,7 +59,7 @@ class _QrScannerModalState extends State<QrScannerModal> {
 | 
			
		||||
 | 
			
		||||
  void _onQRViewCreated(QRViewController controller) {
 | 
			
		||||
    this.controller = controller;
 | 
			
		||||
    StreamSubscription sub;
 | 
			
		||||
    late StreamSubscription sub;
 | 
			
		||||
    sub = controller.scannedDataStream.listen((scanData) {
 | 
			
		||||
      sub.cancel();
 | 
			
		||||
      Navigator.of(context).pop();
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@ import 'package:fluffychat/pages/new_space/new_space_view.dart';
 | 
			
		||||
import 'package:fluffychat/widgets/matrix.dart';
 | 
			
		||||
 | 
			
		||||
class NewSpace extends StatefulWidget {
 | 
			
		||||
  const NewSpace({Key key}) : super(key: key);
 | 
			
		||||
  const NewSpace({Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  NewSpaceController createState() => NewSpaceController();
 | 
			
		||||
@ -38,7 +38,7 @@ class NewSpaceController extends State<NewSpace> {
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
    if (roomID.error == null) {
 | 
			
		||||
      VRouter.of(context).toSegments(['rooms', roomID.result, 'details']);
 | 
			
		||||
      VRouter.of(context).toSegments(['rooms', roomID.result!, 'details']);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -8,13 +8,13 @@ import 'new_space.dart';
 | 
			
		||||
class NewSpaceView extends StatelessWidget {
 | 
			
		||||
  final NewSpaceController controller;
 | 
			
		||||
 | 
			
		||||
  const NewSpaceView(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const NewSpaceView(this.controller, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        title: Text(L10n.of(context).createNewSpace),
 | 
			
		||||
        title: Text(L10n.of(context)!.createNewSpace),
 | 
			
		||||
      ),
 | 
			
		||||
      body: MaxWidthBody(
 | 
			
		||||
        child: Column(
 | 
			
		||||
@ -29,13 +29,13 @@ class NewSpaceView extends StatelessWidget {
 | 
			
		||||
                textInputAction: TextInputAction.go,
 | 
			
		||||
                onSubmitted: controller.submitAction,
 | 
			
		||||
                decoration: InputDecoration(
 | 
			
		||||
                    labelText: L10n.of(context).spaceName,
 | 
			
		||||
                    labelText: L10n.of(context)!.spaceName,
 | 
			
		||||
                    prefixIcon: const Icon(Icons.people_outlined),
 | 
			
		||||
                    hintText: L10n.of(context).enterASpacepName),
 | 
			
		||||
                    hintText: L10n.of(context)!.enterASpacepName),
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
            SwitchListTile.adaptive(
 | 
			
		||||
              title: Text(L10n.of(context).spaceIsPublic),
 | 
			
		||||
              title: Text(L10n.of(context)!.spaceIsPublic),
 | 
			
		||||
              value: controller.publicGroup,
 | 
			
		||||
              onChanged: controller.setPublicGroup,
 | 
			
		||||
            ),
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'dart:async';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
@ -114,7 +112,7 @@ class SearchController extends State<Search> {
 | 
			
		||||
    super.initState();
 | 
			
		||||
    WidgetsBinding.instance?.addPostFrameCallback((_) async {
 | 
			
		||||
      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) {
 | 
			
		||||
        this.server = server;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@ -17,12 +17,12 @@ import 'search.dart';
 | 
			
		||||
class SearchView extends StatelessWidget {
 | 
			
		||||
  final SearchController controller;
 | 
			
		||||
 | 
			
		||||
  const SearchView(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const SearchView(this.controller, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final server = controller.genericSearchTerm?.isValidMatrixId ?? false
 | 
			
		||||
        ? controller.genericSearchTerm.domain
 | 
			
		||||
        ? controller.genericSearchTerm!.domain
 | 
			
		||||
        : controller.server;
 | 
			
		||||
    if (controller.lastServer != server) {
 | 
			
		||||
      controller.lastServer = server;
 | 
			
		||||
@ -44,15 +44,22 @@ class SearchView extends StatelessWidget {
 | 
			
		||||
        'chunk': [],
 | 
			
		||||
      });
 | 
			
		||||
    }).then((QueryPublicRoomsResponse res) {
 | 
			
		||||
      if (controller.genericSearchTerm != null &&
 | 
			
		||||
      final genericSearchTerm = controller.genericSearchTerm;
 | 
			
		||||
      if (genericSearchTerm != null &&
 | 
			
		||||
          !res.chunk.any((room) =>
 | 
			
		||||
              (room.aliases?.contains(controller.genericSearchTerm) ?? false) ||
 | 
			
		||||
              room.canonicalAlias == controller.genericSearchTerm)) {
 | 
			
		||||
        // we have to tack on the original alias
 | 
			
		||||
        res.chunk.add(PublicRoomsChunk.fromJson(<String, dynamic>{
 | 
			
		||||
          'aliases': [controller.genericSearchTerm],
 | 
			
		||||
          'name': controller.genericSearchTerm,
 | 
			
		||||
        }));
 | 
			
		||||
        res.chunk.add(
 | 
			
		||||
          PublicRoomsChunk(
 | 
			
		||||
            aliases: [genericSearchTerm],
 | 
			
		||||
            name: genericSearchTerm,
 | 
			
		||||
            numJoinedMembers: 0,
 | 
			
		||||
            roomId: '!unknown',
 | 
			
		||||
            worldReadable: true,
 | 
			
		||||
            guestCanJoin: true,
 | 
			
		||||
          ),
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
      return res;
 | 
			
		||||
    });
 | 
			
		||||
@ -68,15 +75,14 @@ class SearchView extends StatelessWidget {
 | 
			
		||||
    const tabCount = 3;
 | 
			
		||||
    return DefaultTabController(
 | 
			
		||||
      length: tabCount,
 | 
			
		||||
      initialIndex:
 | 
			
		||||
          controller.controller.text?.startsWith('#') ?? false ? 0 : 1,
 | 
			
		||||
      initialIndex: controller.controller.text.startsWith('#') ? 0 : 1,
 | 
			
		||||
      child: Scaffold(
 | 
			
		||||
        appBar: AppBar(
 | 
			
		||||
          leading: const BackButton(),
 | 
			
		||||
          titleSpacing: 0,
 | 
			
		||||
          title: DefaultAppBarSearchField(
 | 
			
		||||
            autofocus: true,
 | 
			
		||||
            hintText: L10n.of(context).search,
 | 
			
		||||
            hintText: L10n.of(context)!.search,
 | 
			
		||||
            searchController: controller.controller,
 | 
			
		||||
            suffix: const Icon(Icons.search_outlined),
 | 
			
		||||
            onChanged: controller.search,
 | 
			
		||||
@ -84,16 +90,16 @@ class SearchView extends StatelessWidget {
 | 
			
		||||
          bottom: TabBar(
 | 
			
		||||
            indicatorColor: 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),
 | 
			
		||||
            labelPadding: const EdgeInsets.symmetric(
 | 
			
		||||
              horizontal: 8,
 | 
			
		||||
              vertical: 0,
 | 
			
		||||
            ),
 | 
			
		||||
            tabs: [
 | 
			
		||||
              Tab(child: Text(L10n.of(context).discover, 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)!.discover, maxLines: 1)),
 | 
			
		||||
              Tab(child: Text(L10n.of(context)!.chats, maxLines: 1)),
 | 
			
		||||
              Tab(child: Text(L10n.of(context)!.people, maxLines: 1)),
 | 
			
		||||
            ],
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
@ -111,7 +117,7 @@ class SearchView extends StatelessWidget {
 | 
			
		||||
                    backgroundColor: Theme.of(context).secondaryHeaderColor,
 | 
			
		||||
                    child: const Icon(Icons.edit_outlined),
 | 
			
		||||
                  ),
 | 
			
		||||
                  title: Text(L10n.of(context).changeTheServer),
 | 
			
		||||
                  title: Text(L10n.of(context)!.changeTheServer),
 | 
			
		||||
                  onTap: controller.setServer,
 | 
			
		||||
                ),
 | 
			
		||||
                FutureBuilder<QueryPublicRoomsResponse>(
 | 
			
		||||
@ -130,7 +136,7 @@ class SearchView extends StatelessWidget {
 | 
			
		||||
                            ),
 | 
			
		||||
                            Center(
 | 
			
		||||
                              child: Text(
 | 
			
		||||
                                snapshot.error.toLocalizedString(context),
 | 
			
		||||
                                snapshot.error!.toLocalizedString(context),
 | 
			
		||||
                                textAlign: TextAlign.center,
 | 
			
		||||
                                style: const TextStyle(
 | 
			
		||||
                                  color: Colors.grey,
 | 
			
		||||
@ -146,7 +152,7 @@ class SearchView extends StatelessWidget {
 | 
			
		||||
                            child: CircularProgressIndicator.adaptive(
 | 
			
		||||
                                strokeWidth: 2));
 | 
			
		||||
                      }
 | 
			
		||||
                      final publicRoomsResponse = snapshot.data;
 | 
			
		||||
                      final publicRoomsResponse = snapshot.data!;
 | 
			
		||||
                      if (publicRoomsResponse.chunk.isEmpty) {
 | 
			
		||||
                        return Column(
 | 
			
		||||
                          mainAxisSize: MainAxisSize.min,
 | 
			
		||||
@ -159,7 +165,7 @@ class SearchView extends StatelessWidget {
 | 
			
		||||
                            ),
 | 
			
		||||
                            Center(
 | 
			
		||||
                              child: Text(
 | 
			
		||||
                                L10n.of(context).noPublicRoomsFound,
 | 
			
		||||
                                L10n.of(context)!.noPublicRoomsFound,
 | 
			
		||||
                                textAlign: TextAlign.center,
 | 
			
		||||
                                style: const TextStyle(
 | 
			
		||||
                                  color: Colors.grey,
 | 
			
		||||
@ -201,7 +207,7 @@ class SearchView extends StatelessWidget {
 | 
			
		||||
                                    name: publicRoomsResponse.chunk[i].name,
 | 
			
		||||
                                  ),
 | 
			
		||||
                                  Text(
 | 
			
		||||
                                    publicRoomsResponse.chunk[i].name,
 | 
			
		||||
                                    publicRoomsResponse.chunk[i].name!,
 | 
			
		||||
                                    style: const TextStyle(
 | 
			
		||||
                                      fontSize: 16,
 | 
			
		||||
                                      fontWeight: FontWeight.bold,
 | 
			
		||||
@ -210,17 +216,16 @@ class SearchView extends StatelessWidget {
 | 
			
		||||
                                    textAlign: TextAlign.center,
 | 
			
		||||
                                  ),
 | 
			
		||||
                                  Text(
 | 
			
		||||
                                    L10n.of(context).countParticipants(
 | 
			
		||||
                                    L10n.of(context)!.countParticipants(
 | 
			
		||||
                                        publicRoomsResponse
 | 
			
		||||
                                                .chunk[i].numJoinedMembers ??
 | 
			
		||||
                                            0),
 | 
			
		||||
                                            .chunk[i].numJoinedMembers),
 | 
			
		||||
                                    style: const TextStyle(fontSize: 10.5),
 | 
			
		||||
                                    maxLines: 1,
 | 
			
		||||
                                    textAlign: TextAlign.center,
 | 
			
		||||
                                  ),
 | 
			
		||||
                                  Text(
 | 
			
		||||
                                    publicRoomsResponse.chunk[i].topic ??
 | 
			
		||||
                                        L10n.of(context).noDescription,
 | 
			
		||||
                                        L10n.of(context)!.noDescription,
 | 
			
		||||
                                    maxLines: 4,
 | 
			
		||||
                                    textAlign: TextAlign.center,
 | 
			
		||||
                                  ),
 | 
			
		||||
@ -261,7 +266,7 @@ class SearchView extends StatelessWidget {
 | 
			
		||||
                          );
 | 
			
		||||
                          if (roomID.error == null) {
 | 
			
		||||
                            VRouter.of(context)
 | 
			
		||||
                                .toSegments(['rooms', roomID.result]);
 | 
			
		||||
                                .toSegments(['rooms', roomID.result!]);
 | 
			
		||||
                          }
 | 
			
		||||
                        },
 | 
			
		||||
                        leading: Avatar(
 | 
			
		||||
@ -271,7 +276,7 @@ class SearchView extends StatelessWidget {
 | 
			
		||||
                        ),
 | 
			
		||||
                        title: Text(
 | 
			
		||||
                          foundProfile.displayName ??
 | 
			
		||||
                              foundProfile.userId.localpart,
 | 
			
		||||
                              foundProfile.userId.localpart!,
 | 
			
		||||
                          style: const TextStyle(),
 | 
			
		||||
                          maxLines: 1,
 | 
			
		||||
                        ),
 | 
			
		||||
 | 
			
		||||
@ -14,19 +14,19 @@ import '../../widgets/matrix.dart';
 | 
			
		||||
import 'settings_view.dart';
 | 
			
		||||
 | 
			
		||||
class Settings extends StatefulWidget {
 | 
			
		||||
  const Settings({Key key}) : super(key: key);
 | 
			
		||||
  const Settings({Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  SettingsController createState() => SettingsController();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class SettingsController extends State<Settings> {
 | 
			
		||||
  Future<bool> crossSigningCachedFuture;
 | 
			
		||||
  bool crossSigningCached;
 | 
			
		||||
  Future<bool> megolmBackupCachedFuture;
 | 
			
		||||
  bool megolmBackupCached;
 | 
			
		||||
  Future<dynamic> profileFuture;
 | 
			
		||||
  Profile profile;
 | 
			
		||||
  Future<bool>? crossSigningCachedFuture;
 | 
			
		||||
  bool? crossSigningCached;
 | 
			
		||||
  Future<bool>? megolmBackupCachedFuture;
 | 
			
		||||
  bool? megolmBackupCached;
 | 
			
		||||
  Future<dynamic>? profileFuture;
 | 
			
		||||
  Profile? profile;
 | 
			
		||||
  bool profileUpdated = false;
 | 
			
		||||
 | 
			
		||||
  void updateProfile() => setState(() {
 | 
			
		||||
@ -39,19 +39,19 @@ class SettingsController extends State<Settings> {
 | 
			
		||||
      if (PlatformInfos.isMobile)
 | 
			
		||||
        SheetAction(
 | 
			
		||||
          key: AvatarAction.camera,
 | 
			
		||||
          label: L10n.of(context).openCamera,
 | 
			
		||||
          label: L10n.of(context)!.openCamera,
 | 
			
		||||
          isDefaultAction: true,
 | 
			
		||||
          icon: Icons.camera_alt_outlined,
 | 
			
		||||
        ),
 | 
			
		||||
      SheetAction(
 | 
			
		||||
        key: AvatarAction.file,
 | 
			
		||||
        label: L10n.of(context).openGallery,
 | 
			
		||||
        label: L10n.of(context)!.openGallery,
 | 
			
		||||
        icon: Icons.photo_outlined,
 | 
			
		||||
      ),
 | 
			
		||||
      if (profile?.avatarUrl != null)
 | 
			
		||||
        SheetAction(
 | 
			
		||||
          key: AvatarAction.remove,
 | 
			
		||||
          label: L10n.of(context).removeYourAvatar,
 | 
			
		||||
          label: L10n.of(context)!.removeYourAvatar,
 | 
			
		||||
          isDestructiveAction: true,
 | 
			
		||||
          icon: Icons.delete_outlined,
 | 
			
		||||
        ),
 | 
			
		||||
@ -60,7 +60,7 @@ class SettingsController extends State<Settings> {
 | 
			
		||||
        ? actions.single
 | 
			
		||||
        : await showModalActionSheet<AvatarAction>(
 | 
			
		||||
            context: context,
 | 
			
		||||
            title: L10n.of(context).changeYourAvatar,
 | 
			
		||||
            title: L10n.of(context)!.changeYourAvatar,
 | 
			
		||||
            actions: actions,
 | 
			
		||||
          );
 | 
			
		||||
    if (action == null) return;
 | 
			
		||||
@ -91,10 +91,10 @@ class SettingsController extends State<Settings> {
 | 
			
		||||
    } else {
 | 
			
		||||
      final result =
 | 
			
		||||
          await FilePickerCross.importFromStorage(type: FileTypeCross.image);
 | 
			
		||||
      if (result == null) return;
 | 
			
		||||
      if (result.fileName == null) return;
 | 
			
		||||
      file = MatrixFile(
 | 
			
		||||
        bytes: result.toUint8List(),
 | 
			
		||||
        name: result.fileName,
 | 
			
		||||
        name: result.fileName!,
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    final success = await showFutureLoadingDialog(
 | 
			
		||||
@ -111,7 +111,7 @@ class SettingsController extends State<Settings> {
 | 
			
		||||
    final client = Matrix.of(context).client;
 | 
			
		||||
    profileFuture ??= client
 | 
			
		||||
        .getProfileFromUserId(
 | 
			
		||||
      client.userID,
 | 
			
		||||
      client.userID!,
 | 
			
		||||
      cache: !profileUpdated,
 | 
			
		||||
      getFromRooms: !profileUpdated,
 | 
			
		||||
    )
 | 
			
		||||
@ -121,12 +121,12 @@ class SettingsController extends State<Settings> {
 | 
			
		||||
    });
 | 
			
		||||
    if (client.encryption != null) {
 | 
			
		||||
      crossSigningCachedFuture ??=
 | 
			
		||||
          client.encryption?.crossSigning?.isCached()?.then((c) {
 | 
			
		||||
          client.encryption?.crossSigning.isCached().then((c) {
 | 
			
		||||
        if (mounted) setState(() => crossSigningCached = c);
 | 
			
		||||
        return c;
 | 
			
		||||
      });
 | 
			
		||||
      megolmBackupCachedFuture ??=
 | 
			
		||||
          client.encryption?.keyManager?.isCached()?.then((c) {
 | 
			
		||||
          client.encryption?.keyManager.isCached().then((c) {
 | 
			
		||||
        if (mounted) setState(() => megolmBackupCached = c);
 | 
			
		||||
        return c;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,7 @@ import 'settings.dart';
 | 
			
		||||
class SettingsView extends StatelessWidget {
 | 
			
		||||
  final SettingsController controller;
 | 
			
		||||
 | 
			
		||||
  const SettingsView(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const SettingsView(this.controller, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
@ -25,11 +25,11 @@ class SettingsView extends StatelessWidget {
 | 
			
		||||
            expandedHeight: 300.0,
 | 
			
		||||
            floating: true,
 | 
			
		||||
            pinned: true,
 | 
			
		||||
            title: Text(L10n.of(context).settings),
 | 
			
		||||
            title: Text(L10n.of(context)!.settings),
 | 
			
		||||
            backgroundColor: Theme.of(context).appBarTheme.backgroundColor,
 | 
			
		||||
            flexibleSpace: FlexibleSpaceBar(
 | 
			
		||||
              background: ContentBanner(
 | 
			
		||||
                controller.profile?.avatarUrl,
 | 
			
		||||
                mxContent: controller.profile?.avatarUrl,
 | 
			
		||||
                onEdit: controller.setAvatarAction,
 | 
			
		||||
                defaultIcon: Icons.person_outline_outlined,
 | 
			
		||||
              ),
 | 
			
		||||
@ -37,54 +37,54 @@ class SettingsView extends StatelessWidget {
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
        body: ListTileTheme(
 | 
			
		||||
          iconColor: Theme.of(context).textTheme.bodyText1.color,
 | 
			
		||||
          iconColor: Theme.of(context).textTheme.bodyText1!.color,
 | 
			
		||||
          child: ListView(
 | 
			
		||||
            children: <Widget>[
 | 
			
		||||
              ListTile(
 | 
			
		||||
                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'),
 | 
			
		||||
              ),
 | 
			
		||||
              const Divider(thickness: 1),
 | 
			
		||||
              ListTile(
 | 
			
		||||
                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'),
 | 
			
		||||
              ),
 | 
			
		||||
              ListTile(
 | 
			
		||||
                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'),
 | 
			
		||||
              ),
 | 
			
		||||
              ListTile(
 | 
			
		||||
                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'),
 | 
			
		||||
              ),
 | 
			
		||||
              ListTile(
 | 
			
		||||
                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'),
 | 
			
		||||
              ),
 | 
			
		||||
              ListTile(
 | 
			
		||||
                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'),
 | 
			
		||||
              ),
 | 
			
		||||
              const Divider(thickness: 1),
 | 
			
		||||
              ListTile(
 | 
			
		||||
                leading: const Icon(Icons.help_outline_outlined),
 | 
			
		||||
                title: Text(L10n.of(context).help),
 | 
			
		||||
                title: Text(L10n.of(context)!.help),
 | 
			
		||||
                onTap: () => launch(AppConfig.supportUrl),
 | 
			
		||||
              ),
 | 
			
		||||
              ListTile(
 | 
			
		||||
                leading: const Icon(Icons.shield_sharp),
 | 
			
		||||
                title: Text(L10n.of(context).privacy),
 | 
			
		||||
                title: Text(L10n.of(context)!.privacy),
 | 
			
		||||
                onTap: () => launch(AppConfig.privacyUrl),
 | 
			
		||||
              ),
 | 
			
		||||
              ListTile(
 | 
			
		||||
                leading: const Icon(Icons.info_outline_rounded),
 | 
			
		||||
                title: Text(L10n.of(context).about),
 | 
			
		||||
                title: Text(L10n.of(context)!.about),
 | 
			
		||||
                onTap: () => PlatformInfos.showDialog(context),
 | 
			
		||||
              ),
 | 
			
		||||
            ],
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ import 'settings_3pid_view.dart';
 | 
			
		||||
class Settings3Pid extends StatefulWidget {
 | 
			
		||||
  static int sendAttempt = 0;
 | 
			
		||||
 | 
			
		||||
  const Settings3Pid({Key key}) : super(key: key);
 | 
			
		||||
  const Settings3Pid({Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Settings3PidController createState() => Settings3PidController();
 | 
			
		||||
@ -22,12 +22,12 @@ class Settings3PidController extends State<Settings3Pid> {
 | 
			
		||||
    final input = await showTextInputDialog(
 | 
			
		||||
      useRootNavigator: false,
 | 
			
		||||
      context: context,
 | 
			
		||||
      title: L10n.of(context).enterAnEmailAddress,
 | 
			
		||||
      okLabel: L10n.of(context).ok,
 | 
			
		||||
      cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
      title: L10n.of(context)!.enterAnEmailAddress,
 | 
			
		||||
      okLabel: L10n.of(context)!.ok,
 | 
			
		||||
      cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
      textFields: [
 | 
			
		||||
        DialogTextField(
 | 
			
		||||
          hintText: L10n.of(context).enterAnEmailAddress,
 | 
			
		||||
          hintText: L10n.of(context)!.enterAnEmailAddress,
 | 
			
		||||
          keyboardType: TextInputType.emailAddress,
 | 
			
		||||
        ),
 | 
			
		||||
      ],
 | 
			
		||||
@ -46,17 +46,17 @@ class Settings3PidController extends State<Settings3Pid> {
 | 
			
		||||
    final ok = await showOkAlertDialog(
 | 
			
		||||
      useRootNavigator: false,
 | 
			
		||||
      context: context,
 | 
			
		||||
      title: L10n.of(context).weSentYouAnEmail,
 | 
			
		||||
      message: L10n.of(context).pleaseClickOnLink,
 | 
			
		||||
      okLabel: L10n.of(context).iHaveClickedOnLink,
 | 
			
		||||
      title: L10n.of(context)!.weSentYouAnEmail,
 | 
			
		||||
      message: L10n.of(context)!.pleaseClickOnLink,
 | 
			
		||||
      okLabel: L10n.of(context)!.iHaveClickedOnLink,
 | 
			
		||||
    );
 | 
			
		||||
    if (ok == null) return;
 | 
			
		||||
    if (ok != OkCancelResult.ok) return;
 | 
			
		||||
    final success = await showFutureLoadingDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      future: () => Matrix.of(context).client.uiaRequestBackground(
 | 
			
		||||
            (auth) => Matrix.of(context).client.add3PID(
 | 
			
		||||
                  clientSecret,
 | 
			
		||||
                  response.result.sid,
 | 
			
		||||
                  response.result!.sid,
 | 
			
		||||
                  auth: auth,
 | 
			
		||||
                ),
 | 
			
		||||
          ),
 | 
			
		||||
@ -65,15 +65,15 @@ class Settings3PidController extends State<Settings3Pid> {
 | 
			
		||||
    setState(() => request = null);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<List<ThirdPartyIdentifier>> request;
 | 
			
		||||
  Future<List<ThirdPartyIdentifier>?>? request;
 | 
			
		||||
 | 
			
		||||
  void delete3Pid(ThirdPartyIdentifier identifier) async {
 | 
			
		||||
    if (await showOkCancelAlertDialog(
 | 
			
		||||
          useRootNavigator: false,
 | 
			
		||||
          context: context,
 | 
			
		||||
          title: L10n.of(context).areYouSure,
 | 
			
		||||
          okLabel: L10n.of(context).yes,
 | 
			
		||||
          cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
          title: L10n.of(context)!.areYouSure,
 | 
			
		||||
          okLabel: L10n.of(context)!.yes,
 | 
			
		||||
          cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
        ) !=
 | 
			
		||||
        OkCancelResult.ok) {
 | 
			
		||||
      return;
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,7 @@ import 'package:fluffychat/widgets/matrix.dart';
 | 
			
		||||
class Settings3PidView extends StatelessWidget {
 | 
			
		||||
  final Settings3PidController controller;
 | 
			
		||||
 | 
			
		||||
  const Settings3PidView(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const Settings3PidView(this.controller, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
@ -18,20 +18,20 @@ class Settings3PidView extends StatelessWidget {
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        leading: const BackButton(),
 | 
			
		||||
        title: Text(L10n.of(context).passwordRecovery),
 | 
			
		||||
        title: Text(L10n.of(context)!.passwordRecovery),
 | 
			
		||||
        actions: [
 | 
			
		||||
          IconButton(
 | 
			
		||||
            icon: const Icon(Icons.add_outlined),
 | 
			
		||||
            onPressed: controller.add3PidAction,
 | 
			
		||||
            tooltip: L10n.of(context).addEmail,
 | 
			
		||||
            tooltip: L10n.of(context)!.addEmail,
 | 
			
		||||
          )
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
      body: MaxWidthBody(
 | 
			
		||||
        child: FutureBuilder<List<ThirdPartyIdentifier>>(
 | 
			
		||||
        child: FutureBuilder<List<ThirdPartyIdentifier>?>(
 | 
			
		||||
          future: controller.request,
 | 
			
		||||
          builder: (BuildContext context,
 | 
			
		||||
              AsyncSnapshot<List<ThirdPartyIdentifier>> snapshot) {
 | 
			
		||||
              AsyncSnapshot<List<ThirdPartyIdentifier>?> snapshot) {
 | 
			
		||||
            if (snapshot.hasError) {
 | 
			
		||||
              return Center(
 | 
			
		||||
                child: Text(
 | 
			
		||||
@ -44,7 +44,7 @@ class Settings3PidView extends StatelessWidget {
 | 
			
		||||
              return const Center(
 | 
			
		||||
                  child: CircularProgressIndicator.adaptive(strokeWidth: 2));
 | 
			
		||||
            }
 | 
			
		||||
            final identifier = snapshot.data;
 | 
			
		||||
            final identifier = snapshot.data!;
 | 
			
		||||
            return Column(
 | 
			
		||||
              children: [
 | 
			
		||||
                ListTile(
 | 
			
		||||
@ -60,8 +60,8 @@ class Settings3PidView extends StatelessWidget {
 | 
			
		||||
                  ),
 | 
			
		||||
                  title: Text(
 | 
			
		||||
                    identifier.isEmpty
 | 
			
		||||
                        ? L10n.of(context).noPasswordRecoveryDescription
 | 
			
		||||
                        : L10n.of(context)
 | 
			
		||||
                        ? L10n.of(context)!.noPasswordRecoveryDescription
 | 
			
		||||
                        : L10n.of(context)!
 | 
			
		||||
                            .withTheseAddressesRecoveryDescription,
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
@ -77,7 +77,7 @@ class Settings3PidView extends StatelessWidget {
 | 
			
		||||
                          child: Icon(identifier[i].iconData)),
 | 
			
		||||
                      title: Text(identifier[i].address),
 | 
			
		||||
                      trailing: IconButton(
 | 
			
		||||
                        tooltip: L10n.of(context).delete,
 | 
			
		||||
                        tooltip: L10n.of(context)!.delete,
 | 
			
		||||
                        icon: const Icon(Icons.delete_forever_outlined),
 | 
			
		||||
                        color: Colors.red,
 | 
			
		||||
                        onPressed: () => controller.delete3Pid(identifier[i]),
 | 
			
		||||
@ -102,6 +102,5 @@ extension on ThirdPartyIdentifier {
 | 
			
		||||
      case ThirdPartyIdentifierMedium.msisdn:
 | 
			
		||||
        return Icons.phone_android_outlined;
 | 
			
		||||
    }
 | 
			
		||||
    return Icons.device_unknown_outlined;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -10,15 +10,15 @@ import 'package:fluffychat/pages/settings_account/settings_account_view.dart';
 | 
			
		||||
import 'package:fluffychat/widgets/matrix.dart';
 | 
			
		||||
 | 
			
		||||
class SettingsAccount extends StatefulWidget {
 | 
			
		||||
  const SettingsAccount({Key key}) : super(key: key);
 | 
			
		||||
  const SettingsAccount({Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  SettingsAccountController createState() => SettingsAccountController();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class SettingsAccountController extends State<SettingsAccount> {
 | 
			
		||||
  Future<dynamic> profileFuture;
 | 
			
		||||
  Profile profile;
 | 
			
		||||
  Future<dynamic>? profileFuture;
 | 
			
		||||
  Profile? profile;
 | 
			
		||||
  bool profileUpdated = false;
 | 
			
		||||
 | 
			
		||||
  void updateProfile() => setState(() {
 | 
			
		||||
@ -30,13 +30,13 @@ class SettingsAccountController extends State<SettingsAccount> {
 | 
			
		||||
    final input = await showTextInputDialog(
 | 
			
		||||
      useRootNavigator: false,
 | 
			
		||||
      context: context,
 | 
			
		||||
      title: L10n.of(context).editDisplayname,
 | 
			
		||||
      okLabel: L10n.of(context).ok,
 | 
			
		||||
      cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
      title: L10n.of(context)!.editDisplayname,
 | 
			
		||||
      okLabel: L10n.of(context)!.ok,
 | 
			
		||||
      cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
      textFields: [
 | 
			
		||||
        DialogTextField(
 | 
			
		||||
          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(
 | 
			
		||||
      context: context,
 | 
			
		||||
      future: () =>
 | 
			
		||||
          matrix.client.setDisplayName(matrix.client.userID, input.single),
 | 
			
		||||
          matrix.client.setDisplayName(matrix.client.userID!, input.single),
 | 
			
		||||
    );
 | 
			
		||||
    if (success.error == null) {
 | 
			
		||||
      updateProfile();
 | 
			
		||||
@ -56,9 +56,9 @@ class SettingsAccountController extends State<SettingsAccount> {
 | 
			
		||||
    if (await showOkCancelAlertDialog(
 | 
			
		||||
          useRootNavigator: false,
 | 
			
		||||
          context: context,
 | 
			
		||||
          title: L10n.of(context).areYouSureYouWantToLogout,
 | 
			
		||||
          okLabel: L10n.of(context).yes,
 | 
			
		||||
          cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
          title: L10n.of(context)!.areYouSureYouWantToLogout,
 | 
			
		||||
          okLabel: L10n.of(context)!.yes,
 | 
			
		||||
          cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
        ) ==
 | 
			
		||||
        OkCancelResult.cancel) {
 | 
			
		||||
      return;
 | 
			
		||||
@ -74,10 +74,10 @@ class SettingsAccountController extends State<SettingsAccount> {
 | 
			
		||||
    if (await showOkCancelAlertDialog(
 | 
			
		||||
          useRootNavigator: false,
 | 
			
		||||
          context: context,
 | 
			
		||||
          title: L10n.of(context).warning,
 | 
			
		||||
          message: L10n.of(context).deactivateAccountWarning,
 | 
			
		||||
          okLabel: L10n.of(context).ok,
 | 
			
		||||
          cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
          title: L10n.of(context)!.warning,
 | 
			
		||||
          message: L10n.of(context)!.deactivateAccountWarning,
 | 
			
		||||
          okLabel: L10n.of(context)!.ok,
 | 
			
		||||
          cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
        ) ==
 | 
			
		||||
        OkCancelResult.cancel) {
 | 
			
		||||
      return;
 | 
			
		||||
@ -85,9 +85,9 @@ class SettingsAccountController extends State<SettingsAccount> {
 | 
			
		||||
    if (await showOkCancelAlertDialog(
 | 
			
		||||
          useRootNavigator: false,
 | 
			
		||||
          context: context,
 | 
			
		||||
          title: L10n.of(context).areYouSure,
 | 
			
		||||
          okLabel: L10n.of(context).yes,
 | 
			
		||||
          cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
          title: L10n.of(context)!.areYouSure,
 | 
			
		||||
          okLabel: L10n.of(context)!.yes,
 | 
			
		||||
          cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
        ) ==
 | 
			
		||||
        OkCancelResult.cancel) {
 | 
			
		||||
      return;
 | 
			
		||||
@ -95,9 +95,9 @@ class SettingsAccountController extends State<SettingsAccount> {
 | 
			
		||||
    final input = await showTextInputDialog(
 | 
			
		||||
      useRootNavigator: false,
 | 
			
		||||
      context: context,
 | 
			
		||||
      title: L10n.of(context).pleaseEnterYourPassword,
 | 
			
		||||
      okLabel: L10n.of(context).ok,
 | 
			
		||||
      cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
      title: L10n.of(context)!.pleaseEnterYourPassword,
 | 
			
		||||
      okLabel: L10n.of(context)!.ok,
 | 
			
		||||
      cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
      textFields: [
 | 
			
		||||
        const DialogTextField(
 | 
			
		||||
          obscureText: true,
 | 
			
		||||
@ -114,7 +114,7 @@ class SettingsAccountController extends State<SettingsAccount> {
 | 
			
		||||
            auth: AuthenticationPassword(
 | 
			
		||||
              password: input.single,
 | 
			
		||||
              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;
 | 
			
		||||
    profileFuture ??= client
 | 
			
		||||
        .getProfileFromUserId(
 | 
			
		||||
      client.userID,
 | 
			
		||||
      client.userID!,
 | 
			
		||||
      cache: !profileUpdated,
 | 
			
		||||
      getFromRooms: !profileUpdated,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
@ -10,51 +10,51 @@ import 'settings_account.dart';
 | 
			
		||||
 | 
			
		||||
class SettingsAccountView extends StatelessWidget {
 | 
			
		||||
  final SettingsAccountController controller;
 | 
			
		||||
  const SettingsAccountView(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const SettingsAccountView(this.controller, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(title: Text(L10n.of(context).account)),
 | 
			
		||||
      appBar: AppBar(title: Text(L10n.of(context)!.account)),
 | 
			
		||||
      body: ListTileTheme(
 | 
			
		||||
        iconColor: Theme.of(context).textTheme.bodyText1.color,
 | 
			
		||||
        iconColor: Theme.of(context).textTheme.bodyText1!.color,
 | 
			
		||||
        child: MaxWidthBody(
 | 
			
		||||
          withScrolling: true,
 | 
			
		||||
          child: Column(
 | 
			
		||||
            children: [
 | 
			
		||||
              ListTile(
 | 
			
		||||
                title: Text(L10n.of(context).yourUserId),
 | 
			
		||||
                subtitle: Text(Matrix.of(context).client.userID),
 | 
			
		||||
                title: Text(L10n.of(context)!.yourUserId),
 | 
			
		||||
                subtitle: Text(Matrix.of(context).client.userID!),
 | 
			
		||||
                trailing: const Icon(Icons.copy_outlined),
 | 
			
		||||
                onTap: () => FluffyShare.share(
 | 
			
		||||
                  Matrix.of(context).client.userID,
 | 
			
		||||
                  Matrix.of(context).client.userID!,
 | 
			
		||||
                  context,
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
              ListTile(
 | 
			
		||||
                trailing: const Icon(Icons.edit_outlined),
 | 
			
		||||
                title: Text(L10n.of(context).editDisplayname),
 | 
			
		||||
                title: Text(L10n.of(context)!.editDisplayname),
 | 
			
		||||
                subtitle: Text(controller.profile?.displayName ??
 | 
			
		||||
                    Matrix.of(context).client.userID.localpart),
 | 
			
		||||
                    Matrix.of(context).client.userID!.localpart!),
 | 
			
		||||
                onTap: controller.setDisplaynameAction,
 | 
			
		||||
              ),
 | 
			
		||||
              const Divider(height: 1),
 | 
			
		||||
              ListTile(
 | 
			
		||||
                trailing: const Icon(Icons.person_add_outlined),
 | 
			
		||||
                title: Text(L10n.of(context).addAccount),
 | 
			
		||||
                subtitle: Text(L10n.of(context).enableMultiAccounts),
 | 
			
		||||
                title: Text(L10n.of(context)!.addAccount),
 | 
			
		||||
                subtitle: Text(L10n.of(context)!.enableMultiAccounts),
 | 
			
		||||
                onTap: controller.addAccountAction,
 | 
			
		||||
              ),
 | 
			
		||||
              ListTile(
 | 
			
		||||
                trailing: const Icon(Icons.exit_to_app_outlined),
 | 
			
		||||
                title: Text(L10n.of(context).logout),
 | 
			
		||||
                title: Text(L10n.of(context)!.logout),
 | 
			
		||||
                onTap: controller.logoutAction,
 | 
			
		||||
              ),
 | 
			
		||||
              const Divider(height: 1),
 | 
			
		||||
              ListTile(
 | 
			
		||||
                trailing: const Icon(Icons.delete_outlined),
 | 
			
		||||
                title: Text(
 | 
			
		||||
                  L10n.of(context).deleteAccount,
 | 
			
		||||
                  L10n.of(context)!.deleteAccount,
 | 
			
		||||
                  style: const TextStyle(color: Colors.red),
 | 
			
		||||
                ),
 | 
			
		||||
                onTap: controller.deleteAccountAction,
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@ import 'package:fluffychat/widgets/matrix.dart';
 | 
			
		||||
import 'settings_chat_view.dart';
 | 
			
		||||
 | 
			
		||||
class SettingsChat extends StatefulWidget {
 | 
			
		||||
  const SettingsChat({Key key}) : super(key: key);
 | 
			
		||||
  const SettingsChat({Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  SettingsChatController createState() => SettingsChatController();
 | 
			
		||||
@ -21,9 +21,9 @@ class SettingsChatController extends State<SettingsChat> {
 | 
			
		||||
    final input = await showTextInputDialog(
 | 
			
		||||
      useRootNavigator: false,
 | 
			
		||||
      context: context,
 | 
			
		||||
      title: L10n.of(context).editJitsiInstance,
 | 
			
		||||
      okLabel: L10n.of(context).ok,
 | 
			
		||||
      cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
      title: L10n.of(context)!.editJitsiInstance,
 | 
			
		||||
      okLabel: L10n.of(context)!.ok,
 | 
			
		||||
      cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
      textFields: [
 | 
			
		||||
        DialogTextField(
 | 
			
		||||
          initialText: AppConfig.jitsiInstance.replaceFirst(prefix, ''),
 | 
			
		||||
 | 
			
		||||
@ -12,52 +12,52 @@ import 'settings_chat.dart';
 | 
			
		||||
 | 
			
		||||
class SettingsChatView extends StatelessWidget {
 | 
			
		||||
  final SettingsChatController controller;
 | 
			
		||||
  const SettingsChatView(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const SettingsChatView(this.controller, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(title: Text(L10n.of(context).chat)),
 | 
			
		||||
      appBar: AppBar(title: Text(L10n.of(context)!.chat)),
 | 
			
		||||
      body: ListTileTheme(
 | 
			
		||||
        iconColor: Theme.of(context).textTheme.bodyText1.color,
 | 
			
		||||
        iconColor: Theme.of(context).textTheme.bodyText1!.color,
 | 
			
		||||
        child: MaxWidthBody(
 | 
			
		||||
          withScrolling: true,
 | 
			
		||||
          child: Column(
 | 
			
		||||
            children: [
 | 
			
		||||
              SettingsSwitchListTile.adaptive(
 | 
			
		||||
                title: L10n.of(context).renderRichContent,
 | 
			
		||||
                title: L10n.of(context)!.renderRichContent,
 | 
			
		||||
                onChanged: (b) => AppConfig.renderHtml = b,
 | 
			
		||||
                storeKey: SettingKeys.renderHtml,
 | 
			
		||||
                defaultValue: AppConfig.renderHtml,
 | 
			
		||||
              ),
 | 
			
		||||
              SettingsSwitchListTile.adaptive(
 | 
			
		||||
                title: L10n.of(context).hideRedactedEvents,
 | 
			
		||||
                title: L10n.of(context)!.hideRedactedEvents,
 | 
			
		||||
                onChanged: (b) => AppConfig.hideRedactedEvents = b,
 | 
			
		||||
                storeKey: SettingKeys.hideRedactedEvents,
 | 
			
		||||
                defaultValue: AppConfig.hideRedactedEvents,
 | 
			
		||||
              ),
 | 
			
		||||
              SettingsSwitchListTile.adaptive(
 | 
			
		||||
                title: L10n.of(context).hideUnknownEvents,
 | 
			
		||||
                title: L10n.of(context)!.hideUnknownEvents,
 | 
			
		||||
                onChanged: (b) => AppConfig.hideUnknownEvents = b,
 | 
			
		||||
                storeKey: SettingKeys.hideUnknownEvents,
 | 
			
		||||
                defaultValue: AppConfig.hideUnknownEvents,
 | 
			
		||||
              ),
 | 
			
		||||
              SettingsSwitchListTile.adaptive(
 | 
			
		||||
                title: L10n.of(context).autoplayImages,
 | 
			
		||||
                title: L10n.of(context)!.autoplayImages,
 | 
			
		||||
                onChanged: (b) => AppConfig.autoplayImages = b,
 | 
			
		||||
                storeKey: SettingKeys.autoplayImages,
 | 
			
		||||
                defaultValue: AppConfig.autoplayImages,
 | 
			
		||||
              ),
 | 
			
		||||
              if (PlatformInfos.isMobile)
 | 
			
		||||
                SettingsSwitchListTile.adaptive(
 | 
			
		||||
                  title: L10n.of(context).sendOnEnter,
 | 
			
		||||
                  title: L10n.of(context)!.sendOnEnter,
 | 
			
		||||
                  onChanged: (b) => AppConfig.sendOnEnter = b,
 | 
			
		||||
                  storeKey: SettingKeys.sendOnEnter,
 | 
			
		||||
                  defaultValue: AppConfig.sendOnEnter,
 | 
			
		||||
                ),
 | 
			
		||||
              const Divider(height: 1),
 | 
			
		||||
              ListTile(
 | 
			
		||||
                title: Text(L10n.of(context).emoteSettings),
 | 
			
		||||
                title: Text(L10n.of(context)!.emoteSettings),
 | 
			
		||||
                onTap: () => VRouter.of(context).to('emotes'),
 | 
			
		||||
                trailing: const Padding(
 | 
			
		||||
                  padding: EdgeInsets.all(16.0),
 | 
			
		||||
@ -69,7 +69,7 @@ class SettingsChatView extends StatelessWidget {
 | 
			
		||||
                  padding: EdgeInsets.all(16.0),
 | 
			
		||||
                  child: Icon(Icons.phone_outlined),
 | 
			
		||||
                ),
 | 
			
		||||
                title: Text(L10n.of(context).editJitsiInstance),
 | 
			
		||||
                title: Text(L10n.of(context)!.editJitsiInstance),
 | 
			
		||||
                subtitle: Text(AppConfig.jitsiInstance),
 | 
			
		||||
                onTap: controller.setJitsiInstanceAction,
 | 
			
		||||
              ),
 | 
			
		||||
 | 
			
		||||
@ -12,27 +12,27 @@ import '../../widgets/matrix.dart';
 | 
			
		||||
import 'settings_emotes_view.dart';
 | 
			
		||||
 | 
			
		||||
class EmotesSettings extends StatefulWidget {
 | 
			
		||||
  const EmotesSettings({Key key}) : super(key: key);
 | 
			
		||||
  const EmotesSettings({Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  EmotesSettingsController createState() => EmotesSettingsController();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class EmotesSettingsController extends State<EmotesSettings> {
 | 
			
		||||
  String get roomId => VRouter.of(context).pathParameters['roomid'];
 | 
			
		||||
  Room get room =>
 | 
			
		||||
      roomId != null ? Matrix.of(context).client.getRoomById(roomId) : null;
 | 
			
		||||
  String get stateKey => VRouter.of(context).pathParameters['state_key'];
 | 
			
		||||
  String? get roomId => VRouter.of(context).pathParameters['roomid'];
 | 
			
		||||
  Room? get room =>
 | 
			
		||||
      roomId != null ? Matrix.of(context).client.getRoomById(roomId!) : null;
 | 
			
		||||
  String? get stateKey => VRouter.of(context).pathParameters['state_key'];
 | 
			
		||||
 | 
			
		||||
  bool showSave = false;
 | 
			
		||||
  TextEditingController newImageCodeController = TextEditingController();
 | 
			
		||||
  ValueNotifier<ImagePackImageContent> newImageController =
 | 
			
		||||
      ValueNotifier<ImagePackImageContent>(null);
 | 
			
		||||
  ValueNotifier<ImagePackImageContent?> newImageController =
 | 
			
		||||
      ValueNotifier<ImagePackImageContent?>(null);
 | 
			
		||||
 | 
			
		||||
  ImagePackContent _getPack() {
 | 
			
		||||
    final client = Matrix.of(context).client;
 | 
			
		||||
    final event = (room != null
 | 
			
		||||
            ? room.getState('im.ponies.room_emotes', stateKey ?? '')
 | 
			
		||||
            ? room!.getState('im.ponies.room_emotes', stateKey ?? '')
 | 
			
		||||
            : client.accountData['im.ponies.user_emotes']) ??
 | 
			
		||||
        BasicEvent.fromJson(<String, dynamic>{
 | 
			
		||||
          'type': 'm.dummy',
 | 
			
		||||
@ -42,8 +42,8 @@ class EmotesSettingsController extends State<EmotesSettings> {
 | 
			
		||||
    return BasicEvent.fromJson(event.toJson()).parsedImagePackContent;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ImagePackContent _pack;
 | 
			
		||||
  ImagePackContent get pack {
 | 
			
		||||
  ImagePackContent? _pack;
 | 
			
		||||
  ImagePackContent? get pack {
 | 
			
		||||
    if (_pack != null) {
 | 
			
		||||
      return _pack;
 | 
			
		||||
    }
 | 
			
		||||
@ -60,13 +60,13 @@ class EmotesSettingsController extends State<EmotesSettings> {
 | 
			
		||||
      await showFutureLoadingDialog(
 | 
			
		||||
        context: context,
 | 
			
		||||
        future: () => client.setRoomStateWithKey(
 | 
			
		||||
            room.id, 'im.ponies.room_emotes', stateKey ?? '', pack.toJson()),
 | 
			
		||||
            room!.id, 'im.ponies.room_emotes', stateKey ?? '', pack!.toJson()),
 | 
			
		||||
      );
 | 
			
		||||
    } else {
 | 
			
		||||
      await showFutureLoadingDialog(
 | 
			
		||||
        context: context,
 | 
			
		||||
        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) {
 | 
			
		||||
        content['rooms'] = <String, dynamic>{};
 | 
			
		||||
      }
 | 
			
		||||
      if (content['rooms'][room.id] is! Map) {
 | 
			
		||||
        content['rooms'][room.id] = <String, dynamic>{};
 | 
			
		||||
      if (content['rooms'][room!.id] is! Map) {
 | 
			
		||||
        content['rooms'][room!.id] = <String, dynamic>{};
 | 
			
		||||
      }
 | 
			
		||||
      if (content['rooms'][room.id][stateKey ?? ''] is! Map) {
 | 
			
		||||
        content['rooms'][room.id][stateKey ?? ''] = <String, dynamic>{};
 | 
			
		||||
      if (content['rooms'][room!.id][stateKey ?? ''] is! Map) {
 | 
			
		||||
        content['rooms'][room!.id][stateKey ?? ''] = <String, dynamic>{};
 | 
			
		||||
      }
 | 
			
		||||
    } else if (content['rooms'] is Map && content['rooms'][room.id] is Map) {
 | 
			
		||||
      content['rooms'][room.id].remove(stateKey ?? '');
 | 
			
		||||
    } else if (content['rooms'] is Map && content['rooms'][room!.id] is Map) {
 | 
			
		||||
      content['rooms'][room!.id].remove(stateKey ?? '');
 | 
			
		||||
    }
 | 
			
		||||
    // and save
 | 
			
		||||
    await showFutureLoadingDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      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(() {
 | 
			
		||||
        pack.images.remove(oldImageCode);
 | 
			
		||||
        pack!.images.remove(oldImageCode);
 | 
			
		||||
        showSave = true;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
@ -111,13 +111,13 @@ class EmotesSettingsController extends State<EmotesSettings> {
 | 
			
		||||
    ImagePackImageContent image,
 | 
			
		||||
    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;
 | 
			
		||||
      showOkAlertDialog(
 | 
			
		||||
        useRootNavigator: false,
 | 
			
		||||
        context: context,
 | 
			
		||||
        message: L10n.of(context).emoteExists,
 | 
			
		||||
        okLabel: L10n.of(context).ok,
 | 
			
		||||
        message: L10n.of(context)!.emoteExists,
 | 
			
		||||
        okLabel: L10n.of(context)!.ok,
 | 
			
		||||
      );
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
@ -126,29 +126,29 @@ class EmotesSettingsController extends State<EmotesSettings> {
 | 
			
		||||
      showOkAlertDialog(
 | 
			
		||||
        useRootNavigator: false,
 | 
			
		||||
        context: context,
 | 
			
		||||
        message: L10n.of(context).emoteInvalid,
 | 
			
		||||
        okLabel: L10n.of(context).ok,
 | 
			
		||||
        message: L10n.of(context)!.emoteInvalid,
 | 
			
		||||
        okLabel: L10n.of(context)!.ok,
 | 
			
		||||
      );
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    setState(() {
 | 
			
		||||
      pack.images[imageCode] = image;
 | 
			
		||||
      pack.images.remove(oldImageCode);
 | 
			
		||||
      pack!.images[imageCode] = image;
 | 
			
		||||
      pack!.images.remove(oldImageCode);
 | 
			
		||||
      showSave = true;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool isGloballyActive(Client client) =>
 | 
			
		||||
  bool isGloballyActive(Client? client) =>
 | 
			
		||||
      room != null &&
 | 
			
		||||
      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'][room.id]
 | 
			
		||||
      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'][room!.id]
 | 
			
		||||
          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;
 | 
			
		||||
 | 
			
		||||
  bool get readonly =>
 | 
			
		||||
      room == null ? false : !(room.canSendEvent('im.ponies.room_emotes'));
 | 
			
		||||
      room == null ? false : !(room!.canSendEvent('im.ponies.room_emotes'));
 | 
			
		||||
 | 
			
		||||
  void saveAction() async {
 | 
			
		||||
    await _save(context);
 | 
			
		||||
@ -158,24 +158,23 @@ class EmotesSettingsController extends State<EmotesSettings> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void addImageAction() async {
 | 
			
		||||
    if (newImageCodeController.text == null ||
 | 
			
		||||
        newImageCodeController.text.isEmpty ||
 | 
			
		||||
    if (newImageCodeController.text.isEmpty ||
 | 
			
		||||
        newImageController.value == null) {
 | 
			
		||||
      await showOkAlertDialog(
 | 
			
		||||
        useRootNavigator: false,
 | 
			
		||||
        context: context,
 | 
			
		||||
        message: L10n.of(context).emoteWarnNeedToPick,
 | 
			
		||||
        okLabel: L10n.of(context).ok,
 | 
			
		||||
        message: L10n.of(context)!.emoteWarnNeedToPick,
 | 
			
		||||
        okLabel: L10n.of(context)!.ok,
 | 
			
		||||
      );
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    final imageCode = newImageCodeController.text;
 | 
			
		||||
    if (pack.images.containsKey(imageCode)) {
 | 
			
		||||
    if (pack!.images.containsKey(imageCode)) {
 | 
			
		||||
      await showOkAlertDialog(
 | 
			
		||||
        useRootNavigator: false,
 | 
			
		||||
        context: context,
 | 
			
		||||
        message: L10n.of(context).emoteExists,
 | 
			
		||||
        okLabel: L10n.of(context).ok,
 | 
			
		||||
        message: L10n.of(context)!.emoteExists,
 | 
			
		||||
        okLabel: L10n.of(context)!.ok,
 | 
			
		||||
      );
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
@ -183,12 +182,12 @@ class EmotesSettingsController extends State<EmotesSettings> {
 | 
			
		||||
      await showOkAlertDialog(
 | 
			
		||||
        useRootNavigator: false,
 | 
			
		||||
        context: context,
 | 
			
		||||
        message: L10n.of(context).emoteInvalid,
 | 
			
		||||
        okLabel: L10n.of(context).ok,
 | 
			
		||||
        message: L10n.of(context)!.emoteInvalid,
 | 
			
		||||
        okLabel: L10n.of(context)!.ok,
 | 
			
		||||
      );
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    pack.images[imageCode] = newImageController.value;
 | 
			
		||||
    pack!.images[imageCode] = newImageController.value!;
 | 
			
		||||
    await _save(context);
 | 
			
		||||
    setState(() {
 | 
			
		||||
      newImageCodeController.text = '';
 | 
			
		||||
@ -198,13 +197,13 @@ class EmotesSettingsController extends State<EmotesSettings> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void imagePickerAction(
 | 
			
		||||
      ValueNotifier<ImagePackImageContent> controller) async {
 | 
			
		||||
      ValueNotifier<ImagePackImageContent?> controller) async {
 | 
			
		||||
    final result =
 | 
			
		||||
        await FilePickerCross.importFromStorage(type: FileTypeCross.image);
 | 
			
		||||
    if (result == null) return;
 | 
			
		||||
    if (result.fileName == null) return;
 | 
			
		||||
    var file = MatrixImageFile(
 | 
			
		||||
      bytes: result.toUint8List(),
 | 
			
		||||
      name: result.fileName,
 | 
			
		||||
      name: result.fileName!,
 | 
			
		||||
    );
 | 
			
		||||
    try {
 | 
			
		||||
      file = await file.resizeImage(calcBlurhash: false);
 | 
			
		||||
 | 
			
		||||
@ -13,16 +13,16 @@ import 'settings_emotes.dart';
 | 
			
		||||
class EmotesSettingsView extends StatelessWidget {
 | 
			
		||||
  final EmotesSettingsController controller;
 | 
			
		||||
 | 
			
		||||
  const EmotesSettingsView(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const EmotesSettingsView(this.controller, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final client = Matrix.of(context).client;
 | 
			
		||||
    final imageKeys = controller.pack.images.keys.toList();
 | 
			
		||||
    final imageKeys = controller.pack!.images.keys.toList();
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        leading: const BackButton(),
 | 
			
		||||
        title: Text(L10n.of(context).emoteSettings),
 | 
			
		||||
        title: Text(L10n.of(context)!.emoteSettings),
 | 
			
		||||
      ),
 | 
			
		||||
      floatingActionButton: controller.showSave
 | 
			
		||||
          ? FloatingActionButton(
 | 
			
		||||
@ -53,7 +53,7 @@ class EmotesSettingsView extends StatelessWidget {
 | 
			
		||||
                      minLines: 1,
 | 
			
		||||
                      maxLines: 1,
 | 
			
		||||
                      decoration: InputDecoration(
 | 
			
		||||
                        hintText: L10n.of(context).emoteShortcode,
 | 
			
		||||
                        hintText: L10n.of(context)!.emoteShortcode,
 | 
			
		||||
                        prefixText: ': ',
 | 
			
		||||
                        suffixText: ':',
 | 
			
		||||
                        prefixStyle: TextStyle(
 | 
			
		||||
@ -84,7 +84,7 @@ class EmotesSettingsView extends StatelessWidget {
 | 
			
		||||
              ),
 | 
			
		||||
            if (controller.room != null)
 | 
			
		||||
              SwitchListTile.adaptive(
 | 
			
		||||
                title: Text(L10n.of(context).enableEmotesGlobally),
 | 
			
		||||
                title: Text(L10n.of(context)!.enableEmotesGlobally),
 | 
			
		||||
                value: controller.isGloballyActive(client),
 | 
			
		||||
                onChanged: controller.setIsGloballyActive,
 | 
			
		||||
              ),
 | 
			
		||||
@ -100,7 +100,7 @@ class EmotesSettingsView extends StatelessWidget {
 | 
			
		||||
                      child: Padding(
 | 
			
		||||
                        padding: const EdgeInsets.all(16),
 | 
			
		||||
                        child: Text(
 | 
			
		||||
                          L10n.of(context).noEmotesFound,
 | 
			
		||||
                          L10n.of(context)!.noEmotesFound,
 | 
			
		||||
                          style: const TextStyle(fontSize: 20),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
@ -114,7 +114,7 @@ class EmotesSettingsView extends StatelessWidget {
 | 
			
		||||
                          return Container(height: 70);
 | 
			
		||||
                        }
 | 
			
		||||
                        final imageCode = imageKeys[i];
 | 
			
		||||
                        final image = controller.pack.images[imageCode];
 | 
			
		||||
                        final image = controller.pack!.images[imageCode]!;
 | 
			
		||||
                        final textEditingController = TextEditingController();
 | 
			
		||||
                        textEditingController.text = imageCode;
 | 
			
		||||
                        final useShortCuts =
 | 
			
		||||
@ -158,7 +158,7 @@ class EmotesSettingsView extends StatelessWidget {
 | 
			
		||||
                                  minLines: 1,
 | 
			
		||||
                                  maxLines: 1,
 | 
			
		||||
                                  decoration: InputDecoration(
 | 
			
		||||
                                    hintText: L10n.of(context).emoteShortcode,
 | 
			
		||||
                                    hintText: L10n.of(context)!.emoteShortcode,
 | 
			
		||||
                                    prefixText: ': ',
 | 
			
		||||
                                    suffixText: ':',
 | 
			
		||||
                                    prefixStyle: TextStyle(
 | 
			
		||||
@ -217,7 +217,7 @@ class _EmoteImage extends StatelessWidget {
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    const size = 38.0;
 | 
			
		||||
    final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
 | 
			
		||||
    final url = mxc?.getThumbnail(
 | 
			
		||||
    final url = mxc.getThumbnail(
 | 
			
		||||
      Matrix.of(context).client,
 | 
			
		||||
      width: size * devicePixelRatio,
 | 
			
		||||
      height: size * devicePixelRatio,
 | 
			
		||||
@ -233,11 +233,11 @@ class _EmoteImage extends StatelessWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
  _ImagePickerState createState() => _ImagePickerState();
 | 
			
		||||
@ -249,10 +249,10 @@ class _ImagePickerState extends State<_ImagePicker> {
 | 
			
		||||
    if (widget.controller.value == null) {
 | 
			
		||||
      return ElevatedButton(
 | 
			
		||||
        onPressed: () => widget.onPressed(widget.controller),
 | 
			
		||||
        child: Text(L10n.of(context).pickImage),
 | 
			
		||||
        child: Text(L10n.of(context)!.pickImage),
 | 
			
		||||
      );
 | 
			
		||||
    } else {
 | 
			
		||||
      return _EmoteImage(widget.controller.value.url);
 | 
			
		||||
      return _EmoteImage(widget.controller.value!.url);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -6,9 +6,9 @@ import '../../widgets/matrix.dart';
 | 
			
		||||
import 'settings_ignore_list_view.dart';
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
  SettingsIgnoreListController createState() => SettingsIgnoreListController();
 | 
			
		||||
@ -21,7 +21,7 @@ class SettingsIgnoreListController extends State<SettingsIgnoreList> {
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
    if (widget.initialUserId != null) {
 | 
			
		||||
      controller.text = widget.initialUserId.replaceAll('@', '');
 | 
			
		||||
      controller.text = widget.initialUserId!.replaceAll('@', '');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,7 @@ import 'settings_ignore_list.dart';
 | 
			
		||||
class SettingsIgnoreListView extends StatelessWidget {
 | 
			
		||||
  final SettingsIgnoreListController controller;
 | 
			
		||||
 | 
			
		||||
  const SettingsIgnoreListView(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const SettingsIgnoreListView(this.controller, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
@ -20,7 +20,7 @@ class SettingsIgnoreListView extends StatelessWidget {
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        leading: const BackButton(),
 | 
			
		||||
        title: Text(L10n.of(context).ignoredUsers),
 | 
			
		||||
        title: Text(L10n.of(context)!.ignoredUsers),
 | 
			
		||||
      ),
 | 
			
		||||
      body: MaxWidthBody(
 | 
			
		||||
        child: Column(
 | 
			
		||||
@ -39,9 +39,9 @@ class SettingsIgnoreListView extends StatelessWidget {
 | 
			
		||||
                      border: const OutlineInputBorder(),
 | 
			
		||||
                      hintText: 'bad_guy:domain.abc',
 | 
			
		||||
                      prefixText: '@',
 | 
			
		||||
                      labelText: L10n.of(context).ignoreUsername,
 | 
			
		||||
                      labelText: L10n.of(context)!.ignoreUsername,
 | 
			
		||||
                      suffixIcon: IconButton(
 | 
			
		||||
                        tooltip: L10n.of(context).ignore,
 | 
			
		||||
                        tooltip: L10n.of(context)!.ignore,
 | 
			
		||||
                        icon: const Icon(Icons.done_outlined),
 | 
			
		||||
                        onPressed: () => controller.ignoreUser(context),
 | 
			
		||||
                      ),
 | 
			
		||||
@ -49,7 +49,7 @@ class SettingsIgnoreListView extends StatelessWidget {
 | 
			
		||||
                  ),
 | 
			
		||||
                  const SizedBox(height: 16),
 | 
			
		||||
                  Text(
 | 
			
		||||
                    L10n.of(context).ignoreListDescription,
 | 
			
		||||
                    L10n.of(context)!.ignoreListDescription,
 | 
			
		||||
                    style: const TextStyle(color: Colors.orange),
 | 
			
		||||
                  ),
 | 
			
		||||
                ],
 | 
			
		||||
@ -74,7 +74,7 @@ class SettingsIgnoreListView extends StatelessWidget {
 | 
			
		||||
                          title: Text(
 | 
			
		||||
                              s.data?.displayName ?? client.ignoredUsers[i]),
 | 
			
		||||
                          trailing: IconButton(
 | 
			
		||||
                            tooltip: L10n.of(context).delete,
 | 
			
		||||
                            tooltip: L10n.of(context)!.delete,
 | 
			
		||||
                            icon: const Icon(Icons.delete_forever_outlined),
 | 
			
		||||
                            onPressed: () => showFutureLoadingDialog(
 | 
			
		||||
                              context: context,
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@ import 'package:vrouter/vrouter.dart';
 | 
			
		||||
import 'settings_multiple_emotes_view.dart';
 | 
			
		||||
 | 
			
		||||
class MultipleEmotesSettings extends StatefulWidget {
 | 
			
		||||
  const MultipleEmotesSettings({Key key}) : super(key: key);
 | 
			
		||||
  const MultipleEmotesSettings({Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  MultipleEmotesSettingsController createState() =>
 | 
			
		||||
@ -13,7 +13,7 @@ class MultipleEmotesSettings extends StatefulWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class MultipleEmotesSettingsController extends State<MultipleEmotesSettings> {
 | 
			
		||||
  String get roomId => VRouter.of(context).pathParameters['roomid'];
 | 
			
		||||
  String? get roomId => VRouter.of(context).pathParameters['roomid'];
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) => MultipleEmotesSettingsView(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -10,21 +10,21 @@ import 'package:fluffychat/widgets/matrix.dart';
 | 
			
		||||
class MultipleEmotesSettingsView extends StatelessWidget {
 | 
			
		||||
  final MultipleEmotesSettingsController controller;
 | 
			
		||||
 | 
			
		||||
  const MultipleEmotesSettingsView(this.controller, {Key key})
 | 
			
		||||
  const MultipleEmotesSettingsView(this.controller, {Key? key})
 | 
			
		||||
      : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(controller.roomId);
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(controller.roomId!)!;
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        leading: const BackButton(),
 | 
			
		||||
        title: Text(L10n.of(context).emotePacks),
 | 
			
		||||
        title: Text(L10n.of(context)!.emotePacks),
 | 
			
		||||
      ),
 | 
			
		||||
      body: StreamBuilder(
 | 
			
		||||
        stream: room.onUpdate.stream,
 | 
			
		||||
        builder: (context, snapshot) {
 | 
			
		||||
          final packs =
 | 
			
		||||
          final Map<String, Event?> packs =
 | 
			
		||||
              room.states['im.ponies.room_emotes'] ?? <String, Event>{};
 | 
			
		||||
          if (!packs.containsKey('')) {
 | 
			
		||||
            packs[''] = null;
 | 
			
		||||
@ -36,7 +36,8 @@ class MultipleEmotesSettingsView extends StatelessWidget {
 | 
			
		||||
              itemCount: keys.length,
 | 
			
		||||
              itemBuilder: (BuildContext context, int 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.content['pack']['displayname'] is String) {
 | 
			
		||||
                    packName = event.content['pack']['displayname'];
 | 
			
		||||
@ -45,7 +46,7 @@ class MultipleEmotesSettingsView extends StatelessWidget {
 | 
			
		||||
                  }
 | 
			
		||||
                }
 | 
			
		||||
                return ListTile(
 | 
			
		||||
                  title: Text(packName),
 | 
			
		||||
                  title: Text(packName!),
 | 
			
		||||
                  onTap: () async {
 | 
			
		||||
                    VRouter.of(context).toSegments(
 | 
			
		||||
                        ['rooms', room.id, 'details', 'emotes', keys[i]]);
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:collection/collection.dart' show IterableExtension;
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
 | 
			
		||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
			
		||||
@ -19,38 +20,38 @@ class NotificationSettingsItem {
 | 
			
		||||
    NotificationSettingsItem(
 | 
			
		||||
      PushRuleKind.underride,
 | 
			
		||||
      '.m.rule.room_one_to_one',
 | 
			
		||||
      (c) => L10n.of(c).directChats,
 | 
			
		||||
      (c) => L10n.of(c)!.directChats,
 | 
			
		||||
    ),
 | 
			
		||||
    NotificationSettingsItem(
 | 
			
		||||
      PushRuleKind.override,
 | 
			
		||||
      '.m.rule.contains_display_name',
 | 
			
		||||
      (c) => L10n.of(c).containsDisplayName,
 | 
			
		||||
      (c) => L10n.of(c)!.containsDisplayName,
 | 
			
		||||
    ),
 | 
			
		||||
    NotificationSettingsItem(
 | 
			
		||||
      PushRuleKind.content,
 | 
			
		||||
      '.m.rule.contains_user_name',
 | 
			
		||||
      (c) => L10n.of(c).containsUserName,
 | 
			
		||||
      (c) => L10n.of(c)!.containsUserName,
 | 
			
		||||
    ),
 | 
			
		||||
    NotificationSettingsItem(
 | 
			
		||||
      PushRuleKind.override,
 | 
			
		||||
      '.m.rule.invite_for_me',
 | 
			
		||||
      (c) => L10n.of(c).inviteForMe,
 | 
			
		||||
      (c) => L10n.of(c)!.inviteForMe,
 | 
			
		||||
    ),
 | 
			
		||||
    NotificationSettingsItem(
 | 
			
		||||
      PushRuleKind.override,
 | 
			
		||||
      '.m.rule.member_event',
 | 
			
		||||
      (c) => L10n.of(c).memberChanges,
 | 
			
		||||
      (c) => L10n.of(c)!.memberChanges,
 | 
			
		||||
    ),
 | 
			
		||||
    NotificationSettingsItem(
 | 
			
		||||
      PushRuleKind.override,
 | 
			
		||||
      '.m.rule.suppress_notices',
 | 
			
		||||
      (c) => L10n.of(c).botMessages,
 | 
			
		||||
      (c) => L10n.of(c)!.botMessages,
 | 
			
		||||
    ),
 | 
			
		||||
  ];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class SettingsNotifications extends StatefulWidget {
 | 
			
		||||
  const SettingsNotifications({Key key}) : super(key: key);
 | 
			
		||||
  const SettingsNotifications({Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  SettingsNotificationsController createState() =>
 | 
			
		||||
@ -71,31 +72,30 @@ class SettingsNotificationsController extends State<SettingsNotifications> {
 | 
			
		||||
    return NotificationSetting.open();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool getNotificationSetting(NotificationSettingsItem item) {
 | 
			
		||||
  bool? getNotificationSetting(NotificationSettingsItem item) {
 | 
			
		||||
    final pushRules = Matrix.of(context).client.globalPushRules;
 | 
			
		||||
    switch (item.type) {
 | 
			
		||||
      case PushRuleKind.content:
 | 
			
		||||
        return pushRules.content
 | 
			
		||||
            ?.singleWhere((r) => r.ruleId == item.key, orElse: () => null)
 | 
			
		||||
        return pushRules!.content
 | 
			
		||||
            ?.singleWhereOrNull((r) => r.ruleId == item.key)
 | 
			
		||||
            ?.enabled;
 | 
			
		||||
      case PushRuleKind.override:
 | 
			
		||||
        return pushRules.override
 | 
			
		||||
            ?.singleWhere((r) => r.ruleId == item.key, orElse: () => null)
 | 
			
		||||
        return pushRules!.override
 | 
			
		||||
            ?.singleWhereOrNull((r) => r.ruleId == item.key)
 | 
			
		||||
            ?.enabled;
 | 
			
		||||
      case PushRuleKind.room:
 | 
			
		||||
        return pushRules.room
 | 
			
		||||
            ?.singleWhere((r) => r.ruleId == item.key, orElse: () => null)
 | 
			
		||||
        return pushRules!.room
 | 
			
		||||
            ?.singleWhereOrNull((r) => r.ruleId == item.key)
 | 
			
		||||
            ?.enabled;
 | 
			
		||||
      case PushRuleKind.sender:
 | 
			
		||||
        return pushRules.sender
 | 
			
		||||
            ?.singleWhere((r) => r.ruleId == item.key, orElse: () => null)
 | 
			
		||||
        return pushRules!.sender
 | 
			
		||||
            ?.singleWhereOrNull((r) => r.ruleId == item.key)
 | 
			
		||||
            ?.enabled;
 | 
			
		||||
      case PushRuleKind.underride:
 | 
			
		||||
        return pushRules.underride
 | 
			
		||||
            ?.singleWhere((r) => r.ruleId == item.key, orElse: () => null)
 | 
			
		||||
        return pushRules!.underride
 | 
			
		||||
            ?.singleWhereOrNull((r) => r.ruleId == item.key)
 | 
			
		||||
            ?.enabled;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setNotificationSetting(NotificationSettingsItem item, bool enabled) {
 | 
			
		||||
 | 
			
		||||
@ -15,14 +15,15 @@ import 'settings_notifications.dart';
 | 
			
		||||
class SettingsNotificationsView extends StatelessWidget {
 | 
			
		||||
  final SettingsNotificationsController controller;
 | 
			
		||||
 | 
			
		||||
  const SettingsNotificationsView(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const SettingsNotificationsView(this.controller, {Key? key})
 | 
			
		||||
      : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        leading: const BackButton(),
 | 
			
		||||
        title: Text(L10n.of(context).notifications),
 | 
			
		||||
        title: Text(L10n.of(context)!.notifications),
 | 
			
		||||
      ),
 | 
			
		||||
      body: MaxWidthBody(
 | 
			
		||||
        withScrolling: true,
 | 
			
		||||
@ -38,7 +39,7 @@ class SettingsNotificationsView extends StatelessWidget {
 | 
			
		||||
                  SwitchListTile.adaptive(
 | 
			
		||||
                    value: !Matrix.of(context).client.allPushNotificationsMuted,
 | 
			
		||||
                    title: Text(
 | 
			
		||||
                        L10n.of(context).notificationsEnabledForThisAccount),
 | 
			
		||||
                        L10n.of(context)!.notificationsEnabledForThisAccount),
 | 
			
		||||
                    onChanged: (_) => showFutureLoadingDialog(
 | 
			
		||||
                      context: context,
 | 
			
		||||
                      future: () =>
 | 
			
		||||
@ -52,7 +53,7 @@ class SettingsNotificationsView extends StatelessWidget {
 | 
			
		||||
                  if (!Matrix.of(context).client.allPushNotificationsMuted) ...{
 | 
			
		||||
                    if (!kIsWeb && Platform.isAndroid)
 | 
			
		||||
                      ListTile(
 | 
			
		||||
                        title: Text(L10n.of(context).soundVibrationLedColor),
 | 
			
		||||
                        title: Text(L10n.of(context)!.soundVibrationLedColor),
 | 
			
		||||
                        trailing: CircleAvatar(
 | 
			
		||||
                          backgroundColor:
 | 
			
		||||
                              Theme.of(context).scaffoldBackgroundColor,
 | 
			
		||||
@ -64,7 +65,7 @@ class SettingsNotificationsView extends StatelessWidget {
 | 
			
		||||
                    const Divider(thickness: 1),
 | 
			
		||||
                    ListTile(
 | 
			
		||||
                      title: Text(
 | 
			
		||||
                        L10n.of(context).pushRules,
 | 
			
		||||
                        L10n.of(context)!.pushRules,
 | 
			
		||||
                        style: TextStyle(
 | 
			
		||||
                          color: Theme.of(context).colorScheme.secondary,
 | 
			
		||||
                          fontWeight: FontWeight.bold,
 | 
			
		||||
@ -82,20 +83,20 @@ class SettingsNotificationsView extends StatelessWidget {
 | 
			
		||||
                  const Divider(thickness: 1),
 | 
			
		||||
                  ListTile(
 | 
			
		||||
                    title: Text(
 | 
			
		||||
                      L10n.of(context).devices,
 | 
			
		||||
                      L10n.of(context)!.devices,
 | 
			
		||||
                      style: TextStyle(
 | 
			
		||||
                        color: Theme.of(context).colorScheme.secondary,
 | 
			
		||||
                        fontWeight: FontWeight.bold,
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                  FutureBuilder<List<Pusher>>(
 | 
			
		||||
                  FutureBuilder<List<Pusher>?>(
 | 
			
		||||
                    future: Matrix.of(context).client.getPushers(),
 | 
			
		||||
                    builder: (context, snapshot) {
 | 
			
		||||
                      if (snapshot.hasError) {
 | 
			
		||||
                        Center(
 | 
			
		||||
                          child: Text(
 | 
			
		||||
                            snapshot.error.toLocalizedString(context),
 | 
			
		||||
                            snapshot.error!.toLocalizedString(context),
 | 
			
		||||
                          ),
 | 
			
		||||
                        );
 | 
			
		||||
                      }
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,7 @@ import '../bootstrap/bootstrap_dialog.dart';
 | 
			
		||||
import 'settings_security_view.dart';
 | 
			
		||||
 | 
			
		||||
class SettingsSecurity extends StatefulWidget {
 | 
			
		||||
  const SettingsSecurity({Key key}) : super(key: key);
 | 
			
		||||
  const SettingsSecurity({Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  SettingsSecurityController createState() => SettingsSecurityController();
 | 
			
		||||
@ -23,18 +23,18 @@ class SettingsSecurityController extends State<SettingsSecurity> {
 | 
			
		||||
    final input = await showTextInputDialog(
 | 
			
		||||
      useRootNavigator: false,
 | 
			
		||||
      context: context,
 | 
			
		||||
      title: L10n.of(context).changePassword,
 | 
			
		||||
      okLabel: L10n.of(context).ok,
 | 
			
		||||
      cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
      title: L10n.of(context)!.changePassword,
 | 
			
		||||
      okLabel: L10n.of(context)!.ok,
 | 
			
		||||
      cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
      textFields: [
 | 
			
		||||
        DialogTextField(
 | 
			
		||||
          hintText: L10n.of(context).pleaseEnterYourPassword,
 | 
			
		||||
          hintText: L10n.of(context)!.pleaseEnterYourPassword,
 | 
			
		||||
          obscureText: true,
 | 
			
		||||
          minLines: 1,
 | 
			
		||||
          maxLines: 1,
 | 
			
		||||
        ),
 | 
			
		||||
        DialogTextField(
 | 
			
		||||
          hintText: L10n.of(context).chooseAStrongPassword,
 | 
			
		||||
          hintText: L10n.of(context)!.chooseAStrongPassword,
 | 
			
		||||
          obscureText: true,
 | 
			
		||||
          minLines: 1,
 | 
			
		||||
          maxLines: 1,
 | 
			
		||||
@ -50,7 +50,7 @@ class SettingsSecurityController extends State<SettingsSecurity> {
 | 
			
		||||
    );
 | 
			
		||||
    if (success.error == null) {
 | 
			
		||||
      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 =
 | 
			
		||||
        await const FlutterSecureStorage().read(key: SettingKeys.appLockKey);
 | 
			
		||||
    if (currentLock?.isNotEmpty ?? false) {
 | 
			
		||||
      await AppLock.of(context).showLockScreen();
 | 
			
		||||
      await AppLock.of(context)!.showLockScreen();
 | 
			
		||||
    }
 | 
			
		||||
    final newLock = await showTextInputDialog(
 | 
			
		||||
      useRootNavigator: false,
 | 
			
		||||
      context: context,
 | 
			
		||||
      title: L10n.of(context).pleaseChooseAPasscode,
 | 
			
		||||
      message: L10n.of(context).pleaseEnter4Digits,
 | 
			
		||||
      cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
      title: L10n.of(context)!.pleaseChooseAPasscode,
 | 
			
		||||
      message: L10n.of(context)!.pleaseEnter4Digits,
 | 
			
		||||
      cancelLabel: L10n.of(context)!.cancel,
 | 
			
		||||
      textFields: [
 | 
			
		||||
        DialogTextField(
 | 
			
		||||
          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 L10n.of(context).pleaseEnter4Digits;
 | 
			
		||||
            return L10n.of(context)!.pleaseEnter4Digits;
 | 
			
		||||
          },
 | 
			
		||||
          keyboardType: TextInputType.number,
 | 
			
		||||
          obscureText: true,
 | 
			
		||||
@ -85,9 +86,9 @@ class SettingsSecurityController extends State<SettingsSecurity> {
 | 
			
		||||
      await const FlutterSecureStorage()
 | 
			
		||||
          .write(key: SettingKeys.appLockKey, value: newLock.single);
 | 
			
		||||
      if (newLock.single.isEmpty) {
 | 
			
		||||
        AppLock.of(context).disable();
 | 
			
		||||
        AppLock.of(context)!.disable();
 | 
			
		||||
      } else {
 | 
			
		||||
        AppLock.of(context).enable();
 | 
			
		||||
        AppLock.of(context)!.enable();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -12,38 +12,38 @@ import 'settings_security.dart';
 | 
			
		||||
 | 
			
		||||
class SettingsSecurityView extends StatelessWidget {
 | 
			
		||||
  final SettingsSecurityController controller;
 | 
			
		||||
  const SettingsSecurityView(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const SettingsSecurityView(this.controller, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(title: Text(L10n.of(context).security)),
 | 
			
		||||
      appBar: AppBar(title: Text(L10n.of(context)!.security)),
 | 
			
		||||
      body: ListTileTheme(
 | 
			
		||||
        iconColor: Theme.of(context).textTheme.bodyText1.color,
 | 
			
		||||
        iconColor: Theme.of(context).textTheme.bodyText1!.color,
 | 
			
		||||
        child: MaxWidthBody(
 | 
			
		||||
          withScrolling: true,
 | 
			
		||||
          child: Column(
 | 
			
		||||
            children: [
 | 
			
		||||
              ListTile(
 | 
			
		||||
                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'),
 | 
			
		||||
              ),
 | 
			
		||||
              ListTile(
 | 
			
		||||
                trailing: const Icon(Icons.close),
 | 
			
		||||
                title: Text(L10n.of(context).ignoredUsers),
 | 
			
		||||
                title: Text(L10n.of(context)!.ignoredUsers),
 | 
			
		||||
                onTap: () => VRouter.of(context).to('ignorelist'),
 | 
			
		||||
              ),
 | 
			
		||||
              ListTile(
 | 
			
		||||
                trailing: const Icon(Icons.vpn_key_outlined),
 | 
			
		||||
                title: Text(
 | 
			
		||||
                  L10n.of(context).changePassword,
 | 
			
		||||
                  L10n.of(context)!.changePassword,
 | 
			
		||||
                ),
 | 
			
		||||
                onTap: controller.changePasswordAccountAction,
 | 
			
		||||
              ),
 | 
			
		||||
              ListTile(
 | 
			
		||||
                trailing: const Icon(Icons.mail_outlined),
 | 
			
		||||
                title: Text(L10n.of(context).passwordRecovery),
 | 
			
		||||
                title: Text(L10n.of(context)!.passwordRecovery),
 | 
			
		||||
                onTap: () => VRouter.of(context).to('3pid'),
 | 
			
		||||
              ),
 | 
			
		||||
              if (Matrix.of(context).client.encryption != null) ...{
 | 
			
		||||
@ -51,30 +51,30 @@ class SettingsSecurityView extends StatelessWidget {
 | 
			
		||||
                if (PlatformInfos.isMobile)
 | 
			
		||||
                  ListTile(
 | 
			
		||||
                    trailing: const Icon(Icons.lock_outlined),
 | 
			
		||||
                    title: Text(L10n.of(context).appLock),
 | 
			
		||||
                    title: Text(L10n.of(context)!.appLock),
 | 
			
		||||
                    onTap: controller.setAppLockAction,
 | 
			
		||||
                  ),
 | 
			
		||||
                ListTile(
 | 
			
		||||
                  title: Text(L10n.of(context).yourPublicKey),
 | 
			
		||||
                  title: Text(L10n.of(context)!.yourPublicKey),
 | 
			
		||||
                  onTap: () => showOkAlertDialog(
 | 
			
		||||
                    useRootNavigator: false,
 | 
			
		||||
                    context: context,
 | 
			
		||||
                    title: L10n.of(context).yourPublicKey,
 | 
			
		||||
                    title: L10n.of(context)!.yourPublicKey,
 | 
			
		||||
                    message:
 | 
			
		||||
                        Matrix.of(context).client.fingerprintKey.beautified,
 | 
			
		||||
                    okLabel: L10n.of(context).ok,
 | 
			
		||||
                    okLabel: L10n.of(context)!.ok,
 | 
			
		||||
                  ),
 | 
			
		||||
                  trailing: const Icon(Icons.vpn_key_outlined),
 | 
			
		||||
                ),
 | 
			
		||||
                if (!Matrix.of(context).client.encryption.crossSigning.enabled)
 | 
			
		||||
                if (!Matrix.of(context).client.encryption!.crossSigning.enabled)
 | 
			
		||||
                  ListTile(
 | 
			
		||||
                    title: Text(L10n.of(context).crossSigningEnabled),
 | 
			
		||||
                    title: Text(L10n.of(context)!.crossSigningEnabled),
 | 
			
		||||
                    trailing: const Icon(Icons.error, color: Colors.red),
 | 
			
		||||
                    onTap: () => controller.showBootstrapDialog(context),
 | 
			
		||||
                  ),
 | 
			
		||||
                if (!Matrix.of(context).client.encryption.keyManager.enabled)
 | 
			
		||||
                if (!Matrix.of(context).client.encryption!.keyManager.enabled)
 | 
			
		||||
                  ListTile(
 | 
			
		||||
                    title: Text(L10n.of(context).onlineKeyBackupEnabled),
 | 
			
		||||
                    title: Text(L10n.of(context)!.onlineKeyBackupEnabled),
 | 
			
		||||
                    trailing: const Icon(Icons.error, color: Colors.red),
 | 
			
		||||
                    onTap: () => controller.showBootstrapDialog(context),
 | 
			
		||||
                  ),
 | 
			
		||||
@ -88,19 +88,19 @@ class SettingsSecurityView extends StatelessWidget {
 | 
			
		||||
                  future: () async {
 | 
			
		||||
                    return (await Matrix.of(context)
 | 
			
		||||
                            .client
 | 
			
		||||
                            .encryption
 | 
			
		||||
                            .encryption!
 | 
			
		||||
                            .keyManager
 | 
			
		||||
                            .isCached()) &&
 | 
			
		||||
                        (await Matrix.of(context)
 | 
			
		||||
                            .client
 | 
			
		||||
                            .encryption
 | 
			
		||||
                            .encryption!
 | 
			
		||||
                            .crossSigning
 | 
			
		||||
                            .isCached());
 | 
			
		||||
                  }(),
 | 
			
		||||
                  builder: (context, snapshot) => snapshot.data == true
 | 
			
		||||
                      ? Container()
 | 
			
		||||
                      : ListTile(
 | 
			
		||||
                          title: Text(L10n.of(context).keysCached),
 | 
			
		||||
                          title: Text(L10n.of(context)!.keysCached),
 | 
			
		||||
                          trailing: const Icon(Icons.error, color: Colors.red),
 | 
			
		||||
                          onTap: () => controller.showBootstrapDialog(context),
 | 
			
		||||
                        ),
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'dart:io';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:adaptive_theme/adaptive_theme.dart';
 | 
			
		||||
@ -19,6 +17,7 @@ class SettingsStyleView extends StatelessWidget {
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    controller.currentTheme ??= AdaptiveTheme.of(context).mode;
 | 
			
		||||
    const colorPickerSize = 32.0;
 | 
			
		||||
    final wallpaper = Matrix.of(context).wallpaper;
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        leading: const BackButton(),
 | 
			
		||||
@ -86,10 +85,10 @@ class SettingsStyleView extends StatelessWidget {
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
            if (Matrix.of(context).wallpaper != null)
 | 
			
		||||
            if (wallpaper != null)
 | 
			
		||||
              ListTile(
 | 
			
		||||
                title: Image.file(
 | 
			
		||||
                  Matrix.of(context).wallpaper,
 | 
			
		||||
                  wallpaper,
 | 
			
		||||
                  height: 38,
 | 
			
		||||
                  fit: BoxFit.cover,
 | 
			
		||||
                ),
 | 
			
		||||
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user