From c10b38b27b82846fb4cc1e386b5f7560f5788f71 Mon Sep 17 00:00:00 2001 From: Krille Fear Date: Fri, 28 Jan 2022 18:21:20 +0100 Subject: [PATCH] refactor: Make widgets null safe --- lib/pages/search/search.dart | 2 +- lib/widgets/content_banner.dart | 28 ++++++------- lib/widgets/default_app_bar_search_field.dart | 32 ++++++++------- .../local_notifications_extension.dart | 20 ++++++---- lib/widgets/lock_screen.dart | 8 ++-- lib/widgets/log_view.dart | 4 +- lib/widgets/permission_slider_dialog.dart | 18 +++++---- lib/widgets/profile_bottom_sheet.dart | 21 +++++----- lib/widgets/public_room_bottom_sheet.dart | 39 ++++++++++--------- lib/widgets/sentry_switch_list_tile.dart | 11 +++--- lib/widgets/unread_badge_back_button.dart | 6 ++- 11 files changed, 107 insertions(+), 82 deletions(-) diff --git a/lib/pages/search/search.dart b/lib/pages/search/search.dart index 2e5571fc..88edb37f 100644 --- a/lib/pages/search/search.dart +++ b/lib/pages/search/search.dart @@ -45,7 +45,7 @@ class SearchController extends State { showModalBottomSheet( context: context, builder: (c) => PublicRoomBottomSheet( - roomAlias: room.canonicalAlias, + roomAlias: room.canonicalAlias ?? room.roomId, outerContext: context, chunk: room, ), diff --git a/lib/widgets/content_banner.dart b/lib/widgets/content_banner.dart index 58bb326d..34bf320d 100644 --- a/lib/widgets/content_banner.dart +++ b/lib/widgets/content_banner.dart @@ -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), ), ), diff --git a/lib/widgets/default_app_bar_search_field.dart b/lib/widgets/default_app_bar_search_field.dart index 457a9b8b..4ba287b5 100644 --- a/lib/widgets/default_app_bar_search_field.dart +++ b/lib/widgets/default_app_bar_search_field.dart @@ -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 { - 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 { _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 { 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(); diff --git a/lib/widgets/local_notifications_extension.dart b/lib/widgets/local_notifications_extension.dart index 41ef927c..86d850cb 100644 --- a/lib/widgets/local_notifications_extension.dart +++ b/lib/widgets/local_notifications_extension.dart @@ -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: [ diff --git a/lib/widgets/lock_screen.dart b/lib/widgets/lock_screen.dart index 429d8a2c..3a20cec1 100644 --- a/lib/widgets/lock_screen.dart +++ b/lib/widgets/lock_screen.dart @@ -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 { 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 { prefs.getString(SettingKeys.appLockKey)) : const FlutterSecureStorage() .read(key: SettingKeys.appLockKey))) { - AppLock.of(context).didUnlock(); + AppLock.of(context)!.didUnlock(); } else { _textEditingController.clear(); setState(() => _wrongInput = true); diff --git a/lib/widgets/log_view.dart b/lib/widgets/log_view.dart index c20f02ec..d2f8e7ae 100644 --- a/lib/widgets/log_view.dart +++ b/lib/widgets/log_view.dart @@ -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(); diff --git a/lib/widgets/permission_slider_dialog.dart b/lib/widgets/permission_slider_dialog.dart index b4b0f52f..eb7d5e09 100644 --- a/lib/widgets/permission_slider_dialog.dart +++ b/lib/widgets/permission_slider_dialog.dart @@ -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 show(BuildContext context) => PlatformInfos.isCupertinoStyle + Future show(BuildContext context) => PlatformInfos.isCupertinoStyle ? showCupertinoDialog( context: context, builder: (context) => this, @@ -30,7 +32,7 @@ class PermissionSliderDialog extends StatefulWidget { } class _PermissionSliderDialogState extends State { - int _permission; + late int _permission; @override void initState() { _permission = widget.initialPermission; @@ -40,7 +42,7 @@ class _PermissionSliderDialogState extends State { @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 { 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 { ); final buttons = [ AdaptiveFlatButton( - label: L10n.of(context).cancel, + label: L10n.of(context)!.cancel, onPressed: () => Navigator.of(context, rootNavigator: false).pop(null), ), AdaptiveFlatButton( - label: L10n.of(context).confirm, + label: L10n.of(context)!.confirm, onPressed: () => Navigator.of(context, rootNavigator: false).pop(_permission), ), diff --git a/lib/widgets/profile_bottom_sheet.dart b/lib/widgets/profile_bottom_sheet.dart index 8b581a4b..86366ac4 100644 --- a/lib/widgets/profile_bottom_sheet.dart +++ b/lib/widgets/profile_bottom_sheet.dart @@ -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( @@ -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), ), ), diff --git a/lib/widgets/public_room_bottom_sheet.dart b/lib/widgets/public_room_bottom_sheet.dart index 27176b35..a21c3072 100644 --- a/lib/widgets/public_room_bottom_sheet.dart +++ b/lib/widgets/public_room_bottom_sheet.dart @@ -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 _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!), ), ], ); diff --git a/lib/widgets/sentry_switch_list_tile.dart b/lib/widgets/sentry_switch_list_tile.dart index ac1c9451..f5b378b5 100644 --- a/lib/widgets/sentry_switch_list_tile.dart +++ b/lib/widgets/sentry_switch_list_tile.dart @@ -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 { 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(() {}), ), ); }); diff --git a/lib/widgets/unread_badge_back_button.dart b/lib/widgets/unread_badge_back_button.dart index 5eb363ab..2af40308 100644 --- a/lib/widgets/unread_badge_back_button.dart +++ b/lib/widgets/unread_badge_back_button.dart @@ -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