Init repo

This commit is contained in:
PartyDonut 2024-09-15 14:12:28 +02:00
commit 764b6034e3
566 changed files with 212335 additions and 0 deletions

View file

@ -0,0 +1,50 @@
import 'package:flutter/material.dart';
class AdaptiveFab {
final BuildContext context;
final String title;
final Widget child;
final Function() onPressed;
final Key? key;
AdaptiveFab({
required this.context,
this.title = '',
required this.child,
required this.onPressed,
this.key,
});
FloatingActionButton get normal {
return FloatingActionButton(
key: key,
onPressed: onPressed,
tooltip: title,
child: child,
);
}
Widget get extended {
return AnimatedContainer(
key: key,
duration: const Duration(milliseconds: 250),
height: 60,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: ElevatedButton(
onPressed: onPressed,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
child,
const Spacer(),
Flexible(child: Text(title)),
const Spacer(),
],
),
),
),
),
);
}
}

View file

@ -0,0 +1,92 @@
import 'package:fladder/routes/build_routes/route_builder.dart';
import 'package:fladder/widgets/navigation_scaffold/components/adaptive_fab.dart';
import 'package:fladder/widgets/navigation_scaffold/components/navigation_button.dart';
import 'package:flutter/material.dart';
class DestinationModel {
final String label;
final Widget? icon;
final Widget? selectedIcon;
final CustomRoute? route;
final Function()? action;
final String? tooltip;
final Badge? badge;
final AdaptiveFab? floatingActionButton;
// final FloatingActionButton? floatingActionButton;
DestinationModel({
required this.label,
this.icon,
this.selectedIcon,
this.route,
this.action,
this.tooltip,
this.badge,
this.floatingActionButton,
}) : assert(
badge == null || icon == null,
'Only one of icon or badge should be provided, not both.',
);
/// Converts this [DestinationModel] to a [NavigationRailDestination] used in a [NavigationRail].
NavigationRailDestination toNavigationRailDestination({EdgeInsets? padding}) {
if (badge != null) {
return NavigationRailDestination(
icon: badge!,
label: Text(label),
selectedIcon: badge!,
padding: padding,
);
}
return NavigationRailDestination(
icon: icon!,
label: Text(label),
selectedIcon: selectedIcon,
padding: padding,
);
}
/// Converts this [DestinationModel] to a [NavigationDrawerDestination] used in a [NavigationDrawer].
NavigationDrawerDestination toNavigationDrawerDestination() {
if (badge != null) {
return NavigationDrawerDestination(
icon: badge!,
label: Text(label),
selectedIcon: badge!,
);
}
return NavigationDrawerDestination(
icon: icon!,
label: Text(label),
selectedIcon: selectedIcon,
);
}
/// Converts this [DestinationModel] to a [NavigationDestination] used in a [BottomNavigationBar].
NavigationDestination toNavigationDestination() {
if (badge != null) {
return NavigationDestination(
icon: badge!,
label: label,
selectedIcon: badge!,
);
}
return NavigationDestination(
icon: icon!,
label: label,
selectedIcon: selectedIcon,
tooltip: tooltip,
);
}
NavigationButton toNavigationButton(bool selected, bool expanded) {
return NavigationButton(
label: label,
selected: selected,
onPressed: action,
horizontal: expanded,
selectedIcon: selectedIcon!,
icon: icon!,
);
}
}

View file

