fix(mobile): remove redundant iOS code

update code related to LocalAsset model and asset viewer
This commit is contained in:
Peter Ombodi
2026-04-15 16:42:44 +03:00
parent 719c7d955b
commit 4806dc76aa
9 changed files with 71 additions and 272 deletions
-1
View File
@@ -61,7 +61,6 @@ import UIKit
BackgroundWorkerFgHostApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: BackgroundWorkerApiImpl())
ConnectivityApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: ConnectivityApiImpl())
NetworkApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: NetworkApiImpl(viewController: controller))
ViewIntentHostApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: ViewIntentApiImpl())
}
public static func cancelPlugins(with engine: FlutterEngine) {
@@ -1,228 +0,0 @@
// Autogenerated from Pigeon (v26.0.2), do not edit directly.
// See also: https://pub.dev/packages/pigeon
import Foundation
#if os(iOS)
import Flutter
#elseif os(macOS)
import FlutterMacOS
#else
#error("Unsupported platform.")
#endif
private func wrapResult(_ result: Any?) -> [Any?] {
return [result]
}
private func wrapError(_ error: Any) -> [Any?] {
if let pigeonError = error as? PigeonError {
return [
pigeonError.code,
pigeonError.message,
pigeonError.details,
]
}
if let flutterError = error as? FlutterError {
return [
flutterError.code,
flutterError.message,
flutterError.details,
]
}
return [
"\(error)",
"\(type(of: error))",
"Stacktrace: \(Thread.callStackSymbols)",
]
}
private func isNullish(_ value: Any?) -> Bool {
return value is NSNull || value == nil
}
private func nilOrValue<T>(_ value: Any?) -> T? {
if value is NSNull { return nil }
return value as! T?
}
func deepEqualsViewIntent(_ lhs: Any?, _ rhs: Any?) -> Bool {
let cleanLhs = nilOrValue(lhs) as Any?
let cleanRhs = nilOrValue(rhs) as Any?
switch (cleanLhs, cleanRhs) {
case (nil, nil):
return true
case (nil, _), (_, nil):
return false
case is (Void, Void):
return true
case let (cleanLhsHashable, cleanRhsHashable) as (AnyHashable, AnyHashable):
return cleanLhsHashable == cleanRhsHashable
case let (cleanLhsArray, cleanRhsArray) as ([Any?], [Any?]):
guard cleanLhsArray.count == cleanRhsArray.count else { return false }
for (index, element) in cleanLhsArray.enumerated() {
if !deepEqualsViewIntent(element, cleanRhsArray[index]) {
return false
}
}
return true
case let (cleanLhsDictionary, cleanRhsDictionary) as ([AnyHashable: Any?], [AnyHashable: Any?]):
guard cleanLhsDictionary.count == cleanRhsDictionary.count else { return false }
for (key, cleanLhsValue) in cleanLhsDictionary {
guard cleanRhsDictionary.index(forKey: key) != nil else { return false }
if !deepEqualsViewIntent(cleanLhsValue, cleanRhsDictionary[key]!) {
return false
}
}
return true
default:
// Any other type shouldn't be able to be used with pigeon. File an issue if you find this to be untrue.
return false
}
}
func deepHashViewIntent(value: Any?, hasher: inout Hasher) {
if let valueList = value as? [AnyHashable] {
for item in valueList { deepHashViewIntent(value: item, hasher: &hasher) }
return
}
if let valueDict = value as? [AnyHashable: AnyHashable] {
for key in valueDict.keys {
hasher.combine(key)
deepHashViewIntent(value: valueDict[key]!, hasher: &hasher)
}
return
}
if let hashableValue = value as? AnyHashable {
hasher.combine(hashableValue.hashValue)
}
return hasher.combine(String(describing: value))
}
enum ViewIntentType: Int {
case image = 0
case video = 1
}
/// Generated class from Pigeon that represents data sent in messages.
struct ViewIntentPayload: Hashable {
var path: String
var type: ViewIntentType
var mimeType: String
var localAssetId: String? = nil
// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> ViewIntentPayload? {
let path = pigeonVar_list[0] as! String
let type = pigeonVar_list[1] as! ViewIntentType
let mimeType = pigeonVar_list[2] as! String
let localAssetId: String? = nilOrValue(pigeonVar_list[3])
return ViewIntentPayload(
path: path,
type: type,
mimeType: mimeType,
localAssetId: localAssetId
)
}
func toList() -> [Any?] {
return [
path,
type,
mimeType,
localAssetId,
]
}
static func == (lhs: ViewIntentPayload, rhs: ViewIntentPayload) -> Bool {
return deepEqualsViewIntent(lhs.toList(), rhs.toList()) }
func hash(into hasher: inout Hasher) {
deepHashViewIntent(value: toList(), hasher: &hasher)
}
}
private class ViewIntentPigeonCodecReader: FlutterStandardReader {
override func readValue(ofType type: UInt8) -> Any? {
switch type {
case 129:
let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?)
if let enumResultAsInt = enumResultAsInt {
return ViewIntentType(rawValue: enumResultAsInt)
}
return nil
case 130:
return ViewIntentPayload.fromList(self.readValue() as! [Any?])
default:
return super.readValue(ofType: type)
}
}
}
private class ViewIntentPigeonCodecWriter: FlutterStandardWriter {
override func writeValue(_ value: Any) {
if let value = value as? ViewIntentType {
super.writeByte(129)
super.writeValue(value.rawValue)
} else if let value = value as? ViewIntentPayload {
super.writeByte(130)
super.writeValue(value.toList())
} else {
super.writeValue(value)
}
}
}
private class ViewIntentPigeonCodecReaderWriter: FlutterStandardReaderWriter {
override func reader(with data: Data) -> FlutterStandardReader {
return ViewIntentPigeonCodecReader(data: data)
}
override func writer(with data: NSMutableData) -> FlutterStandardWriter {
return ViewIntentPigeonCodecWriter(data: data)
}
}
class ViewIntentPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
static let shared = ViewIntentPigeonCodec(readerWriter: ViewIntentPigeonCodecReaderWriter())
}
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol ViewIntentHostApi {
func consumeViewIntent(completion: @escaping (Result<ViewIntentPayload?, Error>) -> Void)
}
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
class ViewIntentHostApiSetup {
static var codec: FlutterStandardMessageCodec { ViewIntentPigeonCodec.shared }
/// Sets up an instance of `ViewIntentHostApi` to handle messages through the `binaryMessenger`.
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: ViewIntentHostApi?, messageChannelSuffix: String = "") {
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
let consumeViewIntentChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.ViewIntentHostApi.consumeViewIntent\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
consumeViewIntentChannel.setMessageHandler { _, reply in
api.consumeViewIntent { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
consumeViewIntentChannel.setMessageHandler(nil)
}
}
}
@@ -1,5 +0,0 @@
class ViewIntentApiImpl: ViewIntentHostApi {
func consumeViewIntent(completion: @escaping (Result<ViewIntentPayload?, any Error>) -> Void) {
completion(.success(nil))
}
}
@@ -1,5 +1,6 @@
import 'dart:io';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:path/path.dart';
enum ViewIntentAttachmentType { image, video }
@@ -7,9 +8,15 @@ enum ViewIntentAttachmentType { image, video }
class ViewIntentAttachment {
final String path;
final ViewIntentAttachmentType type;
final String mimeType;
final String? localAssetId;
const ViewIntentAttachment({required this.path, required this.type, this.localAssetId});
const ViewIntentAttachment({
required this.path,
required this.type,
required this.mimeType,
this.localAssetId,
});
File get file => File(path);
@@ -18,4 +25,22 @@ class ViewIntentAttachment {
bool get isImage => type == ViewIntentAttachmentType.image;
bool get isVideo => type == ViewIntentAttachmentType.video;
AssetPlaybackStyle get playbackStyle {
if (isVideo) {
return AssetPlaybackStyle.video;
}
final normalizedMimeType = mimeType.toLowerCase();
if (normalizedMimeType == 'image/gif' || normalizedMimeType == 'image/webp') {
return AssetPlaybackStyle.imageAnimated;
}
final normalizedPath = path.toLowerCase();
if (normalizedPath.endsWith('.gif') || normalizedPath.endsWith('.webp')) {
return AssetPlaybackStyle.imageAnimated;
}
return AssetPlaybackStyle.image;
}
}
+36 -22
View File
@@ -14,25 +14,33 @@ PlatformException _createConnectionError(String channelName) {
message: 'Unable to establish connection on channel: "$channelName".',
);
}
bool _deepEquals(Object? a, Object? b) {
if (a is List && b is List) {
return a.length == b.length && a.indexed.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]));
return a.length == b.length &&
a.indexed
.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]));
}
if (a is Map && b is Map) {
return a.length == b.length &&
a.entries.every(
(MapEntry<Object?, Object?> entry) =>
(b as Map<Object?, Object?>).containsKey(entry.key) && _deepEquals(entry.value, b[entry.key]),
);
return a.length == b.length && a.entries.every((MapEntry<Object?, Object?> entry) =>
(b as Map<Object?, Object?>).containsKey(entry.key) &&
_deepEquals(entry.value, b[entry.key]));
}
return a == b;
}
enum ViewIntentType { image, video }
enum ViewIntentType {
image,
video,
}
class ViewIntentPayload {
ViewIntentPayload({required this.path, required this.type, required this.mimeType, this.localAssetId});
ViewIntentPayload({
required this.path,
required this.type,
required this.mimeType,
this.localAssetId,
});
String path;
@@ -43,12 +51,16 @@ class ViewIntentPayload {
String? localAssetId;
List<Object?> _toList() {
return <Object?>[path, type, mimeType, localAssetId];
return <Object?>[
path,
type,
mimeType,
localAssetId,
];
}
Object encode() {
return _toList();
}
return _toList(); }
static ViewIntentPayload decode(Object result) {
result as List<Object?>;
@@ -74,9 +86,11 @@ class ViewIntentPayload {
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
int get hashCode => Object.hashAll(_toList());
int get hashCode => Object.hashAll(_toList())
;
}
class _PigeonCodec extends StandardMessageCodec {
const _PigeonCodec();
@override
@@ -84,10 +98,10 @@ class _PigeonCodec extends StandardMessageCodec {
if (value is int) {
buffer.putUint8(4);
buffer.putInt64(value);
} else if (value is ViewIntentType) {
} else if (value is ViewIntentType) {
buffer.putUint8(129);
writeValue(buffer, value.index);
} else if (value is ViewIntentPayload) {
} else if (value is ViewIntentPayload) {
buffer.putUint8(130);
writeValue(buffer, value.encode());
} else {
@@ -98,10 +112,10 @@ class _PigeonCodec extends StandardMessageCodec {
@override
Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) {
case 129:
case 129:
final int? value = readValue(buffer) as int?;
return value == null ? null : ViewIntentType.values[value];
case 130:
case 130:
return ViewIntentPayload.decode(readValue(buffer)!);
default:
return super.readValueOfType(type, buffer);
@@ -114,8 +128,8 @@ class ViewIntentHostApi {
/// available for dependency injection. If it is left null, the default
/// BinaryMessenger will be used which routes to the host platform.
ViewIntentHostApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
: pigeonVar_binaryMessenger = binaryMessenger,
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
: pigeonVar_binaryMessenger = binaryMessenger,
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
final BinaryMessenger? pigeonVar_binaryMessenger;
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
@@ -123,15 +137,15 @@ class ViewIntentHostApi {
final String pigeonVar_messageChannelSuffix;
Future<ViewIntentPayload?> consumeViewIntent() async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.immich_mobile.ViewIntentHostApi.consumeViewIntent$pigeonVar_messageChannelSuffix';
final String pigeonVar_channelName = 'dev.flutter.pigeon.immich_mobile.ViewIntentHostApi.consumeViewIntent$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
@@ -7,12 +7,9 @@ import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
import 'package:immich_mobile/models/view_intent/view_intent_attachment.model.dart';
import 'package:immich_mobile/platform/native_sync_api.g.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
import 'package:immich_mobile/providers/asset_viewer/video_player_controls_provider.dart';
import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart';
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
import 'package:immich_mobile/providers/auth.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/routing/router.dart';
@@ -89,8 +86,8 @@ class ViewIntentHandler {
return;
}
}
final fallbackAsset = _toViewIntentAsset(attachment);
final checksum = localAssetId != null ? await _computeChecksum(localAssetId) : null;
final fallbackAsset = _toViewIntentAsset(attachment).copyWith(checksum: checksum);
_logger.fine('openAssetViewer for fallbackAsset');
_openAssetViewer(fallbackAsset, _timelineFactory.fromAssets([fallbackAsset], TimelineOrigin.deepLink), 0);
}
@@ -135,13 +132,9 @@ class ViewIntentHandler {
void _openAssetViewer(BaseAsset asset, TimelineService timelineService, int initialIndex) {
_ref.read(assetViewerProvider.notifier).reset();
_ref.read(assetViewerProvider.notifier).setAsset(asset);
_ref.read(currentAssetNotifier.notifier).setAsset(asset);
if (asset.isVideo || asset.isMotionPhoto) {
_ref.read(videoPlaybackValueProvider.notifier).reset();
_ref.read(videoPlayerControlsProvider.notifier).pause();
}
if (asset.isVideo) {
_ref.read(assetViewerProvider.notifier).setControls(false);
_ref.read(assetViewerProvider.notifier).setControls(true);
}
_router.push(AssetViewerRoute(initialIndex: initialIndex, timelineService: timelineService));
@@ -163,13 +156,14 @@ class ViewIntentHandler {
final now = DateTime.now();
return LocalAsset(
id: attachment.path,
id: attachment.localAssetId ?? '',
name: attachment.fileName,
checksum: null,
type: attachment.isVideo ? AssetType.video : AssetType.image,
createdAt: now,
updatedAt: now,
isEdited: false,
playbackStyle: attachment.playbackStyle,
);
}
}
@@ -18,6 +18,7 @@ class ViewHandlerRepository {
return ViewIntentAttachment(
path: result.path,
type: result.type == ViewIntentType.image ? ViewIntentAttachmentType.image : ViewIntentAttachmentType.video,
mimeType: result.mimeType,
localAssetId: result.localAssetId,
);
} catch (_) {
-2
View File
@@ -3,8 +3,6 @@ import 'package:pigeon/pigeon.dart';
@ConfigurePigeon(
PigeonOptions(
dartOut: 'lib/platform/view_intent_api.g.dart',
swiftOut: 'ios/Runner/ViewIntent/ViewIntent.g.swift',
swiftOptions: SwiftOptions(includeErrorClass: false),
kotlinOut: 'android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntent.g.kt',
kotlinOptions: KotlinOptions(package: 'app.alextran.immich.viewintent'),
dartOptions: DartOptions(),
@@ -13,6 +13,7 @@ void main() {
const attachment = ViewIntentAttachment(
path: '/tmp/file.jpg',
type: ViewIntentAttachmentType.image,
mimeType: 'image/jpeg',
localAssetId: '42',
);