203 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			203 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
import 'package:flutter/material.dart';
 | 
						|
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
						|
import 'package:island/models/file.dart';
 | 
						|
import 'package:island/services/responsive.dart';
 | 
						|
import 'package:island/widgets/attachment_uploader.dart';
 | 
						|
import 'package:island/widgets/content/attachment_preview.dart';
 | 
						|
import 'package:island/widgets/post/compose_shared.dart';
 | 
						|
 | 
						|
/// A reusable widget for displaying attachments in compose screens.
 | 
						|
/// Supports both grid and list layouts based on screen width.
 | 
						|
class ComposeAttachments extends ConsumerWidget {
 | 
						|
  final ComposeState state;
 | 
						|
  final bool isCompact;
 | 
						|
 | 
						|
  const ComposeAttachments({
 | 
						|
    super.key,
 | 
						|
    required this.state,
 | 
						|
    this.isCompact = false,
 | 
						|
  });
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context, WidgetRef ref) {
 | 
						|
    if (state.attachments.value.isEmpty) {
 | 
						|
      return const SizedBox.shrink();
 | 
						|
    }
 | 
						|
 | 
						|
    return LayoutBuilder(
 | 
						|
      builder: (context, constraints) {
 | 
						|
        final isWide = isWideScreen(context);
 | 
						|
        return isWide ? _buildWideGrid(ref) : _buildNarrowList(ref);
 | 
						|
      },
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  Widget _buildWideGrid(WidgetRef ref) {
 | 
						|
    return GridView.builder(
 | 
						|
      shrinkWrap: true,
 | 
						|
      padding: EdgeInsets.zero,
 | 
						|
      physics: const NeverScrollableScrollPhysics(),
 | 
						|
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
 | 
						|
        crossAxisCount: 2,
 | 
						|
        crossAxisSpacing: 8,
 | 
						|
        mainAxisSpacing: 8,
 | 
						|
      ),
 | 
						|
      itemCount: state.attachments.value.length,
 | 
						|
      itemBuilder: (context, idx) {
 | 
						|
        return _buildAttachmentItem(ref, idx, isCompact: true);
 | 
						|
      },
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  Widget _buildNarrowList(WidgetRef ref) {
 | 
						|
    return Column(
 | 
						|
      children: [
 | 
						|
        for (var idx = 0; idx < state.attachments.value.length; idx++)
 | 
						|
          Container(
 | 
						|
            margin: const EdgeInsets.only(bottom: 8),
 | 
						|
            child: _buildAttachmentItem(ref, idx, isCompact: false),
 | 
						|
          ),
 | 
						|
      ],
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  Widget _buildAttachmentItem(
 | 
						|
    WidgetRef ref,
 | 
						|
    int idx, {
 | 
						|
    required bool isCompact,
 | 
						|
  }) {
 | 
						|
    final progressMap = state.attachmentProgress.value;
 | 
						|
    return AttachmentPreview(
 | 
						|
      isCompact: isCompact,
 | 
						|
      item: state.attachments.value[idx],
 | 
						|
      progress: progressMap[idx],
 | 
						|
      onRequestUpload: () async {
 | 
						|
        final config = await showModalBottomSheet<AttachmentUploadConfig>(
 | 
						|
          context: ref.context,
 | 
						|
          isScrollControlled: true,
 | 
						|
          useRootNavigator: true,
 | 
						|
          builder:
 | 
						|
              (context) =>
 | 
						|
                  AttachmentUploaderSheet(ref: ref, state: state, index: idx),
 | 
						|
        );
 | 
						|
        if (config != null) {
 | 
						|
          await ComposeLogic.uploadAttachment(
 | 
						|
            ref,
 | 
						|
            state,
 | 
						|
            idx,
 | 
						|
            poolId: config.poolId,
 | 
						|
          );
 | 
						|
        }
 | 
						|
      },
 | 
						|
      onDelete: () => ComposeLogic.deleteAttachment(ref, state, idx),
 | 
						|
      onUpdate: (value) => ComposeLogic.updateAttachment(state, value, idx),
 | 
						|
      onMove: (delta) {
 | 
						|
        state.attachments.value = ComposeLogic.moveAttachment(
 | 
						|
          state.attachments.value,
 | 
						|
          idx,
 | 
						|
          delta,
 | 
						|
        );
 | 
						|
      },
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/// A specialized attachment widget for article compose with expansion tile.
 | 
						|
class ArticleComposeAttachments extends ConsumerWidget {
 | 
						|
  final ComposeState state;
 | 
						|
 | 
						|
  const ArticleComposeAttachments({super.key, required this.state});
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context, WidgetRef ref) {
 | 
						|
    return ValueListenableBuilder<List<UniversalFile>>(
 | 
						|
      valueListenable: state.attachments,
 | 
						|
      builder: (context, attachments, _) {
 | 
						|
        if (attachments.isEmpty) return const SizedBox.shrink();
 | 
						|
        return Theme(
 | 
						|
          data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
 | 
						|
          child: ExpansionTile(
 | 
						|
            initiallyExpanded: true,
 | 
						|
            title: Column(
 | 
						|
              crossAxisAlignment: CrossAxisAlignment.start,
 | 
						|
              children: [
 | 
						|
                Text('attachments'),
 | 
						|
                Text(
 | 
						|
                  'articleAttachmentHint',
 | 
						|
                  style: Theme.of(context).textTheme.bodySmall?.copyWith(
 | 
						|
                    color: Theme.of(context).colorScheme.onSurfaceVariant,
 | 
						|
                  ),
 | 
						|
                ),
 | 
						|
              ],
 | 
						|
            ),
 | 
						|
            children: [
 | 
						|
              ValueListenableBuilder<Map<int, double>>(
 | 
						|
                valueListenable: state.attachmentProgress,
 | 
						|
                builder: (context, progressMap, _) {
 | 
						|
                  return Wrap(
 | 
						|
                    runSpacing: 8,
 | 
						|
                    spacing: 8,
 | 
						|
                    children: [
 | 
						|
                      for (var idx = 0; idx < attachments.length; idx++)
 | 
						|
                        SizedBox(
 | 
						|
                          width: 180,
 | 
						|
                          height: 180,
 | 
						|
                          child: AttachmentPreview(
 | 
						|
                            isCompact: true,
 | 
						|
                            item: attachments[idx],
 | 
						|
                            progress: progressMap[idx],
 | 
						|
                            onRequestUpload: () async {
 | 
						|
                              final config = await showModalBottomSheet<
 | 
						|
                                AttachmentUploadConfig
 | 
						|
                              >(
 | 
						|
                                context: context,
 | 
						|
                                isScrollControlled: true,
 | 
						|
                                builder:
 | 
						|
                                    (context) => AttachmentUploaderSheet(
 | 
						|
                                      ref: ref,
 | 
						|
                                      state: state,
 | 
						|
                                      index: idx,
 | 
						|
                                    ),
 | 
						|
                              );
 | 
						|
                              if (config != null) {
 | 
						|
                                await ComposeLogic.uploadAttachment(
 | 
						|
                                  ref,
 | 
						|
                                  state,
 | 
						|
                                  idx,
 | 
						|
                                  poolId: config.poolId,
 | 
						|
                                );
 | 
						|
                              }
 | 
						|
                            },
 | 
						|
                            onUpdate:
 | 
						|
                                (value) => ComposeLogic.updateAttachment(
 | 
						|
                                  state,
 | 
						|
                                  value,
 | 
						|
                                  idx,
 | 
						|
                                ),
 | 
						|
                            onDelete:
 | 
						|
                                () => ComposeLogic.deleteAttachment(
 | 
						|
                                  ref,
 | 
						|
                                  state,
 | 
						|
                                  idx,
 | 
						|
                                ),
 | 
						|
                            onInsert:
 | 
						|
                                () => ComposeLogic.insertAttachment(
 | 
						|
                                  ref,
 | 
						|
                                  state,
 | 
						|
                                  idx,
 | 
						|
                                ),
 | 
						|
                          ),
 | 
						|
                        ),
 | 
						|
                    ],
 | 
						|
                  );
 | 
						|
                },
 | 
						|
              ),
 | 
						|
              const SizedBox(height: 16),
 | 
						|
            ],
 | 
						|
          ),
 | 
						|
        );
 | 
						|
      },
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 |