@ -0,0 +1,77 @@
import 'package:fladder/screens/shared/animated_fade_size.dart';
import 'package:fladder/util/adaptive_layout.dart';
import 'package:fladder/widgets/shared/item_actions.dart';
import 'package:fladder/widgets/shared/modal_bottom_sheet.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class DrawerListButton extends ConsumerStatefulWidget {
final String label;
final Widget selectedIcon;
final Widget icon;
final Function()? onPressed;
final List<ItemAction> actions;
final bool selected;
final Duration duration;
const DrawerListButton({
required this.label,
required this.selectedIcon,
required this.icon,
this.onPressed,
this.actions = const [],
this.selected = false,
this.duration = const Duration(milliseconds: 125),
super.key,
});
@override
ConsumerState<ConsumerStatefulWidget> createState() => _DrawerListButtonState();
}
class _DrawerListButtonState extends ConsumerState<DrawerListButton> {
bool showPopupButton = false;
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (event) => setState(() => showPopupButton = true),
onExit: (event) => setState(() => showPopupButton = false),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: ListTile(
onTap: widget.onPressed,
horizontalTitleGap: 15,
selected: widget.selected,
selectedTileColor: Theme.of(context).colorScheme.primary,
selectedColor: Theme.of(context).colorScheme.onPrimary,
onLongPress: widget.actions.isNotEmpty && AdaptiveLayout.of(context).inputDevice == InputDevice.touch
? () => showBottomSheetPill(
context: context,
content: (context, scrollController) => ListView(
shrinkWrap: true,
controller: scrollController,
children: widget.actions.listTileItems(context, useIcons: true),
),
)
: null,
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 5),
leading: Padding(
padding: const EdgeInsets.all(3),
child:
AnimatedFadeSize(duration: widget.duration, child: widget.selected ? widget.selectedIcon : widget.icon),
),
trailing: widget.actions.isNotEmpty && AdaptiveLayout.of(context).inputDevice == InputDevice.pointer
? AnimatedOpacity(
duration: const Duration(milliseconds: 125),
opacity: showPopupButton ? 1 : 0,
child: PopupMenuButton(
tooltip: "Options",
itemBuilder: (context) => widget.actions.popupMenuItems(useIcons: true),
),
)
: null,
title: Text(widget.label),
),
),
);
}
}

View file

@ -0,0 +1,59 @@
import 'package:fladder/screens/shared/default_titlebar.dart';
import 'package:fladder/util/adaptive_layout.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';
bool get _isDesktop {
if (kIsWeb) return false;
return [
TargetPlatform.windows,
TargetPlatform.linux,
TargetPlatform.macOS,
].contains(defaultTargetPlatform);
}
class FladderAppbar extends StatelessWidget implements PreferredSize {
final double height;
final String? label;
final bool automaticallyImplyLeading;
const FladderAppbar({this.height = 35, this.automaticallyImplyLeading = false, this.label, super.key});
@override
Widget build(BuildContext context) {
if (AdaptiveLayout.of(context).isDesktop) {
return PreferredSize(
preferredSize: Size(double.infinity, height),
child: SizedBox(
height: height,
child: Row(
children: [
if (automaticallyImplyLeading && context.canPop()) BackButton(),
Expanded(
child: DefaultTitleBar(
label: label,
),
)
],
),
));
} else {
return AppBar(
toolbarHeight: 0,
backgroundColor: Theme.of(context).colorScheme.surface.withOpacity(0),
scrolledUnderElevation: 0,
elevation: 0,
systemOverlayStyle: SystemUiOverlayStyle(),
title: const Text(""),
automaticallyImplyLeading: automaticallyImplyLeading,
);
}
}
@override
Widget get child => Container();
@override
Size get preferredSize => Size(double.infinity, _isDesktop ? height : 0);
}

View file

