♻️ Refactored the file detail
This commit is contained in:
@@ -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<void> 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<void> 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(),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user