Compare commits

...

3 Commits

Author SHA1 Message Date
idubnori
4568e55f3f feat: replace heart icons to thumbs-up across activity 2025-12-16 00:06:57 +09:00
Yaros
35eda735c8 fix(web): recent search doesn't use search type (#24578)
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2025-12-15 12:44:00 +01:00
Diogo Correia
8f7a71d1cf fix(web): download panel being hidden by admin sidebar (#24583) 2025-12-15 12:29:18 +01:00
10 changed files with 43 additions and 45 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

@@ -68,11 +68,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 ? Colors.blue[700] : 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: Colors.blue[700]),
)
: isBottomSheet
? UserCircleAvatar(user: activity.user, size: 30, radius: 15)

View File

@@ -68,7 +68,7 @@ class CommentBubble extends ConsumerWidget {
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),
child: Icon(Icons.thumb_up, color: Colors.blue[600], size: 18),
),
),
],
@@ -82,7 +82,7 @@ class CommentBubble extends ConsumerWidget {
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),
child: Icon(Icons.thumb_up, color: Colors.blue[600], 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-blue-400' : '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-blue-600"><Icon icon={mdiThumbUp} size="20" /></div>
<div class="w-full" title={`${reaction.user.name} (${reaction.user.email})`}>
{$t('user_liked', {

View File

@@ -16,7 +16,7 @@
{#if downloadManager.isDownloading}
<div
transition:fly={{ x: -100, duration: 350 }}
class="fixed bottom-10 start-2 max-h-67.5 w-79 rounded-2xl border dark:border-white/10 p-4 shadow-lg bg-subtle"
class="fixed bottom-10 start-2 max-h-67.5 w-79 z-60 rounded-2xl border dark:border-white/10 p-4 shadow-lg bg-subtle"
>
<Heading size="tiny">{$t('downloading')}</Heading>
<div class="my-2 mb-2 flex max-h-50 flex-col overflow-y-auto text-sm">

View File

@@ -79,10 +79,30 @@
searchStore.isSearchEnabled = false;
};
const buildSearchPayload = (term: string): SmartSearchDto | MetadataSearchDto => {
const searchType = getSearchType();
switch (searchType) {
case 'smart': {
return { query: term };
}
case 'metadata': {
return { originalFileName: term };
}
case 'description': {
return { description: term };
}
case 'ocr': {
return { ocr: term };
}
default: {
return { query: term };
}
}
};
const onHistoryTermClick = async (searchTerm: string) => {
value = searchTerm;
const searchPayload = { query: searchTerm };
await handleSearch(searchPayload);
await handleSearch(buildSearchPayload(searchTerm));
};
const onFilterClick = async () => {
@@ -112,29 +132,7 @@
};
const onSubmit = () => {
const searchType = getSearchType();
let payload = {} as SmartSearchDto | MetadataSearchDto;
switch (searchType) {
case 'smart': {
payload = { query: value } as SmartSearchDto;
break;
}
case 'metadata': {
payload = { originalFileName: value } as MetadataSearchDto;
break;
}
case 'description': {
payload = { description: value } as MetadataSearchDto;
break;
}
case 'ocr': {
payload = { ocr: value } as MetadataSearchDto;
break;
}
}
handlePromiseError(handleSearch(payload));
handlePromiseError(handleSearch(buildSearchPayload(value)));
saveSearchTerm(value);
};