@ -0,0 +1,207 @@
import 'package:ficonsax/ficonsax.dart';
import 'package:fladder/models/media_playback_model.dart';
import 'package:fladder/providers/settings/video_player_settings_provider.dart';
import 'package:fladder/providers/video_player_provider.dart';
import 'package:fladder/screens/shared/fladder_snackbar.dart';
import 'package:fladder/screens/shared/flat_button.dart';
import 'package:fladder/screens/video_player/video_player.dart';
import 'package:fladder/util/adaptive_layout.dart';
import 'package:fladder/util/duration_extensions.dart';
import 'package:fladder/util/list_padding.dart';
import 'package:fladder/util/refresh_state.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:window_manager/window_manager.dart';
class FloatingPlayerBar extends ConsumerStatefulWidget {
const FloatingPlayerBar({super.key});
@override
ConsumerState<ConsumerStatefulWidget> createState() => _CurrentlyPlayingBarState();
}
class _CurrentlyPlayingBarState extends ConsumerState<FloatingPlayerBar> {
bool showExpandButton = false;
Future<void> openFullScreenPlayer() async {
setState(() => showExpandButton = false);
ref.read(mediaPlaybackProvider.notifier).update((state) => state.copyWith(state: VideoPlayerState.fullScreen));
await Navigator.of(context, rootNavigator: true).push(
MaterialPageRoute(
builder: (context) => const VideoPlayer(),
),
);
if (AdaptiveLayout.of(context).isDesktop || kIsWeb) {
final fullScreen = await windowManager.isFullScreen();
if (fullScreen) {
await windowManager.setFullScreen(false);
}
}
if (context.mounted) {
context.refreshData();
}
}
Future<void> stopPlayer() async {
ref.read(mediaPlaybackProvider.notifier).update((state) => state.copyWith(state: VideoPlayerState.disposed));
return ref.read(videoPlayerProvider).stop();
}
@override
Widget build(BuildContext context) {
final playbackInfo = ref.watch(mediaPlaybackProvider);
final player = ref.watch(videoPlayerProvider);
final playbackModel = ref.watch(playBackModel.select((value) => value?.item));
final progress = playbackInfo.position.inMilliseconds / playbackInfo.duration.inMilliseconds;
return Dismissible(
key: Key("CurrentlyPlayingBar"),
confirmDismiss: (direction) async {
if (direction == DismissDirection.up) {
await openFullScreenPlayer();
} else {
await stopPlayer();
}
return false;
},
direction: DismissDirection.vertical,
child: InkWell(
onLongPress: () {
fladderSnackbar(context, title: "Swipe up/down to open/close the player");
},
child: Card(
elevation: 3,
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: 50, maxHeight: 85),
child: LayoutBuilder(builder: (context, constraints) {
return Row(
children: [
Flexible(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8),
child: Row(
children: [
if (playbackInfo.state == VideoPlayerState.minimized)
Card(
child: SizedBox(
child: AspectRatio(
aspectRatio: 1.67,
child: MouseRegion(
onEnter: (event) => setState(() => showExpandButton = true),
onExit: (event) => setState(() => showExpandButton = false),
child: Stack(
children: [
Hero(
tag: "HeroPlayer",
child: Video(
controller: player.controller!,
fit: BoxFit.fitHeight,
controls: NoVideoControls,
wakelock: playbackInfo.playing,
),
),
Positioned.fill(
child: Tooltip(
message: "Expand player",
waitDuration: Duration(milliseconds: 500),
child: AnimatedOpacity(
opacity: showExpandButton ? 1 : 0,
duration: const Duration(milliseconds: 125),
child: Container(
color: Colors.black.withOpacity(0.6),
child: FlatButton(
onTap: () async => openFullScreenPlayer(),
child: Icon(Icons.keyboard_arrow_up_rounded),
),
),
),
),
)
],
),
),
),
),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: Text(
playbackModel?.title ?? "",
style: Theme.of(context).textTheme.titleLarge,
),
),
if (playbackModel?.detailedName(context)?.isNotEmpty == true)
Flexible(
child: Text(
playbackModel?.detailedName(context) ?? "",
style: Theme.of(context).textTheme.titleMedium,
),
),
],
),
),
if (!progress.isNaN && constraints.maxWidth > 500)
Text(
"${playbackInfo.position.readAbleDuration} / ${playbackInfo.duration.readAbleDuration}"),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: IconButton.filledTonal(
onPressed: () => ref.read(videoPlayerProvider).playOrPause(),
icon: playbackInfo.playing
? Icon(Icons.pause_rounded)
: Icon(Icons.play_arrow_rounded),
),
),
if (constraints.maxWidth > 500) ...{
IconButton(
onPressed: () {
final volume = player.player?.state.volume == 0 ? 100.0 : 0.0;
ref.read(videoPlayerSettingsProvider.notifier).setVolume(volume);
player.setVolume(volume);
},
icon: Icon(
ref.watch(videoPlayerSettingsProvider.select((value) => value.volume)) <= 0
? IconsaxBold.volume_cross
: IconsaxBold.volume_high,
),
),
Tooltip(
message: "Stop playback",
waitDuration: Duration(milliseconds: 500),
child: IconButton(
onPressed: () async => stopPlayer(),
icon: Icon(IconsaxBold.stop),
),
),
},
].addInBetween(SizedBox(width: 8)),
),
),
),
LinearProgressIndicator(
minHeight: 6,
backgroundColor: Colors.black.withOpacity(0.25),
color: Theme.of(context).colorScheme.primary,
value: progress.clamp(0, 1),
)
],
),
),
],
);
}),
),
),
),
);
}
}

