1290 lines
		
	
	
		
			42 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			1290 lines
		
	
	
		
			42 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
import 'dart:io';
 | 
						|
import 'package:flutter/material.dart';
 | 
						|
import 'package:flutter/services.dart';
 | 
						|
import 'package:gap/gap.dart';
 | 
						|
import 'package:go_router/go_router.dart';
 | 
						|
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
						|
import 'package:island/services/file_uploader.dart';
 | 
						|
import 'package:island/widgets/alert.dart';
 | 
						|
import 'package:island/widgets/content/sheet.dart';
 | 
						|
import 'package:material_symbols_icons/symbols.dart';
 | 
						|
import 'package:island/screens/posts/compose.dart';
 | 
						|
import 'package:island/models/file.dart';
 | 
						|
import 'package:island/pods/link_preview.dart';
 | 
						|
import 'package:island/pods/network.dart';
 | 
						|
import 'package:mime/mime.dart';
 | 
						|
import 'package:path/path.dart' as path;
 | 
						|
import 'package:island/models/chat.dart';
 | 
						|
import 'package:island/screens/chat/chat.dart';
 | 
						|
import 'package:island/widgets/content/cloud_files.dart';
 | 
						|
import 'package:easy_localization/easy_localization.dart';
 | 
						|
import 'package:share_plus/share_plus.dart';
 | 
						|
import 'package:styled_widget/styled_widget.dart';
 | 
						|
 | 
						|
enum ShareContentType { text, link, file }
 | 
						|
 | 
						|
