mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-13 09:20:31 -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
|
|
@ -1158,5 +1158,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"copyStreamUrl": "Copy stream url"
|
"copyStreamUrl": "Copy stream url",
|
||||||
|
"settingsLayoutSizesTitle": "Layout Sizes",
|
||||||
|
"settingsLayoutSizesDesc": "Choose which layout sizes the app can use based on window size",
|
||||||
|
"settingsLayoutModesTitle": "Layout Modes",
|
||||||
|
"settingsLayoutModesDesc": "Control whether the app can use single or dual-panel layouts",
|
||||||
|
"phone": "Phone",
|
||||||
|
"tablet": "Tablet",
|
||||||
|
"desktop": "Desktop",
|
||||||
|
"layoutModeSingle": "Single",
|
||||||
|
"layoutModeDual": "Dual"
|
||||||
}
|
}
|
||||||
|
|
@ -18,6 +18,7 @@ import 'package:universal_html/html.dart' as html;
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
import 'package:fladder/models/account_model.dart';
|
import 'package:fladder/models/account_model.dart';
|
||||||
|
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||||
import 'package:fladder/models/syncing/i_synced_item.dart';
|
import 'package:fladder/models/syncing/i_synced_item.dart';
|
||||||
import 'package:fladder/providers/crash_log_provider.dart';
|
import 'package:fladder/providers/crash_log_provider.dart';
|
||||||
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||||
|
|
@ -25,6 +26,7 @@ import 'package:fladder/providers/shared_provider.dart';
|
||||||
import 'package:fladder/providers/sync_provider.dart';
|
import 'package:fladder/providers/sync_provider.dart';
|
||||||
import 'package:fladder/providers/user_provider.dart';
|
import 'package:fladder/providers/user_provider.dart';
|
||||||
import 'package:fladder/providers/video_player_provider.dart';
|
import 'package:fladder/providers/video_player_provider.dart';
|
||||||
|
import 'package:fladder/routes/auto_router.dart';
|
||||||
import 'package:fladder/routes/auto_router.gr.dart';
|
import 'package:fladder/routes/auto_router.gr.dart';
|
||||||
import 'package:fladder/screens/login/lock_screen.dart';
|
import 'package:fladder/screens/login/lock_screen.dart';
|
||||||
import 'package:fladder/theme.dart';
|
import 'package:fladder/theme.dart';
|
||||||
|
|
@ -100,11 +102,11 @@ void main() async {
|
||||||
))
|
))
|
||||||
],
|
],
|
||||||
child: AdaptiveLayoutBuilder(
|
child: AdaptiveLayoutBuilder(
|
||||||
fallBack: LayoutState.tablet,
|
fallBack: ViewSize.tablet,
|
||||||
layoutPoints: [
|
layoutPoints: [
|
||||||
LayoutPoints(start: 0, end: 599, type: LayoutState.phone),
|
LayoutPoints(start: 0, end: 599, type: ViewSize.phone),
|
||||||
LayoutPoints(start: 600, end: 1919, type: LayoutState.tablet),
|
LayoutPoints(start: 600, end: 1919, type: ViewSize.tablet),
|
||||||
LayoutPoints(start: 1920, end: 3180, type: LayoutState.desktop),
|
LayoutPoints(start: 1920, end: 3180, type: ViewSize.desktop),
|
||||||
],
|
],
|
||||||
child: const Main(),
|
child: const Main(),
|
||||||
),
|
),
|
||||||
|
|
@ -122,6 +124,7 @@ class Main extends ConsumerStatefulWidget with WindowListener {
|
||||||
class _MainState extends ConsumerState<Main> with WindowListener, WidgetsBindingObserver {
|
class _MainState extends ConsumerState<Main> with WindowListener, WidgetsBindingObserver {
|
||||||
DateTime dateTime = DateTime.now();
|
DateTime dateTime = DateTime.now();
|
||||||
bool hidden = false;
|
bool hidden = false;
|
||||||
|
late final autoRouter = AutoRouter(ref: ref);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeAppLifecycleState(AppLifecycleState state) async {
|
void didChangeAppLifecycleState(AppLifecycleState state) async {
|
||||||
|
|
@ -159,7 +162,7 @@ class _MainState extends ConsumerState<Main> with WindowListener, WidgetsBinding
|
||||||
await ref.read(videoPlayerProvider).pause();
|
await ref.read(videoPlayerProvider).pause();
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
AdaptiveLayout.of(context).router.push(const LockRoute());
|
autoRouter.push(const LockRoute());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -298,7 +301,8 @@ class _MainState extends ConsumerState<Main> with WindowListener, WidgetsBinding
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
themeMode: themeMode,
|
themeMode: themeMode,
|
||||||
routerConfig: AdaptiveLayout.routerOf(context).config(),
|
routerConfig: autoRouter.config(),
|
||||||
|
// routerConfig: AdaptiveLayout.routerOf(context).config(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ part 'home_settings_model.g.dart';
|
||||||
@freezed
|
@freezed
|
||||||
class HomeSettingsModel with _$HomeSettingsModel {
|
class HomeSettingsModel with _$HomeSettingsModel {
|
||||||
factory HomeSettingsModel({
|
factory HomeSettingsModel({
|
||||||
|
@Default({...LayoutMode.values}) Set<LayoutMode> screenLayouts,
|
||||||
|
@Default({...ViewSize.values}) Set<ViewSize> layoutStates,
|
||||||
@Default(HomeBanner.carousel) HomeBanner homeBanner,
|
@Default(HomeBanner.carousel) HomeBanner homeBanner,
|
||||||
@Default(HomeCarouselSettings.combined) HomeCarouselSettings carouselSettings,
|
@Default(HomeCarouselSettings.combined) HomeCarouselSettings carouselSettings,
|
||||||
@Default(HomeNextUp.separate) HomeNextUp nextUp,
|
@Default(HomeNextUp.separate) HomeNextUp nextUp,
|
||||||
|
|
@ -18,6 +20,48 @@ class HomeSettingsModel with _$HomeSettingsModel {
|
||||||
factory HomeSettingsModel.fromJson(Map<String, dynamic> json) => _$HomeSettingsModelFromJson(json);
|
factory HomeSettingsModel.fromJson(Map<String, dynamic> json) => _$HomeSettingsModelFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
T selectAvailableOrSmaller<T>(T value, Set<T> availableOptions, List<T> allOptions) {
|
||||||
|
if (availableOptions.contains(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = allOptions.indexOf(value);
|
||||||
|
|
||||||
|
for (int i = index - 1; i >= 0; i--) {
|
||||||
|
if (availableOptions.contains(allOptions[i])) {
|
||||||
|
return allOptions[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return availableOptions.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ViewSize {
|
||||||
|
phone,
|
||||||
|
tablet,
|
||||||
|
desktop;
|
||||||
|
|
||||||
|
const ViewSize();
|
||||||
|
|
||||||
|
String label(BuildContext context) => switch (this) {
|
||||||
|
ViewSize.phone => context.localized.phone,
|
||||||
|
ViewSize.tablet => context.localized.tablet,
|
||||||
|
ViewSize.desktop => context.localized.desktop,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LayoutMode {
|
||||||
|
single,
|
||||||
|
dual;
|
||||||
|
|
||||||
|
const LayoutMode();
|
||||||
|
|
||||||
|
String label(BuildContext context) => switch (this) {
|
||||||
|
LayoutMode.single => context.localized.layoutModeSingle,
|
||||||
|
LayoutMode.dual => context.localized.layoutModeDual,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
enum HomeBanner {
|
enum HomeBanner {
|
||||||
hide,
|
hide,
|
||||||
carousel,
|
carousel,
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ HomeSettingsModel _$HomeSettingsModelFromJson(Map<String, dynamic> json) {
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$HomeSettingsModel {
|
mixin _$HomeSettingsModel {
|
||||||
|
Set<LayoutMode> get screenLayouts => throw _privateConstructorUsedError;
|
||||||
|
Set<ViewSize> get layoutStates => throw _privateConstructorUsedError;
|
||||||
HomeBanner get homeBanner => throw _privateConstructorUsedError;
|
HomeBanner get homeBanner => throw _privateConstructorUsedError;
|
||||||
HomeCarouselSettings get carouselSettings =>
|
HomeCarouselSettings get carouselSettings =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
|
|
@ -42,7 +44,9 @@ abstract class $HomeSettingsModelCopyWith<$Res> {
|
||||||
_$HomeSettingsModelCopyWithImpl<$Res, HomeSettingsModel>;
|
_$HomeSettingsModelCopyWithImpl<$Res, HomeSettingsModel>;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call(
|
$Res call(
|
||||||
{HomeBanner homeBanner,
|
{Set<LayoutMode> screenLayouts,
|
||||||
|
Set<ViewSize> layoutStates,
|
||||||
|
HomeBanner homeBanner,
|
||||||
HomeCarouselSettings carouselSettings,
|
HomeCarouselSettings carouselSettings,
|
||||||
HomeNextUp nextUp});
|
HomeNextUp nextUp});
|
||||||
}
|
}
|
||||||
|
|
@ -62,11 +66,21 @@ class _$HomeSettingsModelCopyWithImpl<$Res, $Val extends HomeSettingsModel>
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
|
Object? screenLayouts = null,
|
||||||
|
Object? layoutStates = null,
|
||||||
Object? homeBanner = null,
|
Object? homeBanner = null,
|
||||||
Object? carouselSettings = null,
|
Object? carouselSettings = null,
|
||||||
Object? nextUp = null,
|
Object? nextUp = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
|
screenLayouts: null == screenLayouts
|
||||||
|
? _value.screenLayouts
|
||||||
|
: screenLayouts // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Set<LayoutMode>,
|
||||||
|
layoutStates: null == layoutStates
|
||||||
|
? _value.layoutStates
|
||||||
|
: layoutStates // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Set<ViewSize>,
|
||||||
homeBanner: null == homeBanner
|
homeBanner: null == homeBanner
|
||||||
? _value.homeBanner
|
? _value.homeBanner
|
||||||
: homeBanner // ignore: cast_nullable_to_non_nullable
|
: homeBanner // ignore: cast_nullable_to_non_nullable
|
||||||
|
|
@ -92,7 +106,9 @@ abstract class _$$HomeSettingsModelImplCopyWith<$Res>
|
||||||
@override
|
@override
|
||||||
@useResult
|
@useResult
|
||||||
$Res call(
|
$Res call(
|
||||||
{HomeBanner homeBanner,
|
{Set<LayoutMode> screenLayouts,
|
||||||
|
Set<ViewSize> layoutStates,
|
||||||
|
HomeBanner homeBanner,
|
||||||
HomeCarouselSettings carouselSettings,
|
HomeCarouselSettings carouselSettings,
|
||||||
HomeNextUp nextUp});
|
HomeNextUp nextUp});
|
||||||
}
|
}
|
||||||
|
|
@ -110,11 +126,21 @@ class __$$HomeSettingsModelImplCopyWithImpl<$Res>
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
|
Object? screenLayouts = null,
|
||||||
|
Object? layoutStates = null,
|
||||||
Object? homeBanner = null,
|
Object? homeBanner = null,
|
||||||
Object? carouselSettings = null,
|
Object? carouselSettings = null,
|
||||||
Object? nextUp = null,
|
Object? nextUp = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$HomeSettingsModelImpl(
|
return _then(_$HomeSettingsModelImpl(
|
||||||
|
screenLayouts: null == screenLayouts
|
||||||
|
? _value._screenLayouts
|
||||||
|
: screenLayouts // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Set<LayoutMode>,
|
||||||
|
layoutStates: null == layoutStates
|
||||||
|
? _value._layoutStates
|
||||||
|
: layoutStates // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Set<ViewSize>,
|
||||||
homeBanner: null == homeBanner
|
homeBanner: null == homeBanner
|
||||||
? _value.homeBanner
|
? _value.homeBanner
|
||||||
: homeBanner // ignore: cast_nullable_to_non_nullable
|
: homeBanner // ignore: cast_nullable_to_non_nullable
|
||||||
|
|
@ -135,13 +161,35 @@ class __$$HomeSettingsModelImplCopyWithImpl<$Res>
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class _$HomeSettingsModelImpl implements _HomeSettingsModel {
|
class _$HomeSettingsModelImpl implements _HomeSettingsModel {
|
||||||
_$HomeSettingsModelImpl(
|
_$HomeSettingsModelImpl(
|
||||||
{this.homeBanner = HomeBanner.carousel,
|
{final Set<LayoutMode> screenLayouts = const {...LayoutMode.values},
|
||||||
|
final Set<ViewSize> layoutStates = const {...ViewSize.values},
|
||||||
|
this.homeBanner = HomeBanner.carousel,
|
||||||
this.carouselSettings = HomeCarouselSettings.combined,
|
this.carouselSettings = HomeCarouselSettings.combined,
|
||||||
this.nextUp = HomeNextUp.separate});
|
this.nextUp = HomeNextUp.separate})
|
||||||
|
: _screenLayouts = screenLayouts,
|
||||||
|
_layoutStates = layoutStates;
|
||||||
|
|
||||||
factory _$HomeSettingsModelImpl.fromJson(Map<String, dynamic> json) =>
|
factory _$HomeSettingsModelImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
_$$HomeSettingsModelImplFromJson(json);
|
_$$HomeSettingsModelImplFromJson(json);
|
||||||
|
|
||||||
|
final Set<LayoutMode> _screenLayouts;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
Set<LayoutMode> get screenLayouts {
|
||||||
|
if (_screenLayouts is EqualUnmodifiableSetView) return _screenLayouts;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableSetView(_screenLayouts);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Set<ViewSize> _layoutStates;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
Set<ViewSize> get layoutStates {
|
||||||
|
if (_layoutStates is EqualUnmodifiableSetView) return _layoutStates;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableSetView(_layoutStates);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@JsonKey()
|
@JsonKey()
|
||||||
final HomeBanner homeBanner;
|
final HomeBanner homeBanner;
|
||||||
|
|
@ -154,7 +202,7 @@ class _$HomeSettingsModelImpl implements _HomeSettingsModel {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'HomeSettingsModel(homeBanner: $homeBanner, carouselSettings: $carouselSettings, nextUp: $nextUp)';
|
return 'HomeSettingsModel(screenLayouts: $screenLayouts, layoutStates: $layoutStates, homeBanner: $homeBanner, carouselSettings: $carouselSettings, nextUp: $nextUp)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -162,6 +210,10 @@ class _$HomeSettingsModelImpl implements _HomeSettingsModel {
|
||||||
return identical(this, other) ||
|
return identical(this, other) ||
|
||||||
(other.runtimeType == runtimeType &&
|
(other.runtimeType == runtimeType &&
|
||||||
other is _$HomeSettingsModelImpl &&
|
other is _$HomeSettingsModelImpl &&
|
||||||
|
const DeepCollectionEquality()
|
||||||
|
.equals(other._screenLayouts, _screenLayouts) &&
|
||||||
|
const DeepCollectionEquality()
|
||||||
|
.equals(other._layoutStates, _layoutStates) &&
|
||||||
(identical(other.homeBanner, homeBanner) ||
|
(identical(other.homeBanner, homeBanner) ||
|
||||||
other.homeBanner == homeBanner) &&
|
other.homeBanner == homeBanner) &&
|
||||||
(identical(other.carouselSettings, carouselSettings) ||
|
(identical(other.carouselSettings, carouselSettings) ||
|
||||||
|
|
@ -171,8 +223,13 @@ class _$HomeSettingsModelImpl implements _HomeSettingsModel {
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode => Object.hash(
|
||||||
Object.hash(runtimeType, homeBanner, carouselSettings, nextUp);
|
runtimeType,
|
||||||
|
const DeepCollectionEquality().hash(_screenLayouts),
|
||||||
|
const DeepCollectionEquality().hash(_layoutStates),
|
||||||
|
homeBanner,
|
||||||
|
carouselSettings,
|
||||||
|
nextUp);
|
||||||
|
|
||||||
/// Create a copy of HomeSettingsModel
|
/// Create a copy of HomeSettingsModel
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
|
@ -193,13 +250,19 @@ class _$HomeSettingsModelImpl implements _HomeSettingsModel {
|
||||||
|
|
||||||
abstract class _HomeSettingsModel implements HomeSettingsModel {
|
abstract class _HomeSettingsModel implements HomeSettingsModel {
|
||||||
factory _HomeSettingsModel(
|
factory _HomeSettingsModel(
|
||||||
{final HomeBanner homeBanner,
|
{final Set<LayoutMode> screenLayouts,
|
||||||
|
final Set<ViewSize> layoutStates,
|
||||||
|
final HomeBanner homeBanner,
|
||||||
final HomeCarouselSettings carouselSettings,
|
final HomeCarouselSettings carouselSettings,
|
||||||
final HomeNextUp nextUp}) = _$HomeSettingsModelImpl;
|
final HomeNextUp nextUp}) = _$HomeSettingsModelImpl;
|
||||||
|
|
||||||
factory _HomeSettingsModel.fromJson(Map<String, dynamic> json) =
|
factory _HomeSettingsModel.fromJson(Map<String, dynamic> json) =
|
||||||
_$HomeSettingsModelImpl.fromJson;
|
_$HomeSettingsModelImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<LayoutMode> get screenLayouts;
|
||||||
|
@override
|
||||||
|
Set<ViewSize> get layoutStates;
|
||||||
@override
|
@override
|
||||||
HomeBanner get homeBanner;
|
HomeBanner get homeBanner;
|
||||||
@override
|
@override
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,14 @@ part of 'home_settings_model.dart';
|
||||||
_$HomeSettingsModelImpl _$$HomeSettingsModelImplFromJson(
|
_$HomeSettingsModelImpl _$$HomeSettingsModelImplFromJson(
|
||||||
Map<String, dynamic> json) =>
|
Map<String, dynamic> json) =>
|
||||||
_$HomeSettingsModelImpl(
|
_$HomeSettingsModelImpl(
|
||||||
|
screenLayouts: (json['screenLayouts'] as List<dynamic>?)
|
||||||
|
?.map((e) => $enumDecode(_$LayoutModeEnumMap, e))
|
||||||
|
.toSet() ??
|
||||||
|
const {...LayoutMode.values},
|
||||||
|
layoutStates: (json['layoutStates'] as List<dynamic>?)
|
||||||
|
?.map((e) => $enumDecode(_$ViewSizeEnumMap, e))
|
||||||
|
.toSet() ??
|
||||||
|
const {...ViewSize.values},
|
||||||
homeBanner:
|
homeBanner:
|
||||||
$enumDecodeNullable(_$HomeBannerEnumMap, json['homeBanner']) ??
|
$enumDecodeNullable(_$HomeBannerEnumMap, json['homeBanner']) ??
|
||||||
HomeBanner.carousel,
|
HomeBanner.carousel,
|
||||||
|
|
@ -22,12 +30,27 @@ _$HomeSettingsModelImpl _$$HomeSettingsModelImplFromJson(
|
||||||
Map<String, dynamic> _$$HomeSettingsModelImplToJson(
|
Map<String, dynamic> _$$HomeSettingsModelImplToJson(
|
||||||
_$HomeSettingsModelImpl instance) =>
|
_$HomeSettingsModelImpl instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
|
'screenLayouts':
|
||||||
|
instance.screenLayouts.map((e) => _$LayoutModeEnumMap[e]!).toList(),
|
||||||
|
'layoutStates':
|
||||||
|
instance.layoutStates.map((e) => _$ViewSizeEnumMap[e]!).toList(),
|
||||||
'homeBanner': _$HomeBannerEnumMap[instance.homeBanner]!,
|
'homeBanner': _$HomeBannerEnumMap[instance.homeBanner]!,
|
||||||
'carouselSettings':
|
'carouselSettings':
|
||||||
_$HomeCarouselSettingsEnumMap[instance.carouselSettings]!,
|
_$HomeCarouselSettingsEnumMap[instance.carouselSettings]!,
|
||||||
'nextUp': _$HomeNextUpEnumMap[instance.nextUp]!,
|
'nextUp': _$HomeNextUpEnumMap[instance.nextUp]!,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const _$LayoutModeEnumMap = {
|
||||||
|
LayoutMode.single: 'single',
|
||||||
|
LayoutMode.dual: 'dual',
|
||||||
|
};
|
||||||
|
|
||||||
|
const _$ViewSizeEnumMap = {
|
||||||
|
ViewSize.phone: 'phone',
|
||||||
|
ViewSize.tablet: 'tablet',
|
||||||
|
ViewSize.desktop: 'desktop',
|
||||||
|
};
|
||||||
|
|
||||||
const _$HomeBannerEnumMap = {
|
const _$HomeBannerEnumMap = {
|
||||||
HomeBanner.hide: 'hide',
|
HomeBanner.hide: 'hide',
|
||||||
HomeBanner.carousel: 'carousel',
|
HomeBanner.carousel: 'carousel',
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ part of 'api_provider.dart';
|
||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$jellyApiHash() => r'c0cdc4127e7191523b1356e71c54c93f99020c1e';
|
String _$jellyApiHash() => r'9bc824d28d17f88f40c768cefb637144e0fbf346';
|
||||||
|
|
||||||
/// See also [JellyApi].
|
/// See also [JellyApi].
|
||||||
@ProviderFor(JellyApi)
|
@ProviderFor(JellyApi)
|
||||||
|
|
|
||||||
|
|
@ -19,4 +19,8 @@ class HomeSettingsNotifier extends StateNotifier<HomeSettingsModel> {
|
||||||
}
|
}
|
||||||
|
|
||||||
HomeSettingsModel update(HomeSettingsModel Function(HomeSettingsModel currentState) value) => state = value(state);
|
HomeSettingsModel update(HomeSettingsModel Function(HomeSettingsModel currentState) value) => state = value(state);
|
||||||
|
|
||||||
|
void setLayoutModes(Set<LayoutMode> set) => state = state.copyWith(screenLayouts: set);
|
||||||
|
|
||||||
|
void setViewSize(Set<ViewSize> set) => state = state.copyWith(layoutStates: set);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ part of 'background_download_provider.dart';
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$backgroundDownloaderHash() =>
|
String _$backgroundDownloaderHash() =>
|
||||||
r'9a9f91504ae4532ab37290ee9372d2e7a18380a9';
|
r'997d9f4ba79dd0d9d30d5f283b36d5280d10dfaa';
|
||||||
|
|
||||||
/// See also [backgroundDownloader].
|
/// See also [backgroundDownloader].
|
||||||
@ProviderFor(backgroundDownloader)
|
@ProviderFor(backgroundDownloader)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ part of 'user_provider.dart';
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$showSyncButtonProviderHash() =>
|
String _$showSyncButtonProviderHash() =>
|
||||||
r'3468d7309f3859f7b60b1bd317e306e1f5f00555';
|
r'c09f42cd6536425bf9417da41c83e15c135d0edb';
|
||||||
|
|
||||||
/// See also [showSyncButtonProvider].
|
/// See also [showSyncButtonProvider].
|
||||||
@ProviderFor(showSyncButtonProvider)
|
@ProviderFor(showSyncButtonProvider)
|
||||||
|
|
@ -24,7 +24,7 @@ final showSyncButtonProviderProvider = AutoDisposeProvider<bool>.internal(
|
||||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
// ignore: unused_element
|
// ignore: unused_element
|
||||||
typedef ShowSyncButtonProviderRef = AutoDisposeProviderRef<bool>;
|
typedef ShowSyncButtonProviderRef = AutoDisposeProviderRef<bool>;
|
||||||
String _$userHash() => r'e83369c0d569d5a862aa1b92f3f0a45a9d1fe446';
|
String _$userHash() => r'1ab1579051806f114e3f42873a2e100c14115900';
|
||||||
|
|
||||||
/// See also [User].
|
/// See also [User].
|
||||||
@ProviderFor(User)
|
@ProviderFor(User)
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,16 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:fladder/providers/user_provider.dart';
|
import 'package:fladder/providers/user_provider.dart';
|
||||||
import 'package:fladder/routes/auto_router.gr.dart';
|
import 'package:fladder/routes/auto_router.gr.dart';
|
||||||
import 'package:fladder/screens/login/lock_screen.dart';
|
import 'package:fladder/screens/login/lock_screen.dart';
|
||||||
import 'package:fladder/util/adaptive_layout.dart';
|
|
||||||
|
const settingsPageRoute = "settings";
|
||||||
|
|
||||||
@AutoRouterConfig(replaceInRouteName: 'Screen|Page,Route')
|
@AutoRouterConfig(replaceInRouteName: 'Screen|Page,Route')
|
||||||
class AutoRouter extends RootStackRouter {
|
class AutoRouter extends RootStackRouter {
|
||||||
AutoRouter({
|
AutoRouter({
|
||||||
required this.layout,
|
|
||||||
required this.ref,
|
required this.ref,
|
||||||
});
|
});
|
||||||
|
|
||||||
final WidgetRef ref;
|
final WidgetRef ref;
|
||||||
final ScreenLayout layout;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<AutoRouteGuard> get guards => [...super.guards, AuthGuard(ref: ref)];
|
List<AutoRouteGuard> get guards => [...super.guards, AuthGuard(ref: ref)];
|
||||||
|
|
@ -27,44 +26,33 @@ class AutoRouter extends RootStackRouter {
|
||||||
@override
|
@override
|
||||||
List<AutoRoute> get routes => [
|
List<AutoRoute> get routes => [
|
||||||
..._defaultRoutes,
|
..._defaultRoutes,
|
||||||
...(layout == ScreenLayout.dual ? desktopRoutes : mobileRoutes),
|
...otherRoutes,
|
||||||
];
|
];
|
||||||
|
|
||||||
final List<AutoRoute> mobileRoutes = [
|
final List<AutoRoute> otherRoutes = [
|
||||||
_homeRoute.copyWith(
|
_homeRoute.copyWith(
|
||||||
children: [
|
children: [
|
||||||
_dashboardRoute,
|
...homeRoutes,
|
||||||
_favouritesRoute,
|
|
||||||
_syncedRoute,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
AutoRoute(page: DetailsRoute.page, path: '/details', usesPathAsKey: true),
|
|
||||||
AutoRoute(page: LibrarySearchRoute.page, path: '/library', usesPathAsKey: true),
|
|
||||||
AutoRoute(page: SettingsRoute.page, path: '/settings'),
|
|
||||||
..._settingsChildren.map(
|
|
||||||
(e) => e.copyWith(path: "/$e", initial: false),
|
|
||||||
),
|
|
||||||
AutoRoute(page: LockRoute.page, path: '/locked'),
|
|
||||||
];
|
|
||||||
final List<AutoRoute> desktopRoutes = [
|
|
||||||
_homeRoute.copyWith(
|
|
||||||
children: [
|
|
||||||
_dashboardRoute,
|
|
||||||
_favouritesRoute,
|
|
||||||
_syncedRoute,
|
|
||||||
AutoRoute(page: DetailsRoute.page, path: 'details', usesPathAsKey: true),
|
AutoRoute(page: DetailsRoute.page, path: 'details', usesPathAsKey: true),
|
||||||
AutoRoute(page: LibrarySearchRoute.page, path: 'library', usesPathAsKey: true),
|
AutoRoute(page: LibrarySearchRoute.page, path: 'library', usesPathAsKey: true),
|
||||||
AutoRoute(
|
CustomRoute(
|
||||||
page: SettingsRoute.page,
|
page: SettingsRoute.page,
|
||||||
path: 'settings',
|
path: settingsPageRoute,
|
||||||
children: _settingsChildren,
|
children: _settingsChildren,
|
||||||
)
|
transitionsBuilder: TransitionsBuilders.fadeIn,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
AutoRoute(page: LockRoute.page, path: '/locked'),
|
AutoRoute(page: LockRoute.page, path: '/locked'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final List<AutoRoute> homeRoutes = [
|
||||||
|
_dashboardRoute,
|
||||||
|
_favouritesRoute,
|
||||||
|
_syncedRoute,
|
||||||
|
];
|
||||||
|
|
||||||
final List<AutoRoute> _defaultRoutes = [
|
final List<AutoRoute> _defaultRoutes = [
|
||||||
AutoRoute(page: SplashRoute.page, path: '/splash'),
|
AutoRoute(page: SplashRoute.page, path: '/splash'),
|
||||||
AutoRoute(page: LoginRoute.page, path: '/login'),
|
AutoRoute(page: LoginRoute.page, path: '/login'),
|
||||||
|
|
@ -75,25 +63,25 @@ final AutoRoute _dashboardRoute = CustomRoute(
|
||||||
page: DashboardRoute.page,
|
page: DashboardRoute.page,
|
||||||
transitionsBuilder: TransitionsBuilders.fadeIn,
|
transitionsBuilder: TransitionsBuilders.fadeIn,
|
||||||
initial: true,
|
initial: true,
|
||||||
path: 'dashboard',
|
|
||||||
maintainState: false,
|
maintainState: false,
|
||||||
|
path: 'dashboard',
|
||||||
);
|
);
|
||||||
final AutoRoute _favouritesRoute = CustomRoute(
|
final AutoRoute _favouritesRoute = CustomRoute(
|
||||||
page: FavouritesRoute.page,
|
page: FavouritesRoute.page,
|
||||||
transitionsBuilder: TransitionsBuilders.fadeIn,
|
transitionsBuilder: TransitionsBuilders.fadeIn,
|
||||||
path: 'favourites',
|
|
||||||
maintainState: false,
|
maintainState: false,
|
||||||
|
path: 'favourites',
|
||||||
);
|
);
|
||||||
final AutoRoute _syncedRoute = CustomRoute(
|
final AutoRoute _syncedRoute = CustomRoute(
|
||||||
page: SyncedRoute.page,
|
page: SyncedRoute.page,
|
||||||
transitionsBuilder: TransitionsBuilders.fadeIn,
|
transitionsBuilder: TransitionsBuilders.fadeIn,
|
||||||
path: 'synced',
|
|
||||||
maintainState: false,
|
maintainState: false,
|
||||||
|
path: 'synced',
|
||||||
);
|
);
|
||||||
|
|
||||||
final List<AutoRoute> _settingsChildren = [
|
final List<AutoRoute> _settingsChildren = [
|
||||||
CustomRoute(
|
CustomRoute(page: SettingsSelectionRoute.page, transitionsBuilder: TransitionsBuilders.fadeIn, path: 'list'),
|
||||||
page: ClientSettingsRoute.page, initial: true, transitionsBuilder: TransitionsBuilders.fadeIn, path: 'client'),
|
CustomRoute(page: ClientSettingsRoute.page, transitionsBuilder: TransitionsBuilders.fadeIn, path: 'client'),
|
||||||
CustomRoute(page: SecuritySettingsRoute.page, transitionsBuilder: TransitionsBuilders.fadeIn, path: 'security'),
|
CustomRoute(page: SecuritySettingsRoute.page, transitionsBuilder: TransitionsBuilders.fadeIn, path: 'security'),
|
||||||
CustomRoute(page: PlayerSettingsRoute.page, transitionsBuilder: TransitionsBuilders.fadeIn, path: 'player'),
|
CustomRoute(page: PlayerSettingsRoute.page, transitionsBuilder: TransitionsBuilders.fadeIn, path: 'player'),
|
||||||
CustomRoute(page: AboutSettingsRoute.page, transitionsBuilder: TransitionsBuilders.fadeIn, path: 'about'),
|
CustomRoute(page: AboutSettingsRoute.page, transitionsBuilder: TransitionsBuilders.fadeIn, path: 'about'),
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,11 @@
|
||||||
// coverage:ignore-file
|
// coverage:ignore-file
|
||||||
|
|
||||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||||
import 'package:auto_route/auto_route.dart' as _i15;
|
import 'package:auto_route/auto_route.dart' as _i16;
|
||||||
import 'package:fladder/models/item_base_model.dart' as _i16;
|
import 'package:fladder/models/item_base_model.dart' as _i17;
|
||||||
import 'package:fladder/models/items/photos_model.dart' as _i19;
|
import 'package:fladder/models/items/photos_model.dart' as _i20;
|
||||||
import 'package:fladder/models/library_search/library_search_options.dart'
|
import 'package:fladder/models/library_search/library_search_options.dart'
|
||||||
as _i18;
|
as _i19;
|
||||||
import 'package:fladder/routes/nested_details_screen.dart' as _i4;
|
import 'package:fladder/routes/nested_details_screen.dart' as _i4;
|
||||||
import 'package:fladder/screens/dashboard/dashboard_screen.dart' as _i3;
|
import 'package:fladder/screens/dashboard/dashboard_screen.dart' as _i3;
|
||||||
import 'package:fladder/screens/favourites/favourites_screen.dart' as _i5;
|
import 'package:fladder/screens/favourites/favourites_screen.dart' as _i5;
|
||||||
|
|
@ -26,15 +26,17 @@ import 'package:fladder/screens/settings/client_settings_page.dart' as _i2;
|
||||||
import 'package:fladder/screens/settings/player_settings_page.dart' as _i10;
|
import 'package:fladder/screens/settings/player_settings_page.dart' as _i10;
|
||||||
import 'package:fladder/screens/settings/security_settings_page.dart' as _i11;
|
import 'package:fladder/screens/settings/security_settings_page.dart' as _i11;
|
||||||
import 'package:fladder/screens/settings/settings_screen.dart' as _i12;
|
import 'package:fladder/screens/settings/settings_screen.dart' as _i12;
|
||||||
import 'package:fladder/screens/splash_screen.dart' as _i13;
|
import 'package:fladder/screens/settings/settings_selection_screen.dart'
|
||||||
import 'package:fladder/screens/syncing/synced_screen.dart' as _i14;
|
as _i13;
|
||||||
import 'package:flutter/foundation.dart' as _i17;
|
import 'package:fladder/screens/splash_screen.dart' as _i14;
|
||||||
import 'package:flutter/material.dart' as _i20;
|
import 'package:fladder/screens/syncing/synced_screen.dart' as _i15;
|
||||||
|
import 'package:flutter/foundation.dart' as _i18;
|
||||||
|
import 'package:flutter/material.dart' as _i21;
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i1.AboutSettingsPage]
|
/// [_i1.AboutSettingsPage]
|
||||||
class AboutSettingsRoute extends _i15.PageRouteInfo<void> {
|
class AboutSettingsRoute extends _i16.PageRouteInfo<void> {
|
||||||
const AboutSettingsRoute({List<_i15.PageRouteInfo>? children})
|
const AboutSettingsRoute({List<_i16.PageRouteInfo>? children})
|
||||||
: super(
|
: super(
|
||||||
AboutSettingsRoute.name,
|
AboutSettingsRoute.name,
|
||||||
initialChildren: children,
|
initialChildren: children,
|
||||||
|
|
@ -42,7 +44,7 @@ class AboutSettingsRoute extends _i15.PageRouteInfo<void> {
|
||||||
|
|
||||||
static const String name = 'AboutSettingsRoute';
|
static const String name = 'AboutSettingsRoute';
|
||||||
|
|
||||||
static _i15.PageInfo page = _i15.PageInfo(
|
static _i16.PageInfo page = _i16.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
return const _i1.AboutSettingsPage();
|
return const _i1.AboutSettingsPage();
|
||||||
|
|
@ -52,8 +54,8 @@ class AboutSettingsRoute extends _i15.PageRouteInfo<void> {
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i2.ClientSettingsPage]
|
/// [_i2.ClientSettingsPage]
|
||||||
class ClientSettingsRoute extends _i15.PageRouteInfo<void> {
|
class ClientSettingsRoute extends _i16.PageRouteInfo<void> {
|
||||||
const ClientSettingsRoute({List<_i15.PageRouteInfo>? children})
|
const ClientSettingsRoute({List<_i16.PageRouteInfo>? children})
|
||||||
: super(
|
: super(
|
||||||
ClientSettingsRoute.name,
|
ClientSettingsRoute.name,
|
||||||
initialChildren: children,
|
initialChildren: children,
|
||||||
|
|
@ -61,7 +63,7 @@ class ClientSettingsRoute extends _i15.PageRouteInfo<void> {
|
||||||
|
|
||||||
static const String name = 'ClientSettingsRoute';
|
static const String name = 'ClientSettingsRoute';
|
||||||
|
|
||||||
static _i15.PageInfo page = _i15.PageInfo(
|
static _i16.PageInfo page = _i16.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
return const _i2.ClientSettingsPage();
|
return const _i2.ClientSettingsPage();
|
||||||
|
|
@ -71,8 +73,8 @@ class ClientSettingsRoute extends _i15.PageRouteInfo<void> {
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i3.DashboardScreen]
|
/// [_i3.DashboardScreen]
|
||||||
class DashboardRoute extends _i15.PageRouteInfo<void> {
|
class DashboardRoute extends _i16.PageRouteInfo<void> {
|
||||||
const DashboardRoute({List<_i15.PageRouteInfo>? children})
|
const DashboardRoute({List<_i16.PageRouteInfo>? children})
|
||||||
: super(
|
: super(
|
||||||
DashboardRoute.name,
|
DashboardRoute.name,
|
||||||
initialChildren: children,
|
initialChildren: children,
|
||||||
|
|
@ -80,7 +82,7 @@ class DashboardRoute extends _i15.PageRouteInfo<void> {
|
||||||
|
|
||||||
static const String name = 'DashboardRoute';
|
static const String name = 'DashboardRoute';
|
||||||
|
|
||||||
static _i15.PageInfo page = _i15.PageInfo(
|
static _i16.PageInfo page = _i16.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
return const _i3.DashboardScreen();
|
return const _i3.DashboardScreen();
|
||||||
|
|
@ -90,12 +92,12 @@ class DashboardRoute extends _i15.PageRouteInfo<void> {
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i4.DetailsScreen]
|
/// [_i4.DetailsScreen]
|
||||||
class DetailsRoute extends _i15.PageRouteInfo<DetailsRouteArgs> {
|
class DetailsRoute extends _i16.PageRouteInfo<DetailsRouteArgs> {
|
||||||
DetailsRoute({
|
DetailsRoute({
|
||||||
String id = '',
|
String id = '',
|
||||||
_i16.ItemBaseModel? item,
|
_i17.ItemBaseModel? item,
|
||||||
_i17.Key? key,
|
_i18.Key? key,
|
||||||
List<_i15.PageRouteInfo>? children,
|
List<_i16.PageRouteInfo>? children,
|
||||||
}) : super(
|
}) : super(
|
||||||
DetailsRoute.name,
|
DetailsRoute.name,
|
||||||
args: DetailsRouteArgs(
|
args: DetailsRouteArgs(
|
||||||
|
|
@ -109,7 +111,7 @@ class DetailsRoute extends _i15.PageRouteInfo<DetailsRouteArgs> {
|
||||||
|
|
||||||
static const String name = 'DetailsRoute';
|
static const String name = 'DetailsRoute';
|
||||||
|
|
||||||
static _i15.PageInfo page = _i15.PageInfo(
|
static _i16.PageInfo page = _i16.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
final queryParams = data.queryParams;
|
final queryParams = data.queryParams;
|
||||||
|
|
@ -137,9 +139,9 @@ class DetailsRouteArgs {
|
||||||
|
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
final _i16.ItemBaseModel? item;
|
final _i17.ItemBaseModel? item;
|
||||||
|
|
||||||
final _i17.Key? key;
|
final _i18.Key? key;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
@ -149,8 +151,8 @@ class DetailsRouteArgs {
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i5.FavouritesScreen]
|
/// [_i5.FavouritesScreen]
|
||||||
class FavouritesRoute extends _i15.PageRouteInfo<void> {
|
class FavouritesRoute extends _i16.PageRouteInfo<void> {
|
||||||
const FavouritesRoute({List<_i15.PageRouteInfo>? children})
|
const FavouritesRoute({List<_i16.PageRouteInfo>? children})
|
||||||
: super(
|
: super(
|
||||||
FavouritesRoute.name,
|
FavouritesRoute.name,
|
||||||
initialChildren: children,
|
initialChildren: children,
|
||||||
|
|
@ -158,7 +160,7 @@ class FavouritesRoute extends _i15.PageRouteInfo<void> {
|
||||||
|
|
||||||
static const String name = 'FavouritesRoute';
|
static const String name = 'FavouritesRoute';
|
||||||
|
|
||||||
static _i15.PageInfo page = _i15.PageInfo(
|
static _i16.PageInfo page = _i16.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
return const _i5.FavouritesScreen();
|
return const _i5.FavouritesScreen();
|
||||||
|
|
@ -168,8 +170,8 @@ class FavouritesRoute extends _i15.PageRouteInfo<void> {
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i6.HomeScreen]
|
/// [_i6.HomeScreen]
|
||||||
class HomeRoute extends _i15.PageRouteInfo<void> {
|
class HomeRoute extends _i16.PageRouteInfo<void> {
|
||||||
const HomeRoute({List<_i15.PageRouteInfo>? children})
|
const HomeRoute({List<_i16.PageRouteInfo>? children})
|
||||||
: super(
|
: super(
|
||||||
HomeRoute.name,
|
HomeRoute.name,
|
||||||
initialChildren: children,
|
initialChildren: children,
|
||||||
|
|
@ -177,7 +179,7 @@ class HomeRoute extends _i15.PageRouteInfo<void> {
|
||||||
|
|
||||||
static const String name = 'HomeRoute';
|
static const String name = 'HomeRoute';
|
||||||
|
|
||||||
static _i15.PageInfo page = _i15.PageInfo(
|
static _i16.PageInfo page = _i16.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
return const _i6.HomeScreen();
|
return const _i6.HomeScreen();
|
||||||
|
|
@ -187,16 +189,16 @@ class HomeRoute extends _i15.PageRouteInfo<void> {
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i7.LibrarySearchScreen]
|
/// [_i7.LibrarySearchScreen]
|
||||||
class LibrarySearchRoute extends _i15.PageRouteInfo<LibrarySearchRouteArgs> {
|
class LibrarySearchRoute extends _i16.PageRouteInfo<LibrarySearchRouteArgs> {
|
||||||
LibrarySearchRoute({
|
LibrarySearchRoute({
|
||||||
String? viewModelId,
|
String? viewModelId,
|
||||||
List<String>? folderId,
|
List<String>? folderId,
|
||||||
bool? favourites,
|
bool? favourites,
|
||||||
_i18.SortingOrder? sortOrder,
|
_i19.SortingOrder? sortOrder,
|
||||||
_i18.SortingOptions? sortingOptions,
|
_i19.SortingOptions? sortingOptions,
|
||||||
_i19.PhotoModel? photoToView,
|
_i20.PhotoModel? photoToView,
|
||||||
_i17.Key? key,
|
_i18.Key? key,
|
||||||
List<_i15.PageRouteInfo>? children,
|
List<_i16.PageRouteInfo>? children,
|
||||||
}) : super(
|
}) : super(
|
||||||
LibrarySearchRoute.name,
|
LibrarySearchRoute.name,
|
||||||
args: LibrarySearchRouteArgs(
|
args: LibrarySearchRouteArgs(
|
||||||
|
|
@ -220,7 +222,7 @@ class LibrarySearchRoute extends _i15.PageRouteInfo<LibrarySearchRouteArgs> {
|
||||||
|
|
||||||
static const String name = 'LibrarySearchRoute';
|
static const String name = 'LibrarySearchRoute';
|
||||||
|
|
||||||
static _i15.PageInfo page = _i15.PageInfo(
|
static _i16.PageInfo page = _i16.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
final queryParams = data.queryParams;
|
final queryParams = data.queryParams;
|
||||||
|
|
@ -262,13 +264,13 @@ class LibrarySearchRouteArgs {
|
||||||
|
|
||||||
final bool? favourites;
|
final bool? favourites;
|
||||||
|
|
||||||
final _i18.SortingOrder? sortOrder;
|
final _i19.SortingOrder? sortOrder;
|
||||||
|
|
||||||
final _i18.SortingOptions? sortingOptions;
|
final _i19.SortingOptions? sortingOptions;
|
||||||
|
|
||||||
final _i19.PhotoModel? photoToView;
|
final _i20.PhotoModel? photoToView;
|
||||||
|
|
||||||
final _i17.Key? key;
|
final _i18.Key? key;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
@ -278,8 +280,8 @@ class LibrarySearchRouteArgs {
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i8.LockScreen]
|
/// [_i8.LockScreen]
|
||||||
class LockRoute extends _i15.PageRouteInfo<void> {
|
class LockRoute extends _i16.PageRouteInfo<void> {
|
||||||
const LockRoute({List<_i15.PageRouteInfo>? children})
|
const LockRoute({List<_i16.PageRouteInfo>? children})
|
||||||
: super(
|
: super(
|
||||||
LockRoute.name,
|
LockRoute.name,
|
||||||
initialChildren: children,
|
initialChildren: children,
|
||||||
|
|
@ -287,7 +289,7 @@ class LockRoute extends _i15.PageRouteInfo<void> {
|
||||||
|
|
||||||
static const String name = 'LockRoute';
|
static const String name = 'LockRoute';
|
||||||
|
|
||||||
static _i15.PageInfo page = _i15.PageInfo(
|
static _i16.PageInfo page = _i16.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
return const _i8.LockScreen();
|
return const _i8.LockScreen();
|
||||||
|
|
@ -297,8 +299,8 @@ class LockRoute extends _i15.PageRouteInfo<void> {
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i9.LoginScreen]
|
/// [_i9.LoginScreen]
|
||||||
class LoginRoute extends _i15.PageRouteInfo<void> {
|
class LoginRoute extends _i16.PageRouteInfo<void> {
|
||||||
const LoginRoute({List<_i15.PageRouteInfo>? children})
|
const LoginRoute({List<_i16.PageRouteInfo>? children})
|
||||||
: super(
|
: super(
|
||||||
LoginRoute.name,
|
LoginRoute.name,
|
||||||
initialChildren: children,
|
initialChildren: children,
|
||||||
|
|
@ -306,7 +308,7 @@ class LoginRoute extends _i15.PageRouteInfo<void> {
|
||||||
|
|
||||||
static const String name = 'LoginRoute';
|
static const String name = 'LoginRoute';
|
||||||
|
|
||||||
static _i15.PageInfo page = _i15.PageInfo(
|
static _i16.PageInfo page = _i16.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
return const _i9.LoginScreen();
|
return const _i9.LoginScreen();
|
||||||
|
|
@ -316,8 +318,8 @@ class LoginRoute extends _i15.PageRouteInfo<void> {
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i10.PlayerSettingsPage]
|
/// [_i10.PlayerSettingsPage]
|
||||||
class PlayerSettingsRoute extends _i15.PageRouteInfo<void> {
|
class PlayerSettingsRoute extends _i16.PageRouteInfo<void> {
|
||||||
const PlayerSettingsRoute({List<_i15.PageRouteInfo>? children})
|
const PlayerSettingsRoute({List<_i16.PageRouteInfo>? children})
|
||||||
: super(
|
: super(
|
||||||
PlayerSettingsRoute.name,
|
PlayerSettingsRoute.name,
|
||||||
initialChildren: children,
|
initialChildren: children,
|
||||||
|
|
@ -325,7 +327,7 @@ class PlayerSettingsRoute extends _i15.PageRouteInfo<void> {
|
||||||
|
|
||||||
static const String name = 'PlayerSettingsRoute';
|
static const String name = 'PlayerSettingsRoute';
|
||||||
|
|
||||||
static _i15.PageInfo page = _i15.PageInfo(
|
static _i16.PageInfo page = _i16.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
return const _i10.PlayerSettingsPage();
|
return const _i10.PlayerSettingsPage();
|
||||||
|
|
@ -335,8 +337,8 @@ class PlayerSettingsRoute extends _i15.PageRouteInfo<void> {
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i11.SecuritySettingsPage]
|
/// [_i11.SecuritySettingsPage]
|
||||||
class SecuritySettingsRoute extends _i15.PageRouteInfo<void> {
|
class SecuritySettingsRoute extends _i16.PageRouteInfo<void> {
|
||||||
const SecuritySettingsRoute({List<_i15.PageRouteInfo>? children})
|
const SecuritySettingsRoute({List<_i16.PageRouteInfo>? children})
|
||||||
: super(
|
: super(
|
||||||
SecuritySettingsRoute.name,
|
SecuritySettingsRoute.name,
|
||||||
initialChildren: children,
|
initialChildren: children,
|
||||||
|
|
@ -344,7 +346,7 @@ class SecuritySettingsRoute extends _i15.PageRouteInfo<void> {
|
||||||
|
|
||||||
static const String name = 'SecuritySettingsRoute';
|
static const String name = 'SecuritySettingsRoute';
|
||||||
|
|
||||||
static _i15.PageInfo page = _i15.PageInfo(
|
static _i16.PageInfo page = _i16.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
return const _i11.SecuritySettingsPage();
|
return const _i11.SecuritySettingsPage();
|
||||||
|
|
@ -354,8 +356,8 @@ class SecuritySettingsRoute extends _i15.PageRouteInfo<void> {
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i12.SettingsScreen]
|
/// [_i12.SettingsScreen]
|
||||||
class SettingsRoute extends _i15.PageRouteInfo<void> {
|
class SettingsRoute extends _i16.PageRouteInfo<void> {
|
||||||
const SettingsRoute({List<_i15.PageRouteInfo>? children})
|
const SettingsRoute({List<_i16.PageRouteInfo>? children})
|
||||||
: super(
|
: super(
|
||||||
SettingsRoute.name,
|
SettingsRoute.name,
|
||||||
initialChildren: children,
|
initialChildren: children,
|
||||||
|
|
@ -363,7 +365,7 @@ class SettingsRoute extends _i15.PageRouteInfo<void> {
|
||||||
|
|
||||||
static const String name = 'SettingsRoute';
|
static const String name = 'SettingsRoute';
|
||||||
|
|
||||||
static _i15.PageInfo page = _i15.PageInfo(
|
static _i16.PageInfo page = _i16.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
return const _i12.SettingsScreen();
|
return const _i12.SettingsScreen();
|
||||||
|
|
@ -372,12 +374,31 @@ class SettingsRoute extends _i15.PageRouteInfo<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i13.SplashScreen]
|
/// [_i13.SettingsSelectionScreen]
|
||||||
class SplashRoute extends _i15.PageRouteInfo<SplashRouteArgs> {
|
class SettingsSelectionRoute extends _i16.PageRouteInfo<void> {
|
||||||
|
const SettingsSelectionRoute({List<_i16.PageRouteInfo>? children})
|
||||||
|
: super(
|
||||||
|
SettingsSelectionRoute.name,
|
||||||
|
initialChildren: children,
|
||||||
|
);
|
||||||
|
|
||||||
|
static const String name = 'SettingsSelectionRoute';
|
||||||
|
|
||||||
|
static _i16.PageInfo page = _i16.PageInfo(
|
||||||
|
name,
|
||||||
|
builder: (data) {
|
||||||
|
return const _i13.SettingsSelectionScreen();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// generated route for
|
||||||
|
/// [_i14.SplashScreen]
|
||||||
|
class SplashRoute extends _i16.PageRouteInfo<SplashRouteArgs> {
|
||||||
SplashRoute({
|
SplashRoute({
|
||||||
dynamic Function(bool)? loggedIn,
|
dynamic Function(bool)? loggedIn,
|
||||||
_i20.Key? key,
|
_i21.Key? key,
|
||||||
List<_i15.PageRouteInfo>? children,
|
List<_i16.PageRouteInfo>? children,
|
||||||
}) : super(
|
}) : super(
|
||||||
SplashRoute.name,
|
SplashRoute.name,
|
||||||
args: SplashRouteArgs(
|
args: SplashRouteArgs(
|
||||||
|
|
@ -389,12 +410,12 @@ class SplashRoute extends _i15.PageRouteInfo<SplashRouteArgs> {
|
||||||
|
|
||||||
static const String name = 'SplashRoute';
|
static const String name = 'SplashRoute';
|
||||||
|
|
||||||
static _i15.PageInfo page = _i15.PageInfo(
|
static _i16.PageInfo page = _i16.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
final args =
|
final args =
|
||||||
data.argsAs<SplashRouteArgs>(orElse: () => const SplashRouteArgs());
|
data.argsAs<SplashRouteArgs>(orElse: () => const SplashRouteArgs());
|
||||||
return _i13.SplashScreen(
|
return _i14.SplashScreen(
|
||||||
loggedIn: args.loggedIn,
|
loggedIn: args.loggedIn,
|
||||||
key: args.key,
|
key: args.key,
|
||||||
);
|
);
|
||||||
|
|
@ -410,7 +431,7 @@ class SplashRouteArgs {
|
||||||
|
|
||||||
final dynamic Function(bool)? loggedIn;
|
final dynamic Function(bool)? loggedIn;
|
||||||
|
|
||||||
final _i20.Key? key;
|
final _i21.Key? key;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
@ -419,12 +440,12 @@ class SplashRouteArgs {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i14.SyncedScreen]
|
/// [_i15.SyncedScreen]
|
||||||
class SyncedRoute extends _i15.PageRouteInfo<SyncedRouteArgs> {
|
class SyncedRoute extends _i16.PageRouteInfo<SyncedRouteArgs> {
|
||||||
SyncedRoute({
|
SyncedRoute({
|
||||||
_i20.ScrollController? navigationScrollController,
|
_i21.ScrollController? navigationScrollController,
|
||||||
_i20.Key? key,
|
_i21.Key? key,
|
||||||
List<_i15.PageRouteInfo>? children,
|
List<_i16.PageRouteInfo>? children,
|
||||||
}) : super(
|
}) : super(
|
||||||
SyncedRoute.name,
|
SyncedRoute.name,
|
||||||
args: SyncedRouteArgs(
|
args: SyncedRouteArgs(
|
||||||
|
|
@ -436,12 +457,12 @@ class SyncedRoute extends _i15.PageRouteInfo<SyncedRouteArgs> {
|
||||||
|
|
||||||
static const String name = 'SyncedRoute';
|
static const String name = 'SyncedRoute';
|
||||||
|
|
||||||
static _i15.PageInfo page = _i15.PageInfo(
|
static _i16.PageInfo page = _i16.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
final args =
|
final args =
|
||||||
data.argsAs<SyncedRouteArgs>(orElse: () => const SyncedRouteArgs());
|
data.argsAs<SyncedRouteArgs>(orElse: () => const SyncedRouteArgs());
|
||||||
return _i14.SyncedScreen(
|
return _i15.SyncedScreen(
|
||||||
navigationScrollController: args.navigationScrollController,
|
navigationScrollController: args.navigationScrollController,
|
||||||
key: args.key,
|
key: args.key,
|
||||||
);
|
);
|
||||||
|
|
@ -455,9 +476,9 @@ class SyncedRouteArgs {
|
||||||
this.key,
|
this.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i20.ScrollController? navigationScrollController;
|
final _i21.ScrollController? navigationScrollController;
|
||||||
|
|
||||||
final _i20.Key? key;
|
final _i21.Key? key;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
|
||||||
|
|
@ -131,235 +131,243 @@ class _BookViewerControlsState extends ConsumerState<BookViewerControls> {
|
||||||
final previousChapter = details.previousChapter(bookViewerDetails.book);
|
final previousChapter = details.previousChapter(bookViewerDetails.book);
|
||||||
final nextChapter = details.nextChapter(bookViewerDetails.book);
|
final nextChapter = details.nextChapter(bookViewerDetails.book);
|
||||||
|
|
||||||
return MediaQuery.removePadding(
|
final double leftPadding = MediaQuery.of(context).viewPadding.left;
|
||||||
context: context,
|
final double rightPadding = MediaQuery.of(context).viewPadding.right;
|
||||||
child: InputHandler(
|
|
||||||
onKeyEvent: (node, event) => _onKey(event) ? KeyEventResult.handled : KeyEventResult.ignored,
|
return InputHandler(
|
||||||
child: Stack(
|
onKeyEvent: (node, event) => _onKey(event) ? KeyEventResult.handled : KeyEventResult.ignored,
|
||||||
children: [
|
child: Stack(
|
||||||
IgnorePointer(
|
children: [
|
||||||
ignoring: !showControls,
|
IgnorePointer(
|
||||||
child: AnimatedOpacity(
|
ignoring: !showControls,
|
||||||
duration: const Duration(milliseconds: 500),
|
child: AnimatedOpacity(
|
||||||
opacity: showControls ? 1 : 0,
|
duration: const Duration(milliseconds: 500),
|
||||||
child: Stack(
|
opacity: showControls ? 1 : 0,
|
||||||
children: [
|
child: Stack(
|
||||||
Container(
|
children: [
|
||||||
decoration: BoxDecoration(
|
Container(
|
||||||
gradient: LinearGradient(
|
decoration: BoxDecoration(
|
||||||
begin: Alignment.topCenter,
|
gradient: LinearGradient(
|
||||||
end: Alignment.bottomCenter,
|
begin: Alignment.topCenter,
|
||||||
colors: [
|
end: Alignment.bottomCenter,
|
||||||
overlayColor.withValues(alpha: 1),
|
colors: [
|
||||||
overlayColor.withValues(alpha: 0.65),
|
overlayColor.withValues(alpha: 1),
|
||||||
overlayColor.withValues(alpha: 0),
|
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),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (!bookViewerDetails.loading) ...{
|
child: Padding(
|
||||||
if (bookViewerDetails.book != null && bookViewerDetails.pages.isNotEmpty) ...{
|
padding:
|
||||||
Align(
|
EdgeInsets.only(top: topPadding, left: leftPadding, right: rightPadding).copyWith(bottom: 8),
|
||||||
alignment: Alignment.bottomCenter,
|
child: Column(
|
||||||
child: Container(
|
mainAxisSize: MainAxisSize.min,
|
||||||
decoration: BoxDecoration(
|
children: [
|
||||||
gradient: LinearGradient(
|
if (AdaptiveLayout.of(context).isDesktop)
|
||||||
begin: Alignment.topCenter,
|
const Flexible(
|
||||||
end: Alignment.bottomCenter,
|
child: DefaultTitleBar(
|
||||||
colors: [
|
height: 50,
|
||||||
overlayColor.withValues(alpha: 0),
|
brightness: Brightness.dark,
|
||||||
overlayColor.withValues(alpha: 0.65),
|
|
||||||
overlayColor.withValues(alpha: 1),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Padding(
|
Row(
|
||||||
padding: EdgeInsets.only(bottom: bottomPadding).copyWith(top: 16, bottom: 16),
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
child: Column(
|
children: [
|
||||||
mainAxisSize: MainAxisSize.min,
|
const BackButton(),
|
||||||
children: [
|
const SizedBox(
|
||||||
const SizedBox(height: 30),
|
width: 16,
|
||||||
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),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
bookViewerDetails.book?.name ?? "None",
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 16),
|
||||||
} 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),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
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),
|
controller: AdaptiveLayout.scrollOf(context),
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
slivers: [
|
slivers: [
|
||||||
if (AdaptiveLayout.of(context).layout == LayoutState.phone)
|
if (AdaptiveLayout.viewSizeOf(context) == ViewSize.phone)
|
||||||
NestedSliverAppBar(
|
NestedSliverAppBar(
|
||||||
route: LibrarySearchRoute(),
|
route: LibrarySearchRoute(),
|
||||||
parent: context,
|
parent: context,
|
||||||
|
|
@ -102,7 +102,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||||
if (homeBanner && homeCarouselItems.isNotEmpty) ...{
|
if (homeBanner && homeCarouselItems.isNotEmpty) ...{
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Transform.translate(
|
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),
|
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/images_models.dart';
|
||||||
import 'package:fladder/models/items/item_shared_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/chip_button.dart';
|
||||||
import 'package:fladder/screens/shared/media/components/media_header.dart';
|
import 'package:fladder/screens/shared/media/components/media_header.dart';
|
||||||
import 'package:fladder/screens/shared/media/components/small_detail_widgets.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();
|
(MediaQuery.sizeOf(context).height - (MediaQuery.paddingOf(context).top + 150)).clamp(50, 1250).toDouble();
|
||||||
|
|
||||||
final crossAlignment =
|
final crossAlignment =
|
||||||
AdaptiveLayout.of(context).layout != LayoutState.phone ? CrossAxisAlignment.start : CrossAxisAlignment.center;
|
AdaptiveLayout.viewSizeOf(context) != ViewSize.phone ? CrossAxisAlignment.start : CrossAxisAlignment.center;
|
||||||
|
|
||||||
return ConstrainedBox(
|
return ConstrainedBox(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import 'package:ficonsax/ficonsax.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'package:fladder/models/item_base_model.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/items/episode_details_provider.dart';
|
||||||
import 'package:fladder/providers/user_provider.dart';
|
import 'package:fladder/providers/user_provider.dart';
|
||||||
import 'package:fladder/screens/details_screens/components/media_stream_information.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 seasonDetails = details.series;
|
||||||
final episodeDetails = details.episode;
|
final episodeDetails = details.episode;
|
||||||
final wrapAlignment =
|
final wrapAlignment =
|
||||||
AdaptiveLayout.of(context).layout != LayoutState.phone ? WrapAlignment.start : WrapAlignment.center;
|
AdaptiveLayout.viewSizeOf(context) != ViewSize.phone ? WrapAlignment.start : WrapAlignment.center;
|
||||||
|
|
||||||
return DetailScaffold(
|
return DetailScaffold(
|
||||||
label: widget.item.name,
|
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:fladder/util/adaptive_layout.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
|
@ -39,7 +40,7 @@ class _ItemDetailScreenState extends ConsumerState<MovieDetailScreen> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final details = ref.watch(providerInstance);
|
final details = ref.watch(providerInstance);
|
||||||
final wrapAlignment =
|
final wrapAlignment =
|
||||||
AdaptiveLayout.of(context).layout != LayoutState.phone ? WrapAlignment.start : WrapAlignment.center;
|
AdaptiveLayout.viewSizeOf(context) != ViewSize.phone ? WrapAlignment.start : WrapAlignment.center;
|
||||||
|
|
||||||
return DetailScaffold(
|
return DetailScaffold(
|
||||||
label: widget.item.name,
|
label: widget.item.name,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
|
@ -56,7 +57,7 @@ class _PersonDetailScreenState extends ConsumerState<PersonDetailScreen> {
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(15),
|
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
|
||||||
: MediaQuery.of(context).size.width / 3.5,
|
: MediaQuery.of(context).size.width / 3.5,
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
|
@ -43,7 +44,7 @@ class _SeriesDetailScreenState extends ConsumerState<SeriesDetailScreen> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final details = ref.watch(providerId);
|
final details = ref.watch(providerId);
|
||||||
final wrapAlignment =
|
final wrapAlignment =
|
||||||
AdaptiveLayout.of(context).layout != LayoutState.phone ? WrapAlignment.start : WrapAlignment.center;
|
AdaptiveLayout.viewSizeOf(context) != ViewSize.phone ? WrapAlignment.start : WrapAlignment.center;
|
||||||
|
|
||||||
return DetailScaffold(
|
return DetailScaffold(
|
||||||
label: details?.name ?? "",
|
label: details?.name ?? "",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:auto_route/auto_route.dart';
|
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/providers/settings/client_settings_provider.dart';
|
||||||
import 'package:fladder/routes/auto_router.gr.dart';
|
import 'package:fladder/routes/auto_router.gr.dart';
|
||||||
import 'package:fladder/screens/shared/nested_scaffold.dart';
|
import 'package:fladder/screens/shared/nested_scaffold.dart';
|
||||||
|
|
@ -32,7 +33,7 @@ class FavouritesScreen extends ConsumerWidget {
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
controller: AdaptiveLayout.scrollOf(context),
|
controller: AdaptiveLayout.scrollOf(context),
|
||||||
slivers: [
|
slivers: [
|
||||||
if (AdaptiveLayout.of(context).layout == LayoutState.phone)
|
if (AdaptiveLayout.viewSizeOf(context) == ViewSize.phone)
|
||||||
NestedSliverAppBar(
|
NestedSliverAppBar(
|
||||||
searchTitle: "${context.localized.search} ${context.localized.favorites.toLowerCase()}...",
|
searchTitle: "${context.localized.search} ${context.localized.favorites.toLowerCase()}...",
|
||||||
parent: context,
|
parent: context,
|
||||||
|
|
|
||||||
|
|
@ -25,51 +25,54 @@ class HomeScreen extends ConsumerWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final canDownload = ref.watch(showSyncButtonProviderProvider);
|
final canDownload = ref.watch(showSyncButtonProviderProvider);
|
||||||
final destinations = HomeTabs.values.map((e) {
|
final destinations = HomeTabs.values
|
||||||
switch (e) {
|
.map((e) {
|
||||||
case HomeTabs.dashboard:
|
switch (e) {
|
||||||
return DestinationModel(
|
case HomeTabs.dashboard:
|
||||||
label: context.localized.navigationDashboard,
|
return DestinationModel(
|
||||||
icon: const Icon(IconsaxOutline.home),
|
label: context.localized.navigationDashboard,
|
||||||
selectedIcon: const Icon(IconsaxBold.home),
|
icon: const Icon(IconsaxOutline.home),
|
||||||
route: const DashboardRoute(),
|
selectedIcon: const Icon(IconsaxBold.home),
|
||||||
action: () => context.router.navigate(const DashboardRoute()),
|
route: const DashboardRoute(),
|
||||||
floatingActionButton: AdaptiveFab(
|
action: () => context.router.navigate(const DashboardRoute()),
|
||||||
context: context,
|
floatingActionButton: AdaptiveFab(
|
||||||
title: context.localized.search,
|
context: context,
|
||||||
key: Key(e.name.capitalize()),
|
title: context.localized.search,
|
||||||
onPressed: () => context.router.navigate(LibrarySearchRoute()),
|
key: Key(e.name.capitalize()),
|
||||||
child: const Icon(IconsaxOutline.search_normal_1),
|
onPressed: () => context.router.navigate(LibrarySearchRoute()),
|
||||||
),
|
child: const Icon(IconsaxOutline.search_normal_1),
|
||||||
);
|
),
|
||||||
case HomeTabs.favorites:
|
);
|
||||||
return DestinationModel(
|
case HomeTabs.favorites:
|
||||||
label: context.localized.navigationFavorites,
|
return DestinationModel(
|
||||||
icon: const Icon(IconsaxOutline.heart),
|
label: context.localized.navigationFavorites,
|
||||||
selectedIcon: const Icon(IconsaxBold.heart),
|
icon: const Icon(IconsaxOutline.heart),
|
||||||
route: const FavouritesRoute(),
|
selectedIcon: const Icon(IconsaxBold.heart),
|
||||||
floatingActionButton: AdaptiveFab(
|
route: const FavouritesRoute(),
|
||||||
context: context,
|
floatingActionButton: AdaptiveFab(
|
||||||
title: context.localized.filter(0),
|
context: context,
|
||||||
key: Key(e.name.capitalize()),
|
title: context.localized.filter(0),
|
||||||
onPressed: () => context.router.navigate(LibrarySearchRoute(favourites: true)),
|
key: Key(e.name.capitalize()),
|
||||||
child: const Icon(IconsaxOutline.heart_search),
|
onPressed: () => context.router.navigate(LibrarySearchRoute(favourites: true)),
|
||||||
),
|
child: const Icon(IconsaxOutline.heart_search),
|
||||||
action: () => context.router.navigate(const FavouritesRoute()),
|
),
|
||||||
);
|
action: () => context.router.navigate(const FavouritesRoute()),
|
||||||
case HomeTabs.sync:
|
);
|
||||||
if (canDownload) {
|
case HomeTabs.sync:
|
||||||
return DestinationModel(
|
if (canDownload) {
|
||||||
label: context.localized.navigationSync,
|
return DestinationModel(
|
||||||
icon: const Icon(IconsaxOutline.cloud),
|
label: context.localized.navigationSync,
|
||||||
selectedIcon: const Icon(IconsaxBold.cloud),
|
icon: const Icon(IconsaxOutline.cloud),
|
||||||
route: SyncedRoute(),
|
selectedIcon: const Icon(IconsaxBold.cloud),
|
||||||
action: () => context.router.navigate(SyncedRoute()),
|
route: SyncedRoute(),
|
||||||
);
|
action: () => context.router.navigate(SyncedRoute()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return null;
|
})
|
||||||
}
|
.nonNulls
|
||||||
});
|
.toList();
|
||||||
return HeroControllerScope(
|
return HeroControllerScope(
|
||||||
controller: HeroController(),
|
controller: HeroController(),
|
||||||
child: AutoRouter(
|
child: AutoRouter(
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:fladder/models/items/photos_model.dart';
|
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/models/view_model.dart';
|
||||||
import 'package:fladder/providers/library_provider.dart';
|
import 'package:fladder/providers/library_provider.dart';
|
||||||
import 'package:fladder/screens/photo_viewer/photo_viewer_screen.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 {
|
class _TimelineTabState extends ConsumerState<TimelineTab> with AutomaticKeepAliveClientMixin {
|
||||||
final itemScrollController = ItemScrollController();
|
final itemScrollController = ItemScrollController();
|
||||||
double get posterCount {
|
double get posterCount {
|
||||||
if (AdaptiveLayout.of(context).layout == LayoutState.desktop) {
|
if (AdaptiveLayout.viewSizeOf(context) == ViewSize.desktop) {
|
||||||
return 200;
|
return 200;
|
||||||
}
|
}
|
||||||
return 125;
|
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/library_search/library_search_options.dart';
|
||||||
import 'package:fladder/models/media_playback_model.dart';
|
import 'package:fladder/models/media_playback_model.dart';
|
||||||
import 'package:fladder/models/playlist_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/library_search_provider.dart';
|
||||||
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||||
import 'package:fladder/providers/video_player_provider.dart';
|
import 'package:fladder/providers/video_player_provider.dart';
|
||||||
|
|
@ -227,7 +228,7 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
|
||||||
child: MediaQuery.removeViewInsets(
|
child: MediaQuery.removeViewInsets(
|
||||||
context: context,
|
context: context,
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: AdaptiveLayout.of(context).layout == LayoutState.desktop
|
borderRadius: AdaptiveLayout.viewSizeOf(context) == ViewSize.desktop
|
||||||
? BorderRadius.circular(15)
|
? BorderRadius.circular(15)
|
||||||
: BorderRadius.circular(0),
|
: BorderRadius.circular(0),
|
||||||
child: FladderScrollbar(
|
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(width: 6),
|
||||||
const SizedBox.square(dimension: 46, child: SettingsUserIcon()),
|
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:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.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:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'package:fladder/models/settings/home_settings_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/client_settings_provider.dart';
|
||||||
import 'package:fladder/providers/settings/home_settings_provider.dart';
|
|
||||||
import 'package:fladder/providers/shared_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/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_list_tile.dart';
|
||||||
import 'package:fladder/screens/settings/settings_scaffold.dart';
|
import 'package:fladder/screens/settings/settings_scaffold.dart';
|
||||||
import 'package:fladder/screens/settings/widgets/settings_label_divider.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/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/localization_helper.dart';
|
||||||
import 'package:fladder/util/option_dialogue.dart';
|
|
||||||
import 'package:fladder/util/simple_duration_picker.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()
|
@RoutePage()
|
||||||
class ClientSettingsPage extends ConsumerStatefulWidget {
|
class ClientSettingsPage extends ConsumerStatefulWidget {
|
||||||
|
|
@ -48,114 +38,15 @@ class _ClientSettingsPageState extends ConsumerState<ClientSettingsPage> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final clientSettings = ref.watch(clientSettingsProvider);
|
final clientSettings = ref.watch(clientSettingsProvider);
|
||||||
final showBackground = AdaptiveLayout.of(context).layout != LayoutState.phone &&
|
final showBackground = AdaptiveLayout.viewSizeOf(context) != ViewSize.phone &&
|
||||||
AdaptiveLayout.of(context).size != ScreenLayout.single;
|
AdaptiveLayout.layoutModeOf(context) != LayoutMode.single;
|
||||||
final currentFolder = ref.watch(syncProvider.notifier).savePath;
|
|
||||||
Locale currentLocale = WidgetsBinding.instance.platformDispatcher.locale;
|
|
||||||
|
|
||||||
final canSync = ref.watch(userProvider.select((value) => value?.canDownload ?? false));
|
|
||||||
return Card(
|
return Card(
|
||||||
elevation: showBackground ? 2 : 0,
|
elevation: showBackground ? 2 : 0,
|
||||||
child: SettingsScaffold(
|
child: SettingsScaffold(
|
||||||
label: "Fladder",
|
label: "Fladder",
|
||||||
items: [
|
items: [
|
||||||
if (canSync && !kIsWeb) ...[
|
...buildClientSettingsDownload(context, ref, setState),
|
||||||
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(),
|
|
||||||
],
|
|
||||||
SettingsLabelDivider(label: context.localized.lockscreen),
|
SettingsLabelDivider(label: context.localized.lockscreen),
|
||||||
SettingsListTile(
|
SettingsListTile(
|
||||||
label: Text(context.localized.timeOut),
|
label: Text(context.localized.timeOut),
|
||||||
|
|
@ -166,329 +57,18 @@ class _ClientSettingsPageState extends ConsumerState<ClientSettingsPage> {
|
||||||
initialValue: clientSettings.timeOut ?? const Duration(),
|
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)
|
? Duration(minutes: timePicker.inMinutes, seconds: timePicker.inSeconds % 60)
|
||||||
: null);
|
: null);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
SettingsLabelDivider(label: context.localized.dashboard),
|
...buildClientSettingsDashboard(context, ref),
|
||||||
SettingsListTile(
|
...buildClientSettingsVisual(context, ref, nextUpDaysEditor, libraryPageSizeController),
|
||||||
label: Text(context.localized.settingsHomeBannerTitle),
|
...buildClientSettingsTheme(context, ref),
|
||||||
subLabel: Text(context.localized.settingsHomeBannerDescription),
|
if (AdaptiveLayout.inputDeviceOf(context) == InputDevice.pointer) ...[
|
||||||
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(),
|
|
||||||
SettingsLabelDivider(label: context.localized.controls),
|
SettingsLabelDivider(label: context.localized.controls),
|
||||||
SettingsListTile(
|
SettingsListTile(
|
||||||
label: Text(context.localized.mouseDragSupport),
|
label: Text(context.localized.mouseDragSupport),
|
||||||
|
|
@ -499,11 +79,13 @@ class _ClientSettingsPageState extends ConsumerState<ClientSettingsPage> {
|
||||||
trailing: Switch(
|
trailing: Switch(
|
||||||
value: clientSettings.mouseDragSupport,
|
value: clientSettings.mouseDragSupport,
|
||||||
onChanged: (value) => ref
|
onChanged: (value) => ref
|
||||||
.read(clientSettingsProvider.notifier)
|
.read(clientSettingsProvider.notifier)
|
||||||
.update((current) => current.copyWith(mouseDragSupport: !clientSettings.mouseDragSupport)),
|
.update((current) => current.copyWith(mouseDragSupport: !clientSettings.mouseDragSupport)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
const Divider(),
|
||||||
|
...buildClientSettingsAdvanced(context, ref),
|
||||||
if (kDebugMode) ...[
|
if (kDebugMode) ...[
|
||||||
const SizedBox(height: 64),
|
const SizedBox(height: 64),
|
||||||
SettingsListTile(
|
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:auto_route/auto_route.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.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/models/settings/video_player_settings.dart';
|
||||||
import 'package:fladder/providers/settings/video_player_settings_provider.dart';
|
import 'package:fladder/providers/settings/video_player_settings_provider.dart';
|
||||||
import 'package:fladder/screens/settings/settings_list_tile.dart';
|
import 'package:fladder/screens/settings/settings_list_tile.dart';
|
||||||
|
|
@ -34,8 +35,8 @@ class _PlayerSettingsPageState extends ConsumerState<PlayerSettingsPage> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final videoSettings = ref.watch(videoPlayerSettingsProvider);
|
final videoSettings = ref.watch(videoPlayerSettingsProvider);
|
||||||
final provider = ref.read(videoPlayerSettingsProvider.notifier);
|
final provider = ref.read(videoPlayerSettingsProvider.notifier);
|
||||||
final showBackground = AdaptiveLayout.of(context).layout != LayoutState.phone &&
|
final showBackground = AdaptiveLayout.viewSizeOf(context) != ViewSize.phone &&
|
||||||
AdaptiveLayout.of(context).size != ScreenLayout.single;
|
AdaptiveLayout.layoutModeOf(context) != LayoutMode.single;
|
||||||
return Card(
|
return Card(
|
||||||
elevation: showBackground ? 2 : 0,
|
elevation: showBackground ? 2 : 0,
|
||||||
child: SettingsScaffold(
|
child: SettingsScaffold(
|
||||||
|
|
@ -63,20 +64,19 @@ class _PlayerSettingsPageState extends ConsumerState<PlayerSettingsPage> {
|
||||||
SettingsListTile(
|
SettingsListTile(
|
||||||
label: Text(context.localized.videoScalingFillScreenTitle),
|
label: Text(context.localized.videoScalingFillScreenTitle),
|
||||||
subLabel: Text(videoSettings.videoFit.label(context)),
|
subLabel: Text(videoSettings.videoFit.label(context)),
|
||||||
onTap: () => openOptionDialogue(
|
onTap: () => openMultiSelectOptions(
|
||||||
context,
|
context,
|
||||||
label: context.localized.videoScalingFillScreenTitle,
|
label: context.localized.videoScalingFillScreenTitle,
|
||||||
items: BoxFit.values,
|
items: BoxFit.values,
|
||||||
itemBuilder: (type) => RadioListTile(
|
selected: [ref.read(videoPlayerSettingsProvider.select((value) => value.videoFit))],
|
||||||
title: Text(type?.label(context) ?? ""),
|
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,
|
value: type,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
groupValue: ref.read(videoPlayerSettingsProvider.select((value) => value.videoFit)),
|
onChanged: (value) => tap(),
|
||||||
onChanged: (value) {
|
|
||||||
provider.setFitType(value);
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:auto_route/auto_route.dart';
|
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/providers/user_provider.dart';
|
||||||
import 'package:fladder/screens/settings/settings_list_tile.dart';
|
import 'package:fladder/screens/settings/settings_list_tile.dart';
|
||||||
import 'package:fladder/screens/settings/settings_scaffold.dart';
|
import 'package:fladder/screens/settings/settings_scaffold.dart';
|
||||||
|
|
@ -21,8 +22,8 @@ class _UserSettingsPageState extends ConsumerState<SecuritySettingsPage> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final user = ref.watch(userProvider);
|
final user = ref.watch(userProvider);
|
||||||
final showBackground = AdaptiveLayout.of(context).layout != LayoutState.phone &&
|
final showBackground = AdaptiveLayout.viewSizeOf(context) != ViewSize.phone &&
|
||||||
AdaptiveLayout.of(context).size != ScreenLayout.single;
|
AdaptiveLayout.layoutModeOf(context) != LayoutMode.single;
|
||||||
return Card(
|
return Card(
|
||||||
elevation: showBackground ? 2 : 0,
|
elevation: showBackground ? 2 : 0,
|
||||||
child: SettingsScaffold(
|
child: SettingsScaffold(
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.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/providers/user_provider.dart';
|
||||||
import 'package:fladder/screens/shared/user_icon.dart';
|
import 'package:fladder/screens/shared/user_icon.dart';
|
||||||
import 'package:fladder/util/adaptive_layout.dart';
|
import 'package:fladder/util/adaptive_layout.dart';
|
||||||
|
|
@ -10,26 +12,29 @@ import 'package:fladder/util/router_extension.dart';
|
||||||
|
|
||||||
class SettingsScaffold extends ConsumerWidget {
|
class SettingsScaffold extends ConsumerWidget {
|
||||||
final String label;
|
final String label;
|
||||||
final bool showUserIcon;
|
|
||||||
final ScrollController? scrollController;
|
final ScrollController? scrollController;
|
||||||
final List<Widget> items;
|
final List<Widget> items;
|
||||||
final List<Widget> bottomActions;
|
final List<Widget> bottomActions;
|
||||||
|
final bool showUserIcon;
|
||||||
|
final bool showBackButtonNested;
|
||||||
final Widget? floatingActionButton;
|
final Widget? floatingActionButton;
|
||||||
const SettingsScaffold({
|
const SettingsScaffold({
|
||||||
required this.label,
|
required this.label,
|
||||||
this.showUserIcon = false,
|
|
||||||
this.scrollController,
|
this.scrollController,
|
||||||
required this.items,
|
required this.items,
|
||||||
this.bottomActions = const [],
|
this.bottomActions = const [],
|
||||||
this.floatingActionButton,
|
this.floatingActionButton,
|
||||||
|
this.showUserIcon = false,
|
||||||
|
this.showBackButtonNested = false,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final padding = MediaQuery.of(context).padding;
|
final padding = MediaQuery.of(context).padding;
|
||||||
|
final singleLayout = AdaptiveLayout.layoutModeOf(context) == LayoutMode.single;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: AdaptiveLayout.of(context).size == ScreenLayout.dual ? Colors.transparent : null,
|
backgroundColor: AdaptiveLayout.layoutModeOf(context) == LayoutMode.dual ? Colors.transparent : null,
|
||||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
||||||
floatingActionButton: floatingActionButton,
|
floatingActionButton: floatingActionButton,
|
||||||
body: Column(
|
body: Column(
|
||||||
|
|
@ -38,10 +43,11 @@ class SettingsScaffold extends ConsumerWidget {
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
slivers: [
|
slivers: [
|
||||||
if (AdaptiveLayout.of(context).size == ScreenLayout.single)
|
if (singleLayout)
|
||||||
SliverAppBar.large(
|
SliverAppBar.large(
|
||||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
leading: BackButton(
|
||||||
leading: context.router.backButton(),
|
onPressed: () => backAction(context),
|
||||||
|
),
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
titlePadding: const EdgeInsets.symmetric(horizontal: 16)
|
titlePadding: const EdgeInsets.symmetric(horizontal: 16)
|
||||||
.add(EdgeInsets.only(left: padding.left, right: padding.right, bottom: 4)),
|
.add(EdgeInsets.only(left: padding.left, right: padding.right, bottom: 4)),
|
||||||
|
|
@ -51,11 +57,12 @@ class SettingsScaffold extends ConsumerWidget {
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
if (showUserIcon)
|
if (showUserIcon)
|
||||||
SizedBox.fromSize(
|
SizedBox.fromSize(
|
||||||
size: const Size.fromRadius(14),
|
size: const Size.fromRadius(14),
|
||||||
child: UserIcon(
|
child: UserIcon(
|
||||||
user: ref.watch(userProvider),
|
user: ref.watch(userProvider),
|
||||||
cornerRadius: 200,
|
cornerRadius: 200,
|
||||||
))
|
),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
expandedTitleScale: 1.2,
|
expandedTitleScale: 1.2,
|
||||||
|
|
@ -68,9 +75,15 @@ class SettingsScaffold extends ConsumerWidget {
|
||||||
else
|
else
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
padding: MediaQuery.paddingOf(context),
|
||||||
child: Text(AdaptiveLayout.of(context).size == ScreenLayout.single ? label : "",
|
child: Row(
|
||||||
style: Theme.of(context).textTheme.headlineLarge),
|
children: [
|
||||||
|
if (showBackButtonNested)
|
||||||
|
BackButton(
|
||||||
|
onPressed: () => backAction(context),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SliverPadding(
|
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:ficonsax/ficonsax.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.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/auth_provider.dart';
|
||||||
import 'package:fladder/providers/user_provider.dart';
|
import 'package:fladder/providers/user_provider.dart';
|
||||||
import 'package:fladder/routes/auto_router.gr.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/screens/shared/fladder_icon.dart';
|
||||||
import 'package:fladder/util/adaptive_layout.dart';
|
import 'package:fladder/util/adaptive_layout.dart';
|
||||||
import 'package:fladder/util/localization_helper.dart';
|
import 'package:fladder/util/localization_helper.dart';
|
||||||
import 'package:fladder/util/router_extension.dart';
|
|
||||||
import 'package:fladder/util/theme_extensions.dart';
|
import 'package:fladder/util/theme_extensions.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
|
|
@ -27,84 +27,84 @@ class SettingsScreen extends ConsumerStatefulWidget {
|
||||||
class _SettingsScreenState extends ConsumerState<SettingsScreen> {
|
class _SettingsScreenState extends ConsumerState<SettingsScreen> {
|
||||||
final scrollController = ScrollController();
|
final scrollController = ScrollController();
|
||||||
final minVerticalPadding = 20.0;
|
final minVerticalPadding = 20.0;
|
||||||
|
late LayoutMode lastAdaptiveLayout = AdaptiveLayout.layoutModeOf(context);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (AdaptiveLayout.of(context).size == ScreenLayout.single) {
|
return AutoTabsRouter(
|
||||||
return Card(
|
builder: (context, content) {
|
||||||
elevation: 0,
|
checkForNullIndex(context);
|
||||||
child: _leftPane(context),
|
return PopScope(
|
||||||
);
|
canPop: context.tabsRouter.activeIndex == 0 || AdaptiveLayout.layoutModeOf(context) == LayoutMode.dual,
|
||||||
} else {
|
onPopInvokedWithResult: (didPop, result) {
|
||||||
return AutoRouter(
|
if (!didPop) {
|
||||||
builder: (context, content) {
|
context.tabsRouter.setActiveIndex(0);
|
||||||
return Row(
|
}
|
||||||
mainAxisSize: MainAxisSize.max,
|
},
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
child: AdaptiveLayout.layoutModeOf(context) == LayoutMode.single
|
||||||
children: [
|
? Card(
|
||||||
Expanded(flex: 1, child: _leftPane(context)),
|
elevation: 0,
|
||||||
Expanded(
|
child: Stack(
|
||||||
flex: 2,
|
children: [_leftPane(context), content],
|
||||||
child: 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 {
|
IconData get deviceIcon {
|
||||||
if (AdaptiveLayout.of(context).isDesktop) {
|
if (AdaptiveLayout.of(context).isDesktop) {
|
||||||
return IconsaxOutline.monitor;
|
return IconsaxOutline.monitor;
|
||||||
}
|
}
|
||||||
switch (AdaptiveLayout.of(context).layout) {
|
switch (AdaptiveLayout.viewSizeOf(context)) {
|
||||||
case LayoutState.phone:
|
case ViewSize.phone:
|
||||||
return IconsaxOutline.mobile;
|
return IconsaxOutline.mobile;
|
||||||
case LayoutState.tablet:
|
case ViewSize.tablet:
|
||||||
return IconsaxOutline.monitor;
|
return IconsaxOutline.monitor;
|
||||||
case LayoutState.desktop:
|
case ViewSize.desktop:
|
||||||
return IconsaxOutline.monitor;
|
return IconsaxOutline.monitor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _leftPane(BuildContext context) {
|
Widget _leftPane(BuildContext context) {
|
||||||
void navigateTo(PageRouteInfo route) {
|
void navigateTo(PageRouteInfo route) => context.tabsRouter.navigate(route);
|
||||||
AdaptiveLayout.of(context).size == ScreenLayout.single
|
|
||||||
? context.router.navigate(route)
|
|
||||||
: context.router.replace(route);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool containsRoute(PageRouteInfo route) {
|
bool containsRoute(PageRouteInfo route) =>
|
||||||
return context.router.current.name == route.routeName;
|
AdaptiveLayout.layoutModeOf(context) == LayoutMode.dual && context.tabsRouter.current.name == route.routeName;
|
||||||
}
|
|
||||||
|
|
||||||
final quickConnectAvailable =
|
final quickConnectAvailable =
|
||||||
ref.watch(userProvider.select((value) => value?.serverConfiguration?.quickConnectAvailable ?? false));
|
ref.watch(userProvider.select((value) => value?.serverConfiguration?.quickConnectAvailable ?? false));
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
color: context.colors.surface,
|
color: context.colors.surface,
|
||||||
child: SettingsScaffold(
|
child: SettingsScaffold(
|
||||||
label: context.localized.settings,
|
label: context.localized.settings,
|
||||||
scrollController: scrollController,
|
scrollController: scrollController,
|
||||||
|
showBackButtonNested: true,
|
||||||
showUserIcon: true,
|
showUserIcon: true,
|
||||||
items: [
|
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(
|
SettingsListTile(
|
||||||
label: Text(context.localized.settingsClientTitle),
|
label: Text(context.localized.settingsClientTitle),
|
||||||
subLabel: Text(context.localized.settingsClientDesc),
|
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:collection/collection.dart';
|
||||||
import 'package:ficonsax/ficonsax.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/adaptive_layout.dart';
|
||||||
import 'package:fladder/util/list_padding.dart';
|
import 'package:fladder/util/list_padding.dart';
|
||||||
import 'package:fladder/util/localization_helper.dart';
|
import 'package:fladder/util/localization_helper.dart';
|
||||||
import 'package:fladder/util/map_bool_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_bottom_sheet.dart';
|
||||||
import 'package:fladder/widgets/shared/modal_side_sheet.dart';
|
import 'package:fladder/widgets/shared/modal_side_sheet.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class CategoryChip<T> extends StatelessWidget {
|
class CategoryChip<T> extends StatelessWidget {
|
||||||
final Map<T, bool> items;
|
final Map<T, bool> items;
|
||||||
|
|
@ -126,7 +129,7 @@ class CategoryChip<T> extends StatelessWidget {
|
||||||
].addInBetween(const SizedBox(width: 6)),
|
].addInBetween(const SizedBox(width: 6)),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (AdaptiveLayout.of(context).layout != LayoutState.phone) {
|
if (AdaptiveLayout.viewSizeOf(context) != ViewSize.phone) {
|
||||||
await showModalSideSheet(
|
await showModalSideSheet(
|
||||||
context,
|
context,
|
||||||
addDivider: true,
|
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/item_base_model.dart';
|
||||||
import 'package:fladder/models/items/images_models.dart';
|
import 'package:fladder/models/items/images_models.dart';
|
||||||
import 'package:fladder/models/media_playback_model.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/providers/video_player_provider.dart';
|
||||||
import 'package:fladder/routes/auto_router.gr.dart';
|
import 'package:fladder/routes/auto_router.gr.dart';
|
||||||
import 'package:fladder/theme.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(
|
Container(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 6),
|
margin: const EdgeInsets.symmetric(horizontal: 6),
|
||||||
child: const SizedBox(
|
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/item_base_model.dart';
|
||||||
import 'package:fladder/models/items/item_shared_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/poster_image.dart';
|
import 'package:fladder/screens/shared/media/components/poster_image.dart';
|
||||||
import 'package:fladder/util/adaptive_layout.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/item_base_model_extensions.dart';
|
||||||
import 'package:fladder/util/item_base_model/play_item_helpers.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/clickable_text.dart';
|
||||||
import 'package:fladder/widgets/shared/item_actions.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 {
|
class PosterWidget extends ConsumerWidget {
|
||||||
final ItemBaseModel poster;
|
final ItemBaseModel poster;
|
||||||
|
|
@ -69,7 +72,7 @@ class PosterWidget extends ConsumerWidget {
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
Flexible(
|
||||||
child: ClickableText(
|
child: ClickableText(
|
||||||
onTap: AdaptiveLayout.of(context).layout != LayoutState.phone
|
onTap: AdaptiveLayout.viewSizeOf(context) != ViewSize.phone
|
||||||
? () => poster.parentBaseModel.navigateTo(context)
|
? () => poster.parentBaseModel.navigateTo(context)
|
||||||
: null,
|
: null,
|
||||||
text: poster.title,
|
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/media_playback_model.dart';
|
||||||
|
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||||
import 'package:fladder/providers/video_player_provider.dart';
|
import 'package:fladder/providers/video_player_provider.dart';
|
||||||
import 'package:fladder/util/adaptive_layout.dart';
|
import 'package:fladder/util/adaptive_layout.dart';
|
||||||
import 'package:fladder/widgets/navigation_scaffold/components/floating_player_bar.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 {
|
class NestedScaffold extends ConsumerWidget {
|
||||||
final Widget body;
|
final Widget body;
|
||||||
|
|
@ -18,7 +21,7 @@ class NestedScaffold extends ConsumerWidget {
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
||||||
floatingActionButton: switch (AdaptiveLayout.layoutOf(context)) {
|
floatingActionButton: switch (AdaptiveLayout.layoutOf(context)) {
|
||||||
LayoutState.phone => null,
|
ViewSize.phone => null,
|
||||||
_ => switch (playerState) {
|
_ => switch (playerState) {
|
||||||
VideoPlayerState.minimized => const Padding(
|
VideoPlayerState.minimized => const Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,21 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:ficonsax/ficonsax.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/settings/client_settings_provider.dart';
|
||||||
|
import 'package:fladder/providers/sync_provider.dart';
|
||||||
import 'package:fladder/routes/auto_router.gr.dart';
|
import 'package:fladder/routes/auto_router.gr.dart';
|
||||||
import 'package:fladder/screens/shared/nested_scaffold.dart';
|
import 'package:fladder/screens/shared/nested_scaffold.dart';
|
||||||
import 'package:fladder/screens/shared/nested_sliver_appbar.dart';
|
import 'package:fladder/screens/shared/nested_sliver_appbar.dart';
|
||||||
import 'package:fladder/screens/syncing/sync_list_item.dart';
|
import 'package:fladder/screens/syncing/sync_list_item.dart';
|
||||||
import 'package:fladder/util/adaptive_layout.dart';
|
import 'package:fladder/util/adaptive_layout.dart';
|
||||||
import 'package:fladder/util/localization_helper.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/pinch_poster_zoom.dart';
|
||||||
import 'package:fladder/widgets/shared/pull_to_refresh.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()
|
@RoutePage()
|
||||||
class SyncedScreen extends ConsumerStatefulWidget {
|
class SyncedScreen extends ConsumerStatefulWidget {
|
||||||
|
|
@ -40,7 +42,7 @@ class _SyncedScreenState extends ConsumerState<SyncedScreen> {
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
controller: widget.navigationScrollController,
|
controller: widget.navigationScrollController,
|
||||||
slivers: [
|
slivers: [
|
||||||
if (AdaptiveLayout.of(context).layout == LayoutState.phone)
|
if (AdaptiveLayout.viewSizeOf(context) == ViewSize.phone)
|
||||||
NestedSliverAppBar(
|
NestedSliverAppBar(
|
||||||
searchTitle: "${context.localized.search} ...",
|
searchTitle: "${context.localized.search} ...",
|
||||||
parent: context,
|
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/items/media_segments_model.dart';
|
||||||
import 'package:fladder/models/media_playback_model.dart';
|
import 'package:fladder/models/media_playback_model.dart';
|
||||||
import 'package:fladder/models/playback/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/client_settings_provider.dart';
|
||||||
import 'package:fladder/providers/settings/video_player_settings_provider.dart';
|
import 'package:fladder/providers/settings/video_player_settings_provider.dart';
|
||||||
import 'package:fladder/providers/video_player_provider.dart';
|
import 'package:fladder/providers/video_player_provider.dart';
|
||||||
|
|
@ -300,7 +301,7 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => showVideoPlayerOptions(context, () => minimizePlayer(context)),
|
onPressed: () => showVideoPlayerOptions(context, () => minimizePlayer(context)),
|
||||||
icon: const Icon(IconsaxOutline.more)),
|
icon: const Icon(IconsaxOutline.more)),
|
||||||
if (AdaptiveLayout.layoutOf(context) == LayoutState.tablet) ...[
|
if (AdaptiveLayout.layoutOf(context) == ViewSize.tablet) ...[
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => showSubSelection(context),
|
onPressed: () => showSubSelection(context),
|
||||||
icon: const Icon(IconsaxOutline.subtitle),
|
icon: const Icon(IconsaxOutline.subtitle),
|
||||||
|
|
@ -310,7 +311,7 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
||||||
icon: const Icon(IconsaxOutline.audio_square),
|
icon: const Icon(IconsaxOutline.audio_square),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
if (AdaptiveLayout.layoutOf(context) == LayoutState.desktop) ...[
|
if (AdaptiveLayout.layoutOf(context) == ViewSize.desktop) ...[
|
||||||
Flexible(
|
Flexible(
|
||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
onPressed: () => showSubSelection(context),
|
onPressed: () => showSubSelection(context),
|
||||||
|
|
|
||||||
|
|
@ -3,21 +3,11 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'package:fladder/routes/auto_router.dart';
|
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||||
|
import 'package:fladder/providers/settings/home_settings_provider.dart';
|
||||||
import 'package:fladder/util/debug_banner.dart';
|
import 'package:fladder/util/debug_banner.dart';
|
||||||
import 'package:fladder/util/poster_defaults.dart';
|
import 'package:fladder/util/poster_defaults.dart';
|
||||||
|
|
||||||
enum LayoutState {
|
|
||||||
phone,
|
|
||||||
tablet,
|
|
||||||
desktop,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ScreenLayout {
|
|
||||||
single,
|
|
||||||
dual,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum InputDevice {
|
enum InputDevice {
|
||||||
touch,
|
touch,
|
||||||
pointer,
|
pointer,
|
||||||
|
|
@ -26,7 +16,7 @@ enum InputDevice {
|
||||||
class LayoutPoints {
|
class LayoutPoints {
|
||||||
final double start;
|
final double start;
|
||||||
final double end;
|
final double end;
|
||||||
final LayoutState type;
|
final ViewSize type;
|
||||||
LayoutPoints({
|
LayoutPoints({
|
||||||
required this.start,
|
required this.start,
|
||||||
required this.end,
|
required this.end,
|
||||||
|
|
@ -36,7 +26,7 @@ class LayoutPoints {
|
||||||
LayoutPoints copyWith({
|
LayoutPoints copyWith({
|
||||||
double? start,
|
double? start,
|
||||||
double? end,
|
double? end,
|
||||||
LayoutState? type,
|
ViewSize? type,
|
||||||
}) {
|
}) {
|
||||||
return LayoutPoints(
|
return LayoutPoints(
|
||||||
start: start ?? this.start,
|
start: start ?? this.start,
|
||||||
|
|
@ -60,23 +50,21 @@ class LayoutPoints {
|
||||||
}
|
}
|
||||||
|
|
||||||
class AdaptiveLayout extends InheritedWidget {
|
class AdaptiveLayout extends InheritedWidget {
|
||||||
final LayoutState layout;
|
final ViewSize viewSize;
|
||||||
final ScreenLayout size;
|
final LayoutMode layoutMode;
|
||||||
final InputDevice inputDevice;
|
final InputDevice inputDevice;
|
||||||
final TargetPlatform platform;
|
final TargetPlatform platform;
|
||||||
final bool isDesktop;
|
final bool isDesktop;
|
||||||
final AutoRouter router;
|
|
||||||
final PosterDefaults posterDefaults;
|
final PosterDefaults posterDefaults;
|
||||||
final ScrollController controller;
|
final ScrollController controller;
|
||||||
|
|
||||||
const AdaptiveLayout({
|
const AdaptiveLayout({
|
||||||
super.key,
|
super.key,
|
||||||
required this.layout,
|
required this.viewSize,
|
||||||
required this.size,
|
required this.layoutMode,
|
||||||
required this.inputDevice,
|
required this.inputDevice,
|
||||||
required this.platform,
|
required this.platform,
|
||||||
required this.isDesktop,
|
required this.isDesktop,
|
||||||
required this.router,
|
|
||||||
required this.posterDefaults,
|
required this.posterDefaults,
|
||||||
required this.controller,
|
required this.controller,
|
||||||
required super.child,
|
required super.child,
|
||||||
|
|
@ -86,9 +74,9 @@ class AdaptiveLayout extends InheritedWidget {
|
||||||
return context.dependOnInheritedWidgetOfExactType<AdaptiveLayout>();
|
return context.dependOnInheritedWidgetOfExactType<AdaptiveLayout>();
|
||||||
}
|
}
|
||||||
|
|
||||||
static LayoutState layoutOf(BuildContext context) {
|
static ViewSize layoutOf(BuildContext context) {
|
||||||
final AdaptiveLayout? result = maybeOf(context);
|
final AdaptiveLayout? result = maybeOf(context);
|
||||||
return result!.layout;
|
return result!.viewSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PosterDefaults poster(BuildContext context) {
|
static PosterDefaults poster(BuildContext context) {
|
||||||
|
|
@ -96,11 +84,6 @@ class AdaptiveLayout extends InheritedWidget {
|
||||||
return result!.posterDefaults;
|
return result!.posterDefaults;
|
||||||
}
|
}
|
||||||
|
|
||||||
static AutoRouter routerOf(BuildContext context) {
|
|
||||||
final AdaptiveLayout? result = maybeOf(context);
|
|
||||||
return result!.router;
|
|
||||||
}
|
|
||||||
|
|
||||||
static AdaptiveLayout of(BuildContext context) {
|
static AdaptiveLayout of(BuildContext context) {
|
||||||
final AdaptiveLayout? result = maybeOf(context);
|
final AdaptiveLayout? result = maybeOf(context);
|
||||||
return result!;
|
return result!;
|
||||||
|
|
@ -111,14 +94,18 @@ class AdaptiveLayout extends InheritedWidget {
|
||||||
return result!.controller;
|
return result!.controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static LayoutMode layoutModeOf(BuildContext context) => maybeOf(context)!.layoutMode;
|
||||||
|
static ViewSize viewSizeOf(BuildContext context) => maybeOf(context)!.viewSize;
|
||||||
|
|
||||||
|
static InputDevice inputDeviceOf(BuildContext context) => maybeOf(context)!.inputDevice;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool updateShouldNotify(AdaptiveLayout oldWidget) {
|
bool updateShouldNotify(AdaptiveLayout oldWidget) {
|
||||||
return layout != oldWidget.layout ||
|
return viewSize != oldWidget.viewSize ||
|
||||||
size != oldWidget.size ||
|
layoutMode != oldWidget.layoutMode ||
|
||||||
platform != oldWidget.platform ||
|
platform != oldWidget.platform ||
|
||||||
inputDevice != oldWidget.inputDevice ||
|
inputDevice != oldWidget.inputDevice ||
|
||||||
isDesktop != oldWidget.isDesktop ||
|
isDesktop != oldWidget.isDesktop;
|
||||||
router != oldWidget.router;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,7 +113,7 @@ const defaultTitleBarHeight = 35.0;
|
||||||
|
|
||||||
class AdaptiveLayoutBuilder extends ConsumerStatefulWidget {
|
class AdaptiveLayoutBuilder extends ConsumerStatefulWidget {
|
||||||
final List<LayoutPoints> layoutPoints;
|
final List<LayoutPoints> layoutPoints;
|
||||||
final LayoutState fallBack;
|
final ViewSize fallBack;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
const AdaptiveLayoutBuilder({required this.layoutPoints, required this.child, required this.fallBack, super.key});
|
const AdaptiveLayoutBuilder({required this.layoutPoints, required this.child, required this.fallBack, super.key});
|
||||||
|
|
||||||
|
|
@ -135,9 +122,8 @@ class AdaptiveLayoutBuilder extends ConsumerStatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AdaptiveLayoutBuilderState extends ConsumerState<AdaptiveLayoutBuilder> {
|
class _AdaptiveLayoutBuilderState extends ConsumerState<AdaptiveLayoutBuilder> {
|
||||||
late LayoutState layout = widget.fallBack;
|
late ViewSize viewSize = widget.fallBack;
|
||||||
late ScreenLayout size = ScreenLayout.single;
|
late LayoutMode layoutMode = LayoutMode.single;
|
||||||
AutoRouter? router;
|
|
||||||
late TargetPlatform currentPlatform = defaultTargetPlatform;
|
late TargetPlatform currentPlatform = defaultTargetPlatform;
|
||||||
late ScrollController controller = ScrollController();
|
late ScrollController controller = ScrollController();
|
||||||
|
|
||||||
|
|
@ -158,47 +144,45 @@ class _AdaptiveLayoutBuilderState extends ConsumerState<AdaptiveLayoutBuilder> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void calculateLayout() {
|
void calculateLayout() {
|
||||||
LayoutState? newType;
|
ViewSize? newType;
|
||||||
for (var element in widget.layoutPoints) {
|
for (var element in widget.layoutPoints) {
|
||||||
if (MediaQuery.of(context).size.width > element.start && MediaQuery.of(context).size.width < element.end) {
|
if (MediaQuery.of(context).size.width > element.start && MediaQuery.of(context).size.width < element.end) {
|
||||||
newType = element.type;
|
newType = element.type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (newType == LayoutState.phone && isDesktop) {
|
viewSize = newType ?? widget.fallBack;
|
||||||
newType = LayoutState.tablet;
|
|
||||||
}
|
|
||||||
layout = newType ?? widget.fallBack;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void calculateSize() {
|
void calculateSize() {
|
||||||
ScreenLayout newSize;
|
LayoutMode newSize;
|
||||||
if (MediaQuery.of(context).size.width > 0 && MediaQuery.of(context).size.width < 960 && !isDesktop) {
|
if (MediaQuery.of(context).size.width > 0 && MediaQuery.of(context).size.width < 960) {
|
||||||
newSize = ScreenLayout.single;
|
newSize = LayoutMode.single;
|
||||||
} else {
|
} else {
|
||||||
newSize = ScreenLayout.dual;
|
newSize = LayoutMode.dual;
|
||||||
}
|
}
|
||||||
size = newSize;
|
layoutMode = newSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final acceptedLayouts = ref.watch(homeSettingsProvider.select((value) => value.screenLayouts));
|
||||||
|
final acceptedViewSizes = ref.watch(homeSettingsProvider.select((value) => value.layoutStates));
|
||||||
return MediaQuery(
|
return MediaQuery(
|
||||||
data: MediaQuery.of(context).copyWith(
|
data: MediaQuery.of(context).copyWith(
|
||||||
padding: isDesktop || kIsWeb ? const EdgeInsets.only(top: defaultTitleBarHeight, bottom: 16) : null,
|
padding: isDesktop || kIsWeb ? const EdgeInsets.only(top: defaultTitleBarHeight, bottom: 16) : null,
|
||||||
viewPadding: isDesktop || kIsWeb ? const EdgeInsets.only(top: defaultTitleBarHeight, bottom: 16) : null,
|
viewPadding: isDesktop || kIsWeb ? const EdgeInsets.only(top: defaultTitleBarHeight, bottom: 16) : null,
|
||||||
),
|
),
|
||||||
child: AdaptiveLayout(
|
child: AdaptiveLayout(
|
||||||
layout: layout,
|
viewSize: selectAvailableOrSmaller<ViewSize>(viewSize, acceptedViewSizes, ViewSize.values),
|
||||||
controller: controller,
|
controller: controller,
|
||||||
size: size,
|
layoutMode: selectAvailableOrSmaller<LayoutMode>(layoutMode, acceptedLayouts, LayoutMode.values),
|
||||||
inputDevice: (isDesktop || kIsWeb) ? InputDevice.pointer : InputDevice.touch,
|
inputDevice: (isDesktop || kIsWeb) ? InputDevice.pointer : InputDevice.touch,
|
||||||
platform: currentPlatform,
|
platform: currentPlatform,
|
||||||
isDesktop: isDesktop,
|
isDesktop: isDesktop,
|
||||||
router: router ??= AutoRouter(layout: size, ref: ref),
|
posterDefaults: switch (viewSize) {
|
||||||
posterDefaults: switch (layout) {
|
ViewSize.phone => const PosterDefaults(size: 300, ratio: 0.55),
|
||||||
LayoutState.phone => const PosterDefaults(size: 300, ratio: 0.55),
|
ViewSize.tablet => const PosterDefaults(size: 350, ratio: 0.55),
|
||||||
LayoutState.tablet => const PosterDefaults(size: 350, ratio: 0.55),
|
ViewSize.desktop => const PosterDefaults(size: 400, ratio: 0.55),
|
||||||
LayoutState.desktop => const PosterDefaults(size: 400, ratio: 0.55),
|
|
||||||
},
|
},
|
||||||
child: DebugBanner(child: widget.child),
|
child: DebugBanner(child: widget.child),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,53 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
Future<void> openOptionDialogue<T>(
|
Future<List<T>> openMultiSelectOptions<T>(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required String label,
|
required String label,
|
||||||
|
bool allowMultiSelection = false,
|
||||||
|
bool forceAtleastOne = true,
|
||||||
|
required List<T> selected,
|
||||||
required List<T> items,
|
required List<T> items,
|
||||||
bool isNullable = false,
|
Function(List<T> values)? onChanged,
|
||||||
required Widget Function(T? type) itemBuilder,
|
required Widget Function(T type, bool selected, Function onTap) itemBuilder,
|
||||||
}) {
|
}) async {
|
||||||
return showDialog(
|
Set<T> currentSelection = selected.toSet();
|
||||||
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) => StatefulBuilder(
|
||||||
return AlertDialog(
|
builder: (context, setState) => AlertDialog(
|
||||||
title: Text(label),
|
title: Text(label),
|
||||||
content: SizedBox(
|
content: SizedBox(
|
||||||
width: MediaQuery.of(context).size.width * 0.65,
|
width: MediaQuery.of(context).size.width * 0.65,
|
||||||
child: ListView(
|
child: ListView(
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
children: [
|
children: items.map((item) {
|
||||||
if (isNullable) itemBuilder(null),
|
bool isSelected = currentSelection.contains(item);
|
||||||
...items.map(
|
return itemBuilder(
|
||||||
(e) => itemBuilder(e),
|
item,
|
||||||
)
|
isSelected,
|
||||||
],
|
() {
|
||||||
|
setState(() {
|
||||||
|
if (allowMultiSelection) {
|
||||||
|
if (isSelected) {
|
||||||
|
if (!forceAtleastOne || currentSelection.length > 1) {
|
||||||
|
currentSelection.remove(item);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentSelection.add(item);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentSelection = {item};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
onChanged?.call(currentSelection.toList());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
),
|
||||||
);
|
);
|
||||||
|
return currentSelection.toList();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@ class SimpleDurationPicker extends ConsumerWidget {
|
||||||
children: [
|
children: [
|
||||||
if (showNever) ...{
|
if (showNever) ...{
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => onChanged(null),
|
onPressed: () => onChanged(Duration.zero),
|
||||||
child: Text(context.localized.never),
|
child: Text(context.localized.never),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:ficonsax/ficonsax.dart';
|
import 'package:ficonsax/ficonsax.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.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/client_settings_provider.dart';
|
||||||
import 'package:fladder/providers/views_provider.dart';
|
import 'package:fladder/providers/views_provider.dart';
|
||||||
|
import 'package:fladder/routes/auto_router.dart';
|
||||||
import 'package:fladder/routes/auto_router.gr.dart';
|
import 'package:fladder/routes/auto_router.gr.dart';
|
||||||
import 'package:fladder/screens/shared/animated_fade_size.dart';
|
import 'package:fladder/screens/shared/animated_fade_size.dart';
|
||||||
import 'package:fladder/util/adaptive_layout.dart';
|
import 'package:fladder/util/adaptive_layout.dart';
|
||||||
|
|
@ -50,6 +53,8 @@ class _NavigationBodyState extends ConsumerState<NavigationBody> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final views = ref.watch(viewsProvider.select((value) => value.views));
|
final views = ref.watch(viewsProvider.select((value) => value.views));
|
||||||
|
final hasOverlay = AdaptiveLayout.layoutModeOf(context) == LayoutMode.dual ||
|
||||||
|
homeRoutes.any((element) => element.name.contains(context.router.current.name));
|
||||||
ref.listen(
|
ref.listen(
|
||||||
clientSettingsProvider,
|
clientSettingsProvider,
|
||||||
(previous, next) {
|
(previous, next) {
|
||||||
|
|
@ -62,48 +67,66 @@ class _NavigationBodyState extends ConsumerState<NavigationBody> {
|
||||||
);
|
);
|
||||||
|
|
||||||
return switch (AdaptiveLayout.layoutOf(context)) {
|
return switch (AdaptiveLayout.layoutOf(context)) {
|
||||||
LayoutState.phone => MediaQuery.removePadding(
|
ViewSize.phone => MediaQuery.removePadding(
|
||||||
context: widget.parentContext,
|
context: widget.parentContext,
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
),
|
),
|
||||||
LayoutState.tablet => Row(
|
ViewSize.tablet => Row(
|
||||||
children: [
|
children: [
|
||||||
navigationRail(context),
|
AnimatedFadeSize(
|
||||||
|
duration: const Duration(milliseconds: 250),
|
||||||
|
child: hasOverlay ? navigationRail(context) : const SizedBox(),
|
||||||
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: widget.child,
|
child: MediaQuery(
|
||||||
|
data: semiNestedPadding(context, hasOverlay),
|
||||||
|
child: widget.child,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
LayoutState.desktop => Row(
|
ViewSize.desktop => Row(
|
||||||
children: [
|
children: [
|
||||||
AnimatedFadeSize(
|
AnimatedFadeSize(
|
||||||
duration: const Duration(milliseconds: 125),
|
duration: const Duration(milliseconds: 125),
|
||||||
child: expandedSideBar
|
child: hasOverlay
|
||||||
? MediaQuery.removePadding(
|
? expandedSideBar
|
||||||
context: widget.parentContext,
|
? MediaQuery.removePadding(
|
||||||
child: NestedNavigationDrawer(
|
context: widget.parentContext,
|
||||||
isExpanded: expandedSideBar,
|
child: NestedNavigationDrawer(
|
||||||
actionButton: actionButton(),
|
isExpanded: expandedSideBar,
|
||||||
toggleExpanded: (value) {
|
actionButton: actionButton(),
|
||||||
setState(() {
|
toggleExpanded: (value) {
|
||||||
expandedSideBar = value;
|
setState(() {
|
||||||
});
|
expandedSideBar = value;
|
||||||
},
|
});
|
||||||
views: views,
|
},
|
||||||
destinations: widget.destinations,
|
views: views,
|
||||||
currentLocation: widget.currentLocation,
|
destinations: widget.destinations,
|
||||||
),
|
currentLocation: widget.currentLocation,
|
||||||
)
|
),
|
||||||
: navigationRail(context),
|
)
|
||||||
|
: navigationRail(context)
|
||||||
|
: const SizedBox(),
|
||||||
),
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: widget.child,
|
child: MediaQuery(
|
||||||
|
data: semiNestedPadding(context, hasOverlay),
|
||||||
|
child: widget.child,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MediaQueryData semiNestedPadding(BuildContext context, bool hasOverlay) {
|
||||||
|
final paddingOf = MediaQuery.paddingOf(context);
|
||||||
|
return MediaQuery.of(context).copyWith(
|
||||||
|
padding: paddingOf.copyWith(left: hasOverlay ? 0 : paddingOf.left),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
AdaptiveFab? actionButton() {
|
AdaptiveFab? actionButton() {
|
||||||
return (widget.currentIndex >= 0 && widget.currentIndex < widget.destinations.length)
|
return (widget.currentIndex >= 0 && widget.currentIndex < widget.destinations.length)
|
||||||
? widget.destinations[widget.currentIndex].floatingActionButton
|
? widget.destinations[widget.currentIndex].floatingActionButton
|
||||||
|
|
@ -120,7 +143,8 @@ class _NavigationBodyState extends ConsumerState<NavigationBody> {
|
||||||
style: Theme.of(context).textTheme.titleSmall,
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
if (AdaptiveLayout.of(context).platform == TargetPlatform.macOS) SizedBox(height: MediaQuery.of(context).padding.top),
|
if (AdaptiveLayout.of(context).platform == TargetPlatform.macOS)
|
||||||
|
SizedBox(height: MediaQuery.of(context).padding.top),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
key: const Key('navigation_rail'),
|
key: const Key('navigation_rail'),
|
||||||
|
|
@ -130,7 +154,7 @@ class _NavigationBodyState extends ConsumerState<NavigationBody> {
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (AdaptiveLayout.layoutOf(context) != LayoutState.desktop) {
|
if (AdaptiveLayout.layoutOf(context) != ViewSize.desktop) {
|
||||||
widget.drawerKey.currentState?.openDrawer();
|
widget.drawerKey.currentState?.openDrawer();
|
||||||
} else {
|
} else {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -140,7 +164,7 @@ class _NavigationBodyState extends ConsumerState<NavigationBody> {
|
||||||
},
|
},
|
||||||
icon: const Icon(IconsaxBold.menu),
|
icon: const Icon(IconsaxBold.menu),
|
||||||
),
|
),
|
||||||
if (AdaptiveLayout.of(context).size == ScreenLayout.dual) ...[
|
if (AdaptiveLayout.layoutModeOf(context) == LayoutMode.dual) ...[
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
AnimatedFadeSize(
|
AnimatedFadeSize(
|
||||||
child: AnimatedSwitcher(
|
child: AnimatedSwitcher(
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import 'package:ficonsax/ficonsax.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'package:fladder/models/collection_types.dart';
|
import 'package:fladder/models/collection_types.dart';
|
||||||
|
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||||
import 'package:fladder/models/view_model.dart';
|
import 'package:fladder/models/view_model.dart';
|
||||||
import 'package:fladder/routes/auto_router.gr.dart';
|
import 'package:fladder/routes/auto_router.gr.dart';
|
||||||
import 'package:fladder/screens/metadata/refresh_metadata.dart';
|
import 'package:fladder/screens/metadata/refresh_metadata.dart';
|
||||||
|
|
@ -116,11 +117,11 @@ class NestedNavigationDrawer extends ConsumerWidget {
|
||||||
selected: currentLocation.contains(const SettingsRoute().routeName),
|
selected: currentLocation.contains(const SettingsRoute().routeName),
|
||||||
icon: const SizedBox(width: 35, height: 35, child: SettingsUserIcon()),
|
icon: const SizedBox(width: 35, height: 35, child: SettingsUserIcon()),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
switch (AdaptiveLayout.of(context).size) {
|
switch (AdaptiveLayout.layoutModeOf(context)) {
|
||||||
case ScreenLayout.single:
|
case LayoutMode.single:
|
||||||
const SettingsRoute().push(context);
|
const SettingsRoute().push(context);
|
||||||
break;
|
break;
|
||||||
case ScreenLayout.dual:
|
case LayoutMode.dual:
|
||||||
context.router.push(const ClientSettingsRoute());
|
context.router.push(const ClientSettingsRoute());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -135,11 +136,11 @@ class NestedNavigationDrawer extends ConsumerWidget {
|
||||||
icon: const Icon(IconsaxOutline.setting_2),
|
icon: const Icon(IconsaxOutline.setting_2),
|
||||||
selected: currentLocation.contains(const SettingsRoute().routeName),
|
selected: currentLocation.contains(const SettingsRoute().routeName),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
switch (AdaptiveLayout.of(context).size) {
|
switch (AdaptiveLayout.layoutModeOf(context)) {
|
||||||
case ScreenLayout.single:
|
case LayoutMode.single:
|
||||||
const SettingsRoute().push(context);
|
const SettingsRoute().push(context);
|
||||||
break;
|
break;
|
||||||
case ScreenLayout.dual:
|
case LayoutMode.dual:
|
||||||
context.router.push(const ClientSettingsRoute());
|
context.router.push(const ClientSettingsRoute());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.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/providers/user_provider.dart';
|
||||||
import 'package:fladder/routes/auto_router.gr.dart';
|
import 'package:fladder/routes/auto_router.gr.dart';
|
||||||
import 'package:fladder/screens/shared/user_icon.dart';
|
import 'package:fladder/screens/shared/user_icon.dart';
|
||||||
|
import 'package:fladder/util/adaptive_layout.dart';
|
||||||
import 'package:fladder/util/localization_helper.dart';
|
import 'package:fladder/util/localization_helper.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
|
|
||||||
class SettingsUserIcon extends ConsumerWidget {
|
class SettingsUserIcon extends ConsumerWidget {
|
||||||
const SettingsUserIcon({super.key});
|
const SettingsUserIcon({super.key});
|
||||||
|
|
@ -19,7 +23,13 @@ class SettingsUserIcon extends ConsumerWidget {
|
||||||
user: users,
|
user: users,
|
||||||
cornerRadius: 200,
|
cornerRadius: 200,
|
||||||
onLongPress: () => context.router.push(const LockRoute()),
|
onLongPress: () => context.router.push(const LockRoute()),
|
||||||
onTap: () => context.router.navigate(const SettingsRoute()),
|
onTap: () {
|
||||||
|
if (AdaptiveLayout.layoutModeOf(context) == LayoutMode.single) {
|
||||||
|
context.router.push(const SettingsRoute());
|
||||||
|
} else {
|
||||||
|
context.router.push(const ClientSettingsRoute());
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,10 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'package:fladder/models/media_playback_model.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/providers/video_player_provider.dart';
|
||||||
import 'package:fladder/providers/views_provider.dart';
|
import 'package:fladder/providers/views_provider.dart';
|
||||||
|
import 'package:fladder/routes/auto_router.dart';
|
||||||
import 'package:fladder/screens/shared/nested_bottom_appbar.dart';
|
import 'package:fladder/screens/shared/nested_bottom_appbar.dart';
|
||||||
import 'package:fladder/util/adaptive_layout.dart';
|
import 'package:fladder/util/adaptive_layout.dart';
|
||||||
import 'package:fladder/widgets/navigation_scaffold/components/destination_model.dart';
|
import 'package:fladder/widgets/navigation_scaffold/components/destination_model.dart';
|
||||||
|
|
@ -65,7 +67,7 @@ class _NavigationScaffoldState extends ConsumerState<NavigationScaffold> {
|
||||||
extendBody: true,
|
extendBody: true,
|
||||||
floatingActionButtonLocation:
|
floatingActionButtonLocation:
|
||||||
playerState == VideoPlayerState.minimized ? FloatingActionButtonLocation.centerFloat : null,
|
playerState == VideoPlayerState.minimized ? FloatingActionButtonLocation.centerFloat : null,
|
||||||
floatingActionButton: AdaptiveLayout.of(context).size == ScreenLayout.single
|
floatingActionButton: AdaptiveLayout.layoutModeOf(context) == LayoutMode.single
|
||||||
? switch (playerState) {
|
? switch (playerState) {
|
||||||
VideoPlayerState.minimized => const Padding(
|
VideoPlayerState.minimized => const Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
|
@ -83,9 +85,10 @@ class _NavigationScaffoldState extends ConsumerState<NavigationScaffold> {
|
||||||
destinations: widget.destinations,
|
destinations: widget.destinations,
|
||||||
currentLocation: currentLocation,
|
currentLocation: currentLocation,
|
||||||
),
|
),
|
||||||
bottomNavigationBar: AdaptiveLayout.of(context).layout == LayoutState.phone
|
bottomNavigationBar: AdaptiveLayout.viewSizeOf(context) == ViewSize.phone
|
||||||
? HideOnScroll(
|
? HideOnScroll(
|
||||||
controller: AdaptiveLayout.scrollOf(context),
|
controller: AdaptiveLayout.scrollOf(context),
|
||||||
|
forceHide: !homeRoutes.any((element) => element.name.contains(currentLocation)),
|
||||||
child: NestedBottomAppBar(
|
child: NestedBottomAppBar(
|
||||||
child: Transform.translate(
|
child: Transform.translate(
|
||||||
offset: const Offset(0, 8),
|
offset: const Offset(0, 8),
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,25 @@
|
||||||
import 'package:fladder/util/adaptive_layout.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||||
|
import 'package:fladder/util/adaptive_layout.dart';
|
||||||
|
|
||||||
class HideOnScroll extends ConsumerStatefulWidget {
|
class HideOnScroll extends ConsumerStatefulWidget {
|
||||||
final Widget? child;
|
final Widget? child;
|
||||||
final ScrollController? controller;
|
final ScrollController? controller;
|
||||||
final double height;
|
final double height;
|
||||||
final Widget? Function(bool visible)? visibleBuilder;
|
final Widget? Function(bool visible)? visibleBuilder;
|
||||||
final Duration duration;
|
final Duration duration;
|
||||||
|
final bool forceHide;
|
||||||
const HideOnScroll({
|
const HideOnScroll({
|
||||||
this.child,
|
this.child,
|
||||||
this.controller,
|
this.controller,
|
||||||
this.height = kBottomNavigationBarHeight,
|
this.height = kBottomNavigationBarHeight,
|
||||||
this.visibleBuilder,
|
this.visibleBuilder,
|
||||||
this.duration = const Duration(milliseconds: 200),
|
this.duration = const Duration(milliseconds: 200),
|
||||||
|
this.forceHide = false,
|
||||||
super.key,
|
super.key,
|
||||||
}) : assert(child != null || visibleBuilder != null);
|
}) : assert(child != null || visibleBuilder != null);
|
||||||
|
|
||||||
|
|
@ -63,12 +68,16 @@ class _HideOnScrollState extends ConsumerState<HideOnScroll> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (widget.visibleBuilder != null) return widget.visibleBuilder!(isVisible)!;
|
if (widget.visibleBuilder != null) return widget.visibleBuilder!(isVisible)!;
|
||||||
if (widget.child == null) return const SizedBox();
|
if (widget.child == null) return const SizedBox();
|
||||||
if (AdaptiveLayout.of(context).layout == LayoutState.desktop) {
|
if (AdaptiveLayout.viewSizeOf(context) == ViewSize.desktop) {
|
||||||
return widget.child!;
|
return widget.child!;
|
||||||
} else {
|
} else {
|
||||||
return AnimatedAlign(
|
return AnimatedAlign(
|
||||||
alignment: const Alignment(0, -1),
|
alignment: const Alignment(0, -1),
|
||||||
heightFactor: isVisible ? 1.0 : 0,
|
heightFactor: widget.forceHide
|
||||||
|
? 0
|
||||||
|
: isVisible
|
||||||
|
? 1.0
|
||||||
|
: 0,
|
||||||
duration: widget.duration,
|
duration: widget.duration,
|
||||||
child: Wrap(children: [widget.child!]),
|
child: Wrap(children: [widget.child!]),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'package:fladder/models/item_base_model.dart';
|
import 'package:fladder/models/item_base_model.dart';
|
||||||
|
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||||
import 'package:fladder/util/adaptive_layout.dart';
|
import 'package:fladder/util/adaptive_layout.dart';
|
||||||
import 'package:fladder/util/fladder_image.dart';
|
import 'package:fladder/util/fladder_image.dart';
|
||||||
|
|
||||||
|
|
@ -23,7 +24,7 @@ Future<void> showBottomSheetPill({
|
||||||
showDragHandle: true,
|
showDragHandle: true,
|
||||||
enableDrag: true,
|
enableDrag: true,
|
||||||
context: context,
|
context: context,
|
||||||
constraints: AdaptiveLayout.of(context).layout == LayoutState.phone
|
constraints: AdaptiveLayout.viewSizeOf(context) == ViewSize.phone
|
||||||
? BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.9)
|
? BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.9)
|
||||||
: BoxConstraints(
|
: BoxConstraints(
|
||||||
maxWidth: MediaQuery.of(context).size.width * 0.75, maxHeight: MediaQuery.of(context).size.height * 0.85),
|
maxWidth: MediaQuery.of(context).size.width * 0.75, maxHeight: MediaQuery.of(context).size.height * 0.85),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue