diff --git a/lib/comment_tree.dart b/lib/comment_tree.dart index 85814ed..b20c8f1 100644 --- a/lib/comment_tree.dart +++ b/lib/comment_tree.dart @@ -36,8 +36,6 @@ extension on CommentSortType { return (b, a) => a.comment.counts.score.compareTo(b.comment.counts.score); } - - throw Exception('unreachable'); } } @@ -45,9 +43,7 @@ class CommentTree { CommentView comment; List children; - CommentTree(this.comment, [this.children]) { - children ??= []; - } + CommentTree(this.comment, [this.children = const []]); /// takes raw linear comments and turns them into a CommentTree static List fromList(List comments) { diff --git a/lib/gen/assets.gen.dart b/lib/gen/assets.gen.dart index ef8e3bd..c0528b2 100644 --- a/lib/gen/assets.gen.dart +++ b/lib/gen/assets.gen.dart @@ -18,20 +18,20 @@ class AssetGenImage extends AssetImage { final String _assetName; Image image({ - Key key, - ImageFrameBuilder frameBuilder, - ImageLoadingBuilder loadingBuilder, - ImageErrorWidgetBuilder errorBuilder, - String semanticLabel, + Key? key, + ImageFrameBuilder? frameBuilder, + ImageLoadingBuilder? loadingBuilder, + ImageErrorWidgetBuilder? errorBuilder, + String? semanticLabel, bool excludeFromSemantics = false, - double width, - double height, - Color color, - BlendMode colorBlendMode, - BoxFit fit, + double? width, + double? height, + Color? color, + BlendMode? colorBlendMode, + BoxFit? fit, AlignmentGeometry alignment = Alignment.center, ImageRepeat repeat = ImageRepeat.noRepeat, - Rect centerSlice, + Rect? centerSlice, bool matchTextDirection = false, bool gaplessPlayback = false, bool isAntiAlias = false, diff --git a/lib/hooks/debounce.dart b/lib/hooks/debounce.dart index 3349df8..546665e 100644 --- a/lib/hooks/debounce.dart +++ b/lib/hooks/debounce.dart @@ -10,8 +10,8 @@ class Debounce { final VoidCallback callback; const Debounce({ - @required this.loading, - @required this.callback, + required this.loading, + required this.callback, }); void call() => callback(); @@ -24,7 +24,7 @@ Debounce useDebounce( Duration delayDuration = const Duration(seconds: 1), ]) { final loading = useState(false); - final timerHandle = useRef(null); + final timerHandle = useRef(null); cancel() { timerHandle.current?.cancel(); diff --git a/lib/hooks/delayed_loading.dart b/lib/hooks/delayed_loading.dart index 4841bca..06332e4 100644 --- a/lib/hooks/delayed_loading.dart +++ b/lib/hooks/delayed_loading.dart @@ -12,10 +12,10 @@ class DelayedLoading { final VoidCallback cancel; const DelayedLoading({ - @required this.pending, - @required this.loading, - @required this.start, - @required this.cancel, + required this.pending, + required this.loading, + required this.start, + required this.cancel, }); } @@ -26,7 +26,7 @@ DelayedLoading useDelayedLoading( [Duration delayDuration = const Duration(milliseconds: 500)]) { final loading = useState(false); final pending = useState(false); - final timerHandle = useRef(null); + final timerHandle = useRef(null); return DelayedLoading( loading: loading.value, diff --git a/lib/hooks/infinite_scroll.dart b/lib/hooks/infinite_scroll.dart index 0537f4c..4e03cb8 100644 --- a/lib/hooks/infinite_scroll.dart +++ b/lib/hooks/infinite_scroll.dart @@ -2,10 +2,5 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import '../widgets/infinite_scroll.dart'; -InfiniteScrollController useInfiniteScrollController() { - final controller = useMemoized(() => InfiniteScrollController()); - - useEffect(() => controller.dispose, []); - - return controller; -} +InfiniteScrollController useInfiniteScrollController() => + useMemoized(() => InfiniteScrollController()); diff --git a/lib/hooks/logged_in_action.dart b/lib/hooks/logged_in_action.dart index bad1304..ca2c125 100644 --- a/lib/hooks/logged_in_action.dart +++ b/lib/hooks/logged_in_action.dart @@ -14,14 +14,13 @@ import 'stores.dart'; VoidCallback Function( void Function(Jwt token) action, [ - String message, -]) useLoggedInAction(String instanceHost, {bool any = false}) { + String? message, +]) useAnyLoggedInAction() { final context = useContext(); final store = useAccountsStore(); return (action, [message]) { - if (any && store.hasNoAccount || - !any && store.isAnonymousFor(instanceHost)) { + if (store.hasNoAccount) { return () { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text(message ?? 'you have to be logged in to do that'), @@ -31,7 +30,29 @@ VoidCallback Function( )); }; } - final token = store.defaultTokenFor(instanceHost); + return () => action(store.defaultToken!); + }; +} + +VoidCallback Function( + void Function(Jwt token) action, [ + String? message, +]) useLoggedInAction(String instanceHost) { + final context = useContext(); + final store = useAccountsStore(); + + return (action, [message]) { + if (store.isAnonymousFor(instanceHost)) { + return () { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(message ?? 'you have to be logged in to do that'), + action: SnackBarAction( + label: 'log in', + onPressed: () => goTo(context, (_) => AccountsConfigPage())), + )); + }; + } + final token = store.defaultTokenFor(instanceHost)!; return () => action(token); }; } diff --git a/lib/hooks/memo_future.dart b/lib/hooks/memo_future.dart index a70344e..d547ce6 100644 --- a/lib/hooks/memo_future.dart +++ b/lib/hooks/memo_future.dart @@ -3,7 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; /// creates an [AsyncSnapshot] from the Future returned from the valueBuilder. /// [keys] can be used to rebuild the Future -AsyncSnapshot useMemoFuture(Future Function() valueBuilder, - [List keys = const []]) => +AsyncSnapshot useMemoFuture(Future Function() valueBuilder, + [List keys = const []]) => useFuture(useMemoized>(valueBuilder, keys), preserveState: false, initialData: null); diff --git a/lib/hooks/refreshable.dart b/lib/hooks/refreshable.dart index 2378370..c126d8f 100644 --- a/lib/hooks/refreshable.dart +++ b/lib/hooks/refreshable.dart @@ -5,9 +5,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'memo_future.dart'; class Refreshable { - const Refreshable({@required this.snapshot, @required this.refresh}) - : assert(snapshot != null), - assert(refresh != null); + const Refreshable({required this.snapshot, required this.refresh}); final AsyncSnapshot snapshot; final AsyncCallback refresh; @@ -20,9 +18,9 @@ class Refreshable { /// /// `keys` will re-run the initial fetching thus yielding a /// loading state in the AsyncSnapshot -Refreshable useRefreshable(AsyncValueGetter fetcher, - [List keys = const []]) { - final newData = useState(null); +Refreshable useRefreshable(AsyncValueGetter fetcher, + [List keys = const []]) { + final newData = useState(null); final snapshot = useMemoFuture(() async { newData.value = null; return fetcher(); diff --git a/lib/l10n/l10n.dart b/lib/l10n/l10n.dart index 197f0f6..3484ed9 100644 --- a/lib/l10n/l10n.dart +++ b/lib/l10n/l10n.dart @@ -6,7 +6,7 @@ export 'l10n_api.dart'; export 'l10n_from_string.dart'; abstract class LocaleSerde { - static Locale fromJson(String json) { + static Locale? fromJson(String? json) { if (json == null) return null; final lang = json.split('-'); diff --git a/lib/l10n/l10n_api.dart b/lib/l10n/l10n_api.dart index 108c812..97d3116 100644 --- a/lib/l10n/l10n_api.dart +++ b/lib/l10n/l10n_api.dart @@ -6,25 +6,25 @@ extension SortTypeL10n on SortType { String tr(BuildContext context) { switch (this) { case SortType.hot: - return L10n.of(context).hot; + return L10n.of(context)!.hot; case SortType.new_: - return L10n.of(context).new_; + return L10n.of(context)!.new_; case SortType.topYear: - return L10n.of(context).top_year; + return L10n.of(context)!.top_year; case SortType.topMonth: - return L10n.of(context).top_month; + return L10n.of(context)!.top_month; case SortType.topWeek: - return L10n.of(context).top_week; + return L10n.of(context)!.top_week; case SortType.topDay: - return L10n.of(context).top_day; + return L10n.of(context)!.top_day; case SortType.topAll: - return L10n.of(context).top_all; + return L10n.of(context)!.top_all; case SortType.newComments: - return L10n.of(context).new_comments; + return L10n.of(context)!.new_comments; case SortType.active: - return L10n.of(context).active; + return L10n.of(context)!.active; case SortType.mostComments: - return L10n.of(context).most_comments; + return L10n.of(context)!.most_comments; default: throw Exception('unreachable'); } @@ -35,13 +35,13 @@ extension PostListingTypeL10n on PostListingType { String tr(BuildContext context) { switch (this) { case PostListingType.all: - return L10n.of(context).all; + return L10n.of(context)!.all; case PostListingType.community: - return L10n.of(context).community; + return L10n.of(context)!.community; case PostListingType.local: - return L10n.of(context).local; + return L10n.of(context)!.local; case PostListingType.subscribed: - return L10n.of(context).subscribed; + return L10n.of(context)!.subscribed; default: throw Exception('unreachable'); } @@ -52,17 +52,17 @@ extension SearchTypeL10n on SearchType { String tr(BuildContext context) { switch (this) { case SearchType.all: - return L10n.of(context).all; + return L10n.of(context)!.all; case SearchType.comments: - return L10n.of(context).comments; + return L10n.of(context)!.comments; case SearchType.communities: - return L10n.of(context).communities; + return L10n.of(context)!.communities; case SearchType.posts: - return L10n.of(context).posts; + return L10n.of(context)!.posts; case SearchType.url: - return L10n.of(context).url; + return L10n.of(context)!.url; case SearchType.users: - return L10n.of(context).users; + return L10n.of(context)!.users; default: throw Exception('unreachable'); } diff --git a/lib/l10n/l10n_from_string.dart b/lib/l10n/l10n_from_string.dart index 5f873d1..002a706 100644 --- a/lib/l10n/l10n_from_string.dart +++ b/lib/l10n/l10n_from_string.dart @@ -147,255 +147,255 @@ extension L10nFromString on String { String tr(BuildContext context) { switch (this) { case L10nStrings.settings: - return L10n.of(context).settings; + return L10n.of(context)!.settings; case L10nStrings.password: - return L10n.of(context).password; + return L10n.of(context)!.password; case L10nStrings.email_or_username: - return L10n.of(context).email_or_username; + return L10n.of(context)!.email_or_username; case L10nStrings.posts: - return L10n.of(context).posts; + return L10n.of(context)!.posts; case L10nStrings.comments: - return L10n.of(context).comments; + return L10n.of(context)!.comments; case L10nStrings.modlog: - return L10n.of(context).modlog; + return L10n.of(context)!.modlog; case L10nStrings.community: - return L10n.of(context).community; + return L10n.of(context)!.community; case L10nStrings.url: - return L10n.of(context).url; + return L10n.of(context)!.url; case L10nStrings.title: - return L10n.of(context).title; + return L10n.of(context)!.title; case L10nStrings.body: - return L10n.of(context).body; + return L10n.of(context)!.body; case L10nStrings.nsfw: - return L10n.of(context).nsfw; + return L10n.of(context)!.nsfw; case L10nStrings.post: - return L10n.of(context).post; + return L10n.of(context)!.post; case L10nStrings.save: - return L10n.of(context).save; + return L10n.of(context)!.save; case L10nStrings.subscribed: - return L10n.of(context).subscribed; + return L10n.of(context)!.subscribed; case L10nStrings.local: - return L10n.of(context).local; + return L10n.of(context)!.local; case L10nStrings.all: - return L10n.of(context).all; + return L10n.of(context)!.all; case L10nStrings.replies: - return L10n.of(context).replies; + return L10n.of(context)!.replies; case L10nStrings.mentions: - return L10n.of(context).mentions; + return L10n.of(context)!.mentions; case L10nStrings.from: - return L10n.of(context).from; + return L10n.of(context)!.from; case L10nStrings.to: - return L10n.of(context).to; + return L10n.of(context)!.to; case L10nStrings.deleted_by_creator: - return L10n.of(context).deleted_by_creator; + return L10n.of(context)!.deleted_by_creator; case L10nStrings.more: - return L10n.of(context).more; + return L10n.of(context)!.more; case L10nStrings.mark_as_read: - return L10n.of(context).mark_as_read; + return L10n.of(context)!.mark_as_read; case L10nStrings.mark_as_unread: - return L10n.of(context).mark_as_unread; + return L10n.of(context)!.mark_as_unread; case L10nStrings.reply: - return L10n.of(context).reply; + return L10n.of(context)!.reply; case L10nStrings.edit: - return L10n.of(context).edit; + return L10n.of(context)!.edit; case L10nStrings.delete: - return L10n.of(context).delete; + return L10n.of(context)!.delete; case L10nStrings.restore: - return L10n.of(context).restore; + return L10n.of(context)!.restore; case L10nStrings.yes: - return L10n.of(context).yes; + return L10n.of(context)!.yes; case L10nStrings.no: - return L10n.of(context).no; + return L10n.of(context)!.no; case L10nStrings.avatar: - return L10n.of(context).avatar; + return L10n.of(context)!.avatar; case L10nStrings.banner: - return L10n.of(context).banner; + return L10n.of(context)!.banner; case L10nStrings.display_name: - return L10n.of(context).display_name; + return L10n.of(context)!.display_name; case L10nStrings.bio: - return L10n.of(context).bio; + return L10n.of(context)!.bio; case L10nStrings.email: - return L10n.of(context).email; + return L10n.of(context)!.email; case L10nStrings.matrix_user: - return L10n.of(context).matrix_user; + return L10n.of(context)!.matrix_user; case L10nStrings.sort_type: - return L10n.of(context).sort_type; + return L10n.of(context)!.sort_type; case L10nStrings.type: - return L10n.of(context).type; + return L10n.of(context)!.type; case L10nStrings.show_nsfw: - return L10n.of(context).show_nsfw; + return L10n.of(context)!.show_nsfw; case L10nStrings.send_notifications_to_email: - return L10n.of(context).send_notifications_to_email; + return L10n.of(context)!.send_notifications_to_email; case L10nStrings.delete_account: - return L10n.of(context).delete_account; + return L10n.of(context)!.delete_account; case L10nStrings.saved: - return L10n.of(context).saved; + return L10n.of(context)!.saved; case L10nStrings.communities: - return L10n.of(context).communities; + return L10n.of(context)!.communities; case L10nStrings.users: - return L10n.of(context).users; + return L10n.of(context)!.users; case L10nStrings.theme: - return L10n.of(context).theme; + return L10n.of(context)!.theme; case L10nStrings.language: - return L10n.of(context).language; + return L10n.of(context)!.language; case L10nStrings.hot: - return L10n.of(context).hot; + return L10n.of(context)!.hot; case L10nStrings.new_: - return L10n.of(context).new_; + return L10n.of(context)!.new_; case L10nStrings.old: - return L10n.of(context).old; + return L10n.of(context)!.old; case L10nStrings.top: - return L10n.of(context).top; + return L10n.of(context)!.top; case L10nStrings.chat: - return L10n.of(context).chat; + return L10n.of(context)!.chat; case L10nStrings.admin: - return L10n.of(context).admin; + return L10n.of(context)!.admin; case L10nStrings.by: - return L10n.of(context).by; + return L10n.of(context)!.by; case L10nStrings.not_a_mod_or_admin: - return L10n.of(context).not_a_mod_or_admin; + return L10n.of(context)!.not_a_mod_or_admin; case L10nStrings.not_an_admin: - return L10n.of(context).not_an_admin; + return L10n.of(context)!.not_an_admin; case L10nStrings.couldnt_find_post: - return L10n.of(context).couldnt_find_post; + return L10n.of(context)!.couldnt_find_post; case L10nStrings.not_logged_in: - return L10n.of(context).not_logged_in; + return L10n.of(context)!.not_logged_in; case L10nStrings.site_ban: - return L10n.of(context).site_ban; + return L10n.of(context)!.site_ban; case L10nStrings.community_ban: - return L10n.of(context).community_ban; + return L10n.of(context)!.community_ban; case L10nStrings.downvotes_disabled: - return L10n.of(context).downvotes_disabled; + return L10n.of(context)!.downvotes_disabled; case L10nStrings.invalid_url: - return L10n.of(context).invalid_url; + return L10n.of(context)!.invalid_url; case L10nStrings.locked: - return L10n.of(context).locked; + return L10n.of(context)!.locked; case L10nStrings.couldnt_create_comment: - return L10n.of(context).couldnt_create_comment; + return L10n.of(context)!.couldnt_create_comment; case L10nStrings.couldnt_like_comment: - return L10n.of(context).couldnt_like_comment; + return L10n.of(context)!.couldnt_like_comment; case L10nStrings.couldnt_update_comment: - return L10n.of(context).couldnt_update_comment; + return L10n.of(context)!.couldnt_update_comment; case L10nStrings.no_comment_edit_allowed: - return L10n.of(context).no_comment_edit_allowed; + return L10n.of(context)!.no_comment_edit_allowed; case L10nStrings.couldnt_save_comment: - return L10n.of(context).couldnt_save_comment; + return L10n.of(context)!.couldnt_save_comment; case L10nStrings.couldnt_get_comments: - return L10n.of(context).couldnt_get_comments; + return L10n.of(context)!.couldnt_get_comments; case L10nStrings.report_reason_required: - return L10n.of(context).report_reason_required; + return L10n.of(context)!.report_reason_required; case L10nStrings.report_too_long: - return L10n.of(context).report_too_long; + return L10n.of(context)!.report_too_long; case L10nStrings.couldnt_create_report: - return L10n.of(context).couldnt_create_report; + return L10n.of(context)!.couldnt_create_report; case L10nStrings.couldnt_resolve_report: - return L10n.of(context).couldnt_resolve_report; + return L10n.of(context)!.couldnt_resolve_report; case L10nStrings.invalid_post_title: - return L10n.of(context).invalid_post_title; + return L10n.of(context)!.invalid_post_title; case L10nStrings.couldnt_create_post: - return L10n.of(context).couldnt_create_post; + return L10n.of(context)!.couldnt_create_post; case L10nStrings.couldnt_like_post: - return L10n.of(context).couldnt_like_post; + return L10n.of(context)!.couldnt_like_post; case L10nStrings.couldnt_find_community: - return L10n.of(context).couldnt_find_community; + return L10n.of(context)!.couldnt_find_community; case L10nStrings.couldnt_get_posts: - return L10n.of(context).couldnt_get_posts; + return L10n.of(context)!.couldnt_get_posts; case L10nStrings.no_post_edit_allowed: - return L10n.of(context).no_post_edit_allowed; + return L10n.of(context)!.no_post_edit_allowed; case L10nStrings.couldnt_save_post: - return L10n.of(context).couldnt_save_post; + return L10n.of(context)!.couldnt_save_post; case L10nStrings.site_already_exists: - return L10n.of(context).site_already_exists; + return L10n.of(context)!.site_already_exists; case L10nStrings.couldnt_update_site: - return L10n.of(context).couldnt_update_site; + return L10n.of(context)!.couldnt_update_site; case L10nStrings.invalid_community_name: - return L10n.of(context).invalid_community_name; + return L10n.of(context)!.invalid_community_name; case L10nStrings.community_already_exists: - return L10n.of(context).community_already_exists; + return L10n.of(context)!.community_already_exists; case L10nStrings.community_moderator_already_exists: - return L10n.of(context).community_moderator_already_exists; + return L10n.of(context)!.community_moderator_already_exists; case L10nStrings.community_follower_already_exists: - return L10n.of(context).community_follower_already_exists; + return L10n.of(context)!.community_follower_already_exists; case L10nStrings.not_a_moderator: - return L10n.of(context).not_a_moderator; + return L10n.of(context)!.not_a_moderator; case L10nStrings.couldnt_update_community: - return L10n.of(context).couldnt_update_community; + return L10n.of(context)!.couldnt_update_community; case L10nStrings.no_community_edit_allowed: - return L10n.of(context).no_community_edit_allowed; + return L10n.of(context)!.no_community_edit_allowed; case L10nStrings.system_err_login: - return L10n.of(context).system_err_login; + return L10n.of(context)!.system_err_login; case L10nStrings.community_user_already_banned: - return L10n.of(context).community_user_already_banned; + return L10n.of(context)!.community_user_already_banned; case L10nStrings.couldnt_find_that_username_or_email: - return L10n.of(context).couldnt_find_that_username_or_email; + return L10n.of(context)!.couldnt_find_that_username_or_email; case L10nStrings.password_incorrect: - return L10n.of(context).password_incorrect; + return L10n.of(context)!.password_incorrect; case L10nStrings.registration_closed: - return L10n.of(context).registration_closed; + return L10n.of(context)!.registration_closed; case L10nStrings.invalid_password: - return L10n.of(context).invalid_password; + return L10n.of(context)!.invalid_password; case L10nStrings.passwords_dont_match: - return L10n.of(context).passwords_dont_match; + return L10n.of(context)!.passwords_dont_match; case L10nStrings.captcha_incorrect: - return L10n.of(context).captcha_incorrect; + return L10n.of(context)!.captcha_incorrect; case L10nStrings.invalid_username: - return L10n.of(context).invalid_username; + return L10n.of(context)!.invalid_username; case L10nStrings.bio_length_overflow: - return L10n.of(context).bio_length_overflow; + return L10n.of(context)!.bio_length_overflow; case L10nStrings.couldnt_update_user: - return L10n.of(context).couldnt_update_user; + return L10n.of(context)!.couldnt_update_user; case L10nStrings.couldnt_update_private_message: - return L10n.of(context).couldnt_update_private_message; + return L10n.of(context)!.couldnt_update_private_message; case L10nStrings.couldnt_update_post: - return L10n.of(context).couldnt_update_post; + return L10n.of(context)!.couldnt_update_post; case L10nStrings.couldnt_create_private_message: - return L10n.of(context).couldnt_create_private_message; + return L10n.of(context)!.couldnt_create_private_message; case L10nStrings.no_private_message_edit_allowed: - return L10n.of(context).no_private_message_edit_allowed; + return L10n.of(context)!.no_private_message_edit_allowed; case L10nStrings.post_title_too_long: - return L10n.of(context).post_title_too_long; + return L10n.of(context)!.post_title_too_long; case L10nStrings.email_already_exists: - return L10n.of(context).email_already_exists; + return L10n.of(context)!.email_already_exists; case L10nStrings.user_already_exists: - return L10n.of(context).user_already_exists; + return L10n.of(context)!.user_already_exists; case L10nStrings.unsubscribe: - return L10n.of(context).unsubscribe; + return L10n.of(context)!.unsubscribe; case L10nStrings.subscribe: - return L10n.of(context).subscribe; + return L10n.of(context)!.subscribe; case L10nStrings.messages: - return L10n.of(context).messages; + return L10n.of(context)!.messages; case L10nStrings.banned_users: - return L10n.of(context).banned_users; + return L10n.of(context)!.banned_users; case L10nStrings.delete_account_confirm: - return L10n.of(context).delete_account_confirm; + return L10n.of(context)!.delete_account_confirm; case L10nStrings.new_password: - return L10n.of(context).new_password; + return L10n.of(context)!.new_password; case L10nStrings.verify_password: - return L10n.of(context).verify_password; + return L10n.of(context)!.verify_password; case L10nStrings.old_password: - return L10n.of(context).old_password; + return L10n.of(context)!.old_password; case L10nStrings.show_avatars: - return L10n.of(context).show_avatars; + return L10n.of(context)!.show_avatars; case L10nStrings.search: - return L10n.of(context).search; + return L10n.of(context)!.search; case L10nStrings.send_message: - return L10n.of(context).send_message; + return L10n.of(context)!.send_message; case L10nStrings.top_day: - return L10n.of(context).top_day; + return L10n.of(context)!.top_day; case L10nStrings.top_week: - return L10n.of(context).top_week; + return L10n.of(context)!.top_week; case L10nStrings.top_month: - return L10n.of(context).top_month; + return L10n.of(context)!.top_month; case L10nStrings.top_year: - return L10n.of(context).top_year; + return L10n.of(context)!.top_year; case L10nStrings.top_all: - return L10n.of(context).top_all; + return L10n.of(context)!.top_all; case L10nStrings.most_comments: - return L10n.of(context).most_comments; + return L10n.of(context)!.most_comments; case L10nStrings.new_comments: - return L10n.of(context).new_comments; + return L10n.of(context)!.new_comments; case L10nStrings.active: - return L10n.of(context).active; + return L10n.of(context)!.active; default: return this; diff --git a/lib/pages/add_account.dart b/lib/pages/add_account.dart index 68e6dbe..0f35693 100644 --- a/lib/pages/add_account.dart +++ b/lib/pages/add_account.dart @@ -16,8 +16,7 @@ import 'add_instance.dart'; class AddAccountPage extends HookWidget { final String instanceHost; - const AddAccountPage({@required this.instanceHost}) - : assert(instanceHost != null); + const AddAccountPage({required this.instanceHost}); @override Widget build(BuildContext context) { @@ -29,12 +28,12 @@ class AddAccountPage extends HookWidget { final loading = useDelayedLoading(); final selectedInstance = useState(instanceHost); - final icon = useState(null); + final icon = useState(null); useEffect(() { LemmyApiV3(selectedInstance.value) .run(const GetSite()) - .then((site) => icon.value = site.siteView.site.icon); + .then((site) => icon.value = site.siteView?.site.icon); return null; }, [selectedInstance.value]); @@ -69,9 +68,9 @@ class AddAccountPage extends HookWidget { SizedBox( height: 150, child: FullscreenableImage( - url: icon.value, + url: icon.value!, child: CachedNetworkImage( - imageUrl: icon.value, + imageUrl: icon.value!, errorWidget: (_, __, ___) => const SizedBox.shrink(), ), ), @@ -111,13 +110,13 @@ class AddAccountPage extends HookWidget { autofocus: true, controller: usernameController, decoration: - InputDecoration(labelText: L10n.of(context).email_or_username), + InputDecoration(labelText: L10n.of(context)!.email_or_username), ), const SizedBox(height: 5), TextField( controller: passwordController, obscureText: true, - decoration: InputDecoration(labelText: L10n.of(context).password), + decoration: InputDecoration(labelText: L10n.of(context)!.password), ), ElevatedButton( onPressed: usernameController.text.isEmpty || diff --git a/lib/pages/add_instance.dart b/lib/pages/add_instance.dart index 5aafa13..89a8c74 100644 --- a/lib/pages/add_instance.dart +++ b/lib/pages/add_instance.dart @@ -19,8 +19,8 @@ class AddInstancePage extends HookWidget { useValueListenable(instanceController); final accountsStore = useAccountsStore(); - final isSite = useState(null); - final icon = useState(null); + final isSite = useState(null); + final icon = useState(null); final prevInput = usePrevious(instanceController.text); final debounce = useDebounce(() async { if (prevInput == instanceController.text) return; @@ -32,7 +32,7 @@ class AddInstancePage extends HookWidget { } try { icon.value = - (await LemmyApiV3(inst).run(const GetSite())).siteView.site.icon; + (await LemmyApiV3(inst).run(const GetSite())).siteView?.site.icon; isSite.value = true; // ignore: avoid_catches_without_on_clauses } catch (e) { @@ -70,9 +70,9 @@ class AddInstancePage extends HookWidget { SizedBox( height: 150, child: FullscreenableImage( - url: icon.value, + url: icon.value!, child: CachedNetworkImage( - imageUrl: icon.value, + imageUrl: icon.value!, errorWidget: (_, __, ___) => const SizedBox.shrink(), ), )) diff --git a/lib/pages/communities_list.dart b/lib/pages/communities_list.dart index 3dfd7c5..29e1700 100644 --- a/lib/pages/communities_list.dart +++ b/lib/pages/communities_list.dart @@ -15,10 +15,8 @@ class CommunitiesListPage extends StatelessWidget { SortType sortType, ) fetcher; - const CommunitiesListPage({Key key, @required this.fetcher, this.title = ''}) - : assert(fetcher != null), - assert(title != null), - super(key: key); + const CommunitiesListPage({Key? key, required this.fetcher, this.title = ''}) + : super(key: key); @override Widget build(BuildContext context) { @@ -46,9 +44,8 @@ class CommunitiesListPage extends StatelessWidget { class CommunitiesListItem extends StatelessWidget { final CommunityView community; - const CommunitiesListItem({Key key, @required this.community}) - : assert(community != null), - super(key: key); + const CommunitiesListItem({Key? key, required this.community}) + : super(key: key); @override Widget build(BuildContext context) => ListTile( @@ -57,7 +54,7 @@ class CommunitiesListItem extends StatelessWidget { ? Opacity( opacity: 0.7, child: MarkdownText( - community.community.description, + community.community.description!, instanceHost: community.instanceHost, ), ) diff --git a/lib/pages/communities_tab.dart b/lib/pages/communities_tab.dart index 597e1c6..6c95d53 100644 --- a/lib/pages/communities_tab.dart +++ b/lib/pages/communities_tab.dart @@ -7,6 +7,7 @@ import 'package:fuzzy/fuzzy.dart'; import 'package:lemmy_api_client/v3.dart'; import '../hooks/delayed_loading.dart'; +import '../hooks/logged_in_action.dart'; import '../hooks/refreshable.dart'; import '../hooks/stores.dart'; import '../util/extensions/api.dart'; @@ -37,7 +38,7 @@ class CommunitiesTab extends HookWidget { .map( (instanceHost) => LemmyApiV3(instanceHost) .run(const GetSite()) - .then((e) => e.siteView.site), + .then((e) => e.siteView!.site), ) .toList(); @@ -52,8 +53,8 @@ class CommunitiesTab extends HookWidget { sort: SortType.active, savedOnly: false, personId: - accountsStore.defaultTokenFor(instanceHost).payload.sub, - auth: accountsStore.defaultTokenFor(instanceHost).raw, + accountsStore.defaultTokenFor(instanceHost)!.payload.sub, + auth: accountsStore.defaultTokenFor(instanceHost)!.raw, )) .then((e) => e.follows), ) @@ -84,7 +85,7 @@ class CommunitiesTab extends HookWidget { padding: const EdgeInsets.all(8), child: Text( communitiesRefreshable.snapshot.error?.toString() ?? - instancesRefreshable.snapshot.error?.toString(), + instancesRefreshable.snapshot.error!.toString(), ), ) ], @@ -115,8 +116,8 @@ class CommunitiesTab extends HookWidget { } } - final instances = instancesRefreshable.snapshot.data; - final communities = communitiesRefreshable.snapshot.data + final instances = instancesRefreshable.snapshot.data!; + final communities = communitiesRefreshable.snapshot.data! ..forEach((e) => e.sort((a, b) => a.community.name.compareTo(b.community.name))); @@ -128,7 +129,7 @@ class CommunitiesTab extends HookWidget { return IconButton( onPressed: () { filterController.clear(); - primaryFocus.unfocus(); + primaryFocus?.unfocus(); }, icon: const Icon(Icons.clear), ); @@ -236,26 +237,24 @@ class _CommunitySubscribeToggle extends HookWidget { final String instanceHost; const _CommunitySubscribeToggle( - {@required this.instanceHost, @required this.communityId, Key key}) - : assert(instanceHost != null), - assert(communityId != null), - super(key: key); + {required this.instanceHost, required this.communityId, Key? key}) + : super(key: key); @override Widget build(BuildContext context) { final theme = Theme.of(context); final subbed = useState(true); final delayed = useDelayedLoading(); - final accountsStore = useAccountsStore(); + final loggedInAction = useLoggedInAction(instanceHost); - handleTap() async { + handleTap(Jwt token) async { delayed.start(); try { await LemmyApiV3(instanceHost).run(FollowCommunity( communityId: communityId, follow: !subbed.value, - auth: accountsStore.defaultTokenFor(instanceHost).raw, + auth: token.raw, )); subbed.value = !subbed.value; } on Exception catch (err) { @@ -268,7 +267,7 @@ class _CommunitySubscribeToggle extends HookWidget { } return InkWell( - onTap: delayed.pending ? () {} : handleTap, + onTap: delayed.pending ? () {} : loggedInAction(handleTap), child: Container( decoration: delayed.loading ? null diff --git a/lib/pages/community.dart b/lib/pages/community.dart index 5134c99..574c8de 100644 --- a/lib/pages/community.dart +++ b/lib/pages/community.dart @@ -28,26 +28,22 @@ import 'modlog_page.dart'; /// Displays posts, comments, and general info about the given community class CommunityPage extends HookWidget { - final CommunityView _community; + final CommunityView? _community; final String instanceHost; - final String communityName; - final int communityId; + final String? communityName; + final int? communityId; const CommunityPage.fromName({ - @required this.communityName, - @required this.instanceHost, - }) : assert(communityName != null), - assert(instanceHost != null), - communityId = null, + required String this.communityName, + required this.instanceHost, + }) : communityId = null, _community = null; const CommunityPage.fromId({ - @required this.communityId, - @required this.instanceHost, - }) : assert(communityId != null), - assert(instanceHost != null), - communityName = null, + required int this.communityId, + required this.instanceHost, + }) : communityName = null, _community = null; - CommunityPage.fromCommunityView(this._community) + CommunityPage.fromCommunityView(CommunityView this._community) : instanceHost = _community.instanceHost, communityId = _community.community.id, communityName = _community.community.name; @@ -76,7 +72,7 @@ class CommunityPage extends HookWidget { final community = () { if (fullCommunitySnap.hasData) { - return fullCommunitySnap.data.communityView; + return fullCommunitySnap.data!.communityView; } else if (_community != null) { return _community; } else { @@ -173,8 +169,8 @@ class CommunityPage extends HookWidget { color: theme.cardColor, child: TabBar( tabs: [ - Tab(text: L10n.of(context).posts), - Tab(text: L10n.of(context).comments), + Tab(text: L10n.of(context)!.posts), + Tab(text: L10n.of(context)!.comments), const Tab(text: 'About'), ], ), @@ -224,14 +220,13 @@ class CommunityPage extends HookWidget { class _CommunityOverview extends StatelessWidget { final CommunityView community; final String instanceHost; - final int onlineUsers; + final int? onlineUsers; const _CommunityOverview({ - @required this.community, - @required this.instanceHost, - @required this.onlineUsers, - }) : assert(instanceHost != null), - assert(goToInstance != null); + required this.community, + required this.instanceHost, + required this.onlineUsers, + }); @override Widget build(BuildContext context) { @@ -257,7 +252,7 @@ class _CommunityOverview extends StatelessWidget { ), ), FullscreenableImage( - url: community.community.icon, + url: community.community.icon!, child: Avatar( url: community.community.icon, radius: 83 / 2, @@ -270,9 +265,9 @@ class _CommunityOverview extends StatelessWidget { return Stack(children: [ if (community.community.banner != null) FullscreenableImage( - url: community.community.banner, + url: community.community.banner!, child: CachedNetworkImage( - imageUrl: community.community.banner, + imageUrl: community.community.banner!, errorWidget: (_, __, ___) => const SizedBox.shrink(), ), ), @@ -280,7 +275,7 @@ class _CommunityOverview extends StatelessWidget { child: Padding( padding: const EdgeInsets.only(top: 45), child: Column(children: [ - if (community.community.icon != null) icon, + if (icon != null) icon, // NAME Center( child: Padding( @@ -289,7 +284,7 @@ class _CommunityOverview extends StatelessWidget { overflow: TextOverflow.ellipsis, // TODO: fix overflowing text: TextSpan( style: - theme.textTheme.subtitle1.copyWith(shadows: [shadow]), + theme.textTheme.subtitle1?.copyWith(shadows: [shadow]), children: [ const TextSpan( text: '!', @@ -346,7 +341,7 @@ class _CommunityOverview extends StatelessWidget { ), Text(onlineUsers == null ? 'xx' - : compactNumber(onlineUsers)), + : compactNumber(onlineUsers!)), const Spacer(), ], ), @@ -364,14 +359,14 @@ class _CommunityOverview extends StatelessWidget { class _AboutTab extends StatelessWidget { final CommunityView community; - final List moderators; - final int onlineUsers; + final List? moderators; + final int? onlineUsers; const _AboutTab({ - Key key, - @required this.community, - @required this.moderators, - @required this.onlineUsers, + Key? key, + required this.community, + required this.moderators, + required this.onlineUsers, }) : super(key: key); @override @@ -384,7 +379,7 @@ class _AboutTab extends StatelessWidget { if (community.community.description != null) ...[ Padding( padding: const EdgeInsets.symmetric(horizontal: 15), - child: MarkdownText(community.community.description, + child: MarkdownText(community.community.description!, instanceHost: community.instanceHost), ), const _Divider(), @@ -396,10 +391,10 @@ class _AboutTab extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 15), children: [ Chip( - label: Text(L10n.of(context) + label: Text(L10n.of(context)! .number_of_users_online(onlineUsers ?? 0))), Chip( - label: Text(L10n.of(context) + label: Text(L10n.of(context)! .number_of_subscribers(community.counts.subscribers))), Chip( label: Text( @@ -422,16 +417,16 @@ class _AboutTab extends StatelessWidget { communityName: community.community.name, ), ), - child: Text(L10n.of(context).modlog), + child: Text(L10n.of(context)!.modlog), ), ), const _Divider(), - if (moderators != null && moderators.isNotEmpty) ...[ + if (moderators != null && moderators!.isNotEmpty) ...[ Padding( padding: const EdgeInsets.symmetric(horizontal: 15), child: Text('Mods:', style: theme.textTheme.subtitle2), ), - for (final mod in moderators) + for (final mod in moderators!) // TODO: add user picture, maybe make it into reusable component ListTile( title: Text( @@ -463,7 +458,7 @@ class _FollowButton extends HookWidget { Widget build(BuildContext context) { final theme = Theme.of(context); - final isSubbed = useState(community.subscribed ?? false); + final isSubbed = useState(community.subscribed); final delayed = useDelayedLoading(Duration.zero); final loggedInAction = useLoggedInAction(community.instanceHost); @@ -493,7 +488,7 @@ class _FollowButton extends HookWidget { return ElevatedButtonTheme( data: ElevatedButtonThemeData( - style: theme.elevatedButtonTheme.style.copyWith( + style: theme.elevatedButtonTheme.style?.copyWith( shape: MaterialStateProperty.all(const StadiumBorder()), textStyle: MaterialStateProperty.all(theme.textTheme.subtitle1), ), @@ -518,8 +513,8 @@ class _FollowButton extends HookWidget { ? const Icon(Icons.remove, size: 18) : const Icon(Icons.add, size: 18), label: Text(isSubbed.value - ? L10n.of(context).unsubscribe - : L10n.of(context).subscribe), + ? L10n.of(context)!.unsubscribe + : L10n.of(context)!.subscribe), ), ), ), diff --git a/lib/pages/create_post.dart b/lib/pages/create_post.dart index 2373146..73e40d1 100644 --- a/lib/pages/create_post.dart +++ b/lib/pages/create_post.dart @@ -23,18 +23,20 @@ import 'full_post.dart'; /// Fab that triggers the [CreatePost] modal class CreatePostFab extends HookWidget { - final CommunityView community; + final CommunityView? community; const CreatePostFab({this.community}); @override Widget build(BuildContext context) { - final loggedInAction = useLoggedInAction(null, any: true); + final loggedInAction = useAnyLoggedInAction(); return FloatingActionButton( onPressed: loggedInAction((_) => showCupertinoModalPopup( context: context, - builder: (_) => CreatePostPage.toCommunity(community))), + builder: (_) => community == null + ? const CreatePostPage() + : CreatePostPage.toCommunity(community!))), child: const Icon(Icons.add), ); } @@ -42,10 +44,10 @@ class CreatePostFab extends HookWidget { /// Modal for creating a post to some community in some instance class CreatePostPage extends HookWidget { - final CommunityView community; + final CommunityView? community; const CreatePostPage() : community = null; - const CreatePostPage.toCommunity(this.community); + const CreatePostPage.toCommunity(CommunityView this.community); @override Widget build(BuildContext context) { @@ -61,7 +63,8 @@ class CreatePostPage extends HookWidget { final delayed = useDelayedLoading(); final imagePicker = useImagePicker(); final imageUploadLoading = useState(false); - final pictrsDeleteToken = useState(null); + final pictrsDeleteToken = useState(null); + final loggedInAction = useLoggedInAction(selectedInstance.value); final allCommunitiesSnap = useMemoFuture( () => LemmyApiV3(selectedInstance.value) @@ -69,7 +72,7 @@ class CreatePostPage extends HookWidget { type: PostListingType.all, sort: SortType.hot, limit: 9999, - auth: accStore.defaultTokenFor(selectedInstance.value).raw, + auth: accStore.defaultTokenFor(selectedInstance.value)?.raw, )) .then( (value) { @@ -80,14 +83,13 @@ class CreatePostPage extends HookWidget { [selectedInstance.value], ); - uploadPicture() async { + uploadPicture(Jwt token) async { try { final pic = await imagePicker.getImage(source: ImageSource.gallery); // pic is null when the picker was cancelled if (pic != null) { imageUploadLoading.value = true; - final token = accStore.defaultTokenFor(selectedInstance.value); final pictrs = PictrsApi(selectedInstance.value); final upload = await pictrs.upload(filePath: pic.path, auth: token.raw); @@ -105,10 +107,8 @@ class CreatePostPage extends HookWidget { } } - removePicture() { - PictrsApi(selectedInstance.value) - .delete(pictrsDeleteToken.value) - .catchError((_) {}); + removePicture(PictrsUploadFile deleteToken) { + PictrsApi(selectedInstance.value).delete(deleteToken).catchError((_) {}); pictrsDeleteToken.value = null; urlController.text = ''; @@ -140,10 +140,10 @@ class CreatePostPage extends HookWidget { List> communitiesList() { if (allCommunitiesSnap.hasData) { - return allCommunitiesSnap.data.map(communityDropDownItem).toList(); + return allCommunitiesSnap.data!.map(communityDropDownItem).toList(); } else { if (selectedCommunity.value != null) { - return [communityDropDownItem(selectedCommunity.value)]; + return [communityDropDownItem(selectedCommunity.value!)]; } else { return const [ DropdownMenuItem( @@ -163,8 +163,8 @@ class CreatePostPage extends HookWidget { borderRadius: BorderRadius.all(Radius.circular(10)))), child: DropdownButtonHideUnderline( child: DropdownButton( - value: selectedCommunity.value?.community?.id, - hint: Text(L10n.of(context).community), + value: selectedCommunity.value?.community.id, + hint: Text(L10n.of(context)!.community), onChanged: (communityId) => selectedCommunity.value = allCommunitiesSnap.data ?.firstWhere((e) => e.community.id == communityId), @@ -179,7 +179,7 @@ class CreatePostPage extends HookWidget { enabled: pictrsDeleteToken.value == null, controller: urlController, decoration: InputDecoration( - labelText: L10n.of(context).url, + labelText: L10n.of(context)!.url, suffixIcon: const Icon(Icons.link), ), ), @@ -191,8 +191,9 @@ class CreatePostPage extends HookWidget { : Icon(pictrsDeleteToken.value == null ? Icons.add_photo_alternate : Icons.close), - onPressed: - pictrsDeleteToken.value == null ? uploadPicture : removePicture, + onPressed: pictrsDeleteToken.value == null + ? loggedInAction(uploadPicture) + : () => removePicture(pictrsDeleteToken.value!), tooltip: pictrsDeleteToken.value == null ? 'Add picture' : 'Delete picture', ) @@ -202,7 +203,7 @@ class CreatePostPage extends HookWidget { controller: titleController, minLines: 1, maxLines: 2, - decoration: InputDecoration(labelText: L10n.of(context).title), + decoration: InputDecoration(labelText: L10n.of(context)!.title), ); final body = IndexedStack( @@ -213,7 +214,7 @@ class CreatePostPage extends HookWidget { keyboardType: TextInputType.multiline, maxLines: null, minLines: 5, - decoration: InputDecoration(labelText: L10n.of(context).body), + decoration: InputDecoration(labelText: L10n.of(context)!.body), ), Padding( padding: const EdgeInsets.all(16), @@ -225,7 +226,7 @@ class CreatePostPage extends HookWidget { ], ); - handleSubmit() async { + handleSubmit(Jwt token) async { if (selectedCommunity.value == null || titleController.text.isEmpty) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text('Choosing a community and a title is required'), @@ -235,8 +236,6 @@ class CreatePostPage extends HookWidget { final api = LemmyApiV3(selectedInstance.value); - final token = accStore.defaultTokenFor(selectedInstance.value); - delayed.start(); try { final res = await api.run(CreatePost( @@ -244,7 +243,7 @@ class CreatePostPage extends HookWidget { body: bodyController.text.isEmpty ? null : bodyController.text, nsfw: nsfw.value, name: titleController.text, - communityId: selectedCommunity.value.community.id, + communityId: selectedCommunity.value!.community.id, auth: token.raw, )); unawaited(goToReplace(context, (_) => FullPostPage.fromPostView(res))); @@ -285,17 +284,20 @@ class CreatePostPage extends HookWidget { children: [ Checkbox( value: nsfw.value, - onChanged: (val) => nsfw.value = val, + onChanged: (val) { + if (val != null) nsfw.value = val; + }, ), - Text(L10n.of(context).nsfw) + Text(L10n.of(context)!.nsfw) ], ), ), TextButton( - onPressed: delayed.pending ? () {} : handleSubmit, + onPressed: + delayed.pending ? () {} : loggedInAction(handleSubmit), child: delayed.loading ? const CircularProgressIndicator() - : Text(L10n.of(context).post), + : Text(L10n.of(context)!.post), ) ], ), diff --git a/lib/pages/full_post.dart b/lib/pages/full_post.dart index 3986224..0d90580 100644 --- a/lib/pages/full_post.dart +++ b/lib/pages/full_post.dart @@ -20,13 +20,11 @@ import '../widgets/write_comment.dart'; class FullPostPage extends HookWidget { final int id; final String instanceHost; - final PostView post; + final PostView? post; - const FullPostPage({@required this.id, @required this.instanceHost}) - : assert(id != null), - assert(instanceHost != null), - post = null; - FullPostPage.fromPostView(this.post) + const FullPostPage({required this.id, required this.instanceHost}) + : post = null; + FullPostPage.fromPostView(PostView this.post) : id = post.post.id, instanceHost = post.instanceHost; @@ -64,8 +62,8 @@ class FullPostPage extends HookWidget { // VARIABLES final post = fullPostRefreshable.snapshot.hasData - ? fullPostRefreshable.snapshot.data.postView - : this.post; + ? fullPostRefreshable.snapshot.data!.postView + : this.post!; final fullPost = fullPostRefreshable.snapshot.data; @@ -129,7 +127,7 @@ class FullPostPage extends HookWidget { children: [ const SizedBox(height: 15), PostWidget(post, fullPost: true), - if (fullPostRefreshable.snapshot.hasData) + if (fullPost != null) CommentSection( newComments.value.followedBy(fullPost.comments).toList(), postCreatorId: fullPost.postView.creator.id) diff --git a/lib/pages/home_tab.dart b/lib/pages/home_tab.dart index a0a7098..987864a 100644 --- a/lib/pages/home_tab.dart +++ b/lib/pages/home_tab.dart @@ -32,13 +32,14 @@ class HomeTab extends HookWidget { final isc = useInfiniteScrollController(); final theme = Theme.of(context); final instancesIcons = useMemoFuture(() async { - final instances = accStore.instances.toList(growable: false); - final sites = await Future.wait(instances.map( - (e) => LemmyApiV3(e).run(const GetSite()).catchError((e) => null))); + final sites = await Future.wait(accStore.instances.map((e) => + LemmyApiV3(e) + .run(const GetSite()) + .catchError((e) => null))); return { - for (var i = 0; i < sites.length; i++) - instances[i]: sites[i].siteView.site.icon + for (final site in sites) + if (site != null) site.instanceHost: site.siteView?.site.icon }; }); @@ -48,7 +49,8 @@ class HomeTab extends HookWidget { // - listingType == subscribed on an instance that has no longer a logged in account // - instanceHost of a removed instance useEffect(() { - if (accStore.isAnonymousFor(selectedList.value.instanceHost) && + if ((selectedList.value.instanceHost == null || + accStore.isAnonymousFor(selectedList.value.instanceHost!)) && selectedList.value.listingType == PostListingType.subscribed || !accStore.instances.contains(selectedList.value.instanceHost)) { selectedList.value = _SelectedList( @@ -60,7 +62,8 @@ class HomeTab extends HookWidget { return null; }, [ - accStore.isAnonymousFor(selectedList.value.instanceHost), + selectedList.value.instanceHost == null || + accStore.isAnonymousFor(selectedList.value.instanceHost!), accStore.hasNoAccount, accStore.instances.length, ]); @@ -84,10 +87,10 @@ class HomeTab extends HookWidget { ), ListTile( title: Text( - L10n.of(context).subscribed, + L10n.of(context)!.subscribed, style: TextStyle( color: accStore.hasNoAccount - ? theme.textTheme.bodyText1.color.withOpacity(0.4) + ? theme.textTheme.bodyText1?.color?.withOpacity(0.4) : null, ), ), @@ -119,7 +122,7 @@ class HomeTab extends HookWidget { instance.toUpperCase(), style: TextStyle( color: - theme.textTheme.bodyText1.color.withOpacity(0.7)), + theme.textTheme.bodyText1?.color?.withOpacity(0.7)), ), onTap: () => goToInstance(context, instance), dense: true, @@ -127,14 +130,14 @@ class HomeTab extends HookWidget { visualDensity: const VisualDensity( vertical: VisualDensity.minimumDensity), leading: (instancesIcons.hasData && - instancesIcons.data[instance] != null) + instancesIcons.data![instance] != null) ? Padding( padding: const EdgeInsets.only(left: 20), child: SizedBox( width: 25, height: 25, child: CachedNetworkImage( - imageUrl: instancesIcons.data[instance], + imageUrl: instancesIcons.data![instance]!, height: 25, width: 25, ), @@ -144,10 +147,10 @@ class HomeTab extends HookWidget { ), ListTile( title: Text( - L10n.of(context).subscribed, + L10n.of(context)!.subscribed, style: TextStyle( color: accStore.isAnonymousFor(instance) - ? theme.textTheme.bodyText1.color.withOpacity(0.4) + ? theme.textTheme.bodyText1?.color?.withOpacity(0.4) : null), ), onTap: accStore.isAnonymousFor(instance) @@ -162,7 +165,7 @@ class HomeTab extends HookWidget { leading: const SizedBox(width: 20), ), ListTile( - title: Text(L10n.of(context).local), + title: Text(L10n.of(context)!.local), onTap: () => pop(_SelectedList( listingType: PostListingType.local, instanceHost: instance, @@ -170,7 +173,7 @@ class HomeTab extends HookWidget { leading: const SizedBox(width: 20), ), ListTile( - title: Text(L10n.of(context).all), + title: Text(L10n.of(context)!.all), onTap: () => pop(_SelectedList( listingType: PostListingType.all, instanceHost: instance, @@ -229,7 +232,7 @@ class HomeTab extends HookWidget { Flexible( child: Text( title, - style: theme.appBarTheme.textTheme.headline6, + style: theme.appBarTheme.textTheme?.headline6, overflow: TextOverflow.fade, softWrap: false, ), @@ -249,15 +252,13 @@ class HomeTab extends HookWidget { /// Infinite list of posts class InfiniteHomeList extends HookWidget { - final Function onStyleChange; final InfiniteScrollController controller; final _SelectedList selectedList; const InfiniteHomeList({ - @required this.selectedList, - this.onStyleChange, - this.controller, - }) : assert(selectedList != null); + required this.selectedList, + required this.controller, + }); @override Widget build(BuildContext context) { @@ -323,7 +324,7 @@ class InfiniteHomeList extends HookWidget { ? (page, limit, sort) => generalFetcher(page, limit, sort, selectedList.listingType) : fetcherFromInstance( - selectedList.instanceHost, selectedList.listingType), + selectedList.instanceHost!, selectedList.listingType), controller: controller, ); } @@ -331,13 +332,13 @@ class InfiniteHomeList extends HookWidget { class _SelectedList { /// when null it implies the 'EVERYTHING' mode - final String instanceHost; + final String? instanceHost; final PostListingType listingType; const _SelectedList({ - @required this.listingType, + required this.listingType, this.instanceHost, - }) : assert(listingType != null); + }); String toString() => 'SelectedList(instanceHost: $instanceHost, listingType: $listingType)'; diff --git a/lib/pages/inbox.dart b/lib/pages/inbox.dart index 3da27cd..6dcc5a5 100644 --- a/lib/pages/inbox.dart +++ b/lib/pages/inbox.dart @@ -45,6 +45,8 @@ class InboxPage extends HookWidget { ); } + final selectedInstance = selected.value!; + toggleUnreadOnly() { unreadOnly.value = !unreadOnly.value; isc.clear(); @@ -60,7 +62,7 @@ class InboxPage extends HookWidget { isc.clear(); }, title: 'select instance', - groupValue: selected.value, + groupValue: selectedInstance, buttonBuilder: (context, displayString, onPressed) => TextButton( style: TextButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 15), @@ -73,7 +75,7 @@ class InboxPage extends HookWidget { Flexible( child: Text( displayString, - style: theme.appBarTheme.textTheme.headline6, + style: theme.appBarTheme.textTheme?.headline6, overflow: TextOverflow.fade, softWrap: false, ), @@ -93,9 +95,9 @@ class InboxPage extends HookWidget { ], bottom: TabBar( tabs: [ - Tab(text: L10n.of(context).replies), - Tab(text: L10n.of(context).mentions), - Tab(text: L10n.of(context).messages), + Tab(text: L10n.of(context)!.replies), + Tab(text: L10n.of(context)!.mentions), + Tab(text: L10n.of(context)!.messages), ], ), ), @@ -106,8 +108,8 @@ class InboxPage extends HookWidget { controller: isc, defaultSort: SortType.new_, fetcher: (page, batchSize, sortType) => - LemmyApiV3(selected.value).run(GetReplies( - auth: accStore.defaultTokenFor(selected.value).raw, + LemmyApiV3(selectedInstance).run(GetReplies( + auth: accStore.defaultTokenFor(selectedInstance)!.raw, sort: sortType, limit: batchSize, page: page, @@ -124,8 +126,8 @@ class InboxPage extends HookWidget { controller: isc, defaultSort: SortType.new_, fetcher: (page, batchSize, sortType) => - LemmyApiV3(selected.value).run(GetPersonMentions( - auth: accStore.defaultTokenFor(selected.value).raw, + LemmyApiV3(selectedInstance).run(GetPersonMentions( + auth: accStore.defaultTokenFor(selectedInstance)!.raw, sort: sortType, limit: batchSize, page: page, @@ -142,9 +144,9 @@ class InboxPage extends HookWidget { child: Text('no messages'), ), controller: isc, - fetcher: (page, batchSize) => LemmyApiV3(selected.value).run( + fetcher: (page, batchSize) => LemmyApiV3(selectedInstance).run( GetPrivateMessages( - auth: accStore.defaultTokenFor(selected.value).raw, + auth: accStore.defaultTokenFor(selectedInstance)!.raw, limit: batchSize, page: page, unreadOnly: unreadOnly.value, @@ -167,10 +169,9 @@ class PrivateMessageTile extends HookWidget { final bool hideOnRead; const PrivateMessageTile({ - @required this.privateMessageView, + required this.privateMessageView, this.hideOnRead = false, - }) : assert(privateMessageView != null), - assert(hideOnRead != null); + }); static const double _iconSize = 16; @override @@ -189,7 +190,7 @@ class PrivateMessageTile extends HookWidget { final toMe = useMemoized(() => pmv.value.recipient.originInstanceHost == pmv.value.instanceHost && pmv.value.recipient.id == - accStore.defaultTokenFor(pmv.value.instanceHost)?.payload?.sub); + accStore.defaultTokenFor(pmv.value.instanceHost)?.payload.sub); final otherSide = useMemoized(() => toMe ? pmv.value.creator : pmv.value.recipient); @@ -239,7 +240,7 @@ class PrivateMessageTile extends HookWidget { instanceHost: pmv.value.instanceHost, query: DeletePrivateMessage( privateMessageId: pmv.value.privateMessage.id, - auth: accStore.defaultTokenFor(pmv.value.instanceHost)?.raw, + auth: accStore.defaultTokenFor(pmv.value.instanceHost)!.raw, deleted: !deleted.value, ), onSuccess: (val) => deleted.value = val.privateMessage.deleted, @@ -251,7 +252,7 @@ class PrivateMessageTile extends HookWidget { instanceHost: pmv.value.instanceHost, query: MarkPrivateMessageAsRead( privateMessageId: pmv.value.privateMessage.id, - auth: accStore.defaultTokenFor(pmv.value.instanceHost)?.raw, + auth: accStore.defaultTokenFor(pmv.value.instanceHost)!.raw, read: !read.value, ), // TODO: add notification for notifying parent list @@ -280,8 +281,8 @@ class PrivateMessageTile extends HookWidget { Row( children: [ Text( - '${toMe ? L10n.of(context).from : L10n.of(context).to} ', - style: TextStyle(color: theme.textTheme.caption.color), + '${toMe ? L10n.of(context)!.from : L10n.of(context)!.to} ', + style: TextStyle(color: theme.textTheme.caption?.color), ), InkWell( borderRadius: BorderRadius.circular(10), @@ -292,7 +293,7 @@ class PrivateMessageTile extends HookWidget { Padding( padding: const EdgeInsets.only(right: 5), child: CachedNetworkImage( - imageUrl: otherSide.avatar, + imageUrl: otherSide.avatar!, height: 20, width: 20, imageBuilder: (context, imageProvider) => Container( @@ -336,7 +337,7 @@ class PrivateMessageTile extends HookWidget { const SizedBox(height: 5), if (pmv.value.privateMessage.deleted) Text( - L10n.of(context).deleted_by_creator, + L10n.of(context)!.deleted_by_creator, style: const TextStyle(fontStyle: FontStyle.italic), ) else @@ -346,19 +347,19 @@ class PrivateMessageTile extends HookWidget { TileAction( icon: moreIcon, onPressed: showMoreMenu, - tooltip: L10n.of(context).more, + tooltip: L10n.of(context)!.more, ), if (toMe) ...[ TileAction( iconColor: read.value ? theme.accentColor : null, icon: Icons.check, - tooltip: L10n.of(context).mark_as_read, + tooltip: L10n.of(context)!.mark_as_read, onPressed: handleRead, delayedLoading: readDelayed, ), TileAction( icon: Icons.reply, - tooltip: L10n.of(context).reply, + tooltip: L10n.of(context)!.reply, onPressed: () { showCupertinoModalPopup( context: context, @@ -371,7 +372,7 @@ class PrivateMessageTile extends HookWidget { ] else ...[ TileAction( icon: Icons.edit, - tooltip: L10n.of(context).edit, + tooltip: L10n.of(context)!.edit, onPressed: () async { final val = await showCupertinoModalPopup( context: context, @@ -383,8 +384,8 @@ class PrivateMessageTile extends HookWidget { delayedLoading: deleteDelayed, icon: deleted.value ? Icons.restore : Icons.delete, tooltip: deleted.value - ? L10n.of(context).restore - : L10n.of(context).delete, + ? L10n.of(context)!.restore + : L10n.of(context)!.delete, onPressed: handleDelete, ), ] diff --git a/lib/pages/instance.dart b/lib/pages/instance.dart index 35ea25f..9c206fc 100644 --- a/lib/pages/instance.dart +++ b/lib/pages/instance.dart @@ -30,9 +30,8 @@ class InstancePage extends HookWidget { final Future siteFuture; final Future> communitiesFuture; - InstancePage({@required this.instanceHost}) - : assert(instanceHost != null), - siteFuture = LemmyApiV3(instanceHost).run(const GetSite()), + InstancePage({required this.instanceHost}) + : siteFuture = LemmyApiV3(instanceHost).run(const GetSite()), communitiesFuture = LemmyApiV3(instanceHost).run(const ListCommunities( type: PostListingType.local, sort: SortType.hot, limit: 6)); @@ -44,7 +43,7 @@ class InstancePage extends HookWidget { final accStore = useAccountsStore(); final scrollController = useScrollController(); - if (!siteSnap.hasData) { + if (!siteSnap.hasData || siteSnap.data!.siteView == null) { return Scaffold( appBar: AppBar(), body: Center( @@ -57,7 +56,9 @@ class InstancePage extends HookWidget { padding: const EdgeInsets.all(8), child: Text('ERROR: ${siteSnap.error}'), ) - ] else + ] else if (siteSnap.data!.siteView == null) + const Text('ERROR') + else const CircularProgressIndicator(semanticsLabel: 'loading') ], ), @@ -65,7 +66,8 @@ class InstancePage extends HookWidget { ); } - final site = siteSnap.data; + final site = siteSnap.data!; + final siteView = site.siteView!; void _share() => share('https://$instanceHost', context: context); @@ -110,7 +112,7 @@ class InstancePage extends HookWidget { fade: true, scrollController: scrollController, child: Text( - site.siteView.site.name, + siteView.site.name, style: TextStyle(color: colorOnCard), ), ), @@ -122,11 +124,11 @@ class InstancePage extends HookWidget { ], flexibleSpace: FlexibleSpaceBar( background: Stack(children: [ - if (site.siteView.site.banner != null) + if (siteView.site.banner != null) FullscreenableImage( - url: site.siteView.site.banner, + url: siteView.site.banner!, child: CachedNetworkImage( - imageUrl: site.siteView.site.banner, + imageUrl: siteView.site.banner!, errorWidget: (_, __, ___) => const SizedBox.shrink(), ), ), @@ -136,20 +138,20 @@ class InstancePage extends HookWidget { children: [ Padding( padding: const EdgeInsets.only(top: 40), - child: site.siteView.site.icon == null + child: siteView.site.icon == null ? const SizedBox(height: 100, width: 100) : FullscreenableImage( - url: site.siteView.site.icon, + url: siteView.site.icon!, child: CachedNetworkImage( width: 100, height: 100, - imageUrl: site.siteView.site.icon, + imageUrl: siteView.site.icon!, errorWidget: (_, __, ___) => const Icon(Icons.warning), ), ), ), - Text(site.siteView.site.name, + Text(siteView.site.name, style: theme.textTheme.headline6), Text(instanceHost, style: theme.textTheme.caption) ], @@ -164,8 +166,8 @@ class InstancePage extends HookWidget { color: theme.cardColor, child: TabBar( tabs: [ - Tab(text: L10n.of(context).posts), - Tab(text: L10n.of(context).comments), + Tab(text: L10n.of(context)!.posts), + Tab(text: L10n.of(context)!.comments), const Tab(text: 'About'), ], ), @@ -212,17 +214,18 @@ class _AboutTab extends HookWidget { final Future> communitiesFuture; final String instanceHost; - const _AboutTab(this.site, - {@required this.communitiesFuture, @required this.instanceHost}) - : assert(communitiesFuture != null), - assert(instanceHost != null); + const _AboutTab( + this.site, { + required this.communitiesFuture, + required this.instanceHost, + }); void goToBannedUsers(BuildContext context) { goTo( context, (_) => UsersListPage( users: site.banned.reversed.toList(), - title: L10n.of(context).banned_users, + title: L10n.of(context)!.banned_users, ), ); } @@ -246,24 +249,38 @@ class _AboutTab extends HookWidget { auth: accStore.defaultTokenFor(instanceHost)?.raw, ), ), - title: 'Communities of ${site.siteView.site.name}', + title: 'Communities of ${site.siteView?.site.name}', ), ); } + final siteView = site.siteView; + + if (siteView == null) { + return const SingleChildScrollView( + child: Center( + child: Padding( + padding: EdgeInsets.all(16), + child: Text('error'), + ))); + } + return SingleChildScrollView( child: SafeArea( top: false, child: Column( children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15), - child: MarkdownText( - site.siteView.site.description, - instanceHost: instanceHost, + if (siteView.site.description != null) ...[ + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 15, vertical: 15), + child: MarkdownText( + siteView.site.description!, + instanceHost: instanceHost, + ), ), - ), - const _Divider(), + const _Divider(), + ], SizedBox( height: 32, child: ListView( @@ -271,17 +288,18 @@ class _AboutTab extends HookWidget { padding: const EdgeInsets.symmetric(horizontal: 15), children: [ Chip( - label: Text(L10n.of(context) + label: Text(L10n.of(context)! .number_of_users_online(site.online))), Chip( - label: Text(L10n.of(context) - .number_of_users(site.siteView.counts.users))), + label: Text(L10n.of(context)! + .number_of_users(site.siteView!.counts.users))), Chip( label: Text( - '${site.siteView.counts.communities} communities')), - Chip(label: Text('${site.siteView.counts.posts} posts')), + '${site.siteView!.counts.communities} communities')), + Chip(label: Text('${site.siteView!.counts.posts} posts')), Chip( - label: Text('${site.siteView.counts.comments} comments')), + label: + Text('${site.siteView!.counts.comments} comments')), ].spaced(8), ), ), @@ -290,12 +308,12 @@ class _AboutTab extends HookWidget { title: Center( child: Text( 'Trending communities:', - style: theme.textTheme.headline6.copyWith(fontSize: 18), + style: theme.textTheme.headline6?.copyWith(fontSize: 18), ), ), ), if (commSnap.hasData) - for (final c in commSnap.data) + for (final c in commSnap.data!) ListTile( onTap: () => goToCommunity.byId( context, c.instanceHost, c.community.id), @@ -321,7 +339,7 @@ class _AboutTab extends HookWidget { title: Center( child: Text( 'Admins:', - style: theme.textTheme.headline6.copyWith(fontSize: 18), + style: theme.textTheme.headline6?.copyWith(fontSize: 18), ), ), ), @@ -329,18 +347,18 @@ class _AboutTab extends HookWidget { ListTile( title: Text(u.person.originDisplayName), subtitle: u.person.bio != null - ? MarkdownText(u.person.bio, instanceHost: instanceHost) + ? MarkdownText(u.person.bio!, instanceHost: instanceHost) : null, onTap: () => goToUser.fromPersonSafe(context, u.person), leading: Avatar(url: u.person.avatar), ), const _Divider(), ListTile( - title: Center(child: Text(L10n.of(context).banned_users)), + title: Center(child: Text(L10n.of(context)!.banned_users)), onTap: () => goToBannedUsers(context), ), ListTile( - title: Center(child: Text(L10n.of(context).modlog)), + title: Center(child: Text(L10n.of(context)!.modlog)), onTap: () => goTo( context, (context) => ModlogPage.forInstance(instanceHost: instanceHost), diff --git a/lib/pages/manage_account.dart b/lib/pages/manage_account.dart index f7a549f..30c2afc 100644 --- a/lib/pages/manage_account.dart +++ b/lib/pages/manage_account.dart @@ -21,10 +21,7 @@ class ManageAccountPage extends HookWidget { final String instanceHost; final String username; - const ManageAccountPage( - {@required this.instanceHost, @required this.username}) - : assert(instanceHost != null), - assert(username != null); + const ManageAccountPage({required this.instanceHost, required this.username}); @override Widget build(BuildContext context) { @@ -32,9 +29,9 @@ class ManageAccountPage extends HookWidget { final userFuture = useMemoized(() async { final site = await LemmyApiV3(instanceHost).run( - GetSite(auth: accountStore.tokenFor(instanceHost, username).raw)); + GetSite(auth: accountStore.tokenFor(instanceHost, username)!.raw)); - return site.myUser; + return site.myUser!; }); return Scaffold( @@ -51,7 +48,7 @@ class ManageAccountPage extends HookWidget { return const Center(child: CircularProgressIndicator()); } - return _ManageAccount(user: userSnap.data); + return _ManageAccount(user: userSnap.data!); }, ), ); @@ -59,9 +56,7 @@ class ManageAccountPage extends HookWidget { } class _ManageAccount extends HookWidget { - const _ManageAccount({Key key, @required this.user}) - : assert(user != null), - super(key: key); + const _ManageAccount({Key? key, required this.user}) : super(key: key); final LocalUserSettingsView user; @@ -91,12 +86,12 @@ class _ManageAccount extends HookWidget { final newPasswordVerifyController = useTextEditingController(); final oldPasswordController = useTextEditingController(); - final informAcceptedAvatarRef = useRef(null); - final informAcceptedBannerRef = useRef(null); + final informAcceptedAvatarRef = useRef(null); + final informAcceptedBannerRef = useRef(null); final deleteAccountPasswordController = useTextEditingController(); - final token = accountsStore.tokenFor(user.instanceHost, user.person.name); + final token = accountsStore.tokenFor(user.instanceHost, user.person.name)!; handleSubmit() async { saveDelayedLoading.start(); @@ -132,8 +127,8 @@ class _ManageAccount extends HookWidget { email: emailController.text.isEmpty ? null : emailController.text, )); - informAcceptedAvatarRef.current(); - informAcceptedBannerRef.current(); + informAcceptedAvatarRef.current?.call(); + informAcceptedBannerRef.current?.call(); ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text('User settings saved'), @@ -152,28 +147,28 @@ class _ManageAccount extends HookWidget { context: context, builder: (context) => AlertDialog( title: Text( - '${L10n.of(context).delete_account} @${user.instanceHost}@${user.person.name}'), + '${L10n.of(context)!.delete_account} @${user.instanceHost}@${user.person.name}'), content: Column( mainAxisSize: MainAxisSize.min, children: [ - Text(L10n.of(context).delete_account_confirm), + Text(L10n.of(context)!.delete_account_confirm), const SizedBox(height: 10), TextField( controller: deleteAccountPasswordController, obscureText: true, decoration: - InputDecoration(hintText: L10n.of(context).password), + InputDecoration(hintText: L10n.of(context)!.password), ) ], ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), - child: Text(L10n.of(context).no), + child: Text(L10n.of(context)!.no), ), TextButton( onPressed: () => Navigator.of(context).pop(true), - child: Text(L10n.of(context).yes), + child: Text(L10n.of(context)!.yes), ), ], ), @@ -209,7 +204,7 @@ class _ManageAccount extends HookWidget { children: [ _ImagePicker( user: user, - name: L10n.of(context).avatar, + name: L10n.of(context)!.avatar, initialUrl: avatar.current, onChange: (value) => avatar.current = value, informAcceptedRef: informAcceptedAvatarRef, @@ -217,42 +212,42 @@ class _ManageAccount extends HookWidget { const SizedBox(height: 8), _ImagePicker( user: user, - name: L10n.of(context).banner, + name: L10n.of(context)!.banner, initialUrl: banner.current, onChange: (value) => banner.current = value, informAcceptedRef: informAcceptedBannerRef, ), const SizedBox(height: 8), - Text(L10n.of(context).display_name, style: theme.textTheme.headline6), + Text(L10n.of(context)!.display_name, style: theme.textTheme.headline6), TextField(controller: displayNameController), const SizedBox(height: 8), - Text(L10n.of(context).bio, style: theme.textTheme.headline6), + Text(L10n.of(context)!.bio, style: theme.textTheme.headline6), TextField( controller: bioController, minLines: 4, maxLines: 10, ), const SizedBox(height: 8), - Text(L10n.of(context).email, style: theme.textTheme.headline6), + Text(L10n.of(context)!.email, style: theme.textTheme.headline6), TextField(controller: emailController), const SizedBox(height: 8), - Text(L10n.of(context).matrix_user, style: theme.textTheme.headline6), + Text(L10n.of(context)!.matrix_user, style: theme.textTheme.headline6), TextField(controller: matrixUserController), const SizedBox(height: 8), - Text(L10n.of(context).new_password, style: theme.textTheme.headline6), + Text(L10n.of(context)!.new_password, style: theme.textTheme.headline6), TextField( controller: newPasswordController, obscureText: true, ), const SizedBox(height: 8), - Text(L10n.of(context).verify_password, + Text(L10n.of(context)!.verify_password, style: theme.textTheme.headline6), TextField( controller: newPasswordVerifyController, obscureText: true, ), const SizedBox(height: 8), - Text(L10n.of(context).old_password, style: theme.textTheme.headline6), + Text(L10n.of(context)!.old_password, style: theme.textTheme.headline6), TextField( controller: oldPasswordController, obscureText: true, @@ -264,7 +259,7 @@ class _ManageAccount extends HookWidget { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(L10n.of(context).type), + Text(L10n.of(context)!.type), const Text( 'This has currently no effect on lemmur', style: TextStyle(fontSize: 10), @@ -290,7 +285,7 @@ class _ManageAccount extends HookWidget { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(L10n.of(context).sort_type), + Text(L10n.of(context)!.sort_type), const Text( 'This has currently no effect on lemmur', style: TextStyle(fontSize: 10), @@ -308,24 +303,30 @@ class _ManageAccount extends HookWidget { const SizedBox(height: 8), CheckboxListTile( value: showAvatars.value, - onChanged: (checked) => showAvatars.value = checked, - title: Text(L10n.of(context).show_avatars), + onChanged: (checked) { + if (checked != null) showAvatars.value = checked; + }, + title: Text(L10n.of(context)!.show_avatars), subtitle: const Text('This has currently no effect on lemmur'), dense: true, ), const SizedBox(height: 8), CheckboxListTile( value: showNsfw.value, - onChanged: (checked) => showNsfw.value = checked, - title: Text(L10n.of(context).show_nsfw), + onChanged: (checked) { + if (checked != null) showNsfw.value = checked; + }, + title: Text(L10n.of(context)!.show_nsfw), subtitle: const Text('This has currently no effect on lemmur'), dense: true, ), const SizedBox(height: 8), CheckboxListTile( value: sendNotificationsToEmail.value, - onChanged: (checked) => sendNotificationsToEmail.value = checked, - title: Text(L10n.of(context).send_notifications_to_email), + onChanged: (checked) { + if (checked != null) sendNotificationsToEmail.value = checked; + }, + title: Text(L10n.of(context)!.send_notifications_to_email), dense: true, ), const SizedBox(height: 8), @@ -337,7 +338,7 @@ class _ManageAccount extends HookWidget { height: 20, child: CircularProgressIndicator(), ) - : Text(L10n.of(context).save), + : Text(L10n.of(context)!.save), ), const SizedBox(height: 8), ElevatedButton( @@ -345,7 +346,7 @@ class _ManageAccount extends HookWidget { style: ElevatedButton.styleFrom( primary: Colors.red, ), - child: Text(L10n.of(context).delete_account.toUpperCase()), + child: Text(L10n.of(context)!.delete_account.toUpperCase()), ), const BottomSafe(), ], @@ -356,25 +357,23 @@ class _ManageAccount extends HookWidget { /// Picker and cleanuper for local images uploaded to pictrs class _ImagePicker extends HookWidget { final String name; - final String initialUrl; + final String? initialUrl; final LocalUserSettingsView user; - final ValueChanged onChange; + final ValueChanged? onChange; /// _ImagePicker will set the ref to a callback that can inform _ImagePicker /// that the current picture is accepted /// and should no longer allow for deletion of it - final Ref informAcceptedRef; + final Ref informAcceptedRef; const _ImagePicker({ - Key key, - @required this.initialUrl, - @required this.name, - @required this.user, - @required this.onChange, - @required this.informAcceptedRef, - }) : assert(name != null), - assert(user != null), - super(key: key); + Key? key, + required this.initialUrl, + required this.name, + required this.user, + required this.onChange, + required this.informAcceptedRef, + }) : super(key: key); @override Widget build(BuildContext context) { @@ -383,7 +382,7 @@ class _ImagePicker extends HookWidget { final initialUrl = useRef(this.initialUrl); final theme = Theme.of(context); final url = useState(initialUrl.current); - final pictrsDeleteToken = useState(null); + final pictrsDeleteToken = useState(null); final imagePicker = useImagePicker(); final accountsStore = useAccountsStore(); @@ -398,14 +397,15 @@ class _ImagePicker extends HookWidget { final upload = await PictrsApi(user.instanceHost).upload( filePath: pic.path, - auth: - accountsStore.tokenFor(user.instanceHost, user.person.name).raw, + auth: accountsStore + .tokenFor(user.instanceHost, user.person.name)! + .raw, ); pictrsDeleteToken.value = upload.files[0]; url.value = - pathToPictrs(user.instanceHost, pictrsDeleteToken.value.file); + pathToPictrs(user.instanceHost, pictrsDeleteToken.value!.file); - onChange?.call(url.value); + onChange?.call(url.value!); } } on Exception catch (_) { ScaffoldMessenger.of(context).showSnackBar( @@ -415,10 +415,11 @@ class _ImagePicker extends HookWidget { delayedLoading.cancel(); } - removePicture({bool updateState = true}) { - PictrsApi(user.instanceHost) - .delete(pictrsDeleteToken.value) - .catchError((_) {}); + removePicture({ + bool updateState = true, + required PictrsUploadFile pictrsToken, + }) { + PictrsApi(user.instanceHost).delete(pictrsToken).catchError((_) {}); if (updateState) { pictrsDeleteToken.value = null; @@ -436,7 +437,10 @@ class _ImagePicker extends HookWidget { return () { // remove picture from pictrs when exiting if (pictrsDeleteToken.value != null) { - removePicture(updateState: false); + removePicture( + updateState: false, + pictrsToken: pictrsDeleteToken.value!, + ); } }; }, []); @@ -462,13 +466,14 @@ class _ImagePicker extends HookWidget { else IconButton( icon: const Icon(Icons.close), - onPressed: removePicture, + onPressed: () => + removePicture(pictrsToken: pictrsDeleteToken.value!), ) ], ), if (url.value != null) CachedNetworkImage( - imageUrl: url.value, + imageUrl: url.value!, errorWidget: (_, __, ___) => const Icon(Icons.error), ), ], diff --git a/lib/pages/media_view.dart b/lib/pages/media_view.dart index be9512e..297bd59 100644 --- a/lib/pages/media_view.dart +++ b/lib/pages/media_view.dart @@ -26,7 +26,7 @@ class MediaViewPage extends HookWidget { final isDragging = useState(false); final offset = useState(Offset.zero); - final prevOffset = usePrevious(offset.value); + final prevOffset = usePrevious(offset.value) ?? Offset.zero; notImplemented() { ScaffoldMessenger.of(context).showSnackBar(const SnackBar( diff --git a/lib/pages/modlog_page.dart b/lib/pages/modlog_page.dart index 42975a4..5a23547 100644 --- a/lib/pages/modlog_page.dart +++ b/lib/pages/modlog_page.dart @@ -12,22 +12,18 @@ import '../widgets/bottom_safe.dart'; class ModlogPage extends HookWidget { final String instanceHost; final String name; - final int communityId; + final int? communityId; const ModlogPage.forInstance({ - @required this.instanceHost, - }) : assert(instanceHost != null), - communityId = null, + required this.instanceHost, + }) : communityId = null, name = instanceHost; const ModlogPage.forCommunity({ - @required this.instanceHost, - @required this.communityId, - @required String communityName, - }) : assert(instanceHost != null), - assert(communityId != null), - assert(communityName != null), - name = '!$communityName'; + required this.instanceHost, + required int this.communityId, + required String communityName, + }) : name = '!$communityName'; @override Widget build(BuildContext context) { @@ -65,7 +61,7 @@ class ModlogPage extends HookWidget { return Center( child: Text('Error: ${snapshot.error?.toString()}')); } - final modlog = snapshot.data; + final modlog = snapshot.requireData; if (modlog.added.length + modlog.addedToCommunity.length + @@ -78,7 +74,7 @@ class ModlogPage extends HookWidget { modlog.stickiedPosts.length == 0) { WidgetsBinding.instance - .addPostFrameCallback((_) => isDone.value = true); + ?.addPostFrameCallback((_) => isDone.value = true); return const Center(child: Text('no more logs to show')); } @@ -118,9 +114,7 @@ class ModlogPage extends HookWidget { } class _ModlogTable extends StatelessWidget { - const _ModlogTable({Key key, @required this.modlog}) - : assert(modlog != null), - super(key: key); + const _ModlogTable({Key? key, required this.modlog}) : super(key: key); final Modlog modlog; @@ -179,7 +173,7 @@ class _ModlogTable extends StatelessWidget { RichText( text: TextSpan( children: [ - if (removedPost.modRemovePost.removed) + if (removedPost.modRemovePost.removed ?? false) const TextSpan(text: 'removed') else const TextSpan(text: 'restored'), @@ -205,7 +199,7 @@ class _ModlogTable extends StatelessWidget { RichText( text: TextSpan( children: [ - if (lockedPost.modLockPost.locked) + if (lockedPost.modLockPost.locked ?? false) const TextSpan(text: 'locked') else const TextSpan(text: 'unlocked'), @@ -231,7 +225,7 @@ class _ModlogTable extends StatelessWidget { RichText( text: TextSpan( children: [ - if (stickiedPost.modStickyPost.stickied) + if (stickiedPost.modStickyPost.stickied ?? false) const TextSpan(text: 'stickied') else const TextSpan(text: 'unstickied'), @@ -257,7 +251,7 @@ class _ModlogTable extends StatelessWidget { RichText( text: TextSpan( children: [ - if (removedComment.modRemoveComment.removed) + if (removedComment.modRemoveComment.removed ?? false) const TextSpan(text: 'removed') else const TextSpan(text: 'restored'), @@ -286,7 +280,7 @@ class _ModlogTable extends StatelessWidget { RichText( text: TextSpan( children: [ - if (removedCommunity.modRemoveCommunity.removed) + if (removedCommunity.modRemoveCommunity.removed ?? false) const TextSpan(text: 'removed') else const TextSpan(text: 'restored'), @@ -303,7 +297,7 @@ class _ModlogTable extends StatelessWidget { RichText( text: TextSpan( children: [ - if (bannedFromCommunity.modBanFromCommunity.banned) + if (bannedFromCommunity.modBanFromCommunity.banned ?? false) const TextSpan(text: 'banned ') else const TextSpan(text: 'unbanned '), @@ -321,7 +315,7 @@ class _ModlogTable extends StatelessWidget { RichText( text: TextSpan( children: [ - if (banned.modBan.banned) + if (banned.modBan.banned ?? false) const TextSpan(text: 'banned ') else const TextSpan(text: 'unbanned '), @@ -337,7 +331,7 @@ class _ModlogTable extends StatelessWidget { RichText( text: TextSpan( children: [ - if (addedToCommunity.modAddCommunity.removed) + if (addedToCommunity.modAddCommunity.removed ?? false) const TextSpan(text: 'removed ') else const TextSpan(text: 'appointed '), @@ -355,7 +349,7 @@ class _ModlogTable extends StatelessWidget { RichText( text: TextSpan( children: [ - if (added.modAdd.removed) + if (added.modAdd.removed ?? false) const TextSpan(text: 'removed ') else const TextSpan(text: 'apointed '), @@ -402,16 +396,14 @@ class _ModlogEntry { final DateTime when; final PersonSafe mod; final Widget action; - final String reason; + final String? reason; const _ModlogEntry({ - @required this.when, - @required this.mod, - @required this.action, + required this.when, + required this.mod, + required this.action, this.reason, - }) : assert(when != null), - assert(mod != null), - assert(action != null); + }); _ModlogEntry.fromModRemovePostView( ModRemovePostView removedPost, @@ -539,7 +531,7 @@ class _ModlogEntry { ), ), action, - if (reason == null) const Center(child: Text('-')) else Text(reason), + if (reason == null) const Center(child: Text('-')) else Text(reason!), ] .map( (widget) => Padding( diff --git a/lib/pages/profile_tab.dart b/lib/pages/profile_tab.dart index 36ea7fe..4a12ef0 100644 --- a/lib/pages/profile_tab.dart +++ b/lib/pages/profile_tab.dart @@ -80,7 +80,7 @@ class UserProfileTab extends HookWidget { Text( // TODO: fix overflow issues displayValue, - style: theme.appBarTheme.textTheme.headline6, + style: theme.appBarTheme.textTheme?.headline6, overflow: TextOverflow.fade, ), const Icon(Icons.expand_more), @@ -91,8 +91,8 @@ class UserProfileTab extends HookWidget { actions: actions, ), body: UserProfile( - userId: accountsStore.defaultToken.payload.sub, - instanceHost: accountsStore.defaultInstanceHost, + userId: accountsStore.defaultToken!.payload.sub, + instanceHost: accountsStore.defaultInstanceHost!, ), ); } diff --git a/lib/pages/saved_page.dart b/lib/pages/saved_page.dart index 289454d..137978b 100644 --- a/lib/pages/saved_page.dart +++ b/lib/pages/saved_page.dart @@ -13,15 +13,24 @@ class SavedPage extends HookWidget { Widget build(BuildContext context) { final accountStore = useAccountsStore(); + if (accountStore.hasNoAccount) { + Scaffold( + appBar: AppBar(), + body: const Center( + child: Text('no account found'), + ), + ); + } + return DefaultTabController( length: 2, child: Scaffold( appBar: AppBar( - title: Text(L10n.of(context).saved), + title: Text(L10n.of(context)!.saved), bottom: TabBar( tabs: [ - Tab(text: L10n.of(context).posts), - Tab(text: L10n.of(context).comments), + Tab(text: L10n.of(context)!.posts), + Tab(text: L10n.of(context)!.comments), ], ), ), @@ -29,27 +38,27 @@ class SavedPage extends HookWidget { children: [ InfinitePostList( fetcher: (page, batchSize, sortType) => - LemmyApiV3(accountStore.defaultInstanceHost).run( + LemmyApiV3(accountStore.defaultInstanceHost!).run( GetPosts( type: PostListingType.all, sort: sortType, savedOnly: true, page: page, limit: batchSize, - auth: accountStore.defaultToken.raw, + auth: accountStore.defaultToken!.raw, ), ), ), InfiniteCommentList( fetcher: (page, batchSize, sortType) => - LemmyApiV3(accountStore.defaultInstanceHost).run( + LemmyApiV3(accountStore.defaultInstanceHost!).run( GetComments( type: CommentListingType.all, sort: sortType, savedOnly: true, page: page, limit: batchSize, - auth: accountStore.defaultToken.raw, + auth: accountStore.defaultToken!.raw, ), ), ), diff --git a/lib/pages/search_results.dart b/lib/pages/search_results.dart index b7aa4ce..280ce41 100644 --- a/lib/pages/search_results.dart +++ b/lib/pages/search_results.dart @@ -15,11 +15,9 @@ class SearchResultsPage extends HookWidget { final String query; SearchResultsPage({ - @required this.instanceHost, - @required this.query, - }) : assert(instanceHost != null), - assert(query != null), - assert(instanceHost.isNotEmpty), + required this.instanceHost, + required this.query, + }) : assert(instanceHost.isNotEmpty), assert(query.isNotEmpty); @override @@ -31,10 +29,10 @@ class SearchResultsPage extends HookWidget { bottom: TabBar( isScrollable: true, tabs: [ - Tab(text: L10n.of(context).posts), - Tab(text: L10n.of(context).comments), - Tab(text: L10n.of(context).users), - Tab(text: L10n.of(context).communities), + Tab(text: L10n.of(context)!.posts), + Tab(text: L10n.of(context)!.comments), + Tab(text: L10n.of(context)!.users), + Tab(text: L10n.of(context)!.communities), ], ), ), @@ -68,18 +66,16 @@ class _SearchResultsList extends HookWidget { final String instanceHost; const _SearchResultsList({ - @required this.type, - @required this.query, - @required this.instanceHost, - }) : assert(type != null), - assert(query != null), - assert(instanceHost != null); + required this.type, + required this.query, + required this.instanceHost, + }); @override Widget build(BuildContext context) { final accStore = useAccountsStore(); - return SortableInfiniteList( + return SortableInfiniteList( fetcher: (page, batchSize, sort) async { final s = await LemmyApiV3(instanceHost).run(Search( q: query, diff --git a/lib/pages/search_tab.dart b/lib/pages/search_tab.dart index e485b29..f749e2c 100644 --- a/lib/pages/search_tab.dart +++ b/lib/pages/search_tab.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; @@ -18,7 +19,7 @@ class SearchTab extends HookWidget { final accStore = useAccountsStore(); // null if there are no added instances final instanceHost = useState( - accStore.instances.firstWhere((_) => true, orElse: () => null), + accStore.instances.firstWhereOrNull((_) => true), ); if (instanceHost.value == null) { @@ -29,17 +30,18 @@ class SearchTab extends HookWidget { ), ); } + return Scaffold( appBar: AppBar(), body: GestureDetector( - onTapDown: (_) => primaryFocus.unfocus(), + onTapDown: (_) => primaryFocus?.unfocus(), child: ListView( padding: const EdgeInsets.symmetric(horizontal: 20), children: [ TextField( controller: searchInputController, textAlign: TextAlign.center, - decoration: InputDecoration(hintText: L10n.of(context).search), + decoration: InputDecoration(hintText: L10n.of(context)!.search), ), const SizedBox(height: 5), Row( @@ -52,7 +54,7 @@ class SearchTab extends HookWidget { Expanded( child: RadioPicker( values: accStore.instances.toList(), - groupValue: instanceHost.value, + groupValue: instanceHost.value!, onChanged: (value) => instanceHost.value = value, ), ), @@ -63,10 +65,10 @@ class SearchTab extends HookWidget { onPressed: () => goTo( context, (c) => SearchResultsPage( - instanceHost: instanceHost.value, + instanceHost: instanceHost.value!, query: searchInputController.text, )), - child: Text(L10n.of(context).search), + child: Text(L10n.of(context)!.search), ) ], ), diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index 10e3835..e5f3dbe 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -21,7 +21,7 @@ class SettingsPage extends StatelessWidget { @override Widget build(BuildContext context) => Scaffold( appBar: AppBar( - title: Text(L10n.of(context).settings), + title: Text(L10n.of(context)!.settings), ), body: ListView( children: [ @@ -66,7 +66,7 @@ class AppearanceConfigPage extends HookWidget { title: Text(describeEnum(theme)), groupValue: configStore.theme, onChanged: (selected) { - configStore.theme = selected; + if (selected != null) configStore.theme = selected; }, ), SwitchListTile( @@ -79,7 +79,7 @@ class AppearanceConfigPage extends HookWidget { const SizedBox(height: 12), const _SectionHeading('General'), ListTile( - title: Text(L10n.of(context).language), + title: Text(L10n.of(context)!.language), trailing: SizedBox( width: 120, child: RadioPicker( @@ -117,11 +117,11 @@ class AccountsConfigPage extends HookWidget { actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), - child: Text(L10n.of(context).no), + child: Text(L10n.of(context)!.no), ), TextButton( onPressed: () => Navigator.of(context).pop(true), - child: Text(L10n.of(context).yes), + child: Text(L10n.of(context)!.yes), ), ], ), @@ -142,11 +142,11 @@ class AccountsConfigPage extends HookWidget { actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), - child: Text(L10n.of(context).no), + child: Text(L10n.of(context)!.no), ), TextButton( onPressed: () => Navigator.of(context).pop(true), - child: Text(L10n.of(context).yes), + child: Text(L10n.of(context)!.yes), ), ], ), @@ -301,7 +301,7 @@ class _SectionHeading extends StatelessWidget { return Padding( padding: const EdgeInsets.only(left: 20), child: Text(text.toUpperCase(), - style: theme.textTheme.subtitle2.copyWith(color: theme.accentColor)), + style: theme.textTheme.subtitle2?.copyWith(color: theme.accentColor)), ); } } diff --git a/lib/pages/user.dart b/lib/pages/user.dart index 43545d1..60d003b 100644 --- a/lib/pages/user.dart +++ b/lib/pages/user.dart @@ -10,20 +10,16 @@ import 'write_message.dart'; /// Page showing posts, comments, and general info about a user. class UserPage extends HookWidget { - final int userId; + final int? userId; final String instanceHost; final Future _userDetails; - UserPage({@required this.userId, @required this.instanceHost}) - : assert(userId != null), - assert(instanceHost != null), - _userDetails = LemmyApiV3(instanceHost).run(GetPersonDetails( + UserPage({required this.userId, required this.instanceHost}) + : _userDetails = LemmyApiV3(instanceHost).run(GetPersonDetails( personId: userId, savedOnly: true, sort: SortType.active)); - UserPage.fromName({@required this.instanceHost, @required String username}) - : assert(instanceHost != null), - assert(username != null), - userId = null, + UserPage.fromName({required this.instanceHost, required String username}) + : userId = null, _userDetails = LemmyApiV3(instanceHost).run(GetPersonDetails( username: username, savedOnly: true, sort: SortType.active)); @@ -33,7 +29,7 @@ class UserPage extends HookWidget { final body = () { if (userDetailsSnap.hasData) { - return UserProfile.fromFullPersonView(userDetailsSnap.data); + return UserProfile.fromFullPersonView(userDetailsSnap.data!); } else if (userDetailsSnap.hasError) { return const Center(child: Text('Could not find that user.')); } else { @@ -46,11 +42,11 @@ class UserPage extends HookWidget { appBar: AppBar( actions: [ if (userDetailsSnap.hasData) ...[ - SendMessageButton(userDetailsSnap.data.personView.person), + SendMessageButton(userDetailsSnap.data!.personView.person), IconButton( icon: const Icon(Icons.share), onPressed: () => share( - userDetailsSnap.data.personView.person.actorId, + userDetailsSnap.data!.personView.person.actorId, context: context, ), ), diff --git a/lib/pages/users_list.dart b/lib/pages/users_list.dart index e60d24c..55a7b79 100644 --- a/lib/pages/users_list.dart +++ b/lib/pages/users_list.dart @@ -11,9 +11,8 @@ class UsersListPage extends StatelessWidget { final String title; final List users; - const UsersListPage({Key key, @required this.users, this.title}) - : assert(users != null), - super(key: key); + const UsersListPage({Key? key, required this.users, this.title = ''}) + : super(key: key); @override Widget build(BuildContext context) { @@ -23,7 +22,7 @@ class UsersListPage extends StatelessWidget { return Scaffold( appBar: AppBar( backgroundColor: theme.cardColor, - title: Text(title ?? ''), + title: Text(title), ), body: ListView.builder( itemBuilder: (context, i) => UsersListItem(user: users[i]), @@ -36,9 +35,7 @@ class UsersListPage extends StatelessWidget { class UsersListItem extends StatelessWidget { final PersonViewSafe user; - const UsersListItem({Key key, @required this.user}) - : assert(user != null), - super(key: key); + const UsersListItem({Key? key, required this.user}) : super(key: key); @override Widget build(BuildContext context) => ListTile( @@ -47,7 +44,7 @@ class UsersListItem extends StatelessWidget { ? Opacity( opacity: 0.5, child: MarkdownText( - user.person.bio, + user.person.bio!, instanceHost: user.instanceHost, ), ) diff --git a/lib/pages/write_message.dart b/lib/pages/write_message.dart index b0efb44..106a419 100644 --- a/lib/pages/write_message.dart +++ b/lib/pages/write_message.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:lemmy_api_client/v3.dart'; -import '../hooks/stores.dart'; +import '../hooks/logged_in_action.dart'; import '../l10n/l10n.dart'; import '../util/extensions/api.dart'; import '../widgets/markdown_mode_icon.dart'; @@ -14,16 +14,14 @@ class WriteMessagePage extends HookWidget { final String instanceHost; /// if it's non null then this page is used for edit - final PrivateMessage privateMessage; + final PrivateMessage? privateMessage; final bool _isEdit; const WriteMessagePage.send({ - @required this.recipient, - @required this.instanceHost, - }) : assert(recipient != null), - assert(instanceHost != null), - privateMessage = null, + required this.recipient, + required this.instanceHost, + }) : privateMessage = null, _isEdit = false; WriteMessagePage.edit(PrivateMessageView pmv) @@ -34,22 +32,21 @@ class WriteMessagePage extends HookWidget { @override Widget build(BuildContext context) { - final accStore = useAccountsStore(); final showFancy = useState(false); final bodyController = useTextEditingController(text: privateMessage?.content); final loading = useState(false); + final loggedInAction = useLoggedInAction(instanceHost); + final submit = _isEdit ? L10n.of(context)!.save : 'send'; + final title = _isEdit ? 'Edit message' : L10n.of(context)!.send_message; - final submit = _isEdit ? L10n.of(context).save : 'send'; - final title = _isEdit ? 'Edit message' : L10n.of(context).send_message; - - handleSubmit() async { + handleSubmit(Jwt token) async { if (_isEdit) { loading.value = true; try { final msg = await LemmyApiV3(instanceHost).run(EditPrivateMessage( - auth: accStore.defaultTokenFor(instanceHost)?.raw, - privateMessageId: privateMessage.id, + auth: token.raw, + privateMessageId: privateMessage!.id, content: bodyController.text, )); Navigator.of(context).pop(msg); @@ -65,7 +62,7 @@ class WriteMessagePage extends HookWidget { loading.value = true; try { await LemmyApiV3(instanceHost).run(CreatePrivateMessage( - auth: accStore.defaultTokenFor(instanceHost)?.raw, + auth: token.raw, content: bodyController.text, recipientId: recipient.id, )); @@ -124,7 +121,7 @@ class WriteMessagePage extends HookWidget { Align( alignment: Alignment.centerRight, child: TextButton( - onPressed: loading.value ? () {} : handleSubmit, + onPressed: loading.value ? () {} : loggedInAction(handleSubmit), child: loading.value ? const SizedBox( height: 20, diff --git a/lib/stores/accounts_store.dart b/lib/stores/accounts_store.dart index 6166f1d..1f4be0b 100644 --- a/lib/stores/accounts_store.dart +++ b/lib/stores/accounts_store.dart @@ -20,18 +20,18 @@ class AccountsStore extends ChangeNotifier { /// `tokens['instanceHost']['username']` @protected @JsonKey(defaultValue: {'lemmy.ml': {}}) - Map> tokens; + late Map> tokens; /// default account for a given instance /// map where keys are instanceHosts and values are usernames @protected @JsonKey(defaultValue: {}) - Map defaultAccounts; + late Map defaultAccounts; /// default account for the app /// It is in a form of `username@instanceHost` @protected - String defaultAccount; + String? defaultAccount; static Future load() async { final prefs = await _prefs; @@ -63,8 +63,8 @@ class AccountsStore extends ChangeNotifier { .toList() .forEach(defaultAccounts.remove); if (defaultAccount != null) { - final instance = defaultAccount.split('@')[1]; - final username = defaultAccount.split('@')[0]; + final instance = defaultAccount!.split('@')[1]; + final username = defaultAccount!.split('@')[0]; // if instance or username doesn't exist, remove if (!instances.contains(instance) || !usernamesFor(instance).contains(username)) { @@ -97,23 +97,23 @@ class AccountsStore extends ChangeNotifier { } } - String get defaultUsername { - if (defaultAccount == null) { - return null; - } + String? get defaultUsername { + // if (defaultAccount == null) { + // return null; + // } - return defaultAccount.split('@')[0]; + return defaultAccount?.split('@')[0]; } - String get defaultInstanceHost { - if (defaultAccount == null) { - return null; - } + String? get defaultInstanceHost { + // if (defaultAccount == null) { + // return null; + // } - return defaultAccount.split('@')[1]; + return defaultAccount?.split('@')[1]; } - String defaultUsernameFor(String instanceHost) { + String? defaultUsernameFor(String instanceHost) { if (isAnonymousFor(instanceHost)) { return null; } @@ -121,29 +121,29 @@ class AccountsStore extends ChangeNotifier { return defaultAccounts[instanceHost]; } - Jwt get defaultToken { + Jwt? get defaultToken { if (defaultAccount == null) { return null; } - final userTag = defaultAccount.split('@'); - return tokens[userTag[1]][userTag[0]]; + final userTag = defaultAccount!.split('@'); + return tokens[userTag[1]]?[userTag[0]]; } - Jwt defaultTokenFor(String instanceHost) { + Jwt? defaultTokenFor(String instanceHost) { if (isAnonymousFor(instanceHost)) { return null; } - return tokens[instanceHost][defaultAccounts[instanceHost]]; + return tokens[instanceHost]?[defaultAccounts[instanceHost]]; } - Jwt tokenFor(String instanceHost, String username) { + Jwt? tokenFor(String instanceHost, String username) { if (!usernamesFor(instanceHost).contains(username)) { return null; } - return tokens[instanceHost][username]; + return tokens[instanceHost]?[username]; } /// sets globally default account @@ -169,7 +169,7 @@ class AccountsStore extends ChangeNotifier { return true; } - return tokens[instanceHost].isEmpty; + return tokens[instanceHost]!.isEmpty; } /// `true` if no added instance has an account assigned to it @@ -182,7 +182,7 @@ class AccountsStore extends ChangeNotifier { /// Usernames that are assigned to a given instance Iterable usernamesFor(String instanceHost) => - tokens[instanceHost].keys; + tokens[instanceHost]?.keys ?? const Iterable.empty(); /// adds a new account /// if it's the first account ever the account is @@ -203,10 +203,11 @@ class AccountsStore extends ChangeNotifier { usernameOrEmail: usernameOrEmail, password: password, )); - final userData = - await lemmy.run(GetSite(auth: token.raw)).then((value) => value.myUser); + final userData = await lemmy + .run(GetSite(auth: token.raw)) + .then((value) => value.myUser!); - tokens[instanceHost][userData.person.name] = token.copyWith( + tokens[instanceHost]![userData.person.name] = token.copyWith( payload: token.payload.copyWith(sub: userData.person.id), ); @@ -252,7 +253,11 @@ class AccountsStore extends ChangeNotifier { } Future removeAccount(String instanceHost, String username) async { - tokens[instanceHost].remove(username); + if (!tokens.containsKey(instanceHost)) { + throw Exception("instance doesn't exist"); + } + + tokens[instanceHost]!.remove(username); await _assignDefaultAccounts(); notifyListeners(); diff --git a/lib/stores/config_store.dart b/lib/stores/config_store.dart index 7d67ac9..f27822a 100644 --- a/lib/stores/config_store.dart +++ b/lib/stores/config_store.dart @@ -15,7 +15,7 @@ class ConfigStore extends ChangeNotifier { static const prefsKey = 'v1:ConfigStore'; static final _prefs = SharedPreferences.getInstance(); - ThemeMode _theme; + late ThemeMode _theme; @JsonKey(defaultValue: ThemeMode.system) ThemeMode get theme => _theme; set theme(ThemeMode theme) { @@ -24,7 +24,7 @@ class ConfigStore extends ChangeNotifier { save(); } - bool _amoledDarkMode; + late bool _amoledDarkMode; @JsonKey(defaultValue: false) bool get amoledDarkMode => _amoledDarkMode; set amoledDarkMode(bool amoledDarkMode) { @@ -33,11 +33,11 @@ class ConfigStore extends ChangeNotifier { save(); } - Locale _locale; + Locale? _locale; // default value is set in the `load` method because json_serializable does // not accept non-literals as constant values @JsonKey(fromJson: LocaleSerde.fromJson, toJson: LocaleSerde.toJson) - Locale get locale => _locale; + Locale get locale => _locale ?? const Locale('en'); set locale(Locale locale) { _locale = locale; notifyListeners(); @@ -49,7 +49,7 @@ class ConfigStore extends ChangeNotifier { return _$ConfigStoreFromJson( jsonDecode(prefs.getString(prefsKey) ?? '{}') as Map, - ).._locale ??= const Locale('en'); + ); } Future save() async { diff --git a/lib/theme.dart b/lib/theme.dart index 4051240..30a5aba 100644 --- a/lib/theme.dart +++ b/lib/theme.dart @@ -24,7 +24,7 @@ ThemeData _themeFactory({bool dark = false, bool amoled = false}) { iconTheme: IconThemeData(color: theme.colorScheme.onSurface), textTheme: TextTheme( headline6: theme.textTheme.headline6 - .copyWith(fontSize: 20, fontWeight: FontWeight.w500), + ?.copyWith(fontSize: 20, fontWeight: FontWeight.w500), ), ), tabBarTheme: TabBarTheme( diff --git a/lib/url_launcher.dart b/lib/url_launcher.dart index b7aba66..75bda92 100644 --- a/lib/url_launcher.dart +++ b/lib/url_launcher.dart @@ -13,9 +13,9 @@ import 'util/goto.dart'; /// Decides where does a link link to. Either somewhere in-app: /// opens the correct page, or outside of the app: opens in a browser Future linkLauncher({ - @required BuildContext context, - @required String url, - @required String instanceHost, + required BuildContext context, + required String url, + required String instanceHost, }) async { push(Widget Function() builder) { goTo(context, (c) => builder()); @@ -46,8 +46,8 @@ Future linkLauncher({ final matchedInstance = match?.group(1); final rest = match?.group(2); - if (matchedInstance != null && instances.any((e) => e == match.group(1))) { - if (rest.isEmpty || rest == '/') { + if (matchedInstance != null && instances.any((e) => e == match?.group(1))) { + if (rest == null || rest.isEmpty || rest == '/') { return push(() => InstancePage(instanceHost: matchedInstance)); } final split = rest.split('/'); @@ -107,7 +107,7 @@ Future linkLauncher({ Future openInBrowser(String url) async { if (await ul.canLaunch(url)) { - return await ul.launch(url); + await ul.launch(url); } else { throw Exception(); // TODO: handle opening links to stuff in app diff --git a/lib/util/delayed_action.dart b/lib/util/delayed_action.dart index 5b15e84..a21af24 100644 --- a/lib/util/delayed_action.dart +++ b/lib/util/delayed_action.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:lemmy_api_client/v3.dart'; @@ -7,28 +6,24 @@ import '../hooks/delayed_loading.dart'; /// Executes an API action that uses [DelayedLoading], has a try-catch /// that displays a [SnackBar] when the action fails Future delayedAction({ - @required BuildContext context, - @required DelayedLoading delayedLoading, - @required String instanceHost, - @required LemmyApiQuery query, - Function(T) onSuccess, - Function(T) cleanup, + required BuildContext context, + required DelayedLoading delayedLoading, + required String instanceHost, + required LemmyApiQuery query, + void Function(T)? onSuccess, + void Function(T?)? cleanup, }) async { - assert(delayedLoading != null); - assert(instanceHost != null); - assert(query != null); - assert(context != null); - - T val; + T? val; try { delayedLoading.start(); val = await LemmyApiV3(instanceHost).run(query); - onSuccess?.call(val); + onSuccess?.call(val as T); // ignore: avoid_catches_without_on_clauses } catch (e) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text(e.toString()))); + } finally { + cleanup?.call(val); + delayedLoading.cancel(); } - cleanup?.call(val); - delayedLoading.cancel(); } diff --git a/lib/util/extensions/api.dart b/lib/util/extensions/api.dart index eaf25d8..c6f5fee 100644 --- a/lib/util/extensions/api.dart +++ b/lib/util/extensions/api.dart @@ -34,8 +34,9 @@ extension CommunityDisplayNames on CommunitySafe { extension UserDisplayNames on PersonSafe { String get displayName { - if (preferredUsername != null && preferredUsername.isNotEmpty) { - return preferredUsername; + final prefName = preferredUsername; + if (prefName != null && prefName.isNotEmpty) { + return prefName; } return '@$name'; diff --git a/lib/util/share.dart b/lib/util/share.dart index 4813fb7..038e8c3 100644 --- a/lib/util/share.dart +++ b/lib/util/share.dart @@ -6,11 +6,10 @@ import 'package:share/share.dart'; /// on platforms that do not support native sharing Future share( String text, { - String subject, - Rect sharePositionOrigin, - @required BuildContext context, + String? subject, + Rect? sharePositionOrigin, + required BuildContext context, }) async { - assert(context != null); try { return await Share.share( diff --git a/lib/widgets/about_tile.dart b/lib/widgets/about_tile.dart index fb07593..97d636a 100644 --- a/lib/widgets/about_tile.dart +++ b/lib/widgets/about_tile.dart @@ -23,7 +23,12 @@ class AboutTile extends HookWidget { return await PackageInfo.fromPlatform(); } on MissingPluginException { // when we get here it means PackageInfo does not support this platform - return PackageInfo(version: ''); + return PackageInfo( + appName: 'lemmur', + packageName: '', + version: '', + buildNumber: '', + ); } }, ); @@ -31,13 +36,16 @@ class AboutTile extends HookWidget { final changelogSnap = useMemoFuture(() => assetBundle.loadString('CHANGELOG.md')); - if (!packageInfoSnap.hasData || !changelogSnap.hasData) { - return const SizedBox.shrink(); - } - final packageInfo = packageInfoSnap.data; final changelog = changelogSnap.data; + if (!packageInfoSnap.hasData || + !changelogSnap.hasData || + packageInfo == null || + changelog == null) { + return const SizedBox.shrink(); + } + return AboutListTile( icon: const Icon(Icons.info), aboutBoxChildren: [ diff --git a/lib/widgets/avatar.dart b/lib/widgets/avatar.dart index 5b78dab..62d477c 100644 --- a/lib/widgets/avatar.dart +++ b/lib/widgets/avatar.dart @@ -6,13 +6,13 @@ import 'package:flutter/material.dart'; /// Can be disabled with `noBlank` class Avatar extends StatelessWidget { const Avatar({ - Key key, - @required this.url, + Key? key, + required this.url, this.radius = 25, this.noBlank = false, }) : super(key: key); - final String url; + final String? url; final double radius; final bool noBlank; @@ -27,7 +27,9 @@ class Avatar extends StatelessWidget { ); }(); - if (url == null) { + final imageUrl = url; + + if (imageUrl == null) { return blankWidget; } @@ -35,7 +37,7 @@ class Avatar extends StatelessWidget { child: CachedNetworkImage( height: radius * 2, width: radius * 2, - imageUrl: url, + imageUrl: imageUrl, fit: BoxFit.cover, errorWidget: (_, __, ___) => blankWidget, ), diff --git a/lib/widgets/bottom_modal.dart b/lib/widgets/bottom_modal.dart index f58f5e6..95087c0 100644 --- a/lib/widgets/bottom_modal.dart +++ b/lib/widgets/bottom_modal.dart @@ -3,16 +3,15 @@ import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; /// Should be spawned with a [showBottomModal], not routed to. class BottomModal extends StatelessWidget { - final String title; + final String? title; final EdgeInsets padding; final Widget child; const BottomModal({ this.title, this.padding = EdgeInsets.zero, - @required this.child, - }) : assert(padding != null), - assert(child != null); + required this.child, + }); @override Widget build(BuildContext context) { @@ -40,7 +39,7 @@ class BottomModal extends StatelessWidget { Padding( padding: const EdgeInsets.only(left: 70), child: Text( - title, + title!, style: theme.textTheme.subtitle2, textAlign: TextAlign.left, ), @@ -65,10 +64,10 @@ class BottomModal extends StatelessWidget { } /// Helper function for showing a [BottomModal] -Future showBottomModal({ - @required BuildContext context, - @required WidgetBuilder builder, - String title, +Future showBottomModal({ + required BuildContext context, + required WidgetBuilder builder, + String? title, EdgeInsets padding = EdgeInsets.zero, }) => showCustomModalBottomSheet( diff --git a/lib/widgets/comment.dart b/lib/widgets/comment.dart index 57304ad..b402b31 100644 --- a/lib/widgets/comment.dart +++ b/lib/widgets/comment.dart @@ -33,7 +33,7 @@ class CommentWidget extends HookWidget { final bool wasVoted; final bool canBeMarkedAsRead; final bool hideOnRead; - final int userMentionId; + final int? userMentionId; static const colors = [ Colors.pink, @@ -95,10 +95,7 @@ class CommentWidget extends HookWidget { final accStore = useAccountsStore(); final isMine = commentTree.comment.comment.creatorId == - accStore - .defaultTokenFor(commentTree.comment.instanceHost) - ?.payload - ?.sub; + accStore.defaultTokenFor(commentTree.comment.instanceHost)?.payload.sub; final selectable = useState(false); final showRaw = useState(false); final collapsed = useState(false); @@ -221,7 +218,7 @@ class CommentWidget extends HookWidget { if (isDeleted.value) { return Flexible( child: Text( - L10n.of(context).deleted_by_creator, + L10n.of(context)!.deleted_by_creator, style: const TextStyle(fontStyle: FontStyle.italic), ), ); @@ -294,7 +291,7 @@ class CommentWidget extends HookWidget { icon: Icons.more_horiz, onPressed: () => _openMoreMenu(context), delayedLoading: delayedDeletion, - tooltip: L10n.of(context).more, + tooltip: L10n.of(context)!.more, ), _SaveComment(commentTree.comment), if (!isDeleted.value && @@ -303,7 +300,7 @@ class CommentWidget extends HookWidget { TileAction( icon: Icons.reply, onPressed: loggedInAction((_) => reply()), - tooltip: L10n.of(context).reply, + tooltip: L10n.of(context)!.reply, ), TileAction( icon: Icons.arrow_upward, @@ -369,7 +366,7 @@ class CommentWidget extends HookWidget { if (isOP) _CommentTag('OP', theme.accentColor), if (comment.creator.admin) _CommentTag( - L10n.of(context).admin.toUpperCase(), + L10n.of(context)!.admin.toUpperCase(), theme.accentColor, ), if (comment.creator.banned) @@ -417,14 +414,14 @@ class CommentWidget extends HookWidget { class _MarkAsRead extends HookWidget { final CommentView commentView; - final ValueChanged onChanged; - final int userMentionId; + final ValueChanged? onChanged; + final int? userMentionId; const _MarkAsRead( this.commentView, { - @required this.onChanged, - @required this.userMentionId, - }) : assert(commentView != null); + required this.onChanged, + required this.userMentionId, + }); @override Widget build(BuildContext context) { @@ -432,18 +429,19 @@ class _MarkAsRead extends HookWidget { final comment = commentView.comment; final instanceHost = commentView.instanceHost; + final loggedInAction = useLoggedInAction(instanceHost); final isRead = useState(comment.read); final delayedRead = useDelayedLoading(); - Future handleMarkAsSeen() => delayedAction( + Future handleMarkAsSeen(Jwt token) => delayedAction( context: context, delayedLoading: delayedRead, instanceHost: instanceHost, query: MarkCommentAsRead( commentId: comment.id, read: !isRead.value, - auth: accStore.defaultTokenFor(instanceHost)?.raw, + auth: token.raw, ), onSuccess: (val) { isRead.value = val.commentView.comment.read; @@ -451,14 +449,15 @@ class _MarkAsRead extends HookWidget { }, ); - Future handleMarkMentionAsSeen() => delayedAction( + Future handleMarkMentionAsSeen(Jwt token) => + delayedAction( context: context, delayedLoading: delayedRead, instanceHost: instanceHost, query: MarkPersonMentionAsRead( - personMentionId: userMentionId, + personMentionId: userMentionId!, read: !isRead.value, - auth: accStore.defaultTokenFor(instanceHost)?.raw, + auth: token.raw, ), onSuccess: (val) { isRead.value = val.personMention.read; @@ -469,12 +468,13 @@ class _MarkAsRead extends HookWidget { return TileAction( icon: Icons.check, delayedLoading: delayedRead, - onPressed: - userMentionId != null ? handleMarkMentionAsSeen : handleMarkAsSeen, + onPressed: userMentionId != null + ? loggedInAction(handleMarkMentionAsSeen) + : loggedInAction(handleMarkAsSeen), iconColor: isRead.value ? Theme.of(context).accentColor : null, tooltip: isRead.value - ? L10n.of(context).mark_as_unread - : L10n.of(context).mark_as_read, + ? L10n.of(context)!.mark_as_unread + : L10n.of(context)!.mark_as_read, ); } } @@ -487,7 +487,7 @@ class _SaveComment extends HookWidget { @override Widget build(BuildContext context) { final loggedInAction = useLoggedInAction(comment.instanceHost); - final isSaved = useState(comment.saved ?? false); + final isSaved = useState(comment.saved); final delayed = useDelayedLoading(); handleSave(Jwt token) => delayedAction( @@ -529,7 +529,7 @@ class _CommentTag extends StatelessWidget { child: Text(text, style: TextStyle( color: textColorBasedOnBackground(bgColor), - fontSize: Theme.of(context).textTheme.bodyText1.fontSize - 5, + fontSize: Theme.of(context).textTheme.bodyText1!.fontSize! - 5, fontWeight: FontWeight.w800, )), ), diff --git a/lib/widgets/comment_section.dart b/lib/widgets/comment_section.dart index 92ef85b..9fd4ebe 100644 --- a/lib/widgets/comment_section.dart +++ b/lib/widgets/comment_section.dart @@ -26,7 +26,7 @@ class CommentSection extends HookWidget { CommentSection( List rawComments, { - @required this.postCreatorId, + required this.postCreatorId, this.sortType = CommentSortType.hot, }) : comments = CommentTree.sortList(sortType, CommentTree.fromList(rawComments)), @@ -76,7 +76,7 @@ class CommentSection extends HookWidget { }, child: Row( children: [ - Text((sortPairs[sorting.value][1] as String).tr(context)), + Text((sortPairs[sorting.value]![1] as String).tr(context)), const Icon(Icons.arrow_drop_down), ], ), diff --git a/lib/widgets/fullscreenable_image.dart b/lib/widgets/fullscreenable_image.dart index e7460f9..2093ba0 100644 --- a/lib/widgets/fullscreenable_image.dart +++ b/lib/widgets/fullscreenable_image.dart @@ -10,9 +10,9 @@ class FullscreenableImage extends StatelessWidget { final Widget child; const FullscreenableImage({ - Key key, - @required this.url, - @required this.child, + Key? key, + required this.url, + required this.child, }) : super(key: key); @override diff --git a/lib/widgets/infinite_scroll.dart b/lib/widgets/infinite_scroll.dart index 1ce4b12..75e4ffe 100644 --- a/lib/widgets/infinite_scroll.dart +++ b/lib/widgets/infinite_scroll.dart @@ -6,7 +6,7 @@ import '../hooks/ref.dart'; import 'bottom_safe.dart'; class InfiniteScrollController { - VoidCallback clear; + late VoidCallback clear; InfiniteScrollController() { usedBeforeCreation() => throw Exception( @@ -14,10 +14,6 @@ class InfiniteScrollController { clear = usedBeforeCreation; } - - void dispose() { - clear = null; - } } /// `ListView.builder` with asynchronous data fetching and no `itemCount` @@ -36,13 +32,13 @@ class InfiniteScroll extends HookWidget { /// is considered finished final Future> Function(int page, int batchSize) fetcher; - final InfiniteScrollController controller; + final InfiniteScrollController? controller; /// Widget to be added at the beginning of the list final Widget leading; /// Padding for the [ListView.builder] - final EdgeInsetsGeometry padding; + final EdgeInsetsGeometry? padding; /// Widget that will be displayed if there are no items final Widget noItems; @@ -53,13 +49,11 @@ class InfiniteScroll extends HookWidget { this.padding, this.loadingWidget = const ListTile(title: Center(child: CircularProgressIndicator())), - @required this.itemBuilder, - @required this.fetcher, + required this.itemBuilder, + required this.fetcher, this.controller, this.noItems = const SizedBox.shrink(), - }) : assert(itemBuilder != null), - assert(fetcher != null), - assert(batchSize > 0); + }) : assert(batchSize > 0); @override Widget build(BuildContext context) { @@ -69,7 +63,7 @@ class InfiniteScroll extends HookWidget { useEffect(() { if (controller != null) { - controller.clear = () { + controller?.clear = () { data.value = []; hasMore.current = true; }; @@ -82,7 +76,9 @@ class InfiniteScroll extends HookWidget { return RefreshIndicator( onRefresh: () async { - controller.clear(); + data.value = []; + hasMore.current = true; + await HapticFeedback.mediumImpact(); await Future.delayed(const Duration(seconds: 1)); }, diff --git a/lib/widgets/info_table_popup.dart b/lib/widgets/info_table_popup.dart index 261823c..6f6d15d 100644 --- a/lib/widgets/info_table_popup.dart +++ b/lib/widgets/info_table_popup.dart @@ -3,13 +3,10 @@ import 'package:flutter/material.dart'; import 'bottom_modal.dart'; void showInfoTablePopup({ - @required BuildContext context, - @required Map table, - String title, + required BuildContext context, + required Map table, + String? title, }) { - assert(context != null); - assert(table != null); - showBottomModal( context: context, title: title, diff --git a/lib/widgets/markdown_mode_icon.dart b/lib/widgets/markdown_mode_icon.dart index 402896a..a89088c 100644 --- a/lib/widgets/markdown_mode_icon.dart +++ b/lib/widgets/markdown_mode_icon.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; /// used mostly for pages where markdown editor is used /// /// brush icon is rotated to look similarly to build icon -Widget markdownModeIcon({@required bool fancy}) => fancy +Widget markdownModeIcon({required bool fancy}) => fancy ? const Icon(Icons.build) : const RotatedBox( quarterTurns: 1, diff --git a/lib/widgets/markdown_text.dart b/lib/widgets/markdown_text.dart index d48068d..acf63dd 100644 --- a/lib/widgets/markdown_text.dart +++ b/lib/widgets/markdown_text.dart @@ -15,8 +15,7 @@ class MarkdownText extends StatelessWidget { final bool selectable; const MarkdownText(this.text, - {@required this.instanceHost, this.selectable = false}) - : assert(instanceHost != null); + {required this.instanceHost, this.selectable = false}); @override Widget build(BuildContext context) { @@ -32,9 +31,10 @@ class MarkdownText extends StatelessWidget { ), code: theme.textTheme.bodyText1 // TODO: use a font from google fonts maybe? the defaults aren't very pretty - .copyWith(fontFamily: Platform.isIOS ? 'Courier' : 'monospace'), + ?.copyWith(fontFamily: Platform.isIOS ? 'Courier' : 'monospace'), ), onTapLink: (text, href, title) { + if (href == null) return; linkLauncher(context: context, url: href, instanceHost: instanceHost) .catchError( (e) => ScaffoldMessenger.of(context).showSnackBar(SnackBar( diff --git a/lib/widgets/post.dart b/lib/widgets/post.dart index 0e27866..daa6f86 100644 --- a/lib/widgets/post.dart +++ b/lib/widgets/post.dart @@ -33,7 +33,7 @@ enum MediaType { none, } -MediaType whatType(String url) { +MediaType whatType(String? url) { if (url == null || url.isEmpty) return MediaType.none; // TODO: make detection more nuanced @@ -95,13 +95,13 @@ class PostWidget extends HookWidget { Widget build(BuildContext context) { final theme = Theme.of(context); - void _openLink() => linkLauncher( - context: context, url: post.post.url, instanceHost: instanceHost); + void _openLink(String url) => + linkLauncher(context: context, url: url, instanceHost: instanceHost); final urlDomain = () { if (whatType(post.post.url) == MediaType.none) return null; - return urlHost(post.post.url); + return urlHost(post.post.url!); }(); /// assemble info section @@ -141,7 +141,7 @@ class PostWidget extends HookWidget { text: TextSpan( style: TextStyle( fontSize: 15, - color: theme.textTheme.bodyText1.color), + color: theme.textTheme.bodyText1?.color), children: [ const TextSpan( text: '!', @@ -179,10 +179,10 @@ class PostWidget extends HookWidget { text: TextSpan( style: TextStyle( fontSize: 13, - color: theme.textTheme.bodyText1.color), + color: theme.textTheme.bodyText1?.color), children: [ TextSpan( - text: L10n.of(context).by, + text: L10n.of(context)!.by, style: const TextStyle( fontWeight: FontWeight.w300), ), @@ -206,7 +206,7 @@ class PostWidget extends HookWidget { if (post.post.nsfw) const TextSpan(text: ' ยท '), if (post.post.nsfw) TextSpan( - text: L10n.of(context).nsfw, + text: L10n.of(context)!.nsfw, style: const TextStyle(color: Colors.red)), if (urlDomain != null) @@ -260,13 +260,13 @@ class PostWidget extends HookWidget { const Spacer(), InkWell( borderRadius: BorderRadius.circular(20), - onTap: _openLink, + onTap: () => _openLink(post.post.url!), child: Stack( children: [ ClipRRect( borderRadius: BorderRadius.circular(20), child: CachedNetworkImage( - imageUrl: post.post.thumbnailUrl, + imageUrl: post.post.thumbnailUrl!, width: 70, height: 70, fit: BoxFit.cover, @@ -297,11 +297,11 @@ class PostWidget extends HookWidget { return Padding( padding: const EdgeInsets.all(10), child: InkWell( - onTap: _openLink, + onTap: () => _openLink(post.post.url!), child: Container( decoration: BoxDecoration( border: Border.all( - color: Theme.of(context).iconTheme.color.withAlpha(170)), + color: Theme.of(context).iconTheme.color!.withAlpha(170)), borderRadius: BorderRadius.circular(5)), child: Padding( padding: const EdgeInsets.all(10), @@ -312,7 +312,7 @@ class PostWidget extends HookWidget { const Spacer(), Text('$urlDomain ', style: theme.textTheme.caption - .apply(fontStyle: FontStyle.italic)), + ?.apply(fontStyle: FontStyle.italic)), const Icon(Icons.launch, size: 12), ], ), @@ -322,7 +322,7 @@ class PostWidget extends HookWidget { child: Text( post.post.embedTitle ?? '', style: theme.textTheme.subtitle1 - .apply(fontWeightDelta: 2), + ?.apply(fontWeightDelta: 2), maxLines: 2, overflow: TextOverflow.ellipsis, ), @@ -330,12 +330,12 @@ class PostWidget extends HookWidget { ], ), if (post.post.embedDescription != null && - post.post.embedDescription.isNotEmpty) + post.post.embedDescription!.isNotEmpty) Row( children: [ Flexible( child: Text( - post.post.embedDescription, + post.post.embedDescription!, maxLines: 4, overflow: TextOverflow.ellipsis, ), @@ -355,9 +355,9 @@ class PostWidget extends HookWidget { assert(post.post.url != null); return FullscreenableImage( - url: post.post.url, + url: post.post.url!, child: CachedNetworkImage( - imageUrl: post.post.url, + imageUrl: post.post.url!, errorWidget: (_, __, ___) => const Icon(Icons.warning), progressIndicatorBuilder: (context, url, progress) => CircularProgressIndicator(value: progress.progress), @@ -375,7 +375,7 @@ class PostWidget extends HookWidget { Expanded( flex: 999, child: Text( - L10n.of(context).number_of_comments(post.counts.comments), + L10n.of(context)!.number_of_comments(post.counts.comments), overflow: TextOverflow.fade, softWrap: false, ), @@ -411,13 +411,13 @@ class PostWidget extends HookWidget { if (whatType(post.post.url) != MediaType.other && whatType(post.post.url) != MediaType.none) postImage() - else if (post.post.url != null && post.post.url.isNotEmpty) + else if (post.post.url != null && post.post.url!.isNotEmpty) linkPreview(), if (post.post.body != null && fullPost) Padding( padding: const EdgeInsets.all(10), child: MarkdownText( - post.post.body, + post.post.body!, instanceHost: instanceHost, selectable: true, ), @@ -446,7 +446,7 @@ class PostWidget extends HookWidget { heightFactor: 0.8, child: Padding( padding: const EdgeInsets.all(10), - child: MarkdownText(post.post.body, + child: MarkdownText(post.post.body!, instanceHost: instanceHost)), ), ), @@ -469,7 +469,7 @@ class PostWidget extends HookWidget { } else { return Padding( padding: const EdgeInsets.all(10), - child: MarkdownText(post.post.body, + child: MarkdownText(post.post.body!, instanceHost: instanceHost)); } }, @@ -489,8 +489,7 @@ class _Voting extends HookWidget { final bool wasVoted; _Voting(this.post) - : assert(post != null), - wasVoted = (post.myVote ?? VoteType.none) != VoteType.none; + : wasVoted = (post.myVote ?? VoteType.none) != VoteType.none; @override Widget build(BuildContext context) { diff --git a/lib/widgets/post_list_options.dart b/lib/widgets/post_list_options.dart index c16cd1e..c2aa9d4 100644 --- a/lib/widgets/post_list_options.dart +++ b/lib/widgets/post_list_options.dart @@ -12,8 +12,8 @@ class PostListOptions extends StatelessWidget { final bool styleButton; const PostListOptions({ - @required this.onSortChanged, - @required this.sortValue, + required this.onSortChanged, + required this.sortValue, this.styleButton = true, }) : assert(sortValue != null); diff --git a/lib/widgets/radio_picker.dart b/lib/widgets/radio_picker.dart index 29ccc15..f38df63 100644 --- a/lib/widgets/radio_picker.dart +++ b/lib/widgets/radio_picker.dart @@ -6,31 +6,29 @@ import 'bottom_modal.dart'; class RadioPicker extends StatelessWidget { final List values; final T groupValue; - final ValueChanged onChanged; + final ValueChanged? onChanged; /// Map a given value to a string for display - final String Function(T) mapValueToString; - final String title; + final String Function(T)? mapValueToString; + final String? title; /// custom button builder. When null, an OutlinedButton is used final Widget Function( - BuildContext context, String displayValue, VoidCallback onPressed) + BuildContext context, String displayValue, VoidCallback onPressed)? buttonBuilder; - final Widget trailing; + final Widget? trailing; const RadioPicker({ - Key key, - @required this.values, - @required this.groupValue, - @required this.onChanged, + Key? key, + required this.values, + required this.groupValue, + required this.onChanged, this.mapValueToString, this.buttonBuilder, this.title, this.trailing, - }) : assert(values != null), - assert(groupValue != null), - super(key: key); + }) : super(key: key); @override Widget build(BuildContext context) { @@ -63,7 +61,7 @@ class RadioPicker extends StatelessWidget { title: Text(mapValueToString(value)), onChanged: (value) => Navigator.of(context).pop(value), ), - if (trailing != null) trailing + if (trailing != null) trailing! ], ), ); diff --git a/lib/widgets/reveal_after_scroll.dart b/lib/widgets/reveal_after_scroll.dart index 1cc1edd..6b97fd8 100644 --- a/lib/widgets/reveal_after_scroll.dart +++ b/lib/widgets/reveal_after_scroll.dart @@ -14,9 +14,9 @@ class RevealAfterScroll extends HookWidget { final bool fade; const RevealAfterScroll({ - @required this.scrollController, - @required this.child, - @required this.after, + required this.scrollController, + required this.child, + required this.after, this.transition = 15, this.fade = false, }) : assert(scrollController != null), diff --git a/lib/widgets/save_post_button.dart b/lib/widgets/save_post_button.dart index d917b1a..355daab 100644 --- a/lib/widgets/save_post_button.dart +++ b/lib/widgets/save_post_button.dart @@ -14,7 +14,7 @@ class SavePostButton extends HookWidget { @override Widget build(BuildContext context) { - final isSaved = useState(post.saved ?? false); + final isSaved = useState(post.saved); final savedIcon = isSaved.value ? Icons.bookmark : Icons.bookmark_border; final loading = useDelayedLoading(); final loggedInAction = useLoggedInAction(post.instanceHost); diff --git a/lib/widgets/sortable_infinite_list.dart b/lib/widgets/sortable_infinite_list.dart index 875e050..9ce1862 100644 --- a/lib/widgets/sortable_infinite_list.dart +++ b/lib/widgets/sortable_infinite_list.dart @@ -16,21 +16,19 @@ typedef FetcherWithSorting = Future> Function( class SortableInfiniteList extends HookWidget { final FetcherWithSorting fetcher; final Widget Function(T) itemBuilder; - final InfiniteScrollController controller; - final Function onStyleChange; + final InfiniteScrollController? controller; + final Function? onStyleChange; final Widget noItems; final SortType defaultSort; const SortableInfiniteList({ - @required this.fetcher, - @required this.itemBuilder, + required this.fetcher, + required this.itemBuilder, this.controller, this.onStyleChange, - this.noItems, + this.noItems = const SizedBox.shrink(), this.defaultSort = SortType.active, - }) : assert(fetcher != null), - assert(itemBuilder != null), - assert(defaultSort != null); + }); @override Widget build(BuildContext context) { @@ -62,12 +60,12 @@ class SortableInfiniteList extends HookWidget { class InfinitePostList extends StatelessWidget { final FetcherWithSorting fetcher; - final InfiniteScrollController controller; + final InfiniteScrollController? controller; const InfinitePostList({ - @required this.fetcher, + required this.fetcher, this.controller, - }) : assert(fetcher != null); + }); Widget build(BuildContext context) => SortableInfiniteList( onStyleChange: () {}, @@ -85,12 +83,12 @@ class InfinitePostList extends StatelessWidget { class InfiniteCommentList extends StatelessWidget { final FetcherWithSorting fetcher; - final InfiniteScrollController controller; + final InfiniteScrollController? controller; const InfiniteCommentList({ - @required this.fetcher, + required this.fetcher, this.controller, - }) : assert(fetcher != null); + }); Widget build(BuildContext context) => SortableInfiniteList( itemBuilder: (comment) => CommentWidget( diff --git a/lib/widgets/tile_action.dart b/lib/widgets/tile_action.dart index 2f6c040..2344a38 100644 --- a/lib/widgets/tile_action.dart +++ b/lib/widgets/tile_action.dart @@ -9,20 +9,17 @@ class TileAction extends StatelessWidget { final IconData icon; final VoidCallback onPressed; final String tooltip; - final DelayedLoading delayedLoading; - final Color iconColor; + final DelayedLoading? delayedLoading; + final Color? iconColor; const TileAction({ - Key key, + Key? key, this.delayedLoading, this.iconColor, - @required this.icon, - @required this.onPressed, - @required this.tooltip, - }) : assert(icon != null), - assert(onPressed != null), - assert(tooltip != null), - super(key: key); + required this.icon, + required this.onPressed, + required this.tooltip, + }) : super(key: key); @override Widget build(BuildContext context) => IconButton( @@ -34,7 +31,7 @@ class TileAction extends StatelessWidget { : Icon( icon, color: iconColor ?? - Theme.of(context).iconTheme.color.withAlpha(190), + Theme.of(context).iconTheme.color?.withAlpha(190), ), splashRadius: 25, onPressed: delayedLoading?.pending ?? false ? () {} : onPressed, diff --git a/lib/widgets/user_profile.dart b/lib/widgets/user_profile.dart index 7cb4153..e20cb35 100644 --- a/lib/widgets/user_profile.dart +++ b/lib/widgets/user_profile.dart @@ -22,16 +22,13 @@ class UserProfile extends HookWidget { final String instanceHost; final int userId; - final FullPersonView _fullUserView; + final FullPersonView? _fullUserView; - const UserProfile({@required this.userId, @required this.instanceHost}) - : assert(userId != null), - assert(instanceHost != null), - _fullUserView = null; + const UserProfile({required this.userId, required this.instanceHost}) + : _fullUserView = null; - UserProfile.fromFullPersonView(this._fullUserView) - : assert(_fullUserView != null), - userId = _fullUserView.personView.person.id, + UserProfile.fromFullPersonView(FullPersonView this._fullUserView) + : userId = _fullUserView.personView.person.id, instanceHost = _fullUserView.instanceHost; @override @@ -62,8 +59,8 @@ class UserProfile extends HookWidget { ]), ); } - - final userView = userDetailsSnap.data.personView; + final fullPersonView = userDetailsSnap.data!; + final userView = fullPersonView.personView; return DefaultTabController( length: 3, @@ -83,8 +80,8 @@ class UserProfile extends HookWidget { color: theme.cardColor, child: TabBar( tabs: [ - Tab(text: L10n.of(context).posts), - Tab(text: L10n.of(context).comments), + Tab(text: L10n.of(context)!.posts), + Tab(text: L10n.of(context)!.comments), const Tab(text: 'About'), ], ), @@ -119,7 +116,7 @@ class UserProfile extends HookWidget { )) .then((val) => val.comments), ), - _AboutTab(userDetailsSnap.data), + _AboutTab(fullPersonView), ]), ), ); @@ -146,9 +143,9 @@ class _UserOverview extends HookWidget { if (userView.person.banner != null) // TODO: for some reason doesnt react to presses FullscreenableImage( - url: userView.person.banner, + url: userView.person.banner!, child: CachedNetworkImage( - imageUrl: userView.person.banner, + imageUrl: userView.person.banner!, errorWidget: (_, __, ___) => const SizedBox.shrink(), ), ) @@ -207,9 +204,9 @@ class _UserOverview extends HookWidget { child: ClipRRect( borderRadius: const BorderRadius.all(Radius.circular(12)), child: FullscreenableImage( - url: userView.person.avatar, + url: userView.person.avatar!, child: CachedNetworkImage( - imageUrl: userView.person.avatar, + imageUrl: userView.person.avatar!, errorWidget: (_, __, ___) => const SizedBox.shrink(), ), ), @@ -255,7 +252,7 @@ class _UserOverview extends HookWidget { ), const SizedBox(width: 4), Text( - L10n.of(context) + L10n.of(context)! .number_of_posts(userView.counts.postCount), style: TextStyle(color: colorOnTopOfAccentColor), ), @@ -273,7 +270,7 @@ class _UserOverview extends HookWidget { ), const SizedBox(width: 4), Text( - L10n.of(context) + L10n.of(context)! .number_of_comments(userView.counts.commentCount), style: TextStyle(color: colorOnTopOfAccentColor), ), @@ -338,7 +335,7 @@ class _AboutTab extends HookWidget { child: const Divider(), ); - communityTile(String name, String icon, int id) => ListTile( + communityTile(String name, String? icon, int id) => ListTile( dense: true, onTap: () => goToCommunity.byId(context, instanceHost, id), title: Text('!$name'), @@ -371,7 +368,7 @@ class _AboutTab extends HookWidget { if (userDetails.personView.person.bio != null) ...[ Padding( padding: wallPadding, - child: MarkdownText(userDetails.personView.person.bio, + child: MarkdownText(userDetails.personView.person.bio!, instanceHost: instanceHost)), divider, ], @@ -380,7 +377,7 @@ class _AboutTab extends HookWidget { title: Center( child: Text( 'Moderates:', - style: theme.textTheme.headline6.copyWith(fontSize: 18), + style: theme.textTheme.headline6?.copyWith(fontSize: 18), ), ), ), @@ -396,7 +393,7 @@ class _AboutTab extends HookWidget { title: Center( child: Text( 'Subscribed:', - style: theme.textTheme.headline6.copyWith(fontSize: 18), + style: theme.textTheme.headline6?.copyWith(fontSize: 18), ), ), ), diff --git a/lib/widgets/write_comment.dart b/lib/widgets/write_comment.dart index 7044f2f..b5e349a 100644 --- a/lib/widgets/write_comment.dart +++ b/lib/widgets/write_comment.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:lemmur/hooks/logged_in_action.dart'; import 'package:lemmy_api_client/v3.dart'; import '../hooks/delayed_loading.dart'; -import '../hooks/stores.dart'; import '../l10n/l10n.dart'; import 'markdown_mode_icon.dart'; import 'markdown_text.dart'; @@ -13,19 +13,20 @@ import 'markdown_text.dart'; /// or `null` if cancelled class WriteComment extends HookWidget { final Post post; - final Comment comment; + final Comment? comment; const WriteComment.toPost(this.post) : comment = null; - const WriteComment.toComment({@required this.comment, @required this.post}) - : assert(comment != null), - assert(post != null); + const WriteComment.toComment({ + required Comment this.comment, + required this.post, + }); @override Widget build(BuildContext context) { final controller = useTextEditingController(); final showFancy = useState(false); final delayed = useDelayedLoading(); - final accStore = useAccountsStore(); + final loggedInAction = useLoggedInAction(post.instanceHost); final preview = () { final body = () { @@ -38,27 +39,21 @@ class WriteComment extends HookWidget { ); }(); - if (post != null) { - return Column( - children: [ - SelectableText( - post.name, - style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600), - ), - const SizedBox(height: 4), - body, - ], - ); - } - - return body; + return Column( + children: [ + SelectableText( + post.name, + style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600), + ), + const SizedBox(height: 4), + body, + ], + ); }(); - handleSubmit() async { + handleSubmit(Jwt token) async { final api = LemmyApiV3(post.instanceHost); - final token = accStore.defaultTokenFor(post.instanceHost); - delayed.start(); try { final res = await api.run(CreateComment( @@ -119,10 +114,11 @@ class WriteComment extends HookWidget { mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( - onPressed: delayed.pending ? () {} : handleSubmit, + onPressed: + delayed.pending ? () {} : loggedInAction(handleSubmit), child: delayed.loading ? const CircularProgressIndicator() - : Text(L10n.of(context).post), + : Text(L10n.of(context)!.post), ) ], ), diff --git a/pubspec.lock b/pubspec.lock index 4cb7bb5..207920b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -105,7 +105,7 @@ packages: name: cached_network_image url: "https://pub.dartlang.org" source: hosted - version: "2.5.1" + version: "3.0.0" characters: dependency: transitive description: @@ -168,14 +168,14 @@ packages: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "3.0.1" cupertino_icons: dependency: "direct main" description: name: cupertino_icons url: "https://pub.dartlang.org" source: hosted - version: "0.1.3" + version: "1.0.2" dart_style: dependency: transitive description: @@ -222,14 +222,14 @@ packages: name: flutter_blurhash url: "https://pub.dartlang.org" source: hosted - version: "0.5.0" + version: "0.6.0" flutter_cache_manager: dependency: transitive description: name: flutter_cache_manager url: "https://pub.dartlang.org" source: hosted - version: "2.1.2" + version: "3.0.1" flutter_hooks: dependency: "direct main" description: @@ -262,14 +262,14 @@ packages: name: flutter_plugin_android_lifecycle url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.1" flutter_speed_dial: dependency: "direct main" description: name: flutter_speed_dial url: "https://pub.dartlang.org" source: hosted - version: "1.2.5" + version: "3.0.5" flutter_test: dependency: "direct dev" description: flutter @@ -293,7 +293,7 @@ packages: name: fuzzy url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "0.4.0-nullsafety.0" glob: dependency: transitive description: @@ -342,7 +342,7 @@ packages: name: image_picker url: "https://pub.dartlang.org" source: hosted - version: "0.7.3" + version: "0.7.4" image_picker_for_web: dependency: transitive description: @@ -356,7 +356,7 @@ packages: name: image_picker_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.1" intl: dependency: "direct main" description: @@ -398,7 +398,7 @@ packages: name: latinize url: "https://pub.dartlang.org" source: hosted - version: "0.0.2" + version: "0.1.0-nullsafety.0" lemmy_api_client: dependency: "direct main" description: @@ -433,7 +433,7 @@ packages: name: matrix4_transform url: "https://pub.dartlang.org" source: hosted - version: "1.1.7" + version: "2.0.0" meta: dependency: transitive description: @@ -454,7 +454,7 @@ packages: name: modal_bottom_sheet url: "https://pub.dartlang.org" source: hosted - version: "1.0.0+1" + version: "2.0.0" nested: dependency: transitive description: @@ -468,7 +468,7 @@ packages: name: octo_image url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "1.0.0+1" package_config: dependency: transitive description: @@ -482,7 +482,7 @@ packages: name: package_info url: "https://pub.dartlang.org" source: hosted - version: "0.4.3+4" + version: "2.0.0" path: dependency: transitive description: @@ -517,7 +517,7 @@ packages: name: path_provider_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.1" path_provider_windows: dependency: transitive description: @@ -538,14 +538,14 @@ packages: name: petitparser url: "https://pub.dartlang.org" source: hosted - version: "4.0.2" + version: "4.1.0" photo_view: dependency: "direct main" description: name: photo_view url: "https://pub.dartlang.org" source: hosted - version: "0.10.3" + version: "0.11.1" platform: dependency: transitive description: @@ -559,7 +559,7 @@ packages: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "2.0.0" pool: dependency: transitive description: @@ -580,7 +580,7 @@ packages: name: provider url: "https://pub.dartlang.org" source: hosted - version: "4.3.3" + version: "5.0.0" pub_semver: dependency: transitive description: @@ -601,7 +601,7 @@ packages: name: rxdart url: "https://pub.dartlang.org" source: hosted - version: "0.25.0" + version: "0.26.0" share: dependency: "direct main" description: @@ -615,28 +615,42 @@ packages: name: shared_preferences url: "https://pub.dartlang.org" source: hosted - version: "0.5.7+3" + version: "2.0.5" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" shared_preferences_macos: dependency: transitive description: name: shared_preferences_macos url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+11" + version: "2.0.0" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.0.0" shared_preferences_web: dependency: transitive description: name: shared_preferences_web url: "https://pub.dartlang.org" source: hosted - version: "0.1.2+7" + version: "2.0.0" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" shelf: dependency: transitive description: @@ -739,7 +753,7 @@ packages: name: timeago url: "https://pub.dartlang.org" source: hosted - version: "2.0.30" + version: "3.0.2" timing: dependency: transitive description: @@ -760,42 +774,42 @@ packages: name: url_launcher url: "https://pub.dartlang.org" source: hosted - version: "5.7.10" + version: "6.0.3" url_launcher_linux: dependency: transitive description: name: url_launcher_linux url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+4" + version: "2.0.0" url_launcher_macos: dependency: transitive description: name: url_launcher_macos url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+9" + version: "2.0.0" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.9" + version: "2.0.2" url_launcher_web: dependency: transitive description: name: url_launcher_web url: "https://pub.dartlang.org" source: hosted - version: "0.1.5+3" + version: "2.0.0" url_launcher_windows: dependency: transitive description: name: url_launcher_windows url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+3" + version: "2.0.0" uuid: dependency: transitive description: @@ -844,7 +858,7 @@ packages: name: xml url: "https://pub.dartlang.org" source: hosted - version: "5.0.2" + version: "5.1.0" yaml: dependency: transitive description: @@ -854,4 +868,4 @@ packages: version: "3.1.0" sdks: dart: ">=2.12.0 <3.0.0" - flutter: ">=1.24.0-10" + flutter: ">=1.24.0-10.2.pre" diff --git a/pubspec.yaml b/pubspec.yaml index f912715..361f91a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,34 +18,34 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev version: 0.4.1+14 environment: - sdk: ">=2.7.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: # widgets - flutter_speed_dial: ^1.2.5 - photo_view: ^0.10.2 + flutter_speed_dial: ^3.0.5 + photo_view: ^0.11.1 markdown: ^4.0.0 flutter_markdown: ^0.6.1 - cached_network_image: ^2.2.0+1 - modal_bottom_sheet: ^1.0.0+1 + cached_network_image: ^3.0.0 + modal_bottom_sheet: ^2.0.0 # native share: ^2.0.1 - url_launcher: ^5.5.1 - shared_preferences: ">=0.5.0 <2.0.0" - package_info: ^0.4.3 - image_picker: ^0.7.3 + url_launcher: ^6.0.3 + shared_preferences: ^2.0.5 + package_info: ^2.0.0 + image_picker: ^0.7.4 # state management flutter_hooks: ^0.16.0 - provider: ^4.3.1 + provider: ^5.0.0 # utils - timeago: ^2.0.27 - fuzzy: <1.0.0 + timeago: ^3.0.2 + fuzzy: ^0.4.0-nullsafety.0 lemmy_api_client: ^0.14.0 intl: ^0.17.0 - matrix4_transform: ^1.1.7 + matrix4_transform: ^2.0.0 json_annotation: ^4.0.1 flutter: @@ -55,7 +55,7 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^0.1.3 + cupertino_icons: ^1.0.2 dev_dependencies: flutter_test: diff --git a/scripts/gen_l10n_from_string.dart b/scripts/gen_l10n_from_string.dart index 720596d..c3f95b3 100644 --- a/scripts/gen_l10n_from_string.dart +++ b/scripts/gen_l10n_from_string.dart @@ -30,7 +30,7 @@ ${keys.map((key) => " static const $key = '$key';").join('\n')} extension L10nFromString on String { String tr(BuildContext context) { switch (this) { -${keysWithoutVariables.map((key) => " case L10nStrings.$key:\n return L10n.of(context).$key;").join('\n')} +${keysWithoutVariables.map((key) => " case L10nStrings.$key:\n return L10n.of(context)!.$key;").join('\n')} default: return this;