Browse Source

Add new section in settings for Nextcloud apps (#2461)

* Add new section in settings

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 new cell/refactor

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

* Add url scheme for switching account

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

* Add docs

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

* Change icon

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

* Add colors

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

* Add more padding

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

* Remove hardcoded urls

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

* Refactor

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

* Remove unused files

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

* Update lint

---------

Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>
Milen Pivchev 1 year ago
parent
commit
e66bcd6f6f

+ 29 - 1
Nextcloud.xcodeproj/project.pbxproj

@@ -117,6 +117,11 @@
 		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 */; };
 		F3A7AFC62A41AA82001FC89C /* BaseUIXCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A7AFC52A41AA82001FC89C /* BaseUIXCTestCase.swift */; };
+		F39298972A3B12CB00509762 /* BaseNCMoreCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F39298962A3B12CB00509762 /* BaseNCMoreCell.swift */; };
+		F3BB464D2A39ADCC00461F6E /* NCMoreAppSuggestionsCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F3BB464C2A39ADCC00461F6E /* NCMoreAppSuggestionsCell.xib */; };
+		F3BB464F2A39EBE500461F6E /* NCMoreUserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB464E2A39EBE500461F6E /* NCMoreUserCell.swift */; };
+		F3BB46522A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB46512A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift */; };
+		F3BB46542A3A1E9D00461F6E /* CCCellMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB46532A3A1E9D00461F6E /* CCCellMore.swift */; };
 		F700222C1EC479840080073F /* Custom.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F700222B1EC479840080073F /* Custom.xcassets */; };
 		F700222D1EC479840080073F /* Custom.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F700222B1EC479840080073F /* Custom.xcassets */; };
 		F700510122DF63AC003A3356 /* NCShare.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F700510022DF63AC003A3356 /* NCShare.storyboard */; };
