mirror of
https://github.com/immich-app/immich.git
synced 2025-07-08 10:44:15 -04:00
fix: handle login
This commit is contained in:
parent
7f83740b35
commit
877c3b028b
@ -1,11 +1,11 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset.model.dart';
|
||||||
|
|
||||||
class LocalAsset extends Table {
|
class Asset extends Table {
|
||||||
const LocalAsset();
|
const Asset();
|
||||||
|
|
||||||
IntColumn get id => integer().autoIncrement()();
|
IntColumn get id => integer().autoIncrement()();
|
||||||
TextColumn get localId => text().unique()();
|
|
||||||
TextColumn get name => text()();
|
TextColumn get name => text()();
|
||||||
TextColumn get checksum => text().unique()();
|
TextColumn get checksum => text().unique()();
|
||||||
IntColumn get height => integer()();
|
IntColumn get height => integer()();
|
||||||
|
8
mobile-v2/lib/domain/entities/local_asset.entity.dart
Normal file
8
mobile-v2/lib/domain/entities/local_asset.entity.dart
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:immich_mobile/domain/entities/asset.entity.dart';
|
||||||
|
|
||||||
|
class LocalAsset extends Asset {
|
||||||
|
const LocalAsset();
|
||||||
|
|
||||||
|
TextColumn get localId => text().unique()();
|
||||||
|
}
|
8
mobile-v2/lib/domain/entities/remote_asset.entity.dart
Normal file
8
mobile-v2/lib/domain/entities/remote_asset.entity.dart
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:immich_mobile/domain/entities/asset.entity.dart';
|
||||||
|
|
||||||
|
class RemoteAsset extends Asset {
|
||||||
|
const RemoteAsset();
|
||||||
|
|
||||||
|
TextColumn get remoteId => text().unique()();
|
||||||
|
}
|
@ -1,5 +1,3 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
|
||||||
|
|
||||||
enum AssetType {
|
enum AssetType {
|
||||||
// do not change this order!
|
// do not change this order!
|
||||||
other,
|
other,
|
||||||
@ -8,10 +6,8 @@ enum AssetType {
|
|||||||
audio,
|
audio,
|
||||||
}
|
}
|
||||||
|
|
||||||
@immutable
|
class Asset {
|
||||||
class LocalAsset {
|
|
||||||
final int id;
|
final int id;
|
||||||
final String localId;
|
|
||||||
final String name;
|
final String name;
|
||||||
final String checksum;
|
final String checksum;
|
||||||
final int height;
|
final int height;
|
||||||
@ -22,9 +18,8 @@ class LocalAsset {
|
|||||||
final int duration;
|
final int duration;
|
||||||
final bool isLivePhoto;
|
final bool isLivePhoto;
|
||||||
|
|
||||||
const LocalAsset({
|
const Asset({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.localId,
|
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.checksum,
|
required this.checksum,
|
||||||
required this.height,
|
required this.height,
|
||||||
@ -36,16 +31,36 @@ class LocalAsset {
|
|||||||
required this.isLivePhoto,
|
required this.isLivePhoto,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
Asset copyWith({
|
||||||
String toString() {
|
int? id,
|
||||||
return 'LocalAsset(id: $id, localId: $localId, name: $name, checksum: $checksum, height: $height, width: $width, type: $type, createdTime: $createdTime, modifiedTime: $modifiedTime, duration: $duration, isLivePhoto: $isLivePhoto)';
|
String? name,
|
||||||
|
String? checksum,
|
||||||
|
int? height,
|
||||||
|
int? width,
|
||||||
|
AssetType? type,
|
||||||
|
DateTime? createdTime,
|
||||||
|
DateTime? modifiedTime,
|
||||||
|
int? duration,
|
||||||
|
bool? isLivePhoto,
|
||||||
|
}) {
|
||||||
|
return Asset(
|
||||||
|
id: id ?? this.id,
|
||||||
|
name: name ?? this.name,
|
||||||
|
checksum: checksum ?? this.checksum,
|
||||||
|
height: height ?? this.height,
|
||||||
|
width: width ?? this.width,
|
||||||
|
type: type ?? this.type,
|
||||||
|
createdTime: createdTime ?? this.createdTime,
|
||||||
|
modifiedTime: modifiedTime ?? this.modifiedTime,
|
||||||
|
duration: duration ?? this.duration,
|
||||||
|
isLivePhoto: isLivePhoto ?? this.isLivePhoto,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String toJSON() {
|
@override
|
||||||
return """
|
String toString() => """
|
||||||
{
|
{
|
||||||
"id": $id,
|
"id": $id,
|
||||||
"localId": "$localId",
|
|
||||||
"name": "$name",
|
"name": "$name",
|
||||||
"checksum": "$checksum",
|
"checksum": "$checksum",
|
||||||
"height": $height,
|
"height": $height,
|
||||||
@ -56,19 +71,26 @@ class LocalAsset {
|
|||||||
"duration": "$duration",
|
"duration": "$duration",
|
||||||
"isLivePhoto": "$isLivePhoto",
|
"isLivePhoto": "$isLivePhoto",
|
||||||
}""";
|
}""";
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(covariant LocalAsset other) {
|
bool operator ==(covariant Asset other) {
|
||||||
if (identical(this, other)) return true;
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
return other.hashCode == hashCode;
|
return other.id == id &&
|
||||||
|
other.name == name &&
|
||||||
|
other.checksum == checksum &&
|
||||||
|
other.height == height &&
|
||||||
|
other.width == width &&
|
||||||
|
other.type == type &&
|
||||||
|
other.createdTime == createdTime &&
|
||||||
|
other.modifiedTime == modifiedTime &&
|
||||||
|
other.duration == duration &&
|
||||||
|
other.isLivePhoto == isLivePhoto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode {
|
int get hashCode {
|
||||||
return id.hashCode ^
|
return id.hashCode ^
|
||||||
localId.hashCode ^
|
|
||||||
name.hashCode ^
|
name.hashCode ^
|
||||||
checksum.hashCode ^
|
checksum.hashCode ^
|
||||||
height.hashCode ^
|
height.hashCode ^
|
||||||
|
76
mobile-v2/lib/domain/models/local_asset.model.dart
Normal file
76
mobile-v2/lib/domain/models/local_asset.model.dart
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/asset.model.dart';
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class LocalAsset extends Asset {
|
||||||
|
final String localId;
|
||||||
|
|
||||||
|
const LocalAsset({
|
||||||
|
required this.localId,
|
||||||
|
required super.id,
|
||||||
|
required super.name,
|
||||||
|
required super.checksum,
|
||||||
|
required super.height,
|
||||||
|
required super.width,
|
||||||
|
required super.type,
|
||||||
|
required super.createdTime,
|
||||||
|
required super.modifiedTime,
|
||||||
|
required super.duration,
|
||||||
|
required super.isLivePhoto,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => """
|
||||||
|
{
|
||||||
|
"id": $id,
|
||||||
|
"localId": "$localId",
|
||||||
|
"name": "$name",
|
||||||
|
"checksum": "$checksum",
|
||||||
|
"height": $height,
|
||||||
|
"width": $width,
|
||||||
|
"type": "$type",
|
||||||
|
"createdTime": "$createdTime",
|
||||||
|
"modifiedTime": "$modifiedTime",
|
||||||
|
"duration": "$duration",
|
||||||
|
"isLivePhoto": "$isLivePhoto",
|
||||||
|
}""";
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(covariant LocalAsset other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
|
return super == (other) && other.localId == localId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => super.hashCode ^ localId.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
LocalAsset copyWith({
|
||||||
|
int? id,
|
||||||
|
String? localId,
|
||||||
|
String? name,
|
||||||
|
String? checksum,
|
||||||
|
int? height,
|
||||||
|
int? width,
|
||||||
|
AssetType? type,
|
||||||
|
DateTime? createdTime,
|
||||||
|
DateTime? modifiedTime,
|
||||||
|
int? duration,
|
||||||
|
bool? isLivePhoto,
|
||||||
|
}) {
|
||||||
|
return LocalAsset(
|
||||||
|
id: id ?? this.id,
|
||||||
|
localId: localId ?? this.localId,
|
||||||
|
name: name ?? this.name,
|
||||||
|
checksum: checksum ?? this.checksum,
|
||||||
|
height: height ?? this.height,
|
||||||
|
width: width ?? this.width,
|
||||||
|
type: type ?? this.type,
|
||||||
|
createdTime: createdTime ?? this.createdTime,
|
||||||
|
modifiedTime: modifiedTime ?? this.modifiedTime,
|
||||||
|
duration: duration ?? this.duration,
|
||||||
|
isLivePhoto: isLivePhoto ?? this.isLivePhoto,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
76
mobile-v2/lib/domain/models/remote_asset.model.dart
Normal file
76
mobile-v2/lib/domain/models/remote_asset.model.dart
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/asset.model.dart';
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class RemoteAsset extends Asset {
|
||||||
|
final String remoteId;
|
||||||
|
|
||||||
|
const RemoteAsset({
|
||||||
|
required this.remoteId,
|
||||||
|
required super.id,
|
||||||
|
required super.name,
|
||||||
|
required super.checksum,
|
||||||
|
required super.height,
|
||||||
|
required super.width,
|
||||||
|
required super.type,
|
||||||
|
required super.createdTime,
|
||||||
|
required super.modifiedTime,
|
||||||
|
required super.duration,
|
||||||
|
required super.isLivePhoto,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => """
|
||||||
|
{
|
||||||
|
"id": $id,
|
||||||
|
"remoteId": "$remoteId",
|
||||||
|
"name": "$name",
|
||||||
|
"checksum": "$checksum",
|
||||||
|
"height": $height,
|
||||||
|
"width": $width,
|
||||||
|
"type": "$type",
|
||||||
|
"createdTime": "$createdTime",
|
||||||
|
"modifiedTime": "$modifiedTime",
|
||||||
|
"duration": "$duration",
|
||||||
|
"isLivePhoto": "$isLivePhoto",
|
||||||
|
}""";
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(covariant RemoteAsset other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
|
return super == (other) && other.remoteId == remoteId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => super.hashCode ^ remoteId.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
RemoteAsset copyWith({
|
||||||
|
int? id,
|
||||||
|
String? remoteId,
|
||||||
|
String? name,
|
||||||
|
String? checksum,
|
||||||
|
int? height,
|
||||||
|
int? width,
|
||||||
|
AssetType? type,
|
||||||
|
DateTime? createdTime,
|
||||||
|
DateTime? modifiedTime,
|
||||||
|
int? duration,
|
||||||
|
bool? isLivePhoto,
|
||||||
|
}) {
|
||||||
|
return RemoteAsset(
|
||||||
|
id: id ?? this.id,
|
||||||
|
remoteId: remoteId ?? this.remoteId,
|
||||||
|
name: name ?? this.name,
|
||||||
|
checksum: checksum ?? this.checksum,
|
||||||
|
height: height ?? this.height,
|
||||||
|
width: width ?? this.width,
|
||||||
|
type: type ?? this.type,
|
||||||
|
createdTime: createdTime ?? this.createdTime,
|
||||||
|
modifiedTime: modifiedTime ?? this.modifiedTime,
|
||||||
|
duration: duration ?? this.duration,
|
||||||
|
isLivePhoto: isLivePhoto ?? this.isLivePhoto,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:openapi/openapi.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
class ServerConfig {
|
class ServerConfig {
|
||||||
final String? oauthButtonText;
|
final String? oauthButtonText;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:openapi/openapi.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
class ServerFeatures {
|
class ServerFeatures {
|
||||||
final bool hasPasswordLogin;
|
final bool hasPasswordLogin;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:openapi/openapi.dart' as api;
|
import 'package:openapi/api.dart' as api;
|
||||||
|
|
||||||
class User {
|
class User {
|
||||||
const User({
|
const User({
|
||||||
|
@ -6,8 +6,9 @@ import 'package:drift/native.dart';
|
|||||||
import 'package:drift_dev/api/migrations.dart';
|
import 'package:drift_dev/api/migrations.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:immich_mobile/domain/entities/album.entity.dart';
|
import 'package:immich_mobile/domain/entities/album.entity.dart';
|
||||||
import 'package:immich_mobile/domain/entities/asset.entity.dart';
|
import 'package:immich_mobile/domain/entities/local_asset.entity.dart';
|
||||||
import 'package:immich_mobile/domain/entities/log.entity.dart';
|
import 'package:immich_mobile/domain/entities/log.entity.dart';
|
||||||
|
import 'package:immich_mobile/domain/entities/remote_asset.entity.dart';
|
||||||
import 'package:immich_mobile/domain/entities/store.entity.dart';
|
import 'package:immich_mobile/domain/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/domain/entities/user.entity.dart';
|
import 'package:immich_mobile/domain/entities/user.entity.dart';
|
||||||
import 'package:immich_mobile/domain/interfaces/database.interface.dart';
|
import 'package:immich_mobile/domain/interfaces/database.interface.dart';
|
||||||
@ -18,7 +19,7 @@ import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart';
|
|||||||
|
|
||||||
import 'database.repository.drift.dart';
|
import 'database.repository.drift.dart';
|
||||||
|
|
||||||
@DriftDatabase(tables: [Logs, Store, LocalAlbum, LocalAsset, User])
|
@DriftDatabase(tables: [Logs, Store, LocalAlbum, LocalAsset, User, RemoteAsset])
|
||||||
class DriftDatabaseRepository extends $DriftDatabaseRepository
|
class DriftDatabaseRepository extends $DriftDatabaseRepository
|
||||||
implements IDatabaseRepository<GeneratedDatabase> {
|
implements IDatabaseRepository<GeneratedDatabase> {
|
||||||
DriftDatabaseRepository() : super(_openConnection());
|
DriftDatabaseRepository() : super(_openConnection());
|
||||||
|
@ -18,8 +18,7 @@ class LogDriftRepository implements ILogRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> truncateLogs({int limit = 250}) {
|
Future<void> truncateLogs({int limit = 250}) async {
|
||||||
return db.transaction(() async {
|
|
||||||
final totalCount = await db.managers.logs.count();
|
final totalCount = await db.managers.logs.count();
|
||||||
if (totalCount > limit) {
|
if (totalCount > limit) {
|
||||||
final rowsToDelete = totalCount - limit;
|
final rowsToDelete = totalCount - limit;
|
||||||
@ -28,13 +27,11 @@ class LogDriftRepository implements ILogRepository {
|
|||||||
.limit(rowsToDelete)
|
.limit(rowsToDelete)
|
||||||
.delete();
|
.delete();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<bool> add(LogMessage log) async {
|
FutureOr<bool> add(LogMessage log) async {
|
||||||
try {
|
try {
|
||||||
await db.transaction(() async {
|
|
||||||
await db.into(db.logs).insert(LogsCompanion.insert(
|
await db.into(db.logs).insert(LogsCompanion.insert(
|
||||||
content: log.content,
|
content: log.content,
|
||||||
level: log.level,
|
level: log.level,
|
||||||
@ -43,7 +40,6 @@ class LogDriftRepository implements ILogRepository {
|
|||||||
logger: Value(log.logger),
|
logger: Value(log.logger),
|
||||||
stack: Value(log.stack),
|
stack: Value(log.stack),
|
||||||
));
|
));
|
||||||
});
|
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("Error while adding a log to the DB - $e");
|
debugPrint("Error while adding a log to the DB - $e");
|
||||||
|
@ -32,7 +32,6 @@ class StoreDriftRepository with LogContext implements IStoreRepository {
|
|||||||
@override
|
@override
|
||||||
FutureOr<bool> set<T, U>(StoreKey<T, U> key, T value) async {
|
FutureOr<bool> set<T, U>(StoreKey<T, U> key, T value) async {
|
||||||
try {
|
try {
|
||||||
await db.transaction(() async {
|
|
||||||
final storeValue = key.converter.toPrimitive(value);
|
final storeValue = key.converter.toPrimitive(value);
|
||||||
final intValue = (key.type == int) ? storeValue as int : null;
|
final intValue = (key.type == int) ? storeValue as int : null;
|
||||||
final stringValue = (key.type == String) ? storeValue as String : null;
|
final stringValue = (key.type == String) ? storeValue as String : null;
|
||||||
@ -41,7 +40,6 @@ class StoreDriftRepository with LogContext implements IStoreRepository {
|
|||||||
intValue: Value(intValue),
|
intValue: Value(intValue),
|
||||||
stringValue: Value(stringValue),
|
stringValue: Value(stringValue),
|
||||||
));
|
));
|
||||||
});
|
|
||||||
return true;
|
return true;
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
log.severe("Cannot set store value - ${key.name}; id - ${key.id}", e, s);
|
log.severe("Cannot set store value - ${key.name}; id - ${key.id}", e, s);
|
||||||
@ -51,9 +49,7 @@ class StoreDriftRepository with LogContext implements IStoreRepository {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<void> delete(StoreKey key) async {
|
FutureOr<void> delete(StoreKey key) async {
|
||||||
return await db.transaction(() async {
|
|
||||||
await db.managers.store.filter((s) => s.id.equals(key.id)).delete();
|
await db.managers.store.filter((s) => s.id.equals(key.id)).delete();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -66,9 +62,8 @@ class StoreDriftRepository with LogContext implements IStoreRepository {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<void> clearStore() async {
|
FutureOr<void> clearStore() async {
|
||||||
return await db.transaction(() async {
|
|
||||||
await db.managers.store.delete();
|
await db.managers.store.delete();
|
||||||
});
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<T?> _getValueFromStoreData<T, U>(
|
FutureOr<T?> _getValueFromStoreData<T, U>(
|
||||||
|
@ -23,8 +23,8 @@ class UserDriftRepository with LogContext implements IUserRepository {
|
|||||||
@override
|
@override
|
||||||
FutureOr<bool> insertUser(User user) async {
|
FutureOr<bool> insertUser(User user) async {
|
||||||
try {
|
try {
|
||||||
return await db.transaction(() async {
|
await db.into(db.user).insertOnConflictUpdate(
|
||||||
await db.into(db.user).insertOnConflictUpdate(UserCompanion.insert(
|
UserCompanion.insert(
|
||||||
id: user.id,
|
id: user.id,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
@ -36,9 +36,9 @@ class UserDriftRepository with LogContext implements IUserRepository {
|
|||||||
quotaSizeInBytes: Value(user.quotaSizeInBytes),
|
quotaSizeInBytes: Value(user.quotaSizeInBytes),
|
||||||
quotaUsageInBytes: Value(user.quotaSizeInBytes),
|
quotaUsageInBytes: Value(user.quotaSizeInBytes),
|
||||||
updatedAt: Value(user.updatedAt),
|
updatedAt: Value(user.updatedAt),
|
||||||
));
|
),
|
||||||
|
);
|
||||||
return true;
|
return true;
|
||||||
});
|
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
log.severe("Cannot insert User into table - $user", e, s);
|
log.severe("Cannot insert User into table - $user", e, s);
|
||||||
return false;
|
return false;
|
||||||
|
@ -1,26 +1,28 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
|
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
|
||||||
|
import 'package:http/http.dart';
|
||||||
import 'package:immich_mobile/service_locator.dart';
|
import 'package:immich_mobile/service_locator.dart';
|
||||||
|
import 'package:immich_mobile/utils/immich_api_client.dart';
|
||||||
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
|
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
|
||||||
import 'package:openapi/openapi.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
class LoginService with LogContext {
|
class LoginService with LogContext {
|
||||||
const LoginService();
|
const LoginService();
|
||||||
|
|
||||||
Future<bool> isEndpointAvailable(Uri uri, {Dio? dio}) async {
|
Future<bool> isEndpointAvailable(Uri uri, {ImmichApiClient? client}) async {
|
||||||
String baseUrl = uri.toString();
|
String baseUrl = uri.toString();
|
||||||
|
|
||||||
if (!baseUrl.endsWith('/api')) {
|
if (!baseUrl.endsWith('/api')) {
|
||||||
baseUrl += '/api';
|
baseUrl += '/api';
|
||||||
}
|
}
|
||||||
|
|
||||||
final serverAPI =
|
final serverAPI = client?.getServerApi() ??
|
||||||
Openapi(dio: dio, basePathOverride: baseUrl, interceptors: [])
|
ImmichApiClient(endpoint: baseUrl).getServerApi();
|
||||||
.getServerApi();
|
|
||||||
try {
|
try {
|
||||||
await serverAPI.pingServer(validateStatus: (status) => status == 200);
|
await serverAPI.pingServer();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.severe("Exception occured while validating endpoint", e);
|
log.severe("Exception occured while validating endpoint", e);
|
||||||
return false;
|
return false;
|
||||||
@ -28,25 +30,24 @@ class LoginService with LogContext {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> resolveEndpoint(Uri uri, {Dio? dio}) async {
|
Future<String> resolveEndpoint(Uri uri, {Client? client}) async {
|
||||||
final d = dio ?? Dio();
|
|
||||||
String baseUrl = uri.toString();
|
String baseUrl = uri.toString();
|
||||||
|
final d = client ?? ImmichApiClient(endpoint: baseUrl).client;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check for well-known endpoint
|
// Check for well-known endpoint
|
||||||
final res = await d.get(
|
final res = await d.get(
|
||||||
"$baseUrl/.well-known/immich",
|
Uri.parse("$baseUrl/.well-known/immich"),
|
||||||
options: Options(
|
|
||||||
headers: {"Accept": "application/json"},
|
headers: {"Accept": "application/json"},
|
||||||
validateStatus: (status) => status == 200,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final data = jsonDecode(res.data);
|
if (res.statusCode == HttpStatus.ok) {
|
||||||
|
final data = await compute(jsonDecode, res.body);
|
||||||
final endpoint = data['api']['endpoint'].toString();
|
final endpoint = data['api']['endpoint'].toString();
|
||||||
|
|
||||||
// Full URL is relative to base
|
// Full URL is relative to base
|
||||||
return endpoint.startsWith('/') ? "$baseUrl$endpoint" : endpoint;
|
return endpoint.startsWith('/') ? "$baseUrl$endpoint" : endpoint;
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.fine("Could not locate /.well-known/immich at $baseUrl", e);
|
log.fine("Could not locate /.well-known/immich at $baseUrl", e);
|
||||||
}
|
}
|
||||||
@ -57,14 +58,12 @@ class LoginService with LogContext {
|
|||||||
|
|
||||||
Future<String?> passwordLogin(String email, String password) async {
|
Future<String?> passwordLogin(String email, String password) async {
|
||||||
try {
|
try {
|
||||||
final loginResponse = await di<Openapi>().getAuthenticationApi().login(
|
final loginResponse =
|
||||||
loginCredentialDto: LoginCredentialDto((builder) {
|
await di<ImmichApiClient>().getAuthenticationApi().login(
|
||||||
builder.email = email;
|
LoginCredentialDto(email: email, password: password),
|
||||||
builder.password = password;
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return loginResponse.data?.accessToken;
|
return loginResponse?.accessToken;
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
log.severe("Exception occured while performing password login", e, s);
|
log.severe("Exception occured while performing password login", e, s);
|
||||||
}
|
}
|
||||||
@ -74,16 +73,14 @@ class LoginService with LogContext {
|
|||||||
Future<String?> oAuthLogin() async {
|
Future<String?> oAuthLogin() async {
|
||||||
const String oAuthCallbackSchema = 'app.immich';
|
const String oAuthCallbackSchema = 'app.immich';
|
||||||
|
|
||||||
final oAuthApi = di<Openapi>().getOAuthApi();
|
final oAuthApi = di<ImmichApiClient>().getOAuthApi();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final oAuthUrl = await oAuthApi.startOAuth(
|
final oAuthUrl = await oAuthApi.startOAuth(
|
||||||
oAuthConfigDto: OAuthConfigDto((builder) {
|
OAuthConfigDto(redirectUri: "$oAuthCallbackSchema:/"),
|
||||||
builder.redirectUri = "$oAuthCallbackSchema:/";
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final oAuthUrlRes = oAuthUrl.data?.url;
|
final oAuthUrlRes = oAuthUrl?.url;
|
||||||
if (oAuthUrlRes == null) {
|
if (oAuthUrlRes == null) {
|
||||||
log.severe(
|
log.severe(
|
||||||
"oAuth Server URL not available. Kindly ensure oAuth login is enabled in the server",
|
"oAuth Server URL not available. Kindly ensure oAuth login is enabled in the server",
|
||||||
@ -97,12 +94,10 @@ class LoginService with LogContext {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final loginResponse = await oAuthApi.finishOAuth(
|
final loginResponse = await oAuthApi.finishOAuth(
|
||||||
oAuthCallbackDto: OAuthCallbackDto((builder) {
|
OAuthCallbackDto(url: oAuthCallbackUrl),
|
||||||
builder.url = oAuthCallbackUrl;
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return loginResponse.data?.accessToken;
|
return loginResponse?.accessToken;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.severe("Exception occured while performing oauth login", e);
|
log.severe("Exception occured while performing oauth login", e);
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import 'package:immich_mobile/domain/models/server-info/server_config.model.dart';
|
import 'package:immich_mobile/domain/models/server-info/server_config.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/server-info/server_features.model.dart';
|
import 'package:immich_mobile/domain/models/server-info/server_features.model.dart';
|
||||||
|
import 'package:immich_mobile/utils/immich_api_client.dart';
|
||||||
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
|
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
|
||||||
import 'package:openapi/openapi.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
class ServerInfoService with LogContext {
|
class ServerInfoService with LogContext {
|
||||||
final Openapi _api;
|
final ImmichApiClient _api;
|
||||||
|
|
||||||
ServerApi get _serverInfo => _api.getServerApi();
|
ServerApi get _serverInfo => _api.getServerApi();
|
||||||
|
|
||||||
@ -12,8 +13,7 @@ class ServerInfoService with LogContext {
|
|||||||
|
|
||||||
Future<ServerFeatures?> getServerFeatures() async {
|
Future<ServerFeatures?> getServerFeatures() async {
|
||||||
try {
|
try {
|
||||||
final response = await _serverInfo.getServerFeatures();
|
final dto = await _serverInfo.getServerFeatures();
|
||||||
final dto = response.data;
|
|
||||||
if (dto != null) {
|
if (dto != null) {
|
||||||
return ServerFeatures.fromDto(dto);
|
return ServerFeatures.fromDto(dto);
|
||||||
}
|
}
|
||||||
@ -25,8 +25,7 @@ class ServerInfoService with LogContext {
|
|||||||
|
|
||||||
Future<ServerConfig?> getServerConfig() async {
|
Future<ServerConfig?> getServerConfig() async {
|
||||||
try {
|
try {
|
||||||
final response = await _serverInfo.getServerConfig();
|
final dto = await _serverInfo.getServerConfig();
|
||||||
final dto = response.data;
|
|
||||||
if (dto != null) {
|
if (dto != null) {
|
||||||
return ServerConfig.fromDto(dto);
|
return ServerConfig.fromDto(dto);
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
import 'package:immich_mobile/utils/immich_api_client.dart';
|
||||||
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
|
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
|
||||||
import 'package:openapi/openapi.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
class UserService with LogContext {
|
class UserService with LogContext {
|
||||||
final Openapi _api;
|
final ImmichApiClient _api;
|
||||||
|
|
||||||
UsersApi get _userApi => _api.getUsersApi();
|
UsersApi get _userApi => _api.getUsersApi();
|
||||||
|
|
||||||
@ -11,15 +12,14 @@ class UserService with LogContext {
|
|||||||
|
|
||||||
Future<User?> getMyUser() async {
|
Future<User?> getMyUser() async {
|
||||||
try {
|
try {
|
||||||
final response = await _userApi.getMyUser();
|
final userDto = await _userApi.getMyUser();
|
||||||
final dto = response.data;
|
if (userDto == null) {
|
||||||
if (dto == null) {
|
|
||||||
log.severe("Cannot fetch my user.");
|
log.severe("Cannot fetch my user.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final preferences = await _userApi.getMyPreferences();
|
final preferencesDto = await _userApi.getMyPreferences();
|
||||||
return User.fromAdminDto(dto, preferences.data);
|
return User.fromAdminDto(userDto, preferencesDto);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
log.severe("Error while fetching server features", e, s);
|
log.severe("Error while fetching server features", e, s);
|
||||||
}
|
}
|
||||||
|
@ -3,4 +3,6 @@ import 'package:immich_mobile/domain/models/user.model.dart';
|
|||||||
|
|
||||||
class CurrentUserCubit extends Cubit<User> {
|
class CurrentUserCubit extends Cubit<User> {
|
||||||
CurrentUserCubit(super.initialState);
|
CurrentUserCubit(super.initialState);
|
||||||
|
|
||||||
|
void updateUser(User user) => emit(user);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
|
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
|
||||||
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
|
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
|
||||||
@ -11,10 +10,9 @@ import 'package:immich_mobile/i18n/strings.g.dart';
|
|||||||
import 'package:immich_mobile/presentation/modules/common/states/server_info/server_feature_config.state.dart';
|
import 'package:immich_mobile/presentation/modules/common/states/server_info/server_feature_config.state.dart';
|
||||||
import 'package:immich_mobile/presentation/modules/login/models/login_page.model.dart';
|
import 'package:immich_mobile/presentation/modules/login/models/login_page.model.dart';
|
||||||
import 'package:immich_mobile/service_locator.dart';
|
import 'package:immich_mobile/service_locator.dart';
|
||||||
import 'package:immich_mobile/utils/immich_auth_interceptor.dart';
|
import 'package:immich_mobile/utils/immich_api_client.dart';
|
||||||
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
|
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
|
||||||
import 'package:immich_mobile/utils/snackbar_manager.dart';
|
import 'package:immich_mobile/utils/snackbar_manager.dart';
|
||||||
import 'package:openapi/openapi.dart';
|
|
||||||
|
|
||||||
class LoginPageCubit extends Cubit<LoginPageState> with LogContext {
|
class LoginPageCubit extends Cubit<LoginPageState> with LogContext {
|
||||||
LoginPageCubit() : super(LoginPageState.reset());
|
LoginPageCubit() : super(LoginPageState.reset());
|
||||||
@ -66,7 +64,7 @@ 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);
|
||||||
await ServiceLocator.registerPostValidationServices(url);
|
ServiceLocator.registerPostValidationServices(url);
|
||||||
|
|
||||||
// Fetch server features
|
// Fetch server features
|
||||||
await di<ServerFeatureConfigCubit>().getFeatures();
|
await di<ServerFeatureConfigCubit>().getFeatures();
|
||||||
@ -92,6 +90,9 @@ class LoginPageCubit extends Cubit<LoginPageState> with LogContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _postLogin(accessToken);
|
await _postLogin(accessToken);
|
||||||
|
} catch (e, s) {
|
||||||
|
SnackbarManager.showError(t.login.error.error_login);
|
||||||
|
log.severe("Cannot perform password login", e, s);
|
||||||
} finally {
|
} finally {
|
||||||
emit(state.copyWith(isValidationInProgress: false));
|
emit(state.copyWith(isValidationInProgress: false));
|
||||||
}
|
}
|
||||||
@ -109,6 +110,9 @@ class LoginPageCubit extends Cubit<LoginPageState> with LogContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _postLogin(accessToken);
|
await _postLogin(accessToken);
|
||||||
|
} catch (e, s) {
|
||||||
|
SnackbarManager.showError(t.login.error.error_login_oauth);
|
||||||
|
log.severe("Cannot perform oauth login", e, s);
|
||||||
} finally {
|
} finally {
|
||||||
emit(state.copyWith(isValidationInProgress: false));
|
emit(state.copyWith(isValidationInProgress: false));
|
||||||
}
|
}
|
||||||
@ -118,12 +122,7 @@ class LoginPageCubit extends Cubit<LoginPageState> with LogContext {
|
|||||||
await di<IStoreRepository>().set(StoreKey.accessToken, accessToken);
|
await di<IStoreRepository>().set(StoreKey.accessToken, accessToken);
|
||||||
|
|
||||||
/// Set token to interceptor
|
/// Set token to interceptor
|
||||||
final interceptor = di<Openapi>()
|
await di<ImmichApiClient>().init(accessToken: accessToken);
|
||||||
.dio
|
|
||||||
.interceptors
|
|
||||||
.firstWhereOrNull((i) => i is ImmichAuthInterceptor)
|
|
||||||
as ImmichAuthInterceptor?;
|
|
||||||
interceptor?.setAccessToken(accessToken);
|
|
||||||
|
|
||||||
final user = await di<UserService>().getMyUser();
|
final user = await di<UserService>().getMyUser();
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
@ -137,7 +136,7 @@ class LoginPageCubit extends Cubit<LoginPageState> with LogContext {
|
|||||||
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
isValidationInProgress: false,
|
isValidationInProgress: false,
|
||||||
isServerValidated: true,
|
isLoginSuccessful: true,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,3 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
|
||||||
import 'package:dio/dio.dart';
|
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:immich_mobile/domain/interfaces/log.interface.dart';
|
import 'package:immich_mobile/domain/interfaces/log.interface.dart';
|
||||||
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
|
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
|
||||||
@ -15,12 +10,12 @@ import 'package:immich_mobile/domain/repositories/user.repository.dart';
|
|||||||
import 'package:immich_mobile/domain/services/app_setting.service.dart';
|
import 'package:immich_mobile/domain/services/app_setting.service.dart';
|
||||||
import 'package:immich_mobile/domain/services/login.service.dart';
|
import 'package:immich_mobile/domain/services/login.service.dart';
|
||||||
import 'package:immich_mobile/domain/services/server_info.service.dart';
|
import 'package:immich_mobile/domain/services/server_info.service.dart';
|
||||||
|
import 'package:immich_mobile/domain/services/user.service.dart';
|
||||||
import 'package:immich_mobile/presentation/modules/common/states/current_user.state.dart';
|
import 'package:immich_mobile/presentation/modules/common/states/current_user.state.dart';
|
||||||
import 'package:immich_mobile/presentation/modules/common/states/server_info/server_feature_config.state.dart';
|
import 'package:immich_mobile/presentation/modules/common/states/server_info/server_feature_config.state.dart';
|
||||||
import 'package:immich_mobile/presentation/modules/theme/states/app_theme.state.dart';
|
import 'package:immich_mobile/presentation/modules/theme/states/app_theme.state.dart';
|
||||||
import 'package:immich_mobile/presentation/router/router.dart';
|
import 'package:immich_mobile/presentation/router/router.dart';
|
||||||
import 'package:immich_mobile/utils/immich_auth_interceptor.dart';
|
import 'package:immich_mobile/utils/immich_api_client.dart';
|
||||||
import 'package:openapi/openapi.dart';
|
|
||||||
|
|
||||||
final di = GetIt.I;
|
final di = GetIt.I;
|
||||||
|
|
||||||
@ -55,47 +50,24 @@ class ServiceLocator {
|
|||||||
di.registerLazySingleton<AppThemeCubit>(() => AppThemeCubit(di()));
|
di.registerLazySingleton<AppThemeCubit>(() => AppThemeCubit(di()));
|
||||||
}
|
}
|
||||||
|
|
||||||
static FutureOr<void> registerPostValidationServices(String endpoint) async {
|
static void registerPostValidationServices(String endpoint) {
|
||||||
if (di.isRegistered<Openapi>()) {
|
di.registerSingleton<ImmichApiClient>(ImmichApiClient(endpoint: endpoint));
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final deviceInfo = DeviceInfoPlugin();
|
|
||||||
final String deviceModel;
|
|
||||||
if (Platform.isIOS) {
|
|
||||||
deviceModel = (await deviceInfo.iosInfo).utsname.machine;
|
|
||||||
} else {
|
|
||||||
deviceModel = (await deviceInfo.androidInfo).model;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ====== DOMAIN
|
// ====== DOMAIN
|
||||||
|
di.registerFactory<UserService>(() => UserService(di()));
|
||||||
di.registerSingleton<Openapi>(
|
|
||||||
Openapi(
|
|
||||||
dio: Dio(
|
|
||||||
BaseOptions(
|
|
||||||
baseUrl: endpoint,
|
|
||||||
connectTimeout: const Duration(milliseconds: 5000),
|
|
||||||
receiveTimeout: const Duration(milliseconds: 3000),
|
|
||||||
headers: {
|
|
||||||
'deviceModel': deviceModel,
|
|
||||||
'deviceType': Platform.operatingSystem,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
interceptors: [ImmichAuthInterceptor()],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
di.registerFactory<ServerInfoService>(() => ServerInfoService(di()));
|
di.registerFactory<ServerInfoService>(() => ServerInfoService(di()));
|
||||||
|
|
||||||
// ====== PRESENTATION
|
// ====== PRESENTATION
|
||||||
|
|
||||||
di.registerLazySingleton<ServerFeatureConfigCubit>(
|
di.registerLazySingleton<ServerFeatureConfigCubit>(
|
||||||
() => ServerFeatureConfigCubit(di()),
|
() => ServerFeatureConfigCubit(di()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void registerCurrentUser(User user) {
|
static void registerCurrentUser(User user) {
|
||||||
|
if (di.isRegistered<CurrentUserCubit>()) {
|
||||||
|
di<CurrentUserCubit>().updateUser(user);
|
||||||
|
} else {
|
||||||
di.registerSingleton<CurrentUserCubit>(CurrentUserCubit(user));
|
di.registerSingleton<CurrentUserCubit>(CurrentUserCubit(user));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -1,117 +0,0 @@
|
|||||||
/// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
/// *****************************************************
|
|
||||||
/// FlutterGen
|
|
||||||
/// *****************************************************
|
|
||||||
|
|
||||||
// coverage:ignore-file
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use
|
|
||||||
|
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
|
|
||||||
class $AssetsImagesGen {
|
|
||||||
const $AssetsImagesGen();
|
|
||||||
|
|
||||||
/// File path: assets/images/immich-logo.png
|
|
||||||
AssetGenImage get immichLogo =>
|
|
||||||
const AssetGenImage('assets/images/immich-logo.png');
|
|
||||||
|
|
||||||
/// File path: assets/images/immich-text-dark.png
|
|
||||||
AssetGenImage get immichTextDark =>
|
|
||||||
const AssetGenImage('assets/images/immich-text-dark.png');
|
|
||||||
|
|
||||||
/// File path: assets/images/immich-text-light.png
|
|
||||||
AssetGenImage get immichTextLight =>
|
|
||||||
const AssetGenImage('assets/images/immich-text-light.png');
|
|
||||||
|
|
||||||
/// List of all assets
|
|
||||||
List<AssetGenImage> get values =>
|
|
||||||
[immichLogo, immichTextDark, immichTextLight];
|
|
||||||
}
|
|
||||||
|
|
||||||
class Assets {
|
|
||||||
Assets._();
|
|
||||||
|
|
||||||
static const $AssetsImagesGen images = $AssetsImagesGen();
|
|
||||||
}
|
|
||||||
|
|
||||||
class AssetGenImage {
|
|
||||||
const AssetGenImage(
|
|
||||||
this._assetName, {
|
|
||||||
this.size,
|
|
||||||
this.flavors = const {},
|
|
||||||
});
|
|
||||||
|
|
||||||
final String _assetName;
|
|
||||||
|
|
||||||
final Size? size;
|
|
||||||
final Set<String> flavors;
|
|
||||||
|
|
||||||
Image image({
|
|
||||||
Key? key,
|
|
||||||
AssetBundle? bundle,
|
|
||||||
ImageFrameBuilder? frameBuilder,
|
|
||||||
ImageErrorWidgetBuilder? errorBuilder,
|
|
||||||
String? semanticLabel,
|
|
||||||
bool excludeFromSemantics = false,
|
|
||||||
double? scale,
|
|
||||||
double? width,
|
|
||||||
double? height,
|
|
||||||
Color? color,
|
|
||||||
Animation<double>? opacity,
|
|
||||||
BlendMode? colorBlendMode,
|
|
||||||
BoxFit? fit,
|
|
||||||
AlignmentGeometry alignment = Alignment.center,
|
|
||||||
ImageRepeat repeat = ImageRepeat.noRepeat,
|
|
||||||
Rect? centerSlice,
|
|
||||||
bool matchTextDirection = false,
|
|
||||||
bool gaplessPlayback = false,
|
|
||||||
bool isAntiAlias = false,
|
|
||||||
String? package,
|
|
||||||
FilterQuality filterQuality = FilterQuality.low,
|
|
||||||
int? cacheWidth,
|
|
||||||
int? cacheHeight,
|
|
||||||
}) {
|
|
||||||
return Image.asset(
|
|
||||||
_assetName,
|
|
||||||
key: key,
|
|
||||||
bundle: bundle,
|
|
||||||
frameBuilder: frameBuilder,
|
|
||||||
errorBuilder: errorBuilder,
|
|
||||||
semanticLabel: semanticLabel,
|
|
||||||
excludeFromSemantics: excludeFromSemantics,
|
|
||||||
scale: scale,
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
color: color,
|
|
||||||
opacity: opacity,
|
|
||||||
colorBlendMode: colorBlendMode,
|
|
||||||
fit: fit,
|
|
||||||
alignment: alignment,
|
|
||||||
repeat: repeat,
|
|
||||||
centerSlice: centerSlice,
|
|
||||||
matchTextDirection: matchTextDirection,
|
|
||||||
gaplessPlayback: gaplessPlayback,
|
|
||||||
isAntiAlias: isAntiAlias,
|
|
||||||
package: package,
|
|
||||||
filterQuality: filterQuality,
|
|
||||||
cacheWidth: cacheWidth,
|
|
||||||
cacheHeight: cacheHeight,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImageProvider provider({
|
|
||||||
AssetBundle? bundle,
|
|
||||||
String? package,
|
|
||||||
}) {
|
|
||||||
return AssetImage(
|
|
||||||
_assetName,
|
|
||||||
bundle: bundle,
|
|
||||||
package: package,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String get path => _assetName;
|
|
||||||
|
|
||||||
String get keyName => _assetName;
|
|
||||||
}
|
|
@ -3,5 +3,11 @@ 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;
|
||||||
|
|
||||||
|
/// Headers
|
||||||
|
// Auth header
|
||||||
|
const String kImmichHeaderAuthKey = "x-immich-user-token";
|
||||||
|
const String kImmichHeaderDeviceModel = "deviceModel";
|
||||||
|
const String kImmichHeaderDeviceType = "deviceType";
|
||||||
|
|
||||||
/// Global ScaffoldMessengerKey to show snackbars
|
/// Global ScaffoldMessengerKey to show snackbars
|
||||||
final GlobalKey<ScaffoldMessengerState> kScafMessengerKey = GlobalKey();
|
final GlobalKey<ScaffoldMessengerState> kScafMessengerKey = GlobalKey();
|
||||||
|
92
mobile-v2/lib/utils/immich_api_client.dart
Normal file
92
mobile-v2/lib/utils/immich_api_client.dart
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
|
import 'package:immich_mobile/presentation/router/router.dart';
|
||||||
|
import 'package:immich_mobile/service_locator.dart';
|
||||||
|
import 'package:immich_mobile/utils/constants/globals.dart';
|
||||||
|
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
|
class ImmichApiClient extends ApiClient with LogContext {
|
||||||
|
ImmichApiClient({required String endpoint}) : super(basePath: endpoint);
|
||||||
|
|
||||||
|
Future<void> init({String? accessToken}) async {
|
||||||
|
final token =
|
||||||
|
accessToken ?? (await di<IStoreRepository>().get(StoreKey.accessToken));
|
||||||
|
|
||||||
|
if (token != null) {
|
||||||
|
addDefaultHeader(kImmichHeaderAuthKey, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
final deviceInfo = DeviceInfoPlugin();
|
||||||
|
final String deviceModel;
|
||||||
|
if (Platform.isIOS) {
|
||||||
|
deviceModel = (await deviceInfo.iosInfo).utsname.machine;
|
||||||
|
} else {
|
||||||
|
deviceModel = (await deviceInfo.androidInfo).model;
|
||||||
|
}
|
||||||
|
|
||||||
|
addDefaultHeader(kImmichHeaderDeviceModel, deviceModel);
|
||||||
|
addDefaultHeader(kImmichHeaderDeviceType, Platform.operatingSystem);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Response> invokeAPI(
|
||||||
|
String path,
|
||||||
|
String method,
|
||||||
|
List<QueryParam> queryParams,
|
||||||
|
Object? body,
|
||||||
|
Map<String, String> headerParams,
|
||||||
|
Map<String, String> formParams,
|
||||||
|
String? contentType,
|
||||||
|
) async {
|
||||||
|
final res = await super.invokeAPI(
|
||||||
|
path,
|
||||||
|
method,
|
||||||
|
queryParams,
|
||||||
|
body,
|
||||||
|
headerParams,
|
||||||
|
formParams,
|
||||||
|
contentType,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.statusCode == HttpStatus.unauthorized) {
|
||||||
|
log.severe("Token invalid. Redirecting to login route");
|
||||||
|
await di<AppRouter>().replaceAll([const LoginRoute()]);
|
||||||
|
throw ApiException(res.statusCode, "Unauthorized");
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore: avoid-dynamic
|
||||||
|
static dynamic _patchDto(dynamic value, String targetType) {
|
||||||
|
switch (targetType) {
|
||||||
|
case 'UserPreferencesResponseDto':
|
||||||
|
if (value is Map) {
|
||||||
|
if (value['rating'] == null) {
|
||||||
|
value['rating'] = RatingResponse().toJson();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore: avoid-dynamic
|
||||||
|
static dynamic fromJson(
|
||||||
|
// ignore: avoid-dynamic
|
||||||
|
dynamic value,
|
||||||
|
String targetType, {
|
||||||
|
bool growable = false,
|
||||||
|
}) {
|
||||||
|
_patchDto(value, targetType);
|
||||||
|
return ApiClient.fromJson(value, targetType, growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
UsersApi getUsersApi() => UsersApi(this);
|
||||||
|
ServerApi getServerApi() => ServerApi(this);
|
||||||
|
AuthenticationApi getAuthenticationApi() => AuthenticationApi(this);
|
||||||
|
OAuthApi getOAuthApi() => OAuthApi(this);
|
||||||
|
}
|
@ -1,29 +0,0 @@
|
|||||||
import 'package:dio/dio.dart';
|
|
||||||
import 'package:immich_mobile/presentation/router/router.dart';
|
|
||||||
import 'package:immich_mobile/service_locator.dart';
|
|
||||||
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
|
|
||||||
|
|
||||||
class ImmichAuthInterceptor extends Interceptor with LogContext {
|
|
||||||
String? _accessToken;
|
|
||||||
|
|
||||||
void setAccessToken(String token) => _accessToken = token;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
|
||||||
if (_accessToken != null) {
|
|
||||||
options.headers["x-immich-user-token"] = _accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.next(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
|
||||||
if (response.statusCode == 401) {
|
|
||||||
log.severe("Token expired. Logging user out");
|
|
||||||
di<AppRouter>().replaceAll([const LoginRoute()]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handler.next(response);
|
|
||||||
}
|
|
||||||
}
|
|
@ -14,7 +14,6 @@ import 'package:logging/logging.dart';
|
|||||||
class LogManager {
|
class LogManager {
|
||||||
LogManager._();
|
LogManager._();
|
||||||
static final LogManager _instance = LogManager._();
|
static final LogManager _instance = LogManager._();
|
||||||
|
|
||||||
// ignore: match-getter-setter-field-names
|
// ignore: match-getter-setter-field-names
|
||||||
static LogManager get I => _instance;
|
static LogManager get I => _instance;
|
||||||
|
|
||||||
|
@ -262,22 +262,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.1"
|
version: "7.0.1"
|
||||||
dio:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: dio
|
|
||||||
sha256: "0dfb6b6a1979dac1c1245e17cef824d7b452ea29bd33d3467269f9bef3715fb0"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "5.6.0"
|
|
||||||
dio_web_adapter:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: dio_web_adapter
|
|
||||||
sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.0.0"
|
|
||||||
drift:
|
drift:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -451,7 +435,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
http:
|
http:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
|
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
|
||||||
@ -618,22 +602,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
one_of:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: one_of
|
|
||||||
sha256: "25fe0fcf181e761c6fcd604caf9d5fdf952321be17584ba81c72c06bdaa511f0"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.5.0"
|
|
||||||
one_of_serializer:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: one_of_serializer
|
|
||||||
sha256: "3f3dfb5c1578ba3afef1cb47fcc49e585e797af3f2b6c2cc7ed90aad0c5e7b83"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.5.0"
|
|
||||||
openapi:
|
openapi:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -801,14 +769,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.3.0"
|
||||||
quiver:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: quiver
|
|
||||||
sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.2.1"
|
|
||||||
recase:
|
recase:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -16,14 +16,14 @@ dependencies:
|
|||||||
# OS specific path
|
# OS specific path
|
||||||
path_provider: ^2.1.4
|
path_provider: ^2.1.4
|
||||||
path: ^1.9.0
|
path: ^1.9.0
|
||||||
# Bloc
|
# State handling
|
||||||
flutter_bloc: ^8.1.6
|
flutter_bloc: ^8.1.6
|
||||||
# Database
|
# Database
|
||||||
drift: ^2.20.0
|
drift: ^2.20.0
|
||||||
sqlite3: ^2.4.6
|
sqlite3: ^2.4.6
|
||||||
sqlite3_flutter_libs: ^0.5.24
|
sqlite3_flutter_libs: ^0.5.24
|
||||||
# Network
|
# Network
|
||||||
dio: ^5.6.0
|
http: ^1.2.2
|
||||||
# Route handling
|
# Route handling
|
||||||
auto_route: ^9.2.2
|
auto_route: ^9.2.2
|
||||||
# Logging
|
# Logging
|
||||||
@ -55,6 +55,11 @@ dependencies:
|
|||||||
openapi:
|
openapi:
|
||||||
path: openapi
|
path: openapi
|
||||||
|
|
||||||
|
dependency_overrides:
|
||||||
|
# openapi uses an older version of http for backward compatibility. New versions do not have
|
||||||
|
# a breaking change so it is safer to override it and use the latest version for the app
|
||||||
|
http: ^1.2.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
@ -3,6 +3,7 @@ OPENAPI_GENERATOR_VERSION=v7.8.0
|
|||||||
|
|
||||||
# usage: ./bin/generate-open-api.sh
|
# usage: ./bin/generate-open-api.sh
|
||||||
|
|
||||||
|
|
||||||
function dart {
|
function dart {
|
||||||
rm -rf ../mobile/openapi
|
rm -rf ../mobile/openapi
|
||||||
cd ./templates/mobile/serialization/native
|
cd ./templates/mobile/serialization/native
|
||||||
@ -23,11 +24,20 @@ function dart {
|
|||||||
|
|
||||||
function dartDio {
|
function dartDio {
|
||||||
rm -rf ../mobile-v2/openapi
|
rm -rf ../mobile-v2/openapi
|
||||||
npx --yes @openapitools/openapi-generator-cli generate -g dart-dio -i ./immich-openapi-specs.json -o ../mobile-v2/openapi --global-property skipFormModel=false --global-property models,apis,supportingFiles,apiTests=false,apiDocs=false,modelTests=false,modelDocs=false
|
cd ./templates/mobile/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
|
||||||
|
patch --no-backup-if-mismatch -u native_class.mustache <native_class.mustache.patch
|
||||||
|
cd ../../../../
|
||||||
|
|
||||||
|
npx --yes @openapitools/openapi-generator-cli generate -g dart -i ./immich-openapi-specs.json -o ../mobile-v2/openapi -t ./templates/mobile
|
||||||
|
|
||||||
|
# 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.dart <./patch/api.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
|
||||||
echo "export 'package:openapi/src/auth/bearer_auth.dart';" >> ../mobile-v2/openapi/lib/openapi.dart
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function typescript {
|
function typescript {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user