mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-02-24 18:38:48 +01:00
Tweak selected item style
This commit is contained in:
parent
597961c821
commit
bd11aef1db
7 changed files with 137 additions and 11 deletions
68
app/lib/widget/animated_smooth_clip_r_rect.dart
Normal file
68
app/lib/widget/animated_smooth_clip_r_rect.dart
Normal file
|
@ -0,0 +1,68 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:smooth_corner/smooth_corner.dart';
|
||||
|
||||
class AnimatedSmoothClipRRect extends ImplicitlyAnimatedWidget {
|
||||
const AnimatedSmoothClipRRect({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.smoothness = 0.0,
|
||||
required this.borderRadius,
|
||||
this.side = BorderSide.none,
|
||||
super.curve,
|
||||
required super.duration,
|
||||
super.onEnd,
|
||||
});
|
||||
|
||||
@override
|
||||
ImplicitlyAnimatedWidgetState<AnimatedSmoothClipRRect> createState() =>
|
||||
_AnimatedSmoothClipRRectState();
|
||||
|
||||
final Widget child;
|
||||
final BorderRadius borderRadius;
|
||||
final double smoothness;
|
||||
final BorderSide side;
|
||||
}
|
||||
|
||||
class _AnimatedSmoothClipRRectState
|
||||
extends AnimatedWidgetBaseState<AnimatedSmoothClipRRect> {
|
||||
@override
|
||||
void forEachTween(TweenVisitor<dynamic> visitor) {
|
||||
_borderRadius = visitor(
|
||||
_borderRadius,
|
||||
widget.borderRadius,
|
||||
(dynamic value) => Tween<BorderRadius>(begin: value as BorderRadius),
|
||||
) as Tween<BorderRadius>?;
|
||||
_side = visitor(
|
||||
_side,
|
||||
widget.side,
|
||||
(dynamic value) => BorderSideTween(begin: value as BorderSide),
|
||||
) as BorderSideTween?;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SmoothClipRRect(
|
||||
smoothness: widget.smoothness,
|
||||
side: _side?.evaluate(animation) ?? BorderSide.none,
|
||||
borderRadius: _borderRadius?.evaluate(animation) ?? BorderRadius.zero,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
||||
Tween<BorderRadius>? _borderRadius;
|
||||
BorderSideTween? _side;
|
||||
}
|
||||
|
||||
class BorderSideTween extends Tween<BorderSide?> {
|
||||
/// Creates an [BorderSide] tween.
|
||||
///
|
||||
/// The [begin] and [end] properties must be non-null before the tween is
|
||||
/// first used, but the arguments can be null if the values are going to be
|
||||
/// filled in later.
|
||||
BorderSideTween({BorderSide? begin, BorderSide? end})
|
||||
: super(begin: begin, end: end);
|
||||
|
||||
/// Returns the value this variable has at the given animation clock value.
|
||||
@override
|
||||
BorderSide? lerp(double t) => BorderSide.lerp(begin!, end!, t);
|
||||
}
|
|
@ -127,6 +127,8 @@ class _HomeAlbumsState extends State<HomeAlbums>
|
|||
sliver: buildItemStreamList(
|
||||
maxCrossAxisExtent: 256,
|
||||
mainAxisSpacing: 6,
|
||||
childBorderRadius: BorderRadius.zero,
|
||||
indicatorAlignment: const Alignment(-.92, -.92),
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
|
|
|
@ -965,6 +965,7 @@ class _StylePickerState extends State<_StylePicker> {
|
|||
child: Selectable(
|
||||
isSelected: _selected == index,
|
||||
iconSize: 24,
|
||||
indicatorAlignment: Alignment.center,
|
||||
child: child,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:nc_photos/k.dart' as k;
|
||||
import 'package:nc_photos/widget/animated_smooth_clip_r_rect.dart';
|
||||
|
||||
// Overlay a check mark if an item is selected
|
||||
class Selectable extends StatelessWidget {
|
||||
|
@ -9,6 +10,8 @@ class Selectable extends StatelessWidget {
|
|||
this.isSelected = false,
|
||||
required this.iconSize,
|
||||
this.borderRadius,
|
||||
this.childBorderRadius = BorderRadius.zero,
|
||||
this.indicatorAlignment = Alignment.topLeft,
|
||||
this.onTap,
|
||||
this.onLongPress,
|
||||
}) : super(key: key);
|
||||
|
@ -31,24 +34,43 @@ class Selectable extends StatelessWidget {
|
|||
scale: isSelected ? .85 : 1,
|
||||
curve: Curves.easeInOut,
|
||||
duration: k.animationDurationNormal,
|
||||
child: child,
|
||||
child: childBorderRadius != BorderRadius.zero
|
||||
? AnimatedSmoothClipRRect(
|
||||
smoothness: 1,
|
||||
borderRadius:
|
||||
isSelected ? childBorderRadius : BorderRadius.zero,
|
||||
side: BorderSide(
|
||||
color: isSelected
|
||||
? Theme.of(context).colorScheme.primaryContainer
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.primaryContainer
|
||||
.withOpacity(0),
|
||||
width: isSelected ? 4 : 0,
|
||||
),
|
||||
curve: Curves.easeInOut,
|
||||
duration: k.animationDurationNormal,
|
||||
child: child,
|
||||
)
|
||||
: child,
|
||||
),
|
||||
Positioned.fill(
|
||||
Align(
|
||||
alignment: indicatorAlignment,
|
||||
child: AnimatedOpacity(
|
||||
opacity: isSelected ? 1 : 0,
|
||||
duration: k.animationDurationNormal,
|
||||
child: Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.circle,
|
||||
size: iconSize,
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
size: iconSize - 2,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
Icon(
|
||||
Icons.check_circle_outlined,
|
||||
size: iconSize,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -74,6 +96,10 @@ class Selectable extends StatelessWidget {
|
|||
final double iconSize;
|
||||
final BorderRadius? borderRadius;
|
||||
|
||||
/// Border radius used to clip the child widget when selected
|
||||
final BorderRadius childBorderRadius;
|
||||
final Alignment indicatorAlignment;
|
||||
|
||||
final VoidCallback? onTap;
|
||||
final VoidCallback? onLongPress;
|
||||
}
|
||||
|
|
|
@ -64,15 +64,24 @@ mixin SelectableItemStreamListMixin<T extends StatefulWidget> on State<T> {
|
|||
double mainAxisSpacing = 0,
|
||||
ValueChanged<double?>? onMaxExtentChanged,
|
||||
bool isEnableVisibilityCallback = false,
|
||||
BorderRadius? childBorderRadius,
|
||||
Alignment indicatorAlignment = Alignment.topLeft,
|
||||
}) {
|
||||
childBorderRadius ??= BorderRadius.circular(24);
|
||||
|
||||
final Widget content;
|
||||
if (onMaxExtentChanged != null) {
|
||||
content = MeasurableItemList(
|
||||
key: _listKey,
|
||||
maxCrossAxisExtent: maxCrossAxisExtent,
|
||||
itemCount: _items.length,
|
||||
itemBuilder: (context, i) =>
|
||||
_buildItem(context, i, isEnableVisibilityCallback),
|
||||
itemBuilder: (context, i) => _buildItem(
|
||||
context,
|
||||
i,
|
||||
isEnableVisibilityCallback: isEnableVisibilityCallback,
|
||||
childBorderRadius: childBorderRadius!,
|
||||
indicatorAlignment: indicatorAlignment,
|
||||
),
|
||||
staggeredTileBuilder: (index) => _items[index].staggeredTile,
|
||||
mainAxisSpacing: mainAxisSpacing,
|
||||
onMaxExtentChanged: onMaxExtentChanged,
|
||||
|
@ -82,8 +91,13 @@ mixin SelectableItemStreamListMixin<T extends StatefulWidget> on State<T> {
|
|||
key: ObjectKey(maxCrossAxisExtent),
|
||||
maxCrossAxisExtent: maxCrossAxisExtent,
|
||||
itemCount: _items.length,
|
||||
itemBuilder: (context, i) =>
|
||||
_buildItem(context, i, isEnableVisibilityCallback),
|
||||
itemBuilder: (context, i) => _buildItem(
|
||||
context,
|
||||
i,
|
||||
isEnableVisibilityCallback: isEnableVisibilityCallback,
|
||||
childBorderRadius: childBorderRadius!,
|
||||
indicatorAlignment: indicatorAlignment,
|
||||
),
|
||||
staggeredTileBuilder: (index) => _items[index].staggeredTile,
|
||||
mainAxisSpacing: mainAxisSpacing,
|
||||
);
|
||||
|
@ -149,13 +163,20 @@ mixin SelectableItemStreamListMixin<T extends StatefulWidget> on State<T> {
|
|||
}
|
||||
|
||||
Widget _buildItem(
|
||||
BuildContext context, int index, bool isEnableVisibilityCallback) {
|
||||
BuildContext context,
|
||||
int index, {
|
||||
required bool isEnableVisibilityCallback,
|
||||
required BorderRadius childBorderRadius,
|
||||
required Alignment indicatorAlignment,
|
||||
}) {
|
||||
final item = _items[index];
|
||||
Widget content = item.buildWidget(context);
|
||||
if (item.isSelectable) {
|
||||
content = Selectable(
|
||||
isSelected: _selectedItems.contains(item),
|
||||
iconSize: 32,
|
||||
childBorderRadius: childBorderRadius,
|
||||
indicatorAlignment: indicatorAlignment,
|
||||
onTap: () => _onItemTap(item, index),
|
||||
onLongPress: isSelectionMode && platform_k.isWeb
|
||||
? null
|
||||
|
|
|
@ -1134,6 +1134,13 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.99"
|
||||
smooth_corner:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: smooth_corner
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
source_gen:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -95,6 +95,7 @@ dependencies:
|
|||
quiver: ^3.1.0
|
||||
screen_brightness: ^0.2.1
|
||||
shared_preferences: ^2.0.8
|
||||
smooth_corner: ^1.1.0
|
||||
sqlite3: any
|
||||
sqlite3_flutter_libs: ^0.5.8
|
||||
synchronized: ^3.0.0
|
||||
|
|
Loading…
Reference in a new issue