mirror of
https://github.com/immich-app/immich.git
synced 2026-01-12 21:23:43 -08:00
Compare commits
2 Commits
feat/datab
...
mobile-202
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
856894d581 | ||
|
|
5e3f5f2b55 |
BIN
mobile/fonts/GoogleSans/GoogleSans-Bold.ttf
Normal file
BIN
mobile/fonts/GoogleSans/GoogleSans-Bold.ttf
Normal file
Binary file not shown.
BIN
mobile/fonts/GoogleSans/GoogleSans-Italic.ttf
Normal file
BIN
mobile/fonts/GoogleSans/GoogleSans-Italic.ttf
Normal file
Binary file not shown.
BIN
mobile/fonts/GoogleSans/GoogleSans-Medium.ttf
Normal file
BIN
mobile/fonts/GoogleSans/GoogleSans-Medium.ttf
Normal file
Binary file not shown.
BIN
mobile/fonts/GoogleSans/GoogleSans-Regular.ttf
Normal file
BIN
mobile/fonts/GoogleSans/GoogleSans-Regular.ttf
Normal file
Binary file not shown.
BIN
mobile/fonts/GoogleSans/GoogleSans-SemiBold.ttf
Normal file
BIN
mobile/fonts/GoogleSans/GoogleSans-SemiBold.ttf
Normal file
Binary file not shown.
BIN
mobile/fonts/GoogleSansCode/GoogleSansCode-Medium.ttf
Normal file
BIN
mobile/fonts/GoogleSansCode/GoogleSansCode-Medium.ttf
Normal file
Binary file not shown.
BIN
mobile/fonts/GoogleSansCode/GoogleSansCode-Regular.ttf
Normal file
BIN
mobile/fonts/GoogleSansCode/GoogleSansCode-Regular.ttf
Normal file
Binary file not shown.
BIN
mobile/fonts/GoogleSansCode/GoogleSansCode-SemiBold.ttf
Normal file
BIN
mobile/fonts/GoogleSansCode/GoogleSansCode-SemiBold.ttf
Normal file
Binary file not shown.
@@ -51,4 +51,4 @@ const Map<String, Locale> locales = {
|
||||
|
||||
const String translationsPath = 'assets/i18n';
|
||||
|
||||
const List<Locale> localesNotSupportedByOverpass = [Locale('el', 'GR'), Locale('sr', 'Cyrl')];
|
||||
const List<Locale> localesNotSupportedByAppFont = [Locale('el', 'GR'), Locale('sr', 'Cyrl')];
|
||||
|
||||
@@ -100,7 +100,7 @@ class AppLogPage extends HookConsumerWidget {
|
||||
minLeadingWidth: 10,
|
||||
title: Text(
|
||||
truncateLogMessage(logMessage.message, 4),
|
||||
style: TextStyle(fontSize: 14.0, color: context.colorScheme.onSurface, fontFamily: "Inconsolata"),
|
||||
style: TextStyle(fontSize: 14.0, color: context.colorScheme.onSurface, fontFamily: "GoogleSansCode"),
|
||||
),
|
||||
subtitle: Text(
|
||||
"at ${DateFormat("HH:mm:ss.SSS").format(logMessage.createdAt)} in ${logMessage.logger}",
|
||||
|
||||
@@ -57,7 +57,7 @@ class AppLogDetailPage extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: SelectableText(
|
||||
text,
|
||||
style: const TextStyle(fontSize: 12.0, fontWeight: FontWeight.bold, fontFamily: "Inconsolata"),
|
||||
style: const TextStyle(fontSize: 12.0, fontWeight: FontWeight.bold, fontFamily: "GoogleSansCode"),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -88,7 +88,7 @@ class AppLogDetailPage extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: SelectableText(
|
||||
logger.toString(),
|
||||
style: const TextStyle(fontSize: 12.0, fontWeight: FontWeight.bold, fontFamily: "Inconsolata"),
|
||||
style: const TextStyle(fontSize: 12.0, fontWeight: FontWeight.bold, fontFamily: "GoogleSansCode"),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -234,7 +234,7 @@ class FolderPath extends StatelessWidget {
|
||||
Text(
|
||||
currentFolder.path,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inconsolata',
|
||||
fontFamily: 'GoogleSansCode',
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
color: context.colorScheme.onSurface.withAlpha(175),
|
||||
|
||||
@@ -41,7 +41,7 @@ class LoginPage extends HookConsumerWidget {
|
||||
style: TextStyle(
|
||||
color: context.colorScheme.onSurfaceSecondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: "Inconsolata",
|
||||
fontFamily: "GoogleSansCode",
|
||||
),
|
||||
),
|
||||
const Text(' '),
|
||||
@@ -51,7 +51,7 @@ class LoginPage extends HookConsumerWidget {
|
||||
style: TextStyle(
|
||||
color: context.primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: "Inconsolata",
|
||||
fontFamily: "GoogleSansCode",
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
|
||||
@@ -450,7 +450,7 @@ class _SegmentWidget extends StatelessWidget {
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
_segment.date.year.toString(),
|
||||
style: context.textTheme.labelMedium?.copyWith(fontFamily: "OverpassMono", fontWeight: FontWeight.w600),
|
||||
style: context.textTheme.labelMedium?.copyWith(fontFamily: "GoogleSansCode", fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -147,9 +147,9 @@ ImmichTheme decolorizeSurfaces({required ImmichTheme theme}) {
|
||||
}
|
||||
|
||||
String? _getFontFamilyFromLocale(Locale locale) {
|
||||
if (localesNotSupportedByOverpass.contains(locale)) {
|
||||
if (localesNotSupportedByAppFont.contains(locale)) {
|
||||
// Let Flutter use the default font
|
||||
return null;
|
||||
}
|
||||
return 'Overpass';
|
||||
return 'GoogleSans';
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ class AdvancedBottomSheet extends HookConsumerWidget {
|
||||
style: const TextStyle(
|
||||
fontSize: 12.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: "Inconsolata",
|
||||
fontFamily: "GoogleSansCode",
|
||||
),
|
||||
showCursor: true,
|
||||
),
|
||||
|
||||
@@ -36,7 +36,7 @@ class BackupUploadProgressBar extends ConsumerWidget {
|
||||
),
|
||||
Text(
|
||||
" ${uploadProgress.toStringAsFixed(0)}%",
|
||||
style: const TextStyle(fontSize: 12, fontFamily: "OverpassMono"),
|
||||
style: const TextStyle(fontSize: 12, fontFamily: "GoogleSansCode"),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -26,10 +26,10 @@ class BackupUploadStats extends ConsumerWidget {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(uploadFileProgress, style: const TextStyle(fontSize: 10, fontFamily: "OverpassMono")),
|
||||
Text(uploadFileProgress, style: const TextStyle(fontSize: 10, fontFamily: "GoogleSansCode")),
|
||||
Text(
|
||||
_formatUploadFileSpeed(uploadFileSpeed),
|
||||
style: const TextStyle(fontSize: 10, fontFamily: "OverpassMono"),
|
||||
style: const TextStyle(fontSize: 10, fontFamily: "GoogleSansCode"),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -43,7 +43,7 @@ class PinInput extends StatelessWidget {
|
||||
final defaultPinTheme = PinTheme(
|
||||
width: getPinSize().width,
|
||||
height: getPinSize().height,
|
||||
textStyle: TextStyle(fontSize: 24, color: context.colorScheme.onSurface, fontFamily: 'Overpass Mono'),
|
||||
textStyle: TextStyle(fontSize: 24, color: context.colorScheme.onSurface, fontFamily: 'GoogleSansCode'),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(19)),
|
||||
border: Border.all(color: context.colorScheme.surfaceBright),
|
||||
|
||||
@@ -50,7 +50,7 @@ class EntityCountTile extends StatelessWidget {
|
||||
const Spacer(),
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
style: const TextStyle(fontSize: 18, fontFamily: 'OverpassMono', fontWeight: FontWeight.w600),
|
||||
style: const TextStyle(fontSize: 18, fontFamily: 'GoogleSansCode', fontWeight: FontWeight.w600),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: zeroPadding(count, maxDigits),
|
||||
|
||||
@@ -117,7 +117,7 @@ class EndpointInputState extends ConsumerState<EndpointInput> {
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
validator: validateUrl,
|
||||
keyboardType: TextInputType.url,
|
||||
style: const TextStyle(fontFamily: 'Inconsolata', fontWeight: FontWeight.w600, fontSize: 14),
|
||||
style: const TextStyle(fontFamily: 'GoogleSansCode', fontWeight: FontWeight.w600, fontSize: 14),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'http(s)://immich.domain.com',
|
||||
contentPadding: const EdgeInsets.all(16),
|
||||
|
||||
@@ -155,7 +155,7 @@ class LocalNetworkPreference extends HookConsumerWidget {
|
||||
style: context.textTheme.labelLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: enabled ? context.primaryColor : context.colorScheme.onSurface.withAlpha(100),
|
||||
fontFamily: 'Inconsolata',
|
||||
fontFamily: 'GoogleSansCode',
|
||||
),
|
||||
),
|
||||
trailing: IconButton(
|
||||
@@ -175,7 +175,7 @@ class LocalNetworkPreference extends HookConsumerWidget {
|
||||
style: context.textTheme.labelLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: enabled ? context.primaryColor : context.colorScheme.onSurface.withAlpha(100),
|
||||
fontFamily: 'Inconsolata',
|
||||
fontFamily: 'GoogleSansCode',
|
||||
),
|
||||
),
|
||||
trailing: IconButton(
|
||||
|
||||
@@ -110,7 +110,7 @@ class NetworkingSettings extends HookConsumerWidget {
|
||||
currentEndpoint ?? "--",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontFamily: 'Inconsolata',
|
||||
fontFamily: 'GoogleSansCode',
|
||||
fontWeight: FontWeight.bold,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
|
||||
@@ -127,24 +127,26 @@ flutter:
|
||||
assets:
|
||||
- assets/
|
||||
fonts:
|
||||
- family: Inconsolata
|
||||
- family: GoogleSans
|
||||
fonts:
|
||||
- asset: fonts/Inconsolata-Regular.ttf
|
||||
- family: Overpass
|
||||
fonts:
|
||||
- asset: fonts/overpass/Overpass-Regular.ttf
|
||||
- asset: fonts/GoogleSans/GoogleSans-Regular.ttf
|
||||
weight: 400
|
||||
- asset: fonts/overpass/Overpass-Italic.ttf
|
||||
- asset: fonts/GoogleSans/GoogleSans-Italic.ttf
|
||||
style: italic
|
||||
- asset: fonts/overpass/Overpass-Medium.ttf
|
||||
- asset: fonts/GoogleSans/GoogleSans-Medium.ttf
|
||||
weight: 500
|
||||
- asset: fonts/overpass/Overpass-SemiBold.ttf
|
||||
- asset: fonts/GoogleSans/GoogleSans-SemiBold.ttf
|
||||
weight: 600
|
||||
- asset: fonts/overpass/Overpass-Bold.ttf
|
||||
- asset: fonts/GoogleSans/GoogleSans-Bold.ttf
|
||||
weight: 700
|
||||
- family: OverpassMono
|
||||
- family: GoogleSansCode
|
||||
fonts:
|
||||
- asset: fonts/overpass/OverpassMono.ttf
|
||||
- asset: fonts/GoogleSansCode/GoogleSansCode-Regular.ttf
|
||||
weight: 400
|
||||
- asset: fonts/GoogleSansCode/GoogleSansCode-Medium.ttf
|
||||
weight: 500
|
||||
- asset: fonts/GoogleSansCode/GoogleSansCode-SemiBold.ttf
|
||||
weight: 600
|
||||
flutter_launcher_icons:
|
||||
image_path_android: 'assets/immich-logo.png'
|
||||
adaptive_icon_background: '#ffffff'
|
||||
|
||||
@@ -49,6 +49,23 @@ returning
|
||||
"dateTimeOriginal",
|
||||
"timeZone"
|
||||
|
||||
-- AssetRepository.unlockProperties
|
||||
update "asset_exif"
|
||||
set
|
||||
"lockedProperties" = nullif(
|
||||
array(
|
||||
select distinct
|
||||
property
|
||||
from
|
||||
unnest("asset_exif"."lockedProperties") property
|
||||
where
|
||||
not property = any ($1)
|
||||
),
|
||||
'{}'
|
||||
)
|
||||
where
|
||||
"assetId" = $2
|
||||
|
||||
-- AssetRepository.getMetadata
|
||||
select
|
||||
"key",
|
||||
|
||||
@@ -223,6 +223,17 @@ export class AssetRepository {
|
||||
.execute();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, ['description']] })
|
||||
unlockProperties(assetId: string, properties: LockableProperty[]) {
|
||||
return this.db
|
||||
.updateTable('asset_exif')
|
||||
.where('assetId', '=', assetId)
|
||||
.set((eb) => ({
|
||||
lockedProperties: sql`nullif(array(select distinct property from unnest(${eb.ref('asset_exif.lockedProperties')}) property where not property = any(${properties})), '{}')`,
|
||||
}))
|
||||
.execute();
|
||||
}
|
||||
|
||||
async upsertJobStatus(...jobStatus: Insertable<AssetJobStatusTable>[]): Promise<void> {
|
||||
if (jobStatus.length === 0) {
|
||||
return;
|
||||
|
||||
@@ -1758,6 +1758,12 @@ describe(MetadataService.name, () => {
|
||||
GPSLatitude: gps,
|
||||
GPSLongitude: gps,
|
||||
});
|
||||
expect(mocks.asset.unlockProperties).toHaveBeenCalledWith(asset.id, [
|
||||
'description',
|
||||
'latitude',
|
||||
'longitude',
|
||||
'dateTimeOriginal',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -461,6 +461,8 @@ export class MetadataService extends BaseService {
|
||||
await this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.Sidecar, path: sidecarPath });
|
||||
}
|
||||
|
||||
await this.assetRepository.unlockProperties(asset.id, lockedProperties);
|
||||
|
||||
return JobStatus.Success;
|
||||
}
|
||||
|
||||
|
||||
@@ -87,4 +87,64 @@ describe(AssetRepository.name, () => {
|
||||
).resolves.toEqual({ lockedProperties: ['description', 'dateTimeOriginal'] });
|
||||
});
|
||||
});
|
||||
|
||||
describe('unlockProperties', () => {
|
||||
it('should unlock one property', async () => {
|
||||
const { ctx, sut } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||
await ctx.newExif({
|
||||
assetId: asset.id,
|
||||
dateTimeOriginal: '2023-11-19T18:11:00',
|
||||
lockedProperties: ['dateTimeOriginal', 'description'],
|
||||
});
|
||||
|
||||
await expect(
|
||||
ctx.database
|
||||
.selectFrom('asset_exif')
|
||||
.select('lockedProperties')
|
||||
.where('assetId', '=', asset.id)
|
||||
.executeTakeFirstOrThrow(),
|
||||
).resolves.toEqual({ lockedProperties: ['dateTimeOriginal', 'description'] });
|
||||
|
||||
await sut.unlockProperties(asset.id, ['dateTimeOriginal']);
|
||||
|
||||
await expect(
|
||||
ctx.database
|
||||
.selectFrom('asset_exif')
|
||||
.select('lockedProperties')
|
||||
.where('assetId', '=', asset.id)
|
||||
.executeTakeFirstOrThrow(),
|
||||
).resolves.toEqual({ lockedProperties: ['description'] });
|
||||
});
|
||||
|
||||
it('should unlock all properties', async () => {
|
||||
const { ctx, sut } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||
await ctx.newExif({
|
||||
assetId: asset.id,
|
||||
dateTimeOriginal: '2023-11-19T18:11:00',
|
||||
lockedProperties: ['dateTimeOriginal', 'description'],
|
||||
});
|
||||
|
||||
await expect(
|
||||
ctx.database
|
||||
.selectFrom('asset_exif')
|
||||
.select('lockedProperties')
|
||||
.where('assetId', '=', asset.id)
|
||||
.executeTakeFirstOrThrow(),
|
||||
).resolves.toEqual({ lockedProperties: ['dateTimeOriginal', 'description'] });
|
||||
|
||||
await sut.unlockProperties(asset.id, ['description', 'dateTimeOriginal']);
|
||||
|
||||
await expect(
|
||||
ctx.database
|
||||
.selectFrom('asset_exif')
|
||||
.select('lockedProperties')
|
||||
.where('assetId', '=', asset.id)
|
||||
.executeTakeFirstOrThrow(),
|
||||
).resolves.toEqual({ lockedProperties: null });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ export const newAssetRepositoryMock = (): Mocked<RepositoryInterface<AssetReposi
|
||||
upsertExif: vitest.fn(),
|
||||
updateAllExif: vitest.fn(),
|
||||
updateDateTimeOriginal: vitest.fn().mockResolvedValue([]),
|
||||
unlockProperties: vitest.fn().mockResolvedValue([]),
|
||||
upsertJobStatus: vitest.fn(),
|
||||
getForCopy: vitest.fn(),
|
||||
getByDayOfYear: vitest.fn(),
|
||||
|
||||
Reference in New Issue
Block a user