mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-07-28 16:29:49 +00:00
mobile: zoomable fullscreen image view (#651)
* ios: zoomable fullscreen image view * android: zoomable images
This commit is contained in:
+30
-4
@@ -1,12 +1,14 @@
|
||||
import android.graphics.Bitmap
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.gestures.detectTransformGestures
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.*
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import chat.simplex.app.R
|
||||
@@ -20,11 +22,35 @@ fun ImageFullScreenView(imageBitmap: Bitmap, close: () -> Unit) {
|
||||
.background(Color.Black)
|
||||
.clickable(onClick = close)
|
||||
) {
|
||||
var scale by remember { mutableStateOf(1f) }
|
||||
var translationX by remember { mutableStateOf(0f) }
|
||||
var translationY by remember { mutableStateOf(0f) }
|
||||
Image(
|
||||
imageBitmap.asImageBitmap(),
|
||||
contentDescription = stringResource(R.string.image_descr),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentScale = ContentScale.Fit,
|
||||
modifier = Modifier
|
||||
.graphicsLayer(
|
||||
scaleX = scale,
|
||||
scaleY = scale,
|
||||
translationX = translationX,
|
||||
translationY = translationY,
|
||||
)
|
||||
.pointerInput(Unit) {
|
||||
detectTransformGestures(
|
||||
onGesture = { _, pan, gestureZoom, _ ->
|
||||
scale = (scale * gestureZoom).coerceIn(1f, 20f)
|
||||
if (scale > 1) {
|
||||
translationX += pan.x * scale
|
||||
translationY += pan.y * scale
|
||||
} else {
|
||||
translationX = 0f
|
||||
translationY = 0f
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,9 +23,14 @@ struct CIImageView: View {
|
||||
.fullScreenCover(isPresented: $showFullScreenImage) {
|
||||
ZStack {
|
||||
Color.black.edgesIgnoringSafeArea(.all)
|
||||
Image(uiImage: uiImage)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
ZoomableScrollView {
|
||||
ZStack {
|
||||
Color.black.edgesIgnoringSafeArea(.all)
|
||||
Image(uiImage: uiImage)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
}
|
||||
}
|
||||
}
|
||||
.onTapGesture { showFullScreenImage = false }
|
||||
.gesture(
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// ZoomableScrollView.swift
|
||||
// SimpleX
|
||||
//
|
||||
// Created by Evgeny on 13/05/2022.
|
||||
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ZoomableScrollView<Content: View>: UIViewRepresentable {
|
||||
private var content: Content
|
||||
|
||||
init(@ViewBuilder content: () -> Content) {
|
||||
self.content = content()
|
||||
}
|
||||
|
||||
func makeUIView(context: Context) -> UIScrollView {
|
||||
// set up the UIScrollView
|
||||
let scrollView = UIScrollView()
|
||||
scrollView.delegate = context.coordinator // for viewForZooming(in:)
|
||||
scrollView.maximumZoomScale = 20
|
||||
scrollView.minimumZoomScale = 1
|
||||
scrollView.bouncesZoom = true
|
||||
scrollView.backgroundColor = .black
|
||||
scrollView.insetsLayoutMarginsFromSafeArea = false
|
||||
|
||||
// create a UIHostingController to hold our SwiftUI content
|
||||
let hostedView = context.coordinator.hostingController.view!
|
||||
hostedView.translatesAutoresizingMaskIntoConstraints = true
|
||||
hostedView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
hostedView.frame = scrollView.bounds
|
||||
scrollView.addSubview(hostedView)
|
||||
|
||||
return scrollView
|
||||
}
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(hostingController: UIHostingController(rootView: self.content))
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: UIScrollView, context: Context) {
|
||||
// update the hosting controller's SwiftUI content
|
||||
context.coordinator.hostingController.rootView = self.content
|
||||
assert(context.coordinator.hostingController.view.superview == uiView)
|
||||
}
|
||||
|
||||
// MARK: - Coordinator
|
||||
class Coordinator: NSObject, UIScrollViewDelegate {
|
||||
var hostingController: UIHostingController<Content>
|
||||
|
||||
init(hostingController: UIHostingController<Content>) {
|
||||
self.hostingController = hostingController
|
||||
}
|
||||
|
||||
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
|
||||
hostingController.view
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,11 +90,18 @@
|
||||
5CE4407927ADB701007B033A /* EmojiItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE4407827ADB701007B033A /* EmojiItemView.swift */; };
|
||||
5CEACCE327DE9246000BD591 /* ComposeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEACCE227DE9246000BD591 /* ComposeView.swift */; };
|
||||
5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEACCEC27DEA495000BD591 /* MsgContentView.swift */; };
|
||||
640DA1A5282D50D700AB8E8D /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 640DA1A0282D50D700AB8E8D /* libffi.a */; };
|
||||
640DA1A6282D50D700AB8E8D /* libHSsimplex-chat-2.0.0-JAEoKkoXJYeE6TLyZconIN-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 640DA1A1282D50D700AB8E8D /* libHSsimplex-chat-2.0.0-JAEoKkoXJYeE6TLyZconIN-ghc8.10.7.a */; };
|
||||
640DA1A7282D50D700AB8E8D /* libHSsimplex-chat-2.0.0-JAEoKkoXJYeE6TLyZconIN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 640DA1A2282D50D700AB8E8D /* libHSsimplex-chat-2.0.0-JAEoKkoXJYeE6TLyZconIN.a */; };
|
||||
640DA1A8282D50D700AB8E8D /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 640DA1A3282D50D700AB8E8D /* libgmpxx.a */; };
|
||||
640DA1A9282D50D700AB8E8D /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 640DA1A4282D50D700AB8E8D /* libgmp.a */; };
|
||||
5CFE0916282EE05E0002594B /* libHSsimplex-chat-2.0.1-4e10XUnKa54A7I6rlOZBZ.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CFE0911282EE05E0002594B /* libHSsimplex-chat-2.0.1-4e10XUnKa54A7I6rlOZBZ.a */; };
|
||||
5CFE0917282EE05E0002594B /* libHSsimplex-chat-2.0.1-4e10XUnKa54A7I6rlOZBZ.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CFE0911282EE05E0002594B /* libHSsimplex-chat-2.0.1-4e10XUnKa54A7I6rlOZBZ.a */; };
|
||||
5CFE0918282EE05E0002594B /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CFE0912282EE05E0002594B /* libffi.a */; };
|
||||
5CFE0919282EE05E0002594B /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CFE0912282EE05E0002594B /* libffi.a */; };
|
||||
5CFE091A282EE05E0002594B /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CFE0913282EE05E0002594B /* libgmpxx.a */; };
|
||||
5CFE091B282EE05E0002594B /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CFE0913282EE05E0002594B /* libgmpxx.a */; };
|
||||
5CFE091C282EE05E0002594B /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CFE0914282EE05E0002594B /* libgmp.a */; };
|
||||
5CFE091D282EE05E0002594B /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CFE0914282EE05E0002594B /* libgmp.a */; };
|
||||
5CFE091E282EE05E0002594B /* libHSsimplex-chat-2.0.1-4e10XUnKa54A7I6rlOZBZ-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CFE0915282EE05E0002594B /* libHSsimplex-chat-2.0.1-4e10XUnKa54A7I6rlOZBZ-ghc8.10.7.a */; };
|
||||
5CFE091F282EE05E0002594B /* libHSsimplex-chat-2.0.1-4e10XUnKa54A7I6rlOZBZ-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CFE0915282EE05E0002594B /* libHSsimplex-chat-2.0.1-4e10XUnKa54A7I6rlOZBZ-ghc8.10.7.a */; };
|
||||
5CFE0921282EEAF60002594B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */; };
|
||||
5CFE0922282EEAF60002594B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */; };
|
||||
640F50E327CF991C001E05C2 /* SMPServers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640F50E227CF991C001E05C2 /* SMPServers.swift */; };
|
||||
6454036F2822A9750090DDFF /* ComposeFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6454036E2822A9750090DDFF /* ComposeFileView.swift */; };
|
||||
648010AB281ADD15009009B9 /* CIFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648010AA281ADD15009009B9 /* CIFileView.swift */; };
|
||||
@@ -199,11 +206,12 @@
|
||||
5CE4407827ADB701007B033A /* EmojiItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiItemView.swift; sourceTree = "<group>"; };
|
||||
5CEACCE227DE9246000BD591 /* ComposeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeView.swift; sourceTree = "<group>"; };
|
||||
5CEACCEC27DEA495000BD591 /* MsgContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MsgContentView.swift; sourceTree = "<group>"; };
|
||||
640DA1A0282D50D700AB8E8D /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
640DA1A1282D50D700AB8E8D /* libHSsimplex-chat-2.0.0-JAEoKkoXJYeE6TLyZconIN-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-2.0.0-JAEoKkoXJYeE6TLyZconIN-ghc8.10.7.a"; sourceTree = "<group>"; };
|
||||
640DA1A2282D50D700AB8E8D /* libHSsimplex-chat-2.0.0-JAEoKkoXJYeE6TLyZconIN.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-2.0.0-JAEoKkoXJYeE6TLyZconIN.a"; sourceTree = "<group>"; };
|
||||
640DA1A3282D50D700AB8E8D /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
640DA1A4282D50D700AB8E8D /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5CFE0911282EE05E0002594B /* libHSsimplex-chat-2.0.1-4e10XUnKa54A7I6rlOZBZ.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-2.0.1-4e10XUnKa54A7I6rlOZBZ.a"; sourceTree = "<group>"; };
|
||||
5CFE0912282EE05E0002594B /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5CFE0913282EE05E0002594B /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5CFE0914282EE05E0002594B /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5CFE0915282EE05E0002594B /* libHSsimplex-chat-2.0.1-4e10XUnKa54A7I6rlOZBZ-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-2.0.1-4e10XUnKa54A7I6rlOZBZ-ghc8.10.7.a"; sourceTree = "<group>"; };
|
||||
5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ZoomableScrollView.swift; path = Shared/Views/ZoomableScrollView.swift; sourceTree = SOURCE_ROOT; };
|
||||
640F50E227CF991C001E05C2 /* SMPServers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMPServers.swift; sourceTree = "<group>"; };
|
||||
6454036E2822A9750090DDFF /* ComposeFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeFileView.swift; sourceTree = "<group>"; };
|
||||
648010AA281ADD15009009B9 /* CIFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIFileView.swift; sourceTree = "<group>"; };
|
||||
@@ -220,14 +228,14 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
640DA1A6282D50D700AB8E8D /* libHSsimplex-chat-2.0.0-JAEoKkoXJYeE6TLyZconIN-ghc8.10.7.a in Frameworks */,
|
||||
5CFE091A282EE05E0002594B /* libgmpxx.a in Frameworks */,
|
||||
5CFE091E282EE05E0002594B /* libHSsimplex-chat-2.0.1-4e10XUnKa54A7I6rlOZBZ-ghc8.10.7.a in Frameworks */,
|
||||
5CFE091C282EE05E0002594B /* libgmp.a in Frameworks */,
|
||||
5CFE0916282EE05E0002594B /* libHSsimplex-chat-2.0.1-4e10XUnKa54A7I6rlOZBZ.a in Frameworks */,
|
||||
5C8F01CD27A6F0D8007D2C8D /* CodeScanner in Frameworks */,
|
||||
640DA1A9282D50D700AB8E8D /* libgmp.a in Frameworks */,
|
||||
5C764E83279C748B000C6508 /* libz.tbd in Frameworks */,
|
||||
5CFE0918282EE05E0002594B /* libffi.a in Frameworks */,
|
||||
5C764E82279C748B000C6508 /* libiconv.tbd in Frameworks */,
|
||||
640DA1A5282D50D700AB8E8D /* libffi.a in Frameworks */,
|
||||
640DA1A7282D50D700AB8E8D /* libHSsimplex-chat-2.0.0-JAEoKkoXJYeE6TLyZconIN.a in Frameworks */,
|
||||
640DA1A8282D50D700AB8E8D /* libgmpxx.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -242,8 +250,13 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5CFE091F282EE05E0002594B /* libHSsimplex-chat-2.0.1-4e10XUnKa54A7I6rlOZBZ-ghc8.10.7.a in Frameworks */,
|
||||
5CDCAD5F28187D6900503DA2 /* libiconv.tbd in Frameworks */,
|
||||
5CFE091B282EE05E0002594B /* libgmpxx.a in Frameworks */,
|
||||
5CDCAD6128187D8000503DA2 /* libz.tbd in Frameworks */,
|
||||
5CFE0917282EE05E0002594B /* libHSsimplex-chat-2.0.1-4e10XUnKa54A7I6rlOZBZ.a in Frameworks */,
|
||||
5CFE091D282EE05E0002594B /* libgmp.a in Frameworks */,
|
||||
5CFE0919282EE05E0002594B /* libffi.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -292,11 +305,11 @@
|
||||
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
640DA1A0282D50D700AB8E8D /* libffi.a */,
|
||||
640DA1A4282D50D700AB8E8D /* libgmp.a */,
|
||||
640DA1A3282D50D700AB8E8D /* libgmpxx.a */,
|
||||
640DA1A1282D50D700AB8E8D /* libHSsimplex-chat-2.0.0-JAEoKkoXJYeE6TLyZconIN-ghc8.10.7.a */,
|
||||
640DA1A2282D50D700AB8E8D /* libHSsimplex-chat-2.0.0-JAEoKkoXJYeE6TLyZconIN.a */,
|
||||
5CFE0912282EE05E0002594B /* libffi.a */,
|
||||
5CFE0914282EE05E0002594B /* libgmp.a */,
|
||||
5CFE0913282EE05E0002594B /* libgmpxx.a */,
|
||||
5CFE0915282EE05E0002594B /* libHSsimplex-chat-2.0.1-4e10XUnKa54A7I6rlOZBZ-ghc8.10.7.a */,
|
||||
5CFE0911282EE05E0002594B /* libHSsimplex-chat-2.0.1-4e10XUnKa54A7I6rlOZBZ.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
@@ -339,6 +352,7 @@
|
||||
649BCDA12805D6EF00C3A862 /* CIImageView.swift */,
|
||||
648010AA281ADD15009009B9 /* CIFileView.swift */,
|
||||
6454036E2822A9750090DDFF /* ComposeFileView.swift */,
|
||||
5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
@@ -675,6 +689,7 @@
|
||||
649BCDA0280460FD00C3A862 /* ComposeImageView.swift in Sources */,
|
||||
5CA059ED279559F40002BEB4 /* ContentView.swift in Sources */,
|
||||
5CCD403427A5F6DF00368C90 /* AddContactView.swift in Sources */,
|
||||
5CFE0921282EEAF60002594B /* ZoomableScrollView.swift in Sources */,
|
||||
5C3A88CE27DF50170060F1C2 /* DetermineWidth.swift in Sources */,
|
||||
5CB0BA962827143500B3292C /* MakeConnection.swift in Sources */,
|
||||
5C7505A527B679EE00BE3227 /* NavLinkPlain.swift in Sources */,
|
||||
@@ -729,6 +744,7 @@
|
||||
5CDCAD82281A7E2700503DA2 /* Notifications.swift in Sources */,
|
||||
5CDCAD7C2818924D00503DA2 /* FileUtils.swift in Sources */,
|
||||
5CDCAD682818876500503DA2 /* JSON.swift in Sources */,
|
||||
5CFE0922282EEAF60002594B /* ZoomableScrollView.swift in Sources */,
|
||||
5CDCAD7828188FD300503DA2 /* ChatTypes.swift in Sources */,
|
||||
5CDCAD7E2818941F00503DA2 /* API.swift in Sources */,
|
||||
5CDCAD5428186F9700503DA2 /* GroupDefaults.swift in Sources */,
|
||||
|
||||
Reference in New Issue
Block a user