💄 Optimize cloud file list
This commit is contained in:
		
							
								
								
									
										231
									
								
								lib/widgets/content/cloud_file_lightbox.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										231
									
								
								lib/widgets/content/cloud_file_lightbox.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,231 @@
 | 
			
		||||
import 'dart:io';
 | 
			
		||||
import 'dart:math' as math;
 | 
			
		||||
 | 
			
		||||
import 'package:dismissible_page/dismissible_page.dart';
 | 
			
		||||
import 'package:file_saver/file_saver.dart';
 | 
			
		||||
import 'package:flutter/foundation.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_hooks/flutter_hooks.dart';
 | 
			
		||||
import 'package:gal/gal.dart';
 | 
			
		||||
import 'package:gap/gap.dart';
 | 
			
		||||
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/widgets/alert.dart';
 | 
			
		||||
import 'package:island/widgets/content/file_info_sheet.dart';
 | 
			
		||||
import 'package:material_symbols_icons/symbols.dart';
 | 
			
		||||
import 'package:path/path.dart' show extension;
 | 
			
		||||
import 'package:path_provider/path_provider.dart';
 | 
			
		||||
import 'package:photo_view/photo_view.dart';
 | 
			
		||||
 | 
			
		||||
import 'cloud_files.dart';
 | 
			
		||||
 | 
			
		||||
class CloudFileLightbox extends HookConsumerWidget {
 | 
			
		||||
  final SnCloudFile item;
 | 
			
		||||
  final String heroTag;
 | 
			
