From 5467cb56a5f7c02380af1043fd572be5f141aec4 Mon Sep 17 00:00:00 2001 From: nahnah Date: Wed, 4 Feb 2026 22:12:34 +0000 Subject: [PATCH] [imp]: Refactor localization & convert screens to slivers --- lib/l10n/crowdin_localizations.dart | 515 +++++++++++++++---------- lib/screens/app_details_screen.dart | 5 +- lib/screens/library_screen.dart | 125 ++++--- lib/screens/repositories_screen.dart | 536 ++++++++++++++------------- lib/screens/settings_screen.dart | 514 ++++++++++++------------- lib/themes/app_themes.dart | 85 ++++- 6 files changed, 1009 insertions(+), 771 deletions(-) diff --git a/lib/l10n/crowdin_localizations.dart b/lib/l10n/crowdin_localizations.dart index b85c958..e753a7a 100644 --- a/lib/l10n/crowdin_localizations.dart +++ b/lib/l10n/crowdin_localizations.dart @@ -1,306 +1,433 @@ -import 'dart:convert'; +import 'package:crowdin_sdk/crowdin_sdk.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; import 'app_localizations.dart'; -import 'package:crowdin_sdk/crowdin_sdk.dart'; - -import 'package:flutter/widgets.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter_localizations/flutter_localizations.dart'; - class CrowdinLocalization extends AppLocalizations { final AppLocalizations _fallbackTexts; - - CrowdinLocalization(String locale, AppLocalizations fallbackTexts) : _fallbackTexts = fallbackTexts, super(locale); - static const LocalizationsDelegate delegate = _CrowdinLocalizationsDelegate(); + CrowdinLocalization(super.locale, AppLocalizations fallbackTexts) + : _fallbackTexts = fallbackTexts; - static const List> localizationsDelegates = < - LocalizationsDelegate>[ - delegate, - GlobalMaterialLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - ]; + static const LocalizationsDelegate delegate = + _CrowdinLocalizationsDelegate(); - static const List supportedLocales = AppLocalizations.supportedLocales; - - @override - String get app_name => Crowdin.getText(localeName, 'app_name') ?? _fallbackTexts.app_name; + static const List> localizationsDelegates = + >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; - @override - String get welcome => Crowdin.getText(localeName, 'welcome') ?? _fallbackTexts.welcome; + static const List supportedLocales = + AppLocalizations.supportedLocales; - @override - String get search => Crowdin.getText(localeName, 'search') ?? _fallbackTexts.search; + @override + String get app_name => + Crowdin.getText(localeName, 'app_name') ?? _fallbackTexts.app_name; - @override - String get settings => Crowdin.getText(localeName, 'settings') ?? _fallbackTexts.settings; + @override + String get welcome => + Crowdin.getText(localeName, 'welcome') ?? _fallbackTexts.welcome; - @override + @override + String get search => + Crowdin.getText(localeName, 'search') ?? _fallbackTexts.search; + + @override + String get settings => + Crowdin.getText(localeName, 'settings') ?? _fallbackTexts.settings; + + @override String get home => Crowdin.getText(localeName, 'home') ?? _fallbackTexts.home; - @override - String get categories => Crowdin.getText(localeName, 'categories') ?? _fallbackTexts.categories; + @override + String get categories => + Crowdin.getText(localeName, 'categories') ?? _fallbackTexts.categories; - @override - String get updates => Crowdin.getText(localeName, 'updates') ?? _fallbackTexts.updates; + @override + String get updates => + Crowdin.getText(localeName, 'updates') ?? _fallbackTexts.updates; - @override - String get installed => Crowdin.getText(localeName, 'installed') ?? _fallbackTexts.installed; + @override + String get installed => + Crowdin.getText(localeName, 'installed') ?? _fallbackTexts.installed; - @override - String get download => Crowdin.getText(localeName, 'download') ?? _fallbackTexts.download; + @override + String get download => + Crowdin.getText(localeName, 'download') ?? _fallbackTexts.download; - @override - String get install => Crowdin.getText(localeName, 'install') ?? _fallbackTexts.install; + @override + String get install => + Crowdin.getText(localeName, 'install') ?? _fallbackTexts.install; - @override - String get uninstall => Crowdin.getText(localeName, 'uninstall') ?? _fallbackTexts.uninstall; + @override + String get uninstall => + Crowdin.getText(localeName, 'uninstall') ?? _fallbackTexts.uninstall; - @override + @override String get open => Crowdin.getText(localeName, 'open') ?? _fallbackTexts.open; - @override - String get cancel => Crowdin.getText(localeName, 'cancel') ?? _fallbackTexts.cancel; + @override + String get cancel => + Crowdin.getText(localeName, 'cancel') ?? _fallbackTexts.cancel; - @override - String get update_available => Crowdin.getText(localeName, 'update_available') ?? _fallbackTexts.update_available; + @override + String get update_available => + Crowdin.getText(localeName, 'update_available') ?? + _fallbackTexts.update_available; - @override - String get downloading => Crowdin.getText(localeName, 'downloading') ?? _fallbackTexts.downloading; + @override + String get downloading => + Crowdin.getText(localeName, 'downloading') ?? _fallbackTexts.downloading; - @override - String get install_permission_required => Crowdin.getText(localeName, 'install_permission_required') ?? _fallbackTexts.install_permission_required; + @override + String get install_permission_required => + Crowdin.getText(localeName, 'install_permission_required') ?? + _fallbackTexts.install_permission_required; - @override - String get storage_permission_required => Crowdin.getText(localeName, 'storage_permission_required') ?? _fallbackTexts.storage_permission_required; + @override + String get storage_permission_required => + Crowdin.getText(localeName, 'storage_permission_required') ?? + _fallbackTexts.storage_permission_required; - @override - String get cancel_download => Crowdin.getText(localeName, 'cancel_download') ?? _fallbackTexts.cancel_download; + @override + String get cancel_download => + Crowdin.getText(localeName, 'cancel_download') ?? + _fallbackTexts.cancel_download; - @override - String get version => Crowdin.getText(localeName, 'version') ?? _fallbackTexts.version; + @override + String get version => + Crowdin.getText(localeName, 'version') ?? _fallbackTexts.version; - @override + @override String get size => Crowdin.getText(localeName, 'size') ?? _fallbackTexts.size; - @override - String get description => Crowdin.getText(localeName, 'description') ?? _fallbackTexts.description; + @override + String get description => + Crowdin.getText(localeName, 'description') ?? _fallbackTexts.description; - @override - String get permissions => Crowdin.getText(localeName, 'permissions') ?? _fallbackTexts.permissions; + @override + String get permissions => + Crowdin.getText(localeName, 'permissions') ?? _fallbackTexts.permissions; - @override - String get screenshots => Crowdin.getText(localeName, 'screenshots') ?? _fallbackTexts.screenshots; + @override + String get screenshots => + Crowdin.getText(localeName, 'screenshots') ?? _fallbackTexts.screenshots; - @override - String get no_version_available => Crowdin.getText(localeName, 'no_version_available') ?? _fallbackTexts.no_version_available; + @override + String get no_version_available => + Crowdin.getText(localeName, 'no_version_available') ?? + _fallbackTexts.no_version_available; - @override - String get app_information => Crowdin.getText(localeName, 'app_information') ?? _fallbackTexts.app_information; + @override + String get app_information => + Crowdin.getText(localeName, 'app_information') ?? + _fallbackTexts.app_information; - @override - String get package_name => Crowdin.getText(localeName, 'package_name') ?? _fallbackTexts.package_name; + @override + String get package_name => + Crowdin.getText(localeName, 'package_name') ?? + _fallbackTexts.package_name; - @override - String get license => Crowdin.getText(localeName, 'license') ?? _fallbackTexts.license; + @override + String get license => + Crowdin.getText(localeName, 'license') ?? _fallbackTexts.license; - @override - String get added => Crowdin.getText(localeName, 'added') ?? _fallbackTexts.added; + @override + String get added => + Crowdin.getText(localeName, 'added') ?? _fallbackTexts.added; - @override - String get last_updated => Crowdin.getText(localeName, 'last_updated') ?? _fallbackTexts.last_updated; + @override + String get last_updated => + Crowdin.getText(localeName, 'last_updated') ?? + _fallbackTexts.last_updated; - @override - String get version_information => Crowdin.getText(localeName, 'version_information') ?? _fallbackTexts.version_information; + @override + String get version_information => + Crowdin.getText(localeName, 'version_information') ?? + _fallbackTexts.version_information; - @override - String get version_name => Crowdin.getText(localeName, 'version_name') ?? _fallbackTexts.version_name; + @override + String get version_name => + Crowdin.getText(localeName, 'version_name') ?? + _fallbackTexts.version_name; - @override - String get version_code => Crowdin.getText(localeName, 'version_code') ?? _fallbackTexts.version_code; + @override + String get version_code => + Crowdin.getText(localeName, 'version_code') ?? + _fallbackTexts.version_code; - @override - String get min_sdk => Crowdin.getText(localeName, 'min_sdk') ?? _fallbackTexts.min_sdk; + @override + String get min_sdk => + Crowdin.getText(localeName, 'min_sdk') ?? _fallbackTexts.min_sdk; - @override - String get target_sdk => Crowdin.getText(localeName, 'target_sdk') ?? _fallbackTexts.target_sdk; + @override + String get target_sdk => + Crowdin.getText(localeName, 'target_sdk') ?? _fallbackTexts.target_sdk; - @override - String get all_versions => Crowdin.getText(localeName, 'all_versions') ?? _fallbackTexts.all_versions; + @override + String get all_versions => + Crowdin.getText(localeName, 'all_versions') ?? + _fallbackTexts.all_versions; - @override - String get latest => Crowdin.getText(localeName, 'latest') ?? _fallbackTexts.latest; + @override + String get latest => + Crowdin.getText(localeName, 'latest') ?? _fallbackTexts.latest; - @override - String get released => Crowdin.getText(localeName, 'released') ?? _fallbackTexts.released; + @override + String get released => + Crowdin.getText(localeName, 'released') ?? _fallbackTexts.released; - @override - String get loading => Crowdin.getText(localeName, 'loading') ?? _fallbackTexts.loading; + @override + String get loading => + Crowdin.getText(localeName, 'loading') ?? _fallbackTexts.loading; - @override - String get error => Crowdin.getText(localeName, 'error') ?? _fallbackTexts.error; + @override + String get error => + Crowdin.getText(localeName, 'error') ?? _fallbackTexts.error; - @override - String get retry => Crowdin.getText(localeName, 'retry') ?? _fallbackTexts.retry; + @override + String get retry => + Crowdin.getText(localeName, 'retry') ?? _fallbackTexts.retry; - @override - String get share => Crowdin.getText(localeName, 'share') ?? _fallbackTexts.share; + @override + String get share => + Crowdin.getText(localeName, 'share') ?? _fallbackTexts.share; - @override - String get website => Crowdin.getText(localeName, 'website') ?? _fallbackTexts.website; + @override + String get website => + Crowdin.getText(localeName, 'website') ?? _fallbackTexts.website; - @override - String get source_code => Crowdin.getText(localeName, 'source_code') ?? _fallbackTexts.source_code; + @override + String get source_code => + Crowdin.getText(localeName, 'source_code') ?? _fallbackTexts.source_code; - @override - String get issue_tracker => Crowdin.getText(localeName, 'issue_tracker') ?? _fallbackTexts.issue_tracker; + @override + String get issue_tracker => + Crowdin.getText(localeName, 'issue_tracker') ?? + _fallbackTexts.issue_tracker; - @override - String get whats_new => Crowdin.getText(localeName, 'whats_new') ?? _fallbackTexts.whats_new; + @override + String get whats_new => + Crowdin.getText(localeName, 'whats_new') ?? _fallbackTexts.whats_new; - @override - String get show_more => Crowdin.getText(localeName, 'show_more') ?? _fallbackTexts.show_more; + @override + String get show_more => + Crowdin.getText(localeName, 'show_more') ?? _fallbackTexts.show_more; - @override - String get show_less => Crowdin.getText(localeName, 'show_less') ?? _fallbackTexts.show_less; + @override + String get show_less => + Crowdin.getText(localeName, 'show_less') ?? _fallbackTexts.show_less; - @override - String get downloads_stats => Crowdin.getText(localeName, 'downloads_stats') ?? _fallbackTexts.downloads_stats; + @override + String get downloads_stats => + Crowdin.getText(localeName, 'downloads_stats') ?? + _fallbackTexts.downloads_stats; - @override - String get last_day => Crowdin.getText(localeName, 'last_day') ?? _fallbackTexts.last_day; + @override + String get last_day => + Crowdin.getText(localeName, 'last_day') ?? _fallbackTexts.last_day; - @override - String get last_30_days => Crowdin.getText(localeName, 'last_30_days') ?? _fallbackTexts.last_30_days; + @override + String get last_30_days => + Crowdin.getText(localeName, 'last_30_days') ?? + _fallbackTexts.last_30_days; - @override - String get last_365_days => Crowdin.getText(localeName, 'last_365_days') ?? _fallbackTexts.last_365_days; + @override + String get last_365_days => + Crowdin.getText(localeName, 'last_365_days') ?? + _fallbackTexts.last_365_days; - @override - String get not_available => Crowdin.getText(localeName, 'not_available') ?? _fallbackTexts.not_available; + @override + String get not_available => + Crowdin.getText(localeName, 'not_available') ?? + _fallbackTexts.not_available; - @override - String get download_failed => Crowdin.getText(localeName, 'download_failed') ?? _fallbackTexts.download_failed; + @override + String get download_failed => + Crowdin.getText(localeName, 'download_failed') ?? + _fallbackTexts.download_failed; - @override - String get installation_failed => Crowdin.getText(localeName, 'installation_failed') ?? _fallbackTexts.installation_failed; + @override + String get installation_failed => + Crowdin.getText(localeName, 'installation_failed') ?? + _fallbackTexts.installation_failed; - @override - String get uninstall_failed => Crowdin.getText(localeName, 'uninstall_failed') ?? _fallbackTexts.uninstall_failed; + @override + String get uninstall_failed => + Crowdin.getText(localeName, 'uninstall_failed') ?? + _fallbackTexts.uninstall_failed; - @override - String get open_failed => Crowdin.getText(localeName, 'open_failed') ?? _fallbackTexts.open_failed; + @override + String get open_failed => + Crowdin.getText(localeName, 'open_failed') ?? _fallbackTexts.open_failed; - @override - String get device => Crowdin.getText(localeName, 'device') ?? _fallbackTexts.device; + @override + String get device => + Crowdin.getText(localeName, 'device') ?? _fallbackTexts.device; - @override - String get recently_updated => Crowdin.getText(localeName, 'recently_updated') ?? _fallbackTexts.recently_updated; + @override + String get recently_updated => + Crowdin.getText(localeName, 'recently_updated') ?? + _fallbackTexts.recently_updated; - @override - String get refresh => Crowdin.getText(localeName, 'refresh') ?? _fallbackTexts.refresh; + @override + String get refresh => + Crowdin.getText(localeName, 'refresh') ?? _fallbackTexts.refresh; - @override - String get about => Crowdin.getText(localeName, 'about') ?? _fallbackTexts.about; + @override + String get about => + Crowdin.getText(localeName, 'about') ?? _fallbackTexts.about; - @override - String get refreshing_data => Crowdin.getText(localeName, 'refreshing_data') ?? _fallbackTexts.refreshing_data; + @override + String get refreshing_data => + Crowdin.getText(localeName, 'refreshing_data') ?? + _fallbackTexts.refreshing_data; - @override - String get data_refreshed => Crowdin.getText(localeName, 'data_refreshed') ?? _fallbackTexts.data_refreshed; + @override + String get data_refreshed => + Crowdin.getText(localeName, 'data_refreshed') ?? + _fallbackTexts.data_refreshed; - @override - String get refresh_failed => Crowdin.getText(localeName, 'refresh_failed') ?? _fallbackTexts.refresh_failed; + @override + String get refresh_failed => + Crowdin.getText(localeName, 'refresh_failed') ?? + _fallbackTexts.refresh_failed; - @override - String get loading_latest_apps => Crowdin.getText(localeName, 'loading_latest_apps') ?? _fallbackTexts.loading_latest_apps; + @override + String get loading_latest_apps => + Crowdin.getText(localeName, 'loading_latest_apps') ?? + _fallbackTexts.loading_latest_apps; - @override - String get latest_apps => Crowdin.getText(localeName, 'latest_apps') ?? _fallbackTexts.latest_apps; + @override + String get latest_apps => + Crowdin.getText(localeName, 'latest_apps') ?? _fallbackTexts.latest_apps; - @override - String get no_apps_found => Crowdin.getText(localeName, 'no_apps_found') ?? _fallbackTexts.no_apps_found; + @override + String get no_apps_found => + Crowdin.getText(localeName, 'no_apps_found') ?? + _fallbackTexts.no_apps_found; - @override - String get searching => Crowdin.getText(localeName, 'searching') ?? _fallbackTexts.searching; + @override + String get searching => + Crowdin.getText(localeName, 'searching') ?? _fallbackTexts.searching; - @override - String get setup_failed => Crowdin.getText(localeName, 'setup_failed') ?? _fallbackTexts.setup_failed; + @override + String get setup_failed => + Crowdin.getText(localeName, 'setup_failed') ?? + _fallbackTexts.setup_failed; - @override + @override String get back => Crowdin.getText(localeName, 'back') ?? _fallbackTexts.back; - @override - String get allow => Crowdin.getText(localeName, 'allow') ?? _fallbackTexts.allow; + @override + String get allow => + Crowdin.getText(localeName, 'allow') ?? _fallbackTexts.allow; - @override - String get manage_repositories => Crowdin.getText(localeName, 'manage_repositories') ?? _fallbackTexts.manage_repositories; + @override + String get manage_repositories => + Crowdin.getText(localeName, 'manage_repositories') ?? + _fallbackTexts.manage_repositories; - @override - String get enable_disable => Crowdin.getText(localeName, 'enable_disable') ?? _fallbackTexts.enable_disable; + @override + String get enable_disable => + Crowdin.getText(localeName, 'enable_disable') ?? + _fallbackTexts.enable_disable; - @override + @override String get edit => Crowdin.getText(localeName, 'edit') ?? _fallbackTexts.edit; - @override - String get delete => Crowdin.getText(localeName, 'delete') ?? _fallbackTexts.delete; + @override + String get delete => + Crowdin.getText(localeName, 'delete') ?? _fallbackTexts.delete; - @override - String get delete_repository => Crowdin.getText(localeName, 'delete_repository') ?? _fallbackTexts.delete_repository; + @override + String get delete_repository => + Crowdin.getText(localeName, 'delete_repository') ?? + _fallbackTexts.delete_repository; - @override - String delete_repository_confirm(Object name, Object repo) => Crowdin.getText(localeName, 'delete_repository_confirm', {'name': name, 'repo': repo}) ?? _fallbackTexts.delete_repository_confirm(name, repo); + @override + String delete_repository_confirm(Object name, Object repo) => + Crowdin.getText(localeName, 'delete_repository_confirm', { + 'name': name, + 'repo': repo, + }) ?? + _fallbackTexts.delete_repository_confirm(name, repo); - @override - String get updating_repository => Crowdin.getText(localeName, 'updating_repository') ?? _fallbackTexts.updating_repository; + @override + String get updating_repository => + Crowdin.getText(localeName, 'updating_repository') ?? + _fallbackTexts.updating_repository; - @override - String get touch_grass_message => Crowdin.getText(localeName, 'touch_grass_message') ?? _fallbackTexts.touch_grass_message; + @override + String get touch_grass_message => + Crowdin.getText(localeName, 'touch_grass_message') ?? + _fallbackTexts.touch_grass_message; - @override - String get add_repository => Crowdin.getText(localeName, 'add_repository') ?? _fallbackTexts.add_repository; + @override + String get add_repository => + Crowdin.getText(localeName, 'add_repository') ?? + _fallbackTexts.add_repository; - @override + @override String get add => Crowdin.getText(localeName, 'add') ?? _fallbackTexts.add; - @override + @override String get save => Crowdin.getText(localeName, 'save') ?? _fallbackTexts.save; - @override - String get enter_repository_name => Crowdin.getText(localeName, 'enter_repository_name') ?? _fallbackTexts.enter_repository_name; + @override + String get enter_repository_name => + Crowdin.getText(localeName, 'enter_repository_name') ?? + _fallbackTexts.enter_repository_name; - @override - String get enter_repository_url => Crowdin.getText(localeName, 'enter_repository_url') ?? _fallbackTexts.enter_repository_url; + @override + String get enter_repository_url => + Crowdin.getText(localeName, 'enter_repository_url') ?? + _fallbackTexts.enter_repository_url; - @override - String get edit_repository => Crowdin.getText(localeName, 'edit_repository') ?? _fallbackTexts.edit_repository; + @override + String get edit_repository => + Crowdin.getText(localeName, 'edit_repository') ?? + _fallbackTexts.edit_repository; - @override - String get loading_apps => Crowdin.getText(localeName, 'loading_apps') ?? _fallbackTexts.loading_apps; + @override + String get loading_apps => + Crowdin.getText(localeName, 'loading_apps') ?? + _fallbackTexts.loading_apps; - @override - String no_apps_in_category(Object category) => Crowdin.getText(localeName, 'no_apps_in_category', {'category':category}) ?? _fallbackTexts.no_apps_in_category(category); + @override + String no_apps_in_category(Object category) => + Crowdin.getText(localeName, 'no_apps_in_category', { + 'category': category, + }) ?? + _fallbackTexts.no_apps_in_category(category); - @override - String get loading_categories => Crowdin.getText(localeName, 'loading_categories') ?? _fallbackTexts.loading_categories; - - @override - String get no_categories_found => Crowdin.getText(localeName, 'no_categories_found') ?? _fallbackTexts.no_categories_found; + @override + String get loading_categories => + Crowdin.getText(localeName, 'loading_categories') ?? + _fallbackTexts.loading_categories; + @override + String get no_categories_found => + Crowdin.getText(localeName, 'no_categories_found') ?? + _fallbackTexts.no_categories_found; } -class _CrowdinLocalizationsDelegate extends LocalizationsDelegate { +class _CrowdinLocalizationsDelegate + extends LocalizationsDelegate { const _CrowdinLocalizationsDelegate(); @override - Future load(Locale locale) => - AppLocalizations.delegate.load(locale) - .then((fallback) => CrowdinLocalization(locale.toString(), fallback)); + Future load(Locale locale) => AppLocalizations.delegate + .load(locale) + .then((fallback) => CrowdinLocalization(locale.toString(), fallback)); @override - bool isSupported(Locale locale) => AppLocalizations.supportedLocales.contains(locale); + bool isSupported(Locale locale) => + AppLocalizations.supportedLocales.contains(locale); @override bool shouldReload(_CrowdinLocalizationsDelegate old) => false; diff --git a/lib/screens/app_details_screen.dart b/lib/screens/app_details_screen.dart index 68b0b65..acab714 100644 --- a/lib/screens/app_details_screen.dart +++ b/lib/screens/app_details_screen.dart @@ -269,7 +269,7 @@ class _AppDetailsScreenState extends State { if (availableRepos == null || availableRepos.isEmpty) return; // Capture the mounted context before showing dialog - final scaffoldMessenger = ScaffoldMessenger.of(context); + // final scaffoldMessenger = ScaffoldMessenger.of(context); await showModalBottomSheet( context: context, @@ -370,7 +370,7 @@ class _AppDetailsScreenState extends State { return Scaffold( body: CustomScrollView( slivers: [ - SliverAppBar.large( + SliverAppBar( pinned: true, centerTitle: false, title: Row( @@ -428,6 +428,7 @@ class _AppDetailsScreenState extends State { }, ), PopupMenuButton( + icon: const Icon(Symbols.more_vert), onSelected: (value) async { switch (value) { case 'website': diff --git a/lib/screens/library_screen.dart b/lib/screens/library_screen.dart index 23793aa..2511e5d 100644 --- a/lib/screens/library_screen.dart +++ b/lib/screens/library_screen.dart @@ -1,4 +1,3 @@ -import 'package:easy_localization/easy_localization.dart'; import 'package:florid/l10n/app_localizations.dart'; import 'package:florid/providers/app_provider.dart'; import 'package:florid/providers/repositories_provider.dart'; @@ -7,7 +6,7 @@ import 'package:florid/screens/home_screen.dart'; import 'package:florid/utils/menu_actions.dart'; import 'package:florid/widgets/f_tabbar.dart'; import 'package:flutter/material.dart'; -import 'package:material_symbols_icons/material_symbols_icons.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; class LibraryScreen extends StatefulWidget { @@ -37,67 +36,73 @@ class _LibraryScreenState extends State @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text(AppLocalizations.of(context)!.app_name), - backgroundColor: Theme.of(context).colorScheme.surfaceContainerLow, - surfaceTintColor: Theme.of(context).colorScheme.surfaceContainerLow, - actions: [ - PopupMenuButton( - onSelected: (value) { - switch (value) { - case 'refresh': - _refreshData(); - break; - case 'settings': - MenuActions.showSettings(context); - break; - case 'about': - MenuActions.showAbout(context); - break; - } - }, - itemBuilder: (context) => [ - PopupMenuItem( - value: 'refresh', - child: ListTile( - leading: const Icon(Symbols.refresh), - title: Text(AppLocalizations.of(context)!.refresh), - contentPadding: EdgeInsets.zero, - ), - ), - PopupMenuItem( - value: 'settings', - child: ListTile( - leading: const Icon(Symbols.settings), - title: Text(AppLocalizations.of(context)!.settings), - contentPadding: EdgeInsets.zero, - ), - ), - PopupMenuItem( - value: 'about', - child: ListTile( - leading: Icon(Symbols.info), - title: Text(AppLocalizations.of(context)!.about), - contentPadding: EdgeInsets.zero, - ), + body: CustomScrollView( + slivers: [ + SliverAppBar( + title: Text(AppLocalizations.of(context)!.app_name), + backgroundColor: Theme.of(context).colorScheme.surfaceContainerLow, + surfaceTintColor: Theme.of(context).colorScheme.surfaceContainerLow, + actions: [ + PopupMenuButton( + onSelected: (value) { + switch (value) { + case 'refresh': + _refreshData(); + break; + case 'settings': + MenuActions.showSettings(context); + break; + case 'about': + MenuActions.showAbout(context); + break; + } + }, + itemBuilder: (context) => [ + PopupMenuItem( + value: 'refresh', + child: ListTile( + leading: const Icon(Symbols.refresh), + title: Text(AppLocalizations.of(context)!.refresh), + contentPadding: EdgeInsets.zero, + ), + ), + PopupMenuItem( + value: 'settings', + child: ListTile( + leading: const Icon(Symbols.settings), + title: Text(AppLocalizations.of(context)!.settings), + contentPadding: EdgeInsets.zero, + ), + ), + PopupMenuItem( + value: 'about', + child: ListTile( + leading: Icon(Symbols.info), + title: Text(AppLocalizations.of(context)!.about), + contentPadding: EdgeInsets.zero, + ), + ), + ], ), ], + bottom: FTabBar( + controller: _tabController, + onTabChanged: (index) { + _tabController.animateTo(index); + }, + items: [ + FloridTabBarItem( + icon: Symbols.home, + label: AppLocalizations.of(context)!.home, + ), + FloridTabBarItem( + icon: Symbols.category, + label: AppLocalizations.of(context)!.categories, + ), + ], + ), ), - ], - bottom: FTabBar( - controller: _tabController, - onTabChanged: (index) { - _tabController.animateTo(index); - }, - items: [ - FloridTabBarItem(icon: Symbols.home, label: AppLocalizations.of(context)!.home), - FloridTabBarItem(icon: Symbols.category, label: AppLocalizations.of(context)!.categories), - ], - ), - ), - body: Column( - children: [ - Expanded( + SliverFillRemaining( child: TabBarView(controller: _tabController, children: tabs), ), ], diff --git a/lib/screens/repositories_screen.dart b/lib/screens/repositories_screen.dart index f73d10c..a816aa0 100644 --- a/lib/screens/repositories_screen.dart +++ b/lib/screens/repositories_screen.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:florid/l10n/app_localizations.dart'; import 'package:florid/widgets/m_list.dart'; import 'package:flutter/material.dart'; import 'package:material_symbols_icons/symbols.dart'; @@ -56,279 +57,308 @@ class _RepositoriesScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('Manage Repositories')), - body: Consumer( - builder: (context, provider, _) { - if (provider.isLoading) { - return const Center(child: CircularProgressIndicator()); - } + body: CustomScrollView( + slivers: [ + SliverAppBar.medium( + leading: BackButton( + style: ButtonStyle( + backgroundColor: WidgetStateColor.resolveWith( + (states) => Theme.of(context).colorScheme.surfaceContainer, + ), + ), + ), + title: Text(AppLocalizations.of(context)!.manage_repositories), + ), + SliverToBoxAdapter( + child: Consumer( + builder: (context, provider, _) { + if (provider.isLoading) { + return const Center(child: CircularProgressIndicator()); + } - return Padding( - padding: const EdgeInsets.symmetric(vertical: 24.0), - child: Column( - spacing: 16, - children: [ - // Error message if any - if (provider.error != null) - Padding( - padding: const EdgeInsets.all(8.0), - child: Material( - color: Colors.red.shade100, - borderRadius: BorderRadius.circular(8), - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Row( - children: [ - Icon(Symbols.error, color: Colors.red.shade700), - const SizedBox(width: 12), - Expanded( - child: Text( - provider.error ?? 'Unknown error', - style: TextStyle(color: Colors.red.shade700), + return Padding( + padding: const EdgeInsets.symmetric(vertical: 24.0), + child: Column( + spacing: 16, + children: [ + // Error message if any + if (provider.error != null) + Padding( + padding: const EdgeInsets.all(8.0), + child: Material( + color: Colors.red.shade100, + borderRadius: BorderRadius.circular(8), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Row( + children: [ + Icon( + Symbols.error, + color: Colors.red.shade700, + ), + const SizedBox(width: 12), + Expanded( + child: Text( + provider.error ?? 'Unknown error', + style: TextStyle( + color: Colors.red.shade700, + ), + ), + ), + IconButton( + icon: const Icon(Symbols.close), + onPressed: provider.clearError, + color: Colors.red.shade700, + ), + ], ), ), - IconButton( - icon: const Icon(Symbols.close), - onPressed: provider.clearError, - color: Colors.red.shade700, + ), + ), + // Presets section + if (_presets.isNotEmpty) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 4.0, + children: [ + MListHeader(title: 'Preset'), + MListViewBuilder( + itemCount: _presets.length, + itemBuilder: (index) { + final preset = _presets[index]; + final isAdded = provider.repositories.any( + (repo) => repo.url == preset['url'], + ); + + return MListItemData( + title: preset['name']!, + subtitle: preset['description']!, + onTap: () {}, + suffix: Switch( + value: isAdded, + onChanged: (newValue) async { + if (newValue) { + // Add the preset + final repoProvider = context + .read(); + await repoProvider.addRepository( + preset['name']!, + preset['url']!, + ); + // Only proceed with modal and refresh if addition succeeded (no error) + if (repoProvider.error == null && + context.mounted) { + await _runRepositoryActionWithDialog( + context, + () async { + final apiService = context + .read(); + final appProvider = context + .read(); + + await apiService + .clearRepositoryCache(); + await appProvider.refreshAll( + repositoriesProvider: + repoProvider, + ); + }, + ); + } + } else { + // Remove the preset + Repository? addedRepo; + try { + addedRepo = provider.repositories + .firstWhere( + (repo) => + repo.url == preset['url'], + ); + } catch (e) { + addedRepo = null; + } + if (addedRepo != null) { + final repoProvider = context + .read(); + repoProvider.deleteRepository( + addedRepo.id, + ); + // Only proceed with modal and refresh if deletion succeeded (no error) + if (repoProvider.error == null && + context.mounted) { + await _runRepositoryActionWithDialog( + context, + () async { + final apiService = context + .read(); + final appProvider = context + .read(); + + await apiService + .clearRepositoryCache(); + await appProvider.refreshAll( + repositoriesProvider: + repoProvider, + ); + }, + ); + } + } + } + }, + ), + ); + }, ), ], ), - ), - ), - ), - // Presets section - if (_presets.isNotEmpty) - Column( - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 4.0, - children: [ - MListHeader(title: 'Preset'), - MListViewBuilder( - itemCount: _presets.length, - itemBuilder: (index) { - final preset = _presets[index]; - final isAdded = provider.repositories.any( - (repo) => repo.url == preset['url'], - ); - - return MListItemData( - title: preset['name']!, - subtitle: preset['description']!, - onTap: () {}, - suffix: Switch( - value: isAdded, - onChanged: (newValue) async { - if (newValue) { - // Add the preset - final repoProvider = context - .read(); - await repoProvider.addRepository( - preset['name']!, - preset['url']!, - ); - // Only proceed with modal and refresh if addition succeeded (no error) - if (repoProvider.error == null && - context.mounted) { - await _runRepositoryActionWithDialog( - context, - () async { - final apiService = context - .read(); - final appProvider = context - .read(); - - await apiService.clearRepositoryCache(); - await appProvider.refreshAll( - repositoriesProvider: repoProvider, - ); - }, - ); - } - } else { - // Remove the preset - Repository? addedRepo; - try { - addedRepo = provider.repositories - .firstWhere( - (repo) => repo.url == preset['url'], - ); - } catch (e) { - addedRepo = null; - } - if (addedRepo != null) { - final repoProvider = context - .read(); - repoProvider.deleteRepository(addedRepo.id); - // Only proceed with modal and refresh if deletion succeeded (no error) - if (repoProvider.error == null && - context.mounted) { - await _runRepositoryActionWithDialog( - context, - () async { - final apiService = context - .read(); - final appProvider = context - .read(); - - await apiService - .clearRepositoryCache(); - await appProvider.refreshAll( - repositoriesProvider: repoProvider, - ); - }, - ); - } - } - } - }, - ), - ); - }, - ), - ], - ), - // Custom Repositories list - Column( - spacing: 4.0, - children: [ - MListHeader(title: 'Your Repositories'), - provider.repositories - .where( - (repo) => !_presets.any( - (preset) => preset['url'] == repo.url, - ), - ) - .toList() - .isEmpty - ? Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Symbols.inbox, - size: 64, - color: Colors.grey.shade400, - ), - const SizedBox(height: 16), - Text( - 'No custom repositories added', - style: Theme.of( - context, - ).textTheme.titleMedium, - ), - const SizedBox(height: 8), - Text( - 'Add a custom F-Droid repository to get started', - style: Theme.of(context).textTheme.bodySmall, - textAlign: TextAlign.center, - ), - ], - ), - ) - : MListViewBuilder( - itemCount: provider.repositories - .where( - (repo) => !_presets.any( - (preset) => preset['url'] == repo.url, - ), - ) - .length, - itemBuilder: (index) { - final customRepos = provider.repositories + // Custom Repositories list + Column( + spacing: 4.0, + children: [ + MListHeader(title: 'Your Repositories'), + provider.repositories .where( (repo) => !_presets.any( (preset) => preset['url'] == repo.url, ), ) - .toList(); - final repo = customRepos[index]; - return MListItemData( - title: repo.name, - subtitle: repo.url, - suffix: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Switch( - value: repo.isEnabled, - onChanged: (_) async { - await _toggleRepositoryWithDialog( + .toList() + .isEmpty + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Symbols.inbox, + size: 64, + color: Colors.grey.shade400, + ), + const SizedBox(height: 16), + Text( + 'No custom repositories added', + style: Theme.of( context, - provider, - repo.id, - ); - }, - ), - PopupMenuButton( - onSelected: (value) async { - switch (value) { - case 'edit': - _RepositoryListItem( - repository: repo, - )._showEditRepositoryDialog( - context, - repo, - ); - break; - case 'delete': - _RepositoryListItem( - repository: repo, - )._showDeleteConfirmation( - context, - repo, - provider, - ); - break; - } - }, - itemBuilder: (context) => [ - PopupMenuItem( - value: 'edit', - child: Row( - spacing: 16.0, - children: [ - Icon(Symbols.edit_rounded), - Text('Edit'), - ], - ), + ).textTheme.titleMedium, + ), + const SizedBox(height: 8), + Text( + 'Add a custom F-Droid repository to get started', + style: Theme.of( + context, + ).textTheme.bodySmall, + textAlign: TextAlign.center, + ), + ], + ), + ) + : MListViewBuilder( + itemCount: provider.repositories + .where( + (repo) => !_presets.any( + (preset) => preset['url'] == repo.url, ), - PopupMenuItem( - value: 'delete', - child: Row( - spacing: 16.0, - children: [ - Icon( - Symbols.delete_rounded, - fill: 1, - color: Theme.of( - context, - ).colorScheme.error, - ), - Text( - 'Delete', - style: TextStyle( - color: Theme.of( + ) + .length, + itemBuilder: (index) { + final customRepos = provider.repositories + .where( + (repo) => !_presets.any( + (preset) => + preset['url'] == repo.url, + ), + ) + .toList(); + final repo = customRepos[index]; + return MListItemData( + title: repo.name, + subtitle: repo.url, + suffix: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Switch( + value: repo.isEnabled, + onChanged: (_) async { + await _toggleRepositoryWithDialog( + context, + provider, + repo.id, + ); + }, + ), + PopupMenuButton( + onSelected: (value) async { + switch (value) { + case 'edit': + _RepositoryListItem( + repository: repo, + )._showEditRepositoryDialog( context, - ).colorScheme.error, + repo, + ); + break; + case 'delete': + _RepositoryListItem( + repository: repo, + )._showDeleteConfirmation( + context, + repo, + provider, + ); + break; + } + }, + itemBuilder: (context) => [ + PopupMenuItem( + value: 'edit', + child: Row( + spacing: 16.0, + children: [ + Icon(Symbols.edit_rounded), + Text('Edit'), + ], + ), + ), + PopupMenuItem( + value: 'delete', + child: Row( + spacing: 16.0, + children: [ + Icon( + Symbols.delete_rounded, + fill: 1, + color: Theme.of( + context, + ).colorScheme.error, + ), + Text( + 'Delete', + style: TextStyle( + color: Theme.of( + context, + ).colorScheme.error, + ), + ), + ], ), ), ], ), - ), - ], - ), - ], + ], + ), + onTap: () {}, + ); + }, ), - onTap: () {}, - ); - }, - ), - ], - ), - ], + ], + ), + ], + ), + ); + }, ), - ); - }, + ), + ], ), floatingActionButton: FloatingActionButton( onPressed: () => _showAddRepositoryDialog(context), diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 99953a1..085ed7e 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -75,267 +75,271 @@ class _SettingsScreenState extends State { return Consumer( builder: (context, settings, _) { return Scaffold( - appBar: AppBar(title: Text(AppLocalizations.of(context)!.settings)), - body: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 16, - children: [ - Column( - spacing: 4, - children: [ - MListHeader(title: 'Theme Mode'), - MRadioListView( - items: [ - MRadioListItemData( - title: 'Follow system theme', - subtitle: '', - value: ThemeMode.system, - ), - MRadioListItemData( - title: 'Light theme', - subtitle: '', - value: ThemeMode.light, - ), - MRadioListItemData( - title: 'Dark theme', - subtitle: '', - value: ThemeMode.dark, - ), - ], - groupValue: settings.themeMode, - onChanged: (mode) { - settings.setThemeMode(mode); - }, - ), - MListHeader(title: 'Theme Style'), - MRadioListView( - items: [ - MRadioListItemData( - title: 'Material style', - subtitle: '', - value: ThemeStyle.material, - ), - MRadioListItemData( - title: 'Florid style', - subtitle: '', - suffix: Container( - margin: const EdgeInsets.only(right: 8.0), - child: Material( - color: Theme.of(context).colorScheme.secondary, - borderRadius: BorderRadius.circular(99.0), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12.0, - vertical: 2.0, - ), - child: Text( - 'Beta', - style: TextStyle( - color: Theme.of( - context, - ).colorScheme.onSecondary, - ), + body: CustomScrollView( + slivers: [ + SliverAppBar.medium( + title: Text(AppLocalizations.of(context)!.settings), + ), + SliverToBoxAdapter( + child: Column( + spacing: 4, + children: [ + MListHeader(title: 'Theme Mode'), + MRadioListView( + items: [ + MRadioListItemData( + title: 'Follow system theme', + subtitle: '', + value: ThemeMode.system, + ), + MRadioListItemData( + title: 'Light theme', + subtitle: '', + value: ThemeMode.light, + ), + MRadioListItemData( + title: 'Dark theme', + subtitle: '', + value: ThemeMode.dark, + ), + ], + groupValue: settings.themeMode, + onChanged: (mode) { + settings.setThemeMode(mode); + }, + ), + MListHeader(title: 'Theme Style'), + MRadioListView( + items: [ + MRadioListItemData( + title: 'Material style', + subtitle: '', + value: ThemeStyle.material, + ), + MRadioListItemData( + title: 'Florid style', + subtitle: '', + suffix: Container( + margin: const EdgeInsets.only(right: 8.0), + child: Material( + color: Theme.of(context).colorScheme.secondary, + borderRadius: BorderRadius.circular(99.0), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 2.0, + ), + child: Text( + 'Beta', + style: TextStyle( + color: Theme.of( + context, + ).colorScheme.onSecondary, ), ), ), ), - value: ThemeStyle.florid, ), - ], - groupValue: settings.themeStyle, - onChanged: (style) { - settings.setThemeStyle(style); - }, - ), - MListView( - items: [ - MListItemData( - leading: Icon(Symbols.feedback), - title: 'Fedback on Florid theme', - subtitle: - 'Help improve the Florid theme by providing feedback', - onTap: () { - launchUrl( - Uri.parse( - 'https://github.com/Nandanrmenon/florid/discussions/5', - ), - ); - }, - suffix: Icon(Symbols.open_in_new), - ), - ], - ), - ], - ), - Column( - spacing: 4, - children: [ - MListHeader(title: 'General Settings'), - MListView( - items: [ - MListItemData( - leading: Icon(Symbols.language), - title: 'App content language', - onTap: () => _showLanguageDialog(context, settings), - subtitle: SettingsProvider.getLocaleDisplayName( - settings.locale, - ), - suffix: Icon(Symbols.chevron_right), - ), - MListItemData( - leading: Icon(Symbols.cloud), - title: 'Manage repositories', - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - const RepositoriesScreen(), - ), - ); - }, - subtitle: 'Add or remove F-Droid repositories', - suffix: Icon(Symbols.chevron_right), - ), - ], - ), - ], - ), - - Column( - spacing: 4, - children: [ - MListHeader(title: 'Downloads & Storage'), - MListView( - items: [ - MListItemData( - title: 'Auto-install after download', - onTap: () { - settings.setAutoInstallApk( - !settings.autoInstallApk, - ); - }, - subtitle: - 'Install APKs automatically once download finishes', - suffix: Switch( - value: settings.autoInstallApk, - onChanged: (value) { - settings.setAutoInstallApk(value); - }, - ), - ), - MListItemData( - title: 'Delete APK after install', - onTap: () { - settings.setAutoInstallApk( - !settings.autoInstallApk, - ); - }, - subtitle: - 'Remove installer files after successful installation', - suffix: Switch( - value: settings.autoDeleteApk, - onChanged: (value) { - settings.setAutoDeleteApk(value); - }, - ), - ), - ], - ), - MListView( - items: [ - MListItemData( - leading: Icon(Symbols.cleaning_services), - title: 'Clear repository cache', - onTap: () { - _clearRepoCache(context); - }, - subtitle: - 'Refresh app list and metadata on next load', - ), - MListItemData( - leading: Icon(Symbols.delete_sweep), - title: 'Clear APK downloads', - onTap: () { - _clearRepoCache(context); - }, - subtitle: - 'Remove downloaded installer files from storage', - ), - MListItemData( - leading: Icon(Symbols.image_not_supported), - title: 'Clear image cache', - onTap: () { - _clearRepoCache(context); - }, - subtitle: 'Remove cached icons and screenshots', - ), - ], - ), - ], - ), - - Column( - spacing: 4, - children: [ - MListHeader(title: 'About'), - MListView( - items: [ - MListItemData( - leading: Icon(Symbols.info), - title: 'Version', - subtitle: _appVersion.isEmpty - ? 'Loading…' - : _appVersion, - onTap: () {}, - ), - MListItemData( - leading: Icon(Symbols.code_rounded), - title: 'Source code', - onTap: () async { - final url = Uri.parse( - 'https://github.com/Nandanrmenon/florid', - ); - if (await canLaunchUrl(url)) { - await launchUrl(url); - } - }, - ), - MListItemData( - leading: Icon(Symbols.bug_report_rounded), - title: 'Report an issue', - onTap: () async { - final url = Uri.parse( - 'https://github.com/Nandanrmenon/florid/issues/new?template=bug_report.md', - ); - if (await canLaunchUrl(url)) { - await launchUrl(url); - } - }, - ), - MListItemData( - leading: Icon(Symbols.share), - title: 'Share Florid', - onTap: () { - SharePlus.instance.share( - ShareParams( - title: 'Check out Florid!', - text: - 'A modern F-Droid client! https://github.com/Nandanrmenon/florid', - ), - ); - }, - ), - ], - ), - ], - ), - ], + value: ThemeStyle.florid, + ), + ], + groupValue: settings.themeStyle, + onChanged: (style) { + settings.setThemeStyle(style); + }, + ), + MListView( + items: [ + MListItemData( + leading: Icon(Symbols.feedback), + title: 'Fedback on Florid theme', + subtitle: + 'Help improve the Florid theme by providing feedback', + onTap: () { + launchUrl( + Uri.parse( + 'https://github.com/Nandanrmenon/florid/discussions/5', + ), + ); + }, + suffix: Icon(Symbols.open_in_new), + ), + ], + ), + ], + ), ), - ), + SliverToBoxAdapter( + child: Column( + spacing: 4, + children: [ + MListHeader(title: 'General Settings'), + MListView( + items: [ + MListItemData( + leading: Icon(Symbols.language), + title: 'App content language', + onTap: () => _showLanguageDialog(context, settings), + subtitle: SettingsProvider.getLocaleDisplayName( + settings.locale, + ), + suffix: Icon(Symbols.chevron_right), + ), + MListItemData( + leading: Icon(Symbols.cloud), + title: 'Manage repositories', + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + const RepositoriesScreen(), + ), + ); + }, + subtitle: 'Add or remove F-Droid repositories', + suffix: Icon(Symbols.chevron_right), + ), + ], + ), + ], + ), + ), + + SliverToBoxAdapter( + child: Column( + spacing: 4, + children: [ + MListHeader(title: 'Downloads & Storage'), + MListView( + items: [ + MListItemData( + title: 'Auto-install after download', + onTap: () { + settings.setAutoInstallApk( + !settings.autoInstallApk, + ); + }, + subtitle: + 'Install APKs automatically once download finishes', + suffix: Switch( + value: settings.autoInstallApk, + onChanged: (value) { + settings.setAutoInstallApk(value); + }, + ), + ), + MListItemData( + title: 'Delete APK after install', + onTap: () { + settings.setAutoInstallApk( + !settings.autoInstallApk, + ); + }, + subtitle: + 'Remove installer files after successful installation', + suffix: Switch( + value: settings.autoDeleteApk, + onChanged: (value) { + settings.setAutoDeleteApk(value); + }, + ), + ), + ], + ), + MListView( + items: [ + MListItemData( + leading: Icon(Symbols.cleaning_services), + title: 'Clear repository cache', + onTap: () { + _clearRepoCache(context); + }, + subtitle: + 'Refresh app list and metadata on next load', + ), + MListItemData( + leading: Icon(Symbols.delete_sweep), + title: 'Clear APK downloads', + onTap: () { + _clearRepoCache(context); + }, + subtitle: + 'Remove downloaded installer files from storage', + ), + MListItemData( + leading: Icon(Symbols.image_not_supported), + title: 'Clear image cache', + onTap: () { + _clearRepoCache(context); + }, + subtitle: 'Remove cached icons and screenshots', + ), + ], + ), + ], + ), + ), + + SliverToBoxAdapter( + child: Column( + spacing: 4, + children: [ + MListHeader(title: 'About'), + MListView( + items: [ + MListItemData( + leading: Icon(Symbols.info), + title: 'Version', + subtitle: _appVersion.isEmpty + ? 'Loading…' + : _appVersion, + onTap: () {}, + ), + MListItemData( + leading: Icon(Symbols.code_rounded), + title: 'Source code', + onTap: () async { + final url = Uri.parse( + 'https://github.com/Nandanrmenon/florid', + ); + if (await canLaunchUrl(url)) { + await launchUrl(url); + } + }, + ), + MListItemData( + leading: Icon(Symbols.bug_report_rounded), + title: 'Report an issue', + onTap: () async { + final url = Uri.parse( + 'https://github.com/Nandanrmenon/florid/issues/new?template=bug_report.md', + ); + if (await canLaunchUrl(url)) { + await launchUrl(url); + } + }, + ), + MListItemData( + leading: Icon(Symbols.share), + title: 'Share Florid', + onTap: () { + SharePlus.instance.share( + ShareParams( + title: 'Check out Florid!', + text: + 'A modern F-Droid client! https://github.com/Nandanrmenon/florid', + ), + ); + }, + ), + ], + ), + ], + ), + ), + SliverToBoxAdapter(child: const SizedBox(height: 32)), + ], ), ); }, diff --git a/lib/themes/app_themes.dart b/lib/themes/app_themes.dart index 6c01c27..7aa6a25 100644 --- a/lib/themes/app_themes.dart +++ b/lib/themes/app_themes.dart @@ -1,5 +1,6 @@ import 'package:florid/constants.dart'; import 'package:flutter/material.dart'; +import 'package:material_symbols_icons/symbols.dart'; class AppThemes { // Material Theme (Original) @@ -91,6 +92,15 @@ class AppThemes { brightness: Brightness.light, ).onSurface, ), + backgroundColor: ColorScheme.fromSeed( + seedColor: kAppColor, + brightness: Brightness.light, + ).surface, + surfaceTintColor: ColorScheme.fromSeed( + seedColor: kAppColor, + brightness: Brightness.light, + ).surfaceContainerHigh, + elevation: 0, ), floatingActionButtonTheme: FloatingActionButtonThemeData( elevation: 0, @@ -140,7 +150,29 @@ class AppThemes { ), ), ), + iconButtonTheme: IconButtonThemeData( + style: IconButton.styleFrom( + backgroundColor: ColorScheme.fromSeed( + seedColor: kAppColor, + brightness: Brightness.light, + ).surfaceContainerLow, + ), + ), switchTheme: SwitchThemeData( + thumbIcon: WidgetStateProperty.resolveWith(( + Set states, + ) { + if (states.contains(WidgetState.selected)) { + return Icon( + Symbols.check, + color: ColorScheme.fromSeed( + seedColor: kAppColor, + brightness: Brightness.light, + ).onPrimary, + ); + } + return const Icon(Symbols.close); + }), thumbColor: WidgetStateProperty.resolveWith(( Set states, ) { @@ -148,7 +180,7 @@ class AppThemes { return ColorScheme.fromSeed( seedColor: kAppColor, brightness: Brightness.light, - ).primaryContainer; + ).primary; } return null; // Use the default thumb color }), @@ -159,14 +191,26 @@ class AppThemes { return ColorScheme.fromSeed( seedColor: kAppColor, brightness: Brightness.light, - ).secondary; + ).surface; } - return null; // Use the default track color + // Change the default track color to a custom color + return ColorScheme.fromSeed( + seedColor: kAppColor, + brightness: Brightness.light, + ).surfaceContainerLowest; }), trackOutlineWidth: WidgetStateProperty.resolveWith(( Set states, ) { - return 1; + return 2; + }), + trackOutlineColor: WidgetStateProperty.resolveWith(( + Set states, + ) { + return ColorScheme.fromSeed( + seedColor: kAppColor, + brightness: Brightness.light, + ).surfaceContainerHighest; }), ), navigationBarTheme: NavigationBarThemeData( @@ -283,6 +327,14 @@ class AppThemes { brightness: Brightness.dark, ).onSurface, ), + backgroundColor: ColorScheme.fromSeed( + seedColor: kAppColor, + brightness: Brightness.dark, + ).surface, + surfaceTintColor: ColorScheme.fromSeed( + seedColor: kAppColor, + brightness: Brightness.dark, + ).surfaceContainerHigh, ), floatingActionButtonTheme: FloatingActionButtonThemeData( elevation: 0, @@ -336,6 +388,14 @@ class AppThemes { ), ), switchTheme: SwitchThemeData( + thumbIcon: WidgetStateProperty.resolveWith(( + Set states, + ) { + if (states.contains(WidgetState.selected)) { + return const Icon(Symbols.check); + } + return const Icon(Symbols.close); + }), thumbColor: WidgetStateProperty.resolveWith(( Set states, ) { @@ -354,14 +414,25 @@ class AppThemes { return ColorScheme.fromSeed( seedColor: kAppColor, brightness: Brightness.dark, - ).secondary; + ).surface; } - return null; // Use the default track color + return ColorScheme.fromSeed( + seedColor: kAppColor, + brightness: Brightness.dark, + ).surfaceContainerLowest; }), trackOutlineWidth: WidgetStateProperty.resolveWith(( Set states, ) { - return 1; + return 2; + }), + trackOutlineColor: WidgetStateProperty.resolveWith(( + Set states, + ) { + return ColorScheme.fromSeed( + seedColor: kAppColor, + brightness: Brightness.dark, + ).surfaceContainerHighest; }), ), navigationBarTheme: NavigationBarThemeData(