mirror of
https://github.com/immich-app/immich.git
synced 2025-12-07 01:10:00 +03:00
Compare commits
1 Commits
chore-full
...
feat/mobil
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56caf6a133 |
@@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objectVersion = 77;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@@ -27,9 +27,11 @@
|
||||
FAC6F89B2D287C890078CB2F /* ShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = FAC6F8902D287C890078CB2F /* ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
FAC6F8B72D287F120078CB2F /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC6F8B52D287F120078CB2F /* ShareViewController.swift */; };
|
||||
FAC6F8B92D287F120078CB2F /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FAC6F8B32D287F120078CB2F /* MainInterface.storyboard */; };
|
||||
FEAFA8732E4D42F4001E47FE /* Thumbhash.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEAFA8722E4D42F4001E47FE /* Thumbhash.swift */; };
|
||||
FED3B1962E253E9B0030FD97 /* ThumbnailsImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = FED3B1942E253E9B0030FD97 /* ThumbnailsImpl.swift */; };
|
||||
FED3B1972E253E9B0030FD97 /* Thumbnails.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = FED3B1932E253E9B0030FD97 /* Thumbnails.g.swift */; };
|
||||
FE619FDB2E6F5D0600D0B708 /* NativeImageViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE619FD52E6F5D0600D0B708 /* NativeImageViewFactory.swift */; };
|
||||
FE619FDC2E6F5D0600D0B708 /* Thumbhash.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE619FD72E6F5D0600D0B708 /* Thumbhash.swift */; };
|
||||
FE619FDD2E6F5D0600D0B708 /* Thumbnails.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE619FD82E6F5D0600D0B708 /* Thumbnails.g.swift */; };
|
||||
FE619FDE2E6F5D0600D0B708 /* ThumbnailsImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE619FD92E6F5D0600D0B708 /* ThumbnailsImpl.swift */; };
|
||||
FE619FDF2E6F5D0600D0B708 /* NativeImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE619FD42E6F5D0600D0B708 /* NativeImageView.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -111,9 +113,11 @@
|
||||
FAC6F8B42D287F120078CB2F /* ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareExtension.entitlements; sourceTree = "<group>"; };
|
||||
FAC6F8B52D287F120078CB2F /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
|
||||
FAC7416727DB9F5500C668D8 /* RunnerProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerProfile.entitlements; sourceTree = "<group>"; };
|
||||
FEAFA8722E4D42F4001E47FE /* Thumbhash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Thumbhash.swift; sourceTree = "<group>"; };
|
||||
FED3B1932E253E9B0030FD97 /* Thumbnails.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Thumbnails.g.swift; sourceTree = "<group>"; };
|
||||
FED3B1942E253E9B0030FD97 /* ThumbnailsImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThumbnailsImpl.swift; sourceTree = "<group>"; };
|
||||
FE619FD42E6F5D0600D0B708 /* NativeImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeImageView.swift; sourceTree = "<group>"; };
|
||||
FE619FD52E6F5D0600D0B708 /* NativeImageViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeImageViewFactory.swift; sourceTree = "<group>"; };
|
||||
FE619FD72E6F5D0600D0B708 /* Thumbhash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Thumbhash.swift; sourceTree = "<group>"; };
|
||||
FE619FD82E6F5D0600D0B708 /* Thumbnails.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Thumbnails.g.swift; sourceTree = "<group>"; };
|
||||
FE619FD92E6F5D0600D0B708 /* ThumbnailsImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThumbnailsImpl.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
@@ -129,8 +133,6 @@
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
B2CF7F8C2DDE4EBB00744BF6 /* Sync */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
);
|
||||
path = Sync;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -243,6 +245,7 @@
|
||||
97C146F01CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FE619FDA2E6F5D0600D0B708 /* Images */,
|
||||
B21E34A62E5AF9760031FDB9 /* Background */,
|
||||
B2CF7F8C2DDE4EBB00744BF6 /* Sync */,
|
||||
FA9973382CF6DF4B000EF859 /* Runner.entitlements */,
|
||||
@@ -256,7 +259,6 @@
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||
FED3B1952E253E9B0030FD97 /* Images */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
@@ -282,12 +284,22 @@
|
||||
path = ShareExtension;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FED3B1952E253E9B0030FD97 /* Images */ = {
|
||||
FE619FD62E6F5D0600D0B708 /* Viewer */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FEAFA8722E4D42F4001E47FE /* Thumbhash.swift */,
|
||||
FED3B1932E253E9B0030FD97 /* Thumbnails.g.swift */,
|
||||
FED3B1942E253E9B0030FD97 /* ThumbnailsImpl.swift */,
|
||||
FE619FD42E6F5D0600D0B708 /* NativeImageView.swift */,
|
||||
FE619FD52E6F5D0600D0B708 /* NativeImageViewFactory.swift */,
|
||||
);
|
||||
path = Viewer;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FE619FDA2E6F5D0600D0B708 /* Images */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FE619FD62E6F5D0600D0B708 /* Viewer */,
|
||||
FE619FD72E6F5D0600D0B708 /* Thumbhash.swift */,
|
||||
FE619FD82E6F5D0600D0B708 /* Thumbnails.g.swift */,
|
||||
FE619FD92E6F5D0600D0B708 /* ThumbnailsImpl.swift */,
|
||||
);
|
||||
path = Images;
|
||||
sourceTree = "<group>";
|
||||
@@ -558,12 +570,14 @@
|
||||
65F32F31299BD2F800CE9261 /* BackgroundServicePlugin.swift in Sources */,
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||
B21E34AC2E5B09190031FDB9 /* BackgroundWorker.swift in Sources */,
|
||||
FEAFA8732E4D42F4001E47FE /* Thumbhash.swift in Sources */,
|
||||
FED3B1962E253E9B0030FD97 /* ThumbnailsImpl.swift in Sources */,
|
||||
B21E34AA2E5AFD2B0031FDB9 /* BackgroundWorkerApiImpl.swift in Sources */,
|
||||
FED3B1972E253E9B0030FD97 /* Thumbnails.g.swift in Sources */,
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||
B2BE315F2E5E5229006EEF88 /* BackgroundWorker.g.swift in Sources */,
|
||||
FE619FDB2E6F5D0600D0B708 /* NativeImageViewFactory.swift in Sources */,
|
||||
FE619FDC2E6F5D0600D0B708 /* Thumbhash.swift in Sources */,
|
||||
FE619FDD2E6F5D0600D0B708 /* Thumbnails.g.swift in Sources */,
|
||||
FE619FDE2E6F5D0600D0B708 /* ThumbnailsImpl.swift in Sources */,
|
||||
FE619FDF2E6F5D0600D0B708 /* NativeImageView.swift in Sources */,
|
||||
65F32F33299D349D00CE9261 /* BackgroundSyncWorker.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
||||
@@ -15,7 +15,7 @@ import UIKit
|
||||
) -> Bool {
|
||||
// Required for flutter_local_notification
|
||||
if #available(iOS 10.0, *) {
|
||||
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
|
||||
UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
|
||||
}
|
||||
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
@@ -47,6 +47,9 @@ import UIKit
|
||||
FPPNetworkInfoPlusPlugin.register(with: registry.registrar(forPlugin: "org.cocoapods.network-info-plus")!)
|
||||
}
|
||||
}
|
||||
|
||||
let factory = NativeImageViewFactory(messenger: controller.binaryMessenger)
|
||||
registrar(forPlugin: "NativeImageView")!.register(factory, withId: NativeImageViewFactory.id)
|
||||
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
|
||||
@@ -193,12 +193,12 @@ class ThumbnailApiImpl: ThumbnailApi {
|
||||
}
|
||||
}
|
||||
|
||||
private static func requestAsset(assetId: String) -> PHAsset? {
|
||||
static func requestAsset(assetId: String) -> PHAsset? {
|
||||
var asset: PHAsset?
|
||||
assetQueue.sync { asset = assetCache.object(forKey: assetId as NSString) }
|
||||
if asset != nil { return asset }
|
||||
|
||||
guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [assetId], options: Self.fetchOptions).firstObject
|
||||
guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [assetId], options: fetchOptions).firstObject
|
||||
else { return nil }
|
||||
assetQueue.async { assetCache.setObject(asset, forKey: assetId as NSString) }
|
||||
return asset
|
||||
|
||||
219
mobile/ios/Runner/Images/Viewer/NativeImageView.swift
Normal file
219
mobile/ios/Runner/Images/Viewer/NativeImageView.swift
Normal file
@@ -0,0 +1,219 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
import Photos
|
||||
|
||||
// TODO: the bounds this uses for scaling can change with the hero animation,
|
||||
// so it doesn't display the image correctly until swiping to the next asset and back
|
||||
class NativeImageView: NSObject, FlutterPlatformView {
|
||||
private var _containerView: UIView
|
||||
private var _scrollView: UIScrollView
|
||||
private var _imageView: UIImageView
|
||||
private var _image: UIImage?
|
||||
private var _hasSetupZoom = false
|
||||
|
||||
private static let imageManager = PHImageManager.default()
|
||||
private static let fetchOptions = {
|
||||
let fetchOptions = PHFetchOptions()
|
||||
fetchOptions.fetchLimit = 1
|
||||
fetchOptions.wantsIncrementalChangeDetails = false
|
||||
return fetchOptions
|
||||
}()
|
||||
private static let requestOptions = {
|
||||
let requestOptions = PHImageRequestOptions()
|
||||
requestOptions.isNetworkAccessAllowed = true
|
||||
requestOptions.deliveryMode = .opportunistic
|
||||
requestOptions.resizeMode = .none
|
||||
requestOptions.isSynchronous = false
|
||||
requestOptions.version = .current
|
||||
return requestOptions
|
||||
}()
|
||||
|
||||
init(
|
||||
frame: CGRect,
|
||||
viewIdentifier viewId: Int64,
|
||||
arguments args: Any?,
|
||||
binaryMessenger messenger: FlutterBinaryMessenger
|
||||
) {
|
||||
_containerView = UIView(frame: frame)
|
||||
_scrollView = UIScrollView()
|
||||
_imageView = UIImageView()
|
||||
super.init()
|
||||
|
||||
setupViews()
|
||||
|
||||
guard let arguments = args as? [String: Any],
|
||||
let assetId = arguments["assetId"] as? String else {
|
||||
print("Asset ID not provided")
|
||||
return
|
||||
}
|
||||
|
||||
guard let asset = ThumbnailApiImpl.requestAsset(assetId: assetId) else {
|
||||
print("Asset not found for identifier: \(assetId)")
|
||||
return
|
||||
}
|
||||
|
||||
loadImage(from: asset)
|
||||
}
|
||||
|
||||
func view() -> UIView {
|
||||
return _containerView
|
||||
}
|
||||
|
||||
private func setupViews() {
|
||||
// Configure image view
|
||||
_imageView.contentMode = .scaleAspectFit
|
||||
_imageView.preferredImageDynamicRange = .high
|
||||
if #available(iOS 17.0, *) {
|
||||
_imageView.layer.wantsExtendedDynamicRangeContent = true
|
||||
_imageView.layer.contentsFormat = .RGBA16Float
|
||||
}
|
||||
|
||||
// Configure scroll view
|
||||
_scrollView.delegate = self
|
||||
_scrollView.showsVerticalScrollIndicator = false
|
||||
_scrollView.showsHorizontalScrollIndicator = false
|
||||
_scrollView.bouncesZoom = true
|
||||
_scrollView.decelerationRate = .fast
|
||||
_scrollView.contentInsetAdjustmentBehavior = .never
|
||||
_scrollView.backgroundColor = .clear
|
||||
|
||||
// Add double tap gesture
|
||||
let doubleTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap(_:)))
|
||||
doubleTapGesture.numberOfTapsRequired = 2
|
||||
_scrollView.addGestureRecognizer(doubleTapGesture)
|
||||
|
||||
// Setup view hierarchy
|
||||
_scrollView.addSubview(_imageView)
|
||||
_containerView.addSubview(_scrollView)
|
||||
|
||||
// Setup constraints
|
||||
_scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
_scrollView.topAnchor.constraint(equalTo: _containerView.topAnchor),
|
||||
_scrollView.leadingAnchor.constraint(equalTo: _containerView.leadingAnchor),
|
||||
_scrollView.trailingAnchor.constraint(equalTo: _containerView.trailingAnchor),
|
||||
_scrollView.bottomAnchor.constraint(equalTo: _containerView.bottomAnchor)
|
||||
])
|
||||
|
||||
// Observe bounds changes
|
||||
_containerView.addObserver(self, forKeyPath: "bounds", options: [.new], context: nil)
|
||||
}
|
||||
|
||||
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||
if keyPath == "bounds", let image = _image {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.setupZoomScale(for: image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
_containerView.removeObserver(self, forKeyPath: "bounds")
|
||||
}
|
||||
|
||||
private func loadImage(from asset: PHAsset) {
|
||||
Self.imageManager.requestImageDataAndOrientation(
|
||||
for: asset,
|
||||
options: Self.requestOptions
|
||||
) { [weak self] data, uti, orientation, info in
|
||||
guard let data = data else { return }
|
||||
|
||||
if #available(iOS 17.0, *) {
|
||||
var config = UIImageReader.Configuration()
|
||||
config.prefersHighDynamicRange = true
|
||||
let imageReader = UIImageReader(configuration: config)
|
||||
guard let image = imageReader.image(data: data) else { return }
|
||||
DispatchQueue.main.async {
|
||||
self?.setImage(image)
|
||||
}
|
||||
} else {
|
||||
guard let image = UIImage(data: data) else { return }
|
||||
DispatchQueue.main.async {
|
||||
self?.setImage(image)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func setImage(_ image: UIImage) {
|
||||
_image = image
|
||||
_imageView.image = image
|
||||
|
||||
// Wait for next run loop to ensure layout is complete
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.setupZoomScale(for: image)
|
||||
}
|
||||
}
|
||||
|
||||
private func setupZoomScale(for image: UIImage) {
|
||||
guard _scrollView.bounds.size.width > 0, _scrollView.bounds.size.height > 0 else {
|
||||
// View not laid out yet
|
||||
return
|
||||
}
|
||||
|
||||
// Set image view size to match image
|
||||
_imageView.frame = CGRect(origin: .zero, size: image.size)
|
||||
_scrollView.contentSize = image.size
|
||||
|
||||
// Calculate zoom scales
|
||||
let scrollViewSize = _scrollView.bounds.size
|
||||
let widthScale = scrollViewSize.width / image.size.width
|
||||
let heightScale = scrollViewSize.height / image.size.height
|
||||
let minScale = min(widthScale, heightScale)
|
||||
|
||||
_scrollView.minimumZoomScale = minScale
|
||||
_scrollView.maximumZoomScale = max(2.0, minScale * 5.0)
|
||||
_scrollView.zoomScale = minScale
|
||||
|
||||
centerImageView()
|
||||
_hasSetupZoom = true
|
||||
}
|
||||
|
||||
private func centerImageView() {
|
||||
let scrollViewSize = _scrollView.bounds.size
|
||||
let imageViewSize = _imageView.frame.size
|
||||
|
||||
let horizontalInset = max(0, (scrollViewSize.width - imageViewSize.width) / 2)
|
||||
let verticalInset = max(0, (scrollViewSize.height - imageViewSize.height) / 2)
|
||||
|
||||
_scrollView.contentInset = UIEdgeInsets(
|
||||
top: verticalInset,
|
||||
left: horizontalInset,
|
||||
bottom: verticalInset,
|
||||
right: horizontalInset
|
||||
)
|
||||
}
|
||||
|
||||
@objc private func handleDoubleTap(_ gesture: UITapGestureRecognizer) {
|
||||
guard _hasSetupZoom else { return }
|
||||
|
||||
if _scrollView.zoomScale > _scrollView.minimumZoomScale {
|
||||
_scrollView.setZoomScale(_scrollView.minimumZoomScale, animated: true)
|
||||
} else {
|
||||
let tapLocation = gesture.location(in: _imageView)
|
||||
let zoomScale = min(_scrollView.maximumZoomScale, _scrollView.minimumZoomScale * 3.0)
|
||||
let zoomRect = zoomRectForScale(zoomScale, center: tapLocation)
|
||||
_scrollView.zoom(to: zoomRect, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
private func zoomRectForScale(_ scale: CGFloat, center: CGPoint) -> CGRect {
|
||||
var zoomRect = CGRect.zero
|
||||
zoomRect.size.width = _scrollView.frame.size.width / scale
|
||||
zoomRect.size.height = _scrollView.frame.size.height / scale
|
||||
zoomRect.origin.x = center.x - (zoomRect.size.width / 2.0)
|
||||
zoomRect.origin.y = center.y - (zoomRect.size.height / 2.0)
|
||||
return zoomRect
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UIScrollViewDelegate
|
||||
extension NativeImageView: UIScrollViewDelegate {
|
||||
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
|
||||
return _imageView
|
||||
}
|
||||
|
||||
func scrollViewDidZoom(_ scrollView: UIScrollView) {
|
||||
centerImageView()
|
||||
}
|
||||
}
|
||||
29
mobile/ios/Runner/Images/Viewer/NativeImageViewFactory.swift
Normal file
29
mobile/ios/Runner/Images/Viewer/NativeImageViewFactory.swift
Normal file
@@ -0,0 +1,29 @@
|
||||
import Foundation
|
||||
import Flutter
|
||||
|
||||
public class NativeImageViewFactory: NSObject, FlutterPlatformViewFactory {
|
||||
public static let id = "native_image_view"
|
||||
|
||||
private let messenger: FlutterBinaryMessenger
|
||||
|
||||
init(messenger: FlutterBinaryMessenger) {
|
||||
self.messenger = messenger
|
||||
}
|
||||
|
||||
public func create(
|
||||
withFrame frame: CGRect,
|
||||
viewIdentifier viewId: Int64,
|
||||
arguments args: Any?
|
||||
) -> FlutterPlatformView {
|
||||
NativeImageView(
|
||||
frame: frame,
|
||||
viewIdentifier: viewId,
|
||||
arguments: args,
|
||||
binaryMessenger: messenger
|
||||
)
|
||||
}
|
||||
|
||||
public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
|
||||
return FlutterStandardMessageCodec.sharedInstance()
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_sheet.wid
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/top_app_bar.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/video_viewer.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/native_image.widget.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.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';
|
||||
@@ -533,24 +533,21 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
}
|
||||
|
||||
PhotoViewGalleryPageOptions _imageBuilder(BuildContext ctx, BaseAsset asset) {
|
||||
final size = ctx.sizeData;
|
||||
return PhotoViewGalleryPageOptions(
|
||||
key: ValueKey(asset.heroTag),
|
||||
imageProvider: getFullImageProvider(asset, size: size),
|
||||
heroAttributes: PhotoViewHeroAttributes(tag: '${asset.heroTag}_$heroOffset'),
|
||||
filterQuality: FilterQuality.high,
|
||||
tightMode: true,
|
||||
disableScaleGestures: showingBottomSheet,
|
||||
return PhotoViewGalleryPageOptions.customChild(
|
||||
disableScaleGestures: true,
|
||||
onDragStart: _onDragStart,
|
||||
onDragUpdate: _onDragUpdate,
|
||||
onDragEnd: _onDragEnd,
|
||||
onTapDown: _onTapDown,
|
||||
onLongPressStart: asset.isMotionPhoto ? _onLongPress : null,
|
||||
errorBuilder: (_, __, ___) => Container(
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
color: backgroundColor,
|
||||
child: Thumbnail.fromAsset(asset: asset, fit: BoxFit.contain),
|
||||
heroAttributes: PhotoViewHeroAttributes(tag: '${asset.heroTag}_$heroOffset'),
|
||||
filterQuality: FilterQuality.high,
|
||||
basePosition: Alignment.center,
|
||||
child: NativeImageView(
|
||||
key: _getVideoPlayerKey(asset.heroTag),
|
||||
assetId: (asset as LocalAsset).id,
|
||||
width: ctx.width,
|
||||
height: ctx.height,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class NativeImageView extends StatelessWidget {
|
||||
final String assetId;
|
||||
final double width;
|
||||
final double height;
|
||||
|
||||
const NativeImageView({super.key, required this.assetId, required this.width, required this.height});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (defaultTargetPlatform != TargetPlatform.iOS) {
|
||||
return Container(
|
||||
width: width,
|
||||
height: height,
|
||||
color: Colors.grey,
|
||||
child: const Center(child: Text('PHAsset view only available on iOS')),
|
||||
);
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
width: width,
|
||||
height: height,
|
||||
child: UiKitView(
|
||||
viewType: 'native_image_view',
|
||||
layoutDirection: TextDirection.ltr,
|
||||
creationParams: {'assetId': assetId},
|
||||
creationParamsCodec: const StandardMessageCodec(),
|
||||
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
|
||||
Factory<VerticalDragGestureRecognizer>(() => VerticalDragGestureRecognizer()),
|
||||
Factory<HorizontalDragGestureRecognizer>(() => HorizontalDragGestureRecognizer()),
|
||||
Factory<ScaleGestureRecognizer>(() => ScaleGestureRecognizer()),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user