mirror of
https://github.com/immich-app/immich.git
synced 2025-05-31 20:25:32 -04:00
chore: style grid
This commit is contained in:
parent
419d3669a2
commit
53974e7276
@ -1,5 +1,5 @@
|
|||||||
import 'package:immich_mobile/domain/models/asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/render_list.model.dart';
|
import 'package:immich_mobile/domain/services/render_list.service.dart';
|
||||||
|
|
||||||
abstract class IAssetRepository {
|
abstract class IAssetRepository {
|
||||||
/// Batch insert asset
|
/// Batch insert asset
|
||||||
|
@ -1,42 +1,70 @@
|
|||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
enum RenderListGroupBy { month, day }
|
||||||
|
|
||||||
sealed class RenderListElement {
|
sealed class RenderListElement {
|
||||||
const RenderListElement();
|
const RenderListElement({required this.date});
|
||||||
|
|
||||||
|
final DateTime date;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(covariant RenderListElement other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
|
return date == other.date;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => date.hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
class RenderListMonthHeaderElement extends RenderListElement {
|
class RenderListMonthHeaderElement extends RenderListElement {
|
||||||
final String header;
|
late final String header;
|
||||||
|
|
||||||
const RenderListMonthHeaderElement({required this.header});
|
RenderListMonthHeaderElement({required super.date}) {
|
||||||
|
final formatter = DateTime.now().year == date.year
|
||||||
|
? DateFormat.MMMM()
|
||||||
|
: DateFormat.yMMMM();
|
||||||
|
header = formatter.format(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(covariant RenderListMonthHeaderElement other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
|
return super == other && header == other.header;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => super.hashCode ^ date.hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
class RenderListDayHeaderElement extends RenderListElement {
|
class RenderListDayHeaderElement extends RenderListElement {
|
||||||
final String header;
|
final String header;
|
||||||
|
|
||||||
const RenderListDayHeaderElement({required this.header});
|
const RenderListDayHeaderElement({required super.date, required this.header});
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(covariant RenderListDayHeaderElement other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
|
return super == other && header == other.header;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => super.hashCode ^ date.hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
class RenderListAssetElement extends RenderListElement {
|
class RenderListAssetElement extends RenderListElement {
|
||||||
final DateTime date;
|
|
||||||
final int assetCount;
|
final int assetCount;
|
||||||
final int assetOffset;
|
final int assetOffset;
|
||||||
|
|
||||||
const RenderListAssetElement({
|
const RenderListAssetElement({
|
||||||
required this.date,
|
required super.date,
|
||||||
required this.assetCount,
|
required this.assetCount,
|
||||||
required this.assetOffset,
|
required this.assetOffset,
|
||||||
});
|
});
|
||||||
|
|
||||||
RenderListAssetElement copyWith({
|
|
||||||
DateTime? date,
|
|
||||||
int? assetCount,
|
|
||||||
int? assetOffset,
|
|
||||||
}) {
|
|
||||||
return RenderListAssetElement(
|
|
||||||
date: date ?? this.date,
|
|
||||||
assetCount: assetCount ?? this.assetCount,
|
|
||||||
assetOffset: assetOffset ?? this.assetOffset,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() =>
|
||||||
'RenderListAssetElement(date: $date, assetCount: $assetCount, assetOffset: $assetOffset)';
|
'RenderListAssetElement(date: $date, assetCount: $assetCount, assetOffset: $assetOffset)';
|
||||||
@ -45,12 +73,12 @@ class RenderListAssetElement extends RenderListElement {
|
|||||||
bool operator ==(covariant RenderListAssetElement other) {
|
bool operator ==(covariant RenderListAssetElement other) {
|
||||||
if (identical(this, other)) return true;
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
return other.date == date &&
|
return super == other &&
|
||||||
other.assetCount == assetCount &&
|
other.assetCount == assetCount &&
|
||||||
other.assetOffset == assetOffset;
|
other.assetOffset == assetOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
date.hashCode ^ assetCount.hashCode ^ assetOffset.hashCode;
|
super.hashCode ^ assetCount.hashCode ^ assetOffset.hashCode;
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,11 @@ import 'package:drift/drift.dart';
|
|||||||
import 'package:immich_mobile/domain/entities/asset.entity.drift.dart';
|
import 'package:immich_mobile/domain/entities/asset.entity.drift.dart';
|
||||||
import 'package:immich_mobile/domain/interfaces/asset.interface.dart';
|
import 'package:immich_mobile/domain/interfaces/asset.interface.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/render_list.model.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/render_list_element.model.dart';
|
import 'package:immich_mobile/domain/models/render_list_element.model.dart';
|
||||||
import 'package:immich_mobile/domain/repositories/database.repository.dart';
|
import 'package:immich_mobile/domain/repositories/database.repository.dart';
|
||||||
|
import 'package:immich_mobile/domain/services/render_list.service.dart';
|
||||||
import 'package:immich_mobile/utils/extensions/drift.extension.dart';
|
import 'package:immich_mobile/utils/extensions/drift.extension.dart';
|
||||||
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
|
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
|
|
||||||
class RemoteAssetDriftRepository with LogContext implements IAssetRepository {
|
class RemoteAssetDriftRepository with LogContext implements IAssetRepository {
|
||||||
final DriftDatabaseRepository _db;
|
final DriftDatabaseRepository _db;
|
||||||
@ -66,7 +65,6 @@ class RemoteAssetDriftRepository with LogContext implements IAssetRepository {
|
|||||||
..orderBy([OrderingTerm.desc(createdTimeExp)]);
|
..orderBy([OrderingTerm.desc(createdTimeExp)]);
|
||||||
|
|
||||||
int lastAssetOffset = 0;
|
int lastAssetOffset = 0;
|
||||||
final monthFormatter = DateFormat.yMMMM();
|
|
||||||
|
|
||||||
return query
|
return query
|
||||||
.expand((row) {
|
.expand((row) {
|
||||||
@ -76,9 +74,7 @@ class RemoteAssetDriftRepository with LogContext implements IAssetRepository {
|
|||||||
lastAssetOffset += assetCount;
|
lastAssetOffset += assetCount;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
RenderListMonthHeaderElement(
|
RenderListMonthHeaderElement(date: createdTime),
|
||||||
header: monthFormatter.format(createdTime),
|
|
||||||
),
|
|
||||||
RenderListAssetElement(
|
RenderListAssetElement(
|
||||||
date: createdTime,
|
date: createdTime,
|
||||||
assetCount: assetCount,
|
assetCount: assetCount,
|
||||||
|
@ -2,21 +2,21 @@ import 'package:immich_mobile/domain/interfaces/store.interface.dart';
|
|||||||
import 'package:immich_mobile/domain/models/app_setting.model.dart';
|
import 'package:immich_mobile/domain/models/app_setting.model.dart';
|
||||||
|
|
||||||
class AppSettingService {
|
class AppSettingService {
|
||||||
final IStoreRepository store;
|
final IStoreRepository _store;
|
||||||
|
|
||||||
const AppSettingService(this.store);
|
const AppSettingService(this._store);
|
||||||
|
|
||||||
Future<T> getSetting<T>(AppSetting<T> setting) async {
|
Future<T> getSetting<T>(AppSetting<T> setting) async {
|
||||||
final value = await store.tryGet(setting.storeKey);
|
final value = await _store.tryGet(setting.storeKey);
|
||||||
return value ?? setting.defaultValue;
|
return value ?? setting.defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> setSetting<T>(AppSetting<T> setting, T value) async {
|
Future<bool> setSetting<T>(AppSetting<T> setting, T value) async {
|
||||||
return await store.set(setting.storeKey, value);
|
return await _store.set(setting.storeKey, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<T> watchSetting<T>(AppSetting<T> setting) {
|
Stream<T> watchSetting<T>(AppSetting<T> setting) {
|
||||||
return store
|
return _store
|
||||||
.watch(setting.storeKey)
|
.watch(setting.storeKey)
|
||||||
.map((value) => value ?? setting.defaultValue);
|
.map((value) => value ?? setting.defaultValue);
|
||||||
}
|
}
|
||||||
|
@ -5,15 +5,16 @@ import 'package:immich_mobile/domain/interfaces/asset.interface.dart';
|
|||||||
import 'package:immich_mobile/domain/models/asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/render_list_element.model.dart';
|
import 'package:immich_mobile/domain/models/render_list_element.model.dart';
|
||||||
import 'package:immich_mobile/service_locator.dart';
|
import 'package:immich_mobile/service_locator.dart';
|
||||||
|
import 'package:immich_mobile/utils/constants/globals.dart';
|
||||||
|
|
||||||
class RenderList {
|
class RenderList {
|
||||||
final List<RenderListElement> elements;
|
final List<RenderListElement> elements;
|
||||||
final int totalCount;
|
final int totalCount;
|
||||||
|
|
||||||
/// global offset of assets in [_buf]
|
/// offset of the assets from last section in [_buf]
|
||||||
int _bufOffset = 0;
|
int _bufOffset = 0;
|
||||||
|
|
||||||
/// reference to batch of assets loaded from DB with offset [_bufOffset]
|
/// assets cache loaded from DB with offset [_bufOffset]
|
||||||
List<Asset> _buf = [];
|
List<Asset> _buf = [];
|
||||||
|
|
||||||
RenderList({required this.elements, required this.totalCount});
|
RenderList({required this.elements, required this.totalCount});
|
||||||
@ -25,21 +26,18 @@ class RenderList {
|
|||||||
assert(count > 0);
|
assert(count > 0);
|
||||||
assert(offset + count <= totalCount);
|
assert(offset + count <= totalCount);
|
||||||
|
|
||||||
// general case: we have the query to load assets via offset from the DB on demand
|
// the requested slice (offset:offset+count) is not contained in the cache buffer `_buf`
|
||||||
|
// thus, fill the buffer with a new batch of assets that at least contains the requested
|
||||||
|
// assets and some more
|
||||||
if (offset < _bufOffset || offset + count > _bufOffset + _buf.length) {
|
if (offset < _bufOffset || offset + count > _bufOffset + _buf.length) {
|
||||||
// the requested slice (offset:offset+count) is not contained in the cache buffer `_buf`
|
|
||||||
// thus, fill the buffer with a new batch of assets that at least contains the requested
|
|
||||||
// assets and some more
|
|
||||||
|
|
||||||
final bool forward = _bufOffset < offset;
|
final bool forward = _bufOffset < offset;
|
||||||
// if the requested offset is greater than the cached offset, the user scrolls forward "down"
|
|
||||||
const batchSize = 256;
|
|
||||||
const oppositeSize = 64;
|
|
||||||
|
|
||||||
// make sure to load a meaningful amount of data (and not only the requested slice)
|
// make sure to load a meaningful amount of data (and not only the requested slice)
|
||||||
// otherwise, each call to [loadAssets] would result in DB call trashing performance
|
// otherwise, each call to [loadAssets] would result in DB call trashing performance
|
||||||
// fills small requests to [batchSize], adds some legroom into the opposite scroll direction for large requests
|
// fills small requests to [batchSize], adds some legroom into the opposite scroll direction for large requests
|
||||||
final len = math.max(batchSize, count + oppositeSize);
|
final len =
|
||||||
|
math.max(kRenderListBatchSize, count + kRenderListOppositeBatchSize);
|
||||||
|
|
||||||
// when scrolling forward, start shortly before the requested offset...
|
// when scrolling forward, start shortly before the requested offset...
|
||||||
// when scrolling backward, end shortly after the requested offset...
|
// when scrolling backward, end shortly after the requested offset...
|
||||||
// ... to guard against the user scrolling in the other direction
|
// ... to guard against the user scrolling in the other direction
|
||||||
@ -47,9 +45,10 @@ class RenderList {
|
|||||||
final start = math.max(
|
final start = math.max(
|
||||||
0,
|
0,
|
||||||
forward
|
forward
|
||||||
? offset - oppositeSize
|
? offset - kRenderListOppositeBatchSize
|
||||||
: (len > batchSize ? offset : offset + count - len),
|
: (len > kRenderListBatchSize ? offset : offset + count - len),
|
||||||
);
|
);
|
||||||
|
|
||||||
// load the calculated batch (start:start+len) from the DB and put it into the buffer
|
// load the calculated batch (start:start+len) from the DB and put it into the buffer
|
||||||
_buf =
|
_buf =
|
||||||
await di<IAssetRepository>().fetchAssets(offset: start, limit: len);
|
await di<IAssetRepository>().fetchAssets(offset: start, limit: len);
|
||||||
@ -58,6 +57,7 @@ class RenderList {
|
|||||||
assert(_bufOffset <= offset);
|
assert(_bufOffset <= offset);
|
||||||
assert(_bufOffset + _buf.length >= offset + count);
|
assert(_bufOffset + _buf.length >= offset + count);
|
||||||
}
|
}
|
||||||
|
|
||||||
// return the requested slice from the buffer (we made sure before that the assets are loaded!)
|
// return the requested slice from the buffer (we made sure before that the assets are loaded!)
|
||||||
return _buf.slice(offset - _bufOffset, offset - _bufOffset + count);
|
return _buf.slice(offset - _bufOffset, offset - _bufOffset + count);
|
||||||
}
|
}
|
@ -6,32 +6,29 @@ import 'package:immich_mobile/domain/repositories/database.repository.dart';
|
|||||||
import 'package:immich_mobile/service_locator.dart';
|
import 'package:immich_mobile/service_locator.dart';
|
||||||
import 'package:immich_mobile/utils/constants/globals.dart';
|
import 'package:immich_mobile/utils/constants/globals.dart';
|
||||||
import 'package:immich_mobile/utils/immich_api_client.dart';
|
import 'package:immich_mobile/utils/immich_api_client.dart';
|
||||||
import 'package:immich_mobile/utils/log_manager.dart';
|
import 'package:immich_mobile/utils/isolate_helper.dart';
|
||||||
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
|
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
class SyncService with LogContext {
|
class SyncService with LogContext {
|
||||||
final ImmichApiClient _appClient;
|
|
||||||
final DriftDatabaseRepository _db;
|
final DriftDatabaseRepository _db;
|
||||||
|
|
||||||
SyncService(this._appClient, this._db);
|
SyncService(this._db);
|
||||||
|
|
||||||
Future<bool> doFullSyncForUserDrift(
|
Future<bool> doFullSyncForUserDrift(
|
||||||
User user, {
|
User user, {
|
||||||
DateTime? updatedUtil,
|
DateTime? updatedUtil,
|
||||||
int? limit,
|
int? limit,
|
||||||
}) async {
|
}) async {
|
||||||
final clientData = _appClient.clientData;
|
final helper = IsolateHelper()..preIsolateHandling();
|
||||||
try {
|
try {
|
||||||
await _db.computeWithDatabase(
|
await _db.computeWithDatabase(
|
||||||
connect: (connection) => DriftDatabaseRepository(connection),
|
connect: (connection) => DriftDatabaseRepository(connection),
|
||||||
computation: (database) async {
|
computation: (database) async {
|
||||||
ServiceLocator.configureServicesForIsolate(database: database);
|
helper.postIsolateHandling(database: database);
|
||||||
LogManager.I.init();
|
|
||||||
final logger = Logger("SyncService <Isolate>");
|
final logger = Logger("SyncService <Isolate>");
|
||||||
final syncClient =
|
final syncClient = di<ImmichApiClient>().getSyncApi();
|
||||||
ImmichApiClient.clientData(clientData).getSyncApi();
|
|
||||||
|
|
||||||
final chunkSize = limit ?? kFullSyncChunkSize;
|
final chunkSize = limit ?? kFullSyncChunkSize;
|
||||||
final updatedTill = updatedUtil ?? DateTime.now().toUtc();
|
final updatedTill = updatedUtil ?? DateTime.now().toUtc();
|
||||||
|
@ -3,8 +3,13 @@ import 'package:immich_mobile/domain/interfaces/asset.interface.dart';
|
|||||||
import 'package:immich_mobile/domain/models/render_list_element.model.dart';
|
import 'package:immich_mobile/domain/models/render_list_element.model.dart';
|
||||||
import 'package:immich_mobile/presentation/components/image/immich_image.widget.dart';
|
import 'package:immich_mobile/presentation/components/image/immich_image.widget.dart';
|
||||||
import 'package:immich_mobile/service_locator.dart';
|
import 'package:immich_mobile/service_locator.dart';
|
||||||
|
import 'package:immich_mobile/utils/extensions/async_snapshot.extension.dart';
|
||||||
|
import 'package:immich_mobile/utils/extensions/build_context.extension.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||||
|
|
||||||
|
part 'immich_asset_grid_header.widget.dart';
|
||||||
|
|
||||||
class ImAssetGrid extends StatelessWidget {
|
class ImAssetGrid extends StatelessWidget {
|
||||||
const ImAssetGrid({super.key});
|
const ImAssetGrid({super.key});
|
||||||
|
|
||||||
@ -21,11 +26,14 @@ class ImAssetGrid extends StatelessWidget {
|
|||||||
final elements = renderList.elements;
|
final elements = renderList.elements;
|
||||||
return ScrollablePositionedList.builder(
|
return ScrollablePositionedList.builder(
|
||||||
itemCount: elements.length,
|
itemCount: elements.length,
|
||||||
|
addAutomaticKeepAlives: false,
|
||||||
|
minCacheExtent: 100,
|
||||||
itemBuilder: (_, sectionIndex) {
|
itemBuilder: (_, sectionIndex) {
|
||||||
final section = elements[sectionIndex];
|
final section = elements[sectionIndex];
|
||||||
|
|
||||||
return switch (section) {
|
return switch (section) {
|
||||||
RenderListMonthHeaderElement() => Text(section.header),
|
RenderListMonthHeaderElement() =>
|
||||||
|
_MonthHeader(text: section.header),
|
||||||
RenderListDayHeaderElement() => Text(section.header),
|
RenderListDayHeaderElement() => Text(section.header),
|
||||||
RenderListAssetElement() => FutureBuilder(
|
RenderListAssetElement() => FutureBuilder(
|
||||||
future: renderList.loadAssets(
|
future: renderList.loadAssets(
|
||||||
@ -34,12 +42,11 @@ class ImAssetGrid extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
builder: (_, assetsSnap) {
|
builder: (_, assetsSnap) {
|
||||||
final assets = assetsSnap.data;
|
final assets = assetsSnap.data;
|
||||||
if (assets == null) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
return GridView.builder(
|
return GridView.builder(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
|
addAutomaticKeepAlives: false,
|
||||||
|
cacheExtent: 100,
|
||||||
gridDelegate:
|
gridDelegate:
|
||||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
crossAxisCount: 4,
|
crossAxisCount: 4,
|
||||||
@ -47,8 +54,10 @@ class ImAssetGrid extends StatelessWidget {
|
|||||||
itemBuilder: (_, i) {
|
itemBuilder: (_, i) {
|
||||||
return SizedBox.square(
|
return SizedBox.square(
|
||||||
dimension: 200,
|
dimension: 200,
|
||||||
// ignore: avoid-unsafe-collection-methods
|
child: assetsSnap.isWaiting || assets == null
|
||||||
child: ImImage(assets.elementAt(i)),
|
? Container(color: Colors.grey)
|
||||||
|
// ignore: avoid-unsafe-collection-methods
|
||||||
|
: ImImage(assets[i]),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
itemCount: section.assetCount,
|
itemCount: section.assetCount,
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
part of 'immich_asset_grid.widget.dart';
|
||||||
|
|
||||||
|
class _HeaderText extends StatelessWidget {
|
||||||
|
final String text;
|
||||||
|
final TextStyle? style;
|
||||||
|
|
||||||
|
const _HeaderText({required this.text, this.style});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 32.0, left: 16.0, right: 12.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(text, style: style),
|
||||||
|
const Spacer(),
|
||||||
|
IconButton(
|
||||||
|
// ignore: no-empty-block
|
||||||
|
onPressed: () {},
|
||||||
|
icon: Icon(
|
||||||
|
Symbols.check_circle_rounded,
|
||||||
|
color: context.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MonthHeader extends StatelessWidget {
|
||||||
|
final String text;
|
||||||
|
|
||||||
|
const _MonthHeader({required this.text});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _HeaderText(
|
||||||
|
text: text,
|
||||||
|
style: context.textTheme.bodyLarge?.copyWith(fontSize: 24.0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,6 @@ class HomePage extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return const ImAssetGrid();
|
return const Scaffold(body: ImAssetGrid());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -146,6 +146,7 @@ class _LoginPageState extends State<LoginPage>
|
|||||||
child: Column(children: [
|
child: Column(children: [
|
||||||
Expanded(child: rotatingLogo),
|
Expanded(child: rotatingLogo),
|
||||||
serverUrl,
|
serverUrl,
|
||||||
|
const SizedGap.sh(),
|
||||||
Expanded(flex: 2, child: form),
|
Expanded(flex: 2, child: form),
|
||||||
bottom,
|
bottom,
|
||||||
]),
|
]),
|
||||||
|
@ -66,7 +66,8 @@ class LoginPageCubit extends Cubit<LoginPageState> with LogContext {
|
|||||||
url = await loginService.resolveEndpoint(uri);
|
url = await loginService.resolveEndpoint(uri);
|
||||||
|
|
||||||
di<IStoreRepository>().set(StoreKey.serverEndpoint, url);
|
di<IStoreRepository>().set(StoreKey.serverEndpoint, url);
|
||||||
ServiceLocator.registerPostValidationServices(url);
|
ServiceLocator.registerApiClient(url);
|
||||||
|
ServiceLocator.registerPostValidationServices();
|
||||||
ServiceLocator.registerPostGlobalStates();
|
ServiceLocator.registerPostGlobalStates();
|
||||||
|
|
||||||
// Fetch server features
|
// Fetch server features
|
||||||
|
@ -32,28 +32,28 @@ class LoginForm extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocSelector<LoginPageCubit, LoginPageState, bool>(
|
return BlocSelector<LoginPageCubit, LoginPageState, bool>(
|
||||||
selector: (model) => model.isServerValidated,
|
selector: (model) => model.isServerValidated,
|
||||||
builder: (_, isServerValidated) => AnimatedSwitcher(
|
builder: (_, isServerValidated) => SingleChildScrollView(
|
||||||
duration: Durations.medium1,
|
child: AnimatedSwitcher(
|
||||||
child: SingleChildScrollView(
|
duration: Durations.medium1,
|
||||||
child: isServerValidated
|
child: isServerValidated
|
||||||
? _CredentialsPage(
|
? _CredentialsForm(
|
||||||
emailController: emailController,
|
emailController: emailController,
|
||||||
passwordController: passwordController,
|
passwordController: passwordController,
|
||||||
)
|
)
|
||||||
: _ServerPage(controller: serverUrlController),
|
: _ServerForm(controller: serverUrlController),
|
||||||
|
layoutBuilder: (current, previous) =>
|
||||||
|
current ?? (previous.lastOrNull ?? const SizedBox.shrink()),
|
||||||
),
|
),
|
||||||
layoutBuilder: (current, previous) =>
|
|
||||||
current ?? (previous.lastOrNull ?? const SizedBox.shrink()),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ServerPage extends StatelessWidget {
|
class _ServerForm extends StatelessWidget {
|
||||||
final TextEditingController controller;
|
final TextEditingController controller;
|
||||||
final GlobalKey<FormState> _formKey = GlobalKey();
|
final GlobalKey<FormState> _formKey = GlobalKey();
|
||||||
|
|
||||||
_ServerPage({required this.controller});
|
_ServerForm({required this.controller});
|
||||||
|
|
||||||
Future<void> _validateForm(BuildContext context) async {
|
Future<void> _validateForm(BuildContext context) async {
|
||||||
if (_formKey.currentState?.validate() == true) {
|
if (_formKey.currentState?.validate() == true) {
|
||||||
@ -96,20 +96,20 @@ class _ServerPage extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CredentialsPage extends StatefulWidget {
|
class _CredentialsForm extends StatefulWidget {
|
||||||
final TextEditingController emailController;
|
final TextEditingController emailController;
|
||||||
final TextEditingController passwordController;
|
final TextEditingController passwordController;
|
||||||
|
|
||||||
const _CredentialsPage({
|
const _CredentialsForm({
|
||||||
required this.emailController,
|
required this.emailController,
|
||||||
required this.passwordController,
|
required this.passwordController,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_CredentialsPage> createState() => _CredentialsPageState();
|
State<_CredentialsForm> createState() => _CredentialsFormState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CredentialsPageState extends State<_CredentialsPage> {
|
class _CredentialsFormState extends State<_CredentialsForm> {
|
||||||
final passwordFocusNode = FocusNode();
|
final passwordFocusNode = FocusNode();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -23,9 +23,10 @@ abstract class AppColors {
|
|||||||
onError: Color(0xfffffbff),
|
onError: Color(0xfffffbff),
|
||||||
errorContainer: Color(0xffffdad6),
|
errorContainer: Color(0xffffdad6),
|
||||||
onErrorContainer: Color(0xff410002),
|
onErrorContainer: Color(0xff410002),
|
||||||
surface: Color(0xfffefbff),
|
surface: Color(0xFFF0EFF4),
|
||||||
onSurface: Color(0xff1a1b21),
|
onSurface: Color(0xff1a1b21),
|
||||||
onSurfaceVariant: Color(0xff444651),
|
onSurfaceVariant: Color(0xff444651),
|
||||||
|
surfaceContainer: Color(0xfffefbff),
|
||||||
surfaceContainerHighest: Color(0xffe0e2ef),
|
surfaceContainerHighest: Color(0xffe0e2ef),
|
||||||
outline: Color(0xff747782),
|
outline: Color(0xff747782),
|
||||||
outlineVariant: Color(0xffc4c6d3),
|
outlineVariant: Color(0xffc4c6d3),
|
||||||
@ -55,9 +56,10 @@ abstract class AppColors {
|
|||||||
onError: Color(0xff410002),
|
onError: Color(0xff410002),
|
||||||
errorContainer: Color(0xff93000a),
|
errorContainer: Color(0xff93000a),
|
||||||
onErrorContainer: Color(0xffffb4ab),
|
onErrorContainer: Color(0xffffb4ab),
|
||||||
surface: Color(0xff1a1e22),
|
surface: Color(0xFF15181C),
|
||||||
onSurface: Color(0xffe2e2e9),
|
onSurface: Color(0xffe2e2e9),
|
||||||
onSurfaceVariant: Color(0xffc2c6d2),
|
onSurfaceVariant: Color(0xffc2c6d2),
|
||||||
|
surfaceContainer: Color(0xff1a1e22),
|
||||||
surfaceContainerHighest: Color(0xff424852),
|
surfaceContainerHighest: Color(0xff424852),
|
||||||
outline: Color(0xff8c919c),
|
outline: Color(0xff8c919c),
|
||||||
outlineVariant: Color(0xff424751),
|
outlineVariant: Color(0xff424751),
|
||||||
|
@ -18,7 +18,7 @@ enum AppTheme {
|
|||||||
primaryColor: color.primary,
|
primaryColor: color.primary,
|
||||||
iconTheme: const IconThemeData(weight: 500, opticalSize: 24),
|
iconTheme: const IconThemeData(weight: 500, opticalSize: 24),
|
||||||
navigationBarTheme: NavigationBarThemeData(
|
navigationBarTheme: NavigationBarThemeData(
|
||||||
backgroundColor: color.surface,
|
backgroundColor: color.surfaceContainer,
|
||||||
indicatorColor: color.primary,
|
indicatorColor: color.primary,
|
||||||
iconTheme: WidgetStateProperty.resolveWith(
|
iconTheme: WidgetStateProperty.resolveWith(
|
||||||
(Set<WidgetState> states) {
|
(Set<WidgetState> states) {
|
||||||
@ -29,8 +29,9 @@ enum AppTheme {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
scaffoldBackgroundColor: color.surface,
|
||||||
navigationRailTheme: NavigationRailThemeData(
|
navigationRailTheme: NavigationRailThemeData(
|
||||||
backgroundColor: color.surface,
|
backgroundColor: color.surfaceContainer,
|
||||||
elevation: 3,
|
elevation: 3,
|
||||||
indicatorColor: color.primary,
|
indicatorColor: color.primary,
|
||||||
selectedIconTheme:
|
selectedIconTheme:
|
||||||
@ -41,9 +42,21 @@ enum AppTheme {
|
|||||||
color: color.onSurface.withAlpha(175),
|
color: color.onSurface.withAlpha(175),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
inputDecorationTheme: const InputDecorationTheme(
|
inputDecorationTheme: InputDecorationTheme(
|
||||||
border: OutlineInputBorder(),
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(color: color.primary),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(15)),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(color: color.outlineVariant),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(15)),
|
||||||
|
),
|
||||||
|
hintStyle: const TextStyle(
|
||||||
|
fontSize: 16.0,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
textSelectionTheme: TextSelectionThemeData(cursorColor: color.primary),
|
||||||
sliderTheme: SliderThemeData(
|
sliderTheme: SliderThemeData(
|
||||||
valueIndicatorColor:
|
valueIndicatorColor:
|
||||||
Color.alphaBlend(color.primary.withAlpha(80), color.onSurface)
|
Color.alphaBlend(color.primary.withAlpha(80), color.onSurface)
|
||||||
|
@ -25,60 +25,83 @@ final di = GetIt.I;
|
|||||||
class ServiceLocator {
|
class ServiceLocator {
|
||||||
const ServiceLocator._internal();
|
const ServiceLocator._internal();
|
||||||
|
|
||||||
|
static void _registerFactory<T extends Object>(T Function() factoryFun) {
|
||||||
|
if (!di.isRegistered<T>()) {
|
||||||
|
di.registerFactory<T>(factoryFun);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _registerSingleton<T extends Object>(T instance) {
|
||||||
|
if (!di.isRegistered<T>()) {
|
||||||
|
di.registerSingleton<T>(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _registerLazySingleton<T extends Object>(
|
||||||
|
T Function() factoryFun,
|
||||||
|
) {
|
||||||
|
if (!di.isRegistered<T>()) {
|
||||||
|
di.registerLazySingleton<T>(factoryFun);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void configureServices() {
|
static void configureServices() {
|
||||||
di.registerSingleton<DriftDatabaseRepository>(DriftDatabaseRepository());
|
_registerSingleton(DriftDatabaseRepository());
|
||||||
_registerRepositories();
|
_registerRepositories();
|
||||||
_registerPreGlobalStates();
|
_registerPreGlobalStates();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void configureServicesForIsolate({
|
static void configureServicesForIsolate({
|
||||||
required DriftDatabaseRepository database,
|
required DriftDatabaseRepository database,
|
||||||
|
required ImmichApiClient apiClient,
|
||||||
}) {
|
}) {
|
||||||
di.registerSingleton<DriftDatabaseRepository>(database);
|
_registerSingleton(database);
|
||||||
|
_registerSingleton(apiClient);
|
||||||
|
|
||||||
_registerRepositories();
|
_registerRepositories();
|
||||||
|
registerPostValidationServices();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _registerRepositories() {
|
static void _registerRepositories() {
|
||||||
/// Repositories
|
/// Repositories
|
||||||
di.registerFactory<IStoreRepository>(() => StoreDriftRepository(di()));
|
_registerFactory<IStoreRepository>(() => StoreDriftRepository(di()));
|
||||||
di.registerFactory<ILogRepository>(() => LogDriftRepository(di()));
|
_registerFactory<ILogRepository>(() => LogDriftRepository(di()));
|
||||||
di.registerFactory<AppSettingService>(() => AppSettingService(di()));
|
_registerFactory<AppSettingService>(() => AppSettingService(di()));
|
||||||
di.registerFactory<IUserRepository>(() => UserDriftRepository(di()));
|
_registerFactory<IUserRepository>(() => UserDriftRepository(di()));
|
||||||
di.registerFactory<IAssetRepository>(
|
_registerFactory<IAssetRepository>(
|
||||||
() => RemoteAssetDriftRepository(di()),
|
() => RemoteAssetDriftRepository(di()),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Services
|
/// Services
|
||||||
di.registerFactory<LoginService>(() => const LoginService());
|
_registerFactory<LoginService>(() => const LoginService());
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _registerPreGlobalStates() {
|
static void _registerPreGlobalStates() {
|
||||||
di.registerSingleton<AppRouter>(AppRouter());
|
_registerSingleton(AppRouter());
|
||||||
di.registerLazySingleton<AppThemeCubit>(() => AppThemeCubit(di()));
|
_registerLazySingleton<AppThemeCubit>(() => AppThemeCubit(di()));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void registerPostValidationServices(String endpoint) {
|
static void registerApiClient(String endpoint) {
|
||||||
di.registerSingleton<ImmichApiClient>(ImmichApiClient(endpoint: endpoint));
|
_registerSingleton(ImmichApiClient(endpoint: endpoint));
|
||||||
di.registerFactory<UserService>(() => UserService(
|
}
|
||||||
|
|
||||||
|
static void registerPostValidationServices() {
|
||||||
|
_registerFactory<UserService>(() => UserService(
|
||||||
di<ImmichApiClient>().getUsersApi(),
|
di<ImmichApiClient>().getUsersApi(),
|
||||||
));
|
));
|
||||||
di.registerFactory<ServerInfoService>(() => ServerInfoService(
|
_registerFactory<ServerInfoService>(() => ServerInfoService(
|
||||||
di<ImmichApiClient>().getServerApi(),
|
di<ImmichApiClient>().getServerApi(),
|
||||||
));
|
));
|
||||||
di.registerFactory<SyncService>(() => SyncService(di(), di()));
|
_registerFactory<SyncService>(() => SyncService(di()));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void registerPostGlobalStates() {
|
static void registerPostGlobalStates() {
|
||||||
di.registerLazySingleton<ServerFeatureConfigCubit>(
|
_registerLazySingleton<ServerFeatureConfigCubit>(
|
||||||
() => ServerFeatureConfigCubit(di()),
|
() => ServerFeatureConfigCubit(di()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void registerCurrentUser(User user) {
|
static void registerCurrentUser(User user) {
|
||||||
if (di.isRegistered<CurrentUserCubit>()) {
|
_registerSingleton(CurrentUserCubit(user));
|
||||||
di<CurrentUserCubit>().updateUser(user);
|
|
||||||
} else {
|
|
||||||
di.registerSingleton<CurrentUserCubit>(CurrentUserCubit(user));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,10 @@ import 'package:flutter/material.dart';
|
|||||||
/// Log messages stored in the DB
|
/// Log messages stored in the DB
|
||||||
const int kLogMessageLimit = 500;
|
const int kLogMessageLimit = 500;
|
||||||
|
|
||||||
|
/// RenderList constants
|
||||||
|
const int kRenderListBatchSize = 512;
|
||||||
|
const int kRenderListOppositeBatchSize = 128;
|
||||||
|
|
||||||
/// Chunked asset sync size
|
/// Chunked asset sync size
|
||||||
const int kFullSyncChunkSize = 10000;
|
const int kFullSyncChunkSize = 10000;
|
||||||
|
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
extension AsynSnapShotState on AsyncSnapshot {
|
||||||
|
bool get isWaiting => connectionState == ConnectionState.waiting;
|
||||||
|
bool get isDone => connectionState == ConnectionState.done;
|
||||||
|
}
|
@ -5,6 +5,12 @@ extension BuildContextHelper on BuildContext {
|
|||||||
/// Get the current [ThemeData] used
|
/// Get the current [ThemeData] used
|
||||||
ThemeData get theme => Theme.of(this);
|
ThemeData get theme => Theme.of(this);
|
||||||
|
|
||||||
|
/// Get the current [ColorScheme] used
|
||||||
|
ColorScheme get colorScheme => theme.colorScheme;
|
||||||
|
|
||||||
|
/// Get the current [TextTheme] used
|
||||||
|
TextTheme get textTheme => theme.textTheme;
|
||||||
|
|
||||||
/// Get the default [TextStyle]
|
/// Get the default [TextStyle]
|
||||||
TextStyle get defaultTextStyle => DefaultTextStyle.of(this).style;
|
TextStyle get defaultTextStyle => DefaultTextStyle.of(this).style;
|
||||||
|
|
||||||
|
@ -12,21 +12,9 @@ import 'package:immich_mobile/utils/constants/globals.dart';
|
|||||||
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
|
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
@immutable
|
|
||||||
class ImApiClientData {
|
|
||||||
final String endpoint;
|
|
||||||
final Map<String, String> headersMap;
|
|
||||||
|
|
||||||
const ImApiClientData({required this.endpoint, required this.headersMap});
|
|
||||||
}
|
|
||||||
|
|
||||||
class ImmichApiClient extends ApiClient with LogContext {
|
class ImmichApiClient extends ApiClient with LogContext {
|
||||||
ImmichApiClient({required String endpoint}) : super(basePath: endpoint);
|
ImmichApiClient({required String endpoint}) : super(basePath: endpoint);
|
||||||
|
|
||||||
/// Used to recreate the client in Isolates
|
|
||||||
ImApiClientData get clientData =>
|
|
||||||
ImApiClientData(endpoint: basePath, headersMap: defaultHeaderMap);
|
|
||||||
|
|
||||||
Map<String, String> get headers => defaultHeaderMap;
|
Map<String, String> get headers => defaultHeaderMap;
|
||||||
|
|
||||||
Future<void> init({String? accessToken}) async {
|
Future<void> init({String? accessToken}) async {
|
||||||
@ -49,15 +37,6 @@ class ImmichApiClient extends ApiClient with LogContext {
|
|||||||
addDefaultHeader(kImmichHeaderDeviceType, Platform.operatingSystem);
|
addDefaultHeader(kImmichHeaderDeviceType, Platform.operatingSystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
factory ImmichApiClient.clientData(ImApiClientData data) {
|
|
||||||
final client = ImmichApiClient(endpoint: data.endpoint);
|
|
||||||
|
|
||||||
for (final entry in data.headersMap.entries) {
|
|
||||||
client.addDefaultHeader(entry.key, entry.value);
|
|
||||||
}
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Response> invokeAPI(
|
Future<Response> invokeAPI(
|
||||||
String path,
|
String path,
|
||||||
|
46
mobile-v2/lib/utils/isolate_helper.dart
Normal file
46
mobile-v2/lib/utils/isolate_helper.dart
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:immich_mobile/domain/repositories/database.repository.dart';
|
||||||
|
import 'package:immich_mobile/service_locator.dart';
|
||||||
|
import 'package:immich_mobile/utils/immich_api_client.dart';
|
||||||
|
import 'package:immich_mobile/utils/log_manager.dart';
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class _ImApiClientData {
|
||||||
|
final String endpoint;
|
||||||
|
final Map<String, String> headersMap;
|
||||||
|
|
||||||
|
const _ImApiClientData({required this.endpoint, required this.headersMap});
|
||||||
|
}
|
||||||
|
|
||||||
|
class IsolateHelper {
|
||||||
|
// Cache the ApiClient to reconstruct it later after inside the isolate
|
||||||
|
late final _ImApiClientData? _clientData;
|
||||||
|
|
||||||
|
IsolateHelper();
|
||||||
|
|
||||||
|
void preIsolateHandling() {
|
||||||
|
final apiClient = di<ImmichApiClient>();
|
||||||
|
_clientData = _ImApiClientData(
|
||||||
|
endpoint: apiClient.basePath,
|
||||||
|
headersMap: apiClient.defaultHeaderMap,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void postIsolateHandling({required DriftDatabaseRepository database}) {
|
||||||
|
assert(_clientData != null);
|
||||||
|
// Reconstruct client from cached data
|
||||||
|
final client = ImmichApiClient(endpoint: _clientData!.endpoint);
|
||||||
|
for (final entry in _clientData.headersMap.entries) {
|
||||||
|
client.addDefaultHeader(entry.key, entry.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register all services in the isolates memory
|
||||||
|
ServiceLocator.configureServicesForIsolate(
|
||||||
|
database: database,
|
||||||
|
apiClient: client,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Init log manager to continue listening to log events
|
||||||
|
LogManager.I.init();
|
||||||
|
}
|
||||||
|
}
|
@ -24,17 +24,16 @@ function dart {
|
|||||||
|
|
||||||
function dartDio {
|
function dartDio {
|
||||||
rm -rf ../mobile-v2/openapi
|
rm -rf ../mobile-v2/openapi
|
||||||
cd ./templates/mobile/serialization/native
|
cd ./templates/mobile-v2/serialization/native
|
||||||
wget -O native_class.mustache https://raw.githubusercontent.com/OpenAPITools/openapi-generator/$OPENAPI_GENERATOR_VERSION/modules/openapi-generator/src/main/resources/dart2/serialization/native/native_class.mustache
|
wget -O native_class.mustache https://raw.githubusercontent.com/OpenAPITools/openapi-generator/$OPENAPI_GENERATOR_VERSION/modules/openapi-generator/src/main/resources/dart2/serialization/native/native_class.mustache
|
||||||
patch --no-backup-if-mismatch -u native_class.mustache <native_class.mustache.patch
|
patch --no-backup-if-mismatch -u native_class.mustache < native_class.mustache.patch
|
||||||
cd ../../../../
|
cd ../../../../
|
||||||
|
|
||||||
npx --yes @openapitools/openapi-generator-cli generate -g dart -i ./immich-openapi-specs.json -o ../mobile-v2/openapi -t ./templates/mobile
|
npx --yes @openapitools/openapi-generator-cli generate -g dart -i ./immich-openapi-specs.json -o ../mobile-v2/openapi -t ./templates/mobile-v2
|
||||||
|
|
||||||
# Post generate patches
|
# Post generate patches
|
||||||
patch --no-backup-if-mismatch -u ../mobile-v2/openapi/lib/api_client.dart <./patch/api_client.dart.patch
|
patch --no-backup-if-mismatch -u ../mobile-v2/openapi/lib/api_client.dart <./patch/api_client.dart.patch
|
||||||
patch --no-backup-if-mismatch -u ../mobile-v2/openapi/lib/api.dart <./patch/api.dart.patch
|
patch --no-backup-if-mismatch -u ../mobile-v2/openapi/lib/api.dart <./patch/api-v2.dart.patch
|
||||||
patch --no-backup-if-mismatch -u ../mobile-v2/openapi/pubspec.yaml <./patch/pubspec_immich_mobile.yaml.patch
|
|
||||||
# Don't include analysis_options.yaml for the generated openapi files
|
# Don't include analysis_options.yaml for the generated openapi files
|
||||||
# so that language servers can properly exclude the mobile/openapi directory
|
# so that language servers can properly exclude the mobile/openapi directory
|
||||||
rm ../mobile-v2/openapi/analysis_options.yaml
|
rm ../mobile-v2/openapi/analysis_options.yaml
|
||||||
|
8
open-api/patch/api-v2.dart.patch
Normal file
8
open-api/patch/api-v2.dart.patch
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
@@ -15,6 +15,7 @@ import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
+import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:meta/meta.dart';
|
@ -0,0 +1,56 @@
|
|||||||
|
--- native_class.mustache 2023-08-31 23:09:59.584269162 +0200
|
||||||
|
+++ native_class1.mustache 2023-08-31 22:59:53.633083270 +0200
|
||||||
|
@@ -91,14 +91,14 @@
|
||||||
|
{{/isDateTime}}
|
||||||
|
{{#isNullable}}
|
||||||
|
} else {
|
||||||
|
- json[r'{{{baseName}}}'] = null;
|
||||||
|
+ // json[r'{{{baseName}}}'] = null;
|
||||||
|
}
|
||||||
|
{{/isNullable}}
|
||||||
|
{{^isNullable}}
|
||||||
|
{{^required}}
|
||||||
|
{{^defaultValue}}
|
||||||
|
} else {
|
||||||
|
- json[r'{{{baseName}}}'] = null;
|
||||||
|
+ // json[r'{{{baseName}}}'] = null;
|
||||||
|
}
|
||||||
|
{{/defaultValue}}
|
||||||
|
{{/required}}
|
||||||
|
@@ -114,17 +114,6 @@
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
- // Ensure that the map contains the required keys.
|
||||||
|
- // Note 1: the values aren't checked for validity beyond being non-null.
|
||||||
|
- // Note 2: this code is stripped in release mode!
|
||||||
|
- assert(() {
|
||||||
|
- requiredKeys.forEach((key) {
|
||||||
|
- assert(json.containsKey(key), 'Required key "{{{classname}}}[$key]" is missing from JSON.');
|
||||||
|
- assert(json[key] != null, 'Required key "{{{classname}}}[$key]" has a null value in JSON.');
|
||||||
|
- });
|
||||||
|
- return true;
|
||||||
|
- }());
|
||||||
|
-
|
||||||
|
return {{{classname}}}(
|
||||||
|
{{#vars}}
|
||||||
|
{{#isDateTime}}
|
||||||
|
@@ -215,6 +204,10 @@
|
||||||
|
? {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}}
|
||||||
|
: {{{datatypeWithEnum}}}.parse(json[r'{{{baseName}}}'].toString()),
|
||||||
|
{{/isNumber}}
|
||||||
|
+ {{#isDouble}}
|
||||||
|
+ {{{name}}}: (mapValueOfType<num>(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}).toDouble(),
|
||||||
|
+ {{/isDouble}}
|
||||||
|
+ {{^isDouble}}
|
||||||
|
{{^isNumber}}
|
||||||
|
{{^isEnum}}
|
||||||
|
{{{name}}}: mapValueOfType<{{{datatypeWithEnum}}}>(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}},
|
||||||
|
@@ -223,6 +216,7 @@
|
||||||
|
{{{name}}}: {{{enumName}}}.fromJson(json[r'{{{baseName}}}']){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}},
|
||||||
|
{{/isEnum}}
|
||||||
|
{{/isNumber}}
|
||||||
|
+ {{/isDouble}}
|
||||||
|
{{/isMap}}
|
||||||
|
{{/isArray}}
|
||||||
|
{{/complexType}}
|
Loading…
x
Reference in New Issue
Block a user