diff --git a/.github/workflows/pull_request_tests.yml b/.github/workflows/pull_request_tests.yml index 7f2ac700..0245df97 100644 --- a/.github/workflows/pull_request_tests.yml +++ b/.github/workflows/pull_request_tests.yml @@ -1,11 +1,31 @@ name: Pull request tests -permissions: - contents: write - on: pull_request jobs: + dart-analysis: + name: Dart analysis + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version-file: pubspec.yaml + + - name: Generate source files + run: | + flutter gen-l10n + dart run build_runner build + + # Switch back once https://github.com/dart-lang/sdk/issues/60236 is released + # - name: Analyze Dart + # uses: zgosalvez/github-actions-analyze-dart@v3 + - name: Analyze Dart + run: dart format -o none --set-exit-if-changed . + test-android-build: name: Test android build runs-on: ubuntu-latest diff --git a/analysis_options.yaml b/analysis_options.yaml index 366389ad..5472808d 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,29 +1,37 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. +include: package:very_good_analysis/analysis_options.yaml -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutter_lints/flutter.yaml - -linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at https://dart.dev/lints. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. - rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule +formatter: + # Keep formatting consistent by automating trailing commas. + trailing_commas: automate analyzer: + exclude: + - lib/**.g.dart + errors: + # Needed for build runner annotations (i.e., freezed, json_serializable). invalid_annotation_target: ignore + # We don't need to document every single public member. + public_member_api_docs: ignore + # There is no auto fix for this. + sort_pub_dependencies: ignore + + # Ignore for now. + avoid_dynamic_calls: ignore + avoid_catches_without_on_clauses: ignore + avoid_positional_boolean_parameters: ignore + argument_type_not_assignable: ignore + unawaited_futures: ignore + discarded_futures: ignore + parameter_assignments: ignore + lines_longer_than_80_chars: ignore + for_in_of_invalid_type: ignore + not_iterable_spread: ignore + deprecated_member_use: ignore + join_return_with_assignment: ignore + avoid_equals_and_hash_code_on_mutable_classes: ignore + return_of_invalid_type_from_closure: ignore + not_map_spread: ignore + unused_local_variable: ignore + non_bool_condition: ignore + invalid_assignment: ignore diff --git a/l10n.yaml b/l10n.yaml index 0cdc92d1..15338f2d 100644 --- a/l10n.yaml +++ b/l10n.yaml @@ -1,4 +1,3 @@ arb-dir: lib/l10n template-arb-file: app_en.arb output-localization-file: app_localizations.dart -synthetic-package: false diff --git a/lib/main.dart b/lib/main.dart index 76a04996..01156497 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,10 +2,12 @@ import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:interstellar/src/app.dart'; import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/controller/database/database.dart'; -import 'package:interstellar/src/utils/http_client.dart'; +import 'package:interstellar/src/init_push_notifications.dart'; import 'package:interstellar/src/utils/globals.dart'; +import 'package:interstellar/src/utils/http_client.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/markdown/drafts_controller.dart'; import 'package:media_kit/media_kit.dart'; @@ -13,9 +15,6 @@ import 'package:package_info_plus/package_info_plus.dart'; import 'package:provider/provider.dart'; import 'package:window_manager/window_manager.dart'; -import 'src/app.dart'; -import 'src/init_push_notifications.dart'; - void main(List args) async { WidgetsFlutterBinding.ensureInitialized(); MediaKit.ensureInitialized(); @@ -35,7 +34,7 @@ void main(List args) async { .reduce((a, b) => Size(min(a.width, b.width), min(a.height, b.height))); final minWindowSize = screenSize / 8; - WindowOptions windowOptions = WindowOptions(minimumSize: minWindowSize); + final windowOptions = WindowOptions(minimumSize: minWindowSize); windowManager.waitUntilReadyToShow(windowOptions, () async { await windowManager.show(); diff --git a/lib/src/api/api.dart b/lib/src/api/api.dart index 1005f2f3..90923e3b 100644 --- a/lib/src/api/api.dart +++ b/lib/src/api/api.dart @@ -1,9 +1,9 @@ import 'package:interstellar/src/api/bookmark.dart'; import 'package:interstellar/src/api/client.dart'; import 'package:interstellar/src/api/comments.dart'; -import 'package:interstellar/src/api/domains.dart'; -import 'package:interstellar/src/api/community_moderation.dart'; import 'package:interstellar/src/api/community.dart'; +import 'package:interstellar/src/api/community_moderation.dart'; +import 'package:interstellar/src/api/domains.dart'; import 'package:interstellar/src/api/feed.dart'; import 'package:interstellar/src/api/images.dart'; import 'package:interstellar/src/api/messages.dart'; @@ -18,6 +18,22 @@ import 'package:interstellar/src/utils/globals.dart'; import 'package:interstellar/src/utils/utils.dart'; class API { + API(this.client) + : comments = APIComments(client), + domains = MbinAPIDomains(client), + threads = APIThreads(client), + community = APICommunity(client), + communityModeration = APICommunityModeration(client), + feed = APIFeed(client), + messages = APIMessages(client), + moderation = APIModeration(client), + notifications = APINotifications(client), + microblogs = MbinAPIMicroblogs(client), + search = APISearch(client), + users = APIUsers(client), + bookmark = APIBookmark(client), + images = APIImages(client); + final ServerClient client; final APIComments comments; @@ -34,22 +50,6 @@ class API { final APIUsers users; final APIBookmark bookmark; final APIImages images; - - API(this.client) - : comments = APIComments(client), - domains = MbinAPIDomains(client), - threads = APIThreads(client), - community = APICommunity(client), - communityModeration = APICommunityModeration(client), - feed = APIFeed(client), - messages = APIMessages(client), - moderation = APIModeration(client), - notifications = APINotifications(client), - microblogs = MbinAPIMicroblogs(client), - search = APISearch(client), - users = APIUsers(client), - bookmark = APIBookmark(client), - images = APIImages(client); } Future getServerSoftware(String server) async { @@ -59,7 +59,7 @@ Future getServerSoftware(String server) async { try { return ServerSoftware.values.byName( - ((response.bodyJson['software'] as JsonMap)['name'] as String) + ((response.bodyJson['software']! as JsonMap)['name']! as String) .toLowerCase(), ); } catch (_) { diff --git a/lib/src/api/bookmark.dart b/lib/src/api/bookmark.dart index b89f27a3..55b52176 100644 --- a/lib/src/api/bookmark.dart +++ b/lib/src/api/bookmark.dart @@ -1,10 +1,10 @@ import 'package:interstellar/src/api/client.dart'; import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/models/bookmark_list.dart'; +import 'package:interstellar/src/models/comment.dart'; import 'package:interstellar/src/models/post.dart'; import 'package:interstellar/src/utils/models.dart'; import 'package:interstellar/src/utils/utils.dart'; -import 'package:interstellar/src/models/comment.dart'; enum BookmarkListSubject { thread, @@ -34,10 +34,10 @@ enum BookmarkListSubject { } class APIBookmark { - final ServerClient client; - APIBookmark(this.client); + final ServerClient client; + Future<(List, String?)> list({String? list, String? page}) async { switch (client.software) { case ServerSoftware.mbin: @@ -47,10 +47,10 @@ class APIBookmark { final response = await client.get(path, queryParams: query); final json = response.bodyJson; - final itemList = json['items'] as List; + final itemList = json['items']! as List; final items = itemList .map((item) { - var itemType = item['itemType']; + final itemType = item['itemType']; if (itemType == 'entry') { return PostModel.fromMbinEntry(item as JsonMap); } else if (itemType == 'post') { @@ -65,7 +65,7 @@ class APIBookmark { return ( items, - mbinCalcNextPaginationPage(json['pagination'] as JsonMap), + mbinCalcNextPaginationPage(json['pagination']! as JsonMap), ); case ServerSoftware.lemmy: @@ -86,13 +86,13 @@ class APIBookmark { final postJson = postResponse.bodyJson; postJson['next_page'] = lemmyCalcNextIntPage( - postJson['posts'] as List, + postJson['posts']! as List, page, ); final commentJson = commentResponse.bodyJson; commentJson['next_page'] = lemmyCalcNextIntPage( - commentJson['comments'] as List, + commentJson['comments']! as List, page, ); @@ -189,7 +189,7 @@ class APIBookmark { final response = await client.put(path); - return optionalStringList((response.bodyJson['bookmarks'])); + return optionalStringList(response.bodyJson['bookmarks']); case ServerSoftware.lemmy: final path = switch (subjectType) { @@ -246,7 +246,7 @@ class APIBookmark { final response = await client.put(path); - return optionalStringList((response.bodyJson['bookmarks'])); + return optionalStringList(response.bodyJson['bookmarks']); case ServerSoftware.lemmy: throw Exception('Bookmark lists not on Lemmy'); @@ -266,7 +266,7 @@ class APIBookmark { final response = await client.delete(path); - return optionalStringList((response.bodyJson['bookmarks'])); + return optionalStringList(response.bodyJson['bookmarks']); case ServerSoftware.lemmy: final path = switch (subjectType) { @@ -323,7 +323,7 @@ class APIBookmark { final response = await client.delete(path); - return optionalStringList((response.bodyJson['bookmarks'])); + return optionalStringList(response.bodyJson['bookmarks']); case ServerSoftware.lemmy: throw Exception('Bookmark lists not on Lemmy'); diff --git a/lib/src/api/client.dart b/lib/src/api/client.dart index 631ffeeb..6967fa92 100644 --- a/lib/src/api/client.dart +++ b/lib/src/api/client.dart @@ -5,17 +5,17 @@ import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/utils/utils.dart'; class ServerClient { - http.Client httpClient; - ServerSoftware software; - String domain; - List<(String, int)>? _langCodeIdPairs; - ServerClient({ required this.httpClient, required this.software, required this.domain, }); + http.Client httpClient; + ServerSoftware software; + String domain; + List<(String, int)>? _langCodeIdPairs; + Future get( String path, { Map? headers, @@ -65,7 +65,7 @@ class ServerClient { JsonMap? body, Map? queryParams, }) async { - var request = http.Request( + final request = http.Request( method, Uri.https( domain, @@ -80,7 +80,7 @@ class ServerClient { } if (headers != null) request.headers.addAll(headers); - return await sendRequest(request); + return sendRequest(request); } Future sendRequest(http.BaseRequest request) async { @@ -98,7 +98,7 @@ class ServerClient { Map.from( Map.fromEntries( queryParams.entries.where( - (e) => (e.value != null && e.value!.isNotEmpty), + (e) => e.value != null && e.value!.isNotEmpty, ), ), ); @@ -133,7 +133,7 @@ class ServerClient { final json = response.bodyJson; - allLanguages = json['all_languages'] as List; + allLanguages = json['all_languages']! as List; case ServerSoftware.piefed: final response = await get('/site'); @@ -141,7 +141,7 @@ class ServerClient { final json = response.bodyJson; allLanguages = - (json['site'] as JsonMap)['all_languages'] as List; + (json['site']! as JsonMap)['all_languages']! as List; } _langCodeIdPairs = allLanguages diff --git a/lib/src/api/comments.dart b/lib/src/api/comments.dart index c8397821..179468af 100644 --- a/lib/src/api/comments.dart +++ b/lib/src/api/comments.dart @@ -1,5 +1,5 @@ -import 'package:http/http.dart' as http; import 'package:flutter/widgets.dart'; +import 'package:http/http.dart' as http; import 'package:http_parser/http_parser.dart'; import 'package:image_picker/image_picker.dart'; import 'package:interstellar/src/api/client.dart'; @@ -56,17 +56,20 @@ SelectionMenu commentSortSelect(BuildContext context) => ), ]); -const _postTypeMbin = {PostType.thread: 'entry', PostType.microblog: 'posts'}; -const _postTypeMbinComment = { +const Map _postTypeMbin = { + PostType.thread: 'entry', + PostType.microblog: 'posts', +}; +const Map _postTypeMbinComment = { PostType.thread: 'comments', PostType.microblog: 'post-comments', }; class APIComments { - final ServerClient client; - APIComments(this.client); + final ServerClient client; + Future list( PostType postType, int postId, { @@ -144,7 +147,7 @@ class APIComments { final json = response.bodyJson; json['next_page'] = lemmyCalcNextIntPage( - json['comments'] as List, + json['comments']! as List, page, ); @@ -186,10 +189,10 @@ class APIComments { final response = await client.get(path, queryParams: query); return CommentModel.fromLemmy( - (response.bodyJson['comments'] as List).firstWhere( + (response.bodyJson['comments']! as List).firstWhere( (item) => item['comment']['id'] == commentId, ), - possibleChildrenJson: response.bodyJson['comments'] as List, + possibleChildrenJson: response.bodyJson['comments']! as List, langCodeIdPairs: await client.languageCodeIdPairs(), ); @@ -200,7 +203,7 @@ class APIComments { final response = await client.get(path, queryParams: query); return CommentModel.fromPiefed( - (response.bodyJson['comments'] as List).firstWhere( + (response.bodyJson['comments']! as List).firstWhere( (item) => item['comment']['id'] == commentId, ), langCodeIdPairs: await client.languageCodeIdPairs(), @@ -234,7 +237,7 @@ class APIComments { ); return CommentModel.fromLemmy( - response.bodyJson['comment_view'] as JsonMap, + response.bodyJson['comment_view']! as JsonMap, langCodeIdPairs: await client.languageCodeIdPairs(), ); @@ -243,15 +246,11 @@ class APIComments { final response = await client.post( path, - body: { - 'comment_id': commentId, - 'score': newScore, - if (emoji != null) 'emoji': emoji, - }, + body: {'comment_id': commentId, 'score': newScore, 'emoji': ?emoji}, ); return CommentModel.fromPiefed( - response.bodyJson['comment_view'] as JsonMap, + response.bodyJson['comment_view']! as JsonMap, langCodeIdPairs: await client.languageCodeIdPairs(), ); } @@ -278,8 +277,8 @@ class APIComments { PostType postType, int postId, String body, { - int? parentCommentId, required String lang, + int? parentCommentId, XFile? image, String? alt, bool isAdult = false, @@ -335,7 +334,7 @@ class APIComments { ); return CommentModel.fromLemmy( - response.bodyJson['comment_view'] as JsonMap, + response.bodyJson['comment_view']! as JsonMap, langCodeIdPairs: await client.languageCodeIdPairs(), ); @@ -347,13 +346,13 @@ class APIComments { body: { 'body': body, 'post_id': postId, - if (parentCommentId != null) 'parent_id': parentCommentId, + 'parent_id': ?parentCommentId, 'language_id': await client.languageIdFromCode(lang), }, ); return CommentModel.fromPiefed( - response.bodyJson['comment_view'] as JsonMap, + response.bodyJson['comment_view']! as JsonMap, langCodeIdPairs: await client.languageCodeIdPairs(), ); } @@ -381,7 +380,7 @@ class APIComments { ); return CommentModel.fromLemmy( - response.bodyJson['comment_view'] as JsonMap, + response.bodyJson['comment_view']! as JsonMap, langCodeIdPairs: await client.languageCodeIdPairs(), ); @@ -394,7 +393,7 @@ class APIComments { ); return CommentModel.fromPiefed( - response.bodyJson['comment_view'] as JsonMap, + response.bodyJson['comment_view']! as JsonMap, langCodeIdPairs: await client.languageCodeIdPairs(), ); } diff --git a/lib/src/api/community.dart b/lib/src/api/community.dart index 23ad53d8..737d3f52 100644 --- a/lib/src/api/community.dart +++ b/lib/src/api/community.dart @@ -75,10 +75,10 @@ enum APIExploreSort { } class APICommunity { - final ServerClient client; - APICommunity(this.client); + final ServerClient client; + Future list({ String? page, ExploreFilter? filter, @@ -132,7 +132,7 @@ class APICommunity { final json = response.bodyJson; json['next_page'] = lemmyCalcNextIntPage( - json['communities'] as List, + json['communities']! as List, page, ); @@ -162,7 +162,7 @@ class APICommunity { final json = response.bodyJson; json['next_page'] = lemmyCalcNextIntPage( - json['communities'] as List, + json['communities']! as List, page, ); @@ -234,7 +234,7 @@ class APICommunity { final response = await client.get(path, queryParams: query); return DetailedCommunityModel.fromLemmy( - response.bodyJson['community_view'] as JsonMap, + response.bodyJson['community_view']! as JsonMap, ); case ServerSoftware.piefed: @@ -258,17 +258,17 @@ class APICommunity { case ServerSoftware.lemmy: const path = '/community'; - final query = {'name': communityName.toString()}; + final query = {'name': communityName}; final response = await client.get(path, queryParams: query); return DetailedCommunityModel.fromLemmy( - response.bodyJson['community_view'] as JsonMap, + response.bodyJson['community_view']! as JsonMap, ); case ServerSoftware.piefed: const path = '/community'; - final query = {'name': communityName.toString()}; + final query = {'name': communityName}; final response = await client.get(path, queryParams: query); @@ -295,7 +295,7 @@ class APICommunity { ); return DetailedCommunityModel.fromLemmy( - response.bodyJson['community_view'] as JsonMap, + response.bodyJson['community_view']! as JsonMap, ); case ServerSoftware.piefed: @@ -328,7 +328,7 @@ class APICommunity { ); return DetailedCommunityModel.fromLemmy( - response.bodyJson['community_view'] as JsonMap, + response.bodyJson['community_view']! as JsonMap, ); case ServerSoftware.piefed: diff --git a/lib/src/api/community_moderation.dart b/lib/src/api/community_moderation.dart index 010cc9f1..72bf6f72 100644 --- a/lib/src/api/community_moderation.dart +++ b/lib/src/api/community_moderation.dart @@ -6,10 +6,10 @@ import 'package:interstellar/src/utils/utils.dart'; enum ReportStatus { any, approved, pending, rejected } class APICommunityModeration { - final ServerClient client; - APICommunityModeration(this.client); + final ServerClient client; + Future listReports( int communityId, { String? page, @@ -80,7 +80,7 @@ class APICommunityModeration { throw Exception('List banned users not allowed on lemmy'); case ServerSoftware.piefed: - final path = '/community/moderate/bans'; + const path = '/community/moderate/bans'; final query = {'community_id': communityId.toString(), 'page': page}; @@ -111,7 +111,7 @@ class APICommunityModeration { throw Exception('Ban update not implemented on Lemmy yet'); case ServerSoftware.piefed: - final path = '/community/moderate/ban'; + const path = '/community/moderate/ban'; final body = { 'community_id': communityId, 'user_id': userId, @@ -138,7 +138,7 @@ class APICommunityModeration { throw Exception('Ban update not implemented on Lemmy yet'); case ServerSoftware.piefed: - final path = '/community/moderate/unban'; + const path = '/community/moderate/unban'; final body = {'community_id': communityId, 'user_id': userId}; @@ -157,7 +157,7 @@ class APICommunityModeration { }) async { switch (client.software) { case ServerSoftware.mbin: - final path = '/moderate/magazine/new'; + const path = '/moderate/magazine/new'; final response = await client.post( path, @@ -187,7 +187,7 @@ class APICommunityModeration { ); return DetailedCommunityModel.fromLemmy( - response.bodyJson['community_view'] as JsonMap, + response.bodyJson['community_view']! as JsonMap, ); case ServerSoftware.piefed: diff --git a/lib/src/api/domains.dart b/lib/src/api/domains.dart index db9c2bf9..32fef2fd 100644 --- a/lib/src/api/domains.dart +++ b/lib/src/api/domains.dart @@ -3,10 +3,10 @@ import 'package:interstellar/src/models/domain.dart'; import 'package:interstellar/src/screens/explore/explore_screen.dart'; class MbinAPIDomains { - final ServerClient client; - MbinAPIDomains(this.client); + final ServerClient client; + Future list({ String? page, ExploreFilter? filter, diff --git a/lib/src/api/feed.dart b/lib/src/api/feed.dart index a6ed4c2b..aec0d82b 100644 --- a/lib/src/api/feed.dart +++ b/lib/src/api/feed.dart @@ -3,10 +3,10 @@ import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/models/feed.dart'; class APIFeed { - final ServerClient client; - APIFeed(this.client); + final ServerClient client; + Future list({ bool mineOnly = false, bool includeCommunities = false, diff --git a/lib/src/api/feed_source.dart b/lib/src/api/feed_source.dart index 695880e8..f80d784a 100644 --- a/lib/src/api/feed_source.dart +++ b/lib/src/api/feed_source.dart @@ -1,8 +1,8 @@ import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/widgets.dart'; -import 'package:material_symbols_icons/symbols.dart'; -import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/controller/server.dart'; +import 'package:interstellar/src/utils/utils.dart'; +import 'package:material_symbols_icons/symbols.dart'; enum FeedView { threads(icon: Symbols.feed_rounded), @@ -30,8 +30,8 @@ enum FeedView { }; static List match({ - List values = FeedView.values, required int software, + List values = FeedView.values, }) { return values .where((item) => (item.software & software) == software) @@ -105,46 +105,11 @@ enum FeedSort { parent: FeedSort.top, icon: Symbols.trending_up_rounded, ), - topSixHour( - software: - ServerSoftware.mbinFlag | - ServerSoftware.piefedFlag | - ServerSoftware.lemmyFlag, - parent: FeedSort.top, - icon: Symbols.trending_up_rounded, - ), - topTwelveHour( - software: - ServerSoftware.mbinFlag | - ServerSoftware.piefedFlag | - ServerSoftware.lemmyFlag, - parent: FeedSort.top, - icon: Symbols.trending_up_rounded, - ), - topDay( - software: - ServerSoftware.mbinFlag | - ServerSoftware.piefedFlag | - ServerSoftware.lemmyFlag, - parent: FeedSort.top, - icon: Symbols.trending_up_rounded, - ), - topWeek( - software: - ServerSoftware.mbinFlag | - ServerSoftware.piefedFlag | - ServerSoftware.lemmyFlag, - parent: FeedSort.top, - icon: Symbols.trending_up_rounded, - ), - topMonth( - software: - ServerSoftware.mbinFlag | - ServerSoftware.piefedFlag | - ServerSoftware.lemmyFlag, - parent: FeedSort.top, - icon: Symbols.trending_up_rounded, - ), + topSixHour(parent: FeedSort.top, icon: Symbols.trending_up_rounded), + topTwelveHour(parent: FeedSort.top, icon: Symbols.trending_up_rounded), + topDay(parent: FeedSort.top, icon: Symbols.trending_up_rounded), + topWeek(parent: FeedSort.top, icon: Symbols.trending_up_rounded), + topMonth(parent: FeedSort.top, icon: Symbols.trending_up_rounded), topThreeMonths( software: ServerSoftware.piefedFlag | ServerSoftware.lemmyFlag, parent: FeedSort.top, @@ -160,14 +125,7 @@ enum FeedSort { parent: FeedSort.top, icon: Symbols.trending_up_rounded, ), - topYear( - software: - ServerSoftware.mbinFlag | - ServerSoftware.piefedFlag | - ServerSoftware.lemmyFlag, - parent: FeedSort.top, - icon: Symbols.trending_up_rounded, - ), + topYear(parent: FeedSort.top, icon: Symbols.trending_up_rounded), commentedThreeHour( software: ServerSoftware.mbinFlag, diff --git a/lib/src/api/images.dart b/lib/src/api/images.dart index 445ad670..40def466 100644 --- a/lib/src/api/images.dart +++ b/lib/src/api/images.dart @@ -1,22 +1,22 @@ import 'package:http/http.dart' as http; +import 'package:http_parser/http_parser.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:interstellar/src/api/client.dart'; import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:mime/mime.dart'; import 'package:path/path.dart'; -import 'package:http_parser/http_parser.dart'; -import 'package:image_picker/image_picker.dart'; enum ImageStore { platform, catbox, imgLink } class APIImages { - final ServerClient client; - APIImages(this.client); + final ServerClient client; + Future uploadImage({ - ImageStore store = ImageStore.platform, required XFile image, + ImageStore store = ImageStore.platform, }) async { // Use software default image store with catbox as a fallback for mbin. if (store == ImageStore.platform && @@ -50,7 +50,7 @@ class APIImages { final response = await client.sendRequest(request); final imageName = - ((response.bodyJson['files'] as List).first + ((response.bodyJson['files']! as List).first as JsonMap)['file'] as String?; @@ -73,7 +73,7 @@ class APIImages { final response = await client.sendRequest(request); - return response.bodyJson['url'] as String; + return response.bodyJson['url']! as String; } case ImageStore.catbox: const path = 'https://catbox.moe/user/api.php'; @@ -106,10 +106,10 @@ class APIImages { final response = await client.sendRequest(request); - return ((response.bodyJson['images'] as List).first - as JsonMap)['direct_link'] + return ((response.bodyJson['images']! as List).first + as JsonMap)['direct_link']! as String; - } //TODO: add more image store options + } // TODO(olorin99): add more image store options } catch (e) { return ''; } diff --git a/lib/src/api/messages.dart b/lib/src/api/messages.dart index 8488af08..0515cd7a 100644 --- a/lib/src/api/messages.dart +++ b/lib/src/api/messages.dart @@ -4,10 +4,10 @@ import 'package:interstellar/src/models/message.dart'; import 'package:interstellar/src/utils/models.dart'; class APIMessages { - final ServerClient client; - APIMessages(this.client); + final ServerClient client; + Future list({int? myUserId, String? page}) async { switch (client.software) { case ServerSoftware.mbin: @@ -27,7 +27,7 @@ class APIMessages { final json = response.bodyJson; json['next_page'] = lemmyCalcNextIntPage( - json['private_messages'] as List, + json['private_messages']! as List, page, ); @@ -68,7 +68,7 @@ class APIMessages { final json = response.bodyJson; final nextPage = lemmyCalcNextIntPage( - json['private_messages'] as List, + json['private_messages']! as List, page, ); json['next_page'] = nextPage; @@ -118,7 +118,7 @@ class APIMessages { return MessageThreadModel.fromMbin(response.bodyJson); case ServerSoftware.lemmy: - final path = '/private_message'; + const path = '/private_message'; final response = await client.post( path, @@ -135,7 +135,7 @@ class APIMessages { ).items.first; case ServerSoftware.piefed: - final path = '/private_message'; + const path = '/private_message'; final response = await client.post( path, @@ -166,10 +166,10 @@ class APIMessages { return MessageThreadModel.fromMbin(response.bodyJson); case ServerSoftware.lemmy: - return await create(threadId, body); + return create(threadId, body); case ServerSoftware.piefed: - return await create(threadId, body); + return create(threadId, body); } } } diff --git a/lib/src/api/microblogs.dart b/lib/src/api/microblogs.dart index d28f1f05..875b3f4f 100644 --- a/lib/src/api/microblogs.dart +++ b/lib/src/api/microblogs.dart @@ -4,15 +4,15 @@ import 'package:image_picker/image_picker.dart'; import 'package:interstellar/src/api/client.dart'; import 'package:interstellar/src/api/feed_source.dart'; import 'package:interstellar/src/models/post.dart'; +import 'package:interstellar/src/utils/utils.dart'; import 'package:mime/mime.dart'; import 'package:path/path.dart'; -import 'package:interstellar/src/utils/utils.dart'; class MbinAPIMicroblogs { - final ServerClient client; - MbinAPIMicroblogs(this.client); + final ServerClient client; + Future list( FeedSource source, { int? sourceId, @@ -134,7 +134,7 @@ class MbinAPIMicroblogs { request.fields['lang'] = lang; request.fields['isAdult'] = isAdult.toString(); request.fields['alt'] = alt; - var response = await client.sendRequest(request); + final response = await client.sendRequest(request); return PostModel.fromMbinPost(response.bodyJson); } diff --git a/lib/src/api/moderation.dart b/lib/src/api/moderation.dart index 819ab0ca..9e3f1eee 100644 --- a/lib/src/api/moderation.dart +++ b/lib/src/api/moderation.dart @@ -5,8 +5,11 @@ import 'package:interstellar/src/models/modlog.dart'; import 'package:interstellar/src/models/post.dart'; import 'package:interstellar/src/utils/utils.dart'; -const _postTypeMbin = {PostType.thread: 'entry', PostType.microblog: 'post'}; -const _postTypeMbinComment = { +const Map _postTypeMbin = { + PostType.thread: 'entry', + PostType.microblog: 'post', +}; +const Map _postTypeMbinComment = { PostType.thread: 'comments', PostType.microblog: 'post-comment', }; @@ -96,10 +99,10 @@ enum ModLogType { } class APIModeration { - final ServerClient client; - APIModeration(this.client); + final ServerClient client; + Future postPin(PostType postType, int postId, bool pinned) async { switch (client.software) { case ServerSoftware.mbin: @@ -116,7 +119,7 @@ class APIModeration { throw Exception('Moderation not implemented on Lemmy yet'); case ServerSoftware.piefed: - final path = '/post/feature'; + const path = '/post/feature'; final response = await client.post( path, @@ -155,7 +158,7 @@ class APIModeration { throw Exception('Moderation not implemented on Lemmy yet'); case ServerSoftware.piefed: - final path = '/community/moderate/post/nsfw'; + const path = '/community/moderate/post/nsfw'; final body = {'post_id': postId, 'nsfw_status': status}; final response = await client.post(path, body: body); @@ -188,7 +191,7 @@ class APIModeration { throw Exception('Moderation not implemented on Lemmy yet'); case ServerSoftware.piefed: - final path = '/post/remove'; + const path = '/post/remove'; final response = await client.post( path, @@ -220,7 +223,7 @@ class APIModeration { throw Exception('Moderation not implemented on Lemmy yet'); case ServerSoftware.piefed: - final path = '/comment/remove'; + const path = '/comment/remove'; final response = await client.post( path, @@ -232,7 +235,7 @@ class APIModeration { ); return CommentModel.fromPiefed( - response.bodyJson['comment_view'] as JsonMap, + response.bodyJson['comment_view']! as JsonMap, langCodeIdPairs: await client.languageCodeIdPairs(), ); } diff --git a/lib/src/api/notifications.dart b/lib/src/api/notifications.dart index 371c1c79..801d6fde 100644 --- a/lib/src/api/notifications.dart +++ b/lib/src/api/notifications.dart @@ -16,10 +16,10 @@ enum NotificationControlUpdateTargetType { } class APINotifications { - final ServerClient client; - APINotifications(this.client); + final ServerClient client; + Future list({ String? page, NotificationsFilter? filter, @@ -69,7 +69,7 @@ class APINotifications { ); case ServerSoftware.piefed: - final path = '/user/notifications'; + const path = '/user/notifications'; final status = switch (filter) { NotificationsFilter.new_ => 'Unread', NotificationsFilter.read => 'Read', @@ -108,23 +108,23 @@ class APINotifications { final response = await client.get(path); - return response.bodyJson['count'] as int; + return response.bodyJson['count']! as int; case ServerSoftware.lemmy: const path = '/user/unread_count'; final response = await client.get(path); - return (response.bodyJson['replies'] as int) + - (response.bodyJson['mentions'] as int) + - (response.bodyJson['private_messages'] as int); + return (response.bodyJson['replies']! as int) + + (response.bodyJson['mentions']! as int) + + (response.bodyJson['private_messages']! as int); case ServerSoftware.piefed: const path = '/user/notifications_count'; final response = await client.get(path); - return response.bodyJson['count'] as int; + return response.bodyJson['count']! as int; } } @@ -193,10 +193,10 @@ class APINotifications { return switch (notificationType) { NotificationType.message => NotificationModel.fromLemmyMessage( - response.bodyJson['private_message_view'] as JsonMap, + response.bodyJson['private_message_view']! as JsonMap, ), NotificationType.mention => NotificationModel.fromLemmyMention( - response.bodyJson['person_mention_view'] as JsonMap, + response.bodyJson['person_mention_view']! as JsonMap, ), NotificationType.reply => throw Exception( "can't mark Lemmy reply as read", @@ -205,7 +205,7 @@ class APINotifications { }; case ServerSoftware.piefed: - final path = '/user/notification_state'; + const path = '/user/notification_state'; final body = {'notif_id': notificationId, 'read_state': readState}; diff --git a/lib/src/api/oauth.dart b/lib/src/api/oauth.dart index a0a36d73..a3bcee33 100644 --- a/lib/src/api/oauth.dart +++ b/lib/src/api/oauth.dart @@ -37,5 +37,5 @@ Future registerOauthApp(String instanceHost) async { ); ServerClient.checkResponseSuccess(url, response); - return response.bodyJson['identifier'] as String; + return response.bodyJson['identifier']! as String; } diff --git a/lib/src/api/search.dart b/lib/src/api/search.dart index 5947cf5d..39d8eda6 100644 --- a/lib/src/api/search.dart +++ b/lib/src/api/search.dart @@ -4,10 +4,10 @@ import 'package:interstellar/src/models/search.dart'; import 'package:interstellar/src/screens/explore/explore_screen.dart'; class APISearch { - final ServerClient client; - APISearch(this.client); + final ServerClient client; + Future get({ String? page, String? search, @@ -50,10 +50,10 @@ class APISearch { final json = response.bodyJson; String? nextPage; - if ((json['comments'] as List).isNotEmpty || - (json['posts'] as List).isNotEmpty || - (json['communities'] as List).isNotEmpty || - (json['users'] as List).isNotEmpty) { + if ((json['comments']! as List).isNotEmpty || + (json['posts']! as List).isNotEmpty || + (json['communities']! as List).isNotEmpty || + (json['users']! as List).isNotEmpty) { nextPage = (int.parse(page ?? '1') + 1).toString(); } @@ -83,10 +83,10 @@ class APISearch { final json = response.bodyJson; String? nextPage; - if ((json['comments'] as List).isNotEmpty || - (json['posts'] as List).isNotEmpty || - (json['communities'] as List).isNotEmpty || - (json['users'] as List).isNotEmpty) { + if ((json['comments']! as List).isNotEmpty || + (json['posts']! as List).isNotEmpty || + (json['communities']! as List).isNotEmpty || + (json['users']! as List).isNotEmpty) { nextPage = (int.parse(page ?? '1') + 1).toString(); } diff --git a/lib/src/api/threads.dart b/lib/src/api/threads.dart index faf14094..ef3ac8a6 100644 --- a/lib/src/api/threads.dart +++ b/lib/src/api/threads.dart @@ -34,10 +34,10 @@ const Map lemmyFeedSortMap = { }; class APIThreads { - final ServerClient client; - APIThreads(this.client); + final ServerClient client; + Future list( FeedSource source, { int? sourceId, @@ -87,7 +87,7 @@ class APIThreads { final json = response.bodyJson; json['next_page'] = lemmyCalcNextIntPage( - json['posts'] as List, + json['posts']! as List, page, ); @@ -106,7 +106,7 @@ class APIThreads { FeedSource.moderated => {'type_': 'ModeratorView'}, FeedSource.favorited => {'liked_only': 'true'}, FeedSource.community => {'community_id': sourceId!.toString()}, - FeedSource.user => throw Exception('Unreachable'), + FeedSource.user => throw UnreachableError(), FeedSource.domain => throw Exception( 'Domain source not allowed for lemmy', ), @@ -215,11 +215,7 @@ class APIThreads { case ServerSoftware.piefed: final response = await client.post( '/post/like', - body: { - 'post_id': postId, - 'score': newScore, - if (emoji != null) 'emoji': emoji, - }, + body: {'post_id': postId, 'score': newScore, 'emoji': ?emoji}, ); return PostModel.fromPiefed( @@ -253,7 +249,7 @@ class APIThreads { case ServerSoftware.lemmy: throw Exception('Tried to vote on a poll on lemmy'); case ServerSoftware.piefed: - final path = '/post/poll_vote'; + const path = '/post/poll_vote'; final response = await client.post( path, body: {'post_id': postId, 'choice_id': choiceIds}, @@ -365,7 +361,7 @@ class APIThreads { 'Lemmy doesnt support assigning flairs to a post', ); case ServerSoftware.piefed: - final path = '/post/assign_flair'; + const path = '/post/assign_flair'; final response = await client.post( path, body: {'post_id': postId, 'flair_id_list': flairIds}, @@ -538,7 +534,7 @@ class APIThreads { ); request.files.add(multipartFile); request.fields['title'] = title; - for (int i = 0; i < tags.length; i++) { + for (var i = 0; i < tags.length; i++) { request.fields['tags[$i]'] = tags[i]; } request.fields['isOc'] = isOc.toString(); @@ -546,7 +542,7 @@ class APIThreads { request.fields['lang'] = lang; request.fields['isAdult'] = isAdult.toString(); request.fields['alt'] = alt; - var response = await client.sendRequest(request); + final response = await client.sendRequest(request); return PostModel.fromMbinEntry(response.bodyJson); @@ -567,7 +563,7 @@ class APIThreads { final pictrsResponse = await client.sendRequest(uploadRequest); final imageName = - ((pictrsResponse.bodyJson['files'] as List).first + ((pictrsResponse.bodyJson['files']! as List).first! as JsonMap)['file'] as String?; diff --git a/lib/src/api/users.dart b/lib/src/api/users.dart index 5f628d19..e2a2f5db 100644 --- a/lib/src/api/users.dart +++ b/lib/src/api/users.dart @@ -12,10 +12,10 @@ import 'package:mime/mime.dart'; import 'package:path/path.dart'; class APIUsers { - final ServerClient client; - APIUsers(this.client); + final ServerClient client; + Future list({ String? page, ExploreFilter? filter, @@ -59,7 +59,7 @@ class APIUsers { final json = response.bodyJson; json['next_page'] = lemmyCalcNextIntPage( - json['users'] as List, + json['users']! as List, page, ); @@ -85,7 +85,7 @@ class APIUsers { final json = response.bodyJson; json['next_page'] = lemmyCalcNextIntPage( - json['users'] as List, + json['users']! as List, page, ); @@ -109,7 +109,7 @@ class APIUsers { final response = await client.get(path, queryParams: query); return DetailedUserModel.fromLemmy( - response.bodyJson['person_view'] as JsonMap, + response.bodyJson['person_view']! as JsonMap, ); case ServerSoftware.piefed: @@ -119,7 +119,7 @@ class APIUsers { final response = await client.get(path, queryParams: query); return DetailedUserModel.fromPiefed( - response.bodyJson['person_view'] as JsonMap, + response.bodyJson['person_view']! as JsonMap, ); } } @@ -141,7 +141,7 @@ class APIUsers { final response = await client.get(path, queryParams: query); return DetailedUserModel.fromLemmy( - response.bodyJson['person_view'] as JsonMap, + response.bodyJson['person_view']! as JsonMap, ); case ServerSoftware.piefed: @@ -151,7 +151,7 @@ class APIUsers { final response = await client.get(path, queryParams: query); return DetailedUserModel.fromPiefed( - response.bodyJson['person_view'] as JsonMap, + response.bodyJson['person_view']! as JsonMap, ); } } @@ -247,7 +247,7 @@ class APIUsers { ); return DetailedUserModel.fromLemmy( - response.bodyJson['person_view'] as JsonMap, + response.bodyJson['person_view']! as JsonMap, ); case ServerSoftware.piefed: @@ -259,7 +259,7 @@ class APIUsers { ); return DetailedUserModel.fromPiefed( - response.bodyJson['person_view'] as JsonMap, + response.bodyJson['person_view']! as JsonMap, blocked: response.bodyJson['blocked'] as bool? ?? false, ); } @@ -302,7 +302,7 @@ class APIUsers { final pictrsResponse = await client.sendRequest(uploadRequest); final imageName = - ((pictrsResponse.bodyJson['files'] as List).first + ((pictrsResponse.bodyJson['files']! as List).first! as JsonMap)['file'] as String?; @@ -345,7 +345,7 @@ class APIUsers { switch (client.software) { case ServerSoftware.mbin: const path = '/users/avatar'; - var response = await client.delete(path); + final response = await client.delete(path); return DetailedUserModel.fromMbin(response.bodyJson); @@ -370,18 +370,18 @@ class APIUsers { case ServerSoftware.mbin: const path = '/users/cover'; - var request = http.MultipartRequest( + final request = http.MultipartRequest( 'POST', Uri.https(client.domain, client.software.apiPathPrefix + path), ); - var multipartFile = http.MultipartFile.fromBytes( + final multipartFile = http.MultipartFile.fromBytes( 'uploadImage', await image.readAsBytes(), filename: basename(image.path), contentType: MediaType.parse(lookupMimeType(image.path)!), ); request.files.add(multipartFile); - var response = await client.sendRequest(request); + final response = await client.sendRequest(request); return DetailedUserModel.fromMbin(response.bodyJson); @@ -402,7 +402,7 @@ class APIUsers { final pictrsResponse = await client.sendRequest(request); final imageName = - ((pictrsResponse.bodyJson['files'] as List).first + ((pictrsResponse.bodyJson['files']! as List).first! as JsonMap)['file'] as String?; @@ -445,7 +445,7 @@ class APIUsers { switch (client.software) { case ServerSoftware.mbin: const path = '/users/cover'; - var response = await client.delete(path); + final response = await client.delete(path); return DetailedUserModel.fromMbin(response.bodyJson); diff --git a/lib/src/app.dart b/lib/src/app.dart index e05f93ee..6188c714 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -1,13 +1,13 @@ import 'package:dynamic_color/dynamic_color.dart'; import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/material.dart'; -import 'package:interstellar/l10n/app_localizations.dart'; import 'package:flutter_localized_locales/flutter_localized_locales.dart'; +import 'package:interstellar/l10n/app_localizations.dart'; import 'package:interstellar/src/controller/controller.dart'; -import 'package:interstellar/src/screens/account/notification/notification_count_controller.dart'; import 'package:interstellar/src/controller/router.dart'; -import 'package:interstellar/src/utils/utils.dart'; +import 'package:interstellar/src/screens/account/notification/notification_count_controller.dart'; import 'package:interstellar/src/utils/globals.dart'; +import 'package:interstellar/src/utils/utils.dart'; import 'package:intl/locale.dart' as intl_locale; import 'package:provider/provider.dart'; diff --git a/lib/src/app_home.dart b/lib/src/app_home.dart index 67bfe89e..9494074b 100644 --- a/lib/src/app_home.dart +++ b/lib/src/app_home.dart @@ -1,4 +1,5 @@ import 'dart:async'; + import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -13,8 +14,8 @@ import 'package:interstellar/src/screens/settings/account_selection.dart'; import 'package:interstellar/src/screens/settings/profile_selection.dart'; import 'package:interstellar/src/screens/settings/settings_screen.dart'; import 'package:interstellar/src/utils/breakpoints.dart'; -import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/utils/globals.dart'; +import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/wrapper.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; @@ -72,11 +73,7 @@ class _AppHomeState extends State { onTap: (selected, item) async { context.router.pop(); context.router.push( - MessageThreadRoute( - threadId: null, - userId: item.id, - otherUser: item, - ), + MessageThreadRoute(userId: item.id, otherUser: item), ); }, ), @@ -93,7 +90,7 @@ class _AppHomeState extends State { _pageController.jumpToPage(_navIndex); } - void _handleExit(bool didPop, result) async { + Future _handleExit(bool didPop, result) async { if (didPop) return; if (_navIndex != 0) { _changeNav(0); @@ -117,6 +114,8 @@ class _AppHomeState extends State { Widget build(BuildContext context) { final ac = context.watch(); + // AppController should be available for later if needed. + // ignore: cascade_invocations ac.refreshState = () { setState(() { _feedKey = UniqueKey(); @@ -231,7 +230,7 @@ class _AppHomeState extends State { ExploreScreen(key: _exploreKey, focusNode: _exploreFocusNode), SelfFeed(key: _accountKey), InboxScreen(key: _inboxKey), - SettingsScreen(), + const SettingsScreen(), ], ), ), diff --git a/lib/src/controller/controller.dart b/lib/src/controller/controller.dart index 71174291..ade24626 100644 --- a/lib/src/controller/controller.dart +++ b/lib/src/controller/controller.dart @@ -1,5 +1,7 @@ import 'dart:convert'; import 'dart:io'; + +import 'package:drift/drift.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; @@ -26,7 +28,6 @@ import 'package:path_provider/path_provider.dart'; import 'package:simplytranslate/simplytranslate.dart'; import 'package:unifiedpush/unifiedpush.dart'; import 'package:webpush_encryption/webpush_encryption.dart'; -import 'package:drift/drift.dart'; enum HapticsType { light, medium, heavy, selection, vibrate } @@ -122,22 +123,22 @@ class AppController with ChangeNotifier { .getSingleOrNull() ?? true; - Future setExpandNavDrawer(bool value) async => await database + Future setExpandNavDrawer(bool value) async => database .update(database.miscCache) .write(MiscCacheCompanion(expandNavDrawer: Value(value))); - Future setExpandNavStars(bool value) async => await database + Future setExpandNavStars(bool value) async => database .update(database.miscCache) .write(MiscCacheCompanion(expandNavStars: Value(value))); - Future setExpandNavFeeds(bool value) async => await database + Future setExpandNavFeeds(bool value) async => database .update(database.miscCache) .write(MiscCacheCompanion(expandNavFeeds: Value(value))); - Future setExpandNavSubscriptions(bool value) async => await database + Future setExpandNavSubscriptions(bool value) async => database .update(database.miscCache) .write(MiscCacheCompanion(expandNavSubscriptions: Value(value))); - Future setExpandNavFollows(bool value) async => await database + Future setExpandNavFollows(bool value) async => database .update(database.miscCache) .write(MiscCacheCompanion(expandNavFollows: Value(value))); - Future setExpandNavDomains(bool value) async => await database + Future setExpandNavDomains(bool value) async => database .update(database.miscCache) .write(MiscCacheCompanion(expandNavDomains: Value(value))); @@ -429,7 +430,7 @@ class AppController with ChangeNotifier { throw Exception('Register oauth only allowed on mbin'); } - String oauthIdentifier = await registerOauthApp(server); + final oauthIdentifier = await registerOauthApp(server); _servers[server] = Server( name: server, software: software, @@ -465,7 +466,7 @@ class AppController with ChangeNotifier { switch (software) { ServerSoftware.lemmy => 'username_or_email', ServerSoftware.piefed => 'username', - ServerSoftware.mbin => throw Exception('unreachable'), + ServerSoftware.mbin => throw UnreachableError(), }: username, 'password': password, if (software == ServerSoftware.lemmy) 'totp_2fa_token': totp, @@ -473,7 +474,7 @@ class AppController with ChangeNotifier { ); ServerClient.checkResponseSuccess(loginEndpoint, response); - final jwt = response.bodyJson['jwt'] as String; + final jwt = response.bodyJson['jwt']! as String; final user = await API( ServerClient( httpClient: JwtHttpClient(jwt, appHttpClient), @@ -496,7 +497,7 @@ class AppController with ChangeNotifier { final authorizationEndpoint = Uri.https(server, '/authorize'); final tokenEndpoint = Uri.https(server, '/token'); - String identifier = await getMbinOAuthIdentifier(software, server); + final identifier = await getMbinOAuthIdentifier(software, server); final grant = oauth2.AuthorizationCodeGrant( identifier, @@ -653,9 +654,9 @@ class AppController with ChangeNotifier { switch (software) { case ServerSoftware.mbin: - oauth2.Credentials? credentials = _accounts[account]?.oauth; + final credentials = _accounts[account]?.oauth; if (credentials != null) { - String identifier = _servers[instance]!.oauthIdentifier!; + final identifier = _servers[instance]!.oauthIdentifier!; httpClient = oauth2.Client( credentials, identifier: identifier, @@ -668,14 +669,12 @@ class AppController with ChangeNotifier { httpClient: appHttpClient, ); } - break; case ServerSoftware.lemmy: case ServerSoftware.piefed: - String? jwt = _accounts[account]!.jwt; + final jwt = _accounts[account]!.jwt; if (jwt != null) { httpClient = JwtHttpClient(jwt, appHttpClient); } - break; } return API( @@ -785,7 +784,7 @@ class AppController with ChangeNotifier { .insertOnConflictUpdate(FeedsCompanion.insert(name: name)); await database.transaction(() async { - for (var input in value.inputs) { + for (final input in value.inputs) { await database .into(database.feedInputs) .insertOnConflictUpdate( @@ -851,8 +850,7 @@ class AppController with ChangeNotifier { database.profiles, )..where((f) => f.filterLists.contains(name))).get(); for (final profile in profiles) { - final newFilterLists = {...?profile.filterLists}; - newFilterLists.remove(name); + final newFilterLists = {...?profile.filterLists}..remove(name); setProfile(profile.name, profile.copyWith(filterLists: newFilterLists)); } @@ -877,8 +875,7 @@ class AppController with ChangeNotifier { final newFilterLists = { ...?profile.filterLists, newName: profile.filterLists?[oldName] ?? false, - }; - newFilterLists.remove(oldName); + }..remove(oldName); setProfile(profile.name, profile.copyWith(filterLists: newFilterLists)); } @@ -900,19 +897,14 @@ class AppController with ChangeNotifier { switch (type) { case HapticsType.light: HapticFeedback.lightImpact(); - break; case HapticsType.medium: HapticFeedback.mediumImpact(); - break; case HapticsType.heavy: HapticFeedback.heavyImpact(); - break; case HapticsType.selection: HapticFeedback.selectionClick(); - break; case HapticsType.vibrate: HapticFeedback.vibrate(); - break; } } @@ -925,7 +917,7 @@ class AppController with ChangeNotifier { // If marking as read, then check for a db row first, and add one if not present. else if (read) { await database.transaction(() async { - for (var post in posts) { + for (final post in posts) { if (!await isRead(post)) { await database .into(database.readPostCache) @@ -943,7 +935,7 @@ class AppController with ChangeNotifier { // If marking as unread, then delete any matching database rows. else { await database.transaction(() async { - for (var post in posts) { + for (final post in posts) { await (database.delete(database.readPostCache)..where( (f) => f.account.equals(_selectedAccount) & @@ -1036,13 +1028,13 @@ class AppController with ChangeNotifier { } Future addTag({String tag = 'Tag'}) async { - return await database + return database .into(database.tags) .insertReturning(TagsCompanion.insert(tag: tag)); } Future setTag(Tag tag) async { - return await database + return database .into(database.tags) .insertReturning(tag, onConflict: DoUpdate((_) => tag)); } diff --git a/lib/src/controller/database/database.dart b/lib/src/controller/database/database.dart index 1134ee34..58471793 100644 --- a/lib/src/controller/database/database.dart +++ b/lib/src/controller/database/database.dart @@ -1,27 +1,31 @@ +// Needed for drift column constraints. +// ignore_for_file: recursive_getters + import 'dart:convert'; import 'dart:io'; import 'dart:ui'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart' show ThemeMode; + +import 'package:drift/drift.dart'; import 'package:drift_flutter/drift_flutter.dart'; import 'package:flex_color_scheme/flex_color_scheme.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart' show ThemeMode; import 'package:interstellar/src/api/comments.dart'; import 'package:interstellar/src/api/feed_source.dart'; import 'package:interstellar/src/api/images.dart'; +import 'package:interstellar/src/controller/database/database.steps.dart'; import 'package:interstellar/src/controller/feed.dart'; -import 'package:interstellar/src/controller/server.dart'; -import 'package:interstellar/src/controller/profile.dart'; import 'package:interstellar/src/controller/filter_list.dart'; +import 'package:interstellar/src/controller/profile.dart'; +import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/models/post.dart'; -import 'package:interstellar/src/widgets/actions.dart'; import 'package:interstellar/src/utils/utils.dart'; +import 'package:interstellar/src/widgets/actions.dart'; import 'package:interstellar/src/widgets/content_item/content_item.dart'; import 'package:oauth2/oauth2.dart'; +import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:drift/drift.dart'; import 'package:sembast/sembast_io.dart'; -import 'package:path/path.dart'; -import 'database.steps.dart'; part 'database.g.dart'; @@ -55,7 +59,8 @@ class Accounts extends Table { text().withLength(min: 1).clientDefault(() => '@kbin.earth')(); TextColumn get oauth => text().map(const CredentialsConverter()).nullable()(); TextColumn get jwt => text().nullable()(); - BoolColumn get isPushRegistered => boolean().withDefault(Constant(false))(); + BoolColumn get isPushRegistered => + boolean().withDefault(const Constant(false))(); @override Set> get primaryKey => {handle}; @@ -356,13 +361,18 @@ class MiscCache extends Table { .clientDefault(() => '@kbin.earth')(); TextColumn get webPushKeys => text().nullable()(); TextColumn get downloadsDir => text().nullable()(); - BoolColumn get expandNavDrawer => boolean().withDefault(Constant(true))(); - BoolColumn get expandNavStars => boolean().withDefault(Constant(true))(); - BoolColumn get expandNavFeeds => boolean().withDefault(Constant(true))(); + BoolColumn get expandNavDrawer => + boolean().withDefault(const Constant(true))(); + BoolColumn get expandNavStars => + boolean().withDefault(const Constant(true))(); + BoolColumn get expandNavFeeds => + boolean().withDefault(const Constant(true))(); BoolColumn get expandNavSubscriptions => - boolean().withDefault(Constant(true))(); - BoolColumn get expandNavFollows => boolean().withDefault(Constant(true))(); - BoolColumn get expandNavDomains => boolean().withDefault(Constant(true))(); + boolean().withDefault(const Constant(true))(); + BoolColumn get expandNavFollows => + boolean().withDefault(const Constant(true))(); + BoolColumn get expandNavDomains => + boolean().withDefault(const Constant(true))(); @override Set> get primaryKey => {id}; @@ -395,12 +405,12 @@ class Tags extends Table { IntColumn get backgroundColor => integer() .map(const ColorConverter()) .clientDefault( - () => Color.from(alpha: 1, red: 1, green: 1, blue: 1).value32bit, + () => const Color.from(alpha: 1, red: 1, green: 1, blue: 1).value32bit, )(); IntColumn get textColor => integer() .map(const ColorConverter()) .clientDefault( - () => Color.from(alpha: 1, red: 0, green: 0, blue: 0).value32bit, + () => const Color.from(alpha: 1, red: 0, green: 0, blue: 0).value32bit, )(); } @@ -489,7 +499,7 @@ Future initDatabase() async { Future deleteTables() async { // Ensure miscCache is deleted before accounts await database.delete(database.miscCache).go(); - for (var table in database.allTables) { + for (final table in database.allTables) { await database.delete(table).go(); } } @@ -498,10 +508,10 @@ Future migrateDatabase() async { if (PlatformIs.web) return false; final dir = await getApplicationSupportDirectory(); final dbPath = join(dir.path, 'database'); - if (!await File(dbPath).exists()) { + if (!File(dbPath).existsSync()) { return false; } - final Database db = await databaseFactoryIo.openDatabase(dbPath); + final db = await databaseFactoryIo.openDatabase(dbPath); database = InterstellarDatabase(); final mainStore = StoreRef.main(); @@ -523,7 +533,7 @@ Future migrateDatabase() async { late final webPushKeysRecord = mainStore.record('webPushKeys'); final stars = (await starsRecord.get(db) as List? ?? []) - .map((v) => v as String) + .map((v) => v! as String) .toList(); await database.transaction(() async { @@ -546,7 +556,7 @@ Future migrateDatabase() async { ), ), ); - for (var entry in accounts.entries) { + for (final entry in accounts.entries) { await database.into(database.accounts).insertOnConflictUpdate(entry.value); } @@ -558,7 +568,7 @@ Future migrateDatabase() async { ), ), ); - for (var entry in servers.entries) { + for (final entry in servers.entries) { await database.into(database.servers).insertOnConflictUpdate(entry.value); } @@ -568,11 +578,11 @@ Future migrateDatabase() async { )).map((record) => MapEntry(record.key, Feed.fromJson(record.value))), ); await database.transaction(() async { - for (var entry in feeds.entries) { + for (final entry in feeds.entries) { await database .into(database.feeds) .insertOnConflictUpdate(FeedsCompanion.insert(name: entry.key)); - for (var input in entry.value.inputs) { + for (final input in entry.value.inputs) { await database .into(database.feedInputs) .insertOnConflictUpdate( @@ -594,7 +604,7 @@ Future migrateDatabase() async { ), ), ); - for (var entry in filterLists.entries) { + for (final entry in filterLists.entries) { await database .into(database.filterLists) .insertOnConflictUpdate( @@ -609,7 +619,7 @@ Future migrateDatabase() async { } final profiles = await profileStore.find(db); - for (var entry in profiles) { + for (final entry in profiles) { await database .into(database.profiles) .insertOnConflictUpdate( @@ -619,16 +629,16 @@ Future migrateDatabase() async { final readPosts = await readStore.find(db); await database.transaction(() async { - for (var entry in readPosts) { + for (final entry in readPosts) { await database .into(database.readPostCache) .insertOnConflictUpdate( ReadPostCacheCompanion.insert( - account: entry.value['account'] as String, + account: entry.value['account']! as String, postType: PostType.values.byName( entry.value['postType'] as String? ?? 'thread', ), - postId: entry.value['postId'] as int, + postId: entry.value['postId']! as int, ), ); } @@ -637,7 +647,7 @@ Future migrateDatabase() async { final drafts = (await draftsStore.find( db, )).map((d) => Draft.fromJson({...d.value, 'id': d.key})); - for (var entry in drafts) { + for (final entry in drafts) { await database.into(database.drafts).insertOnConflictUpdate(entry); } diff --git a/lib/src/controller/feed.dart b/lib/src/controller/feed.dart index 906e55d9..40612863 100644 --- a/lib/src/controller/feed.dart +++ b/lib/src/controller/feed.dart @@ -7,24 +7,22 @@ part 'feed.g.dart'; @freezed abstract class FeedInput with _$FeedInput { - const FeedInput._(); - @JsonSerializable(explicitToJson: true, includeIfNull: false) const factory FeedInput({ required String name, required FeedSource sourceType, int? serverId, }) = _FeedInput; + const FeedInput._(); factory FeedInput.fromJson(JsonMap json) => _$FeedInputFromJson(json); } @freezed abstract class Feed with _$Feed { - const Feed._(); - @JsonSerializable(explicitToJson: true, includeIfNull: false) const factory Feed({required Set inputs}) = _Feed; + const Feed._(); factory Feed.fromJson(JsonMap json) => _$FeedFromJson(json); diff --git a/lib/src/controller/filter_list.dart b/lib/src/controller/filter_list.dart index b3444144..f89185d7 100644 --- a/lib/src/controller/filter_list.dart +++ b/lib/src/controller/filter_list.dart @@ -1,7 +1,7 @@ +import 'package:drift/drift.dart' show Expression, Insertable, Value; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:interstellar/src/controller/database/database.dart'; import 'package:interstellar/src/utils/utils.dart'; -import 'package:drift/drift.dart' show Insertable, Value, Expression; part 'filter_list.freezed.dart'; part 'filter_list.g.dart'; @@ -10,8 +10,6 @@ enum FilterListMatchMode { simple, wholeWords, regex } @freezed abstract class FilterList with _$FilterList implements Insertable { - const FilterList._(); - @JsonSerializable(explicitToJson: true, includeIfNull: false) const factory FilterList({ required String name, @@ -20,6 +18,7 @@ abstract class FilterList with _$FilterList implements Insertable { required bool caseSensitive, required bool showWithWarning, }) = _FilterList; + const FilterList._(); factory FilterList.fromJson(JsonMap json) => _$FilterListFromJson(json); @@ -55,7 +54,7 @@ abstract class FilterList with _$FilterList implements Insertable { return false; case FilterListMatchMode.wholeWords: - for (var phrase in phrases) { + for (final phrase in phrases) { if (RegExp( '\\b${RegExp.escape(phrase)}\\b', caseSensitive: caseSensitive, @@ -66,7 +65,7 @@ abstract class FilterList with _$FilterList implements Insertable { return false; case FilterListMatchMode.regex: - for (var phrase in phrases) { + for (final phrase in phrases) { if (RegExp(phrase, caseSensitive: caseSensitive).hasMatch(input)) { return true; } diff --git a/lib/src/controller/profile.dart b/lib/src/controller/profile.dart index 5ddf0c8c..d9d6ba2b 100644 --- a/lib/src/controller/profile.dart +++ b/lib/src/controller/profile.dart @@ -1,14 +1,15 @@ +import 'package:drift/drift.dart' show Expression, Insertable, Value; import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/material.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:interstellar/src/api/comments.dart'; import 'package:interstellar/src/api/feed_source.dart'; import 'package:interstellar/src/api/images.dart' show ImageStore; +import 'package:interstellar/src/controller/database/database.dart' + show ProfilesCompanion; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/actions.dart' show ActionLocation, ActionLocationWithTabs, SwipeAction; -import 'package:drift/drift.dart' show Insertable, Value, Expression; -import 'database/database.dart' show ProfilesCompanion; import 'package:interstellar/src/widgets/content_item/content_item.dart'; part 'profile.freezed.dart'; @@ -19,8 +20,6 @@ enum OpenLinksIn { inAppBrowser, externalBrowser } /// Profile class where all fields are required. @freezed abstract class ProfileRequired with _$ProfileRequired { - const ProfileRequired._(); - @JsonSerializable(explicitToJson: true, includeIfNull: false) const factory ProfileRequired({ // If the autoSwitchAccount key is ever changed, be sure to update the AppController code that removes accounts, which references this key. @@ -88,6 +87,7 @@ abstract class ProfileRequired with _$ProfileRequired { required Map filterLists, required bool showErrors, }) = _ProfileRequired; + const ProfileRequired._(); factory ProfileRequired.fromJson(JsonMap json) => _$ProfileRequiredFromJson(json); @@ -214,7 +214,7 @@ abstract class ProfileRequired with _$ProfileRequired { autoTranslate: false, markThreadsReadOnScroll: false, markMicroblogsReadOnScroll: false, - animationSpeed: 1.0, + animationSpeed: 1, inlineReplies: true, showCrossPostComments: true, markCrossPostsAsRead: false, @@ -281,8 +281,6 @@ abstract class ProfileRequired with _$ProfileRequired { abstract class ProfileOptional with _$ProfileOptional implements Insertable { - const ProfileOptional._(); - @JsonSerializable(explicitToJson: true, includeIfNull: false) const factory ProfileOptional({ required String name, @@ -349,6 +347,7 @@ abstract class ProfileOptional required Map? filterLists, required bool? showErrors, }) = _ProfileOptional; + const ProfileOptional._(); factory ProfileOptional.fromJson(JsonMap json) => _$ProfileOptionalFromJson(json); @@ -540,30 +539,22 @@ abstract class ProfileOptional feedViewOrder: other.feedViewOrder ?? feedViewOrder, feedSourceOrder: other.feedSourceOrder ?? feedSourceOrder, feedSortOrder: other.feedSortOrder ?? feedSortOrder, - feedActionBackToTop: - other.feedActionBackToTop ?? this.feedActionBackToTop, - feedActionCreateNew: - other.feedActionCreateNew ?? this.feedActionCreateNew, - feedActionExpandFab: - other.feedActionExpandFab ?? this.feedActionExpandFab, - feedActionRefresh: other.feedActionRefresh ?? this.feedActionRefresh, - feedActionSetFilter: - other.feedActionSetFilter ?? this.feedActionSetFilter, - feedActionSetSort: other.feedActionSetSort ?? this.feedActionSetSort, - feedActionSetView: other.feedActionSetView ?? this.feedActionSetView, + feedActionBackToTop: other.feedActionBackToTop ?? feedActionBackToTop, + feedActionCreateNew: other.feedActionCreateNew ?? feedActionCreateNew, + feedActionExpandFab: other.feedActionExpandFab ?? feedActionExpandFab, + feedActionRefresh: other.feedActionRefresh ?? feedActionRefresh, + feedActionSetFilter: other.feedActionSetFilter ?? feedActionSetFilter, + feedActionSetSort: other.feedActionSetSort ?? feedActionSetSort, + feedActionSetView: other.feedActionSetView ?? feedActionSetView, feedActionHideReadPosts: - other.feedActionHideReadPosts ?? this.feedActionHideReadPosts, - enableSwipeActions: other.enableSwipeActions ?? this.enableSwipeActions, - swipeActionLeftShort: - other.swipeActionLeftShort ?? this.swipeActionLeftShort, - swipeActionLeftLong: - other.swipeActionLeftLong ?? this.swipeActionLeftLong, + other.feedActionHideReadPosts ?? feedActionHideReadPosts, + enableSwipeActions: other.enableSwipeActions ?? enableSwipeActions, + swipeActionLeftShort: other.swipeActionLeftShort ?? swipeActionLeftShort, + swipeActionLeftLong: other.swipeActionLeftLong ?? swipeActionLeftLong, swipeActionRightShort: - other.swipeActionRightShort ?? this.swipeActionRightShort, - swipeActionRightLong: - other.swipeActionRightLong ?? this.swipeActionRightLong, - swipeActionThreshold: - other.swipeActionThreshold ?? this.swipeActionThreshold, + other.swipeActionRightShort ?? swipeActionRightShort, + swipeActionRightLong: other.swipeActionRightLong ?? swipeActionRightLong, + swipeActionThreshold: other.swipeActionThreshold ?? swipeActionThreshold, filterLists: filterLists != null && other.filterLists != null ? {...filterLists!, ...other.filterLists!} : other.filterLists ?? filterLists, @@ -587,25 +578,25 @@ abstract class ProfileOptional return copyWith( feedActionBackToTop: builtProfile.feedActionBackToTop.name == actionName ? ActionLocation.hide - : this.feedActionBackToTop, + : feedActionBackToTop, feedActionCreateNew: builtProfile.feedActionCreateNew.name == actionName ? ActionLocation.hide - : this.feedActionCreateNew, + : feedActionCreateNew, feedActionExpandFab: builtProfile.feedActionExpandFab.name == actionName ? ActionLocation.hide - : this.feedActionExpandFab, + : feedActionExpandFab, feedActionRefresh: builtProfile.feedActionRefresh.name == actionName ? ActionLocation.hide - : this.feedActionRefresh, + : feedActionRefresh, feedActionSetFilter: builtProfile.feedActionSetFilter.name == actionName ? ActionLocationWithTabs.hide - : this.feedActionSetFilter, + : feedActionSetFilter, feedActionSetSort: builtProfile.feedActionSetSort.name == actionName ? ActionLocationWithTabs.hide - : this.feedActionSetSort, + : feedActionSetSort, feedActionSetView: builtProfile.feedActionSetView.name == actionName ? ActionLocationWithTabs.hide - : this.feedActionSetView, + : feedActionSetView, ); } @@ -615,7 +606,7 @@ abstract class ProfileOptional } } -Object? _parseFeedDefaultCombinedSort(Map json, String name) { +Object? _parseFeedDefaultCombinedSort(Map json, String name) { final current = json[name]; if (current != null) return current; return json['feedDefaultTimelineSort']; diff --git a/lib/src/controller/router.dart b/lib/src/controller/router.dart index c21e0483..19750786 100644 --- a/lib/src/controller/router.dart +++ b/lib/src/controller/router.dart @@ -1,12 +1,12 @@ import 'package:auto_route/auto_route.dart'; import 'package:drift_db_viewer/drift_db_viewer.dart'; -import 'database/database.dart'; -import 'router.gr.dart'; +import 'package:interstellar/src/controller/database/database.dart'; +import 'package:interstellar/src/controller/router.gr.dart'; @AutoRouterConfig(replaceInRouteName: 'Screen|Page,Route') class AppRouter extends RootStackRouter { @override - RouteType get defaultRouteType => RouteType.material(); + RouteType get defaultRouteType => const RouteType.material(); @override List get routes => [ diff --git a/lib/src/controller/server.dart b/lib/src/controller/server.dart index 2db19212..9e872e12 100644 --- a/lib/src/controller/server.dart +++ b/lib/src/controller/server.dart @@ -18,9 +18,9 @@ enum ServerSoftware { }; Color get color => switch (this) { - ServerSoftware.mbin => Color(0xff4f2696), - ServerSoftware.lemmy => Color(0xff03a80e), - ServerSoftware.piefed => Color(0xff0e6ef9), + ServerSoftware.mbin => const Color(0xff4f2696), + ServerSoftware.lemmy => const Color(0xff03a80e), + ServerSoftware.piefed => const Color(0xff0e6ef9), }; // Use const ints as bitflags since using enums directly as bitflags is a bit awkward in dart. diff --git a/lib/src/init_push_notifications.dart b/lib/src/init_push_notifications.dart index b8cad55e..097013fd 100644 --- a/lib/src/init_push_notifications.dart +++ b/lib/src/init_push_notifications.dart @@ -23,8 +23,7 @@ Future _downloadImageToAndroidBitmap(String url) async { } Future initPushNotifications(AppController ac) async { - FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = - FlutterLocalNotificationsPlugin(); + final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); await flutterLocalNotificationsPlugin.initialize( const InitializationSettings( @@ -97,7 +96,7 @@ Future getUnifiedPushDistributor(BuildContext context) async { } else if (distributors.length == 1) { return distributors.single; } else { - showDialog( + showDialog( context: context, builder: (context) => AlertDialog( title: Text(l(context).pushNotificationsDialog_title), diff --git a/lib/src/models/bookmark_list.dart b/lib/src/models/bookmark_list.dart index 0f5a8137..ad4db14d 100644 --- a/lib/src/models/bookmark_list.dart +++ b/lib/src/models/bookmark_list.dart @@ -10,7 +10,7 @@ abstract class BookmarkListListModel with _$BookmarkListListModel { }) = _BookmarkListListModel; factory BookmarkListListModel.fromMbin(JsonMap json) => BookmarkListListModel( - items: (json['items'] as List) + items: (json['items']! as List) .map((post) => BookmarkListModel.fromMbin(post as JsonMap)) .toList(), ); @@ -25,8 +25,8 @@ abstract class BookmarkListModel with _$BookmarkListModel { }) = _BookmarkListModel; factory BookmarkListModel.fromMbin(JsonMap json) => BookmarkListModel( - name: json['name'] as String, - isDefault: json['isDefault'] as bool, - count: json['count'] as int, + name: json['name']! as String, + isDefault: json['isDefault']! as bool, + count: json['count']! as int, ); } diff --git a/lib/src/models/comment.dart b/lib/src/models/comment.dart index 48c174ab..afdca6d8 100644 --- a/lib/src/models/comment.dart +++ b/lib/src/models/comment.dart @@ -1,7 +1,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:interstellar/src/models/community.dart'; import 'package:interstellar/src/models/emoji_reaction.dart'; import 'package:interstellar/src/models/image.dart'; -import 'package:interstellar/src/models/community.dart'; import 'package:interstellar/src/models/notification.dart'; import 'package:interstellar/src/models/post.dart'; import 'package:interstellar/src/models/user.dart'; @@ -18,10 +18,10 @@ abstract class CommentListModel with _$CommentListModel { }) = _CommentListModel; factory CommentListModel.fromMbin(JsonMap json) => CommentListModel( - items: (json['items'] as List) + items: (json['items']! as List) .map((post) => CommentModel.fromMbin(post as JsonMap)) .toList(), - nextPage: mbinCalcNextPaginationPage(json['pagination'] as JsonMap), + nextPage: mbinCalcNextPaginationPage(json['pagination']! as JsonMap), ); // Lemmy comment list that needs to be converted to tree format. Used for post comments and comment replies. @@ -29,12 +29,12 @@ abstract class CommentListModel with _$CommentListModel { JsonMap json, { required List<(String, int)> langCodeIdPairs, }) => CommentListModel( - items: (json['comments'] as List) + items: (json['comments']! as List) .where((c) => (c['comment']['path'] as String).split('.').length == 2) .map( (c) => CommentModel.fromLemmy( c as JsonMap, - possibleChildrenJson: json['comments'] as List, + possibleChildrenJson: json['comments']! as List, langCodeIdPairs: langCodeIdPairs, ), ) @@ -47,7 +47,7 @@ abstract class CommentListModel with _$CommentListModel { JsonMap json, { required List<(String, int)> langCodeIdPairs, }) => CommentListModel( - items: (json['comments'] as List) + items: (json['comments']! as List) .map( (c) => CommentModel.fromLemmy( c as JsonMap, @@ -62,7 +62,7 @@ abstract class CommentListModel with _$CommentListModel { JsonMap json, { required List<(String, int)> langCodeIdPairs, }) => CommentListModel( - items: (json['comments'] as List) + items: (json['comments']! as List) .map( (post) => CommentModel.fromPiefed( post as JsonMap, @@ -78,12 +78,12 @@ abstract class CommentListModel with _$CommentListModel { JsonMap json, { required List<(String, int)> langCodeIdPairs, }) => CommentListModel( - items: (json['comments'] as List) + items: (json['comments']! as List) .where((c) => (c['comment']['path'] as String).split('.').length == 2) .map( (c) => CommentModel.fromPiefed( c as JsonMap, - possibleChildrenJson: json['comments'] as List, + possibleChildrenJson: json['comments']! as List, langCodeIdPairs: langCodeIdPairs, ), ) @@ -96,7 +96,7 @@ abstract class CommentListModel with _$CommentListModel { JsonMap json, { required List<(String, int)> langCodeIdPairs, }) => CommentListModel( - items: (json['comments'] as List) + items: (json['comments']! as List) .map( (c) => CommentModel.fromPiefed( c as JsonMap, @@ -139,30 +139,30 @@ abstract class CommentModel with _$CommentModel { }) = _CommentModel; factory CommentModel.fromMbin(JsonMap json) => CommentModel( - id: json['commentId'] as int, - user: DetailedUserModel.fromMbin(json['user'] as JsonMap), - community: CommunityModel.fromMbin(json['magazine'] as JsonMap), + id: json['commentId']! as int, + user: DetailedUserModel.fromMbin(json['user']! as JsonMap), + community: CommunityModel.fromMbin(json['magazine']! as JsonMap), postType: (json['postId'] != null ? PostType.microblog : PostType.thread), - postId: (json['entryId'] ?? json['postId']) as int, + postId: (json['entryId'] ?? json['postId'])! as int, rootId: json['rootId'] as int?, parentId: json['parentId'] as int?, image: mbinGetOptionalImage(json['image'] as JsonMap?), body: json['body'] as String?, - lang: json['lang'] as String, + lang: json['lang']! as String, upvotes: json['favourites'] as int?, downvotes: json['dv'] as int?, boosts: json['uv'] as int?, - myVote: (json['isFavourited'] as bool?) == true + myVote: (json['isFavourited'] as bool?) ?? false ? 1 : ((json['userVote'] as int?) == -1 ? -1 : 0), myBoost: (json['userVote'] as int?) == 1, - createdAt: DateTime.parse(json['createdAt'] as String), + createdAt: DateTime.parse(json['createdAt']! as String), editedAt: optionalDateTime(json['editedAt'] as String?), - children: (json['children'] as List) + children: (json['children']! as List) .map((c) => CommentModel.fromMbin(c as JsonMap)) .toList(), - childCount: json['childCount'] as int, - visibility: json['visibility'] as String, + childCount: json['childCount']! as int, + visibility: json['visibility']! as String, canAuthUserModerate: json['canAuthUserModerate'] as bool?, notificationControlStatus: null, bookmarks: optionalStringList(json['bookmarks']), @@ -172,21 +172,18 @@ abstract class CommentModel with _$CommentModel { factory CommentModel.fromLemmy( JsonMap json, { - List possibleChildrenJson = const [], required List<(String, int)> langCodeIdPairs, + List possibleChildrenJson = const [], }) { - final lemmyComment = json['comment'] as JsonMap; + final lemmyComment = json['comment']! as JsonMap; final lemmyCounts = json['counts'] as JsonMap?; - final lemmyPath = lemmyComment['path'] as String; - final lemmyPathSegments = lemmyPath - .split('.') - .map((e) => int.parse(e)) - .toList(); + final lemmyPath = lemmyComment['path']! as String; + final lemmyPathSegments = lemmyPath.split('.').map(int.parse).toList(); final children = possibleChildrenJson .where((c) { - String childPath = c['comment']['path']; + final String childPath = c['comment']['path']; return childPath.startsWith('$lemmyPath.') && (childPath.split('.').length == lemmyPathSegments.length + 1); @@ -201,22 +198,23 @@ abstract class CommentModel with _$CommentModel { .toList(); return CommentModel( - id: lemmyComment['id'] as int, - user: DetailedUserModel.fromLemmy(json['creator'] as JsonMap), - community: CommunityModel.fromLemmy(json['community'] as JsonMap), + id: lemmyComment['id']! as int, + user: DetailedUserModel.fromLemmy(json['creator']! as JsonMap), + community: CommunityModel.fromLemmy(json['community']! as JsonMap), postType: PostType.thread, - postId: (json['post'] as JsonMap)['id'] as int, + postId: (json['post']! as JsonMap)['id']! as int, rootId: lemmyPathSegments.length > 2 ? lemmyPathSegments[1] : null, parentId: lemmyPathSegments.length > 2 ? lemmyPathSegments[lemmyPathSegments.length - 2] : null, image: null, body: - (lemmyComment['deleted'] as bool) || (lemmyComment['removed'] as bool) + (lemmyComment['deleted']! as bool) || + (lemmyComment['removed']! as bool) ? null - : lemmyComment['content'] as String, + : lemmyComment['content']! as String, lang: langCodeIdPairs - .where((pair) => pair.$2 == lemmyComment['language_id'] as int) + .where((pair) => pair.$2 == lemmyComment['language_id']! as int) .firstOrNull ?.$1, upvotes: lemmyCounts?['upvotes'] as int? ?? 0, @@ -224,7 +222,7 @@ abstract class CommentModel with _$CommentModel { boosts: null, myVote: json['my_vote'] as int?, myBoost: null, - createdAt: DateTime.parse(lemmyComment['published'] as String), + createdAt: DateTime.parse(lemmyComment['published']! as String), editedAt: optionalDateTime(json['updated'] as String?), children: children, childCount: lemmyCounts?['child_count'] as int? ?? 0, @@ -233,33 +231,29 @@ abstract class CommentModel with _$CommentModel { notificationControlStatus: null, bookmarks: [ // Empty string indicates comment is saved. No string indicates comment is not saved. - if (((json['saved'] as bool?) != null) ? json['saved'] as bool : false) - '', + if (((json['saved'] as bool?) != null) && json['saved']! as bool) '', ], - apId: lemmyComment['ap_id'] as String, + apId: lemmyComment['ap_id']! as String, emojiReactions: null, ); } factory CommentModel.fromPiefed( JsonMap json, { - List possibleChildrenJson = const [], required List<(String, int)> langCodeIdPairs, + List possibleChildrenJson = const [], CommunityModel? community, int? postId, }) { - final piefedComment = json['comment'] as JsonMap; - final piefedCounts = json['counts'] as JsonMap; + final piefedComment = json['comment']! as JsonMap; + final piefedCounts = json['counts']! as JsonMap; - final piefedPath = piefedComment['path'] as String; - final piefedPathSegments = piefedPath - .split('.') - .map((e) => int.parse(e)) - .toList(); + final piefedPath = piefedComment['path']! as String; + final piefedPathSegments = piefedPath.split('.').map(int.parse).toList(); var children = possibleChildrenJson .where((c) { - String childPath = c['comment']['path']; + final String childPath = c['comment']['path']; return childPath.startsWith('$piefedPath.') && (childPath.split('.').length == piefedPathSegments.length + 1); @@ -273,8 +267,8 @@ abstract class CommentModel with _$CommentModel { ) .toList(); - postId ??= (json['post'] as JsonMap)['id'] as int; - community ??= CommunityModel.fromPiefed(json['community'] as JsonMap); + postId ??= (json['post']! as JsonMap)['id']! as int; + community ??= CommunityModel.fromPiefed(json['community']! as JsonMap); if (children.isEmpty) { final replies = (json['replies'] as List?) @@ -293,8 +287,8 @@ abstract class CommentModel with _$CommentModel { } return CommentModel( - id: piefedComment['id'] as int, - user: DetailedUserModel.fromPiefed(json['creator'] as JsonMap), + id: piefedComment['id']! as int, + user: DetailedUserModel.fromPiefed(json['creator']! as JsonMap), community: community, postType: PostType.thread, postId: postId, @@ -304,35 +298,35 @@ abstract class CommentModel with _$CommentModel { : null, image: null, body: - (piefedComment['deleted'] as bool) || - (piefedComment['removed'] as bool) + (piefedComment['deleted']! as bool) || + (piefedComment['removed']! as bool) ? null - : piefedComment['body'] as String, + : piefedComment['body']! as String, lang: langCodeIdPairs - .where((pair) => pair.$2 == piefedComment['language_id'] as int) + .where((pair) => pair.$2 == piefedComment['language_id']! as int) .firstOrNull ?.$1, - upvotes: piefedCounts['upvotes'] as int, - downvotes: piefedCounts['downvotes'] as int, + upvotes: piefedCounts['upvotes']! as int, + downvotes: piefedCounts['downvotes']! as int, boosts: null, myVote: json['my_vote'] as int?, myBoost: null, - createdAt: DateTime.parse(piefedComment['published'] as String), + createdAt: DateTime.parse(piefedComment['published']! as String), editedAt: optionalDateTime(json['updated'] as String?), children: children, - childCount: piefedCounts['child_count'] as int, + childCount: piefedCounts['child_count']! as int, visibility: 'visible', canAuthUserModerate: json['can_auth_user_moderate'] as bool?, notificationControlStatus: json['activity_alert'] == null ? null - : json['activity_alert'] as bool + : json['activity_alert']! as bool ? NotificationControlStatus.loud : NotificationControlStatus.default_, bookmarks: [ // Empty string indicates comment is saved. No string indicates comment is not saved. - if (json['saved'] as bool) '', + if (json['saved']! as bool) '', ], - apId: piefedComment['ap_id'] as String, + apId: piefedComment['ap_id']! as String, emojiReactions: (piefedComment['emoji_reactions'] as List?) ?.map((item) => EmojiReactionModel.fromPieFed(item)) diff --git a/lib/src/models/community.dart b/lib/src/models/community.dart index 3786c362..3ee8d17d 100644 --- a/lib/src/models/community.dart +++ b/lib/src/models/community.dart @@ -1,14 +1,14 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:interstellar/src/api/community_moderation.dart'; import 'package:interstellar/src/controller/database/database.dart'; +import 'package:interstellar/src/models/comment.dart'; import 'package:interstellar/src/models/image.dart'; import 'package:interstellar/src/models/notification.dart'; +import 'package:interstellar/src/models/post.dart'; import 'package:interstellar/src/models/user.dart'; import 'package:interstellar/src/utils/models.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/markdown/markdown_mention.dart'; -import 'package:interstellar/src/models/comment.dart'; -import 'package:interstellar/src/models/post.dart'; part 'community.freezed.dart'; @@ -21,15 +21,15 @@ abstract class DetailedCommunityListModel with _$DetailedCommunityListModel { factory DetailedCommunityListModel.fromMbin(JsonMap json) => DetailedCommunityListModel( - items: (json['items'] as List) + items: (json['items']! as List) .map((item) => DetailedCommunityModel.fromMbin(item as JsonMap)) .toList(), - nextPage: mbinCalcNextPaginationPage(json['pagination'] as JsonMap), + nextPage: mbinCalcNextPaginationPage(json['pagination']! as JsonMap), ); factory DetailedCommunityListModel.fromLemmy(JsonMap json) => DetailedCommunityListModel( - items: (json['communities'] as List) + items: (json['communities']! as List) .map((item) => DetailedCommunityModel.fromLemmy(item as JsonMap)) .toList(), nextPage: json['next_page'] as String?, @@ -37,7 +37,7 @@ abstract class DetailedCommunityListModel with _$DetailedCommunityListModel { factory DetailedCommunityListModel.fromPiefed(JsonMap json) => DetailedCommunityListModel( - items: (json['communities'] as List) + items: (json['communities']! as List) .map((item) => DetailedCommunityModel.fromPiefed(item as JsonMap)) .toList(), nextPage: json['next_page'] as String?, @@ -70,23 +70,23 @@ abstract class DetailedCommunityModel with _$DetailedCommunityModel { factory DetailedCommunityModel.fromMbin(JsonMap json) { final community = DetailedCommunityModel( - id: json['magazineId'] as int, - name: json['name'] as String, - title: json['title'] as String, + id: json['magazineId']! as int, + name: json['name']! as String, + title: json['title']! as String, icon: mbinGetOptionalImage(json['icon'] as JsonMap?), description: json['description'] as String?, owner: json['owner'] == null ? null - : UserModel.fromMbin(json['owner'] as JsonMap), + : UserModel.fromMbin(json['owner']! as JsonMap), moderators: ((json['moderators'] ?? []) as List) .map((user) => UserModel.fromMbin(user as JsonMap)) .toList(), - subscriptionsCount: json['subscriptionsCount'] as int, - threadCount: json['entryCount'] as int, - threadCommentCount: json['entryCommentCount'] as int, - microblogCount: json['postCount'] as int, - microblogCommentCount: json['postCommentCount'] as int, - isAdult: json['isAdult'] as bool, + subscriptionsCount: json['subscriptionsCount']! as int, + threadCount: json['entryCount']! as int, + threadCommentCount: json['entryCommentCount']! as int, + microblogCount: json['postCount']! as int, + microblogCommentCount: json['postCommentCount']! as int, + isAdult: json['isAdult']! as bool, isUserSubscribed: json['isUserSubscribed'] as bool?, isBlockedByUser: json['isBlockedByUser'] as bool?, isPostingRestrictedToMods: @@ -94,7 +94,7 @@ abstract class DetailedCommunityModel with _$DetailedCommunityModel { notificationControlStatus: json['notificationStatus'] == null ? null : NotificationControlStatus.fromJson( - json['notificationStatus'] as String, + json['notificationStatus']! as String, ), flairs: [], apId: json['apProfileId'] as String?, @@ -106,30 +106,30 @@ abstract class DetailedCommunityModel with _$DetailedCommunityModel { } factory DetailedCommunityModel.fromLemmy(JsonMap json) { - final lemmyCommunity = json['community'] as JsonMap; - final lemmyCounts = json['counts'] as JsonMap; + final lemmyCommunity = json['community']! as JsonMap; + final lemmyCounts = json['counts']! as JsonMap; final community = DetailedCommunityModel( - id: lemmyCommunity['id'] as int, + id: lemmyCommunity['id']! as int, name: getLemmyPiefedActorName(lemmyCommunity), - title: lemmyCommunity['title'] as String, + title: lemmyCommunity['title']! as String, icon: lemmyGetOptionalImage(lemmyCommunity['icon'] as String?), description: lemmyCommunity['description'] as String?, owner: null, moderators: [], - subscriptionsCount: lemmyCounts['subscribers'] as int, - threadCount: lemmyCounts['posts'] as int, - threadCommentCount: lemmyCounts['comments'] as int, + subscriptionsCount: lemmyCounts['subscribers']! as int, + threadCount: lemmyCounts['posts']! as int, + threadCommentCount: lemmyCounts['comments']! as int, microblogCount: null, microblogCommentCount: null, - isAdult: lemmyCommunity['nsfw'] as bool, - isUserSubscribed: (json['subscribed'] as String) != 'NotSubscribed', + isAdult: lemmyCommunity['nsfw']! as bool, + isUserSubscribed: (json['subscribed']! as String) != 'NotSubscribed', isBlockedByUser: json['blocked'] as bool?, isPostingRestrictedToMods: - (lemmyCommunity['posting_restricted_to_mods']) as bool, + lemmyCommunity['posting_restricted_to_mods']! as bool, notificationControlStatus: null, flairs: [], - apId: lemmyCommunity['actor_id'] as String, + apId: lemmyCommunity['actor_id']! as String, ); communityMentionCache[community.name] = community; @@ -139,42 +139,43 @@ abstract class DetailedCommunityModel with _$DetailedCommunityModel { factory DetailedCommunityModel.fromPiefed(JsonMap json) { final communityView = json['community_view'] as JsonMap? ?? json; - final piefedCommunity = communityView['community'] as JsonMap; - final piefedCounts = communityView['counts'] as JsonMap; + final piefedCommunity = communityView['community']! as JsonMap; + final piefedCounts = communityView['counts']! as JsonMap; final community = DetailedCommunityModel( - id: piefedCommunity['id'] as int, + id: piefedCommunity['id']! as int, name: getLemmyPiefedActorName(piefedCommunity), - title: piefedCommunity['title'] as String, + title: piefedCommunity['title']! as String, icon: lemmyGetOptionalImage(piefedCommunity['icon'] as String?), description: piefedCommunity['description'] as String?, owner: ((json['moderators'] ?? []) as List) .map( - (user) => - UserModel.fromPiefed((user as JsonMap)['moderator'] as JsonMap), + (user) => UserModel.fromPiefed( + (user as JsonMap)['moderator']! as JsonMap, + ), ) .toList() .firstOrNull, moderators: ((json['moderators'] ?? []) as List) .map( - (user) => - UserModel.fromPiefed((user as JsonMap)['moderator'] as JsonMap), + (user) => UserModel.fromPiefed( + (user as JsonMap)['moderator']! as JsonMap, + ), ) .toList(), - subscriptionsCount: piefedCounts['subscriptions_count'] as int, - threadCount: piefedCounts['post_count'] as int, - threadCommentCount: piefedCounts['post_reply_count'] as int, + subscriptionsCount: piefedCounts['subscriptions_count']! as int, + threadCount: piefedCounts['post_count']! as int, + threadCommentCount: piefedCounts['post_reply_count']! as int, microblogCount: null, microblogCommentCount: null, - isAdult: piefedCommunity['nsfw'] as bool, + isAdult: piefedCommunity['nsfw']! as bool, isUserSubscribed: - (communityView['subscribed'] as String) != 'NotSubscribed', + (communityView['subscribed']! as String) != 'NotSubscribed', isBlockedByUser: communityView['blocked'] as bool?, - isPostingRestrictedToMods: - (piefedCommunity['restricted_to_mods']) as bool, + isPostingRestrictedToMods: piefedCommunity['restricted_to_mods']! as bool, notificationControlStatus: communityView['activity_alert'] == null ? null - : communityView['activity_alert'] as bool + : communityView['activity_alert']! as bool ? NotificationControlStatus.loud : NotificationControlStatus.default_, flairs: @@ -191,7 +192,7 @@ abstract class DetailedCommunityModel with _$DetailedCommunityModel { ) .toList() ?? [], - apId: piefedCommunity['actor_id'] as String, + apId: piefedCommunity['actor_id']! as String, ); communityMentionCache[community.name] = community; @@ -210,24 +211,24 @@ abstract class CommunityModel with _$CommunityModel { }) = _CommunityModel; factory CommunityModel.fromMbin(JsonMap json) => CommunityModel( - id: json['magazineId'] as int, - name: json['name'] as String, + id: json['magazineId']! as int, + name: json['name']! as String, icon: mbinGetOptionalImage(json['icon'] as JsonMap?), apId: json['apProfileId'] as String?, ); factory CommunityModel.fromLemmy(JsonMap json) => CommunityModel( - id: json['id'] as int, + id: json['id']! as int, name: getLemmyPiefedActorName(json), icon: lemmyGetOptionalImage(json['icon'] as String?), - apId: json['actor_id'] as String, + apId: json['actor_id']! as String, ); factory CommunityModel.fromPiefed(JsonMap json) => CommunityModel( - id: json['id'] as int, + id: json['id']! as int, name: getLemmyPiefedActorName(json), icon: lemmyGetOptionalImage(json['icon'] as String?), - apId: json['actor_id'] as String, + apId: json['actor_id']! as String, ); factory CommunityModel.fromDetailedCommunity( @@ -248,15 +249,15 @@ abstract class CommunityBanListModel with _$CommunityBanListModel { }) = _CommunityBanListModel; factory CommunityBanListModel.fromMbin(JsonMap json) => CommunityBanListModel( - items: (json['items'] as List) + items: (json['items']! as List) .map((item) => CommunityBanModel.fromMbin(item as JsonMap)) .toList(), - nextPage: mbinCalcNextPaginationPage(json['pagination'] as JsonMap), + nextPage: mbinCalcNextPaginationPage(json['pagination']! as JsonMap), ); factory CommunityBanListModel.fromPiefed(JsonMap json) => CommunityBanListModel( - items: (json['items'] as List) + items: (json['items']! as List) .map((item) => CommunityBanModel.fromPiefed(item as JsonMap)) .toList(), nextPage: json['next_page'] as String?, @@ -277,24 +278,24 @@ abstract class CommunityBanModel with _$CommunityBanModel { factory CommunityBanModel.fromMbin(JsonMap json) => CommunityBanModel( reason: json['reason'] as String?, expiresAt: optionalDateTime(json['expiresAt'] as String?), - community: CommunityModel.fromMbin(json['magazine'] as JsonMap), - bannedUser: UserModel.fromMbin(json['bannedUser'] as JsonMap), - bannedBy: UserModel.fromMbin(json['bannedBy'] as JsonMap), - expired: json['expired'] as bool, + community: CommunityModel.fromMbin(json['magazine']! as JsonMap), + bannedUser: UserModel.fromMbin(json['bannedUser']! as JsonMap), + bannedBy: UserModel.fromMbin(json['bannedBy']! as JsonMap), + expired: json['expired']! as bool, ); factory CommunityBanModel.fromLemmy(JsonMap json) { final expiration = json['expires'] != null - ? DateTime.parse(json['expires'] as String) + ? DateTime.parse(json['expires']! as String) : null; return CommunityBanModel( reason: json['reason'] as String?, expiresAt: expiration, - community: CommunityModel.fromLemmy(json['community'] as JsonMap), - bannedUser: UserModel.fromLemmy(json['banned_person'] as JsonMap), + community: CommunityModel.fromLemmy(json['community']! as JsonMap), + bannedUser: UserModel.fromLemmy(json['banned_person']! as JsonMap), bannedBy: json['moderator'] != null - ? UserModel.fromLemmy(json['moderator'] as JsonMap) + ? UserModel.fromLemmy(json['moderator']! as JsonMap) : null, expired: expiration?.isBefore(DateTime.now()) ?? false, ); @@ -303,10 +304,10 @@ abstract class CommunityBanModel with _$CommunityBanModel { factory CommunityBanModel.fromPiefed(JsonMap json) => CommunityBanModel( reason: json['reason'] as String?, expiresAt: optionalDateTime(json['expires_at'] as String?), - community: CommunityModel.fromPiefed(json['community'] as JsonMap), - bannedUser: UserModel.fromPiefed(json['banned_user'] as JsonMap), - bannedBy: UserModel.fromPiefed(json['banned_by'] as JsonMap), - expired: json['expired'] as bool, + community: CommunityModel.fromPiefed(json['community']! as JsonMap), + bannedUser: UserModel.fromPiefed(json['banned_user']! as JsonMap), + bannedBy: UserModel.fromPiefed(json['banned_by']! as JsonMap), + expired: json['expired']! as bool, ); } @@ -319,10 +320,10 @@ abstract class CommunityReportListModel with _$CommunityReportListModel { factory CommunityReportListModel.fromMbin(JsonMap json) => CommunityReportListModel( - items: (json['items'] as List) + items: (json['items']! as List) .map((item) => CommunityReportModel.fromMbin(item as JsonMap)) .toList(), - nextPage: mbinCalcNextPaginationPage(json['pagination'] as JsonMap), + nextPage: mbinCalcNextPaginationPage(json['pagination']! as JsonMap), ); } @@ -344,30 +345,30 @@ abstract class CommunityReportModel with _$CommunityReportModel { }) = _CommunityReportModel; factory CommunityReportModel.fromMbin(JsonMap json) { - String? type = json['type'] as String?; - PostModel? subjectPost = (switch (type) { - 'entry_report' => PostModel.fromMbinEntry(json['subject'] as JsonMap), - 'post_report' => PostModel.fromMbinPost(json['subject'] as JsonMap), + final type = json['type'] as String?; + final subjectPost = switch (type) { + 'entry_report' => PostModel.fromMbinEntry(json['subject']! as JsonMap), + 'post_report' => PostModel.fromMbinPost(json['subject']! as JsonMap), null => null, String() => null, - }); + }; - CommentModel? subjectComment = (switch (type) { + final subjectComment = switch (type) { 'entry_comment_report' => CommentModel.fromMbin( - json['subject'] as JsonMap, + json['subject']! as JsonMap, ), 'post_comment_report' => CommentModel.fromMbin( - json['subject'] as JsonMap, + json['subject']! as JsonMap, ), null => null, String() => null, - }); + }; return CommunityReportModel( - id: json['reportId'] as int, - community: CommunityModel.fromMbin(json['magazine'] as JsonMap), - reportedBy: UserModel.fromMbin(json['reporting'] as JsonMap), - reportedUser: UserModel.fromMbin(json['reported'] as JsonMap), + id: json['reportId']! as int, + community: CommunityModel.fromMbin(json['magazine']! as JsonMap), + reportedBy: UserModel.fromMbin(json['reporting']! as JsonMap), + reportedUser: UserModel.fromMbin(json['reported']! as JsonMap), subjectPost: subjectPost, subjectComment: subjectComment, reason: json['reason'] as String?, @@ -377,7 +378,7 @@ abstract class CommunityReportModel with _$CommunityReportModel { createdAt: optionalDateTime(json['createdAt'] as String?), consideredAt: optionalDateTime(json['consideredAt'] as String?), consideredBy: json['consideredBy'] != null - ? UserModel.fromMbin(json['consideredBy'] as JsonMap) + ? UserModel.fromMbin(json['consideredBy']! as JsonMap) : null, weight: json['weight'] as int?, ); diff --git a/lib/src/models/config_share.dart b/lib/src/models/config_share.dart index e1495e3f..af4c8327 100644 --- a/lib/src/models/config_share.dart +++ b/lib/src/models/config_share.dart @@ -11,8 +11,6 @@ enum ConfigShareType { profile, filterList, feed } @freezed abstract class ConfigShare with _$ConfigShare { - const ConfigShare._(); - @JsonSerializable(explicitToJson: true, includeIfNull: false) const factory ConfigShare({ // Interstellar version @@ -23,6 +21,7 @@ abstract class ConfigShare with _$ConfigShare { required JsonMap payload, required String hash, }) = _ConfigShare; + const ConfigShare._(); factory ConfigShare.fromJson(JsonMap json) => _$ConfigShareFromJson(json); @@ -47,7 +46,8 @@ abstract class ConfigShare with _$ConfigShare { return config.copyWith(hash: hash); } - // Once the config is parsed, use this to pass in the original json string and verify the hash. + // Once the config is parsed, use this to pass in the original json string + // and verify the hash. bool verifyHash(String jsonStr) { // Remove instance of hash from original string final hashToCheck = strToMd5Base64(jsonStr.replaceFirst(hash, '')); diff --git a/lib/src/models/domain.dart b/lib/src/models/domain.dart index 2df1b995..39a52dca 100644 --- a/lib/src/models/domain.dart +++ b/lib/src/models/domain.dart @@ -12,10 +12,10 @@ abstract class DomainListModel with _$DomainListModel { }) = _DomainListModel; factory DomainListModel.fromMbin(JsonMap json) => DomainListModel( - items: (json['items'] as List) + items: (json['items']! as List) .map((post) => DomainModel.fromMbin(post as JsonMap)) .toList(), - nextPage: mbinCalcNextPaginationPage(json['pagination'] as JsonMap), + nextPage: mbinCalcNextPaginationPage(json['pagination']! as JsonMap), ); } @@ -31,10 +31,10 @@ abstract class DomainModel with _$DomainModel { }) = _DomainModel; factory DomainModel.fromMbin(JsonMap json) => DomainModel( - id: json['domainId'] as int, - name: json['name'] as String, - entryCount: json['entryCount'] as int, - subscriptionsCount: json['subscriptionsCount'] as int, + id: json['domainId']! as int, + name: json['name']! as String, + entryCount: json['entryCount']! as int, + subscriptionsCount: json['subscriptionsCount']! as int, isUserSubscribed: json['isUserSubscribed'] as bool?, isBlockedByUser: json['isBlockedByUser'] as bool?, ); diff --git a/lib/src/models/emoji_reaction.dart b/lib/src/models/emoji_reaction.dart index d55805e6..c7c02854 100644 --- a/lib/src/models/emoji_reaction.dart +++ b/lib/src/models/emoji_reaction.dart @@ -13,9 +13,9 @@ abstract class EmojiReactionModel with _$EmojiReactionModel { }) = _EmojiReactionModel; factory EmojiReactionModel.fromPieFed(JsonMap json) => EmojiReactionModel( - authors: (json['authors'] as List).cast(), - count: json['count'] as int, - token: json['token'] as String, - url: json['url'] as String, + authors: (json['authors']! as List).cast(), + count: json['count']! as int, + token: json['token']! as String, + url: json['url']! as String, ); } diff --git a/lib/src/models/feed.dart b/lib/src/models/feed.dart index bc69805b..01e94b15 100644 --- a/lib/src/models/feed.dart +++ b/lib/src/models/feed.dart @@ -1,7 +1,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:interstellar/src/utils/utils.dart'; +import 'package:interstellar/src/models/image.dart'; import 'package:interstellar/src/utils/models.dart'; -import 'image.dart'; +import 'package:interstellar/src/utils/utils.dart'; part 'feed.freezed.dart'; @@ -12,7 +12,8 @@ abstract class FeedListModel with _$FeedListModel { factory FeedListModel.fromPiefed(JsonMap json) { final items = - ((json['feeds'] as List?) ?? (json['topics'] as List)) + ((json['feeds'] as List?) ?? + (json['topics']! as List)) .map((feed) => FeedModel.fromPiefed(feed)) .toList(); @@ -58,15 +59,15 @@ abstract class FeedModel with _$FeedModel { factory FeedModel.fromPiefed(JsonMap json) { return FeedModel( - id: json['id'] as int, + id: json['id']! as int, userId: json['user_id'] as int?, - title: json['title'] as String, - name: json['name'] as String, + title: json['title']! as String, + name: json['name']! as String, description: json['description'] as String?, isNSFW: json['nsfw'] as bool?, isNSFL: json['nsfl'] as bool?, subscriptionCount: json['subscriptions_count'] as int?, - communityCount: json['communities_count'] as int, + communityCount: json['communities_count']! as int, public: json['public'] as bool?, parentId: (json['parent_feed_id'] as int?) ?? (json['parent_topic_id'] as int?), @@ -77,13 +78,13 @@ abstract class FeedModel with _$FeedModel { owner: json['owner'] as bool?, published: json['published'] == null ? null - : DateTime.parse(json['published'] as String), + : DateTime.parse(json['published']! as String), updated: json['updated'] == null ? null - : DateTime.parse(json['updated'] as String), + : DateTime.parse(json['updated']! as String), children: json['children'] == null ? [] - : (json['children'] as List) + : (json['children']! as List) .map((child) => FeedModel.fromPiefed(child)) .toList(), ); diff --git a/lib/src/models/image.dart b/lib/src/models/image.dart index d217874a..d0186796 100644 --- a/lib/src/models/image.dart +++ b/lib/src/models/image.dart @@ -14,7 +14,7 @@ abstract class ImageModel with _$ImageModel { }) = _ImageModel; factory ImageModel.fromMbin(JsonMap json) => ImageModel( - src: (json['storageUrl'] ?? json['sourceUrl']) as String, + src: (json['storageUrl'] ?? json['sourceUrl'])! as String, altText: json['altText'] as String?, blurHash: json['blurHash'] as String?, blurHashWidth: json['width'] as int?, diff --git a/lib/src/models/message.dart b/lib/src/models/message.dart index 9fe5a635..f5ff6146 100644 --- a/lib/src/models/message.dart +++ b/lib/src/models/message.dart @@ -13,10 +13,10 @@ abstract class MessageListModel with _$MessageListModel { }) = _MessageListModel; factory MessageListModel.fromMbin(JsonMap json) => MessageListModel( - items: (json['items'] as List) + items: (json['items']! as List) .map((post) => MessageThreadModel.fromMbin(post as JsonMap)) .toList(), - nextPage: mbinCalcNextPaginationPage(json['pagination'] as JsonMap), + nextPage: mbinCalcNextPaginationPage(json['pagination']! as JsonMap), ); factory MessageListModel.fromLemmy( @@ -24,16 +24,16 @@ abstract class MessageListModel with _$MessageListModel { int myUserId, { int? filterByThreadId, }) { - Map threads = {}; + final threads = {}; - for (final message in json['private_messages'] as List) { - final creator = (message)['creator'] as JsonMap; - final recipient = (message)['recipient'] as JsonMap; + for (final message in json['private_messages']! as List) { + final creator = message['creator'] as JsonMap; + final recipient = message['recipient'] as JsonMap; // Use the userId of the other person as the threadId - final threadId = (creator['id'] as int) == myUserId - ? recipient['id'] as int - : creator['id'] as int; + final threadId = (creator['id']! as int) == myUserId + ? recipient['id']! as int + : creator['id']! as int; if (filterByThreadId != null && filterByThreadId != threadId) continue; @@ -46,13 +46,11 @@ abstract class MessageListModel with _$MessageListModel { }; } - (threads[threadId]!['messages'] as List).add(message); + (threads[threadId]!['messages']! as List).add(message); } return MessageListModel( - items: threads.values - .map((thread) => MessageThreadModel.fromLemmy(thread)) - .toList(), + items: threads.values.map(MessageThreadModel.fromLemmy).toList(), nextPage: json['next_page'] as String?, ); } @@ -62,16 +60,16 @@ abstract class MessageListModel with _$MessageListModel { int myUserId, { int? filterByThreadId, }) { - Map threads = {}; + final threads = {}; - for (final message in json['private_messages'] as List) { - final creator = (message)['creator'] as JsonMap; - final recipient = (message)['recipient'] as JsonMap; + for (final message in json['private_messages']! as List) { + final creator = message['creator'] as JsonMap; + final recipient = message['recipient'] as JsonMap; // Use the userId of the other person as the threadId - final threadId = (creator['id'] as int) == myUserId - ? recipient['id'] as int - : creator['id'] as int; + final threadId = (creator['id']! as int) == myUserId + ? recipient['id']! as int + : creator['id']! as int; if (filterByThreadId != null && filterByThreadId != threadId) continue; @@ -84,13 +82,11 @@ abstract class MessageListModel with _$MessageListModel { }; } - (threads[threadId]!['messages'] as List).add(message); + (threads[threadId]!['messages']! as List).add(message); } return MessageListModel( - items: threads.values - .map((thread) => MessageThreadModel.fromPiefed(thread)) - .toList(), + items: threads.values.map(MessageThreadModel.fromPiefed).toList(), nextPage: json['next_page'] as String?, ); } @@ -106,44 +102,44 @@ abstract class MessageThreadModel with _$MessageThreadModel { }) = _MessageThreadModel; factory MessageThreadModel.fromMbin(JsonMap json) => MessageThreadModel( - id: json['threadId'] as int, - participants: (json['participants'] as List) + id: json['threadId']! as int, + participants: (json['participants']! as List) .map( (participant) => DetailedUserModel.fromMbin(participant as JsonMap), ) .toList(), - messages: ((json['messages'] ?? json['items']) as List) + messages: ((json['messages'] ?? json['items'])! as List) .map((message) => MessageThreadItemModel.fromMbin(message as JsonMap)) .toList(), nextPage: json['pagination'] == null ? null - : mbinCalcNextPaginationPage(json['pagination'] as JsonMap), + : mbinCalcNextPaginationPage(json['pagination']! as JsonMap), ); factory MessageThreadModel.fromLemmy(JsonMap json) => MessageThreadModel( - id: json['threadId'] as int, - participants: (json['participants'] as List) + id: json['threadId']! as int, + participants: (json['participants']! as List) .map( (participant) => DetailedUserModel.fromLemmy({'person': participant as JsonMap}), ) .toList(), - messages: (json['messages'] as List) - .map((message) => MessageThreadItemModel.fromLemmy(message)) + messages: (json['messages']! as List) + .map(MessageThreadItemModel.fromLemmy) .toList(), nextPage: json['next_page'] as String?, ); factory MessageThreadModel.fromPiefed(JsonMap json) => MessageThreadModel( - id: json['threadId'] as int, - participants: (json['participants'] as List) + id: json['threadId']! as int, + participants: (json['participants']! as List) .map( (participant) => DetailedUserModel.fromPiefed({'person': participant as JsonMap}), ) .toList(), - messages: (json['messages'] as List).reversed - .map((message) => MessageThreadItemModel.fromPiefed(message)) + messages: (json['messages']! as List).reversed + .map(MessageThreadItemModel.fromPiefed) .toList(), nextPage: json['next_page'] as String?, ); @@ -161,34 +157,34 @@ abstract class MessageThreadItemModel with _$MessageThreadItemModel { factory MessageThreadItemModel.fromMbin(JsonMap json) => MessageThreadItemModel( - id: json['messageId'] as int, - sender: UserModel.fromMbin(json['sender'] as JsonMap), - body: json['body'] as String, - createdAt: DateTime.parse(json['createdAt'] as String), - isRead: json['status'] as String == 'read', + id: json['messageId']! as int, + sender: UserModel.fromMbin(json['sender']! as JsonMap), + body: json['body']! as String, + createdAt: DateTime.parse(json['createdAt']! as String), + isRead: json['status']! as String == 'read', ); factory MessageThreadItemModel.fromLemmy(JsonMap json) { - final pm = json['private_message'] as JsonMap; + final pm = json['private_message']! as JsonMap; return MessageThreadItemModel( - id: pm['id'] as int, - sender: UserModel.fromLemmy(json['creator'] as JsonMap), - body: pm['content'] as String, - createdAt: DateTime.parse(pm['published'] as String), - isRead: pm['read'] as bool, + id: pm['id']! as int, + sender: UserModel.fromLemmy(json['creator']! as JsonMap), + body: pm['content']! as String, + createdAt: DateTime.parse(pm['published']! as String), + isRead: pm['read']! as bool, ); } factory MessageThreadItemModel.fromPiefed(JsonMap json) { - final pm = json['private_message'] as JsonMap; + final pm = json['private_message']! as JsonMap; return MessageThreadItemModel( - id: pm['id'] as int, - sender: UserModel.fromPiefed(json['creator'] as JsonMap), - body: pm['content'] as String, - createdAt: DateTime.parse(pm['published'] as String), - isRead: pm['read'] as bool, + id: pm['id']! as int, + sender: UserModel.fromPiefed(json['creator']! as JsonMap), + body: pm['content']! as String, + createdAt: DateTime.parse(pm['published']! as String), + isRead: pm['read']! as bool, ); } } diff --git a/lib/src/models/modlog.dart b/lib/src/models/modlog.dart index 134f08ff..c59fd491 100644 --- a/lib/src/models/modlog.dart +++ b/lib/src/models/modlog.dart @@ -1,12 +1,11 @@ import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:interstellar/src/api/moderation.dart'; import 'package:interstellar/src/models/comment.dart'; import 'package:interstellar/src/models/community.dart'; import 'package:interstellar/src/models/user.dart'; import 'package:interstellar/src/utils/models.dart'; import 'package:interstellar/src/utils/utils.dart'; -import '../api/moderation.dart'; - part 'modlog.freezed.dart'; @freezed @@ -24,28 +23,28 @@ abstract class ModlogItemModel with _$ModlogItemModel { }) = _ModlogItemModel; factory ModlogItemModel.fromMbin(JsonMap json) { - final type = ModLogType.fromMbin(json['type'] as String); + final type = ModLogType.fromMbin(json['type']! as String); return ModlogItemModel( type: type, - createdAt: DateTime.parse(json['createdAt'] as String), + createdAt: DateTime.parse(json['createdAt']! as String), reason: '', - community: CommunityModel.fromMbin(json['magazine'] as JsonMap), - moderator: DetailedUserModel.fromMbin(json['moderator'] as JsonMap), + community: CommunityModel.fromMbin(json['magazine']! as JsonMap), + moderator: DetailedUserModel.fromMbin(json['moderator']! as JsonMap), postId: switch (type) { ModLogType.all => null, ModLogType.postDeleted => - (json['subject'] as JsonMap)['entryId'] as int, + (json['subject']! as JsonMap)['entryId']! as int, ModLogType.postRestored => - (json['subject'] as JsonMap)['entryId'] as int, + (json['subject']! as JsonMap)['entryId']! as int, ModLogType.commentDeleted => null, ModLogType.commentRestored => null, ModLogType.postPinned => null, ModLogType.postUnpinned => null, ModLogType.microblogPostDeleted => - (json['subject'] as JsonMap)['postId'] as int, + (json['subject']! as JsonMap)['postId']! as int, ModLogType.microblogPostRestored => - (json['subject'] as JsonMap)['postId'] as int, + (json['subject']! as JsonMap)['postId']! as int, ModLogType.microblogCommentDeleted => null, ModLogType.microblogCommentRestored => null, ModLogType.ban => null, @@ -60,17 +59,17 @@ abstract class ModlogItemModel with _$ModlogItemModel { postTitle: switch (type) { ModLogType.all => null, ModLogType.postDeleted => - (json['subject'] as JsonMap)['title'] as String, + (json['subject']! as JsonMap)['title']! as String, ModLogType.postRestored => - (json['subject'] as JsonMap)['title'] as String, + (json['subject']! as JsonMap)['title']! as String, ModLogType.commentDeleted => null, ModLogType.commentRestored => null, ModLogType.postPinned => null, ModLogType.postUnpinned => null, ModLogType.microblogPostDeleted => - (json['subject'] as JsonMap)['body'] as String, + (json['subject']! as JsonMap)['body']! as String, ModLogType.microblogPostRestored => - (json['subject'] as JsonMap)['body'] as String, + (json['subject']! as JsonMap)['body']! as String, ModLogType.microblogCommentDeleted => null, ModLogType.microblogCommentRestored => null, ModLogType.ban => null, @@ -87,20 +86,20 @@ abstract class ModlogItemModel with _$ModlogItemModel { ModLogType.postDeleted => null, ModLogType.postRestored => null, ModLogType.commentDeleted => CommentModel.fromMbin( - json['subject'] as JsonMap, + json['subject']! as JsonMap, ), ModLogType.commentRestored => CommentModel.fromMbin( - json['subject'] as JsonMap, + json['subject']! as JsonMap, ), ModLogType.postPinned => null, ModLogType.postUnpinned => null, ModLogType.microblogPostDeleted => null, ModLogType.microblogPostRestored => null, ModLogType.microblogCommentDeleted => CommentModel.fromMbin( - json['subject'] as JsonMap, + json['subject']! as JsonMap, ), ModLogType.microblogCommentRestored => CommentModel.fromMbin( - json['subject'] as JsonMap, + json['subject']! as JsonMap, ), ModLogType.ban => null, ModLogType.unban => null, @@ -124,10 +123,10 @@ abstract class ModlogItemModel with _$ModlogItemModel { ModLogType.microblogCommentDeleted => null, ModLogType.microblogCommentRestored => null, ModLogType.ban => DetailedUserModel.fromMbin( - (json['subject'] as JsonMap)['bannedUser'] as JsonMap, + (json['subject']! as JsonMap)['bannedUser']! as JsonMap, ), ModLogType.unban => DetailedUserModel.fromMbin( - (json['subject'] as JsonMap)['bannedUser'] as JsonMap, + (json['subject']! as JsonMap)['bannedUser']! as JsonMap, ), ModLogType.moderatorAdded => null, ModLogType.moderatorRemoved => null, @@ -143,15 +142,15 @@ abstract class ModlogItemModel with _$ModlogItemModel { JsonMap json, { required List<(String, int)> langCodeIdPairs, }) { - final type = ModLogType.values.byName(json['type'] as String); + final type = ModLogType.values.byName(json['type']! as String); return ModlogItemModel( type: type, - createdAt: DateTime.parse(json['createdAt'] as String), + createdAt: DateTime.parse(json['createdAt']! as String), reason: json['reason'] as String?, - community: CommunityModel.fromLemmy(json['community'] as JsonMap), + community: CommunityModel.fromLemmy(json['community']! as JsonMap), moderator: json['moderator'] != null - ? DetailedUserModel.fromLemmy(json['moderator'] as JsonMap) + ? DetailedUserModel.fromLemmy(json['moderator']! as JsonMap) : null, postId: (json['post'] as JsonMap?)?['id'] as int?, postTitle: (json['post'] as JsonMap?)?['name'] as String?, @@ -171,16 +170,16 @@ abstract class ModlogItemModel with _$ModlogItemModel { ModLogType.microblogCommentDeleted => null, ModLogType.microblogCommentRestored => null, ModLogType.ban => DetailedUserModel.fromLemmy( - json['banned_person'] as JsonMap, + json['banned_person']! as JsonMap, ), ModLogType.unban => DetailedUserModel.fromLemmy( - json['banned_person'] as JsonMap, + json['banned_person']! as JsonMap, ), ModLogType.moderatorAdded => DetailedUserModel.fromLemmy( - json['modded_person'] as JsonMap, + json['modded_person']! as JsonMap, ), ModLogType.moderatorRemoved => DetailedUserModel.fromLemmy( - json['modded_person'] as JsonMap, + json['modded_person']! as JsonMap, ), ModLogType.communityAdded => null, ModLogType.communityRemoved => null, @@ -199,83 +198,85 @@ abstract class ModlogListModel with _$ModlogListModel { }) = _ModlogListModel; factory ModlogListModel.fromMbin(JsonMap json) => ModlogListModel( - items: (json['items'] as List) + items: (json['items']! as List) .map((item) => ModlogItemModel.fromMbin(item as JsonMap)) .toList(), - nextPage: mbinCalcNextPaginationPage(json['pagination'] as JsonMap), + nextPage: mbinCalcNextPaginationPage(json['pagination']! as JsonMap), ); factory ModlogListModel.fromLemmy( JsonMap json, { required List<(String, int)> langCodeIdPairs, }) { - final removedPosts = (json['removed_posts'] as List) + final removedPosts = (json['removed_posts']! as List) .map( (item) => ModlogItemModel.fromLemmy({ - 'type': (item['mod_remove_post'] as JsonMap)['removed'] as bool + 'type': (item['mod_remove_post'] as JsonMap)['removed']! as bool ? ModLogType.postDeleted.name : ModLogType.postRestored.name, 'createdAt': - (item['mod_remove_post'] as JsonMap)['when_'] as String, + (item['mod_remove_post'] as JsonMap)['when_']! as String, 'reason': (item['mod_remove_post'] as JsonMap)['reason'] as String?, ...item, }, langCodeIdPairs: langCodeIdPairs), ) .toList(); - final lockedPosts = (json['locked_posts'] as List).map( + final lockedPosts = (json['locked_posts']! as List).map( (item) => ModlogItemModel.fromLemmy({ - 'type': (item['mod_lock_post'] as JsonMap)['locked'] as bool + 'type': (item['mod_lock_post'] as JsonMap)['locked']! as bool ? ModLogType.postLocked.name : ModLogType.postUnlocked.name, - 'createdAt': (item['mod_lock_post'] as JsonMap)['when_'] as String, + 'createdAt': (item['mod_lock_post'] as JsonMap)['when_']! as String, ...item, }, langCodeIdPairs: langCodeIdPairs), ); - final featuredPosts = (json['featured_posts'] as List).map( + final featuredPosts = (json['featured_posts']! as List).map( (item) => ModlogItemModel.fromLemmy({ - 'type': (item['mod_feature_post'] as JsonMap)['featured'] as bool + 'type': (item['mod_feature_post'] as JsonMap)['featured']! as bool ? ModLogType.postPinned.name : ModLogType.postUnpinned.name, - 'createdAt': (item['mod_feature_post'] as JsonMap)['when_'] as String, + 'createdAt': (item['mod_feature_post'] as JsonMap)['when_']! as String, ...item, }, langCodeIdPairs: langCodeIdPairs), ); - final removedComments = (json['removed_comments'] as List).map( + final removedComments = (json['removed_comments']! as List).map( (item) => ModlogItemModel.fromLemmy({ - 'type': (item['mod_remove_comment'] as JsonMap)['removed'] as bool + 'type': (item['mod_remove_comment'] as JsonMap)['removed']! as bool ? ModLogType.commentDeleted.name : ModLogType.commentRestored.name, - 'createdAt': (item['mod_remove_comment'] as JsonMap)['when_'] as String, + 'createdAt': + (item['mod_remove_comment'] as JsonMap)['when_']! as String, 'reason': (item['mod_remove_comment'] as JsonMap)['reason'] as String?, ...item as JsonMap, - 'creator': {'person': item['commenter'] as JsonMap}, + 'creator': {'person': item['commenter']! as JsonMap}, }, langCodeIdPairs: langCodeIdPairs), ); - final removedCommunities = (json['removed_communities'] as List) + final removedCommunities = (json['removed_communities']! as List) .map( (item) => ModlogItemModel.fromLemmy({ - 'type': (item['mod_remove_community'] as JsonMap)['removed'] as bool + 'type': + (item['mod_remove_community'] as JsonMap)['removed']! as bool ? ModLogType.communityRemoved.name : ModLogType.communityAdded.name, 'createdAt': - (item['mod_remove_community'] as JsonMap)['when_'] as String, + (item['mod_remove_community'] as JsonMap)['when_']! as String, ...item as JsonMap, }, langCodeIdPairs: langCodeIdPairs), ); - final modBannedCommunity = (json['banned_from_community'] as List) + final modBannedCommunity = (json['banned_from_community']! as List) .map( (item) => ModlogItemModel.fromLemmy({ 'type': - (item['mod_ban_from_community'] as JsonMap)['banned'] as bool + (item['mod_ban_from_community'] as JsonMap)['banned']! as bool ? ModLogType.ban.name : ModLogType.unban.name, 'createdAt': - (item['mod_ban_from_community'] as JsonMap)['when_'] as String, + (item['mod_ban_from_community'] as JsonMap)['when_']! as String, ...item, 'reason': (item['mod_ban_from_community'] as JsonMap)['reason'] @@ -286,14 +287,14 @@ abstract class ModlogListModel with _$ModlogListModel { }, langCodeIdPairs: langCodeIdPairs), ); - final modAddedToCommunity = (json['added_to_community'] as List) + final modAddedToCommunity = (json['added_to_community']! as List) .map( (item) => ModlogItemModel.fromLemmy({ - 'type': (item['mod_add_community'] as JsonMap)['removed'] as bool + 'type': (item['mod_add_community'] as JsonMap)['removed']! as bool ? ModLogType.moderatorRemoved.name : ModLogType.moderatorAdded.name, 'createdAt': - (item['mod_add_community'] as JsonMap)['when_'] as String, + (item['mod_add_community'] as JsonMap)['when_']! as String, ...item, 'expires': (item['mod_add_community'] as JsonMap)['expires'] as String?, @@ -308,9 +309,7 @@ abstract class ModlogListModel with _$ModlogListModel { ...removedCommunities, ...modBannedCommunity, ...modAddedToCommunity, - ]; - - items.sort((a, b) => b.createdAt.compareTo(a.createdAt)); + ]..sort((a, b) => b.createdAt.compareTo(a.createdAt)); return ModlogListModel( items: items, diff --git a/lib/src/models/notification.dart b/lib/src/models/notification.dart index dc537b28..90392f6c 100644 --- a/lib/src/models/notification.dart +++ b/lib/src/models/notification.dart @@ -13,10 +13,10 @@ abstract class NotificationListModel with _$NotificationListModel { }) = _NotificationListModel; factory NotificationListModel.fromMbin(JsonMap json) => NotificationListModel( - items: (json['items'] as List) + items: (json['items']! as List) .map((post) => NotificationModel.fromMbin(post as JsonMap)) .toList(), - nextPage: mbinCalcNextPaginationPage(json['pagination'] as JsonMap), + nextPage: mbinCalcNextPaginationPage(json['pagination']! as JsonMap), ); factory NotificationListModel.fromLemmy( @@ -25,13 +25,13 @@ abstract class NotificationListModel with _$NotificationListModel { JsonMap repliesJson, ) => NotificationListModel( items: [ - ...(messagesJson['private_messages'] as List).map( + ...(messagesJson['private_messages']! as List).map( (item) => NotificationModel.fromLemmyMessage(item as JsonMap), ), - ...(mentionsJson['mentions'] as List).map( + ...(mentionsJson['mentions']! as List).map( (item) => NotificationModel.fromLemmyMention(item as JsonMap), ), - ...(repliesJson['replies'] as List).map( + ...(repliesJson['replies']! as List).map( (item) => NotificationModel.fromLemmyReply(item as JsonMap), ), ], @@ -40,7 +40,7 @@ abstract class NotificationListModel with _$NotificationListModel { factory NotificationListModel.fromPiefed(JsonMap json) => NotificationListModel( - items: (json['items'] as List) + items: (json['items']! as List) .map((notif) => NotificationModel.fromPiefed(notif as JsonMap)) .toList(), nextPage: json['next_page'] as String?, @@ -61,52 +61,52 @@ abstract class NotificationModel with _$NotificationModel { final subject = json['subject'] as JsonMap?; return NotificationModel( - id: json['notificationId'] as int, + id: json['notificationId']! as int, type: notificationTypeMap[json['type']], isRead: json['status'] == 'read', subject: subject, creator: subject == null ? null : UserModel.fromMbin( - (subject['user'] ?? subject['sender'] ?? subject['bannedBy']) + (subject['user'] ?? subject['sender'] ?? subject['bannedBy'])! as JsonMap, ), ); } factory NotificationModel.fromLemmyMessage(JsonMap json) { - final pm = json['private_message'] as JsonMap; + final pm = json['private_message']! as JsonMap; return NotificationModel( - id: pm['id'] as int, + id: pm['id']! as int, type: NotificationType.message, - isRead: pm['read'] as bool, + isRead: pm['read']! as bool, subject: json, - creator: UserModel.fromLemmy(json['creator'] as JsonMap), + creator: UserModel.fromLemmy(json['creator']! as JsonMap), ); } factory NotificationModel.fromLemmyMention(JsonMap json) { - final pm = json['person_mention'] as JsonMap; + final pm = json['person_mention']! as JsonMap; return NotificationModel( - id: pm['id'] as int, + id: pm['id']! as int, type: NotificationType.mention, - isRead: pm['read'] as bool, + isRead: pm['read']! as bool, subject: json, - creator: UserModel.fromLemmy(json['creator'] as JsonMap), + creator: UserModel.fromLemmy(json['creator']! as JsonMap), ); } factory NotificationModel.fromLemmyReply(JsonMap json) { - final cr = json['comment_reply'] as JsonMap; + final cr = json['comment_reply']! as JsonMap; return NotificationModel( - id: cr['id'] as int, + id: cr['id']! as int, type: NotificationType.reply, - isRead: cr['read'] as bool, + isRead: cr['read']! as bool, subject: json, - creator: UserModel.fromLemmy(json['creator'] as JsonMap), + creator: UserModel.fromLemmy(json['creator']! as JsonMap), ); } @@ -114,11 +114,11 @@ abstract class NotificationModel with _$NotificationModel { final subject = json; return NotificationModel( - id: json['notif_id'] as int, + id: json['notif_id']! as int, type: notificationTypeMap[json['notif_subtype']], isRead: json['status'] == 'Read', subject: subject, - creator: UserModel.fromPiefed(json['author'] as JsonMap), + creator: UserModel.fromPiefed(json['author']! as JsonMap), ); } } @@ -152,7 +152,7 @@ enum NotificationType { newSignup, } -const notificationTypeMap = { +const Map notificationTypeMap = { 'entry_created_notification': NotificationType.entryCreated, 'entry_edited_notification': NotificationType.entryEdited, 'entry_deleted_notification': NotificationType.entryDeleted, diff --git a/lib/src/models/poll.dart b/lib/src/models/poll.dart index a078ce52..bea5555c 100644 --- a/lib/src/models/poll.dart +++ b/lib/src/models/poll.dart @@ -25,14 +25,14 @@ abstract class PollModel with _$PollModel { }) = _PollModel; factory PollModel.fromPiefed(int postId, JsonMap json) { - final choices = json['choices'] as List; + final choices = json['choices']! as List; final votes = json['my_votes'] as List? ?? []; return PollModel( postId: postId, - endPoll: DateTime.parse(json['end_poll'] as String).toLocal(), - multiple: (json['mode'] as String) == 'multiple', - localOnly: json['local_only'] as bool, + endPoll: DateTime.parse(json['end_poll']! as String).toLocal(), + multiple: (json['mode']! as String) == 'multiple', + localOnly: json['local_only']! as bool, choices: choices .map( (choice) => PollChoiceModel( diff --git a/lib/src/models/post.dart b/lib/src/models/post.dart index f4feb045..fd8037b4 100644 --- a/lib/src/models/post.dart +++ b/lib/src/models/post.dart @@ -1,9 +1,9 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:interstellar/src/controller/database/database.dart'; +import 'package:interstellar/src/models/community.dart'; import 'package:interstellar/src/models/domain.dart'; import 'package:interstellar/src/models/emoji_reaction.dart'; import 'package:interstellar/src/models/image.dart'; -import 'package:interstellar/src/models/community.dart'; import 'package:interstellar/src/models/notification.dart'; import 'package:interstellar/src/models/poll.dart'; import 'package:interstellar/src/models/user.dart'; @@ -23,24 +23,24 @@ abstract class PostListModel with _$PostListModel { }) = _PostListModel; factory PostListModel.fromMbinEntries(JsonMap json) => PostListModel( - items: (json['items'] as List) + items: (json['items']! as List) .map((post) => PostModel.fromMbinEntry(post as JsonMap)) .toList(), - nextPage: mbinCalcNextPaginationPage(json['pagination'] as JsonMap), + nextPage: mbinCalcNextPaginationPage(json['pagination']! as JsonMap), ); factory PostListModel.fromMbinPosts(JsonMap json) => PostListModel( - items: (json['items'] as List) + items: (json['items']! as List) .map((post) => PostModel.fromMbinPost(post as JsonMap)) .toList(), - nextPage: mbinCalcNextPaginationPage(json['pagination'] as JsonMap), + nextPage: mbinCalcNextPaginationPage(json['pagination']! as JsonMap), ); factory PostListModel.fromLemmy( JsonMap json, { required List<(String, int)> langCodeIdPairs, }) => PostListModel( - items: (json['posts'] as List) + items: (json['posts']! as List) .map((post) => {'post_view': post}) .map( (post) => PostModel.fromLemmy( @@ -56,7 +56,7 @@ abstract class PostListModel with _$PostListModel { JsonMap json, { required List<(String, int)> langCodeIdPairs, }) => PostListModel( - items: (json['posts'] as List) + items: (json['posts']! as List) .map((post) => {'post_view': post}) .map( (post) => PostModel.fromPiefed( @@ -108,12 +108,12 @@ abstract class PostModel with _$PostModel { factory PostModel.fromMbinEntry(JsonMap json) => PostModel( type: PostType.thread, - id: json['entryId'] as int, - user: DetailedUserModel.fromMbin(json['user'] as JsonMap), - community: CommunityModel.fromMbin(json['magazine'] as JsonMap), + id: json['entryId']! as int, + user: DetailedUserModel.fromMbin(json['user']! as JsonMap), + community: CommunityModel.fromMbin(json['magazine']! as JsonMap), domain: json['domain'] == null ? null - : DomainModel.fromMbin(json['domain'] as JsonMap), + : DomainModel.fromMbin(json['domain']! as JsonMap), title: json['title'] as String?, // Only include link if it's not an Image post url: (json['type'] == 'image' && json['image'] != null) @@ -121,27 +121,27 @@ abstract class PostModel with _$PostModel { : json['url'] as String?, image: mbinGetOptionalImage(json['image'] as JsonMap?), body: json['body'] as String?, - lang: json['lang'] as String, - numComments: json['numComments'] as int, + lang: json['lang']! as String, + numComments: json['numComments']! as int, upvotes: json['favourites'] as int?, downvotes: json['dv'] as int?, boosts: json['uv'] as int?, - myVote: (json['isFavourited'] as bool?) == true + myVote: (json['isFavourited'] as bool?) ?? false ? 1 : ((json['userVote'] as int?) == -1 ? -1 : 0), myBoost: (json['userVote'] as int?) == 1, - isOC: json['isOc'] as bool, - isNSFW: json['isAdult'] as bool, - isPinned: json['isPinned'] as bool, - createdAt: DateTime.parse(json['createdAt'] as String), + isOC: json['isOc']! as bool, + isNSFW: json['isAdult']! as bool, + isPinned: json['isPinned']! as bool, + createdAt: DateTime.parse(json['createdAt']! as String), editedAt: optionalDateTime(json['editedAt'] as String?), - lastActive: DateTime.parse(json['lastActive'] as String), - visibility: json['visibility'] as String, + lastActive: DateTime.parse(json['lastActive']! as String), + visibility: json['visibility']! as String, canAuthUserModerate: json['canAuthUserModerate'] as bool?, notificationControlStatus: json['notificationStatus'] == null ? null : NotificationControlStatus.fromJson( - json['notificationStatus'] as String, + json['notificationStatus']! as String, ), bookmarks: optionalStringList(json['bookmarks']), read: false, @@ -158,35 +158,35 @@ abstract class PostModel with _$PostModel { factory PostModel.fromMbinPost(JsonMap json) => PostModel( type: PostType.microblog, - id: json['postId'] as int, - user: DetailedUserModel.fromMbin(json['user'] as JsonMap), - community: CommunityModel.fromMbin(json['magazine'] as JsonMap), + id: json['postId']! as int, + user: DetailedUserModel.fromMbin(json['user']! as JsonMap), + community: CommunityModel.fromMbin(json['magazine']! as JsonMap), domain: null, title: null, url: null, image: mbinGetOptionalImage(json['image'] as JsonMap?), body: json['body'] as String?, - lang: json['lang'] as String, - numComments: json['comments'] as int, + lang: json['lang']! as String, + numComments: json['comments']! as int, upvotes: json['favourites'] as int?, downvotes: json['dv'] as int?, boosts: json['uv'] as int?, - myVote: (json['isFavourited'] as bool?) == true + myVote: (json['isFavourited'] as bool?) ?? false ? 1 : ((json['userVote'] as int?) == -1 ? -1 : 0), myBoost: (json['userVote'] as int?) == 1, isOC: null, - isNSFW: json['isAdult'] as bool, - isPinned: json['isPinned'] as bool, - createdAt: DateTime.parse(json['createdAt'] as String), + isNSFW: json['isAdult']! as bool, + isPinned: json['isPinned']! as bool, + createdAt: DateTime.parse(json['createdAt']! as String), editedAt: optionalDateTime(json['editedAt'] as String?), - lastActive: DateTime.parse(json['lastActive'] as String), - visibility: json['visibility'] as String, + lastActive: DateTime.parse(json['lastActive']! as String), + visibility: json['visibility']! as String, canAuthUserModerate: json['canAuthUserModerate'] as bool?, notificationControlStatus: json['notificationStatus'] == null ? null : NotificationControlStatus.fromJson( - json['notificationStatus'] as String, + json['notificationStatus']! as String, ), bookmarks: optionalStringList(json['bookmarks']), read: false, @@ -201,26 +201,28 @@ abstract class PostModel with _$PostModel { JsonMap json, { required List<(String, int)> langCodeIdPairs, }) { - final postView = json['post_view'] as JsonMap; - final lemmyPost = postView['post'] as JsonMap; + final postView = json['post_view']! as JsonMap; + final lemmyPost = postView['post']! as JsonMap; final lemmyCounts = postView['counts'] as JsonMap?; final isImagePost = - ((lemmyPost['url_content_type'] != null && - (lemmyPost['url_content_type'] as String).startsWith('image/')) || + (lemmyPost['url_content_type'] != null && + (lemmyPost['url_content_type']! as String).startsWith('image/')) || (lemmyPost['url'] != null && - (lookupMimeType(lemmyPost['url'] as String)?.startsWith('image/') ?? - false))); + (lookupMimeType( + lemmyPost['url']! as String, + )?.startsWith('image/') ?? + false)); final imageDetails = json['image_details'] as JsonMap?; return PostModel( type: PostType.thread, - id: lemmyPost['id'] as int, - user: DetailedUserModel.fromLemmy(postView['creator'] as JsonMap), - community: CommunityModel.fromLemmy(postView['community'] as JsonMap), + id: lemmyPost['id']! as int, + user: DetailedUserModel.fromLemmy(postView['creator']! as JsonMap), + community: CommunityModel.fromLemmy(postView['community']! as JsonMap), domain: null, - title: lemmyPost['name'] as String, + title: lemmyPost['name']! as String, // Only include link if it's not an Image post url: isImagePost ? null : lemmyPost['url'] as String?, image: lemmyGetOptionalImage( @@ -232,7 +234,7 @@ abstract class PostModel with _$PostModel { ), body: lemmyPost['body'] as String?, lang: langCodeIdPairs - .where((pair) => pair.$2 == lemmyPost['language_id'] as int) + .where((pair) => pair.$2 == lemmyPost['language_id']! as int) .firstOrNull ?.$1, numComments: lemmyCounts?['comments'] as int? ?? 0, @@ -242,23 +244,22 @@ abstract class PostModel with _$PostModel { myVote: postView['my_vote'] as int?, myBoost: null, isOC: null, - isNSFW: lemmyPost['nsfw'] as bool, + isNSFW: lemmyPost['nsfw']! as bool, isPinned: - lemmyPost['featured_community'] as bool || - lemmyPost['featured_local'] as bool, - createdAt: DateTime.parse(lemmyPost['published'] as String), + lemmyPost['featured_community']! as bool || + lemmyPost['featured_local']! as bool, + createdAt: DateTime.parse(lemmyPost['published']! as String), editedAt: optionalDateTime(lemmyPost['updated'] as String?), lastActive: lemmyCounts == null ? DateTime.now() - : DateTime.parse(lemmyCounts['newest_comment_time'] as String), + : DateTime.parse(lemmyCounts['newest_comment_time']! as String), visibility: 'visible', canAuthUserModerate: null, notificationControlStatus: null, bookmarks: [ // Empty string indicates post is saved. No string indicates post is not saved. - if (((postView['saved'] as bool?) != null) - ? postView['saved'] as bool - : false) + if (((postView['saved'] as bool?) != null) && + postView['saved']! as bool) '', ], read: postView['read'] as bool? ?? false, @@ -275,7 +276,7 @@ abstract class PostModel with _$PostModel { [], flairs: [], poll: null, - apId: lemmyPost['ap_id'] as String, + apId: lemmyPost['ap_id']! as String, emojiReactions: null, ); } @@ -285,27 +286,27 @@ abstract class PostModel with _$PostModel { required List<(String, int)> langCodeIdPairs, }) { final postView = json['post_view'] as JsonMap? ?? json; - final piefedPost = postView['post'] as JsonMap; - final piefedCounts = postView['counts'] as JsonMap; + final piefedPost = postView['post']! as JsonMap; + final piefedCounts = postView['counts']! as JsonMap; final isImagePost = - ((piefedPost['url_content_type'] != null && - (piefedPost['url_content_type'] as String).startsWith('image/')) || + (piefedPost['url_content_type'] != null && + (piefedPost['url_content_type']! as String).startsWith('image/')) || (piefedPost['url'] != null && (lookupMimeType( - piefedPost['url'] as String, + piefedPost['url']! as String, )?.startsWith('image/') ?? - false))); + false)); final imageDetails = piefedPost['image_details'] as JsonMap?; return PostModel( type: PostType.thread, - id: piefedPost['id'] as int, - user: DetailedUserModel.fromPiefed(postView['creator'] as JsonMap), - community: CommunityModel.fromPiefed(postView['community'] as JsonMap), + id: piefedPost['id']! as int, + user: DetailedUserModel.fromPiefed(postView['creator']! as JsonMap), + community: CommunityModel.fromPiefed(postView['community']! as JsonMap), domain: null, - title: piefedPost['title'] as String, + title: piefedPost['title']! as String, // Only include link if it's not an Image post url: isImagePost ? null : piefedPost['url'] as String?, image: lemmyGetOptionalImage( @@ -317,31 +318,33 @@ abstract class PostModel with _$PostModel { ), body: piefedPost['body'] as String?, lang: langCodeIdPairs - .where((pair) => pair.$2 == piefedPost['language_id'] as int) + .where((pair) => pair.$2 == piefedPost['language_id']! as int) .firstOrNull ?.$1, - numComments: piefedCounts['comments'] as int, - upvotes: piefedCounts['upvotes'] as int, - downvotes: piefedCounts['downvotes'] as int, + numComments: piefedCounts['comments']! as int, + upvotes: piefedCounts['upvotes']! as int, + downvotes: piefedCounts['downvotes']! as int, boosts: null, myVote: postView['my_vote'] as int?, myBoost: null, isOC: null, - isNSFW: piefedPost['nsfw'] as bool, - isPinned: piefedPost['sticky'] as bool, - createdAt: DateTime.parse(piefedPost['published'] as String), + isNSFW: piefedPost['nsfw']! as bool, + isPinned: piefedPost['sticky']! as bool, + createdAt: DateTime.parse(piefedPost['published']! as String), editedAt: optionalDateTime(piefedPost['updated'] as String?), - lastActive: DateTime.parse(piefedCounts['newest_comment_time'] as String), + lastActive: DateTime.parse( + piefedCounts['newest_comment_time']! as String, + ), visibility: 'visible', canAuthUserModerate: postView['can_auth_user_moderate'] as bool?, notificationControlStatus: postView['activity_alert'] == null ? null - : postView['activity_alert'] as bool + : postView['activity_alert']! as bool ? NotificationControlStatus.loud : NotificationControlStatus.default_, bookmarks: [ // Empty string indicates post is saved. No string indicates post is not saved. - if (postView['saved'] as bool) '', + if (postView['saved']! as bool) '', ], read: postView['read'] as bool? ?? false, crossPosts: @@ -371,11 +374,11 @@ abstract class PostModel with _$PostModel { [], poll: piefedPost['post_type'] == 'Poll' ? PollModel.fromPiefed( - piefedPost['id'] as int, - piefedPost['poll'] as Map, + piefedPost['id']! as int, + piefedPost['poll']! as Map, ) : null, - apId: piefedPost['ap_id'] as String, + apId: piefedPost['ap_id']! as String, emojiReactions: (piefedPost['emoji_reactions'] as List?) ?.map((item) => EmojiReactionModel.fromPieFed(item)) diff --git a/lib/src/models/search.dart b/lib/src/models/search.dart index eebc1472..d5a57947 100644 --- a/lib/src/models/search.dart +++ b/lib/src/models/search.dart @@ -16,18 +16,18 @@ abstract class SearchListModel with _$SearchListModel { }) = _SearchListModel; factory SearchListModel.fromMbin(Map json) { - List items = []; + final items = []; - for (var actor in json['apActors']) { - var type = actor['type']; + for (final actor in json['apActors']) { + final type = actor['type']; if (type == 'user') { items.add(DetailedUserModel.fromMbin(actor['object'] as JsonMap)); } else if (type == 'magazine') { items.add(DetailedCommunityModel.fromMbin(actor['object'] as JsonMap)); } } - for (var item in json['apObjects']) { - var itemType = item['itemType']; + for (final item in json['apObjects']) { + final itemType = item['itemType']; if (itemType == 'entry') { items.add(PostModel.fromMbinEntry(item as JsonMap)); } else if (itemType == 'post') { @@ -36,8 +36,8 @@ abstract class SearchListModel with _$SearchListModel { items.add(CommentModel.fromMbin(item as JsonMap)); } } - for (var item in json['items']) { - var itemType = item['itemType']; + for (final item in json['items']) { + final itemType = item['itemType']; if (itemType == 'entry') { items.add(PostModel.fromMbinEntry(item as JsonMap)); } else if (itemType == 'post') { @@ -57,17 +57,17 @@ abstract class SearchListModel with _$SearchListModel { Map json, { required List<(String, int)> langCodeIdPairs, }) { - List items = []; + final items = []; - for (var user in json['users']) { + for (final user in json['users']) { items.add(DetailedUserModel.fromLemmy(user)); } - for (var community in json['communities']) { + for (final community in json['communities']) { items.add(DetailedCommunityModel.fromLemmy(community as JsonMap)); } - for (var post in json['posts']) { + for (final post in json['posts']) { items.add( PostModel.fromLemmy({ 'post_view': post as JsonMap, @@ -75,7 +75,7 @@ abstract class SearchListModel with _$SearchListModel { ); } - for (var comment in json['comments']) { + for (final comment in json['comments']) { items.add( CommentModel.fromLemmy( comment as JsonMap, @@ -94,17 +94,17 @@ abstract class SearchListModel with _$SearchListModel { Map json, { required List<(String, int)> langCodeIdPairs, }) { - List items = []; + final items = []; - for (var user in json['users']) { + for (final user in json['users']) { items.add(DetailedUserModel.fromPiefed(user)); } - for (var community in json['communities']) { + for (final community in json['communities']) { items.add(DetailedCommunityModel.fromPiefed(community as JsonMap)); } - for (var post in json['posts']) { + for (final post in json['posts']) { items.add( PostModel.fromPiefed({ 'post_view': post as JsonMap, @@ -112,7 +112,7 @@ abstract class SearchListModel with _$SearchListModel { ); } - for (var comment in json['comments']) { + for (final comment in json['comments']) { items.add( CommentModel.fromPiefed( comment as JsonMap, diff --git a/lib/src/models/user.dart b/lib/src/models/user.dart index bdccaf4c..cc95cc0e 100644 --- a/lib/src/models/user.dart +++ b/lib/src/models/user.dart @@ -18,15 +18,15 @@ abstract class DetailedUserListModel with _$DetailedUserListModel { }) = _DetailedUserListModel; factory DetailedUserListModel.fromMbin(JsonMap json) => DetailedUserListModel( - items: (json['items'] as List) + items: (json['items']! as List) .map((post) => DetailedUserModel.fromMbin(post as JsonMap)) .toList(), - nextPage: mbinCalcNextPaginationPage(json['pagination'] as JsonMap), + nextPage: mbinCalcNextPaginationPage(json['pagination']! as JsonMap), ); factory DetailedUserListModel.fromLemmy(JsonMap json) => DetailedUserListModel( - items: (json['users'] as List) + items: (json['users']! as List) .map((item) => DetailedUserModel.fromLemmy(item as JsonMap)) .toList(), nextPage: json['next_page'] as String?, @@ -34,7 +34,7 @@ abstract class DetailedUserListModel with _$DetailedUserListModel { factory DetailedUserListModel.fromPiefed(JsonMap json) => DetailedUserListModel( - items: (json['users'] as List) + items: (json['users']! as List) .map((item) => DetailedUserModel.fromPiefed(item as JsonMap)) .toList(), nextPage: json['next_page'] as String?, @@ -63,13 +63,13 @@ abstract class DetailedUserModel with _$DetailedUserModel { factory DetailedUserModel.fromMbin(JsonMap json) { final user = DetailedUserModel( - id: json['userId'] as int, - name: mbinNormalizeUsername(json['username'] as String), + id: json['userId']! as int, + name: mbinNormalizeUsername(json['username']! as String), displayName: null, avatar: mbinGetOptionalImage(json['avatar'] as JsonMap?), cover: mbinGetOptionalImage(json['cover'] as JsonMap?), - createdAt: DateTime.parse(json['createdAt'] as String), - isBot: json['isBot'] as bool, + createdAt: DateTime.parse(json['createdAt']! as String), + isBot: json['isBot']! as bool, about: json['about'] as String?, followersCount: json['followersCount'] as int?, isFollowedByUser: json['isFollowedByUser'] as bool?, @@ -78,7 +78,7 @@ abstract class DetailedUserModel with _$DetailedUserModel { notificationControlStatus: json['notificationStatus'] == null ? null : NotificationControlStatus.fromJson( - json['notificationStatus'] as String, + json['notificationStatus']! as String, ), tags: [], apId: json['apProfileId'] as String?, @@ -93,13 +93,13 @@ abstract class DetailedUserModel with _$DetailedUserModel { final lemmyPerson = json['person'] as JsonMap? ?? json; return DetailedUserModel( - id: lemmyPerson['id'] as int, + id: lemmyPerson['id']! as int, name: getLemmyPiefedActorName(lemmyPerson), displayName: lemmyPerson['display_name'] as String?, avatar: lemmyGetOptionalImage(lemmyPerson['avatar'] as String?), cover: lemmyGetOptionalImage(lemmyPerson['banner'] as String?), - createdAt: DateTime.parse(lemmyPerson['published'] as String), - isBot: lemmyPerson['bot_account'] as bool, + createdAt: DateTime.parse(lemmyPerson['published']! as String), + isBot: lemmyPerson['bot_account']! as bool, about: lemmyPerson['bio'] as String?, followersCount: null, isFollowedByUser: null, @@ -107,7 +107,7 @@ abstract class DetailedUserModel with _$DetailedUserModel { isBlockedByUser: (json['blocked'] as bool?) ?? false, notificationControlStatus: null, tags: [], - apId: lemmyPerson['actor_id'] as String, + apId: lemmyPerson['actor_id']! as String, ); } @@ -115,13 +115,13 @@ abstract class DetailedUserModel with _$DetailedUserModel { final piefedPerson = json['person'] as JsonMap? ?? json; return DetailedUserModel( - id: piefedPerson['id'] as int, + id: piefedPerson['id']! as int, name: getLemmyPiefedActorName(piefedPerson), displayName: piefedPerson['title'] as String?, avatar: lemmyGetOptionalImage(piefedPerson['avatar'] as String?), cover: lemmyGetOptionalImage(piefedPerson['banner'] as String?), - createdAt: DateTime.parse(piefedPerson['published'] as String), - isBot: piefedPerson['bot'] as bool, + createdAt: DateTime.parse(piefedPerson['published']! as String), + isBot: piefedPerson['bot']! as bool, about: piefedPerson['about'] as String?, followersCount: null, isFollowedByUser: null, @@ -129,7 +129,7 @@ abstract class DetailedUserModel with _$DetailedUserModel { isBlockedByUser: blocked, notificationControlStatus: json['activity_alert'] == null ? null - : json['activity_alert'] as bool + : json['activity_alert']! as bool ? NotificationControlStatus.loud : NotificationControlStatus.default_, tags: [ @@ -137,30 +137,40 @@ abstract class DetailedUserModel with _$DetailedUserModel { ? null : Tag( id: -1, - tag: piefedPerson['note'] as String, - backgroundColor: Color.from( + tag: piefedPerson['note']! as String, + backgroundColor: const Color.from( alpha: 1, red: 0, green: 0, blue: 0, ), - textColor: Color.from(alpha: 1, red: 1, green: 1, blue: 1), + textColor: const Color.from( + alpha: 1, + red: 1, + green: 1, + blue: 1, + ), ), ?piefedPerson['flair'] == null ? null : Tag( id: -1, - tag: piefedPerson['flair'] as String, - backgroundColor: Color.from( + tag: piefedPerson['flair']! as String, + backgroundColor: const Color.from( alpha: 1, red: 0, green: 0, blue: 0, ), - textColor: Color.from(alpha: 1, red: 1, green: 1, blue: 1), + textColor: const Color.from( + alpha: 1, + red: 1, + green: 1, + blue: 1, + ), ), ], - apId: piefedPerson['actor_id'] as String, + apId: piefedPerson['actor_id']! as String, ); } } @@ -177,8 +187,8 @@ abstract class UserModel with _$UserModel { }) = _UserModel; factory UserModel.fromMbin(JsonMap json) => UserModel( - id: json['userId'] as int, - name: mbinNormalizeUsername(json['username'] as String), + id: json['userId']! as int, + name: mbinNormalizeUsername(json['username']! as String), avatar: mbinGetOptionalImage(json['avatar'] as JsonMap?), createdAt: optionalDateTime(json['createdAt'] as String?), isBot: (json['isBot'] ?? false) as bool, @@ -186,21 +196,21 @@ abstract class UserModel with _$UserModel { ); factory UserModel.fromLemmy(JsonMap json) => UserModel( - id: json['id'] as int, + id: json['id']! as int, name: getLemmyPiefedActorName(json), avatar: lemmyGetOptionalImage(json['avatar'] as String?), - createdAt: DateTime.parse(json['published'] as String), - isBot: json['bot_account'] as bool, - apId: json['actor_id'] as String, + createdAt: DateTime.parse(json['published']! as String), + isBot: json['bot_account']! as bool, + apId: json['actor_id']! as String, ); factory UserModel.fromPiefed(JsonMap json) => UserModel( - id: json['id'] as int, + id: json['id']! as int, name: getLemmyPiefedActorName(json), avatar: lemmyGetOptionalImage(json['avatar'] as String?), - createdAt: DateTime.parse(json['published'] as String), - isBot: json['bot'] as bool, - apId: json['actor_id'] as String, + createdAt: DateTime.parse(json['published']! as String), + isBot: json['bot']! as bool, + apId: json['actor_id']! as String, ); factory UserModel.fromDetailedUser(DetailedUserModel user) => UserModel( @@ -233,7 +243,7 @@ abstract class UserSettings with _$UserSettings { }) = _UserSettings; factory UserSettings.fromMbin(JsonMap json) => UserSettings( - showNSFW: !(json['hideAdult'] as bool), + showNSFW: !(json['hideAdult']! as bool), blurNSFW: null, showReadPosts: null, showSubscribedUsers: json['showSubscribedUsers'] as bool?, @@ -250,7 +260,7 @@ abstract class UserSettings with _$UserSettings { ); factory UserSettings.fromLemmy(JsonMap json) => UserSettings( - showNSFW: json['show_nsfw'] as bool, + showNSFW: json['show_nsfw']! as bool, blurNSFW: json['blur_nsfw'] as bool?, showReadPosts: json['show_read_posts'] as bool?, showSubscribedUsers: null, @@ -267,7 +277,7 @@ abstract class UserSettings with _$UserSettings { ); factory UserSettings.fromPiefed(JsonMap json) => UserSettings( - showNSFW: json['show_nsfw'] as bool, + showNSFW: json['show_nsfw']! as bool, blurNSFW: null, showReadPosts: json['show_read_posts'] as bool?, showSubscribedUsers: null, diff --git a/lib/src/screens/account/inbox_screen.dart b/lib/src/screens/account/inbox_screen.dart index 007bb645..a31ca38b 100644 --- a/lib/src/screens/account/inbox_screen.dart +++ b/lib/src/screens/account/inbox_screen.dart @@ -1,13 +1,12 @@ import 'package:flutter/material.dart'; import 'package:interstellar/src/controller/controller.dart'; +import 'package:interstellar/src/screens/account/messages/messages_screen.dart'; import 'package:interstellar/src/screens/account/notification/notification_badge.dart'; import 'package:interstellar/src/screens/account/notification/notification_screen.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; -import 'messages/messages_screen.dart'; - class InboxScreen extends StatefulWidget { const InboxScreen({super.key}); diff --git a/lib/src/screens/account/messages/message_thread_item.dart b/lib/src/screens/account/messages/message_thread_item.dart index c5d23eeb..abb70f91 100644 --- a/lib/src/screens/account/messages/message_thread_item.dart +++ b/lib/src/screens/account/messages/message_thread_item.dart @@ -11,11 +11,6 @@ import 'package:interstellar/src/widgets/markdown/markdown.dart'; import 'package:provider/provider.dart'; class MessageThreadItem extends StatelessWidget { - final bool fromMyUser; - final MessageThreadItemModel? prevMessage; - final MessageThreadItemModel currMessage; - final MessageThreadItemModel? nextMessage; - const MessageThreadItem({ required this.fromMyUser, required this.prevMessage, @@ -24,6 +19,11 @@ class MessageThreadItem extends StatelessWidget { super.key, }); + final bool fromMyUser; + final MessageThreadItemModel? prevMessage; + final MessageThreadItemModel currMessage; + final MessageThreadItemModel? nextMessage; + @override Widget build(BuildContext context) { // Convert to local time so timelines markings are correct. @@ -33,7 +33,7 @@ class MessageThreadItem extends StatelessWidget { final showDate = prevMessage == null || - !DateUtils.isSameDay(currCreatedAt, prevCreatedAt!); + !DateUtils.isSameDay(currCreatedAt, prevCreatedAt); final showTime = prevMessage == null || currCreatedAt.difference(prevCreatedAt!).inMinutes > 15; diff --git a/lib/src/screens/account/messages/message_thread_screen.dart b/lib/src/screens/account/messages/message_thread_screen.dart index 3014e090..25fb1a33 100644 --- a/lib/src/screens/account/messages/message_thread_screen.dart +++ b/lib/src/screens/account/messages/message_thread_screen.dart @@ -1,8 +1,11 @@ +import 'package:auto_route/annotations.dart'; +import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/models/message.dart'; +import 'package:interstellar/src/models/user.dart'; import 'package:interstellar/src/screens/account/messages/message_thread_item.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/loading_button.dart'; @@ -11,10 +14,6 @@ import 'package:interstellar/src/widgets/markdown/markdown_editor.dart'; import 'package:interstellar/src/widgets/paging.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; -import 'package:auto_route/annotations.dart'; -import 'package:auto_route/auto_route.dart'; - -import '../../../models/user.dart'; @RoutePage() class MessageThreadScreen extends StatefulWidget { @@ -158,9 +157,7 @@ class _MessageThreadScreenState extends State { newThread.messages.first, ]); - if (widget.onUpdate != null) { - widget.onUpdate!(newThread); - } + widget.onUpdate?.call(newThread); }, label: Text(l(context).send), icon: const Icon(Symbols.send_rounded), diff --git a/lib/src/screens/account/messages/messages_screen.dart b/lib/src/screens/account/messages/messages_screen.dart index 19abf155..b37f702f 100644 --- a/lib/src/screens/account/messages/messages_screen.dart +++ b/lib/src/screens/account/messages/messages_screen.dart @@ -2,15 +2,15 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:interstellar/src/controller/controller.dart'; -import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/controller/router.gr.dart'; +import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/models/message.dart'; +import 'package:interstellar/src/screens/account/messages/message_item.dart'; +import 'package:interstellar/src/screens/explore/explore_screen.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/paging.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; -import 'package:interstellar/src/screens/explore/explore_screen.dart'; -import 'message_item.dart'; class MessagesScreen extends StatefulWidget { const MessagesScreen({super.key}); @@ -58,7 +58,7 @@ class _MessagesScreenState extends State itemBuilder: (context, item, index) => MessageItem( item, (newValue) { - var newList = _pagingController.value.items; + final newList = _pagingController.value.items; newList![index] = newValue; _pagingController.value = _pagingController.value.copyWith(); }, @@ -84,11 +84,7 @@ class _MessagesScreenState extends State onTap: (selected, item) async { context.router.pop(); await context.router.push( - MessageThreadRoute( - threadId: null, - userId: item.id, - otherUser: item, - ), + MessageThreadRoute(userId: item.id, otherUser: item), ); _pagingController.refresh(); }, diff --git a/lib/src/screens/account/notification/notification_badge.dart b/lib/src/screens/account/notification/notification_badge.dart index 523ccfe2..d4a9d532 100644 --- a/lib/src/screens/account/notification/notification_badge.dart +++ b/lib/src/screens/account/notification/notification_badge.dart @@ -1,14 +1,13 @@ import 'package:flutter/material.dart'; +import 'package:interstellar/src/screens/account/notification/notification_count_controller.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/wrapper.dart'; import 'package:provider/provider.dart'; -import 'notification_count_controller.dart'; - class NotificationBadge extends StatefulWidget { - final Widget child; + const NotificationBadge({required this.child, super.key}); - const NotificationBadge({super.key, required this.child}); + final Widget child; @override State createState() => _NotificationBadgeState(); diff --git a/lib/src/screens/account/notification/notification_count_controller.dart b/lib/src/screens/account/notification/notification_count_controller.dart index bb9c4dbd..8fa6ba9a 100644 --- a/lib/src/screens/account/notification/notification_count_controller.dart +++ b/lib/src/screens/account/notification/notification_count_controller.dart @@ -24,7 +24,7 @@ class NotificationCountController with ChangeNotifier { } } - void reload() async { + Future reload() async { _timer?.cancel(); if (_account != null) { @@ -36,9 +36,11 @@ class NotificationCountController with ChangeNotifier { _update(); } - void _update() async { + Future _update() async { try { - int newValue = _account == null ? 0 : await _api.notifications.getCount(); + final newValue = _account == null + ? 0 + : await _api.notifications.getCount(); if (_value != newValue) { _value = newValue; diff --git a/lib/src/screens/account/notification/notification_item.dart b/lib/src/screens/account/notification/notification_item.dart index c00de6db..250ac637 100644 --- a/lib/src/screens/account/notification/notification_item.dart +++ b/lib/src/screens/account/notification/notification_item.dart @@ -1,11 +1,13 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:interstellar/src/controller/controller.dart'; -import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/controller/router.gr.dart'; +import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/models/community.dart'; import 'package:interstellar/src/models/notification.dart'; import 'package:interstellar/src/models/post.dart'; +import 'package:interstellar/src/screens/account/notification/notification_count_controller.dart'; +import 'package:interstellar/src/screens/feed/post_page.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/display_name.dart'; import 'package:interstellar/src/widgets/loading_button.dart'; @@ -13,10 +15,7 @@ import 'package:interstellar/src/widgets/markdown/markdown.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; -import '../../feed/post_page.dart'; -import 'notification_count_controller.dart'; - -const notificationTitle = { +const Map notificationTitle = { NotificationType.mention: 'mentioned you', NotificationType.postMention: 'mentioned you in a post', NotificationType.commentMention: 'mentioned you in a comment', @@ -60,7 +59,7 @@ class _NotificationItemState extends State { final software = context.watch().serverSoftware; - CommunityModel? bannedCommunity = switch (software) { + final bannedCommunity = switch (software) { ServerSoftware.mbin => widget.item.type == NotificationType.ban && widget.item.subject['magazine'] != null @@ -72,7 +71,7 @@ class _NotificationItemState extends State { ServerSoftware.piefed => null, }; - final String body = switch (software) { + final body = switch (software) { ServerSoftware.mbin => (widget.item.subject['body'] ?? widget.item.subject['reason'] ?? '') as String, @@ -88,7 +87,7 @@ class _NotificationItemState extends State { ServerSoftware.piefed => (widget.item.subject['notif_body']) as String, }; - final void Function()? onTap = switch (software) { + final onTap = switch (software) { ServerSoftware.mbin => widget.item.subject.containsKey('threadId') ? () => context.router.push( diff --git a/lib/src/screens/account/notification/notification_screen.dart b/lib/src/screens/account/notification/notification_screen.dart index 07cb6fef..7b02b2d0 100644 --- a/lib/src/screens/account/notification/notification_screen.dart +++ b/lib/src/screens/account/notification/notification_screen.dart @@ -3,6 +3,8 @@ import 'package:interstellar/src/api/notifications.dart'; import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/models/notification.dart'; +import 'package:interstellar/src/screens/account/notification/notification_count_controller.dart'; +import 'package:interstellar/src/screens/account/notification/notification_item.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/loading_button.dart'; import 'package:interstellar/src/widgets/paging.dart'; @@ -10,9 +12,6 @@ import 'package:interstellar/src/widgets/selection_menu.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; -import 'notification_count_controller.dart'; -import 'notification_item.dart'; - class NotificationsScreen extends StatefulWidget { const NotificationsScreen({super.key}); @@ -120,7 +119,7 @@ class _NotificationsScreenState extends State context.watch().serverSoftware == ServerSoftware.lemmy && item.creator?.name == context.watch().localName - ? SizedBox() + ? const SizedBox() : Padding( padding: const EdgeInsets.only(left: 16, right: 16, bottom: 8), child: NotificationItem( diff --git a/lib/src/screens/account/profile_edit_screen.dart b/lib/src/screens/account/profile_edit_screen.dart index de9e883b..3b1a2f3f 100644 --- a/lib/src/screens/account/profile_edit_screen.dart +++ b/lib/src/screens/account/profile_edit_screen.dart @@ -1,31 +1,32 @@ import 'dart:io'; + import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/models/user.dart'; import 'package:interstellar/src/utils/utils.dart'; +import 'package:interstellar/src/widgets/avatar.dart'; import 'package:interstellar/src/widgets/image.dart'; import 'package:interstellar/src/widgets/loading_button.dart'; import 'package:interstellar/src/widgets/markdown/drafts_controller.dart'; import 'package:interstellar/src/widgets/markdown/markdown_editor.dart'; -import 'package:interstellar/src/widgets/avatar.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; @RoutePage() class ProfileEditScreen extends StatefulWidget { + const ProfileEditScreen(this.user, this.onUpdate, {super.key}); + final DetailedUserModel user; final void Function(DetailedUserModel) onUpdate; - const ProfileEditScreen(this.user, this.onUpdate, {super.key}); - @override State createState() => _ProfileEditScreen(); } class _ProfileEditScreen extends State { - TextEditingController? _aboutTextController; + late TextEditingController? _aboutTextController; XFile? _avatarFile; bool _deleteAvatar = false; XFile? _coverFile; @@ -41,7 +42,7 @@ class _ProfileEditScreen extends State { _initSettings(); } - void _initSettings() async { + Future _initSettings() async { final settings = await context .read() .api @@ -58,7 +59,7 @@ class _ProfileEditScreen extends State { 'profile:about:${context.watch().selectedAccount}', ); - onSave() async { + Future onSave() async { if (_settingsChanged) { _settings = await context .read() @@ -193,7 +194,6 @@ class _ProfileEditScreen extends State { ), Positioned.fill( child: Align( - alignment: Alignment.center, child: LoadingIconButton( style: ButtonStyle( fixedSize: const WidgetStatePropertyAll( @@ -282,7 +282,6 @@ class _ProfileEditScreen extends State { Padding( padding: const EdgeInsets.only(top: 30), child: Column( - mainAxisAlignment: MainAxisAlignment.start, children: [ Text( l(context).account_settings_settings, @@ -304,7 +303,7 @@ class _ProfileEditScreen extends State { value: _settings!.blurNSFW!, onChanged: (bool? value) { setState(() { - _settings!.blurNSFW = value!; + _settings!.blurNSFW = value; _settingsChanged = true; }); }, @@ -317,7 +316,7 @@ class _ProfileEditScreen extends State { value: _settings!.showReadPosts!, onChanged: (bool? value) { setState(() { - _settings!.showReadPosts = value!; + _settings!.showReadPosts = value; _settingsChanged = true; }); }, @@ -330,7 +329,7 @@ class _ProfileEditScreen extends State { value: _settings!.showSubscribedUsers!, onChanged: (bool? value) { setState(() { - _settings!.showSubscribedUsers = value!; + _settings!.showSubscribedUsers = value; _settingsChanged = true; }); }, @@ -345,7 +344,7 @@ class _ProfileEditScreen extends State { value: _settings!.showSubscribedCommunities!, onChanged: (bool? value) { setState(() { - _settings!.showSubscribedCommunities = value!; + _settings!.showSubscribedCommunities = value; _settingsChanged = true; }); }, @@ -360,7 +359,7 @@ class _ProfileEditScreen extends State { value: _settings!.showSubscribedDomains!, onChanged: (bool? value) { setState(() { - _settings!.showSubscribedDomains = value!; + _settings!.showSubscribedDomains = value; _settingsChanged = true; }); }, @@ -375,7 +374,7 @@ class _ProfileEditScreen extends State { value: _settings!.showProfileSubscriptions!, onChanged: (bool? value) { setState(() { - _settings!.showProfileSubscriptions = value!; + _settings!.showProfileSubscriptions = value; _settingsChanged = true; }); }, @@ -390,7 +389,7 @@ class _ProfileEditScreen extends State { value: _settings!.showProfileFollowings!, onChanged: (bool? value) { setState(() { - _settings!.showProfileFollowings = value!; + _settings!.showProfileFollowings = value; _settingsChanged = true; }); }, diff --git a/lib/src/screens/account/self_feed.dart b/lib/src/screens/account/self_feed.dart index a16273d5..cb7e8abd 100644 --- a/lib/src/screens/account/self_feed.dart +++ b/lib/src/screens/account/self_feed.dart @@ -40,7 +40,7 @@ class _SelfFeedState extends State _authError = null; }); }) - .catchError((error) { + .catchError((dynamic error) { if (error is AuthorizationException) { setState(() { _authError = error; diff --git a/lib/src/screens/explore/bookmarks_screen.dart b/lib/src/screens/explore/bookmarks_screen.dart index de6452d1..955e127f 100644 --- a/lib/src/screens/explore/bookmarks_screen.dart +++ b/lib/src/screens/explore/bookmarks_screen.dart @@ -1,19 +1,19 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; -import 'package:interstellar/src/controller/server.dart'; -import 'package:interstellar/src/controller/router.gr.dart'; -import 'package:interstellar/src/screens/feed/post_page.dart'; -import 'package:interstellar/src/widgets/loading_button.dart'; -import 'package:interstellar/src/widgets/paging.dart'; -import 'package:interstellar/src/widgets/text_editor.dart'; -import 'package:provider/provider.dart'; import 'package:interstellar/src/controller/controller.dart'; +import 'package:interstellar/src/controller/router.gr.dart'; +import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/models/bookmark_list.dart'; import 'package:interstellar/src/models/comment.dart'; import 'package:interstellar/src/models/post.dart'; -import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/screens/feed/post_comment.dart'; import 'package:interstellar/src/screens/feed/post_item.dart'; +import 'package:interstellar/src/screens/feed/post_page.dart'; +import 'package:interstellar/src/utils/utils.dart'; +import 'package:interstellar/src/widgets/loading_button.dart'; +import 'package:interstellar/src/widgets/paging.dart'; +import 'package:interstellar/src/widgets/text_editor.dart'; +import 'package:provider/provider.dart'; @RoutePage() class BookmarkListScreen extends StatefulWidget { @@ -106,10 +106,10 @@ class _BookmarkListScreenState extends State { Padding( padding: const EdgeInsets.only(left: 8), child: LoadingFilledButton( - onPressed: () => showDialog( + onPressed: () => showDialog( context: context, builder: (BuildContext context) => AlertDialog( - title: Text('Delete bookmark list'), + title: const Text('Delete bookmark list'), content: Text(_bookmarkLists[index].name), actions: [ OutlinedButton( @@ -153,13 +153,13 @@ class _BookmarkListScreenState extends State { @RoutePage() class BookmarksScreen extends StatefulWidget { - final String? bookmarkList; - const BookmarksScreen({ super.key, @PathParam('bookmarkList') this.bookmarkList, }); + final String? bookmarkList; + @override State createState() => _BookmarksScreenState(); } @@ -168,7 +168,7 @@ class _BookmarksScreenState extends State { late final _pagingController = AdvancedPagingController( logger: context.read().logger, firstPageKey: '', - // TODO: this is not safe, items of different types (comment, microblog, etc.) could have the same id + // TODO(jwr1): this is not safe, items of different types (comment, microblog, etc.) could have the same id getItemId: (item) => item.id, fetchPage: (pageKey) async { final ac = context.read(); @@ -226,7 +226,7 @@ class _BookmarksScreenState extends State { ), ), ), - _ => throw 'unreachable', + _ => throw UnreachableError(), }; }, ), diff --git a/lib/src/screens/explore/community_mod_panel.dart b/lib/src/screens/explore/community_mod_panel.dart index 2717b591..c1fe392d 100644 --- a/lib/src/screens/explore/community_mod_panel.dart +++ b/lib/src/screens/explore/community_mod_panel.dart @@ -1,35 +1,34 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:interstellar/src/api/community_moderation.dart'; import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/controller/router.gr.dart'; +import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/models/community.dart'; import 'package:interstellar/src/screens/explore/user_item.dart'; import 'package:interstellar/src/screens/feed/post_page.dart'; import 'package:interstellar/src/utils/breakpoints.dart'; import 'package:interstellar/src/utils/utils.dart'; -import 'package:interstellar/src/widgets/loading_button.dart'; -import 'package:interstellar/src/api/community_moderation.dart'; import 'package:interstellar/src/widgets/display_name.dart'; +import 'package:interstellar/src/widgets/loading_button.dart'; import 'package:interstellar/src/widgets/paging.dart'; import 'package:interstellar/src/widgets/selection_menu.dart'; -import 'package:interstellar/src/controller/server.dart'; import 'package:material_symbols_icons/symbols.dart'; - import 'package:provider/provider.dart'; @RoutePage() class CommunityModPanelScreen extends StatefulWidget { - final int communityId; - final DetailedCommunityModel initData; - final void Function(DetailedCommunityModel) onUpdate; - const CommunityModPanelScreen({ - super.key, @PathParam('communityId') required this.communityId, required this.initData, required this.onUpdate, + super.key, }); + final int communityId; + final DetailedCommunityModel initData; + final void Function(DetailedCommunityModel) onUpdate; + @override State createState() => _CommunityModPanelScreenState(); @@ -47,7 +46,7 @@ class _CommunityModPanelScreenState extends State { @override Widget build(BuildContext context) { - onUpdate(DetailedCommunityModel newValue) { + void onUpdate(DetailedCommunityModel newValue) { setState(() { _data = newValue; widget.onUpdate(newValue); @@ -64,10 +63,10 @@ class _CommunityModPanelScreenState extends State { title: Text('Mod Panel for ${widget.initData.name}'), bottom: TabBar( tabs: [ - Tab(text: 'Bans'), + const Tab(text: 'Bans'), if (context.read().serverSoftware == ServerSoftware.mbin) - Tab(text: 'Reports'), + const Tab(text: 'Reports'), ], ), ), @@ -86,14 +85,13 @@ class _CommunityModPanelScreenState extends State { } class CommunityModPanelBans extends StatefulWidget { - final DetailedCommunityModel data; - final void Function(DetailedCommunityModel) onUpdate; - const CommunityModPanelBans({ - super.key, required this.data, required this.onUpdate, + super.key, }); + final DetailedCommunityModel data; + final void Function(DetailedCommunityModel) onUpdate; @override State createState() => _CommunityModPanelBansState(); @@ -149,14 +147,13 @@ class _CommunityModPanelBansState extends State { } class CommunityModPanelReports extends StatefulWidget { - final DetailedCommunityModel data; - final void Function(DetailedCommunityModel) onUpdate; - const CommunityModPanelReports({ - super.key, required this.data, required this.onUpdate, + super.key, }); + final DetailedCommunityModel data; + final void Function(DetailedCommunityModel) onUpdate; @override State createState() => @@ -196,7 +193,6 @@ class _MagazineModPanelReportsState extends State { child: Padding( padding: const EdgeInsets.all(16), child: Row( - mainAxisAlignment: MainAxisAlignment.start, children: [ ActionChip( padding: chipDropdownPadding, @@ -247,7 +243,6 @@ class _MagazineModPanelReportsState extends State { child: Padding( padding: const EdgeInsets.all(12), child: Row( - crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded( child: Column( diff --git a/lib/src/screens/explore/community_owner_panel.dart b/lib/src/screens/explore/community_owner_panel.dart index 5ac3056c..935feb42 100644 --- a/lib/src/screens/explore/community_owner_panel.dart +++ b/lib/src/screens/explore/community_owner_panel.dart @@ -14,17 +14,17 @@ import 'package:provider/provider.dart'; @RoutePage() class CommunityOwnerPanelScreen extends StatefulWidget { - final int communityId; - final DetailedCommunityModel initData; - final void Function(DetailedCommunityModel) onUpdate; - const CommunityOwnerPanelScreen({ - super.key, @PathParam('communityId') required this.communityId, required this.initData, required this.onUpdate, + super.key, }); + final int communityId; + final DetailedCommunityModel initData; + final void Function(DetailedCommunityModel) onUpdate; + @override State createState() => _CommunityOwnerPanelScreenState(); @@ -42,7 +42,7 @@ class _CommunityOwnerPanelScreenState extends State { @override Widget build(BuildContext context) { - onUpdate(DetailedCommunityModel newValue) { + void onUpdate(DetailedCommunityModel newValue) { setState(() { _data = newValue; widget.onUpdate(newValue); @@ -76,15 +76,14 @@ class _CommunityOwnerPanelScreenState extends State { } class CommunityOwnerPanelGeneral extends StatefulWidget { - // Data is null for community creation screen - final DetailedCommunityModel? data; - final void Function(DetailedCommunityModel) onUpdate; - const CommunityOwnerPanelGeneral({ - super.key, required this.data, required this.onUpdate, + super.key, }); + // Data is null for community creation screen + final DetailedCommunityModel? data; + final void Function(DetailedCommunityModel) onUpdate; @override State createState() => @@ -222,14 +221,13 @@ class _CommunityOwnerPanelGeneralState } class CommunityOwnerPanelModerators extends StatefulWidget { - final DetailedCommunityModel data; - final void Function(DetailedCommunityModel) onUpdate; - const CommunityOwnerPanelModerators({ - super.key, required this.data, required this.onUpdate, + super.key, }); + final DetailedCommunityModel data; + final void Function(DetailedCommunityModel) onUpdate; @override State createState() => @@ -367,14 +365,13 @@ class _CommunityOwnerPanelModeratorsState } class CommunityOwnerPanelDeletion extends StatefulWidget { - final DetailedCommunityModel data; - final void Function(DetailedCommunityModel) onUpdate; - const CommunityOwnerPanelDeletion({ - super.key, required this.data, required this.onUpdate, + super.key, }); + final DetailedCommunityModel data; + final void Function(DetailedCommunityModel) onUpdate; @override State createState() => @@ -418,7 +415,7 @@ class _CommunityOwnerPanelDeletionState CommunityOwnerPanelDeletionDialog(data: widget.data), ); - if (result == true) { + if (result ?? false) { if (!context.mounted) return; context.router.pop(); } @@ -432,10 +429,9 @@ class _CommunityOwnerPanelDeletionState } class CommunityOwnerPanelDeletionDialog extends StatefulWidget { + const CommunityOwnerPanelDeletionDialog({required this.data, super.key}); final DetailedCommunityModel data; - const CommunityOwnerPanelDeletionDialog({super.key, required this.data}); - @override State createState() => _CommunityOwnerPanelDeletionDialogState(); diff --git a/lib/src/screens/explore/community_screen.dart b/lib/src/screens/explore/community_screen.dart index fea8d57e..998542a5 100644 --- a/lib/src/screens/explore/community_screen.dart +++ b/lib/src/screens/explore/community_screen.dart @@ -12,20 +12,16 @@ import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/avatar.dart'; import 'package:interstellar/src/widgets/loading_button.dart'; import 'package:interstellar/src/widgets/markdown/markdown.dart'; +import 'package:interstellar/src/widgets/menus/community_menu.dart'; import 'package:interstellar/src/widgets/notification_control_segment.dart'; import 'package:interstellar/src/widgets/star_button.dart'; import 'package:interstellar/src/widgets/subscription_button.dart'; -import 'package:interstellar/src/widgets/menus/community_menu.dart'; import 'package:interstellar/src/widgets/tags/tag_widget.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; @RoutePage() class CommunityScreen extends StatefulWidget { - final int communityId; - final DetailedCommunityModel? initData; - final void Function(DetailedCommunityModel)? onUpdate; - const CommunityScreen( @PathParam('communityId') this.communityId, { super.key, @@ -33,6 +29,10 @@ class CommunityScreen extends StatefulWidget { this.onUpdate, }); + final int communityId; + final DetailedCommunityModel? initData; + final void Function(DetailedCommunityModel)? onUpdate; + @override State createState() => _CommunityScreenState(); } @@ -70,7 +70,6 @@ class _CommunityScreenState extends State { return FeedScreen( feed: FeedAggregator.fromSingleSource( - ac, name: _data?.name ?? '', source: FeedSource.community, sourceId: widget.communityId, @@ -93,7 +92,7 @@ class _CommunityScreenState extends State { isSubscribed: _data!.isUserSubscribed, subscriptionCount: _data!.subscriptionsCount, onSubscribe: (selected) async { - var newValue = await ac.api.community.subscribe( + final newValue = await ac.api.community.subscribe( _data!.id, selected, ); @@ -101,14 +100,12 @@ class _CommunityScreenState extends State { setState(() { _data = newValue; }); - if (widget.onUpdate != null) { - widget.onUpdate!(newValue); - } + widget.onUpdate?.call(newValue); }, followMode: false, ), StarButton(globalName!), - if (whenLoggedIn(context, true) == true) + if (whenLoggedIn(context, true) ?? false) LoadingIconButton( onPressed: () async { final newValue = await ac.api.community.block( @@ -119,14 +116,12 @@ class _CommunityScreenState extends State { setState(() { _data = newValue; }); - if (widget.onUpdate != null) { - widget.onUpdate!(newValue); - } + widget.onUpdate?.call(newValue); }, icon: const Icon(Symbols.block_rounded), style: ButtonStyle( foregroundColor: WidgetStatePropertyAll( - _data!.isBlockedByUser == true + _data!.isBlockedByUser ?? false ? Theme.of(context).colorScheme.error : Theme.of(context).disabledColor, ), @@ -140,12 +135,10 @@ class _CommunityScreenState extends State { setState(() { _data = newCommunity; }); - if (widget.onUpdate != null) { - widget.onUpdate!(newCommunity); - } + widget.onUpdate?.call(newCommunity); }, ), - icon: Icon(Symbols.more_vert_rounded), + icon: const Icon(Symbols.more_vert_rounded), ), ], ), @@ -168,9 +161,7 @@ class _CommunityScreenState extends State { setState(() { _data = newValue; }); - if (widget.onUpdate != null) { - widget.onUpdate!(newValue); - } + widget.onUpdate?.call(newValue); }, ), ), diff --git a/lib/src/screens/explore/domain_screen.dart b/lib/src/screens/explore/domain_screen.dart index 151b4be4..cdbd1100 100644 --- a/lib/src/screens/explore/domain_screen.dart +++ b/lib/src/screens/explore/domain_screen.dart @@ -13,12 +13,12 @@ import 'package:provider/provider.dart'; @RoutePage() class DomainScreen extends StatefulWidget { + const DomainScreen(this.domainId, {super.key, this.initData, this.onUpdate}); + final int domainId; final DomainModel? initData; final void Function(DomainModel)? onUpdate; - const DomainScreen(this.domainId, {super.key, this.initData, this.onUpdate}); - @override State createState() => _DomainScreenState(); } @@ -48,7 +48,6 @@ class _DomainScreenState extends State { Widget build(BuildContext context) { return FeedScreen( feed: FeedAggregator.fromSingleSource( - context.read(), name: _data?.name ?? '', source: FeedSource.domain, sourceId: widget.domainId, @@ -72,7 +71,7 @@ class _DomainScreenState extends State { isSubscribed: _data!.isUserSubscribed, subscriptionCount: _data!.subscriptionsCount, onSubscribe: (selected) async { - var newValue = await context + final newValue = await context .read() .api .domains @@ -81,13 +80,11 @@ class _DomainScreenState extends State { setState(() { _data = newValue; }); - if (widget.onUpdate != null) { - widget.onUpdate!(newValue); - } + widget.onUpdate?.call(newValue); }, followMode: false, ), - if (whenLoggedIn(context, true) == true) + if (whenLoggedIn(context, true) ?? false) LoadingIconButton( onPressed: () async { final newValue = await context @@ -99,14 +96,12 @@ class _DomainScreenState extends State { setState(() { _data = newValue; }); - if (widget.onUpdate != null) { - widget.onUpdate!(newValue); - } + widget.onUpdate?.call(newValue); }, icon: const Icon(Symbols.block_rounded), style: ButtonStyle( foregroundColor: WidgetStatePropertyAll( - _data!.isBlockedByUser == true + _data!.isBlockedByUser ?? false ? Theme.of(context).colorScheme.error : Theme.of(context).disabledColor, ), diff --git a/lib/src/screens/explore/explore_screen.dart b/lib/src/screens/explore/explore_screen.dart index 539f3c08..8f982c5f 100644 --- a/lib/src/screens/explore/explore_screen.dart +++ b/lib/src/screens/explore/explore_screen.dart @@ -3,6 +3,10 @@ import 'package:flutter/material.dart'; import 'package:interstellar/src/api/community.dart'; import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/controller/server.dart'; +import 'package:interstellar/src/models/community.dart'; +import 'package:interstellar/src/models/domain.dart'; +import 'package:interstellar/src/models/feed.dart'; +import 'package:interstellar/src/models/user.dart'; import 'package:interstellar/src/screens/explore/explore_screen_item.dart'; import 'package:interstellar/src/utils/debouncer.dart'; import 'package:interstellar/src/utils/utils.dart'; @@ -13,21 +17,8 @@ import 'package:interstellar/src/widgets/wrapper.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; -import 'package:interstellar/src/models/community.dart'; -import 'package:interstellar/src/models/domain.dart'; -import 'package:interstellar/src/models/user.dart'; -import 'package:interstellar/src/models/feed.dart'; - @RoutePage() class ExploreScreen extends StatefulWidget { - final ExploreType? mode; - final int? id; - final bool subOnly; - final FocusNode? focusNode; - final void Function(bool, dynamic)? onTap; - final Set? selected; - final String? title; - const ExploreScreen({ this.mode, this.id, @@ -39,6 +30,14 @@ class ExploreScreen extends StatefulWidget { super.key, }); + final ExploreType? mode; + final int? id; + final bool subOnly; + final FocusNode? focusNode; + final void Function(bool, dynamic)? onTap; + final Set? selected; + final String? title; + @override State createState() => _ExploreScreenState(); } @@ -58,7 +57,7 @@ class _ExploreScreenState extends State late final _pagingController = AdvancedPagingController( logger: context.read().logger, firstPageKey: '', - // TODO: this is not safe, items of different types (comment, microblog, etc.) could have the same id + // TODO(jwr1): this is not safe, items of different types (comment, microblog, etc.) could have the same id getItemId: (item) => item.id, fetchPage: (pageKey) async { final ac = context.read(); @@ -66,7 +65,7 @@ class _ExploreScreenState extends State final searchType = widget.id != null ? ExploreType.all : type; if (searchType == ExploreType.all && _searchController.text.isEmpty) { - return ([], null); + return ([], null); } switch (searchType) { @@ -85,7 +84,7 @@ class _ExploreScreenState extends State if ((context.read().serverSoftware != ServerSoftware.mbin) && _searchController.text.isEmpty) { - return ([], null); + return ([], null); } final newPage = await ac.api.users.list( @@ -208,9 +207,7 @@ class _ExploreScreenState extends State suffixIcon: _searchController.text.isNotEmpty ? IconButton( onPressed: () { - setState(() { - _searchController.clear(); - }); + setState(_searchController.clear); searchDebounce.run(() { _pagingController.refresh(); }); @@ -235,7 +232,6 @@ class _ExploreScreenState extends State SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( - mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ ChoiceChip( @@ -442,20 +438,20 @@ class _ExploreScreenState extends State ], itemBuilder: (context, item, index) { final selected = _selected?.contains(switch (item) { - DetailedCommunityModel i => i.name, - DetailedUserModel i => i.name, - DomainModel i => i.name, - FeedModel i => i.name, + final DetailedCommunityModel i => i.name, + final DetailedUserModel i => i.name, + final DomainModel i => i.name, + final FeedModel i => i.name, _ => '', }); - onSelect(bool selected) { + void onSelect(bool selected) { widget.onTap!(selected, item); final name = switch (item) { - DetailedCommunityModel i => i.name, - DetailedUserModel i => i.name, - DomainModel i => i.name, - FeedModel i => i.name, + final DetailedCommunityModel i => i.name, + final DetailedUserModel i => i.name, + final DomainModel i => i.name, + final FeedModel i => i.name, _ => null, }; if (name == null) return; @@ -479,7 +475,7 @@ class _ExploreScreenState extends State button: _selected == null || widget.onTap == null ? null : Checkbox( - value: selected!, + value: selected, onChanged: (newValue) => onSelect(newValue!), ), ); @@ -489,7 +485,7 @@ class _ExploreScreenState extends State shouldWrap: ac.profile.hideFeedUIOnScroll, parentBuilder: (child) => HideOnScroll( controller: _scrollController, - hiddenOffset: Offset(0, 1.5), + hiddenOffset: const Offset(0, 1.5), duration: ac.calcAnimationDuration(), child: child, ), diff --git a/lib/src/screens/explore/explore_screen_item.dart b/lib/src/screens/explore/explore_screen_item.dart index d5cf62ff..29336a9d 100644 --- a/lib/src/screens/explore/explore_screen_item.dart +++ b/lib/src/screens/explore/explore_screen_item.dart @@ -1,16 +1,19 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:interstellar/src/api/feed_source.dart'; import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/controller/router.gr.dart'; import 'package:interstellar/src/models/comment.dart'; -import 'package:interstellar/src/models/domain.dart'; import 'package:interstellar/src/models/community.dart'; +import 'package:interstellar/src/models/domain.dart'; +import 'package:interstellar/src/models/feed.dart'; import 'package:interstellar/src/models/post.dart'; import 'package:interstellar/src/models/user.dart'; import 'package:interstellar/src/screens/feed/feed_agregator.dart'; import 'package:interstellar/src/screens/feed/post_comment.dart'; import 'package:interstellar/src/screens/feed/post_item.dart'; import 'package:interstellar/src/screens/feed/post_page.dart'; +import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/avatar.dart'; import 'package:interstellar/src/widgets/menus/community_menu.dart'; import 'package:interstellar/src/widgets/menus/user_menu.dart'; @@ -18,16 +21,8 @@ import 'package:interstellar/src/widgets/subscription_button.dart'; import 'package:interstellar/src/widgets/user_status_icons.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; -import 'package:interstellar/src/utils/utils.dart'; -import 'package:interstellar/src/api/feed_source.dart'; -import 'package:interstellar/src/models/feed.dart'; class ExploreScreenItem extends StatelessWidget { - final dynamic item; - final void Function(dynamic newValue) onUpdate; - final void Function()? onTap; - final Widget? button; - const ExploreScreenItem( this.item, this.onUpdate, { @@ -36,6 +31,11 @@ class ExploreScreenItem extends StatelessWidget { this.button, }); + final dynamic item; + final void Function(dynamic newValue) onUpdate; + final void Function()? onTap; + final Widget? button; + @override Widget build(BuildContext context) { // ListTile based items @@ -44,44 +44,44 @@ class ExploreScreenItem extends StatelessWidget { item is DomainModel || item is FeedModel) { final icon = switch (item) { - DetailedCommunityModel i => i.icon, - DetailedUserModel i => i.avatar, - FeedModel i => i.icon, + final DetailedCommunityModel i => i.icon, + final DetailedUserModel i => i.avatar, + final FeedModel i => i.icon, _ => null, }; final title = switch (item) { - DetailedCommunityModel i => i.title, - DetailedUserModel i => i.displayName ?? i.name.split('@').first, - DomainModel i => i.name, - FeedModel i => i.title, - _ => throw 'Unreachable', + final DetailedCommunityModel i => i.title, + final DetailedUserModel i => i.displayName ?? i.name.split('@').first, + final DomainModel i => i.name, + final FeedModel i => i.title, + _ => throw UnreachableError(), }; final subtitle = switch (item) { - DetailedCommunityModel i => i.name, - DetailedUserModel i => i.name, - FeedModel i => normalizeName( + final DetailedCommunityModel i => i.name, + final DetailedUserModel i => i.name, + final FeedModel i => normalizeName( i.name, context.read().instanceHost, ), _ => null, }; final isSubscribed = switch (item) { - DetailedCommunityModel i => i.isUserSubscribed, - DetailedUserModel i => i.isFollowedByUser, - DomainModel i => i.isUserSubscribed, - FeedModel i => i.subscribed, - _ => throw 'Unreachable', + final DetailedCommunityModel i => i.isUserSubscribed, + final DetailedUserModel i => i.isFollowedByUser, + final DomainModel i => i.isUserSubscribed, + final FeedModel i => i.subscribed, + _ => throw UnreachableError(), }; final subscriptions = switch (item) { - DetailedCommunityModel i => i.subscriptionsCount, - DetailedUserModel i => i.followersCount, - DomainModel i => i.subscriptionsCount, - FeedModel i => i.subscriptionCount, - _ => throw 'Unreachable', + final DetailedCommunityModel i => i.subscriptionsCount, + final DetailedUserModel i => i.followersCount, + final DomainModel i => i.subscriptionsCount, + final FeedModel i => i.subscriptionCount, + _ => throw UnreachableError(), }; final onSubscribe = switch (item) { - DetailedCommunityModel i => (selected) async { - var newValue = await context + final DetailedCommunityModel i => (bool selected) async { + final newValue = await context .read() .api .community @@ -89,16 +89,16 @@ class ExploreScreenItem extends StatelessWidget { onUpdate(newValue); }, - DetailedUserModel i => (selected) async { - var newValue = await context.read().api.users.follow( + final DetailedUserModel i => (bool selected) async { + final newValue = await context.read().api.users.follow( i.id, selected, ); onUpdate(newValue); }, - DomainModel i => (selected) async { - var newValue = await context + final DomainModel i => (bool selected) async { + final newValue = await context .read() .api .domains @@ -107,19 +107,19 @@ class ExploreScreenItem extends StatelessWidget { onUpdate(newValue); }, FeedModel _ => null, - _ => throw 'Unreachable', + _ => throw UnreachableError(), }; final navigate = switch (item) { - DetailedCommunityModel i => () => context.router.push( + final DetailedCommunityModel i => () => context.router.push( CommunityRoute(communityId: i.id, initData: i, onUpdate: onUpdate), ), - DetailedUserModel i => () => context.router.push( + final DetailedUserModel i => () => context.router.push( UserRoute(userId: i.id, initData: i, onUpdate: onUpdate), ), - DomainModel i => () => context.router.push( + final DomainModel i => () => context.router.push( DomainRoute(domainId: i.id, initData: i, onUpdate: onUpdate), ), - FeedModel i => () => context.router.push( + final FeedModel i => () => context.router.push( FeedRoute( feed: FeedAggregator( name: title, @@ -133,7 +133,7 @@ class ExploreScreenItem extends StatelessWidget { ), ), ), - _ => throw 'Unreachable', + _ => throw UnreachableError(), }; final onClick = onTap ?? navigate; @@ -149,19 +149,19 @@ class ExploreScreenItem extends StatelessWidget { ], ), onLongPress: () => switch (item) { - DetailedCommunityModel i => showCommunityMenu( + final DetailedCommunityModel i => showCommunityMenu( context, detailedCommunity: i, navigateOption: true, ), - DetailedUserModel i => showUserMenu( + final DetailedUserModel i => showUserMenu( context, user: i, navigateOption: true, ), DomainModel _ => {}, FeedModel _ => {}, - _ => throw 'Unreachable', + _ => throw UnreachableError(), }, subtitle: subtitle == null ? null : Text(subtitle), trailing: button == null @@ -179,7 +179,7 @@ class ExploreScreenItem extends StatelessWidget { button!, IconButton( onPressed: navigate, - icon: Icon(Symbols.open_in_new_rounded), + icon: const Icon(Symbols.open_in_new_rounded), ), ], ), @@ -189,7 +189,7 @@ class ExploreScreenItem extends StatelessWidget { // Card based items return switch (item) { - PostModel item => Card( + final PostModel item => Card( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), clipBehavior: Clip.antiAlias, child: InkWell( @@ -207,7 +207,7 @@ class ExploreScreenItem extends StatelessWidget { ), ), ), - CommentModel item => Padding( + final CommentModel item => Padding( padding: const EdgeInsets.fromLTRB(12, 0, 12, 12), child: PostComment( item, diff --git a/lib/src/screens/explore/mod_log.dart b/lib/src/screens/explore/mod_log.dart index ab48236f..d3ab348d 100644 --- a/lib/src/screens/explore/mod_log.dart +++ b/lib/src/screens/explore/mod_log.dart @@ -1,8 +1,9 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:interstellar/src/api/moderation.dart'; import 'package:interstellar/src/controller/controller.dart'; -import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/controller/router.gr.dart'; +import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/models/modlog.dart'; import 'package:interstellar/src/models/post.dart'; import 'package:interstellar/src/models/user.dart'; @@ -14,13 +15,12 @@ import 'package:interstellar/src/widgets/paging.dart'; import 'package:interstellar/src/widgets/selection_menu.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:provider/provider.dart'; -import 'package:interstellar/src/api/moderation.dart'; @RoutePage() class ModLogCommunityScreen extends StatelessWidget { const ModLogCommunityScreen({ - super.key, @PathParam('communityId') required this.communityId, + super.key, }); final int communityId; @@ -32,8 +32,8 @@ class ModLogCommunityScreen extends StatelessWidget { @RoutePage() class ModLogUserScreen extends StatelessWidget { const ModLogUserScreen({ - super.key, @PathParam('userId') required this.userId, + super.key, }); final int userId; @@ -85,7 +85,7 @@ class _ModLogScreenState extends State { ); ModLogType _filter = ModLogType.all; - Function()? _itemOnTap(ModlogItemModel item) => switch (item.type) { + void Function()? _itemOnTap(ModlogItemModel item) => switch (item.type) { ModLogType.all => null, ModLogType.postDeleted => item.postId == null @@ -348,7 +348,7 @@ class _ModLogScreenState extends State { if (item.reason != null && item.reason!.isNotEmpty) Text( l(context).modlog_reason(item.reason!), - style: TextStyle(fontStyle: FontStyle.italic), + style: const TextStyle(fontStyle: FontStyle.italic), ), ], ), diff --git a/lib/src/screens/explore/user_item.dart b/lib/src/screens/explore/user_item.dart index 85920441..efc6c158 100644 --- a/lib/src/screens/explore/user_item.dart +++ b/lib/src/screens/explore/user_item.dart @@ -1,16 +1,11 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; -import 'package:interstellar/src/models/user.dart'; import 'package:interstellar/src/controller/router.gr.dart'; +import 'package:interstellar/src/models/user.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/avatar.dart'; class UserItemSimple extends StatelessWidget { - final UserModel user; - final bool isOwner; - final List? trailingWidgets; - final bool noTap; - const UserItemSimple( this.user, { this.isOwner = false, @@ -19,6 +14,11 @@ class UserItemSimple extends StatelessWidget { super.key, }); + final UserModel user; + final bool isOwner; + final List? trailingWidgets; + final bool noTap; + @override Widget build(BuildContext context) { return InkWell( diff --git a/lib/src/screens/explore/user_screen.dart b/lib/src/screens/explore/user_screen.dart index d30886b2..a9973dd1 100644 --- a/lib/src/screens/explore/user_screen.dart +++ b/lib/src/screens/explore/user_screen.dart @@ -5,8 +5,8 @@ import 'package:interstellar/src/api/comments.dart'; import 'package:interstellar/src/api/feed_source.dart'; import 'package:interstellar/src/api/notifications.dart'; import 'package:interstellar/src/controller/controller.dart'; -import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/controller/router.gr.dart'; +import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/models/comment.dart'; import 'package:interstellar/src/models/post.dart'; import 'package:interstellar/src/models/user.dart'; @@ -22,6 +22,7 @@ import 'package:interstellar/src/widgets/image.dart'; import 'package:interstellar/src/widgets/loading_button.dart'; import 'package:interstellar/src/widgets/loading_template.dart'; import 'package:interstellar/src/widgets/markdown/markdown.dart'; +import 'package:interstellar/src/widgets/menus/user_menu.dart'; import 'package:interstellar/src/widgets/notification_control_segment.dart'; import 'package:interstellar/src/widgets/paging.dart'; import 'package:interstellar/src/widgets/star_button.dart'; @@ -29,7 +30,6 @@ import 'package:interstellar/src/widgets/subordinate_scroll.dart'; import 'package:interstellar/src/widgets/subscription_button.dart'; import 'package:interstellar/src/widgets/tags/tag_widget.dart'; import 'package:interstellar/src/widgets/user_status_icons.dart'; -import 'package:interstellar/src/widgets/menus/user_menu.dart'; import 'package:interstellar/src/widgets/wrapper.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; @@ -38,10 +38,6 @@ enum UserFeedType { thread, microblog, comment, reply, follower, following } @RoutePage() class UserScreen extends StatefulWidget { - final int userId; - final DetailedUserModel? initData; - final void Function(DetailedUserModel)? onUpdate; - const UserScreen( @PathParam('userId') this.userId, { super.key, @@ -49,6 +45,10 @@ class UserScreen extends StatefulWidget { this.onUpdate, }); + final int userId; + final DetailedUserModel? initData; + final void Function(DetailedUserModel)? onUpdate; + @override State createState() => _UserScreenState(); } @@ -83,6 +83,9 @@ class _UserScreenState extends State { .get(widget.userId) .then((value) async { if (!context.mounted) return value; + + // Falsely detected + // ignore: use_build_context_synchronously final tags = await context.read().getUserTags( value.name, ); @@ -111,7 +114,7 @@ class _UserScreenState extends State { final isLoggedIn = ac.isLoggedIn; final isMyUser = isLoggedIn && - whenLoggedIn(context, true, matchesUsername: user.name) == true; + (whenLoggedIn(context, true, matchesUsername: user.name) ?? false); final currentFeedSortOption = feedSortSelect(context).getOption(_sort); @@ -127,10 +130,10 @@ class _UserScreenState extends State { Padding( padding: const EdgeInsets.only(right: 8), child: ActionChip( - label: Icon(Symbols.bookmarks_rounded, size: 20), + label: const Icon(Symbols.bookmarks_rounded, size: 20), onPressed: () => context.router.push( ac.serverSoftware == ServerSoftware.mbin - ? BookmarkListRoute() + ? const BookmarkListRoute() : BookmarksRoute(bookmarkList: 'default'), ), ), @@ -232,14 +235,12 @@ class _UserScreenState extends State { subscriptionCount: user.followersCount ?? 0, onSubscribe: (selected) async { - var newValue = await ac.api.users + final newValue = await ac.api.users .follow(user.id, selected); setState(() { _data = newValue; }); - if (widget.onUpdate != null) { - widget.onUpdate!(newValue); - } + widget.onUpdate?.call(newValue); }, followMode: true, ), @@ -256,14 +257,12 @@ class _UserScreenState extends State { setState(() { _data = newValue; }); - if (widget.onUpdate != null) { - widget.onUpdate!(newValue); - } + widget.onUpdate?.call(newValue); }, icon: const Icon(Symbols.block_rounded), style: ButtonStyle( foregroundColor: WidgetStatePropertyAll( - user.isBlockedByUser == true + user.isBlockedByUser ?? false ? Theme.of( context, ).colorScheme.error @@ -275,7 +274,6 @@ class _UserScreenState extends State { IconButton( onPressed: () => context.router.push( MessageThreadRoute( - threadId: null, userId: _data?.id, otherUser: _data, ), @@ -291,9 +289,7 @@ class _UserScreenState extends State { setState(() { _data = newUser; }); - if (widget.onUpdate != null) { - widget.onUpdate!(newUser); - } + widget.onUpdate?.call(newUser); }, ), icon: const Icon(Symbols.more_vert_rounded), @@ -321,9 +317,7 @@ class _UserScreenState extends State { setState(() { _data = newValue; }); - if (widget.onUpdate != null) { - widget.onUpdate!(newValue); - } + widget.onUpdate?.call(newValue); }, ), ), @@ -487,7 +481,7 @@ class _UserScreenState extends State { shouldWrap: ac.profile.hideFeedUIOnScroll, parentBuilder: (child) => HideOnScroll( controller: _scrollController, - hiddenOffset: Offset(0, 2), + hiddenOffset: const Offset(0, 2), duration: ac.calcAnimationDuration(), child: child, ), @@ -508,18 +502,17 @@ class _UserScreenState extends State { } class UserScreenBody extends StatefulWidget { - final UserFeedType mode; - final FeedSort sort; - final DetailedUserModel? data; - final bool isActive; - const UserScreenBody({ - super.key, required this.mode, required this.sort, + super.key, this.data, this.isActive = false, }); + final UserFeedType mode; + final FeedSort sort; + final DetailedUserModel? data; + final bool isActive; @override State createState() => _UserScreenBodyState(); @@ -530,12 +523,12 @@ class _UserScreenBodyState extends State late final _pagingController = AdvancedPagingController( logger: context.read().logger, firstPageKey: '', - // TODO: this is not safe, items of different types (comment, microblog, etc.) could have the same id + // TODO(jwr1): this is not safe, items of different types (comment, microblog, etc.) could have the same id getItemId: (item) => item.id, fetchPage: (pageKey) async { final ac = context.read(); - const Map feedToCommentSortMap = { + const feedToCommentSortMap = { FeedSort.active: CommentSort.active, FeedSort.commented: CommentSort.active, FeedSort.hot: CommentSort.hot, @@ -581,15 +574,15 @@ class _UserScreenBodyState extends State return ( switch (newPage) { - PostListModel newPage => newPage.items, - CommentListModel newPage => newPage.items, - DetailedUserListModel newPage => newPage.items, - Object _ => [], + final PostListModel newPage => newPage.items, + final CommentListModel newPage => newPage.items, + final DetailedUserListModel newPage => newPage.items, + Object _ => const [], }, switch (newPage) { - PostListModel newPage => newPage.nextPage, - CommentListModel newPage => newPage.nextPage, - DetailedUserListModel newPage => newPage.nextPage, + final PostListModel newPage => newPage.nextPage, + final CommentListModel newPage => newPage.nextPage, + final DetailedUserListModel newPage => newPage.nextPage, Object _ => null, }, ); diff --git a/lib/src/screens/feed/create_screen.dart b/lib/src/screens/feed/create_screen.dart index a8594075..cd52ed04 100644 --- a/lib/src/screens/feed/create_screen.dart +++ b/lib/src/screens/feed/create_screen.dart @@ -4,23 +4,23 @@ import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/controller/database/database.dart'; -import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/controller/router.gr.dart'; +import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/models/community.dart'; import 'package:interstellar/src/models/post.dart'; import 'package:interstellar/src/screens/explore/community_owner_panel.dart'; import 'package:interstellar/src/utils/ap_urls.dart'; import 'package:interstellar/src/utils/language.dart'; import 'package:interstellar/src/utils/utils.dart'; +import 'package:interstellar/src/widgets/community_picker.dart'; import 'package:interstellar/src/widgets/image_selector.dart'; import 'package:interstellar/src/widgets/list_tile_switch.dart'; import 'package:interstellar/src/widgets/loading_button.dart'; -import 'package:interstellar/src/widgets/community_picker.dart'; import 'package:interstellar/src/widgets/markdown/drafts_controller.dart'; import 'package:interstellar/src/widgets/markdown/markdown_editor.dart'; +import 'package:interstellar/src/widgets/selection_menu.dart'; import 'package:interstellar/src/widgets/tags/post_flairs.dart'; import 'package:interstellar/src/widgets/tags/tag_widget.dart'; -import 'package:interstellar/src/widgets/selection_menu.dart'; import 'package:interstellar/src/widgets/text_editor.dart'; import 'package:interstellar/src/widgets/wrapper.dart'; import 'package:material_symbols_icons/symbols.dart'; @@ -62,7 +62,7 @@ class _CreateScreenState extends State { TextEditingController(text: 'Option 0'), ]; bool _pollModeMultiple = false; - Duration _pollDuration = Duration(days: 3); + Duration _pollDuration = const Duration(days: 3); @override void initState() { @@ -81,7 +81,7 @@ class _CreateScreenState extends State { _titleTextController.text = post.title!; } - String body = 'Cross posted from '; + var body = 'Cross posted from '; body += genPostUrls(context, post).last.toString(); if (post.body != null && post.body!.trim().isNotEmpty) { body += '\n\n'; @@ -132,7 +132,7 @@ class _CreateScreenState extends State { _urlTextController.text.isNotEmpty && (Uri.tryParse(_urlTextController.text)?.isAbsolute ?? false); - linkEditorFetchDataCB(bool override) async { + Future linkEditorFetchDataCB(bool override) async { if (!linkIsValid) return; if (!override && (_titleTextController.text.isNotEmpty || @@ -160,7 +160,7 @@ class _CreateScreenState extends State { label: Text(l(context).link), suffixIcon: LoadingIconButton( onPressed: !linkIsValid ? null : () => linkEditorFetchDataCB(true), - icon: Icon(Symbols.globe_rounded), + icon: const Icon(Symbols.globe_rounded), ), errorText: _urlTextController.text.isEmpty || linkIsValid ? null @@ -327,8 +327,8 @@ class _CreateScreenState extends State { ..._postFlairs.map((flair) => TagWidget(tag: flair)), OutlinedButton.icon( label: Text(l(context).editFlairs), - icon: Icon(Symbols.edit_rounded), - onPressed: () => showModalBottomSheet( + icon: const Icon(Symbols.edit_rounded), + onPressed: () => showModalBottomSheet( context: context, builder: (BuildContext context) => PostFlairsModal( flairs: _postFlairs, @@ -360,26 +360,29 @@ class _CreateScreenState extends State { tabs: [ Tab( text: l(context).create_text, - icon: Icon(Symbols.article_rounded), + icon: const Icon(Symbols.article_rounded), ), Tab( text: l(context).create_image, - icon: Icon(Symbols.image_rounded), + icon: const Icon(Symbols.image_rounded), ), Tab( text: l(context).create_link, - icon: Icon(Symbols.link_rounded), + icon: const Icon(Symbols.link_rounded), ), if (ac.serverSoftware == ServerSoftware.mbin) Tab( text: l(context).create_microblog, - icon: Icon(Symbols.edit_note_rounded), + icon: const Icon(Symbols.edit_note_rounded), ), if (ac.serverSoftware == ServerSoftware.piefed) - Tab(text: l(context).poll, icon: Icon(Symbols.poll_rounded)), + Tab( + text: l(context).poll, + icon: const Icon(Symbols.poll_rounded), + ), Tab( text: l(context).create_community, - icon: Icon(Symbols.group_rounded), + icon: const Icon(Symbols.group_rounded), ), ], ), @@ -565,7 +568,7 @@ class _CreateScreenState extends State { _community == null ? null : () async { - final endDate = _pollDuration == Duration() + final endDate = _pollDuration == Duration.zero ? null : DateTime.now().add(_pollDuration); @@ -614,35 +617,35 @@ class _CreateScreenState extends State { SelectionMenu pollDuration(BuildContext context) => SelectionMenu(l(context).pollDuration, [ SelectionMenuItem( - value: Duration(minutes: 30), + value: const Duration(minutes: 30), title: l(context).pollDuration_minutes(30), ), SelectionMenuItem( - value: Duration(hours: 1), + value: const Duration(hours: 1), title: l(context).pollDuration_hours(1), ), SelectionMenuItem( - value: Duration(hours: 6), + value: const Duration(hours: 6), title: l(context).pollDuration_hours(6), ), SelectionMenuItem( - value: Duration(hours: 12), + value: const Duration(hours: 12), title: l(context).pollDuration_hours(12), ), SelectionMenuItem( - value: Duration(days: 1), + value: const Duration(days: 1), title: l(context).pollDuration_days(1), ), SelectionMenuItem( - value: Duration(days: 3), + value: const Duration(days: 3), title: l(context).pollDuration_days(3), ), SelectionMenuItem( - value: Duration(days: 7), + value: const Duration(days: 7), title: l(context).pollDuration_days(7), ), SelectionMenuItem( - value: Duration(days: 365), + value: const Duration(days: 365), title: l(context).pollDuration_days(365), ), ]); diff --git a/lib/src/screens/feed/feed_agregator.dart b/lib/src/screens/feed/feed_agregator.dart index 07e654c3..ccdacdfc 100644 --- a/lib/src/screens/feed/feed_agregator.dart +++ b/lib/src/screens/feed/feed_agregator.dart @@ -1,15 +1,16 @@ import 'dart:math'; + import 'package:collection/collection.dart'; import 'package:interstellar/src/api/feed_source.dart'; import 'package:interstellar/src/controller/controller.dart'; +import 'package:interstellar/src/controller/feed.dart'; import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/models/post.dart'; -import 'package:interstellar/src/controller/feed.dart'; import 'package:interstellar/src/utils/utils.dart'; double calcLemmyRanking(PostModel post, DateTime time) { - final scaleFactor = 10000; - final gravity = 1.8; + const scaleFactor = 10000; + const gravity = 1.8; final score = (post.upvotes ?? 0) - (post.downvotes ?? 0); return scaleFactor * @@ -36,13 +37,13 @@ int mbinActive(PostModel lhs, PostModel rhs) { } double calcMbinRanking(PostModel post) { - final netscoreMultiplier = 4500; - final commentMultiplier = 1500; + const netscoreMultiplier = 4500; + const commentMultiplier = 1500; // final commentUniqueMultiplier = 5000; - final downvotedCutoff = -5; - final commentDownvotedMultiplier = 500; - final maxAdvantage = 86400; - final maxPenalty = 43200; + const downvotedCutoff = -5; + const commentDownvotedMultiplier = 500; + const maxAdvantage = 86400; + const maxPenalty = 43200; final score = (post.boosts ?? 0) + (post.upvotes ?? 0) - (post.downvotes ?? 0); @@ -51,10 +52,10 @@ double calcMbinRanking(PostModel post) { var commentAdvantage = 0; if (score > downvotedCutoff) { commentAdvantage = post.numComments * commentMultiplier; - //TODO: unique comment check here + //TODO(olorin99): unique comment check here } else { commentAdvantage = post.numComments * commentDownvotedMultiplier; - //TODO: unique comment check here + //TODO(olorin99): unique comment check here } final advantage = max( @@ -67,7 +68,7 @@ double calcMbinRanking(PostModel post) { DateTime.now().millisecondsSinceEpoch / 1000, ); - return min((dateAdvantage + advantage), pow(2, 31) - 1); + return min(dateAdvantage + advantage, pow(2, 31) - 1); } int mbinHot(PostModel lhs, PostModel rhs) { @@ -76,7 +77,7 @@ int mbinHot(PostModel lhs, PostModel rhs) { int top(PostModel lhs, PostModel rhs) { return ((rhs.upvotes ?? 0) - (rhs.downvotes ?? 0)).compareTo( - ((lhs.upvotes ?? 0) - (lhs.downvotes ?? 0)), + (lhs.upvotes ?? 0) - (lhs.downvotes ?? 0), ); } @@ -141,29 +142,29 @@ int commented(PostModel lhs, PostModel rhs) { }; // Copy inputs into mutable lists and include previous remainders if included - var mutableInputs = inputs + final mutableInputs = inputs .map((input) => input.isNotEmpty ? input.toList() : null) .nonNulls .toList(); - int remainderIndex = mutableInputs.length; + final remainderIndex = mutableInputs.length; if (previousRemainder != null) { previousRemainder.sort(sortFunc); mutableInputs.add(previousRemainder); } // Create room for remainders from merge inputs - List> remainder = List.generate( + final remainder = List>.generate( inputs.length + (previousRemainder != null ? 1 : 0), (index) => [], ); - List posts = []; + final posts = []; // Merge until one of the inputs (excluding previous remainders) is drained while (mutableInputs .sublist(0, remainderIndex) .every((input) => input.isNotEmpty)) { // Get first post of each input - List<(int, PostModel)> firsts = []; - for (var (index, input) in mutableInputs.indexed) { + final firsts = <(int, PostModel)>[]; + for (final (index, input) in mutableInputs.indexed) { if (input.isNotEmpty) { firsts.add((index, input.first)); } @@ -181,7 +182,7 @@ int commented(PostModel lhs, PostModel rhs) { } // Save remaining posts for next pass - for (var (index, input) in mutableInputs.indexed) { + for (final (index, input) in mutableInputs.indexed) { remainder[index] = input; } @@ -189,6 +190,12 @@ int commented(PostModel lhs, PostModel rhs) { } class FeedInputState { + FeedInputState({ + required this.title, + required this.source, + required this.sourceId, + }); + final String title; final FeedSource source; final int? sourceId; @@ -198,12 +205,6 @@ class FeedInputState { String? _nextPage = ''; String? _combinedPage = ''; - FeedInputState({ - required this.title, - required this.source, - required this.sourceId, - }); - Future<(List, String?)> fetchPage( AppController ac, String pageKey, @@ -242,7 +243,7 @@ class FeedInputState { page: nullIfEmpty(_nextPage!), sort: sort, ) - : Future.value(); + : Future.value(); final microblogFuture = _combinedPage != null && _combinedMicroblogsLeftover.length < 25 ? ac.api.microblogs.list( @@ -251,7 +252,7 @@ class FeedInputState { page: nullIfEmpty(_combinedPage!), sort: sort, ) - : Future.value(); + : Future.value(); final results = await Future.wait([threadFuture, microblogFuture]); final postLists = results @@ -276,7 +277,7 @@ class FeedInputState { : []; // if final page of input also return leftover posts - var result = [..._leftover, ...merged.$1]; + final result = [..._leftover, ...merged.$1]; if (_nextPage == null) { result.addAll(_combinedThreadsLeftover); } @@ -294,13 +295,9 @@ class FeedInputState { } class FeedAggregator { - final String name; - final List inputs; - const FeedAggregator({required this.name, required this.inputs}); - factory FeedAggregator.fromSingleSource( - AppController ac, { + factory FeedAggregator.fromSingleSource({ required String name, required FeedSource source, int? sourceId, @@ -310,6 +307,8 @@ class FeedAggregator { FeedInputState(title: source.name, source: source, sourceId: sourceId), ], ); + final String name; + final List inputs; static Future create( AppController ac, @@ -349,7 +348,7 @@ class FeedAggregator { final merged = merge(ac.serverSoftware, postInputs, sort); // store leftover posts - for (var (index, posts) in merged.$2.indexed) { + for (final (index, posts) in merged.$2.indexed) { inputs[index]._leftover = posts; } @@ -360,7 +359,7 @@ class FeedAggregator { final result = merged.$1; // if final page also return all leftover posts if (nextPage == null) { - for (var input in inputs) { + for (final input in inputs) { result.addAll(input._leftover); } } @@ -379,12 +378,13 @@ class FeedAggregator { } void refresh() { - for (var input in inputs) { - input._leftover = []; - input._nextPage = ''; - input._combinedPage = ''; - input._combinedThreadsLeftover = []; - input._combinedMicroblogsLeftover = []; + for (final input in inputs) { + input + .._leftover = [] + .._nextPage = '' + .._combinedPage = '' + .._combinedThreadsLeftover = [] + .._combinedMicroblogsLeftover = []; } } diff --git a/lib/src/screens/feed/feed_screen.dart b/lib/src/screens/feed/feed_screen.dart index def59ddb..15853585 100644 --- a/lib/src/screens/feed/feed_screen.dart +++ b/lib/src/screens/feed/feed_screen.dart @@ -1,4 +1,5 @@ import 'package:auto_route/auto_route.dart'; +import 'package:collection/collection.dart'; import 'package:expandable/expandable.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -6,8 +7,8 @@ import 'package:image_picker/image_picker.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:interstellar/src/api/feed_source.dart'; import 'package:interstellar/src/controller/controller.dart'; -import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/controller/router.gr.dart'; +import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/models/community.dart'; import 'package:interstellar/src/models/post.dart'; import 'package:interstellar/src/screens/feed/feed_agregator.dart'; @@ -24,20 +25,14 @@ import 'package:interstellar/src/widgets/hide_on_scroll.dart'; import 'package:interstellar/src/widgets/paging.dart'; import 'package:interstellar/src/widgets/scaffold.dart'; import 'package:interstellar/src/widgets/selection_menu.dart'; -import 'package:interstellar/src/widgets/wrapper.dart'; import 'package:interstellar/src/widgets/subordinate_scroll.dart'; +import 'package:interstellar/src/widgets/wrapper.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; import 'package:visibility_detector/visibility_detector.dart'; -import 'package:collection/collection.dart'; @RoutePage() class FeedScreen extends StatefulWidget { - final FeedAggregator? feed; - final Widget? details; - final DetailedCommunityModel? createPostCommunity; - final ScrollController? scrollController; - const FeedScreen({ super.key, this.feed, @@ -46,6 +41,11 @@ class FeedScreen extends StatefulWidget { this.scrollController, }); + final FeedAggregator? feed; + final Widget? details; + final DetailedCommunityModel? createPostCommunity; + final ScrollController? scrollController; + @override State createState() => _FeedScreenState(); } @@ -86,19 +86,17 @@ class _FeedScreenState extends State context.read().profile.feedDefaultCombinedSort, }; - void _initNavExpanded() async { + Future _initNavExpanded() async { final initExpanded = await context.read().expandNavDrawer; if (initExpanded != _drawerController.expanded) { if (!mounted) return; - setState(() { - _drawerController.toggle(); - }); + setState(_drawerController.toggle); } } List _getFeedTabs(AppController ac, ActionItem tabsAction) { return switch (tabsAction.name) { - String name when name == feedActionSetFilter(context).name => + final String name when name == feedActionSetFilter(context).name => ac.profile.feedSourceOrder .map( (option) => Tab( @@ -107,7 +105,7 @@ class _FeedScreenState extends State ), ) .toList(), - String name when name == feedActionSetView(context).name => + final String name when name == feedActionSetView(context).name => FeedView.match( values: ac.profile.feedViewOrder, software: ac.serverSoftware.bitFlag, @@ -119,7 +117,7 @@ class _FeedScreenState extends State ), ) .toList(), - String name when name == feedActionSetSort(context).name => + final String name when name == feedActionSetSort(context).name => FeedSort.match( values: ac.profile.feedSortOrder, software: ac.serverSoftware.bitFlag, @@ -142,18 +140,14 @@ class _FeedScreenState extends State TabController? controller, ) { return switch (tabsAction.name) { - String name when name == feedActionSetFilter(context).name => + final String name when name == feedActionSetFilter(context).name => ac.profile.feedSourceOrder .mapIndexed( (index, feed) => FeedScreenBody( key: _getFeedKey(index), feed: widget.feed ?? - FeedAggregator.fromSingleSource( - ac, - name: name, - source: feed, - ), + FeedAggregator.fromSingleSource(name: name, source: feed), sort: _sort ?? _defaultSortFromMode(_view), view: _view, details: widget.details, @@ -163,7 +157,7 @@ class _FeedScreenState extends State ), ) .toList(), - String name when name == feedActionSetView(context).name => + final String name when name == feedActionSetView(context).name => FeedView.match( values: ac.profile.feedViewOrder, software: ac.serverSoftware.bitFlag, @@ -174,7 +168,6 @@ class _FeedScreenState extends State feed: widget.feed ?? FeedAggregator.fromSingleSource( - ac, name: name, source: _filter, ), @@ -187,7 +180,7 @@ class _FeedScreenState extends State ), ) .toList(), - String name when name == feedActionSetSort(context).name => + final String name when name == feedActionSetSort(context).name => FeedSort.match( values: ac.profile.feedSortOrder, software: context.read().serverSoftware.bitFlag, @@ -198,7 +191,6 @@ class _FeedScreenState extends State feed: widget.feed ?? FeedAggregator.fromSingleSource( - ac, name: name, source: _filter, ), @@ -261,11 +253,11 @@ class _FeedScreenState extends State // canAuthUserModerate with content items // lemmy and piefed don't return this info final localUserPart = ac.localName; - final userCanModerate = widget.createPostCommunity == null - ? false - : widget.createPostCommunity!.moderators.any( - (mod) => mod.name == localUserPart, - ); + final userCanModerate = + !(widget.createPostCommunity == null) && + widget.createPostCommunity!.moderators.any( + (mod) => mod.name == localUserPart, + ); final actions = [ feedActionCreateNew(context).withProps( @@ -334,14 +326,14 @@ class _FeedScreenState extends State }, ), feedActionRefresh(context).withProps(ac.profile.feedActionRefresh, () { - for (var key in _feedKeyList) { + for (final key in _feedKeyList) { key.currentState?.refresh(); } }), feedActionBackToTop(context).withProps( ac.profile.feedActionBackToTop, () { - for (var key in _feedKeyList) { + for (final key in _feedKeyList) { key.currentState?.backToTop(); } }, @@ -352,25 +344,26 @@ class _FeedScreenState extends State _fabKey.currentState?.toggle(); }, ), - _hideReadPosts - ? feedActionShowReadPosts(context).withProps( - ac.profile.feedActionHideReadPosts, - () => setState(() { - _hideReadPosts = !_hideReadPosts; - for (var key in _feedKeyList) { - key.currentState?.refresh(); - } - }), - ) - : feedActionHideReadPosts(context).withProps( - ac.profile.feedActionHideReadPosts, - () => setState(() { - _hideReadPosts = !_hideReadPosts; - for (var key in _feedKeyList) { - key.currentState?.refresh(); - } - }), - ), + if (_hideReadPosts) + feedActionShowReadPosts(context).withProps( + ac.profile.feedActionHideReadPosts, + () => setState(() { + _hideReadPosts = !_hideReadPosts; + for (final key in _feedKeyList) { + key.currentState?.refresh(); + } + }), + ) + else + feedActionHideReadPosts(context).withProps( + ac.profile.feedActionHideReadPosts, + () => setState(() { + _hideReadPosts = !_hideReadPosts; + for (final key in _feedKeyList) { + key.currentState?.refresh(); + } + }), + ), ]; final tabsAction = [ @@ -397,7 +390,7 @@ class _FeedScreenState extends State shouldWrap: tabsAction != null, parentBuilder: (child) => DefaultTabController( initialIndex: switch (tabsAction?.name) { - String name when name == feedActionSetSort(context).name => + final String name when name == feedActionSetSort(context).name => tabs ?.asMap() .entries @@ -453,9 +446,7 @@ class _FeedScreenState extends State widget.feed == null && Breakpoints.isExpanded(context) ? IconButton( onPressed: () { - setState(() { - _drawerController.toggle(); - }); + setState(_drawerController.toggle); ac.setExpandNavDrawer(_drawerController.expanded); }, icon: const Icon(Symbols.menu_rounded), @@ -525,7 +516,6 @@ class _FeedScreenState extends State feed: widget.feed ?? FeedAggregator.fromSingleSource( - ac, name: widget.feed?.name ?? '', source: _filter, ), @@ -552,7 +542,7 @@ class _FeedScreenState extends State shouldWrap: ac.profile.hideFeedUIOnScroll, parentBuilder: (child) => HideOnScroll( controller: _scrollController, - hiddenOffset: Offset(0, 0.2), + hiddenOffset: const Offset(0, 0.2), duration: ac.calcAnimationDuration(), child: child, ), @@ -578,7 +568,7 @@ class _FeedScreenState extends State if (!mounted) return; setState(() { - _navDrawPersistentState = drawerState!; + _navDrawPersistentState = drawerState; }); }, ), @@ -652,24 +642,23 @@ SelectionMenu feedFilterSelect(BuildContext context) => ); class FeedScreenBody extends StatefulWidget { - final FeedAggregator feed; - final FeedSort sort; - final FeedView view; - final Widget? details; - final bool userCanModerate; - final bool hideReadPosts; - final bool isActive; - const FeedScreenBody({ - super.key, required this.feed, required this.sort, required this.view, + super.key, this.details, this.userCanModerate = false, this.hideReadPosts = false, this.isActive = false, }); + final FeedAggregator feed; + final FeedSort sort; + final FeedView view; + final Widget? details; + final bool userCanModerate; + final bool hideReadPosts; + final bool isActive; @override State createState() => _FeedScreenBodyState(); @@ -686,7 +675,7 @@ class _FeedScreenBodyState extends State if (pageKey.isEmpty) _filterListWarnings.clear(); var (newItems, nextPageKey) = await _tryFetchPage(pageKey); - int emptyPageCount = 0; + var emptyPageCount = 0; while ((emptyPageCount < 2) && newItems.isEmpty && nextPageKey != null && @@ -709,7 +698,9 @@ class _FeedScreenBodyState extends State final Map<(PostType, int), Set> _filterListWarnings = {}; int _lastVisibleIndex = 0; - final _markAsReadDebounce = Debouncer(duration: Duration(milliseconds: 500)); + final _markAsReadDebounce = Debouncer( + duration: const Duration(milliseconds: 500), + ); bool _lastPageFilteredOut = false; late FeedAggregator _aggregator; @@ -754,8 +745,8 @@ class _FeedScreenBodyState extends State // Skip feed filters if it's an explore page // if (widget.sourceId != null) return true; - for (var filterListEntry in ac.filterLists.entries) { - if (filterListActivations[filterListEntry.key] == true) { + for (final filterListEntry in ac.filterLists.entries) { + if (filterListActivations[filterListEntry.key] ?? false) { final filterList = filterListEntry.value; if ((post.title != null && filterList.hasMatch(post.title!)) || @@ -826,7 +817,7 @@ class _FeedScreenBodyState extends State final ac = context.read(); super.build(context); return RefreshIndicator( - onRefresh: () => Future.sync(() => refresh()), + onRefresh: () => Future.sync(refresh), child: CustomScrollView( controller: _scrollController, slivers: [ @@ -883,8 +874,8 @@ class _FeedScreenBodyState extends State final items = _pagingController.value.items; if (items == null) return; - List readPosts = []; - for (int i = index; i >= 0; i--) { + final readPosts = []; + for (var i = index; i >= 0; i--) { final post = items[i]; if (post.read || readPosts.contains(post)) { continue; @@ -892,7 +883,7 @@ class _FeedScreenBodyState extends State readPosts.add(post); } if (readPosts.isNotEmpty) { - var postsMarkedAsRead = await ac.markAsRead( + final postsMarkedAsRead = await ac.markAsRead( readPosts, true, ); diff --git a/lib/src/screens/feed/nav_drawer.dart b/lib/src/screens/feed/nav_drawer.dart index 5e902996..a496a611 100644 --- a/lib/src/screens/feed/nav_drawer.dart +++ b/lib/src/screens/feed/nav_drawer.dart @@ -1,12 +1,13 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:interstellar/src/controller/controller.dart'; -import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/controller/router.gr.dart'; -import 'package:interstellar/src/models/domain.dart'; +import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/models/community.dart'; +import 'package:interstellar/src/models/domain.dart'; import 'package:interstellar/src/models/user.dart'; import 'package:interstellar/src/screens/explore/explore_screen.dart'; +import 'package:interstellar/src/screens/feed/feed_agregator.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/avatar.dart'; import 'package:interstellar/src/widgets/loading_list_tile.dart'; @@ -14,8 +15,6 @@ import 'package:interstellar/src/widgets/settings_header.dart'; import 'package:interstellar/src/widgets/star_button.dart'; import 'package:provider/provider.dart'; -import 'feed_agregator.dart'; - class NavDrawPersistentState { const NavDrawPersistentState({ this.fetchTime = 0, @@ -71,9 +70,9 @@ Future fetchNavDrawerState(AppController ac) async { final initExpandedFollows = await ac.expandNavFollows; final initExpandedDomains = await ac.expandNavDomains; - List subbedCommunities = []; - List subbedUsers = []; - List subbedDomains = []; + var subbedCommunities = []; + var subbedUsers = []; + var subbedDomains = []; if (ac.isLoggedIn) { subbedCommunities = (await ac.api.community.list( filter: ExploreFilter.subscribed, @@ -124,7 +123,7 @@ class _NavDrawerState extends State { if (widget.drawerState == null || widget.drawerState!.fetchTime < DateTime.now() - .subtract(Duration(minutes: 15)) + .subtract(const Duration(minutes: 15)) .millisecondsSinceEpoch) { widget.updateState!(null); } @@ -149,7 +148,7 @@ class _NavDrawerState extends State { Widget build(BuildContext context) { final ac = context.watch(); if (widget.drawerState == null) { - return Center(child: CircularProgressIndicator()); + return const Center(child: CircularProgressIndicator()); } return ListView( @@ -182,7 +181,7 @@ class _NavDrawerState extends State { : star.substring(1).split('@').first, ), onTap: () async { - String name = star.substring(1); + var name = star.substring(1); if (name.endsWith(ac.instanceHost)) { name = name.split('@').first; } @@ -196,7 +195,6 @@ class _NavDrawerState extends State { context.router.push( UserRoute(userId: user.id, initData: user), ); - break; case '!': final community = await ac.api.community.getByName(name); @@ -209,7 +207,6 @@ class _NavDrawerState extends State { initData: community, ), ); - break; } }, trailing: StarButton(star), diff --git a/lib/src/screens/feed/post_comment.dart b/lib/src/screens/feed/post_comment.dart index 25355e76..1b46e5a0 100644 --- a/lib/src/screens/feed/post_comment.dart +++ b/lib/src/screens/feed/post_comment.dart @@ -5,18 +5,18 @@ import 'package:image_picker/image_picker.dart'; import 'package:interstellar/src/api/bookmark.dart'; import 'package:interstellar/src/api/notifications.dart'; import 'package:interstellar/src/controller/controller.dart'; -import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/controller/router.gr.dart'; +import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/models/comment.dart'; import 'package:interstellar/src/utils/ap_urls.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/ban_dialog.dart'; import 'package:interstellar/src/widgets/content_item/content_item.dart'; +import 'package:interstellar/src/widgets/display_name.dart'; import 'package:interstellar/src/widgets/menus/content_menu.dart'; +import 'package:interstellar/src/widgets/user_status_icons.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; -import 'package:interstellar/src/widgets/display_name.dart'; -import 'package:interstellar/src/widgets/user_status_icons.dart'; import 'package:simplytranslate/simplytranslate.dart'; class PostComment extends StatefulWidget { @@ -80,9 +80,7 @@ class _PostCommentState extends State { } void collapse() { - setState(() { - _expandableController.toggle(); - }); + setState(_expandableController.toggle); } @override @@ -146,9 +144,9 @@ class _PostCommentState extends State { widget.onUpdate(widget.comment.copyWith(user: user)), opUserId: widget.opUserId, boosts: widget.comment.boosts, - isBoosted: widget.comment.myBoost == true, + isBoosted: widget.comment.myBoost ?? false, onBoost: whenLoggedIn(context, () async { - var newValue = await ac.api.comments.boost( + final newValue = await ac.api.comments.boost( widget.comment.postType, widget.comment.id, ); @@ -161,7 +159,7 @@ class _PostCommentState extends State { }), upVotes: widget.comment.upvotes, onUpVote: whenLoggedIn(context, () async { - var newValue = await ac.api.comments.vote( + final newValue = await ac.api.comments.vote( widget.comment.postType, widget.comment.id, 1, @@ -178,7 +176,7 @@ class _PostCommentState extends State { downVotes: widget.comment.downvotes, isDownVoted: widget.comment.myVote == -1, onDownVote: whenLoggedIn(context, () async { - var newValue = await ac.api.comments.vote( + final newValue = await ac.api.comments.vote( widget.comment.postType, widget.comment.id, -1, @@ -198,7 +196,7 @@ class _PostCommentState extends State { XFile? image, String? alt, }) async { - var newSubComment = await ac.api.comments.create( + final newSubComment = await ac.api.comments.create( widget.comment.postType, widget.comment.postId, body, @@ -224,7 +222,7 @@ class _PostCommentState extends State { }), onEdit: widget.comment.visibility != 'soft_deleted' ? whenLoggedIn(context, (body) async { - var newValue = await ac.api.comments.edit( + final newValue = await ac.api.comments.edit( widget.comment.postType, widget.comment.id, body, @@ -295,7 +293,7 @@ class _PostCommentState extends State { .toList(), matchesSoftware: ServerSoftware.mbin, ), - onAddBookmark: whenLoggedIn(context, (() async { + onAddBookmark: whenLoggedIn(context, () async { final newBookmarks = await ac.api.bookmark.addBookmarkToDefault( subjectType: BookmarkListSubject.fromPostType( postType: widget.comment.postType, @@ -304,7 +302,7 @@ class _PostCommentState extends State { subjectId: widget.comment.id, ); widget.onUpdate(widget.comment.copyWith(bookmarks: newBookmarks)); - })), + }), onAddBookmarkToList: whenLoggedIn(context, (String listName) async { final newBookmarks = await ac.api.bookmark.addBookmarkToList( subjectType: BookmarkListSubject.fromPostType( @@ -358,7 +356,7 @@ class _PostCommentState extends State { onEmojiReact: widget.comment.emojiReactions == null ? null : whenLoggedIn(context, (emoji) async { - var newValue = await ac.api.comments.vote( + final newValue = await ac.api.comments.vote( widget.comment.postType, widget.comment.id, 1, @@ -376,7 +374,7 @@ class _PostCommentState extends State { final menuWidget = IconButton( padding: EdgeInsets.zero, - constraints: BoxConstraints(), + constraints: const BoxConstraints(), icon: const Icon(Symbols.more_vert_rounded), onPressed: () { showContentMenu( @@ -484,7 +482,7 @@ class _PostCommentState extends State { (item) => PostComment( item.value, (newValue) { - var newChildren = [...widget.comment.children!]; + final newChildren = [...widget.comment.children!]; newChildren[item.key] = newValue; widget.onUpdate( widget.comment.copyWith( diff --git a/lib/src/screens/feed/post_item.dart b/lib/src/screens/feed/post_item.dart index a11eb006..77796919 100644 --- a/lib/src/screens/feed/post_item.dart +++ b/lib/src/screens/feed/post_item.dart @@ -9,9 +9,9 @@ import 'package:interstellar/src/utils/ap_urls.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/ban_dialog.dart'; import 'package:interstellar/src/widgets/content_item/content_item.dart'; +import 'package:interstellar/src/widgets/super_hero.dart'; import 'package:provider/provider.dart'; import 'package:simplytranslate/simplytranslate.dart'; -import 'package:interstellar/src/widgets/super_hero.dart'; class PostItem extends StatefulWidget { const PostItem( @@ -109,21 +109,20 @@ class _PostItemState extends State { createdAt: widget.item.createdAt, editedAt: widget.item.editedAt, poll: widget.item.poll, - isPreview: widget.item.type == PostType.microblog - ? false - : widget.isPreview, - fullImageSize: widget.isPreview - ? switch (widget.item.type) { - PostType.thread => ac.profile.fullImageSizeThreads, - PostType.microblog => ac.profile.fullImageSizeMicroblogs, - } - : true, + isPreview: + !(widget.item.type == PostType.microblog) && widget.isPreview, + fullImageSize: + !widget.isPreview || + switch (widget.item.type) { + PostType.thread => ac.profile.fullImageSizeThreads, + PostType.microblog => ac.profile.fullImageSizeMicroblogs, + }, showCommunityFirst: widget.item.type == PostType.thread, read: widget.isTopLevel && widget.item.read, feedView: widget.isTopLevel, isPinned: widget.item.isPinned, isNSFW: widget.item.isNSFW, - isOC: widget.item.isOC == true, + isOC: widget.item.isOC ?? false, user: widget.item.user, updateUser: (user) async => widget.onUpdate(widget.item.copyWith(user: user)), @@ -131,7 +130,7 @@ class _PostItemState extends State { domain: widget.item.domain?.name, domainIdOnClick: widget.item.domain?.id, boosts: widget.item.boosts, - isBoosted: widget.item.myBoost == true, + isBoosted: widget.item.myBoost ?? false, onBoost: whenLoggedIn(context, () async { widget.onUpdate( (await ac.markAsRead([ @@ -350,7 +349,7 @@ class _PostItemState extends State { 1, emoji: emoji, ), - PostType.microblog => throw 'Unreachable', + PostType.microblog => throw UnreachableError(), }, ], true)).first, ); diff --git a/lib/src/screens/feed/post_page.dart b/lib/src/screens/feed/post_page.dart index d4aa8791..afd6f525 100644 --- a/lib/src/screens/feed/post_page.dart +++ b/lib/src/screens/feed/post_page.dart @@ -1,33 +1,33 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:interstellar/src/api/bookmark.dart'; import 'package:interstellar/src/api/comments.dart'; +import 'package:interstellar/src/api/notifications.dart'; import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/controller/router.gr.dart'; +import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/models/comment.dart'; import 'package:interstellar/src/models/post.dart'; import 'package:interstellar/src/screens/feed/post_comment.dart'; import 'package:interstellar/src/screens/feed/post_item.dart'; import 'package:interstellar/src/utils/ap_urls.dart'; import 'package:interstellar/src/utils/utils.dart'; -import 'package:interstellar/src/widgets/menus/content_menu.dart'; +import 'package:interstellar/src/widgets/ban_dialog.dart'; +import 'package:interstellar/src/widgets/content_item/content_item.dart'; import 'package:interstellar/src/widgets/context_menu.dart'; import 'package:interstellar/src/widgets/loading_button.dart'; import 'package:interstellar/src/widgets/loading_template.dart'; +import 'package:interstellar/src/widgets/menus/content_menu.dart'; import 'package:interstellar/src/widgets/paging.dart'; -import 'package:interstellar/src/widgets/content_item/content_item.dart'; -import 'package:interstellar/src/controller/server.dart'; -import 'package:interstellar/src/api/bookmark.dart'; -import 'package:interstellar/src/api/notifications.dart'; -import 'package:interstellar/src/widgets/ban_dialog.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; @RoutePage() class ThreadPage extends StatelessWidget { const ThreadPage({ - super.key, @PathParam('id') required this.postId, + super.key, this.initData, this.onUpdate, this.userCanModerate = false, @@ -51,8 +51,8 @@ class ThreadPage extends StatelessWidget { @RoutePage() class MicroblogPage extends StatelessWidget { const MicroblogPage({ - super.key, @PathParam('id') required this.postId, + super.key, this.initData, this.onUpdate, this.userCanModerate = false, @@ -73,7 +73,7 @@ class MicroblogPage extends StatelessWidget { ); } -void pushPostPage( +Future pushPostPage( BuildContext context, { int? postId, PostType? postType, @@ -136,9 +136,9 @@ class _PostPageState extends State { _initData(); } - void _initData() async { + Future _initData() async { if (widget.initData != null) { - _data = widget.initData!; + _data = widget.initData; } // Cross posts are only returned on fetching single post not on list // so need to fetch full post info. @@ -176,8 +176,8 @@ class _PostPageState extends State { } void _updateCrossPost(PostModel crossPost) { - var newCrossPosts = _data!.crossPosts.toList(); - int indexOfPost = newCrossPosts.indexWhere( + final newCrossPosts = _data!.crossPosts.toList(); + final indexOfPost = newCrossPosts.indexWhere( (post) => post.id == crossPost.id, ); newCrossPosts[indexOfPost] = crossPost; @@ -199,7 +199,7 @@ class _PostPageState extends State { }, community: crossPost.community, boosts: crossPost.boosts, - isBoosted: crossPost.myBoost == true, + isBoosted: crossPost.myBoost ?? false, onBoost: whenLoggedIn(context, () async { _updateCrossPost( (await ac.markAsRead([ @@ -393,7 +393,7 @@ class _PostPageState extends State { 1, emoji: emoji, ), - PostType.microblog => throw 'Unreachable', + PostType.microblog => throw UnreachableError(), }, ], true)).first, ); @@ -415,7 +415,7 @@ class _PostPageState extends State { return const LoadingTemplate(); } - PostModel post = _data!; + final post = _data!; final ac = context.read(); @@ -479,7 +479,7 @@ class _PostPageState extends State { XFile? image, String? alt, }) async { - var newComment = await context + final newComment = await context .read() .api .comments @@ -524,6 +524,9 @@ class _PostPageState extends State { post.id, ), }; + + if (!context.mounted) return; + _onUpdate( post.copyWith( body: '_${l(context).postDeleted}_', @@ -580,7 +583,7 @@ class _PostPageState extends State { .map( (crossPost) => SliverMainAxisGroup( slivers: [ - SliverToBoxAdapter(child: const Divider()), + const SliverToBoxAdapter(child: Divider()), SliverToBoxAdapter( child: ListTile( title: Text( @@ -620,11 +623,11 @@ class _PostPageState extends State { class CommentSection extends StatefulWidget { const CommentSection({ - super.key, required this.id, required this.postType, required this.sort, required this.opUserId, + super.key, this.canModerate = false, }); diff --git a/lib/src/screens/settings/about_screen.dart b/lib/src/screens/settings/about_screen.dart index 2bcf6293..3132281f 100644 --- a/lib/src/screens/settings/about_screen.dart +++ b/lib/src/screens/settings/about_screen.dart @@ -1,10 +1,10 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:interstellar/src/controller/controller.dart'; -import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/controller/router.gr.dart'; -import 'package:interstellar/src/utils/utils.dart'; +import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/utils/globals.dart'; +import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/open_webpage.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; @@ -39,7 +39,7 @@ class _AboutScreenState extends State { ListTile( leading: const Icon(Symbols.bug_report_rounded), title: Text(l(context).settings_debug), - onTap: () => context.router.push(DebugSettingsRoute()), + onTap: () => context.router.push(const DebugSettingsRoute()), ), if (context.read().serverSoftware != ServerSoftware.piefed) @@ -81,7 +81,7 @@ class _AboutScreenState extends State { title: Text(l(context).settings_mbinCommunity), onTap: () async { try { - String name = _mbinCommunityName; + var name = _mbinCommunityName; if (name.endsWith(context.read().instanceHost)) { name = name.split('@').first; } @@ -111,7 +111,7 @@ class _AboutScreenState extends State { title: Text(l(context).settings_mbinConfigsCommunity), onTap: () async { try { - String name = mbinConfigsCommunityName; + var name = mbinConfigsCommunityName; if (name.endsWith(context.read().instanceHost)) { name = name.split('@').first; } diff --git a/lib/src/screens/settings/account_migration.dart b/lib/src/screens/settings/account_migration.dart index 20475e28..c076a58a 100644 --- a/lib/src/screens/settings/account_migration.dart +++ b/lib/src/screens/settings/account_migration.dart @@ -48,7 +48,7 @@ class _AccountMigrationScreenState extends State { ac.servers[_destinationAccount!.split('@').last]!.software == ServerSoftware.mbin; - void migrationCommand() async { + Future migrationCommand() async { try { if (_migrationProgress != MigrationOrResetProgress.pending) return; @@ -135,7 +135,7 @@ class _AccountMigrationScreenState extends State { }); final destAPI = await ac.getApiForAccount(_destinationAccount!); final destAccountHost = _destinationAccount!.split('@').last; - for (var item in _migrateCommunitySubscriptions.found) { + for (final item in _migrateCommunitySubscriptions.found) { try { final res = await destAPI.community.getByName( denormalizeName(item, destAccountHost), @@ -149,7 +149,7 @@ class _AccountMigrationScreenState extends State { } if (progressAndCheckCancel()) return; } - for (var item in _migrateCommunityBlocks.found) { + for (final item in _migrateCommunityBlocks.found) { try { final res = await destAPI.community.getByName( denormalizeName(item, destAccountHost), @@ -163,7 +163,7 @@ class _AccountMigrationScreenState extends State { } if (progressAndCheckCancel()) return; } - for (var item in _migrateUserFollows.found) { + for (final item in _migrateUserFollows.found) { try { final res = await destAPI.users.getByName( denormalizeName(item, destAccountHost), @@ -177,7 +177,7 @@ class _AccountMigrationScreenState extends State { } if (progressAndCheckCancel()) return; } - for (var item in _migrateUserBlocks.found) { + for (final item in _migrateUserBlocks.found) { try { final res = await destAPI.users.getByName( denormalizeName(item, destAccountHost), @@ -250,15 +250,16 @@ class _AccountMigrationScreenState extends State { ? null : Text(_sourceAccount!), onTap: () async { - final newSourceAccount = await showModalBottomSheet( - context: context, - builder: (BuildContext context) { - return AccountSelectWidget( - oldAccount: _sourceAccount ?? '', - onlyNonGuestAccounts: true, + final newSourceAccount = + await showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return AccountSelectWidget( + oldAccount: _sourceAccount ?? '', + onlyNonGuestAccounts: true, + ); + }, ); - }, - ); if (newSourceAccount == null) return; @@ -278,7 +279,7 @@ class _AccountMigrationScreenState extends State { : Text(_destinationAccount!), onTap: () async { final newDestinationAccount = - await showModalBottomSheet( + await showModalBottomSheet( context: context, builder: (BuildContext context) { return AccountSelectWidget( @@ -368,7 +369,7 @@ class _AccountMigrationScreenState extends State { Step( state: step1Complete ? StepState.indexed : StepState.disabled, title: Text(l(context).settings_accountMigration_step3), - content: const Row(children: []), + content: const Row(), ), ], ), diff --git a/lib/src/screens/settings/account_reset.dart b/lib/src/screens/settings/account_reset.dart index acdeb1fa..f24bb378 100644 --- a/lib/src/screens/settings/account_reset.dart +++ b/lib/src/screens/settings/account_reset.dart @@ -3,12 +3,11 @@ import 'package:flutter/material.dart'; import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/screens/explore/explore_screen.dart'; +import 'package:interstellar/src/screens/settings/account_migration.dart'; import 'package:interstellar/src/screens/settings/account_selection.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:provider/provider.dart'; -import './account_migration.dart'; - @RoutePage() class AccountResetScreen extends StatefulWidget { const AccountResetScreen({super.key}); @@ -40,7 +39,7 @@ class _AccountResetScreenState extends State { ac.servers[_selectedAccount!.split('@').last]!.software == ServerSoftware.mbin; - void resetCommand() async { + Future resetCommand() async { try { if (_resetProgress != MigrationOrResetProgress.pending) return; @@ -118,7 +117,7 @@ class _AccountResetScreenState extends State { setState(() { _resetProgress = MigrationOrResetProgress.writingDestination; }); - for (var item in _resetCommunitySubscriptions.found) { + for (final item in _resetCommunitySubscriptions.found) { try { await api.community.subscribe(item, false); _resetCommunitySubscriptions.complete.add(item); @@ -127,7 +126,7 @@ class _AccountResetScreenState extends State { } if (progressAndCheckCancel()) return; } - for (var item in _resetCommunityBlocks.found) { + for (final item in _resetCommunityBlocks.found) { try { await api.community.block(item, false); _resetCommunityBlocks.complete.add(item); @@ -136,7 +135,7 @@ class _AccountResetScreenState extends State { } if (progressAndCheckCancel()) return; } - for (var item in _resetUserFollows.found) { + for (final item in _resetUserFollows.found) { try { await api.users.follow(item, false); _resetUserFollows.complete.add(item); @@ -145,7 +144,7 @@ class _AccountResetScreenState extends State { } if (progressAndCheckCancel()) return; } - for (var item in _resetUserBlocks.found) { + for (final item in _resetUserBlocks.found) { try { await api.users.putBlock(item, false); _resetUserBlocks.complete.add(item); @@ -206,15 +205,16 @@ class _AccountResetScreenState extends State { ? null : Text(_selectedAccount!), onTap: () async { - final newSourceAccount = await showModalBottomSheet( - context: context, - builder: (BuildContext context) { - return AccountSelectWidget( - oldAccount: _selectedAccount ?? '', - onlyNonGuestAccounts: true, + final newSourceAccount = + await showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return AccountSelectWidget( + oldAccount: _selectedAccount ?? '', + onlyNonGuestAccounts: true, + ); + }, ); - }, - ); if (newSourceAccount == null) return; @@ -296,7 +296,7 @@ class _AccountResetScreenState extends State { Step( state: step1Complete ? StepState.indexed : StepState.disabled, title: Text(l(context).settings_accountReset_step3), - content: const Row(children: []), + content: const Row(), ), ], ), diff --git a/lib/src/screens/settings/account_selection.dart b/lib/src/screens/settings/account_selection.dart index 2c63b042..933854a1 100644 --- a/lib/src/screens/settings/account_selection.dart +++ b/lib/src/screens/settings/account_selection.dart @@ -11,7 +11,7 @@ Future switchAccount( BuildContext context, [ String? oldAccount, ]) async { - return await showModalBottomSheet( + return showModalBottomSheet( context: context, builder: (BuildContext context) { return AccountSelectWidget( @@ -26,7 +26,7 @@ Future selectAccountWithNone( BuildContext context, [ String? oldAccount, ]) async { - return await showModalBottomSheet( + return showModalBottomSheet( context: context, builder: (BuildContext context) { return AccountSelectWidget(oldAccount: oldAccount, showNoneOption: true); @@ -35,11 +35,6 @@ Future selectAccountWithNone( } class AccountSelectWidget extends StatefulWidget { - final bool showNoneOption; - final bool showAccountControls; - final bool onlyNonGuestAccounts; - final String? oldAccount; - const AccountSelectWidget({ this.showNoneOption = false, this.showAccountControls = false, @@ -48,6 +43,11 @@ class AccountSelectWidget extends StatefulWidget { super.key, }); + final bool showNoneOption; + final bool showAccountControls; + final bool onlyNonGuestAccounts; + final String? oldAccount; + @override State createState() => _AccountSelectWidgetState(); } @@ -154,7 +154,7 @@ class _AccountSelectWidgetState extends State { ListTile( title: Text(l(context).addAccount), leading: const Icon(Symbols.login_rounded), - onTap: () => context.router.push(LoginSelectRoute()), + onTap: () => context.router.push(const LoginSelectRoute()), ), const SizedBox(height: 16), ], diff --git a/lib/src/screens/settings/behavior_screen.dart b/lib/src/screens/settings/behavior_screen.dart index 4274e38c..05b00a14 100644 --- a/lib/src/screens/settings/behavior_screen.dart +++ b/lib/src/screens/settings/behavior_screen.dart @@ -133,7 +133,6 @@ class _BehaviorSettingsScreenState extends State { value: ac.profile.animationSpeed, divisions: 4, max: 4, - min: 0, label: ac.profile.animationSpeed == 0 ? l(context).settings_animationDisabled : ac.profile.animationSpeed.toString(), @@ -163,7 +162,7 @@ class _BehaviorSettingsScreenState extends State { ac.setDefaultDownloadDir(null); setState(() {}); }, - icon: Icon(Symbols.clear_rounded), + icon: const Icon(Symbols.clear_rounded), ) : null, onTap: () async { diff --git a/lib/src/screens/settings/data_utilities.dart b/lib/src/screens/settings/data_utilities.dart index 07534c26..b4edec3e 100644 --- a/lib/src/screens/settings/data_utilities.dart +++ b/lib/src/screens/settings/data_utilities.dart @@ -16,12 +16,12 @@ class DataUtilitiesScreen extends StatelessWidget { ListTile( title: Text(l(context).settings_accountMigration), subtitle: Text(l(context).settings_accountMigration_help), - onTap: () => context.router.push(AccountMigrationRoute()), + onTap: () => context.router.push(const AccountMigrationRoute()), ), ListTile( title: Text(l(context).settings_accountReset), subtitle: Text(l(context).settings_accountReset_help), - onTap: () => context.router.push(AccountResetRoute()), + onTap: () => context.router.push(const AccountResetRoute()), ), ], ), diff --git a/lib/src/screens/settings/debug/debug_screen.dart b/lib/src/screens/settings/debug/debug_screen.dart index 14001468..c2958522 100644 --- a/lib/src/screens/settings/debug/debug_screen.dart +++ b/lib/src/screens/settings/debug/debug_screen.dart @@ -1,20 +1,20 @@ import 'dart:io'; + import 'package:auto_route/auto_route.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:interstellar/src/controller/controller.dart'; +import 'package:interstellar/src/controller/database/database.dart'; import 'package:interstellar/src/controller/router.gr.dart'; +import 'package:interstellar/src/utils/sqlite/sqlite.dart' + if (dart.library.io) 'package:interstellar/src/utils/sqlite/sqlite_native.dart' + if (dart.library.js_interop) 'package:interstellar/src/utils/sqlite/sqlite_web.dart'; import 'package:interstellar/src/utils/utils.dart'; +import 'package:interstellar/src/widgets/list_tile_switch.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:provider/provider.dart'; -import 'package:interstellar/src/widgets/list_tile_switch.dart'; -import 'package:interstellar/src/controller/database/database.dart'; - -import 'package:interstellar/src/utils/sqlite/sqlite.dart' - if (dart.library.io) 'package:interstellar/src/utils/sqlite/sqlite_native.dart' - if (dart.library.js_interop) 'package:interstellar/src/utils/sqlite/sqlite_web.dart'; @RoutePage() class DebugSettingsScreen extends StatelessWidget { @@ -39,7 +39,7 @@ class DebugSettingsScreen extends StatelessWidget { ListTile( leading: const Icon(Symbols.schema_rounded), title: Text(l(context).settings_debug_inspectDatabase), - onTap: () => context.router.push(NamedRoute('DriftDbViewer')), + onTap: () => context.router.push(const NamedRoute('DriftDbViewer')), ), if (!PlatformIs.web) ...[ ListTile( @@ -105,7 +105,7 @@ class DebugSettingsScreen extends StatelessWidget { File(tmpPath).delete(); } catch (e) { ac.logger.e('Attempted to import invalid database'); - throw 'Attempted to import invalid database'; + throw Exception('Attempted to import invalid database'); } final dbDir = await getApplicationSupportDirectory(); @@ -121,7 +121,7 @@ class DebugSettingsScreen extends StatelessWidget { ListTile( leading: const Icon(Symbols.storage_rounded), title: Text(l(context).settings_debug_clearDatabase), - onTap: () => showDialog( + onTap: () => showDialog( context: context, builder: (context) => AlertDialog( title: Text(l(context).settings_debug_clearDatabase), @@ -146,7 +146,7 @@ class DebugSettingsScreen extends StatelessWidget { ListTile( leading: const Icon(Symbols.person_rounded), title: Text(l(context).settings_debug_clearAccounts), - onTap: () => showDialog( + onTap: () => showDialog( context: context, builder: (context) => AlertDialog( title: Text(l(context).settings_debug_clearAccounts), @@ -158,7 +158,7 @@ class DebugSettingsScreen extends StatelessWidget { FilledButton( onPressed: () async { final accountKeys = ac.accounts.keys.toList(); - for (var account in accountKeys) { + for (final account in accountKeys) { await ac.removeAccount(account); } ac.logger.i('Cleared accounts'); @@ -174,7 +174,7 @@ class DebugSettingsScreen extends StatelessWidget { ListTile( leading: const Icon(Symbols.tune_rounded), title: Text(l(context).settings_debug_clearProfiles), - onTap: () => showDialog( + onTap: () => showDialog( context: context, builder: (context) => AlertDialog( title: Text(l(context).settings_debug_clearProfiles), @@ -186,8 +186,8 @@ class DebugSettingsScreen extends StatelessWidget { FilledButton( onPressed: () async { final profileNames = await ac.getProfileNames(); - for (var profile in profileNames) { - ac.deleteProfile(profile); + for (final profile in profileNames) { + await ac.deleteProfile(profile); } ac.logger.i('Cleared profiles'); if (!context.mounted) return; @@ -202,7 +202,7 @@ class DebugSettingsScreen extends StatelessWidget { ListTile( leading: const Icon(Symbols.mark_email_read_rounded), title: Text(l(context).settings_debug_clearReadPosts), - onTap: () => showDialog( + onTap: () => showDialog( context: context, builder: (context) => AlertDialog( title: Text(l(context).settings_debug_clearReadPosts), @@ -227,7 +227,7 @@ class DebugSettingsScreen extends StatelessWidget { ListTile( leading: const Icon(Symbols.download_for_offline_rounded), title: Text(l(context).settings_debug_clearFeedCache), - onTap: () => showDialog( + onTap: () => showDialog( context: context, builder: (context) => AlertDialog( title: Text(l(context).settings_debug_clearFeedCache), @@ -252,7 +252,7 @@ class DebugSettingsScreen extends StatelessWidget { ListTile( leading: const Icon(Symbols.label_rounded), title: Text(l(context).settings_debug_clearTags), - onTap: () => showDialog( + onTap: () => showDialog( context: context, builder: (context) => AlertDialog( title: Text(l(context).settings_debug_clearTags), @@ -278,7 +278,7 @@ class DebugSettingsScreen extends StatelessWidget { ListTile( leading: const Icon(Symbols.list_rounded), title: Text(l(context).settings_debug_log), - onTap: () => context.router.push(LogConsole()), + onTap: () => context.router.push(const LogConsole()), ), ], ), diff --git a/lib/src/screens/settings/debug/log_console.dart b/lib/src/screens/settings/debug/log_console.dart index cc47c9fa..0615ad5a 100644 --- a/lib/src/screens/settings/debug/log_console.dart +++ b/lib/src/screens/settings/debug/log_console.dart @@ -1,13 +1,14 @@ import 'dart:io'; + import 'package:auto_route/annotations.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/utils/utils.dart'; +import 'package:logger/logger.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:path_provider/path_provider.dart'; import 'package:provider/provider.dart'; -import 'package:logger/logger.dart'; @RoutePage() class LogConsole extends StatefulWidget { @@ -22,9 +23,9 @@ class _LogConsoleState extends State { List _logLines = []; final ScrollController _controller = ScrollController(); - void _fetchLogFile() async { + Future _fetchLogFile() async { final logFile = await context.read().logFile; - List lines = await logFile?.readAsLines() ?? []; + final lines = await logFile?.readAsLines() ?? []; setState(() { _logFile = logFile; _logLines = lines; @@ -91,10 +92,9 @@ class _LogConsoleState extends State { ], ), body: Container( - constraints: BoxConstraints.expand(), + constraints: const BoxConstraints.expand(), color: Colors.black, child: SingleChildScrollView( - scrollDirection: Axis.vertical, controller: _controller, child: SingleChildScrollView( scrollDirection: Axis.horizontal, @@ -118,7 +118,7 @@ class _LogConsoleState extends State { children: [ Text( '${line.key}: ', - style: TextStyle( + style: const TextStyle( fontSize: 12, color: Colors.amberAccent, ), diff --git a/lib/src/screens/settings/display_screen.dart b/lib/src/screens/settings/display_screen.dart index edc5723d..7d34d8e1 100644 --- a/lib/src/screens/settings/display_screen.dart +++ b/lib/src/screens/settings/display_screen.dart @@ -1,10 +1,11 @@ import 'dart:math'; + import 'package:auto_route/auto_route.dart'; import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/material.dart'; import 'package:interstellar/src/controller/controller.dart'; -import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/controller/router.gr.dart'; +import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/utils/language.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/list_tile_switch.dart'; @@ -110,7 +111,7 @@ class DisplaySettingsScreen extends StatelessWidget { ListTile( leading: const Icon(Symbols.vertical_split_rounded), title: Text(l(context).settings_postLayoutOrder), - onTap: () => context.router.push(PostLayoutSettingsRoute()), + onTap: () => context.router.push(const PostLayoutSettingsRoute()), ), ListTileSwitch( leading: const Icon(Symbols.view_day_rounded), @@ -132,7 +133,6 @@ class DisplaySettingsScreen extends StatelessWidget { Slider( value: ac.profile.dividerThickness, max: 10, - min: 0, onChanged: ac.profile.showPostsCards && !ac.profile.compactMode ? null diff --git a/lib/src/screens/settings/feed_actions_screen.dart b/lib/src/screens/settings/feed_actions_screen.dart index 76a5e019..a021bf6d 100644 --- a/lib/src/screens/settings/feed_actions_screen.dart +++ b/lib/src/screens/settings/feed_actions_screen.dart @@ -4,9 +4,9 @@ import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/actions.dart'; import 'package:interstellar/src/widgets/list_tile_select.dart'; +import 'package:interstellar/src/widgets/list_tile_switch.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; -import 'package:interstellar/src/widgets/list_tile_switch.dart'; @RoutePage() class FeedActionsSettingsScreen extends StatelessWidget { @@ -175,8 +175,6 @@ class FeedActionsSettingsScreen extends StatelessWidget { ), Slider( value: ac.profile.swipeActionThreshold, - max: 1, - min: 0, onChanged: (newValue) => ac.updateProfile( ac.selectedProfileValue.copyWith( swipeActionThreshold: newValue, diff --git a/lib/src/screens/settings/feed_defaults/feed_sort_order.dart b/lib/src/screens/settings/feed_defaults/feed_sort_order.dart index e4262ff2..edf92ce9 100644 --- a/lib/src/screens/settings/feed_defaults/feed_sort_order.dart +++ b/lib/src/screens/settings/feed_defaults/feed_sort_order.dart @@ -3,11 +3,11 @@ import 'package:collection/collection.dart'; import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/material.dart'; import 'package:interstellar/src/api/feed_source.dart'; +import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/controller/profile.dart'; +import 'package:interstellar/src/utils/utils.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; -import 'package:interstellar/src/controller/controller.dart'; -import 'package:interstellar/src/utils/utils.dart'; @RoutePage() class FeedSortOrderSettingsScreen extends StatefulWidget { diff --git a/lib/src/screens/settings/feed_defaults/feed_source_order.dart b/lib/src/screens/settings/feed_defaults/feed_source_order.dart index 875efe01..d53467df 100644 --- a/lib/src/screens/settings/feed_defaults/feed_source_order.dart +++ b/lib/src/screens/settings/feed_defaults/feed_source_order.dart @@ -3,11 +3,11 @@ import 'package:collection/collection.dart'; import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/material.dart'; import 'package:interstellar/src/api/feed_source.dart'; +import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/controller/profile.dart'; +import 'package:interstellar/src/utils/utils.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; -import 'package:interstellar/src/controller/controller.dart'; -import 'package:interstellar/src/utils/utils.dart'; @RoutePage() class FeedSourceOrderSettingsScreen extends StatefulWidget { diff --git a/lib/src/screens/settings/feed_defaults/feed_view_order.dart b/lib/src/screens/settings/feed_defaults/feed_view_order.dart index 9e2ce73c..7778e120 100644 --- a/lib/src/screens/settings/feed_defaults/feed_view_order.dart +++ b/lib/src/screens/settings/feed_defaults/feed_view_order.dart @@ -3,11 +3,11 @@ import 'package:collection/collection.dart'; import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/material.dart'; import 'package:interstellar/src/api/feed_source.dart'; +import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/controller/profile.dart'; +import 'package:interstellar/src/utils/utils.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; -import 'package:interstellar/src/controller/controller.dart'; -import 'package:interstellar/src/utils/utils.dart'; @RoutePage() class FeedViewOrderSettingsScreen extends StatefulWidget { diff --git a/lib/src/screens/settings/feed_defaults_screen.dart b/lib/src/screens/settings/feed_defaults_screen.dart index db05d1e3..5cfe6eb3 100644 --- a/lib/src/screens/settings/feed_defaults_screen.dart +++ b/lib/src/screens/settings/feed_defaults_screen.dart @@ -6,9 +6,9 @@ import 'package:interstellar/src/controller/router.gr.dart'; import 'package:interstellar/src/screens/feed/feed_screen.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/list_tile_select.dart'; +import 'package:interstellar/src/widgets/list_tile_switch.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; -import 'package:interstellar/src/widgets/list_tile_switch.dart'; @RoutePage() class FeedDefaultSettingsScreen extends StatelessWidget { @@ -25,17 +25,20 @@ class FeedDefaultSettingsScreen extends StatelessWidget { ListTile( leading: const Icon(Symbols.filter_list_rounded), title: Text(l(context).settings_feedSourceOrder), - onTap: () => context.router.push(FeedSourceOrderSettingsRoute()), + onTap: () => + context.router.push(const FeedSourceOrderSettingsRoute()), ), ListTile( leading: const Icon(Symbols.tab_rounded), title: Text(l(context).settings_feedViewOrder), - onTap: () => context.router.push(FeedViewOrderSettingsRoute()), + onTap: () => + context.router.push(const FeedViewOrderSettingsRoute()), ), ListTile( leading: const Icon(Symbols.sort_rounded), title: Text(l(context).settings_feedSortOrder), - onTap: () => context.router.push(FeedSortOrderSettingsRoute()), + onTap: () => + context.router.push(const FeedSortOrderSettingsRoute()), ), ListTileSelect( title: l(context).settings_feedDefaults_threadsSort, diff --git a/lib/src/screens/settings/feed_settings_screen.dart b/lib/src/screens/settings/feed_settings_screen.dart index 1186e47e..b3e3c52f 100644 --- a/lib/src/screens/settings/feed_settings_screen.dart +++ b/lib/src/screens/settings/feed_settings_screen.dart @@ -1,24 +1,24 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:interstellar/src/api/feed_source.dart'; import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/controller/feed.dart'; -import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/controller/router.gr.dart'; -import 'package:material_symbols_icons/symbols.dart'; -import 'package:provider/provider.dart'; -import 'package:interstellar/src/api/feed_source.dart'; +import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/models/community.dart'; import 'package:interstellar/src/models/config_share.dart'; import 'package:interstellar/src/models/domain.dart'; import 'package:interstellar/src/models/feed.dart'; import 'package:interstellar/src/models/user.dart'; -import 'package:interstellar/src/utils/utils.dart'; -import 'package:interstellar/src/widgets/loading_button.dart'; -import 'package:interstellar/src/widgets/text_editor.dart'; import 'package:interstellar/src/screens/explore/explore_screen.dart'; import 'package:interstellar/src/screens/feed/feed_agregator.dart'; import 'package:interstellar/src/screens/settings/about_screen.dart'; +import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/context_menu.dart'; +import 'package:interstellar/src/widgets/loading_button.dart'; +import 'package:interstellar/src/widgets/text_editor.dart'; +import 'package:material_symbols_icons/symbols.dart'; +import 'package:provider/provider.dart'; @RoutePage() class FeedSettingsScreen extends StatefulWidget { @@ -85,7 +85,7 @@ class _FeedSettingsScreenState extends State { ); if (!mounted) return; - String communityName = mbinConfigsCommunityName; + var communityName = mbinConfigsCommunityName; if (communityName.endsWith(ac.instanceHost)) { communityName = communityName.split('@').first; } @@ -158,13 +158,13 @@ void newFeed(BuildContext context) { name: normalizeName(item.name, ac.instanceHost), sourceType: FeedSource.feed, serverId: item.id, - ), // TODO: tmp until proper getByName method can be made + ), // TODO(olorin99): tmp until proper getByName method can be made }, ); - String title = item.title; + var title = item.title; if (ac.feeds[title] != null) { - await showDialog( + await showDialog( context: context, builder: (context) { return AlertDialog( @@ -180,7 +180,7 @@ void newFeed(BuildContext context) { ), LoadingFilledButton( onPressed: () async { - int num = 0; + var num = 0; while (ac.feeds[title] != null) { title = '${item.title} ${num++}'; } @@ -223,13 +223,13 @@ void newFeed(BuildContext context) { name: normalizeName(item.name, ac.instanceHost), sourceType: FeedSource.topic, serverId: item.id, - ), // TODO: tmp until proper getByName method can be made + ), // TODO(olorin99): tmp until proper getByName method can be made }, ); - String title = item.title; + var title = item.title; if (ac.feeds[title] != null) { - await showDialog( + await showDialog( context: context, builder: (context) { return AlertDialog( @@ -245,7 +245,7 @@ void newFeed(BuildContext context) { ), LoadingFilledButton( onPressed: () async { - int num = 0; + var num = 0; while (ac.feeds[title] != null) { title = '${item.title} ${num++}'; } @@ -280,15 +280,15 @@ void newFeed(BuildContext context) { @RoutePage() class EditFeedScreen extends StatefulWidget { - final String? feed; - final Feed? feedData; - const EditFeedScreen({ @PathParam('feed') required this.feed, this.feedData, super.key, }); + final String? feed; + final Feed? feedData; + @override State createState() => _EditFeedScreenState(); } @@ -305,7 +305,9 @@ class _EditFeedScreenState extends State { nameController.text = widget.feed!; } - feedData = widget.feedData == null ? Feed(inputs: {}) : widget.feedData!; + feedData = widget.feedData == null + ? const Feed(inputs: {}) + : widget.feedData!; } void addInput(FeedInput input) { @@ -315,8 +317,8 @@ class _EditFeedScreenState extends State { } void removeInput(FeedInput input) { - final inputs = {...feedData.inputs}; - inputs.remove(input); + final inputs = {...feedData.inputs}..remove(input); + setState(() { feedData = feedData.copyWith(inputs: inputs); }); @@ -362,10 +364,10 @@ class _EditFeedScreenState extends State { .toSet(), onTap: (selected, item) { var name = switch (item) { - DetailedCommunityModel i => i.name, - DetailedUserModel i => i.name, - DomainModel i => i.name, - FeedModel i => i.name, + final DetailedCommunityModel i => i.name, + final DetailedUserModel i => i.name, + final DomainModel i => i.name, + final FeedModel i => i.name, _ => null, }; final source = switch (item) { @@ -376,7 +378,7 @@ class _EditFeedScreenState extends State { _ => null, }; final id = switch (item) { - FeedModel i => i.id, + final FeedModel i => i.id, _ => null, }; diff --git a/lib/src/screens/settings/filter_lists_screen.dart b/lib/src/screens/settings/filter_lists_screen.dart index e659a7f6..45801101 100644 --- a/lib/src/screens/settings/filter_lists_screen.dart +++ b/lib/src/screens/settings/filter_lists_screen.dart @@ -58,7 +58,7 @@ class _FilterListsScreenState extends State { ); if (!context.mounted) return; - String communityName = mbinConfigsCommunityName; + var communityName = mbinConfigsCommunityName; if (communityName.endsWith( context.read().instanceHost, )) { @@ -89,7 +89,7 @@ class _FilterListsScreenState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: Switch( - value: ac.profile.filterLists[name] == true, + value: ac.profile.filterLists[name] ?? false, onChanged: (value) { ac.updateProfile( ac.selectedProfileValue.copyWith( @@ -119,15 +119,15 @@ class _FilterListsScreenState extends State { @RoutePage() class EditFilterListScreen extends StatefulWidget { - final String? filterList; - final FilterList? importFilterList; - const EditFilterListScreen({ @PathParam('filterList') required this.filterList, this.importFilterList, super.key, }); + final String? filterList; + final FilterList? importFilterList; + @override State createState() => _EditFilterListScreenState(); } @@ -173,7 +173,7 @@ class _EditFilterListScreenState extends State { if (widget.filterList != null && widget.importFilterList == null) ...[ ListTileSwitch( title: Text(l(context).filterList_activateFilter), - value: ac.profile.filterLists[widget.filterList] == true, + value: ac.profile.filterLists[widget.filterList] ?? false, onChanged: (value) { ac.updateProfile( ac.selectedProfileValue.copyWith( @@ -207,9 +207,8 @@ class _EditFilterListScreenState extends State { child: InputChip( label: Text(phrase), onDeleted: () async { - final newPhrases = filterListData.phrases.toSet(); - - newPhrases.remove(phrase); + final newPhrases = filterListData.phrases.toSet() + ..remove(phrase); setState(() { filterListData = filterListData.copyWith( @@ -253,9 +252,8 @@ class _EditFilterListScreenState extends State { if (phrase == null) return; - final newPhrases = filterListData.phrases.toSet(); - - newPhrases.add(phrase); + final newPhrases = filterListData.phrases.toSet() + ..add(phrase); setState(() { filterListData = filterListData.copyWith( diff --git a/lib/src/screens/settings/login_confirm.dart b/lib/src/screens/settings/login_confirm.dart index f596d807..6336c20a 100644 --- a/lib/src/screens/settings/login_confirm.dart +++ b/lib/src/screens/settings/login_confirm.dart @@ -1,21 +1,21 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:interstellar/src/controller/controller.dart'; +import 'package:interstellar/src/controller/database/database.dart'; import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/loading_button.dart'; import 'package:interstellar/src/widgets/password_editor.dart'; import 'package:interstellar/src/widgets/text_editor.dart'; import 'package:provider/provider.dart'; -import 'package:interstellar/src/controller/database/database.dart'; @RoutePage() class LoginConfirmScreen extends StatefulWidget { + const LoginConfirmScreen(this.software, this.server, {super.key}); + final ServerSoftware software; final String server; - const LoginConfirmScreen(this.software, this.server, {super.key}); - @override State createState() => _LoginConfirmScreenState(); } @@ -58,7 +58,7 @@ class _LoginConfirmScreenState extends State { label: l(context).usernameOrEmail, keyboardType: TextInputType.emailAddress, onChanged: (_) => setState(() {}), - autofillHints: [ + autofillHints: const [ AutofillHints.username, AutofillHints.email, ], @@ -86,7 +86,7 @@ class _LoginConfirmScreenState extends State { children: [ OutlinedButton( onPressed: () { - String account = '@${widget.server}'; + final account = '@${widget.server}'; context.read().setAccount( account, const Account(handle: '', isPushRegistered: false), diff --git a/lib/src/screens/settings/login_select.dart b/lib/src/screens/settings/login_select.dart index 392d8943..dbc2ce73 100644 --- a/lib/src/screens/settings/login_select.dart +++ b/lib/src/screens/settings/login_select.dart @@ -2,8 +2,8 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:interstellar/src/api/api.dart'; import 'package:interstellar/src/controller/controller.dart'; -import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/controller/router.gr.dart'; +import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/server_software_indicator.dart'; import 'package:interstellar/src/widgets/text_editor.dart'; @@ -116,21 +116,24 @@ class _LoginSelectScreenState extends State { Widget build(BuildContext context) { final language = Localizations.localeOf(context).languageCode; - genServerList(List<(String, ServerSoftware)> servers) => servers - .map( - (v) => ListTile( - title: Row( - children: [ServerSoftwareIndicator(label: v.$1, software: v.$2)], - ), - onTap: () => _initiateLogin(v.$1), - ), - ) - .toList(); + List genServerList(List<(String, ServerSoftware)> servers) => + servers + .map( + (v) => ListTile( + title: Row( + children: [ + ServerSoftwareIndicator(label: v.$1, software: v.$2), + ], + ), + onTap: () => _initiateLogin(v.$1), + ), + ) + .toList(); return Scaffold( appBar: AppBar(title: Text(l(context).addAccount)), body: ListView( - padding: const EdgeInsets.all(16.0), + padding: const EdgeInsets.all(16), children: [ TextEditor( _instanceHostController, @@ -153,7 +156,7 @@ class _LoginSelectScreenState extends State { ), if (_recommendedInstances.containsKey(language)) ...[ ...genServerList(_recommendedInstances[language]!), - Divider(), + const Divider(), ], ...genServerList(_recommendedInstances['']!), ], diff --git a/lib/src/screens/settings/notification_screen.dart b/lib/src/screens/settings/notification_screen.dart index aa8e35e7..e6b0a66b 100644 --- a/lib/src/screens/settings/notification_screen.dart +++ b/lib/src/screens/settings/notification_screen.dart @@ -27,7 +27,7 @@ class _NotificationSettingsScreenState _initSettings(); } - void _initSettings() async { + Future _initSettings() async { final settings = await context .read() .api @@ -38,7 +38,7 @@ class _NotificationSettingsScreenState }); } - void _saveSettings() async { + Future _saveSettings() async { final settings = await context .read() .api @@ -69,7 +69,7 @@ class _NotificationSettingsScreenState ), value: ac.isPushRegistered, onChanged: (bool? value) async { - if (value == true) { + if (value ?? false) { await ac.registerPush(context); } else if (value == false) { await ac.unregisterPush(); @@ -84,7 +84,7 @@ class _NotificationSettingsScreenState value: _settings!.notifyOnNewEntry!, onChanged: (bool? value) { setState(() { - _settings!.notifyOnNewEntry = value!; + _settings!.notifyOnNewEntry = value; _saveSettings(); }); }, @@ -94,7 +94,7 @@ class _NotificationSettingsScreenState value: _settings!.notifyOnNewPost!, onChanged: (bool? value) { setState(() { - _settings!.notifyOnNewPost = value!; + _settings!.notifyOnNewPost = value; _saveSettings(); }); }, @@ -104,7 +104,7 @@ class _NotificationSettingsScreenState value: _settings!.notifyOnNewEntryReply!, onChanged: (bool? value) { setState(() { - _settings!.notifyOnNewEntryReply = value!; + _settings!.notifyOnNewEntryReply = value; _saveSettings(); }); }, @@ -116,7 +116,7 @@ class _NotificationSettingsScreenState value: _settings!.notifyOnNewEntryCommentReply!, onChanged: (bool? value) { setState(() { - _settings!.notifyOnNewEntryCommentReply = value!; + _settings!.notifyOnNewEntryCommentReply = value; _saveSettings(); }); }, @@ -126,7 +126,7 @@ class _NotificationSettingsScreenState value: _settings!.notifyOnNewPostReply!, onChanged: (bool? value) { setState(() { - _settings!.notifyOnNewPostReply = value!; + _settings!.notifyOnNewPostReply = value; _saveSettings(); }); }, @@ -138,7 +138,7 @@ class _NotificationSettingsScreenState value: _settings!.notifyOnNewPostCommentReply!, onChanged: (bool? value) { setState(() { - _settings!.notifyOnNewPostCommentReply = value!; + _settings!.notifyOnNewPostCommentReply = value; _saveSettings(); }); }, diff --git a/lib/src/screens/settings/post_layout.dart b/lib/src/screens/settings/post_layout.dart index 4c20d1c9..6d5c34dc 100644 --- a/lib/src/screens/settings/post_layout.dart +++ b/lib/src/screens/settings/post_layout.dart @@ -1,14 +1,13 @@ -import 'dart:io'; import 'package:auto_route/annotations.dart'; import 'package:collection/collection.dart'; import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/material.dart'; +import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/controller/profile.dart'; +import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/content_item/content_item.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; -import 'package:interstellar/src/controller/controller.dart'; -import 'package:interstellar/src/utils/utils.dart'; @RoutePage() class PostLayoutSettingsScreen extends StatefulWidget { diff --git a/lib/src/screens/settings/profile_selection.dart b/lib/src/screens/settings/profile_selection.dart index 4e143586..bb3ad7e5 100644 --- a/lib/src/screens/settings/profile_selection.dart +++ b/lib/src/screens/settings/profile_selection.dart @@ -15,7 +15,7 @@ import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; Future switchProfileSelect(BuildContext context) async { - await showModalBottomSheet( + await showModalBottomSheet( context: context, builder: (BuildContext context) { return const _ProfileSelectWidget(); @@ -33,7 +33,7 @@ class _ProfileSelectWidget extends StatefulWidget { class _ProfileSelectWidgetState extends State<_ProfileSelectWidget> { List? profileList; - void getProfiles() async { + Future getProfiles() async { final profileNames = await context.read().getProfileNames(); setState(() { profileList = profileNames; @@ -68,7 +68,7 @@ class _ProfileSelectWidgetState extends State<_ProfileSelectWidget> { ), IconButton( onPressed: () { - showDialog( + showDialog( context: context, builder: (context) => AlertDialog( title: Text(l(context).profile_about_title), @@ -140,7 +140,7 @@ class _ProfileSelectWidgetState extends State<_ProfileSelectWidget> { ); if (!context.mounted) return; - String communityName = mbinConfigsCommunityName; + var communityName = mbinConfigsCommunityName; if (communityName.endsWith( context.read().instanceHost, )) { @@ -194,10 +194,6 @@ class _ProfileSelectWidgetState extends State<_ProfileSelectWidget> { @RoutePage() class EditProfileScreen extends StatefulWidget { - final String? profile; - final List profileList; - final ProfileOptional? importProfile; - const EditProfileScreen({ required this.profile, required this.profileList, @@ -205,6 +201,10 @@ class EditProfileScreen extends StatefulWidget { super.key, }); + final String? profile; + final List profileList; + final ProfileOptional? importProfile; + @override State createState() => _EditProfileScreenState(); } diff --git a/lib/src/screens/settings/settings_screen.dart b/lib/src/screens/settings/settings_screen.dart index f6352606..7b9c1f20 100644 --- a/lib/src/screens/settings/settings_screen.dart +++ b/lib/src/screens/settings/settings_screen.dart @@ -1,8 +1,8 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:interstellar/src/controller/controller.dart'; -import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/controller/router.gr.dart'; +import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/screens/settings/account_selection.dart'; import 'package:interstellar/src/screens/settings/profile_selection.dart'; import 'package:interstellar/src/utils/utils.dart'; @@ -24,42 +24,42 @@ class SettingsScreen extends StatelessWidget { ListTile( leading: const Icon(Symbols.settings_rounded), title: Text(l(context).settings_behavior), - onTap: () => context.router.push(BehaviorSettingsRoute()), + onTap: () => context.router.push(const BehaviorSettingsRoute()), ), ListTile( leading: const Icon(Symbols.palette_rounded), title: Text(l(context).settings_display), - onTap: () => context.router.push(DisplaySettingsRoute()), + onTap: () => context.router.push(const DisplaySettingsRoute()), ), ListTile( leading: const Icon(Symbols.feed_rounded), title: Text(l(context).feeds), - onTap: () => context.router.push(FeedSettingsRoute()), + onTap: () => context.router.push(const FeedSettingsRoute()), ), ListTile( leading: const Icon(Symbols.filter_list_rounded), title: Text(l(context).settings_feedActions), - onTap: () => context.router.push(FeedActionsSettingsRoute()), + onTap: () => context.router.push(const FeedActionsSettingsRoute()), ), ListTile( leading: const Icon(Symbols.tune_rounded), title: Text(l(context).settings_feedDefaults), - onTap: () => context.router.push(FeedDefaultSettingsRoute()), + onTap: () => context.router.push(const FeedDefaultSettingsRoute()), ), ListTile( leading: const Icon(Symbols.label_rounded), title: Text(l(context).tags), - onTap: () => context.router.push(TagsRoute()), + onTap: () => context.router.push(const TagsRoute()), ), ListTile( leading: const Icon(Symbols.filter_1_rounded), title: Text(l(context).filterLists), - onTap: () => context.router.push(FilterListsRoute()), + onTap: () => context.router.push(const FilterListsRoute()), ), ListTile( leading: const Icon(Symbols.notifications_rounded), title: Text(l(context).settings_notifications), - onTap: () => context.router.push(NotificationSettingsRoute()), + onTap: () => context.router.push(const NotificationSettingsRoute()), enabled: ac.serverSoftware == ServerSoftware.mbin && context @@ -72,12 +72,12 @@ class SettingsScreen extends StatelessWidget { ListTile( leading: const Icon(Symbols.database_rounded), title: Text(l(context).settings_dataUtilities), - onTap: () => context.router.push(DataUtilitiesRoute()), + onTap: () => context.router.push(const DataUtilitiesRoute()), ), ListTile( leading: const Icon(Symbols.info_rounded), title: Text(l(context).settings_about), - onTap: () => context.router.push(AboutRoute()), + onTap: () => context.router.push(const AboutRoute()), ), const Divider(), ListTile( diff --git a/lib/src/utils/debouncer.dart b/lib/src/utils/debouncer.dart index f6f4b7c3..c9f97c79 100644 --- a/lib/src/utils/debouncer.dart +++ b/lib/src/utils/debouncer.dart @@ -1,11 +1,11 @@ import 'dart:async'; class Debouncer { + Debouncer({required this.duration}); + final Duration duration; Timer? _timer; - Debouncer({required this.duration}); - void run(void Function() cb) { _timer?.cancel(); _timer = Timer(duration, cb); diff --git a/lib/src/utils/globals.dart b/lib/src/utils/globals.dart index 6cb7aa90..6644f956 100644 --- a/lib/src/utils/globals.dart +++ b/lib/src/utils/globals.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:interstellar/src/utils/utils.dart'; -final isWebViewSupported = PlatformIs.mobile; +final bool isWebViewSupported = PlatformIs.mobile; final GlobalKey scaffoldMessengerKey = GlobalKey(); @@ -10,7 +10,7 @@ final GlobalKey scaffoldMessengerKey = late final String appVersion; late final http.Client appHttpClient; -final oauthRedirectUri = Uri.parse( +final Uri oauthRedirectUri = Uri.parse( PlatformIs.web ? '${Uri.base.origin}/auth.html' : PlatformIs.linux || PlatformIs.windows diff --git a/lib/src/utils/http_client.dart b/lib/src/utils/http_client.dart index e6fa0a5c..6e305e7a 100644 --- a/lib/src/utils/http_client.dart +++ b/lib/src/utils/http_client.dart @@ -3,11 +3,11 @@ import 'dart:async'; import 'package:http/http.dart' as http; class UserAgentHttpClient extends http.BaseClient { + UserAgentHttpClient(this._userAgent); + final String _userAgent; final http.Client _httpClient = http.Client(); - UserAgentHttpClient(this._userAgent); - @override Future send(http.BaseRequest request) async { request.headers['User-Agent'] = _userAgent; @@ -22,12 +22,12 @@ class UserAgentHttpClient extends http.BaseClient { } class JwtHttpClient extends http.BaseClient { + JwtHttpClient(this._jwt, this._httpClient); + final String _jwt; final http.Client _httpClient; - JwtHttpClient(this._jwt, this._httpClient); - @override Future send(http.BaseRequest request) async { request.headers['authorization'] = 'Bearer $_jwt'; diff --git a/lib/src/utils/language.dart b/lib/src/utils/language.dart index c8bc6f29..92e076e6 100644 --- a/lib/src/utils/language.dart +++ b/lib/src/utils/language.dart @@ -1,7 +1,7 @@ import 'package:flutter/widgets.dart'; -import 'package:interstellar/l10n/app_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localized_locales/flutter_localized_locales.dart'; +import 'package:interstellar/l10n/app_localizations.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/selection_menu.dart'; diff --git a/lib/src/utils/models.dart b/lib/src/utils/models.dart index 58ed8c8c..ced2cf9b 100644 --- a/lib/src/utils/models.dart +++ b/lib/src/utils/models.dart @@ -8,8 +8,8 @@ List? optionalStringList(Object? json) => json == null ? null : (json as List).cast(); String? mbinCalcNextPaginationPage(JsonMap pagination) { - return (pagination['currentPage'] as int) < (pagination['maxPage'] as int) - ? ((pagination['currentPage'] as int) + 1).toString() + return (pagination['currentPage']! as int) < (pagination['maxPage']! as int) + ? ((pagination['currentPage']! as int) + 1).toString() : null; } @@ -33,11 +33,11 @@ String mbinNormalizeUsername(String username) { /// Converts lemmy and piefed's local name to Mbin's standard name String getLemmyPiefedActorName(JsonMap json) { - final name = (json['user_name'] ?? json['name']) as String; + final name = (json['user_name'] ?? json['name'])! as String; - return (json['local'] as bool) + return (json['local']! as bool) ? name - : '$name@${Uri.parse(json['actor_id'] as String).host}'; + : '$name@${Uri.parse(json['actor_id']! as String).host}'; } String? lemmyCalcNextIntPage(List list, String? currentPage) => diff --git a/lib/src/utils/share.dart b/lib/src/utils/share.dart index e1441255..a9c413d2 100644 --- a/lib/src/utils/share.dart +++ b/lib/src/utils/share.dart @@ -8,9 +8,9 @@ import 'package:share_plus/share_plus.dart'; Future shareUri(Uri uri) async { if (PlatformIs.mobile) { - return await Share.shareUri(uri); + return Share.shareUri(uri); } else { - return await Share.share(uri.toString()); + return Share.share(uri.toString()); } } diff --git a/lib/src/utils/sqlite/sqlite_web.dart b/lib/src/utils/sqlite/sqlite_web.dart index 25995eec..cba0bfb1 100644 --- a/lib/src/utils/sqlite/sqlite_web.dart +++ b/lib/src/utils/sqlite/sqlite_web.dart @@ -1,4 +1,3 @@ -import 'package:sqlite3/common.dart'; import 'package:sqlite3/wasm.dart'; // get sqlite on web diff --git a/lib/src/utils/trie.dart b/lib/src/utils/trie.dart index 4a67d17c..175e8a48 100644 --- a/lib/src/utils/trie.dart +++ b/lib/src/utils/trie.dart @@ -1,11 +1,11 @@ class Trie { - Set ends; - Map> children; - Trie([Set? ends, Map>? children]) : ends = ends ?? {}, children = children ?? >{}; + Set ends; + Map> children; + void addChild(String term, Set newEnds) { if (term.isEmpty) { ends.addAll(newEnds); @@ -37,13 +37,14 @@ class Trie { return results; } + @override String toString() => 'Trie($ends,$children)'; static String normalizeTerm(String term) { term = term.toLowerCase(); // Replace anything that's alphanumeric with a single space - final replacedTerm = term.replaceAll(r'[^0-9a-z]+', ' '); + final replacedTerm = term.replaceAll('[^0-9a-z]+', ' '); // If term only contains symbols, than don't use replaced term. if (replacedTerm.isNotEmpty) term = replacedTerm; diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index 53d17ff2..26ab2944 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -39,36 +39,36 @@ String dateDiffFormat(DateTime input) { final difference = DateTime.now().difference(input); if (difference.inDays > 0) { - var years = (difference.inDays / 365).truncate(); + final years = (difference.inDays / 365).truncate(); if (years >= 1) { return '${years}Y'; } - var months = (difference.inDays / 30).truncate(); + final months = (difference.inDays / 30).truncate(); if (months >= 1) { return '${months}M'; } - var weeks = (difference.inDays / 7).truncate(); + final weeks = (difference.inDays / 7).truncate(); if (weeks >= 1) { return '${weeks}w'; } - var days = difference.inDays; + final days = difference.inDays; return '${days}d'; } - var hours = difference.inHours; + final hours = difference.inHours; if (hours > 0) { return '${hours}h'; } - var minutes = difference.inMinutes; + final minutes = difference.inMinutes; if (minutes > 0) { return '${minutes}m'; } - var seconds = difference.inSeconds; + final seconds = difference.inSeconds; return '${seconds}s'; } @@ -130,9 +130,10 @@ String readableShortcut(SingleActivator shortcut) { if (shortcut.alt) text += 'Alt+'; if (shortcut.shift) text += 'Shift+'; if (shortcut.meta) text += 'Meta+'; + text += switch (shortcut.trigger.keyLabel) { ' ' => 'Space', - String key => key, + final String key => key, }; return text; @@ -148,12 +149,7 @@ List reverseList(List list, bool enabled) { return list; } -const chipDropdownPadding = EdgeInsets.only( - left: 4, - top: 6, - right: 0, - bottom: 6, -); +const chipDropdownPadding = EdgeInsets.only(left: 4, top: 6, bottom: 6); ScrollPhysics? appTabViewPhysics(BuildContext context) => context.watch().profile.disableTabSwiping @@ -162,9 +158,9 @@ ScrollPhysics? appTabViewPhysics(BuildContext context) => class DefaultTabControllerListener extends StatefulWidget { const DefaultTabControllerListener({ + required this.child, super.key, this.onTabSelected, - required this.child, }); final void Function(int index)? onTabSelected; @@ -296,38 +292,6 @@ String? mbinGetSortTime(FeedSort? sort) { typedef JsonMap = Map; -class InterstellarRoute extends MaterialPageRoute { - InterstellarRoute({ - required super.builder, - this.transitionDuration = const Duration(milliseconds: 300), - this.reverseTransitionDuration = const Duration(milliseconds: 300), - }); - - @override - Duration transitionDuration; - @override - Duration reverseTransitionDuration; -} - -Future pushRoute( - BuildContext context, { - required WidgetBuilder builder, - Duration duration = const Duration(milliseconds: 300), -}) async { - final animationSpeed = context.read().profile.animationSpeed; - final adjustedDuration = animationSpeed == 0 - ? Duration.zero - : duration * (1.0 / animationSpeed); - - await Navigator.of(context).push( - InterstellarRoute( - transitionDuration: adjustedDuration, - reverseTransitionDuration: adjustedDuration, - builder: builder, - ), - ); -} - // apparently Platform is unsupported on web so have to check for web before checking specific platform class PlatformIs { static const bool web = kIsWeb; @@ -341,3 +305,10 @@ class PlatformIs { static final bool mobile = android || iOS; static final bool desktop = linux || macOS || windows; } + +class UnreachableError extends Error { + UnreachableError(); + + @override + String toString() => 'Unreachable Error, you should not be seeing this!'; +} diff --git a/lib/src/widgets/actions.dart b/lib/src/widgets/actions.dart index 5c393f47..c652f43a 100644 --- a/lib/src/widgets/actions.dart +++ b/lib/src/widgets/actions.dart @@ -8,12 +8,6 @@ enum ActionLocation { hide, appBar, fabTap, fabHold, fabMenu } enum ActionLocationWithTabs { hide, appBar, fabTap, fabHold, fabMenu, tabs } class ActionItem { - final String name; - final IconData icon; - final void Function()? callback; - final ActionLocation? location; - final Color? color; - const ActionItem({ required this.name, required this.icon, @@ -22,6 +16,12 @@ class ActionItem { this.color, }); + final String name; + final IconData icon; + final void Function()? callback; + final ActionLocation? location; + final Color? color; + ActionItem withProps(ActionLocation location, void Function()? callback) => ActionItem( name: name, diff --git a/lib/src/widgets/avatar.dart b/lib/src/widgets/avatar.dart index d2f49737..679e1ddc 100644 --- a/lib/src/widgets/avatar.dart +++ b/lib/src/widgets/avatar.dart @@ -3,12 +3,6 @@ import 'package:flutter_blurhash/flutter_blurhash.dart'; import 'package:interstellar/src/models/image.dart'; class Avatar extends StatelessWidget { - final ImageModel? image; - final ImageProvider? overrideImageProvider; - final double? radius; - final double? borderRadius; - final Color? backgroundColor; - const Avatar( this.image, { super.key, @@ -18,6 +12,12 @@ class Avatar extends StatelessWidget { this.backgroundColor, }); + final ImageModel? image; + final ImageProvider? overrideImageProvider; + final double? radius; + final double? borderRadius; + final Color? backgroundColor; + @override Widget build(BuildContext context) { return CircleAvatar( diff --git a/lib/src/widgets/ban_dialog.dart b/lib/src/widgets/ban_dialog.dart index 0318df57..91cd5e22 100644 --- a/lib/src/widgets/ban_dialog.dart +++ b/lib/src/widgets/ban_dialog.dart @@ -21,11 +21,11 @@ Future openBanDialog( } class BanDialog extends StatefulWidget { + const BanDialog({required this.user, required this.community, super.key}); + final DetailedUserModel user; final CommunityModel community; - const BanDialog({required this.user, required this.community, super.key}); - @override State createState() => _BanDialogState(); } diff --git a/lib/src/widgets/community_picker.dart b/lib/src/widgets/community_picker.dart index 3b7554b7..25eaf857 100644 --- a/lib/src/widgets/community_picker.dart +++ b/lib/src/widgets/community_picker.dart @@ -10,10 +10,6 @@ import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; class CommunityPicker extends StatefulWidget { - final DetailedCommunityModel? value; - final void Function(DetailedCommunityModel?) onChange; - final bool microblogMode; - const CommunityPicker({ required this.value, required this.onChange, @@ -21,6 +17,10 @@ class CommunityPicker extends StatefulWidget { super.key, }); + final DetailedCommunityModel? value; + final void Function(DetailedCommunityModel?) onChange; + final bool microblogMode; + @override State createState() => _CommunityPickerState(); } @@ -42,18 +42,18 @@ class _CommunityPickerState extends State { hintText: l(context).selectCommunity, prefixIcon: widget.value?.icon == null ? null - : Avatar(widget.value!.icon!, radius: 14), + : Avatar(widget.value!.icon, radius: 14), suffixIcon: widget.value == null ? null : IconButton( onPressed: () => context.router.push( CommunityRoute( communityId: widget.value!.id, - initData: widget.value!, + initData: widget.value, onUpdate: (newValue) => widget.onChange(newValue), ), ), - icon: Icon(Symbols.open_in_new_rounded), + icon: const Icon(Symbols.open_in_new_rounded), ), helperText: widget.microblogMode ? l(context).microblog_communityHelperText @@ -98,9 +98,9 @@ class _CommunityPickerState extends State { optionsViewBuilder: (context, onSelected, options) => Align( alignment: AlignmentDirectional.topStart, child: Material( - elevation: 4.0, + elevation: 4, child: ConstrainedBox( - constraints: BoxConstraints(maxHeight: 250), + constraints: const BoxConstraints(maxHeight: 250), child: ListView.builder( padding: EdgeInsets.zero, shrinkWrap: true, @@ -113,7 +113,7 @@ class _CommunityPickerState extends State { }, child: Builder( builder: (BuildContext context) { - final bool highlight = + final highlight = AutocompleteHighlightedOption.of(context) == index; if (highlight) { SchedulerBinding.instance.addPostFrameCallback(( @@ -130,7 +130,7 @@ class _CommunityPickerState extends State { if (option.icon != null) Padding( padding: const EdgeInsets.only(right: 12), - child: Avatar(option.icon!, radius: 14), + child: Avatar(option.icon, radius: 14), ), Flexible( child: Padding( diff --git a/lib/src/widgets/content_item/action_buttons.dart b/lib/src/widgets/content_item/action_buttons.dart index e94a5749..a8f2e158 100644 --- a/lib/src/widgets/content_item/action_buttons.dart +++ b/lib/src/widgets/content_item/action_buttons.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:interstellar/src/controller/controller.dart'; +import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/emoji_picker/emoji_picker.dart'; import 'package:interstellar/src/widgets/loading_button.dart'; import 'package:material_symbols_icons/symbols.dart'; -import 'package:interstellar/src/utils/utils.dart'; import 'package:provider/provider.dart'; class ActionButtons extends StatelessWidget { @@ -87,7 +87,7 @@ class ActionButtons extends StatelessWidget { childBuilder: (onClick, focusNode) => IconButton( onPressed: onClick, focusNode: focusNode, - icon: Icon(Symbols.add_reaction_rounded), + icon: const Icon(Symbols.add_reaction_rounded), ), onSelect: (emoji) => onEmojiReact!(emoji), ), diff --git a/lib/src/widgets/content_item/content_info.dart b/lib/src/widgets/content_item/content_info.dart index 134a4313..91bd0896 100644 --- a/lib/src/widgets/content_item/content_info.dart +++ b/lib/src/widgets/content_item/content_info.dart @@ -1,8 +1,8 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:interstellar/src/controller/controller.dart'; -import 'package:interstellar/src/controller/router.gr.dart'; import 'package:interstellar/src/controller/database/database.dart'; +import 'package:interstellar/src/controller/router.gr.dart'; import 'package:interstellar/src/models/community.dart'; import 'package:interstellar/src/models/user.dart'; import 'package:interstellar/src/utils/language.dart'; diff --git a/lib/src/widgets/content_item/content_item.dart b/lib/src/widgets/content_item/content_item.dart index bdd5d290..5969f2c6 100644 --- a/lib/src/widgets/content_item/content_item.dart +++ b/lib/src/widgets/content_item/content_item.dart @@ -7,37 +7,106 @@ import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/controller/database/database.dart'; import 'package:interstellar/src/controller/profile.dart'; import 'package:interstellar/src/controller/router.gr.dart'; +import 'package:interstellar/src/models/community.dart'; import 'package:interstellar/src/models/emoji_reaction.dart'; import 'package:interstellar/src/models/image.dart'; import 'package:interstellar/src/models/notification.dart'; import 'package:interstellar/src/models/poll.dart'; import 'package:interstellar/src/models/post.dart'; +import 'package:interstellar/src/models/user.dart'; +import 'package:interstellar/src/utils/language.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/content_item/action_buttons.dart'; -import 'package:interstellar/src/models/user.dart'; -import 'package:interstellar/src/models/community.dart'; import 'package:interstellar/src/widgets/content_item/content_info.dart'; -import 'package:interstellar/src/widgets/content_item/poll.dart'; -import 'package:interstellar/src/widgets/menus/content_menu.dart'; +import 'package:interstellar/src/widgets/content_item/content_item_link_panel.dart'; import 'package:interstellar/src/widgets/content_item/content_reply.dart'; +import 'package:interstellar/src/widgets/content_item/poll.dart'; import 'package:interstellar/src/widgets/content_item/swipe_item.dart'; import 'package:interstellar/src/widgets/image.dart'; import 'package:interstellar/src/widgets/loading_button.dart'; import 'package:interstellar/src/widgets/markdown/drafts_controller.dart'; import 'package:interstellar/src/widgets/markdown/markdown.dart'; import 'package:interstellar/src/widgets/markdown/markdown_editor.dart'; +import 'package:interstellar/src/widgets/menus/content_menu.dart'; import 'package:interstellar/src/widgets/tags/tag_widget.dart'; import 'package:interstellar/src/widgets/video.dart'; import 'package:interstellar/src/widgets/wrapper.dart'; -import 'package:interstellar/src/utils/language.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; -import 'package:interstellar/src/widgets/content_item/content_item_link_panel.dart'; import 'package:simplytranslate/simplytranslate.dart'; enum PostComponent { title, image, info, body, link, flairs } class ContentItem extends StatefulWidget { + const ContentItem({ + required this.originInstance, + required this.id, + required this.contentTypeName, + required this.editDraftResourceId, + required this.replyDraftResourceId, + this.title, + this.image, + this.link, + this.body, + this.translation, + this.lang, + this.onTranslate, + this.createdAt, + this.editedAt, + this.poll, + this.isPreview = false, + this.fullImageSize = false, + this.showCommunityFirst = false, + this.read = false, + this.feedView = true, + this.isPinned = false, + this.isNSFW = false, + this.isOC = false, + this.user, + this.opUserId, + this.community, + this.domain, + this.domainIdOnClick, + this.boosts, + this.isBoosted = false, + this.onBoost, + this.upVotes, + this.isUpVoted = false, + this.onUpVote, + this.downVotes, + this.isDownVoted = false, + this.onDownVote, + this.numComments, + this.onReply, + this.onReport, + this.onEdit, + this.onDelete, + this.onMarkAsRead, + this.updateUser, + this.onModeratePin, + this.onModerateMarkNSFW, + this.onModerateDelete, + this.onModerateBan, + this.filterListWarnings, + this.activeBookmarkLists, + this.loadPossibleBookmarkLists, + this.onAddBookmark, + this.onAddBookmarkToList, + this.onRemoveBookmark, + this.onRemoveBookmarkFromList, + this.notificationControlStatus, + this.onNotificationControlStatusChange, + this.isCompact = false, + this.onClick, + this.onUpdateFlairs, + this.flairs = const [], + this.crossPost, + this.shareLinks = const [], + this.emojiReactions, + this.onEmojiReact, + super.key, + }); + final String originInstance; final int id; @@ -130,75 +199,6 @@ class ContentItem extends StatefulWidget { final List? emojiReactions; final Future Function(String emoji)? onEmojiReact; - const ContentItem({ - required this.originInstance, - required this.id, - this.title, - this.image, - this.link, - this.body, - this.translation, - this.lang, - this.onTranslate, - this.createdAt, - this.editedAt, - this.poll, - this.isPreview = false, - this.fullImageSize = false, - this.showCommunityFirst = false, - this.read = false, - this.feedView = true, - this.isPinned = false, - this.isNSFW = false, - this.isOC = false, - this.user, - this.opUserId, - this.community, - this.domain, - this.domainIdOnClick, - this.boosts, - this.isBoosted = false, - this.onBoost, - this.upVotes, - this.isUpVoted = false, - this.onUpVote, - this.downVotes, - this.isDownVoted = false, - this.onDownVote, - this.numComments, - required this.contentTypeName, - this.onReply, - this.onReport, - this.onEdit, - this.onDelete, - this.onMarkAsRead, - this.updateUser, - this.onModeratePin, - this.onModerateMarkNSFW, - this.onModerateDelete, - this.onModerateBan, - required this.editDraftResourceId, - required this.replyDraftResourceId, - this.filterListWarnings, - this.activeBookmarkLists, - this.loadPossibleBookmarkLists, - this.onAddBookmark, - this.onAddBookmarkToList, - this.onRemoveBookmark, - this.onRemoveBookmarkFromList, - this.notificationControlStatus, - this.onNotificationControlStatusChange, - this.isCompact = false, - this.onClick, - this.onUpdateFlairs, - this.flairs = const [], - this.crossPost, - this.shareLinks = const [], - this.emojiReactions, - this.onEmojiReact, - super.key, - }); - @override State createState() => _ContentItemState(); } @@ -296,7 +296,7 @@ class _ContentItemState extends State { final menuWidget = IconButton( padding: EdgeInsets.zero, - constraints: BoxConstraints(), + constraints: const BoxConstraints(), icon: const Icon(Symbols.more_vert_rounded), onPressed: () { showContentMenu( @@ -374,7 +374,7 @@ class _ContentItemState extends State { ) : null; - List components = []; + var components = []; final order = widget.contentTypeName == l(context).comment ? ProfileRequired.defaultProfile.postComponentOrder : ac.profile.postComponentOrder; @@ -459,7 +459,7 @@ class _ContentItemState extends State { if (widget.onEdit != null && _editTextController != null) { components.add( Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: Column( children: [ MarkdownEditor( @@ -631,16 +631,16 @@ class _ContentItemState extends State { maxLines: 4, overflow: TextOverflow.ellipsis, ), - Divider(), + const Divider(), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text(getLanguageName(context, widget.lang!)), - Icon(Symbols.arrow_right), + const Icon(Symbols.arrow_right), Text(widget.translation!.targetLanguage.name), ], ), - Divider(), + const Divider(), Text( widget.translation!.translations.text, maxLines: 4, @@ -653,16 +653,16 @@ class _ContentItemState extends State { mainAxisSize: MainAxisSize.min, children: [ Markdown(widget.body!, widget.originInstance, nsfw: isNSFW), - Divider(), + const Divider(), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text(getLanguageName(context, widget.lang!)), - Icon(Symbols.arrow_right), + const Icon(Symbols.arrow_right), Text(widget.translation!.targetLanguage.name), ], ), - Divider(), + const Divider(), Markdown( widget.translation!.translations.text, widget.originInstance, @@ -684,11 +684,13 @@ class _ContentItemState extends State { }) { if (widget.image == null) return null; final fullImage = !isThumbnail && widget.fullImageSize; - final double imageSize = compact - ? 96 - : width > 800 - ? 128 - : 64; + final imageSize = + (compact + ? 96 + : width > 800 + ? 128 + : 64) + .toDouble(); final imageOpenTitle = widget.title ?? widget.body ?? ''; final enableBlur = @@ -713,7 +715,7 @@ class _ContentItemState extends State { } Widget compact() { - // TODO: Figure out how to use full existing height of row, instead of fixed value. + // TODO(jwr1): Figure out how to use full existing height of row, instead of fixed value. final imageWidget = getImage(context, isThumbnail: true, compact: true); final ac = context.read(); diff --git a/lib/src/widgets/content_item/content_item_link_panel.dart b/lib/src/widgets/content_item/content_item_link_panel.dart index 399d6045..20ff3118 100644 --- a/lib/src/widgets/content_item/content_item_link_panel.dart +++ b/lib/src/widgets/content_item/content_item_link_panel.dart @@ -40,7 +40,7 @@ const _youtubeAltSources = [ ]; class ContentItemLinkPanel extends StatefulWidget { - const ContentItemLinkPanel({super.key, required this.link}); + const ContentItemLinkPanel({required this.link, super.key}); final Uri link; @@ -92,7 +92,7 @@ class _ContentItemLinkPanelState extends State { controller.open(); } }, - style: TextButton.styleFrom(shape: const LinearBorder()), + style: TextButton.styleFrom(shape: LinearBorder.none), ), ); }, @@ -123,7 +123,7 @@ class _ContentItemLinkPanelState extends State { height: 40, child: InkWell( child: Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: Text.rich( TextSpan( children: [ @@ -136,7 +136,7 @@ class _ContentItemLinkPanelState extends State { ), TextSpan( text: widget.link.toString().substring( - ('${widget.link.scheme}://${widget.link.host}') + '${widget.link.scheme}://${widget.link.host}' .length, ), style: Theme.of(context).textTheme.bodyMedium!.apply( diff --git a/lib/src/widgets/content_item/content_reply.dart b/lib/src/widgets/content_item/content_reply.dart index 123b4567..c056de61 100644 --- a/lib/src/widgets/content_item/content_reply.dart +++ b/lib/src/widgets/content_item/content_reply.dart @@ -3,25 +3,25 @@ import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/controller/server.dart'; -import 'package:interstellar/src/widgets/image_selector.dart'; -import 'package:interstellar/src/widgets/markdown/markdown.dart'; -import 'package:material_symbols_icons/symbols.dart'; -import 'package:provider/provider.dart'; import 'package:interstellar/src/utils/language.dart'; import 'package:interstellar/src/utils/utils.dart'; +import 'package:interstellar/src/widgets/content_item/content_item.dart'; +import 'package:interstellar/src/widgets/image_selector.dart'; import 'package:interstellar/src/widgets/loading_button.dart'; import 'package:interstellar/src/widgets/markdown/drafts_controller.dart'; +import 'package:interstellar/src/widgets/markdown/markdown.dart'; import 'package:interstellar/src/widgets/markdown/markdown_editor.dart'; -import 'package:interstellar/src/widgets/content_item/content_item.dart'; +import 'package:material_symbols_icons/symbols.dart'; +import 'package:provider/provider.dart'; class ContentReply extends StatefulWidget { const ContentReply({ - super.key, - this.inline = true, required this.content, required this.onReply, required this.onComplete, required this.draftResourceId, + super.key, + this.inline = true, }); final bool inline; @@ -33,7 +33,7 @@ class ContentReply extends StatefulWidget { String? alt, }) onReply; - final Function() onComplete; + final void Function() onComplete; final String draftResourceId; @override @@ -64,12 +64,11 @@ class _ContentReplyState extends State { ); return Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: Column( mainAxisSize: MainAxisSize.min, children: [ Flexible( - fit: FlexFit.loose, child: MarkdownEditor( _textController, originInstance: null, @@ -104,7 +103,7 @@ class _ContentReplyState extends State { }); } }, - icon: Icon(Symbols.globe_rounded), + icon: const Icon(Symbols.globe_rounded), tooltip: getLanguageName(context, _replyLanguage), ), const SizedBox(width: 8), @@ -139,12 +138,12 @@ class _ContentReplyState extends State { @RoutePage() class ContentReplyScreen extends StatelessWidget { const ContentReplyScreen({ - super.key, - this.inline = true, required this.content, required this.onReply, required this.onComplete, required this.draftResourceId, + super.key, + this.inline = true, }); final bool inline; @@ -156,7 +155,7 @@ class ContentReplyScreen extends StatelessWidget { String? alt, }) onReply; - final Function() onComplete; + final void Function() onComplete; final String draftResourceId; @override diff --git a/lib/src/widgets/content_item/poll.dart b/lib/src/widgets/content_item/poll.dart index fbc68799..cfa0bd96 100644 --- a/lib/src/widgets/content_item/poll.dart +++ b/lib/src/widgets/content_item/poll.dart @@ -1,15 +1,16 @@ import 'dart:math'; + import 'package:auto_route/auto_route.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/models/poll.dart'; -import 'package:interstellar/src/widgets/wrapper.dart'; import 'package:interstellar/src/utils/utils.dart'; +import 'package:interstellar/src/widgets/wrapper.dart'; import 'package:provider/provider.dart'; class Poll extends StatefulWidget { - const Poll({super.key, required this.poll}); + const Poll({required this.poll, super.key}); final PollModel poll; @@ -58,7 +59,7 @@ class _PollState extends State { Widget build(BuildContext context) { final ac = context.read(); - final int totalVotes = _choices + final totalVotes = _choices .map((choice) => choice.numVotes) .fold(0, (a, b) => a + b); @@ -165,7 +166,7 @@ class _PollState extends State { : [_selectedAnswer!]; if (votes.isEmpty) { - await showDialog( + await showDialog( context: context, builder: (context) => AlertDialog( title: Text(l(context).pollSubmitError), diff --git a/lib/src/widgets/content_item/swipe_item.dart b/lib/src/widgets/content_item/swipe_item.dart index fabb8df3..2c35ee6f 100644 --- a/lib/src/widgets/content_item/swipe_item.dart +++ b/lib/src/widgets/content_item/swipe_item.dart @@ -1,13 +1,12 @@ import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/material.dart'; - +import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/widgets/actions.dart'; import 'package:provider/provider.dart'; -import 'package:interstellar/src/controller/controller.dart'; - class SwipeItem extends StatefulWidget { const SwipeItem({ + required this.child, super.key, this.onUpVote, this.onDownVote, @@ -19,7 +18,6 @@ class SwipeItem extends StatefulWidget { this.onModerateMarkNSFW, this.onModerateDelete, this.onModerateBan, - required this.child, }); final Widget child; @@ -84,9 +82,9 @@ class _SwipeItemState extends State { Widget build(BuildContext context) { final ac = context.watch(); - double actionThreshold = ac.profile.swipeActionThreshold; + final actionThreshold = ac.profile.swipeActionThreshold; - List actions = [ + final actions = [ getSwipeAction(ac.profile.swipeActionLeftShort), getSwipeAction(ac.profile.swipeActionLeftLong), getSwipeAction(ac.profile.swipeActionRightShort), @@ -98,7 +96,7 @@ class _SwipeItemState extends State { final actionSelectorValue = _dismissDirection == DismissDirection.endToStart ? 2 : 0; - for (int i = 0; i < 2; i++) { + for (var i = 0; i < 2; i++) { if (_dismissThreshold > actionThreshold && (_dismissThreshold < (actionThreshold * (i + 2)) || i == 1)) { _dismissThreshold = 0; diff --git a/lib/src/widgets/context_menu.dart b/lib/src/widgets/context_menu.dart index 3f87cd74..9ce86cd6 100644 --- a/lib/src/widgets/context_menu.dart +++ b/lib/src/widgets/context_menu.dart @@ -1,28 +1,19 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:interstellar/src/widgets/loading_button.dart'; +import 'package:interstellar/src/widgets/loading_list_tile.dart'; import 'package:interstellar/src/widgets/open_webpage.dart'; import 'package:material_symbols_icons/symbols.dart'; -import 'package:interstellar/src/widgets/loading_list_tile.dart'; class ContextMenuAction { - final Widget? child; - final IconData? icon; - final Future Function()? onTap; - const ContextMenuAction({this.child, this.icon, this.onTap}); -} -class ContextMenuItem { - final String? title; - final String? subtitle; final Widget? child; final IconData? icon; - final double iconFill; final Future Function()? onTap; - final List? subItems; - final Widget? trailing; +} +class ContextMenuItem { const ContextMenuItem({ this.title, this.subtitle, @@ -34,17 +25,20 @@ class ContextMenuItem { this.trailing, }); + final String? title; + final String? subtitle; + final Widget? child; + final IconData? icon; + final double iconFill; + final Future Function()? onTap; + final List? subItems; + final Widget? trailing; + ContextMenu? get subItemsContextMenu => subItems == null ? null : ContextMenu(title: title, items: subItems!); } class ContextMenu { - final String? title; - final List actions; - final List links; - final List items; - final double actionSpacing; - const ContextMenu({ this.title, this.actions = const [], @@ -53,9 +47,15 @@ class ContextMenu { this.actionSpacing = 12, }); + final String? title; + final List actions; + final List links; + final List items; + final double actionSpacing; + Future openMenu( BuildContext context, - ) async => await showModalBottomSheet( + ) async => showModalBottomSheet( context: context, builder: (BuildContext context) { return Column( @@ -73,7 +73,6 @@ class ContextMenu { Padding( padding: const EdgeInsets.only(top: 12), child: Wrap( - direction: Axis.horizontal, alignment: WrapAlignment.center, crossAxisAlignment: WrapCrossAlignment.center, spacing: actionSpacing, @@ -113,18 +112,14 @@ class ContextMenu { mainAxisSize: MainAxisSize.min, children: [ if (entry.key == 0) - Padding( - padding: const EdgeInsets.only( - right: 4, - ), - child: const Icon(Symbols.home_rounded), + const Padding( + padding: EdgeInsets.only(right: 4), + child: Icon(Symbols.home_rounded), ), if (entry.key == links.length - 1) - Padding( - padding: const EdgeInsets.only( - right: 4, - ), - child: const ImageIcon( + const Padding( + padding: EdgeInsets.only(right: 4), + child: ImageIcon( AssetImage( 'assets/icons/fediverse.png', ), @@ -195,7 +190,9 @@ class ContextMenu { onPressed: () async => item .subItemsContextMenu! .openMenu(context), - icon: Icon(Symbols.arrow_right_rounded), + icon: const Icon( + Symbols.arrow_right_rounded, + ), ) : null), ), diff --git a/lib/src/widgets/display_name.dart b/lib/src/widgets/display_name.dart index 332a1d6f..845973c7 100644 --- a/lib/src/widgets/display_name.dart +++ b/lib/src/widgets/display_name.dart @@ -20,9 +20,9 @@ class DisplayName extends StatelessWidget { @override Widget build(BuildContext context) { - var nameTuple = name.split('@'); - String localName = displayName ?? nameTuple.first; - String? hostName = nameTuple.length > 1 ? nameTuple[1] : null; + final nameTuple = name.split('@'); + final localName = displayName ?? nameTuple.first; + final hostName = nameTuple.length > 1 ? nameTuple[1] : null; return Row( mainAxisSize: MainAxisSize.min, @@ -30,13 +30,13 @@ class DisplayName extends StatelessWidget { if (icon != null) Padding( padding: const EdgeInsets.only(right: 3), - child: Avatar(icon!, radius: 14), + child: Avatar(icon, radius: 14), ), Flexible( child: InkWell( onTap: onTap, child: Padding( - padding: const EdgeInsets.all(3.0), + padding: const EdgeInsets.all(3), child: Text( localName + (context.watch().profile.alwaysShowInstance diff --git a/lib/src/widgets/emoji_picker/emoji_builder.dart b/lib/src/widgets/emoji_picker/emoji_builder.dart index 1de802a9..6dd0ebb4 100644 --- a/lib/src/widgets/emoji_picker/emoji_builder.dart +++ b/lib/src/widgets/emoji_picker/emoji_builder.dart @@ -8,12 +8,12 @@ Builder emojiBuilder(BuilderOptions options) => EmojiBuilder(options.config['sourcePrefix']); class EmojiBuilder implements Builder { - String _sourcePrefix; - EmojiBuilder(this._sourcePrefix); + final String _sourcePrefix; + @override - Future build(BuildStep buildStep) async { + Future build(BuildStep buildStep) async { await buildStep.writeAsString( AssetId( buildStep.inputId.package, @@ -39,9 +39,10 @@ class EmojiBuilder implements Builder { ); final messagesJson = jsonDecode(messagesResponse.body); - final s = StringBuffer(); - - s.write(''' + final s = StringBuffer() + ..write(''' +// dart format off +// ignore_for_file: type=lint // ignore_for_file: prefer_single_quotes import 'package:interstellar/src/utils/trie.dart'; @@ -50,30 +51,31 @@ import "./emoji_class.dart"; '''); - final List emojiGroups = []; + final emojiGroups = []; for (var i = 0; i < (messagesJson['groups'] as List).length; i++) { final group = messagesJson['groups'][i]; - assert(group['order'] == i); + assert(group['order'] == i, 'Emoji group order value should match index'); emojiGroups.add(group['message']); } - s.write('final emojiGroups = '); - s.write(jsonEncode(emojiGroups)); - s.write(';\n'); + s + ..write('final emojiGroups = ') + ..write(jsonEncode(emojiGroups)) + ..write(';\n'); final trie = Trie(); s.write('final emojiList = [\n'); { - int i = 0; - for (var emoji in dataJson) { + var i = 0; + for (final emoji in dataJson) { if (emoji['group'] == null || emoji['order'] == null) continue; - assert(emoji['order'] == i + 1); + assert(emoji['order'] == i + 1, 'Emoji order value should match index'); final tags = [ ...?emoji['tags'], @@ -83,27 +85,28 @@ import "./emoji_class.dart"; ]; trie.addChild(Trie.normalizeTerm(emoji['label']), {i}); - for (var tag in tags) { + for (final tag in tags) { trie.addChild(Trie.normalizeTerm(tag), {i}); } - s.write('Emoji("'); - s.write(emoji['unicode']); - s.write('","'); - s.write(emoji['label']); - s.write('",'); - s.write(emoji['group']); - s.write('),\n'); + s + ..write('const Emoji("') + ..write(emoji['unicode']) + ..write('","') + ..write(emoji['label']) + ..write('",') + ..write(emoji['group']) + ..write('),\n'); i++; } } - s.write('];\n\n'); - - s.write('final Trie emojiTrie = '); - s.write(trie.toString()); - s.write(';\n'); + s + ..write('];\n\n') + ..write('final Trie emojiTrie = ') + ..write(trie.toString()) + ..write(';\n'); return s.toString(); } diff --git a/lib/src/widgets/emoji_picker/emoji_class.dart b/lib/src/widgets/emoji_picker/emoji_class.dart index a4e3f0f4..8170442d 100644 --- a/lib/src/widgets/emoji_picker/emoji_class.dart +++ b/lib/src/widgets/emoji_picker/emoji_class.dart @@ -1,13 +1,13 @@ import 'package:interstellar/src/utils/trie.dart'; -import './emojis.g.dart'; +import 'package:interstellar/src/widgets/emoji_picker/emojis.g.dart'; class Emoji { + const Emoji(this.unicode, this.label, this.group); + final String unicode; final String label; final int group; - - const Emoji(this.unicode, this.label, this.group); } List> searchEmojis(String term) { @@ -17,12 +17,9 @@ List> searchEmojis(String term) { .map((index) => emojiList[index]) .toList(); - final List> results = List.generate( - emojiGroups.length, - (i) => [], - ); + final results = List>.generate(emojiGroups.length, (i) => []); - for (var match in matches) { + for (final match in matches) { results[match.group].add(match); } diff --git a/lib/src/widgets/emoji_picker/emoji_picker.dart b/lib/src/widgets/emoji_picker/emoji_picker.dart index 5a2daa43..4a0c6e51 100644 --- a/lib/src/widgets/emoji_picker/emoji_picker.dart +++ b/lib/src/widgets/emoji_picker/emoji_picker.dart @@ -2,19 +2,18 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; -import 'package:interstellar/src/utils/breakpoints.dart'; -import 'package:interstellar/src/widgets/emoji_picker/emoji_class.dart'; import 'package:interstellar/src/controller/controller.dart'; +import 'package:interstellar/src/utils/breakpoints.dart'; import 'package:interstellar/src/utils/debouncer.dart'; import 'package:interstellar/src/utils/utils.dart'; +import 'package:interstellar/src/widgets/emoji_picker/emoji_class.dart'; +import 'package:interstellar/src/widgets/emoji_picker/emojis.g.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; -import './emojis.g.dart'; +// TODO(jwr1): Allow custom emoji groups (quick access, and server specific emojis) -// TODO: Allow custom emoji groups (quick access, and server specific emojis) - -final emojiGroupIcons = [ +final List emojiGroupIcons = [ Symbols.emoji_emotions_rounded, Symbols.emoji_people_rounded, Symbols.invert_colors_rounded, @@ -29,9 +28,9 @@ final emojiGroupIcons = [ class EmojiPicker extends StatefulWidget { const EmojiPicker({ - super.key, required this.childBuilder, required this.onSelect, + super.key, }); final Widget Function(void Function() onClick, FocusNode focusNode) @@ -67,28 +66,24 @@ class _EmojiPickerState extends State { } void _calculateVisibleEmojiGroups() { - Set newVisibleEmojiGroups = {}; + final newVisibleEmojiGroups = {}; final viewportStart = _scrollController.offset; final viewportEnd = viewportStart + _scrollController.position.viewportDimension; - final List groupPositions = _emojiGroupGlobalKeys - .asMap() - .entries - .map((entry) { - final context = entry.value.currentContext; - if (context == null) return null; + final groupPositions = _emojiGroupGlobalKeys.asMap().entries.map((entry) { + final context = entry.value.currentContext; + if (context == null) return null; - final renderObject = context.findRenderObject(); - if (renderObject == null) return null; - final viewport = RenderAbstractViewport.of(renderObject); + final renderObject = context.findRenderObject(); + if (renderObject == null) return null; + final viewport = RenderAbstractViewport.of(renderObject); - final reveal = viewport.getOffsetToReveal(renderObject, 0.0); + final reveal = viewport.getOffsetToReveal(renderObject, 0); - return reveal.offset; - }) - .toList(); + return reveal.offset; + }).toList(); for (var i = 0; i < emojiGroups.length; i++) { final currPos = groupPositions[i]; @@ -135,7 +130,7 @@ class _EmojiPickerState extends State { final isCompact = Breakpoints.isCompact(context); const double buttonSize = 40; - final int buttonsWide = isCompact + final buttonsWide = isCompact ? (emojiGroups.length / 2).ceil() : emojiGroups.length; @@ -154,7 +149,7 @@ class _EmojiPickerState extends State { ), menuChildren: [ Card( - margin: EdgeInsets.all(8), + margin: const EdgeInsets.all(8), child: SizedBox( width: buttonSize * buttonsWide, child: Column( @@ -164,7 +159,7 @@ class _EmojiPickerState extends State { TextField( controller: _searchController, decoration: InputDecoration( - prefixIcon: Icon(Symbols.search_rounded), + prefixIcon: const Icon(Symbols.search_rounded), suffixIcon: IconButton( onPressed: _searchController.text.isEmpty ? null @@ -172,7 +167,7 @@ class _EmojiPickerState extends State { _searchController.text = ''; _searchEmojis(); }, - icon: Icon(Symbols.close_rounded), + icon: const Icon(Symbols.close_rounded), disabledColor: Theme.of(context).disabledColor, ), border: const OutlineInputBorder(), @@ -180,7 +175,7 @@ class _EmojiPickerState extends State { ), onChanged: (newSearch) => _searchDebounce.run(_searchEmojis), ), - SizedBox(height: 8), + const SizedBox(height: 8), Container( decoration: BoxDecoration( border: Border( @@ -233,7 +228,9 @@ class _EmojiPickerState extends State { padding: const EdgeInsets.only(top: 8, left: 4), child: Text( emojiGroups[groupIndex], - style: TextStyle(fontWeight: FontWeight.bold), + style: const TextStyle( + fontWeight: FontWeight.bold, + ), ), ), ), @@ -253,7 +250,7 @@ class _EmojiPickerState extends State { }, icon: Text( emoji.unicode, - style: TextStyle(fontSize: 24), + style: const TextStyle(fontSize: 24), ), style: buttonStyle, tooltip: emoji.label, diff --git a/lib/src/widgets/error_page.dart b/lib/src/widgets/error_page.dart index 7207cf06..82b27fad 100644 --- a/lib/src/widgets/error_page.dart +++ b/lib/src/widgets/error_page.dart @@ -5,11 +5,11 @@ import 'package:oauth2/oauth2.dart'; import 'package:provider/provider.dart'; class AuthErrorPage extends StatelessWidget { - const AuthErrorPage({super.key, required this.error}); + const AuthErrorPage({required this.error, super.key}); final AuthorizationException error; - void relogin(BuildContext context) async { + Future relogin(BuildContext context) async { final ac = context.read(); final software = ac.serverSoftware; final server = ac.instanceHost; @@ -142,7 +142,7 @@ class _NoItemsFoundIndicatorState extends State { if (_loading) { return Container( alignment: Alignment.center, - child: CircularProgressIndicator(), + child: const CircularProgressIndicator(), ); } else { return InkWell( diff --git a/lib/src/widgets/floating_menu.dart b/lib/src/widgets/floating_menu.dart index b7b34afd..ae09d630 100644 --- a/lib/src/widgets/floating_menu.dart +++ b/lib/src/widgets/floating_menu.dart @@ -1,13 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/widgets/actions.dart'; import 'package:provider/provider.dart'; -import 'package:interstellar/src/controller/controller.dart'; class FloatingMenu extends StatefulWidget { - final ActionItem? tapAction; - final ActionItem? holdAction; - final List menuActions; - const FloatingMenu({ required this.tapAction, required this.holdAction, @@ -15,6 +11,10 @@ class FloatingMenu extends StatefulWidget { super.key, }); + final ActionItem? tapAction; + final ActionItem? holdAction; + final List menuActions; + @override State createState() => FloatingMenuState(); } diff --git a/lib/src/widgets/hide_on_scroll.dart b/lib/src/widgets/hide_on_scroll.dart index 97410722..ef0b3cb6 100644 --- a/lib/src/widgets/hide_on_scroll.dart +++ b/lib/src/widgets/hide_on_scroll.dart @@ -3,11 +3,11 @@ import 'package:flutter/rendering.dart'; class HideOnScroll extends StatefulWidget { const HideOnScroll({ - super.key, required this.controller, required this.hiddenOffset, - this.duration = Duration.zero, required this.child, + super.key, + this.duration = Duration.zero, }); final ScrollController? controller; diff --git a/lib/src/widgets/image.dart b/lib/src/widgets/image.dart index 1d7bdfa8..9925befa 100644 --- a/lib/src/widgets/image.dart +++ b/lib/src/widgets/image.dart @@ -1,5 +1,7 @@ import 'dart:math'; + import 'package:auto_route/auto_route.dart'; +import 'package:extended_image/extended_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_blurhash/flutter_blurhash.dart'; import 'package:interstellar/src/controller/controller.dart'; @@ -9,19 +11,12 @@ import 'package:interstellar/src/utils/share.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/blur.dart'; import 'package:interstellar/src/widgets/loading_button.dart'; -import 'package:interstellar/src/widgets/wrapper.dart'; import 'package:interstellar/src/widgets/super_hero.dart'; +import 'package:interstellar/src/widgets/wrapper.dart'; import 'package:material_symbols_icons/symbols.dart'; -import 'package:extended_image/extended_image.dart'; import 'package:provider/provider.dart'; class AdvancedImage extends StatelessWidget { - final ImageModel image; - final BoxFit fit; - final String? openTitle; - final bool enableBlur; - final String? hero; - const AdvancedImage( this.image, { super.key, @@ -31,9 +26,15 @@ class AdvancedImage extends StatelessWidget { this.hero, }); + final ImageModel image; + final BoxFit fit; + final String? openTitle; + final bool enableBlur; + final String? hero; + static String getHeroTag() { final rng = Random(); - final tagLength = 64; + const tagLength = 64; return String.fromCharCodes( List.generate(tagLength, (index) => rng.nextInt(33) + 89), ); @@ -53,19 +54,14 @@ class AdvancedImage extends StatelessWidget { tag: tag, child: GestureDetector( onTap: () => context.router.push( - AdvancedImageRoute( - image: image, - title: openTitle!, - fit: BoxFit.contain, - hero: tag, - ), + AdvancedImageRoute(image: image, title: openTitle!, hero: tag), ), child: child, ), ), child: Wrapper( shouldWrap: enableBlur, - parentBuilder: (child) => Blur(child), + parentBuilder: Blur.new, child: Stack( alignment: Alignment.center, fit: StackFit.passthrough, @@ -79,7 +75,6 @@ class AdvancedImage extends StatelessWidget { heroBuilderForSlidingPage: (child) { return SuperHero(tag: tag, child: child); }, - cache: true, mode: openTitle != null ? ExtendedImageMode.none : ExtendedImageMode.gesture, @@ -105,7 +100,7 @@ class AdvancedImage extends StatelessWidget { return SizedBox( height: image.blurHashHeight?.toDouble(), width: image.blurHashWidth?.toDouble(), - child: Center(child: CircularProgressIndicator()), + child: const Center(child: CircularProgressIndicator()), ); } } else if (state.extendedImageLoadState == LoadState.failed) { @@ -126,19 +121,19 @@ class AdvancedImage extends StatelessWidget { @RoutePage() class AdvancedImagePage extends StatefulWidget { - final ImageModel image; - final String title; - final String? hero; - final BoxFit fit; - const AdvancedImagePage( this.image, { - super.key, required this.title, + super.key, this.hero, this.fit = BoxFit.contain, }); + final ImageModel image; + final String title; + final String? hero; + final BoxFit fit; + @override State createState() => _AdvancedImagePageState(); } @@ -151,9 +146,7 @@ class _AdvancedImagePageState extends State { @override Widget build(BuildContext context) { - const shadows = [ - Shadow(color: Colors.black, blurRadius: 1.0, offset: Offset(0, 1)), - ]; + const shadows = [Shadow(blurRadius: 1, offset: Offset(0, 1))]; final titleStyle = Theme.of( context, @@ -184,7 +177,7 @@ class _AdvancedImagePageState extends State { ), if (!PlatformIs.linux) LoadingIconButton( - onPressed: () async => await shareFile( + onPressed: () async => shareFile( Uri.parse(widget.image.src), widget.image.src.split('/').last, ), @@ -196,7 +189,6 @@ class _AdvancedImagePageState extends State { children: [ Positioned.fill( child: ExtendedImageSlidePage( - slideAxis: SlideAxis.both, child: SafeArea( child: AdvancedImage( widget.image, @@ -213,7 +205,7 @@ class _AdvancedImagePageState extends State { child: Padding( padding: const EdgeInsets.all(8), child: TextButton( - onPressed: () => showDialog( + onPressed: () => showDialog( context: context, builder: (context) => AlertDialog( title: Text(l(context).altText), @@ -226,7 +218,7 @@ class _AdvancedImagePageState extends State { ], ), ), - child: Text('ALT', style: TextStyle(fontSize: 20)), + child: const Text('ALT', style: TextStyle(fontSize: 20)), ), ), ), diff --git a/lib/src/widgets/image_selector.dart b/lib/src/widgets/image_selector.dart index 2b570523..e43409b2 100644 --- a/lib/src/widgets/image_selector.dart +++ b/lib/src/widgets/image_selector.dart @@ -39,7 +39,7 @@ class _ImageSelectorState extends State { child: IconButton( onPressed: widget.enabled ? () async { - XFile? image = await ImagePicker().pickImage( + final image = await ImagePicker().pickImage( source: ImageSource.gallery, ); if (image != null) { @@ -58,7 +58,7 @@ class _ImageSelectorState extends State { child: IconButton( onPressed: widget.enabled ? () async { - XFile? image = await ImagePicker().pickImage( + final image = await ImagePicker().pickImage( source: ImageSource.camera, ); widget.onSelected(image, _altTextController.text); diff --git a/lib/src/widgets/list_tile_select.dart b/lib/src/widgets/list_tile_select.dart index 8d703e7a..bb84faf9 100644 --- a/lib/src/widgets/list_tile_select.dart +++ b/lib/src/widgets/list_tile_select.dart @@ -3,25 +3,25 @@ import 'package:interstellar/src/widgets/selection_menu.dart'; import 'package:material_symbols_icons/symbols.dart'; class ListTileSelect extends StatelessWidget { - final String title; - final IconData? icon; - final SelectionMenu selectionMenu; - final T value; - final T? oldValue; - final void Function(T newValue) onChange; - final bool enabled; - const ListTileSelect({ - super.key, required this.title, - this.icon, required this.selectionMenu, required this.value, required this.oldValue, required this.onChange, + super.key, + this.icon, this.enabled = true, }); + final String title; + final IconData? icon; + final SelectionMenu selectionMenu; + final T value; + final T? oldValue; + final void Function(T newValue) onChange; + final bool enabled; + @override Widget build(BuildContext context) { final curOption = selectionMenu.getOption(value); diff --git a/lib/src/widgets/list_tile_switch.dart b/lib/src/widgets/list_tile_switch.dart index 8650d046..c5fdbb35 100644 --- a/lib/src/widgets/list_tile_switch.dart +++ b/lib/src/widgets/list_tile_switch.dart @@ -1,13 +1,6 @@ import 'package:flutter/material.dart'; class ListTileSwitch extends StatelessWidget { - final bool value; - final void Function(bool)? onChanged; - final Widget? leading; - final Widget? title; - final Widget? subtitle; - final bool enabled; - const ListTileSwitch({ required this.value, required this.onChanged, @@ -18,6 +11,13 @@ class ListTileSwitch extends StatelessWidget { super.key, }); + final bool value; + final void Function(bool)? onChanged; + final Widget? leading; + final Widget? title; + final Widget? subtitle; + final bool enabled; + @override Widget build(BuildContext context) { return ListTile( diff --git a/lib/src/widgets/loading_button.dart b/lib/src/widgets/loading_button.dart index 3bc5b895..b6f93db6 100644 --- a/lib/src/widgets/loading_button.dart +++ b/lib/src/widgets/loading_button.dart @@ -10,7 +10,7 @@ class _LoadingButtonIndicator extends StatelessWidget { return Container( width: 24, height: 24, - padding: const EdgeInsets.all(2.0), + padding: const EdgeInsets.all(2), child: const CircularProgressIndicator( color: Colors.white, strokeWidth: 3, @@ -20,14 +20,6 @@ class _LoadingButtonIndicator extends StatelessWidget { } class LoadingFilledButton extends StatefulWidget { - final Future Function()? onPressed; - final Widget label; - final Widget? icon; - final ButtonStyle? style; - - /// When true, vibrates `success` type on success, and `warning` type on error - final bool uesHaptics; - const LoadingFilledButton({ required this.onPressed, required this.label, @@ -37,6 +29,14 @@ class LoadingFilledButton extends StatefulWidget { super.key, }); + final Future Function()? onPressed; + final Widget label; + final Widget? icon; + final ButtonStyle? style; + + /// When true, vibrates `success` type on success, and `warning` type on error + final bool uesHaptics; + @override State createState() => _LoadingFilledButtonState(); } @@ -71,11 +71,6 @@ class _LoadingFilledButtonState extends State { } class LoadingTonalButton extends StatefulWidget { - final Future Function()? onPressed; - final Widget label; - final Widget? icon; - final ButtonStyle? style; - const LoadingTonalButton({ required this.onPressed, required this.label, @@ -84,6 +79,11 @@ class LoadingTonalButton extends StatefulWidget { super.key, }); + final Future Function()? onPressed; + final Widget label; + final Widget? icon; + final ButtonStyle? style; + @override State createState() => _LoadingTonalButtonState(); } @@ -114,11 +114,6 @@ class _LoadingTonalButtonState extends State { } class LoadingOutlinedButton extends StatefulWidget { - final Future Function()? onPressed; - final Widget label; - final Widget? icon; - final ButtonStyle? style; - const LoadingOutlinedButton({ required this.onPressed, required this.label, @@ -127,6 +122,11 @@ class LoadingOutlinedButton extends StatefulWidget { super.key, }); + final Future Function()? onPressed; + final Widget label; + final Widget? icon; + final ButtonStyle? style; + @override State createState() => _LoadingOutlinedButtonState(); } @@ -157,11 +157,6 @@ class _LoadingOutlinedButtonState extends State { } class LoadingTextButton extends StatefulWidget { - final Future Function()? onPressed; - final Widget label; - final Widget? icon; - final ButtonStyle? style; - const LoadingTextButton({ required this.onPressed, required this.label, @@ -170,6 +165,11 @@ class LoadingTextButton extends StatefulWidget { super.key, }); + final Future Function()? onPressed; + final Widget label; + final Widget? icon; + final ButtonStyle? style; + @override State createState() => _LoadingTextButtonState(); } @@ -200,21 +200,21 @@ class _LoadingTextButtonState extends State { } class LoadingIconButton extends StatefulWidget { - final Future Function()? onPressed; - final Future Function()? onLongPress; - final Widget icon; - final ButtonStyle? style; - final String? tooltip; - const LoadingIconButton({ required this.onPressed, - this.onLongPress, required this.icon, + this.onLongPress, this.style, this.tooltip, super.key, }); + final Future Function()? onPressed; + final Future Function()? onLongPress; + final Widget icon; + final ButtonStyle? style; + final String? tooltip; + @override State createState() => _LoadingIconButtonState(); } @@ -257,14 +257,6 @@ class _LoadingIconButtonState extends State { } class LoadingFilterChip extends StatefulWidget { - final Widget? icon; - final Widget label; - final bool selected; - final Future Function(bool)? onSelected; - final bool enabled; - - final String? tooltip; - const LoadingFilterChip({ required this.label, required this.selected, @@ -275,6 +267,14 @@ class LoadingFilterChip extends StatefulWidget { super.key, }); + final Widget? icon; + final Widget label; + final bool selected; + final Future Function(bool)? onSelected; + final bool enabled; + + final String? tooltip; + @override State createState() => _LoadingFilterChipState(); } @@ -307,14 +307,6 @@ class _LoadingFilterChipState extends State { } class LoadingInputChip extends StatefulWidget { - final Widget? icon; - final Widget label; - final bool selected; - final Future Function(bool)? onSelected; - final bool enabled; - - final String? tooltip; - const LoadingInputChip({ required this.label, required this.selected, @@ -325,6 +317,14 @@ class LoadingInputChip extends StatefulWidget { super.key, }); + final Widget? icon; + final Widget label; + final bool selected; + final Future Function(bool)? onSelected; + final bool enabled; + + final String? tooltip; + @override State createState() => _LoadingInputChipState(); } diff --git a/lib/src/widgets/loading_list_tile.dart b/lib/src/widgets/loading_list_tile.dart index 2275b0e4..298bddcf 100644 --- a/lib/src/widgets/loading_list_tile.dart +++ b/lib/src/widgets/loading_list_tile.dart @@ -8,7 +8,7 @@ class _LoadingTileIndicator extends StatelessWidget { return Container( width: 24, height: 24, - padding: const EdgeInsets.all(2.0), + padding: const EdgeInsets.all(2), child: const CircularProgressIndicator( color: Colors.white, strokeWidth: 3, @@ -18,13 +18,6 @@ class _LoadingTileIndicator extends StatelessWidget { } class LoadingListTile extends StatefulWidget { - final Future Function()? onTap; - final Widget? leading; - final Widget? title; - final Widget? subtitle; - final Widget? trailing; - final bool enabled; - const LoadingListTile({ required this.onTap, this.leading, @@ -35,6 +28,13 @@ class LoadingListTile extends StatefulWidget { super.key, }); + final Future Function()? onTap; + final Widget? leading; + final Widget? title; + final Widget? subtitle; + final Widget? trailing; + final bool enabled; + @override State createState() => _LoadingListTileState(); } @@ -60,7 +60,7 @@ class _LoadingListTileState extends State { if (mounted) setState(() => _isLoading = false); } }, - trailing: _isLoading ? _LoadingTileIndicator() : widget.trailing, + trailing: _isLoading ? const _LoadingTileIndicator() : widget.trailing, enabled: widget.enabled, ); } diff --git a/lib/src/widgets/loading_template.dart b/lib/src/widgets/loading_template.dart index 1dd9f65a..1de7699b 100644 --- a/lib/src/widgets/loading_template.dart +++ b/lib/src/widgets/loading_template.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; class LoadingTemplate extends StatelessWidget { - final Widget? title; - const LoadingTemplate({this.title, super.key}); + final Widget? title; + @override Widget build(BuildContext context) { return Scaffold( diff --git a/lib/src/widgets/markdown/drafts_controller.dart b/lib/src/widgets/markdown/drafts_controller.dart index fcf2c2c1..a13071e7 100644 --- a/lib/src/widgets/markdown/drafts_controller.dart +++ b/lib/src/widgets/markdown/drafts_controller.dart @@ -1,27 +1,27 @@ +import 'package:drift/drift.dart'; import 'package:flutter/material.dart'; import 'package:interstellar/src/controller/database/database.dart'; -import 'package:drift/drift.dart'; class DraftAutoController { - final Draft? Function() read; - final Future Function(String body) save; - final Future Function() discard; - const DraftAutoController({ required this.read, required this.save, required this.discard, }); + + final Draft? Function() read; + final Future Function(String body) save; + final Future Function() discard; } class DraftsController with ChangeNotifier { - List _drafts = []; - List get drafts => _drafts; - DraftsController() { _init(); } + List _drafts = []; + List get drafts => _drafts; + Future _init() async { _drafts = await database.select(database.drafts).get(); @@ -31,7 +31,7 @@ class DraftsController with ChangeNotifier { DraftAutoController auto(String resourceId) { return DraftAutoController( read: () { - for (var draft in _drafts) { + for (final draft in _drafts) { if (draft.resourceId == resourceId) return draft; } @@ -63,7 +63,7 @@ class DraftsController with ChangeNotifier { } Draft? readByDate(DateTime at) { - for (var draft in _drafts) { + for (final draft in _drafts) { if (draft.at == at) return draft; } diff --git a/lib/src/widgets/markdown/markdown.dart b/lib/src/widgets/markdown/markdown.dart index c57e08d5..9a0e638a 100644 --- a/lib/src/widgets/markdown/markdown.dart +++ b/lib/src/widgets/markdown/markdown.dart @@ -3,20 +3,14 @@ import 'package:flutter_markdown/flutter_markdown.dart' as mdf; import 'package:interstellar/src/models/image.dart'; import 'package:interstellar/src/widgets/image.dart'; import 'package:interstellar/src/widgets/markdown/markdown_config_share.dart'; +import 'package:interstellar/src/widgets/markdown/markdown_mention.dart'; +import 'package:interstellar/src/widgets/markdown/markdown_spoiler.dart'; +import 'package:interstellar/src/widgets/markdown/markdown_subscript_superscript.dart'; +import 'package:interstellar/src/widgets/markdown/markdown_video.dart'; import 'package:interstellar/src/widgets/open_webpage.dart'; import 'package:interstellar/src/widgets/video.dart'; -import './markdown_mention.dart'; -import './markdown_spoiler.dart'; -import './markdown_subscript_superscript.dart'; -import './markdown_video.dart'; - class Markdown extends StatelessWidget { - final String data; - final String originInstance; - final ThemeData? themeData; - final bool nsfw; - const Markdown( this.data, this.originInstance, { @@ -25,6 +19,11 @@ class Markdown extends StatelessWidget { super.key, }); + final String data; + final String originInstance; + final ThemeData? themeData; + final bool nsfw; + @override Widget build(BuildContext context) { return mdf.MarkdownBody( @@ -36,7 +35,7 @@ class Markdown extends StatelessWidget { mdf.MarkdownStyleSheet( blockquoteDecoration: BoxDecoration( color: Colors.blue.shade500.withAlpha(50), - borderRadius: BorderRadius.circular(2.0), + borderRadius: BorderRadius.circular(2), ), ), ), diff --git a/lib/src/widgets/markdown/markdown_config_share.dart b/lib/src/widgets/markdown/markdown_config_share.dart index 29ed196b..51bbff7e 100644 --- a/lib/src/widgets/markdown/markdown_config_share.dart +++ b/lib/src/widgets/markdown/markdown_config_share.dart @@ -4,10 +4,10 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart' as mdf; import 'package:interstellar/src/controller/controller.dart'; +import 'package:interstellar/src/controller/feed.dart'; import 'package:interstellar/src/controller/filter_list.dart'; -import 'package:interstellar/src/controller/router.gr.dart'; import 'package:interstellar/src/controller/profile.dart'; -import 'package:interstellar/src/controller/feed.dart'; +import 'package:interstellar/src/controller/router.gr.dart'; import 'package:interstellar/src/models/config_share.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/loading_button.dart'; @@ -19,13 +19,13 @@ class ConfigShareMarkdownSyntax extends md.BlockSyntax { @override RegExp get pattern => RegExp(r'^```interstellar$'); - final String endString = r'```'; + final String endString = '```'; @override md.Node parse(md.BlockParser parser) { parser.advance(); - final List body = []; + final body = []; while (!parser.isDone) { if (parser.current.content == endString) { @@ -55,9 +55,9 @@ class ConfigShareMarkdownBuilder extends mdf.MarkdownElementBuilder { } class ConfigShareWidget extends StatefulWidget { - final String text; + const ConfigShareWidget({required this.text, super.key}); - const ConfigShareWidget({super.key, required this.text}); + final String text; @override State createState() => _ConfigShareWidgetState(); @@ -90,13 +90,11 @@ class _ConfigShareWidgetState extends State { ...config.payload, 'name': config.name, }); - break; case ConfigShareType.filterList: configFilterList = FilterList.fromJson({ ...config.payload, 'name': config.name, }); - break; case ConfigShareType.feed: configFeed = Feed.fromJson(config.payload); } @@ -166,7 +164,7 @@ class _ConfigShareWidgetState extends State { EditProfileRoute( profile: config.name, profileList: profileList, - importProfile: configProfile!, + importProfile: configProfile, ), ); }, @@ -174,7 +172,7 @@ class _ConfigShareWidgetState extends State { () async => context.router.push( EditFilterListRoute( filterList: config.name, - importFilterList: configFilterList!, + importFilterList: configFilterList, ), ), ConfigShareType.feed => () async => context.router.push( diff --git a/lib/src/widgets/markdown/markdown_editor.dart b/lib/src/widgets/markdown/markdown_editor.dart index 650cbaab..d16c42f1 100644 --- a/lib/src/widgets/markdown/markdown_editor.dart +++ b/lib/src/widgets/markdown/markdown_editor.dart @@ -1,33 +1,24 @@ import 'dart:convert'; + import 'package:auto_route/auto_route.dart'; import 'package:expandable/expandable.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:image_picker/image_picker.dart'; -import 'package:interstellar/src/widgets/emoji_picker/emoji_picker.dart'; import 'package:interstellar/src/controller/controller.dart'; +import 'package:interstellar/src/controller/database/database.dart'; import 'package:interstellar/src/models/config_share.dart'; import 'package:interstellar/src/utils/debouncer.dart'; import 'package:interstellar/src/utils/utils.dart'; +import 'package:interstellar/src/widgets/emoji_picker/emoji_picker.dart'; import 'package:interstellar/src/widgets/loading_button.dart'; import 'package:interstellar/src/widgets/markdown/drafts_controller.dart'; +import 'package:interstellar/src/widgets/markdown/markdown.dart'; import 'package:interstellar/src/widgets/wrapper.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; -import 'package:interstellar/src/controller/database/database.dart'; -import './markdown.dart'; class MarkdownEditor extends StatefulWidget { - final TextEditingController controller; - final String? originInstance; - final DraftAutoController draftController; - final void Function(String)? onChanged; - final bool? enabled; - final String? label; - final bool? draftDisableAutoLoad; - final bool autoFocus; - final bool inline; - const MarkdownEditor( this.controller, { required this.originInstance, @@ -41,6 +32,16 @@ class MarkdownEditor extends StatefulWidget { super.key, }); + final TextEditingController controller; + final String? originInstance; + final DraftAutoController draftController; + final void Function(String)? onChanged; + final bool? enabled; + final String? label; + final bool? draftDisableAutoLoad; + final bool autoFocus; + final bool inline; + @override State createState() => _MarkdownEditorState(); } @@ -187,7 +188,7 @@ class _MarkdownEditorState extends State { ? '' : ' (${readableShortcut(action.shortcut!)})'), style: TextButton.styleFrom( - shape: const LinearBorder(), + shape: LinearBorder.none, ), ), ), @@ -219,7 +220,7 @@ class _MarkdownEditorState extends State { .add_reaction_rounded, ), style: TextButton.styleFrom( - shape: const LinearBorder(), + shape: LinearBorder.none, ), ), onSelect: (emoji) { @@ -272,7 +273,7 @@ class _MarkdownEditorState extends State { Symbols.share_rounded, ), style: TextButton.styleFrom( - shape: const LinearBorder(), + shape: LinearBorder.none, ), tooltip: l(context).configShare, ), @@ -353,7 +354,6 @@ class _MarkdownEditorState extends State { ], ), Flexible( - fit: FlexFit.loose, child: Builder( builder: (context) { return ListView( @@ -410,7 +410,7 @@ class _MarkdownEditorState extends State { bindings: { const SingleActivator(LogicalKeyboardKey.enter): () => execAction(const _MarkdownEditorActionEnter()), - for (var action in _actions( + for (final action in _actions( context, ).where((action) => action.shortcut != null)) action.shortcut!: () => execAction(action.action), @@ -494,7 +494,7 @@ List<_MarkdownEditorActionInfo> _actions(BuildContext context) => [ showDivider: true, ), _MarkdownEditorActionInfo( - action: const _MarkdownEditorActionLink(), + action: const _MarkdownEditorActionLink(isImage: false), icon: Symbols.link_rounded, tooltip: l(context).markdownEditor_link, shortcut: const SingleActivator(LogicalKeyboardKey.keyK, control: true), @@ -578,12 +578,6 @@ List<_MarkdownEditorActionInfo> _actions(BuildContext context) => [ ]; class _MarkdownEditorActionInfo { - final _MarkdownEditorActionBase action; - final IconData icon; - final String tooltip; - final SingleActivator? shortcut; - final bool showDivider; - const _MarkdownEditorActionInfo({ required this.action, required this.icon, @@ -591,6 +585,12 @@ class _MarkdownEditorActionInfo { this.shortcut, this.showDivider = false, }); + + final _MarkdownEditorActionBase action; + final IconData icon; + final String tooltip; + final SingleActivator? shortcut; + final bool showDivider; } abstract class _MarkdownEditorActionBase { @@ -618,23 +618,23 @@ abstract class _MarkdownEditorActionBase { } class _MarkdownEditorData { - String text; - int selectionStart; - int selectionEnd; - String selectionText; - _MarkdownEditorData({ required this.text, required this.selectionStart, required this.selectionEnd, }) : selectionText = text.substring(selectionStart, selectionEnd); + + String text; + int selectionStart; + int selectionEnd; + String selectionText; } class _MarkdownEditorActionReplace extends _MarkdownEditorActionBase { - final String chars; - const _MarkdownEditorActionReplace(this.chars); + final String chars; + @override Future<_MarkdownEditorData> run(_MarkdownEditorData input) async { var text = input.text; @@ -652,12 +652,12 @@ class _MarkdownEditorActionReplace extends _MarkdownEditorActionBase { } class _MarkdownEditorActionInline extends _MarkdownEditorActionBase { - final String startChars; - final String endChars; - const _MarkdownEditorActionInline(this.startChars, [String? endChars]) : endChars = endChars ?? startChars; + final String startChars; + final String endChars; + @override Future<_MarkdownEditorData> run(_MarkdownEditorData input) async { var text = input.text; @@ -696,16 +696,16 @@ class _MarkdownEditorActionInline extends _MarkdownEditorActionBase { } class _MarkdownEditorActionBlock extends _MarkdownEditorActionBase { + const _MarkdownEditorActionBlock(this.startChars, [this.endChars = '']); + final String startChars; final String endChars; - const _MarkdownEditorActionBlock(this.startChars, [this.endChars = '']); - @override Future<_MarkdownEditorData> run(_MarkdownEditorData input) async { var text = input.text; - _MarkdownEditorData line = getCurrentLine(input); + final line = getCurrentLine(input); if (line.selectionText.startsWith(startChars) && line.selectionText.endsWith(endChars)) { @@ -741,9 +741,9 @@ class _MarkdownEditorActionBlock extends _MarkdownEditorActionBase { } class _MarkdownEditorActionLink extends _MarkdownEditorActionBase { - final bool isImage; + const _MarkdownEditorActionLink({required this.isImage}); - const _MarkdownEditorActionLink({this.isImage = false}); + final bool isImage; @override Future<_MarkdownEditorData> run(_MarkdownEditorData input) async { @@ -802,22 +802,22 @@ class _MarkdownEditorActionLink extends _MarkdownEditorActionBase { } class _MarkdownEditorActionImage extends _MarkdownEditorActionBase { - final BuildContext context; - const _MarkdownEditorActionImage({required this.context}); + final BuildContext context; + @override Future<_MarkdownEditorData> run(_MarkdownEditorData input) async { - final imageStartChar = '!'; + const imageStartChar = '!'; - final helpMessage = 'altr'; + const helpMessage = 'altr'; var text = input.text; if (input.selectionText.isEmpty) { - XFile? image = await ImagePicker().pickImage(source: ImageSource.gallery); + final image = await ImagePicker().pickImage(source: ImageSource.gallery); - String url = ''; + var url = ''; if (image != null && context.mounted) { url = await context.read().api.images.uploadImage( store: context.read().profile.defaultImageStore, @@ -873,15 +873,15 @@ class _MarkdownEditorActionImage extends _MarkdownEditorActionBase { } class _MarkdownEditorActionInsertSection extends _MarkdownEditorActionBase { - final String sectionText; - const _MarkdownEditorActionInsertSection(this.sectionText); + final String sectionText; + @override Future<_MarkdownEditorData> run(_MarkdownEditorData input) async { var text = input.text; - int prefixNewlines = 2; + var prefixNewlines = 2; if (input.selectionStart - 1 >= 0 && text[input.selectionStart - 1] == '\n') { prefixNewlines--; @@ -892,7 +892,7 @@ class _MarkdownEditorActionInsertSection extends _MarkdownEditorActionBase { } } - int suffixNewlines = 2; + var suffixNewlines = 2; if (input.selectionStart < text.length && text[input.selectionStart] == '\n') { suffixNewlines--; @@ -925,7 +925,7 @@ class _MarkdownEditorActionEnter extends _MarkdownEditorActionBase { @override Future<_MarkdownEditorData> run(_MarkdownEditorData input) async { - var text = input.text; + final text = input.text; final line = getCurrentLine(input); @@ -1003,16 +1003,16 @@ class _MarkdownEditorActionEnter extends _MarkdownEditorActionBase { } class _MarkdownEditorDraftItem extends StatefulWidget { - final Draft draft; - final void Function() onApply; - final String originInstance; - const _MarkdownEditorDraftItem({ required this.draft, required this.onApply, required this.originInstance, }); + final Draft draft; + final void Function() onApply; + final String originInstance; + @override State<_MarkdownEditorDraftItem> createState() => __MarkdownEditorDraftItemState(); @@ -1025,7 +1025,7 @@ class __MarkdownEditorDraftItemState extends State<_MarkdownEditorDraftItem> { @override Widget build(BuildContext context) { - discardDraft() { + void discardDraft() { context.read().removeByDate(widget.draft.at); } @@ -1053,7 +1053,7 @@ class __MarkdownEditorDraftItemState extends State<_MarkdownEditorDraftItem> { children: [ IconButton( onPressed: () { - showDialog( + showDialog( context: context, builder: (context) { return AlertDialog( @@ -1145,7 +1145,7 @@ class _MarkdownEditorConfigShareDialogState loadNames(); } - void loadNames() async { + Future loadNames() async { final ac = context.read(); _profiles = await ac.getProfileNames(); _filterLists = ac.filterLists.keys.toList(); diff --git a/lib/src/widgets/markdown/markdown_mention.dart b/lib/src/widgets/markdown/markdown_mention.dart index 711d0b34..43ee0516 100644 --- a/lib/src/widgets/markdown/markdown_mention.dart +++ b/lib/src/widgets/markdown/markdown_mention.dart @@ -3,14 +3,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart' as mdf; import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/controller/router.gr.dart'; -import 'package:interstellar/src/models/image.dart'; import 'package:interstellar/src/models/community.dart'; +import 'package:interstellar/src/models/image.dart'; import 'package:interstellar/src/models/user.dart'; import 'package:interstellar/src/widgets/avatar.dart'; import 'package:markdown/markdown.dart' as md; import 'package:provider/provider.dart'; class MentionMarkdownSyntax extends md.InlineSyntax { + MentionMarkdownSyntax() : super(_mentionPattern); + /* Should match the following patterns: @@ -46,16 +48,10 @@ class MentionMarkdownSyntax extends md.InlineSyntax { */ static const String _mdLinkPattern = r'\[.*?\]\(\s*' + _mentionPattern + r'(?:\s*".*?")?\s*\)'; - static final _mdLinkPatternRegExp = RegExp( - _mdLinkPattern, - multiLine: true, - caseSensitive: true, - ); + static final _mdLinkPatternRegExp = RegExp(_mdLinkPattern, multiLine: true); static final _borderRegExp = RegExp(r'[^a-z0-9@/\\]', caseSensitive: false); - MentionMarkdownSyntax() : super(_mentionPattern); - @override bool tryMatch(md.InlineParser parser, [int? startMatchPos]) { startMatchPos ??= parser.pos; @@ -107,10 +103,10 @@ class MentionMarkdownSyntax extends md.InlineSyntax { } class MentionMarkdownBuilder extends mdf.MarkdownElementBuilder { - final String originInstance; - MentionMarkdownBuilder({required this.originInstance}); + final String originInstance; + @override Widget? visitElementAfter(md.Element element, TextStyle? preferredStyle) { return RichText( @@ -127,11 +123,11 @@ class MentionMarkdownBuilder extends mdf.MarkdownElementBuilder { } class MentionWidget extends StatefulWidget { + const MentionWidget(this.name, this.originInstance, {super.key}); + final String name; final String originInstance; - const MentionWidget(this.name, this.originInstance, {super.key}); - @override State createState() => MentionWidgetState(); } @@ -151,7 +147,7 @@ class MentionWidgetState extends State { _fetchData(); } - void _fetchData() async { + Future _fetchData() async { final modifier = widget.name[0]; final split = widget.name.substring(1).split('@'); final name = split[0]; @@ -214,7 +210,7 @@ class MentionWidgetState extends State { Widget build(BuildContext context) { return InputChip( label: Text(_displayName), - avatar: _icon != null ? Avatar(_icon!) : null, + avatar: _icon != null ? Avatar(_icon) : null, onPressed: _onClick, visualDensity: const VisualDensity( vertical: VisualDensity.minimumDensity, diff --git a/lib/src/widgets/markdown/markdown_spoiler.dart b/lib/src/widgets/markdown/markdown_spoiler.dart index 83d52640..13290dfe 100644 --- a/lib/src/widgets/markdown/markdown_spoiler.dart +++ b/lib/src/widgets/markdown/markdown_spoiler.dart @@ -2,11 +2,10 @@ import 'package:expandable/expandable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart' as mdf; import 'package:interstellar/src/utils/utils.dart'; +import 'package:interstellar/src/widgets/markdown/markdown.dart'; import 'package:markdown/markdown.dart' as md; import 'package:material_symbols_icons/symbols.dart'; -import './markdown.dart'; - class SpoilerMarkdownSyntax extends md.BlockSyntax { @override RegExp get pattern => RegExp(r'^\s{0,3}:{3,}\s*spoiler\s+(\S.*)$'); @@ -16,11 +15,11 @@ class SpoilerMarkdownSyntax extends md.BlockSyntax { @override md.Node parse(md.BlockParser parser) { final Match? match = pattern.firstMatch(parser.current.content); - final String? title = match?.group(1)?.trim(); + final title = match?.group(1)?.trim(); parser.advance(); - final List body = []; + final body = []; while (!parser.isDone) { if (endPattern.hasMatch(parser.current.content)) { @@ -41,13 +40,13 @@ class SpoilerMarkdownSyntax extends md.BlockSyntax { } class SpoilerMarkdownBuilder extends mdf.MarkdownElementBuilder { - final String originInstance; - SpoilerMarkdownBuilder({required this.originInstance}); + final String originInstance; + @override Widget? visitElementAfter(md.Element element, TextStyle? preferredStyle) { - String text = element.textContent; + final text = element.textContent; final splitIndex = text.indexOf('\n'); final title = text.substring(0, splitIndex).trim(); final body = text.substring(splitIndex + 1).trim(); @@ -61,18 +60,18 @@ class SpoilerMarkdownBuilder extends mdf.MarkdownElementBuilder { } class SpoilerWidget extends StatefulWidget { - final String originInstance; - - final String? title; - final String? body; - const SpoilerWidget({ - super.key, required this.originInstance, + super.key, this.title, this.body, }); + final String originInstance; + + final String? title; + final String? body; + @override State createState() => _SpoilerWidgetState(); } diff --git a/lib/src/widgets/markdown/markdown_subscript_superscript.dart b/lib/src/widgets/markdown/markdown_subscript_superscript.dart index b1733ee6..b58b4fa7 100644 --- a/lib/src/widgets/markdown/markdown_subscript_superscript.dart +++ b/lib/src/widgets/markdown/markdown_subscript_superscript.dart @@ -25,7 +25,7 @@ class SuperscriptMarkdownSyntax extends md.InlineSyntax { class SubscriptMarkdownBuilder extends mdf.MarkdownElementBuilder { @override Widget visitElementAfter(md.Element element, TextStyle? preferredStyle) { - final String textContent = element.textContent; + final textContent = element.textContent; return SubscriptSuperscriptWidget(text: textContent, isSuperscript: false); } @@ -34,22 +34,22 @@ class SubscriptMarkdownBuilder extends mdf.MarkdownElementBuilder { class SuperscriptMarkdownBuilder extends mdf.MarkdownElementBuilder { @override Widget visitElementAfter(md.Element element, TextStyle? preferredStyle) { - final String textContent = element.textContent; + final textContent = element.textContent; return SubscriptSuperscriptWidget(text: textContent, isSuperscript: true); } } class SubscriptSuperscriptWidget extends StatelessWidget { - final String text; - final bool isSuperscript; - const SubscriptSuperscriptWidget({ - super.key, required this.text, required this.isSuperscript, + super.key, }); + final String text; + final bool isSuperscript; + @override Widget build(BuildContext context) { return RichText( @@ -57,7 +57,7 @@ class SubscriptSuperscriptWidget extends StatelessWidget { children: [ WidgetSpan( child: Transform.translate( - offset: Offset(0.0, isSuperscript ? -5.0 : 3.0), + offset: Offset(0, isSuperscript ? -5.0 : 3.0), child: Text( text, style: Theme.of( diff --git a/lib/src/widgets/markdown/markdown_video.dart b/lib/src/widgets/markdown/markdown_video.dart index d6255365..f19f5316 100644 --- a/lib/src/widgets/markdown/markdown_video.dart +++ b/lib/src/widgets/markdown/markdown_video.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart' as mdf; -import 'package:markdown/markdown.dart' as md; import 'package:interstellar/src/widgets/video.dart'; +import 'package:markdown/markdown.dart' as md; class VideoMarkdownSyntax extends md.InlineSyntax { VideoMarkdownSyntax() : super(r'!\[video\/mp4\]\((https:\/\/[^\s]+\.mp4)\)'); @@ -14,6 +14,8 @@ class VideoMarkdownSyntax extends md.InlineSyntax { } class YoutubeEmbedSyntax extends md.InlineSyntax { + YoutubeEmbedSyntax() : super(_youtubePattern); + //from here https://stackoverflow.com/a/61033353 static const _youtubePattern = r'(?:https?:\/\/)?(?:www\.)?youtu(?:\.be\/|be.com\/\S*(?:watch|embed)(?:(?:(?=\/[-a-zA-Z0-9_]{11,}(?!\S))\/)|(?:\S*v=|v\/)))([-a-zA-Z0-9_]{11,})'; @@ -21,15 +23,9 @@ class YoutubeEmbedSyntax extends md.InlineSyntax { static const String _mdLinkPattern = r'\[(.*?)\]\(\s*' + _youtubePattern + r'(?:\s*".*?")?\s*\)'; - static final _mdLinkPatternRegExp = RegExp( - _mdLinkPattern, - multiLine: true, - caseSensitive: true, - ); + static final _mdLinkPatternRegExp = RegExp(_mdLinkPattern, multiLine: true); static final _borderRegExp = RegExp(r'[^a-z0-9@/\\]', caseSensitive: false); - YoutubeEmbedSyntax() : super(_youtubePattern); - bool _isMarkdownLink = false; @override @@ -37,7 +33,7 @@ class YoutubeEmbedSyntax extends md.InlineSyntax { startMatchPos ??= parser.pos; _isMarkdownLink = String.fromCharCode(parser.charAt(parser.pos)) == '['; - bool isAutoLink = String.fromCharCode(parser.charAt(parser.pos)) == '<'; + final isAutoLink = String.fromCharCode(parser.charAt(parser.pos)) == '<'; if (isAutoLink) { startMatchPos += 1; } @@ -78,20 +74,22 @@ class YoutubeEmbedSyntax extends md.InlineSyntax { final anchor = md.Element.text('a', match[_isMarkdownLink ? 1 : 0]!); anchor.attributes['href'] = link; - parser.addNode(anchor); + parser + ..addNode(anchor) + ..addNode(md.Element.text('video', link)); - parser.addNode(md.Element.text('video', link)); return true; } } class VideoMarkdownBuilder extends mdf.MarkdownElementBuilder { - final bool enableBlur; VideoMarkdownBuilder({this.enableBlur = false}); + final bool enableBlur; + @override Widget visitElementAfter(md.Element element, TextStyle? preferredStyle) { - var textContent = element.textContent; + final textContent = element.textContent; return VideoPlayer(Uri.parse(textContent), enableBlur: enableBlur); } diff --git a/lib/src/widgets/menus/community_menu.dart b/lib/src/widgets/menus/community_menu.dart index da939480..4719088e 100644 --- a/lib/src/widgets/menus/community_menu.dart +++ b/lib/src/widgets/menus/community_menu.dart @@ -2,18 +2,18 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:interstellar/src/api/feed_source.dart'; import 'package:interstellar/src/controller/controller.dart'; -import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/controller/router.gr.dart'; +import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/models/community.dart'; import 'package:interstellar/src/screens/explore/explore_screen.dart'; import 'package:interstellar/src/screens/explore/user_item.dart'; -import 'package:interstellar/src/utils/ap_urls.dart'; -import 'package:interstellar/src/widgets/subscription_button.dart'; -import 'package:interstellar/src/widgets/star_button.dart'; -import 'package:interstellar/src/widgets/loading_button.dart'; -import 'package:interstellar/src/widgets/context_menu.dart'; import 'package:interstellar/src/screens/settings/feed_settings_screen.dart'; +import 'package:interstellar/src/utils/ap_urls.dart'; import 'package:interstellar/src/utils/utils.dart'; +import 'package:interstellar/src/widgets/context_menu.dart'; +import 'package:interstellar/src/widgets/loading_button.dart'; +import 'package:interstellar/src/widgets/star_button.dart'; +import 'package:interstellar/src/widgets/subscription_button.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; @@ -21,7 +21,7 @@ Future showCommunityMenu( BuildContext context, { DetailedCommunityModel? detailedCommunity, CommunityModel? community, - Function(DetailedCommunityModel)? update, + void Function(DetailedCommunityModel)? update, bool navigateOption = false, }) async { final ac = context.read(); @@ -29,13 +29,13 @@ Future showCommunityMenu( if (detailedCommunity == null && community == null) return; final name = detailedCommunity?.name ?? community!.name; - final globalName = (name).contains('@') + final globalName = name.contains('@') ? '!$name' : '!$name@${ac.instanceHost}'; - final isModerator = detailedCommunity == null - ? false - : detailedCommunity.moderators.any((mod) => mod.name == ac.localName); + final isModerator = + !(detailedCommunity == null) && + detailedCommunity.moderators.any((mod) => mod.name == ac.localName); return ContextMenu( actions: [ @@ -44,7 +44,7 @@ Future showCommunityMenu( isSubscribed: detailedCommunity?.isUserSubscribed, subscriptionCount: detailedCommunity?.subscriptionsCount, onSubscribe: (selected) async { - var newValue = await ac.api.community.subscribe( + final newValue = await ac.api.community.subscribe( detailedCommunity?.id ?? community!.id, selected, ); @@ -73,7 +73,7 @@ Future showCommunityMenu( style: ButtonStyle( foregroundColor: WidgetStatePropertyAll( detailedCommunity != null && - detailedCommunity.isBlockedByUser == true + (detailedCommunity.isBlockedByUser ?? false) ? Theme.of(context).colorScheme.error : Theme.of(context).disabledColor, ), @@ -99,7 +99,7 @@ Future showCommunityMenu( if (detailedCommunity != null) ContextMenuItem( title: l(context).viewMods, - onTap: () => showDialog( + onTap: () => showDialog( context: context, builder: (context) => AlertDialog( title: Text(l(context).modsOf(name)), diff --git a/lib/src/widgets/menus/content_menu.dart b/lib/src/widgets/menus/content_menu.dart index 0eff27b1..584ced97 100644 --- a/lib/src/widgets/menus/content_menu.dart +++ b/lib/src/widgets/menus/content_menu.dart @@ -1,29 +1,29 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:interstellar/src/controller/controller.dart'; +import 'package:interstellar/src/controller/router.gr.dart'; +import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/utils/language.dart'; +import 'package:interstellar/src/utils/utils.dart'; +import 'package:interstellar/src/widgets/content_item/content_item.dart'; +import 'package:interstellar/src/widgets/context_menu.dart'; import 'package:interstellar/src/widgets/emoji_picker/emoji_picker.dart'; +import 'package:interstellar/src/widgets/loading_button.dart'; import 'package:interstellar/src/widgets/menus/community_menu.dart'; import 'package:interstellar/src/widgets/menus/user_menu.dart'; +import 'package:interstellar/src/widgets/notification_control_segment.dart'; +import 'package:interstellar/src/widgets/report_content.dart'; import 'package:interstellar/src/widgets/tags/post_flairs.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; -import 'package:interstellar/src/controller/controller.dart'; -import 'package:interstellar/src/controller/router.gr.dart'; -import 'package:interstellar/src/utils/utils.dart'; -import 'package:interstellar/src/widgets/loading_button.dart'; -import 'package:interstellar/src/widgets/notification_control_segment.dart'; -import 'package:interstellar/src/widgets/report_content.dart'; -import 'package:interstellar/src/widgets/content_item/content_item.dart'; -import 'package:interstellar/src/widgets/context_menu.dart'; -import 'package:interstellar/src/controller/server.dart'; Future showContentMenu( BuildContext context, ContentItem widget, { - Function()? onEdit, - Function(String lang)? onTranslate, - Function()? onReply, + void Function()? onEdit, + Future Function(String lang)? onTranslate, + void Function()? onReply, }) async { final ac = context.read(); @@ -36,7 +36,7 @@ Future showContentMenu( childBuilder: (onClick, focusNode) => IconButton( onPressed: onClick, focusNode: focusNode, - icon: Icon(Symbols.add_reaction_rounded), + icon: const Icon(Symbols.add_reaction_rounded), ), onSelect: (emoji) => widget.onEmojiReact!(emoji), ), @@ -110,7 +110,7 @@ Future showContentMenu( ContextMenuItem( title: l(context).crossPost, onTap: () => - context.router.push(CreateRoute(crossPost: widget.crossPost!)), + context.router.push(CreateRoute(crossPost: widget.crossPost)), ), if (widget.domain != null) ContextMenuItem( @@ -231,7 +231,7 @@ Future showContentMenu( onTap: () async { final community = await ac.api.community.get(widget.community!.id); if (!context.mounted) return; - showModalBottomSheet( + showModalBottomSheet( context: context, builder: (BuildContext context) => PostFlairsModal( flairs: widget.flairs, @@ -251,7 +251,7 @@ Future showContentMenu( if (widget.body != null) ContextMenuItem( title: l(context).viewSource, - onTap: () => showDialog( + onTap: () => showDialog( context: context, builder: (context) => AlertDialog( title: Text(l(context).viewSource), @@ -301,7 +301,7 @@ Future showContentMenu( if (!context.mounted) return; context.router.pop(); }, - icon: Icon(Symbols.arrow_right_rounded), + icon: const Icon(Symbols.arrow_right_rounded), ), ), if (widget.user != null) @@ -403,7 +403,10 @@ Future showBookmarksMenu(BuildContext context, ContentItem widget) async { icon: Symbols.bookmark_rounded, iconFill: 1, onTap: () async { - widget.onRemoveBookmarkFromList!(listName); + await widget.onRemoveBookmarkFromList!(listName); + + if (!context.mounted) return; + context.router.pop(); context.router.pop(); }, @@ -412,7 +415,10 @@ Future showBookmarksMenu(BuildContext context, ContentItem widget) async { title: l(context).bookmark_addToX(listName), icon: Symbols.bookmark_rounded, onTap: () async { - widget.onAddBookmarkToList!(listName); + await widget.onAddBookmarkToList!(listName); + + if (!context.mounted) return; + context.router.pop(); context.router.pop(); }, diff --git a/lib/src/widgets/menus/user_menu.dart b/lib/src/widgets/menus/user_menu.dart index 8bc8e204..adec7cfe 100644 --- a/lib/src/widgets/menus/user_menu.dart +++ b/lib/src/widgets/menus/user_menu.dart @@ -1,34 +1,33 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:interstellar/src/api/feed_source.dart'; import 'package:interstellar/src/controller/controller.dart'; -import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/controller/router.gr.dart'; -import 'package:interstellar/src/api/feed_source.dart'; +import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/models/user.dart'; import 'package:interstellar/src/screens/explore/explore_screen.dart'; +import 'package:interstellar/src/screens/settings/feed_settings_screen.dart'; import 'package:interstellar/src/utils/ap_urls.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/context_menu.dart'; -import 'package:interstellar/src/widgets/subscription_button.dart'; -import 'package:interstellar/src/widgets/star_button.dart'; import 'package:interstellar/src/widgets/loading_button.dart'; -import 'package:interstellar/src/screens/settings/feed_settings_screen.dart'; +import 'package:interstellar/src/widgets/star_button.dart'; +import 'package:interstellar/src/widgets/subscription_button.dart'; import 'package:interstellar/src/widgets/tags/tag_screen.dart'; import 'package:material_symbols_icons/symbols.dart'; - import 'package:provider/provider.dart'; Future showUserMenu( BuildContext context, { required DetailedUserModel user, - Function(DetailedUserModel)? update, + void Function(DetailedUserModel)? update, bool navigateOption = false, }) async { final ac = context.read(); final isMe = ac.isLoggedIn && - whenLoggedIn(context, true, matchesUsername: user.name) == true; + (whenLoggedIn(context, true, matchesUsername: user.name) ?? false); final globalName = user.name.contains('@') ? '@${user.name}' : '@${user.name}@${ac.instanceHost}'; @@ -41,7 +40,7 @@ Future showUserMenu( isSubscribed: user.isFollowedByUser, subscriptionCount: user.followersCount, onSubscribe: (selected) async { - var newValue = await ac.api.users.follow(user.id, selected); + final newValue = await ac.api.users.follow(user.id, selected); if (update != null) { update(newValue); } @@ -70,7 +69,7 @@ Future showUserMenu( icon: const Icon(Symbols.block_rounded), style: ButtonStyle( foregroundColor: WidgetStatePropertyAll( - user.isBlockedByUser == true + user.isBlockedByUser ?? false ? Theme.of(context).colorScheme.error : Theme.of(context).disabledColor, ), @@ -81,11 +80,7 @@ Future showUserMenu( ContextMenuAction( icon: Symbols.mail_rounded, onTap: () => context.router.push( - MessageThreadRoute( - threadId: null, - userId: user.id, - otherUser: user, - ), + MessageThreadRoute(userId: user.id, otherUser: user), ), ), ], @@ -118,17 +113,15 @@ Future showUserMenu( ), ContextMenuItem( title: l(context).tags, - onTap: () async { - showModalBottomSheet( - context: context, - builder: (context) => TagsList( - username: user.name, - onUpdate: (tags) async { - await ac.reassignTagsToUser(tags, user.name); - }, - ), - ); - }, + onTap: () async => showModalBottomSheet( + context: context, + builder: (context) => TagsList( + username: user.name, + onUpdate: (tags) async { + await ac.reassignTagsToUser(tags, user.name); + }, + ), + ), ), if (ac.serverSoftware == ServerSoftware.lemmy) ContextMenuItem( diff --git a/lib/src/widgets/notification_control_segment.dart b/lib/src/widgets/notification_control_segment.dart index 4443a4c5..255e80bc 100644 --- a/lib/src/widgets/notification_control_segment.dart +++ b/lib/src/widgets/notification_control_segment.dart @@ -7,11 +7,11 @@ import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; class NotificationControlSegment extends StatelessWidget { + const NotificationControlSegment(this.value, this.onChange, {super.key}); + final NotificationControlStatus value; final Future Function(NotificationControlStatus) onChange; - const NotificationControlSegment(this.value, this.onChange, {super.key}); - @override Widget build(BuildContext context) { return SegmentedButton( diff --git a/lib/src/widgets/open_webpage.dart b/lib/src/widgets/open_webpage.dart index 0e3590df..2822a07b 100644 --- a/lib/src/widgets/open_webpage.dart +++ b/lib/src/widgets/open_webpage.dart @@ -1,77 +1,79 @@ +import 'dart:async'; + import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:interstellar/src/controller/router.gr.dart'; +import 'package:interstellar/src/utils/globals.dart'; import 'package:interstellar/src/utils/share.dart'; import 'package:interstellar/src/utils/utils.dart'; -import 'package:interstellar/src/utils/globals.dart'; import 'package:interstellar/src/widgets/loading_button.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:webview_flutter/webview_flutter.dart'; -import 'package:interstellar/src/controller/router.gr.dart'; -void openWebpagePrimary(BuildContext context, Uri uri) { - launchUrl(uri); -} +void openWebpagePrimary(BuildContext context, Uri uri) => launchUrl(uri); -void openWebpageSecondary(BuildContext context, Uri uri) { - showDialog( - context: context, - builder: (BuildContext context) => AlertDialog( - title: Text(l(context).openLink), - content: SelectableText(uri.toString()), - actions: [ - OutlinedButton( - onPressed: () => context.router.pop(), - child: Text(l(context).cancel), - ), +void openWebpageSecondary(BuildContext context, Uri uri) => showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + title: Text(l(context).openLink), + content: SelectableText(uri.toString()), + actions: [ + OutlinedButton( + onPressed: () => context.router.pop(), + child: Text(l(context).cancel), + ), + FilledButton.tonal( + onPressed: () { + context.router.pop(); + unawaited(shareUri(uri)); + }, + child: Text(l(context).share), + ), + LoadingTonalButton( + onPressed: () async { + await Clipboard.setData(ClipboardData(text: uri.toString())); + + if (!context.mounted) return; + context.router.pop(); + }, + label: Text(l(context).copy), + ), + if (isWebViewSupported) FilledButton.tonal( onPressed: () { context.router.pop(); - shareUri(uri); - }, - child: Text(l(context).share), - ), - LoadingTonalButton( - onPressed: () async { - await Clipboard.setData(ClipboardData(text: uri.toString())); - if (!context.mounted) return; - context.router.pop(); - }, - label: Text(l(context).copy), - ), - if (isWebViewSupported) - FilledButton.tonal( - onPressed: () { - context.router.pop(); + final controller = WebViewController(); + unawaited( + controller.setJavaScriptMode(JavaScriptMode.unrestricted), + ); + unawaited(controller.loadRequest(uri)); - var controller = WebViewController() - ..setJavaScriptMode(JavaScriptMode.unrestricted) - ..loadRequest(uri); - - context.router.push(WebViewRoute(controller: controller)); - }, - child: Text(l(context).webView), - ), - FilledButton( - onPressed: () { - context.router.pop(); - - launchUrl(uri); + unawaited( + context.router.push(WebViewRoute(controller: controller)), + ); }, - child: Text(l(context).browser), + child: Text(l(context).webView), ), - ], - actionsOverflowAlignment: OverflowBarAlignment.center, - actionsOverflowButtonSpacing: 8, - actionsOverflowDirection: VerticalDirection.up, - ), - ); -} + FilledButton( + onPressed: () { + context.router.pop(); + + unawaited(launchUrl(uri)); + }, + child: Text(l(context).browser), + ), + ], + actionsOverflowAlignment: OverflowBarAlignment.center, + actionsOverflowButtonSpacing: 8, + actionsOverflowDirection: VerticalDirection.up, + ), +); @RoutePage() class WebViewScreen extends StatelessWidget { - const WebViewScreen({super.key, required this.controller}); + const WebViewScreen({required this.controller, super.key}); final WebViewController controller; diff --git a/lib/src/widgets/paging.dart b/lib/src/widgets/paging.dart index a0b60786..4bdac557 100644 --- a/lib/src/widgets/paging.dart +++ b/lib/src/widgets/paging.dart @@ -42,16 +42,19 @@ class AdvancedPagingController /// Keeps track of the current operation. /// If the operation changes during its execution, the operation is cancelled. /// - /// Instead of using this property directly, use [fetchNextPage], [refresh], or [cancel]. - /// If you are extending this class, check and set this property before and after the fetch operation. + /// Instead of using this property directly, use [fetchNextPage], [refresh], + /// or [cancel]. + /// If you are extending this class, check and set this property before and + /// after the fetch operation. @protected @visibleForTesting Object? operation; /// Fetches the next page. /// - /// If called while a page is fetching or no more pages are available, this method does nothing. - void fetchNextPage() async { + /// If called while a page is fetching or no more pages are available, this + /// method does nothing. + Future fetchNextPage() async { // We are already loading a new page. if (this.operation != null) return; @@ -61,9 +64,9 @@ class AdvancedPagingController // we use a local copy of value, // so that we only send one notification now and at the end of the method. - PagingState state = value; + var state = value; PageKeyType? newNextPage; - Set newItemIds = {..._itemIds}; + final newItemIds = {..._itemIds}; try { // There are no more pages to load. @@ -79,9 +82,10 @@ class AdvancedPagingController final result = await _fetchPage(thisPage); - // Only include a new item if it's unique identifier does not already exist - List newItems = []; - for (var item in result.$1) { + // Only include a new item if it's unique identifier does not already + // exist + final newItems = []; + for (final item in result.$1) { final itemId = _getItemId(item); if (!newItemIds.contains(itemId)) { newItems.add(item); @@ -90,7 +94,8 @@ class AdvancedPagingController } // Update our state in case it was modified during the fetch operation. - // This beaks atomicity, but is necessary to allow users to modify the state during a fetch. + // This beaks atomicity, but is necessary to allow users to modify the + // state during a fetch. state = value; state = state.copyWith( @@ -133,7 +138,8 @@ class AdvancedPagingController /// Cancels the current fetch operation. /// - /// This can be called right before a call to [fetchNextPage] to force a new fetch. + /// This can be called right before a call to [fetchNextPage] to force a new + /// fetch. void cancel() { operation = null; value = value.copyWith(isLoading: false); @@ -161,9 +167,9 @@ class AdvancedPagingController void prependPage(PageKeyType key, List items) { var state = value; - Set newItemIds = {..._itemIds}; - List newItems = []; - for (var item in items) { + final newItemIds = {..._itemIds}; + final newItems = []; + for (final item in items) { final itemId = _getItemId(item); if (!newItemIds.contains(itemId)) { newItems.add(item); @@ -183,9 +189,9 @@ class AdvancedPagingController class AdvancedPagingListener extends StatelessWidget { const AdvancedPagingListener({ - super.key, required this.controller, required this.builder, + super.key, }); final AdvancedPagingController @@ -209,7 +215,8 @@ class AdvancedPagingListener /// Derived from [PagedSliverList]. A [SliverList] with pagination capabilities. /// -/// Will automatically pass in `state` and `fetchNextPage` to a [PagedSliverList] based on the passed in [AdvancedPagingController]. +/// Will automatically pass in `state` and `fetchNextPage` to a +/// [PagedSliverList] based on the passed in [AdvancedPagingController]. class AdvancedPagedSliverList extends StatelessWidget { const AdvancedPagedSliverList({ @@ -266,7 +273,7 @@ class AdvancedPagedScrollView @override Widget build(BuildContext context) => RefreshIndicator( - onRefresh: () => Future.sync(() => controller.refresh()), + onRefresh: () => Future.sync(controller.refresh), child: CustomScrollView( controller: scrollController, slivers: [ diff --git a/lib/src/widgets/password_editor.dart b/lib/src/widgets/password_editor.dart index 93d7e814..f803d473 100644 --- a/lib/src/widgets/password_editor.dart +++ b/lib/src/widgets/password_editor.dart @@ -2,11 +2,11 @@ import 'package:flutter/material.dart'; import 'package:interstellar/src/utils/utils.dart'; class PasswordEditor extends StatefulWidget { + const PasswordEditor(this.controller, {this.onChanged, super.key}); + final TextEditingController controller; final void Function(String)? onChanged; - const PasswordEditor(this.controller, {this.onChanged, super.key}); - @override State createState() => _PasswordEditorState(); } @@ -32,7 +32,7 @@ class _PasswordEditorState extends State { ), ), onChanged: widget.onChanged, - autofillHints: [AutofillHints.password], + autofillHints: const [AutofillHints.password], obscureText: obscureText, ); } diff --git a/lib/src/widgets/report_content.dart b/lib/src/widgets/report_content.dart index 82257541..9490e358 100644 --- a/lib/src/widgets/report_content.dart +++ b/lib/src/widgets/report_content.dart @@ -11,10 +11,10 @@ Future reportContent(BuildContext context, String contentTypeName) => ); class ReportContentBody extends StatefulWidget { - final String contentTypeName; - const ReportContentBody({required this.contentTypeName, super.key}); + final String contentTypeName; + @override State createState() => _ReportContentBodyState(); } diff --git a/lib/src/widgets/scaffold.dart b/lib/src/widgets/scaffold.dart index 7ab4e5ec..a5fb94de 100644 --- a/lib/src/widgets/scaffold.dart +++ b/lib/src/widgets/scaffold.dart @@ -2,14 +2,9 @@ import 'package:expandable/expandable.dart'; import 'package:flutter/material.dart'; import 'package:interstellar/src/utils/breakpoints.dart'; -/// Wrapper of [Scaffold] which displays the drawer persistently based on screen size. +/// Wrapper of [Scaffold] which displays the drawer persistently based on screen +/// size. class AdvancedScaffold extends StatelessWidget { - final Widget body; - final PreferredSizeWidget? appBar; - final Widget? floatingActionButton; - final Widget? drawer; - final ExpandableController? controller; - const AdvancedScaffold({ required this.body, this.appBar, @@ -19,6 +14,12 @@ class AdvancedScaffold extends StatelessWidget { super.key, }); + final Widget body; + final PreferredSizeWidget? appBar; + final Widget? floatingActionButton; + final Widget? drawer; + final ExpandableController? controller; + @override Widget build(BuildContext context) { final hasDrawer = drawer != null; @@ -33,7 +34,7 @@ class AdvancedScaffold extends StatelessWidget { Expandable( controller: controller, collapsed: Container(), - expanded: SizedBox(width: 360, child: drawer!), + expanded: SizedBox(width: 360, child: drawer), ), Expanded(child: body), ], diff --git a/lib/src/widgets/selection_menu.dart b/lib/src/widgets/selection_menu.dart index 4ba98368..646d642c 100644 --- a/lib/src/widgets/selection_menu.dart +++ b/lib/src/widgets/selection_menu.dart @@ -2,13 +2,6 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; class SelectionMenuItem { - final T value; - final String title; - final IconData? icon; - final Color? iconColor; - final String? subtitle; - final List>? subItems; - const SelectionMenuItem({ required this.value, required this.title, @@ -18,16 +11,23 @@ class SelectionMenuItem { this.subItems, }); + final T value; + final String title; + final IconData? icon; + final Color? iconColor; + final String? subtitle; + final List>? subItems; + SelectionMenu? get subItemsSelectionMenu => subItems == null ? null : SelectionMenu(title, subItems!); } class SelectionMenu { + const SelectionMenu(this.title, this.options); + final String title; final List> options; - const SelectionMenu(this.title, this.options); - Future askSelection(BuildContext context, T? oldSelection) async => showModalBottomSheet( context: context, @@ -64,7 +64,7 @@ class SelectionMenu { option.subItems != null && option.subItems!.isNotEmpty ? IconButton( - icon: Icon(Icons.arrow_right), + icon: const Icon(Icons.arrow_right), onPressed: () async { final subSelection = await option .subItemsSelectionMenu! @@ -86,7 +86,7 @@ class SelectionMenu { ); SelectionMenuItem getOption(T value) { - for (var option in options) { + for (final option in options) { if (option.subItems == null) continue; try { return option.subItemsSelectionMenu!.getOption(value); diff --git a/lib/src/widgets/server_software_indicator.dart b/lib/src/widgets/server_software_indicator.dart index c8737d91..4c185a68 100644 --- a/lib/src/widgets/server_software_indicator.dart +++ b/lib/src/widgets/server_software_indicator.dart @@ -2,15 +2,15 @@ import 'package:flutter/material.dart'; import 'package:interstellar/src/controller/server.dart'; class ServerSoftwareIndicator extends StatelessWidget { - final String label; - final ServerSoftware software; - const ServerSoftwareIndicator({ - super.key, required this.label, required this.software, + super.key, }); + final String label; + final ServerSoftware software; + @override Widget build(BuildContext context) { return Badge( @@ -18,7 +18,7 @@ class ServerSoftwareIndicator extends StatelessWidget { backgroundColor: software.color, textColor: Colors.white, alignment: Alignment.centerRight, - offset: Offset(20, -6), + offset: const Offset(20, -6), child: Text(label), ); } diff --git a/lib/src/widgets/settings_header.dart b/lib/src/widgets/settings_header.dart index a10115d7..50786dee 100644 --- a/lib/src/widgets/settings_header.dart +++ b/lib/src/widgets/settings_header.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; class SettingsHeader extends StatelessWidget { - final String text; - const SettingsHeader(this.text, {super.key}); + final String text; + @override Widget build(BuildContext context) { return Text( diff --git a/lib/src/widgets/star_button.dart b/lib/src/widgets/star_button.dart index 7c802459..ee3af2b3 100644 --- a/lib/src/widgets/star_button.dart +++ b/lib/src/widgets/star_button.dart @@ -4,10 +4,10 @@ import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; class StarButton extends StatelessWidget { - final String name; - const StarButton(this.name, {super.key}); + final String name; + @override Widget build(BuildContext context) { final isStarred = context.watch().stars.contains(name); diff --git a/lib/src/widgets/subordinate_scroll.dart b/lib/src/widgets/subordinate_scroll.dart index a8e25b66..fc4f0ba0 100644 --- a/lib/src/widgets/subordinate_scroll.dart +++ b/lib/src/widgets/subordinate_scroll.dart @@ -65,15 +65,11 @@ class SubordinateScrollController extends ScrollController { } void _detachFromParent() { - for (final position in positions) { - _parent.detach(position); - } + positions.forEach(_parent.detach); } void _attachToParent() { - for (final position in positions) { - _parent.attach(position); - } + positions.forEach(_parent.attach); } @override diff --git a/lib/src/widgets/subscription_button.dart b/lib/src/widgets/subscription_button.dart index f3823ab5..7f196030 100644 --- a/lib/src/widgets/subscription_button.dart +++ b/lib/src/widgets/subscription_button.dart @@ -7,11 +7,6 @@ import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; class SubscriptionButton extends StatelessWidget { - final bool? isSubscribed; - final int? subscriptionCount; - final Future Function(bool) onSubscribe; - final bool followMode; - const SubscriptionButton({ required this.isSubscribed, required this.subscriptionCount, @@ -20,6 +15,11 @@ class SubscriptionButton extends StatelessWidget { super.key, }); + final bool? isSubscribed; + final int? subscriptionCount; + final Future Function(bool) onSubscribe; + final bool followMode; + @override Widget build(BuildContext context) { return LoadingFilterChip( @@ -58,7 +58,7 @@ class SubscriptionButton extends StatelessWidget { ), ); - if (confirm == true) await onSubscribe(newValue); + if (confirm ?? false) await onSubscribe(newValue); } : onSubscribe, ), diff --git a/lib/src/widgets/super_hero.dart b/lib/src/widgets/super_hero.dart index d5e29fbf..b8b39334 100644 --- a/lib/src/widgets/super_hero.dart +++ b/lib/src/widgets/super_hero.dart @@ -5,11 +5,11 @@ import 'package:flutter/material.dart'; class SuperHero extends Hero { const SuperHero({ required super.tag, + required super.child, super.key, super.createRectTween, super.flightShuttleBuilder, super.placeholderBuilder, super.transitionOnUserGestures, - required super.child, }); } diff --git a/lib/src/widgets/tags/post_flairs.dart b/lib/src/widgets/tags/post_flairs.dart index 8c5188db..bc6b3602 100644 --- a/lib/src/widgets/tags/post_flairs.dart +++ b/lib/src/widgets/tags/post_flairs.dart @@ -5,10 +5,10 @@ import 'package:interstellar/src/widgets/tags/tag_widget.dart'; class PostFlairsModal extends StatefulWidget { const PostFlairsModal({ - super.key, required this.flairs, required this.availableFlairs, required this.onUpdate, + super.key, }); final List flairs; @@ -73,7 +73,7 @@ class _PostFlairsModalState extends State { } }, controlAffinity: ListTileControlAffinity.leading, - secondary: SizedBox(width: 28), + secondary: const SizedBox(width: 28), ), ), ], diff --git a/lib/src/widgets/tags/tag_editor.dart b/lib/src/widgets/tags/tag_editor.dart index 2bb2fed2..7182f005 100644 --- a/lib/src/widgets/tags/tag_editor.dart +++ b/lib/src/widgets/tags/tag_editor.dart @@ -1,15 +1,15 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_colorpicker/flutter_colorpicker.dart'; import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/controller/database/database.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/text_editor.dart'; -import 'package:flutter_colorpicker/flutter_colorpicker.dart'; import 'package:provider/provider.dart'; @RoutePage() class TagEditorScreen extends StatefulWidget { - const TagEditorScreen({super.key, required this.tag, required this.onUpdate}); + const TagEditorScreen({required this.tag, required this.onUpdate, super.key}); final Tag tag; final void Function(Tag?) onUpdate; @@ -54,8 +54,8 @@ class _TagEditorScreenState extends State { ), title: Text(l(context).color), onTap: () { - Color c = _backgroundColor; - showDialog( + var c = _backgroundColor; + showDialog( context: context, builder: (context) => AlertDialog( title: Text(l(context).pickColor), @@ -125,7 +125,7 @@ class _TagEditorScreenState extends State { ); } catch (err) { if (!context.mounted) return; - await showDialog( + await showDialog( context: context, builder: (context) => AlertDialog( title: Text(l(context).tags_exist), diff --git a/lib/src/widgets/tags/tag_screen.dart b/lib/src/widgets/tags/tag_screen.dart index cef0743e..4e9a90e1 100644 --- a/lib/src/widgets/tags/tag_screen.dart +++ b/lib/src/widgets/tags/tag_screen.dart @@ -1,9 +1,10 @@ +import 'dart:async'; + import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:interstellar/src/controller/controller.dart'; -import 'package:interstellar/src/controller/router.gr.dart'; import 'package:interstellar/src/controller/database/database.dart'; -import 'package:interstellar/src/screens/explore/user_screen.dart'; +import 'package:interstellar/src/controller/router.gr.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/loading_button.dart'; import 'package:interstellar/src/widgets/tags/tag_widget.dart'; @@ -53,23 +54,27 @@ class _TagsListState extends State { if (widget.activeTags != null) { _activeTags = widget.activeTags!; } else if (widget.username != null) { - ac - .getUserTags(widget.username!) - .then( - (tags) => setState(() { - _activeTags = tags; - }), - ); + unawaited( + ac + .getUserTags(widget.username!) + .then( + (tags) => setState(() { + _activeTags = tags; + }), + ), + ); } if (widget.availableTags != null) { setState(() { _availableTags = widget.availableTags!; }); } else { - ac.getTags().then( - (tags) => setState(() { - _availableTags = tags; - }), + unawaited( + ac.getTags().then( + (tags) => setState(() { + _availableTags = tags; + }), + ), ); } } @@ -117,7 +122,7 @@ class _TagsListState extends State { final isActive = activeTagIds.contains(tag.id); - toggleTag(bool? newValue) { + void toggleTag(bool? newValue) { if (newValue == null) return; if (newValue) { @@ -144,7 +149,7 @@ class _TagsListState extends State { : IconButton( onPressed: () => context.router.push(TagUsersRoute(tag: tag)), - icon: Icon(Symbols.person_rounded), + icon: const Icon(Symbols.person_rounded), ), onTap: widget.onUpdate != null ? () => toggleTag(!isActive) : null, trailing: IconButton( @@ -192,14 +197,16 @@ class TagUsersScreenState extends State { void initState() { super.initState(); - context - .read() - .getTagUsers(widget.tag.id) - .then( - (users) => setState(() { - _users = users; - }), - ); + unawaited( + context + .read() + .getTagUsers(widget.tag.id) + .then( + (users) => setState(() { + _users = users; + }), + ), + ); } @override @@ -209,7 +216,7 @@ class TagUsersScreenState extends State { return Scaffold( appBar: AppBar(title: Text(widget.tag.tag)), body: _users == null - ? Center(child: CircularProgressIndicator()) + ? const Center(child: CircularProgressIndicator()) : _users!.isEmpty ? Center( child: Padding( @@ -226,7 +233,7 @@ class TagUsersScreenState extends State { (username) => ListTile( title: Text(username), onTap: () async { - String name = username; + var name = username; if (name.endsWith(ac.instanceHost)) { name = name.split('@').first; @@ -235,8 +242,10 @@ class TagUsersScreenState extends State { if (!context.mounted) return; - context.router.push( - UserRoute(userId: user.id, initData: user), + unawaited( + context.router.push( + UserRoute(userId: user.id, initData: user), + ), ); }, ), @@ -248,9 +257,9 @@ class TagUsersScreenState extends State { } class TagsFloatingButton extends StatelessWidget { - const TagsFloatingButton({super.key, required this.onUpdate}); + const TagsFloatingButton({required this.onUpdate, super.key}); - final Function(Tag) onUpdate; + final void Function(Tag) onUpdate; @override Widget build(BuildContext context) { @@ -258,14 +267,14 @@ class TagsFloatingButton extends StatelessWidget { return FloatingActionButton.extended( label: Text(l(context).tags_new), - icon: Icon(Symbols.add_rounded), + icon: const Icon(Symbols.add_rounded), onPressed: () async { Tag? tag; try { tag = await ac.addTag(); } catch (err) { if (!context.mounted) return; - await showDialog( + await showDialog( context: context, builder: (context) { return AlertDialog( @@ -279,7 +288,7 @@ class TagsFloatingButton extends StatelessWidget { ), LoadingFilledButton( onPressed: () async { - int num = 0; + var num = 0; while (tag == null) { try { tag = await ac.addTag(tag: 'Tag ${num++}'); @@ -298,7 +307,7 @@ class TagsFloatingButton extends StatelessWidget { ); } if (!context.mounted || tag == null) return; - bool cancelled = true; + var cancelled = true; await context.router.push( TagEditorRoute( tag: tag!, @@ -335,10 +344,12 @@ class _TagsScreenState extends State { void initState() { super.initState(); - context.read().getTags().then( - (tags) => setState(() { - _availableTags = tags; - }), + unawaited( + context.read().getTags().then( + (tags) => setState(() { + _availableTags = tags; + }), + ), ); } diff --git a/lib/src/widgets/tags/tag_widget.dart b/lib/src/widgets/tags/tag_widget.dart index 02666dd2..f74edbc7 100644 --- a/lib/src/widgets/tags/tag_widget.dart +++ b/lib/src/widgets/tags/tag_widget.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:interstellar/src/controller/database/database.dart'; class TagWidget extends StatelessWidget { - const TagWidget({super.key, required this.tag}); + const TagWidget({required this.tag, super.key}); final Tag tag; diff --git a/lib/src/widgets/text_editor.dart b/lib/src/widgets/text_editor.dart index d37a9426..b4f857f7 100644 --- a/lib/src/widgets/text_editor.dart +++ b/lib/src/widgets/text_editor.dart @@ -1,15 +1,6 @@ import 'package:flutter/material.dart'; class TextEditor extends StatelessWidget { - final TextEditingController controller; - final TextInputType? keyboardType; - final String? label; - final String? hint; - final void Function(String)? onChanged; - final bool? enabled; - final int? maxLength; - final List? autofillHints; - const TextEditor( this.controller, { this.keyboardType, @@ -22,6 +13,15 @@ class TextEditor extends StatelessWidget { super.key, }); + final TextEditingController controller; + final TextInputType? keyboardType; + final String? label; + final String? hint; + final void Function(String)? onChanged; + final bool? enabled; + final int? maxLength; + final List? autofillHints; + @override Widget build(BuildContext context) { return TextField( diff --git a/lib/src/widgets/user_status_icons.dart b/lib/src/widgets/user_status_icons.dart index e3500465..13f5d501 100644 --- a/lib/src/widgets/user_status_icons.dart +++ b/lib/src/widgets/user_status_icons.dart @@ -5,15 +5,15 @@ import 'package:interstellar/src/utils/utils.dart'; import 'package:material_symbols_icons/symbols.dart'; class UserStatusIcons extends StatelessWidget { - final DateTime? cakeDay; - final bool isBot; - const UserStatusIcons({ required this.cakeDay, required this.isBot, super.key, }); + final DateTime? cakeDay; + final bool isBot; + @override Widget build(BuildContext context) { final now = DateTime.now(); diff --git a/lib/src/widgets/video.dart b/lib/src/widgets/video.dart index a08f3d0f..9be72338 100644 --- a/lib/src/widgets/video.dart +++ b/lib/src/widgets/video.dart @@ -1,6 +1,11 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; +import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/utils/share.dart'; +import 'package:interstellar/src/widgets/blur.dart'; import 'package:interstellar/src/widgets/loading_button.dart'; +import 'package:interstellar/src/widgets/wrapper.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:media_kit/media_kit.dart'; import 'package:media_kit_video/media_kit_video.dart'; @@ -9,9 +14,6 @@ import 'package:media_kit_video/media_kit_video_controls/media_kit_video_control import 'package:provider/provider.dart'; import 'package:youtube_explode_dart/youtube_explode_dart.dart' as youtube_explode_dart; -import 'package:interstellar/src/controller/controller.dart'; -import 'package:interstellar/src/widgets/wrapper.dart'; -import 'package:interstellar/src/widgets/blur.dart'; bool isSupportedYouTubeVideo(Uri link) { return [ @@ -23,11 +25,11 @@ bool isSupportedYouTubeVideo(Uri link) { } class VideoPlayer extends StatefulWidget { + const VideoPlayer(this.uri, {super.key, this.enableBlur = false}); + final Uri uri; final bool enableBlur; - const VideoPlayer(this.uri, {super.key, this.enableBlur = false}); - @override State createState() => _VideoPlayerState(); } @@ -60,21 +62,26 @@ class _VideoPlayerState extends State { if (!mounted) return; - // Use best muxed stream if available, else use best separate video and audio streams - // TODO: calculate best quality for device based on screen size and data saver mode, also add manual stream selection + // Use best muxed stream if available, else use best separate video and + // audio streams + // + // TODO(jwr1): calculate best quality for device based on screen size + // and data saver mode, also add manual stream selection if (manifest.muxed.isNotEmpty) { final muxedStream = manifest.muxed.bestQuality; - player.open(Media(muxedStream.url.toString()), play: autoPlay); + await player.open(Media(muxedStream.url.toString()), play: autoPlay); } else { final videoStream = manifest.video.bestQuality; final audioStream = manifest.audio.withHighestBitrate(); final media = Media(videoStream.url.toString()); - player.open(media, play: _isPlaying); - player.setAudioTrack(AudioTrack.uri(audioStream.url.toString())); + await player.open(media, play: _isPlaying); + await player.setAudioTrack( + AudioTrack.uri(audioStream.url.toString()), + ); } } else { - player.open(Media(widget.uri.toString()), play: _isPlaying); + await player.open(Media(widget.uri.toString()), play: _isPlaying); } } catch (e) { error = e.toString(); @@ -84,12 +91,13 @@ class _VideoPlayerState extends State { @override void initState() { super.initState(); - _initController(); + unawaited(_initController()); } @override Widget build(BuildContext context) { - // TODO: implement top buttons by setting a MaterialVideoControls & MaterialDesktopVideoControlsTheme + // TODO(jwr1): implement top buttons by setting a + // MaterialVideoControls and MaterialDesktopVideoControlsTheme return SizedBox( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.width * 9.0 / 16.0, @@ -97,7 +105,7 @@ class _VideoPlayerState extends State { children: [ if (error != null) DecoratedBox( - decoration: BoxDecoration(color: Colors.black), + decoration: const BoxDecoration(color: Colors.black), child: Center(child: Text(error!)), ), if (error == null) @@ -111,7 +119,7 @@ class _VideoPlayerState extends State { onPressed: () { setState(() { _isPlaying = !_isPlaying; - player.playOrPause(); + unawaited(player.playOrPause()); }); }, icon: const Icon(Symbols.play_arrow_rounded, fill: 1), @@ -127,7 +135,9 @@ class _VideoPlayerState extends State { children: [ media_kit_video_controls.AdaptiveVideoControls(state), if (!_isPlaying) - Center(child: MaterialPlayOrPauseButton(iconSize: 56)), + const Center( + child: MaterialPlayOrPauseButton(iconSize: 56), + ), if (!state.isFullscreen()) Align( alignment: Alignment.topRight, @@ -135,8 +145,7 @@ class _VideoPlayerState extends State { mainAxisSize: MainAxisSize.min, children: [ LoadingIconButton( - onPressed: () async => - await shareUri(widget.uri), + onPressed: () async => shareUri(widget.uri), icon: const Icon(Symbols.share_rounded), ), ], @@ -155,7 +164,7 @@ class _VideoPlayerState extends State { @override void dispose() { yt?.close(); - player.dispose(); + unawaited(player.dispose()); super.dispose(); } @@ -164,7 +173,7 @@ class _VideoPlayerState extends State { super.didChangeDependencies(); WidgetsBinding.instance.addPostFrameCallback((_) { if (!(ModalRoute.of(context)?.isCurrent ?? false)) { - player.pause(); + unawaited(player.pause()); } }); } diff --git a/lib/src/widgets/wrapper.dart b/lib/src/widgets/wrapper.dart index 83810ca8..b900a818 100644 --- a/lib/src/widgets/wrapper.dart +++ b/lib/src/widgets/wrapper.dart @@ -1,17 +1,17 @@ import 'package:flutter/material.dart'; class Wrapper extends StatelessWidget { - final bool shouldWrap; - final Widget Function(Widget child) parentBuilder; - final Widget child; - const Wrapper({ - super.key, required this.shouldWrap, required this.parentBuilder, required this.child, + super.key, }); + final bool shouldWrap; + final Widget Function(Widget child) parentBuilder; + final Widget child; + @override Widget build(BuildContext context) { return shouldWrap ? parentBuilder(child) : child; diff --git a/pubspec.lock b/pubspec.lock index 2f39f897..b90f2da2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -66,7 +66,7 @@ packages: source: hosted version: "11.1.0" auto_route_generator: - dependency: "direct dev" + dependency: "direct main" description: name: auto_route_generator sha256: "04300eaf5821962aae8b5cd94f67013fd2fd326dc3be212d3ec1ae7470f09834" @@ -82,7 +82,7 @@ packages: source: hosted version: "2.1.2" build: - dependency: "direct dev" + dependency: "direct main" description: name: build sha256: "275bf6bb2a00a9852c28d4e0b410da1d833a734d57d39d44f94bfc895a484ec3" @@ -106,7 +106,7 @@ packages: source: hosted version: "4.0.4" build_runner: - dependency: "direct dev" + dependency: "direct main" description: name: build_runner sha256: b4d854962a32fd9f8efc0b76f98214790b833af8b2e9b2df6bfc927c0415a072 @@ -266,7 +266,7 @@ packages: source: hosted version: "2.1.0" drift_dev: - dependency: "direct dev" + dependency: "direct main" description: name: drift_dev sha256: "892dfb5d69d9e604bdcd102a9376de8b41768cf7be93fd26b63cfc4d8f91ad5f" @@ -423,21 +423,13 @@ packages: source: hosted version: "1.1.0" flutter_launcher_icons: - dependency: "direct dev" + dependency: "direct main" description: name: flutter_launcher_icons sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" url: "https://pub.dev" source: hosted version: "0.14.4" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" - url: "https://pub.dev" - source: hosted - version: "6.0.0" flutter_local_notifications: dependency: "direct main" description: @@ -543,7 +535,7 @@ packages: source: sdk version: "0.0.0" freezed: - dependency: "direct dev" + dependency: "direct main" description: name: freezed sha256: "03dd9b7423ff0e31b7e01b2204593e5e1ac5ee553b6ea9d8184dff4a26b9fb07" @@ -743,7 +735,7 @@ packages: source: hosted version: "4.9.0" json_serializable: - dependency: "direct dev" + dependency: "direct main" description: name: json_serializable sha256: "5b89c1e32ae3840bb20a1b3434e3a590173ad3cb605896fb0f60487ce2f8104e" @@ -782,14 +774,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.5" - lints: - dependency: transitive - description: - name: lints - sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0 - url: "https://pub.dev" - source: hosted - version: "6.0.0" logger: dependency: "direct main" description: @@ -1555,6 +1539,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + very_good_analysis: + dependency: "direct main" + description: + name: very_good_analysis + sha256: "96245839dbcc45dfab1af5fa551603b5c7a282028a64746c19c547d21a7f1e3a" + url: "https://pub.dev" + source: hosted + version: "10.0.0" visibility_detector: dependency: "direct main" description: @@ -1734,4 +1726,4 @@ packages: version: "3.0.5" sdks: dart: ">=3.9.0 <4.0.0" - flutter: "3.38.5" + flutter: "3.38.9" diff --git a/pubspec.yaml b/pubspec.yaml index 32a46c44..b4b6edfa 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,7 +7,7 @@ version: 0.11.1+23 environment: sdk: '>=3.9.0 <4.0.0' - flutter: 3.38.5 + flutter: 3.38.9 dependencies: flutter: @@ -60,22 +60,20 @@ dependencies: sqlite3: ^2.9.4 collection: ^1.19.1 flutter_colorpicker: ^1.1.0 - auto_route: ^11.1.0 - flutter_web_auth_2: - git: - url: https://github.com/interstellar-app/flutter_web_auth_2.git - path: flutter_web_auth_2 - ref: b1af971b79160979320a4865e238dcaacf69b624 - -dev_dependencies: - flutter_lints: ^6.0.0 flutter_launcher_icons: ^0.14.4 build_runner: ^2.10.5 freezed: ^3.2.4 json_serializable: ^6.11.1 drift_dev: ^2.28.3 build: ^4.0.4 + very_good_analysis: ^10.0.0 auto_route_generator: ^10.4.0 + auto_route: ^11.1.0 + flutter_web_auth_2: + git: + url: https://github.com/interstellar-app/flutter_web_auth_2.git + path: flutter_web_auth_2 + ref: b1af971b79160979320a4865e238dcaacf69b624 # Needed for 16kb page, remove once this pr (https://github.com/google/webcrypto.dart/pull/238) is merged and the next version is released. dependency_overrides: