Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions lib/packages/ui/src/models/content/content_action_handlers.dart

This file was deleted.

61 changes: 0 additions & 61 deletions lib/packages/ui/src/models/content/content_media.dart

This file was deleted.

1 change: 0 additions & 1 deletion lib/packages/ui/src/models/content/content_media_type.dart

This file was deleted.

10 changes: 0 additions & 10 deletions lib/packages/ui/src/models/content/content_view_mode.dart

This file was deleted.

13 changes: 0 additions & 13 deletions lib/packages/ui/src/models/identity/avatar_data.dart

This file was deleted.

9 changes: 0 additions & 9 deletions lib/packages/ui/src/models/identity/identity_name_data.dart

This file was deleted.

41 changes: 0 additions & 41 deletions lib/packages/ui/src/utils/links/link_navigation_utils.dart

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

import 'package:thunder/src/features/settings/domain/swipe_action.dart';

typedef SwipeBackgroundBuilder = Widget Function(
typedef ThunderSwipeBackgroundBuilder<T> = Widget Function(
BuildContext context,
DismissDirection effectiveDirection,
double progress,
SwipeAction? action,
ThunderSwipeAction<T>? action,
);

typedef SwipeIconResolver = IconData? Function(SwipeAction action);
class ThunderSwipeAction<T> {
const ThunderSwipeAction({
required this.value,
this.icon,
required this.color,
});

final T value;
final IconData? icon;
final Color Function(BuildContext context) color;
}

/// A dismissible widget that supports multiple swipe actions.
class MultiActionDismissible extends StatefulWidget {
const MultiActionDismissible({
class ThunderMultiActionDismissible<T> extends StatefulWidget {
const ThunderMultiActionDismissible({
super.key,
required this.child,
required this.direction,
Expand All @@ -31,73 +38,40 @@ class MultiActionDismissible extends StatefulWidget {
this.backgroundMaxWidthFactor = 1.0,
});

/// The content of the dismissible widget.
final Widget child;

/// The allowed swipe directions.
final DismissDirection direction;

/// The thresholds (0.0 - 1.0 of width) at which successive actions should trigger.
///
/// For example: [0.15, 0.35, 0.6] supports up to 3 actions.
/// If there are more thresholds than actions on a side, the last action repeats for further thresholds.
/// If empty, no actions will be triggered.
final List<double> actionThresholds;

/// The actions to be displayed on the left side of the widget, ordered from shortest to longest swipe.
final List<SwipeAction> leftActions;

/// The actions to be displayed on the right side of the widget, ordered from shortest to longest swipe.
final List<SwipeAction> rightActions;

/// The action to be performed when the user releases the widget.
final void Function(SwipeAction action)? onAction;

/// A callback that is called when the progress changes.
final void Function(double progress, DismissDirection direction, SwipeAction? action)? onProgressChanged;

/// A callback that is called when the user presses down on the widget.
final List<ThunderSwipeAction<T>> leftActions;
final List<ThunderSwipeAction<T>> rightActions;
final void Function(ThunderSwipeAction<T> action)? onAction;
final void Function(double progress, DismissDirection direction, ThunderSwipeAction<T>? action)? onProgressChanged;
final VoidCallback? onPointerDown;

/// A callback that is called when the user releases the widget.
final void Function(double verticalDelta)? onDragEnd;

/// Whether to produce haptic feedback when the action changes.
final bool enableHaptics;

/// Whether to temporarily disable swipe. This is used to allow the system back gesture to work when the user is swiping right.
final bool enableBackSwipeOverride;

/// A custom background builder. If not provided, a default will be used.
final SwipeBackgroundBuilder? backgroundBuilder;

/// The max width fraction used by the default background.
final ThunderSwipeBackgroundBuilder<T>? backgroundBuilder;
final double backgroundMaxWidthFactor;

@override
State<MultiActionDismissible> createState() => _MultiActionDismissibleState();
State<ThunderMultiActionDismissible<T>> createState() => _ThunderMultiActionDismissibleState<T>();
}

class _MultiActionDismissibleState extends State<MultiActionDismissible> {
double _progress = 0.0;
SwipeAction? _currentAction;
class _ThunderMultiActionDismissibleState<T> extends State<ThunderMultiActionDismissible<T>> {
double _progress = 0;
ThunderSwipeAction<T>? _currentAction;
DismissDirection _currentDirection = DismissDirection.startToEnd;
bool _overrideSwipe = false;
double _lastVerticalDelta = 0.0;

void _handlePointerDown() {
widget.onPointerDown?.call();
}
double _lastVerticalDelta = 0;

void _handlePointerMove(PointerMoveEvent event) {
_lastVerticalDelta = event.delta.dy;

if (!widget.enableBackSwipeOverride) return;
if (widget.direction != DismissDirection.endToStart) return;

final bool isSwipingRight = event.delta.dx > 0;
final isSwipingRight = event.delta.dx > 0;

if (isSwipingRight && !_overrideSwipe && _progress == 0.0) {
if (isSwipingRight && !_overrideSwipe && _progress == 0) {
setState(() => _overrideSwipe = true);
} else if (!isSwipingRight && _overrideSwipe) {
setState(() => _overrideSwipe = false);
Expand All @@ -106,16 +80,16 @@ class _MultiActionDismissibleState extends State<MultiActionDismissible> {

void _handlePointerUp() {
if (_overrideSwipe) setState(() => _overrideSwipe = false);
if (_currentAction != null && _currentAction != SwipeAction.none) widget.onAction?.call(_currentAction!);
if (_currentAction != null) widget.onAction?.call(_currentAction!);
widget.onDragEnd?.call(_lastVerticalDelta);
}

void _onUpdate(DismissUpdateDetails details) {
final progress = details.progress;
final dir = details.direction;

SwipeAction? next;
final bool isStartToEnd = dir == DismissDirection.startToEnd;
ThunderSwipeAction<T>? next;
final isStartToEnd = dir == DismissDirection.startToEnd;
if (widget.actionThresholds.isNotEmpty && progress > widget.actionThresholds.first) {
int tierIndex = 0;
for (int i = 0; i < widget.actionThresholds.length; i++) {
Expand All @@ -125,16 +99,17 @@ class _MultiActionDismissibleState extends State<MultiActionDismissible> {
break;
}
}
final List<SwipeAction> actions = isStartToEnd ? widget.leftActions : widget.rightActions;

final actions = isStartToEnd ? widget.leftActions : widget.rightActions;
if (actions.isNotEmpty) {
final int actionIndex = tierIndex.clamp(0, actions.length - 1);
final actionIndex = tierIndex.clamp(0, actions.length - 1);
next = actions[actionIndex];
}
} else {
next = null;
}

final bool actionChanged = next != _currentAction && next != null;
final actionChanged = next != _currentAction && next != null;

setState(() {
_progress = progress;
Expand All @@ -152,13 +127,14 @@ class _MultiActionDismissibleState extends State<MultiActionDismissible> {
Widget _buildDefaultBackground(BuildContext context) {
final alignment = _currentDirection == DismissDirection.startToEnd ? Alignment.centerLeft : Alignment.centerRight;
final actions = _currentDirection == DismissDirection.startToEnd ? widget.leftActions : widget.rightActions;
final fallback = actions.isNotEmpty ? actions.first : SwipeAction.none;
final defaultColor = fallback.getColor(context);
final double leadingThreshold = widget.actionThresholds.isNotEmpty ? widget.actionThresholds.first : 1.0;
final backgroundColor = _currentAction != null ? _currentAction!.getColor(context) : defaultColor.withValues(alpha: leadingThreshold == 0 ? 0 : (_progress / leadingThreshold).clamp(0.0, 1.0));
final fallback = actions.isNotEmpty ? actions.first : null;

final leadingThreshold = widget.actionThresholds.isNotEmpty ? widget.actionThresholds.first : 1.0;
final defaultColor = fallback?.color(context) ?? Theme.of(context).colorScheme.primaryContainer;
final backgroundColor = _currentAction != null ? _currentAction!.color(context) : defaultColor.withValues(alpha: leadingThreshold == 0 ? 0 : (_progress / leadingThreshold).clamp(0.0, 1.0));

final width = MediaQuery.of(context).size.width * widget.backgroundMaxWidthFactor * _progress;
final icon = _currentAction?.getIcon();
final icon = _currentAction?.icon;

return AnimatedContainer(
alignment: alignment,
Expand All @@ -174,7 +150,6 @@ class _MultiActionDismissibleState extends State<MultiActionDismissible> {
@override
Widget build(BuildContext context) {
final disabled = widget.direction == DismissDirection.none;

Widget content = widget.child;

if (!disabled) {
Expand All @@ -195,7 +170,7 @@ class _MultiActionDismissibleState extends State<MultiActionDismissible> {

return Listener(
behavior: HitTestBehavior.opaque,
onPointerDown: (_) => _handlePointerDown(),
onPointerDown: (_) => widget.onPointerDown?.call(),
onPointerMove: _handlePointerMove,
onPointerUp: (_) => _handlePointerUp(),
child: content,
Expand Down
Loading