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,135 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../../models/nextcloud_item.dart';
|
||||
import '../gradual_bottom_sheet.dart';
|
||||
import '../synced_header_scaffold.dart' show formatBytes;
|
||||
import 'details_activity_tab.dart';
|
||||
import 'details_info_tab.dart';
|
||||
import 'details_versions_tab.dart';
|
||||
|
||||
IconData _detailsItemIcon(NextcloudItemType type) {
|
||||
switch (type) {
|
||||
case NextcloudItemType.folder:
|
||||
return Icons.folder_rounded;
|
||||
case NextcloudItemType.image:
|
||||
return Icons.image_rounded;
|
||||
case NextcloudItemType.video:
|
||||
return Icons.movie_rounded;
|
||||
case NextcloudItemType.audio:
|
||||
return Icons.audiotrack_rounded;
|
||||
case NextcloudItemType.document:
|
||||
return Icons.description_rounded;
|
||||
case NextcloudItemType.archive:
|
||||
return Icons.folder_zip_rounded;
|
||||
case NextcloudItemType.file:
|
||||
return Icons.insert_drive_file_rounded;
|
||||
}
|
||||
}
|
||||
|
||||
/// Reusable "Details" bottom sheet for a single file/folder — Info,
|
||||
/// Versions, and Activity tabs. Sharing lives in its own sheet
|
||||
/// ([ShareSheet]), triggered separately from wherever a "Share" action
|
||||
/// appears. Triggered from long-press selection (exactly one item), the
|
||||
/// media viewer's action bar, and the "open externally" flow for
|
||||
/// unsupported file types.
|
||||
class DetailsSheet extends StatelessWidget {
|
||||
final NextcloudItem item;
|
||||
|
||||
const DetailsSheet({super.key, required this.item});
|
||||
|
||||
static Future<void> show(BuildContext context, NextcloudItem item) {
|
||||
return showGradualBottomSheet(
|
||||
context,
|
||||
builder: (context, scrollController) => DetailsSheet(item: item),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DefaultTabController(
|
||||
length: 3,
|
||||
child: Column(
|
||||
children: [
|
||||
DetailsHeader(item: item),
|
||||
const TabBar(
|
||||
tabs: [
|
||||
Tab(icon: Icon(Icons.info_outline_rounded), text: 'Info'),
|
||||
Tab(icon: Icon(Icons.history_rounded), text: 'Versions'),
|
||||
Tab(icon: Icon(Icons.electric_bolt_rounded), text: 'Activity'),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
children: [
|
||||
DetailsInfoTab(item: item),
|
||||
DetailsVersionsTab(item: item),
|
||||
DetailsActivityTab(item: item),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Icon/name/size/date row shared by [DetailsSheet] and the media viewer's
|
||||
/// collapsed "peek" state (which shows just this, before the user drags the
|
||||
/// sheet open far enough to reveal the tabs).
|
||||
class DetailsHeader extends StatelessWidget {
|
||||
final NextcloudItem item;
|
||||
|
||||
const DetailsHeader({super.key, required this.item});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final colorScheme = theme.colorScheme;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 12, 20, 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 44,
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(
|
||||
_detailsItemIcon(item.type),
|
||||
color: colorScheme.onPrimaryContainer,
|
||||
size: 22,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
item.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
item.isFolder
|
||||
? DateFormat.yMMMd().format(item.lastModified)
|
||||
: '${formatBytes(item.size)} • ${DateFormat.yMMMd().format(item.lastModified)}',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user