refactor: Make widgets null safe

This commit is contained in:
Krille Fear 2022-01-28 18:21:20 +01:00
parent 227869d84d
commit c10b38b27b
11 changed files with 107 additions and 82 deletions

View File

@ -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,
),

View File

@ -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,8 +54,7 @@ class ContentBanner extends StatelessWidget {
bottom: 0,
child: Opacity(
opacity: opacity,
child:
(!loading && mxContent != null && mxContent.host.isNotEmpty)
child: (!loading && mxContent.host.isNotEmpty)
? CachedNetworkImage(
imageUrl: src.toString(),
height: 300,
@ -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),
),
),

View File

@ -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();

View File

@ -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: [

View File

@ -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);

View File

@ -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();

View File

@ -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),
),

View File

@ -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),
),
),

View File

@ -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!),
),
],
);

View File

@ -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(() {}),
),
);
});

View File

@ -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