@@ -811,6 +816,11 @@
 		F31F69602A2F907800162F76 /* __Snapshots__ */ = {isa = PBXFileReference; lastKnownFileType = folder; path = __Snapshots__; 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>"; };
+		F39298962A3B12CB00509762 /* BaseNCMoreCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseNCMoreCell.swift; sourceTree = "<group>"; };
+		F3BB464C2A39ADCC00461F6E /* NCMoreAppSuggestionsCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NCMoreAppSuggestionsCell.xib; sourceTree = "<group>"; };
+		F3BB464E2A39EBE500461F6E /* NCMoreUserCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMoreUserCell.swift; sourceTree = "<group>"; };
+		F3BB46512A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMoreAppSuggestionsCell.swift; sourceTree = "<group>"; };
+		F3BB46532A3A1E9D00461F6E /* CCCellMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CCCellMore.swift; sourceTree = "<group>"; };
 		F3A7AFC52A41AA82001FC89C /* BaseUIXCTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseUIXCTestCase.swift; sourceTree = "<group>"; };
 		F700222B1EC479840080073F /* Custom.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Custom.xcassets; sourceTree = "<group>"; };
 		F700510022DF63AC003A3356 /* NCShare.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCShare.storyboard; sourceTree = "<group>"; };
@@ -1529,6 +1539,19 @@
 			path = Advanced;
 			sourceTree = "<group>";
 		};
+		F3BB46502A39EC2D00461F6E /* Cells */ = {
+			isa = PBXGroup;
+			children = (
+				F3BB464C2A39ADCC00461F6E /* NCMoreAppSuggestionsCell.xib */,
+				F3BB46512A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift */,
+				F7EFC0C5256BC77700461AAD /* NCMoreUserCell.xib */,
+				F3BB464E2A39EBE500461F6E /* NCMoreUserCell.swift */,
+				F3BB46532A3A1E9D00461F6E /* CCCellMore.swift */,
+				F39298962A3B12CB00509762 /* BaseNCMoreCell.swift */,
+			);
+			path = Cells;
+			sourceTree = "<group>";
+		};
 		C0046CDB2A17B98400D87C9D /* NextcloudUITests */ = {
 			isa = PBXGroup;
 			children = (
@@ -2265,7 +2288,7 @@
 		F7CB68942541670D0050EC94 /* More */ = {
 			isa = PBXGroup;
 			children = (
-				F7EFC0C5256BC77700461AAD /* NCMoreUserCell.xib */,
+				F3BB46502A39EC2D00461F6E /* Cells */,
 				F7CB68992541676B0050EC94 /* NCMore.storyboard */,
 				F73F537E1E929C8500F8678D /* NCMore.swift */,
 			);
@@ -3102,6 +3125,7 @@
 				F7362A1F220C853A005101B5 /* LaunchScreen.storyboard in Resources */,
 				F77444F622281649000D5EB0 /* NCGridMediaCell.xib in Resources */,
 				F78ACD4421903CF20088454D /* NCListCell.xib in Resources */,
+				F3BB464D2A39ADCC00461F6E /* NCMoreAppSuggestionsCell.xib in Resources */,
 				F7F4F10727ECDBDB008676F9 /* Inconsolata-Black.ttf in Resources */,
 				F761856D29E98543006EB3B0 /* NCIntroCollectionViewCell.xib in Resources */,
 				F78ACD4621903D010088454D /* NCGridCell.xib in Resources */,
@@ -3597,6 +3621,7 @@
 				F7B8CD91261AF3F7007C1359 /* NCNetworkingChunkedUpload.swift in Sources */,
 				F757CC8D29E82D0500F31428 /* NCGroupfolders.swift in Sources */,
 				F760329F252F0F8E0015A421 /* NCTransferCell.swift in Sources */,
+				F3BB46542A3A1E9D00461F6E /* CCCellMore.swift in Sources */,
 				AF68326A27BE65A90010BF0B /* NCMenuAction.swift in Sources */,
 				F7682FE023C36B0500983A04 /* NCMainTabBar.swift in Sources */,
 				F7A0D1352591FBC5008F8A13 /* String+Extension.swift in Sources */,
@@ -3606,6 +3631,7 @@
 				F70460522499061800BB98A7 /* NotificationCenter+MainThread.swift in Sources */,
 				F78F74362163781100C2ADAD /* NCTrash.swift in Sources */,
 				AF817EF1274BC781009ED85B /* NCUserBaseUrl.swift in Sources */,
+				F39298972A3B12CB00509762 /* BaseNCMoreCell.swift in Sources */,
 				AF2D7C7C2742556F00ADF566 /* NCShareLinkCell.swift in Sources */,
 				F7E41316294A19B300839300 /* UIView+Extension.swift in Sources */,
 				F31F69502A2F707E00162F76 /* SwiftUIView+Extensions.swift in Sources */,
@@ -3663,6 +3689,7 @@
 				F76D364628A4F8BF00214537 /* NCActivityIndicator.swift in Sources */,
 				F7020FCE2233D7F700B7297D /* NCCreateFormUploadVoiceNote.swift in Sources */,
 				F7134186259747BA00768D21 /* NCPushNotification.m in Sources */,
+				F3BB464F2A39EBE500461F6E /* NCMoreUserCell.swift in Sources */,
 				F726EEEC1FED1C820030B9C8 /* NCEndToEndInitialize.swift in Sources */,
 				F79A65C62191D95E00FF6DCC /* NCSelect.swift in Sources */,
 				F75D19E325EFE09000D74598 /* NCTrash+Menu.swift in Sources */,
@@ -3692,6 +3719,7 @@
 				F749B64A297B0CBB00087535 /* NCManageDatabase+Share.swift in Sources */,
 				F7C9555521F0C5470024296E /* NCActivity.swift in Sources */,
 				F7725A60251F33BB00D125E0 /* NCFiles.swift in Sources */,
+				F3BB46522A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift in Sources */,
 				F704B5E52430AA8000632F5F /* NCCreateFormUploadConflict.swift in Sources */,
 				F765608F23BF813600765969 /* NCContentPresenter.swift in Sources */,
 				F343A4BB2A1E734600DDA874 /* Optional+Extension.swift in Sources */,

+ 20 - 0
iOSClient/AppDelegate.swift

@@ -947,6 +947,26 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
                 }
             }
             return true
+
+        /*
+         Example:
+         nextcloud://open-and-switch-account?user=marinofaggiana&url=https://cloud.nextcloud.com
+         */
+
+        } else if !account.isEmpty && scheme == NCGlobal.shared.appScheme && action == "open-and-switch-account" {
+            guard let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return false }
+            let queryItems = urlComponents.queryItems
+
+            guard let userScheme = CCUtility.value(forKey: "user", fromQueryItems: queryItems) else { return false }
+            guard let urlScheme = CCUtility.value(forKey: "url", fromQueryItems: queryItems) else { return false }
+
+            // If the account doesn't exist, return false which will open the app without switching
+            if getMatchedAccount(userId: userScheme, url: urlScheme) == nil {
+                return false
+            }
+
+            // Otherwise open the app and switch accounts
+            return true
         } else {
             let applicationHandle = NCApplicationHandle()
             let isHandled = applicationHandle.applicationOpenURL(url)

+ 15 - 0
iOSClient/Images.xcassets/more-apps-template.imageset/Contents.json

@@ -0,0 +1,15 @@
+{
+  "images" : [
+    {
+      "filename" : "more-apps.svg",
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  },
+  "properties" : {
+    "template-rendering-intent" : "template"
+  }
+}

File diff suppressed because it is too large
+ 0 - 0
iOSClient/Images.xcassets/more-apps-template.imageset/more-apps.svg


+ 15 - 0
iOSClient/Images.xcassets/notes-template.imageset/Contents.json

@@ -0,0 +1,15 @@
+{
+  "images" : [
+    {
+      "filename" : "notes.svg",
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  },
+  "properties" : {
+    "template-rendering-intent" : "template"
+  }
+}

+ 1 - 0
iOSClient/Images.xcassets/notes-template.imageset/notes.svg

@@ -0,0 +1 @@
+<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m0 0h24v24h-24z" fill="none"/><path d="m3 17.25v3.75h3.75l11.06-11.06-3.75-3.75zm17.71-10.21c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75z" fill="#fff"/></svg>

+ 15 - 0
iOSClient/Images.xcassets/talk-template.imageset/Contents.json

@@ -0,0 +1,15 @@
+{
+  "images" : [
+    {
+      "filename" : "talk.png",
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  },
+  "properties" : {
+    "template-rendering-intent" : "template"
+  }
+}

BIN
iOSClient/Images.xcassets/talk-template.imageset/talk.png


+ 36 - 0
iOSClient/More/Cells/BaseNCMoreCell.swift

@@ -0,0 +1,36 @@
+//
+//  BaseNCMoreCell.swift
+//  Nextcloud
+//
+//  Created by Milen on 15.06.23.
+//  Copyright © 2023 Marino Faggiana. All rights reserved.
+//
+
+import Foundation
+
+class BaseNCMoreCell: UITableViewCell {
+    let selectionColor: UIView = UIView()
+    let defaultCornerRadius: CGFloat = 10.0
+
+    override var frame: CGRect {
+        get {
+            return super.frame
+        }
+        set (newFrame) {
+            var frame = newFrame
+            let newWidth = frame.width * 0.90
+            let space = (frame.width - newWidth) / 2
+            frame.size.width = newWidth
+            frame.origin.x += space
+            super.frame = frame
+        }
+    }
+
+    override func awakeFromNib() {
+        super.awakeFromNib()
+
+        selectedBackgroundView = selectionColor
+        backgroundColor = .secondarySystemGroupedBackground
+        layer.cornerRadius = defaultCornerRadius
+    }
+}

+ 18 - 0
iOSClient/More/Cells/CCCellMore.swift

@@ -0,0 +1,18 @@
+//
+//  CCCellMore.swift
+//  Nextcloud
+//
+//  Created by Milen on 14.06.23.
+//  Copyright © 2023 Marino Faggiana. All rights reserved.
+//
+
+import Foundation
+
+class CCCellMore: BaseNCMoreCell {
+    @IBOutlet weak var labelText: UILabel!
+    @IBOutlet weak var imageIcon: UIImageView!
+    @IBOutlet weak var separator: UIView!
+    @IBOutlet weak var separatorHeigth: NSLayoutConstraint!
+
+    static let reuseIdentifier = "CCCellMore"
+}

+ 57 - 0
iOSClient/More/Cells/NCMoreAppSuggestionsCell.swift

@@ -0,0 +1,57 @@
+//
+//  NCMoreAppSuggestionsCell.swift
+//  Nextcloud
+//
+//  Created by Milen on 14.06.23.
+//  Copyright © 2023 Marino Faggiana. All rights reserved.
+//
+
+import Foundation
+
+class NCMoreAppSuggestionsCell: BaseNCMoreCell {
+    @IBOutlet weak var talkView: UIStackView!
+    @IBOutlet weak var notesView: UIStackView!
+    @IBOutlet weak var moreAppsView: UIStackView!
+
+    static let reuseIdentifier = "NCMoreAppSuggestionsCell"
+
+    static func fromNib() -> UINib {
+        return UINib(nibName: "NCMoreAppSuggestionsCell", bundle: nil)
+    }
+
+    override func awakeFromNib() {
+        super.awakeFromNib()
+        backgroundColor = .clear
+
+        talkView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(talkTapped)))
+        notesView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(notesTapped)))
+        moreAppsView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(moreAppsTapped)))
+    }
+
+    @objc func talkTapped() {
+        guard let url = URL(string: NCGlobal.shared.talkSchemeUrl) else { return }
+
+        if UIApplication.shared.canOpenURL(url) {
+            UIApplication.shared.open(url)
+        } else {
+            guard let url = URL(string: NCGlobal.shared.talkAppStoreUrl) else { return }
+            UIApplication.shared.open(url)
+        }
+    }
+
+    @objc func notesTapped() {
+        guard let url = URL(string: NCGlobal.shared.notesSchemeUrl) else { return }
+
+        if UIApplication.shared.canOpenURL(url) {
+            UIApplication.shared.open(url)
+        } else {
+            guard let url = URL(string: NCGlobal.shared.notesAppStoreUrl) else { return }
+            UIApplication.shared.open(url)
+        }
+    }
+
+    @objc func moreAppsTapped() {
+        guard let url = URL(string: NCGlobal.shared.moreAppsUrl) else { return }
+        UIApplication.shared.open(url)
+    }
+}