View file

@ -0,0 +1,170 @@
import 'package:ficonsax/ficonsax.dart';
import 'package:fladder/providers/views_provider.dart';
import 'package:fladder/widgets/navigation_scaffold/components/navigation_drawer.dart';
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/routes/build_routes/settings_routes.dart';
import 'package:fladder/screens/shared/animated_fade_size.dart';
import 'package:fladder/util/adaptive_layout.dart';
import 'package:fladder/widgets/navigation_scaffold/components/adaptive_fab.dart';
import 'package:fladder/widgets/navigation_scaffold/components/destination_model.dart';
import 'package:fladder/widgets/navigation_scaffold/components/settings_user_icon.dart';
class NavigationBody extends ConsumerStatefulWidget {
final BuildContext parentContext;
final Widget child;
final int currentIndex;
final List<DestinationModel> destinations;
final String currentLocation;
final GlobalKey<ScaffoldState> drawerKey;
const NavigationBody({
required this.parentContext,
required this.child,
required this.currentIndex,
required this.destinations,
required this.currentLocation,
required this.drawerKey,
super.key,
});
@override
ConsumerState<ConsumerStatefulWidget> createState() => _NavigationBodyState();
}
class _NavigationBodyState extends ConsumerState<NavigationBody> {
bool expandedSideBar = true;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((value) {
ref.read(viewsProvider.notifier).fetchViews();
});
}
@override
Widget build(BuildContext context) {
final views = ref.watch(viewsProvider.select((value) => value.views));
return switch (AdaptiveLayout.layoutOf(context)) {
LayoutState.phone => MediaQuery.removePadding(
context: widget.parentContext,
child: widget.child,
),
LayoutState.tablet => Row(
children: [
navigationRail(context),
Flexible(
child: widget.child,
)
],
),
LayoutState.desktop => Row(
children: [
AnimatedFadeSize(
duration: const Duration(milliseconds: 125),
child: expandedSideBar
? MediaQuery.removePadding(
context: widget.parentContext,
child: NestedNavigationDrawer(
isExpanded: expandedSideBar,
actionButton: actionButton(),
toggleExpanded: (value) {
setState(() {
expandedSideBar = value;
});
},
views: views,
destinations: widget.destinations,
currentLocation: widget.currentLocation,
),
)
: navigationRail(context),
),
Flexible(
child: widget.child,
),
],
)
};
}
AdaptiveFab? actionButton() {
return (widget.currentIndex >= 0 && widget.currentIndex < widget.destinations.length)
? widget.destinations[widget.currentIndex].floatingActionButton
: null;
}
Widget navigationRail(BuildContext context) {
return Padding(
key: const Key('navigation_rail'),
padding: AdaptiveLayout.of(context).isDesktop ? EdgeInsets.zero : MediaQuery.of(context).padding,
child: Column(
children: [
if (AdaptiveLayout.of(context).isDesktop && AdaptiveLayout.of(context).platform != TargetPlatform.macOS) ...{
const SizedBox(height: 4),
Text(
"Fladder",
style: Theme.of(context).textTheme.titleSmall,
),
},
const SizedBox(height: 8),
IconButton(
onPressed: () {
if (AdaptiveLayout.layoutOf(context) != LayoutState.desktop) {
widget.drawerKey.currentState?.openDrawer();
} else {
setState(() {
expandedSideBar = true;
});
}
},
icon: const Icon(IconsaxBold.menu),
),
if (AdaptiveLayout.of(context).isDesktop) ...[
const SizedBox(height: 8),
AnimatedFadeSize(
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 250),
transitionBuilder: (Widget child, Animation<double> animation) {
return ScaleTransition(scale: animation, child: child);
},
child: actionButton()?.normal,
),
),
],
const Spacer(),
IconTheme(
data: IconThemeData(size: 28),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
...widget.destinations.mapIndexed(
(index, destination) => destination.toNavigationButton(widget.currentIndex == index, false),
)
],
),
),
const Spacer(),
SizedBox(
height: 48,
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 250),
child: widget.currentLocation.contains(SettingsRoute().route)
? Card(
color: Theme.of(context).colorScheme.primaryContainer,
child: Padding(
padding: const EdgeInsets.all(10),
child: Icon(IconsaxBold.setting_3),
),
)
: const SettingsUserIcon()),
),
if (AdaptiveLayout.of(context).inputDevice == InputDevice.pointer) const SizedBox(height: 16),
],
),
);
}
}

