Merge tag 'v3.3.3' into codex/immich-release-20261014

# Conflicts:
#	mobile/pubspec.yaml
This commit is contained in:
Codex
2026-10-14 21:24:13 +03:00
17 changed files with 153 additions and 97 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "immich-e2e",
"version": "3.3.1",
"version": "3.3.3",
"description": "",
"main": "index.js",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "immich-ml"
version = "3.3.1"
version = "3.3.3"
description = ""
authors = [{ name = "Hau Tran", email = "alex.tran1502@gmail.com" }]
requires-python = ">=3.12,<4.0"
+1 -1
View File
@@ -676,7 +676,7 @@ wheels = [
[[package]]
name = "immich-ml"
version = "3.3.1"
version = "3.3.3"
source = { editable = "." }
dependencies = [
{ name = "fastapi" },
+4 -4
View File
@@ -22,8 +22,8 @@ platform :android do
task: 'bundle',
build_type: 'Release',
properties: {
"android.injected.version.code" => 3030199,
"android.injected.version.name" => "3.3.1",
"android.injected.version.code" => 3030399,
"android.injected.version.name" => "3.3.3",
}
)
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab', track: 'beta')
@@ -35,8 +35,8 @@ platform :android do
task: 'bundle',
build_type: 'Release',
properties: {
"android.injected.version.code" => 3030199,
"android.injected.version.name" => "3.3.1",
"android.injected.version.code" => 3030399,
"android.injected.version.name" => "3.3.3",
}
)
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
+1 -1
View File
@@ -78,7 +78,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>3.3.1</string>
<string>3.3.3</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/memory.model.dart';
import 'package:immich_mobile/extensions/datetime_extensions.dart';
import 'package:immich_mobile/extensions/object_extensions.dart';
import 'package:immich_mobile/generated/translations.g.dart';
import 'package:intl/intl.dart';
@@ -10,7 +11,7 @@ String getMemoryTitle(Translations t, Memory memory, {RemoteAsset? asset, bool p
switch (memory.type) {
MemoryTypeEnum.onThisDay =>
preferDate
? DateFormat.yMMMMd().format(memory.memoryAt)
? DateFormat.yMMMMd(resolvedDateTimeLocale()).format(memory.memoryAt)
: t.years_ago(years: DateTime.now().year - memory.data.year),
MemoryTypeEnum.birthday => _getBirthdayTitle(t, memory.data, asset),
};
+1 -1
View File
@@ -2,7 +2,7 @@ name: immich_mobile
description: Immich - selfhosted backup media file on mobile phone
publish_to: 'none'
version: 3.3.1-local.3+3030201
version: 3.3.3-local.1+3030400
environment:
sdk: '>=3.12.0 <4.0.0'
@@ -2,6 +2,7 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:immich_mobile/domain/models/memory.model.dart';
import 'package:immich_mobile/presentation/pages/memory_list.page.dart';
import 'package:immich_mobile/providers/infrastructure/memory.provider.dart';
import 'package:intl/intl.dart';
import '../factories/remote_asset_factory.dart';
import 'presentation_context.dart';
@@ -44,6 +45,15 @@ void main() {
expect(find.textContaining('ago'), findsNothing);
});
testWidgets('shows the date when intl has no date formats for the app language', (tester) async {
Intl.defaultLocale = 'kab';
addTearDown(() => Intl.defaultLocale = null);
await pumpPage(tester, [newMemory(type: MemoryTypeEnum.onThisDay, memoryAt: DateTime(2025, 10, 7))]);
await tester.pump();
expect(find.text('October 7, 2025'), findsOneWidget);
});
testWidgets('shows the person name for a birthday memory', (tester) async {
await pumpPage(tester, [
newMemory(type: MemoryTypeEnum.birthday, memoryAt: DateTime(2025, 10, 7), personName: 'Alice'),
+1 -1
View File
@@ -18211,7 +18211,7 @@
"info": {
"title": "Immich",
"description": "Immich API",
"version": "3.3.1",
"version": "3.3.3",
"contact": {}
},
"tags": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "immich-monorepo",
"version": "3.3.1",
"version": "3.3.3",
"description": "Monorepo for Immich",
"type": "module",
"private": true,
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@immich/cli",
"version": "3.3.1",
"version": "3.3.3",
"description": "Command Line Interface (CLI) for Immich",
"repository": {
"type": "git",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@immich/sdk",
"version": "3.3.1",
"version": "3.3.3",
"description": "Auto-generated TypeScript SDK for the Immich API",
"repository": {
"type": "git",
+1 -1
View File
@@ -1,6 +1,6 @@
/**
* Immich
* 3.3.1
* 3.3.3
* DO NOT MODIFY - This file has been generated using oazapfts.
* See https://www.npmjs.com/package/oazapfts
*/
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "immich",
"version": "3.3.1",
"version": "3.3.3",
"description": "",
"author": "",
"private": true,
+58 -26
View File
@@ -239,9 +239,12 @@ from
"person_group"
inner join "person" as "owned" on "owned"."personGroupId" = "person_group"."id"
and "owned"."ownerId" = $5
inner join "asset_face" on "asset_face"."personGroupId" = "person_group"."id"
and "asset_face"."deletedAt" is null
and "asset_face"."isVisible" is true
inner join (
select
"asset_face"."personGroupId",
count("asset"."id") as "faceCount"
from
"asset_face"
inner join "asset" on "asset"."id" = "asset_face"."assetId"
and "asset"."visibility" = 'timeline'
and "asset"."deletedAt" is null
@@ -260,17 +263,28 @@ from
"album_asset"."assetId" = "asset"."id"
)
)
where
"asset_face"."personGroupId" = any (
array(
(
select
"person"."personGroupId"
from
"person"
where
"person"."ownerId" = $8
)
)
)
and "asset_face"."deletedAt" is null
and "asset_face"."isVisible" is true
group by
"asset_face"."personGroupId"
) as "faces" on "faces"."personGroupId" = "person_group"."id"
where
"owned"."isHidden" = $8
and 1 = 1
group by
"person_group"."id",
"owned"."ownerId",
"owned"."personGroupId"
having
(
"owned"."name" != $9
or count("asset"."id") >= COALESCE(
or "faces"."faceCount" >= COALESCE(
(
SELECT
value -> 'people' ->> 'minimumFaces'
@@ -283,17 +297,19 @@ having
'3'
)::int
)
and "owned"."isHidden" = $11
and 1 = 1
order by
"owned"."isHidden" asc,
"owned"."isFavorite" desc,
NULLIF("owned"."name", '') is null asc,
count("asset"."id") desc,
"faces"."faceCount" desc,
NULLIF("owned"."name", '') asc nulls last,
"owned"."createdAt"
limit
$11
offset
$12
offset
$13
-- PersonRepository.getAllWithoutFaces
select
@@ -901,9 +917,12 @@ from
"person_group"
inner join "person" as "owned" on "owned"."personGroupId" = "person_group"."id"
and "owned"."ownerId" = $2
inner join "asset_face" on "asset_face"."personGroupId" = "person_group"."id"
and "asset_face"."deletedAt" is null
and "asset_face"."isVisible" is true
inner join (
select
"asset_face"."personGroupId",
count("asset"."id") as "faceCount"
from
"asset_face"
inner join "asset" on "asset"."id" = "asset_face"."assetId"
and "asset"."visibility" = 'timeline'
and "asset"."deletedAt" is null
@@ -923,27 +942,40 @@ from
)
)
where
1 = 1
group by
"person_group"."id",
"owned"."ownerId",
"owned"."personGroupId"
having
"asset_face"."personGroupId" = any (
array(
(
"owned"."name" != $5
or count("asset"."id") >= COALESCE(
select
"person"."personGroupId"
from
"person"
where
"person"."ownerId" = $5
)
)
)
and "asset_face"."deletedAt" is null
and "asset_face"."isVisible" is true
group by
"asset_face"."personGroupId"
) as "faces" on "faces"."personGroupId" = "person_group"."id"
where
(
"owned"."name" != $6
or "faces"."faceCount" >= COALESCE(
(
SELECT
value -> 'people' ->> 'minimumFaces'
FROM
user_metadata
WHERE
"userId" = $6
"userId" = $7
AND key = 'preferences'
),
'3'
)::int
)
and 1 = 1
) as "people"
-- PersonRepository.createGroup
+26 -13
View File
@@ -224,8 +224,6 @@ const asPerson = ({ ownedPerson, otherPeople, sharedBy, sharedWith }: PersonGrou
sharedWith,
});
const faceCount = (eb: ExpressionBuilder<DB, 'asset'>) => eb.fn.count('asset.id');
const withPerson = ({ viewingUserId }: WithPersonOptions) => {
return (eb: ExpressionBuilder<DB, 'asset_face'>) =>
jsonObjectFrom(
@@ -448,25 +446,40 @@ export class PersonRepository {
.innerJoin('person as owned', (join) =>
join.onRef('owned.personGroupId', '=', 'person_group.id').on('owned.ownerId', '=', userId),
)
.innerJoin('asset_face', (join) =>
join
.onRef('asset_face.personGroupId', '=', 'person_group.id')
.on('asset_face.deletedAt', 'is', null)
.on('asset_face.isVisible', 'is', true),
)
.innerJoin(
(eb) =>
eb
.selectFrom('asset_face')
.innerJoin('asset', (join) =>
join
.onRef('asset.id', '=', 'asset_face.assetId')
.on('asset.visibility', '=', sql.lit(AssetVisibility.Timeline))
.on('asset.deletedAt', 'is', null)
.on((eb) => eb.or([eb('asset.ownerId', '=', anyUuid([userId, ...partnerIds])), inSharedAlbum(eb, userId)])),
.on((eb) =>
eb.or([eb('asset.ownerId', '=', anyUuid([userId, ...partnerIds])), inSharedAlbum(eb, userId)]),
),
)
.groupBy(['person_group.id', 'owned.ownerId', 'owned.personGroupId'])
.having((eb) =>
.select('asset_face.personGroupId')
.select((eb) => eb.fn.count<number>('asset.id').as('faceCount'))
// an array instead of a join or `in`, which the planner may turn into a scan of every face in the instance
.where('asset_face.personGroupId', '=', (eb) => {
const ownedGroupIds = eb
.selectFrom('person')
.select('person.personGroupId')
.where('person.ownerId', '=', userId);
return eb.fn.any(eb.fn<string[]>('array', [ownedGroupIds]));
})
.where('asset_face.deletedAt', 'is', null)
.where('asset_face.isVisible', 'is', true)
.groupBy('asset_face.personGroupId')
.as('faces'),
(join) => join.onRef('faces.personGroupId', '=', 'person_group.id'),
)
.where((eb) =>
eb.or([
eb('owned.name', '!=', ''),
eb(
faceCount,
'faces.faceCount',
'>=',
sql<number>`COALESCE(
(SELECT value -> 'people' ->> 'minimumFaces'
@@ -509,7 +522,7 @@ export class PersonRepository {
.$if(!options.closestFaceAssetId, (qb) =>
qb
.orderBy(sql`NULLIF("owned"."name", '') is null`, 'asc')
.orderBy(faceCount, 'desc')
.orderBy('faces.faceCount', 'desc')
.orderBy(sql`NULLIF("owned"."name", '')`, (om) => om.asc().nullsLast())
.orderBy('owned.createdAt'),
)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "immich-web",
"version": "3.3.1",
"version": "3.3.3",
"license": "GNU Affero General Public License version 3",
"type": "module",
"scripts": {