Browse Source

Better media detail view (#2556)

* WIP

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* WIP

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* WIP

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* oops

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* WIP

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* WIP

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* WIP

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* WIP

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* WIP

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* WIP

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* WIP

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* Remove useless things

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* WIP

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* WIP

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* WIP

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* WIP

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* Add button for opening detail

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* Load location from metadata

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* Fix crash + other things

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* WIP

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* WIP

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* WIP

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* Refuce font size

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* WIP

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* WIP

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* WIP

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* Reduce font size

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* WIP

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* WIP

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* WIP

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* WIP

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* WIP

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* Update iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift

Co-authored-by: Aditya Tyagi <77538183+adityagi02@users.noreply.github.com>
Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* Stop live photo from playing when detail view is open

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* Fix ipad layout

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* Handle live photo

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* FInishing touches

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* Finishing touches 2

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* Small spacing changes

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* Refactor

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* Fix for tips view

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* Final

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

* Oopsie

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>

---------

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>
Co-authored-by: Aditya Tyagi <77538183+adityagi02@users.noreply.github.com>
Milen Pivchev 1 year ago
parent
commit
9d9b4cac27

+ 5 - 0
.github/workflows/xcode.yml

@@ -44,6 +44,11 @@ jobs:
       run: wget "https://raw.githubusercontent.com/firebase/quickstart-ios/master/mock-GoogleService-Info.plist" -O GoogleService-Info.plist
     - name: Install docker
       run: |
+        # Workaround for https://github.com/actions/runner-images/issues/8104
+        brew remove --ignore-dependencies qemu
+        curl -o ./qemu.rb https://raw.githubusercontent.com/Homebrew/homebrew-core/dc0669eca9479e9eeb495397ba3a7480aaa45c2e/Formula/qemu.rb
+        brew install ./qemu.rb
+
         brew install docker
         colima start
     - name: Create docker test server and export enviroment variables

+ 1 - 1
Brand/Database.swift

@@ -26,4 +26,4 @@ import Foundation
 // Database Realm
 //
 let databaseName                    = "nextcloud.realm"
-let databaseSchemaVersion: UInt64   = 306
+let databaseSchemaVersion: UInt64   = 307

+ 16 - 4
Nextcloud.xcodeproj/project.pbxproj

@@ -68,7 +68,6 @@
 		AFCE353527E4ED5900FEA6C2 /* DateFormatter+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353427E4ED5900FEA6C2 /* DateFormatter+Extension.swift */; };
 		AFCE353727E4ED7B00FEA6C2 /* NCShareCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353627E4ED7B00FEA6C2 /* NCShareCells.swift */; };
 		AFCE353927E5DE0500FEA6C2 /* NCShare+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353827E5DE0400FEA6C2 /* NCShare+Helper.swift */; };
-		AFD33240276A02C100F5AE02 /* UIApplication+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFD3323F276A02C000F5AE02 /* UIApplication+Extension.swift */; };
 		C0046CDD2A17B98400D87C9D /* LoginUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0046CDC2A17B98400D87C9D /* LoginUITests.swift */; };
 		C03BA14A2A17BC57002C8BA3 /* XLForm in Frameworks */ = {isa = PBXBuildFile; productRef = C03BA1492A17BC57002C8BA3 /* XLForm */; };
 		C03BA14C2A17BC60002C8BA3 /* UICKeyChainStore in Frameworks */ = {isa = PBXBuildFile; productRef = C03BA14B2A17BC60002C8BA3 /* UICKeyChainStore */; };
@@ -117,6 +116,13 @@
 		F343A4BF2A1E734600DDA874 /* Optional+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F343A4BA2A1E734600DDA874 /* Optional+Extension.swift */; };
 		F343A4C02A1E734600DDA874 /* Optional+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F343A4BA2A1E734600DDA874 /* Optional+Extension.swift */; };
 		F343A4C12A1E734600DDA874 /* Optional+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F343A4BA2A1E734600DDA874 /* Optional+Extension.swift */; };
+		F359D8672A7D03420023F405 /* NCUtility+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F359D8662A7D03420023F405 /* NCUtility+Exif.swift */; };
+		F359D8682A7D03420023F405 /* NCUtility+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F359D8662A7D03420023F405 /* NCUtility+Exif.swift */; };
+		F359D8692A7D03420023F405 /* NCUtility+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F359D8662A7D03420023F405 /* NCUtility+Exif.swift */; };
+		F359D86A2A7D03420023F405 /* NCUtility+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F359D8662A7D03420023F405 /* NCUtility+Exif.swift */; };
+		F359D86B2A7D03420023F405 /* NCUtility+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F359D8662A7D03420023F405 /* NCUtility+Exif.swift */; };
+		F359D86C2A7D03420023F405 /* NCUtility+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F359D8662A7D03420023F405 /* NCUtility+Exif.swift */; };
+		F359D86D2A7D03420023F405 /* NCUtility+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F359D8662A7D03420023F405 /* NCUtility+Exif.swift */; };
 		F39298972A3B12CB00509762 /* BaseNCMoreCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F39298962A3B12CB00509762 /* BaseNCMoreCell.swift */; };
 		F3953BD72A6E87E000EE03F9 /* BaseIntegrationXCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3953BD62A6E87E000EE03F9 /* BaseIntegrationXCTestCase.swift */; };
 		F3A7AFC62A41AA82001FC89C /* BaseUIXCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A7AFC52A41AA82001FC89C /* BaseUIXCTestCase.swift */; };
@@ -818,7 +824,6 @@
 		AFCE353427E4ED5900FEA6C2 /* DateFormatter+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+Extension.swift"; sourceTree = "<group>"; };
 		AFCE353627E4ED7B00FEA6C2 /* NCShareCells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareCells.swift; sourceTree = "<group>"; };
 		AFCE353827E5DE0400FEA6C2 /* NCShare+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCShare+Helper.swift"; sourceTree = "<group>"; };
-		AFD3323F276A02C000F5AE02 /* UIApplication+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIApplication+Extension.swift"; sourceTree = "<group>"; };
 		C0046CDA2A17B98400D87C9D /* NextcloudUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NextcloudUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
 		C0046CDC2A17B98400D87C9D /* LoginUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginUITests.swift; sourceTree = "<group>"; };
 		C04E2F202A17BB4D001BAD85 /* NextcloudIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NextcloudIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -834,6 +839,7 @@
 		F33AAF992A60394C006ECCBD /* NCMoreUserCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NCMoreUserCell.xib; sourceTree = "<group>"; };
 		F343A4B22A1E01FF00DDA874 /* PHAsset+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PHAsset+Extension.swift"; sourceTree = "<group>"; };
 		F343A4BA2A1E734600DDA874 /* Optional+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Optional+Extension.swift"; sourceTree = "<group>"; };
+		F359D8662A7D03420023F405 /* NCUtility+Exif.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCUtility+Exif.swift"; sourceTree = "<group>"; };
 		F39298962A3B12CB00509762 /* BaseNCMoreCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseNCMoreCell.swift; sourceTree = "<group>"; };
 		F3953BD62A6E87E000EE03F9 /* BaseIntegrationXCTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseIntegrationXCTestCase.swift; sourceTree = "<group>"; };
 		F3A7AFC52A41AA82001FC89C /* BaseUIXCTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseUIXCTestCase.swift; sourceTree = "<group>"; };
@@ -2103,7 +2109,6 @@
 				F343A4B22A1E01FF00DDA874 /* PHAsset+Extension.swift */,
 				F7A0D1342591FBC5008F8A13 /* String+Extension.swift */,
 				AF1A9B6327D0CA1E00F17A9E /* UIAlertController+Extension.swift */,
-				AFD3323F276A02C000F5AE02 /* UIApplication+Extension.swift */,
 				AF7E504D27A2D8FF00B5E4AF /* UIBarButton+Extension.swift */,
 				F70CEF5523E9C7E50007035B /* UIColor+Extension.swift */,
 				F79B645F26CA661600838ACA /* UIControl+Extension.swift */,
@@ -2229,6 +2234,7 @@
 				F707C26421A2DC5200F6181E /* NCStoreReview.swift */,
 				AF817EF0274BC781009ED85B /* NCUserBaseUrl.swift */,
 				F70BFC7320E0FA7C00C67599 /* NCUtility.swift */,
+				F359D8662A7D03420023F405 /* NCUtility+Exif.swift */,
 				AF93474B27E34120002537EE /* NCUtility+Image.swift */,
 				F74AF3A3247FB6AE00AC767B /* NCUtilityFileSystem.swift */,
 				F702F2FC25EE5D2C008F8E80 /* NYMnemonic */,
@@ -3324,6 +3330,7 @@
 				AF4BF617275629E20081CEEF /* NCManageDatabase+Account.swift in Sources */,
 				F7BF9D872934CA21009EE9A6 /* NCManageDatabase+LayoutForView.swift in Sources */,
 				F749B64F297B0CBB00087535 /* NCManageDatabase+Share.swift in Sources */,
+				F359D86D2A7D03420023F405 /* NCUtility+Exif.swift in Sources */,
 				D575039F27146F93008DC9DC /* String+Extension.swift in Sources */,
 				F769CA1A2966EA3C00039397 /* ComponentView.swift in Sources */,
 				F757CC8829E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift in Sources */,
@@ -3401,6 +3408,7 @@
 				F7490E8C29882D02009DCE94 /* CCUtility.m in Sources */,
 				F7490E7729882C10009DCE94 /* UIColor+Extension.swift in Sources */,
 				F70716E62987F81500E72C1D /* DocumentActionViewController.swift in Sources */,
+				F359D86C2A7D03420023F405 /* NCUtility+Exif.swift in Sources */,
 				F7490E8429882C89009DCE94 /* NCManageDatabase+Share.swift in Sources */,
 				F7490E6F29882B67009DCE94 /* UIImage+Extension.swift in Sources */,
 				F7490E7E29882C6E009DCE94 /* NCManageDatabase+Account.swift in Sources */,
@@ -3438,6 +3446,7 @@
 				F70460532499095400BB98A7 /* NotificationCenter+MainThread.swift in Sources */,
 				F70BFC7520E0FA7D00C67599 /* NCUtility.swift in Sources */,
 				AF22B20C277C6F4D00DAB0CC /* NCShareCell.swift in Sources */,
+				F359D86A2A7D03420023F405 /* NCUtility+Exif.swift in Sources */,
 				F7E98C1727E0D0FC001F9F19 /* NCManageDatabase+Video.swift in Sources */,
 				F79B646126CA661600838ACA /* UIControl+Extension.swift in Sources */,
 				F77C973A2953143A00FDDD09 /* NCCameraRoll.swift in Sources */,
@@ -3504,6 +3513,7 @@
 				F783030328B4C4DD00B84583 /* ThreadSafeDictionary.swift in Sources */,
 				F77ED59128C9CE9D00E24ED0 /* ToolbarData.swift in Sources */,
 				F78302F728B4C3C900B84583 /* NCManageDatabase.swift in Sources */,
+				F359D8682A7D03420023F405 /* NCUtility+Exif.swift in Sources */,
 				F7346E1628B0EF5C006CE2D2 /* Widget.swift in Sources */,
 				F78302F828B4C3E100B84583 /* NCManageDatabase+Activity.swift in Sources */,
 				F783030228B4C4B800B84583 /* NCUtility.swift in Sources */,
@@ -3572,6 +3582,7 @@
 				F798F0E725880609000DAFFD /* UIColor+Extension.swift in Sources */,
 				F74B6D992A7E239A00F03C5F /* NCManageDatabase+Chunk.swift in Sources */,
 				F7D68FCF28CB9051009139F3 /* NCManageDatabase+DashboardWidget.swift in Sources */,
+				F359D86B2A7D03420023F405 /* NCUtility+Exif.swift in Sources */,
 				F7864AD02A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */,
 				AF4BF61B27562A4B0081CEEF /* NCManageDatabase+Metadata.swift in Sources */,
 				F70460542499095400BB98A7 /* NotificationCenter+MainThread.swift in Sources */,
@@ -3719,6 +3730,7 @@
 				F7CA212D25F1333300826ABB /* NCAccountRequest.swift in Sources */,
 				F765F73125237E3F00391DBE /* NCRecent.swift in Sources */,
 				F76B3CCE1EAE01BD00921AC9 /* NCBrand.swift in Sources */,
+				F359D8672A7D03420023F405 /* NCUtility+Exif.swift in Sources */,
 				F7581D2425EFDDDF004DC699 /* NCMedia+Menu.swift in Sources */,
 				F738D4902756740100CD1D38 /* NCLoginNavigationController.swift in Sources */,
 				F77B0E981D118A16002130FE /* CCManageAccount.m in Sources */,
@@ -3732,7 +3744,6 @@
 				F7EFA47825ADBA500083159A /* NCViewerProviderContextMenu.swift in Sources */,
 				F755BD9B20594AC7008C5FBB /* NCService.swift in Sources */,
 				F7E8A391295DC5E0006CB2D0 /* View+Extension.swift in Sources */,
-				AFD33240276A02C100F5AE02 /* UIApplication+Extension.swift in Sources */,
 				F79B869B265E19D40085C0E0 /* NSMutableAttributedString+Extension.swift in Sources */,
 				F7B7504B2397D38F004E13EC /* UIImage+Extension.swift in Sources */,
 				F7EFC0CD256BF8DD00461AAD /* NCUserStatus.swift in Sources */,
@@ -3818,6 +3829,7 @@
 				F7A8D73F28F181EF008BBE1C /* NCGlobal.swift in Sources */,
 				F74B6D972A7E239A00F03C5F /* NCManageDatabase+Chunk.swift in Sources */,
 				F749B653297B0F2400087535 /* NCManageDatabase+Avatar.swift in Sources */,
+				F359D8692A7D03420023F405 /* NCUtility+Exif.swift in Sources */,
 				F763D29F2A249C4500A3C901 /* NCManageDatabase+Capabilities.swift in Sources */,
 				F7A8D74328F1826F008BBE1C /* String+Extension.swift in Sources */,
 				F7A8D73728F17E1E008BBE1C /* NCManageDatabase+Account.swift in Sources */,

+ 8 - 0
iOSClient/Data/NCManageDatabase+Metadata.swift

