From 84cfe643f5544cdc02c7d1ee1fa0430d35659d8d Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Fri, 14 Nov 2025 01:04:15 +0800 Subject: [PATCH] :alien: Adopt the new folder system (w.i.p) --- lib/models/file_list_item.dart | 3 +- lib/models/file_list_item.freezed.dart | 43 ++-- lib/models/folder.dart | 19 ++ lib/models/folder.freezed.dart | 286 +++++++++++++++++++++++ lib/models/folder.g.dart | 27 +++ lib/pods/file_list.dart | 7 +- lib/screens/files/file_detail.dart | 172 ++++++++++---- lib/widgets/content/file_info_sheet.dart | 5 +- lib/widgets/content/sheet.dart | 8 +- lib/widgets/file_list_view.dart | 6 +- 10 files changed, 499 insertions(+), 77 deletions(-) create mode 100644 lib/models/folder.dart create mode 100644 lib/models/folder.freezed.dart create mode 100644 lib/models/folder.g.dart diff --git a/lib/models/file_list_item.dart b/lib/models/file_list_item.dart index 7e39e69a..9772c66e 100644 --- a/lib/models/file_list_item.dart +++ b/lib/models/file_list_item.dart @@ -1,10 +1,11 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:island/models/file.dart'; +import 'package:island/models/folder.dart'; part 'file_list_item.freezed.dart'; @freezed sealed class FileListItem with _$FileListItem { const factory FileListItem.file(SnCloudFileIndex fileIndex) = FileItem; - const factory FileListItem.folder(String name) = FolderItem; + const factory FileListItem.folder(SnCloudFolder folder) = FolderItem; } diff --git a/lib/models/file_list_item.freezed.dart b/lib/models/file_list_item.freezed.dart index 85415c65..555bc4f5 100644 --- a/lib/models/file_list_item.freezed.dart +++ b/lib/models/file_list_item.freezed.dart @@ -119,11 +119,11 @@ return folder(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen({TResult Function( SnCloudFileIndex fileIndex)? file,TResult Function( String name)? folder,required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen({TResult Function( SnCloudFileIndex fileIndex)? file,TResult Function( SnCloudFolder folder)? folder,required TResult orElse(),}) {final _that = this; switch (_that) { case FileItem() when file != null: return file(_that.fileIndex);case FolderItem() when folder != null: -return folder(_that.name);case _: +return folder(_that.folder);case _: return orElse(); } @@ -141,11 +141,11 @@ return folder(_that.name);case _: /// } /// ``` -@optionalTypeArgs TResult when({required TResult Function( SnCloudFileIndex fileIndex) file,required TResult Function( String name) folder,}) {final _that = this; +@optionalTypeArgs TResult when({required TResult Function( SnCloudFileIndex fileIndex) file,required TResult Function( SnCloudFolder folder) folder,}) {final _that = this; switch (_that) { case FileItem(): return file(_that.fileIndex);case FolderItem(): -return folder(_that.name);} +return folder(_that.folder);} } /// A variant of `when` that fallback to returning `null` /// @@ -159,11 +159,11 @@ return folder(_that.name);} /// } /// ``` -@optionalTypeArgs TResult? whenOrNull({TResult? Function( SnCloudFileIndex fileIndex)? file,TResult? Function( String name)? folder,}) {final _that = this; +@optionalTypeArgs TResult? whenOrNull({TResult? Function( SnCloudFileIndex fileIndex)? file,TResult? Function( SnCloudFolder folder)? folder,}) {final _that = this; switch (_that) { case FileItem() when file != null: return file(_that.fileIndex);case FolderItem() when folder != null: -return folder(_that.name);case _: +return folder(_that.folder);case _: return null; } @@ -250,10 +250,10 @@ $SnCloudFileIndexCopyWith<$Res> get fileIndex { class FolderItem implements FileListItem { - const FolderItem(this.name); + const FolderItem(this.folder); - final String name; + final SnCloudFolder folder; /// Create a copy of FileListItem /// with the given fields replaced by the non-null parameter values. @@ -265,16 +265,16 @@ $FolderItemCopyWith get copyWith => _$FolderItemCopyWithImpl Object.hash(runtimeType,name); +int get hashCode => Object.hash(runtimeType,folder); @override String toString() { - return 'FileListItem.folder(name: $name)'; + return 'FileListItem.folder(folder: $folder)'; } @@ -285,11 +285,11 @@ abstract mixin class $FolderItemCopyWith<$Res> implements $FileListItemCopyWith< factory $FolderItemCopyWith(FolderItem value, $Res Function(FolderItem) _then) = _$FolderItemCopyWithImpl; @useResult $Res call({ - String name + SnCloudFolder folder }); - +$SnCloudFolderCopyWith<$Res> get folder; } /// @nodoc @@ -302,14 +302,23 @@ class _$FolderItemCopyWithImpl<$Res> /// Create a copy of FileListItem /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') $Res call({Object? name = null,}) { +@pragma('vm:prefer-inline') $Res call({Object? folder = null,}) { return _then(FolderItem( -null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable -as String, +null == folder ? _self.folder : folder // ignore: cast_nullable_to_non_nullable +as SnCloudFolder, )); } - +/// Create a copy of FileListItem +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SnCloudFolderCopyWith<$Res> get folder { + + return $SnCloudFolderCopyWith<$Res>(_self.folder, (value) { + return _then(_self.copyWith(folder: value)); + }); +} } // dart format on diff --git a/lib/models/folder.dart b/lib/models/folder.dart new file mode 100644 index 00000000..1f9e08e9 --- /dev/null +++ b/lib/models/folder.dart @@ -0,0 +1,19 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'folder.freezed.dart'; +part 'folder.g.dart'; + +@freezed +sealed class SnCloudFolder with _$SnCloudFolder { + const factory SnCloudFolder({ + required String id, + required String name, + required String? parentFolderId, + required String accountId, + required DateTime createdAt, + required DateTime updatedAt, + }) = _SnCloudFolder; + + factory SnCloudFolder.fromJson(Map json) => + _$SnCloudFolderFromJson(json); +} diff --git a/lib/models/folder.freezed.dart b/lib/models/folder.freezed.dart new file mode 100644 index 00000000..c63359c7 --- /dev/null +++ b/lib/models/folder.freezed.dart @@ -0,0 +1,286 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'folder.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$SnCloudFolder { + + String get id; String get name; String? get parentFolderId; String get accountId; DateTime get createdAt; DateTime get updatedAt; +/// Create a copy of SnCloudFolder +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SnCloudFolderCopyWith get copyWith => _$SnCloudFolderCopyWithImpl(this as SnCloudFolder, _$identity); + + /// Serializes this SnCloudFolder to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SnCloudFolder&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.parentFolderId, parentFolderId) || other.parentFolderId == parentFolderId)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,name,parentFolderId,accountId,createdAt,updatedAt); + +@override +String toString() { + return 'SnCloudFolder(id: $id, name: $name, parentFolderId: $parentFolderId, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt)'; +} + + +} + +/// @nodoc +abstract mixin class $SnCloudFolderCopyWith<$Res> { + factory $SnCloudFolderCopyWith(SnCloudFolder value, $Res Function(SnCloudFolder) _then) = _$SnCloudFolderCopyWithImpl; +@useResult +$Res call({ + String id, String name, String? parentFolderId, String accountId, DateTime createdAt, DateTime updatedAt +}); + + + + +} +/// @nodoc +class _$SnCloudFolderCopyWithImpl<$Res> + implements $SnCloudFolderCopyWith<$Res> { + _$SnCloudFolderCopyWithImpl(this._self, this._then); + + final SnCloudFolder _self; + final $Res Function(SnCloudFolder) _then; + +/// Create a copy of SnCloudFolder +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? parentFolderId = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,parentFolderId: freezed == parentFolderId ? _self.parentFolderId : parentFolderId // ignore: cast_nullable_to_non_nullable +as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable +as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable +as DateTime, + )); +} + +} + + +/// Adds pattern-matching-related methods to [SnCloudFolder]. +extension SnCloudFolderPatterns on SnCloudFolder { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _SnCloudFolder value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SnCloudFolder() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _SnCloudFolder value) $default,){ +final _that = this; +switch (_that) { +case _SnCloudFolder(): +return $default(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _SnCloudFolder value)? $default,){ +final _that = this; +switch (_that) { +case _SnCloudFolder() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String name, String? parentFolderId, String accountId, DateTime createdAt, DateTime updatedAt)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _SnCloudFolder() when $default != null: +return $default(_that.id,_that.name,_that.parentFolderId,_that.accountId,_that.createdAt,_that.updatedAt);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String id, String name, String? parentFolderId, String accountId, DateTime createdAt, DateTime updatedAt) $default,) {final _that = this; +switch (_that) { +case _SnCloudFolder(): +return $default(_that.id,_that.name,_that.parentFolderId,_that.accountId,_that.createdAt,_that.updatedAt);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String name, String? parentFolderId, String accountId, DateTime createdAt, DateTime updatedAt)? $default,) {final _that = this; +switch (_that) { +case _SnCloudFolder() when $default != null: +return $default(_that.id,_that.name,_that.parentFolderId,_that.accountId,_that.createdAt,_that.updatedAt);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _SnCloudFolder implements SnCloudFolder { + const _SnCloudFolder({required this.id, required this.name, required this.parentFolderId, required this.accountId, required this.createdAt, required this.updatedAt}); + factory _SnCloudFolder.fromJson(Map json) => _$SnCloudFolderFromJson(json); + +@override final String id; +@override final String name; +@override final String? parentFolderId; +@override final String accountId; +@override final DateTime createdAt; +@override final DateTime updatedAt; + +/// Create a copy of SnCloudFolder +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SnCloudFolderCopyWith<_SnCloudFolder> get copyWith => __$SnCloudFolderCopyWithImpl<_SnCloudFolder>(this, _$identity); + +@override +Map toJson() { + return _$SnCloudFolderToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnCloudFolder&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.parentFolderId, parentFolderId) || other.parentFolderId == parentFolderId)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,name,parentFolderId,accountId,createdAt,updatedAt); + +@override +String toString() { + return 'SnCloudFolder(id: $id, name: $name, parentFolderId: $parentFolderId, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt)'; +} + + +} + +/// @nodoc +abstract mixin class _$SnCloudFolderCopyWith<$Res> implements $SnCloudFolderCopyWith<$Res> { + factory _$SnCloudFolderCopyWith(_SnCloudFolder value, $Res Function(_SnCloudFolder) _then) = __$SnCloudFolderCopyWithImpl; +@override @useResult +$Res call({ + String id, String name, String? parentFolderId, String accountId, DateTime createdAt, DateTime updatedAt +}); + + + + +} +/// @nodoc +class __$SnCloudFolderCopyWithImpl<$Res> + implements _$SnCloudFolderCopyWith<$Res> { + __$SnCloudFolderCopyWithImpl(this._self, this._then); + + final _SnCloudFolder _self; + final $Res Function(_SnCloudFolder) _then; + +/// Create a copy of SnCloudFolder +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? parentFolderId = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,}) { + return _then(_SnCloudFolder( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,parentFolderId: freezed == parentFolderId ? _self.parentFolderId : parentFolderId // ignore: cast_nullable_to_non_nullable +as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable +as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable +as DateTime, + )); +} + + +} + +// dart format on diff --git a/lib/models/folder.g.dart b/lib/models/folder.g.dart new file mode 100644 index 00000000..89967ea1 --- /dev/null +++ b/lib/models/folder.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'folder.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_SnCloudFolder _$SnCloudFolderFromJson(Map json) => + _SnCloudFolder( + id: json['id'] as String, + name: json['name'] as String, + parentFolderId: json['parent_folder_id'] as String?, + accountId: json['account_id'] as String, + createdAt: DateTime.parse(json['created_at'] as String), + updatedAt: DateTime.parse(json['updated_at'] as String), + ); + +Map _$SnCloudFolderToJson(_SnCloudFolder instance) => + { + 'id': instance.id, + 'name': instance.name, + 'parent_folder_id': instance.parentFolderId, + 'account_id': instance.accountId, + 'created_at': instance.createdAt.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), + }; diff --git a/lib/pods/file_list.dart b/lib/pods/file_list.dart index f89eede0..cbfb2bdd 100644 --- a/lib/pods/file_list.dart +++ b/lib/pods/file_list.dart @@ -1,6 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/file.dart'; import 'package:island/models/file_list_item.dart'; +import 'package:island/models/folder.dart'; import 'package:island/pods/network.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; @@ -31,8 +32,10 @@ class CloudFileListNotifier extends _$CloudFileListNotifier queryParameters: {'path': _currentPath}, ); - final List folders = - (response.data['folders'] as List).cast(); + final List folders = + (response.data['folders'] as List) + .map((e) => SnCloudFolder.fromJson(e as Map)) + .toList(); final List files = (response.data['files'] as List) .map((e) => SnCloudFileIndex.fromJson(e as Map)) diff --git a/lib/screens/files/file_detail.dart b/lib/screens/files/file_detail.dart index 5eda7b10..8cfccd4f 100644 --- a/lib/screens/files/file_detail.dart +++ b/lib/screens/files/file_detail.dart @@ -13,6 +13,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/file.dart'; import 'package:island/pods/config.dart'; import 'package:island/pods/network.dart'; +import 'package:island/services/responsive.dart'; import 'package:island/utils/format.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/widgets/app_scaffold.dart'; @@ -34,16 +35,55 @@ class FileDetailScreen extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final serverUrl = ref.watch(serverUrlProvider); + final isWide = isWideScreen(context); + + // Animation controller for the drawer + final animationController = useAnimationController( + duration: const Duration(milliseconds: 300), + ); + final animation = useMemoized( + () => Tween(begin: 0, end: 1).animate( + CurvedAnimation(parent: animationController, curve: Curves.easeInOut), + ), + [animationController], + ); + + final showDrawer = useState(false); void showInfoSheet() { - showModalBottomSheet( - useRootNavigator: true, - context: context, - isScrollControlled: true, - builder: (context) => FileInfoSheet(item: item), - ); + if (isWide) { + // Show as animated right panel on wide screens + showDrawer.value = !showDrawer.value; + if (showDrawer.value) { + animationController.forward(); + } else { + animationController.reverse(); + } + } else { + // Show as bottom sheet on narrow screens + showModalBottomSheet( + useRootNavigator: true, + context: context, + isScrollControlled: true, + builder: (context) => FileInfoSheet(item: item), + ); + } } + // Listen to drawer state changes + useEffect(() { + void listener() { + if (!animationController.isAnimating) { + if (animationController.value == 0) { + showDrawer.value = false; + } + } + } + + animationController.addListener(listener); + return () => animationController.removeListener(listener); + }, [animationController]); + return AppScaffold( isNoBackground: true, appBar: AppBar( @@ -55,7 +95,29 @@ class FileDetailScreen extends HookConsumerWidget { title: Text(item.name.isEmpty ? 'File Details' : item.name), actions: _buildAppBarActions(context, ref, showInfoSheet), ), - body: _buildContent(context, ref, serverUrl), + body: AnimatedBuilder( + animation: animation, + builder: (context, child) { + return Row( + children: [ + // Main content area + Expanded(child: _buildContent(context, ref, serverUrl)), + // Animated drawer panel + if (isWide) + SizedBox( + height: double.infinity, + width: animation.value * 400, // Max width of 400px + child: Container( + child: + animation.value > 0.1 + ? FileInfoSheet(item: item, onClose: showInfoSheet) + : const SizedBox.shrink(), + ), + ), + ], + ); + }, + ), ); } @@ -168,15 +230,9 @@ class FileDetailScreen extends HookConsumerWidget { 'image' => _buildImageContent(context, ref, uri), 'video' => _buildVideoContent(context, ref, uri), 'audio' => _buildAudioContent(context, ref, uri), - _ when item.mimeType == 'application/pdf' => _buildPdfContent( - context, - ref, - uri, - ), - _ when item.mimeType?.startsWith('text/') == true => _buildTextContent( - context, - ref, - uri, + _ when item.mimeType == 'application/pdf' => _PdfContent(uri: uri), + _ when item.mimeType?.startsWith('text/') == true => _TextContent( + uri: uri, ), _ => _buildGenericContent(context, ref), }; @@ -327,41 +383,6 @@ class FileDetailScreen extends HookConsumerWidget { ); } - Widget _buildPdfContent(BuildContext context, WidgetRef ref, String uri) { - final pdfViewer = useMemoized(() => SfPdfViewer.network(uri), [uri]); - return pdfViewer; - } - - Widget _buildTextContent(BuildContext context, WidgetRef ref, String uri) { - final textFuture = useMemoized( - () => ref - .read(apiClientProvider) - .get(uri) - .then((response) => response.data as String), - [uri], - ); - - return FutureBuilder( - future: textFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator()); - } else if (snapshot.hasError) { - return Center(child: Text('Error loading text: ${snapshot.error}')); - } else if (snapshot.hasData) { - return SingleChildScrollView( - padding: EdgeInsets.all(20), - child: SelectableText( - snapshot.data!, - style: const TextStyle(fontFamily: 'monospace', fontSize: 14), - ), - ); - } - return const Center(child: Text('No content')); - }, - ); - } - Widget _buildGenericContent(BuildContext context, WidgetRef ref) { Future downloadFile() async { try { @@ -466,3 +487,52 @@ class FileDetailScreen extends HookConsumerWidget { ); } } + +class _PdfContent extends HookConsumerWidget { + final String uri; + + const _PdfContent({required this.uri}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final pdfViewer = useMemoized(() => SfPdfViewer.network(uri), [uri]); + return pdfViewer; + } +} + +class _TextContent extends HookConsumerWidget { + final String uri; + + const _TextContent({required this.uri}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final textFuture = useMemoized( + () => ref + .read(apiClientProvider) + .get(uri) + .then((response) => response.data as String), + [uri], + ); + + return FutureBuilder( + future: textFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center(child: Text('Error loading text: ${snapshot.error}')); + } else if (snapshot.hasData) { + return SingleChildScrollView( + padding: EdgeInsets.all(20), + child: SelectableText( + snapshot.data!, + style: const TextStyle(fontFamily: 'monospace', fontSize: 14), + ), + ); + } + return const Center(child: Text('No content')); + }, + ); + } +} diff --git a/lib/widgets/content/file_info_sheet.dart b/lib/widgets/content/file_info_sheet.dart index c41667a1..079e5f6a 100644 --- a/lib/widgets/content/file_info_sheet.dart +++ b/lib/widgets/content/file_info_sheet.dart @@ -13,8 +13,8 @@ import 'package:url_launcher/url_launcher_string.dart'; class FileInfoSheet extends StatelessWidget { final SnCloudFile item; - - const FileInfoSheet({super.key, required this.item}); + final VoidCallback? onClose; + const FileInfoSheet({super.key, required this.item, this.onClose}); @override Widget build(BuildContext context) { @@ -22,6 +22,7 @@ class FileInfoSheet extends StatelessWidget { final exifData = item.fileMeta?['exif'] as Map? ?? {}; return SheetScaffold( + onClose: onClose, titleText: 'fileInfoTitle'.tr(), child: SingleChildScrollView( child: Column( diff --git a/lib/widgets/content/sheet.dart b/lib/widgets/content/sheet.dart index 65874ff7..22d915ae 100644 --- a/lib/widgets/content/sheet.dart +++ b/lib/widgets/content/sheet.dart @@ -8,6 +8,7 @@ class SheetScaffold extends StatelessWidget { final Widget child; final double heightFactor; final double? height; + final VoidCallback? onClose; const SheetScaffold({ super.key, this.title, @@ -16,6 +17,7 @@ class SheetScaffold extends StatelessWidget { this.actions = const [], this.heightFactor = 0.8, this.height, + this.onClose, }); @override @@ -50,7 +52,11 @@ class SheetScaffold extends StatelessWidget { ...actions, IconButton( icon: const Icon(Symbols.close), - onPressed: () => Navigator.pop(context), + onPressed: + () => + onClose != null + ? onClose?.call() + : Navigator.pop(context), style: IconButton.styleFrom(minimumSize: const Size(36, 36)), ), ], diff --git a/lib/widgets/file_list_view.dart b/lib/widgets/file_list_view.dart index f5aab075..9ab6c17e 100644 --- a/lib/widgets/file_list_view.dart +++ b/lib/widgets/file_list_view.dart @@ -160,7 +160,7 @@ class FileListView extends HookConsumerWidget { ), ), title: Text( - folderItem.name, + folderItem.folder.name, maxLines: 1, overflow: TextOverflow.ellipsis, ), @@ -169,8 +169,8 @@ class FileListView extends HookConsumerWidget { // Navigate to folder final newPath = currentPath.value == '/' - ? '/${folderItem.name}' - : '${currentPath.value}/${folderItem.name}'; + ? '/${folderItem.folder.name}' + : '${currentPath.value}/${folderItem.folder.name}'; currentPath.value = newPath; }, ),