Files
noo/lib/widgets/swipeable_item.dart
T
ayushyaandClaude Sonnet 5 d68cc43368 Add Trash/Shares/Recent tabs, file sharing, Details sheet, and rebrand to Noo
Major additions since the initial rebuild:
- New tabs: Trash, Shares, Recent, configurable tab visibility/order/default
- Nextcloud sharing: search users/teams, internal link, email shares,
  public links, "others with access" - in its own Share bottom sheet
- Details bottom sheet (Info/Versions/Activity) with a persistent peek
  panel in the media viewer, reachable from selection, swipe, and the
  media viewer
- Files/Photos: tap-and-hold multi-select with a sticky selection toolbar,
  swipe actions (favorite/delete/share), sticky sort/filter controls,
  list/grid views, breadcrumb chips, whole-account Photos view
- Media viewer: pinch/double-tap zoom on images, predictive-back support,
  fixed PDF zoom-out floor
- Settings: Material You theming incl. AMOLED black, configurable bottom
  bar tabs, swipe actions, media seek bar style, and folder-listing cache
  policy (never/periodic/manual) to cut down on refetching
- Login flow moved to an in-app Chrome Custom Tab with retry-tolerant
  polling
- Replaced ad hoc widgets with native Material 3 equivalents (toggle
  IconButtons, SliverAppBar selection toolbar, SearchBar, drag handles)
- Rebrand: app renamed to Noo throughout, package id -> dev.ayushya.noo,
  new adaptive launcher icon

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 22:44:42 -04:00

114 lines
3.6 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../providers/server_provider.dart';
/// Wraps [child] with the user's configured swipe-left/swipe-right actions
/// (favorite, delete, share, or none) — shared between the Files list view
/// and the Photos grid so both behave identically. Callers own the actual
/// side effects (favoriting, sharing, deleting); this widget only decides
/// which action a given swipe direction maps to and drives the gesture.
class SwipeableItem extends StatelessWidget {
final Key itemKey;
final ServerProvider provider;
final Future<void> Function() onFavorite;
final Future<void> Function() onShare;
final Future<void> Function() onDelete;
final Widget child;
const SwipeableItem({
super.key,
required this.itemKey,
required this.provider,
required this.onFavorite,
required this.onShare,
required this.onDelete,
required this.child,
});
static DismissDirection _directionFor(SwipeAction left, SwipeAction right) {
final hasLeft = left != SwipeAction.none;
final hasRight = right != SwipeAction.none;
if (hasLeft && hasRight) return DismissDirection.horizontal;
if (hasRight) return DismissDirection.startToEnd;
if (hasLeft) return DismissDirection.endToStart;
return DismissDirection.none;
}
static Widget _background(
BuildContext context,
SwipeAction action,
Alignment alignment,
) {
if (action == SwipeAction.none) return const SizedBox.shrink();
final colorScheme = Theme.of(context).colorScheme;
final Color color;
final IconData icon;
switch (action) {
case SwipeAction.delete:
color = colorScheme.error;
icon = Icons.delete_rounded;
case SwipeAction.favorite:
color = Colors.red.shade400;
icon = Icons.favorite_rounded;
case SwipeAction.share:
color = colorScheme.primary;
icon = Icons.share_rounded;
case SwipeAction.none:
return const SizedBox.shrink();
}
return Container(
color: color,
alignment: alignment,
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Icon(icon, color: Colors.white),
);
}
@override
Widget build(BuildContext context) {
final rightAction = provider.swipeRightAction;
final leftAction = provider.swipeLeftAction;
final direction = _directionFor(leftAction, rightAction);
if (direction == DismissDirection.none) return child;
return Dismissible(
key: itemKey,
direction: direction,
background: _background(context, rightAction, Alignment.centerLeft),
secondaryBackground: _background(
context,
leftAction,
Alignment.centerRight,
),
confirmDismiss: (dir) async {
final action = dir == DismissDirection.startToEnd
? rightAction
: leftAction;
switch (action) {
case SwipeAction.favorite:
HapticFeedback.selectionClick();
await onFavorite();
return false;
case SwipeAction.share:
HapticFeedback.selectionClick();
await onShare();
return false;
case SwipeAction.delete:
HapticFeedback.mediumImpact();
return true; // Actual delete happens in onDismissed.
case SwipeAction.none:
return false;
}
},
onDismissed: (dir) {
final action = dir == DismissDirection.startToEnd
? rightAction
: leftAction;
if (action == SwipeAction.delete) onDelete();
},
child: child,
);
}
}