refactor(mobile): widgets (#9291)

* refactor(mobile): widgets

* update
This commit is contained in:
Alex 2024-05-06 23:04:21 -05:00 committed by GitHub
parent 7520ffd6c3
commit 5806a3ce25
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
203 changed files with 318 additions and 318 deletions

View file

@ -0,0 +1,105 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/providers/activity.provider.dart';
import 'package:immich_mobile/providers/album/current_album.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
class ActivityTextField extends HookConsumerWidget {
final bool isEnabled;
final String? likeId;
final Function(String) onSubmit;
const ActivityTextField({
required this.onSubmit,
this.isEnabled = true,
this.likeId,
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final album = ref.watch(currentAlbumProvider)!;
final asset = ref.watch(currentAssetProvider);
final activityNotifier = ref
.read(albumActivityProvider(album.remoteId!, asset?.remoteId).notifier);
final user = ref.watch(currentUserProvider);
final inputController = useTextEditingController();
final inputFocusNode = useFocusNode();
final liked = likeId != null;
// Show keyboard immediately on activities open
useEffect(
() {
inputFocusNode.requestFocus();
return null;
},
[],
);
// Pass text to callback and reset controller
void onEditingComplete() {
onSubmit(inputController.text);
inputController.clear();
inputFocusNode.unfocus();
}
Future<void> addLike() async {
await activityNotifier.addLike();
}
Future<void> removeLike() async {
if (liked) {
await activityNotifier.removeActivity(likeId!);
}
}
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: TextField(
controller: inputController,
enabled: isEnabled,
focusNode: inputFocusNode,
textInputAction: TextInputAction.send,
autofocus: false,
decoration: InputDecoration(
border: InputBorder.none,
focusedBorder: InputBorder.none,
prefixIcon: user != null
? Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: UserCircleAvatar(
user: user,
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: liked ? removeLike : addLike,
),
),
suffixIconColor: liked ? Colors.red[700] : null,
hintText: !isEnabled
? 'shared_album_activities_input_disable'.tr()
: 'shared_album_activities_input_hint'.tr(),
hintStyle: TextStyle(
fontWeight: FontWeight.normal,
fontSize: 14,
color: Colors.grey[600],
),
),
onEditingComplete: onEditingComplete,
onTapOutside: (_) => inputFocusNode.unfocus(),
),
);
}
}

View file

@ -0,0 +1,118 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/datetime_extensions.dart';
import 'package:immich_mobile/models/activities/activity.model.dart';
import 'package:immich_mobile/providers/image/immich_remote_thumbnail_provider.dart';
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
class ActivityTile extends HookConsumerWidget {
final Activity activity;
const ActivityTile(this.activity, {super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final asset = ref.watch(currentAssetProvider);
final isLike = activity.type == ActivityType.like;
// Asset thumbnail is displayed when we are accessing activities from the album page
// currentAssetProvider will not be set until we open the gallery viewer
final showAssetThumbnail = asset == null && activity.assetId != null;
return ListTile(
minVerticalPadding: 15,
leading: isLike
? Container(
width: 44,
alignment: Alignment.center,
child: Icon(
Icons.favorite_rounded,
color: Colors.red[700],
),
)
: UserCircleAvatar(user: activity.user),
title: _ActivityTitle(
userName: activity.user.name,
createdAt: activity.createdAt.timeAgo(),
leftAlign: isLike || showAssetThumbnail,
),
// No subtitle for like, so center title
titleAlignment:
!isLike ? ListTileTitleAlignment.top : ListTileTitleAlignment.center,
trailing: showAssetThumbnail
? _ActivityAssetThumbnail(activity.assetId!)
: null,
subtitle: !isLike ? Text(activity.comment!) : null,
);
}
}
class _ActivityTitle extends StatelessWidget {
final String userName;
final String createdAt;
final bool leftAlign;
const _ActivityTitle({
required this.userName,
required this.createdAt,
required this.leftAlign,
});
@override
Widget build(BuildContext context) {
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(
userName,
style: textStyle,
overflow: TextOverflow.ellipsis,
),
if (leftAlign)
Text(
"",
style: textStyle,
),
Expanded(
child: Text(
createdAt,
style: textStyle,
overflow: TextOverflow.ellipsis,
textAlign: leftAlign ? TextAlign.left : TextAlign.right,
),
),
],
);
}
}
class _ActivityAssetThumbnail extends StatelessWidget {
final String assetId;
const _ActivityAssetThumbnail(this.assetId);
@override
Widget build(BuildContext context) {
return Container(
width: 40,
height: 30,
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(4)),
image: DecorationImage(
image: ImmichRemoteThumbnailProvider(
assetId: assetId,
),
fit: BoxFit.cover,
),
),
child: const SizedBox.shrink(),
);
}
}

View file

@ -0,0 +1,75 @@
import 'package:flutter/material.dart';
import 'package:immich_mobile/widgets/activities/activity_tile.dart';
import 'package:immich_mobile/widgets/common/confirm_dialog.dart';
/// Wraps an [ActivityTile] and makes it dismissible
class DismissibleActivity extends StatelessWidget {
final String activityId;
final ActivityTile body;
final Function(String)? onDismiss;
const DismissibleActivity(
this.activityId,
this.body, {
this.onDismiss,
super.key,
});
@override
Widget build(BuildContext context) {
return Dismissible(
key: Key(activityId),
dismissThresholds: const {
DismissDirection.horizontal: 0.7,
},
direction: DismissDirection.horizontal,
confirmDismiss: (direction) => onDismiss != null
? 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: (_) async => onDismiss?.call(activityId),
// LTR
background: _DismissBackground(withDeleteIcon: onDismiss != null),
// RTL
secondaryBackground: _DismissBackground(
withDeleteIcon: onDismiss != null,
alignment: AlignmentDirectional.centerEnd,
),
child: body,
);
}
}
class _DismissBackground extends StatelessWidget {
final AlignmentDirectional alignment;
final bool withDeleteIcon;
const _DismissBackground({
required this.withDeleteIcon,
this.alignment = AlignmentDirectional.centerStart,
});
@override
Widget build(BuildContext context) {
return Container(
alignment: alignment,
color: withDeleteIcon ? Colors.red[400] : Colors.grey[600],
child: withDeleteIcon
? const Padding(
padding: EdgeInsets.all(15),
child: Icon(
Icons.delete_sweep_rounded,
color: Colors.black,
),
)
: null,
);
}
}