diff --git a/android/build.gradle b/android/build.gradle index 713d7f6..ce647a4 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.2.0' + classpath 'com.android.tools.build:gradle:7.4.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/lib/clients/inventory_client.dart b/lib/clients/inventory_client.dart index 0307beb..3482e6a 100644 --- a/lib/clients/inventory_client.dart +++ b/lib/clients/inventory_client.dart @@ -14,7 +14,10 @@ class InventoryClient extends ChangeNotifier { InventoryClient({required this.apiClient}); Future> _getDirectory(Record record) async { - final dir = await _currentDirectory; + NeosDirectory? dir; + try { + dir = await _currentDirectory; + } catch(_) {} final List records; if (dir == null || record.isRoot) { records = await RecordApi.getUserRecordsAt( @@ -84,8 +87,8 @@ class InventoryClient extends ChangeNotifier { notifyListeners(); } - Future navigateUp() async { - final dir = await _currentDirectory; + Future navigateUp({int times = 1}) async { + var dir = await _currentDirectory; if (dir == null) { throw "Failed to navigate up: No directory loaded."; } @@ -93,7 +96,11 @@ class InventoryClient extends ChangeNotifier { throw "Failed navigate up: Already at root"; } - _currentDirectory = Future.value(dir.parent); + for (int i = 0; i < times; i++) { + dir = dir?.parent; + } + + _currentDirectory = Future.value(dir); notifyListeners(); } } diff --git a/lib/widgets/inventory/inventory_browser.dart b/lib/widgets/inventory/inventory_browser.dart index d2ae8b5..4f7809b 100644 --- a/lib/widgets/inventory/inventory_browser.dart +++ b/lib/widgets/inventory/inventory_browser.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:cached_network_image/cached_network_image.dart'; +import 'package:collection/collection.dart'; import 'package:contacts_plus_plus/auxiliary.dart'; import 'package:contacts_plus_plus/clients/inventory_client.dart'; import 'package:contacts_plus_plus/models/inventory/neos_path.dart'; @@ -38,9 +39,8 @@ class _InventoryBrowserState extends State with AutomaticKeepA super.build(context); return ChangeNotifierProvider.value( value: Provider.of(context), - child: Consumer( - builder: (BuildContext context, InventoryClient iClient, Widget? child) { - return FutureBuilder( + child: Consumer(builder: (BuildContext context, InventoryClient iClient, Widget? child) { + return FutureBuilder( future: iClient.directoryFuture, builder: (context, snapshot) { final currentDir = snapshot.data; @@ -66,12 +66,12 @@ class _InventoryBrowserState extends State with AutomaticKeepA child: Builder( builder: (context) { if (snapshot.hasError) { - FlutterError.reportError(FlutterErrorDetails(exception: snapshot.error!, stack: snapshot.stackTrace)); + FlutterError.reportError( + FlutterErrorDetails(exception: snapshot.error!, stack: snapshot.stackTrace)); return DefaultErrorWidget( message: snapshot.error.toString(), - onRetry: () async { + onRetry: () { iClient.loadInventoryRoot(); - await iClient.directoryFuture; }, ); } @@ -80,36 +80,56 @@ class _InventoryBrowserState extends State with AutomaticKeepA records.sort((a, b) => a.name.compareTo(b.name)); final paths = records - .where((element) => element.recordType == RecordType.link || element.recordType == RecordType.directory) + .where((element) => + element.recordType == RecordType.link || element.recordType == RecordType.directory) .toList(); final objects = records - .where((element) => element.recordType != RecordType.link && element.recordType != RecordType.directory) + .where((element) => + element.recordType != RecordType.link && element.recordType != RecordType.directory) .toList(); + final pathSegments = directory?.absolutePathSegments ?? []; return Stack( children: [ ListView( children: [ Padding( - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), - child: Text( - directory?.absolutePath ?? NeosDirectory.rootName, - style: Theme - .of(context) - .textTheme - .labelLarge - ?.copyWith(color: Theme - .of(context) - .colorScheme - .primary), - ), - ), + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), + child: Wrap( + children: pathSegments + .mapIndexed( + (idx, segment) => Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (idx != 0) const Icon(Icons.chevron_right), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: TextButton( + style: TextButton.styleFrom( + foregroundColor: idx == pathSegments.length - 1 + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onSurface, + ), + onPressed: () { + iClient.navigateUp(times: pathSegments.length - 1 - idx); + }, + child: Text(segment), + ), + ), + ], + ), + ) + .toList(), + )), GridView.builder( padding: const EdgeInsets.symmetric(horizontal: 8.0), physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, itemCount: paths.length, gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: 256, childAspectRatio: 4, crossAxisSpacing: 8, mainAxisSpacing: 8), + maxCrossAxisExtent: 256, + childAspectRatio: 4, + crossAxisSpacing: 8, + mainAxisSpacing: 8), itemBuilder: (context, index) { final record = paths[index]; return PathInventoryTile( @@ -144,13 +164,12 @@ class _InventoryBrowserState extends State with AutomaticKeepA await Navigator.push( context, MaterialPageRoute( - builder: (context) => - PhotoView( - minScale: PhotoViewComputedScale.contained, - imageProvider: CachedNetworkImageProvider( - Aux.neosDbToHttp(record.thumbnailUri)), - heroAttributes: PhotoViewHeroAttributes(tag: record.id), - ), + builder: (context) => PhotoView( + minScale: PhotoViewComputedScale.contained, + imageProvider: + CachedNetworkImageProvider(Aux.neosDbToHttp(record.thumbnailUri)), + heroAttributes: PhotoViewHeroAttributes(tag: record.id), + ), ), ); }, @@ -169,26 +188,32 @@ class _InventoryBrowserState extends State with AutomaticKeepA ), ], ), - if (snapshot.connectionState == ConnectionState.waiting) - Align( - alignment: Alignment.center, - child: Container( + Align( + alignment: Alignment.topCenter, + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 250), + child: snapshot.connectionState == ConnectionState.waiting ? const LinearProgressIndicator() : null, + ), + ), + Align( + alignment: Alignment.topCenter, + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 250), + child: snapshot.connectionState == ConnectionState.waiting ? Container( width: double.infinity, height: double.infinity, color: Colors.black38, - child: const Center(child: CircularProgressIndicator()), - ), - ) + ) : null, + ), + ) ], ); }, ), ), ); - } - ); - } - ), + }); + }), ); }