mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-07 21:48:14 -08:00
feat: UI 2.0 and other Improvements (#357)
Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
parent
9ca06eaa37
commit
e7b5bb40ff
169 changed files with 4584 additions and 3626 deletions
|
|
@ -1,7 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fladder/util/list_padding.dart';
|
||||
|
||||
class ActionContent extends StatelessWidget {
|
||||
final Widget? title;
|
||||
final Widget child;
|
||||
|
|
@ -23,6 +21,7 @@ class ActionContent extends StatelessWidget {
|
|||
padding: padding ?? MediaQuery.paddingOf(context).add(const EdgeInsets.symmetric(horizontal: 16)),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 16,
|
||||
children: [
|
||||
if (title != null) ...[
|
||||
title!,
|
||||
|
|
@ -42,7 +41,7 @@ class ActionContent extends StatelessWidget {
|
|||
children: actions,
|
||||
)
|
||||
],
|
||||
].addInBetween(const SizedBox(height: 16)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
0
lib/widgets/shared/background_item_image.dart
Normal file
0
lib/widgets/shared/background_item_image.dart
Normal file
77
lib/widgets/shared/button_group.dart
Normal file
77
lib/widgets/shared/button_group.dart
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class ExpressiveButtonGroup<T> extends StatelessWidget {
|
||||
final List<ButtonGroupOption<T>> options;
|
||||
final Set<T> selectedValues;
|
||||
final ValueChanged<Set<T>> onSelected;
|
||||
final bool multiSelection;
|
||||
|
||||
const ExpressiveButtonGroup({
|
||||
super.key,
|
||||
required this.options,
|
||||
required this.selectedValues,
|
||||
required this.onSelected,
|
||||
this.multiSelection = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
spacing: 2,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: List.generate(options.length, (index) {
|
||||
final option = options[index];
|
||||
final isSelected = selectedValues.contains(option.value);
|
||||
final isFirst = index == 0;
|
||||
final isLast = index == options.length - 1;
|
||||
|
||||
final borderRadius = BorderRadius.horizontal(
|
||||
left: isSelected || isFirst ? const Radius.circular(20) : const Radius.circular(6),
|
||||
right: isSelected || isLast ? const Radius.circular(20) : const Radius.circular(6),
|
||||
);
|
||||
|
||||
return ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(borderRadius: borderRadius),
|
||||
elevation: isSelected ? 3 : 0,
|
||||
backgroundColor: isSelected
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
foregroundColor:
|
||||
isSelected ? Theme.of(context).colorScheme.onPrimary : Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
textStyle: Theme.of(context).textTheme.labelLarge,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||
),
|
||||
onPressed: () {
|
||||
final newSet = Set<T>.from(selectedValues);
|
||||
if (multiSelection) {
|
||||
isSelected ? newSet.remove(option.value) : newSet.add(option.value);
|
||||
} else {
|
||||
newSet
|
||||
..clear()
|
||||
..add(option.value);
|
||||
}
|
||||
onSelected(newSet);
|
||||
},
|
||||
label: option.child,
|
||||
icon: isSelected ? option.selected ?? const Icon(Icons.check_rounded) : option.icon,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ButtonGroupOption<T> {
|
||||
final T value;
|
||||
final Icon? icon;
|
||||
final Icon? selected;
|
||||
final Widget child;
|
||||
|
||||
const ButtonGroupOption({
|
||||
required this.value,
|
||||
this.icon,
|
||||
this.selected,
|
||||
required this.child,
|
||||
});
|
||||
}
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||
import 'package:fladder/screens/shared/flat_button.dart';
|
||||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
||||
import 'package:fladder/widgets/shared/modal_bottom_sheet.dart';
|
||||
|
||||
class EnumBox<T> extends StatelessWidget {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flexible_scrollbar/flexible_scrollbar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flexible_scrollbar/flexible_scrollbar.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class FladderScrollbar extends ConsumerWidget {
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@ import 'package:flutter/rendering.dart';
|
|||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
||||
|
||||
class HideOnScroll extends ConsumerStatefulWidget {
|
||||
final Widget? child;
|
||||
|
|
@ -28,59 +27,64 @@ class HideOnScroll extends ConsumerStatefulWidget {
|
|||
}
|
||||
|
||||
class _HideOnScrollState extends ConsumerState<HideOnScroll> {
|
||||
late final scrollController = widget.controller ?? ScrollController();
|
||||
late final ScrollController scrollController = widget.controller ?? ScrollController();
|
||||
bool isVisible = true;
|
||||
bool atEdge = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
scrollController.addListener(listen);
|
||||
scrollController.addListener(_onScroll);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
scrollController.removeListener(listen);
|
||||
scrollController.removeListener(_onScroll);
|
||||
if (widget.controller == null) {
|
||||
scrollController.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void listen() {
|
||||
final direction = scrollController.position.userScrollDirection;
|
||||
void _onScroll() {
|
||||
final position = scrollController.position;
|
||||
final direction = position.userScrollDirection;
|
||||
|
||||
if (scrollController.offset < scrollController.position.maxScrollExtent) {
|
||||
if (direction == ScrollDirection.forward) {
|
||||
if (!isVisible) {
|
||||
setState(() => isVisible = true);
|
||||
}
|
||||
} else if (direction == ScrollDirection.reverse) {
|
||||
if (isVisible) {
|
||||
setState(() => isVisible = false);
|
||||
}
|
||||
}
|
||||
bool newVisible;
|
||||
if (position.atEdge && position.pixels >= position.maxScrollExtent) {
|
||||
// Always show when scrolled to bottom
|
||||
newVisible = true;
|
||||
} else {
|
||||
setState(() {
|
||||
isVisible = true;
|
||||
});
|
||||
newVisible = direction == ScrollDirection.forward;
|
||||
}
|
||||
|
||||
if (newVisible != isVisible) {
|
||||
setState(() => isVisible = newVisible);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.visibleBuilder != null) return widget.visibleBuilder!(isVisible)!;
|
||||
if (widget.visibleBuilder != null) {
|
||||
return widget.visibleBuilder!(isVisible) ?? const SizedBox();
|
||||
}
|
||||
|
||||
if (widget.child == null) return const SizedBox();
|
||||
|
||||
if (AdaptiveLayout.viewSizeOf(context) == ViewSize.desktop) {
|
||||
return widget.child!;
|
||||
} else {
|
||||
return AnimatedAlign(
|
||||
alignment: const Alignment(0, -1),
|
||||
heightFactor: widget.forceHide
|
||||
? 0
|
||||
: isVisible
|
||||
? 1.0
|
||||
: 0,
|
||||
duration: widget.duration,
|
||||
child: Wrap(children: [widget.child!]),
|
||||
);
|
||||
}
|
||||
|
||||
return AnimatedAlign(
|
||||
alignment: const Alignment(0, -1),
|
||||
heightFactor: widget.forceHide
|
||||
? 0
|
||||
: isVisible
|
||||
? 1.0
|
||||
: 0,
|
||||
duration: widget.duration,
|
||||
child: Wrap(
|
||||
children: [widget.child!],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,20 +2,19 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
|
||||
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
||||
import 'package:fladder/util/disable_keypad_focus.dart';
|
||||
import 'package:fladder/util/list_padding.dart';
|
||||
import 'package:fladder/util/sticky_header_text.dart';
|
||||
|
||||
class HorizontalList extends ConsumerStatefulWidget {
|
||||
class HorizontalList<T> extends ConsumerStatefulWidget {
|
||||
final String? label;
|
||||
final List<Widget> titleActions;
|
||||
final Function()? onLabelClick;
|
||||
final String? subtext;
|
||||
final List items;
|
||||
final List<T> items;
|
||||
final int? startIndex;
|
||||
final Widget Function(BuildContext context, int index) itemBuilder;
|
||||
final bool scrollToEnd;
|
||||
|
|
@ -42,19 +41,16 @@ class HorizontalList extends ConsumerStatefulWidget {
|
|||
}
|
||||
|
||||
class _HorizontalListState extends ConsumerState<HorizontalList> {
|
||||
final itemScrollController = ItemScrollController();
|
||||
late final scrollOffsetController = ScrollOffsetController();
|
||||
final GlobalKey _firstItemKey = GlobalKey();
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
final contentPadding = 8.0;
|
||||
double? contentWidth;
|
||||
double? _firstItemWidth;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
Future.microtask(() async {
|
||||
if (widget.startIndex != null) {
|
||||
itemScrollController.jumpTo(index: widget.startIndex!);
|
||||
scrollOffsetController.animateScroll(
|
||||
offset: -widget.contentPadding.left, duration: const Duration(milliseconds: 125));
|
||||
}
|
||||
});
|
||||
_measureFirstItem(scrollTo: true);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -62,19 +58,56 @@ class _HorizontalListState extends ConsumerState<HorizontalList> {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
void _measureFirstItem({bool scrollTo = false}) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (widget.startIndex != null) {
|
||||
final context = _firstItemKey.currentContext;
|
||||
if (context != null) {
|
||||
final box = context.findRenderObject() as RenderBox;
|
||||
_firstItemWidth = box.size.width;
|
||||
if (scrollTo) {
|
||||
_scrollToPosition(widget.startIndex!);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _scrollToPosition(int index) {
|
||||
final offset = index * _firstItemWidth! + index * contentPadding;
|
||||
_scrollController.animateTo(
|
||||
offset,
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
|
||||
void _scrollToStart() {
|
||||
itemScrollController.scrollTo(index: 0, duration: const Duration(milliseconds: 250), curve: Curves.easeInOut);
|
||||
_scrollController.animateTo(
|
||||
0,
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
|
||||
void _scrollToEnd() {
|
||||
itemScrollController.scrollTo(
|
||||
index: widget.items.length, duration: const Duration(milliseconds: 250), curve: Curves.easeInOut);
|
||||
_scrollController.animateTo(
|
||||
(_firstItemWidth ?? 200) * widget.items.length + 200,
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
|
||||
int getFirstVisibleIndex() {
|
||||
if (widget.startIndex == null) return 0;
|
||||
if (!_scrollController.hasClients || _firstItemWidth == null) return 0;
|
||||
return (_scrollController.offset / _firstItemWidth!).floor().clamp(0, widget.items.length - 1);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hasPointer = AdaptiveLayout.of(context).inputDevice == InputDevice.pointer;
|
||||
return Column(
|
||||
final content = Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
|
|
@ -97,11 +130,13 @@ class _HorizontalListState extends ConsumerState<HorizontalList> {
|
|||
),
|
||||
),
|
||||
if (widget.subtext != null)
|
||||
Opacity(
|
||||
opacity: 0.5,
|
||||
child: Text(
|
||||
widget.subtext!,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
Flexible(
|
||||
child: Opacity(
|
||||
opacity: 0.5,
|
||||
child: Text(
|
||||
widget.subtext!,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
...widget.titleActions
|
||||
|
|
@ -120,8 +155,8 @@ class _HorizontalListState extends ConsumerState<HorizontalList> {
|
|||
onLongPress: () => _scrollToStart(),
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
scrollOffsetController.animateScroll(
|
||||
offset: -(MediaQuery.of(context).size.width / 1.75),
|
||||
_scrollController.animateTo(
|
||||
_scrollController.offset + -(MediaQuery.of(context).size.width / 1.75),
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOut);
|
||||
},
|
||||
|
|
@ -134,12 +169,8 @@ class _HorizontalListState extends ConsumerState<HorizontalList> {
|
|||
IconButton(
|
||||
tooltip: "Scroll to current",
|
||||
onPressed: () {
|
||||
if (widget.startIndex != null) {
|
||||
itemScrollController.jumpTo(index: widget.startIndex!);
|
||||
scrollOffsetController.animateScroll(
|
||||
offset: -widget.contentPadding.left,
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOutQuad);
|
||||
if (_firstItemWidth != null && widget.startIndex != null) {
|
||||
_scrollToPosition(widget.startIndex!);
|
||||
}
|
||||
},
|
||||
icon: const Icon(
|
||||
|
|
@ -151,8 +182,8 @@ class _HorizontalListState extends ConsumerState<HorizontalList> {
|
|||
onLongPress: () => _scrollToEnd(),
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
scrollOffsetController.animateScroll(
|
||||
offset: (MediaQuery.of(context).size.width / 1.75),
|
||||
_scrollController.animateTo(
|
||||
_scrollController.offset + (MediaQuery.of(context).size.width / 1.75),
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOut);
|
||||
},
|
||||
|
|
@ -170,23 +201,30 @@ class _HorizontalListState extends ConsumerState<HorizontalList> {
|
|||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
height: widget.height ??
|
||||
height: (widget.height ??
|
||||
AdaptiveLayout.poster(context).size *
|
||||
ref.watch(clientSettingsProvider.select((value) => value.posterSize)),
|
||||
child: ScrollablePositionedList.separated(
|
||||
shrinkWrap: widget.shrinkWrap,
|
||||
itemScrollController: itemScrollController,
|
||||
scrollOffsetController: scrollOffsetController,
|
||||
padding: widget.contentPadding,
|
||||
itemCount: widget.items.length,
|
||||
ref.watch(clientSettingsProvider.select((value) => value.posterSize))),
|
||||
child: ListView.separated(
|
||||
controller: _scrollController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
separatorBuilder: (context, index) => const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
itemBuilder: widget.itemBuilder,
|
||||
padding: widget.contentPadding,
|
||||
itemBuilder: (context, index) => index == getFirstVisibleIndex()
|
||||
? Container(
|
||||
key: _firstItemKey,
|
||||
child: widget.itemBuilder(context, index),
|
||||
)
|
||||
: widget.itemBuilder(context, index),
|
||||
separatorBuilder: (context, index) => SizedBox(width: contentPadding),
|
||||
itemCount: widget.items.length,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
return widget.startIndex == null
|
||||
? content
|
||||
: LayoutBuilder(builder: (context, constraints) {
|
||||
_measureFirstItem();
|
||||
return content;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
import 'package:fladder/theme.dart';
|
||||
import 'package:fladder/util/fladder_image.dart';
|
||||
|
||||
Future<void> showBottomSheetPill({
|
||||
|
|
@ -18,31 +17,58 @@ Future<void> showBottomSheetPill({
|
|||
ScrollController scrollController,
|
||||
) content,
|
||||
}) async {
|
||||
final screenSize = MediaQuery.sizeOf(context);
|
||||
await showModalBottomSheet(
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
useRootNavigator: true,
|
||||
showDragHandle: true,
|
||||
enableDrag: true,
|
||||
context: context,
|
||||
constraints: AdaptiveLayout.viewSizeOf(context) == ViewSize.phone
|
||||
? BoxConstraints(maxHeight: screenSize.height * 0.9)
|
||||
: BoxConstraints(maxWidth: screenSize.width * 0.75, maxHeight: screenSize.height * 0.85),
|
||||
builder: (context) {
|
||||
final controller = ScrollController();
|
||||
return ListView(
|
||||
shrinkWrap: true,
|
||||
controller: controller,
|
||||
children: [
|
||||
if (item != null) ...{
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: ItemBottomSheetPreview(item: item),
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.sizeOf(context).height * 0.85,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8).add(MediaQuery.paddingOf(context)),
|
||||
child: Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: FladderTheme.largeShape.borderRadius,
|
||||
),
|
||||
const Divider(),
|
||||
},
|
||||
content(context, controller),
|
||||
],
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Container(
|
||||
height: 8,
|
||||
width: 35,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
borderRadius: FladderTheme.largeShape.borderRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
controller: controller,
|
||||
children: [
|
||||
if (item != null) ...{
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: ItemBottomSheetPreview(item: item),
|
||||
),
|
||||
const Divider(),
|
||||
},
|
||||
content(context, ScrollController()),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fladder/theme.dart';
|
||||
|
||||
Future<void> showModalSideSheet(
|
||||
BuildContext context, {
|
||||
required Widget content,
|
||||
|
|
@ -30,13 +32,18 @@ Future<void> showModalSideSheet(
|
|||
pageBuilder: (context, animation1, animation2) {
|
||||
return Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Sheet(
|
||||
header: header,
|
||||
backButton: backButton,
|
||||
closeButton: closeButton,
|
||||
actions: actions,
|
||||
content: content,
|
||||
addDivider: addDivider,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0).copyWith(
|
||||
top: MediaQuery.paddingOf(context).top,
|
||||
),
|
||||
child: Sheet(
|
||||
header: header,
|
||||
backButton: backButton,
|
||||
closeButton: closeButton,
|
||||
actions: actions,
|
||||
content: content,
|
||||
addDivider: addDivider,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
@ -64,32 +71,40 @@ class Sheet extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = MediaQuery.of(context).size;
|
||||
final mediaQuery = MediaQuery.of(context);
|
||||
final size = mediaQuery.size;
|
||||
final theme = Theme.of(context);
|
||||
final colorScheme = theme.colorScheme;
|
||||
final padding = mediaQuery.padding.copyWith(left: 0, top: 0);
|
||||
|
||||
return Material(
|
||||
elevation: 1,
|
||||
color: colorScheme.surface,
|
||||
surfaceTintColor: colorScheme.onSurface,
|
||||
borderRadius: const BorderRadius.horizontal(left: Radius.circular(20)),
|
||||
child: Padding(
|
||||
padding: MediaQuery.of(context).padding,
|
||||
child: Container(
|
||||
constraints: BoxConstraints(
|
||||
minWidth: 256,
|
||||
maxWidth: size.width <= 600 ? size.width : 400,
|
||||
minHeight: size.height,
|
||||
maxHeight: size.height,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildHeader(context),
|
||||
Expanded(
|
||||
child: content,
|
||||
),
|
||||
if (actions?.isNotEmpty ?? false) _buildFooter(context)
|
||||
],
|
||||
return MediaQuery(
|
||||
data: mediaQuery.copyWith(
|
||||
padding: mediaQuery.padding.copyWith(
|
||||
left: 0,
|
||||
)),
|
||||
child: Material(
|
||||
elevation: 1,
|
||||
color: colorScheme.surface,
|
||||
surfaceTintColor: colorScheme.onSurface,
|
||||
borderRadius: FladderTheme.largeShape.borderRadius,
|
||||
child: Padding(
|
||||
padding: padding,
|
||||
child: Container(
|
||||
constraints: BoxConstraints(
|
||||
minWidth: 256,
|
||||
maxWidth: size.width <= 600 ? size.width : 400,
|
||||
minHeight: size.height,
|
||||
maxHeight: size.height,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildHeader(context),
|
||||
Expanded(
|
||||
child: content,
|
||||
),
|
||||
if (actions?.isNotEmpty ?? false) _buildFooter(context)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
||||
import 'package:fladder/util/refresh_state.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
enum ScrollState {
|
||||
|
|
@ -22,45 +23,41 @@ class ScrollStatePosition extends ConsumerStatefulWidget {
|
|||
}
|
||||
|
||||
class _ScrollStatePositionState extends ConsumerState<ScrollStatePosition> {
|
||||
late final scrollController = widget.controller ?? ScrollController();
|
||||
ScrollState scrollState = ScrollState.top;
|
||||
late final ScrollController _scrollController = widget.controller ?? ScrollController();
|
||||
ScrollState _scrollState = ScrollState.top;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
scrollController.addListener(listen);
|
||||
_scrollController.addListener(_onScroll);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
scrollController.removeListener(listen);
|
||||
_scrollController.removeListener(_onScroll);
|
||||
if (widget.controller == null) {
|
||||
_scrollController.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void listen() {
|
||||
if (scrollController.offset < scrollController.position.maxScrollExtent) {
|
||||
if (scrollController.position.atEdge) {
|
||||
bool isTop = scrollController.position.pixels == 0;
|
||||
if (isTop) {
|
||||
setState(() {
|
||||
scrollState = ScrollState.top;
|
||||
});
|
||||
print('At the top');
|
||||
} else {
|
||||
setState(() {
|
||||
scrollState = ScrollState.bottom;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setState(() {
|
||||
scrollState = ScrollState.middle;
|
||||
});
|
||||
}
|
||||
void _onScroll() {
|
||||
final position = _scrollController.position;
|
||||
final newState = () {
|
||||
if (position.pixels == 0) return ScrollState.top;
|
||||
if (position.pixels >= position.maxScrollExtent) return ScrollState.bottom;
|
||||
return ScrollState.middle;
|
||||
}();
|
||||
|
||||
if (newState != _scrollState) {
|
||||
setState(() {
|
||||
_scrollState = newState;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.positionBuilder(scrollState);
|
||||
return widget.positionBuilder(_scrollState);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue