💄 Redesigned attachment zoom view
This commit is contained in:
		@@ -1,5 +1,4 @@
 | 
			
		||||
import 'dart:io';
 | 
			
		||||
import 'dart:math' show max;
 | 
			
		||||
 | 
			
		||||
import 'package:dio/dio.dart';
 | 
			
		||||
import 'package:dismissible_page/dismissible_page.dart';
 | 
			
		||||
@@ -48,11 +47,14 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
 | 
			
		||||
  bool _showOverlay = true;
 | 
			
		||||
  bool _dismissable = true;
 | 
			
		||||
 | 
			
		||||
  int _page = 0;
 | 
			
		||||
 | 
			
		||||
  void _updatePage() {
 | 
			
		||||
    setState(() {
 | 
			
		||||
      if (_isCompletedDownload) {
 | 
			
		||||
        setState(() => _isCompletedDownload = false);
 | 
			
		||||
      }
 | 
			
		||||
      _page = _pageController.page?.round() ?? 0;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -222,31 +224,11 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
 | 
			
		||||
                      BoxDecoration(color: Colors.transparent),
 | 
			
		||||
                );
 | 
			
		||||
              }),
 | 
			
		||||
              Positioned(
 | 
			
		||||
                top: max(MediaQuery.of(context).padding.top, 8),
 | 
			
		||||
                left: 14,
 | 
			
		||||
                child: IgnorePointer(
 | 
			
		||||
                  ignoring: !_showOverlay,
 | 
			
		||||
                  child: IconButton(
 | 
			
		||||
                    constraints: const BoxConstraints(),
 | 
			
		||||
                    icon: const Icon(Icons.close),
 | 
			
		||||
                    style: ButtonStyle(
 | 
			
		||||
                      backgroundColor: MaterialStateProperty.all(
 | 
			
		||||
                        Theme.of(context).colorScheme.surface.withOpacity(0.5),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                    onPressed: () {
 | 
			
		||||
                      Navigator.of(context).pop();
 | 
			
		||||
                    },
 | 
			
		||||
                  ).opacity(_showOverlay ? 1 : 0, animate: true).animate(
 | 
			
		||||
                      const Duration(milliseconds: 300), Curves.easeInOut),
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
              Align(
 | 
			
		||||
                alignment: Alignment.bottomCenter,
 | 
			
		||||
                child: IgnorePointer(
 | 
			
		||||
                  child: Container(
 | 
			
		||||
                    height: 300,
 | 
			
		||||
                    height: 200,
 | 
			
		||||
                    decoration: BoxDecoration(
 | 
			
		||||
                      gradient: LinearGradient(
 | 
			
		||||
                        begin: Alignment.bottomCenter,
 | 
			
		||||
@@ -269,153 +251,130 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
 | 
			
		||||
                child: Material(
 | 
			
		||||
                  color: Colors.transparent,
 | 
			
		||||
                  child: Builder(builder: (context) {
 | 
			
		||||
                    final ud = context.read<UserDirectoryProvider>();
 | 
			
		||||
                    final item = widget.data.elementAt(
 | 
			
		||||
                      widget.data.length > 1
 | 
			
		||||
                          ? _pageController.page?.round() ?? 0
 | 
			
		||||
                          : 0,
 | 
			
		||||
                    );
 | 
			
		||||
                    final account = ud.getFromCache(item.accountId);
 | 
			
		||||
 | 
			
		||||
                    return Column(
 | 
			
		||||
                      crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
                    return Row(
 | 
			
		||||
                      children: [
 | 
			
		||||
                        if (item.accountId > 0)
 | 
			
		||||
                          Row(
 | 
			
		||||
                            children: [
 | 
			
		||||
                              IgnorePointer(
 | 
			
		||||
                                child: AccountImage(
 | 
			
		||||
                                  content: account?.avatar,
 | 
			
		||||
                                  radius: 19,
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
                              const Gap(8),
 | 
			
		||||
                              Expanded(
 | 
			
		||||
                                child: IgnorePointer(
 | 
			
		||||
                                  child: Column(
 | 
			
		||||
                                    crossAxisAlignment:
 | 
			
		||||
                                        CrossAxisAlignment.start,
 | 
			
		||||
                                    children: [
 | 
			
		||||
                                      Text(
 | 
			
		||||
                                        'attachmentUploadBy'.tr(),
 | 
			
		||||
                                        style: Theme.of(context)
 | 
			
		||||
                                            .textTheme
 | 
			
		||||
                                            .bodySmall,
 | 
			
		||||
                                      ),
 | 
			
		||||
                                      Text(
 | 
			
		||||
                                        account?.nick ?? 'unknown'.tr(),
 | 
			
		||||
                                        style: Theme.of(context)
 | 
			
		||||
                                            .textTheme
 | 
			
		||||
                                            .bodyMedium,
 | 
			
		||||
                                      ),
 | 
			
		||||
                                    ],
 | 
			
		||||
                                  ),
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
                              if (widget.data.length > 1)
 | 
			
		||||
                                IgnorePointer(
 | 
			
		||||
                                  child: Text(
 | 
			
		||||
                                    '${(_pageController.page?.round() ?? 0) + 1}/${widget.data.length}',
 | 
			
		||||
                                    style: GoogleFonts.robotoMono(fontSize: 13),
 | 
			
		||||
                                  ).padding(right: 8),
 | 
			
		||||
                                ),
 | 
			
		||||
                              InkWell(
 | 
			
		||||
                                borderRadius:
 | 
			
		||||
                                    const BorderRadius.all(Radius.circular(16)),
 | 
			
		||||
                                onTap: _isDownloading
 | 
			
		||||
                                    ? null
 | 
			
		||||
                                    : () => _saveToAlbum(widget.data.length > 1
 | 
			
		||||
                                        ? _pageController.page?.round() ?? 0
 | 
			
		||||
                                        : 0),
 | 
			
		||||
                                child: Container(
 | 
			
		||||
                                  padding: const EdgeInsets.all(6),
 | 
			
		||||
                                  child: !_isDownloading
 | 
			
		||||
                                      ? !_isCompletedDownload
 | 
			
		||||
                                          ? const Icon(Symbols.save_alt)
 | 
			
		||||
                                          : const Icon(Symbols.download_done)
 | 
			
		||||
                                      : SizedBox(
 | 
			
		||||
                                          width: 24,
 | 
			
		||||
                                          height: 24,
 | 
			
		||||
                                          child: CircularProgressIndicator(
 | 
			
		||||
                                            value: _progressOfDownload,
 | 
			
		||||
                                            strokeWidth: 3,
 | 
			
		||||
                                          ),
 | 
			
		||||
                                        ),
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
                            ],
 | 
			
		||||
                          ),
 | 
			
		||||
                        const Gap(4),
 | 
			
		||||
                        IgnorePointer(
 | 
			
		||||
                          child: Text(
 | 
			
		||||
                            item.alt,
 | 
			
		||||
                            maxLines: 2,
 | 
			
		||||
                            overflow: TextOverflow.ellipsis,
 | 
			
		||||
                            style: const TextStyle(
 | 
			
		||||
                              fontSize: 15,
 | 
			
		||||
                              fontWeight: FontWeight.w500,
 | 
			
		||||
                        IconButton(
 | 
			
		||||
                          iconSize: 18,
 | 
			
		||||
                          constraints: const BoxConstraints(),
 | 
			
		||||
                          icon: const Icon(Icons.close),
 | 
			
		||||
                          style: ButtonStyle(
 | 
			
		||||
                            backgroundColor: MaterialStateProperty.all(
 | 
			
		||||
                              Theme.of(context)
 | 
			
		||||
                                  .colorScheme
 | 
			
		||||
                                  .surface
 | 
			
		||||
                                  .withOpacity(0.5),
 | 
			
		||||
                            ),
 | 
			
		||||
                          ),
 | 
			
		||||
                          onPressed: () {
 | 
			
		||||
                            Navigator.of(context).pop();
 | 
			
		||||
                          },
 | 
			
		||||
                        ),
 | 
			
		||||
                        const Gap(2),
 | 
			
		||||
                        IgnorePointer(
 | 
			
		||||
                          child: Wrap(
 | 
			
		||||
                            spacing: 6,
 | 
			
		||||
                            children: [
 | 
			
		||||
                              if (item.metadata['exif'] == null)
 | 
			
		||||
                                Text(
 | 
			
		||||
                                  '#${item.rid}',
 | 
			
		||||
                                  style: metaTextStyle,
 | 
			
		||||
                                ),
 | 
			
		||||
                              if (item.metadata['exif']?['Model'] != null)
 | 
			
		||||
                                Text(
 | 
			
		||||
                                  'attachmentShotOn'.tr(args: [
 | 
			
		||||
                                    item.metadata['exif']?['Model'],
 | 
			
		||||
                                  ]),
 | 
			
		||||
                                  style: metaTextStyle,
 | 
			
		||||
                                ).padding(right: 2),
 | 
			
		||||
                              if (item.metadata['exif']?['Megapixels'] !=
 | 
			
		||||
                                      null &&
 | 
			
		||||
                                  item.metadata['exif']?['Model'] != null)
 | 
			
		||||
                                Text(
 | 
			
		||||
                                  '${item.metadata['exif']?['Megapixels']}MP',
 | 
			
		||||
                                  style: metaTextStyle,
 | 
			
		||||
                                )
 | 
			
		||||
                              else
 | 
			
		||||
                                Text(
 | 
			
		||||
                                  item.size.formatBytes(),
 | 
			
		||||
                                  style: metaTextStyle,
 | 
			
		||||
                                ),
 | 
			
		||||
                              if (item.metadata['width'] != null &&
 | 
			
		||||
                                  item.metadata['height'] != null)
 | 
			
		||||
                                Text(
 | 
			
		||||
                                  '${item.metadata['width']}x${item.metadata['height']}',
 | 
			
		||||
                                  style: metaTextStyle,
 | 
			
		||||
                                ),
 | 
			
		||||
                            ],
 | 
			
		||||
                        IconButton(
 | 
			
		||||
                            iconSize: 20,
 | 
			
		||||
                            constraints: const BoxConstraints(),
 | 
			
		||||
                            padding: EdgeInsets.zero,
 | 
			
		||||
                            visualDensity: VisualDensity.compact,
 | 
			
		||||
                            icon: const Icon(Symbols.hide).padding(all: 6),
 | 
			
		||||
                            onPressed: () {
 | 
			
		||||
                              setState(() => _showOverlay = false);
 | 
			
		||||
                            }),
 | 
			
		||||
                        Expanded(
 | 
			
		||||
                          child: IgnorePointer(
 | 
			
		||||
                            child: Builder(builder: (context) {
 | 
			
		||||
                              final item = widget.data.elementAt(_page);
 | 
			
		||||
                              final doShowCameraInfo =
 | 
			
		||||
                                  item.metadata['exif']?['Model'] != null;
 | 
			
		||||
                              final exif = item.metadata['exif'];
 | 
			
		||||
                              return Column(
 | 
			
		||||
                                children: [
 | 
			
		||||
                                  if (widget.data.length > 1)
 | 
			
		||||
                                    Text(
 | 
			
		||||
                                      '${_page + 1}/${widget.data.length}',
 | 
			
		||||
                                      style:
 | 
			
		||||
                                          GoogleFonts.robotoMono(fontSize: 13),
 | 
			
		||||
                                    ).padding(right: 8),
 | 
			
		||||
                                  if (doShowCameraInfo)
 | 
			
		||||
                                    Text(
 | 
			
		||||
                                      'attachmentShotOn'
 | 
			
		||||
                                          .tr(args: [exif?['Model']]),
 | 
			
		||||
                                      style: metaTextStyle,
 | 
			
		||||
                                      textAlign: TextAlign.center,
 | 
			
		||||
                                    ),
 | 
			
		||||
                                  if (doShowCameraInfo)
 | 
			
		||||
                                    Row(
 | 
			
		||||
                                      spacing: 4,
 | 
			
		||||
                                      mainAxisSize: MainAxisSize.min,
 | 
			
		||||
                                      children: [
 | 
			
		||||
                                        if (exif?['Megapixels'] != null)
 | 
			
		||||
                                          Text(
 | 
			
		||||
                                            '${exif?['Megapixels']}MP',
 | 
			
		||||
                                            style: metaTextStyle,
 | 
			
		||||
                                          ),
 | 
			
		||||
                                        if (exif?['ISO'] != null)
 | 
			
		||||
                                          Text(
 | 
			
		||||
                                            'ISO${exif['ISO']}',
 | 
			
		||||
                                            style: metaTextStyle,
 | 
			
		||||
                                          ),
 | 
			
		||||
                                        if (exif?['FNumber'] != null)
 | 
			
		||||
                                          Text(
 | 
			
		||||
                                            'f/${exif['FNumber']}',
 | 
			
		||||
                                            style: metaTextStyle,
 | 
			
		||||
                                          ),
 | 
			
		||||
                                      ],
 | 
			
		||||
                                    )
 | 
			
		||||
                                ],
 | 
			
		||||
                              );
 | 
			
		||||
                            }),
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                        const Gap(4),
 | 
			
		||||
                        InkWell(
 | 
			
		||||
                          onTap: () {
 | 
			
		||||
                        IconButton(
 | 
			
		||||
                          constraints: const BoxConstraints(),
 | 
			
		||||
                          padding: EdgeInsets.zero,
 | 
			
		||||
                          visualDensity: VisualDensity.compact,
 | 
			
		||||
                          icon: Container(
 | 
			
		||||
                            padding: const EdgeInsets.all(6),
 | 
			
		||||
                            child: !_isDownloading
 | 
			
		||||
                                ? !_isCompletedDownload
 | 
			
		||||
                                    ? const Icon(Symbols.save_alt)
 | 
			
		||||
                                    : const Icon(Symbols.download_done)
 | 
			
		||||
                                : SizedBox(
 | 
			
		||||
                                    width: 20,
 | 
			
		||||
                                    height: 20,
 | 
			
		||||
                                    child: CircularProgressIndicator(
 | 
			
		||||
                                      backgroundColor: Theme.of(context)
 | 
			
		||||
                                          .colorScheme
 | 
			
		||||
                                          .surfaceContainerHighest,
 | 
			
		||||
                                      value: _progressOfDownload,
 | 
			
		||||
                                      strokeWidth: 3,
 | 
			
		||||
                                    ),
 | 
			
		||||
                                  ),
 | 
			
		||||
                          ),
 | 
			
		||||
                          onPressed:
 | 
			
		||||
                              _isDownloading ? null : () => _saveToAlbum(_page),
 | 
			
		||||
                        ),
 | 
			
		||||
                        IconButton(
 | 
			
		||||
                          iconSize: 18,
 | 
			
		||||
                          constraints: const BoxConstraints(),
 | 
			
		||||
                          icon: const Icon(Icons.info_outline),
 | 
			
		||||
                          style: ButtonStyle(
 | 
			
		||||
                            backgroundColor: MaterialStateProperty.all(
 | 
			
		||||
                              Theme.of(context)
 | 
			
		||||
                                  .colorScheme
 | 
			
		||||
                                  .surface
 | 
			
		||||
                                  .withOpacity(0.5),
 | 
			
		||||
                            ),
 | 
			
		||||
                          ),
 | 
			
		||||
                          onPressed: () {
 | 
			
		||||
                            _showDetail = true;
 | 
			
		||||
                            showModalBottomSheet(
 | 
			
		||||
                              context: context,
 | 
			
		||||
                              builder: (context) => _AttachmentZoomDetailPopup(
 | 
			
		||||
                                data: widget.data.elementAt(
 | 
			
		||||
                                    widget.data.length > 1
 | 
			
		||||
                                        ? _pageController.page?.round() ?? 0
 | 
			
		||||
                                        : 0),
 | 
			
		||||
                                data: widget.data.elementAt(_page),
 | 
			
		||||
                              ),
 | 
			
		||||
                            ).then((_) {
 | 
			
		||||
                              _showDetail = false;
 | 
			
		||||
                            });
 | 
			
		||||
                          },
 | 
			
		||||
                          child: Text(
 | 
			
		||||
                            'viewDetailedAttachment'.tr(),
 | 
			
		||||
                            style: metaTextStyle.copyWith(
 | 
			
		||||
                                decoration: TextDecoration.underline),
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ],
 | 
			
		||||
                    );
 | 
			
		||||
@@ -427,18 +386,20 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
        onTap: () {
 | 
			
		||||
          if (_showOverlay) {
 | 
			
		||||
            Navigator.pop(context);
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
          setState(() => _showOverlay = !_showOverlay);
 | 
			
		||||
        },
 | 
			
		||||
        onVerticalDragUpdate: (details) {
 | 
			
		||||
          if (_showDetail) return;
 | 
			
		||||
          if (_showDetail || !_dismissable) return;
 | 
			
		||||
          if (details.delta.dy <= -20) {
 | 
			
		||||
            _showDetail = true;
 | 
			
		||||
            showModalBottomSheet(
 | 
			
		||||
              context: context,
 | 
			
		||||
              builder: (context) => _AttachmentZoomDetailPopup(
 | 
			
		||||
                data: widget.data.elementAt(widget.data.length > 1
 | 
			
		||||
                    ? _pageController.page?.round() ?? 0
 | 
			
		||||
                    : 0),
 | 
			
		||||
                data: widget.data.elementAt(_page),
 | 
			
		||||
              ),
 | 
			
		||||
            ).then((_) {
 | 
			
		||||
              _showDetail = false;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user