mirror of
https://github.com/immich-app/immich.git
synced 2026-06-22 14:52:17 -07:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 06eae08d44 | |||
| c484bd99b6 | |||
| c0bf5a4c56 | |||
| d9d50d2848 | |||
| c7453a67fd | |||
| e918e3a313 | |||
| 92293eba19 | |||
| e93f0db224 | |||
| 46d8be8ffc | |||
| df051c24b3 |
@@ -73,6 +73,7 @@ jobs:
|
||||
needs: pre-job
|
||||
permissions:
|
||||
contents: read
|
||||
deployments: write
|
||||
pull-requests: write
|
||||
if: ${{ github.actor != 'dependabot[bot]' && fromJSON(needs.pre-job.outputs.should_run).mobile == true }}
|
||||
runs-on: mich
|
||||
@@ -142,9 +143,18 @@ jobs:
|
||||
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
|
||||
IS_MAIN: ${{ github.ref == 'refs/heads/main' }}
|
||||
IS_MAIN_DEPLOYMENT: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
IS_DEPLOYMENT_BUILD: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork) }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
run: |
|
||||
if [[ $IS_DEPLOYMENT_BUILD == 'true' ]]; then
|
||||
export ANDROID_APP_LABEL='Immich Staging'
|
||||
fi
|
||||
|
||||
if [[ $IS_MAIN == 'true' ]]; then
|
||||
if [[ $IS_MAIN_DEPLOYMENT == 'true' ]]; then
|
||||
export ANDROID_APPLICATION_ID=app.immich.main
|
||||
fi
|
||||
flutter build apk --release
|
||||
flutter build apk --release --split-per-abi --target-platform android-arm,android-arm64,android-x64
|
||||
else
|
||||
@@ -158,6 +168,50 @@ jobs:
|
||||
name: release-apk-signed
|
||||
path: mobile/build/app/outputs/flutter-apk/*.apk
|
||||
|
||||
- name: Publish Android APK deployment
|
||||
if: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork) }}
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
APK_URL: ${{ steps.upload-apk.outputs.artifact-url }}
|
||||
with:
|
||||
script: |
|
||||
const artifactUrl = process.env.APK_URL;
|
||||
const isPullRequest = context.eventName === "pull_request";
|
||||
const pullNumber = context.payload.pull_request?.number;
|
||||
const environment = isPullRequest ? `mobile-android-apk-pr-${pullNumber}` : "mobile-android-apk";
|
||||
const description = isPullRequest
|
||||
? `Signed Android APK for PR #${pullNumber}`
|
||||
: "Latest signed Android APK from main";
|
||||
const ref = isPullRequest ? context.payload.pull_request.head.sha : context.sha;
|
||||
|
||||
if (!artifactUrl) {
|
||||
throw new Error("The Android APK artifact URL was not generated");
|
||||
}
|
||||
|
||||
const runUrl = `${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
||||
const deployment = await github.rest.repos.createDeployment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref,
|
||||
environment,
|
||||
description,
|
||||
auto_merge: false,
|
||||
required_contexts: [],
|
||||
production_environment: false,
|
||||
transient_environment: isPullRequest,
|
||||
});
|
||||
|
||||
await github.rest.repos.createDeploymentStatus({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
deployment_id: deployment.data.id,
|
||||
state: "success",
|
||||
environment,
|
||||
environment_url: artifactUrl,
|
||||
log_url: runUrl,
|
||||
description: "Signed APK artifact is ready for testing",
|
||||
});
|
||||
|
||||
- name: Comment APK download link on PR
|
||||
if: ${{ github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork }}
|
||||
uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0
|
||||
|
||||
@@ -13,6 +13,16 @@ if (keystorePropertiesFile.exists()) {
|
||||
keystorePropertiesFile.withInputStream { keystoreProperties.load(it) }
|
||||
}
|
||||
|
||||
def androidApplicationId = System.getenv("ANDROID_APPLICATION_ID")?.trim()
|
||||
if (!androidApplicationId) {
|
||||
androidApplicationId = "app.alextran.immich"
|
||||
}
|
||||
|
||||
def androidAppLabel = System.getenv("ANDROID_APP_LABEL")?.trim()
|
||||
if (!androidAppLabel) {
|
||||
androidAppLabel = "Immich"
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = flutter.ndkVersion
|
||||
@@ -37,11 +47,12 @@ android {
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "app.alextran.immich"
|
||||
applicationId androidApplicationId
|
||||
minSdk = 26
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode flutter.versionCode
|
||||
versionName flutter.versionName
|
||||
manifestPlaceholders = [appLabel: androidAppLabel]
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
||||
|
||||
<application android:label="Immich" android:name=".ImmichApp" android:usesCleartextTraffic="true"
|
||||
<application android:label="${appLabel}" android:name=".ImmichApp" android:usesCleartextTraffic="true"
|
||||
android:icon="@mipmap/ic_launcher" android:requestLegacyExternalStorage="true"
|
||||
android:largeHeap="true" android:enableOnBackInvokedCallback="false" android:allowBackup="false"
|
||||
android:networkSecurityConfig="@xml/network_security_config">
|
||||
|
||||
@@ -38,8 +38,8 @@
|
||||
</p>
|
||||
|
||||
> [!WARNING]
|
||||
> ⚠️ Always follow [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) backup plan for your precious photos and videos!
|
||||
>
|
||||
> ⚠️ Değerli fotoğraflarınız ve videolarınız için daima [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) yedekleme planını uygulayın!
|
||||
>
|
||||
|
||||
> [!NOTE]
|
||||
> Kurulum dahil olmak üzere resmi belgeleri https://immich.app/ adresinde bulabilirsiniz.
|
||||
|
||||
@@ -129,6 +129,7 @@ from
|
||||
and "integrity_report"."type" = $1
|
||||
where
|
||||
"asset"."deletedAt" is null
|
||||
and "asset"."isExternal" = false
|
||||
and "integrity_report"."createdAt" >= $2
|
||||
and "integrity_report"."createdAt" <= $3
|
||||
order by
|
||||
|
||||
@@ -177,6 +177,7 @@ export class IntegrityRepository {
|
||||
'asset.id as assetId',
|
||||
'integrity_report.id as reportId',
|
||||
])
|
||||
.where('asset.isExternal', '=', sql.lit(false))
|
||||
.$if(startMarker !== undefined, (qb) => qb.where('integrity_report.createdAt', '>=', startMarker!))
|
||||
.$if(endMarker !== undefined, (qb) => qb.where('integrity_report.createdAt', '<=', endMarker!))
|
||||
.orderBy('integrity_report.createdAt', 'asc')
|
||||
|
||||
@@ -2939,6 +2939,8 @@ describe(MediaService.name, () => {
|
||||
'7',
|
||||
'-global_quality:v',
|
||||
'23',
|
||||
'-b:v',
|
||||
'6897k',
|
||||
'-maxrate',
|
||||
'10000k',
|
||||
'-bufsize',
|
||||
|
||||
@@ -788,6 +788,12 @@ export class QsvSwDecodeConfig extends BaseHWConfig {
|
||||
const options = [`-${this.useCQP() ? 'q:v' : 'global_quality:v'}`, `${this.config.crf}`];
|
||||
const bitrates = this.getBitrateDistribution();
|
||||
if (bitrates.max > 0) {
|
||||
// Workaround for https://github.com/immich-app/immich/issues/29220, to be revisited
|
||||
// QSV seems to ignore -maxrate without -b:v
|
||||
// -b:v alongside global_quality uses QVBR
|
||||
if (!this.useCQP()) {
|
||||
options.push('-b:v', `${bitrates.target}${bitrates.unit}`);
|
||||
}
|
||||
options.push('-maxrate', `${bitrates.max}${bitrates.unit}`, '-bufsize', `${bitrates.max * 2}${bitrates.unit}`);
|
||||
}
|
||||
return options;
|
||||
|
||||
@@ -686,6 +686,22 @@ describe(IntegrityService.name, () => {
|
||||
nextCursor: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should skip external library files', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const job = ctx.getMock(JobRepository);
|
||||
job.queue.mockResolvedValue(void 0);
|
||||
|
||||
const { user } = await ctx.newUser();
|
||||
|
||||
await ctx.newAsset({ ownerId: user.id, isExternal: true });
|
||||
|
||||
await sut.handleChecksumFiles({ refreshOnly: false });
|
||||
|
||||
await expect(
|
||||
ctx.get(IntegrityRepository).getIntegrityReport({ limit: 100 }, IntegrityReport.ChecksumFail),
|
||||
).resolves.toEqual({ items: [], nextCursor: undefined });
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleChecksumRefresh', () => {
|
||||
|
||||
@@ -324,6 +324,18 @@
|
||||
shortcut: { key: ' ' },
|
||||
onShortcut: () => (videoPlayer?.paused ? videoPlayer?.play() : videoPlayer?.pause()),
|
||||
},
|
||||
{
|
||||
shortcut: { shift: true, key: 'ArrowLeft' },
|
||||
onShortcut: () =>
|
||||
videoPlayer ? (videoPlayer.currentTime = Math.max(videoPlayer.currentTime - 0.4, 0)) : undefined,
|
||||
},
|
||||
{
|
||||
shortcut: { shift: true, key: 'ArrowRight' },
|
||||
onShortcut: () =>
|
||||
videoPlayer
|
||||
? (videoPlayer.currentTime = Math.min(videoPlayer.currentTime + 0.4, videoPlayer.duration))
|
||||
: undefined,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
|
||||
@@ -24,7 +24,8 @@ class FaceManager {
|
||||
});
|
||||
|
||||
readonly people = $derived.by(() => {
|
||||
const people = new SvelteMap<string, PersonResponseDto>();
|
||||
// eslint-disable-next-line svelte/prefer-svelte-reactivity
|
||||
const people = new Map<string, PersonResponseDto>();
|
||||
|
||||
for (const face of this.data) {
|
||||
if (face.person) {
|
||||
@@ -32,7 +33,7 @@ class FaceManager {
|
||||
}
|
||||
}
|
||||
|
||||
return people.values();
|
||||
return Array.from(people.values());
|
||||
});
|
||||
|
||||
readonly facesByPersonId = $derived.by(() => {
|
||||
|
||||
Reference in New Issue
Block a user