View file

@ -0,0 +1,125 @@
import 'package:fladder/util/widget_extensions.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class NavigationButton extends ConsumerStatefulWidget {
final String? label;
final Widget selectedIcon;
final Widget icon;
final bool horizontal;
final Function()? onPressed;
final bool selected;
final Duration duration;
const NavigationButton({
required this.label,
required this.selectedIcon,
required this.icon,
this.horizontal = false,
this.onPressed,
this.selected = false,
this.duration = const Duration(milliseconds: 125),
super.key,
});
@override
ConsumerState<ConsumerStatefulWidget> createState() => _NavigationButtonState();
}
class _NavigationButtonState extends ConsumerState<NavigationButton> {
@override
Widget build(BuildContext context) {
return Tooltip(
waitDuration: const Duration(seconds: 1),
preferBelow: false,
triggerMode: TooltipTriggerMode.longPress,
message: widget.label ?? "",
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: widget.horizontal
? Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: getChildren(context),
)
: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: getChildren(context),
),
),
);
}
List<Widget> getChildren(BuildContext context) {
return [
Flexible(
child: ElevatedButton(
style: ButtonStyle(
elevation: WidgetStatePropertyAll(0),
padding: WidgetStatePropertyAll(EdgeInsets.zero),
backgroundColor: WidgetStatePropertyAll(Colors.transparent),
foregroundColor: WidgetStateProperty.resolveWith((states) {
return widget.selected
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurface.withOpacity(0.45);
})),
onPressed: widget.onPressed,
child: AnimatedContainer(
curve: Curves.fastOutSlowIn,
duration: widget.duration,
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
AnimatedSwitcher(
duration: widget.duration,
child: widget.selected
? widget.selectedIcon.setKey(Key("${widget.label}+selected"))
: widget.icon.setKey(Key("${widget.label}+normal")),
),
if (widget.horizontal && widget.label != null)
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: _Label(widget: widget),
)
],
),
AnimatedContainer(
duration: Duration(milliseconds: 250),
margin: EdgeInsets.only(top: widget.selected ? 8 : 0),
height: widget.selected ? 6 : 0,
width: widget.selected ? 14 : 0,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Theme.of(context).colorScheme.primary.withOpacity(widget.selected ? 1 : 0),
),
),
],
),
),
),
),
),
];
}
}
class _Label extends StatelessWidget {
const _Label({required this.widget});
final NavigationButton widget;
@override
Widget build(BuildContext context) {
return Text(
widget.label!,
maxLines: 1,
overflow: TextOverflow.fade,
style:
Theme.of(context).textTheme.labelMedium?.copyWith(color: Theme.of(context).colorScheme.onSecondaryContainer),
);
}
}

View file

