From 95a645c06cf2a76231c4b8dbea87ec593961c205 Mon Sep 17 00:00:00 2001 From: Jagandeep Brar Date: Tue, 14 Jun 2022 21:18:43 -0400 Subject: [PATCH] fix(lists): reorderable lists could not be reordered on some platforms --- lib/system/environment.dart | 2 +- lib/types/indexer_icon.dart | 88 +-- .../flutter_reorderable_list_view.dart | 550 ------------------ .../ui/list_view/reorderable_list_view.dart | 3 +- .../reorderable_list_view_builder.dart | 3 +- 5 files changed, 48 insertions(+), 598 deletions(-) delete mode 100644 lib/widgets/ui/list_view/flutter_reorderable_list_view.dart diff --git a/lib/system/environment.dart b/lib/system/environment.dart index 43b3e656..5aaedcac 100644 --- a/lib/system/environment.dart +++ b/lib/system/environment.dart @@ -3,5 +3,5 @@ class LunaEnvironment { static const String commit = 'master'; - static const String flavor = 'stable'; + static const String flavor = 'edge'; } diff --git a/lib/types/indexer_icon.dart b/lib/types/indexer_icon.dart index a36fb61b..0a20ecf2 100644 --- a/lib/types/indexer_icon.dart +++ b/lib/types/indexer_icon.dart @@ -3,65 +3,67 @@ import 'package:lunasea/core.dart'; part 'indexer_icon.g.dart'; +const _GENERIC = 'generic'; +const _DOGNZB = 'dognzb'; +const _DRUNKENSLUG = 'drunkenslug'; +const _NZBFINDER = 'nzbfinder'; +const _NZBGEEK = 'nzbgeek'; +const _NZBHYDRA = 'nzbhydra'; +const _NZBSU = 'nzbsu'; + +@JsonEnum() @HiveType(typeId: 22, adapterName: 'LunaIndexerIconAdapter') enum LunaIndexerIcon { + @JsonValue(_GENERIC) @HiveField(0) - GENERIC, - @HiveField(1) - DOGNZB, - @HiveField(2) - DRUNKENSLUG, - @HiveField(3) - NZBFINDER, - @HiveField(4) - NZBGEEK, - @HiveField(5) - NZBHYDRA, - @HiveField(6) - NZBSU, -} + GENERIC(_GENERIC), -extension LunaIndexerIconExtension on LunaIndexerIcon { - LunaIndexerIcon fromKey(String key) { + @JsonValue(_DOGNZB) + @HiveField(1) + DOGNZB(_DOGNZB), + + @JsonValue(_DRUNKENSLUG) + @HiveField(2) + DRUNKENSLUG(_DRUNKENSLUG), + + @JsonValue(_NZBFINDER) + @HiveField(3) + NZBFINDER(_NZBFINDER), + + @JsonValue(_NZBGEEK) + @HiveField(4) + NZBGEEK(_NZBGEEK), + + @JsonValue(_NZBHYDRA) + @HiveField(5) + NZBHYDRA(_NZBHYDRA), + + @JsonValue(_NZBSU) + @HiveField(6) + NZBSU(_NZBSU); + + final String key; + const LunaIndexerIcon(this.key); + + static LunaIndexerIcon fromKey(String key) { switch (key) { - case 'generic': - return LunaIndexerIcon.GENERIC; - case 'dognzb': + case _DOGNZB: return LunaIndexerIcon.DOGNZB; - case 'drunkenslug': + case _DRUNKENSLUG: return LunaIndexerIcon.DRUNKENSLUG; - case 'nzbfinder': + case _NZBFINDER: return LunaIndexerIcon.NZBFINDER; - case 'nzbgeek': + case _NZBGEEK: return LunaIndexerIcon.NZBGEEK; - case 'nzbhydra': + case _NZBHYDRA: return LunaIndexerIcon.NZBHYDRA; - case 'nzbsu': + case _NZBSU: return LunaIndexerIcon.NZBSU; default: return LunaIndexerIcon.GENERIC; } } - String get key { - switch (this) { - case LunaIndexerIcon.GENERIC: - return 'generic'; - case LunaIndexerIcon.DOGNZB: - return 'dognzb'; - case LunaIndexerIcon.DRUNKENSLUG: - return 'drunkenslug'; - case LunaIndexerIcon.NZBFINDER: - return 'nzbfinder'; - case LunaIndexerIcon.NZBGEEK: - return 'nzbgeek'; - case LunaIndexerIcon.NZBHYDRA: - return 'nzbhydra'; - case LunaIndexerIcon.NZBSU: - return 'nzbsu'; - } - } - String get name { switch (this) { case LunaIndexerIcon.GENERIC: diff --git a/lib/widgets/ui/list_view/flutter_reorderable_list_view.dart b/lib/widgets/ui/list_view/flutter_reorderable_list_view.dart deleted file mode 100644 index 3711fa0d..00000000 --- a/lib/widgets/ui/list_view/flutter_reorderable_list_view.dart +++ /dev/null @@ -1,550 +0,0 @@ -/// This is a modified version of [ReorderableListView] that was originally pulled from Flutter stable v2.0.4. -/// -/// Change includes ensuring that the `padding` is pulled and applied to the inner padding/list padding, and not applied to the list externally. -/// This fixes the bug that caused the list to get clipped from the top padding. - -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// ignore_for_file: unnecessary_null_comparison - -import 'dart:ui' show lerpDouble; - -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; - -/// A list whose items the user can interactively reorder by dragging. -/// -/// This class is appropriate for views with a small number of -/// children because constructing the [List] requires doing work for every -/// child that could possibly be displayed in the list view instead of just -/// those children that are actually visible. -/// -/// All list items must have a key. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=3fB1mxOsqJE} -/// -/// This sample shows by dragging the user can reorder the items of the list. -/// The [onReorder] parameter is required and will be called when a child -/// widget is dragged to a new position. -/// -/// {@tool dartpad --template=stateful_widget_scaffold} -/// -/// ```dart -/// final List _items = List.generate(50, (int index) => index); -/// -/// @override -/// Widget build(BuildContext context){ -/// final ColorScheme colorScheme = Theme.of(context).colorScheme; -/// final Color oddItemColor = colorScheme.primary.withOpacity(0.05); -/// final Color evenItemColor = colorScheme.primary.withOpacity(0.15); -/// -/// return ReorderableListView( -/// padding: const EdgeInsets.symmetric(horizontal: 40), -/// children: [ -/// for (int index = 0; index < _items.length; index++) -/// ListTile( -/// key: Key('$index'), -/// tileColor: _items[index].isOdd ? oddItemColor : evenItemColor, -/// title: Text('Item ${_items[index]}'), -/// ), -/// ], -/// onReorder: (int oldIndex, int newIndex) { -/// setState(() { -/// if (oldIndex < newIndex) { -/// newIndex -= 1; -/// } -/// final int item = _items.removeAt(oldIndex); -/// _items.insert(newIndex, item); -/// }); -/// }, -/// ); -/// } -/// -/// ``` -/// -///{@end-tool} -class FlutterReorderableListView extends StatefulWidget { - /// Creates a reorderable list from a pre-built list of widgets. - /// - /// See also: - /// - /// * [ReorderableListView.builder], which allows you to build a reorderable - /// list where the items are built as needed when scrolling the list. - FlutterReorderableListView({ - Key? key, - required List children, - required this.onReorder, - this.proxyDecorator, - this.buildDefaultDragHandles = true, - this.padding, - this.header, - this.scrollDirection = Axis.vertical, - this.reverse = false, - this.scrollController, - this.primary, - this.physics, - this.shrinkWrap = false, - this.anchor = 0.0, - this.cacheExtent, - this.dragStartBehavior = DragStartBehavior.start, - this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, - this.restorationId, - this.clipBehavior = Clip.hardEdge, - }) : assert(scrollDirection != null), - assert(onReorder != null), - assert(children != null), - assert( - children.every((Widget w) => w.key != null), - 'All children of this widget must have a key.', - ), - assert(buildDefaultDragHandles != null), - itemBuilder = ((BuildContext context, int index) => children[index]), - itemCount = children.length, - super(key: key); - - /// Creates a reorderable list from widget items that are created on demand. - /// - /// This constructor is appropriate for list views with a large number of - /// children because the builder is called only for those children - /// that are actually visible. - /// - /// The `itemBuilder` callback will be called only with indices greater than - /// or equal to zero and less than `itemCount`. - /// - /// The `itemBuilder` should always return a non-null widget, and actually - /// create the widget instances when called. Avoid using a builder that - /// returns a previously-constructed widget; if the list view's children are - /// created in advance, or all at once when the [ReorderableListView] itself - /// is created, it is more efficient to use the [ReorderableListView] - /// constructor. Even more efficient, however, is to create the instances - /// on demand using this constructor's `itemBuilder` callback. - /// - /// See also: - /// - /// * [ReorderableListView], which allows you to build a reorderable - /// list with all the items passed into the constructor. - const FlutterReorderableListView.builder({ - Key? key, - required this.itemBuilder, - required this.itemCount, - required this.onReorder, - this.proxyDecorator, - this.buildDefaultDragHandles = true, - this.padding, - this.header, - this.scrollDirection = Axis.vertical, - this.reverse = false, - this.scrollController, - this.primary, - this.physics, - this.shrinkWrap = false, - this.anchor = 0.0, - this.cacheExtent, - this.dragStartBehavior = DragStartBehavior.start, - this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, - this.restorationId, - this.clipBehavior = Clip.hardEdge, - }) : assert(scrollDirection != null), - assert(itemCount >= 0), - assert(onReorder != null), - assert(buildDefaultDragHandles != null), - super(key: key); - - /// {@macro flutter.widgets.reorderable_list.itemBuilder} - final IndexedWidgetBuilder itemBuilder; - - /// {@macro flutter.widgets.reorderable_list.itemCount} - final int itemCount; - - /// {@macro flutter.widgets.reorderable_list.onReorder} - final ReorderCallback onReorder; - - /// {@macro flutter.widgets.reorderable_list.proxyDecorator} - final ReorderItemProxyDecorator? proxyDecorator; - - /// If true: on desktop platforms, a drag handle is stacked over the - /// center of each item's trailing edge; on mobile platforms, a long - /// press anywhere on the item starts a drag. - /// - /// The default desktop drag handle is just an [Icons.drag_handle] - /// wrapped by a [ReorderableDragStartListener]. On mobile - /// platforms, the entire item is wrapped with a - /// [ReorderableDelayedDragStartListener]. - /// - /// To change the appearance or the layout of the drag handles, make - /// this parameter false and wrap each list item, or a widget within - /// each list item, with [ReorderableDragStartListener] or - /// [ReorderableDelayedDragStartListener], or a custom subclass - /// of [ReorderableDragStartListener]. - /// - /// The following sample specifies `buildDefaultDragHandles: false`, and - /// uses a [Card] at the leading edge of each item for the item's drag handle. - /// - /// {@tool dartpad --template=stateful_widget_scaffold} - /// - /// ```dart - /// final List _items = List.generate(50, (int index) => index); - /// - /// @override - /// Widget build(BuildContext context){ - /// final ColorScheme colorScheme = Theme.of(context).colorScheme; - /// final Color oddItemColor = colorScheme.primary.withOpacity(0.05); - /// final Color evenItemColor = colorScheme.primary.withOpacity(0.15); - /// - /// return ReorderableListView( - /// buildDefaultDragHandles: false, - /// children: [ - /// for (int index = 0; index < _items.length; index++) - /// Container( - /// key: Key('$index'), - /// color: _items[index].isOdd ? oddItemColor : evenItemColor, - /// child: Row( - /// children: [ - /// Container( - /// width: 64, - /// height: 64, - /// padding: const EdgeInsets.all(8), - /// child: ReorderableDragStartListener( - /// index: index, - /// child: Card( - /// color: colorScheme.primary, - /// elevation: 2, - /// ), - /// ), - /// ), - /// Text('Item ${_items[index]}'), - /// ], - /// ), - /// ), - /// ], - /// onReorder: (int oldIndex, int newIndex) { - /// setState(() { - /// if (oldIndex < newIndex) { - /// newIndex -= 1; - /// } - /// final int item = _items.removeAt(oldIndex); - /// _items.insert(newIndex, item); - /// }); - /// }, - /// ); - /// } - /// ``` - ///{@end-tool} - final bool buildDefaultDragHandles; - - /// {@macro flutter.widgets.reorderable_list.padding} - final EdgeInsets? padding; - - /// A non-reorderable header item to show before the items of the list. - /// - /// If null, no header will appear before the list. - final Widget? header; - - /// {@macro flutter.widgets.scroll_view.scrollDirection} - final Axis scrollDirection; - - /// {@macro flutter.widgets.scroll_view.reverse} - final bool reverse; - - /// {@macro flutter.widgets.scroll_view.controller} - final ScrollController? scrollController; - - /// {@macro flutter.widgets.scroll_view.primary} - - /// Defaults to true when [scrollDirection] is [Axis.vertical] and - /// [scrollController] is null. - final bool? primary; - - /// {@macro flutter.widgets.scroll_view.physics} - final ScrollPhysics? physics; - - /// {@macro flutter.widgets.scroll_view.shrinkWrap} - final bool shrinkWrap; - - /// {@macro flutter.widgets.scroll_view.anchor} - final double anchor; - - /// {@macro flutter.rendering.RenderViewportBase.cacheExtent} - final double? cacheExtent; - - /// {@macro flutter.widgets.scrollable.dragStartBehavior} - final DragStartBehavior dragStartBehavior; - - /// {@macro flutter.widgets.scroll_view.keyboardDismissBehavior} - /// - /// The default is [ScrollViewKeyboardDismissBehavior.manual] - final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior; - - /// {@macro flutter.widgets.scrollable.restorationId} - final String? restorationId; - - /// {@macro flutter.material.Material.clipBehavior} - /// - /// Defaults to [Clip.hardEdge]. - final Clip clipBehavior; - - @override - _ReorderableListViewState createState() => _ReorderableListViewState(); -} - -class _ReorderableListViewState extends State { - Widget _wrapWithSemantics(Widget child, int index) { - void reorder(int startIndex, int endIndex) { - if (startIndex != endIndex) widget.onReorder(startIndex, endIndex); - } - - // First, determine which semantics actions apply. - final Map semanticsActions = - {}; - - // Create the appropriate semantics actions. - void moveToStart() => reorder(index, 0); - void moveToEnd() => reorder(index, widget.itemCount); - void moveBefore() => reorder(index, index - 1); - // To move after, we go to index+2 because we are moving it to the space - // before index+2, which is after the space at index+1. - void moveAfter() => reorder(index, index + 2); - - final MaterialLocalizations localizations = - MaterialLocalizations.of(context); - - // If the item can move to before its current position in the list. - if (index > 0) { - semanticsActions[ - CustomSemanticsAction(label: localizations.reorderItemToStart)] = - moveToStart; - String reorderItemBefore = localizations.reorderItemUp; - if (widget.scrollDirection == Axis.horizontal) { - reorderItemBefore = Directionality.of(context) == TextDirection.ltr - ? localizations.reorderItemLeft - : localizations.reorderItemRight; - } - semanticsActions[CustomSemanticsAction(label: reorderItemBefore)] = - moveBefore; - } - - // If the item can move to after its current position in the list. - if (index < widget.itemCount - 1) { - String reorderItemAfter = localizations.reorderItemDown; - if (widget.scrollDirection == Axis.horizontal) { - reorderItemAfter = Directionality.of(context) == TextDirection.ltr - ? localizations.reorderItemRight - : localizations.reorderItemLeft; - } - semanticsActions[CustomSemanticsAction(label: reorderItemAfter)] = - moveAfter; - semanticsActions[ - CustomSemanticsAction(label: localizations.reorderItemToEnd)] = - moveToEnd; - } - - // We pass toWrap with a GlobalKey into the item so that when it - // gets dragged, the accessibility framework can preserve the selected - // state of the dragging item. - // - // We also apply the relevant custom accessibility actions for moving the item - // up, down, to the start, and to the end of the list. - return MergeSemantics( - child: Semantics( - customSemanticsActions: semanticsActions, - child: child, - ), - ); - } - - Widget _itemBuilder(BuildContext context, int index) { - final Widget item = widget.itemBuilder(context, index); - assert(() { - if (item.key == null) { - throw FlutterError( - 'Every item of ReorderableListView must have a key.', - ); - } - return true; - }()); - - // _ReorderableItem so the widget versions can have them as well. - final Widget itemWithSemantics = _wrapWithSemantics(item, index); - final Key itemGlobalKey = - _ReorderableListViewChildGlobalKey(item.key!, this); - - if (widget.buildDefaultDragHandles) { - switch (Theme.of(context).platform) { - case TargetPlatform.fuchsia: - case TargetPlatform.linux: - case TargetPlatform.windows: - case TargetPlatform.macOS: - switch (widget.scrollDirection) { - case Axis.horizontal: - return Stack( - key: itemGlobalKey, - children: [ - itemWithSemantics, - Positioned.directional( - textDirection: Directionality.of(context), - start: 0, - end: 0, - bottom: 8, - child: Align( - alignment: AlignmentDirectional.bottomCenter, - child: ReorderableDragStartListener( - index: index, - child: const Icon(Icons.drag_handle), - ), - ), - ), - ], - ); - case Axis.vertical: - return Stack( - key: itemGlobalKey, - children: [ - itemWithSemantics, - Positioned.directional( - textDirection: Directionality.of(context), - top: 0, - bottom: 0, - end: 8, - child: Align( - alignment: AlignmentDirectional.centerEnd, - child: ReorderableDragStartListener( - index: index, - child: const Icon(Icons.drag_handle), - ), - ), - ), - ], - ); - } - case TargetPlatform.iOS: - case TargetPlatform.android: - return ReorderableDelayedDragStartListener( - key: itemGlobalKey, - index: index, - child: itemWithSemantics, - ); - } - } - - return KeyedSubtree( - key: itemGlobalKey, - child: itemWithSemantics, - ); - } - - Widget _proxyDecorator(Widget child, int index, Animation animation) { - return AnimatedBuilder( - animation: animation, - builder: (BuildContext context, Widget? child) { - final double animValue = Curves.easeInOut.transform(animation.value); - final double elevation = lerpDouble(0, 6, animValue)!; - return Material( - child: child, - elevation: elevation, - ); - }, - child: child, - ); - } - - @override - Widget build(BuildContext context) { - assert(debugCheckHasMaterialLocalizations(context)); - assert(debugCheckHasOverlay(context)); - - // If there is a header we can't just apply the padding to the list, - // so if header is not present, we wrap the CustomScrollView in the padding form the left and right - // and add the padding from the bottom and top to the sliver list. If header if present - // we only padd top of the header and bottom of the sliver list (or the equivalent for other axis directions). - final EdgeInsets padding = widget.padding ?? EdgeInsets.zero; - late EdgeInsets outerPadding; - late EdgeInsets listPadding; - switch (widget.scrollDirection) { - case Axis.horizontal: - if (widget.reverse) { - outerPadding = EdgeInsets.fromLTRB( - 0, padding.top, padding.right, padding.bottom); - listPadding = EdgeInsets.fromLTRB(padding.left, 0, 0, 0); - } else { - outerPadding = - EdgeInsets.fromLTRB(padding.left, padding.top, 0, padding.bottom); - listPadding = EdgeInsets.fromLTRB(0, 0, padding.right, 0); - } - break; - case Axis.vertical: - if (widget.reverse) { - outerPadding = EdgeInsets.fromLTRB(padding.left, 0, padding.right, 0); - listPadding = EdgeInsets.fromLTRB( - 0, padding.top, 0, widget.header != null ? 0 : padding.bottom); - } else { - outerPadding = EdgeInsets.fromLTRB(padding.left, 0, padding.right, 0); - listPadding = EdgeInsets.fromLTRB( - 0, widget.header != null ? 0 : padding.top, 0, padding.bottom); - } - break; - } - - return Padding( - padding: outerPadding, - child: CustomScrollView( - scrollDirection: widget.scrollDirection, - reverse: widget.reverse, - controller: widget.scrollController, - primary: widget.primary, - physics: widget.physics, - shrinkWrap: widget.shrinkWrap, - anchor: widget.anchor, - cacheExtent: widget.cacheExtent, - dragStartBehavior: widget.dragStartBehavior, - keyboardDismissBehavior: widget.keyboardDismissBehavior, - restorationId: widget.restorationId, - clipBehavior: widget.clipBehavior, - slivers: [ - if (widget.header != null) - SliverPadding( - padding: EdgeInsets.fromLTRB(0, widget.reverse ? 0 : padding.top, - 0, widget.reverse ? padding.bottom : 0), - sliver: SliverToBoxAdapter(child: widget.header), - ), - SliverPadding( - padding: listPadding, - sliver: SliverReorderableList( - itemBuilder: _itemBuilder, - itemCount: widget.itemCount, - onReorder: widget.onReorder, - proxyDecorator: widget.proxyDecorator ?? _proxyDecorator, - ), - ), - ], - ), - ); - } -} - -// A global key that takes its identity from the object and uses a value of a -// particular type to identify itself. -// -// The difference with GlobalObjectKey is that it uses [==] instead of [identical] -// of the objects used to generate widgets. -@optionalTypeArgs -class _ReorderableListViewChildGlobalKey extends GlobalObjectKey { - const _ReorderableListViewChildGlobalKey(this.subKey, this.state) - : super(subKey); - - final Key subKey; - final State state; - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is _ReorderableListViewChildGlobalKey && - other.subKey == subKey && - other.state == state; - } - - @override - int get hashCode => hashValues(subKey, state); -} diff --git a/lib/widgets/ui/list_view/reorderable_list_view.dart b/lib/widgets/ui/list_view/reorderable_list_view.dart index abec6374..3c9b22a5 100644 --- a/lib/widgets/ui/list_view/reorderable_list_view.dart +++ b/lib/widgets/ui/list_view/reorderable_list_view.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; -import 'package:lunasea/widgets/ui/list_view/flutter_reorderable_list_view.dart'; class LunaReorderableListView extends StatelessWidget { final List children; @@ -25,7 +24,7 @@ class LunaReorderableListView extends StatelessWidget { return Scrollbar( controller: controller, interactive: true, - child: FlutterReorderableListView( + child: ReorderableListView( scrollController: controller, keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, children: children, diff --git a/lib/widgets/ui/list_view/reorderable_list_view_builder.dart b/lib/widgets/ui/list_view/reorderable_list_view_builder.dart index f2db9b39..d94e9ee1 100644 --- a/lib/widgets/ui/list_view/reorderable_list_view_builder.dart +++ b/lib/widgets/ui/list_view/reorderable_list_view_builder.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; -import 'package:lunasea/widgets/ui/list_view/flutter_reorderable_list_view.dart'; class LunaReorderableListViewBuilder extends StatelessWidget { final int itemCount; @@ -27,7 +26,7 @@ class LunaReorderableListViewBuilder extends StatelessWidget { return Scrollbar( controller: controller, interactive: true, - child: FlutterReorderableListView.builder( + child: ReorderableListView.builder( scrollController: controller, keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, padding: padding as EdgeInsets? ??