mirror of
				https://gitlab.com/famedly/fluffychat.git
				synced 2025-11-04 06:17:26 +01:00 
			
		
		
		
	Merge branch 'krille/null-safe-widgets' into 'main'
refactor: Make widgets null safe See merge request famedly/fluffychat!698
This commit is contained in:
		
						commit
						e4ca34ddcc
					
				@ -10,6 +10,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
import 'package:matrix/matrix.dart';
 | 
			
		||||
import 'package:path_provider/path_provider.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
 | 
			
		||||
import 'package:fluffychat/utils/sentry_controller.dart';
 | 
			
		||||
import '../../../utils/matrix_sdk_extensions.dart/event_extension.dart';
 | 
			
		||||
 | 
			
		||||
@ -66,7 +67,7 @@ class _AudioPlayerState extends State<AudioPlayerWidget> {
 | 
			
		||||
          await widget.event.downloadAndDecryptAttachmentCached();
 | 
			
		||||
      if (matrixFile == null) throw ('Download failed');
 | 
			
		||||
      final tempDir = await getTemporaryDirectory();
 | 
			
		||||
      final fileName = widget.event.content.tryGet<String>('url')!;
 | 
			
		||||
      final fileName = widget.event.attachmentOrThumbnailMxcUrl()!.toString();
 | 
			
		||||
      final file = File('${tempDir.path}/$fileName');
 | 
			
		||||
      await file.writeAsBytes(matrixFile.bytes);
 | 
			
		||||
 | 
			
		||||
@ -79,7 +80,7 @@ class _AudioPlayerState extends State<AudioPlayerWidget> {
 | 
			
		||||
      Logs().v('Could not download audio file', e, s);
 | 
			
		||||
      ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
        SnackBar(
 | 
			
		||||
          content: Text(e.toString()),
 | 
			
		||||
          content: Text(e.toLocalizedString(context)),
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -41,7 +41,7 @@ class _EventVideoPlayerState extends State<EventVideoPlayer> {
 | 
			
		||||
        _networkUri = html.Url.createObjectUrlFromBlob(blob);
 | 
			
		||||
      } else {
 | 
			
		||||
        final tempDir = await getTemporaryDirectory();
 | 
			
		||||
        final fileName = widget.event.content.tryGet<String>('url')!;
 | 
			
		||||
        final fileName = widget.event.attachmentOrThumbnailMxcUrl()!.toString();
 | 
			
		||||
        final file = File('${tempDir.path}/$fileName');
 | 
			
		||||
        if (await file.exists() == false) {
 | 
			
		||||
          await file.writeAsBytes(videoFile.bytes);
 | 
			
		||||
 | 
			
		||||
@ -45,7 +45,7 @@ class SearchController extends State<Search> {
 | 
			
		||||
    showModalBottomSheet(
 | 
			
		||||
      context: context,
 | 
			
		||||
      builder: (c) => PublicRoomBottomSheet(
 | 
			
		||||
        roomAlias: room.canonicalAlias,
 | 
			
		||||
        roomAlias: room.canonicalAlias ?? room.roomId,
 | 
			
		||||
        outerContext: context,
 | 
			
		||||
        chunk: room,
 | 
			
		||||
      ),
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:cached_network_image/cached_network_image.dart';
 | 
			
		||||
@ -10,8 +12,8 @@ class ContentBanner extends StatelessWidget {
 | 
			
		||||
  final double height;
 | 
			
		||||
  final IconData defaultIcon;
 | 
			
		||||
  final bool loading;
 | 
			
		||||
  final Function onEdit;
 | 
			
		||||
  final Client client;
 | 
			
		||||
  final void Function()? onEdit;
 | 
			
		||||
  final Client? client;
 | 
			
		||||
  final double opacity;
 | 
			
		||||
 | 
			
		||||
  const ContentBanner(this.mxContent,
 | 
			
		||||
@ -21,7 +23,7 @@ class ContentBanner extends StatelessWidget {
 | 
			
		||||
      this.onEdit,
 | 
			
		||||
      this.client,
 | 
			
		||||
      this.opacity = 0.75,
 | 
			
		||||
      Key key})
 | 
			
		||||
      Key? key})
 | 
			
		||||
      : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@ -29,7 +31,8 @@ class ContentBanner extends StatelessWidget {
 | 
			
		||||
    final mediaQuery = MediaQuery.of(context);
 | 
			
		||||
    final bannerSize =
 | 
			
		||||
        (mediaQuery.size.width * mediaQuery.devicePixelRatio).toInt();
 | 
			
		||||
    final src = mxContent?.getThumbnail(
 | 
			
		||||
    final onEdit = this.onEdit;
 | 
			
		||||
    final src = mxContent.getThumbnail(
 | 
			
		||||
      client ?? Matrix.of(context).client,
 | 
			
		||||
      width: bannerSize,
 | 
			
		||||
      height: bannerSize,
 | 
			
		||||
@ -51,14 +54,13 @@ class ContentBanner extends StatelessWidget {
 | 
			
		||||
            bottom: 0,
 | 
			
		||||
            child: Opacity(
 | 
			
		||||
              opacity: opacity,
 | 
			
		||||
              child:
 | 
			
		||||
                  (!loading && mxContent != null && mxContent.host.isNotEmpty)
 | 
			
		||||
                      ? CachedNetworkImage(
 | 
			
		||||
                          imageUrl: src.toString(),
 | 
			
		||||
                          height: 300,
 | 
			
		||||
                          fit: BoxFit.cover,
 | 
			
		||||
                        )
 | 
			
		||||
                      : Icon(defaultIcon, size: 200),
 | 
			
		||||
              child: (!loading && mxContent.host.isNotEmpty)
 | 
			
		||||
                  ? CachedNetworkImage(
 | 
			
		||||
                      imageUrl: src.toString(),
 | 
			
		||||
                      height: 300,
 | 
			
		||||
                      fit: BoxFit.cover,
 | 
			
		||||
                    )
 | 
			
		||||
                  : Icon(defaultIcon, size: 200),
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
          if (onEdit != null)
 | 
			
		||||
@ -69,7 +71,7 @@ class ContentBanner extends StatelessWidget {
 | 
			
		||||
                mini: true,
 | 
			
		||||
                onPressed: onEdit,
 | 
			
		||||
                backgroundColor: Theme.of(context).backgroundColor,
 | 
			
		||||
                foregroundColor: Theme.of(context).textTheme.bodyText1.color,
 | 
			
		||||
                foregroundColor: Theme.of(context).textTheme.bodyText1?.color,
 | 
			
		||||
                child: const Icon(Icons.camera_alt_outlined),
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
@ -5,22 +7,22 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
import '../config/app_config.dart';
 | 
			
		||||
 | 
			
		||||
class DefaultAppBarSearchField extends StatefulWidget {
 | 
			
		||||
  final TextEditingController searchController;
 | 
			
		||||
  final void Function(String) onChanged;
 | 
			
		||||
  final void Function(String) onSubmit;
 | 
			
		||||
  final Widget suffix;
 | 
			
		||||
  final TextEditingController? searchController;
 | 
			
		||||
  final void Function(String)? onChanged;
 | 
			
		||||
  final void Function(String)? onSubmit;
 | 
			
		||||
  final Widget? suffix;
 | 
			
		||||
  final bool autofocus;
 | 
			
		||||
  final String prefixText;
 | 
			
		||||
  final String hintText;
 | 
			
		||||
  final String labelText;
 | 
			
		||||
  final EdgeInsets padding;
 | 
			
		||||
  final String? prefixText;
 | 
			
		||||
  final String? hintText;
 | 
			
		||||
  final String? labelText;
 | 
			
		||||
  final EdgeInsets? padding;
 | 
			
		||||
  final bool readOnly;
 | 
			
		||||
  final Widget prefixIcon;
 | 
			
		||||
  final Widget? prefixIcon;
 | 
			
		||||
  final bool unfocusOnClear;
 | 
			
		||||
  final bool autocorrect;
 | 
			
		||||
 | 
			
		||||
  const DefaultAppBarSearchField({
 | 
			
		||||
    Key key,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    this.searchController,
 | 
			
		||||
    this.onChanged,
 | 
			
		||||
    this.onSubmit,
 | 
			
		||||
@ -42,14 +44,14 @@ class DefaultAppBarSearchField extends StatefulWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class DefaultAppBarSearchFieldState extends State<DefaultAppBarSearchField> {
 | 
			
		||||
  TextEditingController _searchController;
 | 
			
		||||
  late final TextEditingController _searchController;
 | 
			
		||||
  bool _lastTextWasEmpty = false;
 | 
			
		||||
  final FocusNode _focusNode = FocusNode();
 | 
			
		||||
 | 
			
		||||
  void requestFocus() => _focusNode.requestFocus();
 | 
			
		||||
 | 
			
		||||
  void _updateSearchController() {
 | 
			
		||||
    final thisTextIsEmpty = _searchController.text?.isEmpty ?? false;
 | 
			
		||||
    final thisTextIsEmpty = _searchController.text.isEmpty;
 | 
			
		||||
    if (_lastTextWasEmpty != thisTextIsEmpty) {
 | 
			
		||||
      setState(() => _lastTextWasEmpty = thisTextIsEmpty);
 | 
			
		||||
    }
 | 
			
		||||
@ -61,7 +63,7 @@ class DefaultAppBarSearchFieldState extends State<DefaultAppBarSearchField> {
 | 
			
		||||
    _searchController = widget.searchController ?? TextEditingController();
 | 
			
		||||
    // we need to remove the listener in the dispose method, so we need a reference to the callback
 | 
			
		||||
    _searchController.addListener(_updateSearchController);
 | 
			
		||||
    _focusNode.addListener(() => setState(() => null));
 | 
			
		||||
    _focusNode.addListener(() => setState(() {}));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@ -108,9 +110,9 @@ class DefaultAppBarSearchFieldState extends State<DefaultAppBarSearchField> {
 | 
			
		||||
          suffixIcon: !widget.readOnly &&
 | 
			
		||||
                  (_focusNode.hasFocus ||
 | 
			
		||||
                      (widget.suffix == null &&
 | 
			
		||||
                          (_searchController.text?.isNotEmpty ?? false)))
 | 
			
		||||
                          (_searchController.text.isNotEmpty)))
 | 
			
		||||
              ? IconButton(
 | 
			
		||||
                  tooltip: L10n.of(context).clearText,
 | 
			
		||||
                  tooltip: L10n.of(context)!.clearText,
 | 
			
		||||
                  icon: const Icon(Icons.backspace_outlined),
 | 
			
		||||
                  onPressed: () {
 | 
			
		||||
                    _searchController.clear();
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'dart:io';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/foundation.dart';
 | 
			
		||||
@ -18,14 +20,18 @@ extension LocalNotificationsExtension on MatrixState {
 | 
			
		||||
    final roomId = eventUpdate.roomID;
 | 
			
		||||
    if (webHasFocus && activeRoomId == roomId) return;
 | 
			
		||||
    final room = client.getRoomById(roomId);
 | 
			
		||||
    if (room == null) {
 | 
			
		||||
      Logs().w('Can not display notification for unknown room $roomId');
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (room.notificationCount == 0) return;
 | 
			
		||||
    final event = Event.fromJson(eventUpdate.content, room);
 | 
			
		||||
    final title =
 | 
			
		||||
        room.getLocalizedDisplayname(MatrixLocals(L10n.of(widget.context)));
 | 
			
		||||
        room.getLocalizedDisplayname(MatrixLocals(L10n.of(widget.context)!));
 | 
			
		||||
    final body = event.getLocalizedBody(
 | 
			
		||||
      MatrixLocals(L10n.of(widget.context)),
 | 
			
		||||
      MatrixLocals(L10n.of(widget.context)!),
 | 
			
		||||
      withSenderNamePrefix:
 | 
			
		||||
          !room.isDirectChat || room.lastEvent.senderId == client.userID,
 | 
			
		||||
          !room.isDirectChat || room.lastEvent?.senderId == client.userID,
 | 
			
		||||
      plaintextBody: true,
 | 
			
		||||
      hideReply: true,
 | 
			
		||||
      hideEdit: true,
 | 
			
		||||
@ -51,7 +57,7 @@ extension LocalNotificationsExtension on MatrixState {
 | 
			
		||||
        width: 56,
 | 
			
		||||
        height: 56,
 | 
			
		||||
      );
 | 
			
		||||
      File appIconFile;
 | 
			
		||||
      File? appIconFile;
 | 
			
		||||
      if (appIconUrl != null) {
 | 
			
		||||
        final tempDirectory = await getApplicationSupportDirectory();
 | 
			
		||||
        final avatarDirectory =
 | 
			
		||||
@ -68,15 +74,15 @@ extension LocalNotificationsExtension on MatrixState {
 | 
			
		||||
        body: body,
 | 
			
		||||
        replacesId: linuxNotificationIds[roomId] ?? 0,
 | 
			
		||||
        appName: AppConfig.applicationName,
 | 
			
		||||
        appIcon: appIconFile.path,
 | 
			
		||||
        appIcon: appIconFile?.path ?? '',
 | 
			
		||||
        actions: [
 | 
			
		||||
          NotificationAction(
 | 
			
		||||
            DesktopNotificationActions.dismiss.name,
 | 
			
		||||
            L10n.of(widget.context).dismiss,
 | 
			
		||||
            L10n.of(widget.context)!.dismiss,
 | 
			
		||||
          ),
 | 
			
		||||
          NotificationAction(
 | 
			
		||||
            DesktopNotificationActions.seen.name,
 | 
			
		||||
            L10n.of(widget.context).markAsRead,
 | 
			
		||||
            L10n.of(widget.context)!.markAsRead,
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
        hints: [
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter_app_lock/flutter_app_lock.dart';
 | 
			
		||||
@ -12,7 +14,7 @@ import 'package:fluffychat/config/themes.dart';
 | 
			
		||||
import 'layouts/one_page_card.dart';
 | 
			
		||||
 | 
			
		||||
class LockScreen extends StatefulWidget {
 | 
			
		||||
  const LockScreen({Key key}) : super(key: key);
 | 
			
		||||
  const LockScreen({Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  _LockScreenState createState() => _LockScreenState();
 | 
			
		||||
@ -37,7 +39,7 @@ class _LockScreenState extends State<LockScreen> {
 | 
			
		||||
              automaticallyImplyLeading: false,
 | 
			
		||||
              elevation: 0,
 | 
			
		||||
              centerTitle: true,
 | 
			
		||||
              title: Text(L10n.of(context).pleaseEnterYourPin),
 | 
			
		||||
              title: Text(L10n.of(context)!.pleaseEnterYourPin),
 | 
			
		||||
              backgroundColor: Colors.transparent,
 | 
			
		||||
            ),
 | 
			
		||||
            extendBodyBehindAppBar: true,
 | 
			
		||||
@ -78,7 +80,7 @@ class _LockScreenState extends State<LockScreen> {
 | 
			
		||||
                              prefs.getString(SettingKeys.appLockKey))
 | 
			
		||||
                          : const FlutterSecureStorage()
 | 
			
		||||
                              .read(key: SettingKeys.appLockKey))) {
 | 
			
		||||
                    AppLock.of(context).didUnlock();
 | 
			
		||||
                    AppLock.of(context)!.didUnlock();
 | 
			
		||||
                  } else {
 | 
			
		||||
                    _textEditingController.clear();
 | 
			
		||||
                    setState(() => _wrongInput = true);
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,11 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:matrix/matrix.dart';
 | 
			
		||||
 | 
			
		||||
class LogViewer extends StatefulWidget {
 | 
			
		||||
  const LogViewer({Key key}) : super(key: key);
 | 
			
		||||
  const LogViewer({Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  _LogViewerState createState() => _LogViewerState();
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/cupertino.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
@ -8,11 +10,11 @@ import 'package:fluffychat/widgets/adaptive_flat_button.dart';
 | 
			
		||||
 | 
			
		||||
class PermissionSliderDialog extends StatefulWidget {
 | 
			
		||||
  const PermissionSliderDialog({
 | 
			
		||||
    Key key,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    this.initialPermission = 0,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  Future<int> show(BuildContext context) => PlatformInfos.isCupertinoStyle
 | 
			
		||||
  Future<int?> show(BuildContext context) => PlatformInfos.isCupertinoStyle
 | 
			
		||||
      ? showCupertinoDialog<int>(
 | 
			
		||||
          context: context,
 | 
			
		||||
          builder: (context) => this,
 | 
			
		||||
@ -30,7 +32,7 @@ class PermissionSliderDialog extends StatefulWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _PermissionSliderDialogState extends State<PermissionSliderDialog> {
 | 
			
		||||
  int _permission;
 | 
			
		||||
  late int _permission;
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    _permission = widget.initialPermission;
 | 
			
		||||
@ -40,7 +42,7 @@ class _PermissionSliderDialogState extends State<PermissionSliderDialog> {
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final title = Text(
 | 
			
		||||
      L10n.of(context).setPermissionsLevel,
 | 
			
		||||
      L10n.of(context)!.setPermissionsLevel,
 | 
			
		||||
      textAlign: TextAlign.center,
 | 
			
		||||
    );
 | 
			
		||||
    final content = Column(
 | 
			
		||||
@ -48,9 +50,9 @@ class _PermissionSliderDialogState extends State<PermissionSliderDialog> {
 | 
			
		||||
      children: [
 | 
			
		||||
        Text('Level: ' +
 | 
			
		||||
            (_permission == 100
 | 
			
		||||
                ? '$_permission (${L10n.of(context).admin})'
 | 
			
		||||
                ? '$_permission (${L10n.of(context)!.admin})'
 | 
			
		||||
                : _permission >= 50
 | 
			
		||||
                    ? '$_permission (${L10n.of(context).moderator})'
 | 
			
		||||
                    ? '$_permission (${L10n.of(context)!.moderator})'
 | 
			
		||||
                    : _permission.toString())),
 | 
			
		||||
        SizedBox(
 | 
			
		||||
          height: 56,
 | 
			
		||||
@ -65,12 +67,12 @@ class _PermissionSliderDialogState extends State<PermissionSliderDialog> {
 | 
			
		||||
    );
 | 
			
		||||
    final buttons = [
 | 
			
		||||
      AdaptiveFlatButton(
 | 
			
		||||
        label: L10n.of(context).cancel,
 | 
			
		||||
        label: L10n.of(context)!.cancel,
 | 
			
		||||
        onPressed: () =>
 | 
			
		||||
            Navigator.of(context, rootNavigator: false).pop<int>(null),
 | 
			
		||||
      ),
 | 
			
		||||
      AdaptiveFlatButton(
 | 
			
		||||
        label: L10n.of(context).confirm,
 | 
			
		||||
        label: L10n.of(context)!.confirm,
 | 
			
		||||
        onPressed: () =>
 | 
			
		||||
            Navigator.of(context, rootNavigator: false).pop<int>(_permission),
 | 
			
		||||
      ),
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'dart:math';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
@ -16,9 +18,9 @@ class ProfileBottomSheet extends StatelessWidget {
 | 
			
		||||
  final String userId;
 | 
			
		||||
  final BuildContext outerContext;
 | 
			
		||||
  const ProfileBottomSheet({
 | 
			
		||||
    @required this.userId,
 | 
			
		||||
    @required this.outerContext,
 | 
			
		||||
    Key key,
 | 
			
		||||
    required this.userId,
 | 
			
		||||
    required this.outerContext,
 | 
			
		||||
    Key? key,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  void _startDirectChat(BuildContext context) async {
 | 
			
		||||
@ -28,7 +30,7 @@ class ProfileBottomSheet extends StatelessWidget {
 | 
			
		||||
      future: () => client.startDirectChat(userId),
 | 
			
		||||
    );
 | 
			
		||||
    if (result.error == null) {
 | 
			
		||||
      VRouter.of(context).toSegments(['rooms', result.result]);
 | 
			
		||||
      VRouter.of(context).toSegments(['rooms', result.result!]);
 | 
			
		||||
      Navigator.of(context, rootNavigator: false).pop();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
@ -52,7 +54,7 @@ class ProfileBottomSheet extends StatelessWidget {
 | 
			
		||||
                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: FutureBuilder<Profile>(
 | 
			
		||||
@ -69,19 +71,20 @@ class ProfileBottomSheet extends StatelessWidget {
 | 
			
		||||
                                  alignment: Alignment.center,
 | 
			
		||||
                                  color: Theme.of(context).secondaryHeaderColor,
 | 
			
		||||
                                  child: snapshot.hasError
 | 
			
		||||
                                      ? Text(snapshot.error
 | 
			
		||||
                                      ? Text(snapshot.error!
 | 
			
		||||
                                          .toLocalizedString(context))
 | 
			
		||||
                                      : const CircularProgressIndicator
 | 
			
		||||
                                          .adaptive(strokeWidth: 2),
 | 
			
		||||
                                )
 | 
			
		||||
                              : ContentBanner(
 | 
			
		||||
                                  profile.avatarUrl,
 | 
			
		||||
                                  profile.avatarUrl!,
 | 
			
		||||
                                  defaultIcon: Icons.person_outline,
 | 
			
		||||
                                  client: Matrix.of(context).client,
 | 
			
		||||
                                ),
 | 
			
		||||
                        ),
 | 
			
		||||
                        ListTile(
 | 
			
		||||
                          title: Text(profile?.displayName ?? userId.localpart),
 | 
			
		||||
                          title: Text(
 | 
			
		||||
                              profile?.displayName ?? userId.localpart ?? ''),
 | 
			
		||||
                          subtitle: Text(userId),
 | 
			
		||||
                          trailing: const Icon(Icons.account_box_outlined),
 | 
			
		||||
                        ),
 | 
			
		||||
@ -90,7 +93,7 @@ class ProfileBottomSheet extends StatelessWidget {
 | 
			
		||||
                          padding: const EdgeInsets.all(12),
 | 
			
		||||
                          child: ElevatedButton.icon(
 | 
			
		||||
                            onPressed: () => _startDirectChat(context),
 | 
			
		||||
                            label: Text(L10n.of(context).newChat),
 | 
			
		||||
                            label: Text(L10n.of(context)!.newChat),
 | 
			
		||||
                            icon: const Icon(Icons.send_outlined),
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'dart:math';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
@ -16,12 +18,12 @@ import '../utils/localized_exception_extension.dart';
 | 
			
		||||
class PublicRoomBottomSheet extends StatelessWidget {
 | 
			
		||||
  final String roomAlias;
 | 
			
		||||
  final BuildContext outerContext;
 | 
			
		||||
  final PublicRoomsChunk chunk;
 | 
			
		||||
  final PublicRoomsChunk? chunk;
 | 
			
		||||
  const PublicRoomBottomSheet({
 | 
			
		||||
    @required this.roomAlias,
 | 
			
		||||
    @required this.outerContext,
 | 
			
		||||
    required this.roomAlias,
 | 
			
		||||
    required this.outerContext,
 | 
			
		||||
    this.chunk,
 | 
			
		||||
    Key key,
 | 
			
		||||
    Key? key,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  void _joinRoom(BuildContext context) async {
 | 
			
		||||
@ -31,11 +33,11 @@ class PublicRoomBottomSheet extends StatelessWidget {
 | 
			
		||||
      future: () => client.joinRoom(roomAlias),
 | 
			
		||||
    );
 | 
			
		||||
    if (result.error == null) {
 | 
			
		||||
      if (client.getRoomById(result.result) == null) {
 | 
			
		||||
      if (client.getRoomById(result.result!) == null) {
 | 
			
		||||
        await client.onSync.stream.firstWhere(
 | 
			
		||||
            (sync) => sync.rooms?.join?.containsKey(result.result) ?? false);
 | 
			
		||||
      }
 | 
			
		||||
      VRouter.of(context).toSegments(['rooms', result.result]);
 | 
			
		||||
      VRouter.of(context).toSegments(['rooms', result.result!]);
 | 
			
		||||
      Navigator.of(context, rootNavigator: false).pop();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
@ -46,6 +48,7 @@ class PublicRoomBottomSheet extends StatelessWidget {
 | 
			
		||||
      (r.aliases?.contains(roomAlias) ?? false);
 | 
			
		||||
 | 
			
		||||
  Future<PublicRoomsChunk> _search(BuildContext context) async {
 | 
			
		||||
    final chunk = this.chunk;
 | 
			
		||||
    if (chunk != null) return chunk;
 | 
			
		||||
    final query = await Matrix.of(context).client.queryPublicRooms(
 | 
			
		||||
          server: roomAlias.domain,
 | 
			
		||||
@ -53,16 +56,15 @@ class PublicRoomBottomSheet extends StatelessWidget {
 | 
			
		||||
            genericSearchTerm: roomAlias,
 | 
			
		||||
          ),
 | 
			
		||||
        );
 | 
			
		||||
    if (!query.chunk.any(_testRoom) ?? true) {
 | 
			
		||||
      throw (L10n.of(context).noRoomsFound);
 | 
			
		||||
    if (!query.chunk.any(_testRoom)) {
 | 
			
		||||
      throw (L10n.of(context)!.noRoomsFound);
 | 
			
		||||
    }
 | 
			
		||||
    return query.chunk.firstWhere(_testRoom);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final roomAlias =
 | 
			
		||||
        this.roomAlias ?? chunk.canonicalAlias ?? chunk.aliases?.first ?? '';
 | 
			
		||||
    final roomAlias = this.roomAlias;
 | 
			
		||||
    return Center(
 | 
			
		||||
      child: SizedBox(
 | 
			
		||||
        width: min(
 | 
			
		||||
@ -84,12 +86,12 @@ class PublicRoomBottomSheet extends StatelessWidget {
 | 
			
		||||
                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,
 | 
			
		||||
                ),
 | 
			
		||||
                actions: [
 | 
			
		||||
                  TextButton.icon(
 | 
			
		||||
                    onPressed: () => _joinRoom(context),
 | 
			
		||||
                    label: Text(L10n.of(context).joinRoom),
 | 
			
		||||
                    label: Text(L10n.of(context)!.joinRoom),
 | 
			
		||||
                    icon: const Icon(Icons.login_outlined),
 | 
			
		||||
                  ),
 | 
			
		||||
                ],
 | 
			
		||||
@ -108,26 +110,27 @@ class PublicRoomBottomSheet extends StatelessWidget {
 | 
			
		||||
                            color: Theme.of(context).secondaryHeaderColor,
 | 
			
		||||
                            child: snapshot.hasError
 | 
			
		||||
                                ? Text(
 | 
			
		||||
                                    snapshot.error.toLocalizedString(context))
 | 
			
		||||
                                    snapshot.error!.toLocalizedString(context))
 | 
			
		||||
                                : const CircularProgressIndicator.adaptive(
 | 
			
		||||
                                    strokeWidth: 2),
 | 
			
		||||
                          )
 | 
			
		||||
                        else
 | 
			
		||||
                          ContentBanner(
 | 
			
		||||
                            profile.avatarUrl,
 | 
			
		||||
                            profile.avatarUrl!,
 | 
			
		||||
                            height: 156,
 | 
			
		||||
                            defaultIcon: Icons.person_outline,
 | 
			
		||||
                            client: Matrix.of(context).client,
 | 
			
		||||
                          ),
 | 
			
		||||
                        ListTile(
 | 
			
		||||
                          title: Text(profile?.name ?? roomAlias.localpart),
 | 
			
		||||
                          title:
 | 
			
		||||
                              Text(profile?.name ?? roomAlias.localpart ?? ''),
 | 
			
		||||
                          subtitle: Text(
 | 
			
		||||
                              '${L10n.of(context).participant}: ${profile?.numJoinedMembers ?? 0}'),
 | 
			
		||||
                              '${L10n.of(context)!.participant}: ${profile?.numJoinedMembers ?? 0}'),
 | 
			
		||||
                          trailing: const Icon(Icons.account_box_outlined),
 | 
			
		||||
                        ),
 | 
			
		||||
                        if (profile?.topic != null && profile.topic.isNotEmpty)
 | 
			
		||||
                        if (profile?.topic?.isNotEmpty ?? false)
 | 
			
		||||
                          ListTile(
 | 
			
		||||
                            subtitle: Html(data: profile.topic),
 | 
			
		||||
                            subtitle: Html(data: profile!.topic!),
 | 
			
		||||
                          ),
 | 
			
		||||
                      ],
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
@ -1,13 +1,14 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:fluffychat/utils/sentry_controller.dart';
 | 
			
		||||
 | 
			
		||||
class SentrySwitchListTile extends StatefulWidget {
 | 
			
		||||
  final String label;
 | 
			
		||||
 | 
			
		||||
  const SentrySwitchListTile.adaptive({Key key, this.label}) : super(key: key);
 | 
			
		||||
  const SentrySwitchListTile.adaptive({Key? key, required this.label})
 | 
			
		||||
      : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  _SentrySwitchListTileState createState() => _SentrySwitchListTileState();
 | 
			
		||||
@ -23,11 +24,11 @@ class _SentrySwitchListTileState extends State<SentrySwitchListTile> {
 | 
			
		||||
        builder: (context, snapshot) {
 | 
			
		||||
          _enabled = snapshot.data ?? false;
 | 
			
		||||
          return SwitchListTile.adaptive(
 | 
			
		||||
            title: Text(widget.label ?? L10n.of(context).sendBugReports),
 | 
			
		||||
            title: Text(widget.label),
 | 
			
		||||
            value: _enabled,
 | 
			
		||||
            onChanged: (b) =>
 | 
			
		||||
                SentryController.toggleSentryAction(context, b).then(
 | 
			
		||||
              (_) => setState(() => null),
 | 
			
		||||
              (_) => setState(() {}),
 | 
			
		||||
            ),
 | 
			
		||||
          );
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:matrix/matrix.dart';
 | 
			
		||||
@ -9,8 +11,8 @@ class UnreadBadgeBackButton extends StatelessWidget {
 | 
			
		||||
  final String roomId;
 | 
			
		||||
 | 
			
		||||
  const UnreadBadgeBackButton({
 | 
			
		||||
    Key key,
 | 
			
		||||
    @required this.roomId,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.roomId,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user