+ 154 - 0
iOSClient/More/Cells/NCMoreAppSuggestionsCell.xib

@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+    <device id="retina6_12" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
+        <capability name="System colors in document resources" minToolsVersion="11.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+        <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="0.0" id="dVh-cS-UwU" userLabel="App Suggestion Cell" customClass="NCMoreAppSuggestionsCell" customModule="Nextcloud" customModuleProvider="target">
+            <rect key="frame" x="0.0" y="0.0" width="393" height="44"/>
+            <autoresizingMask key="autoresizingMask"/>
+            <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="dVh-cS-UwU" id="9Ma-CX-ckc">
+                <rect key="frame" x="0.0" y="0.0" width="393" height="44"/>
+                <autoresizingMask key="autoresizingMask"/>
+                <subviews>
+                    <stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="ppx-X2-oTM">
+                        <rect key="frame" x="88" y="0.0" width="217" height="44"/>
+                        <subviews>
+                            <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalCentering" alignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="Tbt-MZ-3jf">
+                                <rect key="frame" x="0.0" y="0.0" width="67" height="44"/>
+                                <subviews>
+                                    <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="749" image="talk-template" translatesAutoresizingMaskIntoConstraints="NO" id="Uby-L5-yV4" userLabel="Icon">
+                                        <rect key="frame" x="24.666666666666671" y="8" width="18" height="18"/>
+                                        <color key="tintColor" systemColor="linkColor"/>
+                                        <constraints>
+                                            <constraint firstAttribute="height" constant="18" id="Vl1-Ip-OUw"/>
+                                            <constraint firstAttribute="width" constant="18" id="m7P-Or-Esh"/>
+                                        </constraints>
+                                    </imageView>
+                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Talk" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9cY-LF-9EI">
+                                        <rect key="frame" x="8" y="26" width="51" height="10"/>
+                                        <fontDescription key="fontDescription" type="system" pointSize="10"/>
+                                        <color key="textColor" systemColor="systemBlueColor"/>
+                                        <nil key="highlightedColor"/>
+                                    </label>
+                                </subviews>
+                                <color key="backgroundColor" systemColor="secondarySystemGroupedBackgroundColor"/>
+                                <constraints>
+                                    <constraint firstAttribute="trailing" secondItem="9cY-LF-9EI" secondAttribute="trailing" constant="8" id="9lr-vm-6sL"/>
+                                    <constraint firstAttribute="bottom" secondItem="9cY-LF-9EI" secondAttribute="bottom" constant="8" id="BIL-hp-tNO"/>
+                                    <constraint firstItem="9cY-LF-9EI" firstAttribute="leading" secondItem="Tbt-MZ-3jf" secondAttribute="leading" constant="8" id="UjW-1p-fCe"/>
+                                    <constraint firstItem="9cY-LF-9EI" firstAttribute="top" secondItem="Uby-L5-yV4" secondAttribute="bottom" id="twR-46-lPV"/>
+                                    <constraint firstItem="Uby-L5-yV4" firstAttribute="top" secondItem="Tbt-MZ-3jf" secondAttribute="top" constant="8" id="xcP-gx-xol"/>
+                                </constraints>
+                                <userDefinedRuntimeAttributes>
+                                    <userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
+                                        <integer key="value" value="8"/>
+                                    </userDefinedRuntimeAttribute>
+                                </userDefinedRuntimeAttributes>
+                            </stackView>
+                            <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalCentering" alignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="4jA-wm-kCc">
+                                <rect key="frame" x="75" y="0.0" width="67" height="44"/>
+                                <subviews>
+                                    <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="749" image="notes-template" translatesAutoresizingMaskIntoConstraints="NO" id="BT7-Nt-9RH" userLabel="Icon">
+                                        <rect key="frame" x="24.666666666666657" y="8" width="18" height="18"/>
+                                        <color key="tintColor" systemColor="linkColor"/>
+                                        <constraints>
+                                            <constraint firstAttribute="height" constant="18" id="3YI-i4-Ykd"/>
+                                            <constraint firstAttribute="width" constant="18" id="Riu-Cr-O0l"/>
+                                        </constraints>
+                                    </imageView>
+                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Notes" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="tQM-o0-e3W">
+                                        <rect key="frame" x="8" y="26" width="51" height="10"/>
+                                        <fontDescription key="fontDescription" type="system" pointSize="10"/>
+                                        <color key="textColor" systemColor="systemBlueColor"/>
+                                        <nil key="highlightedColor"/>
+                                    </label>
+                                </subviews>
+                                <color key="backgroundColor" systemColor="secondarySystemGroupedBackgroundColor"/>
+                                <constraints>
+                                    <constraint firstItem="tQM-o0-e3W" firstAttribute="top" secondItem="BT7-Nt-9RH" secondAttribute="bottom" id="1ho-7Y-4Ty"/>
+                                    <constraint firstItem="BT7-Nt-9RH" firstAttribute="top" secondItem="4jA-wm-kCc" secondAttribute="top" constant="8" id="4QH-zk-5ph"/>
+                                    <constraint firstAttribute="bottom" secondItem="tQM-o0-e3W" secondAttribute="bottom" constant="8" id="E3e-ra-dwn"/>
+                                    <constraint firstItem="tQM-o0-e3W" firstAttribute="leading" secondItem="4jA-wm-kCc" secondAttribute="leading" constant="8" id="jg3-Wn-IgW"/>
+                                    <constraint firstAttribute="trailing" secondItem="tQM-o0-e3W" secondAttribute="trailing" constant="8" id="yNV-v7-Z9z"/>
+                                </constraints>
+                                <userDefinedRuntimeAttributes>
+                                    <userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
+                                        <integer key="value" value="8"/>
+                                    </userDefinedRuntimeAttribute>
+                                </userDefinedRuntimeAttributes>
+                            </stackView>
+                            <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalCentering" alignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="SA6-fX-Xmx">
+                                <rect key="frame" x="150" y="0.0" width="67" height="44"/>
+                                <subviews>
+                                    <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="749" image="more-apps-template" translatesAutoresizingMaskIntoConstraints="NO" id="dzE-0b-iBn" userLabel="Icon">
+                                        <rect key="frame" x="24.666666666666686" y="8" width="18" height="18"/>
+                                        <color key="tintColor" systemColor="linkColor"/>
+                                        <constraints>
+                                            <constraint firstAttribute="width" constant="18" id="Yo0-sL-LJN"/>
+                                            <constraint firstAttribute="height" constant="18" id="bOc-vN-4Ry"/>
+                                        </constraints>
+                                    </imageView>
+                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="More apps" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GIQ-5h-VeA">
+                                        <rect key="frame" x="8" y="26" width="51" height="12"/>
+                                        <fontDescription key="fontDescription" type="system" pointSize="10"/>
+                                        <color key="textColor" systemColor="systemBlueColor"/>
+                                        <nil key="highlightedColor"/>
+                                    </label>
+                                </subviews>
+                                <color key="backgroundColor" systemColor="secondarySystemGroupedBackgroundColor"/>
+                                <constraints>
+                                    <constraint firstAttribute="bottom" secondItem="GIQ-5h-VeA" secondAttribute="bottom" constant="8" id="8xI-Ad-3Gc"/>
+                                    <constraint firstAttribute="trailing" secondItem="GIQ-5h-VeA" secondAttribute="trailing" constant="8" id="dW8-DS-4wp"/>
+                                    <constraint firstItem="dzE-0b-iBn" firstAttribute="top" secondItem="SA6-fX-Xmx" secondAttribute="top" constant="8" id="ixx-ut-Ss2"/>
+                                    <constraint firstItem="GIQ-5h-VeA" firstAttribute="top" secondItem="dzE-0b-iBn" secondAttribute="bottom" id="rWG-kv-J2m"/>
+                                    <constraint firstItem="GIQ-5h-VeA" firstAttribute="leading" secondItem="SA6-fX-Xmx" secondAttribute="leading" constant="8" id="zjO-Go-Md7"/>
+                                </constraints>
+                                <userDefinedRuntimeAttributes>
+                                    <userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
+                                        <integer key="value" value="8"/>
+                                    </userDefinedRuntimeAttribute>
+                                </userDefinedRuntimeAttributes>
+                            </stackView>
+                        </subviews>
+                        <constraints>
+                            <constraint firstAttribute="bottom" secondItem="Tbt-MZ-3jf" secondAttribute="bottom" id="hS2-sl-Kz0"/>
+                            <constraint firstItem="Tbt-MZ-3jf" firstAttribute="top" secondItem="ppx-X2-oTM" secondAttribute="top" id="u4O-GE-2yF"/>
+                        </constraints>
+                    </stackView>
+                </subviews>
+                <constraints>
+                    <constraint firstAttribute="bottom" secondItem="ppx-X2-oTM" secondAttribute="bottom" id="Ler-rD-wfw"/>
+                    <constraint firstItem="ppx-X2-oTM" firstAttribute="top" secondItem="9Ma-CX-ckc" secondAttribute="top" id="Yxe-5b-StO"/>
+                    <constraint firstItem="ppx-X2-oTM" firstAttribute="centerX" secondItem="9Ma-CX-ckc" secondAttribute="centerX" id="mtt-g7-1xb"/>
+                </constraints>
+            </tableViewCellContentView>
+            <connections>
+                <outlet property="moreAppsView" destination="SA6-fX-Xmx" id="fiE-FN-en3"/>
+                <outlet property="notesView" destination="4jA-wm-kCc" id="XuQ-8X-RDH"/>
+                <outlet property="talkView" destination="Tbt-MZ-3jf" id="1Tf-ff-0k1"/>
+            </connections>
+            <point key="canvasLocation" x="-221" y="-65"/>
+        </tableViewCell>
+    </objects>
+    <resources>
+        <image name="more-apps-template" width="32" height="32"/>
+        <image name="notes-template" width="24" height="24"/>
+        <image name="talk-template" width="600" height="600"/>
+        <systemColor name="linkColor">
+            <color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+        </systemColor>
+        <systemColor name="secondarySystemGroupedBackgroundColor">
+            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+        </systemColor>
+        <systemColor name="systemBlueColor">
+            <color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+        </systemColor>
+    </resources>
+</document>

