import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../services/app_state.dart'; import '../services/cloud/cloud_storage_provider.dart'; enum _BrowseRoot { myFiles, sharedWithMe } /// Lets the user navigate whichever cloud storage provider is connected — /// their own files, and (if the provider supports it) files others have /// shared with them — and pick a parent location. Confirming looks for (or /// creates) the `Show Me The Fuel Refund` folder under that location, so /// two people pointing at the same shared parent converge on the same app /// folder, regardless of which provider each of them is using. Picking a /// folder that's already named that directly (e.g. a shared folder set up /// by someone else) uses it as-is instead of nesting another one inside — /// see `CloudSyncService.selectAppFolder`. class CloudFolderBrowserScreen extends StatefulWidget { const CloudFolderBrowserScreen({super.key}); @override State createState() => _CloudFolderBrowserScreenState(); } class _CloudFolderBrowserScreenState extends State { late final CloudStorageSession _session; _BrowseRoot _root = _BrowseRoot.myFiles; final List _pathStack = []; List? _folders; bool _loading = true; String? _error; bool _confirming = false; @override void initState() { super.initState(); _session = context.read().activeProvider!.beginSession(); _load(); } @override void dispose() { _session.close(); super.dispose(); } String get _rootLabel => _root == _BrowseRoot.myFiles ? 'My Files' : 'Shared with me'; /// The folder ID that "Use This Folder" would act on, or null if the /// current view is a virtual listing (top-level "Shared with me") rather /// than an actual folder. String? get _currentFolderId { if (_pathStack.isNotEmpty) return _pathStack.last.id; if (_root == _BrowseRoot.myFiles) return 'root'; return null; } /// The currently browsed-into folder's own name, so [_useThisFolder] can /// tell whether the user picked a folder already named /// `Show Me The Fuel Refund` — null at a virtual root ("My Files"), /// which is never itself named that. String? get _currentFolderName => _pathStack.isNotEmpty ? _pathStack.last.name : null; String get _breadcrumbPath => ([_rootLabel] + _pathStack.map((f) => f.name).toList()).join(' / '); Future _load() async { setState(() { _loading = true; _error = null; }); try { List folders; if (_pathStack.isNotEmpty) { folders = await _session.listFolders(parentId: _pathStack.last.id); } else if (_root == _BrowseRoot.myFiles) { folders = await _session.listFolders(parentId: 'root'); } else { folders = await _session.listFolders(sharedWithMe: true); } if (!mounted) return; setState(() { _folders = folders; _loading = false; }); } catch (e) { if (!mounted) return; setState(() { _error = 'Could not load folders: $e'; _loading = false; }); } } void _switchRoot(_BrowseRoot root) { if (root == _root) return; setState(() { _root = root; _pathStack.clear(); }); _load(); } void _openFolder(CloudFolder folder) { setState(() => _pathStack.add(folder)); _load(); } void _goToBreadcrumb(int index) { // index == -1 means the root label itself. setState(() { if (index < 0) { _pathStack.clear(); } else { _pathStack.removeRange(index + 1, _pathStack.length); } }); _load(); } Future _useThisFolder() async { final parentId = _currentFolderId; if (parentId == null) return; setState(() => _confirming = true); try { await context.read().chooseCloudFolder( parentId: parentId, breadcrumbPath: _breadcrumbPath, currentFolderName: _currentFolderName, ); if (mounted) Navigator.of(context).pop(); } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Could not use this folder: $e')), ); } } finally { if (mounted) setState(() => _confirming = false); } } @override Widget build(BuildContext context) { final providerName = context.read().activeProvider!.displayName; final canUseCurrentFolder = _currentFolderId != null; return Scaffold( appBar: AppBar(title: Text('Choose $providerName Folder')), body: Column( children: [ if (_session.supportsSharedWithMe) SegmentedButton<_BrowseRoot>( segments: const [ ButtonSegment(value: _BrowseRoot.myFiles, label: Text('My Files')), ButtonSegment(value: _BrowseRoot.sharedWithMe, label: Text('Shared with me')), ], selected: {_root}, onSelectionChanged: (selection) => _switchRoot(selection.first), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ TextButton( onPressed: () => _goToBreadcrumb(-1), child: Text(_rootLabel), ), for (var i = 0; i < _pathStack.length; i++) ...[ const Icon(Icons.chevron_right, size: 18), TextButton( onPressed: () => _goToBreadcrumb(i), child: Text(_pathStack[i].name), ), ], ], ), ), ), const Divider(height: 1), Expanded(child: _buildBody()), ], ), bottomNavigationBar: SafeArea( child: Padding( padding: const EdgeInsets.all(16), child: FilledButton.icon( onPressed: (!canUseCurrentFolder || _confirming) ? null : _useThisFolder, icon: _confirming ? const SizedBox(height: 16, width: 16, child: CircularProgressIndicator(strokeWidth: 2)) : const Icon(Icons.check), label: Text('Use "$_breadcrumbPath"'), ), ), ), ); } Widget _buildBody() { if (_loading) { return const Center(child: CircularProgressIndicator()); } if (_error != null) { return Center(child: Padding(padding: const EdgeInsets.all(24), child: Text(_error!))); } final folders = _folders ?? []; if (folders.isEmpty) { return const Center(child: Text('No folders here.')); } return ListView.builder( itemCount: folders.length, itemBuilder: (context, index) { final folder = folders[index]; return ListTile( leading: const Icon(Icons.folder_outlined), title: Text(folder.name), trailing: const Icon(Icons.chevron_right), onTap: () => _openFolder(folder), ); }, ); } }