From 6669a06e538b9ff6109eb4127c9f92d0c32a96dc Mon Sep 17 00:00:00 2001 From: PartyDonut <42371342+PartyDonut@users.noreply.github.com> Date: Sat, 19 Oct 2024 15:25:32 +0200 Subject: [PATCH] fix: padding issues (#47) ## Pull Request Description This fixes a bunch of padding issues, and also improves padding in other areas. Issue Number: #29 --------- Co-authored-by: PartyDonut --- .../collections/add_to_collection.dart | 186 +++++---- lib/screens/metadata/identifty_screen.dart | 353 ++++++++---------- lib/screens/metadata/refresh_metadata.dart | 34 +- lib/screens/playlists/add_to_playlists.dart | 200 +++++----- lib/screens/settings/settings_list_tile.dart | 69 ++-- lib/screens/settings/settings_scaffold.dart | 11 +- .../widgets/settings_label_divider.dart | 7 +- lib/screens/shared/adaptive_dialog.dart | 9 +- lib/screens/shared/default_titlebar.dart | 10 +- lib/screens/shared/detail_scaffold.dart | 144 +++---- lib/screens/syncing/sync_item_details.dart | 344 ++++++++--------- .../syncing/widgets/sync_markedfordelete.dart | 9 +- .../video_player/video_player_controls.dart | 78 ++-- lib/util/adaptive_layout.dart | 36 +- .../components/navigation_body.dart | 130 +++---- lib/widgets/shared/alert_content.dart | 49 +++ lib/widgets/shared/item_actions.dart | 50 ++- lib/widgets/shared/modal_bottom_sheet.dart | 84 +++-- 18 files changed, 926 insertions(+), 877 deletions(-) create mode 100644 lib/widgets/shared/alert_content.dart diff --git a/lib/screens/collections/add_to_collection.dart b/lib/screens/collections/add_to_collection.dart index e64f64a..4882327 100644 --- a/lib/screens/collections/add_to_collection.dart +++ b/lib/screens/collections/add_to_collection.dart @@ -1,14 +1,16 @@ -import 'package:ficonsax/ficonsax.dart'; -import 'package:fladder/providers/collections_provider.dart'; -import 'package:fladder/screens/shared/adaptive_dialog.dart'; -import 'package:fladder/screens/shared/fladder_snackbar.dart'; -import 'package:fladder/util/localization_helper.dart'; -import 'package:fladder/widgets/shared/modal_bottom_sheet.dart'; import 'package:flutter/material.dart'; + +import 'package:ficonsax/ficonsax.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:fladder/models/item_base_model.dart'; +import 'package:fladder/providers/collections_provider.dart'; +import 'package:fladder/screens/shared/adaptive_dialog.dart'; +import 'package:fladder/screens/shared/fladder_snackbar.dart'; import 'package:fladder/screens/shared/outlined_text_field.dart'; +import 'package:fladder/util/localization_helper.dart'; +import 'package:fladder/widgets/shared/alert_content.dart'; +import 'package:fladder/widgets/shared/modal_bottom_sheet.dart'; Future addItemToCollection(BuildContext context, List item) { return showDialogAdaptive( @@ -40,71 +42,62 @@ class _AddToCollectionState extends ConsumerState { @override Widget build(BuildContext context) { final collectonOptions = ref.watch(provider); - return Card( - color: Theme.of(context).colorScheme.surface, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox(height: MediaQuery.paddingOf(context).top), - Container( - color: Theme.of(context).colorScheme.surface, - child: Column( + return ActionContent( + title: Container( + color: Theme.of(context).colorScheme.surface, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (widget.items.length == 1) - Text( - 'Add to collection', - style: Theme.of(context).textTheme.titleLarge, - ) - else - Text( - 'Add ${widget.items.length} item(s) to collection', - style: Theme.of(context).textTheme.titleLarge, - ), - IconButton( - onPressed: () => ref.read(provider.notifier).setItems(widget.items), - icon: const Icon(IconsaxOutline.refresh), - ) - ], + if (widget.items.length == 1) + Text( + 'Add to collection', + style: Theme.of(context).textTheme.titleLarge, + ) + else + Text( + 'Add ${widget.items.length} item(s) to collection', + style: Theme.of(context).textTheme.titleLarge, ), - ), - if (widget.items.length == 1) ItemBottomSheetPreview(item: widget.items.first), - ], - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Row( - children: [ - Flexible( - child: OutlinedTextField( - label: 'New collection', - controller: controller, - onChanged: (value) => setState(() {}), - ), - ), - const SizedBox(width: 32), IconButton( - onPressed: controller.text.isNotEmpty - ? () async { - await ref.read(provider.notifier).addToNewCollection( - name: controller.text, - ); - setState(() => controller.text = ''); - } - : null, - icon: const Icon(Icons.add_rounded)), - const SizedBox(width: 4), + onPressed: () => ref.read(provider.notifier).setItems(widget.items), + icon: const Icon(IconsaxOutline.refresh), + ) ], ), + if (widget.items.length == 1) ItemBottomSheetPreview(item: widget.items.first), + ], + ), + ), + child: Column( + children: [ + Row( + children: [ + Flexible( + child: OutlinedTextField( + label: 'New collection', + controller: controller, + onChanged: (value) => setState(() {}), + ), + ), + const SizedBox(width: 32), + IconButton( + onPressed: controller.text.isNotEmpty + ? () async { + await ref.read(provider.notifier).addToNewCollection( + name: controller.text, + ); + setState(() => controller.text = ''); + } + : null, + icon: const Icon(Icons.add_rounded)), + ], ), Flexible( child: ListView( shrinkWrap: true, + padding: const EdgeInsets.symmetric(vertical: 12), children: [ ...collectonOptions.collections.entries.map( (e) { @@ -125,25 +118,37 @@ class _AddToCollectionState extends ConsumerState { }, ); } else { - return ListTile( - title: Text(e.key.name), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - ElevatedButton( - onPressed: () async { - final response = - await ref.read(provider.notifier).addToCollection(boxSet: e.key, add: true); - if (context.mounted) { - fladderSnackbar(context, - title: response.isSuccessful - ? "Added to ${e.key.name} collection" - : 'Unable to add to ${e.key.name} collection - (${response.statusCode}) - ${response.base.reasonPhrase}'); - } - }, - child: Icon(Icons.add_rounded, color: Theme.of(context).colorScheme.primary), + return Container( + margin: const EdgeInsets.all(8), + child: Card( + elevation: 0, + child: Padding( + padding: const EdgeInsets.all(8), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Text( + e.key.name, + style: Theme.of(context).textTheme.bodyLarge, + ), + ), + ElevatedButton( + onPressed: () async { + final response = + await ref.read(provider.notifier).addToCollection(boxSet: e.key, add: true); + if (context.mounted) { + fladderSnackbar(context, + title: response.isSuccessful + ? "Added to ${e.key.name} collection" + : 'Unable to add to ${e.key.name} collection - (${response.statusCode}) - ${response.base.reasonPhrase}'); + } + }, + child: Icon(Icons.add_rounded, color: Theme.of(context).colorScheme.primary), + ), + ], ), - ], + ), ), ); } @@ -152,23 +157,14 @@ class _AddToCollectionState extends ConsumerState { ], ), ), - Container( - color: Theme.of(context).colorScheme.surface, - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - FilledButton( - onPressed: () => Navigator.of(context).pop(), - child: Text(context.localized.close), - ) - ], - ), - ), - ), ], ), + actions: [ + FilledButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(context.localized.close), + ) + ], ); } } diff --git a/lib/screens/metadata/identifty_screen.dart b/lib/screens/metadata/identifty_screen.dart index 127508e..25730ec 100644 --- a/lib/screens/metadata/identifty_screen.dart +++ b/lib/screens/metadata/identifty_screen.dart @@ -13,6 +13,7 @@ import 'package:fladder/screens/shared/focused_outlined_text_field.dart'; import 'package:fladder/screens/shared/media/external_urls.dart'; import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/string_extensions.dart'; +import 'package:fladder/widgets/shared/alert_content.dart'; Future showIdentifyScreen(BuildContext context, ItemBaseModel item) async { return showDialogAdaptive( @@ -50,215 +51,181 @@ class _IdentifyScreenState extends ConsumerState with TickerProv final state = ref.watch(provider); final posters = state.results; final processing = state.processing; - return MediaQuery.removePadding( - context: context, - child: Card( + return ActionContent( + showDividers: false, + title: Container( color: Theme.of(context).colorScheme.surface, child: Column( mainAxisSize: MainAxisSize.min, children: [ - Container( - color: Theme.of(context).colorScheme.surface, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox(height: MediaQuery.paddingOf(context).top), - Padding( - padding: const EdgeInsets.all(16.0), - child: Row( - children: [ - Text( - widget.item.detailedName(context) ?? widget.item.name, - style: Theme.of(context).textTheme.titleLarge, - ), - const Spacer(), - IconButton( - onPressed: () async => await ref.read(provider.notifier).fetchInformation(), - icon: const Icon(IconsaxOutline.refresh)), - ], - ), - ), - TabBar( - isScrollable: true, - controller: tabController, - onTap: (value) { - setState(() { - currentTab = value; - }); - }, - tabs: [ - Tab( - text: context.localized.search, - ), - Tab( - text: context.localized.result, - ) - ], - ) - ], - ), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.all(16), - child: TabBarView( - controller: tabController, - children: [ - inputFields(state), - if (posters.isEmpty) - Center( - child: processing - ? const CircularProgressIndicator.adaptive(strokeCap: StrokeCap.round) - : Text(context.localized.noResults), - ) - else - Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Text(context.localized.replaceAllImages), - const SizedBox(width: 16), - Switch.adaptive( - value: state.replaceAllImages, - onChanged: (value) { - ref - .read(provider.notifier) - .update((state) => state.copyWith(replaceAllImages: value)); - }, - ), - ], - ), - Flexible( - child: ListView( - shrinkWrap: true, - children: posters - .map((result) => ListTile( - title: Row( - children: [ - SizedBox( - width: 75, - child: Card( - child: CachedNetworkImage( - imageUrl: result.imageUrl ?? "", - errorWidget: (context, url, error) => SizedBox( - height: 75, - child: Card( - child: Center( - child: Text(result.name?.getInitials() ?? ""), - ), - ), - ), - ), - ), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "${result.name ?? ""}${result.productionYear != null ? "(${result.productionYear})" : ""}"), - Opacity( - opacity: 0.65, - child: Text(result.providerIds?.keys.join(',') ?? "")) - ], - ), - ), - Tooltip( - message: context.localized.openWebLink, - child: IconButton( - onPressed: () { - final providerKeyEntry = result.providerIds?.entries.first; - final providerKey = providerKeyEntry?.key; - final providerValue = providerKeyEntry?.value; - - final externalId = state.externalIds - .firstWhereOrNull((element) => element.key == providerKey) - ?.urlFormatString; - - final url = - externalId?.replaceAll("{0}", providerValue?.toString() ?? ""); - - launchUrl(context, url ?? ""); - }, - icon: const Icon(Icons.launch_rounded)), - ), - Tooltip( - message: "Select result", - child: IconButton( - onPressed: !processing - ? () async { - final response = - await ref.read(provider.notifier).setIdentity(result); - if (response?.isSuccessful == true) { - fladderSnackbar(context, - title: - context.localized.setIdentityTo(result.name ?? "")); - } else { - fladderSnackbarResponse(context, response, - altTitle: context.localized.somethingWentWrong); - } - - Navigator.of(context).pop(); - } - : null, - icon: const Icon(Icons.save_alt_rounded), - ), - ) - ], - ), - )) - .toList(), - ), - ), - ], - ) - ], + Row( + children: [ + Text( + widget.item.detailedName(context) ?? widget.item.name, + style: Theme.of(context).textTheme.titleLarge, ), - ), + const Spacer(), + IconButton( + onPressed: () async => await ref.read(provider.notifier).fetchInformation(), + icon: const Icon(IconsaxOutline.refresh)), + ], ), - Container( - color: Theme.of(context).colorScheme.surface, - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.end, - children: [ - ElevatedButton(onPressed: () => Navigator.of(context).pop(), child: Text(context.localized.cancel)), - const SizedBox(width: 16), - FilledButton( - onPressed: !processing - ? () async { - await ref.read(provider.notifier).remoteSearch(); - tabController.animateTo(1); - } - : null, - child: processing - ? SizedBox( - width: 21, - height: 21, - child: CircularProgressIndicator.adaptive( - backgroundColor: Theme.of(context).colorScheme.onPrimary, strokeCap: StrokeCap.round), - ) - : Text(context.localized.search), - ), - SizedBox(height: MediaQuery.paddingOf(context).bottom), - ], + TabBar( + isScrollable: true, + controller: tabController, + onTap: (value) { + setState(() { + currentTab = value; + }); + }, + tabs: [ + Tab( + text: context.localized.search, ), - ), - ), + Tab( + text: context.localized.result, + ) + ], + ) ], ), ), + child: TabBarView( + controller: tabController, + children: [ + inputFields(state), + if (posters.isEmpty) + Center( + child: processing + ? const CircularProgressIndicator.adaptive(strokeCap: StrokeCap.round) + : Text(context.localized.noResults), + ) + else + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text(context.localized.replaceAllImages), + const SizedBox(width: 16), + Switch.adaptive( + value: state.replaceAllImages, + onChanged: (value) { + ref.read(provider.notifier).update((state) => state.copyWith(replaceAllImages: value)); + }, + ), + ], + ), + Flexible( + child: ListView( + shrinkWrap: true, + children: posters + .map((result) => ListTile( + title: Row( + children: [ + SizedBox( + width: 75, + child: Card( + child: CachedNetworkImage( + imageUrl: result.imageUrl ?? "", + errorWidget: (context, url, error) => SizedBox( + height: 75, + child: Card( + child: Center( + child: Text(result.name?.getInitials() ?? ""), + ), + ), + ), + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "${result.name ?? ""}${result.productionYear != null ? "(${result.productionYear})" : ""}"), + Opacity(opacity: 0.65, child: Text(result.providerIds?.keys.join(',') ?? "")) + ], + ), + ), + Tooltip( + message: context.localized.openWebLink, + child: IconButton( + onPressed: () { + final providerKeyEntry = result.providerIds?.entries.first; + final providerKey = providerKeyEntry?.key; + final providerValue = providerKeyEntry?.value; + + final externalId = state.externalIds + .firstWhereOrNull((element) => element.key == providerKey) + ?.urlFormatString; + + final url = externalId?.replaceAll("{0}", providerValue?.toString() ?? ""); + + launchUrl(context, url ?? ""); + }, + icon: const Icon(Icons.launch_rounded)), + ), + Tooltip( + message: "Select result", + child: IconButton( + onPressed: !processing + ? () async { + final response = await ref.read(provider.notifier).setIdentity(result); + if (response?.isSuccessful == true) { + fladderSnackbar(context, + title: context.localized.setIdentityTo(result.name ?? "")); + } else { + fladderSnackbarResponse(context, response, + altTitle: context.localized.somethingWentWrong); + } + + Navigator.of(context).pop(); + } + : null, + icon: const Icon(Icons.save_alt_rounded), + ), + ) + ], + ), + )) + .toList(), + ), + ), + ], + ) + ], + ), + actions: [ + ElevatedButton(onPressed: () => Navigator.of(context).pop(), child: Text(context.localized.cancel)), + const SizedBox(width: 16), + FilledButton( + onPressed: !processing + ? () async { + await ref.read(provider.notifier).remoteSearch(); + tabController.animateTo(1); + } + : null, + child: processing + ? SizedBox( + width: 21, + height: 21, + child: CircularProgressIndicator.adaptive( + backgroundColor: Theme.of(context).colorScheme.onPrimary, strokeCap: StrokeCap.round), + ) + : Text(context.localized.search), + ), + ], ); } ListView inputFields(IdentifyModel state) { return ListView( shrinkWrap: true, + padding: EdgeInsets.zero, children: [ Row( mainAxisAlignment: MainAxisAlignment.end, diff --git a/lib/screens/metadata/refresh_metadata.dart b/lib/screens/metadata/refresh_metadata.dart index a4980a2..9494cbc 100644 --- a/lib/screens/metadata/refresh_metadata.dart +++ b/lib/screens/metadata/refresh_metadata.dart @@ -1,3 +1,7 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; + import 'package:fladder/jellyfin/enum_models.dart'; import 'package:fladder/providers/user_provider.dart'; import 'package:fladder/screens/settings/settings_list_tile.dart'; @@ -5,8 +9,6 @@ import 'package:fladder/screens/shared/fladder_snackbar.dart'; import 'package:fladder/util/adaptive_layout.dart'; import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/widgets/shared/enum_selection.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; Future showRefreshPopup(BuildContext context, String itemId, String itemName) async { return showDialog( @@ -60,18 +62,22 @@ class _RefreshPopupDialogState extends ConsumerState { ), ), ), + const Divider(), const SizedBox(height: 16), - EnumBox( - current: refreshMode.label(context), - itemBuilder: (context) => MetadataRefresh.values - .map((value) => PopupMenuItem( - value: value, - child: Text(value.label(context)), - onTap: () => setState(() { - refreshMode = value; - }), - )) - .toList(), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: EnumBox( + current: refreshMode.label(context), + itemBuilder: (context) => MetadataRefresh.values + .map((value) => PopupMenuItem( + value: value, + child: Text(value.label(context)), + onTap: () => setState(() { + refreshMode = value; + }), + )) + .toList(), + ), ), if (refreshMode != MetadataRefresh.defaultRefresh) SettingsListTile( @@ -87,7 +93,7 @@ class _RefreshPopupDialogState extends ConsumerState { style: Theme.of(context).textTheme.bodyLarge, ), ), - const SizedBox(height: 16), + const Divider(), Container( color: Theme.of(context).colorScheme.surface, child: Padding( diff --git a/lib/screens/playlists/add_to_playlists.dart b/lib/screens/playlists/add_to_playlists.dart index 16bcd73..2771083 100644 --- a/lib/screens/playlists/add_to_playlists.dart +++ b/lib/screens/playlists/add_to_playlists.dart @@ -1,14 +1,16 @@ -import 'package:ficonsax/ficonsax.dart'; -import 'package:fladder/providers/playlist_provider.dart'; -import 'package:fladder/screens/shared/adaptive_dialog.dart'; -import 'package:fladder/screens/shared/fladder_snackbar.dart'; -import 'package:fladder/util/localization_helper.dart'; -import 'package:fladder/widgets/shared/modal_bottom_sheet.dart'; import 'package:flutter/material.dart'; + +import 'package:ficonsax/ficonsax.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:fladder/models/item_base_model.dart'; +import 'package:fladder/providers/playlist_provider.dart'; +import 'package:fladder/screens/shared/adaptive_dialog.dart'; +import 'package:fladder/screens/shared/fladder_snackbar.dart'; import 'package:fladder/screens/shared/outlined_text_field.dart'; +import 'package:fladder/util/localization_helper.dart'; +import 'package:fladder/widgets/shared/alert_content.dart'; +import 'package:fladder/widgets/shared/modal_bottom_sheet.dart'; Future addItemToPlaylist(BuildContext context, List item) { return showDialogAdaptive(context: context, builder: (context) => AddToPlaylist(items: item)); @@ -35,125 +37,117 @@ class _AddToPlaylistState extends ConsumerState { @override Widget build(BuildContext context) { final collectonOptions = ref.watch(provider); - return Card( - color: Theme.of(context).colorScheme.surface, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox(height: MediaQuery.paddingOf(context).top), - Container( - color: Theme.of(context).colorScheme.surface, - child: Column( + return ActionContent( + title: Container( + color: Theme.of(context).colorScheme.surface, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (widget.items.length == 1) - Text( - 'Add to collection', - style: Theme.of(context).textTheme.titleLarge, - ) - else - Text( - 'Add ${widget.items.length} item(s) to collection', - style: Theme.of(context).textTheme.titleLarge, - ), - IconButton( - onPressed: () => ref.read(provider.notifier).setItems(widget.items), - icon: const Icon(IconsaxOutline.refresh), - ) - ], + if (widget.items.length == 1) + Text( + 'Add to collection', + style: Theme.of(context).textTheme.titleLarge, + ) + else + Text( + 'Add ${widget.items.length} item(s) to collection', + style: Theme.of(context).textTheme.titleLarge, ), - ), - if (widget.items.length == 1) ItemBottomSheetPreview(item: widget.items.first), - ], - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Row( - children: [ - Flexible( - child: OutlinedTextField( - label: 'New Playlist', - controller: controller, - onChanged: (value) => setState(() {}), - ), - ), - const SizedBox(width: 32), IconButton( - onPressed: controller.text.isNotEmpty - ? () async { - final response = await ref.read(provider.notifier).addToNewPlaylist( - name: controller.text, - ); - if (context.mounted) { - fladderSnackbar(context, - title: response.isSuccessful - ? "Added to new ${controller.text} playlist" - : 'Unable to create new playlist - (${response.statusCode}) - ${response.base.reasonPhrase}'); - } - setState(() => controller.text = ''); - } - : null, - icon: const Icon(Icons.add_rounded)), - const SizedBox(width: 8), + onPressed: () => ref.read(provider.notifier).setItems(widget.items), + icon: const Icon(IconsaxOutline.refresh), + ) ], ), + if (widget.items.length == 1) ItemBottomSheetPreview(item: widget.items.first), + ], + ), + ), + child: Column( + children: [ + Row( + children: [ + Flexible( + child: OutlinedTextField( + label: 'New Playlist', + controller: controller, + onChanged: (value) => setState(() {}), + ), + ), + const SizedBox(width: 32), + IconButton( + onPressed: controller.text.isNotEmpty + ? () async { + final response = await ref.read(provider.notifier).addToNewPlaylist( + name: controller.text, + ); + if (context.mounted) { + fladderSnackbar(context, + title: response.isSuccessful + ? "Added to new ${controller.text} playlist" + : 'Unable to create new playlist - (${response.statusCode}) - ${response.base.reasonPhrase}'); + } + setState(() => controller.text = ''); + } + : null, + icon: const Icon(Icons.add_rounded)), + ], ), Flexible( child: ListView( shrinkWrap: true, + padding: const EdgeInsets.symmetric(vertical: 12), children: [ ...collectonOptions.collections.entries.map( (e) { - return ListTile( - title: Text(e.key.name), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton.filledTonal( - style: IconButton.styleFrom( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), - onPressed: () async { - final response = await ref.read(provider.notifier).addToPlaylist(playlist: e.key); - if (context.mounted) { - fladderSnackbar(context, - title: response.isSuccessful - ? "Added to ${e.key.name} playlist" - : 'Unable to add to playlist - (${response.statusCode}) - ${response.base.reasonPhrase}'); - } - }, - icon: Icon(Icons.add_rounded, color: Theme.of(context).colorScheme.primary), + return Container( + margin: const EdgeInsets.symmetric(vertical: 8), + child: Card( + elevation: 0, + child: Padding( + padding: const EdgeInsets.all(10), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Text( + e.key.name, + style: Theme.of(context).textTheme.bodyLarge, + )), + IconButton.filledTonal( + style: IconButton.styleFrom( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), + onPressed: () async { + final response = await ref.read(provider.notifier).addToPlaylist(playlist: e.key); + if (context.mounted) { + fladderSnackbar(context, + title: response.isSuccessful + ? "Added to ${e.key.name} playlist" + : 'Unable to add to playlist - (${response.statusCode}) - ${response.base.reasonPhrase}'); + } + }, + icon: Icon(Icons.add_rounded, color: Theme.of(context).colorScheme.primary), + ), + ], ), - ], + ), ), ); }, ), - const SizedBox(height: 8), ], ), ), - Container( - color: Theme.of(context).colorScheme.surface, - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - FilledButton( - onPressed: () => Navigator.of(context).pop(), - child: Text(context.localized.close), - ) - ], - ), - ), - ), ], ), + actions: [ + FilledButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(context.localized.close), + ) + ], ); } } diff --git a/lib/screens/settings/settings_list_tile.dart b/lib/screens/settings/settings_list_tile.dart index b7e853c..0490e3c 100644 --- a/lib/screens/settings/settings_list_tile.dart +++ b/lib/screens/settings/settings_list_tile.dart @@ -31,41 +31,44 @@ class SettingsListTile extends StatelessWidget { shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only(topLeft: Radius.circular(8), bottomLeft: Radius.circular(8))), margin: EdgeInsets.zero, - child: ListTile( - minVerticalPadding: 12, - minLeadingWidth: 16, - minTileHeight: 75, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - horizontalTitleGap: 0, - titleAlignment: ListTileTitleAlignment.center, - contentPadding: const EdgeInsets.only(right: 12), - leading: (suffix ?? iconWidget) != null - ? Padding( - padding: const EdgeInsets.only(left: 8.0, right: 16.0), - child: AnimatedContainer( - duration: const Duration(milliseconds: 125), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primaryContainer.withOpacity(selected ? 1 : 0), - borderRadius: BorderRadius.circular(selected ? 5 : 20), + child: MediaQuery( + data: const MediaQueryData(padding: EdgeInsets.zero), + child: ListTile( + minVerticalPadding: 12, + minLeadingWidth: 16, + minTileHeight: 75, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + horizontalTitleGap: 0, + titleAlignment: ListTileTitleAlignment.center, + contentPadding: const EdgeInsets.only(right: 12, left: 2), + leading: (suffix ?? iconWidget) != null + ? Padding( + padding: const EdgeInsets.only(left: 8.0, right: 16.0), + child: AnimatedContainer( + duration: const Duration(milliseconds: 125), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primaryContainer.withOpacity(selected ? 1 : 0), + borderRadius: BorderRadius.circular(selected ? 5 : 20), + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 12), + child: (suffix ?? iconWidget), + ), ), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 12), - child: (suffix ?? iconWidget), - ), - ), - ) - : suffix ?? const SizedBox(), - title: label, - titleTextStyle: Theme.of(context).textTheme.titleLarge, - trailing: Padding( - padding: const EdgeInsets.only(left: 16), - child: trailing, + ) + : suffix ?? const SizedBox(), + title: label, + titleTextStyle: Theme.of(context).textTheme.titleLarge, + trailing: Padding( + padding: const EdgeInsets.only(left: 16), + child: trailing, + ), + selected: selected, + textColor: contentColor, + iconColor: contentColor, + subtitle: subLabel, + onTap: onTap, ), - selected: selected, - textColor: contentColor, - iconColor: contentColor, - subtitle: subLabel, - onTap: onTap, ), ); } diff --git a/lib/screens/settings/settings_scaffold.dart b/lib/screens/settings/settings_scaffold.dart index ee811ad..ec76ee7 100644 --- a/lib/screens/settings/settings_scaffold.dart +++ b/lib/screens/settings/settings_scaffold.dart @@ -43,11 +43,11 @@ class SettingsScaffold extends ConsumerWidget { backgroundColor: Theme.of(context).scaffoldBackgroundColor, leading: context.router.backButton(), flexibleSpace: FlexibleSpaceBar( - titlePadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16) + titlePadding: const EdgeInsets.symmetric(horizontal: 16) .add(EdgeInsets.only(left: padding.left, right: padding.right)), title: Row( children: [ - Text(label, style: Theme.of(context).textTheme.headlineSmall), + Text(label, style: Theme.of(context).textTheme.headlineLarge), const Spacer(), if (showUserIcon) SizedBox.fromSize( @@ -73,8 +73,11 @@ class SettingsScaffold extends ConsumerWidget { style: Theme.of(context).textTheme.headlineLarge), ), ), - SliverList( - delegate: SliverChildListDelegate(items), + SliverPadding( + padding: MediaQuery.paddingOf(context), + sliver: SliverList( + delegate: SliverChildListDelegate(items), + ), ), if (bottomActions.isEmpty) const SliverToBoxAdapter(child: SizedBox(height: kBottomNavigationBarHeight + 40)), diff --git a/lib/screens/settings/widgets/settings_label_divider.dart b/lib/screens/settings/widgets/settings_label_divider.dart index 8beb2ca..d547c81 100644 --- a/lib/screens/settings/widgets/settings_label_divider.dart +++ b/lib/screens/settings/widgets/settings_label_divider.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; + import 'package:flutter_riverpod/flutter_riverpod.dart'; class SettingsLabelDivider extends ConsumerWidget { @@ -8,11 +9,7 @@ class SettingsLabelDivider extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8).add( - EdgeInsets.symmetric( - horizontal: MediaQuery.paddingOf(context).horizontal, - ), - ), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Text( label, style: Theme.of(context).textTheme.titleMedium?.copyWith( diff --git a/lib/screens/shared/adaptive_dialog.dart b/lib/screens/shared/adaptive_dialog.dart index 9dd6810..c3867fc 100644 --- a/lib/screens/shared/adaptive_dialog.dart +++ b/lib/screens/shared/adaptive_dialog.dart @@ -1,12 +1,13 @@ -import 'package:fladder/util/adaptive_layout.dart'; import 'package:flutter/material.dart'; +import 'package:fladder/util/adaptive_layout.dart'; + Future showDialogAdaptive( - {required BuildContext context, bool useSafeArea = true, required Widget Function(BuildContext context) builder}) { + {required BuildContext context, required Widget Function(BuildContext context) builder}) { if (AdaptiveLayout.of(context).inputDevice == InputDevice.pointer) { return showDialog( context: context, - useSafeArea: useSafeArea, + useSafeArea: false, builder: (context) => Dialog( child: builder(context), ), @@ -14,7 +15,7 @@ Future showDialogAdaptive( } else { return showDialog( context: context, - useSafeArea: useSafeArea, + useSafeArea: false, builder: (context) => Dialog.fullscreen( child: builder(context), ), diff --git a/lib/screens/shared/default_titlebar.dart b/lib/screens/shared/default_titlebar.dart index c090992..26de057 100644 --- a/lib/screens/shared/default_titlebar.dart +++ b/lib/screens/shared/default_titlebar.dart @@ -9,7 +9,7 @@ class DefaultTitleBar extends ConsumerStatefulWidget { final String? label; final double? height; final Brightness? brightness; - const DefaultTitleBar({this.height = 35, this.label, this.brightness, super.key}); + const DefaultTitleBar({this.height = defaultTitleBarHeight, this.label, this.brightness, super.key}); @override ConsumerState createState() => _DefaultTitleBarState(); @@ -161,7 +161,13 @@ class _DefaultTitleBarState extends ConsumerState with WindowLi ), ], ), - TargetPlatform.macOS => null, + TargetPlatform.macOS => const Row( + children: [ + Spacer(), + Text("Fladder"), + SizedBox(width: 16), + ], + ), _ => Text(widget.label ?? "Fladder"), }, ); diff --git a/lib/screens/shared/detail_scaffold.dart b/lib/screens/shared/detail_scaffold.dart index f33f18d..754fe1e 100644 --- a/lib/screens/shared/detail_scaffold.dart +++ b/lib/screens/shared/detail_scaffold.dart @@ -140,85 +140,87 @@ class _DetailScaffoldState extends ConsumerState { //Top row buttons IconTheme( data: IconThemeData(color: Theme.of(context).colorScheme.onSurface), - child: Transform.translate( - offset: const Offset(0, kToolbarHeight), + child: Padding( + padding: MediaQuery.paddingOf(context).add( + const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + ), child: Row( children: [ - Padding( - padding: const EdgeInsets.only(left: 16), - child: IconButton.filledTonal( - style: IconButton.styleFrom( - backgroundColor: backGroundColor, - ), - onPressed: () => context.router.popBack(), - icon: Padding( - padding: - EdgeInsets.all(AdaptiveLayout.of(context).inputDevice == InputDevice.pointer ? 0 : 4), - child: const Icon(IconsaxOutline.arrow_left_2), - ), + IconButton.filledTonal( + style: IconButton.styleFrom( + backgroundColor: backGroundColor, + ), + onPressed: () => context.router.popBack(), + icon: Padding( + padding: EdgeInsets.all(AdaptiveLayout.of(context).inputDevice == InputDevice.pointer ? 0 : 4), + child: const Icon(IconsaxOutline.arrow_left_2), ), ), const Spacer(), - Padding( - padding: const EdgeInsets.only(right: 16), - child: AnimatedSize( - duration: const Duration(milliseconds: 250), - child: Container( - decoration: BoxDecoration( - color: backGroundColor, borderRadius: FladderTheme.defaultShape.borderRadius), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.item != null) ...[ - Builder( - builder: (context) { - final newActions = widget.actions?.call(context); - if (AdaptiveLayout.of(context).inputDevice == InputDevice.pointer) { - return PopupMenuButton( - tooltip: context.localized.moreOptions, - enabled: newActions?.isNotEmpty == true, - icon: Icon(widget.item!.type.icon), - itemBuilder: (context) => newActions?.popupMenuItems(useIcons: true) ?? [], - ); - } else { - return IconButton( - onPressed: () => showBottomSheetPill( - context: context, - content: (context, scrollController) => ListView( - controller: scrollController, - shrinkWrap: true, - children: newActions?.listTileItems(context, useIcons: true) ?? [], - ), + AnimatedSize( + duration: const Duration(milliseconds: 250), + child: Container( + decoration: + BoxDecoration(color: backGroundColor, borderRadius: FladderTheme.defaultShape.borderRadius), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.item != null) ...[ + Builder( + builder: (context) { + final newActions = widget.actions?.call(context); + if (AdaptiveLayout.of(context).inputDevice == InputDevice.pointer) { + return PopupMenuButton( + tooltip: context.localized.moreOptions, + enabled: newActions?.isNotEmpty == true, + icon: Icon(widget.item!.type.icon), + itemBuilder: (context) => newActions?.popupMenuItems(useIcons: true) ?? [], + ); + } else { + return IconButton( + onPressed: () => showBottomSheetPill( + context: context, + content: (context, scrollController) => ListView( + controller: scrollController, + shrinkWrap: true, + children: newActions?.listTileItems(context, useIcons: true) ?? [], ), - icon: Icon( - widget.item!.type.icon, - ), - ); - } - }, - ), - ], - if (AdaptiveLayout.of(context).inputDevice == InputDevice.pointer) - Builder( - builder: (context) => Tooltip( - message: context.localized.refresh, - child: IconButton( - onPressed: () => context.refreshData(), - icon: const Icon(IconsaxOutline.refresh), - ), - ), - ), - if (AdaptiveLayout.of(context).size == ScreenLayout.single) - const SizedBox(height: 30, width: 30, child: SettingsUserIcon()), - Tooltip( - message: context.localized.home, - child: IconButton( - onPressed: () => context.router.navigate(const DashboardRoute()), - icon: const Icon(IconsaxOutline.home), - ), + ), + icon: Icon( + widget.item!.type.icon, + ), + ); + } + }, ), ], - ), + if (AdaptiveLayout.of(context).inputDevice == InputDevice.pointer) + Builder( + builder: (context) => Tooltip( + message: context.localized.refresh, + child: IconButton( + onPressed: () => context.refreshData(), + icon: const Icon(IconsaxOutline.refresh), + ), + ), + ), + if (AdaptiveLayout.of(context).size == ScreenLayout.single) + Container( + margin: const EdgeInsets.symmetric(horizontal: 6), + child: const SizedBox( + height: 30, + width: 30, + child: SettingsUserIcon(), + ), + ), + Tooltip( + message: context.localized.home, + child: IconButton( + onPressed: () => context.router.navigate(const DashboardRoute()), + icon: const Icon(IconsaxOutline.home), + ), + ), + ], ), ), ), diff --git a/lib/screens/syncing/sync_item_details.dart b/lib/screens/syncing/sync_item_details.dart index 35eb6d1..2e0efe4 100644 --- a/lib/screens/syncing/sync_item_details.dart +++ b/lib/screens/syncing/sync_item_details.dart @@ -1,5 +1,9 @@ +import 'package:flutter/material.dart'; + import 'package:background_downloader/background_downloader.dart'; import 'package:ficonsax/ficonsax.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + import 'package:fladder/models/items/episode_model.dart'; import 'package:fladder/models/syncing/sync_item.dart'; import 'package:fladder/providers/settings/client_settings_provider.dart'; @@ -17,9 +21,8 @@ import 'package:fladder/util/adaptive_layout.dart'; import 'package:fladder/util/list_padding.dart'; import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/size_formatting.dart'; +import 'package:fladder/widgets/shared/alert_content.dart'; import 'package:fladder/widgets/shared/icon_button_await.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; Future showSyncItemDetails( BuildContext context, @@ -28,7 +31,6 @@ Future showSyncItemDetails( ) { return showDialogAdaptive( context: context, - useSafeArea: false, builder: (context) => SyncItemDetails( syncItem: syncItem, ), @@ -54,144 +56,138 @@ class _SyncItemDetailsState extends ConsumerState { final downloadTask = ref.read(downloadTasksProvider(syncedItem.id)); return SyncMarkedForDelete( - syncedItem: syncedItem, - child: SafeArea( - child: Padding( - padding: const EdgeInsets.all(16.0), + syncedItem: syncedItem, + child: ActionContent( + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Card( + elevation: 1, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Text(baseItem?.type.label(context) ?? ""), + )), + Text( + context.localized.navigationSync, + style: Theme.of(context).textTheme.titleMedium, + ), + IconButton( + onPressed: () => Navigator.pop(context), + icon: const Icon(IconsaxBold.close_circle), + ) + ], + ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Card( - elevation: 1, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text(baseItem?.type.label(context) ?? ""), - )), - Text( - context.localized.navigationSync, - style: Theme.of(context).textTheme.titleMedium, - ), - IconButton( - onPressed: () => Navigator.pop(context), - icon: const Icon(IconsaxBold.close_circle), - ) - ], - ), if (baseItem != null) ...{ - const Divider(), - Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox( - height: (AdaptiveLayout.poster(context).size * - ref.watch(clientSettingsProvider.select((value) => value.posterSize))) * - 0.6, - child: IgnorePointer( - child: PosterWidget( - aspectRatio: 0.7, - poster: baseItem, - inlineTitle: true, - ), + Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + height: (AdaptiveLayout.poster(context).size * + ref.watch(clientSettingsProvider.select((value) => value.posterSize))) * + 0.6, + child: IgnorePointer( + child: PosterWidget( + aspectRatio: 0.7, + poster: baseItem, + inlineTitle: true, ), ), - Expanded( - child: SyncProgressBuilder( - item: syncedItem, - builder: (context, combinedStream) { - return Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - baseItem.detailedName(context) ?? "", - style: Theme.of(context).textTheme.titleMedium, - ), - SyncSubtitle(syncItem: syncedItem), - SyncLabel( - label: context.localized - .totalSize(ref.watch(syncSizeProvider(syncedItem)).byteFormat ?? '--'), - status: ref.watch(syncStatusesProvider(syncedItem)).value ?? SyncStatus.partially, - ), - ].addInBetween(const SizedBox(height: 8)), - ), + ), + Expanded( + child: SyncProgressBuilder( + item: syncedItem, + builder: (context, combinedStream) { + return Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + baseItem.detailedName(context) ?? "", + style: Theme.of(context).textTheme.titleMedium, + ), + SyncSubtitle(syncItem: syncedItem), + SyncLabel( + label: context.localized + .totalSize(ref.watch(syncSizeProvider(syncedItem)).byteFormat ?? '--'), + status: ref.watch(syncStatusesProvider(syncedItem)).value ?? SyncStatus.partially, + ), + ].addInBetween(const SizedBox(height: 8)), ), - if (combinedStream?.task != null) ...{ - if (combinedStream?.status != TaskStatus.paused) - IconButton( - onPressed: () => - ref.read(backgroundDownloaderProvider).pause(combinedStream!.task!), - icon: const Icon(IconsaxBold.pause), - ), - if (combinedStream?.status == TaskStatus.paused) ...[ - IconButton( - onPressed: () => - ref.read(backgroundDownloaderProvider).resume(combinedStream!.task!), - icon: const Icon(IconsaxBold.play), - ), - IconButton( - onPressed: () => ref.read(syncProvider.notifier).deleteFullSyncFiles(syncedItem), - icon: const Icon(IconsaxBold.stop), - ), - ], - const SizedBox(width: 16) - }, - if (combinedStream != null && combinedStream.hasDownload) - SizedBox.fromSize( - size: const Size.fromRadius(35), - child: Stack( - fit: StackFit.expand, - alignment: Alignment.center, - children: [ - CircularProgressIndicator( - value: combinedStream.progress, - strokeWidth: 8, - backgroundColor: Theme.of(context).colorScheme.surface.withOpacity(0.5), - strokeCap: StrokeCap.round, - color: combinedStream.status.color(context), - ), - Center(child: Text("${((combinedStream.progress) * 100).toStringAsFixed(0)}%")) - ], - )), - ], - ); - }, - ), - ), - if (!hasFile && !downloadTask.hasDownload && syncedItem.hasVideoFile) - IconButtonAwait( - onPressed: () async => await ref.read(syncProvider.notifier).syncVideoFile(syncedItem, false), - icon: const Icon(IconsaxOutline.cloud_change), - ) - else if (hasFile) - IconButtonAwait( - color: Theme.of(context).colorScheme.error, - onPressed: () { - showDefaultAlertDialog( - context, - context.localized.syncRemoveDataTitle, - context.localized.syncRemoveDataDesc, - (context) { - ref.read(syncProvider.notifier).deleteFullSyncFiles(syncedItem); - Navigator.of(context).pop(); + ), + if (combinedStream?.task != null) ...{ + if (combinedStream?.status != TaskStatus.paused) + IconButton( + onPressed: () => + ref.read(backgroundDownloaderProvider).pause(combinedStream!.task!), + icon: const Icon(IconsaxBold.pause), + ), + if (combinedStream?.status == TaskStatus.paused) ...[ + IconButton( + onPressed: () => + ref.read(backgroundDownloaderProvider).resume(combinedStream!.task!), + icon: const Icon(IconsaxBold.play), + ), + IconButton( + onPressed: () => ref.read(syncProvider.notifier).deleteFullSyncFiles(syncedItem), + icon: const Icon(IconsaxBold.stop), + ), + ], + const SizedBox(width: 16) }, - context.localized.delete, - (context) => Navigator.of(context).pop(), - context.localized.cancel, - ); - }, - icon: const Icon(IconsaxOutline.trash), - ), - ].addInBetween(const SizedBox(width: 16)), - ), + if (combinedStream != null && combinedStream.hasDownload) + SizedBox.fromSize( + size: const Size.fromRadius(35), + child: Stack( + fit: StackFit.expand, + alignment: Alignment.center, + children: [ + CircularProgressIndicator( + value: combinedStream.progress, + strokeWidth: 8, + backgroundColor: Theme.of(context).colorScheme.surface.withOpacity(0.5), + strokeCap: StrokeCap.round, + color: combinedStream.status.color(context), + ), + Center(child: Text("${((combinedStream.progress) * 100).toStringAsFixed(0)}%")) + ], + )), + ], + ); + }, + ), + ), + if (!hasFile && !downloadTask.hasDownload && syncedItem.hasVideoFile) + IconButtonAwait( + onPressed: () async => await ref.read(syncProvider.notifier).syncVideoFile(syncedItem, false), + icon: const Icon(IconsaxOutline.cloud_change), + ) + else if (hasFile) + IconButtonAwait( + color: Theme.of(context).colorScheme.error, + onPressed: () { + showDefaultAlertDialog( + context, + context.localized.syncRemoveDataTitle, + context.localized.syncRemoveDataDesc, + (context) { + ref.read(syncProvider.notifier).deleteFullSyncFiles(syncedItem); + Navigator.of(context).pop(); + }, + context.localized.delete, + (context) => Navigator.of(context).pop(), + context.localized.cancel, + ); + }, + icon: const Icon(IconsaxOutline.trash), + ), + ].addInBetween(const SizedBox(width: 16)), ), }, const Divider(), @@ -206,53 +202,45 @@ class _SyncItemDetailsState extends ConsumerState { ], ), ), - Padding( - padding: const EdgeInsets.only(top: 16), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - if (baseItem is! EpisodeModel) - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.errorContainer, - foregroundColor: Theme.of(context).colorScheme.onErrorContainer, - ), - onPressed: () { - showDefaultAlertDialog( - context, - context.localized.syncDeleteItemTitle, - context.localized.syncDeleteItemDesc(baseItem?.detailedName(context) ?? ""), - (context) async { - await ref.read(syncProvider.notifier).removeSync(syncedItem); - Navigator.pop(context); - Navigator.pop(context); - }, - context.localized.delete, - (context) => Navigator.pop(context), - context.localized.cancel, - ); - }, - child: Text(context.localized.delete), - ) - else if (syncedItem.parentId != null) - ElevatedButton( - onPressed: () { - final parentItem = ref.read(syncProvider.notifier).getParentItem(syncedItem.parentId!); - setState(() { - if (parentItem != null) { - syncedItem = parentItem; - } - }); - }, - child: Text(context.localized.syncOpenParent), - ) - ], - ), - ) ], ), - ), - ), - ); + actions: [ + if (baseItem is! EpisodeModel) + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.errorContainer, + foregroundColor: Theme.of(context).colorScheme.onErrorContainer, + ), + onPressed: () { + showDefaultAlertDialog( + context, + context.localized.syncDeleteItemTitle, + context.localized.syncDeleteItemDesc(baseItem?.detailedName(context) ?? ""), + (context) async { + await ref.read(syncProvider.notifier).removeSync(syncedItem); + Navigator.pop(context); + Navigator.pop(context); + }, + context.localized.delete, + (context) => Navigator.pop(context), + context.localized.cancel, + ); + }, + child: Text(context.localized.delete), + ) + else if (syncedItem.parentId != null) + ElevatedButton( + onPressed: () { + final parentItem = ref.read(syncProvider.notifier).getParentItem(syncedItem.parentId!); + setState(() { + if (parentItem != null) { + syncedItem = parentItem; + } + }); + }, + child: Text(context.localized.syncOpenParent), + ) + ], + )); } } diff --git a/lib/screens/syncing/widgets/sync_markedfordelete.dart b/lib/screens/syncing/widgets/sync_markedfordelete.dart index 75c02bc..27c5aca 100644 --- a/lib/screens/syncing/widgets/sync_markedfordelete.dart +++ b/lib/screens/syncing/widgets/sync_markedfordelete.dart @@ -1,9 +1,12 @@ -import 'package:ficonsax/ficonsax.dart'; -import 'package:fladder/models/syncing/sync_item.dart'; -import 'package:fladder/util/list_padding.dart'; import 'package:flutter/material.dart'; + +import 'package:ficonsax/ficonsax.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:fladder/models/syncing/sync_item.dart'; +import 'package:fladder/util/list_padding.dart'; + +///This is a wrapper widget for marking a synced item as deleted (while it is being deleted) class SyncMarkedForDelete extends ConsumerWidget { final SyncedItem syncedItem; final Widget child; diff --git a/lib/screens/video_player/video_player_controls.dart b/lib/screens/video_player/video_player_controls.dart index e8d7017..84c2e61 100644 --- a/lib/screens/video_player/video_player_controls.dart +++ b/lib/screens/video_player/video_player_controls.dart @@ -208,46 +208,51 @@ class _DesktopControlsState extends ConsumerState { Colors.black.withOpacity(0), ], )), - child: Padding( - padding: - EdgeInsets.only(top: topPadding + (AdaptiveLayout.of(context).platform == TargetPlatform.macOS ? 28 : 0.0)), - child: Container( - alignment: Alignment.topCenter, - height: 80, - child: Column( - children: [ - if (AdaptiveLayout.of(context).isDesktop) - const Flexible( - child: Align( - alignment: Alignment.topRight, - child: DefaultTitleBar(), - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, + child: Stack( + children: [ + if (AdaptiveLayout.of(context).isDesktop) + const Flexible( + child: Align( + alignment: Alignment.topRight, + child: DefaultTitleBar(), + ), + ), + Flexible( + child: Padding( + padding: MediaQuery.paddingOf(context).copyWith(bottom: 0), + child: Container( + alignment: Alignment.topCenter, + height: 80, + child: Column( children: [ - IconButton( - onPressed: () => minimizePlayer(context), - icon: const Icon( - IconsaxOutline.arrow_down_1, - size: 24, - ), - ), - const SizedBox(width: 16), - Flexible( - child: Text( - currentItem?.title ?? "", - style: Theme.of(context).textTheme.titleLarge, + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + IconButton( + onPressed: () => minimizePlayer(context), + icon: const Icon( + IconsaxOutline.arrow_down_1, + size: 24, + ), + ), + const SizedBox(width: 16), + Flexible( + child: Text( + currentItem?.title ?? "", + style: Theme.of(context).textTheme.titleLarge, + ), + ), + ], ), ), ], ), ), - ], + ), ), - ), + ], ), ); } @@ -264,10 +269,9 @@ class _DesktopControlsState extends ConsumerState { ], )), child: Padding( - padding: EdgeInsets.only(bottom: bottomPadding) - .copyWith(bottom: 21) - .add(const EdgeInsets.symmetric(vertical: 16)) - .add(EdgeInsets.symmetric(horizontal: AdaptiveLayout.of(context).isDesktop ? 32 : 0)), + padding: MediaQuery.paddingOf(context).add( + const EdgeInsets.symmetric(horizontal: 16).copyWith(bottom: 12), + ), child: Column( children: [ Padding( diff --git a/lib/util/adaptive_layout.dart b/lib/util/adaptive_layout.dart index d0a9fbb..28659ba 100644 --- a/lib/util/adaptive_layout.dart +++ b/lib/util/adaptive_layout.dart @@ -121,6 +121,8 @@ class AdaptiveLayout extends InheritedWidget { } } +const defaultTitleBarHeight = 35.0; + class AdaptiveLayoutBuilder extends ConsumerStatefulWidget { final List layoutPoints; final LayoutState fallBack; @@ -182,20 +184,26 @@ class _AdaptiveLayoutBuilderState extends ConsumerState { @override Widget build(BuildContext context) { - return AdaptiveLayout( - layout: layout, - controller: controller, - size: size, - inputDevice: (isDesktop || kIsWeb) ? InputDevice.pointer : InputDevice.touch, - platform: currentPlatform, - isDesktop: isDesktop, - router: router, - posterDefaults: switch (layout) { - LayoutState.phone => const PosterDefaults(size: 300, ratio: 0.55), - LayoutState.tablet => const PosterDefaults(size: 350, ratio: 0.55), - LayoutState.desktop => const PosterDefaults(size: 400, ratio: 0.55), - }, - child: widget.child, + return MediaQuery( + data: MediaQuery.of(context).copyWith( + padding: isDesktop || kIsWeb ? const EdgeInsets.only(top: defaultTitleBarHeight, bottom: 16) : null, + viewPadding: isDesktop || kIsWeb ? const EdgeInsets.only(top: defaultTitleBarHeight, bottom: 16) : null, + ), + child: AdaptiveLayout( + layout: layout, + controller: controller, + size: size, + inputDevice: (isDesktop || kIsWeb) ? InputDevice.pointer : InputDevice.touch, + platform: currentPlatform, + isDesktop: isDesktop, + router: router, + posterDefaults: switch (layout) { + LayoutState.phone => const PosterDefaults(size: 300, ratio: 0.55), + LayoutState.tablet => const PosterDefaults(size: 350, ratio: 0.55), + LayoutState.desktop => const PosterDefaults(size: 400, ratio: 0.55), + }, + child: widget.child, + ), ); } } diff --git a/lib/widgets/navigation_scaffold/components/navigation_body.dart b/lib/widgets/navigation_scaffold/components/navigation_body.dart index 4bf3d9e..6e0ad17 100644 --- a/lib/widgets/navigation_scaffold/components/navigation_body.dart +++ b/lib/widgets/navigation_scaffold/components/navigation_body.dart @@ -111,76 +111,78 @@ class _NavigationBodyState extends ConsumerState { } Widget navigationRail(BuildContext context) { - return Padding( - key: const Key('navigation_rail'), - padding: AdaptiveLayout.of(context).isDesktop ? EdgeInsets.zero : MediaQuery.of(context).padding, - child: Column( - children: [ - if (AdaptiveLayout.of(context).isDesktop && AdaptiveLayout.of(context).platform != TargetPlatform.macOS) ...{ - const SizedBox(height: 4), - Text( - "Fladder", - style: Theme.of(context).textTheme.titleSmall, - ), - }, - if (AdaptiveLayout.of(context).platform == TargetPlatform.macOS) - const SizedBox(height: 32) - else - const SizedBox(height: 16), - IconButton( - onPressed: () { - if (AdaptiveLayout.layoutOf(context) != LayoutState.desktop) { - widget.drawerKey.currentState?.openDrawer(); - } else { - setState(() { - expandedSideBar = true; - }); - } - }, - icon: const Icon(IconsaxBold.menu), + return Column( + children: [ + if (AdaptiveLayout.of(context).isDesktop && AdaptiveLayout.of(context).platform != TargetPlatform.macOS) ...{ + const SizedBox(height: 4), + Text( + "Fladder", + style: Theme.of(context).textTheme.titleSmall, ), - if (AdaptiveLayout.of(context).size == ScreenLayout.dual) ...[ - const SizedBox(height: 8), - AnimatedFadeSize( - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 250), - transitionBuilder: (Widget child, Animation animation) { - return ScaleTransition(scale: animation, child: child); - }, - child: actionButton()?.normal, - ), - ), - ], - const Spacer(), - IconTheme( - data: const IconThemeData(size: 28), + }, + Flexible( + child: Padding( + key: const Key('navigation_rail'), + padding: MediaQuery.paddingOf(context).copyWith(right: 0), child: Column( - crossAxisAlignment: CrossAxisAlignment.center, children: [ - ...widget.destinations.mapIndexed( - (index, destination) => destination.toNavigationButton(widget.currentIndex == index, false), - ) + IconButton( + onPressed: () { + if (AdaptiveLayout.layoutOf(context) != LayoutState.desktop) { + widget.drawerKey.currentState?.openDrawer(); + } else { + setState(() { + expandedSideBar = true; + }); + } + }, + icon: const Icon(IconsaxBold.menu), + ), + if (AdaptiveLayout.of(context).size == ScreenLayout.dual) ...[ + const SizedBox(height: 8), + AnimatedFadeSize( + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 250), + transitionBuilder: (Widget child, Animation animation) { + return ScaleTransition(scale: animation, child: child); + }, + child: actionButton()?.normal, + ), + ), + ], + const Spacer(), + IconTheme( + data: const IconThemeData(size: 28), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ...widget.destinations.mapIndexed( + (index, destination) => destination.toNavigationButton(widget.currentIndex == index, false), + ) + ], + ), + ), + const Spacer(), + SizedBox( + height: 48, + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 250), + child: widget.currentLocation.contains(const SettingsRoute().routeName) + ? Card( + color: Theme.of(context).colorScheme.primaryContainer, + child: const Padding( + padding: EdgeInsets.all(10), + child: Icon(IconsaxBold.setting_3), + ), + ) + : const SettingsUserIcon()), + ), + if (AdaptiveLayout.of(context).inputDevice == InputDevice.pointer) const SizedBox(height: 16), ], ), ), - const Spacer(), - SizedBox( - height: 48, - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 250), - child: widget.currentLocation.contains(const SettingsRoute().routeName) - ? Card( - color: Theme.of(context).colorScheme.primaryContainer, - child: const Padding( - padding: EdgeInsets.all(10), - child: Icon(IconsaxBold.setting_3), - ), - ) - : const SettingsUserIcon()), - ), - if (AdaptiveLayout.of(context).inputDevice == InputDevice.pointer) const SizedBox(height: 16), - ], - ), + ), + ], ); } } diff --git a/lib/widgets/shared/alert_content.dart b/lib/widgets/shared/alert_content.dart new file mode 100644 index 0000000..ebb1c48 --- /dev/null +++ b/lib/widgets/shared/alert_content.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; + +import 'package:fladder/util/list_padding.dart'; + +class ActionContent extends StatelessWidget { + final Widget? title; + final Widget child; + final List actions; + final bool showDividers; + final EdgeInsetsGeometry? padding; + const ActionContent({ + this.title, + required this.child, + this.padding, + this.showDividers = true, + this.actions = const [], + super.key, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: padding ?? MediaQuery.paddingOf(context).add(const EdgeInsets.symmetric(horizontal: 16)), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (title != null) ...[ + title!, + if (showDividers) + const Divider( + height: 4, + ), + ], + Expanded(child: child), + if (actions.isNotEmpty) ...[ + if (showDividers) + const Divider( + height: 4, + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: actions, + ) + ], + ].addInBetween(const SizedBox(height: 16)), + ), + ); + } +} diff --git a/lib/widgets/shared/item_actions.dart b/lib/widgets/shared/item_actions.dart index 40064c7..4df9f4c 100644 --- a/lib/widgets/shared/item_actions.dart +++ b/lib/widgets/shared/item_actions.dart @@ -1,9 +1,10 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:async'; -import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:collection/collection.dart'; + abstract class ItemAction { Widget toMenuItemButton(); PopupMenuEntry toPopupMenuItem({bool useIcons = false}); @@ -67,7 +68,11 @@ class ItemActionButton extends ItemAction { iconTheme: IconThemeData(color: Theme.of(context).colorScheme.onSurface), ), child: Row( - children: [if (icon != null) icon!, const SizedBox(width: 8), if (label != null) Flexible(child: label!)], + children: [ + if (icon != null) icon!, + const SizedBox(width: 8), + if (label != null) Flexible(child: label!) + ], ), ), ); @@ -82,25 +87,38 @@ class ItemActionButton extends ItemAction { } @override - ListTile toListItem(BuildContext context, {bool useIcons = false, bool shouldPop = true}) { - return ListTile( - onTap: () { + Widget toListItem(BuildContext context, {bool useIcons = false, bool shouldPop = true}) { + return ElevatedButton( + style: ButtonStyle( + backgroundColor: const WidgetStatePropertyAll(Colors.transparent), + padding: const WidgetStatePropertyAll(EdgeInsets.symmetric(horizontal: 12)), + minimumSize: const WidgetStatePropertyAll(Size(50, 50)), + elevation: const WidgetStatePropertyAll(0), + foregroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.onSurface), + ), + onPressed: () { if (shouldPop) { Navigator.of(context).pop(); } action?.call(); }, - title: useIcons - ? Builder(builder: (context) { - return Theme( - data: ThemeData( - iconTheme: IconThemeData(color: Theme.of(context).colorScheme.onSurface), - ), - child: Row( - children: [if (icon != null) icon!, const SizedBox(width: 8), if (label != null) Flexible(child: label!)], - ), - ); - }) + child: useIcons + ? Builder( + builder: (context) { + return Theme( + data: ThemeData( + iconTheme: IconThemeData(color: Theme.of(context).colorScheme.onSurface), + ), + child: Row( + children: [ + if (icon != null) icon!, + const SizedBox(width: 8), + if (label != null) Flexible(child: label!) + ], + ), + ); + }, + ) : label, ); } diff --git a/lib/widgets/shared/modal_bottom_sheet.dart b/lib/widgets/shared/modal_bottom_sheet.dart index fdeb473..5403d87 100644 --- a/lib/widgets/shared/modal_bottom_sheet.dart +++ b/lib/widgets/shared/modal_bottom_sheet.dart @@ -1,8 +1,10 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; + import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/util/adaptive_layout.dart'; import 'package:fladder/util/fladder_image.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; Future showBottomSheetPill({ ItemBaseModel? item, @@ -32,7 +34,10 @@ Future showBottomSheetPill({ controller: controller, children: [ if (item != null) ...{ - ItemBottomSheetPreview(item: item), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: ItemBottomSheetPreview(item: item), + ), const Divider(), }, content(context, controller), @@ -51,49 +56,46 @@ class ItemBottomSheetPreview extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { return Column( children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Row( - children: [ - Card( - child: SizedBox( - height: 90, - child: AspectRatio( - aspectRatio: 1, - child: FladderImage( - image: item.images?.primary, - fit: BoxFit.contain, - ), + Row( + children: [ + Card( + child: SizedBox( + height: 90, + child: AspectRatio( + aspectRatio: 1, + child: FladderImage( + image: item.images?.primary, + fit: BoxFit.contain, ), ), ), - const SizedBox(width: 16), - Flexible( - child: Column( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - item.title, - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.titleLarge, - ), - if (item.subText?.isNotEmpty ?? false) - Opacity( - opacity: 0.75, - child: Text( - item.subText!, - overflow: TextOverflow.ellipsis, - maxLines: 2, - style: Theme.of(context).textTheme.titleMedium, - ), + ), + const SizedBox(width: 16), + Flexible( + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.title, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.titleLarge, + ), + if (item.subText?.isNotEmpty ?? false) + Opacity( + opacity: 0.75, + child: Text( + item.subText!, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: Theme.of(context).textTheme.titleMedium, ), - ], - ), + ), + ], ), - ], - ), + ), + ], ), ], );