		||||
  const CloudFileLightbox({
 | 
			
		||||
    super.key,
 | 
			
		||||
    required this.item,
 | 
			
		||||
    required this.heroTag,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context, WidgetRef ref) {
 | 
			
		||||
    final serverUrl = ref.watch(serverUrlProvider);
 | 
			
		||||
    final photoViewController = useMemoized(() => PhotoViewController(), []);
 | 
			
		||||
    final rotation = useState(0);
 | 
			
		||||
 | 
			
		||||
    final showOriginal = useState(false);
 | 
			
		||||
 | 
			
		||||
    Future<void> saveToGallery() async {
 | 
			
		||||
      try {
 | 
			
		||||
        // Show loading indicator
 | 
			
		||||
        showSnackBar('Saving image...');
 | 
			
		||||
 | 
			
		||||
        // Get the image URL
 | 
			
		||||
        final client = ref.watch(apiClientProvider);
 | 
			
		||||
 | 
			
		||||
        // Create a temporary file to save the image
 | 
			
		||||
        final tempDir = await getTemporaryDirectory();
 | 
			
		||||
        var extName = extension(item.name).trim();
 | 
			
		||||
        if (extName.isEmpty) {
 | 
			
		||||
          extName = item.mimeType?.split('/').lastOrNull ?? 'jpeg';
 | 
			
		||||
        }
 | 
			
		||||
        final filePath = '${tempDir.path}/${item.id}.$extName';
 | 
			
		||||
 | 
			
		||||
        await client.download(
 | 
			
		||||
          '/drive/files/${item.id}',
 | 
			
		||||
          filePath,
 | 
			
		||||
          queryParameters: {'original': true},
 | 
			
		||||
        );
 | 
			
		||||
        if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
 | 
			
		||||
          // Save to gallery
 | 
			
		||||
          await Gal.putImage(filePath, album: 'Solar Network');
 | 
			
		||||
          // Show success message
 | 
			
		||||
          showSnackBar('Image saved to gallery');
 | 
			
		||||
        } else {
 | 
			
		||||
          await FileSaver.instance.saveFile(
 | 
			
		||||
            name: item.name.isEmpty ? '${item.id}.$extName' : item.name,
 | 
			
		||||
            file: File(filePath),
 | 
			
		||||
          );
 | 
			
		||||
          showSnackBar('Image saved to $filePath');
 | 
			
		||||
        }
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        showErrorAlert(e);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void showInfoSheet() {
 | 
			
		||||
      showModalBottomSheet(
 | 
			
		||||
        useRootNavigator: true,
 | 
			
		||||
        context: context,
 | 
			
		||||
        isScrollControlled: true,
 | 
			
		||||
        builder: (context) => FileInfoSheet(item: item),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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,
 | 
			
		||||
              heroAttributes: PhotoViewHeroAttributes(tag: heroTag),
 | 
			
		||||
              imageProvider: CloudImageWidget.provider(
 | 
			
		||||
                fileId: item.id,
 | 
			
		||||
                serverUrl: serverUrl,
 | 
			
		||||
                original: showOriginal.value,
 | 
			
		||||
              ),
 | 
			
		||||
              // Apply rotation transformation
 | 
			
		||||
              customSize: MediaQuery.of(context).size,
 | 
			
		||||
              basePosition: Alignment.center,
 | 
			
		||||
              filterQuality: FilterQuality.high,
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
          // Close button and save button
 | 
			
		||||
          Positioned(
 | 
			
		||||
            top: MediaQuery.of(context).padding.top + 16,
 | 
			
		||||
            right: 16,
 | 
			
		||||
            left: 16,
 | 
			
		||||
            child: Row(
 | 
			
		||||
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
 | 
			
		||||
              children: [
 | 
			
		||||
                Row(
 | 
			
		||||
                  children: [
 | 
			
		||||
                    if (!kIsWeb)
 | 
			
		||||
                      IconButton(
 | 
			
		||||
                        icon: Icon(
 | 
			
		||||
                          Icons.save_alt,
 | 
			
		||||
                          color: Colors.white,
 | 
			
		||||
                          shadows: shadow,
 | 
			
		||||
                        ),
 | 
			
		||||
                        onPressed: () async {
 | 
			
		||||
                          saveToGallery();
 | 
			
		||||
                        },
 | 
			
		||||
                      ),
 | 
			
		||||
                    IconButton(
 | 
			
		||||
                      onPressed: () {
 | 
			
		||||
                        showOriginal.value = !showOriginal.value;
 | 
			
		||||
                      },
 | 
			
		||||
                      icon: Icon(
 | 
			
		||||
                        showOriginal.value ? Symbols.hd : Symbols.sd,
 | 
			
		||||
                        color: Colors.white,
 | 
			
		||||
                        shadows: shadow,
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
                IconButton(
 | 
			
		||||
                  icon: Icon(Icons.close, color: Colors.white, shadows: shadow),
 | 
			
		||||
                  onPressed: () => Navigator.of(context).pop(),
 | 
			
		||||
                ),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
          // Rotation controls
 | 
			
		||||
          Positioned(
 | 
			
		||||
            bottom: MediaQuery.of(context).padding.bottom + 16,
 | 
			
		||||
            left: 16,
 | 
			
		||||
            right: 16,
 | 
			
		||||
            child: Row(
 | 
			
		||||
              children: [
 | 
			
		||||
                IconButton(
 | 
			
		||||
                  icon: Icon(
 | 
			
		||||
                    Icons.info_outline,
 | 
			
		||||
                    color: Colors.white,
 | 
			
		||||
                    shadows: shadow,
 | 
			
		||||
                  ),
 | 
			
		||||
                  onPressed: showInfoSheet,
 | 
			
		||||
                ),
 | 
			
		||||
                Spacer(),
 | 
			
		||||
                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(
 | 
			
		||||
                        color: Colors.black54,
 | 
			
		||||
                        blurRadius: 5.0,
 | 
			
		||||
                        offset: Offset(1.0, 1.0),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ],
 | 
			
		||||
                  ),
 | 
			
		||||
                  onPressed: () {
 | 
			
		||||
                    rotation.value = (rotation.value + 1) % 4;
 | 
			
		||||
                    photoViewController.rotation =
 | 
			
		||||
                        rotation.value * -math.pi / 2;
 | 
			
		||||
                  },
 | 
			
		||||
                ),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user