@@ -100,6 +100,10 @@ class tableMetadata: Object, NCUserBaseUrl {
     @objc dynamic var urlBase = ""
     @objc dynamic var user = ""
     @objc dynamic var userId = ""
+    @objc dynamic var latitude: Double = 0
+    @objc dynamic var longitude: Double = 0
+    @objc dynamic var height: Int = 0
+    @objc dynamic var width: Int = 0
 
     override static func primaryKey() -> String {
         return "ocId"
@@ -332,6 +336,10 @@ extension NCManageDatabase {
         metadata.urlBase = file.urlBase
         metadata.user = file.user
         metadata.userId = file.userId
+        metadata.latitude = file.latitude
+        metadata.longitude = file.longitude
+        metadata.height = file.height
+        metadata.width = file.width
 
         // E2EE find the fileName for fileNameView
         if isDirectoryE2EE || file.e2eEncrypted {

+ 0 - 1
iOSClient/Data/NCManageDatabase.swift

@@ -473,7 +473,6 @@ class NCManageDatabase: NSObject {
         return nil
     }
 
-    // MARK: -
     // MARK: Table Photo Library
 
     @discardableResult

+ 0 - 34
iOSClient/Extensions/UIApplication+Extension.swift

@@ -1,34 +0,0 @@
-//
-//  UIApplication+Extension.swift
-//  Nextcloud
-//
-//  Created by Henrik Storch on 15.12.2021.
-//  Copyright (c) 2021 Henrik Storch. All rights reserved.
-//
-//  Author Henrik Storch <henrik.storch@nextcloud.com>
-//
-//  This program is free software: you can redistribute it and/or modify
-//  it under the terms of the GNU General Public License as published by
-//  the Free Software Foundation, either version 3 of the License, or
-//  (at your option) any later version.
-//
-//  This program is distributed in the hope that it will be useful,
-//  but WITHOUT ANY WARRANTY; without even the implied warranty of
-//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//  GNU General Public License for more details.
-//
-//  You should have received a copy of the GNU General Public License
-//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-//
-import UIKit
-
-extension UIApplication {
-    // indicates if current device is in landscape orientation
-    var isLandscape: Bool {
-        if UIDevice.current.orientation.isValidInterfaceOrientation {
-            return UIDevice.current.orientation.isLandscape
-        } else {
-            return windows.first?.windowScene?.interfaceOrientation.isLandscape ?? false
-        }
-    }
-}

+ 14 - 0
iOSClient/Extensions/UIDevice+Extension.swift

@@ -36,3 +36,17 @@ extension UIDevice {
         }
     }
 }
+
+extension UIDeviceOrientation {
+    /// According to Apple... if the device is laid flat the UI is neither portrait nor landscape, so this flag ignores that and checks if the UI is REALLY in landscape. Thanks Apple.
+    /// 
+    /// Unless you really need to use this, you can instead try `traitCollection.verticalSizeClass` and `traitCollection.horizontalSizeClass`.
+    var isLandscapeHardCheck: Bool {
+        if UIDevice.current.orientation.isValidInterfaceOrientation {
+            return UIDevice.current.orientation.isLandscape
+        } else {
+            return UIApplication.shared.windows.first?.windowScene?.interfaceOrientation.isLandscape ?? false
+        }
+    }
+}
+

+ 1 - 1
iOSClient/Menu/NCMenu+FloatingPanel.swift

@@ -42,7 +42,7 @@ class NCMenuFloatingPanelLayout: FloatingPanelLayout {
         // sometimes UIScreen.main.bounds.size.height is not updated correctly
         // this ensures we use the correct height value
         // can't use `layoutFor size` since menu is dieplayed on top of the whole screen not just the VC
-        let screenHeight = UIApplication.shared.isLandscape
+        let screenHeight = UIDevice.current.orientation.isLandscapeHardCheck
         ? min(UIScreen.main.bounds.size.width, UIScreen.main.bounds.size.height)
         : max(UIScreen.main.bounds.size.width, UIScreen.main.bounds.size.height)
         let window = UIApplication.shared.connectedScenes.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }.first { $0.isKeyWindow }

+ 0 - 1
iOSClient/Networking/NCNetworking.swift

@@ -385,7 +385,6 @@ class NCNetworking: NSObject, NKCommonDelegate {
                 if let result = NCManageDatabase.shared.getE2eEncryption(predicate: NSPredicate(format: "fileNameIdentifier == %@ AND serverUrl == %@", metadata.fileName, metadata.serverUrl)) {
                     NCEndToEndEncryption.sharedManager()?.decryptFile(metadata.fileName, fileNameView: metadata.fileNameView, ocId: metadata.ocId, key: result.key, initializationVector: result.initializationVector, authenticationTag: result.authenticationTag)
                 }
-                CCUtility.setExif(metadata) { _, _, _, _, _ in }
 #endif
                 NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDownloadedFile, userInfo: ["ocId": metadata.ocId, "serverUrl": metadata.serverUrl, "account": metadata.account, "selector": selector, "error": error])
 

+ 5 - 2
iOSClient/Supporting Files/en.lproj/Localizable.strings

@@ -442,7 +442,9 @@
 "_pull_down_"                               = "Pull down to refresh";
 "_no_photo_load_"                           = "No photo or video";
 "_tutorial_autoupload_view_"                = "You can enable auto uploads from \"Settings\"";
-"_no_date_"                                 = "No date";
+"_no_date_information_"                     = "No date information";
+"_no_camera_information_"                   = "No camera information";
+"_no_lens_information_"                     = "No lens information";
 "_today_"                                   = "Today";
 "_yesterday_"                               = "Yesterday";
 "_time_"                                    = "Time: %@\n\n%@";
@@ -863,7 +865,8 @@
 "_the_entered_page_number_does_not_exist_"   = "The entered page number does not exist";
 "_error_something_wrong_"   = "Something went wrong";
 "_resolution_"              = "Resolution";
-"_try_download_full_resolution_"            = "For more detail try to download the image in full resolution";
+"_try_download_full_resolution_"            = "Download full resolution image";
+"_full_resolution_image_info_"            = "This may reveal more information about the photo.";
 "_copied_path_"             = "Copied path";
 "_copy_path_"               = "Copy path";
 "_certificates_"            = "Certificates";

+ 1 - 1
iOSClient/Transfers/NCTransfers.swift

@@ -242,7 +242,7 @@ class NCTransfers: NCCollectionViewCommon, NCTransferCellDelegate {
         if self.appDelegate.account != metadata.account {
             cell.labelInfo.text = NSLocalizedString("_waiting_for_", comment: "") + " " + NSLocalizedString("_user_", comment: "").lowercased() + " \(metadata.userId) " + NSLocalizedString("_in_", comment: "") + " \(metadata.urlBase)"
         }
-        let isWiFi = NCNetworking.shared.networkReachability == NKCommon.TypeReachability.reachableEthernetOrWiFi
+        let isWiFi = NCNetworking.shared.networkReachability == .reachableEthernetOrWiFi
         if metadata.session == NCNetworking.shared.sessionIdentifierBackgroundWWan && !isWiFi {
             cell.labelInfo.text = NSLocalizedString("_waiting_for_", comment: "") + " " + NSLocalizedString("_reachable_wifi_", comment: "")
         }

+ 0 - 4
iOSClient/Utility/CCUtility.h

@@ -244,10 +244,6 @@
 + (BOOL)isPermissionToRead:(NSInteger) permissionValue;
 + (BOOL)isPermissionToReadCreateUpdate:(NSInteger) permissionValue;
 
-// ===== EXIF =====
-
-+ (void)setExif:(tableMetadata *)metadata withCompletionHandler:(void(^)(double latitude, double longitude, NSString *location, NSDate *date, NSString *lensModel))completition;
-
 // ===== Third parts =====
 
 + (NSString *)getExtension:(NSString*)fileName;

+ 115 - 291
iOSClient/Utility/CCUtility.m

@@ -51,11 +51,11 @@
 + (void)storeAllChainInService
 {
     UICKeyChainStore *store = [UICKeyChainStore keyChainStore];
-    
+
     NSArray *items = store.allItems;
-    
+
     for (NSDictionary *item in items) {
-        
+
         [UICKeyChainStore setString:[item objectForKey:@"value"] forKey:[item objectForKey:@"key"] service:NCGlobal.shared.serviceShareKeyChain];
         [UICKeyChainStore removeItemForKey:[item objectForKey:@"key"]];
     }
@@ -87,13 +87,13 @@
 + (BOOL)getEnableTouchFaceID
 {
     NSString *valueString = [UICKeyChainStore stringForKey:@"enableTouchFaceID" service:NCGlobal.shared.serviceShareKeyChain];
-    
+
     // Default TRUE
     if (valueString == nil) {
         [self setEnableTouchFaceID:YES];
         return true;
     }
-    
+
     return [valueString boolValue];
 }
 
@@ -112,15 +112,15 @@
 + (NSString *)getGroupBySettings
 {
     NSString *groupby = [UICKeyChainStore stringForKey:@"groupby" service:NCGlobal.shared.serviceShareKeyChain];
-    
+
     if (groupby == nil) {
-        
+
         [self setGroupBySettings:@"none"];
         return @"none";
     }
-    
+
     return @"none";
-    
+
     //return groupby;
 }
 
@@ -136,14 +136,14 @@
         [CCUtility setIntro:YES];
         return YES;
     }
-    
+
     return [[UICKeyChainStore stringForKey:@"intro" service:NCGlobal.shared.serviceShareKeyChain] boolValue];
 }
 
 + (BOOL)getIntroMessageOldVersion
 {
     NSString *key = [INTRO_MessageType stringByAppendingString:@"Intro"];
-    
+
     return [[UICKeyChainStore stringForKey:key service:NCGlobal.shared.serviceShareKeyChain] boolValue];
 }
 
@@ -157,12 +157,12 @@
 + (NSString *)getIncrementalNumber
 {
     long number = [[UICKeyChainStore stringForKey:@"incrementalnumber" service:NCGlobal.shared.serviceShareKeyChain] intValue];
-    
+
     number++;
     if (number >= 9999) number = 1;
-    
+
     [UICKeyChainStore setString:[NSString stringWithFormat:@"%ld", number] forKey:@"incrementalnumber" service:NCGlobal.shared.serviceShareKeyChain];
-    
+
     return [NSString stringWithFormat:@"%04ld", number];
 }
 
@@ -240,10 +240,10 @@
 + (NSString *)getFileNameMask:(NSString *)key
 {
     NSString *mask = [UICKeyChainStore stringForKey:key service:NCGlobal.shared.serviceShareKeyChain];
-    
+
     if (mask == nil)
         mask = @"";
-    
+
     return mask;
 }
 
@@ -288,13 +288,13 @@
 + (BOOL)getFormatCompatibility
 {
     NSString *valueString = [UICKeyChainStore stringForKey:@"formatCompatibility" service:NCGlobal.shared.serviceShareKeyChain];
-    
+
     // Default TRUE
     if (valueString == nil) {
         [self setFormatCompatibility:YES];
         return true;
     }
-    
+
     return [valueString boolValue];
 }
 
@@ -307,7 +307,7 @@
 + (NSString *)getEndToEndCertificate:(NSString *)account
 {
     NSString *key, *certificate;
-    
+
     key = [E2E_certificate stringByAppendingString:account];
     certificate = [UICKeyChainStore stringForKey:key service:NCGlobal.shared.serviceShareKeyChain];
 
@@ -316,7 +316,7 @@
         key = [@"EndToEndPublicKey_" stringByAppendingString:account];
         certificate = [UICKeyChainStore stringForKey:key service:NCGlobal.shared.serviceShareKeyChain];
     }
-    
+
     return certificate;
 }
 
@@ -366,7 +366,7 @@
 {
     BOOL isE2EEEnabled = [[NCGlobal shared] capabilityE2EEEnabled];
     NSString* versionE2EE = [[NCGlobal shared] capabilityE2EEApiVersion];
-    
+
     NSString *certificate = [self getEndToEndCertificate:account];
     NSString *publicKey = [self getEndToEndPublicKey:account];
     NSString *privateKey = [self getEndToEndPrivateKey:account];
@@ -385,7 +385,7 @@
     [self setEndToEndPrivateKey:account privateKey:nil];
     [self setEndToEndPublicKey:account publicKey:nil];
     [self setEndToEndPassphrase:account passphrase:nil];
-    
+
     // OLD
     [UICKeyChainStore setString:nil forKey:[@"EndToEndPublicKey_" stringByAppendingString:account] service:NCGlobal.shared.serviceShareKeyChain];
 }
@@ -486,7 +486,7 @@
 + (NSInteger)getMediaWidthImage
 {
     NSString *width = [UICKeyChainStore stringForKey:@"mediaWidthImage" service:NCGlobal.shared.serviceShareKeyChain];
-    
+
     if (width == nil) {
         return 80;
     } else {
@@ -556,13 +556,13 @@
 + (BOOL)getLivePhoto
 {
     NSString *valueString = [UICKeyChainStore stringForKey:@"livePhoto" service:NCGlobal.shared.serviceShareKeyChain];
-    
+
     // Default TRUE
     if (valueString == nil) {
         [self setLivePhoto:YES];
         return true;
     }
-    
+
     return [valueString boolValue];
 }
 
@@ -575,13 +575,13 @@
 + (NSString *)getMediaSortDate
 {
     NSString *valueString = [UICKeyChainStore stringForKey:@"mediaSortDate" service:NCGlobal.shared.serviceShareKeyChain];
-    
+
     // Default TRUE
     if (valueString == nil) {
         [self setMediaSortDate:@"date"];
         return @"date";
     }
-    
+
     return valueString;
 }
 
@@ -642,7 +642,7 @@
 + (NSInteger)getLogLevel
 {
     NSString *value = [UICKeyChainStore stringForKey:@"logLevel" service:NCGlobal.shared.serviceShareKeyChain];
-    
+
     if (value == nil) {
         return 1;
     } else {
@@ -670,7 +670,7 @@
 + (NSInteger)getCleanUpDay
 {
     NSString *size = [UICKeyChainStore stringForKey:@"cleanUpDay" service:NCGlobal.shared.serviceShareKeyChain];
-    
+
     if (size == nil) {
         NSInteger days = [[NCBrandOptions shared] cleanUpDay];
         return days;
@@ -712,7 +712,7 @@
 #pragma --------------------------------------------------------------------------------------------
 
 + (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
-{    
+{
     NSError *error = nil;
     BOOL success = [URL setResourceValue:[NSNumber numberWithBool: YES] forKey: NSURLIsExcludedFromBackupKey error: &error];
     if(success) {
@@ -720,7 +720,7 @@
     } else {
         NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);
     }
-    
+
     return success;
 }
 
@@ -728,7 +728,7 @@
 {
     NSString *appVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
     NSString *userAgent = [[NCBrandOptions shared] userAgent];
-    
+
     return [NSString stringWithFormat:@"Mozilla/5.0 (iOS) %@/%@", userAgent, appVersion];
 }
 
@@ -779,11 +779,11 @@
 + (NSString *)removeForbiddenCharactersServer:(NSString *)fileName
 {
     NSArray *arrayForbiddenCharacters = [NSArray arrayWithObjects:@"/", nil];
-    
+
     for (NSString *currentCharacter in arrayForbiddenCharacters) {
         fileName = [fileName stringByReplacingOccurrencesOfString:currentCharacter withString:@""];
     }
-    
+
     return fileName;
 }
 
@@ -791,24 +791,24 @@
 + (NSString *)removeForbiddenCharactersFileSystem:(NSString *)fileName
 {
     NSArray *arrayForbiddenCharacters = [NSArray arrayWithObjects:@"\\",@"<",@">",@":",@"\"",@"|",@"?",@"*",@"/", nil];
-    
+
     for (NSString *currentCharacter in arrayForbiddenCharacters) {
         fileName = [fileName stringByReplacingOccurrencesOfString:currentCharacter withString:@""];
     }
-    
+
     return fileName;
 }
 
 + (NSString*)stringAppendServerUrl:(NSString *)serverUrl addFileName:(NSString *)addFileName
 {
     NSString *result;
-    
+
     if (serverUrl == nil || addFileName == nil) return nil;
     if ([addFileName isEqualToString:@""]) return serverUrl;
-    
+
     if ([serverUrl isEqualToString:@"/"]) result = [serverUrl stringByAppendingString:addFileName];
     else result = [NSString stringWithFormat:@"%@/%@", serverUrl, addFileName];
-    
+
     return result;
 }
 
@@ -818,44 +818,44 @@
     [formatter setDateFormat:@"yy-MM-dd HH-mm-ss"];
     NSString *fileNameDate = [formatter stringFromDate:[NSDate date]];
     NSString *returnFileName;
-    
+
     if ([fileName isEqualToString:@""] && ![extension isEqualToString:@""]) {
         returnFileName = [NSString stringWithFormat:@"%@.%@", fileNameDate, extension];
     }
-    
+
     if (![fileName isEqualToString:@""] && [extension isEqualToString:@""]) {
         returnFileName = [NSString stringWithFormat:@"%@ %@", fileName, fileNameDate];
     }
-    
+
     if ([fileName isEqualToString:@""] && [extension isEqualToString:@""]) {
         returnFileName = fileNameDate;
     }
-    
+
     if (![fileName isEqualToString:@""] && ![extension isEqualToString:@""]) {
         returnFileName = [NSString stringWithFormat:@"%@ %@.%@", fileName, fileNameDate, extension];
     }
-    
+
     return returnFileName;
 }
 
 + (NSString *)createFileName:(NSString *)fileName fileDate:(NSDate *)fileDate fileType:(PHAssetMediaType)fileType keyFileName:(NSString *)keyFileName keyFileNameType:(NSString *)keyFileNameType keyFileNameOriginal:(NSString *)keyFileNameOriginal forcedNewFileName:(BOOL)forcedNewFileName
 {
     BOOL addFileNameType = NO;
-    
+
     // Original FileName ?
     if ([self getOriginalFileName:keyFileNameOriginal] && !forcedNewFileName) {
         return fileName;
     }
-    
+
     NSString *numberFileName;
     if ([fileName length] > 8) numberFileName = [fileName substringWithRange:NSMakeRange(04, 04)];
     else numberFileName = [CCUtility getIncrementalNumber];
-    
+
     NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
     [formatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];
     [formatter setDateFormat:@"yy-MM-dd HH-mm-ss"];
     NSString *fileNameDate = [formatter stringFromDate:fileDate];
-    
+
     NSString *fileNameType = @"";
     if (fileType == PHAssetMediaTypeImage)
         fileNameType = NSLocalizedString(@"_photo_", nil);
@@ -869,15 +869,15 @@
     // Use File Name Type
     if (keyFileNameType)
         addFileNameType = [CCUtility getFileNameType:keyFileNameType];
-    
+
     NSString *fileNameExt = [[fileName pathExtension] lowercaseString];
-    
+
     if (keyFileName) {
-        
+
         fileName = [CCUtility getFileNameMask:keyFileName];
-        
+
         if ([fileName length] > 0) {
-            
+
             [formatter setDateFormat:@"dd"];
             NSString *dayNumber = [formatter stringFromDate:fileDate];
             [formatter setDateFormat:@"MMM"];
@@ -898,7 +898,7 @@
             NSString *second = [formatter stringFromDate:fileDate];
             [formatter setDateFormat:@"a"];
             NSString *ampm = [formatter stringFromDate:fileDate];
-            
+
             // Replace string with date
 
             fileName = [fileName stringByReplacingOccurrencesOfString:@"DD" withString:dayNumber];
@@ -917,26 +917,26 @@
                 fileName = [NSString stringWithFormat:@"%@%@%@.%@", fileNameType, fileName, numberFileName, fileNameExt];
             else
                 fileName = [NSString stringWithFormat:@"%@%@.%@", fileName, numberFileName, fileNameExt];
-            
+
             fileName = [fileName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
-            
+
         } else {
-            
+
             if (addFileNameType)
                 fileName = [NSString stringWithFormat:@"%@ %@ %@.%@", fileNameType, fileNameDate, numberFileName, fileNameExt];
             else
                 fileName = [NSString stringWithFormat:@"%@ %@.%@", fileNameDate, numberFileName, fileNameExt];
         }
-        
+
     } else {
-        
+
         if (addFileNameType)
             fileName = [NSString stringWithFormat:@"%@ %@ %@.%@", fileNameType, fileNameDate, numberFileName, fileNameExt];
         else
             fileName = [NSString stringWithFormat:@"%@ %@.%@", fileNameDate, numberFileName, fileNameExt];
 
     }
-    
+
     return fileName;
 }
 
@@ -944,42 +944,42 @@
 {
     NSString *path;
     NSURL *dirGroup = [CCUtility getDirectoryGroup];
-    
+
     NSLog(@"[LOG] Dir Group");
     NSLog(@"%@", [dirGroup path]);
     NSLog(@"[LOG] Program application ");
     NSLog(@"%@", [[CCUtility getDirectoryDocuments] stringByDeletingLastPathComponent]);
-    
+
     // create Directory Documents
     path = [CCUtility getDirectoryDocuments];
     if (![[NSFileManager defaultManager] fileExistsAtPath: path])
         [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
-    
+
     // create Directory audio => Library, Application Support, audio
     path = [CCUtility getDirectoryAudio];
     if (![[NSFileManager defaultManager] fileExistsAtPath: path])
         [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
-    
+
     // create Directory database Nextcloud
     path = [[dirGroup URLByAppendingPathComponent:[[NCGlobal shared] appDatabaseNextcloud]] path];
     if (![[NSFileManager defaultManager] fileExistsAtPath:path])
         [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
-    
+
     // create Directory User Data
     path = [[dirGroup URLByAppendingPathComponent:NCGlobal.shared.appUserData] path];
     if (![[NSFileManager defaultManager] fileExistsAtPath:path])
         [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
-    
+
     // create Directory Provider Storage
     path = [CCUtility getDirectoryProviderStorage];
     if (![[NSFileManager defaultManager] fileExistsAtPath: path])
         [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
-    
+
     // create Directory Scan
     path = [[dirGroup URLByAppendingPathComponent:NCGlobal.shared.appScan] path];
     if (![[NSFileManager defaultManager] fileExistsAtPath:path])
         [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
-    
+
     // create Directory Temp
     path = NSTemporaryDirectory();
     if (![[NSFileManager defaultManager] fileExistsAtPath:path])
@@ -1006,14 +1006,14 @@
 + (NSString *)getDirectoryDocuments
 {
     NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
-    
+
     return [paths objectAtIndex:0];
 }
 
 + (NSString *)getDirectoryReaderMetadata
 {
     NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
-    
+
     return [NSString stringWithFormat:@"%@/Reader Metadata", [paths objectAtIndex:0]];
 }
 
@@ -1021,7 +1021,7 @@
 + (NSString *)getDirectoryAudio
 {
     NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
-    
+
     return [NSString stringWithFormat:@"%@/%@", [paths objectAtIndex:0], @"audio"];
 }
 
@@ -1029,27 +1029,27 @@
 + (NSString *)getDirectoryCerificates
 {
     NSString *path = [[[CCUtility getDirectoryGroup] URLByAppendingPathComponent:NCGlobal.shared.appCertificates] path];
-    
+
     if (![[NSFileManager defaultManager] fileExistsAtPath:path])
         [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
-    
+
     return path;
 }
 
 + (NSString *)getDirectoryUserData
 {
     NSString *path = [[[CCUtility getDirectoryGroup] URLByAppendingPathComponent:NCGlobal.shared.appUserData] path];
-    
+
     if (![[NSFileManager defaultManager] fileExistsAtPath:path])
         [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
-    
+
     return path;
 }
 
 + (NSString *)getDirectoryProviderStorage
 {
     NSString *path = [[[CCUtility getDirectoryGroup] URLByAppendingPathComponent:NCGlobal.shared.directoryProviderStorage] path];
-    
+
     if (![[NSFileManager defaultManager] fileExistsAtPath:path])
         [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
 
@@ -1059,7 +1059,7 @@
 + (NSString *)getDirectoryProviderStorageOcId:(NSString *)ocId
 {
     NSString *path = [NSString stringWithFormat:@"%@/%@", [self getDirectoryProviderStorage], ocId];
-    
+
     if (![[NSFileManager defaultManager] fileExistsAtPath:path])
         [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
 
@@ -1069,14 +1069,14 @@
 + (NSString *)getDirectoryProviderStorageOcId:(NSString *)ocId fileNameView:(NSString *)fileNameView
 {
     NSString *fileNamePath = [NSString stringWithFormat:@"%@/%@", [self getDirectoryProviderStorageOcId:ocId], fileNameView];
-    
+
     // if do not exists create file 0 length
     // causes files with lenth 0 to never be downloaded, because already exist
     // also makes it impossible to delete any file with length 0 (from cache)
     if ([[NSFileManager defaultManager] fileExistsAtPath:fileNamePath] == NO) {
         [[NSFileManager defaultManager] createFileAtPath:fileNamePath contents:nil attributes:nil];
     }
-    
+
     return fileNamePath;
 }
 
@@ -1112,9 +1112,9 @@
 + (int64_t)fileProviderStorageSize:(NSString *)ocId fileNameView:(NSString *)fileNameView
 {
     NSString *fileNamePath = [self getDirectoryProviderStorageOcId:ocId fileNameView:fileNameView];
-    
+
     int64_t fileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:fileNamePath error:nil] fileSize];
-    
+
     return fileSize;
 }
 
@@ -1122,10 +1122,10 @@
 {
     NSString *fileNamePathPreview = [self getDirectoryProviderStoragePreviewOcId:ocId etag:etag];
     NSString *fileNamePathIcon = [self getDirectoryProviderStorageIconOcId:ocId etag:etag];
-    
+
     unsigned long long fileSizePreview = [[[NSFileManager defaultManager] attributesOfItemAtPath:fileNamePathPreview error:nil] fileSize];
     unsigned long long fileSizeIcon = [[[NSFileManager defaultManager] attributesOfItemAtPath:fileNamePathIcon error:nil] fileSize];
-    
+
     if (fileSizePreview > 0 && fileSizeIcon > 0) return true;
     else return false;
 }
@@ -1134,7 +1134,7 @@
 {
     NSURL *dirGroup = [CCUtility getDirectoryGroup];
     NSString *path = [[dirGroup URLByAppendingPathComponent:NCGlobal.shared.appApplicationSupport] path];
-    
+
     [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
 }
 
@@ -1172,22 +1172,22 @@
     NSString *title;
     NSDate *today = [NSDate date];
     NSDate *yesterday = [today dateByAddingTimeInterval: -86400.0];
-    
+
     if ([date isEqualToDate:[CCUtility datetimeWithOutTime:[NSDate distantPast]]]) {
-        
+
         title =  NSLocalizedString(@"_no_date_", nil);
-        
+
     } else {
-        
+
         title = [NSDateFormatter localizedStringFromDate:date dateStyle:NSDateFormatterLongStyle timeStyle:0];
-        
+
         if ([date isEqualToDate:[CCUtility datetimeWithOutTime:today]])
             title = [NSString stringWithFormat:NSLocalizedString(@"_today_", nil)];
-        
+
         if ([date isEqualToDate:[CCUtility datetimeWithOutTime:yesterday]])
             title = [NSString stringWithFormat:NSLocalizedString(@"_yesterday_", nil)];
     }
-    
+
     return title;
 }
 
@@ -1220,18 +1220,18 @@
     NSString *path = [serverUrl stringByReplacingOccurrencesOfString:homeServer withString:@""];
     return path;
 }
-                                       
+
 + (NSString *)returnFileNamePathFromFileName:(NSString *)metadataFileName serverUrl:(NSString *)serverUrl urlBase:(NSString *)urlBase userId:(NSString *)userId account:(NSString *)account
 {
     if (metadataFileName == nil || serverUrl == nil || urlBase == nil) {
         return @"";
     }
-    
+
     NSString *homeServer = [[NCUtilityFileSystem shared] getHomeServerWithUrlBase:urlBase userId:userId];
     NSString *fileName = [NSString stringWithFormat:@"%@/%@", [serverUrl stringByReplacingOccurrencesOfString:homeServer withString:@""], metadataFileName];
-    
+
     if ([fileName hasPrefix:@"/"]) fileName = [fileName substringFromIndex:1];
-    
+
     return fileName;
 }
 
@@ -1239,33 +1239,33 @@
 {
     CFStringRef fileUTI = nil;
     NSString *returnFileUTI = nil;
-    
+
     if ([fileNameView isEqualToString:@"."]) {
-        
+
         return returnFileUTI;
-        
+
     } else {
         CFStringRef fileExtension = (__bridge CFStringRef)[fileNameView pathExtension];
         NSString *ext = (__bridge NSString *)fileExtension;
         ext = ext.uppercaseString;
         fileUTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, fileExtension, NULL);
-        
+
         if (fileUTI != nil) {
             returnFileUTI = (__bridge NSString *)fileUTI;
             CFRelease(fileUTI);
         }
     }
-    
+
     return returnFileUTI;
 }
 
 + (NSString *)getDirectoryScan
 {
     NSString *path = [[[CCUtility getDirectoryGroup] URLByAppendingPathComponent:NCGlobal.shared.appScan] path];
-    
+
     if (![[NSFileManager defaultManager] fileExistsAtPath:path])
         [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
-    
+
     return path;
 }
 
@@ -1274,9 +1274,9 @@
 #pragma --------------------------------------------------------------------------------------------
 
 + (NSInteger) getPermissionsValueByCanEdit:(BOOL)canEdit andCanCreate:(BOOL)canCreate andCanChange:(BOOL)canChange andCanDelete:(BOOL)canDelete andCanShare:(BOOL)canShare andIsFolder:(BOOL) isFolder
-{    
+{
     NSInteger permissionsValue = NCGlobal.shared.permissionReadShare;
-    
+
     if (canEdit && !isFolder) {
         permissionsValue = permissionsValue + NCGlobal.shared.permissionUpdateShare;
     }
@@ -1292,7 +1292,7 @@
     if (canShare) {
         permissionsValue = permissionsValue + NCGlobal.shared.permissionShareShare;
     }
-    
+
     return permissionsValue;
 }
 
@@ -1317,16 +1317,16 @@
 }
 
 + (BOOL) isAnyPermissionToEdit:(NSInteger) permissionValue {
-    
+
     BOOL canCreate = [self isPermissionToCanCreate:permissionValue];
     BOOL canChange = [self isPermissionToCanChange:permissionValue];
     BOOL canDelete = [self isPermissionToCanDelete:permissionValue];
-    
-    
+
+
     BOOL canEdit = (canCreate || canChange || canDelete);
-    
+
     return canEdit;
-    
+
 }
 
 + (BOOL) isPermissionToRead:(NSInteger) permissionValue {
@@ -1335,192 +1335,16 @@
 }
 
 + (BOOL) isPermissionToReadCreateUpdate:(NSInteger) permissionValue {
-    
+
     BOOL canRead   = [self isPermissionToRead:permissionValue];
     BOOL canCreate = [self isPermissionToCanCreate:permissionValue];
     BOOL canChange = [self isPermissionToCanChange:permissionValue];
-    
-    
-    BOOL canEdit = (canCreate && canChange && canRead);
-    
-    return canEdit;
-    
-}
 
-#pragma --------------------------------------------------------------------------------------------
-#pragma mark ===== EXIF =====
-#pragma --------------------------------------------------------------------------------------------
 
-+ (void)setExif:(tableMetadata *)metadata withCompletionHandler:(void(^)(double latitude, double longitude, NSString *location, NSDate *date, NSString *lensModel))completition
-{
-    NSString *dateTime;
-    NSString *latitudeRef;
-    NSString *longitudeRef;
-    NSString *stringLatitude = @"0";
-    NSString *stringLongitude = @"0";
-    __block NSString *location = @"";
-    
-    double latitude = 0;
-    double longitude = 0;
-    
-    NSDate *date = nil;
-    long fileSize = 0;
-    int pixelY = 0;
-    int pixelX = 0;
-    NSString *lensModel = @"";
-
-    if (![metadata.classFile isEqualToString:@"image"] || ![CCUtility fileProviderStorageExists:metadata]) {
-        completition(latitude, longitude, location, date, lensModel);
-        return;
-    }
-    
-    NSURL *url = [NSURL fileURLWithPath:[CCUtility getDirectoryProviderStorageOcId:metadata.ocId fileNameView:metadata.fileNameView]];
-    CGImageSourceRef originalSource =  CGImageSourceCreateWithURL((CFURLRef) url, NULL);
-    if (!originalSource) {
-        completition(latitude, longitude, location, date, lensModel);
-        return;
-    }
-    
-    CFDictionaryRef fileProperties = CGImageSourceCopyProperties(originalSource, nil);
-    if (!fileProperties) {
-        CFRelease(originalSource);
-        completition(latitude, longitude, location, date, lensModel);
-        return;
-    }
-    
-    // FILES PROPERTIES
-    NSNumber *fileSizeNumber = CFDictionaryGetValue(fileProperties, kCGImagePropertyFileSize);
-    fileSize = [fileSizeNumber longValue];
-    
-    CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(originalSource, 0, NULL);
-    if (!imageProperties) {
-        CFRelease(originalSource);
-        CFRelease(fileProperties);
-        completition(latitude, longitude, location, date, lensModel);
-        return;
-    }
+    BOOL canEdit = (canCreate && canChange && canRead);
 
-    CFDictionaryRef tiff = CFDictionaryGetValue(imageProperties, kCGImagePropertyTIFFDictionary);
-    CFDictionaryRef gps = CFDictionaryGetValue(imageProperties, kCGImagePropertyGPSDictionary);
-    CFDictionaryRef exif = CFDictionaryGetValue(imageProperties, kCGImagePropertyExifDictionary);
-    
-    if (exif) {
-        
-        NSString *sPixelX = (NSString *)CFDictionaryGetValue(exif, kCGImagePropertyExifPixelXDimension);
-        pixelX = [sPixelX intValue];
-        NSString *sPixelY = (NSString *)CFDictionaryGetValue(exif, kCGImagePropertyExifPixelYDimension);
-        pixelY = [sPixelY intValue];
-        lensModel = (NSString *)CFDictionaryGetValue(exif, kCGImagePropertyExifLensModel);
-        dateTime = (NSString *)CFDictionaryGetValue(exif, kCGImagePropertyExifDateTimeOriginal);
-        NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
-        [dateFormatter setDateFormat:@"yyyy:MM:dd HH:mm:ss"];
-        date = [dateFormatter dateFromString:dateTime];
-    }
- 
-    if (tiff) {
-        
-        dateTime = (NSString *)CFDictionaryGetValue(tiff, kCGImagePropertyTIFFDateTime);
-        NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
-        [dateFormatter setDateFormat:@"yyyy:MM:dd HH:mm:ss"];
-        date = [dateFormatter dateFromString:dateTime];
-    }
-    
-    if (gps) {
-        
-        latitude = [(NSString *)CFDictionaryGetValue(gps, kCGImagePropertyGPSLatitude) doubleValue];
-        longitude = [(NSString *)CFDictionaryGetValue(gps, kCGImagePropertyGPSLongitude) doubleValue];
-        
-        latitudeRef = (NSString *)CFDictionaryGetValue(gps, kCGImagePropertyGPSLatitudeRef);
-        longitudeRef = (NSString *)CFDictionaryGetValue(gps, kCGImagePropertyGPSLongitudeRef);
-        
-        // conversion 4 decimal +N -S
-        // The latitude in degrees. Positive values indicate latitudes north of the equator. Negative values indicate latitudes south of the equator.
-        if ([latitudeRef isEqualToString:@"N"]) {
-            stringLatitude = [NSString stringWithFormat:@"+%.4f", latitude];
-        } else {
-            stringLatitude = [NSString stringWithFormat:@"-%.4f", latitude];
-            latitude *= -1;
-        }
-        
-        // conversion 4 decimal +E -W
-        // The longitude in degrees. Measurements are relative to the zero meridian, with positive values extending east of the meridian
-        // and negative values extending west of the meridian.
-        if ([longitudeRef isEqualToString:@"E"]) {
-            stringLongitude = [NSString stringWithFormat:@"+%.4f", longitude];
-        } else {
-            stringLongitude = [NSString stringWithFormat:@"-%.4f", longitude];
-            longitude *= -1;
-        }
-        
-        if (latitude == 0 || longitude == 0) {
-            stringLatitude = @"0";
-            stringLongitude = @"0";
-        }
-    }
+    return canEdit;
 
-    // Wite data EXIF in DB
-    if (tiff || gps) {
-        [[NCManageDatabase shared] setLocalFileWithOcId:metadata.ocId exifDate:date exifLatitude:stringLatitude exifLongitude:stringLongitude exifLensModel:lensModel];
-        if ([stringLatitude doubleValue] != 0 || [stringLongitude doubleValue] != 0) {
-            
-            // If exists already geocoder data in TableGPS exit
-            location = [[NCManageDatabase shared] getLocationFromGeoLatitude:stringLatitude longitude:stringLongitude];
-            if (location != nil) {
-                CFRelease(originalSource);
-                CFRelease(imageProperties);
-                CFRelease(fileProperties);
-                completition(latitude, longitude, location, date, lensModel);
-                return;
-            }
-            
-            CLGeocoder *geocoder = [[CLGeocoder alloc] init];
-            CLLocation *llocation = [[CLLocation alloc] initWithLatitude:latitude longitude:longitude];
-            
-            [geocoder reverseGeocodeLocation:llocation completionHandler:^(NSArray *placemarks, NSError *error) {
-                        
-                if (error == nil && [placemarks count] > 0) {
-                    
-                    CLPlacemark *placemark = [placemarks lastObject];
-                    
-                    NSString *thoroughfare = @"";
-                    NSString *postalCode = @"";
-                    NSString *locality = @"";
-                    NSString *administrativeArea = @"";
-                    NSString *country = @"";
-                    
-                    if (placemark.thoroughfare) thoroughfare = placemark.thoroughfare;
-                    if (placemark.postalCode) postalCode = placemark.postalCode;
-                    if (placemark.locality) locality = placemark.locality;
-                    if (placemark.administrativeArea) administrativeArea = placemark.administrativeArea;
-                    if (placemark.country) country = placemark.country;
-                    
-                    location = [NSString stringWithFormat:@"%@ %@ %@ %@ %@", thoroughfare, postalCode, locality, administrativeArea, country];
-                    location = [location stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
-                    
-                    // GPS
-                    if ([location length] > 0) {
-                        
-                        [[NCManageDatabase shared] addGeocoderLocation:location placemarkAdministrativeArea:placemark.administrativeArea placemarkCountry:placemark.country placemarkLocality:placemark.locality placemarkPostalCode:placemark.postalCode placemarkThoroughfare:placemark.thoroughfare latitude:stringLatitude longitude:stringLongitude];
-                    }
-                    
-                    CFRelease(originalSource);
-                    CFRelease(imageProperties);
-                    CFRelease(fileProperties);
-                    completition(latitude, longitude, location, date, lensModel);
-                }
-            }];
-        } else {
-            CFRelease(originalSource);
-            CFRelease(imageProperties);
-            CFRelease(fileProperties);
-            completition(latitude, longitude, location, date, lensModel);
-        }
-    } else {
-        CFRelease(originalSource);
-        CFRelease(imageProperties);
-        CFRelease(fileProperties);
-        completition(latitude, longitude, location, date, lensModel);
-    }
 }
 
 #pragma --------------------------------------------------------------------------------------------
@@ -1548,10 +1372,10 @@
 + (NSDate *)datetimeWithOutTime:(NSDate *)datDate
 {
     if (datDate == nil) return nil;
-    
+
     NSDateComponents* comps = [[NSCalendar currentCalendar] components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay fromDate:datDate];
     datDate = [[NSCalendar currentCalendar] dateFromComponents:comps];
-    
+
     return datDate;
 }
 

+ 149 - 0
iOSClient/Utility/NCUtility+Exif.swift

@@ -0,0 +1,149 @@
+//
+//  NCUtility+Exif.swift
+//  Nextcloud
+//
+//  Created by Milen on 04.08.23.
+//  Copyright © 2023 Marino Faggiana. All rights reserved.
+//
+
+import Foundation
+
+public struct ExifData {
+    var colorModel: String?
+    var width: Int?
+    var height: Int?
+    var dpiWidth: Int?
+    var dpiHeight: Int?
+    var depth: Int?
+    var orientation: Int?
+    var apertureValue: Double?
+    var exposureValue: Int?
+    var shutterSpeedApex: Double?
+    var iso: Int?
+    var lensLength: Int?
+    var brightnessValue: String?
+    var dateTimeDigitized: String?
+    var dateTimeOriginal: String?
+    var offsetTime: String?
+    var offsetTimeDigitized: String?
+    var offsetTimeOriginal: String?
+    var make: String?
+    var model: String?
+    var software: String?
+    var tileLength: Double?
+    var tileWidth: Double?
+    var xResolution: Double?
+    var yResolution: Double?
+    var altitude: String?
+    var destBearing: String?
+    var hPositioningError: String?
+    var imgDirection: String?
+    var latitude: Double?
+    var longitude: Double?
+    var speed: Double?
+    var location: String?
+    var lensModel: String?
+    var date: Date?
+}
+
+extension NCUtility {
+    func getExif(metadata: tableMetadata, completion: @escaping (ExifData) -> Void) {
+        var data = ExifData()
+
+        writeExifFromMetadata(metadata: metadata, data: &data)
+
+        if let latitude = data.latitude, let longitude = data.longitude {
+            getLocation(latitude: latitude, longitude: longitude) { location in
+                data.location = location
+                completion(data)
+            }
+        }
+
+        if metadata.classFile != "image" || !CCUtility.fileProviderStorageExists(metadata) {
+            print("Storage exists or file is not an image")
+        }
+
+        let url = URL(fileURLWithPath: CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView))
+
+        guard let originalSource = CGImageSourceCreateWithURL(url as CFURL, nil),
+              let imageProperties = CGImageSourceCopyPropertiesAtIndex(originalSource, 0, nil) as NSDictionary? else {
+            print("Could not get image properties")
+            completion(data)
+            return
+        }
+
+        data.colorModel = imageProperties[kCGImagePropertyColorModel] as? String
+        data.height = imageProperties[kCGImagePropertyPixelWidth] as? Int
+        data.width = imageProperties[kCGImagePropertyPixelHeight] as? Int
+        data.dpiWidth = imageProperties[kCGImagePropertyDPIWidth] as? Int
+        data.dpiHeight = imageProperties[kCGImagePropertyDPIHeight] as? Int
+        data.depth = imageProperties[kCGImagePropertyDepth] as? Int
+        data.orientation = imageProperties[kCGImagePropertyOrientation] as? Int
+
+        if let tiffData = imageProperties[kCGImagePropertyTIFFDictionary] as? NSDictionary {
+            data.make = tiffData[kCGImagePropertyTIFFMake] as? String
+            data.model = tiffData[kCGImagePropertyTIFFModel] as? String
+            data.software = tiffData[kCGImagePropertyTIFFSoftware] as? String
+            data.tileLength = tiffData[kCGImagePropertyTIFFTileLength] as? Double
+            data.tileWidth = tiffData[kCGImagePropertyTIFFTileWidth] as? Double
+            data.xResolution = tiffData[kCGImagePropertyTIFFXResolution] as? Double
+            data.yResolution = tiffData[kCGImagePropertyTIFFYResolution] as? Double
+
+            let dateTime = tiffData[kCGImagePropertyTIFFDateTime] as? String
+            let dateFormatter = DateFormatter()
+            dateFormatter.dateFormat = "yyyy:MM:dd HH:mm:ss"
+            data.date = dateFormatter.date(from: dateTime ?? "")
+        }
+
+        if let exifData = imageProperties[kCGImagePropertyExifDictionary] as? NSDictionary {
+            data.apertureValue = exifData[kCGImagePropertyExifFNumber] as? Double
+            data.exposureValue = exifData[kCGImagePropertyExifExposureBiasValue] as? Int
+            data.shutterSpeedApex = exifData[kCGImagePropertyExifShutterSpeedValue] as? Double
+            data.iso = (exifData[kCGImagePropertyExifISOSpeedRatings] as? [Int])?[0]
+            data.lensLength = exifData[kCGImagePropertyExifFocalLenIn35mmFilm] as? Int
+            data.brightnessValue = exifData[kCGImagePropertyExifBrightnessValue] as? String
+            data.dateTimeDigitized = exifData[kCGImagePropertyExifDateTimeDigitized] as? String
+            data.dateTimeOriginal = exifData[kCGImagePropertyExifDateTimeOriginal] as? String
+            data.offsetTime = exifData[kCGImagePropertyExifOffsetTime] as? String
+            data.offsetTimeDigitized = exifData[kCGImagePropertyExifOffsetTimeDigitized] as? String
+            data.offsetTimeOriginal = exifData[kCGImagePropertyExifOffsetTimeOriginal] as? String
+            data.lensModel = exifData[kCGImagePropertyExifLensModel] as? String
+        }
+
+        if let gpsData = imageProperties[kCGImagePropertyGPSDictionary] as? NSDictionary {
+            data.altitude = gpsData[kCGImagePropertyGPSAltitude] as? String
+            data.destBearing = gpsData[kCGImagePropertyGPSDestBearing] as? String
+            data.hPositioningError = gpsData[kCGImagePropertyGPSHPositioningError] as? String
+            data.imgDirection = gpsData[kCGImagePropertyGPSImgDirection] as? String
+            data.latitude = gpsData[kCGImagePropertyGPSLatitude] as? Double
+            data.longitude = gpsData[kCGImagePropertyGPSLongitude] as? Double
+            data.speed = gpsData[kCGImagePropertyGPSSpeed] as? Double
+        }
+
+        writeExifFromMetadata(metadata: metadata, data: &data)
+
+        if let latitude = data.latitude, let longitude = data.longitude {
+            getLocation(latitude: latitude, longitude: longitude) { location in
+                data.location = location
+                completion(data)
+            }
+        }
+
+        completion(data)
+    }
+
+    /**
+     Since non-downloaded images are usually thumbnails, the server sends some exif metadata of the real image. This function writes that data to the local exif object, if that data doesn't exist already.
+     */
+    private func writeExifFromMetadata(metadata: tableMetadata, data: inout ExifData) {
+        if metadata.latitude != 0, metadata.longitude != 0 {
+            if data.latitude == nil { data.latitude = metadata.latitude }
+            if data.longitude == nil { data.longitude = metadata.longitude }
+        }
+
+        if metadata.height != 0, metadata.width != 0 {
+            if data.height == nil { data.height = metadata.height }
+            if data.width == nil { data.width = metadata.width }
+        }
+    }
+}

+ 162 - 147
iOSClient/Utility/NCUtility.swift

@@ -13,7 +13,7 @@
 //  (at your option) any later version.
 //
 //  This program is distributed in the hope that it will be useful,
-//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  but WITHOUT ANY WARRANTY without even the implied warranty of
 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 //  GNU General Public License for more details.
 //
@@ -27,6 +27,7 @@ import PDFKit
 import Accelerate
 import CoreMedia
 import Photos
+import Alamofire
 
 #if !EXTENSION
 import SVGKit
@@ -40,37 +41,37 @@ class NCUtility: NSObject {
 
 #if !EXTENSION
     func convertSVGtoPNGWriteToUserData(svgUrlString: String, fileName: String? = nil, width: CGFloat? = nil, rewrite: Bool, account: String, id: Int? = nil, completion: @escaping (_ imageNamePath: String?, _ id: Int?) -> Void) {
-
+        
         var fileNamePNG = ""
-
+        
         guard let svgUrlString = svgUrlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
               let iconURL = URL(string: svgUrlString) else {
             return completion(nil, id)
         }
-
+        
         if let fileName = fileName {
             fileNamePNG = fileName
         } else {
             fileNamePNG = iconURL.deletingPathExtension().lastPathComponent + ".png"
         }
-
+        
         let imageNamePath = CCUtility.getDirectoryUserData() + "/" + fileNamePNG
-
+        
         if !FileManager.default.fileExists(atPath: imageNamePath) || rewrite == true {
-
+            
             NextcloudKit.shared.downloadContent(serverUrl: iconURL.absoluteString) { _, data, error in
-
+                
                 if error == .success && data != nil {
-
+                    
                     if let image = UIImage(data: data!) {
-
+                        
                         var newImage: UIImage = image
-
+                        
                         if width != nil {
-
+                            
                             let ratio = image.size.height / image.size.width
                             let newSize = CGSize(width: width!, height: width! * ratio)
-
+                            
                             let renderFormat = UIGraphicsImageRendererFormat.default()
                             renderFormat.opaque = false
                             let renderer = UIGraphicsImageRenderer(size: CGSize(width: newSize.width, height: newSize.height), format: renderFormat)
@@ -79,108 +80,108 @@ class NCUtility: NSObject {
                                 image.draw(in: CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height))
                             }
                         }
-
+                        
                         guard let pngImageData = newImage.pngData() else {
                             return completion(nil, id)
                         }
-
+                        
                         try? pngImageData.write(to: URL(fileURLWithPath: imageNamePath))
-
+                        
                         return completion(imageNamePath, id)
-
+                        
                     } else {
-
+                        
                         guard let svgImage: SVGKImage = SVGKImage(data: data) else {
                             return completion(nil, id)
                         }
-
+                        
                         if width != nil {
                             let scale = svgImage.size.height / svgImage.size.width
                             svgImage.size = CGSize(width: width!, height: width! * scale)
                         }
-
+                        
                         guard let image: UIImage = svgImage.uiImage else {
                             return completion(nil, id)
                         }
                         guard let pngImageData = image.pngData() else {
                             return completion(nil, id)
                         }
-
+                        
                         try? pngImageData.write(to: URL(fileURLWithPath: imageNamePath))
-
+                        
                         return completion(imageNamePath, id)
                     }
                 } else {
                     return completion(nil, id)
                 }
             }
-
+            
         } else {
             return completion(imageNamePath, id)
         }
     }
 #endif
-
+    
     @objc func isSimulatorOrTestFlight() -> Bool {
         guard let path = Bundle.main.appStoreReceiptURL?.path else {
             return false
         }
         return path.contains("CoreSimulator") || path.contains("sandboxReceipt")
     }
-
+    
     @objc func isSimulator() -> Bool {
         guard let path = Bundle.main.appStoreReceiptURL?.path else {
             return false
         }
         return path.contains("CoreSimulator")
     }
-
+    
     @objc func isRichDocument(_ metadata: tableMetadata) -> Bool {
-
+        
         guard let mimeType = CCUtility.getMimeType(metadata.fileNameView) else {
             return false
         }
-
+        
         // contentype
         for richdocumentMimetype: String in NCGlobal.shared.capabilityRichdocumentsMimetypes {
             if richdocumentMimetype.contains(metadata.contentType) || metadata.contentType == "text/plain" {
                 return true
             }
         }
-
+        
         // mimetype
         if NCGlobal.shared.capabilityRichdocumentsMimetypes.count > 0 && mimeType.components(separatedBy: ".").count > 2 {
-
+            
             let mimeTypeArray = mimeType.components(separatedBy: ".")
             let mimeType = mimeTypeArray[mimeTypeArray.count - 2] + "." + mimeTypeArray[mimeTypeArray.count - 1]
-
+            
             for richdocumentMimetype: String in NCGlobal.shared.capabilityRichdocumentsMimetypes {
                 if richdocumentMimetype.contains(mimeType) {
                     return true
                 }
             }
         }
-
+        
         return false
     }
-
+    
     @objc func isDirectEditing(account: String, contentType: String) -> [String] {
-
+        
         var editor: [String] = []
-
+        
         guard let results = NCManageDatabase.shared.getDirectEditingEditors(account: account) else {
             return editor
         }
-
+        
         for result: tableDirectEditingEditors in results {
             for mimetype in result.mimetypes {
                 if mimetype == contentType {
                     editor.append(result.editor)
                 }
-
+                
                 // HARDCODE
                 // https://github.com/nextcloud/text/issues/913
-
+                
                 if mimetype == "text/markdown" && contentType == "text/x-markdown" {
                     editor.append(result.editor)
                 }
@@ -194,37 +195,37 @@ class NCUtility: NSObject {
                 }
             }
         }
-
+        
         // HARDCODE
         // if editor.count == 0 {
         //    editor.append(NCGlobal.shared.editorText)
         // }
-
+        
         return Array(Set(editor))
     }
-
+    
 #if !EXTENSION
     @objc func removeAllSettings() {
-
+        
         URLCache.shared.memoryCapacity = 0
         URLCache.shared.diskCapacity = 0
-
+        
         NCManageDatabase.shared.clearDatabase(account: nil, removeAccount: true)
-
+        
         CCUtility.removeGroupDirectoryProviderStorage()
         CCUtility.removeGroupLibraryDirectory()
-
+        
         CCUtility.removeDocumentsDirectory()
         CCUtility.removeTemporaryDirectory()
-
+        
         CCUtility.createDirectoryStandard()
-
+        
         CCUtility.deleteAllChainStore()
     }
 #endif
-
+    
     @objc func permissionsContainsString(_ metadataPermissions: String, permissions: String) -> Bool {
-
+        
         for char in permissions {
             if metadataPermissions.contains(char) == false {
                 return false
@@ -232,12 +233,12 @@ class NCUtility: NSObject {
         }
         return true
     }
-
+    
     @objc func getCustomUserAgentNCText() -> String {
         let userAgent: String = CCUtility.getUserAgent()
         if UIDevice.current.userInterfaceIdiom == .phone {
             // NOTE: Hardcoded (May 2022)
-            // Tested for iPhone SE (1st), iOS 12; iPhone Pro Max, iOS 15.4
+            // Tested for iPhone SE (1st), iOS 12 iPhone Pro Max, iOS 15.4
             // 605.1.15 = WebKit build version
             // 15E148 = frozen iOS build number according to: https://chromestatus.com/feature/4558585463832576
             return userAgent + " " + "AppleWebKit/605.1.15 Mobile/15E148"
@@ -245,9 +246,9 @@ class NCUtility: NSObject {
             return userAgent
         }
     }
-
+    
     @objc func getCustomUserAgentOnlyOffice() -> String {
-
+        
         let appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString")!
         if UIDevice.current.userInterfaceIdiom == .pad {
             return "Mozilla/5.0 (iPad) Nextcloud-iOS/\(appVersion)"
@@ -255,44 +256,44 @@ class NCUtility: NSObject {
             return "Mozilla/5.0 (iPhone) Mobile Nextcloud-iOS/\(appVersion)"
         }
     }
-
+    
     @objc func pdfThumbnail(url: URL, width: CGFloat = 240) -> UIImage? {
-
+        
         guard let data = try? Data(contentsOf: url), let page = PDFDocument(data: data)?.page(at: 0) else {
             return nil
         }
-
+        
         let pageSize = page.bounds(for: .mediaBox)
         let pdfScale = width / pageSize.width
-
+        
         // Apply if you're displaying the thumbnail on screen
         let scale = UIScreen.main.scale * pdfScale
         let screenSize = CGSize(width: pageSize.width * scale, height: pageSize.height * scale)
-
+        
         return page.thumbnail(of: screenSize, for: .mediaBox)
     }
-
+    
     @objc func isQuickLookDisplayable(metadata: tableMetadata) -> Bool {
         return true
     }
-
+    
     @objc func ocIdToFileId(ocId: String?) -> String? {
-
+        
         guard let ocId = ocId else { return nil }
-
+        
         let items = ocId.components(separatedBy: "oc")
         if items.count < 2 { return nil }
         guard let intFileId = Int(items[0]) else { return nil }
         return String(intFileId)
     }
-
+    
     func getUserStatus(userIcon: String?, userStatus: String?, userMessage: String?) -> (onlineStatus: UIImage?, statusMessage: String, descriptionMessage: String) {
-
+        
         var onlineStatus: UIImage?
         var statusMessage: String = ""
         var descriptionMessage: String = ""
         var messageUserDefined: String = ""
-
+        
         if userStatus?.lowercased() == "online" {
             onlineStatus = UIImage(named: "circle_fill")!.image(color: UIColor(red: 103.0/255.0, green: 176.0/255.0, blue: 134.0/255.0, alpha: 1.0), size: 50)
             messageUserDefined = NSLocalizedString("_online_", comment: "")
@@ -311,7 +312,7 @@ class NCUtility: NSObject {
             messageUserDefined = NSLocalizedString("_invisible_", comment: "")
             descriptionMessage = NSLocalizedString("_invisible_description_", comment: "")
         }
-
+        
         if let userIcon = userIcon {
             statusMessage = userIcon + " "
         }
@@ -322,18 +323,18 @@ class NCUtility: NSObject {
         if statusMessage == "" {
             statusMessage = messageUserDefined
         }
-
+        
         return(onlineStatus, statusMessage, descriptionMessage)
     }
-
+    
     func imageFromVideo(url: URL, at time: TimeInterval) -> UIImage? {
-
+        
         let asset = AVURLAsset(url: url)
         let assetIG = AVAssetImageGenerator(asset: asset)
-
+        
         assetIG.appliesPreferredTrackTransform = true
         assetIG.apertureMode = AVAssetImageGenerator.ApertureMode.encodedPixels
-
+        
         let cmTime = CMTime(seconds: time, preferredTimescale: 60)
         let thumbnailImageRef: CGImage
         do {
@@ -342,19 +343,19 @@ class NCUtility: NSObject {
             print("Error: \(error)")
             return nil
         }
-
+        
         return UIImage(cgImage: thumbnailImageRef)
     }
-
+    
     func imageFromVideo(url: URL, at time: TimeInterval, completion: @escaping (UIImage?) -> Void) {
         DispatchQueue.global().async {
-
+            
             let asset = AVURLAsset(url: url)
             let assetIG = AVAssetImageGenerator(asset: asset)
-
+            
             assetIG.appliesPreferredTrackTransform = true
             assetIG.apertureMode = AVAssetImageGenerator.ApertureMode.encodedPixels
-
+            
             let cmTime = CMTime(seconds: time, preferredTimescale: 60)
             let thumbnailImageRef: CGImage
             do {
@@ -363,46 +364,46 @@ class NCUtility: NSObject {
                 print("Error: \(error)")
                 return completion(nil)
             }
-
+            
             DispatchQueue.main.async {
                 completion(UIImage(cgImage: thumbnailImageRef))
             }
         }
     }
-
+    
     func createImageFrom(fileNameView: String, ocId: String, etag: String, classFile: String) {
-
+        
         var originalImage, scaleImagePreview, scaleImageIcon: UIImage?
-
+        
         let fileNamePath = CCUtility.getDirectoryProviderStorageOcId(ocId, fileNameView: fileNameView)!
         let fileNamePathPreview = CCUtility.getDirectoryProviderStoragePreviewOcId(ocId, etag: etag)!
         let fileNamePathIcon = CCUtility.getDirectoryProviderStorageIconOcId(ocId, etag: etag)!
-
+        
         if CCUtility.fileProviderStorageSize(ocId, fileNameView: fileNameView) > 0 && FileManager().fileExists(atPath: fileNamePathPreview) && FileManager().fileExists(atPath: fileNamePathIcon) { return }
         if classFile != NKCommon.TypeClassFile.image.rawValue && classFile != NKCommon.TypeClassFile.video.rawValue { return }
-
+        
         if classFile == NKCommon.TypeClassFile.image.rawValue {
-
+            
             originalImage = UIImage(contentsOfFile: fileNamePath)
-
+            
             scaleImagePreview = originalImage?.resizeImage(size: CGSize(width: NCGlobal.shared.sizePreview, height: NCGlobal.shared.sizePreview))
             scaleImageIcon = originalImage?.resizeImage(size: CGSize(width: NCGlobal.shared.sizeIcon, height: NCGlobal.shared.sizeIcon))
-
+            
             try? scaleImagePreview?.jpegData(compressionQuality: 0.7)?.write(to: URL(fileURLWithPath: fileNamePathPreview))
             try? scaleImageIcon?.jpegData(compressionQuality: 0.7)?.write(to: URL(fileURLWithPath: fileNamePathIcon))
-
+            
         } else if classFile == NKCommon.TypeClassFile.video.rawValue {
-
+            
             let videoPath = NSTemporaryDirectory()+"tempvideo.mp4"
             NCUtilityFileSystem.shared.linkItem(atPath: fileNamePath, toPath: videoPath)
-
+            
             originalImage = imageFromVideo(url: URL(fileURLWithPath: videoPath), at: 0)
-
+            
             try? originalImage?.jpegData(compressionQuality: 0.7)?.write(to: URL(fileURLWithPath: fileNamePathPreview))
             try? originalImage?.jpegData(compressionQuality: 0.7)?.write(to: URL(fileURLWithPath: fileNamePathIcon))
         }
     }
-
+    
     @objc func getVersionApp(withBuild: Bool = true) -> String {
         if let dictionary = Bundle.main.infoDictionary {
             if let version = dictionary["CFBundleShortVersionString"], let build = dictionary["CFBundleVersion"] {
@@ -415,11 +416,11 @@ class NCUtility: NSObject {
         }
         return ""
     }
-
+    
     func loadImage(named imageName: String, color: UIColor = UIColor.systemGray, size: CGFloat = 50, symbolConfiguration: Any? = nil, renderingMode: UIImage.RenderingMode = .alwaysOriginal) -> UIImage {
-
+        
         var image: UIImage?
-
+        
         // see https://stackoverflow.com/questions/71764255
         let sfSymbolName = imageName.replacingOccurrences(of: "_", with: ".")
         if let symbolConfiguration = symbolConfiguration {
@@ -433,15 +434,15 @@ class NCUtility: NSObject {
         if let image = image {
             return image
         }
-
+        
         return  UIImage(named: "file")!.image(color: color, size: size)
     }
-
+    
     @objc func loadUserImage(for user: String, displayName: String?, userBaseUrl: NCUserBaseUrl) -> UIImage {
-
+        
         let fileName = userBaseUrl.userBaseUrl + "-" + user + ".png"
         let localFilePath = String(CCUtility.getDirectoryUserData()) + "/" + fileName
-
+        
         if let localImage = UIImage(contentsOfFile: localFilePath) {
             return createAvatar(image: localImage, size: 30)
         } else if let loadedAvatar = NCManageDatabase.shared.getImageAvatarLoaded(fileName: fileName) {
@@ -450,27 +451,27 @@ class NCUtility: NSObject {
             return avatarImg
         } else { return getDefaultUserIcon() }
     }
-
+    
     func getDefaultUserIcon() -> UIImage {
-            
+        
         let config = UIImage.SymbolConfiguration(pointSize: 30)
         return NCUtility.shared.loadImage(named: "person.crop.circle", symbolConfiguration: config)
     }
-
+    
     @objc func createAvatar(image: UIImage, size: CGFloat) -> UIImage {
-
+        
         var avatarImage = image
         let rect = CGRect(x: 0, y: 0, width: size, height: size)
-
+        
         UIGraphicsBeginImageContextWithOptions(rect.size, false, 3.0)
         UIBezierPath(roundedRect: rect, cornerRadius: rect.size.height).addClip()
         avatarImage.draw(in: rect)
         avatarImage = UIGraphicsGetImageFromCurrentImageContext() ?? image
         UIGraphicsEndImageContext()
-
+        
         return avatarImage
     }
-
+    
     func createAvatar(displayName: String, size: CGFloat) -> UIImage? {
         guard let initials = displayName.uppercaseInitials else {
             return nil
@@ -478,7 +479,7 @@ class NCUtility: NSObject {
         let userColor = NCGlobal.shared.usernameToColor(displayName)
         let rect = CGRect(x: 0, y: 0, width: size, height: size)
         var avatarImage: UIImage?
-
+        
         UIGraphicsBeginImageContextWithOptions(rect.size, false, 3.0)
         let context = UIGraphicsGetCurrentContext()
         UIBezierPath(roundedRect: rect, cornerRadius: rect.size.height).addClip()
@@ -493,16 +494,16 @@ class NCUtility: NSObject {
                 withAttributes: [NSAttributedString.Key.paragraphStyle: textStyle])
         avatarImage = UIGraphicsGetImageFromCurrentImageContext()
         UIGraphicsEndImageContext()
-
+        
         return avatarImage
     }
-
+    
     /*
-    Facebook's comparison algorithm:
-    */
-
+     Facebook's comparison algorithm:
+     */
+    
     func compare(tolerance: Float, expected: Data, observed: Data) throws -> Bool {
-
+        
         enum customError: Error {
             case unableToGetUIImageFromData
             case unableToGetCGImageFromData
@@ -510,7 +511,7 @@ class NCUtility: NSObject {
             case imagesHasDifferentSizes
             case unableToInitializeContext
         }
-
+        
         guard let expectedUIImage = UIImage(data: expected), let observedUIImage = UIImage(data: observed) else {
             throw customError.unableToGetUIImageFromData
         }
@@ -525,17 +526,17 @@ class NCUtility: NSObject {
         }
         let imageSize = CGSize(width: expectedCGImage.width, height: expectedCGImage.height)
         let numberOfPixels = Int(imageSize.width * imageSize.height)
-
+        
         // Checking that our `UInt32` buffer has same number of bytes as image has.
         let bytesPerRow = min(expectedCGImage.bytesPerRow, observedCGImage.bytesPerRow)
         assert(MemoryLayout<UInt32>.stride == bytesPerRow / Int(imageSize.width))
-
+        
         let expectedPixels = UnsafeMutablePointer<UInt32>.allocate(capacity: numberOfPixels)
         let observedPixels = UnsafeMutablePointer<UInt32>.allocate(capacity: numberOfPixels)
-
+        
         let expectedPixelsRaw = UnsafeMutableRawPointer(expectedPixels)
         let observedPixelsRaw = UnsafeMutableRawPointer(observedPixels)
-
+        
         let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
         guard let expectedContext = CGContext(data: expectedPixelsRaw, width: Int(imageSize.width), height: Int(imageSize.height),
                                               bitsPerComponent: expectedCGImage.bitsPerComponent, bytesPerRow: bytesPerRow,
@@ -551,13 +552,13 @@ class NCUtility: NSObject {
             observedPixels.deallocate()
             throw customError.unableToInitializeContext
         }
-
+        
         expectedContext.draw(expectedCGImage, in: CGRect(origin: .zero, size: imageSize))
         observedContext.draw(observedCGImage, in: CGRect(origin: .zero, size: imageSize))
-
+        
         let expectedBuffer = UnsafeBufferPointer(start: expectedPixels, count: numberOfPixels)
         let observedBuffer = UnsafeBufferPointer(start: observedPixels, count: numberOfPixels)
-
+        
         var isEqual = true
         if tolerance == 0 {
             isEqual = expectedBuffer.elementsEqual(observedBuffer)
@@ -574,50 +575,50 @@ class NCUtility: NSObject {
                 }
             }
         }
-
+        
         expectedPixels.deallocate()
         observedPixels.deallocate()
-
+        
         return isEqual
     }
-
+    
     func stringFromTime(_ time: CMTime) -> String {
-
+        
         let interval = Int(CMTimeGetSeconds(time))
-
+        
         let seconds = interval % 60
         let minutes = (interval / 60) % 60
         let hours = (interval / 3600)
-
+        
         if hours > 0 {
             return String(format: "%02d:%02d:%02d", hours, minutes, seconds)
         } else {
             return String(format: "%02d:%02d", minutes, seconds)
         }
     }
-
+    
     func colorNavigationController(_ navigationController: UINavigationController?, backgroundColor: UIColor, titleColor: UIColor, tintColor: UIColor?, withoutShadow: Bool) {
-
+        
         let appearance = UINavigationBarAppearance()
         appearance.titleTextAttributes = [.foregroundColor: titleColor]
         appearance.largeTitleTextAttributes = [.foregroundColor: titleColor]
-
+        
         if withoutShadow {
             appearance.shadowColor = .clear
             appearance.shadowImage = UIImage()
         }
-
+        
         if let tintColor = tintColor {
             navigationController?.navigationBar.tintColor = tintColor
         }
-
+        
         navigationController?.view.backgroundColor = backgroundColor
         navigationController?.navigationBar.barTintColor = titleColor
         navigationController?.navigationBar.standardAppearance = appearance
         navigationController?.navigationBar.compactAppearance = appearance
         navigationController?.navigationBar.scrollEdgeAppearance = appearance
     }
-
+    
     func getEncondingDataType(data: Data) -> String.Encoding? {
         if let _ = String(data: data, encoding: .utf8) {
             return .utf8
@@ -687,14 +688,14 @@ class NCUtility: NSObject {
         }
         return nil
     }
-
+    
     func SYSTEM_VERSION_LESS_THAN(version: String) -> Bool {
         return UIDevice.current.systemVersion.compare(version,
-         options: NSString.CompareOptions.numeric) == ComparisonResult.orderedAscending
+                                                      options: NSString.CompareOptions.numeric) == ComparisonResult.orderedAscending
     }
-
+    
     func getAvatarFromIconUrl(metadata: tableMetadata) -> String? {
-
+        
         var ownerId: String?
         if metadata.iconUrl.contains("http") && metadata.iconUrl.contains("avatar") {
             let splitIconUrl = metadata.iconUrl.components(separatedBy: "/")
@@ -709,7 +710,7 @@ class NCUtility: NSObject {
         }
         return ownerId
     }
-
+    
     // https://stackoverflow.com/questions/25471114/how-to-validate-an-e-mail-address-in-swift
     func isValidEmail(_ email: String) -> Bool {
         
@@ -719,11 +720,11 @@ class NCUtility: NSObject {
     }
     
     func createFilePreviewImage(ocId: String, etag: String, fileNameView: String, classFile: String, status: Int, createPreviewMedia: Bool) -> UIImage? {
-
+        
         var imagePreview: UIImage?
         let filePath = CCUtility.getDirectoryProviderStorageOcId(ocId, fileNameView: fileNameView)!
         let iconImagePath = CCUtility.getDirectoryProviderStorageIconOcId(ocId, etag: etag)!
-
+        
         if FileManager().fileExists(atPath: iconImagePath) {
             imagePreview = UIImage(contentsOfFile: iconImagePath)
         } else if !createPreviewMedia {
@@ -743,10 +744,10 @@ class NCUtility: NSObject {
                 } catch { }
             }
         }
-
+        
         return imagePreview
     }
-
+    
     func isDirectoryE2EE(serverUrl: String, userBase: NCUserBaseUrl) -> Bool {
         return isDirectoryE2EE(account: userBase.account, urlBase: userBase.urlBase, userId: userBase.userId, serverUrl: serverUrl)
     }
@@ -794,11 +795,11 @@ class NCUtility: NSObject {
     }
 
     func createViewImageAndText(image: UIImage, title: String? = nil) -> UIView {
-
+        
         let imageView = UIImageView()
         let titleView = UIView()
         let label = UILabel()
-
+        
         if let title = title {
             label.text = title + " "
         } else {
@@ -807,15 +808,15 @@ class NCUtility: NSObject {
         label.sizeToFit()
         label.center = titleView.center
         label.textAlignment = NSTextAlignment.center
-
+        
         imageView.image = image
-
+        
         let imageAspect = (imageView.image?.size.width ?? 0) / (imageView.image?.size.height ?? 0)
         let imageX = label.frame.origin.x - label.frame.size.height * imageAspect
         let imageY = label.frame.origin.y
         let imageWidth = label.frame.size.height * imageAspect
         let imageHeight = label.frame.size.height
-
+        
         if title != nil {
             imageView.frame = CGRect(x: imageX, y: imageY, width: imageWidth, height: imageHeight)
             titleView.addSubview(label)
@@ -823,12 +824,26 @@ class NCUtility: NSObject {
             imageView.frame = CGRect(x: imageX / 2, y: imageY, width: imageWidth, height: imageHeight)
         }
         imageView.contentMode = UIView.ContentMode.scaleAspectFit
-
+        
         titleView.addSubview(imageView)
         titleView.sizeToFit()
-
+        
         return titleView
     }
+
+    func getLocation(latitude: Double, longitude: Double, completion: @escaping (String?) -> Void) {
+        let geocoder = CLGeocoder()
+        let llocation = CLLocation(latitude: latitude, longitude: longitude)
+
+        geocoder.reverseGeocodeLocation(llocation) { placemarks, error in
+            if error == nil, let placemark = placemarks?.first {
+                let locationComponents: [String] = [placemark.name, placemark.locality, placemark.country]
+                    .compactMap{$0}
+
+                completion(locationComponents.joined(separator: ", "))
+            }
+        }
+    }
 }
 
 

+ 0 - 1
iOSClient/Viewer/NCViewer.swift

@@ -42,7 +42,6 @@ class NCViewer: NSObject {
         self.metadatas = metadatas
 
         var editor = editor
-        var xxxxxxx = NextcloudKit.shared.nkCommonInstance.getInternalTypeIdentifier(typeIdentifier: metadata.contentType)
 
         // URL
         if metadata.classFile == NKCommon.TypeClassFile.url.rawValue {

+ 60 - 77
iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift

@@ -30,10 +30,13 @@ import MobileVLCKit
 import JGProgressHUD
 import Alamofire
 
-class NCViewerMedia: UIViewController {
+public protocol NCViewerMediaViewDelegate: AnyObject {
+    func didOpenDetail()
+    func didCloseDetail()
+}
 
+class NCViewerMedia: UIViewController {
     @IBOutlet weak var detailViewTopConstraint: NSLayoutConstraint!
-    @IBOutlet weak var detailViewHeighConstraint: NSLayoutConstraint!
     @IBOutlet weak var imageViewTopConstraint: NSLayoutConstraint!
     @IBOutlet weak var imageViewBottomConstraint: NSLayoutConstraint!
     @IBOutlet weak var scrollView: UIScrollView!
@@ -55,6 +58,7 @@ class NCViewerMedia: UIViewController {
     var doubleTapGestureRecognizer: UITapGestureRecognizer = UITapGestureRecognizer()
     var imageViewConstraint: CGFloat = 0
     var isDetailViewInitializze: Bool = false
+    weak var delegate: NCViewerMediaViewDelegate?
 
     // MARK: - View Life Cycle
 
@@ -88,7 +92,7 @@ class NCViewerMedia: UIViewController {
             statusViewImage.image = nil
             statusLabel.text = ""
         }
-        
+
         if metadata.isAudioOrVideo {
 
             playerToolBar = Bundle.main.loadNibNamed("NCPlayerToolBar", owner: self, options: nil)?.first as? NCPlayerToolBar
@@ -109,11 +113,11 @@ class NCViewerMedia: UIViewController {
         preferences.drawing.foregroundColor = .white
         preferences.drawing.backgroundColor = NCBrandColor.shared.nextcloud
         preferences.drawing.textAlignment = .left
-        preferences.drawing.arrowPosition = .top
+        preferences.drawing.arrowPosition = .bottom
         preferences.drawing.cornerRadius = 10
 
-        preferences.animating.dismissTransform = CGAffineTransform(translationX: 0, y: 100)
-        preferences.animating.showInitialTransform = CGAffineTransform(translationX: 0, y: -100)
+        preferences.animating.dismissTransform = CGAffineTransform(translationX: 0, y: -15)
+        preferences.animating.showInitialTransform = CGAffineTransform(translationX: 0, y: -15)
         preferences.animating.showInitialAlpha = 0
         preferences.animating.showDuration = 0.5
         preferences.animating.dismissDuration = 0
@@ -133,7 +137,7 @@ class NCViewerMedia: UIViewController {
         super.viewWillAppear(animated)
 
         viewerMediaPage?.navigationController?.navigationBar.prefersLargeTitles = false
-        viewerMediaPage?.navigationItem.title = metadata.fileNameView
+        viewerMediaPage?.navigationItem.title = (metadata.fileNameView as NSString).deletingPathExtension
 
         if metadata.isImage, let viewerMediaPage = self.viewerMediaPage {
             if viewerMediaPage.modifiedOcId.contains(metadata.ocId) {
@@ -147,7 +151,7 @@ class NCViewerMedia: UIViewController {
         super.viewDidAppear(animated)
 
         viewerMediaPage?.clearCommandCenter()
-        
+
         if metadata.isAudioOrVideo {
             if let ncplayer = self.ncplayer {
                 if ncplayer.url == nil {
@@ -207,6 +211,7 @@ class NCViewerMedia: UIViewController {
         }
 
         NotificationCenter.default.addObserver(self, selector: #selector(openDetail(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterOpenMediaDetail), object: nil)
+        NotificationCenter.default.addObserver(self, selector: #selector(closeDetail(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadStartFile), object: nil)
     }
 
     override func viewWillDisappear(_ animated: Bool) {
@@ -225,6 +230,10 @@ class NCViewerMedia: UIViewController {
 
     override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
 
+        if UIDevice.current.orientation.isValidInterfaceOrientation {
+            closeDetail()
+        }
+
         self.tipView?.dismiss()
         if metadata.isVideo {
             self.imageVideoContainer.isHidden = true
@@ -237,7 +246,7 @@ class NCViewerMedia: UIViewController {
             self.scrollView.zoom(to: CGRect(x: 0, y: 0, width: self.scrollView.bounds.width, height: self.scrollView.bounds.height), animated: false)
             self.view.layoutIfNeeded()
             UIView.animate(withDuration: context.transitionDuration) {
-                if self.detailView.isShow() {
+                if self.detailView.isShown {
                     self.openDetail()
                 }
             }
@@ -252,9 +261,8 @@ class NCViewerMedia: UIViewController {
     // MARK: - Tip
 
     func showTip() {
-
-        if !NCManageDatabase.shared.tipExists(NCGlobal.shared.tipNCViewerMediaDetailView), let view = self.navigationController?.navigationBar {
-            self.tipView?.show(forView: view)
+        if !NCManageDatabase.shared.tipExists(NCGlobal.shared.tipNCViewerMediaDetailView) {
+            self.tipView?.show(forView: detailView)
         }
     }
 
@@ -274,14 +282,6 @@ class NCViewerMedia: UIViewController {
                     NCNetworking.shared.download(metadata: metadata, selector: "") { _, _ in }
                 }
             }
-
-            NCNetworking.shared.download(metadata: metadata, selector: "") { _, _ in
-                let image = getImageMetadata(metadata)
-                if self.metadata.ocId == metadata.ocId {
-                    self.image = image
-                    self.imageVideoContainer.image = image
-                }
-            }
         }
 
         // Get image
@@ -382,7 +382,7 @@ class NCViewerMedia: UIViewController {
 
     @objc func didDoubleTapWith(gestureRecognizer: UITapGestureRecognizer) {
 
-        guard metadata.isImage, !detailView.isShow()  else { return }
+        guard metadata.isImage, !detailView.isShown else { return }
 
         let pointInView = gestureRecognizer.location(in: self.imageVideoContainer)
         var newZoomScale = self.scrollView.maximumZoomScale
@@ -406,20 +406,8 @@ class NCViewerMedia: UIViewController {
         let currentLocation = gestureRecognizer.translation(in: self.view)
 
         switch gestureRecognizer.state {
-
-        case .began:
-
-//        let velocity = gestureRecognizer.velocity(in: self.view)
-
-//            gesture moving Up
-//            if velocity.y < 0 {
-
-//            }
-            break
-
         case .ended:
-
-            if detailView.isShow() {
+            if detailView.isShown {
                 self.imageViewTopConstraint.constant = -imageViewConstraint
                 self.imageViewBottomConstraint.constant = imageViewConstraint
             } else {
@@ -428,7 +416,6 @@ class NCViewerMedia: UIViewController {
             }
 
         case .changed:
-
             imageViewTopConstraint.constant = (currentLocation.y - imageViewConstraint)
             imageViewBottomConstraint.constant = -(currentLocation.y - imageViewConstraint)
 
@@ -459,44 +446,39 @@ class NCViewerMedia: UIViewController {
     }
 }
 
-// MARK: -
-
 extension NCViewerMedia {
-
     @objc func openDetail(_ notification: NSNotification) {
-
         if let userInfo = notification.userInfo as NSDictionary?, let ocId = userInfo["ocId"] as? String, ocId == metadata.ocId {
             openDetail()
         }
     }
 
-    private func openDetail() {
+    @objc func closeDetail(_ notification: NSNotification) {
+        closeDetail()
+    }
 
+    func toggleDetail () {
+        detailView.isShown ? closeDetail() : openDetail()
+    }
+
+    private func openDetail() {
+        delegate?.didOpenDetail()
         self.dismissTip()
-        
-        CCUtility.setExif(metadata) { latitude, longitude, location, date, lensModel in
 
-            if latitude != -1 && latitude != 0 && longitude != -1 && longitude != 0 {
-                self.detailViewHeighConstraint.constant = self.view.bounds.height / 2
-            } else {
-                self.detailViewHeighConstraint.constant = 170
-            }
+        statusLabel.isHidden = true
+        statusViewImage.isHidden = true
+
+        NCUtility.shared.getExif(metadata: metadata) { exif in
             self.view.layoutIfNeeded()
-            self.detailView.show(
-                metadata: self.metadata,
-                image: self.image,
-                textColor: self.viewerMediaPage?.textColor,
-                mediaMetadata: (latitude: latitude, longitude: longitude, location: location, date: date, lensModel: lensModel),
-                ncplayer: self.ncplayer,
-                delegate: self)
-                
+            self.showDetailView(exif: exif)
+
             if let image = self.imageVideoContainer.image {
                 let ratioW = self.imageVideoContainer.frame.width / image.size.width
                 let ratioH = self.imageVideoContainer.frame.height / image.size.height
-                let ratio = ratioW < ratioH ? ratioW : ratioH
+                let ratio = min(ratioW, ratioH)
                 let imageHeight = image.size.height * ratio
-                let VideoContainerHeight = self.imageVideoContainer.frame.height * ratio
-                let height = max(imageHeight, VideoContainerHeight)
+                let imageContainerHeight = self.imageVideoContainer.frame.height * ratio
+                let height = max(imageHeight, imageContainerHeight)
                 self.imageViewConstraint = self.detailView.frame.height - ((self.view.frame.height - height) / 2) + self.view.safeAreaInsets.bottom
                 if self.imageViewConstraint < 0 { self.imageViewConstraint = 0 }
             }
@@ -504,9 +486,8 @@ extension NCViewerMedia {
             UIView.animate(withDuration: 0.3) {
                 self.imageViewTopConstraint.constant = -self.imageViewConstraint
                 self.imageViewBottomConstraint.constant = self.imageViewConstraint
-                self.detailViewTopConstraint.constant = self.detailViewHeighConstraint.constant
+                self.detailViewTopConstraint.constant = self.detailView.frame.height
                 self.view.layoutIfNeeded()
-            } completion: { _ in
             }
 
             self.scrollView.pinchGestureRecognizer?.isEnabled = false
@@ -514,39 +495,42 @@ extension NCViewerMedia {
     }
 
     private func closeDetail() {
-
+        delegate?.didCloseDetail()
         self.detailView.hide()
         imageViewConstraint = 0
 
+        statusLabel.isHidden = false
+        statusViewImage.isHidden = false
+
         UIView.animate(withDuration: 0.3) {
             self.imageViewTopConstraint.constant = 0
             self.imageViewBottomConstraint.constant = 0
             self.detailViewTopConstraint.constant = 0
             self.view.layoutIfNeeded()
-        } completion: { _ in
         }
 
         scrollView.pinchGestureRecognizer?.isEnabled = true
     }
 
-    func reloadDetail() {
+    private func showDetailView(exif: ExifData) {
+        self.detailView.show(
+            metadata: self.metadata,
+            image: self.image,
+            textColor: self.viewerMediaPage?.textColor,
+            exif: exif,
+            ncplayer: self.ncplayer,
+            delegate: self)
+    }
 
-        if self.detailView.isShow() {
-            CCUtility.setExif(metadata) { (latitude, longitude, location, date, lensModel) in
-                self.detailView.show(
-                    metadata: self.metadata,
-                    image: self.image,
-                    textColor: self.viewerMediaPage?.textColor,
-                    mediaMetadata: (latitude: latitude, longitude: longitude, location: location, date: date, lensModel: lensModel),
-                    ncplayer: self.ncplayer,
-                    delegate: self)
+    func reloadDetail() {
+        if self.detailView.isShown {
+            NCUtility.shared.getExif(metadata: metadata) { exif in
+                self.showDetailView(exif: exif)
             }
         }
     }
 }
 
-// MARK: -
-
 extension NCViewerMedia: UIScrollViewDelegate {
 
     func viewForZooming(in scrollView: UIScrollView) -> UIView? {
@@ -563,9 +547,9 @@ extension NCViewerMedia: UIScrollViewDelegate {
                 let ratio = ratioW < ratioH ? ratioW : ratioH
                 let newWidth = image.size.width * ratio
                 let newHeight = image.size.height * ratio
-                let conditionLeft = newWidth*scrollView.zoomScale > imageVideoContainer.frame.width
+                let conditionLeft = newWidth * scrollView.zoomScale > imageVideoContainer.frame.width
                 let left = 0.5 * (conditionLeft ? newWidth - imageVideoContainer.frame.width : (scrollView.frame.width - scrollView.contentSize.width))
-                let conditioTop = newHeight*scrollView.zoomScale > imageVideoContainer.frame.height
+                let conditioTop = newHeight * scrollView.zoomScale > imageVideoContainer.frame.height
 
                 let top = 0.5 * (conditioTop ? newHeight - imageVideoContainer.frame.height : (scrollView.frame.height - scrollView.contentSize.height))
 
@@ -583,7 +567,6 @@ extension NCViewerMedia: UIScrollViewDelegate {
 extension NCViewerMedia: NCViewerMediaDetailViewDelegate {
 
     func downloadFullResolution() {
-        closeDetail()
         NCNetworking.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorOpenDetail) { _, _ in }
     }
 }

+ 155 - 117
iOSClient/Viewer/NCViewerMedia/NCViewerMediaDetailView.swift

@@ -24,52 +24,55 @@
 import UIKit
 import MapKit
 import NextcloudKit
-
-typealias NCImageMetadata = (latitude: Double, longitude: Double, location: String?, date: Date?, lensModel: String?)
+import Alamofire
 
 public protocol NCViewerMediaDetailViewDelegate: AnyObject {
     func downloadFullResolution()
 }
 
 class NCViewerMediaDetailView: UIView {
-
-    @IBOutlet weak var separator: UIView!
-    @IBOutlet weak var sizeLabel: UILabel!
-    @IBOutlet weak var sizeValue: UILabel!
-    @IBOutlet weak var dateLabel: UILabel!
-    @IBOutlet weak var dateValue: UILabel!
-    @IBOutlet weak var dimLabel: UILabel!
-    @IBOutlet weak var dimValue: UILabel!
-    @IBOutlet weak var lensModelLabel: UILabel!
-    @IBOutlet weak var lensModelValue: UILabel!
-    @IBOutlet weak var messageButton: UIButton!
     @IBOutlet weak var mapContainer: UIView!
-    @IBOutlet weak var locationButton: UIButton!
-
-    var latitude: Double = 0
-    var longitude: Double = 0
-    var location: String?
-    var date: Date?
-    var lensModel: String?
-    var metadata: tableMetadata?
-    var mapView: MKMapView?
-    var ncplayer: NCPlayer?
+    @IBOutlet weak var outerMapContainer: UIView!
+    @IBOutlet weak var dayLabel: UILabel!
+    @IBOutlet weak var dateLabel: UILabel!
+    @IBOutlet weak var noDateLabel: UILabel!
+    @IBOutlet weak var timeLabel: UILabel!
+    @IBOutlet weak var nameLabel: UILabel!
+    @IBOutlet weak var modelLabel: UILabel!
+    @IBOutlet weak var deviceContainer: UIView!
+    @IBOutlet weak var outerContainer: UIView!
+    @IBOutlet weak var lensLabel: UILabel!
+    @IBOutlet weak var megaPixelLabel: UILabel!
+    @IBOutlet weak var megaPixelLabelDivider: UILabel!
+    @IBOutlet weak var resolutionLabel: UILabel!
+    @IBOutlet weak var resolutionLabelDivider: UILabel!
+    @IBOutlet weak var sizeLabel: UILabel!
+    @IBOutlet weak var extensionLabel: UILabel!
+    @IBOutlet weak var livePhotoImageView: UIImageView!
+    @IBOutlet weak var isoLabel: UILabel!
+    @IBOutlet weak var lensSizeLabel: UILabel!
+    @IBOutlet weak var exposureValueLabel: UILabel!
+    @IBOutlet weak var apertureLabel: UILabel!
+    @IBOutlet weak var shutterSpeedLabel: UILabel!
+    @IBOutlet weak var locationLabel: UILabel!
+    @IBOutlet weak var downloadImageButton: UIButton!
+    @IBOutlet weak var downloadImageLabel: UILabel!
+    @IBOutlet weak var downloadImageButtonContainer: UIStackView!
+    @IBOutlet weak var dateContainer: UIView!
+    @IBOutlet weak var lensInfoStackViewLeadingConstraint: NSLayoutConstraint!
+    @IBOutlet weak var lensInfoStackViewTrailingConstraint: NSLayoutConstraint!
+    @IBOutlet weak var lensInfoLeadingFakePadding: UILabel!
+    @IBOutlet weak var lensInfoTrailingFakePadding: UILabel!
+
+    private var metadata: tableMetadata?
+    private var mapView: MKMapView?
+    private var ncplayer: NCPlayer?
     weak var delegate: NCViewerMediaDetailViewDelegate?
 
-    override func awakeFromNib() {
-        super.awakeFromNib()
-
-        separator.backgroundColor = .separator
-        sizeLabel.text = ""
-        sizeValue.text = ""
-        dateLabel.text = ""
-        dateValue.text = ""
-        dimLabel.text = ""
-        dimValue.text = ""
-        lensModelLabel.text = ""
-        lensModelValue.text = ""
-        messageButton.setTitle("", for: .normal)
-        locationButton.setTitle("", for: .normal)
+    private var exif: ExifData?
+
+    var isShown: Bool {
+        return !self.isHidden
     }
 
     deinit {
@@ -82,134 +85,169 @@ class NCViewerMediaDetailView: UIView {
     func show(metadata: tableMetadata,
               image: UIImage?,
               textColor: UIColor?,
-              mediaMetadata: NCImageMetadata,
+              exif: ExifData,
               ncplayer: NCPlayer?,
               delegate: NCViewerMediaDetailViewDelegate?) {
 
         self.metadata = metadata
-        self.latitude = mediaMetadata.latitude
-        self.longitude = mediaMetadata.longitude
-        self.location = mediaMetadata.location
-        self.date = mediaMetadata.date
-        self.lensModel = mediaMetadata.lensModel
+        self.exif = exif
         self.ncplayer = ncplayer
         self.delegate = delegate
 
-        if mapView == nil && (latitude != -1 && latitude != 0 && longitude != -1 && longitude != 0) {
+        outerMapContainer.isHidden = true
+        downloadImageButtonContainer.isHidden = true
+
+        if let latitude = exif.latitude, let longitude = exif.longitude, NCNetworking.shared.networkReachability != .notReachable {
+            // We hide the map view on phones in landscape (aka compact height), since there is too little space to fit all of it.
+            mapContainer.isHidden = traitCollection.verticalSizeClass == .compact
 
+            outerMapContainer.isHidden = false
             let annotation = MKPointAnnotation()
             annotation.coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
+            let region = MKCoordinateRegion(center: annotation.coordinate, latitudinalMeters: 500, longitudinalMeters: 500)
+
+            if mapView == nil, mapView?.region.center.latitude != latitude, mapView?.region.center.longitude != longitude {
+                let mapView = MKMapView()
+                self.mapView = mapView
+                mapContainer.subviews.forEach { $0.removeFromSuperview() }
+                self.mapContainer.addSubview(mapView)
+                mapView.translatesAutoresizingMaskIntoConstraints = false
+                NSLayoutConstraint.activate([
+                    mapView.topAnchor.constraint(equalTo: self.mapContainer.topAnchor),
+                    mapView.bottomAnchor.constraint(equalTo: self.mapContainer.bottomAnchor),
+                    mapView.leadingAnchor.constraint(equalTo: self.mapContainer.leadingAnchor),
+                    mapView.trailingAnchor.constraint(equalTo: self.mapContainer.trailingAnchor)
+                ])
+
+                mapView.isZoomEnabled = true
+                mapView.isScrollEnabled = false
+                mapView.isUserInteractionEnabled = false
+                mapView.addAnnotation(annotation)
+
+                mapView.setRegion(region, animated: false)
+            }
+        }
 
-            let mapView = MKMapView()
-            self.mapView = mapView
-            self.mapContainer.addSubview(mapView)
-
-            mapView.translatesAutoresizingMaskIntoConstraints = false
-            NSLayoutConstraint.activate([
-                mapView.topAnchor.constraint(equalTo: self.mapContainer.topAnchor),
-                mapView.bottomAnchor.constraint(equalTo: self.mapContainer.bottomAnchor),
-                mapView.leadingAnchor.constraint(equalTo: self.mapContainer.leadingAnchor),
-                mapView.trailingAnchor.constraint(equalTo: self.mapContainer.trailingAnchor)
-            ])
-
-            mapView.layer.cornerRadius = 6
-            mapView.isZoomEnabled = true
-            mapView.isScrollEnabled = false
-            mapView.isUserInteractionEnabled = false
-            mapView.addAnnotation(annotation)
-            mapView.setRegion(MKCoordinateRegion(center: annotation.coordinate, latitudinalMeters: 500, longitudinalMeters: 500), animated: false)
+        if let make = exif.make, let model = exif.model, let lensModel = exif.lensModel {
+            modelLabel.text = "\(make) \(model)"
+            lensLabel.text = lensModel
+                .replacingOccurrences(of: make, with: "")
+                .replacingOccurrences(of: model, with: "")
+                .replacingOccurrences(of: "f/", with: "ƒ").trimmingCharacters(in: .whitespacesAndNewlines).firstUppercased
+        } else {
+            modelLabel.text = NSLocalizedString("_no_camera_information_", comment: "")
+            lensLabel.text = NSLocalizedString("_no_lens_information_", comment: "")
         }
 
-        // Size
-        sizeLabel.text = NSLocalizedString("_size_", comment: "")
-        sizeValue.text = CCUtility.transformedSize(metadata.size)
-        sizeValue.textColor = textColor
+        nameLabel.text = (metadata.fileNameView as NSString).deletingPathExtension
+        sizeLabel.text = CCUtility.transformedSize(metadata.size)
+
+        if let shutterSpeedApex = exif.shutterSpeedApex {
+            prepareLensInfoViewsForData()
+            shutterSpeedLabel.text = "1/\(Int(pow(2, shutterSpeedApex))) s"
+        }
+
+        if let iso = exif.iso {
+            prepareLensInfoViewsForData()
+            isoLabel.text = "ISO \(iso)"
+        }
+
+        if let apertureValue = exif.apertureValue {
+            apertureLabel.text = "ƒ\(apertureValue)"
+        }
+
+        if let exposureValue = exif.exposureValue {
+            exposureValueLabel.text = "\(exposureValue) ev"
+        }
+
+        if let lensLength = exif.lensLength {
+            lensSizeLabel.text = "\(lensLength) mm"
+        }
+
+        if let date = exif.date {
+            dateContainer.isHidden = false
+            noDateLabel.isHidden = true
 
-        // Date
-        if let date = date {
             let formatter = DateFormatter()
-            formatter.dateStyle = .full
-            formatter.timeStyle = .medium
+
+            formatter.dateFormat = "EEEE"
+            let dayString = formatter.string(from: date as Date)
+            dayLabel.text = dayString
+
+            formatter.dateFormat = "d MMM yyyy"
             let dateString = formatter.string(from: date as Date)
+            dateLabel.text = dateString
 
-            dateLabel.text = NSLocalizedString("_date_", comment: "")
-            dateValue.text = dateString
+            formatter.dateFormat = "HH:mm"
+            let timeString = formatter.string(from: date as Date)
+            timeLabel.text = timeString
         } else {
-            dateLabel.text = NSLocalizedString("_date_", comment: "")
-            dateValue.text = NSLocalizedString("_not_available_", comment: "")
+            noDateLabel.text = NSLocalizedString("_no_date_information_", comment: "")
         }
-        dateValue.textColor = textColor
 
-        // Dimension
-        if let image = image {
-            dimLabel.text = NSLocalizedString("_resolution_", comment: "")
-            dimValue.text = "\(Int(image.size.width)) x \(Int(image.size.height))"
+        if let height = exif.height, let width = exif.width {
+            megaPixelLabel.isHidden = false
+            megaPixelLabelDivider.isHidden = false
+            resolutionLabel.isHidden = false
+            resolutionLabelDivider.isHidden = false
+
+            resolutionLabel.text = "\(width) x \(height)"
+
+            let megaPixels: Double = Double(width * height) / 1000000
+            megaPixelLabel.text = megaPixels < 1 ? String(format: "%.1f MP", megaPixels) : "\(Int(megaPixels)) MP"
         }
-        dimValue.textColor = textColor
 
-        // Model
-        if let lensModel = lensModel {
-            lensModelLabel.text = NSLocalizedString("_model_", comment: "")
-            lensModelValue.text = lensModel
-            lensModelValue.textColor = textColor
+        extensionLabel.text = metadata.fileExtension.uppercased()
+
+        if exif.location?.isEmpty == false {
+            locationLabel.text = exif.location
         }
 
-        // Message
-        if metadata.isImage && !CCUtility.fileProviderStorageExists(metadata) && metadata.session.isEmpty {
-            messageButton.setTitle(NSLocalizedString("_try_download_full_resolution_", comment: ""), for: .normal)
-            messageButton.isHidden = false
-        } else {
-            messageButton.setTitle("", for: .normal)
-            messageButton.isHidden = true
+        if metadata.livePhoto {
+            livePhotoImageView.isHidden = false
         }
 
-        // Location
-        if let location = location {
-            locationButton.setTitle(location, for: .normal)
-            locationButton.isHidden = false
-        } else {
-            locationButton.setTitle("", for: .normal)
-            locationButton.isHidden = true
+        if metadata.isImage && !CCUtility.fileProviderStorageExists(metadata) && metadata.session.isEmpty {
+            downloadImageButton.setTitle(NSLocalizedString("_try_download_full_resolution_", comment: ""), for: .normal)
+            downloadImageLabel.text = NSLocalizedString("_full_resolution_image_info_", comment: "")
+            downloadImageButtonContainer.isHidden = false
         }
 
         self.isHidden = false
+        layoutIfNeeded()
     }
 
     func hide() {
         self.isHidden = true
     }
 
-    func isShow() -> Bool {
-        return !self.isHidden
+    private func prepareLensInfoViewsForData() {
+        lensInfoLeadingFakePadding.isHidden = true
+        lensInfoTrailingFakePadding.isHidden = true
+        lensInfoStackViewLeadingConstraint.constant = 5
+        lensInfoStackViewTrailingConstraint.constant = 5
     }
 
     // MARK: - Action
 
     @IBAction func touchLocation(_ sender: Any) {
+        guard let latitude = exif?.latitude, let longitude = exif?.longitude else { return }
 
-        guard latitude != -1, latitude != 0, longitude != -1, longitude != 0 else { return }
-
-        let latitude: CLLocationDegrees = self.latitude
-        let longitude: CLLocationDegrees = self.longitude
+        let latitudeDeg: CLLocationDegrees = latitude
+        let longitudeDeg: CLLocationDegrees = longitude
 
-        let regionDistance: CLLocationDistance = 10000
-        let coordinates = CLLocationCoordinate2DMake(latitude, longitude)
-        let regionSpan = MKCoordinateRegion(center: coordinates, latitudinalMeters: regionDistance, longitudinalMeters: regionDistance)
-        let options = [
-            MKLaunchOptionsMapCenterKey: NSValue(mkCoordinate: regionSpan.center),
-            MKLaunchOptionsMapSpanKey: NSValue(mkCoordinateSpan: regionSpan.span)
-        ]
+        let coordinates = CLLocationCoordinate2DMake(latitudeDeg, longitudeDeg)
         let placemark = MKPlacemark(coordinate: coordinates, addressDictionary: nil)
         let mapItem = MKMapItem(placemark: placemark)
-        mapItem.name = location
-        mapItem.openInMaps(launchOptions: options)
-    }
 
-    @IBAction func touchFavorite(_ sender: Any) {
+        if let location = exif?.location {
+            mapItem.name = location
+        }
 
+        mapItem.openInMaps()
     }
 
-    @IBAction func touchMessage(_ sender: Any) {
+    @IBAction func touchDownload(_ sender: Any) {
         delegate?.downloadFullResolution()
     }
 }

+ 454 - 160
iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.storyboard

@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="ne8-hS-cp3">
-    <device id="retina5_5" orientation="portrait" appearance="light"/>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22113.3" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="ne8-hS-cp3">
+    <device id="retina6_72" orientation="portrait" appearance="light"/>
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22089.1"/>
         <capability name="Safe area layout guides" minToolsVersion="9.0"/>
         <capability name="System colors in document resources" minToolsVersion="11.0"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@@ -18,17 +18,17 @@
                         <viewControllerLayoutGuide type="bottom" id="baP-ZX-MR4"/>
                     </layoutGuides>
                     <view key="view" opaque="NO" contentMode="scaleToFill" id="wvY-tB-6ZK">
-                        <rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
+                        <rect key="frame" x="0.0" y="0.0" width="430" height="932"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <subviews>
                             <containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fpt-Rz-5fT">
-                                <rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
+                                <rect key="frame" x="0.0" y="0.0" width="430" height="932"/>
                                 <connections>
                                     <segue destination="A4d-OP-AMT" kind="embed" id="sDx-bX-1Jt"/>
                                 </connections>
                             </containerView>
                             <progressView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" progress="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="sD9-1i-ZdY">
-                                <rect key="frame" x="0.0" y="20" width="414" height="1"/>
+                                <rect key="frame" x="0.0" y="59" width="430" height="1"/>
                                 <constraints>
                                     <constraint firstAttribute="height" constant="1" id="F4E-lI-3jZ"/>
                                 </constraints>
@@ -73,181 +73,439 @@
                         <viewControllerLayoutGuide type="bottom" id="zwn-Sc-mqc"/>
                     </layoutGuides>
                     <view key="view" contentMode="scaleToFill" id="fIE-H6-KKc">
-                        <rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
+                        <rect key="frame" x="0.0" y="0.0" width="430" height="932"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <subviews>
                             <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" maximumZoomScale="4" translatesAutoresizingMaskIntoConstraints="NO" id="CdQ-LC-Trx">
-                                <rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
+                                <rect key="frame" x="0.0" y="0.0" width="430" height="932"/>
                                 <subviews>
                                     <imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="networkInProgress" translatesAutoresizingMaskIntoConstraints="NO" id="kPV-JM-UnM">
-                                        <rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
+                                        <rect key="frame" x="0.0" y="0.0" width="430" height="932"/>
                                     </imageView>
                                     <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="2AU-85-K8y">
-                                        <rect key="frame" x="10" y="30" width="30" height="30"/>
+                                        <rect key="frame" x="10" y="69" width="30" height="30"/>
                                         <constraints>
                                             <constraint firstAttribute="height" constant="30" id="l1v-vA-4gG"/>
                                             <constraint firstAttribute="width" constant="30" id="mSt-o6-S1g"/>
                                         </constraints>
                                     </imageView>
                                     <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="DAi-gz-qGP">
-                                        <rect key="frame" x="50" y="36.666666666666664" width="344" height="17"/>
+                                        <rect key="frame" x="50" y="75.666666666666671" width="360" height="17"/>
                                         <fontDescription key="fontDescription" type="system" pointSize="14"/>
                                         <color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                         <nil key="highlightedColor"/>
                                     </label>
-                                    <view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="P8R-4f-zAl" customClass="NCViewerMediaDetailView" customModule="Nextcloud" customModuleProvider="target">
-                                        <rect key="frame" x="0.0" y="336" width="414" height="400"/>
+                                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="P8R-4f-zAl" customClass="NCViewerMediaDetailView" customModule="Nextcloud" customModuleProvider="target">
+                                        <rect key="frame" x="0.0" y="398" width="430" height="455.33333333333326"/>
                                         <subviews>
-                                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="HRq-3M-yeb">
-                                                <rect key="frame" x="15" y="20" width="384" height="1"/>
-                                                <color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                                                <constraints>
-                                                    <constraint firstAttribute="height" constant="1" id="X4S-cr-F2P"/>
-                                                </constraints>
-                                            </view>
-                                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="dJP-ZX-iug">
-                                                <rect key="frame" x="15" y="150" width="384" height="222"/>
-                                                <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                                                <gestureRecognizers/>
-                                                <connections>
-                                                    <outletCollection property="gestureRecognizers" destination="fvW-pC-4g1" appends="YES" id="gVL-xL-CmY"/>
-                                                </connections>
-                                            </view>
-                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="size" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WXS-Lw-DkI">
-                                                <rect key="frame" x="15" y="36" width="80" height="16"/>
-                                                <constraints>
-                                                    <constraint firstAttribute="width" constant="80" id="DLa-7b-rDS"/>
-                                                </constraints>
-                                                <fontDescription key="fontDescription" type="system" pointSize="13"/>
-                                                <color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                                                <nil key="highlightedColor"/>
-                                            </label>
-                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="size value" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XLb-0a-du9">
-                                                <rect key="frame" x="105" y="36" width="294" height="16"/>
-                                                <fontDescription key="fontDescription" type="system" pointSize="13"/>
-                                                <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                                                <nil key="highlightedColor"/>
-                                            </label>
-                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="date" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Son-CZ-zFa">
-                                                <rect key="frame" x="15" y="62" width="80" height="16"/>
-                                                <constraints>
-                                                    <constraint firstAttribute="width" constant="80" id="e83-SZ-3FA"/>
-                                                </constraints>
-                                                <fontDescription key="fontDescription" type="system" pointSize="13"/>
-                                                <color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                                                <nil key="highlightedColor"/>
-                                            </label>
-                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="date value" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hBd-KD-Jq5">
-                                                <rect key="frame" x="105" y="62" width="294" height="16"/>
-                                                <fontDescription key="fontDescription" type="system" pointSize="13"/>
-                                                <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                                                <nil key="highlightedColor"/>
-                                            </label>
-                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="dim" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uaE-Lv-t0Q">
-                                                <rect key="frame" x="15" y="88" width="80" height="16"/>
-                                                <constraints>
-                                                    <constraint firstAttribute="width" constant="80" id="iwq-Fq-8U0"/>
-                                                </constraints>
-                                                <fontDescription key="fontDescription" type="system" pointSize="13"/>
-                                                <color key="textColor" white="0.33333333329999998" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                                                <nil key="highlightedColor"/>
-                                            </label>
-                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="dim value" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="n1C-OB-gq2">
-                                                <rect key="frame" x="105" y="88" width="294" height="16"/>
-                                                <fontDescription key="fontDescription" type="system" pointSize="13"/>
-                                                <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                                                <nil key="highlightedColor"/>
-                                            </label>
-                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="lens" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uYI-ic-d8g">
-                                                <rect key="frame" x="15" y="114" width="80" height="16"/>
-                                                <constraints>
-                                                    <constraint firstAttribute="width" constant="80" id="egy-z4-Im6"/>
-                                                </constraints>
-                                                <fontDescription key="fontDescription" type="system" pointSize="13"/>
-                                                <color key="textColor" white="0.33333333329999998" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                                                <nil key="highlightedColor"/>
-                                            </label>
-                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="lens value" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ix8-uQ-chU">
-                                                <rect key="frame" x="105" y="114" width="294" height="16"/>
-                                                <fontDescription key="fontDescription" type="system" pointSize="13"/>
-                                                <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                                                <nil key="highlightedColor"/>
-                                            </label>
-                                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" buttonType="system" lineBreakMode="tailTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="GWF-lf-c8t">
-                                                <rect key="frame" x="15" y="140" width="384" height="30"/>
-                                                <constraints>
-                                                    <constraint firstAttribute="height" constant="30" id="ehZ-sm-6Om"/>
-                                                </constraints>
-                                                <fontDescription key="fontDescription" type="system" pointSize="13"/>
-                                                <state key="normal" title="message"/>
-                                                <connections>
-                                                    <action selector="touchMessage:" destination="P8R-4f-zAl" eventType="touchUpInside" id="pa6-KU-ig2"/>
-                                                </connections>
-                                            </button>
-                                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="oov-9f-Oeu">
-                                                <rect key="frame" x="15" y="372" width="384" height="28"/>
-                                                <fontDescription key="fontDescription" type="system" pointSize="13"/>
-                                                <state key="normal" title="location"/>
-                                                <connections>
-                                                    <action selector="touchLocation:" destination="P8R-4f-zAl" eventType="touchUpInside" id="Z9s-pM-WsS"/>
-                                                </connections>
-                                            </button>
+                                            <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" spacing="12" translatesAutoresizingMaskIntoConstraints="NO" id="Z0h-yS-myv">
+                                                <rect key="frame" x="15" y="10" width="400" height="435.33333333333331"/>
+                                                <subviews>
+                                                    <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="IBY-Wf-Fqo">
+                                                        <rect key="frame" x="0.0" y="0.0" width="400" height="40.666666666666664"/>
+                                                        <subviews>
+                                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="No date information" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0Mb-ss-cAE" userLabel="Day">
+                                                                <rect key="frame" x="0.0" y="0.0" width="400" height="19.333333333333332"/>
+                                                                <fontDescription key="fontDescription" type="system" pointSize="16"/>
+                                                                <nil key="textColor"/>
+                                                                <nil key="highlightedColor"/>
+                                                            </label>
+                                                            <view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="v7L-HF-dAc">
+                                                                <rect key="frame" x="0.0" y="20.333333333333314" width="400" height="20"/>
+                                                                <subviews>
+                                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Friday" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="f9E-HN-L3Y" userLabel="Day">
+                                                                        <rect key="frame" x="0.0" y="0.0" width="46.666666666666664" height="20"/>
+                                                                        <fontDescription key="fontDescription" type="system" pointSize="17"/>
+                                                                        <nil key="textColor"/>
+                                                                        <nil key="highlightedColor"/>
+                                                                    </label>
+                                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="·" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ANo-uV-FWJ">
+                                                                        <rect key="frame" x="51.666666666666671" y="0.0" width="6.6666666666666643" height="20"/>
+                                                                        <fontDescription key="fontDescription" type="system" weight="black" pointSize="17"/>
+                                                                        <nil key="textColor"/>
+                                                                        <nil key="highlightedColor"/>
+                                                                    </label>
+                                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="13 July 2023" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4LY-PI-0gT" userLabel="Date">
+                                                                        <rect key="frame" x="63.333333333333321" y="0.0" width="97.666666666666686" height="20"/>
+                                                                        <fontDescription key="fontDescription" type="system" pointSize="17"/>
+                                                                        <nil key="textColor"/>
+                                                                        <nil key="highlightedColor"/>
+                                                                    </label>
+                                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="·" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="YkY-Ae-DWl">
+                                                                        <rect key="frame" x="166" y="0.0" width="6.6666666666666572" height="20"/>
+                                                                        <fontDescription key="fontDescription" type="system" weight="black" pointSize="17"/>
+                                                                        <nil key="textColor"/>
+                                                                        <nil key="highlightedColor"/>
+                                                                    </label>
+                                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="20:04" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="POq-uU-ask" userLabel="Time">
+                                                                        <rect key="frame" x="177.66666666666666" y="0.0" width="45.666666666666657" height="20"/>
+                                                                        <fontDescription key="fontDescription" type="system" pointSize="17"/>
+                                                                        <nil key="textColor"/>
+                                                                        <nil key="highlightedColor"/>
+                                                                    </label>
+                                                                </subviews>
+                                                                <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                                                <constraints>
+                                                                    <constraint firstItem="4LY-PI-0gT" firstAttribute="top" secondItem="v7L-HF-dAc" secondAttribute="top" id="0Nj-od-jgr"/>
+                                                                    <constraint firstAttribute="height" constant="20" id="5Y4-56-xQm"/>
+                                                                    <constraint firstAttribute="bottom" secondItem="f9E-HN-L3Y" secondAttribute="bottom" id="683-b3-OWL"/>
+                                                                    <constraint firstItem="4LY-PI-0gT" firstAttribute="leading" secondItem="ANo-uV-FWJ" secondAttribute="trailing" constant="5" id="8NU-Iw-MKs"/>
+                                                                    <constraint firstItem="f9E-HN-L3Y" firstAttribute="top" secondItem="v7L-HF-dAc" secondAttribute="top" id="C6J-KV-6Rt"/>
+                                                                    <constraint firstAttribute="bottom" secondItem="YkY-Ae-DWl" secondAttribute="bottom" id="OfB-zH-kmh"/>
+                                                                    <constraint firstItem="YkY-Ae-DWl" firstAttribute="leading" secondItem="4LY-PI-0gT" secondAttribute="trailing" constant="5" id="TPG-Ft-gS9"/>
+                                                                    <constraint firstItem="ANo-uV-FWJ" firstAttribute="leading" secondItem="f9E-HN-L3Y" secondAttribute="trailing" constant="5" id="UfF-b1-NXC"/>
+                                                                    <constraint firstItem="POq-uU-ask" firstAttribute="leading" secondItem="YkY-Ae-DWl" secondAttribute="trailing" constant="5" id="UhC-VA-9kZ"/>
+                                                                    <constraint firstAttribute="bottom" secondItem="ANo-uV-FWJ" secondAttribute="bottom" id="acf-iK-YWp"/>
+                                                                    <constraint firstAttribute="bottom" secondItem="4LY-PI-0gT" secondAttribute="bottom" id="bNV-o6-ntx"/>
+                                                                    <constraint firstItem="POq-uU-ask" firstAttribute="top" secondItem="v7L-HF-dAc" secondAttribute="top" id="cgF-AS-UnZ"/>
+                                                                    <constraint firstAttribute="bottom" secondItem="POq-uU-ask" secondAttribute="bottom" id="e8Y-HT-Oq1"/>
+                                                                    <constraint firstItem="YkY-Ae-DWl" firstAttribute="top" secondItem="v7L-HF-dAc" secondAttribute="top" id="snm-cE-GUd"/>
+                                                                    <constraint firstItem="f9E-HN-L3Y" firstAttribute="leading" secondItem="v7L-HF-dAc" secondAttribute="leading" id="w48-hI-Njc"/>
+                                                                    <constraint firstItem="ANo-uV-FWJ" firstAttribute="top" secondItem="v7L-HF-dAc" secondAttribute="top" id="zkd-LW-elc"/>
+                                                                </constraints>
+                                                            </view>
+                                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="IMG_003" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PVW-g9-z43" userLabel="Title">
+                                                                <rect key="frame" x="0.0" y="21.333333333333314" width="400" height="19.333333333333329"/>
+                                                                <fontDescription key="fontDescription" type="system" pointSize="16"/>
+                                                                <color key="textColor" systemColor="secondaryLabelColor"/>
+                                                                <nil key="highlightedColor"/>
+                                                            </label>
+                                                        </subviews>
+                                                    </stackView>
+                                                    <view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="YSP-f2-T7B">
+                                                        <rect key="frame" x="0.0" y="52.666666666666693" width="400" height="117.66666666666669"/>
+                                                        <subviews>
+                                                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ucG-To-acr">
+                                                                <rect key="frame" x="0.0" y="0.0" width="400" height="35"/>
+                                                                <subviews>
+                                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Apple iPhone 12 Pro" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cA8-2k-F4N" userLabel="Device">
+                                                                        <rect key="frame" x="5" y="7.9999999999999982" width="145.33333333333334" height="19.333333333333329"/>
+                                                                        <fontDescription key="fontDescription" type="system" pointSize="16"/>
+                                                                        <nil key="textColor"/>
+                                                                        <nil key="highlightedColor"/>
+                                                                    </label>
+                                                                    <stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="RMk-ip-NjG">
+                                                                        <rect key="frame" x="345" y="7.6666666666666288" width="50" height="20"/>
+                                                                        <subviews>
+                                                                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Zh3-hb-yoa">
+                                                                                <rect key="frame" x="0.0" y="0.0" width="50" height="20"/>
+                                                                                <subviews>
+                                                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" text="HEIF" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ux8-XJ-hpJ">
+                                                                                        <rect key="frame" x="4" y="2" width="42" height="16"/>
+                                                                                        <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                                                                                        <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                                                                        <nil key="highlightedColor"/>
+                                                                                    </label>
+                                                                                </subviews>
+                                                                                <color key="backgroundColor" systemColor="systemGray2Color"/>
+                                                                                <constraints>
+                                                                                    <constraint firstAttribute="trailing" secondItem="Ux8-XJ-hpJ" secondAttribute="trailing" constant="4" id="ILq-QM-ZRq"/>
+                                                                                    <constraint firstItem="Ux8-XJ-hpJ" firstAttribute="leading" secondItem="Zh3-hb-yoa" secondAttribute="leading" constant="4" id="MXv-aT-9Pp"/>
+                                                                                    <constraint firstAttribute="bottom" secondItem="Ux8-XJ-hpJ" secondAttribute="bottom" constant="2" id="dxJ-k4-SDG"/>
+                                                                                    <constraint firstItem="Ux8-XJ-hpJ" firstAttribute="top" secondItem="Zh3-hb-yoa" secondAttribute="top" constant="2" id="vKd-Kp-jlU"/>
+                                                                                </constraints>
+                                                                                <userDefinedRuntimeAttributes>
+                                                                                    <userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
+                                                                                        <integer key="value" value="3"/>
+                                                                                    </userDefinedRuntimeAttribute>
+                                                                                </userDefinedRuntimeAttributes>
+                                                                            </view>
+                                                                            <imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="livephoto" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="Pix-ui-Onk">
+                                                                                <rect key="frame" x="0.0" y="-3.5527136788005009e-15" width="20" height="20.333333333333336"/>
+                                                                                <color key="tintColor" systemColor="systemGrayColor"/>
+                                                                                <constraints>
+                                                                                    <constraint firstAttribute="width" constant="20" id="f4P-jF-ymT"/>
+                                                                                </constraints>
+                                                                            </imageView>
+                                                                        </subviews>
+                                                                        <constraints>
+                                                                            <constraint firstAttribute="height" constant="20" id="HZ6-y7-FsB"/>
+                                                                        </constraints>
+                                                                    </stackView>
+                                                                </subviews>
+                                                                <color key="backgroundColor" systemColor="systemGray4Color"/>
+                                                                <constraints>
+                                                                    <constraint firstItem="RMk-ip-NjG" firstAttribute="centerY" secondItem="ucG-To-acr" secondAttribute="centerY" id="5eG-3c-FkU"/>
+                                                                    <constraint firstAttribute="trailing" secondItem="RMk-ip-NjG" secondAttribute="trailing" constant="5" id="A8u-lU-5wY"/>
+                                                                    <constraint firstItem="cA8-2k-F4N" firstAttribute="leading" secondItem="ucG-To-acr" secondAttribute="leading" constant="5" id="SUB-is-irh"/>
+                                                                    <constraint firstAttribute="height" constant="35" id="rKP-vs-CL4"/>
+                                                                    <constraint firstItem="cA8-2k-F4N" firstAttribute="centerY" secondItem="ucG-To-acr" secondAttribute="centerY" id="yG7-dQ-2xn"/>
+                                                                </constraints>
+                                                            </view>
+                                                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="PAH-Zm-6f7" userLabel="Separator">
+                                                                <rect key="frame" x="0.0" y="35" width="400" height="1"/>
+                                                                <color key="backgroundColor" systemColor="systemGray3Color"/>
+                                                                <constraints>
+                                                                    <constraint firstAttribute="height" constant="1" id="oDS-NK-8Fh"/>
+                                                                </constraints>
+                                                            </view>
+                                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Ultra Wide Camera - 13 mm " textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="d4R-Eg-BEV">
+                                                                <rect key="frame" x="5" y="43" width="388" height="19.333333333333329"/>
+                                                                <fontDescription key="fontDescription" type="system" pointSize="16"/>
+                                                                <color key="textColor" systemColor="secondaryLabelColor"/>
+                                                                <nil key="highlightedColor"/>
+                                                            </label>
+                                                            <stackView opaque="NO" contentMode="scaleToFill" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="6cz-nf-75X">
+                                                                <rect key="frame" x="5" y="64.333333333333314" width="390" height="19.333333333333329"/>
+                                                                <subviews>
+                                                                    <label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="12 MP" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sxI-vc-KfJ" userLabel="Megapixel">
+                                                                        <rect key="frame" x="0.0" y="0.0" width="0.0" height="19.333333333333332"/>
+                                                                        <fontDescription key="fontDescription" type="system" pointSize="16"/>
+                                                                        <color key="textColor" systemColor="secondaryLabelColor"/>
+                                                                        <nil key="highlightedColor"/>
+                                                                    </label>
+                                                                    <label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="·" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="EKQ-RW-a5p">
+                                                                        <rect key="frame" x="0.0" y="0.0" width="0.0" height="19.333333333333332"/>
+                                                                        <fontDescription key="fontDescription" type="system" weight="black" pointSize="16"/>
+                                                                        <color key="textColor" systemColor="secondaryLabelColor"/>
+                                                                        <nil key="highlightedColor"/>
+                                                                    </label>
+                                                                    <label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="3024 x 4032 " textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6A5-qu-7x1" userLabel="Resolution">
+                                                                        <rect key="frame" x="0.0" y="0.0" width="0.0" height="19.333333333333332"/>
+                                                                        <fontDescription key="fontDescription" type="system" pointSize="16"/>
+                                                                        <color key="textColor" systemColor="secondaryLabelColor"/>
+                                                                        <nil key="highlightedColor"/>
+                                                                    </label>
+                                                                    <label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="·" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PVg-lD-V6e">
+                                                                        <rect key="frame" x="0.0" y="0.0" width="0.0" height="19.333333333333332"/>
+                                                                        <fontDescription key="fontDescription" type="system" weight="black" pointSize="16"/>
+                                                                        <color key="textColor" systemColor="secondaryLabelColor"/>
+                                                                        <nil key="highlightedColor"/>
+                                                                    </label>
+                                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="3,1 MB" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="yOh-G2-fPm" userLabel="Size">
+                                                                        <rect key="frame" x="0.0" y="0.0" width="390" height="19.333333333333332"/>
+                                                                        <fontDescription key="fontDescription" type="system" pointSize="16"/>
+                                                                        <color key="textColor" systemColor="secondaryLabelColor"/>
+                                                                        <nil key="highlightedColor"/>
+                                                                    </label>
+                                                                </subviews>
+                                                            </stackView>
+                                                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="EkY-Gj-aPB" userLabel="Separator">
+                                                                <rect key="frame" x="0.0" y="90.666666666666686" width="400" height="1"/>
+                                                                <color key="backgroundColor" systemColor="systemGray3Color"/>
+                                                                <constraints>
+                                                                    <constraint firstAttribute="height" constant="1" id="r0C-1B-Hav"/>
+                                                                </constraints>
+                                                            </view>
+                                                            <stackView opaque="NO" contentMode="scaleToFill" distribution="equalCentering" translatesAutoresizingMaskIntoConstraints="NO" id="IFA-TY-kPl">
+                                                                <rect key="frame" x="0.0" y="95.666666666666686" width="400" height="18"/>
+                                                                <subviews>
+                                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="|" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="TJr-Hv-2Gl">
+                                                                        <rect key="frame" x="0.0" y="0.0" width="3.3333333333333335" height="18"/>
+                                                                        <fontDescription key="fontDescription" type="system" weight="thin" pointSize="15"/>
+                                                                        <color key="textColor" systemColor="systemGray5Color"/>
+                                                                        <nil key="highlightedColor"/>
+                                                                    </label>
+                                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="-" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6Cs-pM-Ful" userLabel="ISO">
+                                                                        <rect key="frame" x="38" y="0.0" width="6.6666666666666643" height="18"/>
+                                                                        <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                                                                        <color key="textColor" systemColor="secondaryLabelColor"/>
+                                                                        <nil key="highlightedColor"/>
+                                                                    </label>
+                                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="|" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kVB-Rd-ORU">
+                                                                        <rect key="frame" x="79.333333333333329" y="0.0" width="3.3333333333333286" height="18"/>
+                                                                        <fontDescription key="fontDescription" type="system" weight="thin" pointSize="15"/>
+                                                                        <color key="textColor" systemColor="tertiaryLabelColor"/>
+                                                                        <nil key="highlightedColor"/>
+                                                                    </label>
+                                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="-" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="crU-0D-RI9" userLabel="Lens size">
+                                                                        <rect key="frame" x="117.33333333333334" y="0.0" width="6.6666666666666714" height="18"/>
+                                                                        <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                                                                        <color key="textColor" systemColor="secondaryLabelColor"/>
+                                                                        <nil key="highlightedColor"/>
+                                                                    </label>
+                                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="|" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7Nx-B1-R1C">
+                                                                        <rect key="frame" x="158.66666666666666" y="0.0" width="3.3333333333333428" height="18"/>
+                                                                        <fontDescription key="fontDescription" type="system" weight="thin" pointSize="15"/>
+                                                                        <color key="textColor" systemColor="tertiaryLabelColor"/>
+                                                                        <nil key="highlightedColor"/>
+                                                                    </label>
+                                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="-" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="CDk-Yx-uJI" userLabel="Exposure value">
+                                                                        <rect key="frame" x="196.66666666666666" y="0.0" width="6.6666666666666572" height="18"/>
+                                                                        <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                                                                        <color key="textColor" systemColor="secondaryLabelColor"/>
+                                                                        <nil key="highlightedColor"/>
+                                                                    </label>
+                                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="|" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PGe-rH-CVF">
+                                                                        <rect key="frame" x="238" y="0.0" width="3.3333333333333428" height="18"/>
+                                                                        <fontDescription key="fontDescription" type="system" weight="thin" pointSize="15"/>
+                                                                        <color key="textColor" systemColor="tertiaryLabelColor"/>
+                                                                        <nil key="highlightedColor"/>
+                                                                    </label>
+                                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="-" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Dyh-MY-ZPm" userLabel="Aperture">
+                                                                        <rect key="frame" x="276" y="0.0" width="6.6666666666666856" height="18"/>
+                                                                        <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                                                                        <color key="textColor" systemColor="secondaryLabelColor"/>
+                                                                        <nil key="highlightedColor"/>
+                                                                    </label>
+                                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="|" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="t3F-Mb-moo">
+                                                                        <rect key="frame" x="317.33333333333331" y="0.0" width="3.3333333333333144" height="18"/>
+                                                                        <fontDescription key="fontDescription" type="system" weight="thin" pointSize="15"/>
+                                                                        <color key="textColor" systemColor="tertiaryLabelColor"/>
+                                                                        <nil key="highlightedColor"/>
+                                                                    </label>
+                                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="-" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="LOe-Mx-JH3" userLabel="Shutter speed">
+                                                                        <rect key="frame" x="355.33333333333331" y="0.0" width="6.6666666666666856" height="18"/>
+                                                                        <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                                                                        <color key="textColor" systemColor="secondaryLabelColor"/>
+                                                                        <nil key="highlightedColor"/>
+                                                                    </label>
+                                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="|" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Tux-yK-e5c">
+                                                                        <rect key="frame" x="396.66666666666669" y="0.0" width="3.3333333333333144" height="18"/>
+                                                                        <fontDescription key="fontDescription" type="system" weight="thin" pointSize="15"/>
+                                                                        <color key="textColor" systemColor="systemGray5Color"/>
+                                                                        <nil key="highlightedColor"/>
+                                                                    </label>
+                                                                </subviews>
+                                                            </stackView>
+                                                        </subviews>
+                                                        <color key="backgroundColor" systemColor="systemGray5Color"/>
+                                                        <constraints>
+                                                            <constraint firstAttribute="trailing" secondItem="d4R-Eg-BEV" secondAttribute="trailing" constant="7" id="0if-5h-4Hd"/>
+                                                            <constraint firstItem="EkY-Gj-aPB" firstAttribute="top" secondItem="6cz-nf-75X" secondAttribute="bottom" constant="7" id="1bM-RZ-yX0"/>
+                                                            <constraint firstAttribute="trailing" secondItem="IFA-TY-kPl" secondAttribute="trailing" id="9Cx-KD-RKx"/>
+                                                            <constraint firstAttribute="trailing" secondItem="EkY-Gj-aPB" secondAttribute="trailing" id="D88-MT-aJT"/>
+                                                            <constraint firstItem="ucG-To-acr" firstAttribute="top" secondItem="YSP-f2-T7B" secondAttribute="top" id="Dcv-Ag-2Q0"/>
+                                                            <constraint firstItem="d4R-Eg-BEV" firstAttribute="top" secondItem="PAH-Zm-6f7" secondAttribute="bottom" constant="7" id="Fzh-eW-7xS"/>
+                                                            <constraint firstItem="EkY-Gj-aPB" firstAttribute="leading" secondItem="YSP-f2-T7B" secondAttribute="leading" id="ItR-2I-zaS"/>
+                                                            <constraint firstItem="IFA-TY-kPl" firstAttribute="leading" secondItem="YSP-f2-T7B" secondAttribute="leading" id="Nhf-Tc-J12"/>
+                                                            <constraint firstItem="IFA-TY-kPl" firstAttribute="top" secondItem="EkY-Gj-aPB" secondAttribute="bottom" constant="4" id="Ris-Xd-8hF"/>
+                                                            <constraint firstItem="ucG-To-acr" firstAttribute="leading" secondItem="YSP-f2-T7B" secondAttribute="leading" id="RzN-xI-Nhl"/>
+                                                            <constraint firstItem="6cz-nf-75X" firstAttribute="top" secondItem="d4R-Eg-BEV" secondAttribute="bottom" constant="2" id="bI7-Xl-VUb"/>
+                                                            <constraint firstAttribute="trailing" secondItem="6cz-nf-75X" secondAttribute="trailing" constant="5" id="bqY-6X-bea"/>
+                                                            <constraint firstAttribute="trailing" secondItem="ucG-To-acr" secondAttribute="trailing" id="edf-ki-HLP"/>
+                                                            <constraint firstItem="6cz-nf-75X" firstAttribute="leading" secondItem="YSP-f2-T7B" secondAttribute="leading" constant="5" id="foV-h4-V7i"/>
+                                                            <constraint firstItem="PAH-Zm-6f7" firstAttribute="top" secondItem="ucG-To-acr" secondAttribute="bottom" id="jt5-Mj-0b5"/>
+                                                            <constraint firstAttribute="bottom" secondItem="IFA-TY-kPl" secondAttribute="bottom" constant="4" id="osF-4r-x4s"/>
+                                                            <constraint firstItem="PAH-Zm-6f7" firstAttribute="leading" secondItem="YSP-f2-T7B" secondAttribute="leading" id="tQZ-L6-6SM"/>
+                                                            <constraint firstAttribute="trailing" secondItem="PAH-Zm-6f7" secondAttribute="trailing" id="vQ3-9j-Ttg"/>
+                                                            <constraint firstItem="d4R-Eg-BEV" firstAttribute="leading" secondItem="YSP-f2-T7B" secondAttribute="leading" constant="5" id="zTL-0L-Yvq"/>
+                                                        </constraints>
+                                                        <userDefinedRuntimeAttributes>
+                                                            <userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
+                                                                <integer key="value" value="8"/>
+                                                            </userDefinedRuntimeAttribute>
+                                                        </userDefinedRuntimeAttributes>
+                                                    </view>
+                                                    <stackView opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="kH6-LI-qFN">
+                                                        <rect key="frame" x="0.0" y="182.33333333333337" width="400" height="185"/>
+                                                        <subviews>
+                                                            <view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="dJP-ZX-iug">
+                                                                <rect key="frame" x="0.0" y="0.0" width="400" height="150"/>
+                                                                <color key="backgroundColor" systemColor="systemGray6Color"/>
+                                                                <gestureRecognizers/>
+                                                                <constraints>
+                                                                    <constraint firstAttribute="height" constant="150" id="fJT-bf-c1B"/>
+                                                                </constraints>
+                                                            </view>
+                                                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="HcW-yY-GaX">
+                                                                <rect key="frame" x="0.0" y="150" width="400" height="35"/>
+                                                                <subviews>
+                                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bOA-Hx-qza" userLabel="Location">
+                                                                        <rect key="frame" x="5" y="17.666666666666629" width="0.0" height="0.0"/>
+                                                                        <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                                                                        <color key="textColor" systemColor="linkColor"/>
+                                                                        <nil key="highlightedColor"/>
+                                                                    </label>
+                                                                    <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="chevron.right" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="1yH-o3-9JB">
+                                                                        <rect key="frame" x="5" y="9.666666666666627" width="20" height="16.333333333333329"/>
+                                                                        <color key="tintColor" systemColor="secondaryLabelColor"/>
+                                                                        <constraints>
+                                                                            <constraint firstAttribute="width" constant="20" id="DXq-TZ-AgJ"/>
+                                                                            <constraint firstAttribute="height" constant="20" id="Qce-PW-4lw"/>
+                                                                        </constraints>
+                                                                    </imageView>
+                                                                </subviews>
+                                                                <color key="backgroundColor" systemColor="systemGray5Color"/>
+                                                                <constraints>
+                                                                    <constraint firstItem="1yH-o3-9JB" firstAttribute="centerY" secondItem="HcW-yY-GaX" secondAttribute="centerY" id="K0K-hv-VdN"/>
+                                                                    <constraint firstItem="bOA-Hx-qza" firstAttribute="leading" secondItem="HcW-yY-GaX" secondAttribute="leading" constant="5" id="KKt-Eb-NmT"/>
+                                                                    <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="1yH-o3-9JB" secondAttribute="trailing" id="iUk-3x-ojN"/>
+                                                                    <constraint firstItem="bOA-Hx-qza" firstAttribute="centerY" secondItem="HcW-yY-GaX" secondAttribute="centerY" id="quS-PT-b4t"/>
+                                                                    <constraint firstAttribute="height" constant="35" id="xGF-2V-n2g"/>
+                                                                    <constraint firstItem="1yH-o3-9JB" firstAttribute="leading" secondItem="bOA-Hx-qza" secondAttribute="trailing" id="xTF-7x-FMG"/>
+                                                                </constraints>
+                                                            </view>
+                                                        </subviews>
+                                                        <userDefinedRuntimeAttributes>
+                                                            <userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
+                                                                <integer key="value" value="8"/>
+                                                            </userDefinedRuntimeAttribute>
+                                                        </userDefinedRuntimeAttributes>
+                                                        <connections>
+                                                            <outletCollection property="gestureRecognizers" destination="fvW-pC-4g1" appends="YES" id="DUs-ga-raf"/>
+                                                        </connections>
+                                                    </stackView>
+                                                    <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="swf-k6-4W6">
+                                                        <rect key="frame" x="0.0" y="379.33333333333337" width="400" height="56"/>
+                                                        <subviews>
+                                                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Umg-YQ-OqC">
+                                                                <rect key="frame" x="0.0" y="0.0" width="400" height="34.333333333333336"/>
+                                                                <state key="normal" title="Button"/>
+                                                                <buttonConfiguration key="configuration" style="gray" title="Button">
+                                                                    <color key="baseBackgroundColor" systemColor="systemGray5Color"/>
+                                                                </buttonConfiguration>
+                                                                <connections>
+                                                                    <outletCollection property="gestureRecognizers" destination="LMd-B2-U1g" appends="YES" id="Gap-7a-xBo"/>
+                                                                </connections>
+                                                            </button>
+                                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="This may reveal more information about the photo." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZH0-dn-sQ5" userLabel="Info">
+                                                                <rect key="frame" x="0.0" y="40.333333333333258" width="400" height="15.666666666666664"/>
+                                                                <fontDescription key="fontDescription" type="system" pointSize="13"/>
+                                                                <color key="textColor" systemColor="secondaryLabelColor"/>
+                                                                <nil key="highlightedColor"/>
+                                                            </label>
+                                                        </subviews>
+                                                    </stackView>
+                                                </subviews>
+                                            </stackView>
                                         </subviews>
-                                        <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                         <constraints>
-                                            <constraint firstItem="GWF-lf-c8t" firstAttribute="leading" secondItem="P8R-4f-zAl" secondAttribute="leading" constant="15" id="0Ub-x3-dQs"/>
-                                            <constraint firstItem="WXS-Lw-DkI" firstAttribute="top" secondItem="HRq-3M-yeb" secondAttribute="bottom" constant="15" id="2RK-lk-nQe"/>
-                                            <constraint firstItem="uaE-Lv-t0Q" firstAttribute="top" secondItem="Son-CZ-zFa" secondAttribute="bottom" constant="10" id="3jD-4U-zwi"/>
-                                            <constraint firstItem="XLb-0a-du9" firstAttribute="centerY" secondItem="WXS-Lw-DkI" secondAttribute="centerY" id="6k5-Ur-AKZ"/>
-                                            <constraint firstAttribute="trailing" secondItem="n1C-OB-gq2" secondAttribute="trailing" constant="15" id="6o9-lb-LZc"/>
-                                            <constraint firstAttribute="trailing" secondItem="dJP-ZX-iug" secondAttribute="trailing" constant="15" id="AbM-Mc-iC7"/>
-                                            <constraint firstAttribute="trailing" secondItem="HRq-3M-yeb" secondAttribute="trailing" constant="15" id="DIq-e4-T4P"/>
-                                            <constraint firstAttribute="height" constant="400" id="HzF-N7-BQ8"/>
-                                            <constraint firstItem="XLb-0a-du9" firstAttribute="leading" secondItem="WXS-Lw-DkI" secondAttribute="trailing" constant="10" id="Iqg-lN-NpB"/>
-                                            <constraint firstItem="n1C-OB-gq2" firstAttribute="centerY" secondItem="uaE-Lv-t0Q" secondAttribute="centerY" id="KMX-7y-YXP"/>
-                                            <constraint firstItem="uYI-ic-d8g" firstAttribute="leading" secondItem="P8R-4f-zAl" secondAttribute="leading" constant="15" id="MTM-eQ-CXC"/>
-                                            <constraint firstItem="dJP-ZX-iug" firstAttribute="top" secondItem="ix8-uQ-chU" secondAttribute="bottom" constant="20" id="Mxt-43-sew"/>
-                                            <constraint firstItem="ix8-uQ-chU" firstAttribute="centerY" secondItem="uYI-ic-d8g" secondAttribute="centerY" id="Oae-3R-C5Z"/>
-                                            <constraint firstAttribute="trailing" secondItem="GWF-lf-c8t" secondAttribute="trailing" constant="15" id="QJp-6z-62f"/>
-                                            <constraint firstItem="oov-9f-Oeu" firstAttribute="top" secondItem="dJP-ZX-iug" secondAttribute="bottom" id="SXt-nb-5Bf"/>
-                                            <constraint firstAttribute="trailing" secondItem="ix8-uQ-chU" secondAttribute="trailing" constant="15" id="TIp-le-wVn"/>
-                                            <constraint firstItem="hBd-KD-Jq5" firstAttribute="centerY" secondItem="Son-CZ-zFa" secondAttribute="centerY" id="Tez-na-gqC"/>
-                                            <constraint firstItem="HRq-3M-yeb" firstAttribute="top" secondItem="P8R-4f-zAl" secondAttribute="top" constant="20" id="UF1-fO-9hX"/>
-                                            <constraint firstAttribute="bottom" secondItem="oov-9f-Oeu" secondAttribute="bottom" id="YuK-2v-kzk"/>
-                                            <constraint firstItem="hBd-KD-Jq5" firstAttribute="leading" secondItem="Son-CZ-zFa" secondAttribute="trailing" constant="10" id="YuO-13-KTh"/>
-                                            <constraint firstItem="WXS-Lw-DkI" firstAttribute="leading" secondItem="P8R-4f-zAl" secondAttribute="leading" constant="15" id="bRd-bi-Imh"/>
-                                            <constraint firstItem="uYI-ic-d8g" firstAttribute="top" secondItem="uaE-Lv-t0Q" secondAttribute="bottom" constant="10" id="bkM-Ic-ZoE"/>
-                                            <constraint firstItem="dJP-ZX-iug" firstAttribute="leading" secondItem="P8R-4f-zAl" secondAttribute="leading" constant="15" id="ife-ps-Lx5"/>
-                                            <constraint firstItem="oov-9f-Oeu" firstAttribute="leading" secondItem="P8R-4f-zAl" secondAttribute="leading" constant="15" id="lXY-IM-uQB"/>
-                                            <constraint firstItem="GWF-lf-c8t" firstAttribute="top" secondItem="ix8-uQ-chU" secondAttribute="bottom" constant="10" id="m7R-Xs-f01"/>
-                                            <constraint firstAttribute="trailing" secondItem="oov-9f-Oeu" secondAttribute="trailing" constant="15" id="mpQ-4V-Yfc"/>
-                                            <constraint firstItem="HRq-3M-yeb" firstAttribute="leading" secondItem="P8R-4f-zAl" secondAttribute="leading" constant="15" id="oJB-7U-UpU"/>
-                                            <constraint firstAttribute="trailing" secondItem="XLb-0a-du9" secondAttribute="trailing" constant="15" id="p7C-ox-HFw"/>
-                                            <constraint firstAttribute="trailing" secondItem="hBd-KD-Jq5" secondAttribute="trailing" constant="15" id="sKA-YA-3Uc"/>
-                                            <constraint firstItem="uaE-Lv-t0Q" firstAttribute="leading" secondItem="P8R-4f-zAl" secondAttribute="leading" constant="15" id="tnj-dz-2rk"/>
-                                            <constraint firstItem="ix8-uQ-chU" firstAttribute="leading" secondItem="uYI-ic-d8g" secondAttribute="trailing" constant="10" id="vPD-6e-zeT"/>
-                                            <constraint firstItem="Son-CZ-zFa" firstAttribute="leading" secondItem="P8R-4f-zAl" secondAttribute="leading" constant="15" id="vsg-gX-rNv"/>
-                                            <constraint firstItem="n1C-OB-gq2" firstAttribute="leading" secondItem="uaE-Lv-t0Q" secondAttribute="trailing" constant="10" id="yAH-Dt-LC5"/>
-                                            <constraint firstItem="Son-CZ-zFa" firstAttribute="top" secondItem="WXS-Lw-DkI" secondAttribute="bottom" constant="10" id="z9W-ZR-cUN"/>
+                                            <constraint firstItem="Z0h-yS-myv" firstAttribute="leading" secondItem="P8R-4f-zAl" secondAttribute="leading" constant="15" id="Ian-z6-Edo"/>
+                                            <constraint firstItem="Z0h-yS-myv" firstAttribute="top" secondItem="P8R-4f-zAl" secondAttribute="top" constant="10" id="bVs-TI-kHP"/>
+                                            <constraint firstAttribute="width" relation="greaterThanOrEqual" constant="814" id="cMZ-Da-ac2"/>
+                                            <constraint firstAttribute="trailing" secondItem="Z0h-yS-myv" secondAttribute="trailing" constant="15" id="hZn-el-dtj"/>
+                                            <constraint firstAttribute="bottom" secondItem="Z0h-yS-myv" secondAttribute="bottom" constant="10" id="qOC-jf-xdx"/>
                                         </constraints>
                                         <connections>
-                                            <outlet property="dateLabel" destination="Son-CZ-zFa" id="0Wi-XW-0hw"/>
-                                            <outlet property="dateValue" destination="hBd-KD-Jq5" id="ple-nX-OpH"/>
-                                            <outlet property="dimLabel" destination="uaE-Lv-t0Q" id="MkZ-ko-UrJ"/>
-                                            <outlet property="dimValue" destination="n1C-OB-gq2" id="Ln4-gV-wXg"/>
-                                            <outlet property="lensModelLabel" destination="uYI-ic-d8g" id="DUn-uJ-sVj"/>
-                                            <outlet property="lensModelValue" destination="ix8-uQ-chU" id="GNF-8F-10P"/>
-                                            <outlet property="locationButton" destination="oov-9f-Oeu" id="cGg-Gb-m5S"/>
-                                            <outlet property="mapContainer" destination="dJP-ZX-iug" id="9xS-le-Hhe"/>
-                                            <outlet property="messageButton" destination="GWF-lf-c8t" id="cls-X9-Urf"/>
-                                            <outlet property="separator" destination="HRq-3M-yeb" id="ENP-xc-AWZ"/>
-                                            <outlet property="sizeLabel" destination="WXS-Lw-DkI" id="JG0-G0-oHg"/>
-                                            <outlet property="sizeValue" destination="XLb-0a-du9" id="9jm-Ku-sgt"/>
+                                            <outlet property="apertureLabel" destination="Dyh-MY-ZPm" id="cME-5C-3wz"/>
+                                            <outlet property="dateContainer" destination="v7L-HF-dAc" id="InI-Sl-2kQ"/>
+                                            <outlet property="dateLabel" destination="4LY-PI-0gT" id="dpX-Q9-Aaw"/>
+                                            <outlet property="dayLabel" destination="f9E-HN-L3Y" id="yks-Cu-1pt"/>
+                                            <outlet property="deviceContainer" destination="ucG-To-acr" id="B2F-qe-NwF"/>
+                                            <outlet property="downloadImageButton" destination="Umg-YQ-OqC" id="lFI-g5-fCc"/>
+                                            <outlet property="downloadImageButtonContainer" destination="swf-k6-4W6" id="atX-Zc-tax"/>
+                                            <outlet property="downloadImageLabel" destination="ZH0-dn-sQ5" id="4jd-7p-REW"/>
+                                            <outlet property="exposureValueLabel" destination="CDk-Yx-uJI" id="oZK-rh-iJI"/>
+                                            <outlet property="extensionLabel" destination="Ux8-XJ-hpJ" id="zPk-z9-QBK"/>
+                                            <outlet property="isoLabel" destination="6Cs-pM-Ful" id="lGL-oS-88h"/>
+                                            <outlet property="lensInfoLeadingFakePadding" destination="TJr-Hv-2Gl" id="uDd-r6-1XW"/>
+                                            <outlet property="lensInfoStackViewLeadingConstraint" destination="Nhf-Tc-J12" id="G0O-8L-Yp5"/>
+                                            <outlet property="lensInfoStackViewTrailingConstraint" destination="9Cx-KD-RKx" id="WMF-xt-feG"/>
+                                            <outlet property="lensInfoTrailingFakePadding" destination="Tux-yK-e5c" id="6mU-T2-gMI"/>
+                                            <outlet property="lensLabel" destination="d4R-Eg-BEV" id="ZaX-do-ccU"/>
+                                            <outlet property="lensSizeLabel" destination="crU-0D-RI9" id="ERb-7F-G0b"/>
+                                            <outlet property="livePhotoImageView" destination="Pix-ui-Onk" id="PyP-ld-K3T"/>
+                                            <outlet property="locationLabel" destination="bOA-Hx-qza" id="O8Y-V8-Xzp"/>
+                                            <outlet property="mapContainer" destination="dJP-ZX-iug" id="xqo-Mi-GOO"/>
+                                            <outlet property="megaPixelLabel" destination="sxI-vc-KfJ" id="Uz6-l8-4aI"/>
+                                            <outlet property="megaPixelLabelDivider" destination="EKQ-RW-a5p" id="Rjp-Id-djL"/>
+                                            <outlet property="modelLabel" destination="cA8-2k-F4N" id="Lwb-L5-0Hs"/>
+                                            <outlet property="nameLabel" destination="PVW-g9-z43" id="CC5-mu-Ftr"/>
+                                            <outlet property="noDateLabel" destination="0Mb-ss-cAE" id="dH8-pr-6Jp"/>
+                                            <outlet property="outerContainer" destination="YSP-f2-T7B" id="hxB-u3-8vl"/>
+                                            <outlet property="outerMapContainer" destination="kH6-LI-qFN" id="5I3-oe-jgb"/>
+                                            <outlet property="resolutionLabel" destination="6A5-qu-7x1" id="5Ei-Rx-Q4s"/>
+                                            <outlet property="resolutionLabelDivider" destination="PVg-lD-V6e" id="p6g-6E-XM6"/>
+                                            <outlet property="shutterSpeedLabel" destination="LOe-Mx-JH3" id="Qhq-jN-nXV"/>
+                                            <outlet property="sizeLabel" destination="yOh-G2-fPm" id="4ZL-ki-KhM"/>
+                                            <outlet property="timeLabel" destination="POq-uU-ask" id="ue5-U7-Ub1"/>
                                         </connections>
                                     </view>
                                 </subviews>
                                 <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                 <constraints>
+                                    <constraint firstItem="P8R-4f-zAl" firstAttribute="centerX" secondItem="CdQ-LC-Trx" secondAttribute="centerX" id="IFp-cD-s1W"/>
                                     <constraint firstItem="DAi-gz-qGP" firstAttribute="centerY" secondItem="2AU-85-K8y" secondAttribute="centerY" id="Lls-5R-JBM"/>
                                     <constraint firstAttribute="trailing" secondItem="DAi-gz-qGP" secondAttribute="trailing" constant="20" id="QWE-Iy-fcM"/>
                                     <constraint firstItem="kPV-JM-UnM" firstAttribute="leading" secondItem="CdQ-LC-Trx" secondAttribute="leading" id="asL-Ft-Lmc"/>
@@ -261,22 +519,24 @@
                             </scrollView>
                         </subviews>
                         <viewLayoutGuide key="safeArea" id="Yo6-7W-moG"/>
-                        <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                         <constraints>
                             <constraint firstAttribute="bottom" secondItem="CdQ-LC-Trx" secondAttribute="bottom" id="4qB-8y-OcG"/>
                             <constraint firstAttribute="trailing" secondItem="CdQ-LC-Trx" secondAttribute="trailing" id="IwE-oE-d3Y"/>
                             <constraint firstItem="2AU-85-K8y" firstAttribute="leading" secondItem="Yo6-7W-moG" secondAttribute="leading" constant="10" id="X10-OG-EKg"/>
                             <constraint firstItem="Yo6-7W-moG" firstAttribute="top" secondItem="2AU-85-K8y" secondAttribute="top" constant="-10" id="avO-83-uMQ"/>
-                            <constraint firstItem="Yo6-7W-moG" firstAttribute="bottom" secondItem="P8R-4f-zAl" secondAttribute="top" constant="400" id="bor-cg-Alz"/>
+                            <constraint firstItem="Yo6-7W-moG" firstAttribute="bottom" secondItem="P8R-4f-zAl" secondAttribute="top" constant="500" id="bor-cg-Alz"/>
                             <constraint firstItem="P8R-4f-zAl" firstAttribute="leading" secondItem="Yo6-7W-moG" secondAttribute="leading" id="dly-i5-fPW"/>
                             <constraint firstItem="CdQ-LC-Trx" firstAttribute="leading" secondItem="fIE-H6-KKc" secondAttribute="leading" id="g8C-2m-KkX"/>
                             <constraint firstItem="CdQ-LC-Trx" firstAttribute="top" secondItem="fIE-H6-KKc" secondAttribute="top" id="hcQ-lB-JwU"/>
-                            <constraint firstItem="Yo6-7W-moG" firstAttribute="trailing" secondItem="P8R-4f-zAl" secondAttribute="trailing" id="jf2-Nv-gFi"/>
                         </constraints>
+                        <variation key="heightClass=regular-widthClass=regular">
+                            <mask key="constraints">
+                                <exclude reference="dly-i5-fPW"/>
+                            </mask>
+                        </variation>
                     </view>
                     <connections>
                         <outlet property="detailView" destination="P8R-4f-zAl" id="xFW-qq-Cdi"/>
-                        <outlet property="detailViewHeighConstraint" destination="HzF-N7-BQ8" id="KE0-MF-H7w"/>
                         <outlet property="detailViewTopConstraint" destination="bor-cg-Alz" id="xcM-bF-u7e"/>
                         <outlet property="imageVideoContainer" destination="kPV-JM-UnM" id="2pA-VW-FuK"/>
                         <outlet property="imageViewBottomConstraint" destination="vEd-X2-yGs" id="wp3-67-aZ2"/>
@@ -287,19 +547,53 @@
                     </connections>
                 </viewController>
                 <placeholder placeholderIdentifier="IBFirstResponder" id="fbE-Jv-mLH" userLabel="First Responder" sceneMemberID="firstResponder"/>
-                <tapGestureRecognizer id="fvW-pC-4g1">
+                <tapGestureRecognizer id="fvW-pC-4g1" userLabel="Open location Gesture Recognizer">
                     <connections>
                         <action selector="touchLocation:" destination="P8R-4f-zAl" id="qyd-d2-RyB"/>
                     </connections>
                 </tapGestureRecognizer>
+                <tapGestureRecognizer id="LMd-B2-U1g" userLabel="Download Gesture Recognizer">
+                    <connections>
+                        <action selector="touchDownload:" destination="P8R-4f-zAl" id="f13-sg-aRk"/>
+                    </connections>
+                </tapGestureRecognizer>
             </objects>
-            <point key="canvasLocation" x="4547.826086956522" y="776.9021739130435"/>
+            <point key="canvasLocation" x="4547.4418604651164" y="776.39484978540781"/>
         </scene>
     </scenes>
     <resources>
+        <image name="chevron.right" catalog="system" width="97" height="128"/>
+        <image name="livephoto" catalog="system" width="128" height="124"/>
         <image name="networkInProgress" width="300" height="300"/>
+        <systemColor name="linkColor">
+            <color red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+        </systemColor>
+        <systemColor name="secondaryLabelColor">
+            <color red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
+        </systemColor>
         <systemColor name="systemBackgroundColor">
             <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
         </systemColor>
+        <systemColor name="systemGray2Color">
+            <color red="0.68235294120000001" green="0.68235294120000001" blue="0.69803921570000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+        </systemColor>
+        <systemColor name="systemGray3Color">
+            <color red="0.78039215689999997" green="0.78039215689999997" blue="0.80000000000000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+        </systemColor>
+        <systemColor name="systemGray4Color">
+            <color red="0.81960784310000001" green="0.81960784310000001" blue="0.83921568629999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+        </systemColor>
+        <systemColor name="systemGray5Color">
+            <color red="0.8980392157" green="0.8980392157" blue="0.91764705879999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+        </systemColor>
+        <systemColor name="systemGray6Color">
+            <color red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+        </systemColor>
+        <systemColor name="systemGrayColor">
+            <color red="0.5568627451" green="0.5568627451" blue="0.57647058819999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+        </systemColor>
+        <systemColor name="tertiaryLabelColor">
+            <color red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.29803921570000003" colorSpace="custom" customColorSpace="sRGB"/>
+        </systemColor>
     </resources>
 </document>

+ 47 - 31
iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift

@@ -24,8 +24,8 @@
 import UIKit
 import NextcloudKit
 import MediaPlayer
-import JGProgressHUD
 import Alamofire
+import JGProgressHUD
 
 enum ScreenMode {
     case full, normal
@@ -66,15 +66,10 @@ class NCViewerMediaPage: UIViewController {
     var previousTrackCommand: Any?
 
     var timerAutoHide: Timer?
-    var timerAutoHideSeconds: Double {
-        get {
-            if NCUtility.shared.isSimulator() {
-                return 4
-            } else {
-                return 4
-            }
-        }
-    }
+    private var timerAutoHideSeconds: Double = 4
+
+    private lazy var moreNavigationItem = UIBarButtonItem(image: UIImage(named: "more")!.image(color: .label, size: 25), style: .plain, target: self, action: #selector(openMenuMore))
+    private lazy var imageDetailNavigationItem = UIBarButtonItem(image: UIImage(systemName: "info.circle")!.image(color: .label, size: 22), style: .plain, target: self, action: #selector(toggleDetail))
 
     // MARK: - View Life Cycle
 
@@ -86,19 +81,13 @@ class NCViewerMediaPage: UIViewController {
 
     required init?(coder: NSCoder) {
         super.init(coder: coder)
-        
+
         viewerMediaScreenMode = .normal
     }
 
     override func viewDidLoad() {
         super.viewDidLoad()
 
-        if metadatas.count == 1, let metadata = metadatas.first, !metadata.url.isEmpty {
-            // it's a video from URL
-        } else {
-            navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(named: "more")!.image(color: .label, size: 25), style: .plain, target: self, action: #selector(self.openMenuMore))
-        }
-
         singleTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didSingleTapWith(gestureRecognizer:)))
         panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(didPanWith(gestureRecognizer:)))
         longtapGestureRecognizer = UILongPressGestureRecognizer()
@@ -117,7 +106,6 @@ class NCViewerMediaPage: UIViewController {
         progressView.trackTintColor = .clear
         progressView.progress = 0
 
-
         let viewerMedia = getViewerMedia(index: currentIndex, metadata: metadatas[currentIndex])
         pageViewController.setViewControllers([viewerMedia], direction: .forward, animated: true, completion: nil)
         changeScreenMode(mode: viewerMediaScreenMode)
@@ -138,6 +126,12 @@ class NCViewerMediaPage: UIViewController {
         NotificationCenter.default.addObserver(self, selector: #selector(uploadedFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile), object: nil)
 
         NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterApplicationDidBecomeActive), object: nil)
+
+        if currentViewController.metadata.isImage {
+            navigationItem.rightBarButtonItems = [moreNavigationItem, imageDetailNavigationItem]
+        } else {
+            navigationItem.rightBarButtonItems = [moreNavigationItem]
+        }
     }
 
     deinit {
@@ -191,14 +185,13 @@ class NCViewerMediaPage: UIViewController {
         return hideStatusBar
     }
 
-    // MARK: -
-    
     func getViewerMedia(index: Int, metadata: tableMetadata) -> NCViewerMedia {
-        
+    
         let viewerMedia = UIStoryboard(name: "NCViewerMediaPage", bundle: nil).instantiateViewController(withIdentifier: "NCViewerMedia") as! NCViewerMedia
         viewerMedia.index = index
         viewerMedia.metadata = metadata
         viewerMedia.viewerMediaPage = self
+        viewerMedia.delegate = self
 
         singleTapGestureRecognizer.require(toFail: viewerMedia.doubleTapGestureRecognizer)
 
@@ -206,16 +199,19 @@ class NCViewerMediaPage: UIViewController {
     }
 
     @objc func viewUnload() {
-
         navigationController?.popViewController(animated: true)
     }
 
-    @objc func openMenuMore() {
+    @objc private func openMenuMore() {
 
         let imageIcon = UIImage(contentsOfFile: CCUtility.getDirectoryProviderStorageIconOcId(currentViewController.metadata.ocId, etag: currentViewController.metadata.etag))
         NCViewer.shared.toggleMenu(viewController: self, metadata: currentViewController.metadata, webView: false, imageIcon: imageIcon)
     }
 
+    @objc private func toggleDetail() {
+        currentViewController.toggleDetail()
+    }
+
     func changeScreenMode(mode: ScreenMode) {
 
         let metadata = currentViewController.metadata
@@ -240,11 +236,11 @@ class NCViewerMediaPage: UIViewController {
                 textColor = .white
             } else {
                 NCUtility.shared.colorNavigationController(navigationController, backgroundColor: .systemBackground, titleColor: .label, tintColor: nil, withoutShadow: false)
-                view.backgroundColor = .systemBackground
+                view.backgroundColor = .systemGray6
                 textColor = .label
             }
 
-        } else {
+        } else if !currentViewController.detailView.isShown {
 
             navigationController?.setNavigationBarHidden(true, animated: true)
             hideStatusBar = true
@@ -274,13 +270,11 @@ class NCViewerMediaPage: UIViewController {
     }
 
     @objc func startTimerAutoHide() {
-
         timerAutoHide?.invalidate()
         timerAutoHide = Timer.scheduledTimer(timeInterval: timerAutoHideSeconds, target: self, selector: #selector(autoHide), userInfo: nil, repeats: true)
     }
 
     @objc func autoHide() {
-
         let metadata = currentViewController.metadata
         if metadata.isVideo, viewerMediaScreenMode == .normal {
             changeScreenMode(mode: .full)
@@ -558,6 +552,16 @@ extension NCViewerMediaPage: UIPageViewControllerDelegate, UIPageViewControllerD
 
         guard let nextViewController = pendingViewControllers.first as? NCViewerMedia else { return }
         nextIndex = nextViewController.index
+
+        if nextViewController.metadata.isImage {
+            navigationItem.rightBarButtonItems = [moreNavigationItem, imageDetailNavigationItem]
+        } else {
+            navigationItem.rightBarButtonItems = [moreNavigationItem]
+        }
+
+        if nextViewController.detailView.isShown {
+            changeScreenMode(mode: .normal)
+        }
     }
 
     // END TRANSITION
@@ -619,6 +623,7 @@ extension NCViewerMediaPage: UIGestureRecognizerDelegate {
     }
 
     @objc func didSingleTapWith(gestureRecognizer: UITapGestureRecognizer) {
+        if currentViewController.detailView.isShown { return }
 
         if viewerMediaScreenMode == .full {
             changeScreenMode(mode: .normal)
@@ -627,12 +632,10 @@ extension NCViewerMediaPage: UIGestureRecognizerDelegate {
         }
     }
 
-    //
-    // LIVE PHOTO
-    //
+    // MARK: - Live Photo
     @objc func didLongpressGestureEvent(gestureRecognizer: UITapGestureRecognizer) {
 
-        if !currentViewController.metadata.livePhoto { return }
+        if !currentViewController.metadata.livePhoto || currentViewController.detailView.isShown { return }
 
         if gestureRecognizer.state == .began {
             let fileName = (currentViewController.metadata.fileNameView as NSString).deletingPathExtension + ".mov"
@@ -664,3 +667,16 @@ extension UIPageViewController {
         }
     }
 }
+
+extension NCViewerMediaPage: NCViewerMediaViewDelegate {
+    func didOpenDetail() {
+        changeScreenMode(mode: .normal)
+        imageDetailNavigationItem.image = UIImage(systemName: "info.circle.fill")
+    }
+
+    func didCloseDetail() {
+        imageDetailNavigationItem.image = UIImage(systemName: "info.circle")
+    }
+}
+
+