+ 23 - 0
iOSClient/More/Cells/NCMoreUserCell.swift

@@ -0,0 +1,23 @@
+//
+//  NCMoreUserCell.swift
+//  Nextcloud
+//
+//  Created by Milen on 14.06.23.
+//  Copyright © 2023 Marino Faggiana. All rights reserved.
+//
+
+import Foundation
+import MarqueeLabel
+
+class NCMoreUserCell: BaseNCMoreCell {
+    @IBOutlet weak var displayName: UILabel!
+    @IBOutlet weak var avatar: UIImageView!
+    @IBOutlet weak var icon: UIImageView!
+    @IBOutlet weak var status: MarqueeLabel!
+
+    static let reuseIdentifier = "NCMoreUserCell"
+
+    static func fromNib() -> UINib {
+        return UINib(nibName: "NCMoreUserCell", bundle: nil)
+    }
+}

+ 3 - 3
iOSClient/More/NCMoreUserCell.xib → iOSClient/More/Cells/NCMoreUserCell.xib

@@ -1,15 +1,15 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21225" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
     <device id="retina4_7" orientation="portrait" appearance="light"/>
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21207"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
     <objects>
         <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="CustomCellFileAndDirectory"/>
         <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
-        <tableViewCell contentMode="scaleToFill" selectionStyle="blue" indentationWidth="0.0" reuseIdentifier="userCell" rowHeight="107" id="2" customClass="NCMoreUserCell" customModule="Nextcloud" customModuleProvider="target">
+        <tableViewCell contentMode="scaleToFill" selectionStyle="blue" indentationWidth="0.0" reuseIdentifier="NCMoreUserCell" rowHeight="107" id="2" customClass="NCMoreUserCell" customModule="Nextcloud" customModuleProvider="target">
             <rect key="frame" x="0.0" y="0.0" width="600" height="75"/>
             <autoresizingMask key="autoresizingMask" flexibleMaxY="YES"/>
             <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="2" id="sQq-jC-UEV">

