diff --git a/lib/screens/files/file_detail.dart b/lib/screens/files/file_detail.dart index 8cfccd4f..d218b816 100644 --- a/lib/screens/files/file_detail.dart +++ b/lib/screens/files/file_detail.dart @@ -1,7 +1,6 @@ import 'dart:io'; import 'dart:math' as math; -import 'package:dismissible_page/dismissible_page.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:file_saver/file_saver.dart'; import 'package:flutter/foundation.dart'; @@ -227,265 +226,16 @@ class FileDetailScreen extends HookConsumerWidget { final uri = '$serverUrl/drive/files/${item.id}'; return switch (item.mimeType?.split('/').firstOrNull) { - 'image' => _buildImageContent(context, ref, uri), - 'video' => _buildVideoContent(context, ref, uri), - 'audio' => _buildAudioContent(context, ref, uri), + 'image' => _ImageContent(item: item, uri: uri), + 'video' => _VideoContent(item: item, uri: uri), + 'audio' => _AudioContent(item: item, uri: uri), _ when item.mimeType == 'application/pdf' => _PdfContent(uri: uri), _ when item.mimeType?.startsWith('text/') == true => _TextContent( uri: uri, ), - _ => _buildGenericContent(context, ref), + _ => _GenericContent(item: item), }; } - - Widget _buildImageContent(BuildContext context, WidgetRef ref, String uri) { - final photoViewController = useMemoized(() => PhotoViewController(), []); - final rotation = useState(0); - final showOriginal = useState(false); - - final shadow = [ - Shadow(color: Colors.black54, blurRadius: 5.0, offset: Offset(1.0, 1.0)), - ]; - - return DismissiblePage( - isFullScreen: true, - backgroundColor: Colors.transparent, - direction: DismissiblePageDismissDirection.down, - onDismissed: () { - Navigator.of(context).pop(); - }, - child: Stack( - children: [ - Positioned.fill( - child: PhotoView( - backgroundDecoration: BoxDecoration( - color: Colors.black.withOpacity(0.9), - ), - controller: photoViewController, - imageProvider: CloudImageWidget.provider( - fileId: item.id, - serverUrl: ref.watch(serverUrlProvider), - original: showOriginal.value, - ), - customSize: MediaQuery.of(context).size, - basePosition: Alignment.center, - filterQuality: FilterQuality.high, - ), - ), - // Controls overlay - Positioned( - bottom: MediaQuery.of(context).padding.bottom + 16, - left: 16, - right: 16, - child: Row( - children: [ - IconButton( - icon: Icon( - Icons.remove, - color: Colors.white, - shadows: shadow, - ), - onPressed: () { - photoViewController.scale = - (photoViewController.scale ?? 1) - 0.05; - }, - ), - IconButton( - icon: Icon(Icons.add, color: Colors.white, shadows: shadow), - onPressed: () { - photoViewController.scale = - (photoViewController.scale ?? 1) + 0.05; - }, - ), - const Gap(8), - IconButton( - icon: Icon( - Icons.rotate_left, - color: Colors.white, - shadows: shadow, - ), - onPressed: () { - rotation.value = (rotation.value - 1) % 4; - photoViewController.rotation = - rotation.value * -math.pi / 2; - }, - ), - IconButton( - icon: Icon( - Icons.rotate_right, - color: Colors.white, - shadows: shadow, - ), - onPressed: () { - rotation.value = (rotation.value + 1) % 4; - photoViewController.rotation = - rotation.value * -math.pi / 2; - }, - ), - const Spacer(), - IconButton( - onPressed: () { - showOriginal.value = !showOriginal.value; - }, - icon: Icon( - showOriginal.value ? Symbols.hd : Symbols.sd, - color: Colors.white, - shadows: shadow, - ), - ), - ], - ), - ), - ], - ), - ); - } - - Widget _buildVideoContent(BuildContext context, WidgetRef ref, String uri) { - var ratio = - item.fileMeta?['ratio'] is num - ? item.fileMeta!['ratio'].toDouble() - : 1.0; - if (ratio == 0) ratio = 1.0; - - return DismissiblePage( - isFullScreen: true, - backgroundColor: Colors.black, - direction: DismissiblePageDismissDirection.down, - onDismissed: () { - Navigator.of(context).pop(); - }, - child: Center( - child: AspectRatio( - aspectRatio: ratio, - child: UniversalVideo(uri: uri, autoplay: true), - ), - ), - ); - } - - Widget _buildAudioContent(BuildContext context, WidgetRef ref, String uri) { - return DismissiblePage( - isFullScreen: true, - backgroundColor: Theme.of(context).colorScheme.surface, - direction: DismissiblePageDismissDirection.down, - onDismissed: () { - Navigator.of(context).pop(); - }, - child: Center( - child: ConstrainedBox( - constraints: BoxConstraints( - maxWidth: math.min(360, MediaQuery.of(context).size.width * 0.8), - ), - child: UniversalAudio(uri: uri, filename: item.name), - ), - ), - ); - } - - Widget _buildGenericContent(BuildContext context, WidgetRef ref) { - Future downloadFile() async { - try { - showSnackBar('Downloading file...'); - - final client = ref.read(apiClientProvider); - final tempDir = await getTemporaryDirectory(); - var extName = extension(item.name).trim(); - if (extName.isEmpty) { - extName = item.mimeType?.split('/').lastOrNull ?? 'bin'; - } - final filePath = '${tempDir.path}/${item.id}.$extName'; - - await client.download( - '/drive/files/${item.id}', - filePath, - queryParameters: {'original': true}, - ); - - await FileSaver.instance.saveFile( - name: item.name.isEmpty ? '${item.id}.$extName' : item.name, - file: File(filePath), - ); - showSnackBar('File saved to downloads'); - } catch (e) { - showErrorAlert(e); - } - } - - return DismissiblePage( - isFullScreen: true, - backgroundColor: Theme.of(context).colorScheme.surface, - direction: DismissiblePageDismissDirection.down, - onDismissed: () { - Navigator.of(context).pop(); - }, - child: Center( - child: Container( - margin: const EdgeInsets.all(32), - padding: const EdgeInsets.all(32), - decoration: BoxDecoration( - border: Border.all( - color: Theme.of(context).colorScheme.outline, - width: 1, - ), - borderRadius: BorderRadius.circular(16), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Symbols.insert_drive_file, - size: 64, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - const Gap(16), - Text( - item.name, - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.onSurface, - ), - textAlign: TextAlign.center, - ), - const Gap(8), - Text( - formatFileSize(item.size), - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - const Gap(24), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - FilledButton.icon( - onPressed: downloadFile, - icon: const Icon(Symbols.download), - label: Text('download').tr(), - ), - const Gap(16), - OutlinedButton.icon( - onPressed: () { - showModalBottomSheet( - useRootNavigator: true, - context: context, - isScrollControlled: true, - builder: (context) => FileInfoSheet(item: item), - ); - }, - icon: const Icon(Symbols.info), - label: Text('info').tr(), - ), - ], - ), - ], - ), - ), - ), - ); - } } class _PdfContent extends HookConsumerWidget { @@ -536,3 +286,245 @@ class _TextContent extends HookConsumerWidget { ); } } + +class _ImageContent extends HookConsumerWidget { + final SnCloudFile item; + final String uri; + + const _ImageContent({required this.item, required this.uri}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final photoViewController = useMemoized(() => PhotoViewController(), []); + final rotation = useState(0); + final showOriginal = useState(false); + + final shadow = [ + Shadow(color: Colors.black54, blurRadius: 5.0, offset: Offset(1.0, 1.0)), + ]; + + return Stack( + children: [ + Positioned.fill( + child: PhotoView( + backgroundDecoration: BoxDecoration( + color: Colors.black.withOpacity(0.9), + ), + controller: photoViewController, + imageProvider: CloudImageWidget.provider( + fileId: item.id, + serverUrl: ref.watch(serverUrlProvider), + original: showOriginal.value, + ), + customSize: MediaQuery.of(context).size, + basePosition: Alignment.center, + filterQuality: FilterQuality.high, + ), + ), + // Controls overlay + Positioned( + bottom: MediaQuery.of(context).padding.bottom + 16, + left: 16, + right: 16, + child: Row( + children: [ + IconButton( + icon: Icon(Icons.remove, color: Colors.white, shadows: shadow), + onPressed: () { + photoViewController.scale = + (photoViewController.scale ?? 1) - 0.05; + }, + ), + IconButton( + icon: Icon(Icons.add, color: Colors.white, shadows: shadow), + onPressed: () { + photoViewController.scale = + (photoViewController.scale ?? 1) + 0.05; + }, + ), + const Gap(8), + IconButton( + icon: Icon( + Icons.rotate_left, + color: Colors.white, + shadows: shadow, + ), + onPressed: () { + rotation.value = (rotation.value - 1) % 4; + photoViewController.rotation = rotation.value * -math.pi / 2; + }, + ), + IconButton( + icon: Icon( + Icons.rotate_right, + color: Colors.white, + shadows: shadow, + ), + onPressed: () { + rotation.value = (rotation.value + 1) % 4; + photoViewController.rotation = rotation.value * -math.pi / 2; + }, + ), + const Spacer(), + IconButton( + onPressed: () { + showOriginal.value = !showOriginal.value; + }, + icon: Icon( + showOriginal.value ? Symbols.hd : Symbols.sd, + color: Colors.white, + shadows: shadow, + ), + ), + ], + ), + ), + ], + ); + } +} + +class _VideoContent extends HookConsumerWidget { + final SnCloudFile item; + final String uri; + + const _VideoContent({required this.item, required this.uri}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + var ratio = + item.fileMeta?['ratio'] is num + ? item.fileMeta!['ratio'].toDouble() + : 1.0; + if (ratio == 0) ratio = 1.0; + + return Center( + child: AspectRatio( + aspectRatio: ratio, + child: UniversalVideo(uri: uri, autoplay: true), + ), + ); + } +} + +class _AudioContent extends HookConsumerWidget { + final SnCloudFile item; + final String uri; + + const _AudioContent({required this.item, required this.uri}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Center( + child: ConstrainedBox( + constraints: BoxConstraints( + maxWidth: math.min(360, MediaQuery.of(context).size.width * 0.8), + ), + child: UniversalAudio(uri: uri, filename: item.name), + ), + ); + } +} + +class _GenericContent extends HookConsumerWidget { + final SnCloudFile item; + + const _GenericContent({required this.item}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + Future downloadFile() async { + try { + showSnackBar('Downloading file...'); + + final client = ref.read(apiClientProvider); + final tempDir = await getTemporaryDirectory(); + var extName = extension(item.name).trim(); + if (extName.isEmpty) { + extName = item.mimeType?.split('/').lastOrNull ?? 'bin'; + } + final filePath = '${tempDir.path}/${item.id}.$extName'; + + await client.download( + '/drive/files/${item.id}', + filePath, + queryParameters: {'original': true}, + ); + + await FileSaver.instance.saveFile( + name: item.name.isEmpty ? '${item.id}.$extName' : item.name, + file: File(filePath), + ); + showSnackBar('File saved to downloads'); + } catch (e) { + showErrorAlert(e); + } + } + + return Center( + child: Container( + margin: const EdgeInsets.all(32), + padding: const EdgeInsets.all(32), + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).colorScheme.outline, + width: 1, + ), + borderRadius: BorderRadius.circular(16), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Symbols.insert_drive_file, + size: 64, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + const Gap(16), + Text( + item.name, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.onSurface, + ), + textAlign: TextAlign.center, + ), + const Gap(8), + Text( + formatFileSize(item.size), + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + const Gap(24), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + FilledButton.icon( + onPressed: downloadFile, + icon: const Icon(Symbols.download), + label: Text('download').tr(), + ), + const Gap(16), + OutlinedButton.icon( + onPressed: () { + showModalBottomSheet( + useRootNavigator: true, + context: context, + isScrollControlled: true, + builder: (context) => FileInfoSheet(item: item), + ); + }, + icon: const Icon(Symbols.info), + label: Text('info').tr(), + ), + ], + ), + ], + ), + ), + ); + } +}