feat: replace heart icons to thumbs-up across activity (#24590)

* feat: replace heart icons to thumbs-up across activity

* fix: update thumb_up icon color to use primaryColor in activity components

* chore: web colors

* chore: modify colors

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
pull/24620/head^2
idubnori 2025-12-17 00:27:09 +09:00 committed by GitHub
parent cb2bd47816
commit 997aec2441
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 23 additions and 22 deletions

View File

@ -47,7 +47,7 @@ class LikeActivityActionButton extends ConsumerWidget {
return BaseActionButton(
maxWidth: 60,
iconData: liked != null ? Icons.favorite : Icons.favorite_border,
iconData: liked != null ? Icons.thumb_up : Icons.thumb_up_off_alt,
label: "like".t(context: context),
onPressed: () => onTap(liked),
iconOnly: iconOnly,
@ -57,7 +57,7 @@ class LikeActivityActionButton extends ConsumerWidget {
// default to empty heart during loading
loading: () => BaseActionButton(
iconData: Icons.favorite_border,
iconData: Icons.thumb_up_off_alt,
label: "like".t(context: context),
iconOnly: iconOnly,
menuItem: menuItem,

View File

@ -2,6 +2,7 @@ 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/extensions/build_context_extensions.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';
@ -68,11 +69,11 @@ class ActivityTextField extends HookConsumerWidget {
suffixIcon: Padding(
padding: const EdgeInsets.only(right: 10),
child: IconButton(
icon: Icon(liked ? Icons.favorite_rounded : Icons.favorite_border_rounded),
icon: Icon(liked ? Icons.thumb_up : Icons.thumb_up_off_alt),
onPressed: liked ? removeLike : addLike,
),
),
suffixIconColor: liked ? Colors.red[700] : null,
suffixIconColor: liked ? context.primaryColor : null,
hintText: !isEnabled ? 'shared_album_activities_input_disable'.tr() : 'say_something'.tr(),
hintStyle: TextStyle(fontWeight: FontWeight.normal, fontSize: 14, color: Colors.grey[600]),
),

View File

@ -37,7 +37,7 @@ class ActivityTile extends HookConsumerWidget {
? Container(
width: isBottomSheet ? 30 : 44,
alignment: Alignment.center,
child: Icon(Icons.favorite_rounded, color: Colors.red[700]),
child: Icon(Icons.thumb_up, color: context.primaryColor),
)
: isBottomSheet
? UserCircleAvatar(user: activity.user, size: 30, radius: 15)

View File

@ -67,8 +67,8 @@ class CommentBubble extends ConsumerWidget {
bottom: 6,
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(color: Colors.white.withValues(alpha: 0.7), shape: BoxShape.circle),
child: Icon(Icons.favorite, color: Colors.red[600], size: 18),
decoration: BoxDecoration(color: context.colorScheme.surfaceContainer, shape: BoxShape.circle),
child: Icon(Icons.thumb_up, color: context.primaryColor, size: 18),
),
),
],
@ -81,8 +81,8 @@ class CommentBubble extends ConsumerWidget {
if (isLike && !showThumbnail) {
likes = Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(color: Colors.white.withValues(alpha: 0.7), shape: BoxShape.circle),
child: Icon(Icons.favorite, color: Colors.red[600], size: 18),
decoration: BoxDecoration(color: context.colorScheme.surfaceContainer, shape: BoxShape.circle),
child: Icon(Icons.thumb_up, color: context.primaryColor, size: 18),
);
}

View File

@ -77,15 +77,15 @@ void main() {
overrides: overrides,
);
expect(find.widgetWithIcon(IconButton, Icons.favorite_rounded), findsOneWidget);
expect(find.widgetWithIcon(IconButton, Icons.favorite_border_rounded), findsNothing);
expect(find.widgetWithIcon(IconButton, Icons.thumb_up), findsOneWidget);
expect(find.widgetWithIcon(IconButton, Icons.thumb_up_off_alt), findsNothing);
});
testWidgets('Bordered icon if likedId == null', (tester) async {
await tester.pumpConsumerWidget(ActivityTextField(onSubmit: (_) {}), overrides: overrides);
expect(find.widgetWithIcon(IconButton, Icons.favorite_border_rounded), findsOneWidget);
expect(find.widgetWithIcon(IconButton, Icons.favorite_rounded), findsNothing);
expect(find.widgetWithIcon(IconButton, Icons.thumb_up_off_alt), findsOneWidget);
expect(find.widgetWithIcon(IconButton, Icons.thumb_up), findsNothing);
});
testWidgets('Adds new like', (tester) async {

View File

@ -91,17 +91,17 @@ void main() {
group('Like Activity', () {
final activity = Activity(id: '1', createdAt: DateTime(100), type: ActivityType.like, user: UserStub.admin);
testWidgets('Like contains filled heart as leading', (tester) async {
testWidgets('Like contains filled thumbs-up as leading', (tester) async {
await tester.pumpConsumerWidget(ActivityTile(activity), overrides: overrides);
// Leading widget should not be null
final listTile = tester.widget<ListTile>(find.byType(ListTile));
expect(listTile.leading, isNotNull);
// And should have a favorite icon
final favoIconFinder = find.widgetWithIcon(listTile.leading!.runtimeType, Icons.favorite_rounded);
// And should have a thumb_up icon
final thumbUpIconFinder = find.widgetWithIcon(listTile.leading!.runtimeType, Icons.thumb_up);
expect(favoIconFinder, findsOneWidget);
expect(thumbUpIconFinder, findsOneWidget);
});
testWidgets('Like title is center aligned', (tester) async {

View File

@ -2,7 +2,7 @@
import { locale } from '$lib/stores/preferences.store';
import type { ActivityResponseDto } from '@immich/sdk';
import { Icon } from '@immich/ui';
import { mdiCommentOutline, mdiHeart, mdiHeartOutline } from '@mdi/js';
import { mdiCommentOutline, mdiThumbUp, mdiThumbUpOutline } from '@mdi/js';
interface Props {
isLiked: ActivityResponseDto | null;
@ -19,7 +19,7 @@
<div class="w-full flex p-4 items-center justify-center rounded-full gap-5 bg-subtle border bg-opacity-60">
<button type="button" class={disabled ? 'cursor-not-allowed' : ''} onclick={onFavorite} {disabled}>
<div class="flex gap-2 items-center justify-center">
<Icon icon={isLiked ? mdiHeart : mdiHeartOutline} size="24" class={isLiked ? 'text-red-400' : 'text-fg'} />
<Icon icon={isLiked ? mdiThumbUp : mdiThumbUpOutline} size="24" class={isLiked ? 'text-primary' : 'text-fg'} />
{#if numberOfLikes}
<div class="text-l">{numberOfLikes.toLocaleString($locale)}</div>
{/if}

View File

@ -13,7 +13,7 @@
import { isTenMinutesApart } from '$lib/utils/timesince';
import { ReactionType, type ActivityResponseDto, type AssetTypeEnum, type UserResponseDto } from '@immich/sdk';
import { Icon, IconButton, LoadingSpinner, toastManager } from '@immich/ui';
import { mdiClose, mdiDeleteOutline, mdiDotsVertical, mdiHeart, mdiSend } from '@mdi/js';
import { mdiClose, mdiDeleteOutline, mdiDotsVertical, mdiSend, mdiThumbUp } from '@mdi/js';
import * as luxon from 'luxon';
import { t } from 'svelte-i18n';
import UserAvatar from '../shared-components/user-avatar.svelte';
@ -181,7 +181,7 @@
{:else if reaction.type === ReactionType.Like}
<div class="relative">
<div class="flex py-3 ps-3 mt-3 gap-4 items-center text-sm">
<div class="text-red-600"><Icon icon={mdiHeart} size="20" /></div>
<div class="text-primary"><Icon icon={mdiThumbUp} size="20" /></div>
<div class="w-full" title={`${reaction.user.name} (${reaction.user.email})`}>
{$t('user_liked', {
@ -254,7 +254,7 @@
shortcut: { key: 'Enter' },
onShortcut: () => handleSendComment(),
}}
class="h-[18px] {disabled
class="h-4.5 {disabled
? 'cursor-not-allowed'
: ''} w-full max-h-56 pe-2 items-center overflow-y-auto leading-4 outline-none resize-none bg-gray-200"
></textarea>