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>
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user