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>
73 lines
2.4 KiB
Dart
73 lines
2.4 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
/// Tappable "Home / Folder / Sub-folder" trail shown whenever the user has
|
|
/// navigated below the root, in both the Files and Photos tabs.
|
|
class Breadcrumbs extends StatelessWidget {
|
|
final List<String> pathStack;
|
|
final ValueChanged<int> onTap;
|
|
|
|
const Breadcrumbs({super.key, required this.pathStack, required this.onTap});
|
|
|
|
String _labelFor(int index) {
|
|
if (index == 0) return 'Home';
|
|
final segments = pathStack[index]
|
|
.split('/')
|
|
.where((s) => s.isNotEmpty)
|
|
.toList();
|
|
return segments.isEmpty ? 'Home' : segments.last;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
final colorScheme = theme.colorScheme;
|
|
final lastIndex = pathStack.length - 1;
|
|
|
|
return SingleChildScrollView(
|
|
scrollDirection: Axis.horizontal,
|
|
child: Row(
|
|
children: [
|
|
for (var i = 0; i < pathStack.length; i++) ...[
|
|
if (i > 0)
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 2),
|
|
child: Icon(
|
|
Icons.chevron_right_rounded,
|
|
size: 16,
|
|
color: colorScheme.outlineVariant,
|
|
),
|
|
),
|
|
if (i == lastIndex)
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
child: Text(
|
|
_labelFor(i),
|
|
style: theme.textTheme.bodySmall?.copyWith(
|
|
fontWeight: FontWeight.w700,
|
|
color: colorScheme.onSurface,
|
|
),
|
|
),
|
|
)
|
|
else
|
|
// A chip's whole pill is the tap target (not just the text),
|
|
// so the previous plain-text crumbs were fiddly to hit.
|
|
ActionChip(
|
|
label: Text(_labelFor(i)),
|
|
labelStyle: theme.textTheme.bodySmall?.copyWith(
|
|
fontWeight: FontWeight.w600,
|
|
color: colorScheme.primary,
|
|
),
|
|
onPressed: () => onTap(i),
|
|
backgroundColor: colorScheme.surfaceContainerHigh,
|
|
side: BorderSide.none,
|
|
visualDensity: VisualDensity.compact,
|
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|