class ShareContent {
 | 
						|
  final ShareContentType type;
 | 
						|
  final String? text;
 | 
						|
  final String? link;
 | 
						|
  final List<XFile>? files;
 | 
						|
 | 
						|
  const ShareContent({required this.type, this.text, this.link, this.files});
 | 
						|
 | 
						|
  ShareContent.text(String this.text)
 | 
						|
    : type = ShareContentType.text,
 | 
						|
      link = null,
 | 
						|
      files = null;
 | 
						|
 | 
						|
  ShareContent.link(String this.link)
 | 
						|
    : type = ShareContentType.link,
 | 
						|
      text = null,
 | 
						|
      files = null;
 | 
						|
 | 
						|
  ShareContent.files(List<XFile> this.files)
 | 
						|
    : type = ShareContentType.file,
 | 
						|
      text = null,
 | 
						|
      link = null;
 | 
						|
 | 
						|
  String get displayText {
 | 
						|
    switch (type) {
 | 
						|
      case ShareContentType.text:
 | 
						|
        return text ?? '';
 | 
						|
      case ShareContentType.link:
 | 
						|
        return link ?? '';
 | 
						|
      case ShareContentType.file:
 | 
						|
        return files?.map((f) => f.name).join(', ') ?? '';
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class ShareSheet extends ConsumerStatefulWidget {
 | 
						|
  final ShareContent content;
 | 
						|
  final String? title;
 | 
						|
  final bool toSystem;
 | 
						|
  final VoidCallback? onClose;
 | 
						|
 | 
						|
  const ShareSheet({
 | 
						|
    super.key,
 | 
						|
    required this.content,
 | 
						|
    this.title,
 | 
						|
    this.toSystem = false,
 | 
						|
    this.onClose,
 | 
						|
  });
 | 
						|
 | 
						|
  // Convenience constructors
 | 
						|
  ShareSheet.text({
 | 
						|
    Key? key,
 | 
						|
    required String text,
 | 
						|
    String? title,
 | 
						|
    bool toSystem = false,
 | 
						|
    VoidCallback? onClose,
 | 
						|
  }) : this(
 | 
						|
         key: key,
 | 
						|
         content: ShareContent.text(text),
 | 
						|
         title: title,
 | 
						|
         toSystem: toSystem,
 | 
						|
         onClose: onClose,
 | 
						|
       );
 | 
						|
 | 
						|
  ShareSheet.link({
 | 
						|
    Key? key,
 | 
						|
    required String link,
 | 
						|
    String? title,
 | 
						|
    bool toSystem = false,
 | 
						|
    VoidCallback? onClose,
 | 
						|
  }) : this(
 | 
						|
         key: key,
 | 
						|
         content: ShareContent.link(link),
 | 
						|
         title: title,
 | 
						|
         toSystem: toSystem,
 | 
						|
         onClose: onClose,
 | 
						|
       );
 | 
						|
 | 
						|
  ShareSheet.files({
 | 
						|
    Key? key,
 | 
						|
    required List<XFile> files,
 | 
						|
    String? title,
 | 
						|
    bool toSystem = false,
 | 
						|
    VoidCallback? onClose,
 | 
						|
  }) : this(
 | 
						|
         key: key,
 | 
						|
         content: ShareContent.files(files),
 | 
						|
         title: title,
 | 
						|
         toSystem: toSystem,
 | 
						|
         onClose: onClose,
 | 
						|
       );
 | 
						|
 | 
						|
  @override
 | 
						|
  ConsumerState<ShareSheet> createState() => _ShareSheetState();
 | 
						|
}
 | 
						|
 | 
						|
class _ShareSheetState extends ConsumerState<ShareSheet> {
 | 
						|
  bool _isLoading = false;
 | 
						|
  final TextEditingController _messageController = TextEditingController();
 | 
						|
  final Map<String, List<double>> _fileUploadProgress = {};
 | 
						|
 | 
						|
  @override
 | 
						|
  void dispose() {
 | 
						|
    _messageController.dispose();
 | 
						|
    super.dispose();
 | 
						|
  }
 | 
						|
 | 
						|
  Future<void> _shareToPost() async {
 | 
						|
    setState(() => _isLoading = true);
 | 
						|
    try {
 | 
						|
      // Convert ShareContent to PostComposeInitialState
 | 
						|
      String content = '';
 | 
						|
      List<UniversalFile> attachments = [];
 | 
						|
 | 
						|
      switch (widget.content.type) {
 | 
						|
        case ShareContentType.text:
 | 
						|
          content = widget.content.text ?? '';
 | 
						|
          break;
 | 
						|
        case ShareContentType.link:
 | 
						|
          content = widget.content.link ?? '';
 | 
						|
          break;
 | 
						|
        case ShareContentType.file:
 | 
						|
          if (widget.content.files != null) {
 | 
						|
            // Convert XFiles to UniversalFiles
 | 
						|
            for (final file in widget.content.files!) {
 | 
						|
              var mimeType = file.mimeType;
 | 
						|
              mimeType ??= lookupMimeType(file.path);
 | 
						|
 | 
						|
              UniversalFileType fileType;
 | 
						|
              if (mimeType?.startsWith('image/') == true) {
 | 
						|
                fileType = UniversalFileType.image;
 | 
						|
              } else if (mimeType?.startsWith('video/') == true) {
 | 
						|
                fileType = UniversalFileType.video;
 | 
						|
              } else if (mimeType?.startsWith('audio/') == true) {
 | 
						|
                fileType = UniversalFileType.audio;
 | 
						|
              } else {
 | 
						|
                fileType = UniversalFileType.file;
 | 
						|
              }
 | 
						|
 | 
						|
              attachments.add(UniversalFile(data: file, type: fileType));
 | 
						|
            }
 | 
						|
          }
 | 
						|
          break;
 | 
						|
      }
 | 
						|
 | 
						|
      final initialState = PostComposeInitialState(
 | 
						|
        title: widget.title,
 | 
						|
        content: content,
 | 
						|
        attachments: attachments,
 | 
						|
      );
 | 
						|
 | 
						|
      // Navigate to compose screen
 | 
						|
      if (mounted) {
 | 
						|
        context.pushNamed('postCompose', extra: initialState);
 | 
						|
        Navigator.of(context).pop(); // Close the share sheet
 | 
						|
      }
 | 
						|
    } catch (e) {
 | 
						|
      showErrorAlert(e);
 | 
						|
    } finally {
 | 
						|
      setState(() => _isLoading = false);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  Future<void> _shareToSpecificChat(SnChatRoom chatRoom) async {
 | 
						|
    setState(() => _isLoading = true);
 | 
						|
    try {
 | 
						|
      final apiClient = ref.read(apiClientProvider);
 | 
						|
 | 
						|
      String content = _messageController.text.trim();
 | 
						|
      List<String> attachmentIds = [];
 | 
						|
 | 
						|
      // Handle different content types
 | 
						|
      switch (widget.content.type) {
 | 
						|
        case ShareContentType.text:
 | 
						|
          if (content.isEmpty) {
 | 
						|
            content = widget.content.text ?? '';
 | 
						|
          } else if (widget.content.text?.isNotEmpty == true) {
 | 
						|
            content = '$content\n\n${widget.content.text}';
 | 
						|
          }
 | 
						|
          break;
 | 
						|
        case ShareContentType.link:
 | 
						|
          if (content.isEmpty) {
 | 
						|
            content = widget.content.link ?? '';
 | 
						|
          } else if (widget.content.link?.isNotEmpty == true) {
 | 
						|
            content = '$content\n\n${widget.content.link}';
 | 
						|
          }
 | 
						|
          break;
 | 
						|
        case ShareContentType.file:
 | 
						|
          // Upload files to cloud storage
 | 
						|
          if (widget.content.files?.isNotEmpty == true) {
 | 
						|
            final universalFiles =
 | 
						|
                widget.content.files!.map((file) {
 | 
						|
                  UniversalFileType fileType;
 | 
						|
                  if (file.mimeType?.startsWith('image/') == true) {
 | 
						|
                    fileType = UniversalFileType.image;
 | 
						|
                  } else if (file.mimeType?.startsWith('video/') == true) {
 | 
						|
                    fileType = UniversalFileType.video;
 | 
						|
                  } else if (file.mimeType?.startsWith('audio/') == true) {
 | 
						|
                    fileType = UniversalFileType.audio;
 | 
						|
                  } else {
 | 
						|
                    fileType = UniversalFileType.file;
 | 
						|
                  }
 | 
						|
                  return UniversalFile(data: file, type: fileType);
 | 
						|
                }).toList();
 | 
						|
 | 
						|
            // Initialize progress tracking
 | 
						|
            final messageId = DateTime.now().millisecondsSinceEpoch.toString();
 | 
						|
            _fileUploadProgress[messageId] = List.filled(
 | 
						|
              universalFiles.length,
 | 
						|
              0.0,
 | 
						|
            );
 | 
						|
 | 
						|
            // Upload each file
 | 
						|
            for (var idx = 0; idx < universalFiles.length; idx++) {
 | 
						|
              final file = universalFiles[idx];
 | 
						|
              final cloudFile =
 | 
						|
                  await FileUploader.createCloudFile(
 | 
						|
                    client: apiClient,
 | 
						|
                    fileData: file,
 | 
						|
                    onProgress: (progress, _) {
 | 
						|
                      if (mounted) {
 | 
						|
                        setState(() {
 | 
						|
                          _fileUploadProgress[messageId]?[idx] = progress;
 | 
						|
                        });
 | 
						|
                      }
 | 
						|
                    },
 | 
						|
                  ).future;
 | 
						|
 | 
						|
              if (cloudFile == null) {
 | 
						|
                throw Exception('Failed to upload file: ${file.data.name}');
 | 
						|
              }
 | 
						|
              attachmentIds.add(cloudFile.id);
 | 
						|
            }
 | 
						|
          }
 | 
						|
          break;
 | 
						|
      }
 | 
						|
 | 
						|
      if (content.isEmpty && attachmentIds.isEmpty) {
 | 
						|
        throw Exception('No content to share');
 | 
						|
      }
 | 
						|
 | 
						|
      // Send message to chat room
 | 
						|
      await apiClient.post(
 | 
						|
        '/sphere/chat/${chatRoom.id}/messages',
 | 
						|
        data: {'content': content, 'attachments_id': attachmentIds, 'meta': {}},
 | 
						|
      );
 | 
						|
 | 
						|
      if (mounted) {
 | 
						|
        // Show success message
 | 
						|
        showSnackBar(
 | 
						|
          'shareToSpecificChatSuccess'.tr(
 | 
						|
            args: [chatRoom.name ?? 'directChat'.tr()],
 | 
						|
          ),
 | 
						|
        );
 | 
						|
 | 
						|
        // Show navigation prompt
 | 
						|
        final shouldNavigate = await showDialog<bool>(
 | 
						|
          context: context,
 | 
						|
          builder:
 | 
						|
              (context) => AlertDialog(
 | 
						|
                title: Text('shareSuccess'.tr()),
 | 
						|
                content: Text('wouldYouLikeToGoToChat'.tr()),
 | 
						|
                actions: [
 | 
						|
                  TextButton(
 | 
						|
                    onPressed: () => Navigator.of(context).pop(false),
 | 
						|
                    child: Text('no'.tr()),
 | 
						|
                  ),
 | 
						|
                  TextButton(
 | 
						|
                    onPressed: () => Navigator.of(context).pop(true),
 | 
						|
                    child: Text('yes'.tr()),
 | 
						|
                  ),
 | 
						|
                ],
 | 
						|
              ),
 | 
						|
        );
 | 
						|
 | 
						|
        // Close the share sheet
 | 
						|
        if (mounted) {
 | 
						|
          Navigator.of(context).pop();
 | 
						|
        }
 | 
						|
 | 
						|
        // Navigate to chat if requested
 | 
						|
        if (shouldNavigate == true && mounted) {
 | 
						|
          context.push('/sphere/chat/${chatRoom.id}');
 | 
						|
        }
 | 
						|
      }
 | 
						|
    } catch (e) {
 | 
						|
      if (mounted) {
 | 
						|
        showSnackBar('Failed to share to chat: $e');
 | 
						|
      }
 | 
						|
    } finally {
 | 
						|
      if (mounted) {
 | 
						|
        setState(() => _isLoading = false);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  Future<void> _shareToSystem() async {
 | 
						|
    if (!widget.toSystem) return;
 | 
						|
 | 
						|
    final box = context.findRenderObject() as RenderBox?;
 | 
						|
 | 
						|
    setState(() => _isLoading = true);
 | 
						|
    try {
 | 
						|
      switch (widget.content.type) {
 | 
						|
        case ShareContentType.text:
 | 
						|
          if (widget.content.text?.isNotEmpty == true) {
 | 
						|
            await Share.share(
 | 
						|
              widget.content.text!,
 | 
						|
              sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
 | 
						|
            );
 | 
						|
          }
 | 
						|
          break;
 | 
						|
        case ShareContentType.link:
 | 
						|
          if (widget.content.link?.isNotEmpty == true) {
 | 
						|
            await Share.share(
 | 
						|
              widget.content.link!,
 | 
						|
              sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
 | 
						|
            );
 | 
						|
          }
 | 
						|
          break;
 | 
						|
        case ShareContentType.file:
 | 
						|
          if (widget.content.files?.isNotEmpty == true) {
 | 
						|
            await Share.shareXFiles(
 | 
						|
              widget.content.files!,
 | 
						|
              sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
 | 
						|
            );
 | 
						|
          }
 | 
						|
          break;
 | 
						|
      }
 | 
						|
    } catch (e) {
 | 
						|
      showErrorAlert(e);
 | 
						|
    } finally {
 | 
						|
      if (mounted) {
 | 
						|
        setState(() => _isLoading = false);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  Future<void> _copyToClipboard() async {
 | 
						|
    try {
 | 
						|
      String textToCopy = '';
 | 
						|
      switch (widget.content.type) {
 | 
						|
        case ShareContentType.text:
 | 
						|
          textToCopy = widget.content.text ?? '';
 | 
						|
          break;
 | 
						|
        case ShareContentType.link:
 | 
						|
          textToCopy = widget.content.link ?? '';
 | 
						|
          break;
 | 
						|
        case ShareContentType.file:
 | 
						|
          textToCopy =
 | 
						|
              widget.content.files?.map((f) => f.name).join('\n') ?? '';
 | 
						|
          break;
 | 
						|
      }
 | 
						|
 | 
						|
      await Clipboard.setData(ClipboardData(text: textToCopy));
 | 
						|
      if (mounted) showSnackBar('copyToClipboard'.tr());
 | 
						|
    } catch (e) {
 | 
						|
      showErrorAlert(e);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context) {
 | 
						|
    return SheetScaffold(
 | 
						|
      titleText: widget.title ?? 'share'.tr(),
 | 
						|
      heightFactor: 0.75,
 | 
						|
      child: Column(
 | 
						|
        children: [
 | 
						|
          // Share options with keyboard avoidance
 | 
						|
          Expanded(
 | 
						|
            child: SingleChildScrollView(
 | 
						|
              child: Column(
 | 
						|
                crossAxisAlignment: CrossAxisAlignment.start,
 | 
						|
                children: [
 | 
						|
                  // Content preview
 | 
						|
                  Container(
 | 
						|
                    margin: const EdgeInsets.all(16),
 | 
						|
                    padding: const EdgeInsets.all(16),
 | 
						|
                    decoration: BoxDecoration(
 | 
						|
                      color:
 | 
						|
                          Theme.of(context).colorScheme.surfaceContainerHighest,
 | 
						|
                      borderRadius: BorderRadius.circular(12),
 | 
						|
                    ),
 | 
						|
                    child: Column(
 | 
						|
                      crossAxisAlignment: CrossAxisAlignment.start,
 | 
						|
                      children: [
 | 
						|
                        Text(
 | 
						|
                          'contentToShare'.tr(),
 | 
						|
                          style: Theme.of(
 | 
						|
                            context,
 | 
						|
                          ).textTheme.labelMedium?.copyWith(
 | 
						|
                            color:
 | 
						|
                                Theme.of(context).colorScheme.onSurfaceVariant,
 | 
						|
                          ),
 | 
						|
                        ),
 | 
						|
                        const SizedBox(height: 8),
 | 
						|
                        _ContentPreview(content: widget.content),
 | 
						|
                      ],
 | 
						|
                    ),
 | 
						|
                  ),
 | 
						|
                  // Quick actions row (horizontally scrollable)
 | 
						|
                  Padding(
 | 
						|
                    padding: const EdgeInsets.symmetric(horizontal: 16),
 | 
						|
                    child: Column(
 | 
						|
                      crossAxisAlignment: CrossAxisAlignment.start,
 | 
						|
                      children: [
 | 
						|
                        Text(
 | 
						|
                          'quickActions'.tr(),
 | 
						|
                          style: Theme.of(
 | 
						|
                            context,
 | 
						|
                          ).textTheme.titleSmall?.copyWith(
 | 
						|
                            color:
 | 
						|
                                Theme.of(context).colorScheme.onSurfaceVariant,
 | 
						|
                          ),
 | 
						|
                        ),
 | 
						|
                        const SizedBox(height: 12),
 | 
						|
                        SizedBox(
 | 
						|
                          height: 80,
 | 
						|
                          child: ListView(
 | 
						|
                            scrollDirection: Axis.horizontal,
 | 
						|
                            children: [
 | 
						|
                              _CompactShareOption(
 | 
						|
                                icon: Symbols.post_add,
 | 
						|
                                title: 'post'.tr(),
 | 
						|
                                onTap: _isLoading ? null : _shareToPost,
 | 
						|
                              ),
 | 
						|
                              const SizedBox(width: 12),
 | 
						|
                              _CompactShareOption(
 | 
						|
                                icon: Symbols.content_copy,
 | 
						|
                                title: 'copy'.tr(),
 | 
						|
                                onTap: _isLoading ? null : _copyToClipboard,
 | 
						|
                              ),
 | 
						|
                              if (widget.toSystem) ...<Widget>[
 | 
						|
                                const SizedBox(width: 12),
 | 
						|
                                _CompactShareOption(
 | 
						|
                                  icon: Symbols.share,
 | 
						|
                                  title: 'share'.tr(),
 | 
						|
                                  onTap: _isLoading ? null : _shareToSystem,
 | 
						|
                                ),
 | 
						|
                              ],
 | 
						|
                            ],
 | 
						|
                          ),
 | 
						|
                        ),
 | 
						|
                      ],
 | 
						|
                    ),
 | 
						|
                  ),
 | 
						|
 | 
						|
                  const SizedBox(height: 24),
 | 
						|
 | 
						|
                  // Chat section
 | 
						|
                  Padding(
 | 
						|
                    padding: const EdgeInsets.symmetric(horizontal: 16),
 | 
						|
                    child: Column(
 | 
						|
                      crossAxisAlignment: CrossAxisAlignment.start,
 | 
						|
                      children: [
 | 
						|
                        Text(
 | 
						|
                          'sendToChat'.tr(),
 | 
						|
                          style: Theme.of(
 | 
						|
                            context,
 | 
						|
                          ).textTheme.titleSmall?.copyWith(
 | 
						|
                            color:
 | 
						|
                                Theme.of(context).colorScheme.onSurfaceVariant,
 | 
						|
                          ),
 | 
						|
                        ),
 | 
						|
                        const SizedBox(height: 12),
 | 
						|
 | 
						|
                        // Additional message input
 | 
						|
                        Container(
 | 
						|
                          margin: const EdgeInsets.only(bottom: 16),
 | 
						|
                          child: TextField(
 | 
						|
                            controller: _messageController,
 | 
						|
                            decoration: InputDecoration(
 | 
						|
                              hintText: 'addAdditionalMessage'.tr(),
 | 
						|
                              border: OutlineInputBorder(
 | 
						|
                                borderRadius: BorderRadius.circular(12),
 | 
						|
                              ),
 | 
						|
                              contentPadding: const EdgeInsets.symmetric(
 | 
						|
                                horizontal: 16,
 | 
						|
                                vertical: 12,
 | 
						|
                              ),
 | 
						|
                            ),
 | 
						|
                            onTapOutside:
 | 
						|
                                (_) =>
 | 
						|
                                    FocusManager.instance.primaryFocus
 | 
						|
                                        ?.unfocus(),
 | 
						|
                            maxLines: 3,
 | 
						|
                            minLines: 1,
 | 
						|
                            enabled: !_isLoading,
 | 
						|
                          ),
 | 
						|
                        ),
 | 
						|
 | 
						|
                        _ChatRoomsList(
 | 
						|
                          onChatSelected:
 | 
						|
                              _isLoading ? null : _shareToSpecificChat,
 | 
						|
                        ),
 | 
						|
                      ],
 | 
						|
                    ),
 | 
						|
                  ),
 | 
						|
 | 
						|
                  const SizedBox(height: 16),
 | 
						|
                ],
 | 
						|
              ),
 | 
						|
            ),
 | 
						|
          ),
 | 
						|
 | 
						|
          // Loading indicator and file upload progress
 | 
						|
          if (_isLoading)
 | 
						|
            Container(
 | 
						|
              padding: const EdgeInsets.all(16),
 | 
						|
              child: Column(
 | 
						|
                children: [
 | 
						|
                  const CircularProgressIndicator(),
 | 
						|
                  const SizedBox(height: 8),
 | 
						|
                  if (_fileUploadProgress.isNotEmpty)
 | 
						|
                    ..._fileUploadProgress.entries.map((entry) {
 | 
						|
                      final progress = entry.value;
 | 
						|
                      final averageProgress =
 | 
						|
                          progress.isEmpty
 | 
						|
                              ? 0.0
 | 
						|
                              : progress.reduce((a, b) => a + b) /
 | 
						|
                                  progress.length;
 | 
						|
                      return Column(
 | 
						|
                        children: [
 | 
						|
                          Text(
 | 
						|
                            'uploadingFiles'.tr(),
 | 
						|
                            style: Theme.of(context).textTheme.bodySmall,
 | 
						|
                          ),
 | 
						|
                          const SizedBox(height: 4),
 | 
						|
                          LinearProgressIndicator(value: averageProgress),
 | 
						|
                          const SizedBox(height: 4),
 | 
						|
                          Text(
 | 
						|
                            '${(averageProgress * 100).toInt()}%',
 | 
						|
                            style: Theme.of(context).textTheme.bodySmall,
 | 
						|
                          ),
 | 
						|
                        ],
 | 
						|
                      );
 | 
						|
                    }),
 | 
						|
                ],
 | 
						|
              ),
 | 
						|
            ),
 | 
						|
        ],
 | 
						|
      ),
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class _ChatRoomsList extends ConsumerWidget {
 | 
						|
  final Function(SnChatRoom)? onChatSelected;
 | 
						|
 | 
						|
  const _ChatRoomsList({this.onChatSelected});
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context, WidgetRef ref) {
 | 
						|
    final chatRooms = ref.watch(chatroomsJoinedProvider);
 | 
						|
 | 
						|
    return chatRooms.when(
 | 
						|
      data: (rooms) {
 | 
						|
        if (rooms.isEmpty) {
 | 
						|
          return Container(
 | 
						|
            height: 80,
 | 
						|
            decoration: BoxDecoration(
 | 
						|
              color: Theme.of(context).colorScheme.surfaceContainerHighest,
 | 
						|
              borderRadius: BorderRadius.circular(12),
 | 
						|
              border: Border.all(
 | 
						|
                color: Theme.of(context).colorScheme.outline.withOpacity(0.2),
 | 
						|
              ),
 | 
						|
            ),
 | 
						|
            child: Center(
 | 
						|
              child: Text(
 | 
						|
                'noChatRoomsAvailable'.tr(),
 | 
						|
                style: Theme.of(context).textTheme.bodyMedium?.copyWith(
 | 
						|
                  color: Theme.of(context).colorScheme.onSurfaceVariant,
 | 
						|
                ),
 | 
						|
              ),
 | 
						|
            ),
 | 
						|
          );
 | 
						|
        }
 | 
						|
 | 
						|
        return SizedBox(
 | 
						|
          height: 80,
 | 
						|
          child: ListView.separated(
 | 
						|
            scrollDirection: Axis.horizontal,
 | 
						|
            itemCount: rooms.length,
 | 
						|
            separatorBuilder: (context, index) => const SizedBox(width: 12),
 | 
						|
            itemBuilder: (context, index) {
 | 
						|
              final room = rooms[index];
 | 
						|
              return _ChatRoomOption(
 | 
						|
                room: room,
 | 
						|
                onTap:
 | 
						|
                    onChatSelected != null ? () => onChatSelected!(room) : null,
 | 
						|
              );
 | 
						|
            },
 | 
						|
          ),
 | 
						|
        );
 | 
						|
      },
 | 
						|
      loading:
 | 
						|
          () => SizedBox(
 | 
						|
            height: 80,
 | 
						|
            child: Center(
 | 
						|
              child: CircularProgressIndicator(
 | 
						|
                strokeWidth: 2,
 | 
						|
                color: Theme.of(context).colorScheme.primary,
 | 
						|
              ),
 | 
						|
            ),
 | 
						|
          ),
 | 
						|
      error:
 | 
						|
          (error, stack) => Container(
 | 
						|
            height: 80,
 | 
						|
            decoration: BoxDecoration(
 | 
						|
              color: Theme.of(context).colorScheme.errorContainer,
 | 
						|
              borderRadius: BorderRadius.circular(12),
 | 
						|
            ),
 | 
						|
            child: Center(
 | 
						|
              child: Text(
 | 
						|
                'failedToLoadChats'.tr(),
 | 
						|
                style: Theme.of(context).textTheme.bodyMedium?.copyWith(
 | 
						|
                  color: Theme.of(context).colorScheme.onErrorContainer,
 | 
						|
                ),
 | 
						|
              ),
 | 
						|
            ),
 | 
						|
          ),
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class _ChatRoomOption extends StatelessWidget {
 | 
						|
  final SnChatRoom room;
 | 
						|
  final VoidCallback? onTap;
 | 
						|
 | 
						|
  const _ChatRoomOption({required this.room, this.onTap});
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context) {
 | 
						|
    final isDirect = room.type == 1; // Assuming type 1 is direct chat
 | 
						|
    final displayName =
 | 
						|
        room.name ??
 | 
						|
        (isDirect && room.members != null
 | 
						|
            ? room.members!.map((m) => m.account.nick).join(', ')
 | 
						|
            : 'unknownChat'.tr());
 | 
						|
 | 
						|
    return GestureDetector(
 | 
						|
      onTap: onTap,
 | 
						|
      child: Container(
 | 
						|
        width: 72,
 | 
						|
        padding: const EdgeInsets.all(8),
 | 
						|
        decoration: BoxDecoration(
 | 
						|
          color:
 | 
						|
              onTap != null
 | 
						|
                  ? Theme.of(context).colorScheme.surfaceContainerHighest
 | 
						|
                  : Theme.of(
 | 
						|
                    context,
 | 
						|
                  ).colorScheme.surfaceContainerHighest.withOpacity(0.5),
 | 
						|
          borderRadius: BorderRadius.circular(12),
 | 
						|
          border: Border.all(
 | 
						|
            color: Theme.of(context).colorScheme.outline.withOpacity(0.2),
 | 
						|
          ),
 | 
						|
        ),
 | 
						|
        child: Column(
 | 
						|
          mainAxisAlignment: MainAxisAlignment.center,
 | 
						|
          children: [
 | 
						|
            // Chat room avatar
 | 
						|
            Container(
 | 
						|
              width: 32,
 | 
						|
              height: 32,
 | 
						|
              decoration: BoxDecoration(
 | 
						|
                color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
 | 
						|
                borderRadius: BorderRadius.circular(16),
 | 
						|
              ),
 | 
						|
              child:
 | 
						|
                  room.picture != null
 | 
						|
                      ? ClipRRect(
 | 
						|
                        borderRadius: BorderRadius.circular(16),
 | 
						|
                        child: CloudFileWidget(
 | 
						|
                          item: room.picture!,
 | 
						|
                          fit: BoxFit.cover,
 | 
						|
                        ),
 | 
						|
                      )
 | 
						|
                      : Icon(
 | 
						|
                        isDirect ? Symbols.person : Symbols.group,
 | 
						|
                        size: 20,
 | 
						|
                        color: Theme.of(context).colorScheme.primary,
 | 
						|
                      ),
 | 
						|
            ),
 | 
						|
            const SizedBox(height: 4),
 | 
						|
            // Chat room name
 | 
						|
            Text(
 | 
						|
              displayName,
 | 
						|
              style: Theme.of(context).textTheme.labelSmall?.copyWith(
 | 
						|
                color:
 | 
						|
                    onTap != null
 | 
						|
                        ? Theme.of(context).colorScheme.onSurface
 | 
						|
                        : Theme.of(
 | 
						|
                          context,
 | 
						|
                        ).colorScheme.onSurfaceVariant.withOpacity(0.5),
 | 
						|
              ),
 | 
						|
              textAlign: TextAlign.center,
 | 
						|
              maxLines: 1,
 | 
						|
              overflow: TextOverflow.ellipsis,
 | 
						|
            ),
 | 
						|
          ],
 | 
						|
        ),
 | 
						|
      ),
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class _CompactShareOption extends StatelessWidget {
 | 
						|
  final IconData icon;
 | 
						|
  final String title;
 | 
						|
  final VoidCallback? onTap;
 | 
						|
 | 
						|
  const _CompactShareOption({
 | 
						|
    required this.icon,
 | 
						|
    required this.title,
 | 
						|
    this.onTap,
 | 
						|
  });
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context) {
 | 
						|
    return GestureDetector(
 | 
						|
      onTap: onTap,
 | 
						|
      child: Container(
 | 
						|
        width: 72,
 | 
						|
        padding: const EdgeInsets.all(8),
 | 
						|
        decoration: BoxDecoration(
 | 
						|
          color:
 | 
						|
              onTap != null
 | 
						|
                  ? Theme.of(context).colorScheme.surfaceContainerHighest
 | 
						|
                  : Theme.of(
 | 
						|
                    context,
 | 
						|
                  ).colorScheme.surfaceContainerHighest.withOpacity(0.5),
 | 
						|
          borderRadius: BorderRadius.circular(12),
 | 
						|
          border: Border.all(
 | 
						|
            color: Theme.of(context).colorScheme.outline.withOpacity(0.2),
 | 
						|
          ),
 | 
						|
        ),
 | 
						|
        child: Column(
 | 
						|
          mainAxisAlignment: MainAxisAlignment.center,
 | 
						|
          children: [
 | 
						|
            Icon(
 | 
						|
              icon,
 | 
						|
              size: 24,
 | 
						|
              color:
 | 
						|
                  onTap != null
 | 
						|
                      ? Theme.of(context).colorScheme.primary
 | 
						|
                      : Theme.of(
 | 
						|
                        context,
 | 
						|
                      ).colorScheme.onSurfaceVariant.withOpacity(0.5),
 | 
						|
            ),
 | 
						|
            const SizedBox(height: 4),
 | 
						|
            Text(
 | 
						|
              title,
 | 
						|
              style: Theme.of(context).textTheme.labelSmall?.copyWith(
 | 
						|
                color:
 | 
						|
                    onTap != null
 | 
						|
                        ? Theme.of(context).colorScheme.onSurface
 | 
						|
                        : Theme.of(
 | 
						|
                          context,
 | 
						|
                        ).colorScheme.onSurfaceVariant.withOpacity(0.5),
 | 
						|
              ),
 | 
						|
              textAlign: TextAlign.center,
 | 
						|
              maxLines: 1,
 | 
						|
              overflow: TextOverflow.ellipsis,
 | 
						|
            ),
 | 
						|
          ],
 | 
						|
        ),
 | 
						|
      ),
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class _ContentPreview extends StatelessWidget {
 | 
						|
  final ShareContent content;
 | 
						|
 | 
						|
  const _ContentPreview({required this.content});
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context) {
 | 
						|
    switch (content.type) {
 | 
						|
      case ShareContentType.text:
 | 
						|
        return _TextPreview(text: content.text ?? '');
 | 
						|
      case ShareContentType.link:
 | 
						|
        return _LinkPreview(link: content.link ?? '');
 | 
						|
      case ShareContentType.file:
 | 
						|
        return _FilePreview(files: content.files ?? []);
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
const double kPreviewMaxHeight = 80;
 | 
						|
 | 
						|
class _TextPreview extends StatelessWidget {
 | 
						|
  final String text;
 | 
						|
 | 
						|
  const _TextPreview({required this.text});
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context) {
 | 
						|
    return Container(
 | 
						|
      constraints: const BoxConstraints(maxHeight: kPreviewMaxHeight),
 | 
						|
      child: Text(text, style: Theme.of(context).textTheme.bodyMedium),
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class _LinkPreview extends ConsumerWidget {
 | 
						|
  final String link;
 | 
						|
 | 
						|
  const _LinkPreview({required this.link});
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context, WidgetRef ref) {
 | 
						|
    final linkPreviewAsync = ref.watch(linkPreviewProvider(link));
 | 
						|
 | 
						|
    return linkPreviewAsync.when(
 | 
						|
      loading:
 | 
						|
          () => Container(
 | 
						|
            constraints: const BoxConstraints(maxHeight: kPreviewMaxHeight),
 | 
						|
            child: Row(
 | 
						|
              children: [
 | 
						|
                const SizedBox(
 | 
						|
                  width: 16,
 | 
						|
                  height: 16,
 | 
						|
                  child: CircularProgressIndicator(strokeWidth: 2),
 | 
						|
                ),
 | 
						|
                const SizedBox(width: 8),
 | 
						|
                Text(
 | 
						|
                  'Loading link preview...',
 | 
						|
                  style: Theme.of(context).textTheme.bodySmall?.copyWith(
 | 
						|
                    color: Theme.of(context).colorScheme.onSurfaceVariant,
 | 
						|
                  ),
 | 
						|
                ),
 | 
						|
              ],
 | 
						|
            ),
 | 
						|
          ),
 | 
						|
      error: (error, stackTrace) => _buildFallbackPreview(context),
 | 
						|
      data: (embed) {
 | 
						|
        if (embed == null) {
 | 
						|
          return _buildFallbackPreview(context);
 | 
						|
        }
 | 
						|
 | 
						|
        return Container(
 | 
						|
          constraints: const BoxConstraints(
 | 
						|
            maxHeight: 120,
 | 
						|
          ), // Increased height for rich preview
 | 
						|
          child: Row(
 | 
						|
            crossAxisAlignment: CrossAxisAlignment.start,
 | 
						|
            children: [
 | 
						|
              // Favicon and image
 | 
						|
              if (embed.imageUrl != null ||
 | 
						|
                  (embed.faviconUrl?.isNotEmpty ?? false))
 | 
						|
                Container(
 | 
						|
                  width: 60,
 | 
						|
                  height: 60,
 | 
						|
                  margin: const EdgeInsets.only(right: 12),
 | 
						|
                  decoration: BoxDecoration(
 | 
						|
                    borderRadius: BorderRadius.circular(8),
 | 
						|
                    color:
 | 
						|
                        Theme.of(context).colorScheme.surfaceContainerHighest,
 | 
						|
                  ),
 | 
						|
                  child: ClipRRect(
 | 
						|
                    borderRadius: BorderRadius.circular(8),
 | 
						|
                    child:
 | 
						|
                        embed.imageUrl != null
 | 
						|
                            ? Image.network(
 | 
						|
                              embed.imageUrl!,
 | 
						|
                              fit: BoxFit.cover,
 | 
						|
                              errorBuilder: (context, error, stackTrace) {
 | 
						|
                                return _buildFaviconFallback(
 | 
						|
                                  context,
 | 
						|
                                  embed.faviconUrl ?? '',
 | 
						|
                                );
 | 
						|
                              },
 | 
						|
                            )
 | 
						|
                            : _buildFaviconFallback(
 | 
						|
                              context,
 | 
						|
                              embed.faviconUrl ?? '',
 | 
						|
                            ),
 | 
						|
                  ),
 | 
						|
                ),
 | 
						|
              // Content
 | 
						|
              Expanded(
 | 
						|
                child: Column(
 | 
						|
                  crossAxisAlignment: CrossAxisAlignment.start,
 | 
						|
                  children: [
 | 
						|
                    // Site name
 | 
						|
                    if (embed.siteName?.isNotEmpty ?? false)
 | 
						|
                      Text(
 | 
						|
                        embed.siteName!,
 | 
						|
                        style: Theme.of(context).textTheme.labelSmall?.copyWith(
 | 
						|
                          color: Theme.of(context).colorScheme.primary,
 | 
						|
                        ),
 | 
						|
                        maxLines: 1,
 | 
						|
                        overflow: TextOverflow.ellipsis,
 | 
						|
                      ),
 | 
						|
                    // Title
 | 
						|
                    Text(
 | 
						|
                      embed.title,
 | 
						|
                      style: Theme.of(context).textTheme.titleSmall?.copyWith(
 | 
						|
                        fontWeight: FontWeight.w600,
 | 
						|
                      ),
 | 
						|
                      maxLines: 2,
 | 
						|
                      overflow: TextOverflow.ellipsis,
 | 
						|
                    ),
 | 
						|
                    // Description
 | 
						|
                    if (embed.description != null &&
 | 
						|
                        embed.description!.isNotEmpty)
 | 
						|
                      Padding(
 | 
						|
                        padding: const EdgeInsets.only(top: 4),
 | 
						|
                        child: Text(
 | 
						|
                          embed.description!,
 | 
						|
                          style: Theme.of(
 | 
						|
                            context,
 | 
						|
                          ).textTheme.bodySmall?.copyWith(
 | 
						|
                            color:
 | 
						|
                                Theme.of(context).colorScheme.onSurfaceVariant,
 | 
						|
                          ),
 | 
						|
                          maxLines: 2,
 | 
						|
                          overflow: TextOverflow.ellipsis,
 | 
						|
                        ),
 | 
						|
                      ),
 | 
						|
                    // URL
 | 
						|
                    Padding(
 | 
						|
                      padding: const EdgeInsets.only(top: 4),
 | 
						|
                      child: Text(
 | 
						|
                        embed.url,
 | 
						|
                        style: Theme.of(context).textTheme.labelSmall?.copyWith(
 | 
						|
                          color: Theme.of(context).colorScheme.onSurfaceVariant,
 | 
						|
                          decoration: TextDecoration.underline,
 | 
						|
                        ),
 | 
						|
                        maxLines: 1,
 | 
						|
                        overflow: TextOverflow.ellipsis,
 | 
						|
                      ),
 | 
						|
                    ),
 | 
						|
                  ],
 | 
						|
                ),
 | 
						|
              ),
 | 
						|
            ],
 | 
						|
          ),
 | 
						|
        );
 | 
						|
      },
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  Widget _buildFallbackPreview(BuildContext context) {
 | 
						|
    return Container(
 | 
						|
      constraints: const BoxConstraints(maxHeight: kPreviewMaxHeight),
 | 
						|
      child: Column(
 | 
						|
        crossAxisAlignment: CrossAxisAlignment.start,
 | 
						|
        children: [
 | 
						|
          Row(
 | 
						|
            children: [
 | 
						|
              Icon(
 | 
						|
                Symbols.link,
 | 
						|
                size: 16,
 | 
						|
                color: Theme.of(context).colorScheme.primary,
 | 
						|
              ),
 | 
						|
              const SizedBox(width: 8),
 | 
						|
              Text(
 | 
						|
                'Link',
 | 
						|
                style: Theme.of(context).textTheme.labelSmall?.copyWith(
 | 
						|
                  color: Theme.of(context).colorScheme.primary,
 | 
						|
                ),
 | 
						|
              ),
 | 
						|
              const Gap(6),
 | 
						|
              Text(
 | 
						|
                'Link embed was not loaded.',
 | 
						|
                style: Theme.of(context).textTheme.labelSmall,
 | 
						|
              ).opacity(0.75),
 | 
						|
            ],
 | 
						|
          ),
 | 
						|
          const SizedBox(height: 8),
 | 
						|
          Expanded(
 | 
						|
            child: SelectableText(
 | 
						|
              link,
 | 
						|
              style: Theme.of(context).textTheme.bodyMedium?.copyWith(
 | 
						|
                color: Theme.of(context).colorScheme.primary,
 | 
						|
                decoration: TextDecoration.underline,
 | 
						|
              ),
 | 
						|
            ),
 | 
						|
          ),
 | 
						|
        ],
 | 
						|
      ),
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  Widget _buildFaviconFallback(BuildContext context, String faviconUrl) {
 | 
						|
    if (faviconUrl.isNotEmpty) {
 | 
						|
      return Image.network(
 | 
						|
        faviconUrl,
 | 
						|
        fit: BoxFit.contain,
 | 
						|
        errorBuilder: (context, error, stackTrace) {
 | 
						|
          return Icon(
 | 
						|
            Symbols.link,
 | 
						|
            color: Theme.of(context).colorScheme.primary,
 | 
						|
            size: 24,
 | 
						|
          );
 | 
						|
        },
 | 
						|
      );
 | 
						|
    }
 | 
						|
    return Icon(
 | 
						|
      Symbols.link,
 | 
						|
      color: Theme.of(context).colorScheme.primary,
 | 
						|
      size: 24,
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class _FilePreview extends StatelessWidget {
 | 
						|
  final List<XFile> files;
 | 
						|
 | 
						|
  const _FilePreview({required this.files});
 | 
						|
 | 
						|
  bool _isImageFile(String fileName) {
 | 
						|
    final ext = path.extension(fileName).toLowerCase();
 | 
						|
    return ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'].contains(ext);
 | 
						|
  }
 | 
						|
 | 
						|
  bool _isVideoFile(String fileName) {
 | 
						|
    final ext = path.extension(fileName).toLowerCase();
 | 
						|
    return ['.mp4', '.mov', '.avi', '.mkv', '.webm'].contains(ext);
 | 
						|
  }
 | 
						|
 | 
						|
  IconData _getFileIcon(String fileName) {
 | 
						|
    final ext = path.extension(fileName).toLowerCase();
 | 
						|
 | 
						|
    if (_isImageFile(fileName)) return Symbols.image;
 | 
						|
    if (_isVideoFile(fileName)) return Symbols.video_file;
 | 
						|
 | 
						|
    switch (ext) {
 | 
						|
      case '.pdf':
 | 
						|
        return Symbols.picture_as_pdf;
 | 
						|
      case '.doc':
 | 
						|
      case '.docx':
 | 
						|
        return Symbols.description;
 | 
						|
      case '.xls':
 | 
						|
      case '.xlsx':
 | 
						|
        return Symbols.table_chart;
 | 
						|
      case '.ppt':
 | 
						|
      case '.pptx':
 | 
						|
        return Symbols.slideshow;
 | 
						|
      case '.zip':
 | 
						|
      case '.rar':
 | 
						|
      case '.7z':
 | 
						|
        return Symbols.folder_zip;
 | 
						|
      case '.txt':
 | 
						|
        return Symbols.text_snippet;
 | 
						|
      default:
 | 
						|
        return Symbols.attach_file;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context) {
 | 
						|
    return Container(
 | 
						|
      constraints: const BoxConstraints(maxHeight: kPreviewMaxHeight),
 | 
						|
      child: Column(
 | 
						|
        crossAxisAlignment: CrossAxisAlignment.start,
 | 
						|
        children: [
 | 
						|
          Row(
 | 
						|
            children: [
 | 
						|
              Icon(
 | 
						|
                Symbols.attach_file,
 | 
						|
                size: 16,
 | 
						|
                color: Theme.of(context).colorScheme.primary,
 | 
						|
              ),
 | 
						|
              const SizedBox(width: 8),
 | 
						|
              Text(
 | 
						|
                '${files.length} file${files.length > 1 ? 's' : ''}',
 | 
						|
                style: Theme.of(context).textTheme.labelSmall?.copyWith(
 | 
						|
                  color: Theme.of(context).colorScheme.primary,
 | 
						|
                ),
 | 
						|
              ),
 | 
						|
            ],
 | 
						|
          ),
 | 
						|
          const SizedBox(height: 8),
 | 
						|
          Expanded(
 | 
						|
            child: ListView.builder(
 | 
						|
              itemCount: files.length,
 | 
						|
              itemBuilder: (context, index) {
 | 
						|
                final file = files[index];
 | 
						|
                final isImage = _isImageFile(file.name);
 | 
						|
 | 
						|
                return Container(
 | 
						|
                  margin: const EdgeInsets.only(bottom: 8),
 | 
						|
                  padding: const EdgeInsets.all(8),
 | 
						|
                  decoration: BoxDecoration(
 | 
						|
                    color: Theme.of(context).colorScheme.surface,
 | 
						|
                    borderRadius: BorderRadius.circular(8),
 | 
						|
                    border: Border.all(
 | 
						|
                      color: Theme.of(
 | 
						|
                        context,
 | 
						|
                      ).colorScheme.outline.withOpacity(0.2),
 | 
						|
                    ),
 | 
						|
                  ),
 | 
						|
                  child: Row(
 | 
						|
                    children: [
 | 
						|
                      if (isImage)
 | 
						|
                        ClipRRect(
 | 
						|
                          borderRadius: BorderRadius.circular(4),
 | 
						|
                          child: Image.file(
 | 
						|
                            File(file.path),
 | 
						|
                            width: 40,
 | 
						|
                            height: 40,
 | 
						|
                            fit: BoxFit.cover,
 | 
						|
                            errorBuilder: (context, error, stackTrace) {
 | 
						|
                              return Container(
 | 
						|
                                width: 40,
 | 
						|
                                height: 40,
 | 
						|
                                color:
 | 
						|
                                    Theme.of(
 | 
						|
                                      context,
 | 
						|
                                    ).colorScheme.surfaceVariant,
 | 
						|
                                child: Icon(
 | 
						|
                                  Symbols.broken_image,
 | 
						|
                                  size: 20,
 | 
						|
                                  color:
 | 
						|
                                      Theme.of(
 | 
						|
                                        context,
 | 
						|
                                      ).colorScheme.onSurfaceVariant,
 | 
						|
                                ),
 | 
						|
                              );
 | 
						|
                            },
 | 
						|
                          ),
 | 
						|
                        )
 | 
						|
                      else
 | 
						|
                        Container(
 | 
						|
                          width: 40,
 | 
						|
                          height: 40,
 | 
						|
                          decoration: BoxDecoration(
 | 
						|
                            color:
 | 
						|
                                Theme.of(context).colorScheme.primaryContainer,
 | 
						|
                            borderRadius: BorderRadius.circular(4),
 | 
						|
                          ),
 | 
						|
                          child: Icon(
 | 
						|
                            _getFileIcon(file.name),
 | 
						|
                            size: 20,
 | 
						|
                            color:
 | 
						|
                                Theme.of(
 | 
						|
                                  context,
 | 
						|
                                ).colorScheme.onPrimaryContainer,
 | 
						|
                          ),
 | 
						|
                        ),
 | 
						|
                      const SizedBox(width: 12),
 | 
						|
                      Expanded(
 | 
						|
                        child: Column(
 | 
						|
                          crossAxisAlignment: CrossAxisAlignment.start,
 | 
						|
                          children: [
 | 
						|
                            Text(
 | 
						|
                              file.name,
 | 
						|
                              style: Theme.of(context).textTheme.bodyMedium,
 | 
						|
                              maxLines: 1,
 | 
						|
                              overflow: TextOverflow.ellipsis,
 | 
						|
                            ),
 | 
						|
                            FutureBuilder<int>(
 | 
						|
                              future: file.length(),
 | 
						|
                              builder: (context, snapshot) {
 | 
						|
                                if (snapshot.hasData) {
 | 
						|
                                  final size = snapshot.data!;
 | 
						|
                                  final sizeStr =
 | 
						|
                                      size < 1024
 | 
						|
                                          ? '${size}B'
 | 
						|
                                          : size < 1024 * 1024
 | 
						|
                                          ? '${(size / 1024).toStringAsFixed(1)}KB'
 | 
						|
                                          : '${(size / (1024 * 1024)).toStringAsFixed(1)}MB';
 | 
						|
                                  return Text(
 | 
						|
                                    sizeStr,
 | 
						|
                                    style: Theme.of(
 | 
						|
                                      context,
 | 
						|
                                    ).textTheme.bodySmall?.copyWith(
 | 
						|
                                      color:
 | 
						|
                                          Theme.of(
 | 
						|
                                            context,
 | 
						|
                                          ).colorScheme.onSurfaceVariant,
 | 
						|
                                    ),
 | 
						|
                                  );
 | 
						|
                                }
 | 
						|
                                return const SizedBox.shrink();
 | 
						|
                              },
 | 
						|
                            ),
 | 
						|
                          ],
 | 
						|
                        ),
 | 
						|
                      ),
 | 
						|
                    ],
 | 
						|
                  ),
 | 
						|
                );
 | 
						|
              },
 | 
						|
            ),
 | 
						|
          ),
 | 
						|
        ],
 | 
						|
      ),
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
// Helper functions to show the share sheet
 | 
						|
void showShareSheet({
 | 
						|
  required BuildContext context,
 | 
						|
  required ShareContent content,
 | 
						|
  String? title,
 | 
						|
  bool toSystem = false,
 | 
						|
  VoidCallback? onClose,
 | 
						|
}) {
 | 
						|
  showModalBottomSheet(
 | 
						|
    context: context,
 | 
						|
    isScrollControlled: true,
 | 
						|
    useRootNavigator: true,
 | 
						|
    builder:
 | 
						|
        (context) => ShareSheet(
 | 
						|
          content: content,
 | 
						|
          title: title,
 | 
						|
          toSystem: toSystem,
 | 
						|
          onClose: onClose,
 | 
						|
        ),
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
void showShareSheetText({
 | 
						|
  required BuildContext context,
 | 
						|
  required String text,
 | 
						|
  String? title,
 | 
						|
  bool toSystem = false,
 | 
						|
  VoidCallback? onClose,
 | 
						|
}) {
 | 
						|
  showShareSheet(
 | 
						|
    context: context,
 | 
						|
    content: ShareContent.text(text),
 | 
						|
    title: title,
 | 
						|
    toSystem: toSystem,
 | 
						|
    onClose: onClose,
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
void showShareSheetLink({
 | 
						|
  required BuildContext context,
 | 
						|
  required String link,
 | 
						|
  String? title,
 | 
						|
  bool toSystem = false,
 | 
						|
  VoidCallback? onClose,
 | 
						|
}) {
 | 
						|
  showShareSheet(
 | 
						|
    context: context,
 | 
						|
    content: ShareContent.link(link),
 | 
						|
    title: title,
 | 
						|
    toSystem: toSystem,
 | 
						|
    onClose: onClose,
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
void showShareSheetFiles({
 | 
						|
  required BuildContext context,
 | 
						|
  required List<XFile> files,
 | 
						|
  String? title,
 | 
						|
  bool toSystem = false,
 | 
						|
  VoidCallback? onClose,
 | 
						|
}) {
 | 
						|
  showShareSheet(
 | 
						|
    context: context,
 | 
						|
    content: ShareContent.files(files),
 | 
						|
    title: title,
 | 
						|
    toSystem: toSystem,
 | 
						|
    onClose: onClose,
 | 
						|
  );
 | 
						|
}
 |