@ -0,0 +1,153 @@
import 'package:ficonsax/ficonsax.dart';
import 'package:fladder/models/collection_types.dart';
import 'package:fladder/models/view_model.dart';
import 'package:fladder/routes/build_routes/home_routes.dart';
import 'package:fladder/routes/build_routes/route_builder.dart';
import 'package:fladder/routes/build_routes/settings_routes.dart';
import 'package:fladder/screens/metadata/refresh_metadata.dart';
import 'package:fladder/screens/shared/animated_fade_size.dart';
import 'package:fladder/util/adaptive_layout.dart';
import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/widgets/navigation_scaffold/components/adaptive_fab.dart';
import 'package:fladder/widgets/navigation_scaffold/components/destination_model.dart';
import 'package:fladder/widgets/navigation_scaffold/components/drawer_list_button.dart';
import 'package:fladder/widgets/navigation_scaffold/components/settings_user_icon.dart';
import 'package:fladder/widgets/shared/item_actions.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class NestedNavigationDrawer extends ConsumerWidget {
final bool isExpanded;
final Function(bool expanded) toggleExpanded;
final List<DestinationModel> destinations;
final AdaptiveFab? actionButton;
final List<ViewModel> views;
final String currentLocation;
const NestedNavigationDrawer(
{this.isExpanded = false,
required this.toggleExpanded,
required this.actionButton,
required this.destinations,
required this.views,
required this.currentLocation,
super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return NavigationDrawer(
key: const Key('navigation_drawer'),
backgroundColor: isExpanded ? Colors.transparent : null,
surfaceTintColor: isExpanded ? Colors.transparent : null,
children: [
if (AdaptiveLayout.of(context).isDesktop || kIsWeb) const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.fromLTRB(28, 0, 16, 0),
child: Row(
children: [
Expanded(
child: Text(
context.localized.navigation,
style: Theme.of(context).textTheme.titleMedium,
),
),
IconButton(
onPressed: () => toggleExpanded(false),
icon: const Icon(IconsaxOutline.menu),
),
],
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: actionButton != null ? 8 : 0),
child: AnimatedFadeSize(
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 250),
transitionBuilder: (Widget child, Animation<double> animation) {
return ScaleTransition(scale: animation, child: child);
},
child: actionButton?.extended,
),
),
),
...destinations.map((destination) => DrawerListButton(
label: destination.label,
selected: destination.route?.route == currentLocation,
selectedIcon: destination.selectedIcon!,
icon: destination.icon!,
onPressed: () {
destination.action!();
Scaffold.of(context).closeDrawer();
},
)),
if (views.isNotEmpty) ...{
const Divider(indent: 28, endIndent: 28),
Padding(
padding: const EdgeInsets.fromLTRB(28, 16, 16, 10),
child: Text(
context.localized.library(2),
style: Theme.of(context).textTheme.titleMedium,
),
),
...views.map((library) => DrawerListButton(
label: library.name,
selected: currentLocation.contains(library.id),
actions: [
ItemActionButton(
label: Text(context.localized.scanLibrary),
icon: Icon(IconsaxOutline.refresh),
action: () => showRefreshPopup(context, library.id, library.name),
),
],
onPressed: () {
context.routePushOrGo(LibrarySearchRoute(id: library.id));
Scaffold.of(context).closeDrawer();
},
selectedIcon: Icon(library.collectionType.icon),
icon: Icon(library.collectionType.iconOutlined))),
},
const Divider(indent: 28, endIndent: 28),
if (isExpanded)
Transform.translate(
offset: Offset(-8, 0),
child: DrawerListButton(
label: context.localized.settings,
selectedIcon: const Icon(IconsaxBold.setting_3),
selected: currentLocation.contains(SettingsRoute().basePath),
icon: const SizedBox(width: 35, height: 35, child: SettingsUserIcon()),
onPressed: () {
switch (AdaptiveLayout.of(context).size) {
case ScreenLayout.single:
context.routePush(SettingsRoute());
break;
case ScreenLayout.dual:
context.routeGo(ClientSettingsRoute());
break;
}
Scaffold.of(context).closeDrawer();
},
),
)
else
DrawerListButton(
label: context.localized.settings,
selectedIcon: Icon(IconsaxBold.setting_2),
icon: Icon(IconsaxOutline.setting_2),
selected: currentLocation.contains(SettingsRoute().basePath),
onPressed: () {
switch (AdaptiveLayout.of(context).size) {
case ScreenLayout.single:
context.routePush(SettingsRoute());
break;
case ScreenLayout.dual:
context.routeGo(ClientSettingsRoute());
break;
}
Scaffold.of(context).closeDrawer();
},
),
if (AdaptiveLayout.of(context).isDesktop || kIsWeb) const SizedBox(height: 8),
],
);
}
}

View file

@ -0,0 +1,30 @@
import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/routes/build_routes/route_builder.dart';
import 'package:fladder/routes/build_routes/settings_routes.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:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class SettingsUserIcon extends ConsumerWidget {
const SettingsUserIcon({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final users = ref.watch(userProvider);
return Tooltip(
message: context.localized.settings,
waitDuration: const Duration(seconds: 1),
child: UserIcon(
user: users,
cornerRadius: 200,
onLongPress: () => context.routePush(LockScreenRoute()),
onTap: () => switch (AdaptiveLayout.of(context).size) {
ScreenLayout.single => context.routePush(SettingsRoute()),
ScreenLayout.dual => context.routePush(ClientSettingsRoute()),
},
),
);
}
}