+ 52 - 81
iOSClient/More/NCMore.swift

@@ -23,7 +23,6 @@
 
 import UIKit
 import NextcloudKit
-import MarqueeLabel
 
 class NCMore: UIViewController, UITableViewDelegate, UITableViewDataSource {
 
@@ -33,13 +32,13 @@ class NCMore: UIViewController, UITableViewDelegate, UITableViewDataSource {
     @IBOutlet weak var progressQuota: UIProgressView!
     @IBOutlet weak var viewQuota: UIView!
 
+    var moreAppsMenu: [NKExternalSite] = []
     var functionMenu: [NKExternalSite] = []
     var externalSiteMenu: [NKExternalSite] = []
     var settingsMenu: [NKExternalSite] = []
     var quotaMenu: [NKExternalSite] = []
 
     let appDelegate = UIApplication.shared.delegate as! AppDelegate
-    let defaultCornerRadius: CGFloat = 10.0
     let applicationHandle = NCApplicationHandle()
     
     var tabAccount: tableAccount?
@@ -55,7 +54,8 @@ class NCMore: UIViewController, UITableViewDelegate, UITableViewDataSource {
         tableView.delegate = self
         tableView.dataSource = self
         tableView.backgroundColor = .systemGroupedBackground
-        tableView.register(UINib(nibName: "NCMoreUserCell", bundle: nil), forCellReuseIdentifier: "userCell")
+        tableView.register(NCMoreUserCell.fromNib(), forCellReuseIdentifier: NCMoreUserCell.reuseIdentifier)
+        tableView.register(NCMoreAppSuggestionsCell.fromNib(), forCellReuseIdentifier: NCMoreAppSuggestionsCell.reuseIdentifier)
 
         // create tap gesture recognizer
         let tapQuota = UITapGestureRecognizer(target: self, action: #selector(tapLabelQuotaExternalSite))
@@ -90,6 +90,7 @@ class NCMore: UIViewController, UITableViewDelegate, UITableViewDataSource {
         var quota: String = ""
 
         // Clear
+        moreAppsMenu.removeAll()
         functionMenu.removeAll()
         externalSiteMenu.removeAll()
         settingsMenu.removeAll()
@@ -97,6 +98,10 @@ class NCMore: UIViewController, UITableViewDelegate, UITableViewDataSource {
         labelQuotaExternalSite.text = ""
         progressQuota.progressTintColor = NCBrandColor.shared.brandElement
 
+        // ITEM : More apps
+        item = NKExternalSite()
+        moreAppsMenu.append(item)
+
         // ITEM : Transfer
         item = NKExternalSite()
         item.name = "_transfers_"
@@ -265,65 +270,67 @@ class NCMore: UIViewController, UITableViewDelegate, UITableViewDataSource {
     }
 
     func numberOfSections(in tableView: UITableView) -> Int {
+        let defaultSections = 4
 
         if externalSiteMenu.count == 0 {
-            return 3
+            return defaultSections
         } else {
-            return 4
+            return defaultSections + 1
         }
     }
     
     func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
-        
         if section == 0 {
             return 10
+        } else if section == 1 || section == 2 {
+            return 1
         } else {
             return 20
         }
     }
 
     func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
-
-        var cont = 0
+        var count = 0
 
         if section == 0 {
-            cont = tabAccount == nil ? 0 : 1
+            count = tabAccount == nil ? 0 : 1
         } else if section == 1 {
-            // Menu Normal
-            cont = functionMenu.count
+            // Menu More apps
+            count = moreAppsMenu.count
+        } else if section == 2 {
+            // Menu function
+            count = functionMenu.count
         } else {
             switch numberOfSections(in: tableView) {
             case 3:
                 // Menu Settings
-                if section == 2 {
-                    cont = settingsMenu.count
+                if section == 3 {
+                    count = settingsMenu.count
                 }
             case 4:
                 // Menu External Site
-                if section == 2 {
-                    cont = externalSiteMenu.count
+                if section == 4 {
+                    count = externalSiteMenu.count
                 }
                 // Menu Settings
-                if section == 3 {
-                    cont = settingsMenu.count
+                if section == 5 {
+                    count = settingsMenu.count
                 }
             default:
-                cont = 0
+                count = 0
             }
         }
 
-        return cont
+        return count
     }
     
     func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
 
         var item = NKExternalSite()
 
-        // change color selection and disclosure indicator
-        let selectionColor: UIView = UIView()
         if indexPath.section == 0 {
 
-            let cell = tableView.dequeueReusableCell(withIdentifier: "userCell", for: indexPath) as! NCMoreUserCell
+            let cell = tableView.dequeueReusableCell(withIdentifier: NCMoreUserCell.reuseIdentifier, for: indexPath) as! NCMoreUserCell
 
             cell.avatar.image = nil
             cell.icon.image = nil
@@ -343,8 +350,6 @@ class NCMore: UIViewController, UITableViewDelegate, UITableViewDataSource {
                 }
                 cell.displayName.textColor = .label
             }
-            cell.selectedBackgroundView = selectionColor
-            cell.backgroundColor = .secondarySystemGroupedBackground
             cell.accessoryType = UITableViewCell.AccessoryType.disclosureIndicator
 
             if NCGlobal.shared.capabilityUserStatusEnabled, let account = NCManageDatabase.shared.getAccount(predicate: NSPredicate(format: "account == %@", appDelegate.account)) {
@@ -360,25 +365,33 @@ class NCMore: UIViewController, UITableViewDelegate, UITableViewDataSource {
                 }
             }
             
-            cell.layer.cornerRadius = defaultCornerRadius
             cell.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner, .layerMaxXMaxYCorner, .layerMinXMaxYCorner]
 
             return cell
 
+        } else if indexPath.section == 1 {
+            let cell = tableView.dequeueReusableCell(withIdentifier: NCMoreAppSuggestionsCell.reuseIdentifier, for: indexPath) as! NCMoreAppSuggestionsCell
+
+            return cell
         } else {
 
             let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CCCellMore
 
-            // Menu Normal
+            // Menu More Apps
             if indexPath.section == 1 {
+                item = moreAppsMenu[indexPath.row]
+            }
+
+            // Menu Function
+            if indexPath.section == 2 {
                 item = functionMenu[indexPath.row]
             }
             // Menu External Site
-            if numberOfSections(in: tableView) == 4 && indexPath.section == 2 {
+            if numberOfSections(in: tableView) == 5 && indexPath.section == 3 {
                 item = externalSiteMenu[indexPath.row]
             }
             // Menu Settings
-            if (numberOfSections(in: tableView) == 3 && indexPath.section == 2) || (numberOfSections(in: tableView) == 4 && indexPath.section == 3) {
+            if (numberOfSections(in: tableView) == 4 && indexPath.section == 3) || (numberOfSections(in: tableView) == 5 && indexPath.section == 4) {
                 item = settingsMenu[indexPath.row]
             }
 
@@ -387,18 +400,14 @@ class NCMore: UIViewController, UITableViewDelegate, UITableViewDataSource {
             cell.labelText?.text = NSLocalizedString(item.name, comment: "")
             cell.labelText.textColor = .label
 
-            cell.selectedBackgroundView = selectionColor
-            cell.backgroundColor = .secondarySystemGroupedBackground
             cell.accessoryType = UITableViewCell.AccessoryType.disclosureIndicator
 
             cell.separator.backgroundColor = .separator
             cell.separatorHeigth.constant = 0.4
             
-            cell.layer.cornerRadius = 0
             let rows = tableView.numberOfRows(inSection: indexPath.section)
             
             if indexPath.row == 0 {
-                cell.layer.cornerRadius = defaultCornerRadius
                 if indexPath.row == rows - 1 {
                     cell.separator.backgroundColor = .clear
                     cell.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner, .layerMaxXMaxYCorner, .layerMinXMaxYCorner]
@@ -406,9 +415,10 @@ class NCMore: UIViewController, UITableViewDelegate, UITableViewDataSource {
                     cell.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner]
                 }
             } else if indexPath.row == rows - 1 {
-                cell.layer.cornerRadius = defaultCornerRadius
                 cell.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]
                 cell.separator.backgroundColor = .clear
+            } else {
+                cell.layer.cornerRadius = 0
             }
             
             return cell
@@ -420,23 +430,29 @@ class NCMore: UIViewController, UITableViewDelegate, UITableViewDataSource {
 
         var item = NKExternalSite()
 
+        // Menu Function
         if indexPath.section == 0 {
             tapImageLogoManageAccount()
             return
         }
 
-        // Menu Function
+        // Menu More Apps
         if indexPath.section == 1 {
+            item = moreAppsMenu[indexPath.row]
+        }
+
+        // Menu Function
+        if indexPath.section == 2 {
             item = functionMenu[indexPath.row]
         }
 
         // Menu External Site
-        if numberOfSections(in: tableView) == 4 && indexPath.section == 2 {
+        if numberOfSections(in: tableView) == 5 && indexPath.section == 3 {
             item = externalSiteMenu[indexPath.row]
         }
 
         // Menu Settings
-        if (numberOfSections(in: tableView) == 3 && indexPath.section == 2) || (numberOfSections(in: tableView) == 4 && indexPath.section == 3) {
+        if (numberOfSections(in: tableView) == 4 && indexPath.section == 3) || (numberOfSections(in: tableView) == 5 && indexPath.section == 4) {
             item = settingsMenu[indexPath.row]
         }
 
@@ -483,53 +499,8 @@ class NCMore: UIViewController, UITableViewDelegate, UITableViewDataSource {
             alertController.addAction(actionYes)
             alertController.addAction(actionNo)
             self.present(alertController, animated: true, completion: nil)
-
         } else {
             applicationHandle.didSelectItem(item, viewController: self)
         }
     }
 }
-
-class CCCellMore: UITableViewCell {
-
-    @IBOutlet weak var labelText: UILabel!
-    @IBOutlet weak var imageIcon: UIImageView!
-    @IBOutlet weak var separator: UIView!
-    @IBOutlet weak var separatorHeigth: NSLayoutConstraint!
-
-    override var frame: CGRect {
-        get {
-            return super.frame
-        }
-        set (newFrame) {
-            var frame = newFrame
-            let newWidth = frame.width * 0.90
-            let space = (frame.width - newWidth) / 2
-            frame.size.width = newWidth
-            frame.origin.x += space
-            super.frame = frame
-        }
-    }
-}
-
-class NCMoreUserCell: UITableViewCell {
-
-    @IBOutlet weak var displayName: UILabel!
-    @IBOutlet weak var avatar: UIImageView!
-    @IBOutlet weak var icon: UIImageView!
-    @IBOutlet weak var status: MarqueeLabel!
-    
-    override var frame: CGRect {
-        get {
-            return super.frame
-        }
-        set (newFrame) {
-            var frame = newFrame
-            let newWidth = frame.width * 0.90
-            let space = (frame.width - newWidth) / 2
-            frame.size.width = newWidth
-            frame.origin.x += space
-            super.frame = frame
-        }
-    }
-}

+ 8 - 1
iOSClient/NCGlobal.swift

@@ -442,6 +442,13 @@ class NCGlobal: NSObject {
     var capabilityExternalSites: Bool                           = false
     var capabilityGroupfoldersEnabled: Bool                     = false // NC27
 
+    // MORE APPS
+    let talkSchemeUrl                                           = "nextcloudtalk://"
+    let notesSchemeUrl                                          = "nextcloudnotes://"
+    let talkAppStoreUrl                                         = "https://apps.apple.com/de/app/nextcloud-talk/id1296825574"
+    let notesAppStoreUrl                                        = "https://apps.apple.com/de/app/nextcloud-notes/id813973264"
+    let moreAppsUrl                                             = "https://www.apple.com/us/search/nextcloud?src=globalnav"
+
     // SNAPSHOT PREVIEW
-    let defaultSnapshotConfiguration = "DefaultPreviewConfiguration"
+    let defaultSnapshotConfiguration                            = "DefaultPreviewConfiguration"
 }

Some files were not shown because too many files changed in this diff