forked from Cutlery/immich
		
	* refactor: scaffoldwhen to log errors during scaffold body render * refactor: onError and onLoading scaffoldbody * refactor: more scaffold body to custom extension * refactor: add skiploadingonrefresh * Snackbar color --------- Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
		
			
				
	
	
		
			315 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			315 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
import 'package:cached_network_image/cached_network_image.dart';
 | 
						|
import 'package:collection/collection.dart';
 | 
						|
import 'package:easy_localization/easy_localization.dart';
 | 
						|
import 'package:flutter/material.dart';
 | 
						|
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
 | 
						|
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
						|
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
 | 
						|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
 | 
						|
import 'package:immich_mobile/modules/activities/models/activity.model.dart';
 | 
						|
import 'package:immich_mobile/modules/activities/providers/activity.provider.dart';
 | 
						|
import 'package:immich_mobile/shared/models/store.dart';
 | 
						|
import 'package:immich_mobile/shared/ui/confirm_dialog.dart';
 | 
						|
import 'package:immich_mobile/shared/ui/user_circle_avatar.dart';
 | 
						|
import 'package:immich_mobile/extensions/datetime_extensions.dart';
 | 
						|
import 'package:immich_mobile/utils/image_url_builder.dart';
 | 
						|
 | 
						|
class ActivitiesPage extends HookConsumerWidget {
 | 
						|
  final String albumId;
 | 
						|
  final String? assetId;
 | 
						|
  final bool withAssetThumbs;
 | 
						|
  final String appBarTitle;
 | 
						|
  final bool isOwner;
 | 
						|
  final bool isReadOnly;
 | 
						|
  const ActivitiesPage(
 | 
						|
    this.albumId, {
 | 
						|
    this.appBarTitle = "",
 | 
						|
    this.assetId,
 | 
						|
    this.withAssetThumbs = true,
 | 
						|
    this.isOwner = false,
 | 
						|
    this.isReadOnly = false,
 | 
						|
    super.key,
 | 
						|
  });
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context, WidgetRef ref) {
 | 
						|
    final provider =
 | 
						|
        activityStateProvider((albumId: albumId, assetId: assetId));
 | 
						|
    final activities = ref.watch(provider);
 | 
						|
    final inputController = useTextEditingController();
 | 
						|
    final inputFocusNode = useFocusNode();
 | 
						|
    final listViewScrollController = useScrollController();
 | 
						|
    final currentUser = Store.tryGet(StoreKey.currentUser);
 | 
						|
 | 
						|
    useEffect(
 | 
						|
      () {
 | 
						|
        inputFocusNode.requestFocus();
 | 
						|
        return null;
 | 
						|
      },
 | 
						|
      [],
 | 
						|
    );
 | 
						|
 | 
						|
    buildTitleWithTimestamp(Activity activity, {bool leftAlign = true}) {
 | 
						|
      final textColor = context.isDarkTheme ? Colors.white : Colors.black;
 | 
						|
      final textStyle = context.textTheme.bodyMedium
 | 
						|
          ?.copyWith(color: textColor.withOpacity(0.6));
 | 
						|
 | 
						|
      return Row(
 | 
						|
        mainAxisAlignment: leftAlign
 | 
						|
            ? MainAxisAlignment.start
 | 
						|
            : MainAxisAlignment.spaceBetween,
 | 
						|
        mainAxisSize: leftAlign ? MainAxisSize.min : MainAxisSize.max,
 | 
						|
        children: [
 | 
						|
          Text(
 | 
						|
            activity.user.name,
 | 
						|
            style: textStyle,
 | 
						|
            overflow: TextOverflow.ellipsis,
 | 
						|
          ),
 | 
						|
          if (leftAlign)
 | 
						|
            Text(
 | 
						|
              " • ",
 | 
						|
              style: textStyle,
 | 
						|
            ),
 | 
						|
          Expanded(
 | 
						|
            child: Text(
 | 
						|
              activity.createdAt.copyWith().timeAgo(),
 | 
						|
              style: textStyle,
 | 
						|
              overflow: TextOverflow.ellipsis,
 | 
						|
              textAlign: leftAlign ? TextAlign.left : TextAlign.right,
 | 
						|
            ),
 | 
						|
          ),
 | 
						|
        ],
 | 
						|
      );
 | 
						|
    }
 | 
						|
 | 
						|
    buildAssetThumbnail(Activity activity) {
 | 
						|
      return withAssetThumbs && activity.assetId != null
 | 
						|
          ? Container(
 | 
						|
              width: 40,
 | 
						|
              height: 30,
 | 
						|
              decoration: BoxDecoration(
 | 
						|
                borderRadius: const BorderRadius.all(Radius.circular(4)),
 | 
						|
                image: DecorationImage(
 | 
						|
                  image: CachedNetworkImageProvider(
 | 
						|
                    getThumbnailUrlForRemoteId(
 | 
						|
                      activity.assetId!,
 | 
						|
                    ),
 | 
						|
                    cacheKey: getThumbnailCacheKeyForRemoteId(
 | 
						|
                      activity.assetId!,
 | 
						|
                    ),
 | 
						|
                    headers: {
 | 
						|
                      "Authorization":
 | 
						|
                          'Bearer ${Store.get(StoreKey.accessToken)}',
 | 
						|
                    },
 | 
						|
                  ),
 | 
						|
                  fit: BoxFit.cover,
 | 
						|
                ),
 | 
						|
              ),
 | 
						|
              child: const SizedBox.shrink(),
 | 
						|
            )
 | 
						|
          : null;
 | 
						|
    }
 | 
						|
 | 
						|
    buildTextField(String? likedId) {
 | 
						|
      final liked = likedId != null;
 | 
						|
      return Padding(
 | 
						|
        padding: const EdgeInsets.only(bottom: 10),
 | 
						|
        child: TextField(
 | 
						|
          controller: inputController,
 | 
						|
          enabled: !isReadOnly,
 | 
						|
          focusNode: inputFocusNode,
 | 
						|
          textInputAction: TextInputAction.send,
 | 
						|
          autofocus: false,
 | 
						|
          decoration: InputDecoration(
 | 
						|
            border: InputBorder.none,
 | 
						|
            focusedBorder: InputBorder.none,
 | 
						|
            prefixIcon: currentUser != null
 | 
						|
                ? Padding(
 | 
						|
                    padding: const EdgeInsets.symmetric(horizontal: 15),
 | 
						|
                    child: UserCircleAvatar(
 | 
						|
                      user: currentUser,
 | 
						|
                      size: 30,
 | 
						|
                      radius: 15,
 | 
						|
                    ),
 | 
						|
                  )
 | 
						|
                : null,
 | 
						|
            suffixIcon: Padding(
 | 
						|
              padding: const EdgeInsets.only(right: 10),
 | 
						|
              child: IconButton(
 | 
						|
                icon: Icon(
 | 
						|
                  liked
 | 
						|
                      ? Icons.favorite_rounded
 | 
						|
                      : Icons.favorite_border_rounded,
 | 
						|
                ),
 | 
						|
                onPressed: () async {
 | 
						|
                  liked
 | 
						|
                      ? await ref
 | 
						|
                          .read(provider.notifier)
 | 
						|
                          .removeActivity(likedId)
 | 
						|
                      : await ref.read(provider.notifier).addLike();
 | 
						|
                },
 | 
						|
              ),
 | 
						|
            ),
 | 
						|
            suffixIconColor: liked ? Colors.red[700] : null,
 | 
						|
            hintText: isReadOnly
 | 
						|
                ? 'shared_album_activities_input_disable'.tr()
 | 
						|
                : 'shared_album_activities_input_hint'.tr(),
 | 
						|
            hintStyle: TextStyle(
 | 
						|
              fontWeight: FontWeight.normal,
 | 
						|
              fontSize: 14,
 | 
						|
              color: Colors.grey[600],
 | 
						|
            ),
 | 
						|
          ),
 | 
						|
          onEditingComplete: () async {
 | 
						|
            await ref.read(provider.notifier).addComment(inputController.text);
 | 
						|
            inputController.clear();
 | 
						|
            inputFocusNode.unfocus();
 | 
						|
            listViewScrollController.animateTo(
 | 
						|
              listViewScrollController.position.maxScrollExtent,
 | 
						|
              duration: const Duration(milliseconds: 800),
 | 
						|
              curve: Curves.fastOutSlowIn,
 | 
						|
            );
 | 
						|
          },
 | 
						|
          onTapOutside: (_) => inputFocusNode.unfocus(),
 | 
						|
        ),
 | 
						|
      );
 | 
						|
    }
 | 
						|
 | 
						|
    getDismissibleWidget(
 | 
						|
      Widget widget,
 | 
						|
      Activity activity,
 | 
						|
      bool canDelete,
 | 
						|
    ) {
 | 
						|
      return Dismissible(
 | 
						|
        key: Key(activity.id),
 | 
						|
        dismissThresholds: const {
 | 
						|
          DismissDirection.horizontal: 0.7,
 | 
						|
        },
 | 
						|
        direction: DismissDirection.horizontal,
 | 
						|
        confirmDismiss: (direction) => canDelete
 | 
						|
            ? showDialog(
 | 
						|
                context: context,
 | 
						|
                builder: (context) => ConfirmDialog(
 | 
						|
                  onOk: () {},
 | 
						|
                  title: "shared_album_activity_remove_title",
 | 
						|
                  content: "shared_album_activity_remove_content",
 | 
						|
                  ok: "delete_dialog_ok",
 | 
						|
                ),
 | 
						|
              )
 | 
						|
            : Future.value(false),
 | 
						|
        onDismissed: (direction) async =>
 | 
						|
            await ref.read(provider.notifier).removeActivity(activity.id),
 | 
						|
        background: Container(
 | 
						|
          color: canDelete ? Colors.red[400] : Colors.grey[600],
 | 
						|
          alignment: AlignmentDirectional.centerStart,
 | 
						|
          child: canDelete
 | 
						|
              ? const Padding(
 | 
						|
                  padding: EdgeInsets.all(15),
 | 
						|
                  child: Icon(
 | 
						|
                    Icons.delete_sweep_rounded,
 | 
						|
                    color: Colors.black,
 | 
						|
                  ),
 | 
						|
                )
 | 
						|
              : null,
 | 
						|
        ),
 | 
						|
        secondaryBackground: Container(
 | 
						|
          color: canDelete ? Colors.red[400] : Colors.grey[600],
 | 
						|
          alignment: AlignmentDirectional.centerEnd,
 | 
						|
          child: canDelete
 | 
						|
              ? const Padding(
 | 
						|
                  padding: EdgeInsets.all(15),
 | 
						|
                  child: Icon(
 | 
						|
                    Icons.delete_sweep_rounded,
 | 
						|
                    color: Colors.black,
 | 
						|
                  ),
 | 
						|
                )
 | 
						|
              : null,
 | 
						|
        ),
 | 
						|
        child: widget,
 | 
						|
      );
 | 
						|
    }
 | 
						|
 | 
						|
    return Scaffold(
 | 
						|
      appBar: AppBar(title: Text(appBarTitle)),
 | 
						|
      body: activities.widgetWhen(
 | 
						|
        onData: (data) {
 | 
						|
          final liked = data.firstWhereOrNull(
 | 
						|
            (a) =>
 | 
						|
                a.type == ActivityType.like &&
 | 
						|
                a.user.id == currentUser?.id &&
 | 
						|
                a.assetId == assetId,
 | 
						|
          );
 | 
						|
 | 
						|
          return SafeArea(
 | 
						|
            child: Stack(
 | 
						|
              children: [
 | 
						|
                ListView.builder(
 | 
						|
                  controller: listViewScrollController,
 | 
						|
                  itemCount: data.length + 1,
 | 
						|
                  itemBuilder: (context, index) {
 | 
						|
                    // Vertical gap after the last element
 | 
						|
                    if (index == data.length) {
 | 
						|
                      return const SizedBox(
 | 
						|
                        height: 80,
 | 
						|
                      );
 | 
						|
                    }
 | 
						|
 | 
						|
                    final activity = data[index];
 | 
						|
                    final canDelete =
 | 
						|
                        activity.user.id == currentUser?.id || isOwner;
 | 
						|
 | 
						|
                    return Padding(
 | 
						|
                      padding: const EdgeInsets.all(5),
 | 
						|
                      child: activity.type == ActivityType.comment
 | 
						|
                          ? getDismissibleWidget(
 | 
						|
                              ListTile(
 | 
						|
                                minVerticalPadding: 15,
 | 
						|
                                leading: UserCircleAvatar(user: activity.user),
 | 
						|
                                title: buildTitleWithTimestamp(
 | 
						|
                                  activity,
 | 
						|
                                  leftAlign: withAssetThumbs &&
 | 
						|
                                      activity.assetId != null,
 | 
						|
                                ),
 | 
						|
                                titleAlignment: ListTileTitleAlignment.top,
 | 
						|
                                trailing: buildAssetThumbnail(activity),
 | 
						|
                                subtitle: Text(activity.comment!),
 | 
						|
                              ),
 | 
						|
                              activity,
 | 
						|
                              canDelete,
 | 
						|
                            )
 | 
						|
                          : getDismissibleWidget(
 | 
						|
                              ListTile(
 | 
						|
                                minVerticalPadding: 15,
 | 
						|
                                leading: Container(
 | 
						|
                                  width: 44,
 | 
						|
                                  alignment: Alignment.center,
 | 
						|
                                  child: Icon(
 | 
						|
                                    Icons.favorite_rounded,
 | 
						|
                                    color: Colors.red[700],
 | 
						|
                                  ),
 | 
						|
                                ),
 | 
						|
                                title: buildTitleWithTimestamp(activity),
 | 
						|
                                trailing: buildAssetThumbnail(activity),
 | 
						|
                              ),
 | 
						|
                              activity,
 | 
						|
                              canDelete,
 | 
						|
                            ),
 | 
						|
                    );
 | 
						|
                  },
 | 
						|
                ),
 | 
						|
                Align(
 | 
						|
                  alignment: Alignment.bottomCenter,
 | 
						|
                  child: Container(
 | 
						|
                    color: context.scaffoldBackgroundColor,
 | 
						|
                    child: buildTextField(liked?.id),
 | 
						|
                  ),
 | 
						|
                ),
 | 
						|
              ],
 | 
						|
            ),
 | 
						|
          );
 | 
						|
        },
 | 
						|
      ),
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 |