mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-08 23:18:16 -07:00
feature: Rework responsive layout (#217)
Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
parent
e07f280124
commit
8012fdcea8
48 changed files with 1468 additions and 1040 deletions
|
|
@ -131,235 +131,243 @@ class _BookViewerControlsState extends ConsumerState<BookViewerControls> {
|
|||
final previousChapter = details.previousChapter(bookViewerDetails.book);
|
||||
final nextChapter = details.nextChapter(bookViewerDetails.book);
|
||||
|
||||
return MediaQuery.removePadding(
|
||||
context: context,
|
||||
child: InputHandler(
|
||||
onKeyEvent: (node, event) => _onKey(event) ? KeyEventResult.handled : KeyEventResult.ignored,
|
||||
child: Stack(
|
||||
children: [
|
||||
IgnorePointer(
|
||||
ignoring: !showControls,
|
||||
child: AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
opacity: showControls ? 1 : 0,
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
overlayColor.withValues(alpha: 1),
|
||||
overlayColor.withValues(alpha: 0.65),
|
||||
overlayColor.withValues(alpha: 0),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: topPadding).copyWith(bottom: 8),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (AdaptiveLayout.of(context).isDesktop)
|
||||
const Flexible(
|
||||
child: DefaultTitleBar(
|
||||
height: 50,
|
||||
brightness: Brightness.dark,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const BackButton(),
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
bookViewerDetails.book?.name ?? "None",
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
final double leftPadding = MediaQuery.of(context).viewPadding.left;
|
||||
final double rightPadding = MediaQuery.of(context).viewPadding.right;
|
||||
|
||||
return InputHandler(
|
||||
onKeyEvent: (node, event) => _onKey(event) ? KeyEventResult.handled : KeyEventResult.ignored,
|
||||
child: Stack(
|
||||
children: [
|
||||
IgnorePointer(
|
||||
ignoring: !showControls,
|
||||
child: AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
opacity: showControls ? 1 : 0,
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
overlayColor.withValues(alpha: 1),
|
||||
overlayColor.withValues(alpha: 0.65),
|
||||
overlayColor.withValues(alpha: 0),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (!bookViewerDetails.loading) ...{
|
||||
if (bookViewerDetails.book != null && bookViewerDetails.pages.isNotEmpty) ...{
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
overlayColor.withValues(alpha: 0),
|
||||
overlayColor.withValues(alpha: 0.65),
|
||||
overlayColor.withValues(alpha: 1),
|
||||
],
|
||||
child: Padding(
|
||||
padding:
|
||||
EdgeInsets.only(top: topPadding, left: leftPadding, right: rightPadding).copyWith(bottom: 8),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (AdaptiveLayout.of(context).isDesktop)
|
||||
const Flexible(
|
||||
child: DefaultTitleBar(
|
||||
height: 50,
|
||||
brightness: Brightness.dark,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(bottom: bottomPadding).copyWith(top: 16, bottom: 16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(height: 30),
|
||||
Row(
|
||||
children: [
|
||||
const SizedBox(width: 8),
|
||||
Tooltip(
|
||||
message: bookViewerSettings.readDirection == ReadDirection.leftToRight
|
||||
? previousChapter?.name != null
|
||||
? "Load ${previousChapter?.name}"
|
||||
: ""
|
||||
: nextChapter?.name != null
|
||||
? "Load ${nextChapter?.name}"
|
||||
: "",
|
||||
child: IconButton.filled(
|
||||
onPressed: bookViewerSettings.readDirection == ReadDirection.leftToRight
|
||||
? previousChapter != null
|
||||
? () async => await loadNextBook(previousChapter)
|
||||
: null
|
||||
: nextChapter != null
|
||||
? () async => await loadNextBook(nextChapter)
|
||||
: null,
|
||||
icon: const Icon(IconsaxOutline.backward),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.7),
|
||||
borderRadius: BorderRadius.circular(60),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Row(
|
||||
children: [
|
||||
if (bookViewerSettings.readDirection == ReadDirection.leftToRight)
|
||||
...controls(currentPage, bookViewerSettings, bookViewerDetails)
|
||||
else
|
||||
...controls(currentPage, bookViewerSettings, bookViewerDetails)
|
||||
.reversed,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Tooltip(
|
||||
message: bookViewerSettings.readDirection == ReadDirection.leftToRight
|
||||
? nextChapter?.name != null
|
||||
? "Load ${nextChapter?.name}"
|
||||
: ""
|
||||
: previousChapter?.name != null
|
||||
? "Load ${previousChapter?.name}"
|
||||
: "",
|
||||
child: IconButton.filled(
|
||||
onPressed: bookViewerSettings.readDirection == ReadDirection.leftToRight
|
||||
? nextChapter != null
|
||||
? () async => await loadNextBook(nextChapter)
|
||||
: null
|
||||
: previousChapter != null
|
||||
? () async => await loadNextBook(previousChapter)
|
||||
: null,
|
||||
icon: const Icon(IconsaxOutline.forward),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Transform.flip(
|
||||
flipX: bookViewerSettings.readDirection == ReadDirection.rightToLeft,
|
||||
child: IconButton(
|
||||
onPressed: () => widget.controller
|
||||
.animateToPage(1, duration: pageAnimDuration, curve: pageAnimCurve),
|
||||
icon: const Icon(IconsaxOutline.backward)),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
showBookViewerSettings(context);
|
||||
},
|
||||
icon: const Icon(IconsaxOutline.setting_2),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: chapters.length > 1
|
||||
? () {
|
||||
showBookViewerChapters(
|
||||
context,
|
||||
widget.provider,
|
||||
onPressed: (book) async {
|
||||
Navigator.of(context).pop();
|
||||
loadNextBook(book);
|
||||
},
|
||||
);
|
||||
}
|
||||
: () => fladderSnackbar(context, title: "No other chapters"),
|
||||
icon: const Icon(IconsaxOutline.bookmark_2),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const BackButton(),
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
bookViewerDetails.book?.name ?? "None",
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
} else
|
||||
const Center(
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.menu_book_rounded),
|
||||
SizedBox(width: 8),
|
||||
Text("Unable to load book"),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (bookViewerDetails.loading)
|
||||
Center(
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (bookViewerDetails.book != null) ...{
|
||||
Flexible(
|
||||
child: Text("Loading ${bookViewerDetails.book?.name}",
|
||||
style: Theme.of(context).textTheme.titleMedium),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
},
|
||||
const CircularProgressIndicator.adaptive(strokeCap: StrokeCap.round),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!bookViewerDetails.loading) ...{
|
||||
if (bookViewerDetails.book != null && bookViewerDetails.pages.isNotEmpty) ...{
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
overlayColor.withValues(alpha: 0),
|
||||
overlayColor.withValues(alpha: 0.65),
|
||||
overlayColor.withValues(alpha: 1),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: bottomPadding,
|
||||
left: leftPadding,
|
||||
right: rightPadding,
|
||||
).copyWith(
|
||||
top: 16,
|
||||
bottom: 16,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(height: 30),
|
||||
Row(
|
||||
children: [
|
||||
const SizedBox(width: 8),
|
||||
Tooltip(
|
||||
message: bookViewerSettings.readDirection == ReadDirection.leftToRight
|
||||
? previousChapter?.name != null
|
||||
? "Load ${previousChapter?.name}"
|
||||
: ""
|
||||
: nextChapter?.name != null
|
||||
? "Load ${nextChapter?.name}"
|
||||
: "",
|
||||
child: IconButton.filled(
|
||||
onPressed: bookViewerSettings.readDirection == ReadDirection.leftToRight
|
||||
? previousChapter != null
|
||||
? () async => await loadNextBook(previousChapter)
|
||||
: null
|
||||
: nextChapter != null
|
||||
? () async => await loadNextBook(nextChapter)
|
||||
: null,
|
||||
icon: const Icon(IconsaxOutline.backward),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.7),
|
||||
borderRadius: BorderRadius.circular(60),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Row(
|
||||
children: [
|
||||
if (bookViewerSettings.readDirection == ReadDirection.leftToRight)
|
||||
...controls(currentPage, bookViewerSettings, bookViewerDetails)
|
||||
else
|
||||
...controls(currentPage, bookViewerSettings, bookViewerDetails)
|
||||
.reversed,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Tooltip(
|
||||
message: bookViewerSettings.readDirection == ReadDirection.leftToRight
|
||||
? nextChapter?.name != null
|
||||
? "Load ${nextChapter?.name}"
|
||||
: ""
|
||||
: previousChapter?.name != null
|
||||
? "Load ${previousChapter?.name}"
|
||||
: "",
|
||||
child: IconButton.filled(
|
||||
onPressed: bookViewerSettings.readDirection == ReadDirection.leftToRight
|
||||
? nextChapter != null
|
||||
? () async => await loadNextBook(nextChapter)
|
||||
: null
|
||||
: previousChapter != null
|
||||
? () async => await loadNextBook(previousChapter)
|
||||
: null,
|
||||
icon: const Icon(IconsaxOutline.forward),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Transform.flip(
|
||||
flipX: bookViewerSettings.readDirection == ReadDirection.rightToLeft,
|
||||
child: IconButton(
|
||||
onPressed: () => widget.controller
|
||||
.animateToPage(1, duration: pageAnimDuration, curve: pageAnimCurve),
|
||||
icon: const Icon(IconsaxOutline.backward)),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
showBookViewerSettings(context);
|
||||
},
|
||||
icon: const Icon(IconsaxOutline.setting_2),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: chapters.length > 1
|
||||
? () {
|
||||
showBookViewerChapters(
|
||||
context,
|
||||
widget.provider,
|
||||
onPressed: (book) async {
|
||||
Navigator.of(context).pop();
|
||||
loadNextBook(book);
|
||||
},
|
||||
);
|
||||
}
|
||||
: () => fladderSnackbar(context, title: "No other chapters"),
|
||||
icon: const Icon(IconsaxOutline.bookmark_2),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
} else
|
||||
const Center(
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.menu_book_rounded),
|
||||
SizedBox(width: 8),
|
||||
Text("Unable to load book"),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (bookViewerDetails.loading)
|
||||
Center(
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (bookViewerDetails.book != null) ...{
|
||||
Flexible(
|
||||
child: Text("Loading ${bookViewerDetails.book?.name}",
|
||||
style: Theme.of(context).textTheme.titleMedium),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
},
|
||||
const CircularProgressIndicator.adaptive(strokeCap: StrokeCap.round),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
|||
controller: AdaptiveLayout.scrollOf(context),
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
if (AdaptiveLayout.of(context).layout == LayoutState.phone)
|
||||
if (AdaptiveLayout.viewSizeOf(context) == ViewSize.phone)
|
||||
NestedSliverAppBar(
|
||||
route: LibrarySearchRoute(),
|
||||
parent: context,
|
||||
|
|
@ -102,7 +102,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
|||
if (homeBanner && homeCarouselItems.isNotEmpty) ...{
|
||||
SliverToBoxAdapter(
|
||||
child: Transform.translate(
|
||||
offset: Offset(0, AdaptiveLayout.layoutOf(context) == LayoutState.phone ? -14 : 0),
|
||||
offset: Offset(0, AdaptiveLayout.layoutOf(context) == ViewSize.phone ? -14 : 0),
|
||||
child: HomeBannerWidget(posters: homeCarouselItems),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
|
||||
import 'package:fladder/models/items/images_models.dart';
|
||||
import 'package:fladder/models/items/item_shared_models.dart';
|
||||
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||
import 'package:fladder/screens/shared/media/components/chip_button.dart';
|
||||
import 'package:fladder/screens/shared/media/components/media_header.dart';
|
||||
import 'package:fladder/screens/shared/media/components/small_detail_widgets.dart';
|
||||
|
|
@ -55,7 +56,7 @@ class OverviewHeader extends ConsumerWidget {
|
|||
(MediaQuery.sizeOf(context).height - (MediaQuery.paddingOf(context).top + 150)).clamp(50, 1250).toDouble();
|
||||
|
||||
final crossAlignment =
|
||||
AdaptiveLayout.of(context).layout != LayoutState.phone ? CrossAxisAlignment.start : CrossAxisAlignment.center;
|
||||
AdaptiveLayout.viewSizeOf(context) != ViewSize.phone ? CrossAxisAlignment.start : CrossAxisAlignment.center;
|
||||
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import 'package:ficonsax/ficonsax.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||
import 'package:fladder/providers/items/episode_details_provider.dart';
|
||||
import 'package:fladder/providers/user_provider.dart';
|
||||
import 'package:fladder/screens/details_screens/components/media_stream_information.dart';
|
||||
|
|
@ -43,7 +44,7 @@ class _ItemDetailScreenState extends ConsumerState<EpisodeDetailScreen> {
|
|||
final seasonDetails = details.series;
|
||||
final episodeDetails = details.episode;
|
||||
final wrapAlignment =
|
||||
AdaptiveLayout.of(context).layout != LayoutState.phone ? WrapAlignment.start : WrapAlignment.center;
|
||||
AdaptiveLayout.viewSizeOf(context) != ViewSize.phone ? WrapAlignment.start : WrapAlignment.center;
|
||||
|
||||
return DetailScaffold(
|
||||
label: widget.item.name,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
|
@ -39,7 +40,7 @@ class _ItemDetailScreenState extends ConsumerState<MovieDetailScreen> {
|
|||
Widget build(BuildContext context) {
|
||||
final details = ref.watch(providerInstance);
|
||||
final wrapAlignment =
|
||||
AdaptiveLayout.of(context).layout != LayoutState.phone ? WrapAlignment.start : WrapAlignment.center;
|
||||
AdaptiveLayout.viewSizeOf(context) != ViewSize.phone ? WrapAlignment.start : WrapAlignment.center;
|
||||
|
||||
return DetailScaffold(
|
||||
label: widget.item.name,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
|
|
@ -56,7 +57,7 @@ class _PersonDetailScreenState extends ConsumerState<PersonDetailScreen> {
|
|||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
width: AdaptiveLayout.of(context).layout == LayoutState.phone
|
||||
width: AdaptiveLayout.viewSizeOf(context) == ViewSize.phone
|
||||
? MediaQuery.of(context).size.width
|
||||
: MediaQuery.of(context).size.width / 3.5,
|
||||
child: AspectRatio(
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
|
|
@ -43,7 +44,7 @@ class _SeriesDetailScreenState extends ConsumerState<SeriesDetailScreen> {
|
|||
Widget build(BuildContext context) {
|
||||
final details = ref.watch(providerId);
|
||||
final wrapAlignment =
|
||||
AdaptiveLayout.of(context).layout != LayoutState.phone ? WrapAlignment.start : WrapAlignment.center;
|
||||
AdaptiveLayout.viewSizeOf(context) != ViewSize.phone ? WrapAlignment.start : WrapAlignment.center;
|
||||
|
||||
return DetailScaffold(
|
||||
label: details?.name ?? "",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||
import 'package:fladder/routes/auto_router.gr.dart';
|
||||
import 'package:fladder/screens/shared/nested_scaffold.dart';
|
||||
|
|
@ -32,7 +33,7 @@ class FavouritesScreen extends ConsumerWidget {
|
|||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
controller: AdaptiveLayout.scrollOf(context),
|
||||
slivers: [
|
||||
if (AdaptiveLayout.of(context).layout == LayoutState.phone)
|
||||
if (AdaptiveLayout.viewSizeOf(context) == ViewSize.phone)
|
||||
NestedSliverAppBar(
|
||||
searchTitle: "${context.localized.search} ${context.localized.favorites.toLowerCase()}...",
|
||||
parent: context,
|
||||
|
|
|
|||
|
|
@ -25,51 +25,54 @@ class HomeScreen extends ConsumerWidget {
|
|||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final canDownload = ref.watch(showSyncButtonProviderProvider);
|
||||
final destinations = HomeTabs.values.map((e) {
|
||||
switch (e) {
|
||||
case HomeTabs.dashboard:
|
||||
return DestinationModel(
|
||||
label: context.localized.navigationDashboard,
|
||||
icon: const Icon(IconsaxOutline.home),
|
||||
selectedIcon: const Icon(IconsaxBold.home),
|
||||
route: const DashboardRoute(),
|
||||
action: () => context.router.navigate(const DashboardRoute()),
|
||||
floatingActionButton: AdaptiveFab(
|
||||
context: context,
|
||||
title: context.localized.search,
|
||||
key: Key(e.name.capitalize()),
|
||||
onPressed: () => context.router.navigate(LibrarySearchRoute()),
|
||||
child: const Icon(IconsaxOutline.search_normal_1),
|
||||
),
|
||||
);
|
||||
case HomeTabs.favorites:
|
||||
return DestinationModel(
|
||||
label: context.localized.navigationFavorites,
|
||||
icon: const Icon(IconsaxOutline.heart),
|
||||
selectedIcon: const Icon(IconsaxBold.heart),
|
||||
route: const FavouritesRoute(),
|
||||
floatingActionButton: AdaptiveFab(
|
||||
context: context,
|
||||
title: context.localized.filter(0),
|
||||
key: Key(e.name.capitalize()),
|
||||
onPressed: () => context.router.navigate(LibrarySearchRoute(favourites: true)),
|
||||
child: const Icon(IconsaxOutline.heart_search),
|
||||
),
|
||||
action: () => context.router.navigate(const FavouritesRoute()),
|
||||
);
|
||||
case HomeTabs.sync:
|
||||
if (canDownload) {
|
||||
return DestinationModel(
|
||||
label: context.localized.navigationSync,
|
||||
icon: const Icon(IconsaxOutline.cloud),
|
||||
selectedIcon: const Icon(IconsaxBold.cloud),
|
||||
route: SyncedRoute(),
|
||||
action: () => context.router.navigate(SyncedRoute()),
|
||||
);
|
||||
final destinations = HomeTabs.values
|
||||
.map((e) {
|
||||
switch (e) {
|
||||
case HomeTabs.dashboard:
|
||||
return DestinationModel(
|
||||
label: context.localized.navigationDashboard,
|
||||
icon: const Icon(IconsaxOutline.home),
|
||||
selectedIcon: const Icon(IconsaxBold.home),
|
||||
route: const DashboardRoute(),
|
||||
action: () => context.router.navigate(const DashboardRoute()),
|
||||
floatingActionButton: AdaptiveFab(
|
||||
context: context,
|
||||
title: context.localized.search,
|
||||
key: Key(e.name.capitalize()),
|
||||
onPressed: () => context.router.navigate(LibrarySearchRoute()),
|
||||
child: const Icon(IconsaxOutline.search_normal_1),
|
||||
),
|
||||
);
|
||||
case HomeTabs.favorites:
|
||||
return DestinationModel(
|
||||
label: context.localized.navigationFavorites,
|
||||
icon: const Icon(IconsaxOutline.heart),
|
||||
selectedIcon: const Icon(IconsaxBold.heart),
|
||||
route: const FavouritesRoute(),
|
||||
floatingActionButton: AdaptiveFab(
|
||||
context: context,
|
||||
title: context.localized.filter(0),
|
||||
key: Key(e.name.capitalize()),
|
||||
onPressed: () => context.router.navigate(LibrarySearchRoute(favourites: true)),
|
||||
child: const Icon(IconsaxOutline.heart_search),
|
||||
),
|
||||
action: () => context.router.navigate(const FavouritesRoute()),
|
||||
);
|
||||
case HomeTabs.sync:
|
||||
if (canDownload) {
|
||||
return DestinationModel(
|
||||
label: context.localized.navigationSync,
|
||||
icon: const Icon(IconsaxOutline.cloud),
|
||||
selectedIcon: const Icon(IconsaxBold.cloud),
|
||||
route: SyncedRoute(),
|
||||
action: () => context.router.navigate(SyncedRoute()),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
})
|
||||
.nonNulls
|
||||
.toList();
|
||||
return HeroControllerScope(
|
||||
controller: HeroController(),
|
||||
child: AutoRouter(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:fladder/models/items/photos_model.dart';
|
||||
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||
import 'package:fladder/models/view_model.dart';
|
||||
import 'package:fladder/providers/library_provider.dart';
|
||||
import 'package:fladder/screens/photo_viewer/photo_viewer_screen.dart';
|
||||
|
|
@ -27,7 +28,7 @@ class TimelineTab extends ConsumerStatefulWidget {
|
|||
class _TimelineTabState extends ConsumerState<TimelineTab> with AutomaticKeepAliveClientMixin {
|
||||
final itemScrollController = ItemScrollController();
|
||||
double get posterCount {
|
||||
if (AdaptiveLayout.of(context).layout == LayoutState.desktop) {
|
||||
if (AdaptiveLayout.viewSizeOf(context) == ViewSize.desktop) {
|
||||
return 200;
|
||||
}
|
||||
return 125;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import 'package:fladder/models/library_search/library_search_model.dart';
|
|||
import 'package:fladder/models/library_search/library_search_options.dart';
|
||||
import 'package:fladder/models/media_playback_model.dart';
|
||||
import 'package:fladder/models/playlist_model.dart';
|
||||
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||
import 'package:fladder/providers/library_search_provider.dart';
|
||||
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||
import 'package:fladder/providers/video_player_provider.dart';
|
||||
|
|
@ -227,7 +228,7 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
|
|||
child: MediaQuery.removeViewInsets(
|
||||
context: context,
|
||||
child: ClipRRect(
|
||||
borderRadius: AdaptiveLayout.of(context).layout == LayoutState.desktop
|
||||
borderRadius: AdaptiveLayout.viewSizeOf(context) == ViewSize.desktop
|
||||
? BorderRadius.circular(15)
|
||||
: BorderRadius.circular(0),
|
||||
child: FladderScrollbar(
|
||||
|
|
@ -419,7 +420,7 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
|
|||
),
|
||||
);
|
||||
}),
|
||||
if (AdaptiveLayout.of(context).layout == LayoutState.phone) ...[
|
||||
if (AdaptiveLayout.layoutModeOf(context) == LayoutMode.single) ...[
|
||||
const SizedBox(width: 6),
|
||||
const SizedBox.square(dimension: 46, child: SettingsUserIcon()),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||
import 'package:fladder/providers/settings/home_settings_provider.dart';
|
||||
import 'package:fladder/screens/settings/settings_list_tile.dart';
|
||||
import 'package:fladder/screens/settings/widgets/settings_label_divider.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:fladder/util/option_dialogue.dart';
|
||||
|
||||
List<Widget> buildClientSettingsAdvanced(BuildContext context, WidgetRef ref) {
|
||||
return [
|
||||
SettingsLabelDivider(label: context.localized.advanced),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.settingsLayoutSizesTitle),
|
||||
subLabel: Text(context.localized.settingsLayoutSizesDesc),
|
||||
onTap: () async {
|
||||
final newItems = await openMultiSelectOptions<ViewSize>(
|
||||
context,
|
||||
label: context.localized.settingsLayoutSizesTitle,
|
||||
items: ViewSize.values,
|
||||
allowMultiSelection: true,
|
||||
selected: ref.read(homeSettingsProvider.select((value) => value.layoutStates.toList())),
|
||||
itemBuilder: (type, selected, tap) => CheckboxListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
value: selected,
|
||||
onChanged: (value) => tap(),
|
||||
title: Text(type.name),
|
||||
),
|
||||
);
|
||||
ref.read(homeSettingsProvider.notifier).setViewSize(newItems.toSet());
|
||||
},
|
||||
trailing: Card(
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
shadowColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(ref
|
||||
.watch(homeSettingsProvider.select((value) => value.layoutStates.toList()))
|
||||
.map((e) => e.label(context))
|
||||
.join(', ')),
|
||||
),
|
||||
),
|
||||
),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.settingsLayoutModesTitle),
|
||||
subLabel: Text(context.localized.settingsLayoutModesDesc),
|
||||
onTap: () async {
|
||||
final newItems = await openMultiSelectOptions<LayoutMode>(
|
||||
context,
|
||||
label: context.localized.settingsLayoutModesTitle,
|
||||
items: LayoutMode.values,
|
||||
allowMultiSelection: true,
|
||||
selected: ref.read(homeSettingsProvider.select((value) => value.screenLayouts.toList())),
|
||||
itemBuilder: (type, selected, tap) => CheckboxListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
value: selected,
|
||||
onChanged: (value) => tap(),
|
||||
title: Text(type.label(context)),
|
||||
),
|
||||
);
|
||||
ref.read(homeSettingsProvider.notifier).setLayoutModes(newItems.toSet());
|
||||
},
|
||||
trailing: Card(
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
shadowColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(ref
|
||||
.watch(homeSettingsProvider.select((value) => value.screenLayouts.toList()))
|
||||
.map((e) => e.label(context))
|
||||
.join(', ')),
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||
import 'package:fladder/providers/settings/home_settings_provider.dart';
|
||||
import 'package:fladder/screens/settings/settings_list_tile.dart';
|
||||
import 'package:fladder/screens/settings/widgets/settings_label_divider.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:fladder/widgets/shared/enum_selection.dart';
|
||||
|
||||
List<Widget> buildClientSettingsDashboard(BuildContext context, WidgetRef ref) {
|
||||
final clientSettings = ref.watch(clientSettingsProvider);
|
||||
return [
|
||||
SettingsLabelDivider(label: context.localized.dashboard),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.settingsHomeBannerTitle),
|
||||
subLabel: Text(context.localized.settingsHomeBannerDescription),
|
||||
trailing: EnumBox(
|
||||
current: ref.watch(
|
||||
homeSettingsProvider.select(
|
||||
(value) => value.homeBanner.label(context),
|
||||
),
|
||||
),
|
||||
itemBuilder: (context) => HomeBanner.values
|
||||
.map(
|
||||
(entry) => PopupMenuItem(
|
||||
value: entry,
|
||||
child: Text(entry.label(context)),
|
||||
onTap: () =>
|
||||
ref.read(homeSettingsProvider.notifier).update((context) => context.copyWith(homeBanner: entry)),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
if (ref.watch(homeSettingsProvider.select((value) => value.homeBanner)) != HomeBanner.hide)
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.settingsHomeBannerInformationTitle),
|
||||
subLabel: Text(context.localized.settingsHomeBannerInformationDesc),
|
||||
trailing: EnumBox(
|
||||
current: ref.watch(
|
||||
homeSettingsProvider.select((value) => value.carouselSettings.label(context)),
|
||||
),
|
||||
itemBuilder: (context) => HomeCarouselSettings.values
|
||||
.map(
|
||||
(entry) => PopupMenuItem(
|
||||
value: entry,
|
||||
child: Text(entry.label(context)),
|
||||
onTap: () => ref
|
||||
.read(homeSettingsProvider.notifier)
|
||||
.update((context) => context.copyWith(carouselSettings: entry)),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.settingsHomeNextUpTitle),
|
||||
subLabel: Text(context.localized.settingsHomeNextUpDesc),
|
||||
trailing: EnumBox(
|
||||
current: ref.watch(
|
||||
homeSettingsProvider.select(
|
||||
(value) => value.nextUp.label(context),
|
||||
),
|
||||
),
|
||||
itemBuilder: (context) => HomeNextUp.values
|
||||
.map(
|
||||
(entry) => PopupMenuItem(
|
||||
value: entry,
|
||||
child: Text(entry.label(context)),
|
||||
onTap: () =>
|
||||
ref.read(homeSettingsProvider.notifier).update((context) => context.copyWith(nextUp: entry)),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.clientSettingsShowAllCollectionsTitle),
|
||||
subLabel: Text(context.localized.clientSettingsShowAllCollectionsDesc),
|
||||
onTap: () => ref
|
||||
.read(clientSettingsProvider.notifier)
|
||||
.update((current) => current.copyWith(showAllCollectionTypes: !current.showAllCollectionTypes)),
|
||||
trailing: Switch(
|
||||
value: clientSettings.showAllCollectionTypes,
|
||||
onChanged: (value) => ref
|
||||
.read(clientSettingsProvider.notifier)
|
||||
.update((current) => current.copyWith(showAllCollectionTypes: value)),
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:ficonsax/ficonsax.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||
import 'package:fladder/providers/sync_provider.dart';
|
||||
import 'package:fladder/providers/user_provider.dart';
|
||||
import 'package:fladder/screens/settings/settings_list_tile.dart';
|
||||
import 'package:fladder/screens/settings/widgets/settings_label_divider.dart';
|
||||
import 'package:fladder/screens/shared/default_alert_dialog.dart';
|
||||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:fladder/util/size_formatting.dart';
|
||||
|
||||
List<Widget> buildClientSettingsDownload(BuildContext context, WidgetRef ref, Function setState) {
|
||||
final clientSettings = ref.watch(clientSettingsProvider);
|
||||
final currentFolder = ref.watch(syncProvider.notifier).savePath;
|
||||
final canSync = ref.watch(userProvider.select((value) => value?.canDownload ?? false));
|
||||
|
||||
return [
|
||||
if (canSync && !kIsWeb) ...[
|
||||
SettingsLabelDivider(label: context.localized.downloadsTitle),
|
||||
if (AdaptiveLayout.of(context).isDesktop) ...[
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.downloadsPath),
|
||||
subLabel: Text(currentFolder ?? "-"),
|
||||
onTap: currentFolder != null
|
||||
? () async => await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(context.localized.pathEditTitle),
|
||||
content: Text(context.localized.pathEditDesc),
|
||||
actions: [
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
String? selectedDirectory = await FilePicker.platform.getDirectoryPath(
|
||||
dialogTitle: context.localized.pathEditSelect, initialDirectory: currentFolder);
|
||||
if (selectedDirectory != null) {
|
||||
ref.read(clientSettingsProvider.notifier).setSyncPath(selectedDirectory);
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(context.localized.change),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
: () async {
|
||||
String? selectedDirectory = await FilePicker.platform
|
||||
.getDirectoryPath(dialogTitle: context.localized.pathEditSelect, initialDirectory: currentFolder);
|
||||
if (selectedDirectory != null) {
|
||||
ref.read(clientSettingsProvider.notifier).setSyncPath(selectedDirectory);
|
||||
}
|
||||
},
|
||||
trailing: currentFolder?.isNotEmpty == true
|
||||
? IconButton(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
onPressed: () async => await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(context.localized.pathClearTitle),
|
||||
content: Text(context.localized.pathEditDesc),
|
||||
actions: [
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
ref.read(clientSettingsProvider.notifier).setSyncPath(null);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(context.localized.clear),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
icon: const Icon(IconsaxOutline.folder_minus),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
],
|
||||
FutureBuilder(
|
||||
future: ref.watch(syncProvider.notifier).directorySize,
|
||||
builder: (context, snapshot) {
|
||||
final data = snapshot.data ?? 0;
|
||||
return SettingsListTile(
|
||||
label: Text(context.localized.downloadsSyncedData),
|
||||
subLabel: Text(data.byteFormat ?? ""),
|
||||
trailing: FilledButton(
|
||||
onPressed: () {
|
||||
showDefaultAlertDialog(
|
||||
context,
|
||||
context.localized.downloadsClearTitle,
|
||||
context.localized.downloadsClearDesc,
|
||||
(context) async {
|
||||
await ref.read(syncProvider.notifier).clear();
|
||||
setState(() {});
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
context.localized.clear,
|
||||
(context) => Navigator.of(context).pop(),
|
||||
context.localized.cancel,
|
||||
);
|
||||
},
|
||||
child: Text(context.localized.clear),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.clientSettingsRequireWifiTitle),
|
||||
subLabel: Text(context.localized.clientSettingsRequireWifiDesc),
|
||||
onTap: () => ref.read(clientSettingsProvider.notifier).setRequireWifi(!clientSettings.requireWifi),
|
||||
trailing: Switch(
|
||||
value: clientSettings.requireWifi,
|
||||
onChanged: (value) => ref.read(clientSettingsProvider.notifier).setRequireWifi(value),
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
],
|
||||
];
|
||||
}
|
||||
112
lib/screens/settings/client_sections/client_settings_theme.dart
Normal file
112
lib/screens/settings/client_sections/client_settings_theme.dart
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||
import 'package:fladder/screens/settings/settings_list_tile.dart';
|
||||
import 'package:fladder/screens/settings/widgets/settings_label_divider.dart';
|
||||
import 'package:fladder/util/color_extensions.dart';
|
||||
import 'package:fladder/util/custom_color_themes.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:fladder/util/option_dialogue.dart';
|
||||
import 'package:fladder/util/theme_mode_extension.dart';
|
||||
|
||||
List<Widget> buildClientSettingsTheme(BuildContext context, WidgetRef ref) {
|
||||
final clientSettings = ref.watch(clientSettingsProvider);
|
||||
return [
|
||||
SettingsLabelDivider(label: context.localized.theme),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.mode),
|
||||
subLabel: Text(clientSettings.themeMode.label(context)),
|
||||
onTap: () => openMultiSelectOptions<ThemeMode>(
|
||||
context,
|
||||
label: "${context.localized.theme} ${context.localized.mode}",
|
||||
items: ThemeMode.values,
|
||||
selected: [ref.read(clientSettingsProvider.select((value) => value.themeMode))],
|
||||
onChanged: (values) => ref.read(clientSettingsProvider.notifier).setThemeMode(values.first),
|
||||
itemBuilder: (type, selected, tap) => RadioListTile(
|
||||
value: type,
|
||||
title: Text(type.label(context)),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
groupValue: ref.read(clientSettingsProvider.select((value) => value.themeMode)),
|
||||
onChanged: (value) => tap(),
|
||||
),
|
||||
),
|
||||
),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.color),
|
||||
subLabel: Text(clientSettings.themeColor?.name ?? context.localized.dynamicText),
|
||||
onTap: () => openMultiSelectOptions<ColorThemes?>(
|
||||
context,
|
||||
label: context.localized.settingsLayoutModesTitle,
|
||||
items: [null, ...ColorThemes.values],
|
||||
selected: [(ref.read(clientSettingsProvider.select((value) => value.themeColor)))],
|
||||
onChanged: (values) => ref.read(clientSettingsProvider.notifier).setThemeColor(values.first),
|
||||
itemBuilder: (type, selected, tap) => RadioListTile<ColorThemes?>(
|
||||
groupValue: ref.read(clientSettingsProvider.select((value) => value.themeColor)),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
value: type,
|
||||
onChanged: (value) => tap(),
|
||||
title: Row(
|
||||
children: [
|
||||
Container(
|
||||
height: 24,
|
||||
width: 24,
|
||||
decoration: BoxDecoration(
|
||||
gradient: type == null
|
||||
? const SweepGradient(
|
||||
center: FractionalOffset.center,
|
||||
colors: <Color>[
|
||||
Color(0xFF4285F4), // blue
|
||||
Color(0xFF34A853), // green
|
||||
Color(0xFFFBBC05), // yellow
|
||||
Color(0xFFEA4335), // red
|
||||
Color(0xFF4285F4), // blue again to seamlessly transition to the start
|
||||
],
|
||||
stops: <double>[0.0, 0.25, 0.5, 0.75, 1.0],
|
||||
)
|
||||
: null,
|
||||
color: type?.color,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(type?.name ?? context.localized.dynamicText),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.clientSettingsSchemeVariantTitle),
|
||||
subLabel: Text(clientSettings.schemeVariant.label(context)),
|
||||
onTap: () async {
|
||||
await openMultiSelectOptions<DynamicSchemeVariant>(
|
||||
context,
|
||||
label: context.localized.settingsLayoutModesTitle,
|
||||
items: DynamicSchemeVariant.values,
|
||||
selected: [(ref.read(clientSettingsProvider.select((value) => value.schemeVariant)))],
|
||||
onChanged: (values) => ref.read(clientSettingsProvider.notifier).setSchemeVariant(values.first),
|
||||
itemBuilder: (type, selected, tap) => RadioListTile<DynamicSchemeVariant>(
|
||||
groupValue: selected ? type : null,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
value: type,
|
||||
onChanged: (value) => tap(),
|
||||
title: Text(type.label(context)),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.amoledBlack),
|
||||
subLabel: Text(clientSettings.amoledBlack ? context.localized.enabled : context.localized.disabled),
|
||||
onTap: () => ref.read(clientSettingsProvider.notifier).setAmoledBlack(!clientSettings.amoledBlack),
|
||||
trailing: Switch(
|
||||
value: clientSettings.amoledBlack,
|
||||
onChanged: (value) => ref.read(clientSettingsProvider.notifier).setAmoledBlack(value),
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
];
|
||||
}
|
||||
155
lib/screens/settings/client_sections/client_settings_visual.dart
Normal file
155
lib/screens/settings/client_sections/client_settings_visual.dart
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||
import 'package:fladder/screens/settings/settings_list_tile.dart';
|
||||
import 'package:fladder/screens/settings/widgets/settings_label_divider.dart';
|
||||
import 'package:fladder/screens/shared/input_fields.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:fladder/widgets/shared/fladder_slider.dart';
|
||||
|
||||
List<Widget> buildClientSettingsVisual(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
TextEditingController nextUpDaysEditor,
|
||||
TextEditingController libraryPageSizeController,
|
||||
) {
|
||||
final clientSettings = ref.watch(clientSettingsProvider);
|
||||
Locale currentLocale = WidgetsBinding.instance.platformDispatcher.locale;
|
||||
return [
|
||||
SettingsLabelDivider(label: context.localized.settingsVisual),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.displayLanguage),
|
||||
trailing: Localizations.override(
|
||||
context: context,
|
||||
locale: ref.watch(
|
||||
clientSettingsProvider.select(
|
||||
(value) => (value.selectedLocale ?? currentLocale),
|
||||
),
|
||||
),
|
||||
child: Builder(builder: (context) {
|
||||
return EnumBox(
|
||||
current: context.localized.nativeName,
|
||||
itemBuilder: (context) {
|
||||
return [
|
||||
...AppLocalizations.supportedLocales.map(
|
||||
(entry) => PopupMenuItem(
|
||||
value: entry,
|
||||
child: Localizations.override(
|
||||
context: context,
|
||||
locale: entry,
|
||||
child: Builder(builder: (context) {
|
||||
return Text(
|
||||
context.localized.nativeName,
|
||||
);
|
||||
}),
|
||||
),
|
||||
onTap: () => ref
|
||||
.read(clientSettingsProvider.notifier)
|
||||
.update((state) => state.copyWith(selectedLocale: entry)),
|
||||
),
|
||||
)
|
||||
];
|
||||
},
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.settingsBlurredPlaceholderTitle),
|
||||
subLabel: Text(context.localized.settingsBlurredPlaceholderDesc),
|
||||
onTap: () => ref.read(clientSettingsProvider.notifier).setBlurPlaceholders(!clientSettings.blurPlaceHolders),
|
||||
trailing: Switch(
|
||||
value: clientSettings.blurPlaceHolders,
|
||||
onChanged: (value) => ref.read(clientSettingsProvider.notifier).setBlurPlaceholders(value),
|
||||
),
|
||||
),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.settingsBlurEpisodesTitle),
|
||||
subLabel: Text(context.localized.settingsBlurEpisodesDesc),
|
||||
onTap: () => ref.read(clientSettingsProvider.notifier).setBlurEpisodes(!clientSettings.blurUpcomingEpisodes),
|
||||
trailing: Switch(
|
||||
value: clientSettings.blurUpcomingEpisodes,
|
||||
onChanged: (value) => ref.read(clientSettingsProvider.notifier).setBlurEpisodes(value),
|
||||
),
|
||||
),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.settingsEnableOsMediaControls),
|
||||
onTap: () => ref.read(clientSettingsProvider.notifier).setMediaKeys(!clientSettings.enableMediaKeys),
|
||||
trailing: Switch(
|
||||
value: clientSettings.enableMediaKeys,
|
||||
onChanged: (value) => ref.read(clientSettingsProvider.notifier).setMediaKeys(value),
|
||||
),
|
||||
),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.settingsNextUpCutoffDays),
|
||||
trailing: SizedBox(
|
||||
width: 100,
|
||||
child: IntInputField(
|
||||
suffix: context.localized.days,
|
||||
controller: nextUpDaysEditor,
|
||||
onSubmitted: (value) {
|
||||
if (value != null) {
|
||||
ref.read(clientSettingsProvider.notifier).update((current) => current.copyWith(
|
||||
nextUpDateCutoff: Duration(days: value),
|
||||
));
|
||||
}
|
||||
},
|
||||
)),
|
||||
),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.libraryPageSizeTitle),
|
||||
subLabel: Text(context.localized.libraryPageSizeDesc),
|
||||
trailing: SizedBox(
|
||||
width: 100,
|
||||
child: IntInputField(
|
||||
controller: libraryPageSizeController,
|
||||
placeHolder: "500",
|
||||
onSubmitted: (value) => ref.read(clientSettingsProvider.notifier).update(
|
||||
(current) => current.copyWith(libraryPageSize: value),
|
||||
),
|
||||
)),
|
||||
),
|
||||
SettingsListTile(
|
||||
label: Text(AdaptiveLayout.of(context).isDesktop
|
||||
? context.localized.settingsShowScaleSlider
|
||||
: context.localized.settingsPosterPinch),
|
||||
onTap: () => ref.read(clientSettingsProvider.notifier).update(
|
||||
(current) => current.copyWith(pinchPosterZoom: !current.pinchPosterZoom),
|
||||
),
|
||||
trailing: Switch(
|
||||
value: clientSettings.pinchPosterZoom,
|
||||
onChanged: (value) => ref.read(clientSettingsProvider.notifier).update(
|
||||
(current) => current.copyWith(pinchPosterZoom: value),
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.settingsPosterSize),
|
||||
trailing: Text(
|
||||
clientSettings.posterSize.toString(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: FladderSlider(
|
||||
min: 0.5,
|
||||
max: 1.5,
|
||||
value: clientSettings.posterSize,
|
||||
divisions: 20,
|
||||
onChanged: (value) =>
|
||||
ref.read(clientSettingsProvider.notifier).update((current) => current.copyWith(posterSize: value)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(),
|
||||
];
|
||||
}
|
||||
|
|
@ -2,33 +2,23 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:ficonsax/ficonsax.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||
import 'package:fladder/providers/settings/home_settings_provider.dart';
|
||||
import 'package:fladder/providers/shared_provider.dart';
|
||||
import 'package:fladder/providers/sync_provider.dart';
|
||||
import 'package:fladder/providers/user_provider.dart';
|
||||
import 'package:fladder/routes/auto_router.gr.dart';
|
||||
import 'package:fladder/screens/settings/client_sections/client_settings_advanced.dart';
|
||||
import 'package:fladder/screens/settings/client_sections/client_settings_dashboard.dart';
|
||||
import 'package:fladder/screens/settings/client_sections/client_settings_download.dart';
|
||||
import 'package:fladder/screens/settings/client_sections/client_settings_theme.dart';
|
||||
import 'package:fladder/screens/settings/client_sections/client_settings_visual.dart';
|
||||
import 'package:fladder/screens/settings/settings_list_tile.dart';
|
||||
import 'package:fladder/screens/settings/settings_scaffold.dart';
|
||||
import 'package:fladder/screens/settings/widgets/settings_label_divider.dart';
|
||||
import 'package:fladder/screens/shared/default_alert_dialog.dart';
|
||||
import 'package:fladder/screens/shared/input_fields.dart';
|
||||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
import 'package:fladder/util/color_extensions.dart';
|
||||
import 'package:fladder/util/custom_color_themes.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:fladder/util/option_dialogue.dart';
|
||||
import 'package:fladder/util/simple_duration_picker.dart';
|
||||
import 'package:fladder/util/size_formatting.dart';
|
||||
import 'package:fladder/util/theme_mode_extension.dart';
|
||||
import 'package:fladder/widgets/shared/enum_selection.dart';
|
||||
import 'package:fladder/widgets/shared/fladder_slider.dart';
|
||||
|
||||
@RoutePage()
|
||||
class ClientSettingsPage extends ConsumerStatefulWidget {
|
||||
|
|
@ -48,114 +38,15 @@ class _ClientSettingsPageState extends ConsumerState<ClientSettingsPage> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final clientSettings = ref.watch(clientSettingsProvider);
|
||||
final showBackground = AdaptiveLayout.of(context).layout != LayoutState.phone &&
|
||||
AdaptiveLayout.of(context).size != ScreenLayout.single;
|
||||
final currentFolder = ref.watch(syncProvider.notifier).savePath;
|
||||
Locale currentLocale = WidgetsBinding.instance.platformDispatcher.locale;
|
||||
final showBackground = AdaptiveLayout.viewSizeOf(context) != ViewSize.phone &&
|
||||
AdaptiveLayout.layoutModeOf(context) != LayoutMode.single;
|
||||
|
||||
final canSync = ref.watch(userProvider.select((value) => value?.canDownload ?? false));
|
||||
return Card(
|
||||
elevation: showBackground ? 2 : 0,
|
||||
child: SettingsScaffold(
|
||||
label: "Fladder",
|
||||
items: [
|
||||
if (canSync && !kIsWeb) ...[
|
||||
SettingsLabelDivider(label: context.localized.downloadsTitle),
|
||||
if (AdaptiveLayout.of(context).isDesktop) ...[
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.downloadsPath),
|
||||
subLabel: Text(currentFolder ?? "-"),
|
||||
onTap: currentFolder != null
|
||||
? () async => await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(context.localized.pathEditTitle),
|
||||
content: Text(context.localized.pathEditDesc),
|
||||
actions: [
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
String? selectedDirectory = await FilePicker.platform.getDirectoryPath(
|
||||
dialogTitle: context.localized.pathEditSelect, initialDirectory: currentFolder);
|
||||
if (selectedDirectory != null) {
|
||||
ref.read(clientSettingsProvider.notifier).setSyncPath(selectedDirectory);
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(context.localized.change),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
: () async {
|
||||
String? selectedDirectory = await FilePicker.platform.getDirectoryPath(
|
||||
dialogTitle: context.localized.pathEditSelect, initialDirectory: currentFolder);
|
||||
if (selectedDirectory != null) {
|
||||
ref.read(clientSettingsProvider.notifier).setSyncPath(selectedDirectory);
|
||||
}
|
||||
},
|
||||
trailing: currentFolder?.isNotEmpty == true
|
||||
? IconButton(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
onPressed: () async => await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(context.localized.pathClearTitle),
|
||||
content: Text(context.localized.pathEditDesc),
|
||||
actions: [
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
ref.read(clientSettingsProvider.notifier).setSyncPath(null);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(context.localized.clear),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
icon: const Icon(IconsaxOutline.folder_minus),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
],
|
||||
FutureBuilder(
|
||||
future: ref.watch(syncProvider.notifier).directorySize,
|
||||
builder: (context, snapshot) {
|
||||
final data = snapshot.data ?? 0;
|
||||
return SettingsListTile(
|
||||
label: Text(context.localized.downloadsSyncedData),
|
||||
subLabel: Text(data.byteFormat ?? ""),
|
||||
trailing: FilledButton(
|
||||
onPressed: () {
|
||||
showDefaultAlertDialog(
|
||||
context,
|
||||
context.localized.downloadsClearTitle,
|
||||
context.localized.downloadsClearDesc,
|
||||
(context) async {
|
||||
await ref.read(syncProvider.notifier).clear();
|
||||
setState(() {});
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
context.localized.clear,
|
||||
(context) => Navigator.of(context).pop(),
|
||||
context.localized.cancel,
|
||||
);
|
||||
},
|
||||
child: Text(context.localized.clear),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.clientSettingsRequireWifiTitle),
|
||||
subLabel: Text(context.localized.clientSettingsRequireWifiDesc),
|
||||
onTap: () => ref.read(clientSettingsProvider.notifier).setRequireWifi(!clientSettings.requireWifi),
|
||||
trailing: Switch(
|
||||
value: clientSettings.requireWifi,
|
||||
onChanged: (value) => ref.read(clientSettingsProvider.notifier).setRequireWifi(value),
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
],
|
||||
...buildClientSettingsDownload(context, ref, setState),
|
||||
SettingsLabelDivider(label: context.localized.lockscreen),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.timeOut),
|
||||
|
|
@ -166,329 +57,18 @@ class _ClientSettingsPageState extends ConsumerState<ClientSettingsPage> {
|
|||
initialValue: clientSettings.timeOut ?? const Duration(),
|
||||
);
|
||||
|
||||
ref.read(clientSettingsProvider.notifier).setTimeOut(timePicker != null
|
||||
if (timePicker == null) return;
|
||||
|
||||
ref.read(clientSettingsProvider.notifier).setTimeOut(timePicker != Duration.zero
|
||||
? Duration(minutes: timePicker.inMinutes, seconds: timePicker.inSeconds % 60)
|
||||
: null);
|
||||
},
|
||||
),
|
||||
const Divider(),
|
||||
SettingsLabelDivider(label: context.localized.dashboard),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.settingsHomeBannerTitle),
|
||||
subLabel: Text(context.localized.settingsHomeBannerDescription),
|
||||
trailing: EnumBox(
|
||||
current: ref.watch(
|
||||
homeSettingsProvider.select(
|
||||
(value) => value.homeBanner.label(context),
|
||||
),
|
||||
),
|
||||
itemBuilder: (context) => HomeBanner.values
|
||||
.map(
|
||||
(entry) => PopupMenuItem(
|
||||
value: entry,
|
||||
child: Text(entry.label(context)),
|
||||
onTap: () => ref
|
||||
.read(homeSettingsProvider.notifier)
|
||||
.update((context) => context.copyWith(homeBanner: entry)),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
if (ref.watch(homeSettingsProvider.select((value) => value.homeBanner)) != HomeBanner.hide)
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.settingsHomeBannerInformationTitle),
|
||||
subLabel: Text(context.localized.settingsHomeBannerInformationDesc),
|
||||
trailing: EnumBox(
|
||||
current: ref.watch(
|
||||
homeSettingsProvider.select((value) => value.carouselSettings.label(context)),
|
||||
),
|
||||
itemBuilder: (context) => HomeCarouselSettings.values
|
||||
.map(
|
||||
(entry) => PopupMenuItem(
|
||||
value: entry,
|
||||
child: Text(entry.label(context)),
|
||||
onTap: () => ref
|
||||
.read(homeSettingsProvider.notifier)
|
||||
.update((context) => context.copyWith(carouselSettings: entry)),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.settingsHomeNextUpTitle),
|
||||
subLabel: Text(context.localized.settingsHomeNextUpDesc),
|
||||
trailing: EnumBox(
|
||||
current: ref.watch(
|
||||
homeSettingsProvider.select(
|
||||
(value) => value.nextUp.label(context),
|
||||
),
|
||||
),
|
||||
itemBuilder: (context) => HomeNextUp.values
|
||||
.map(
|
||||
(entry) => PopupMenuItem(
|
||||
value: entry,
|
||||
child: Text(entry.label(context)),
|
||||
onTap: () =>
|
||||
ref.read(homeSettingsProvider.notifier).update((context) => context.copyWith(nextUp: entry)),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.clientSettingsShowAllCollectionsTitle),
|
||||
subLabel: Text(context.localized.clientSettingsShowAllCollectionsDesc),
|
||||
onTap: () => ref
|
||||
.read(clientSettingsProvider.notifier)
|
||||
.update((current) => current.copyWith(showAllCollectionTypes: !current.showAllCollectionTypes)),
|
||||
trailing: Switch(
|
||||
value: clientSettings.showAllCollectionTypes,
|
||||
onChanged: (value) => ref
|
||||
.read(clientSettingsProvider.notifier)
|
||||
.update((current) => current.copyWith(showAllCollectionTypes: value)),
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
SettingsLabelDivider(label: context.localized.settingsVisual),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.displayLanguage),
|
||||
trailing: Localizations.override(
|
||||
context: context,
|
||||
locale: ref.watch(
|
||||
clientSettingsProvider.select(
|
||||
(value) => (value.selectedLocale ?? currentLocale),
|
||||
),
|
||||
),
|
||||
child: Builder(builder: (context) {
|
||||
return EnumBox(
|
||||
current: context.localized.nativeName,
|
||||
itemBuilder: (context) {
|
||||
return [
|
||||
...AppLocalizations.supportedLocales.map(
|
||||
(entry) => PopupMenuItem(
|
||||
value: entry,
|
||||
child: Localizations.override(
|
||||
context: context,
|
||||
locale: entry,
|
||||
child: Builder(builder: (context) {
|
||||
return Text(
|
||||
context.localized.nativeName,
|
||||
);
|
||||
}),
|
||||
),
|
||||
onTap: () => ref
|
||||
.read(clientSettingsProvider.notifier)
|
||||
.update((state) => state.copyWith(selectedLocale: entry)),
|
||||
),
|
||||
)
|
||||
];
|
||||
},
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.settingsBlurredPlaceholderTitle),
|
||||
subLabel: Text(context.localized.settingsBlurredPlaceholderDesc),
|
||||
onTap: () =>
|
||||
ref.read(clientSettingsProvider.notifier).setBlurPlaceholders(!clientSettings.blurPlaceHolders),
|
||||
trailing: Switch(
|
||||
value: clientSettings.blurPlaceHolders,
|
||||
onChanged: (value) => ref.read(clientSettingsProvider.notifier).setBlurPlaceholders(value),
|
||||
),
|
||||
),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.settingsBlurEpisodesTitle),
|
||||
subLabel: Text(context.localized.settingsBlurEpisodesDesc),
|
||||
onTap: () =>
|
||||
ref.read(clientSettingsProvider.notifier).setBlurEpisodes(!clientSettings.blurUpcomingEpisodes),
|
||||
trailing: Switch(
|
||||
value: clientSettings.blurUpcomingEpisodes,
|
||||
onChanged: (value) => ref.read(clientSettingsProvider.notifier).setBlurEpisodes(value),
|
||||
),
|
||||
),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.settingsEnableOsMediaControls),
|
||||
onTap: () => ref.read(clientSettingsProvider.notifier).setMediaKeys(!clientSettings.enableMediaKeys),
|
||||
trailing: Switch(
|
||||
value: clientSettings.enableMediaKeys,
|
||||
onChanged: (value) => ref.read(clientSettingsProvider.notifier).setMediaKeys(value),
|
||||
),
|
||||
),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.settingsNextUpCutoffDays),
|
||||
trailing: SizedBox(
|
||||
width: 100,
|
||||
child: IntInputField(
|
||||
suffix: context.localized.days,
|
||||
controller: nextUpDaysEditor,
|
||||
onSubmitted: (value) {
|
||||
if (value != null) {
|
||||
ref.read(clientSettingsProvider.notifier).update((current) => current.copyWith(
|
||||
nextUpDateCutoff: Duration(days: value),
|
||||
));
|
||||
}
|
||||
},
|
||||
)),
|
||||
),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.libraryPageSizeTitle),
|
||||
subLabel: Text(context.localized.libraryPageSizeDesc),
|
||||
trailing: SizedBox(
|
||||
width: 100,
|
||||
child: IntInputField(
|
||||
controller: libraryPageSizeController,
|
||||
placeHolder: "500",
|
||||
onSubmitted: (value) => ref.read(clientSettingsProvider.notifier).update(
|
||||
(current) => current.copyWith(libraryPageSize: value),
|
||||
),
|
||||
)),
|
||||
),
|
||||
SettingsListTile(
|
||||
label: Text(AdaptiveLayout.of(context).isDesktop
|
||||
? context.localized.settingsShowScaleSlider
|
||||
: context.localized.settingsPosterPinch),
|
||||
onTap: () => ref.read(clientSettingsProvider.notifier).update(
|
||||
(current) => current.copyWith(pinchPosterZoom: !current.pinchPosterZoom),
|
||||
),
|
||||
trailing: Switch(
|
||||
value: clientSettings.pinchPosterZoom,
|
||||
onChanged: (value) => ref.read(clientSettingsProvider.notifier).update(
|
||||
(current) => current.copyWith(pinchPosterZoom: value),
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.settingsPosterSize),
|
||||
trailing: Text(
|
||||
clientSettings.posterSize.toString(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: FladderSlider(
|
||||
min: 0.5,
|
||||
max: 1.5,
|
||||
value: clientSettings.posterSize,
|
||||
divisions: 20,
|
||||
onChanged: (value) => ref
|
||||
.read(clientSettingsProvider.notifier)
|
||||
.update((current) => current.copyWith(posterSize: value)),
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
],
|
||||
),
|
||||
SettingsLabelDivider(label: context.localized.theme),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.mode),
|
||||
subLabel: Text(clientSettings.themeMode.label(context)),
|
||||
onTap: () => openOptionDialogue(
|
||||
context,
|
||||
label: "${context.localized.theme} ${context.localized.mode}",
|
||||
items: ThemeMode.values,
|
||||
itemBuilder: (type) => RadioListTile(
|
||||
value: type,
|
||||
title: Text(type?.label(context) ?? context.localized.other),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
groupValue: ref.read(clientSettingsProvider.select((value) => value.themeMode)),
|
||||
onChanged: (value) => ref.read(clientSettingsProvider.notifier).setThemeMode(value),
|
||||
),
|
||||
),
|
||||
),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.color),
|
||||
subLabel: Text(clientSettings.themeColor?.name ?? context.localized.dynamicText),
|
||||
onTap: () => openOptionDialogue<ColorThemes>(
|
||||
context,
|
||||
isNullable: !kIsWeb,
|
||||
label: context.localized.themeColor,
|
||||
items: ColorThemes.values,
|
||||
itemBuilder: (type) => Consumer(
|
||||
builder: (context, ref, child) => ListTile(
|
||||
title: Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: type == ref.watch(clientSettingsProvider.select((value) => value.themeColor)),
|
||||
onChanged: (value) => ref.read(clientSettingsProvider.notifier).setThemeColor(type),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Container(
|
||||
height: 24,
|
||||
width: 24,
|
||||
decoration: BoxDecoration(
|
||||
gradient: type == null
|
||||
? const SweepGradient(
|
||||
center: FractionalOffset.center,
|
||||
colors: <Color>[
|
||||
Color(0xFF4285F4), // blue
|
||||
Color(0xFF34A853), // green
|
||||
Color(0xFFFBBC05), // yellow
|
||||
Color(0xFFEA4335), // red
|
||||
Color(0xFF4285F4), // blue again to seamlessly transition to the start
|
||||
],
|
||||
stops: <double>[0.0, 0.25, 0.5, 0.75, 1.0],
|
||||
)
|
||||
: null,
|
||||
color: type?.color,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(type?.name ?? context.localized.dynamicText),
|
||||
],
|
||||
),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
onTap: () => ref.read(clientSettingsProvider.notifier).setThemeColor(type),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.clientSettingsSchemeVariantTitle),
|
||||
subLabel: Text(clientSettings.schemeVariant.label(context)),
|
||||
onTap: () => openOptionDialogue<DynamicSchemeVariant>(
|
||||
context,
|
||||
isNullable: false,
|
||||
label: context.localized.themeColor,
|
||||
items: DynamicSchemeVariant.values,
|
||||
itemBuilder: (type) => Consumer(
|
||||
builder: (context, ref, child) => ListTile(
|
||||
title: Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: type == ref.watch(clientSettingsProvider.select((value) => value.schemeVariant)),
|
||||
onChanged: (value) => ref.read(clientSettingsProvider.notifier).setSchemeVariant(type),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(type?.label(context) ?? ""),
|
||||
],
|
||||
),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
onTap: () => ref.read(clientSettingsProvider.notifier).setSchemeVariant(type),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.amoledBlack),
|
||||
subLabel: Text(clientSettings.amoledBlack ? context.localized.enabled : context.localized.disabled),
|
||||
onTap: () => ref.read(clientSettingsProvider.notifier).setAmoledBlack(!clientSettings.amoledBlack),
|
||||
trailing: Switch(
|
||||
value: clientSettings.amoledBlack,
|
||||
onChanged: (value) => ref.read(clientSettingsProvider.notifier).setAmoledBlack(value),
|
||||
),
|
||||
),
|
||||
if (AdaptiveLayout.of(context).isDesktop) ...[
|
||||
const Divider(),
|
||||
...buildClientSettingsDashboard(context, ref),
|
||||
...buildClientSettingsVisual(context, ref, nextUpDaysEditor, libraryPageSizeController),
|
||||
...buildClientSettingsTheme(context, ref),
|
||||
if (AdaptiveLayout.inputDeviceOf(context) == InputDevice.pointer) ...[
|
||||
SettingsLabelDivider(label: context.localized.controls),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.mouseDragSupport),
|
||||
|
|
@ -499,11 +79,13 @@ class _ClientSettingsPageState extends ConsumerState<ClientSettingsPage> {
|
|||
trailing: Switch(
|
||||
value: clientSettings.mouseDragSupport,
|
||||
onChanged: (value) => ref
|
||||
.read(clientSettingsProvider.notifier)
|
||||
.update((current) => current.copyWith(mouseDragSupport: !clientSettings.mouseDragSupport)),
|
||||
.read(clientSettingsProvider.notifier)
|
||||
.update((current) => current.copyWith(mouseDragSupport: !clientSettings.mouseDragSupport)),
|
||||
),
|
||||
),
|
||||
],
|
||||
const Divider(),
|
||||
...buildClientSettingsAdvanced(context, ref),
|
||||
if (kDebugMode) ...[
|
||||
const SizedBox(height: 64),
|
||||
SettingsListTile(
|
||||
|
|
@ -554,7 +136,6 @@ class _ClientSettingsPageState extends ConsumerState<ClientSettingsPage> {
|
|||
},
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||
import 'package:fladder/models/settings/video_player_settings.dart';
|
||||
import 'package:fladder/providers/settings/video_player_settings_provider.dart';
|
||||
import 'package:fladder/screens/settings/settings_list_tile.dart';
|
||||
|
|
@ -34,8 +35,8 @@ class _PlayerSettingsPageState extends ConsumerState<PlayerSettingsPage> {
|
|||
Widget build(BuildContext context) {
|
||||
final videoSettings = ref.watch(videoPlayerSettingsProvider);
|
||||
final provider = ref.read(videoPlayerSettingsProvider.notifier);
|
||||
final showBackground = AdaptiveLayout.of(context).layout != LayoutState.phone &&
|
||||
AdaptiveLayout.of(context).size != ScreenLayout.single;
|
||||
final showBackground = AdaptiveLayout.viewSizeOf(context) != ViewSize.phone &&
|
||||
AdaptiveLayout.layoutModeOf(context) != LayoutMode.single;
|
||||
return Card(
|
||||
elevation: showBackground ? 2 : 0,
|
||||
child: SettingsScaffold(
|
||||
|
|
@ -63,20 +64,19 @@ class _PlayerSettingsPageState extends ConsumerState<PlayerSettingsPage> {
|
|||
SettingsListTile(
|
||||
label: Text(context.localized.videoScalingFillScreenTitle),
|
||||
subLabel: Text(videoSettings.videoFit.label(context)),
|
||||
onTap: () => openOptionDialogue(
|
||||
onTap: () => openMultiSelectOptions(
|
||||
context,
|
||||
label: context.localized.videoScalingFillScreenTitle,
|
||||
items: BoxFit.values,
|
||||
itemBuilder: (type) => RadioListTile(
|
||||
title: Text(type?.label(context) ?? ""),
|
||||
selected: [ref.read(videoPlayerSettingsProvider.select((value) => value.videoFit))],
|
||||
onChanged: (values) => ref.read(videoPlayerSettingsProvider.notifier).setFitType(values.first),
|
||||
itemBuilder: (type, selected, tap) => RadioListTile(
|
||||
groupValue: ref.read(videoPlayerSettingsProvider.select((value) => value.videoFit)),
|
||||
title: Text(type.label(context)),
|
||||
value: type,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
groupValue: ref.read(videoPlayerSettingsProvider.select((value) => value.videoFit)),
|
||||
onChanged: (value) {
|
||||
provider.setFitType(value);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
onChanged: (value) => tap(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||
import 'package:fladder/providers/user_provider.dart';
|
||||
import 'package:fladder/screens/settings/settings_list_tile.dart';
|
||||
import 'package:fladder/screens/settings/settings_scaffold.dart';
|
||||
|
|
@ -21,8 +22,8 @@ class _UserSettingsPageState extends ConsumerState<SecuritySettingsPage> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final user = ref.watch(userProvider);
|
||||
final showBackground = AdaptiveLayout.of(context).layout != LayoutState.phone &&
|
||||
AdaptiveLayout.of(context).size != ScreenLayout.single;
|
||||
final showBackground = AdaptiveLayout.viewSizeOf(context) != ViewSize.phone &&
|
||||
AdaptiveLayout.layoutModeOf(context) != LayoutMode.single;
|
||||
return Card(
|
||||
elevation: showBackground ? 2 : 0,
|
||||
child: SettingsScaffold(
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||
import 'package:fladder/providers/user_provider.dart';
|
||||
import 'package:fladder/screens/shared/user_icon.dart';
|
||||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
|
|
@ -10,26 +12,29 @@ import 'package:fladder/util/router_extension.dart';
|
|||
|
||||
class SettingsScaffold extends ConsumerWidget {
|
||||
final String label;
|
||||
final bool showUserIcon;
|
||||
final ScrollController? scrollController;
|
||||
final List<Widget> items;
|
||||
final List<Widget> bottomActions;
|
||||
final bool showUserIcon;
|
||||
final bool showBackButtonNested;
|
||||
final Widget? floatingActionButton;
|
||||
const SettingsScaffold({
|
||||
required this.label,
|
||||
this.showUserIcon = false,
|
||||
this.scrollController,
|
||||
required this.items,
|
||||
this.bottomActions = const [],
|
||||
this.floatingActionButton,
|
||||
this.showUserIcon = false,
|
||||
this.showBackButtonNested = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final padding = MediaQuery.of(context).padding;
|
||||
final singleLayout = AdaptiveLayout.layoutModeOf(context) == LayoutMode.single;
|
||||
return Scaffold(
|
||||
backgroundColor: AdaptiveLayout.of(context).size == ScreenLayout.dual ? Colors.transparent : null,
|
||||
backgroundColor: AdaptiveLayout.layoutModeOf(context) == LayoutMode.dual ? Colors.transparent : null,
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
||||
floatingActionButton: floatingActionButton,
|
||||
body: Column(
|
||||
|
|
@ -38,10 +43,11 @@ class SettingsScaffold extends ConsumerWidget {
|
|||
child: CustomScrollView(
|
||||
controller: scrollController,
|
||||
slivers: [
|
||||
if (AdaptiveLayout.of(context).size == ScreenLayout.single)
|
||||
if (singleLayout)
|
||||
SliverAppBar.large(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
leading: context.router.backButton(),
|
||||
leading: BackButton(
|
||||
onPressed: () => backAction(context),
|
||||
),
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
titlePadding: const EdgeInsets.symmetric(horizontal: 16)
|
||||
.add(EdgeInsets.only(left: padding.left, right: padding.right, bottom: 4)),
|
||||
|
|
@ -51,11 +57,12 @@ class SettingsScaffold extends ConsumerWidget {
|
|||
const Spacer(),
|
||||
if (showUserIcon)
|
||||
SizedBox.fromSize(
|
||||
size: const Size.fromRadius(14),
|
||||
child: UserIcon(
|
||||
user: ref.watch(userProvider),
|
||||
cornerRadius: 200,
|
||||
))
|
||||
size: const Size.fromRadius(14),
|
||||
child: UserIcon(
|
||||
user: ref.watch(userProvider),
|
||||
cornerRadius: 200,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
expandedTitleScale: 1.2,
|
||||
|
|
@ -68,9 +75,15 @@ class SettingsScaffold extends ConsumerWidget {
|
|||
else
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
child: Text(AdaptiveLayout.of(context).size == ScreenLayout.single ? label : "",
|
||||
style: Theme.of(context).textTheme.headlineLarge),
|
||||
padding: MediaQuery.paddingOf(context),
|
||||
child: Row(
|
||||
children: [
|
||||
if (showBackButtonNested)
|
||||
BackButton(
|
||||
onPressed: () => backAction(context),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverPadding(
|
||||
|
|
@ -99,4 +112,16 @@ class SettingsScaffold extends ConsumerWidget {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
void backAction(BuildContext context) {
|
||||
if (kIsWeb) {
|
||||
if (AdaptiveLayout.layoutModeOf(context) == LayoutMode.single && context.tabsRouter.activeIndex != 0) {
|
||||
context.tabsRouter.setActiveIndex(0);
|
||||
} else {
|
||||
context.router.popForced();
|
||||
}
|
||||
} else {
|
||||
context.router.popBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'package:auto_route/auto_route.dart';
|
|||
import 'package:ficonsax/ficonsax.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||
import 'package:fladder/providers/auth_provider.dart';
|
||||
import 'package:fladder/providers/user_provider.dart';
|
||||
import 'package:fladder/routes/auto_router.gr.dart';
|
||||
|
|
@ -13,7 +14,6 @@ import 'package:fladder/screens/settings/settings_scaffold.dart';
|
|||
import 'package:fladder/screens/shared/fladder_icon.dart';
|
||||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:fladder/util/router_extension.dart';
|
||||
import 'package:fladder/util/theme_extensions.dart';
|
||||
|
||||
@RoutePage()
|
||||
|
|
@ -27,84 +27,84 @@ class SettingsScreen extends ConsumerStatefulWidget {
|
|||
class _SettingsScreenState extends ConsumerState<SettingsScreen> {
|
||||
final scrollController = ScrollController();
|
||||
final minVerticalPadding = 20.0;
|
||||
late LayoutMode lastAdaptiveLayout = AdaptiveLayout.layoutModeOf(context);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (AdaptiveLayout.of(context).size == ScreenLayout.single) {
|
||||
return Card(
|
||||
elevation: 0,
|
||||
child: _leftPane(context),
|
||||
);
|
||||
} else {
|
||||
return AutoRouter(
|
||||
builder: (context, content) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(flex: 1, child: _leftPane(context)),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: content,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
return AutoTabsRouter(
|
||||
builder: (context, content) {
|
||||
checkForNullIndex(context);
|
||||
return PopScope(
|
||||
canPop: context.tabsRouter.activeIndex == 0 || AdaptiveLayout.layoutModeOf(context) == LayoutMode.dual,
|
||||
onPopInvokedWithResult: (didPop, result) {
|
||||
if (!didPop) {
|
||||
context.tabsRouter.setActiveIndex(0);
|
||||
}
|
||||
},
|
||||
child: AdaptiveLayout.layoutModeOf(context) == LayoutMode.single
|
||||
? Card(
|
||||
elevation: 0,
|
||||
child: Stack(
|
||||
children: [_leftPane(context), content],
|
||||
),
|
||||
)
|
||||
: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(flex: 1, child: _leftPane(context)),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: content,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
//We have to navigate to the first screen after switching layouts && index == 0 otherwise the dual-layout is empty
|
||||
void checkForNullIndex(BuildContext context) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
final currentIndex = context.tabsRouter.activeIndex;
|
||||
if (AdaptiveLayout.layoutModeOf(context) == LayoutMode.dual && currentIndex == 0) {
|
||||
context.tabsRouter.setActiveIndex(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
IconData get deviceIcon {
|
||||
if (AdaptiveLayout.of(context).isDesktop) {
|
||||
return IconsaxOutline.monitor;
|
||||
}
|
||||
switch (AdaptiveLayout.of(context).layout) {
|
||||
case LayoutState.phone:
|
||||
switch (AdaptiveLayout.viewSizeOf(context)) {
|
||||
case ViewSize.phone:
|
||||
return IconsaxOutline.mobile;
|
||||
case LayoutState.tablet:
|
||||
case ViewSize.tablet:
|
||||
return IconsaxOutline.monitor;
|
||||
case LayoutState.desktop:
|
||||
case ViewSize.desktop:
|
||||
return IconsaxOutline.monitor;
|
||||
}
|
||||
}
|
||||
|
||||
Widget _leftPane(BuildContext context) {
|
||||
void navigateTo(PageRouteInfo route) {
|
||||
AdaptiveLayout.of(context).size == ScreenLayout.single
|
||||
? context.router.navigate(route)
|
||||
: context.router.replace(route);
|
||||
}
|
||||
void navigateTo(PageRouteInfo route) => context.tabsRouter.navigate(route);
|
||||
|
||||
bool containsRoute(PageRouteInfo route) {
|
||||
return context.router.current.name == route.routeName;
|
||||
}
|
||||
bool containsRoute(PageRouteInfo route) =>
|
||||
AdaptiveLayout.layoutModeOf(context) == LayoutMode.dual && context.tabsRouter.current.name == route.routeName;
|
||||
|
||||
final quickConnectAvailable =
|
||||
ref.watch(userProvider.select((value) => value?.serverConfiguration?.quickConnectAvailable ?? false));
|
||||
|
||||
return Container(
|
||||
color: context.colors.surface,
|
||||
child: SettingsScaffold(
|
||||
label: context.localized.settings,
|
||||
scrollController: scrollController,
|
||||
showBackButtonNested: true,
|
||||
showUserIcon: true,
|
||||
items: [
|
||||
if (context.router.canNavigateBack && AdaptiveLayout.of(context).size == ScreenLayout.dual)
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: IconButton.filledTonal(
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.surface.withValues(alpha: 0.8),
|
||||
),
|
||||
onPressed: () => context.router.popBack(),
|
||||
icon: Padding(
|
||||
padding: EdgeInsets.all(AdaptiveLayout.of(context).inputDevice == InputDevice.pointer ? 0 : 4),
|
||||
child: const Icon(IconsaxOutline.arrow_left_2),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.settingsClientTitle),
|
||||
subLabel: Text(context.localized.settingsClientDesc),
|
||||
|
|
|
|||
14
lib/screens/settings/settings_selection_screen.dart
Normal file
14
lib/screens/settings/settings_selection_screen.dart
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
|
||||
//Empty screen that "overlays" the settings selection on single layout
|
||||
@RoutePage()
|
||||
class SettingsSelectionScreen extends StatelessWidget {
|
||||
const SettingsSelectionScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const SizedBox.expand();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,15 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:ficonsax/ficonsax.dart';
|
||||
|
||||
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||
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/map_bool_helper.dart';
|
||||
import 'package:fladder/widgets/shared/modal_bottom_sheet.dart';
|
||||
import 'package:fladder/widgets/shared/modal_side_sheet.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CategoryChip<T> extends StatelessWidget {
|
||||
final Map<T, bool> items;
|
||||
|
|
@ -126,7 +129,7 @@ class CategoryChip<T> extends StatelessWidget {
|
|||
].addInBetween(const SizedBox(width: 6)),
|
||||
);
|
||||
|
||||
if (AdaptiveLayout.of(context).layout != LayoutState.phone) {
|
||||
if (AdaptiveLayout.viewSizeOf(context) != ViewSize.phone) {
|
||||
await showModalSideSheet(
|
||||
context,
|
||||
addDivider: true,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/images_models.dart';
|
||||
import 'package:fladder/models/media_playback_model.dart';
|
||||
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||
import 'package:fladder/providers/video_player_provider.dart';
|
||||
import 'package:fladder/routes/auto_router.gr.dart';
|
||||
import 'package:fladder/theme.dart';
|
||||
|
|
@ -242,7 +243,8 @@ class _DetailScaffoldState extends ConsumerState<DetailScaffold> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (AdaptiveLayout.of(context).size == ScreenLayout.single)
|
||||
if (AdaptiveLayout.layoutModeOf(context) == LayoutMode.single ||
|
||||
AdaptiveLayout.viewSizeOf(context) == ViewSize.phone)
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 6),
|
||||
child: const SizedBox(
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/item_shared_models.dart';
|
||||
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||
import 'package:fladder/screens/shared/media/components/poster_image.dart';
|
||||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
import 'package:fladder/util/item_base_model/item_base_model_extensions.dart';
|
||||
import 'package:fladder/util/item_base_model/play_item_helpers.dart';
|
||||
import 'package:fladder/widgets/shared/clickable_text.dart';
|
||||
import 'package:fladder/widgets/shared/item_actions.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class PosterWidget extends ConsumerWidget {
|
||||
final ItemBaseModel poster;
|
||||
|
|
@ -69,7 +72,7 @@ class PosterWidget extends ConsumerWidget {
|
|||
children: [
|
||||
Flexible(
|
||||
child: ClickableText(
|
||||
onTap: AdaptiveLayout.of(context).layout != LayoutState.phone
|
||||
onTap: AdaptiveLayout.viewSizeOf(context) != ViewSize.phone
|
||||
? () => poster.parentBaseModel.navigateTo(context)
|
||||
: null,
|
||||
text: poster.title,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/models/media_playback_model.dart';
|
||||
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||
import 'package:fladder/providers/video_player_provider.dart';
|
||||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
import 'package:fladder/widgets/navigation_scaffold/components/floating_player_bar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class NestedScaffold extends ConsumerWidget {
|
||||
final Widget body;
|
||||
|
|
@ -18,7 +21,7 @@ class NestedScaffold extends ConsumerWidget {
|
|||
backgroundColor: Colors.transparent,
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
||||
floatingActionButton: switch (AdaptiveLayout.layoutOf(context)) {
|
||||
LayoutState.phone => null,
|
||||
ViewSize.phone => null,
|
||||
_ => switch (playerState) {
|
||||
VideoPlayerState.minimized => const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
|
|
|
|||
|
|
@ -1,19 +1,21 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:ficonsax/ficonsax.dart';
|
||||
import 'package:fladder/providers/sync_provider.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||
import 'package:fladder/providers/sync_provider.dart';
|
||||
import 'package:fladder/routes/auto_router.gr.dart';
|
||||
import 'package:fladder/screens/shared/nested_scaffold.dart';
|
||||
import 'package:fladder/screens/shared/nested_sliver_appbar.dart';
|
||||
import 'package:fladder/screens/syncing/sync_list_item.dart';
|
||||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:fladder/util/sliver_list_padding.dart';
|
||||
import 'package:fladder/widgets/shared/pinch_poster_zoom.dart';
|
||||
import 'package:fladder/widgets/shared/pull_to_refresh.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/util/sliver_list_padding.dart';
|
||||
|
||||
@RoutePage()
|
||||
class SyncedScreen extends ConsumerStatefulWidget {
|
||||
|
|
@ -40,7 +42,7 @@ class _SyncedScreenState extends ConsumerState<SyncedScreen> {
|
|||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
controller: widget.navigationScrollController,
|
||||
slivers: [
|
||||
if (AdaptiveLayout.of(context).layout == LayoutState.phone)
|
||||
if (AdaptiveLayout.viewSizeOf(context) == ViewSize.phone)
|
||||
NestedSliverAppBar(
|
||||
searchTitle: "${context.localized.search} ...",
|
||||
parent: context,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import 'package:screen_brightness/screen_brightness.dart';
|
|||
import 'package:fladder/models/items/media_segments_model.dart';
|
||||
import 'package:fladder/models/media_playback_model.dart';
|
||||
import 'package:fladder/models/playback/playback_model.dart';
|
||||
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||
import 'package:fladder/providers/settings/video_player_settings_provider.dart';
|
||||
import 'package:fladder/providers/video_player_provider.dart';
|
||||
|
|
@ -300,7 +301,7 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
|||
IconButton(
|
||||
onPressed: () => showVideoPlayerOptions(context, () => minimizePlayer(context)),
|
||||
icon: const Icon(IconsaxOutline.more)),
|
||||
if (AdaptiveLayout.layoutOf(context) == LayoutState.tablet) ...[
|
||||
if (AdaptiveLayout.layoutOf(context) == ViewSize.tablet) ...[
|
||||
IconButton(
|
||||
onPressed: () => showSubSelection(context),
|
||||
icon: const Icon(IconsaxOutline.subtitle),
|
||||
|
|
@ -310,7 +311,7 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
|||
icon: const Icon(IconsaxOutline.audio_square),
|
||||
),
|
||||
],
|
||||
if (AdaptiveLayout.layoutOf(context) == LayoutState.desktop) ...[
|
||||
if (AdaptiveLayout.layoutOf(context) == ViewSize.desktop) ...[
|
||||
Flexible(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () => showSubSelection(context),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue