Browse Source

Revert "Fix"

This reverts commit d030c00d9bef204cd6bb5e9b1dea6d53eb8053fa.
Marino Faggiana 1 year ago
parent
commit
f0fa2db8b1
100 changed files with 12360 additions and 0 deletions
  1. 17 0
      iOSClient/.tx/config
  2. 134 0
      iOSClient/Account Request/NCAccountRequest.storyboard
  3. 235 0
      iOSClient/Account Request/NCAccountRequest.swift
  4. 129 0
      iOSClient/Account Request/NCShareAccounts.storyboard
  5. 124 0
      iOSClient/Account Request/NCShareAccounts.swift
  6. 135 0
      iOSClient/Activity/NCActivity.storyboard
  7. 555 0
      iOSClient/Activity/NCActivity.swift
  8. 59 0
      iOSClient/Activity/NCActivityCommentView.swift
  9. 60 0
      iOSClient/Activity/NCActivityCommentView.xib
  10. 263 0
      iOSClient/Activity/NCActivityTableViewCell.swift
  11. 983 0
      iOSClient/AppDelegate.swift
  12. 46 0
      iOSClient/BrowserWeb/NCBrowserWeb.storyboard
  13. 120 0
      iOSClient/BrowserWeb/NCBrowserWeb.swift
  14. 293 0
      iOSClient/Color/NCColorPicker.storyboard
  15. 215 0
      iOSClient/Color/NCColorPicker.swift
  16. 566 0
      iOSClient/Data/NCDataSource.swift
  17. 204 0
      iOSClient/Data/NCDatabase.swift
  18. 75 0
      iOSClient/Data/NCElementsJSON.swift
  19. 483 0
      iOSClient/Data/NCManageDatabase+Account.swift
  20. 320 0
      iOSClient/Data/NCManageDatabase+Activity.swift
  21. 134 0
      iOSClient/Data/NCManageDatabase+Avatar.swift
  22. 123 0
      iOSClient/Data/NCManageDatabase+DashboardWidget.swift
  23. 240 0
      iOSClient/Data/NCManageDatabase+Directory.swift
  24. 261 0
      iOSClient/Data/NCManageDatabase+E2EE.swift
  25. 94 0
      iOSClient/Data/NCManageDatabase+Groupfolders.swift
  26. 128 0
      iOSClient/Data/NCManageDatabase+LayoutForView.swift
  27. 1127 0
      iOSClient/Data/NCManageDatabase+Metadata.swift
  28. 233 0
      iOSClient/Data/NCManageDatabase+Share.swift
  29. 143 0
      iOSClient/Data/NCManageDatabase+Video.swift
  30. 1086 0
      iOSClient/Data/NCManageDatabase.swift
  31. 690 0
      iOSClient/Diagnostics/NCCapabilitiesViewController.storyboard
  32. 350 0
      iOSClient/Diagnostics/NCCapabilitiesViewController.swift
  33. 132 0
      iOSClient/EmptyView/NCEmptyDataSet.swift
  34. 67 0
      iOSClient/EmptyView/NCEmptyView.xib
  35. 40 0
      iOSClient/Extensions/Array+Extension.swift
  36. 41 0
      iOSClient/Extensions/Data+Extension.swift
  37. 33 0
      iOSClient/Extensions/DateFormatter+Extension.swift
  38. 38 0
      iOSClient/Extensions/NSMutableAttributedString+Extension.swift
  39. 33 0
      iOSClient/Extensions/NSNotificationCenter+MainThread.h
  40. 45 0
      iOSClient/Extensions/NSNotificationCenter+MainThread.m
  41. 34 0
      iOSClient/Extensions/NotificationCenter+MainThread.swift
  42. 92 0
      iOSClient/Extensions/String+Extension.swift
  43. 101 0
      iOSClient/Extensions/UIAlertController+Extension.swift
  44. 34 0
      iOSClient/Extensions/UIApplication+Extension.swift
  45. 55 0
      iOSClient/Extensions/UIBarButton+Extension.swift
  46. 148 0
      iOSClient/Extensions/UIColor+Extension.swift
  47. 48 0
      iOSClient/Extensions/UIControl+Extension.swift
  48. 38 0
      iOSClient/Extensions/UIDevice+Extension.swift
  49. 31 0
      iOSClient/Extensions/UIFont+Extension.swift
  50. 231 0
      iOSClient/Extensions/UIImage+Extension.swift
  51. 53 0
      iOSClient/Extensions/UIImage+animatedGIF.h
  52. 142 0
      iOSClient/Extensions/UIImage+animatedGIF.m
  53. 79 0
      iOSClient/Extensions/UINavigationController+Extension.swift
  54. 32 0
      iOSClient/Extensions/UITabBarController+Extension.swift
  55. 77 0
      iOSClient/Extensions/UIToolbar+Extension.swift
  56. 48 0
      iOSClient/Extensions/UIView+Extension.swift
  57. 64 0
      iOSClient/Extensions/UIViewController+Extension.swift
  58. 16 0
      iOSClient/Extensions/View+Extension.swift
  59. 53 0
      iOSClient/Favorites/NCFavorite.storyboard
  60. 125 0
      iOSClient/Favorites/NCFavorite.swift
  61. 53 0
      iOSClient/Files/NCFiles.storyboard
  62. 198 0
      iOSClient/Files/NCFiles.swift
  63. BIN
      iOSClient/Font/Inconsolata/Inconsolata-Black.ttf
  64. BIN
      iOSClient/Font/Inconsolata/Inconsolata-Bold.ttf
  65. BIN
      iOSClient/Font/Inconsolata/Inconsolata-ExtraBold.ttf
  66. BIN
      iOSClient/Font/Inconsolata/Inconsolata-ExtraLight.ttf
  67. BIN
      iOSClient/Font/Inconsolata/Inconsolata-Light.ttf
  68. BIN
      iOSClient/Font/Inconsolata/Inconsolata-Medium.ttf
  69. BIN
      iOSClient/Font/Inconsolata/Inconsolata-Regular.ttf
  70. BIN
      iOSClient/Font/Inconsolata/Inconsolata-SemiBold.ttf
  71. 59 0
      iOSClient/GUI/ComponentView.swift
  72. 98 0
      iOSClient/GUI/HUDView.swift
  73. 53 0
      iOSClient/Groupfolders/NCGroupfolders.storyboard
  74. 156 0
      iOSClient/Groupfolders/NCGroupfolders.swift
  75. 6 0
      iOSClient/Images.xcassets/Contents.json
  76. 26 0
      iOSClient/Images.xcassets/MenuGroupByAlphabetic.imageset/Contents.json
  77. BIN
      iOSClient/Images.xcassets/MenuGroupByAlphabetic.imageset/MenuGroupByAlphabetic.png
  78. BIN
      iOSClient/Images.xcassets/MenuGroupByAlphabetic.imageset/MenuGroupByAlphabetic@3x-1.png
  79. BIN
      iOSClient/Images.xcassets/MenuGroupByAlphabetic.imageset/MenuGroupByAlphabetic@3x.png
  80. 26 0
      iOSClient/Images.xcassets/MenuGroupByDate.imageset/Contents.json
  81. BIN
      iOSClient/Images.xcassets/MenuGroupByDate.imageset/MenuGroupByDate.png
  82. BIN
      iOSClient/Images.xcassets/MenuGroupByDate.imageset/MenuGroupByDate@2x.png
  83. BIN
      iOSClient/Images.xcassets/MenuGroupByDate.imageset/MenuGroupByDate@3x.png
  84. 26 0
      iOSClient/Images.xcassets/MenuGroupByFile.imageset/Contents.json
  85. BIN
      iOSClient/Images.xcassets/MenuGroupByFile.imageset/MenuGroupByFile.png
  86. BIN
      iOSClient/Images.xcassets/MenuGroupByFile.imageset/MenuGroupByFile@2x.png
  87. BIN
      iOSClient/Images.xcassets/MenuGroupByFile.imageset/MenuGroupByFile@3x.png
  88. 26 0
      iOSClient/Images.xcassets/MenuOrderByFileName.imageset/Contents.json
  89. BIN
      iOSClient/Images.xcassets/MenuOrderByFileName.imageset/MenuOrderByFileName.png
  90. BIN
      iOSClient/Images.xcassets/MenuOrderByFileName.imageset/MenuOrderByFileName@2x.png
  91. BIN
      iOSClient/Images.xcassets/MenuOrderByFileName.imageset/MenuOrderByFileName@3x.png
  92. 26 0
      iOSClient/Images.xcassets/MenuOrdeyByDate.imageset/Contents.json
  93. BIN
      iOSClient/Images.xcassets/MenuOrdeyByDate.imageset/MenuOrdeyByDate.png
  94. BIN
      iOSClient/Images.xcassets/MenuOrdeyByDate.imageset/MenuOrdeyByDate@2x.png
  95. BIN
      iOSClient/Images.xcassets/MenuOrdeyByDate.imageset/MenuOrdeyByDate@3x.png
  96. 26 0
      iOSClient/Images.xcassets/MenuOrdinamentoAscendente.imageset/Contents.json
  97. BIN
      iOSClient/Images.xcassets/MenuOrdinamentoAscendente.imageset/MenuOrdinamentoAscendente.png
  98. BIN
      iOSClient/Images.xcassets/MenuOrdinamentoAscendente.imageset/MenuOrdinamentoAscendente@2x.png
  99. BIN
      iOSClient/Images.xcassets/MenuOrdinamentoAscendente.imageset/MenuOrdinamentoAscendente@3x.png
  100. 26 0
      iOSClient/Images.xcassets/MenuOrdinamentoDiscendente.imageset/Contents.json

+ 17 - 0
iOSClient/.tx/config

@@ -0,0 +1,17 @@
+[main]
+host = https://www.transifex.com
+
+[o:nextcloud:p:nextcloud:r:ios]
+file_filter = Supporting Files/<lang>.lproj/Localizable.strings
+source_file = Supporting Files/en.lproj/Localizable.strings
+source_lang = en
+type        = STRINGS
+lang_map    = hu_HU: hu, es_DO: es-DO, es_419: es-419, cs_CZ: cs-CZ, es_EC: es-EC, es_GT: es-GT, es_PY: es-PY, es_SV: es-SV, pt_BR: pt-BR, fi_FI: fi-FI, nb_NO: nb-NO, es_UY: es-UY, es_MX: es-MX, sk_SK: sk-SK, pt_PT: pt-PT, en_GB: en-GB, ka_GE: ka-GE, es_HN: es-HN, zh_CN: zh-Hans, es_AR: es-AR, es_NI: es-NI, es_PE: es-PE, ja_JP: ja-JP, es_CL: es-CL, es_PR: es-PR, zh_TW: zh-Hant-TW, es_CO: es-CO, es_CR: es-CR, es_PA: es-PA
+
+[o:nextcloud:p:nextcloud:r:ios-info]
+file_filter = Supporting Files/<lang>.lproj/InfoPlist.strings
+source_file = Supporting Files/en.lproj/InfoPlist.strings
+source_lang = en
+type        = STRINGS
+lang_map    = es_NI: es-NI, es_PY: es-PY, fi_FI: fi-FI, nb_NO: nb-NO, es_PE: es-PE, es_UY: es-UY, pt_BR: pt-BR, cs_CZ: cs-CZ, en_GB: en-GB, es_CR: es-CR, es_GT: es-GT, es_419: es-419, zh_CN: zh-Hans, es_CO: es-CO, es_DO: es-DO, es_PA: es-PA, es_PR: es-PR, es_SV: es-SV, es_EC: es-EC, es_MX: es-MX, hu_HU: hu, ka_GE: ka-GE, zh_TW: zh-Hant-TW, es_AR: es-AR, sk_SK: sk-SK, es_CL: es-CL, es_HN: es-HN, pt_PT: pt-PT, ja_JP: ja-JP
+

+ 134 - 0
iOSClient/Account Request/NCAccountRequest.storyboard

@@ -0,0 +1,134 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="V0q-CP-xMJ">
+    <device id="retina5_5" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
+        <capability name="System colors in document resources" minToolsVersion="11.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <scenes>
+        <!--Account Request-->
+        <scene sceneID="L90-uG-f4z">
+            <objects>
+                <viewController id="V0q-CP-xMJ" customClass="NCAccountRequest" customModule="Nextcloud" customModuleProvider="target" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="4vK-ua-S0e"/>
+                        <viewControllerLayoutGuide type="bottom" id="hTI-Bw-Fws"/>
+                    </layoutGuides>
+                    <view key="view" contentMode="scaleToFill" id="gzh-6E-hc4">
+                        <rect key="frame" x="0.0" y="0.0" width="300" height="310"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <subviews>
+                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Accounts" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nZr-nE-ths">
+                                <rect key="frame" x="5" y="5" width="290" height="18"/>
+                                <fontDescription key="fontDescription" type="system" weight="medium" pointSize="15"/>
+                                <nil key="textColor"/>
+                                <nil key="highlightedColor"/>
+                            </label>
+                            <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="Pdo-MB-AhU">
+                                <rect key="frame" x="7" y="28" width="286" height="282"/>
+                                <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                                <prototypes>
+                                    <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="Cell" rowHeight="60" id="Q4K-la-J3W">
+                                        <rect key="frame" x="0.0" y="50" width="286" height="60"/>
+                                        <autoresizingMask key="autoresizingMask"/>
+                                        <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Q4K-la-J3W" id="IkA-cK-iZV">
+                                            <rect key="frame" x="0.0" y="0.0" width="286" height="60"/>
+                                            <autoresizingMask key="autoresizingMask"/>
+                                            <subviews>
+                                                <imageView clipsSubviews="YES" userInteractionEnabled="NO" tag="10" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="4cH-oC-YBd" userLabel="Avatar">
+                                                    <rect key="frame" x="0.0" y="15" width="30" height="30"/>
+                                                    <constraints>
+                                                        <constraint firstAttribute="height" constant="30" id="Efd-BU-u61"/>
+                                                        <constraint firstAttribute="width" constant="30" id="qFy-Tu-ov6"/>
+                                                    </constraints>
+                                                </imageView>
+                                                <label opaque="NO" userInteractionEnabled="NO" tag="20" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pWI-iZ-BTy" userLabel="User">
+                                                    <rect key="frame" x="40" y="20.333333333333336" width="226" height="19.333333333333336"/>
+                                                    <fontDescription key="fontDescription" type="boldSystem" pointSize="16"/>
+                                                    <nil key="textColor"/>
+                                                    <nil key="highlightedColor"/>
+                                                </label>
+                                                <label opaque="NO" userInteractionEnabled="NO" tag="30" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sWT-NJ-ihR" userLabel="Url">
+                                                    <rect key="frame" x="40" y="40.666666666666664" width="226" height="16"/>
+                                                    <fontDescription key="fontDescription" type="system" pointSize="13"/>
+                                                    <nil key="textColor"/>
+                                                    <nil key="highlightedColor"/>
+                                                </label>
+                                                <imageView clipsSubviews="YES" userInteractionEnabled="NO" tag="40" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="x47-dQ-alI" userLabel="Active">
+                                                    <rect key="frame" x="271" y="22.666666666666668" width="15" height="15.000000000000004"/>
+                                                    <constraints>
+                                                        <constraint firstAttribute="height" constant="15" id="SXt-LG-c5N"/>
+                                                        <constraint firstAttribute="width" constant="15" id="vdZ-4R-gY5"/>
+                                                    </constraints>
+                                                </imageView>
+                                            </subviews>
+                                            <constraints>
+                                                <constraint firstItem="4cH-oC-YBd" firstAttribute="leading" secondItem="IkA-cK-iZV" secondAttribute="leading" id="3cz-yE-yoQ"/>
+                                                <constraint firstAttribute="trailing" secondItem="x47-dQ-alI" secondAttribute="trailing" id="Dna-rj-LLQ"/>
+                                                <constraint firstItem="x47-dQ-alI" firstAttribute="centerY" secondItem="IkA-cK-iZV" secondAttribute="centerY" id="Ghs-sZ-Bzf"/>
+                                                <constraint firstItem="sWT-NJ-ihR" firstAttribute="leading" secondItem="4cH-oC-YBd" secondAttribute="trailing" constant="10" id="MxV-Du-vUw"/>
+                                                <constraint firstItem="4cH-oC-YBd" firstAttribute="centerY" secondItem="IkA-cK-iZV" secondAttribute="centerY" id="WuT-iT-3xk"/>
+                                                <constraint firstItem="sWT-NJ-ihR" firstAttribute="top" secondItem="pWI-iZ-BTy" secondAttribute="bottom" constant="1" id="ZVm-u7-JGI"/>
+                                                <constraint firstItem="x47-dQ-alI" firstAttribute="leading" secondItem="pWI-iZ-BTy" secondAttribute="trailing" constant="5" id="e8E-uH-gvt"/>
+                                                <constraint firstItem="pWI-iZ-BTy" firstAttribute="centerY" secondItem="IkA-cK-iZV" secondAttribute="centerY" id="jUS-89-RRO"/>
+                                                <constraint firstItem="x47-dQ-alI" firstAttribute="leading" secondItem="sWT-NJ-ihR" secondAttribute="trailing" constant="5" id="lIk-Sj-EgC"/>
+                                                <constraint firstItem="pWI-iZ-BTy" firstAttribute="leading" secondItem="4cH-oC-YBd" secondAttribute="trailing" constant="10" id="mlI-8s-1Ae"/>
+                                            </constraints>
+                                        </tableViewCellContentView>
+                                        <color key="backgroundColor" systemColor="secondarySystemGroupedBackgroundColor"/>
+                                    </tableViewCell>
+                                </prototypes>
+                                <connections>
+                                    <outlet property="dataSource" destination="V0q-CP-xMJ" id="xmA-NY-Eyz"/>
+                                    <outlet property="delegate" destination="V0q-CP-xMJ" id="j64-3x-N2u"/>
+                                </connections>
+                            </tableView>
+                            <progressView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" progress="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="e8R-j7-ObS">
+                                <rect key="frame" x="0.0" y="308" width="300" height="2"/>
+                                <constraints>
+                                    <constraint firstAttribute="height" constant="2" id="G3U-2x-imL"/>
+                                </constraints>
+                                <color key="progressTintColor" systemColor="systemBlueColor"/>
+                            </progressView>
+                        </subviews>
+                        <color key="backgroundColor" systemColor="secondarySystemGroupedBackgroundColor"/>
+                        <constraints>
+                            <constraint firstItem="e8R-j7-ObS" firstAttribute="leading" secondItem="gzh-6E-hc4" secondAttribute="leading" id="1gS-Ca-1Ov"/>
+                            <constraint firstItem="Pdo-MB-AhU" firstAttribute="top" secondItem="nZr-nE-ths" secondAttribute="bottom" constant="5" id="5vV-YC-uzH"/>
+                            <constraint firstAttribute="trailing" secondItem="Pdo-MB-AhU" secondAttribute="trailing" constant="7" id="819-yV-vz7"/>
+                            <constraint firstItem="hTI-Bw-Fws" firstAttribute="top" secondItem="e8R-j7-ObS" secondAttribute="bottom" id="Cko-PC-TiW"/>
+                            <constraint firstAttribute="trailing" secondItem="nZr-nE-ths" secondAttribute="trailing" constant="5" id="DPW-MV-oKc"/>
+                            <constraint firstItem="hTI-Bw-Fws" firstAttribute="top" secondItem="Pdo-MB-AhU" secondAttribute="bottom" id="Ife-Ku-hGQ"/>
+                            <constraint firstItem="nZr-nE-ths" firstAttribute="leading" secondItem="gzh-6E-hc4" secondAttribute="leading" constant="5" id="SI9-xL-6s8"/>
+                            <constraint firstItem="Pdo-MB-AhU" firstAttribute="leading" secondItem="gzh-6E-hc4" secondAttribute="leading" constant="7" id="Y5n-ju-hts"/>
+                            <constraint firstAttribute="trailing" secondItem="e8R-j7-ObS" secondAttribute="trailing" id="chh-t9-vJN"/>
+                            <constraint firstItem="nZr-nE-ths" firstAttribute="top" secondItem="4vK-ua-S0e" secondAttribute="bottom" constant="5" id="oyJ-sj-j5N"/>
+                        </constraints>
+                    </view>
+                    <navigationItem key="navigationItem" id="Zon-2j-rsc"/>
+                    <size key="freeformSize" width="300" height="310"/>
+                    <connections>
+                        <outlet property="progressView" destination="e8R-j7-ObS" id="2gM-MB-IhE"/>
+                        <outlet property="tableView" destination="Pdo-MB-AhU" id="AcD-SW-2ga"/>
+                        <outlet property="titleLabel" destination="nZr-nE-ths" id="UbA-Dl-0Ad"/>
+                    </connections>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="qdm-Cl-C5l" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="1453.125" y="133.75"/>
+        </scene>
+    </scenes>
+    <resources>
+        <systemColor name="secondarySystemGroupedBackgroundColor">
+            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+        </systemColor>
+        <systemColor name="systemBackgroundColor">
+            <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>

+ 235 - 0
iOSClient/Account Request/NCAccountRequest.swift

@@ -0,0 +1,235 @@
+//
+//  NCAccountRequest.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 26/02/21.
+//  Copyright © 2021 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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
+import NextcloudKit
+
+public protocol NCAccountRequestDelegate: AnyObject {
+    func accountRequestAddAccount()
+    func accountRequestChangeAccount(account: String)
+}
+
+// optional func
+public extension NCAccountRequestDelegate {
+    func accountRequestAddAccount() {}
+    func accountRequestChangeAccount(account: String) {}
+}
+
+class NCAccountRequest: UIViewController {
+
+    @IBOutlet weak var titleLabel: UILabel!
+    @IBOutlet weak var tableView: UITableView!
+    @IBOutlet weak var progressView: UIProgressView!
+
+    public var accounts: [tableAccount] = []
+    public var activeAccount: tableAccount?
+    public let heightCell: CGFloat = 60
+    public var enableTimerProgress: Bool = true
+    public var enableAddAccount: Bool = false
+    public var dismissDidEnterBackground: Bool = false
+    public weak var delegate: NCAccountRequestDelegate?
+
+    private var timer: Timer?
+    private var time: Float = 0
+    private let secondsAutoDismiss: Float = 3
+
+    // MARK: - View Life Cycle
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+
+        titleLabel.text = NSLocalizedString("_account_select_", comment: "")
+        tableView.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.size.width, height: 1))
+        tableView.separatorStyle = UITableViewCell.SeparatorStyle.none
+
+        view.backgroundColor = .secondarySystemBackground
+        tableView.backgroundColor = .secondarySystemBackground
+
+        progressView.trackTintColor = .clear
+        progressView.progress = 1
+        if enableTimerProgress {
+            progressView.isHidden = false
+        } else {
+            progressView.isHidden = true
+        }
+
+        NotificationCenter.default.addObserver(self, selector: #selector(startTimer), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterApplicationDidBecomeActive), object: nil)
+        NotificationCenter.default.addObserver(self, selector: #selector(applicationDidEnterBackground), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterApplicationDidEnterBackground), object: nil)
+    }
+
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+    }
+
+    override func viewDidAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+
+        let visibleCells = tableView.visibleCells
+        var numAccounts = accounts.count
+        if enableAddAccount { numAccounts += 1 }
+
+        if visibleCells.count == numAccounts {
+            tableView.isScrollEnabled = false
+        }
+    }
+
+    override func viewWillDisappear(_ animated: Bool) {
+        super.viewWillDisappear(animated)
+
+        timer?.invalidate()
+    }
+
+    // MARK: - NotificationCenter
+
+    @objc func applicationDidEnterBackground() {
+
+        if dismissDidEnterBackground {
+            dismiss(animated: false)
+        }
+    }
+
+    // MARK: - Progress
+
+    @objc func startTimer() {
+
+        if enableTimerProgress {
+            time = 0
+            timer?.invalidate()
+            timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateProgress), userInfo: nil, repeats: true)
+            progressView.isHidden = false
+        } else {
+            progressView.isHidden = true
+        }
+    }
+
+    @objc func updateProgress() {
+
+        time += 0.1
+        if time >= secondsAutoDismiss {
+            dismiss(animated: true)
+        } else {
+            progressView.progress = 1 - (time / secondsAutoDismiss)
+        }
+    }
+}
+
+extension NCAccountRequest: UITableViewDelegate {
+
+    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
+
+        timer?.invalidate()
+        progressView.progress = 0
+    }
+
+    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
+        if decelerate {
+//            startTimer()
+        }
+    }
+
+    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
+//        startTimer()
+    }
+
+    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
+        return heightCell
+    }
+
+    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+        if indexPath.row == accounts.count {
+
+            dismiss(animated: true)
+            delegate?.accountRequestAddAccount()
+
+        } else {
+
+            let account = accounts[indexPath.row]
+            if account.account != activeAccount?.account {
+                dismiss(animated: true) {
+                    self.delegate?.accountRequestChangeAccount(account: account.account)
+                }
+            } else {
+                dismiss(animated: true)
+            }
+        }
+    }
+}
+
+extension NCAccountRequest: UITableViewDataSource {
+
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        if enableAddAccount {
+            return accounts.count + 1
+        } else {
+            return accounts.count
+        }
+    }
+
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
+        cell.backgroundColor = tableView.backgroundColor
+
+        let avatarImage = cell.viewWithTag(10) as? UIImageView
+        let userLabel = cell.viewWithTag(20) as? UILabel
+        let urlLabel = cell.viewWithTag(30) as? UILabel
+        let activeImage = cell.viewWithTag(40) as? UIImageView
+
+        userLabel?.text = ""
+        urlLabel?.text = ""
+
+        if indexPath.row == accounts.count {
+
+            avatarImage?.image = NCUtility.shared.loadImage(named: "plus").image(color: .systemBlue, size: 15)
+            avatarImage?.contentMode = .center
+            userLabel?.text = NSLocalizedString("_add_account_", comment: "")
+            userLabel?.textColor = .systemBlue
+            userLabel?.font = UIFont.systemFont(ofSize: 15)
+
+        } else {
+
+            let account = accounts[indexPath.row]
+
+            avatarImage?.image = NCUtility.shared.loadUserImage(
+                for: account.user,
+                   displayName: account.displayName,
+                   userBaseUrl: account)
+
+            if account.alias.isEmpty {
+                userLabel?.text = account.user.uppercased()
+                urlLabel?.text = (URL(string: account.urlBase)?.host ?? "")
+            } else {
+                userLabel?.text = account.alias.uppercased()
+            }
+
+            if account.active {
+                activeImage?.image = NCUtility.shared.loadImage(named: "checkmark").image(color: .systemBlue, size: 30)
+            } else {
+                activeImage?.image = nil
+            }
+        }
+
+        return cell
+    }
+}

+ 129 - 0
iOSClient/Account Request/NCShareAccounts.storyboard

@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="V0q-CP-xMJ">
+    <device id="retina5_5" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
+        <capability name="Image references" minToolsVersion="12.0"/>
+        <capability name="System colors in document resources" minToolsVersion="11.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <scenes>
+        <!--Share Accounts-->
+        <scene sceneID="L90-uG-f4z">
+            <objects>
+                <viewController id="V0q-CP-xMJ" customClass="NCShareAccounts" customModule="Nextcloud" customModuleProvider="target" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="4vK-ua-S0e"/>
+                        <viewControllerLayoutGuide type="bottom" id="hTI-Bw-Fws"/>
+                    </layoutGuides>
+                    <view key="view" contentMode="scaleToFill" id="gzh-6E-hc4">
+                        <rect key="frame" x="0.0" y="0.0" width="300" height="310"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <subviews>
+                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Accounts" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nZr-nE-ths">
+                                <rect key="frame" x="5" y="5" width="290" height="18"/>
+                                <fontDescription key="fontDescription" type="system" weight="medium" pointSize="15"/>
+                                <nil key="textColor"/>
+                                <nil key="highlightedColor"/>
+                            </label>
+                            <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="Pdo-MB-AhU">
+                                <rect key="frame" x="7" y="28" width="286" height="282"/>
+                                <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                                <prototypes>
+                                    <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="Cell" rowHeight="60" id="Q4K-la-J3W">
+                                        <rect key="frame" x="0.0" y="50" width="286" height="60"/>
+                                        <autoresizingMask key="autoresizingMask"/>
+                                        <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Q4K-la-J3W" id="IkA-cK-iZV">
+                                            <rect key="frame" x="0.0" y="0.0" width="286" height="60"/>
+                                            <autoresizingMask key="autoresizingMask"/>
+                                            <subviews>
+                                                <imageView clipsSubviews="YES" userInteractionEnabled="NO" tag="10" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="4cH-oC-YBd" userLabel="Avatar">
+                                                    <rect key="frame" x="0.0" y="16" width="30" height="28.666666666666664"/>
+                                                    <color key="tintColor" systemColor="systemGray2Color"/>
+                                                    <constraints>
+                                                        <constraint firstAttribute="height" constant="30" id="Efd-BU-u61"/>
+                                                        <constraint firstAttribute="width" constant="30" id="qFy-Tu-ov6"/>
+                                                    </constraints>
+                                                    <imageReference key="image" image="person.circle" catalog="system" variableValue="0.80000000000000004"/>
+                                                </imageView>
+                                                <label opaque="NO" userInteractionEnabled="NO" tag="20" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pWI-iZ-BTy" userLabel="User">
+                                                    <rect key="frame" x="40" y="21.666666666666668" width="226" height="17.000000000000004"/>
+                                                    <fontDescription key="fontDescription" type="boldSystem" pointSize="14"/>
+                                                    <nil key="textColor"/>
+                                                    <nil key="highlightedColor"/>
+                                                </label>
+                                                <label opaque="NO" userInteractionEnabled="NO" tag="30" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sWT-NJ-ihR" userLabel="Url">
+                                                    <rect key="frame" x="40" y="39.666666666666664" width="226" height="16"/>
+                                                    <fontDescription key="fontDescription" type="system" pointSize="13"/>
+                                                    <nil key="textColor"/>
+                                                    <nil key="highlightedColor"/>
+                                                </label>
+                                                <imageView clipsSubviews="YES" userInteractionEnabled="NO" tag="40" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="chevron.right" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="x47-dQ-alI" userLabel="Active">
+                                                    <rect key="frame" x="271" y="24.666666666666671" width="15" height="11.333333333333329"/>
+                                                    <color key="tintColor" systemColor="systemGray2Color"/>
+                                                    <constraints>
+                                                        <constraint firstAttribute="height" constant="15" id="SXt-LG-c5N"/>
+                                                        <constraint firstAttribute="width" constant="15" id="vdZ-4R-gY5"/>
+                                                    </constraints>
+                                                </imageView>
+                                            </subviews>
+                                            <constraints>
+                                                <constraint firstItem="4cH-oC-YBd" firstAttribute="leading" secondItem="IkA-cK-iZV" secondAttribute="leading" id="3cz-yE-yoQ"/>
+                                                <constraint firstAttribute="trailing" secondItem="x47-dQ-alI" secondAttribute="trailing" id="Dna-rj-LLQ"/>
+                                                <constraint firstItem="x47-dQ-alI" firstAttribute="centerY" secondItem="IkA-cK-iZV" secondAttribute="centerY" id="Ghs-sZ-Bzf"/>
+                                                <constraint firstItem="sWT-NJ-ihR" firstAttribute="leading" secondItem="4cH-oC-YBd" secondAttribute="trailing" constant="10" id="MxV-Du-vUw"/>
+                                                <constraint firstItem="4cH-oC-YBd" firstAttribute="centerY" secondItem="IkA-cK-iZV" secondAttribute="centerY" id="WuT-iT-3xk"/>
+                                                <constraint firstItem="sWT-NJ-ihR" firstAttribute="top" secondItem="pWI-iZ-BTy" secondAttribute="bottom" constant="1" id="ZVm-u7-JGI"/>
+                                                <constraint firstItem="x47-dQ-alI" firstAttribute="leading" secondItem="pWI-iZ-BTy" secondAttribute="trailing" constant="5" id="e8E-uH-gvt"/>
+                                                <constraint firstItem="pWI-iZ-BTy" firstAttribute="centerY" secondItem="IkA-cK-iZV" secondAttribute="centerY" id="jUS-89-RRO"/>
+                                                <constraint firstItem="x47-dQ-alI" firstAttribute="leading" secondItem="sWT-NJ-ihR" secondAttribute="trailing" constant="5" id="lIk-Sj-EgC"/>
+                                                <constraint firstItem="pWI-iZ-BTy" firstAttribute="leading" secondItem="4cH-oC-YBd" secondAttribute="trailing" constant="10" id="mlI-8s-1Ae"/>
+                                            </constraints>
+                                        </tableViewCellContentView>
+                                        <color key="backgroundColor" systemColor="secondarySystemGroupedBackgroundColor"/>
+                                    </tableViewCell>
+                                </prototypes>
+                                <connections>
+                                    <outlet property="dataSource" destination="V0q-CP-xMJ" id="xmA-NY-Eyz"/>
+                                    <outlet property="delegate" destination="V0q-CP-xMJ" id="j64-3x-N2u"/>
+                                </connections>
+                            </tableView>
+                        </subviews>
+                        <color key="backgroundColor" systemColor="secondarySystemGroupedBackgroundColor"/>
+                        <constraints>
+                            <constraint firstItem="Pdo-MB-AhU" firstAttribute="top" secondItem="nZr-nE-ths" secondAttribute="bottom" constant="5" id="5vV-YC-uzH"/>
+                            <constraint firstAttribute="trailing" secondItem="Pdo-MB-AhU" secondAttribute="trailing" constant="7" id="819-yV-vz7"/>
+                            <constraint firstAttribute="trailing" secondItem="nZr-nE-ths" secondAttribute="trailing" constant="5" id="DPW-MV-oKc"/>
+                            <constraint firstItem="hTI-Bw-Fws" firstAttribute="top" secondItem="Pdo-MB-AhU" secondAttribute="bottom" id="Ife-Ku-hGQ"/>
+                            <constraint firstItem="nZr-nE-ths" firstAttribute="leading" secondItem="gzh-6E-hc4" secondAttribute="leading" constant="5" id="SI9-xL-6s8"/>
+                            <constraint firstItem="Pdo-MB-AhU" firstAttribute="leading" secondItem="gzh-6E-hc4" secondAttribute="leading" constant="7" id="Y5n-ju-hts"/>
+                            <constraint firstItem="nZr-nE-ths" firstAttribute="top" secondItem="4vK-ua-S0e" secondAttribute="bottom" constant="5" id="oyJ-sj-j5N"/>
+                        </constraints>
+                    </view>
+                    <navigationItem key="navigationItem" id="Zon-2j-rsc"/>
+                    <size key="freeformSize" width="300" height="310"/>
+                    <connections>
+                        <outlet property="tableView" destination="Pdo-MB-AhU" id="AcD-SW-2ga"/>
+                        <outlet property="titleLabel" destination="nZr-nE-ths" id="UbA-Dl-0Ad"/>
+                    </connections>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="qdm-Cl-C5l" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="1453.125" y="133.75"/>
+        </scene>
+    </scenes>
+    <resources>
+        <image name="chevron.right" catalog="system" width="97" height="128"/>
+        <image name="person.circle" catalog="system" width="128" height="123"/>
+        <systemColor name="secondarySystemGroupedBackgroundColor">
+            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+        </systemColor>
+        <systemColor name="systemBackgroundColor">
+            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+        </systemColor>
+        <systemColor name="systemGray2Color">
+            <color red="0.68235294117647061" green="0.68235294117647061" blue="0.69803921568627447" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+        </systemColor>
+    </resources>
+</document>

+ 124 - 0
iOSClient/Account Request/NCShareAccounts.swift

@@ -0,0 +1,124 @@
+//
+//  NCShareAccounts.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 22/11/22.
+//  Copyright © 2021 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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
+import NextcloudKit
+
+public protocol NCShareAccountsDelegate: AnyObject {
+    func selected(url: String, user: String)
+}
+
+// optional func
+public extension NCShareAccountsDelegate {
+    func selected(url: String, user: String) {}
+}
+
+class NCShareAccounts: UIViewController {
+
+    @IBOutlet weak var titleLabel: UILabel!
+    @IBOutlet weak var tableView: UITableView!
+
+    public var accounts: [NKShareAccounts.DataAccounts] = []
+    public let heightCell: CGFloat = 60
+    public var enableTimerProgress: Bool = true
+    public var dismissDidEnterBackground: Bool = true
+    public weak var delegate: NCShareAccountsDelegate?
+
+    // MARK: - View Life Cycle
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+
+        titleLabel.text = NSLocalizedString("_account_select_to_add_", comment: "")
+
+        tableView.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.size.width, height: 1))
+        tableView.separatorStyle = UITableViewCell.SeparatorStyle.none
+
+        view.backgroundColor = .secondarySystemBackground
+        tableView.backgroundColor = .secondarySystemBackground
+    }
+
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+    }
+
+    override func viewDidAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+
+        let visibleCells = tableView.visibleCells
+
+        if visibleCells.count == accounts.count {
+            tableView.isScrollEnabled = false
+        }
+    }
+}
+
+extension NCShareAccounts: UITableViewDelegate {
+
+    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
+        return heightCell
+    }
+
+    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+        dismiss(animated: true) {
+            let account = self.accounts[indexPath.row]
+            self.delegate?.selected(url: account.url, user: account.user)
+        }
+    }
+}
+
+extension NCShareAccounts: UITableViewDataSource {
+
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return accounts.count
+    }
+
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
+        cell.backgroundColor = tableView.backgroundColor
+
+        let avatarImage = cell.viewWithTag(10) as? UIImageView
+        let userLabel = cell.viewWithTag(20) as? UILabel
+        let urlLabel = cell.viewWithTag(30) as? UILabel
+
+        userLabel?.text = ""
+        urlLabel?.text = ""
+
+        let account = accounts[indexPath.row]
+
+        if let image = account.image {
+            avatarImage?.image = image
+        }
+
+        if let name = account.name, !name.isEmpty {
+            userLabel?.text = name.uppercased() + " (\(account.user))"
+        } else {
+            userLabel?.text = account.user.uppercased()
+        }
+        urlLabel?.text = (URL(string: account.url)?.host ?? "")
+
+        return cell
+    }
+}

+ 135 - 0
iOSClient/Activity/NCActivity.storyboard

@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="nhT-TJ-YvX">
+    <device id="retina6_1" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <scenes>
+        <!--Activity-->
+        <scene sceneID="bVi-HG-3eX">
+            <objects>
+                <viewController storyboardIdentifier="NCActivity.storyboard" extendedLayoutIncludesOpaqueBars="YES" id="nhT-TJ-YvX" customClass="NCActivity" customModule="Nextcloud" customModuleProvider="target" sceneMemberID="viewController">
+                    <view key="view" contentMode="scaleToFill" id="vOO-VC-ekK">
+                        <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <subviews>
+                            <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="20" sectionFooterHeight="1" translatesAutoresizingMaskIntoConstraints="NO" id="X49-xg-JXO">
+                                <rect key="frame" x="0.0" y="44" width="414" height="818"/>
+                                <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                <prototypes>
+                                    <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="tableCell" rowHeight="120" id="ggj-aE-fnh" customClass="NCActivityTableViewCell" customModule="Nextcloud" customModuleProvider="target">
+                                        <rect key="frame" x="0.0" y="44.5" width="414" height="120"/>
+                                        <autoresizingMask key="autoresizingMask"/>
+                                        <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="ggj-aE-fnh" id="i35-U4-bEk">
+                                            <rect key="frame" x="0.0" y="0.0" width="414" height="120"/>
+                                            <autoresizingMask key="autoresizingMask"/>
+                                            <subviews>
+                                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fcO-YL-MuT">
+                                                    <rect key="frame" x="88" y="5" width="316" height="25"/>
+                                                    <fontDescription key="fontDescription" name=".AppleSystemUIFont" family=".AppleSystemUIFont" pointSize="15"/>
+                                                    <nil key="textColor"/>
+                                                    <nil key="highlightedColor"/>
+                                                </label>
+                                                <imageView contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="LQ8-cO-794" userLabel="avatar">
+                                                    <rect key="frame" x="50" y="0.0" width="30" height="30"/>
+                                                    <constraints>
+                                                        <constraint firstAttribute="width" constant="30" id="OKz-e8-DzD"/>
+                                                        <constraint firstAttribute="height" constant="30" id="fwd-J4-5uY"/>
+                                                    </constraints>
+                                                </imageView>
+                                                <imageView userInteractionEnabled="NO" alpha="0.59999999999999998" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="xNG-sf-PnA">
+                                                    <rect key="frame" x="20" y="5" width="20" height="20"/>
+                                                    <constraints>
+                                                        <constraint firstAttribute="width" constant="20" id="Lbv-yi-vAh"/>
+                                                        <constraint firstAttribute="height" constant="20" id="TML-VJ-2i3"/>
+                                                    </constraints>
+                                                </imageView>
+                                                <collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="KpO-no-BMl">
+                                                    <rect key="frame" x="50" y="40" width="364" height="60"/>
+                                                    <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                                    <constraints>
+                                                        <constraint firstAttribute="height" constant="60" id="deQ-wc-jNT" userLabel="height = 60"/>
+                                                    </constraints>
+                                                    <collectionViewFlowLayout key="collectionViewLayout" scrollDirection="horizontal" minimumLineSpacing="10" minimumInteritemSpacing="10" id="nnU-ds-wda">
+                                                        <size key="itemSize" width="50" height="50"/>
+                                                        <size key="headerReferenceSize" width="0.0" height="0.0"/>
+                                                        <size key="footerReferenceSize" width="0.0" height="0.0"/>
+                                                        <inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
+                                                    </collectionViewFlowLayout>
+                                                    <cells>
+                                                        <collectionViewCell clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="collectionCell" id="uIN-z5-7vk" customClass="NCActivityCollectionViewCell" customModule="Nextcloud" customModuleProvider="target">
+                                                            <rect key="frame" x="0.0" y="5" width="50" height="50"/>
+                                                            <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                                                            <view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
+                                                                <rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
+                                                                <autoresizingMask key="autoresizingMask"/>
+                                                                <subviews>
+                                                                    <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="J5b-2X-AsF">
+                                                                        <rect key="frame" x="0.0" y="-1" width="50" height="50"/>
+                                                                        <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                                                                    </imageView>
+                                                                </subviews>
+                                                            </view>
+                                                            <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                                            <connections>
+                                                                <outlet property="imageView" destination="J5b-2X-AsF" id="7dI-m8-o1E"/>
+                                                            </connections>
+                                                        </collectionViewCell>
+                                                    </cells>
+                                                </collectionView>
+                                            </subviews>
+                                            <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                            <constraints>
+                                                <constraint firstAttribute="trailing" secondItem="fcO-YL-MuT" secondAttribute="trailing" constant="10" id="1pG-qk-inI"/>
+                                                <constraint firstItem="LQ8-cO-794" firstAttribute="top" secondItem="i35-U4-bEk" secondAttribute="top" id="3fU-rp-D7s"/>
+                                                <constraint firstItem="xNG-sf-PnA" firstAttribute="top" secondItem="i35-U4-bEk" secondAttribute="top" constant="5" id="4EB-59-y1Y"/>
+                                                <constraint firstItem="xNG-sf-PnA" firstAttribute="leading" secondItem="i35-U4-bEk" secondAttribute="leading" constant="20" id="CRN-18-SeU"/>
+                                                <constraint firstAttribute="bottom" secondItem="KpO-no-BMl" secondAttribute="bottom" constant="20" id="ULe-Tt-dBj"/>
+                                                <constraint firstItem="fcO-YL-MuT" firstAttribute="leading" secondItem="xNG-sf-PnA" secondAttribute="trailing" constant="48" id="am5-CT-0kZ" userLabel="Subject.leading = Icon.trailing + 50"/>
+                                                <constraint firstItem="LQ8-cO-794" firstAttribute="leading" secondItem="xNG-sf-PnA" secondAttribute="trailing" constant="10" id="aqp-Wu-9Hk"/>
+                                                <constraint firstItem="fcO-YL-MuT" firstAttribute="top" secondItem="i35-U4-bEk" secondAttribute="top" constant="5" id="faC-by-km5"/>
+                                                <constraint firstItem="KpO-no-BMl" firstAttribute="leading" secondItem="i35-U4-bEk" secondAttribute="leading" constant="50" id="l0Y-89-eTm"/>
+                                                <constraint firstAttribute="trailing" secondItem="KpO-no-BMl" secondAttribute="trailing" id="vWj-9H-JLc"/>
+                                                <constraint firstItem="KpO-no-BMl" firstAttribute="top" secondItem="fcO-YL-MuT" secondAttribute="bottom" constant="10" id="z1e-vv-qJb"/>
+                                            </constraints>
+                                        </tableViewCellContentView>
+                                        <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                        <connections>
+                                            <outlet property="avatar" destination="LQ8-cO-794" id="2PE-uD-pug"/>
+                                            <outlet property="collectionView" destination="KpO-no-BMl" id="xvR-CA-cZx"/>
+                                            <outlet property="collectionViewHeightConstraint" destination="deQ-wc-jNT" id="nAT-iS-UjK"/>
+                                            <outlet property="icon" destination="xNG-sf-PnA" id="hxb-Vr-oQX"/>
+                                            <outlet property="subject" destination="fcO-YL-MuT" id="L4q-rj-l04"/>
+                                            <outlet property="subjectTrailingConstraint" destination="am5-CT-0kZ" id="PeK-M5-hHW"/>
+                                        </connections>
+                                    </tableViewCell>
+                                </prototypes>
+                                <connections>
+                                    <outlet property="dataSource" destination="nhT-TJ-YvX" id="4jS-6C-FKt"/>
+                                    <outlet property="delegate" destination="nhT-TJ-YvX" id="ab1-4g-bMH"/>
+                                    <outlet property="prefetchDataSource" destination="nhT-TJ-YvX" id="317-AD-uQe"/>
+                                </connections>
+                            </tableView>
+                        </subviews>
+                        <viewLayoutGuide key="safeArea" id="USa-eR-a1s"/>
+                        <constraints>
+                            <constraint firstItem="X49-xg-JXO" firstAttribute="trailing" secondItem="USa-eR-a1s" secondAttribute="trailing" id="5we-Fh-GVu"/>
+                            <constraint firstItem="X49-xg-JXO" firstAttribute="top" secondItem="USa-eR-a1s" secondAttribute="top" id="E1U-4Q-6uu"/>
+                            <constraint firstItem="USa-eR-a1s" firstAttribute="bottom" secondItem="X49-xg-JXO" secondAttribute="bottom" id="aHq-g4-dUG"/>
+                            <constraint firstItem="X49-xg-JXO" firstAttribute="leading" secondItem="USa-eR-a1s" secondAttribute="leading" id="pfF-ag-f7x"/>
+                        </constraints>
+                    </view>
+                    <connections>
+                        <outlet property="tableView" destination="X49-xg-JXO" id="GUb-8b-mIS"/>
+                        <outlet property="viewContainerConstraint" destination="E1U-4Q-6uu" id="NpJ-Iz-DtL"/>
+                    </connections>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="UOE-pW-DRy" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="-330.39999999999998" y="139.65517241379311"/>
+        </scene>
+    </scenes>
+</document>

+ 555 - 0
iOSClient/Activity/NCActivity.swift

@@ -0,0 +1,555 @@
+//
+//  NCActivity.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 17/01/2019.
+//  Copyright © 2019 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//  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
+import SwiftRichString
+import NextcloudKit
+
+class NCActivity: UIViewController, NCSharePagingContent {
+
+    @IBOutlet weak var tableView: UITableView!
+
+    var commentView: NCActivityCommentView?
+    var textField: UITextField? { commentView?.newCommentField }
+
+    @IBOutlet weak var viewContainerConstraint: NSLayoutConstraint!
+    var height: CGFloat = 0
+
+    var metadata: tableMetadata?
+    var showComments: Bool = false
+
+    private let appDelegate = UIApplication.shared.delegate as! AppDelegate
+
+    var allItems: [DateCompareable] = []
+    var sectionDates: [Date] = []
+
+    var insets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
+    var didSelectItemEnable: Bool = true
+    var objectType: String?
+
+    var isFetchingActivity = false
+    var hasActivityToLoad = true {
+        didSet { tableView.tableFooterView?.isHidden = hasActivityToLoad }
+    }
+    var dateAutomaticFetch: Date?
+
+    // MARK: - View Life Cycle
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+
+        self.navigationController?.navigationBar.prefersLargeTitles = true
+        view.backgroundColor = .systemBackground
+        self.title = NSLocalizedString("_activity_", comment: "")
+
+        tableView.allowsSelection = false
+        tableView.separatorColor = UIColor.clear
+        tableView.contentInset = insets
+        tableView.backgroundColor = .systemBackground
+
+        if showComments {
+            setupComments()
+        }
+    }
+
+    func setupComments() {
+        // Display Name & Quota
+        guard let activeAccount = NCManageDatabase.shared.getActiveAccount(), height > 0 else {
+            return
+        }
+
+        tableView.register(UINib(nibName: "NCShareCommentsCell", bundle: nil), forCellReuseIdentifier: "cell")
+        commentView = Bundle.main.loadNibNamed("NCActivityCommentView", owner: self, options: nil)?.first as? NCActivityCommentView
+        commentView?.setup(urlBase: appDelegate, account: activeAccount) { newComment in
+            guard let newComment = newComment, !newComment.isEmpty, let metadata = self.metadata else { return }
+            NextcloudKit.shared.putComments(fileId: metadata.fileId, message: newComment) { _, error in
+                if error == .success {
+                    self.commentView?.newCommentField.text?.removeAll()
+                    self.loadComments()
+                } else {
+                    NCContentPresenter.shared.showError(error: error)
+                }
+            }
+        }
+    }
+
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+
+        appDelegate.activeViewController = self
+
+        navigationController?.setFileAppreance()
+
+        NotificationCenter.default.addObserver(self, selector: #selector(initialize), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterInitialize), object: nil)
+        initialize()
+    }
+
+    override func viewWillDisappear(_ animated: Bool) {
+        super.viewWillDisappear(animated)
+        NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterInitialize), object: nil)
+    }
+
+    override func viewWillLayoutSubviews() {
+        super.viewWillLayoutSubviews()
+        tableView.tableFooterView = makeTableFooterView()
+        tableView.tableHeaderView = commentView
+        commentView?.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
+        commentView?.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
+        viewContainerConstraint.constant = height
+    }
+
+    // MARK: - NotificationCenter
+
+    @objc func initialize() {
+        loadDataSource()
+        fetchAll(isInitial: true)
+        view.setNeedsLayout()
+    }
+
+    func makeTableFooterView() -> UIView {
+        let view = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 100))
+        view.backgroundColor = .clear
+        view.isHidden = self.hasActivityToLoad
+
+        let label = UILabel()
+        label.font = UIFont.systemFont(ofSize: 15)
+        label.textColor = UIColor.systemGray
+        label.textAlignment = .center
+        label.text = NSLocalizedString("_no_activity_footer_", comment: "")
+        label.frame = CGRect(x: 0, y: 10, width: tableView.frame.width, height: 60)
+
+        view.addSubview(label)
+        return view
+    }
+}
+
+// MARK: - Table View
+
+extension NCActivity: UITableViewDelegate {
+
+    func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
+        return 120
+    }
+
+    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
+        return 60
+    }
+
+    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
+        return UITableView.automaticDimension
+    }
+
+    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
+
+        let view = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 60))
+        view.backgroundColor = .clear
+
+        let label = UILabel()
+        label.font = UIFont.boldSystemFont(ofSize: 13)
+        label.textColor = .label
+        label.text = CCUtility.getTitleSectionDate(sectionDates[section])
+        label.textAlignment = .center
+        label.layer.cornerRadius = 11
+        label.layer.masksToBounds = true
+        label.layer.backgroundColor = UIColor(red: 152.0/255.0, green: 167.0/255.0, blue: 181.0/255.0, alpha: 0.8).cgColor
+        let widthFrame = label.intrinsicContentSize.width + 30
+        let xFrame = tableView.bounds.width / 2 - widthFrame / 2
+        label.frame = CGRect(x: xFrame, y: 10, width: widthFrame, height: 22)
+
+        view.addSubview(label)
+        return view
+    }
+}
+
+extension NCActivity: UITableViewDataSource {
+
+    func numberOfSections(in tableView: UITableView) -> Int {
+        return sectionDates.count
+    }
+
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        let date = sectionDates[section]
+        return allItems.filter({ Calendar.current.isDate($0.dateKey, inSameDayAs: date) }).count
+    }
+
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let date = sectionDates[indexPath.section]
+        let sectionItems = allItems
+            .filter({ Calendar.current.isDate($0.dateKey, inSameDayAs: date) })
+        let cellData = sectionItems[indexPath.row]
+
+        if let activityData = cellData as? tableActivity {
+            return makeActivityCell(activityData, for: indexPath)
+        } else if let commentData = cellData as? tableComments {
+            return makeCommentCell(commentData, for: indexPath)
+        } else {
+            return UITableViewCell()
+        }
+    }
+
+    func makeCommentCell(_ comment: tableComments, for indexPath: IndexPath) -> UITableViewCell {
+        guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? NCShareCommentsCell else {
+            return UITableViewCell()
+        }
+
+        cell.tableComments = comment
+        cell.delegate = self
+        cell.sizeToFit()
+
+        // Image
+        let fileName = appDelegate.userBaseUrl + "-" + comment.actorId + ".png"
+        NCOperationQueue.shared.downloadAvatar(user: comment.actorId, dispalyName: comment.actorDisplayName, fileName: fileName, cell: cell, view: tableView, cellImageView: cell.fileAvatarImageView)
+        // Username
+        cell.labelUser.text = comment.actorDisplayName
+        cell.labelUser.textColor = .label
+        // Date
+        cell.labelDate.text = CCUtility.dateDiff(comment.creationDateTime as Date)
+        cell.labelDate.textColor = .systemGray4
+        // Message
+        cell.labelMessage.text = comment.message
+        cell.labelMessage.textColor = .label
+        // Button Menu
+        if comment.actorId == appDelegate.userId {
+            cell.buttonMenu.isHidden = false
+        } else {
+            cell.buttonMenu.isHidden = true
+        }
+
+        return cell
+    }
+
+    func makeActivityCell(_ activity: tableActivity, for indexPath: IndexPath) -> UITableViewCell {
+        guard let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath) as? NCActivityTableViewCell else {
+            return UITableViewCell()
+        }
+
+        var orderKeysId: [String] = []
+
+        cell.idActivity = activity.idActivity
+
+        cell.avatar.image = nil
+        cell.avatar.isHidden = true
+        cell.subjectTrailingConstraint.constant = 10
+        cell.didSelectItemEnable = self.didSelectItemEnable
+        cell.subject.textColor = .label
+        cell.viewController = self
+
+        // icon
+        if activity.icon.count > 0 {
+
+            let fileNameIcon = (activity.icon as NSString).lastPathComponent
+            let fileNameLocalPath = CCUtility.getDirectoryUserData() + "/" + fileNameIcon
+
+            if FileManager.default.fileExists(atPath: fileNameLocalPath) {
+                if let image = UIImage(contentsOfFile: fileNameLocalPath) {
+                    cell.icon.image = image
+                }
+            } else {
+                NextcloudKit.shared.downloadContent(serverUrl: activity.icon) { _, data, error in
+                    if error == .success {
+                        do {
+                            try data!.write(to: NSURL(fileURLWithPath: fileNameLocalPath) as URL, options: .atomic)
+                            self.tableView.reloadData()
+                        } catch { return }
+                    }
+                }
+            }
+        }
+
+        // avatar
+        if activity.user.count > 0 && activity.user != appDelegate.userId {
+
+            cell.subjectTrailingConstraint.constant = 50
+            cell.avatar.isHidden = false
+            cell.fileUser = activity.user
+
+            let fileName = appDelegate.userBaseUrl + "-" + activity.user + ".png"
+
+            NCOperationQueue.shared.downloadAvatar(user: activity.user, dispalyName: nil, fileName: fileName, cell: cell, view: tableView, cellImageView: cell.fileAvatarImageView)
+        }
+
+        // subject
+        cell.subject.text = activity.subject
+        if activity.subjectRich.count > 0 {
+
+            var subject = activity.subjectRich
+            var keys: [String] = []
+
+            if let regex = try? NSRegularExpression(pattern: "\\{[a-z0-9]+\\}", options: .caseInsensitive) {
+                let string = subject as NSString
+                keys = regex.matches(in: subject, options: [], range: NSRange(location: 0, length: string.length)).map {
+                    string.substring(with: $0.range).replacingOccurrences(of: "[\\{\\}]", with: "", options: .regularExpression)
+                }
+            }
+
+            for key in keys {
+                if let result = NCManageDatabase.shared.getActivitySubjectRich(account: appDelegate.account, idActivity: activity.idActivity, key: key) {
+                    orderKeysId.append(result.id)
+                    subject = subject.replacingOccurrences(of: "{\(key)}", with: "<bold>" + result.name + "</bold>")
+                }
+            }
+
+            let normal = Style {
+                $0.font = UIFont.systemFont(ofSize: cell.subject.font.pointSize)
+                $0.lineSpacing = 1.5
+            }
+            let bold = Style { $0.font = UIFont.systemFont(ofSize: cell.subject.font.pointSize, weight: .bold) }
+            let date = Style { $0.font = UIFont.systemFont(ofSize: cell.subject.font.pointSize - 3)
+                $0.color = UIColor.lightGray
+            }
+
+            subject += "\n" + "<date>" + CCUtility.dateDiff(activity.date as Date) + "</date>"
+            cell.subject.attributedText = subject.set(style: StyleGroup(base: normal, ["bold": bold, "date": date]))
+        }
+
+        // CollectionView
+        cell.activityPreviews = NCManageDatabase.shared.getActivityPreview(account: activity.account, idActivity: activity.idActivity, orderKeysId: orderKeysId)
+        if cell.activityPreviews.count == 0 {
+            cell.collectionViewHeightConstraint.constant = 0
+        } else {
+            cell.collectionViewHeightConstraint.constant = 60
+        }
+        cell.collectionView.reloadData()
+
+        return cell
+    }
+}
+
+// MARK: - ScrollView
+
+extension NCActivity: UIScrollViewDelegate {
+    func scrollViewDidScroll(_ scrollView: UIScrollView) {
+        guard
+            scrollView.contentOffset.y > 50,
+            scrollView.contentSize.height - scrollView.frame.height - scrollView.contentOffset.y < -50
+        else { return }
+        fetchAll(isInitial: false)
+    }
+}
+
+// MARK: - NC API & Algorithm
+
+extension NCActivity {
+
+    func fetchAll(isInitial: Bool) {
+        guard !isFetchingActivity else { return }
+        self.isFetchingActivity = true
+
+        var bottom: CGFloat = 0
+        if let mainTabBar = self.tabBarController?.tabBar as? NCMainTabBar {
+            bottom = -mainTabBar.getHight()
+        }
+        NCActivityIndicator.shared.start(backgroundView: self.view, bottom: bottom-5, style: .medium)
+
+        let dispatchGroup = DispatchGroup()
+        loadComments(disptachGroup: dispatchGroup)
+
+        if !isInitial, let activity = allItems.compactMap({ $0 as? tableActivity }).last {
+            loadActivity(idActivity: activity.idActivity, disptachGroup: dispatchGroup)
+        } else {
+            checkRecentActivity(disptachGroup: dispatchGroup)
+        }
+
+        dispatchGroup.notify(queue: .main) {
+            self.loadDataSource()
+            NCActivityIndicator.shared.stop()
+
+            // otherwise is triggered again
+            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
+                self.isFetchingActivity = false
+            }
+        }
+    }
+
+    func loadDataSource() {
+
+        var newItems = [DateCompareable]()
+        if showComments, let metadata = metadata, let account = NCManageDatabase.shared.getActiveAccount() {
+            let comments = NCManageDatabase.shared.getComments(account: account.account, objectId: metadata.fileId)
+            newItems += comments
+        }
+
+        let activities = NCManageDatabase.shared.getActivity(
+            predicate: NSPredicate(format: "account == %@", appDelegate.account),
+            filterFileId: metadata?.fileId)
+        newItems += activities.filter
+
+        self.allItems = newItems.sorted(by: { $0.dateKey > $1.dateKey })
+        self.sectionDates = self.allItems.reduce(into: Set<Date>()) { partialResult, next in
+            let newDay = Calendar.current.startOfDay(for: next.dateKey)
+            partialResult.insert(newDay)
+        }.sorted(by: >)
+        self.tableView.reloadData()
+    }
+
+    func loadComments(disptachGroup: DispatchGroup? = nil) {
+        guard showComments, let metadata = metadata else { return }
+        disptachGroup?.enter()
+
+        NextcloudKit.shared.getComments(fileId: metadata.fileId) { account, comments, data, error in
+            if error == .success, let comments = comments {
+                NCManageDatabase.shared.addComments(comments, account: metadata.account, objectId: metadata.fileId)
+            } else if error.errorCode != NCGlobal.shared.errorResourceNotFound {
+                NCContentPresenter.shared.showError(error: error)
+            }
+
+            if let disptachGroup = disptachGroup {
+                disptachGroup.leave()
+            } else {
+                self.loadDataSource()
+            }
+        }
+    }
+
+    /// Check if most recent activivities are loaded, if not trigger reload
+    func checkRecentActivity(disptachGroup: DispatchGroup) {
+        guard let result = NCManageDatabase.shared.getLatestActivityId(account: appDelegate.account), metadata == nil, hasActivityToLoad else {
+            return self.loadActivity(idActivity: 0, disptachGroup: disptachGroup)
+        }
+        let resultActivityId = max(result.activityFirstKnown, result.activityLastGiven)
+
+        disptachGroup.enter()
+
+        NextcloudKit.shared.getActivity(
+            since: 0,
+            limit: 1,
+            objectId: nil,
+            objectType: objectType,
+            previews: true) { account, _, activityFirstKnown, activityLastGiven, data, error in
+                defer { disptachGroup.leave() }
+
+                let largestActivityId = max(activityFirstKnown, activityLastGiven)
+                guard error == .success,
+                      account == self.appDelegate.account,
+                      largestActivityId > resultActivityId
+                else {
+                    self.hasActivityToLoad = error.errorCode == NCGlobal.shared.errorNotModified ? false : self.hasActivityToLoad
+                    return
+                }
+
+                self.loadActivity(idActivity: 0, limit: largestActivityId - resultActivityId, disptachGroup: disptachGroup)
+            }
+    }
+
+    func loadActivity(idActivity: Int, limit: Int = 200, disptachGroup: DispatchGroup) {
+        guard hasActivityToLoad else { return }
+
+        var resultActivityId = 0
+        disptachGroup.enter()
+
+        NextcloudKit.shared.getActivity(
+            since: idActivity,
+            limit: min(limit, 200),
+            objectId: metadata?.fileId,
+            objectType: objectType,
+            previews: true) { account, activities, activityFirstKnown, activityLastGiven, data, error in
+                defer { disptachGroup.leave() }
+                guard error == .success,
+                      account == self.appDelegate.account,
+                      !activities.isEmpty
+                else {
+                    self.hasActivityToLoad = error.errorCode == NCGlobal.shared.errorNotModified ? false : self.hasActivityToLoad
+                    return
+                }
+                NCManageDatabase.shared.addActivity(activities, account: account)
+
+                // update most recently loaded activity only when all activities are loaded (not filtered)
+                let largestActivityId = max(activityFirstKnown, activityLastGiven)
+                if let result = NCManageDatabase.shared.getLatestActivityId(account: self.appDelegate.account) {
+                    resultActivityId = max(result.activityFirstKnown, result.activityLastGiven)
+                }
+                if self.metadata == nil, largestActivityId > resultActivityId {
+                    NCManageDatabase.shared.updateLatestActivityId(activityFirstKnown: activityFirstKnown, activityLastGiven: activityLastGiven, account: account)
+                }
+            }
+    }
+}
+
+extension NCActivity: NCShareCommentsCellDelegate {
+    func showProfile(with tableComment: tableComments?, sender: Any) {
+        guard let tableComment = tableComment else {
+            return
+        }
+        self.showProfileMenu(userId: tableComment.actorId)
+    }
+
+    func tapMenu(with tableComments: tableComments?, sender: Any) {
+        toggleMenu(with: tableComments)
+    }
+
+    func toggleMenu(with tableComments: tableComments?) {
+        var actions = [NCMenuAction]()
+
+        actions.append(
+            NCMenuAction(
+                title: NSLocalizedString("_edit_comment_", comment: ""),
+                icon: UIImage(named: "pencil")!.image(color: UIColor.systemGray, size: 50),
+                action: { _ in
+                    guard let metadata = self.metadata, let tableComments = tableComments else { return }
+
+                    let alert = UIAlertController(title: NSLocalizedString("_edit_comment_", comment: ""), message: nil, preferredStyle: .alert)
+                    alert.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel, handler: nil))
+
+                    alert.addTextField(configurationHandler: { textField in
+                        textField.placeholder = NSLocalizedString("_new_comment_", comment: "")
+                    })
+
+                    alert.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .default, handler: { _ in
+                        guard let message = alert.textFields?.first?.text, message != "" else { return }
+
+                        NextcloudKit.shared.updateComments(fileId: metadata.fileId, messageId: tableComments.messageId, message: message) { _, error in
+                            if error == .success {
+                                self.loadComments()
+                            } else {
+                                NCContentPresenter.shared.showError(error: error)
+                            }
+                        }
+                    }))
+
+                    self.present(alert, animated: true)
+                }
+            )
+        )
+
+        actions.append(
+            NCMenuAction(
+                title: NSLocalizedString("_delete_comment_", comment: ""),
+                icon: NCUtility.shared.loadImage(named: "trash"),
+                action: { _ in
+                    guard let metadata = self.metadata, let tableComments = tableComments else { return }
+
+                    NextcloudKit.shared.deleteComments(fileId: metadata.fileId, messageId: tableComments.messageId) { _, error in
+                        if error == .success {
+                            self.loadComments()
+                        } else {
+                            NCContentPresenter.shared.showError(error: error)
+                        }
+                    }
+                }
+            )
+        )
+
+        presentMenu(with: actions)
+    }
+}

+ 59 - 0
iOSClient/Activity/NCActivityCommentView.swift

@@ -0,0 +1,59 @@
+//
+//  NCActivityCommentView.swift
+//  Nextcloud
+//
+//  Created by Henrik Storch on 04.01.22.
+//  Copyright © 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
+
+class NCActivityCommentView: UIView, UITextFieldDelegate {
+    @IBOutlet weak var imageItem: UIImageView!
+    @IBOutlet weak var labelUser: UILabel!
+    @IBOutlet weak var newCommentField: UITextField!
+
+    var completionHandler: ((String?) -> Void)?
+
+    func setup(urlBase: NCUserBaseUrl, account: tableAccount, completionHandler: @escaping (String?) -> Void) {
+        self.completionHandler = completionHandler
+        newCommentField.placeholder = NSLocalizedString("_new_comment_", comment: "")
+        newCommentField.delegate = self
+
+        let fileName = urlBase.userBaseUrl + "-" + urlBase.user + ".png"
+        let fileNameLocalPath = String(CCUtility.getDirectoryUserData()) + "/" + fileName
+        if let image = UIImage(contentsOfFile: fileNameLocalPath) {
+            imageItem.image = image
+        } else {
+            imageItem.image = UIImage(named: "avatar")
+        }
+
+        if account.displayName.isEmpty {
+            labelUser.text = account.user
+        } else {
+            labelUser.text = account.displayName
+        }
+        labelUser.textColor = .label
+    }
+
+    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
+        textField.resignFirstResponder()
+        completionHandler?(textField.text)
+        return true
+    }
+}

+ 60 - 0
iOSClient/Activity/NCActivityCommentView.xib

@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+    <device id="retina6_1" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.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"/>
+        <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="GuF-Pi-nHv" customClass="NCActivityCommentView" customModule="Nextcloud" customModuleProvider="target">
+            <rect key="frame" x="0.0" y="0.0" width="269" height="100"/>
+            <subviews>
+                <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="YXy-gE-g7y">
+                    <rect key="frame" x="10" y="10" width="40" height="40"/>
+                    <constraints>
+                        <constraint firstAttribute="width" constant="40" id="kUz-t2-bFL"/>
+                        <constraint firstAttribute="height" constant="40" id="yRS-7c-bMw"/>
+                    </constraints>
+                </imageView>
+                <textField opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="249" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" clearButtonMode="always" translatesAutoresizingMaskIntoConstraints="NO" id="5pv-VB-vbL">
+                    <rect key="frame" x="60" y="60" width="199" height="30"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="30" id="OLX-lD-EIH"/>
+                    </constraints>
+                    <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                    <textInputTraits key="textInputTraits"/>
+                </textField>
+                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="user" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0Ja-ik-S0n">
+                    <rect key="frame" x="60" y="21.5" width="199" height="17"/>
+                    <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                    <color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                    <nil key="highlightedColor"/>
+                </label>
+            </subviews>
+            <viewLayoutGuide key="safeArea" id="Y5x-Vi-PYA"/>
+            <constraints>
+                <constraint firstItem="YXy-gE-g7y" firstAttribute="top" secondItem="GuF-Pi-nHv" secondAttribute="top" constant="10" id="26g-rF-Ags"/>
+                <constraint firstAttribute="trailing" secondItem="0Ja-ik-S0n" secondAttribute="trailing" constant="10" id="7ue-4o-ZT2"/>
+                <constraint firstAttribute="height" constant="100" id="IsL-V9-dXU"/>
+                <constraint firstItem="0Ja-ik-S0n" firstAttribute="centerY" secondItem="YXy-gE-g7y" secondAttribute="centerY" id="NxM-vu-j06"/>
+                <constraint firstItem="5pv-VB-vbL" firstAttribute="leading" secondItem="YXy-gE-g7y" secondAttribute="trailing" constant="10" id="Oza-Za-mDZ"/>
+                <constraint firstItem="5pv-VB-vbL" firstAttribute="top" secondItem="YXy-gE-g7y" secondAttribute="bottom" constant="10" id="iie-Nv-YUr"/>
+                <constraint firstItem="0Ja-ik-S0n" firstAttribute="leading" secondItem="YXy-gE-g7y" secondAttribute="trailing" constant="10" id="j0L-NP-Z4H"/>
+                <constraint firstAttribute="trailing" secondItem="5pv-VB-vbL" secondAttribute="trailing" constant="10" id="oXJ-ov-XCK"/>
+                <constraint firstItem="YXy-gE-g7y" firstAttribute="leading" secondItem="GuF-Pi-nHv" secondAttribute="leading" constant="10" id="t5p-fd-swt"/>
+                <constraint firstAttribute="bottom" secondItem="5pv-VB-vbL" secondAttribute="bottom" constant="10" id="yEr-QL-mtD"/>
+            </constraints>
+            <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
+            <connections>
+                <outlet property="imageItem" destination="YXy-gE-g7y" id="yWc-3P-gIU"/>
+                <outlet property="labelUser" destination="0Ja-ik-S0n" id="GkS-TV-2ic"/>
+                <outlet property="newCommentField" destination="5pv-VB-vbL" id="8vL-Mt-0rZ"/>
+            </connections>
+            <point key="canvasLocation" x="-231.15942028985509" y="-99.776785714285708"/>
+        </view>
+    </objects>
+</document>

+ 263 - 0
iOSClient/Activity/NCActivityTableViewCell.swift

@@ -0,0 +1,263 @@
+//
+//  NCActivityCollectionViewCell.swift
+//  Nextcloud
+//
+//  Created by Henrik Storch on 17/01/2019.
+//  Copyright © 2021. 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 Foundation
+import NextcloudKit
+import FloatingPanel
+import JGProgressHUD
+
+class NCActivityCollectionViewCell: UICollectionViewCell {
+
+    @IBOutlet weak var imageView: UIImageView!
+
+    var fileId = ""
+
+    override func awakeFromNib() {
+        super.awakeFromNib()
+    }
+}
+
+class NCActivityTableViewCell: UITableViewCell, NCCellProtocol {
+
+    private let appDelegate = UIApplication.shared.delegate as! AppDelegate
+
+    @IBOutlet weak var collectionView: UICollectionView!
+    @IBOutlet weak var icon: UIImageView!
+    @IBOutlet weak var avatar: UIImageView!
+    @IBOutlet weak var subject: UILabel!
+    @IBOutlet weak var subjectTrailingConstraint: NSLayoutConstraint!
+    @IBOutlet weak var collectionViewHeightConstraint: NSLayoutConstraint!
+
+    private var user: String = ""
+
+    var idActivity: Int = 0
+    var activityPreviews: [tableActivityPreview] = []
+    var didSelectItemEnable: Bool = true
+    var viewController: UIViewController?
+
+    var fileAvatarImageView: UIImageView? {
+        get { return avatar }
+    }
+    var fileUser: String? {
+        get { return user }
+        set { user = newValue ?? "" }
+    }
+
+    override func awakeFromNib() {
+        super.awakeFromNib()
+
+        collectionView.delegate = self
+        collectionView.dataSource = self
+        let avatarRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapAvatarImage))
+        avatar.addGestureRecognizer(avatarRecognizer)
+    }
+
+    @objc func tapAvatarImage() {
+        guard let fileUser = fileUser else { return }
+        viewController?.showProfileMenu(userId: fileUser)
+    }
+}
+
+// MARK: - Collection View
+
+extension NCActivityTableViewCell: UICollectionViewDelegate {
+
+    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+
+        // Select not permitted
+        if !didSelectItemEnable {
+            return
+        }
+
+        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath) as? NCActivityCollectionViewCell
+
+        let activityPreview = activityPreviews[indexPath.row]
+
+        if activityPreview.view == "trashbin" {
+
+            var responder: UIResponder? = collectionView
+            while !(responder is UIViewController) {
+                responder = responder?.next
+                if responder == nil {
+                    break
+                }
+            }
+            if (responder as? UIViewController)!.navigationController != nil {
+                if let viewController = UIStoryboard(name: "NCTrash", bundle: nil).instantiateInitialViewController() as? NCTrash {
+                    if let result = NCManageDatabase.shared.getTrashItem(fileId: String(activityPreview.fileId), account: activityPreview.account) {
+                        viewController.blinkFileId = result.fileId
+                        viewController.trashPath = result.filePath
+                        (responder as? UIViewController)!.navigationController?.pushViewController(viewController, animated: true)
+                    } else {
+                        let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_trash_file_not_found_")
+                        NCContentPresenter.shared.showError(error: error)
+                    }
+                }
+            }
+
+            return
+        }
+
+        if activityPreview.view == NCGlobal.shared.appName && activityPreview.mimeType != "dir" {
+
+            guard let activitySubjectRich = NCManageDatabase.shared.getActivitySubjectRich(account: activityPreview.account, idActivity: activityPreview.idActivity, id: String(activityPreview.fileId)) else {
+                return
+            }
+
+            if let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "fileId == %@", activitySubjectRich.id)) {
+                if let filePath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView) {
+                    do {
+                        let attr = try FileManager.default.attributesOfItem(atPath: filePath)
+                        let fileSize = attr[FileAttributeKey.size] as! UInt64
+                        if fileSize > 0 {
+                            if let viewController = self.viewController {
+                                NCViewer.shared.view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: cell?.imageView.image)
+                            }
+                            return
+                        }
+                    } catch {
+                        print("Error: \(error)")
+                    }
+                }
+            }
+
+            let hud = JGProgressHUD()
+            hud.indicatorView = JGProgressHUDRingIndicatorView()
+            if let indicatorView = hud.indicatorView as? JGProgressHUDRingIndicatorView {
+                indicatorView.ringWidth = 1.5
+            }
+            guard let view = appDelegate.window?.rootViewController?.view else { return }
+            hud.show(in: view)
+
+            NextcloudKit.shared.getFileFromFileId(fileId: String(activityPreview.fileId)) { account, file, data, error in
+                if let file = file {
+                    let isDirectoryE2EE = NCUtility.shared.isDirectoryE2EE(file: file)
+                    let metadata = NCManageDatabase.shared.convertFileToMetadata(file, isDirectoryE2EE: isDirectoryE2EE)
+                    NCManageDatabase.shared.addMetadata(metadata)
+
+                    let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
+                    let fileNameLocalPath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)!
+
+                    NextcloudKit.shared.download(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, requestHandler: { _ in
+                    }, taskHandler: { _ in
+                    }, progressHandler: { progress in
+                        hud.progress = Float(progress.fractionCompleted)
+                    }) { account, _, _, _, _, _, error in
+                        hud.dismiss()
+                        if account == self.appDelegate.account && error == .success {
+                            NCManageDatabase.shared.addLocalFile(metadata: metadata)
+                            if let viewController = self.viewController {
+                                NCViewer.shared.view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: cell?.imageView.image)
+                            }
+                        }
+                    }
+                } else {
+                    hud.dismiss()
+                    NCContentPresenter.shared.showError(error: error)
+                }
+            }
+        }
+    }
+}
+
+extension NCActivityTableViewCell: UICollectionViewDataSource {
+
+    func numberOfSections(in collectionView: UICollectionView) -> Int {
+        return 1
+    }
+
+    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+        let results = activityPreviews.unique { $0.fileId }
+        return results.count
+    }
+
+    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+
+        let cell: NCActivityCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath) as! NCActivityCollectionViewCell
+
+        cell.imageView.image = nil
+
+        let activityPreview = activityPreviews[indexPath.row]
+        let fileId = String(activityPreview.fileId)
+
+        // Trashbin
+        if activityPreview.view == "trashbin" {
+
+            let source = activityPreview.source
+
+            NCUtility.shared.convertSVGtoPNGWriteToUserData(svgUrlString: source, width: 100, rewrite: false, account: appDelegate.account, id: idActivity) { imageNamePath, id in
+                if let imageNamePath = imageNamePath, id == self.idActivity, let image = UIImage(contentsOfFile: imageNamePath) {
+                    cell.imageView.image = image
+                } else {
+                    cell.imageView.image = UIImage(named: "file")
+                }
+            }
+
+        } else {
+
+            if activityPreview.isMimeTypeIcon {
+
+                let source = activityPreview.source
+
+                NCUtility.shared.convertSVGtoPNGWriteToUserData(svgUrlString: source, width: 100, rewrite: false, account: appDelegate.account, id: idActivity) { imageNamePath, id in
+                    if let imageNamePath = imageNamePath, id == self.idActivity, let image = UIImage(contentsOfFile: imageNamePath) {
+                        cell.imageView.image = image
+                    } else {
+                        cell.imageView.image = UIImage(named: "file")
+                    }
+                }
+
+            } else {
+
+                if let activitySubjectRich = NCManageDatabase.shared.getActivitySubjectRich(account: activityPreview.account, idActivity: idActivity, id: fileId) {
+
+                    let fileNamePath = CCUtility.getDirectoryUserData() + "/" + activitySubjectRich.name
+                    
+                    if FileManager.default.fileExists(atPath: fileNamePath), let image = UIImage(contentsOfFile: fileNamePath) {
+                        cell.imageView.image = image
+                    } else {
+                        NCOperationQueue.shared.downloadThumbnailActivity(fileNamePathOrFileId: activityPreview.source, fileNamePreviewLocalPath: fileNamePath, fileId: fileId, cell: cell, collectionView: collectionView)
+                    }
+                }
+            }
+        }
+
+        return cell
+    }
+
+}
+
+extension NCActivityTableViewCell: UICollectionViewDelegateFlowLayout {
+
+    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
+        return CGSize(width: 50, height: 50)
+    }
+
+    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
+        return 20
+    }
+
+    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
+        return UIEdgeInsets(top: 0, left: 0, bottom: 10, right: 0)
+    }
+}

+ 983 - 0
iOSClient/AppDelegate.swift

@@ -0,0 +1,983 @@
+//
+//  AppDelegate.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 04/09/14 (19/02/21 swift).
+//  Copyright (c) 2014 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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
+import BackgroundTasks
+import NextcloudKit
+import TOPasscodeViewController
+import LocalAuthentication
+import Firebase
+import WidgetKit
+
+@UIApplicationMain
+class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, TOPasscodeViewControllerDelegate, NCAccountRequestDelegate, NCViewCertificateDetailsDelegate, NCUserBaseUrl {
+
+    var backgroundSessionCompletionHandler: (() -> Void)?
+    var window: UIWindow?
+
+    @objc var account: String = ""
+    @objc var urlBase: String = ""
+    @objc var user: String = ""
+    @objc var userId: String = ""
+    @objc var password: String = ""
+
+    var deletePasswordSession: Bool = false
+    var activeLogin: NCLogin?
+    var activeLoginWeb: NCLoginWeb?
+    var activeServerUrl: String = ""
+    @objc var activeViewController: UIViewController?
+    var mainTabBar: NCMainTabBar?
+    var activeMetadata: tableMetadata?
+
+    let listFilesVC = ThreadSafeDictionary<String,NCFiles>()
+    let listFavoriteVC = ThreadSafeDictionary<String,NCFavorite>()
+    let listOfflineVC = ThreadSafeDictionary<String,NCOffline>()
+    let listGroupfoldersVC = ThreadSafeDictionary<String,NCGroupfolders>()
+
+    var disableSharesView: Bool = false
+    var documentPickerViewController: NCDocumentPickerViewController?
+    var timerErrorNetworking: Timer?
+
+    private var privacyProtectionWindow: UIWindow?
+
+    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
+
+        NCSettingsBundleHelper.checkAndExecuteSettings(delay: 0)
+
+        let userAgent = CCUtility.getUserAgent() as String
+        let versionNextcloudiOS = String(format: NCBrandOptions.shared.textCopyrightNextcloudiOS, NCUtility.shared.getVersionApp())
+
+        // Register initialize
+        NotificationCenter.default.addObserver(self, selector: #selector(initialize), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterInitialize), object: nil)
+
+        UserDefaults.standard.register(defaults: ["UserAgent": userAgent])
+        if !CCUtility.getDisableCrashservice() && !NCBrandOptions.shared.disable_crash_service {
+            FirebaseApp.configure()
+        }
+
+        CCUtility.createDirectoryStandard()
+        CCUtility.emptyTemporaryDirectory()
+
+        NextcloudKit.shared.setup(delegate: NCNetworking.shared)
+        NextcloudKit.shared.setup(userAgent: userAgent)
+
+        startTimerErrorNetworking()
+
+        // LOG
+        var levelLog = 0
+        if let pathDirectoryGroup = CCUtility.getDirectoryGroup()?.path {
+            NextcloudKit.shared.nkCommonInstance.pathLog = pathDirectoryGroup
+        }
+
+        if NCBrandOptions.shared.disable_log {
+
+            NCUtilityFileSystem.shared.deleteFile(filePath: NextcloudKit.shared.nkCommonInstance.filenamePathLog)
+            NCUtilityFileSystem.shared.deleteFile(filePath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first! + "/" + NextcloudKit.shared.nkCommonInstance.filenameLog)
+
+        } else {
+
+            levelLog = CCUtility.getLogLevel()
+            NextcloudKit.shared.nkCommonInstance.levelLog = levelLog
+            NextcloudKit.shared.nkCommonInstance.copyLogToDocumentDirectory = true
+            NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Start session with level \(levelLog) " + versionNextcloudiOS + " in state \(UIApplication.shared.applicationState.rawValue) where (0 active, 1 inactive, 2 background).")
+        }
+
+        // LOG Account
+        if let account = NCManageDatabase.shared.getActiveAccount() {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Account active \(account.account)")
+            if CCUtility.getPassword(account.account).isEmpty {
+                NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] PASSWORD NOT FOUND for \(account.account)")
+            }
+        }
+
+        // Activate user account
+        if let activeAccount = NCManageDatabase.shared.getActiveAccount() {
+
+            settingAccount(activeAccount.account, urlBase: activeAccount.urlBase, user: activeAccount.user, userId: activeAccount.userId, password: CCUtility.getPassword(activeAccount.account))
+            NCBrandColor.shared.settingThemingColor(account: activeAccount.account)
+
+        } else {
+
+            CCUtility.deleteAllChainStore()
+            if let bundleID = Bundle.main.bundleIdentifier {
+                UserDefaults.standard.removePersistentDomain(forName: bundleID)
+            }
+            NCBrandColor.shared.createImagesThemingColor()
+        }
+
+        // Create user color
+        NCBrandColor.shared.createUserColors()
+
+        // Push Notification & display notification
+        application.registerForRemoteNotifications()
+        UNUserNotificationCenter.current().delegate = self
+        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { _, _ in }
+
+        // Store review
+        if !NCUtility.shared.isSimulatorOrTestFlight() {
+            let review = NCStoreReview()
+            review.incrementAppRuns()
+            review.showStoreReview()
+        }
+
+        // Background task: register
+        BGTaskScheduler.shared.register(forTaskWithIdentifier: NCGlobal.shared.refreshTask, using: nil) { task in
+            self.handleRefreshTask(task)
+        }
+        BGTaskScheduler.shared.register(forTaskWithIdentifier: NCGlobal.shared.processingTask, using: nil) { task in
+            self.handleProcessingTask(task)
+        }
+
+        // Intro
+        if NCBrandOptions.shared.disable_intro {
+            CCUtility.setIntro(true)
+            if account.isEmpty {
+                openLogin(viewController: nil, selector: NCGlobal.shared.introLogin, openLoginWeb: false)
+            }
+        } else {
+            if !CCUtility.getIntro() {
+                if let viewController = UIStoryboard(name: "NCIntro", bundle: nil).instantiateInitialViewController() {
+                    let navigationController = NCLoginNavigationController.init(rootViewController: viewController)
+                    window?.rootViewController = navigationController
+                    window?.makeKeyAndVisible()
+                }
+            }
+        }
+
+        // Passcode
+        self.presentPasscode {
+            self.enableTouchFaceID()
+        }
+
+        return true
+    }
+
+    // MARK: - Life Cycle
+
+    // L' applicazione entrerà in attivo (sempre)
+    func applicationDidBecomeActive(_ application: UIApplication) {
+
+        NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Application did become active")
+
+        NCSettingsBundleHelper.setVersionAndBuildNumber()
+        NCSettingsBundleHelper.checkAndExecuteSettings(delay: 0.5)
+        
+        // START OBSERVE/TIMER UPLOAD PROCESS
+        NCNetworkingProcessUpload.shared.observeTableMetadata()
+        NCNetworkingProcessUpload.shared.startTimer()
+
+        self.deletePasswordSession = false
+
+        if !NCAskAuthorization.shared.isRequesting {
+            hidePrivacyProtectionWindow()
+        }
+
+        if !account.isEmpty {
+            NCNetworkingProcessUpload.shared.verifyUploadZombie()
+        }
+
+        // Start Auto Upload
+        NCAutoUpload.shared.initAutoUpload(viewController: nil) { items in
+            NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Initialize Auto upload with \(items) uploads")
+        }
+
+        NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterApplicationDidBecomeActive)
+    }
+
+    // L' applicazione entrerà in primo piano (dopo il background)
+    func applicationWillEnterForeground(_ application: UIApplication) {
+        guard !account.isEmpty, let activeAccount = NCManageDatabase.shared.getActiveAccount() else { return }
+
+        NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Application will enter in foreground")
+
+        if activeAccount.account != account {
+            settingAccount(activeAccount.account, urlBase: activeAccount.urlBase, user: activeAccount.user, userId: activeAccount.userId, password: CCUtility.getPassword(activeAccount.account))
+        } else {
+            // Request Service Server Nextcloud
+            NCService.shared.startRequestServicesServer()
+        }
+
+        // Required unsubscribing / subscribing
+        NCPushNotification.shared().pushNotification()
+
+        // Request TouchID, FaceID
+        enableTouchFaceID()
+        
+        NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterApplicationWillEnterForeground)
+        NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterRichdocumentGrabFocus)
+        NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSourceNetwork)
+    }
+
+    // L' applicazione si dimetterà dallo stato di attivo
+    func applicationWillResignActive(_ application: UIApplication) {
+        // Nextcloud update share accounts
+        if let error = updateShareAccounts() {
+            NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Create share accounts \(error.localizedDescription)")
+        }
+        NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Application will resign active")
+        guard !account.isEmpty else { return }
+
+        // STOP OBSERVE/TIMER UPLOAD PROCESS
+        NCNetworkingProcessUpload.shared.invalidateObserveTableMetadata()
+        NCNetworkingProcessUpload.shared.stopTimer()
+
+        if CCUtility.getPrivacyScreenEnabled() {
+            // Privacy
+            showPrivacyProtectionWindow()
+        }
+
+        // Reload Widget
+        WidgetCenter.shared.reloadAllTimelines()
+
+        // Clear operation queue
+        NCOperationQueue.shared.cancelAllQueue()
+        // Clear download
+        NCNetworking.shared.cancelAllDownloadTransfer()
+
+        // Clear older files
+        let days = CCUtility.getCleanUpDay()
+        if let directory = CCUtility.getDirectoryProviderStorage() {
+            NCUtilityFileSystem.shared.cleanUp(directory: directory, days: TimeInterval(days))
+        }
+
+        NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterApplicationWillResignActive)
+    }
+
+    // L' applicazione è entrata nello sfondo
+    func applicationDidEnterBackground(_ application: UIApplication) {
+        guard !account.isEmpty else { return }
+
+        NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Application did enter in background")
+
+        scheduleAppRefresh()
+        scheduleAppProcessing()
+
+        // Passcode
+        presentPasscode { }
+
+        NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterApplicationDidEnterBackground)
+    }
+
+    // L'applicazione terminerà
+    func applicationWillTerminate(_ application: UIApplication) {
+
+        NCNetworking.shared.cancelAllDownloadTransfer()
+
+        if UIApplication.shared.backgroundRefreshStatus == .available {
+
+            let content = UNMutableNotificationContent()
+            content.title = NCBrandOptions.shared.brand
+            content.body = NSLocalizedString("_keep_running_", comment: "")
+            let req = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
+            let notificationCenter = UNUserNotificationCenter.current()
+            notificationCenter.add(req)
+        }
+
+        NextcloudKit.shared.nkCommonInstance.writeLog("bye bye")
+    }
+
+    // MARK: -
+
+    @objc private func initialize() {
+        guard !account.isEmpty else { return }
+
+        NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] initialize Main")
+
+        // Registeration push notification
+        NCPushNotification.shared().pushNotification()
+
+        // Unlock E2EE
+        NCNetworkingE2EE.shared.unlockAll(account: account)
+
+        // Start services
+        NCService.shared.startRequestServicesServer()
+
+        // close detail
+        NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterMenuDetailClose)
+
+        // Reload Widget
+        WidgetCenter.shared.reloadAllTimelines()
+
+        // Registeration domain File Provider
+        // FileProviderDomain *fileProviderDomain = [FileProviderDomain new];
+        // [fileProviderDomain removeAllDomains];
+        // [fileProviderDomain registerDomains];
+    }
+
+    // MARK: - Background Task
+
+    /*
+    @discussion Schedule a refresh task request to ask that the system launch your app briefly so that you can download data and keep your app's contents up-to-date. The system will fulfill this request intelligently based on system conditions and app usage.
+     < MAX 30 seconds >
+     */
+    func scheduleAppRefresh() {
+
+        let request = BGAppRefreshTaskRequest(identifier: NCGlobal.shared.refreshTask)
+        request.earliestBeginDate = Date(timeIntervalSinceNow: 60) // Refresh after 60 seconds.
+        do {
+            try BGTaskScheduler.shared.submit(request)
+            NextcloudKit.shared.nkCommonInstance.writeLog("[SUCCESS] Refresh task success submit request 60 seconds \(request)")
+        } catch {
+            NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Refresh task failed to submit request: \(error)")
+        }
+    }
+
+    /*
+     @discussion Schedule a processing task request to ask that the system launch your app when conditions are favorable for battery life to handle deferrable, longer-running processing, such as syncing, database maintenance, or similar tasks. The system will attempt to fulfill this request to the best of its ability within the next two days as long as the user has used your app within the past week.
+     < MAX over 1 minute >
+     */
+    func scheduleAppProcessing() {
+
+        let request = BGProcessingTaskRequest(identifier: NCGlobal.shared.processingTask)
+        request.earliestBeginDate = Date(timeIntervalSinceNow: 5 * 60) // Refresh after 5 minutes.
+        request.requiresNetworkConnectivity = false
+        request.requiresExternalPower = false
+        do {
+            try BGTaskScheduler.shared.submit(request)
+            NextcloudKit.shared.nkCommonInstance.writeLog("[SUCCESS] Background Processing task success submit request 5 minutes \(request)")
+        } catch {
+            NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Background Processing task failed to submit request: \(error)")
+        }
+    }
+
+    func handleRefreshTask(_ task: BGTask) {
+        scheduleAppRefresh()
+        
+        guard !account.isEmpty else {
+            task.setTaskCompleted(success: true)
+            return
+        }
+
+        NextcloudKit.shared.setup(delegate: NCNetworking.shared)
+
+        NCAutoUpload.shared.initAutoUpload(viewController: nil) { items in
+            NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Refresh task auto upload with \(items) uploads")
+            NCNetworkingProcessUpload.shared.start { items in
+                NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Refresh task upload process with \(items) uploads")
+                task.setTaskCompleted(success: true)
+            }
+        }
+    }
+
+    func handleProcessingTask(_ task: BGTask) {
+        scheduleAppProcessing()
+        
+        guard !account.isEmpty else {
+            task.setTaskCompleted(success: true)
+            return
+        }
+
+        NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Processing task")
+        task.setTaskCompleted(success: true)
+    }
+
+    // MARK: - Background Networking Session
+
+    func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
+
+        NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Start handle Events For Background URLSession: \(identifier)")
+        // Reload Widget
+        WidgetCenter.shared.reloadAllTimelines()
+        backgroundSessionCompletionHandler = completionHandler
+    }
+
+    // MARK: - Push Notifications
+
+    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
+        completionHandler([.list, .banner, .sound])
+    }
+
+    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
+        completionHandler()
+    }
+
+    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
+        NCNetworking.shared.checkPushNotificationServerProxyCertificateUntrusted(viewController: self.window?.rootViewController) { error in
+            if error == .success {
+                NCPushNotification.shared().registerForRemoteNotifications(withDeviceToken: deviceToken)
+            }
+        }
+    }
+
+    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
+        NCPushNotification.shared().applicationdidReceiveRemoteNotification(userInfo) { result in
+            completionHandler(result)
+        }
+    }
+
+    // MARK: - Login & checkErrorNetworking
+
+    @objc func openLogin(viewController: UIViewController?, selector: Int, openLoginWeb: Bool) {
+
+        // [WEBPersonalized] [AppConfig]
+        if NCBrandOptions.shared.use_login_web_personalized || NCBrandOptions.shared.use_AppConfig {
+
+            if activeLoginWeb?.view.window == nil {
+                activeLoginWeb = UIStoryboard(name: "NCLogin", bundle: nil).instantiateViewController(withIdentifier: "NCLoginWeb") as? NCLoginWeb
+                activeLoginWeb?.urlBase = NCBrandOptions.shared.loginBaseUrl
+                showLoginViewController(activeLoginWeb, contextViewController: viewController)
+            }
+            return
+        }
+
+        // Nextcloud standard login
+        if selector == NCGlobal.shared.introSignup {
+
+            if activeLoginWeb?.view.window == nil {
+                activeLoginWeb = UIStoryboard(name: "NCLogin", bundle: nil).instantiateViewController(withIdentifier: "NCLoginWeb") as? NCLoginWeb
+                if selector == NCGlobal.shared.introSignup {
+                    activeLoginWeb?.urlBase = NCBrandOptions.shared.linkloginPreferredProviders
+                } else {
+                    activeLoginWeb?.urlBase = self.urlBase
+                }
+                showLoginViewController(activeLoginWeb, contextViewController: viewController)
+            }
+
+        } else if NCBrandOptions.shared.disable_intro && NCBrandOptions.shared.disable_request_login_url {
+
+            if activeLoginWeb?.view.window == nil {
+                activeLoginWeb = UIStoryboard(name: "NCLogin", bundle: nil).instantiateViewController(withIdentifier: "NCLoginWeb") as? NCLoginWeb
+                activeLoginWeb?.urlBase = NCBrandOptions.shared.loginBaseUrl
+                showLoginViewController(activeLoginWeb, contextViewController: viewController)
+            }
+
+        } else if openLoginWeb {
+
+            // Used also for reinsert the account (change passwd)
+            if activeLoginWeb?.view.window == nil {
+                activeLoginWeb = UIStoryboard(name: "NCLogin", bundle: nil).instantiateViewController(withIdentifier: "NCLoginWeb") as? NCLoginWeb
+                activeLoginWeb?.urlBase = urlBase
+                activeLoginWeb?.user = user
+                showLoginViewController(activeLoginWeb, contextViewController: viewController)
+            }
+
+        } else {
+
+            if activeLogin?.view.window == nil {
+                activeLogin = UIStoryboard(name: "NCLogin", bundle: nil).instantiateViewController(withIdentifier: "NCLogin") as? NCLogin
+                showLoginViewController(activeLogin, contextViewController: viewController)
+            }
+        }
+    }
+
+    func showLoginViewController(_ viewController: UIViewController?, contextViewController: UIViewController?) {
+
+        if contextViewController == nil {
+            if let viewController = viewController {
+                let navigationController = NCLoginNavigationController.init(rootViewController: viewController)
+                navigationController.navigationBar.barStyle = .black
+                navigationController.navigationBar.tintColor = NCBrandColor.shared.customerText
+                navigationController.navigationBar.barTintColor = NCBrandColor.shared.customer
+                navigationController.navigationBar.isTranslucent = false
+                window?.rootViewController = navigationController
+                window?.makeKeyAndVisible()
+            }
+        } else if contextViewController is UINavigationController {
+            if let contextViewController = contextViewController, let viewController = viewController {
+                (contextViewController as! UINavigationController).pushViewController(viewController, animated: true)
+            }
+        } else {
+            if let viewController = viewController, let contextViewController = contextViewController {
+                let navigationController = NCLoginNavigationController.init(rootViewController: viewController)
+                navigationController.modalPresentationStyle = .fullScreen
+                navigationController.navigationBar.barStyle = .black
+                navigationController.navigationBar.tintColor = NCBrandColor.shared.customerText
+                navigationController.navigationBar.barTintColor = NCBrandColor.shared.customer
+                navigationController.navigationBar.isTranslucent = false
+                contextViewController.present(navigationController, animated: true) { }
+            }
+        }
+    }
+    
+    @objc func startTimerErrorNetworking() {
+        timerErrorNetworking = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(checkErrorNetworking), userInfo: nil, repeats: true)
+    }
+
+    @objc private func checkErrorNetworking() {
+        
+        // check unauthorized server (401/403)
+        if account != "" && CCUtility.getPassword(account)!.count == 0 {
+            openLogin(viewController: window?.rootViewController, selector: NCGlobal.shared.introLogin, openLoginWeb: true)
+        }
+    }
+    
+    func trustCertificateError(host: String) {
+
+        guard let currentHost = URL(string: self.urlBase)?.host,
+              let pushNotificationServerProxyHost = URL(string: NCBrandOptions.shared.pushNotificationServerProxy)?.host,
+              host != pushNotificationServerProxyHost,
+              host == currentHost
+        else { return }
+
+        let certificateHostSavedPath = CCUtility.getDirectoryCerificates()! + "/" + host + ".der"
+        var title = NSLocalizedString("_ssl_certificate_changed_", comment: "")
+
+        if !FileManager.default.fileExists(atPath: certificateHostSavedPath) {
+            title = NSLocalizedString("_connect_server_anyway_", comment: "")
+        }
+
+        let alertController = UIAlertController(title: title, message: NSLocalizedString("_server_is_trusted_", comment: ""), preferredStyle: .alert)
+
+        alertController.addAction(UIAlertAction(title: NSLocalizedString("_yes_", comment: ""), style: .default, handler: { action in
+            NCNetworking.shared.writeCertificate(host: host)
+        }))
+
+        alertController.addAction(UIAlertAction(title: NSLocalizedString("_no_", comment: ""), style: .default, handler: { action in }))
+
+        alertController.addAction(UIAlertAction(title: NSLocalizedString("_certificate_details_", comment: ""), style: .default, handler: { action in
+            if let navigationController = UIStoryboard(name: "NCViewCertificateDetails", bundle: nil).instantiateInitialViewController() as? UINavigationController {
+                let viewController = navigationController.topViewController as! NCViewCertificateDetails
+                viewController.delegate = self
+                viewController.host = host
+                self.window?.rootViewController?.present(navigationController, animated: true)
+            }
+        }))
+
+        window?.rootViewController?.present(alertController, animated: true)
+    }
+
+    func viewCertificateDetailsDismiss(host: String) {
+        trustCertificateError(host: host)
+    }
+
+    // MARK: - Account
+
+    @objc func settingAccount(_ account: String, urlBase: String, user: String, userId: String, password: String) {
+
+        let accountTestBackup = self.account + "/" + self.userId
+        let accountTest = account +  "/" + userId
+
+        self.account = account
+        self.urlBase = urlBase
+        self.user = user
+        self.userId = userId
+        self.password = password
+
+        _ = NCActionCenter.shared
+
+        NextcloudKit.shared.setup(account: account, user: user, userId: userId, password: password, urlBase: urlBase)
+        let serverVersionMajor = NCManageDatabase.shared.getCapabilitiesServerInt(account: account, elements: NCElementsJSON.shared.capabilitiesVersionMajor)
+        if serverVersionMajor > 0 {
+            NextcloudKit.shared.setup(nextcloudVersion: serverVersionMajor)
+        }
+
+        DispatchQueue.main.async {
+            if UIApplication.shared.applicationState != .background && accountTestBackup != accountTest {
+                NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterInitialize, second: 0.2)
+            }
+        }
+    }
+
+    @objc func deleteAccount(_ account: String, wipe: Bool) {
+
+        if let account = NCManageDatabase.shared.getAccount(predicate: NSPredicate(format: "account == %@", account)) {
+            NCPushNotification.shared().unsubscribingNextcloudServerPushNotification(account.account, urlBase: account.urlBase, user: account.user, withSubscribing: false)
+        }
+        
+        let results = NCManageDatabase.shared.getTableLocalFiles(predicate: NSPredicate(format: "account == %@", account), sorted: "ocId", ascending: false)
+        for result in results {
+            CCUtility.removeFile(atPath: CCUtility.getDirectoryProviderStorageOcId(result.ocId))
+        }
+        NCManageDatabase.shared.clearDatabase(account: account, removeAccount: true)
+        
+        CCUtility.clearAllKeysEnd(toEnd: account)
+        CCUtility.clearAllKeysPushNotification(account)
+        CCUtility.setPassword(account, password: nil)
+
+        if wipe {
+            settingAccount("", urlBase: "", user: "", userId: "", password: "")
+            let accounts = NCManageDatabase.shared.getAccounts()
+            if accounts?.count ?? 0 > 0 {
+                if let newAccount = accounts?.first {
+                    self.changeAccount(newAccount)
+                }
+            } else {
+                openLogin(viewController: window?.rootViewController, selector: NCGlobal.shared.introLogin, openLoginWeb: false)
+            }
+        }
+    }
+
+    @objc func changeAccount(_ account: String) {
+
+        NCManageDatabase.shared.setAccountActive(account)
+        if let tableAccount = NCManageDatabase.shared.getActiveAccount() {
+
+            NCOperationQueue.shared.cancelAllQueue()
+            NCNetworking.shared.cancelAllTask()
+
+            settingAccount(tableAccount.account, urlBase: tableAccount.urlBase, user: tableAccount.user, userId: tableAccount.userId, password: CCUtility.getPassword(tableAccount.account))
+        }
+    }
+
+    func updateShareAccounts() -> Error? {
+        guard let dirGroupApps = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: NCBrandOptions.shared.capabilitiesGroupApps) else { return nil }
+
+        let tableAccount = NCManageDatabase.shared.getAllAccount()
+        var accounts = [NKShareAccounts.DataAccounts]()
+        for account in tableAccount {
+            let name = account.alias.isEmpty ? account.displayName : account.alias
+            let userBaseUrl = account.user + "-" + (URL(string: account.urlBase)?.host ?? "")
+            let avatarFileName = userBaseUrl + "-\(account.user).png"
+            let pathAvatarFileName = String(CCUtility.getDirectoryUserData()) + "/" + avatarFileName
+            let image = UIImage(contentsOfFile: pathAvatarFileName)
+            accounts.append(NKShareAccounts.DataAccounts(withUrl: account.urlBase, user: account.user, name: name, image: image))
+        }
+        return NKShareAccounts().putShareAccounts(at: dirGroupApps, app: NCGlobal.shared.appScheme, dataAccounts: accounts)
+    }
+
+    // MARK: - Account Request
+
+    func accountRequestChangeAccount(account: String) {
+        changeAccount(account)
+    }
+    
+    func requestAccount() {
+
+        if isPasscodePresented() { return }
+        if !CCUtility.getAccountRequest() { return }
+
+        let accounts = NCManageDatabase.shared.getAllAccount()
+
+        if accounts.count > 1 {
+            
+            if let vcAccountRequest = UIStoryboard(name: "NCAccountRequest", bundle: nil).instantiateInitialViewController() as? NCAccountRequest {
+
+                vcAccountRequest.activeAccount = NCManageDatabase.shared.getActiveAccount()
+                vcAccountRequest.accounts = accounts
+                vcAccountRequest.enableTimerProgress = true
+                vcAccountRequest.enableAddAccount = false
+                vcAccountRequest.dismissDidEnterBackground = false
+                vcAccountRequest.delegate = self
+
+                let screenHeighMax = UIScreen.main.bounds.height - (UIScreen.main.bounds.height/5)
+                let numberCell = accounts.count
+                let height = min(CGFloat(numberCell * Int(vcAccountRequest.heightCell) + 45), screenHeighMax)
+
+                let popup = NCPopupViewController(contentController: vcAccountRequest, popupWidth: 300, popupHeight: height+20)
+                popup.backgroundAlpha = 0.8
+
+                window?.rootViewController?.present(popup, animated: true)
+                
+                vcAccountRequest.startTimer()
+            }
+        }
+    }
+
+    // MARK: - Passcode
+
+    func presentPasscode(completion: @escaping () -> ()) {
+
+        let laContext = LAContext()
+        var error: NSError?
+
+        defer { self.requestAccount() }
+
+        let presentedViewController = window?.rootViewController?.presentedViewController
+        guard !account.isEmpty, CCUtility.isPasscodeAtStartEnabled(), !(presentedViewController is NCLoginNavigationController) else { return }
+
+        // Make sure we have a privacy window (in case it's not enabled)
+        showPrivacyProtectionWindow()
+
+        let passcodeViewController = TOPasscodeViewController(passcodeType: .sixDigits, allowCancel: false)
+        passcodeViewController.delegate = self
+        passcodeViewController.keypadButtonShowLettering = false
+        if CCUtility.getEnableTouchFaceID() && laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
+            if error == nil {
+                if laContext.biometryType == .faceID  {
+                    passcodeViewController.biometryType = .faceID
+                } else if laContext.biometryType == .touchID  {
+                    passcodeViewController.biometryType = .touchID
+                }
+                passcodeViewController.allowBiometricValidation = true
+                passcodeViewController.automaticallyPromptForBiometricValidation = false
+            }
+        }
+
+        // show passcode on top of privacy window
+        privacyProtectionWindow?.rootViewController?.present(passcodeViewController, animated: true, completion: {
+            completion()
+        })
+    }
+
+    func isPasscodePresented() -> Bool {
+        return privacyProtectionWindow?.rootViewController?.presentedViewController is TOPasscodeViewController
+    }
+
+    func enableTouchFaceID() {
+        guard !account.isEmpty,
+              CCUtility.getEnableTouchFaceID(),
+              CCUtility.isPasscodeAtStartEnabled(),
+              let passcodeViewController = privacyProtectionWindow?.rootViewController?.presentedViewController as? TOPasscodeViewController
+        else { return }
+
+        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+            LAContext().evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: NCBrandOptions.shared.brand) { (success, error) in
+                if success {
+                    DispatchQueue.main.async {
+                        passcodeViewController.dismiss(animated: true) {
+                            self.hidePrivacyProtectionWindow()
+                            self.requestAccount()
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    func didInputCorrectPasscode(in passcodeViewController: TOPasscodeViewController) {
+        DispatchQueue.main.async {
+            passcodeViewController.dismiss(animated: true) {
+                self.hidePrivacyProtectionWindow()
+                self.requestAccount()
+            }
+        }
+    }
+
+    func passcodeViewController(_ passcodeViewController: TOPasscodeViewController, isCorrectCode code: String) -> Bool {
+        return code == CCUtility.getPasscode()
+    }
+
+    func didPerformBiometricValidationRequest(in passcodeViewController: TOPasscodeViewController) {
+        enableTouchFaceID()
+    }
+
+    // MARK: - Privacy Protection
+
+    private func showPrivacyProtectionWindow() {
+        guard privacyProtectionWindow == nil else {
+            privacyProtectionWindow?.isHidden = false
+            return
+        }
+
+        privacyProtectionWindow = UIWindow(frame: UIScreen.main.bounds)
+
+        let storyboard = UIStoryboard(name: "LaunchScreen", bundle: nil)
+        let initialViewController = storyboard.instantiateInitialViewController()
+
+        self.privacyProtectionWindow?.rootViewController = initialViewController
+
+        privacyProtectionWindow?.windowLevel = .alert + 1
+        privacyProtectionWindow?.makeKeyAndVisible()
+    }
+
+    func hidePrivacyProtectionWindow() {
+        guard !(privacyProtectionWindow?.rootViewController?.presentedViewController is TOPasscodeViewController) else { return }
+        UIWindow.animate(withDuration: 0.25) {
+            self.privacyProtectionWindow?.alpha = 0
+        } completion: { _ in
+            self.privacyProtectionWindow?.isHidden = true
+            self.privacyProtectionWindow = nil
+        }
+    }
+
+    // MARK: - Universal Links
+
+    func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
+
+        let applicationHandle = NCApplicationHandle()
+        return applicationHandle.applicationOpenUserActivity(userActivity)
+    }
+
+    // MARK: - Scheme URL
+
+    func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
+
+        let scheme = url.scheme
+        let action = url.host
+        var fileName: String = ""
+        var serverUrl: String = ""
+
+        /*
+         Example:
+         nextcloud://open-action?action=create-voice-memo&&user=marinofaggiana&url=https://cloud.nextcloud.com
+         */
+
+        if !account.isEmpty && scheme == NCGlobal.shared.appScheme && action == "open-action" {
+
+            if let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) {
+
+                let queryItems = urlComponents.queryItems
+                guard let actionScheme = CCUtility.value(forKey: "action", fromQueryItems: queryItems), let rootViewController = window?.rootViewController else { return false }
+                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 getMatchedAccount(userId: userScheme, url: urlScheme) == nil {
+                    let message = String(format: NSLocalizedString("_account_not_exists_", comment: ""), userScheme, urlScheme)
+
+                    let alertController = UIAlertController(title: NSLocalizedString("_info_", comment: ""), message: message, preferredStyle: .alert)
+                    alertController.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .default, handler: { _ in }))
+
+                    window?.rootViewController?.present(alertController, animated: true, completion: { })
+
+                    return false
+                }
+
+
+                switch actionScheme {
+                case NCGlobal.shared.actionUploadAsset:
+
+                    NCAskAuthorization.shared.askAuthorizationPhotoLibrary(viewController: rootViewController) { hasPermission in
+                        if hasPermission {
+                            NCPhotosPickerViewController.init(viewController: rootViewController, maxSelectedAssets: 0, singleSelectedMode: false)
+                        }
+                    }
+                    
+                case NCGlobal.shared.actionScanDocument:
+
+                    NCDocumentCamera.shared.openScannerDocument(viewController: rootViewController)
+
+                case NCGlobal.shared.actionTextDocument:
+                    
+                    guard let navigationController = UIStoryboard(name: "NCCreateFormUploadDocuments", bundle: nil).instantiateInitialViewController(), let directEditingCreators = NCManageDatabase.shared.getDirectEditingCreators(account: account), let directEditingCreator = directEditingCreators.first(where: { $0.editor == NCGlobal.shared.editorText}) else { return false }
+                    
+                    navigationController.modalPresentationStyle = UIModalPresentationStyle.formSheet
+
+                    let viewController = (navigationController as! UINavigationController).topViewController as! NCCreateFormUploadDocuments
+                    viewController.editorId = NCGlobal.shared.editorText
+                    viewController.creatorId = directEditingCreator.identifier
+                    viewController.typeTemplate = NCGlobal.shared.templateDocument
+                    viewController.serverUrl = activeServerUrl
+                    viewController.titleForm = NSLocalizedString("_create_nextcloudtext_document_", comment: "")
+
+                    rootViewController.present(navigationController, animated: true, completion: nil)
+                    
+                case NCGlobal.shared.actionVoiceMemo:
+                    
+                    NCAskAuthorization.shared.askAuthorizationAudioRecord(viewController: rootViewController) { hasPermission in
+                        if hasPermission {
+                            let fileName = CCUtility.createFileNameDate(NSLocalizedString("_voice_memo_filename_", comment: ""), extension: "m4a")!
+                            let viewController = UIStoryboard(name: "NCAudioRecorderViewController", bundle: nil).instantiateInitialViewController() as! NCAudioRecorderViewController
+
+                            viewController.delegate = self
+                            viewController.createRecorder(fileName: fileName)
+                            viewController.modalTransitionStyle = .crossDissolve
+                            viewController.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
+
+                            rootViewController.present(viewController, animated: true, completion: nil)
+                        }
+                    }
+
+                default:
+                    print("No action")
+                }
+            }
+            return true
+        }
+
+        /*
+         Example:
+         nextcloud://open-file?path=Talk/IMG_0000123.jpg&user=marinofaggiana&link=https://cloud.nextcloud.com/f/123
+         */
+
+        else if !account.isEmpty && scheme == NCGlobal.shared.appScheme && action == "open-file" {
+
+            if let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) {
+
+                let queryItems = urlComponents.queryItems
+                guard let userScheme = CCUtility.value(forKey: "user", fromQueryItems: queryItems) else { return false }
+                guard let pathScheme = CCUtility.value(forKey: "path", fromQueryItems: queryItems) else { return false }
+                guard let linkScheme = CCUtility.value(forKey: "link", fromQueryItems: queryItems) else { return false }
+                guard let matchedAccount = getMatchedAccount(userId: userScheme, url: linkScheme) else {
+                    guard let domain = URL(string: linkScheme)?.host else { return true }
+                    fileName = (pathScheme as NSString).lastPathComponent
+                    let message = String(format: NSLocalizedString("_account_not_available_", comment: ""), userScheme, domain, fileName)
+
+                    let alertController = UIAlertController(title: NSLocalizedString("_info_", comment: ""), message: message, preferredStyle: .alert)
+                    alertController.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .default, handler: { _ in }))
+
+                    window?.rootViewController?.present(alertController, animated: true, completion: { })
+
+                    return false
+                }
+
+                let davFiles = NextcloudKit.shared.nkCommonInstance.dav + "/files/" + self.userId
+                if pathScheme.contains("/") {
+                    fileName = (pathScheme as NSString).lastPathComponent
+                    serverUrl = matchedAccount.urlBase + "/" + davFiles + "/" + (pathScheme as NSString).deletingLastPathComponent
+                } else {
+                    fileName = pathScheme
+                    serverUrl = matchedAccount.urlBase + "/" + davFiles
+                }
+
+                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+                    NCActionCenter.shared.openFileViewInFolder(serverUrl: serverUrl, fileNameBlink: nil, fileNameOpen: fileName)
+                }
+            }
+            return true
+        } else {
+            let applicationHandle = NCApplicationHandle()
+            let isHandled = applicationHandle.applicationOpenURL(url)
+            if isHandled {
+                return true
+            } else {
+                app.open(url)
+                return true
+            }
+        }
+    }
+
+    func getMatchedAccount(userId: String, url: String) -> tableAccount? {
+
+        if let activeAccount = NCManageDatabase.shared.getActiveAccount() {
+            let urlBase = URL(string: activeAccount.urlBase)
+            if url.contains(urlBase?.host ?? "") && userId == activeAccount.userId {
+               return activeAccount
+            } else {
+                let accounts = NCManageDatabase.shared.getAllAccount()
+                for account in accounts {
+                    let urlBase = URL(string: account.urlBase)
+                    if url.contains(urlBase?.host ?? "") && userId == account.userId {
+                        changeAccount(account.account)
+                        return account
+                    }
+                }
+            }
+        }
+        return nil
+    }
+}
+
+// MARK: - NCAudioRecorder ViewController Delegate
+
+extension AppDelegate: NCAudioRecorderViewControllerDelegate {
+
+    func didFinishRecording(_ viewController: NCAudioRecorderViewController, fileName: String) {
+
+        guard
+            let navigationController = UIStoryboard(name: "NCCreateFormUploadVoiceNote", bundle: nil).instantiateInitialViewController() as? UINavigationController,
+                let viewController = navigationController.topViewController as? NCCreateFormUploadVoiceNote
+        else { return }
+        navigationController.modalPresentationStyle = .formSheet
+        viewController.setup(serverUrl: activeServerUrl, fileNamePath: NSTemporaryDirectory() + fileName, fileName: fileName)
+        window?.rootViewController?.present(navigationController, animated: true)
+    }
+
+    func didFinishWithoutRecording(_ viewController: NCAudioRecorderViewController, fileName: String) {
+    }
+}
+
+extension AppDelegate: NCCreateFormUploadConflictDelegate {
+    func dismissCreateFormUploadConflict(metadatas: [tableMetadata]?) {
+        guard let metadatas = metadatas, !metadatas.isEmpty else { return }
+        NCNetworkingProcessUpload.shared.createProcessUploads(metadatas: metadatas) { _ in }
+    }
+}

+ 46 - 0
iOSClient/BrowserWeb/NCBrowserWeb.storyboard

@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="OOi-qQ-BCK">
+    <device id="retina6_1" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <scenes>
+        <!--Browser Web-->
+        <scene sceneID="qXu-Mk-wdV">
+            <objects>
+                <viewController hidesBottomBarWhenPushed="YES" id="OOi-qQ-BCK" customClass="NCBrowserWeb" customModule="Nextcloud" customModuleProvider="target" sceneMemberID="viewController">
+                    <view key="view" contentMode="scaleToFill" id="Aai-ip-ntL">
+                        <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <subviews>
+                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Lht-2f-Ep4">
+                                <rect key="frame" x="10" y="58" width="30" height="30"/>
+                                <constraints>
+                                    <constraint firstAttribute="height" constant="30" id="ckh-Eb-Ze8"/>
+                                    <constraint firstAttribute="width" constant="30" id="oHS-Ba-9eq"/>
+                                </constraints>
+                                <connections>
+                                    <action selector="touchUpInsideButtonExit:" destination="OOi-qQ-BCK" eventType="touchUpInside" id="hcA-Hb-TJd"/>
+                                </connections>
+                            </button>
+                        </subviews>
+                        <viewLayoutGuide key="safeArea" id="fTU-bd-6qR"/>
+                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                        <constraints>
+                            <constraint firstItem="Lht-2f-Ep4" firstAttribute="leading" secondItem="fTU-bd-6qR" secondAttribute="leading" constant="10" id="Oui-0m-UD8"/>
+                            <constraint firstItem="Lht-2f-Ep4" firstAttribute="top" secondItem="fTU-bd-6qR" secondAttribute="top" constant="10" id="Qy7-ho-gnO"/>
+                        </constraints>
+                    </view>
+                    <connections>
+                        <outlet property="buttonExit" destination="Lht-2f-Ep4" id="dsx-PX-MaQ"/>
+                    </connections>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="4JZ-dx-ros" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="4511.594202898551" y="-1210.0446428571429"/>
+        </scene>
+    </scenes>
+</document>

+ 120 - 0
iOSClient/BrowserWeb/NCBrowserWeb.swift

@@ -0,0 +1,120 @@
+//
+//  NCBrowserWeb.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 22/08/2019.
+//  Copyright (c) 2019 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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
+import WebKit
+
+@objc protocol NCBrowserWebDelegate: AnyObject {
+    @objc optional func browserWebDismiss()
+}
+
+class NCBrowserWeb: UIViewController {
+
+    @objc var urlBase = ""
+    @objc var isHiddenButtonExit = false
+    @objc var titleBrowser: String?
+    @objc weak var delegate: NCBrowserWebDelegate?
+
+    @IBOutlet weak var buttonExit: UIButton!
+
+    // MARK: - View Life Cycle
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+
+        let webView = WKWebView(frame: CGRect.zero)
+        webView.navigationDelegate = self
+        view.addSubview(webView)
+        webView.translatesAutoresizingMaskIntoConstraints = false
+        webView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
+        webView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
+        webView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
+        webView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
+
+        if isHiddenButtonExit {
+            buttonExit.isHidden = true
+        } else {
+            self.view.bringSubviewToFront(buttonExit)
+            let image = NCUtility.shared.loadImage(named: "xmark", color: .systemBlue)
+            buttonExit.setImage(image, for: .normal)
+        }
+
+        if let url = URL(string: urlBase) {
+            loadWebPage(webView: webView, url: url)
+        } else {
+            let url = URL(fileURLWithPath: urlBase)
+            loadWebPage(webView: webView, url: url)
+        }
+    }
+
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+
+        if let titleBrowser = titleBrowser {
+            navigationItem.title = titleBrowser
+        }
+    }
+
+    deinit {
+
+    }
+
+    // MARK: - Action
+
+    @IBAction func touchUpInsideButtonExit(_ sender: UIButton) {
+        self.dismiss(animated: true) {
+            self.delegate?.browserWebDismiss?()
+        }
+    }
+
+    func loadWebPage(webView: WKWebView, url: URL) {
+
+        let language = NSLocale.preferredLanguages[0] as String
+        var request = URLRequest(url: url)
+
+        request.addValue("true", forHTTPHeaderField: "OCS-APIRequest")
+        request.addValue(language, forHTTPHeaderField: "Accept-Language")
+        
+        webView.customUserAgent = CCUtility.getUserAgent()
+        webView.load(request)
+    }
+}
+
+extension NCBrowserWeb: WKNavigationDelegate {
+
+    public func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
+        DispatchQueue.global().async {
+            if let serverTrust = challenge.protectionSpace.serverTrust {
+                completionHandler(Foundation.URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust: serverTrust))
+            } else {
+                completionHandler(URLSession.AuthChallengeDisposition.useCredential, nil)
+            }
+        }
+    }
+
+    public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
+        DispatchQueue.global().async {
+            decisionHandler(.allow)
+        }
+    }
+}

+ 293 - 0
iOSClient/Color/NCColorPicker.storyboard

@@ -0,0 +1,293 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Y6W-OH-hqX">
+    <device id="retina4_0" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
+        <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"/>
+    </dependencies>
+    <scenes>
+        <!--Color Picker-->
+        <scene sceneID="s0d-6b-0kx">
+            <objects>
+                <viewController id="Y6W-OH-hqX" customClass="NCColorPicker" customModule="Nextcloud" customModuleProvider="target" sceneMemberID="viewController">
+                    <view key="view" contentMode="scaleToFill" id="5EZ-qb-Rvc">
+                        <rect key="frame" x="0.0" y="0.0" width="200" height="320"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <subviews>
+                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="kwJ-4R-6nM">
+                                <rect key="frame" x="10" y="10" width="15" height="15"/>
+                                <constraints>
+                                    <constraint firstAttribute="height" constant="15" id="AE2-yu-3y4"/>
+                                    <constraint firstAttribute="width" constant="15" id="Foq-O9-Wep"/>
+                                </constraints>
+                                <state key="normal" image="xmark"/>
+                                <connections>
+                                    <action selector="closeAction:" destination="Y6W-OH-hqX" eventType="touchUpInside" id="nq5-sT-FEb"/>
+                                </connections>
+                            </button>
+                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="8DH-gC-coa">
+                                <rect key="frame" x="0.0" y="8.5" width="200" height="18"/>
+                                <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                                <nil key="textColor"/>
+                                <nil key="highlightedColor"/>
+                            </label>
+                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="JUR-Vj-yBU">
+                                <rect key="frame" x="15" y="45" width="40" height="40"/>
+                                <color key="backgroundColor" systemColor="labelColor"/>
+                                <constraints>
+                                    <constraint firstAttribute="width" constant="40" id="LDf-aO-ruY"/>
+                                    <constraint firstAttribute="height" constant="40" id="U5p-9E-WGx"/>
+                                </constraints>
+                                <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
+                                <connections>
+                                    <action selector="orangeButtonAction:" destination="Y6W-OH-hqX" eventType="touchUpInside" id="qjx-Me-0xU"/>
+                                </connections>
+                            </button>
+                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Zr2-rF-YUt">
+                                <rect key="frame" x="15" y="100" width="40" height="40"/>
+                                <color key="backgroundColor" systemColor="labelColor"/>
+                                <constraints>
+                                    <constraint firstAttribute="height" constant="40" id="krX-Ql-hLX"/>
+                                    <constraint firstAttribute="width" constant="40" id="pBq-i1-K9T"/>
+                                </constraints>
+                                <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
+                                <connections>
+                                    <action selector="redButtonAction:" destination="Y6W-OH-hqX" eventType="touchUpInside" id="b2r-oG-GvU"/>
+                                </connections>
+                            </button>
+                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dOn-SY-wnl">
+                                <rect key="frame" x="15" y="155" width="40" height="40"/>
+                                <color key="backgroundColor" systemColor="labelColor"/>
+                                <constraints>
+                                    <constraint firstAttribute="height" constant="40" id="BI1-aU-56D"/>
+                                    <constraint firstAttribute="width" constant="40" id="v3I-Na-zQ5"/>
+                                </constraints>
+                                <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
+                                <connections>
+                                    <action selector="purpleButtonAction:" destination="Y6W-OH-hqX" eventType="touchUpInside" id="Ep7-vV-zH8"/>
+                                </connections>
+                            </button>
+                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="C2a-jB-FVB">
+                                <rect key="frame" x="15" y="210" width="40" height="40"/>
+                                <color key="backgroundColor" systemColor="labelColor"/>
+                                <constraints>
+                                    <constraint firstAttribute="height" constant="40" id="Sfk-IP-7JV"/>
+                                    <constraint firstAttribute="width" constant="40" id="n8f-nK-weh"/>
+                                </constraints>
+                                <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
+                                <connections>
+                                    <action selector="blueButtonAction:" destination="Y6W-OH-hqX" eventType="touchUpInside" id="M04-54-NPB"/>
+                                </connections>
+                            </button>
+                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="8xv-8Y-A50">
+                                <rect key="frame" x="15" y="265" width="40" height="40"/>
+                                <color key="backgroundColor" systemColor="labelColor"/>
+                                <constraints>
+                                    <constraint firstAttribute="height" constant="40" id="Jgq-IJ-7jj"/>
+                                    <constraint firstAttribute="width" constant="40" id="h5m-EQ-2Go"/>
+                                </constraints>
+                                <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
+                                <connections>
+                                    <action selector="greenButtonAction:" destination="Y6W-OH-hqX" eventType="touchUpInside" id="VVw-Ra-U8N"/>
+                                </connections>
+                            </button>
+                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="OlH-Ak-sRA">
+                                <rect key="frame" x="80" y="45" width="40" height="40"/>
+                                <color key="backgroundColor" systemColor="labelColor"/>
+                                <constraints>
+                                    <constraint firstAttribute="height" constant="40" id="3kj-5z-0de"/>
+                                    <constraint firstAttribute="width" constant="40" id="vjr-4J-pHk"/>
+                                </constraints>
+                                <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
+                                <connections>
+                                    <action selector="cyanButtonAction:" destination="Y6W-OH-hqX" eventType="touchUpInside" id="Qxa-md-cPa"/>
+                                </connections>
+                            </button>
+                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="FU6-q8-9Iw">
+                                <rect key="frame" x="80" y="100" width="40" height="40"/>
+                                <color key="backgroundColor" systemColor="labelColor"/>
+                                <constraints>
+                                    <constraint firstAttribute="width" constant="40" id="NGk-p2-vQ4"/>
+                                    <constraint firstAttribute="height" constant="40" id="Rni-gX-9zj"/>
+                                </constraints>
+                                <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
+                                <connections>
+                                    <action selector="yellowButtonAction:" destination="Y6W-OH-hqX" eventType="touchUpInside" id="DuE-By-m9k"/>
+                                </connections>
+                            </button>
+                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="LwF-26-oss">
+                                <rect key="frame" x="80" y="155" width="40" height="40"/>
+                                <color key="backgroundColor" systemColor="labelColor"/>
+                                <constraints>
+                                    <constraint firstAttribute="height" constant="40" id="Pqo-uv-KrN"/>
+                                    <constraint firstAttribute="width" constant="40" id="y5l-31-c4D"/>
+                                </constraints>
+                                <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
+                                <connections>
+                                    <action selector="grayButtonAction:" destination="Y6W-OH-hqX" eventType="touchUpInside" id="wP2-wX-3Hl"/>
+                                </connections>
+                            </button>
+                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="I9w-cx-QlY">
+                                <rect key="frame" x="80" y="210" width="40" height="40"/>
+                                <color key="backgroundColor" systemColor="labelColor"/>
+                                <constraints>
+                                    <constraint firstAttribute="height" constant="40" id="Vgv-il-6Vw"/>
+                                    <constraint firstAttribute="width" constant="40" id="dHD-fs-7m0"/>
+                                </constraints>
+                                <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
+                                <connections>
+                                    <action selector="brownButtonAction:" destination="Y6W-OH-hqX" eventType="touchUpInside" id="AH7-Kr-g9S"/>
+                                </connections>
+                            </button>
+                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="12u-cu-XGu">
+                                <rect key="frame" x="80" y="265" width="40" height="40"/>
+                                <color key="backgroundColor" systemColor="labelColor"/>
+                                <constraints>
+                                    <constraint firstAttribute="width" constant="40" id="8vW-vR-kNP"/>
+                                    <constraint firstAttribute="height" constant="40" id="F3v-dP-jmB"/>
+                                </constraints>
+                                <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
+                                <connections>
+                                    <action selector="systemBlueButtonAction:" destination="Y6W-OH-hqX" eventType="touchUpInside" id="V6v-x6-0xf"/>
+                                </connections>
+                            </button>
+                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="tt7-sI-TfL">
+                                <rect key="frame" x="145" y="45" width="40" height="40"/>
+                                <color key="backgroundColor" systemColor="labelColor"/>
+                                <constraints>
+                                    <constraint firstAttribute="width" constant="40" id="b6h-GE-ftL"/>
+                                    <constraint firstAttribute="height" constant="40" id="mfL-IL-0ZL"/>
+                                </constraints>
+                                <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
+                                <connections>
+                                    <action selector="systemIndigoButtonAction:" destination="Y6W-OH-hqX" eventType="touchUpInside" id="rNl-zX-67r"/>
+                                </connections>
+                            </button>
+                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Arw-up-GyD">
+                                <rect key="frame" x="145" y="100" width="40" height="40"/>
+                                <color key="backgroundColor" systemColor="labelColor"/>
+                                <constraints>
+                                    <constraint firstAttribute="width" constant="40" id="A9m-mr-Ec3"/>
+                                    <constraint firstAttribute="height" constant="40" id="s6X-2m-m44"/>
+                                </constraints>
+                                <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
+                                <connections>
+                                    <action selector="systemMintButtonAction:" destination="Y6W-OH-hqX" eventType="touchUpInside" id="aDb-dN-ifP"/>
+                                </connections>
+                            </button>
+                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="I7I-Ed-32n">
+                                <rect key="frame" x="145" y="155" width="40" height="40"/>
+                                <color key="backgroundColor" systemColor="labelColor"/>
+                                <constraints>
+                                    <constraint firstAttribute="height" constant="40" id="Gd6-bt-nR7"/>
+                                    <constraint firstAttribute="width" constant="40" id="eRN-Vo-lal"/>
+                                </constraints>
+                                <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
+                                <connections>
+                                    <action selector="systemPinkButtonAction:" destination="Y6W-OH-hqX" eventType="touchUpInside" id="emC-0N-gqJ"/>
+                                </connections>
+                            </button>
+                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Uaq-hC-U4a">
+                                <rect key="frame" x="145" y="210" width="40" height="40"/>
+                                <color key="backgroundColor" systemColor="labelColor"/>
+                                <constraints>
+                                    <constraint firstAttribute="height" constant="40" id="DqE-d1-FMQ"/>
+                                    <constraint firstAttribute="width" constant="40" id="L0n-3P-wA1"/>
+                                </constraints>
+                                <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
+                                <connections>
+                                    <action selector="defaultButtonAction:" destination="Y6W-OH-hqX" eventType="touchUpInside" id="VhY-1n-fwQ"/>
+                                </connections>
+                            </button>
+                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="iB2-gu-3IO">
+                                <rect key="frame" x="145" y="265" width="40" height="40"/>
+                                <color key="backgroundColor" systemColor="labelColor"/>
+                                <constraints>
+                                    <constraint firstAttribute="width" constant="40" id="62A-PY-UZr"/>
+                                    <constraint firstAttribute="height" constant="40" id="P8F-Uh-nef"/>
+                                </constraints>
+                                <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
+                                <connections>
+                                    <action selector="customButtonAction:" destination="Y6W-OH-hqX" eventType="touchUpInside" id="sST-c5-Zap"/>
+                                </connections>
+                            </button>
+                        </subviews>
+                        <viewLayoutGuide key="safeArea" id="vDu-zF-Fre"/>
+                        <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                        <constraints>
+                            <constraint firstItem="vDu-zF-Fre" firstAttribute="trailing" secondItem="I7I-Ed-32n" secondAttribute="trailing" constant="15" id="3ke-4j-aut"/>
+                            <constraint firstItem="12u-cu-XGu" firstAttribute="centerY" secondItem="8xv-8Y-A50" secondAttribute="centerY" id="3ns-C4-1Xk"/>
+                            <constraint firstItem="FU6-q8-9Iw" firstAttribute="centerY" secondItem="Zr2-rF-YUt" secondAttribute="centerY" id="4Ul-fS-EGZ"/>
+                            <constraint firstItem="I9w-cx-QlY" firstAttribute="centerY" secondItem="C2a-jB-FVB" secondAttribute="centerY" id="Adb-Hg-6jK"/>
+                            <constraint firstItem="vDu-zF-Fre" firstAttribute="trailing" secondItem="8DH-gC-coa" secondAttribute="trailing" id="EXV-D4-maX"/>
+                            <constraint firstItem="I7I-Ed-32n" firstAttribute="centerY" secondItem="LwF-26-oss" secondAttribute="centerY" id="HyJ-T6-sBi"/>
+                            <constraint firstItem="vDu-zF-Fre" firstAttribute="trailing" secondItem="iB2-gu-3IO" secondAttribute="trailing" constant="15" id="Ig3-DV-ieD"/>
+                            <constraint firstItem="Arw-up-GyD" firstAttribute="centerY" secondItem="FU6-q8-9Iw" secondAttribute="centerY" id="K5S-f9-g07"/>
+                            <constraint firstItem="C2a-jB-FVB" firstAttribute="leading" secondItem="vDu-zF-Fre" secondAttribute="leading" constant="15" id="Lpi-y7-FCx"/>
+                            <constraint firstItem="OlH-Ak-sRA" firstAttribute="centerX" secondItem="vDu-zF-Fre" secondAttribute="centerX" id="T5p-2s-e2t"/>
+                            <constraint firstItem="I9w-cx-QlY" firstAttribute="centerX" secondItem="vDu-zF-Fre" secondAttribute="centerX" id="Vzf-ZI-4Y7"/>
+                            <constraint firstItem="JUR-Vj-yBU" firstAttribute="leading" secondItem="vDu-zF-Fre" secondAttribute="leading" constant="15" id="ZH3-0g-M1x"/>
+                            <constraint firstItem="8DH-gC-coa" firstAttribute="leading" secondItem="vDu-zF-Fre" secondAttribute="leading" id="bAT-qS-Fr3"/>
+                            <constraint firstItem="C2a-jB-FVB" firstAttribute="top" secondItem="dOn-SY-wnl" secondAttribute="bottom" constant="15" id="byc-af-adU"/>
+                            <constraint firstItem="vDu-zF-Fre" firstAttribute="trailing" secondItem="Arw-up-GyD" secondAttribute="trailing" constant="15" id="cyP-IZ-1wa"/>
+                            <constraint firstItem="vDu-zF-Fre" firstAttribute="trailing" secondItem="tt7-sI-TfL" secondAttribute="trailing" constant="15" id="dOh-Zh-y8X"/>
+                            <constraint firstItem="8DH-gC-coa" firstAttribute="centerY" secondItem="kwJ-4R-6nM" secondAttribute="centerY" id="dkq-zI-tcR"/>
+                            <constraint firstItem="LwF-26-oss" firstAttribute="centerX" secondItem="vDu-zF-Fre" secondAttribute="centerX" id="eli-R1-TLW"/>
+                            <constraint firstItem="dOn-SY-wnl" firstAttribute="leading" secondItem="vDu-zF-Fre" secondAttribute="leading" constant="15" id="gfe-aq-7nk"/>
+                            <constraint firstItem="kwJ-4R-6nM" firstAttribute="top" secondItem="vDu-zF-Fre" secondAttribute="top" constant="10" id="hAt-TC-6LC"/>
+                            <constraint firstItem="Zr2-rF-YUt" firstAttribute="top" secondItem="JUR-Vj-yBU" secondAttribute="bottom" constant="15" id="kLH-Zl-k0m"/>
+                            <constraint firstItem="tt7-sI-TfL" firstAttribute="centerY" secondItem="OlH-Ak-sRA" secondAttribute="centerY" id="ksI-o1-K8w"/>
+                            <constraint firstItem="LwF-26-oss" firstAttribute="centerY" secondItem="dOn-SY-wnl" secondAttribute="centerY" id="l31-GR-n7H"/>
+                            <constraint firstItem="iB2-gu-3IO" firstAttribute="centerY" secondItem="12u-cu-XGu" secondAttribute="centerY" id="les-by-5nK"/>
+                            <constraint firstItem="12u-cu-XGu" firstAttribute="centerX" secondItem="vDu-zF-Fre" secondAttribute="centerX" id="qE7-PC-xCD"/>
+                            <constraint firstItem="Uaq-hC-U4a" firstAttribute="centerY" secondItem="I9w-cx-QlY" secondAttribute="centerY" id="qqP-HH-Ver"/>
+                            <constraint firstItem="kwJ-4R-6nM" firstAttribute="leading" secondItem="vDu-zF-Fre" secondAttribute="leading" constant="10" id="rHM-tU-erV"/>
+                            <constraint firstItem="OlH-Ak-sRA" firstAttribute="centerY" secondItem="JUR-Vj-yBU" secondAttribute="centerY" id="rwN-7j-Caf"/>
+                            <constraint firstItem="8xv-8Y-A50" firstAttribute="top" secondItem="I9w-cx-QlY" secondAttribute="bottom" constant="15" id="tNf-9p-HHV"/>
+                            <constraint firstItem="vDu-zF-Fre" firstAttribute="trailing" secondItem="Uaq-hC-U4a" secondAttribute="trailing" constant="15" id="tzG-Kx-can"/>
+                            <constraint firstItem="dOn-SY-wnl" firstAttribute="top" secondItem="Zr2-rF-YUt" secondAttribute="bottom" constant="15" id="uIJ-Xj-Oe3"/>
+                            <constraint firstItem="FU6-q8-9Iw" firstAttribute="centerX" secondItem="vDu-zF-Fre" secondAttribute="centerX" id="vzc-ef-AhZ"/>
+                            <constraint firstItem="Zr2-rF-YUt" firstAttribute="leading" secondItem="vDu-zF-Fre" secondAttribute="leading" constant="15" id="zBA-2T-5J8"/>
+                            <constraint firstItem="8xv-8Y-A50" firstAttribute="leading" secondItem="vDu-zF-Fre" secondAttribute="leading" constant="15" id="zJA-Gg-OZr"/>
+                            <constraint firstItem="JUR-Vj-yBU" firstAttribute="top" secondItem="vDu-zF-Fre" secondAttribute="top" constant="45" id="zbI-KC-mx0"/>
+                        </constraints>
+                    </view>
+                    <size key="freeformSize" width="200" height="320"/>
+                    <connections>
+                        <outlet property="blueButton" destination="C2a-jB-FVB" id="lIY-Ag-Nkv"/>
+                        <outlet property="brownButton" destination="I9w-cx-QlY" id="b8T-np-0mw"/>
+                        <outlet property="closeButton" destination="kwJ-4R-6nM" id="woU-Kz-IXU"/>
+                        <outlet property="customButton" destination="iB2-gu-3IO" id="Vtm-VH-D0l"/>
+                        <outlet property="cyanButton" destination="OlH-Ak-sRA" id="26d-bc-OiU"/>
+                        <outlet property="defaultButton" destination="Uaq-hC-U4a" id="t6X-aV-hPF"/>
+                        <outlet property="grayButton" destination="LwF-26-oss" id="lzV-jY-LNd"/>
+                        <outlet property="greenButton" destination="8xv-8Y-A50" id="teG-ST-UCN"/>
+                        <outlet property="orangeButton" destination="JUR-Vj-yBU" id="aGO-8f-0Em"/>
+                        <outlet property="purpleButton" destination="dOn-SY-wnl" id="hes-XJ-gMJ"/>
+                        <outlet property="redButton" destination="Zr2-rF-YUt" id="jib-wX-2Of"/>
+                        <outlet property="systemBlueButton" destination="12u-cu-XGu" id="aOV-BK-urz"/>
+                        <outlet property="systemIndigoButton" destination="tt7-sI-TfL" id="tUi-Th-IDf"/>
+                        <outlet property="systemMintButton" destination="Arw-up-GyD" id="gqG-tN-WiJ"/>
+                        <outlet property="systemPinkButton" destination="I7I-Ed-32n" id="YzP-1f-nkJ"/>
+                        <outlet property="titleLabel" destination="8DH-gC-coa" id="k2U-jx-f6R"/>
+                        <outlet property="yellowButton" destination="FU6-q8-9Iw" id="oAa-NT-Qhd"/>
+                    </connections>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="Ief-a0-LHa" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="-233.4375" y="50.70422535211268"/>
+        </scene>
+    </scenes>
+    <resources>
+        <image name="xmark" width="24" height="24"/>
+        <systemColor name="labelColor">
+            <color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+        </systemColor>
+        <systemColor name="systemBackgroundColor">
+            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+        </systemColor>
+    </resources>
+</document>

+ 215 - 0
iOSClient/Color/NCColorPicker.swift

@@ -0,0 +1,215 @@
+//
+//  NCColorPicker.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 24/07/22.
+//  Copyright © 2022 Marino Faggiana. All rights reserved.
+//
+
+import Foundation
+import UIKit
+
+class NCColorPicker: UIViewController {
+
+    @IBOutlet weak var closeButton: UIButton!
+    @IBOutlet weak var titleLabel: UILabel!
+
+    @IBOutlet weak var orangeButton: UIButton!
+    @IBOutlet weak var redButton: UIButton!
+    @IBOutlet weak var purpleButton: UIButton!
+    @IBOutlet weak var blueButton: UIButton!
+    @IBOutlet weak var greenButton: UIButton!
+    @IBOutlet weak var cyanButton: UIButton!
+    @IBOutlet weak var yellowButton: UIButton!
+    @IBOutlet weak var grayButton: UIButton!
+    @IBOutlet weak var brownButton: UIButton!
+
+    @IBOutlet weak var systemBlueButton: UIButton!
+    @IBOutlet weak var systemIndigoButton: UIButton!
+    @IBOutlet weak var systemMintButton: UIButton!
+    @IBOutlet weak var systemPinkButton: UIButton!
+
+    @IBOutlet weak var defaultButton: UIButton!
+    @IBOutlet weak var customButton: UIButton!
+
+    var metadata: tableMetadata?
+    var tapAction: UITapGestureRecognizer?
+    var selectedColor: UIColor?
+
+    // MARK: - View Life Cycle
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+
+        view.backgroundColor = .secondarySystemBackground
+
+        if let metadata = metadata {
+            let serverUrl = metadata.serverUrl + "/" + metadata.fileName
+            if let tableDirectory = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", metadata.account, serverUrl)), let hex = tableDirectory.colorFolder, let color = UIColor(hex: hex) {
+                selectedColor = color
+            }
+        }
+
+        closeButton.setImage(NCUtility.shared.loadImage(named: "xmark", color: .label), for: .normal)
+        titleLabel.text = NSLocalizedString("_select_color_", comment: "")
+
+        orangeButton.backgroundColor = .orange
+        orangeButton.layer.cornerRadius = 5
+        orangeButton.layer.masksToBounds = true
+
+        redButton.backgroundColor = .red
+        redButton.layer.cornerRadius = 5
+        redButton.layer.masksToBounds = true
+
+        purpleButton.backgroundColor = .purple
+        purpleButton.layer.cornerRadius = 5
+        purpleButton.layer.masksToBounds = true
+
+        blueButton.backgroundColor = .blue
+        blueButton.layer.cornerRadius = 5
+        blueButton.layer.masksToBounds = true
+
+        brownButton.backgroundColor = .brown
+        brownButton.layer.cornerRadius = 5
+        brownButton.layer.masksToBounds = true
+
+        greenButton.backgroundColor = .green
+        greenButton.layer.cornerRadius = 5
+        greenButton.layer.masksToBounds = true
+
+        grayButton.backgroundColor = .gray
+        grayButton.layer.cornerRadius = 5
+        grayButton.layer.masksToBounds = true
+
+        cyanButton.backgroundColor = .cyan
+        cyanButton.layer.cornerRadius = 5
+        cyanButton.layer.masksToBounds = true
+
+        yellowButton.backgroundColor = .yellow
+        yellowButton.layer.cornerRadius = 5
+        yellowButton.layer.masksToBounds = true
+
+        systemBlueButton.backgroundColor = .systemBlue
+        systemBlueButton.layer.cornerRadius = 5
+        systemBlueButton.layer.masksToBounds = true
+
+        systemMintButton.backgroundColor = NCBrandColor.shared.systemMint
+        systemMintButton.layer.cornerRadius = 5
+        systemMintButton.layer.masksToBounds = true
+
+        systemPinkButton.backgroundColor = .systemPink
+        systemPinkButton.layer.cornerRadius = 5
+        systemPinkButton.layer.masksToBounds = true
+
+        customButton.setImage(UIImage(named: "rgb"), for: .normal)
+        if let selectedColor = selectedColor {
+            customButton.backgroundColor = selectedColor
+        } else {
+            customButton.backgroundColor = .secondarySystemBackground
+        }
+        customButton.layer.cornerRadius = 5
+        customButton.layer.masksToBounds = true
+
+        systemIndigoButton.backgroundColor = .systemIndigo
+        systemIndigoButton.layer.cornerRadius = 5
+        systemIndigoButton.layer.masksToBounds = true
+
+        defaultButton.backgroundColor = NCBrandColor.shared.brandElement
+        defaultButton.layer.cornerRadius = 5
+        defaultButton.layer.masksToBounds = true
+    }
+
+    // MARK: - Action
+
+    @IBAction func closeAction(_ sender: UIButton) {
+        dismiss(animated: true)
+    }
+
+    @IBAction func orangeButtonAction(_ sender: AnyObject) {
+        updateColor(hexColor: UIColor.orange.hexString)
+    }
+
+    @IBAction func redButtonAction(_ sender: AnyObject) {
+        updateColor(hexColor: UIColor.red.hexString)
+    }
+
+    @IBAction func purpleButtonAction(_ sender: AnyObject) {
+        updateColor(hexColor: UIColor.purple.hexString)
+    }
+
+    @IBAction func blueButtonAction(_ sender: AnyObject) {
+        updateColor(hexColor: UIColor.blue.hexString)
+    }
+
+    @IBAction func greenButtonAction(_ sender: AnyObject) {
+        updateColor(hexColor: UIColor.green.hexString)
+    }
+
+    @IBAction func cyanButtonAction(_ sender: AnyObject) {
+        updateColor(hexColor: UIColor.cyan.hexString)
+    }
+
+    @IBAction func yellowButtonAction(_ sender: AnyObject) {
+        updateColor(hexColor: UIColor.yellow.hexString)
+    }
+
+    @IBAction func grayButtonAction(_ sender: AnyObject) {
+        updateColor(hexColor: UIColor.gray.hexString)
+    }
+
+    @IBAction func brownButtonAction(_ sender: AnyObject) {
+        updateColor(hexColor: UIColor.brown.hexString)
+    }
+
+    @IBAction func systemBlueButtonAction(_ sender: AnyObject) {
+        updateColor(hexColor: UIColor.systemBlue.hexString)
+    }
+
+    @IBAction func systemIndigoButtonAction(_ sender: AnyObject) {
+        updateColor(hexColor: UIColor.systemIndigo.hexString)
+    }
+
+    @IBAction func systemMintButtonAction(_ sender: AnyObject) {
+        updateColor(hexColor: NCBrandColor.shared.systemMint.hexString)
+    }
+
+    @IBAction func systemPinkButtonAction(_ sender: AnyObject) {
+        updateColor(hexColor: UIColor.systemPink.hexString)
+    }
+
+    @IBAction func defaultButtonAction(_ sender: AnyObject) {
+        updateColor(hexColor: nil)
+    }
+
+    @IBAction func customButtonAction(_ sender: AnyObject) {
+
+        let picker = UIColorPickerViewController()
+        picker.delegate = self
+        picker.supportsAlpha = false
+        if let selectedColor = selectedColor {
+            picker.selectedColor = selectedColor
+        }
+        self.present(picker, animated: true, completion: nil)
+    }
+
+    // MARK: -
+
+    func updateColor(hexColor: String?) {
+        if let metadata = metadata {
+            let serverUrl = metadata.serverUrl + "/" + metadata.fileName
+            if NCManageDatabase.shared.setDirectory(serverUrl: serverUrl, colorFolder: hexColor, account: metadata.account) != nil {
+                self.dismiss(animated: true)
+                NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource, userInfo: ["serverUrl": metadata.serverUrl])
+            }
+        }
+        self.dismiss(animated: true)
+    }
+}
+
+extension NCColorPicker: UIColorPickerViewControllerDelegate {
+
+    func colorPickerViewControllerDidFinish(_ viewController: UIColorPickerViewController) {
+        let hexColor = viewController.selectedColor.hexString
+        updateColor(hexColor: hexColor)
+    }
+}

+ 566 - 0
iOSClient/Data/NCDataSource.swift

@@ -0,0 +1,566 @@
+//
+//  NCDataSource.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 06/09/2020.
+//  Copyright © 2020 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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
+import NextcloudKit
+
+class NCDataSource: NSObject {
+
+    var metadatas: [tableMetadata] = []
+    var metadatasForSection: [NCMetadataForSection] = []
+
+    var directory: tableDirectory?
+    var groupByField: String = ""
+
+    private var sectionsValue: [String] = []
+    private var providers: [NKSearchProvider]?
+    private var searchResults: [NKSearchResult]?
+    private var localFiles: [tableLocalFile] = []
+
+    private var ascending: Bool = true
+    private var sort: String = ""
+    private var directoryOnTop: Bool = true
+    private var favoriteOnTop: Bool = true
+    private var filterLivePhoto: Bool = true
+
+    override init() {
+        super.init()
+    }
+
+    init(metadatas: [tableMetadata], account: String, directory: tableDirectory? = nil, sort: String? = "none", ascending: Bool? = false, directoryOnTop: Bool? = true, favoriteOnTop: Bool? = true, filterLivePhoto: Bool? = true, groupByField: String = "name", providers: [NKSearchProvider]? = nil, searchResults: [NKSearchResult]? = nil) {
+        super.init()
+
+        self.metadatas = metadatas.filter({
+            !NCGlobal.shared.includeHiddenFiles.contains($0.fileNameView)
+        })
+        self.directory = directory
+        self.localFiles = NCManageDatabase.shared.getTableLocalFile(account: account)
+        self.sort = sort ?? "none"
+        self.ascending = ascending ?? false
+        self.directoryOnTop = directoryOnTop ?? true
+        self.favoriteOnTop = favoriteOnTop ?? true
+        self.filterLivePhoto = filterLivePhoto ?? true
+        self.groupByField = groupByField
+        // unified search
+        self.providers = providers
+        self.searchResults = searchResults
+
+        createSections()
+    }
+
+    // MARK: -
+
+    func clearDataSource() {
+
+        self.metadatas.removeAll()
+        self.metadatasForSection.removeAll()
+        self.directory = nil
+        self.sectionsValue.removeAll()
+        self.providers = nil
+        self.searchResults = nil
+        self.localFiles.removeAll()
+    }
+
+    func clearDirectory() {
+
+        self.directory = nil
+    }
+
+    func changeGroupByField(_ groupByField: String) {
+
+        self.groupByField = groupByField
+        print("DATASOURCE: set group by filed " + groupByField)
+        self.metadatasForSection.removeAll()
+        self.sectionsValue.removeAll()
+        print("DATASOURCE: remove  all sections")
+
+        createSections()
+    }
+
+    func addSection(metadatas: [tableMetadata], searchResult: NKSearchResult?) {
+
+        self.metadatas.append(contentsOf: metadatas)
+
+        if let searchResult = searchResult {
+            self.searchResults?.append(searchResult)
+        }
+
+        createSections()
+    }
+
+    internal func createSections() {
+
+        // get all Section
+        for metadata in self.metadatas {
+            // skipped livePhoto
+            if filterLivePhoto && metadata.livePhoto && (metadata.fileNameView as NSString).pathExtension.lowercased() == "mov" {
+                continue
+            }
+            let section = NSLocalizedString(self.getSectionValue(metadata: metadata), comment: "")
+            if !self.sectionsValue.contains(section) {
+                self.sectionsValue.append(section)
+            }
+        }
+
+        // Unified search
+        if let providers = self.providers, !providers.isEmpty {
+            let sectionsDictionary = ThreadSafeDictionary<String,Int>()
+            for section in self.sectionsValue {
+                if let provider = providers.filter({ $0.id == section}).first {
+                    sectionsDictionary[section] = provider.order
+                }
+            }
+            self.sectionsValue.removeAll()
+            let sectionsDictionarySorted = sectionsDictionary.sorted(by: { $0.value < $1.value } )
+            for section in sectionsDictionarySorted {
+                if section.key == NCGlobal.shared.appName {
+                    self.sectionsValue.insert(section.key, at: 0)
+                } else {
+                    self.sectionsValue.append(section.key)
+                }
+            }
+
+        } else {
+
+        // normal
+            let directory = NSLocalizedString("directory", comment: "").lowercased().firstUppercased
+            self.sectionsValue = self.sectionsValue.sorted {
+                if directoryOnTop && $0 == directory {
+                    return true
+                } else if directoryOnTop && $1 == directory {
+                    return false
+                }
+                if self.ascending {
+                    return $0 < $1
+                } else {
+                    return $0 > $1
+                }
+            }
+        }
+
+        for sectionValue in self.sectionsValue {
+            if !existsMetadataForSection(sectionValue: sectionValue) {
+                print("DATASOURCE: create metadata for section: " + sectionValue)
+                createMetadataForSection(sectionValue: sectionValue)
+            }
+        }
+    }
+
+    internal func createMetadataForSection(sectionValue: String) {
+
+        var searchResult: NKSearchResult?
+        if let providers = self.providers, !providers.isEmpty, let searchResults = self.searchResults {
+            searchResult = searchResults.filter({ $0.id == sectionValue}).first
+        }
+        let metadatas = self.metadatas.filter({ getSectionValue(metadata: $0) == sectionValue})
+        let metadataForSection = NCMetadataForSection.init(sectionValue: sectionValue,
+                                                            metadatas: metadatas,
+                                                            localFiles: self.localFiles,
+                                                            lastSearchResult: searchResult,
+                                                            sort: self.sort,
+                                                            ascending: self.ascending,
+                                                            directoryOnTop: self.directoryOnTop,
+                                                            favoriteOnTop: self.favoriteOnTop,
+                                                            filterLivePhoto: self.filterLivePhoto)
+        metadatasForSection.append(metadataForSection)
+    }
+
+    func getMetadataSourceForAllSections() -> [tableMetadata] {
+
+        var metadatas: [tableMetadata] = []
+
+        for section in metadatasForSection {
+            metadatas.append(contentsOf: section.metadatas)
+        }
+
+        return metadatas
+    }
+
+    // MARK: -
+
+    @discardableResult
+    func appendMetadatasToSection(_ metadatas: [tableMetadata], metadataForSection: NCMetadataForSection, lastSearchResult: NKSearchResult) -> [IndexPath] {
+        
+        guard let sectionIndex =  getSectionIndex(metadataForSection.sectionValue) else { return [] }
+        var indexPaths: [IndexPath] = []
+
+        self.metadatas.append(contentsOf: metadatas)
+        metadataForSection.metadatas.append(contentsOf: metadatas)
+        metadataForSection.lastSearchResult = lastSearchResult
+        metadataForSection.createMetadatas()
+
+        for metadata in metadatas {
+            if let rowIndex = metadataForSection.metadatas.firstIndex(where: {$0.ocId == metadata.ocId}) {
+                indexPaths.append(IndexPath(row: rowIndex, section: sectionIndex))
+            }
+        }
+
+        return indexPaths
+    }
+
+    @discardableResult
+    func addMetadata(_ metadata: tableMetadata) -> (indexPath: IndexPath?, sameSections: Bool) {
+
+        let numberOfSections = self.numberOfSections()
+        let sectionValue = getSectionValue(metadata: metadata)
+
+        // ADD metadatasSource
+        if let rowIndex = self.metadatas.firstIndex(where: {$0.fileNameView == metadata.fileNameView || $0.ocId == metadata.ocId}) {
+            self.metadatas[rowIndex] = metadata
+        } else {
+            self.metadatas.append(metadata)
+        }
+
+        // ADD metadataForSection
+        if let sectionIndex = getSectionIndex(sectionValue), let metadataForSection = getMetadataForSection(sectionIndex) {
+            if let rowIndex = metadataForSection.metadatas.firstIndex(where: {$0.fileNameView == metadata.fileNameView || $0.ocId == metadata.ocId}) {
+                metadataForSection.metadatas[rowIndex] = metadata
+                return (IndexPath(row: rowIndex, section: sectionIndex), self.isSameNumbersOfSections(numberOfSections: numberOfSections))
+            } else {
+                metadataForSection.metadatas.append(metadata)
+                metadataForSection.createMetadatas()
+                if let rowIndex = metadataForSection.metadatas.firstIndex(where: {$0.ocId == metadata.ocId}) {
+                    return (IndexPath(row: rowIndex, section: sectionIndex), self.isSameNumbersOfSections(numberOfSections: numberOfSections))
+                }
+                return (nil, self.isSameNumbersOfSections(numberOfSections: numberOfSections))
+            }
+        } else {
+            // NEW section
+            createSections()
+            // get IndexPath of new section
+            if let sectionIndex = getSectionIndex(sectionValue), let metadataForSection = getMetadataForSection(sectionIndex) {
+                if let rowIndex = metadataForSection.metadatas.firstIndex(where: {$0.fileNameView == metadata.fileNameView || $0.ocId == metadata.ocId}) {
+                    return (IndexPath(row: rowIndex, section: sectionIndex), self.isSameNumbersOfSections(numberOfSections: numberOfSections))
+                }
+            }
+        }
+
+        return (nil, self.isSameNumbersOfSections(numberOfSections: numberOfSections))
+    }
+
+    func deleteMetadata(ocId: String) -> (indexPath: IndexPath?, sameSections: Bool) {
+
+        let numberOfSections = self.numberOfSections()
+        var indexPathReturn: IndexPath?
+        var sectionValue = ""
+
+        // DELETE metadataForSection (IMPORTANT FIRST)
+        let (indexPath, metadataForSection) = self.getIndexPathMetadata(ocId: ocId)
+        if let indexPath = indexPath, let metadataForSection = metadataForSection, indexPath.row < metadataForSection.metadatas.count {
+            metadataForSection.metadatas.remove(at: indexPath.row)
+            if metadataForSection.metadatas.count == 0 {
+                // REMOVE sectionsValue / metadatasForSection
+                sectionValue = metadataForSection.sectionValue
+                if let sectionIndex = getSectionIndex(sectionValue) {
+                    self.sectionsValue.remove(at: sectionIndex)
+                }
+                if let index = getIndexMetadatasForSection(sectionValue) {
+                    self.metadatasForSection.remove(at: index)
+                }
+            } else {
+                metadataForSection.createMetadatas()
+            }
+            indexPathReturn = indexPath
+        } else { return (nil, false) }
+
+        // DELETE metadatasSource (IMPORTANT LAST)
+        if let rowIndex = self.metadatas.firstIndex(where: {$0.ocId == ocId}) {
+            self.metadatas.remove(at: rowIndex)
+        }
+
+        return (indexPathReturn, self.isSameNumbersOfSections(numberOfSections: numberOfSections))
+    }
+
+    @discardableResult
+    func reloadMetadata(ocId: String, ocIdTemp: String? = nil) -> (indexPath: IndexPath?, sameSections: Bool) {
+
+        let numberOfSections = self.numberOfSections()
+        var ocIdSearch = ocId
+
+        guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) else { return (nil, self.isSameNumbersOfSections(numberOfSections: numberOfSections)) }
+
+        if let ocIdTemp = ocIdTemp {
+            ocIdSearch = ocIdTemp
+        }
+
+        // UPDATE metadataForSection (IMPORTANT FIRST)
+        let (indexPath, metadataForSection) = self.getIndexPathMetadata(ocId: ocIdSearch)
+        if let indexPath = indexPath, let metadataForSection = metadataForSection {
+            metadataForSection.metadatas[indexPath.row] = metadata
+            metadataForSection.createMetadatas()
+        }
+
+        // UPDATE metadatasSource (IMPORTANT LAST)
+        if let rowIndex = self.metadatas.firstIndex(where: {$0.ocId == ocIdSearch}) {
+            self.metadatas[rowIndex] = metadata
+        }
+
+        let result = self.getIndexPathMetadata(ocId: ocId)
+        return (result.indexPath, self.isSameNumbersOfSections(numberOfSections: numberOfSections))
+    }
+
+    // MARK: -
+
+    func getIndexPathMetadata(ocId: String) -> (indexPath: IndexPath?, metadataForSection: NCMetadataForSection?) {
+        guard let metadata = self.metadatas.filter({ $0.ocId == ocId}).first else { return (nil, nil) }
+        let sectionValue = getSectionValue(metadata: metadata)
+        guard let sectionIndex = getSectionIndex(sectionValue), let metadataForSection = getMetadataForSection(sectionValue), let rowIndex = metadataForSection.metadatas.firstIndex(where: {$0.ocId == ocId}) else { return (nil, nil) }
+        return (IndexPath(row: rowIndex, section: sectionIndex), metadataForSection)
+    }
+
+    func isSameNumbersOfSections(numberOfSections: Int) -> Bool {
+        guard self.metadatasForSection.count > 0 else { return false }
+        return numberOfSections == self.numberOfSections()
+    }
+
+    func numberOfSections() -> Int {
+        guard self.sectionsValue.count > 0 else { return 1 }
+        return self.sectionsValue.count
+    }
+    
+    func numberOfItemsInSection(_ section: Int) -> Int {
+        guard self.sectionsValue.count > 0 && self.metadatas.count > 0, let metadataForSection = getMetadataForSection(section) else { return 0}
+        return metadataForSection.metadatas.count
+    }
+
+    func cellForItemAt(indexPath: IndexPath) -> tableMetadata? {
+        guard metadatasForSection.count > 0 && indexPath.section < metadatasForSection.count, let metadataForSection = getMetadataForSection(indexPath.section), indexPath.row < metadataForSection.metadatas.count else { return nil }
+        return metadataForSection.metadatas[indexPath.row]
+    }
+
+    func getSectionValue(indexPath: IndexPath) -> String {
+        guard metadatasForSection.count > 0 , let metadataForSection = self.getMetadataForSection(indexPath.section) else { return ""}
+        return metadataForSection.sectionValue
+    }
+
+    func getSectionValueLocalization(indexPath: IndexPath) -> String {
+        guard metadatasForSection.count > 0 , let metadataForSection = self.getMetadataForSection(indexPath.section) else { return ""}
+        if let searchResults = self.searchResults, let searchResult = searchResults.filter({ $0.id == metadataForSection.sectionValue}).first {
+            return searchResult.name
+        }
+        return metadataForSection.sectionValue
+    }
+
+    func getFooterInformationAllMetadatas() -> (directories: Int, files: Int, size: Int64) {
+
+        var directories: Int = 0
+        var files: Int = 0
+        var size: Int64 = 0
+
+        for metadataForSection in metadatasForSection {
+            directories += metadataForSection.numDirectory
+            files += metadataForSection.numFile
+            size += metadataForSection.totalSize
+        }
+
+        return (directories, files, size)
+    }
+
+    // MARK: -
+
+    internal func getSectionValue(metadata: tableMetadata) -> String {
+
+        switch self.groupByField {
+        case "name":
+            return NSLocalizedString(metadata.name, comment: "")
+        case "classFile":
+            return NSLocalizedString(metadata.classFile, comment: "").lowercased().firstUppercased
+        default:
+            return NSLocalizedString(metadata.classFile, comment: "")
+        }
+    }
+
+    internal func getIndexMetadatasForSection(_ sectionValue: String) -> Int? {
+        return self.metadatasForSection.firstIndex(where: {$0.sectionValue == sectionValue })
+    }
+
+    internal func getSectionIndex(_ sectionValue: String) -> Int? {
+         return self.sectionsValue.firstIndex(where: {$0 == sectionValue })
+    }
+
+    internal func existsMetadataForSection(sectionValue: String) -> Bool {
+        return !self.metadatasForSection.filter({ $0.sectionValue == sectionValue }).isEmpty
+    }
+
+    internal func getMetadataForSection(_ section: Int) -> NCMetadataForSection? {
+        guard section < sectionsValue.count, let metadataForSection = self.metadatasForSection.filter({ $0.sectionValue == sectionsValue[section]}).first else { return nil }
+        return metadataForSection
+    }
+
+    internal func getMetadataForSection(_ sectionValue: String) -> NCMetadataForSection? {
+        guard let metadataForSection = self.metadatasForSection.filter({ $0.sectionValue == sectionValue }).first else { return nil }
+        return metadataForSection
+    }
+}
+
+// MARK: -
+
+class NCMetadataForSection: NSObject {
+
+    var sectionValue: String
+    var metadatas: [tableMetadata]
+    var localFiles: [tableLocalFile]
+    var lastSearchResult: NKSearchResult?
+    var unifiedSearchInProgress: Bool = false
+
+    private var sort : String
+    private var ascending: Bool
+    private var directoryOnTop: Bool
+    private var favoriteOnTop: Bool
+    private var filterLivePhoto: Bool
+
+    private var metadatasSorted: [tableMetadata] = []
+    private var metadatasFavoriteDirectory: [tableMetadata] = []
+    private var metadatasFavoriteFile: [tableMetadata] = []
+    private var metadatasDirectory: [tableMetadata] = []
+    private var metadatasFile: [tableMetadata] = []
+
+    public var numDirectory: Int = 0
+    public var numFile: Int = 0
+    public var totalSize: Int64 = 0
+    public var metadataOffLine: [String] = []
+    public var directories: [tableDirectory]?
+
+    init(sectionValue: String, metadatas: [tableMetadata], localFiles: [tableLocalFile], lastSearchResult: NKSearchResult?, sort: String, ascending: Bool, directoryOnTop: Bool, favoriteOnTop: Bool, filterLivePhoto: Bool) {
+
+        self.sectionValue = sectionValue
+        self.metadatas = metadatas
+        self.localFiles = localFiles
+        self.lastSearchResult = lastSearchResult
+        self.sort = sort
+        self.ascending = ascending
+        self.directoryOnTop = directoryOnTop
+        self.favoriteOnTop = favoriteOnTop
+        self.filterLivePhoto = filterLivePhoto
+
+        super.init()
+
+        createMetadatas()
+    }
+
+    func createMetadatas() {
+
+        // Clear
+        //
+        metadatasSorted.removeAll()
+        metadatasFavoriteDirectory.removeAll()
+        metadatasFavoriteFile.removeAll()
+        metadatasDirectory.removeAll()
+        metadatasFile.removeAll()
+        metadataOffLine.removeAll()
+
+        numDirectory = 0
+        numFile = 0
+        totalSize = 0
+
+        var ocIds: [String] = []
+        let metadataInSession = metadatas.filter({ !$0.session.isEmpty })
+
+        // Metadata order
+        //
+        if sort != "none" && sort != "" {
+            metadatasSorted = metadatas.sorted {
+
+                switch sort {
+                case "date":
+                    if ascending {
+                        return ($0.date as Date) < ($1.date as Date)
+                    } else {
+                        return ($0.date as Date) > ($1.date as Date)
+                    }
+                case "size":
+                    if ascending {
+                        return $0.size < $1.size
+                    } else {
+                        return $0.size > $1.size
+                    }
+                default:
+                    if ascending {
+                        return $0.fileNameView.lowercased() < $1.fileNameView.lowercased()
+                    } else {
+                        return $0.fileNameView.lowercased() > $1.fileNameView.lowercased()
+                    }
+                }
+            }
+        } else {
+            metadatasSorted = metadatas
+        }
+
+        // Initialize datasource
+        //
+        for metadata in metadatasSorted {
+
+            // skipped the root file
+            if metadata.fileName == "." || metadata.serverUrl == ".." {
+                continue
+            }
+
+            // skipped livePhoto
+            if filterLivePhoto && metadata.livePhoto && (metadata.fileNameView as NSString).pathExtension.lowercased() == "mov" {
+                continue
+            }
+
+            // Upload [REPLACE] skip
+            if metadata.session.isEmpty && !metadataInSession.filter({ $0.fileNameView == metadata.fileNameView }).isEmpty {
+                continue
+            }
+
+            // Upload [REPLACE] skip
+            if metadata.session.isEmpty && !metadataInSession.filter({ $0.fileNameView == metadata.fileNameView }).isEmpty {
+                continue
+            }
+
+            // Organized the metadata
+            if metadata.favorite && favoriteOnTop {
+                if metadata.directory {
+                    metadatasFavoriteDirectory.append(metadata)
+                } else {
+                    metadatasFavoriteFile.append(metadata)
+                }
+            } else if  metadata.directory && directoryOnTop {
+                metadatasDirectory.append(metadata)
+            } else {
+                metadatasFile.append(metadata)
+            }
+
+            //Info
+            if metadata.directory {
+                ocIds.append(metadata.ocId)
+                numDirectory += 1
+            } else {
+                numFile += 1
+                totalSize += metadata.size
+            }
+        }
+
+        directories = NCManageDatabase.shared.getTablesDirectory(predicate: NSPredicate(format: "ocId IN %@", ocIds), sorted: "serverUrl", ascending: true)
+
+        metadatas.removeAll()
+
+        // Struct view : favorite dir -> favorite file -> directory -> files
+        metadatas += metadatasFavoriteDirectory
+        metadatas += metadatasFavoriteFile
+        metadatas += metadatasDirectory
+        metadatas += metadatasFile
+    }
+}

+ 204 - 0
iOSClient/Data/NCDatabase.swift

@@ -0,0 +1,204 @@
+//
+//  NCDatabase.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 06/05/17.
+//  Copyright © 2017 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//  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
+import RealmSwift
+import NextcloudKit
+
+protocol DateCompareable {
+    var dateKey: Date { get }
+}
+
+class tableCapabilities: Object {
+
+    @objc dynamic var account = ""
+    @objc dynamic var jsondata: Data?
+
+    override static func primaryKey() -> String {
+        return "account"
+    }
+}
+
+class tableChunk: Object {
+
+    @objc dynamic var account = ""
+    @objc dynamic var chunkFolder = ""
+    @objc dynamic var fileName = ""
+    @objc dynamic var index = ""
+    @objc dynamic var ocId = ""
+    @objc dynamic var size: Int64 = 0
+
+    override static func primaryKey() -> String {
+        return "index"
+    }
+}
+
+class tableComments: Object, DateCompareable {
+    var dateKey: Date { creationDateTime as Date }
+
+    @objc dynamic var account = ""
+    @objc dynamic var actorDisplayName = ""
+    @objc dynamic var actorId = ""
+    @objc dynamic var actorType = ""
+    @objc dynamic var creationDateTime = NSDate()
+    @objc dynamic var isUnread: Bool = false
+    @objc dynamic var message = ""
+    @objc dynamic var messageId = ""
+    @objc dynamic var objectId = ""
+    @objc dynamic var objectType = ""
+    @objc dynamic var path = ""
+    @objc dynamic var verb = ""
+
+    override static func primaryKey() -> String {
+        return "messageId"
+    }
+}
+
+class tableDirectEditingCreators: Object {
+
+    @objc dynamic var account = ""
+    @objc dynamic var editor = ""
+    @objc dynamic var ext = ""
+    @objc dynamic var identifier = ""
+    @objc dynamic var mimetype = ""
+    @objc dynamic var name = ""
+    @objc dynamic var templates: Int = 0
+}
+
+class tableDirectEditingEditors: Object {
+
+    @objc dynamic var account = ""
+    @objc dynamic var editor = ""
+    let mimetypes = List<String>()
+    @objc dynamic var name = ""
+    let optionalMimetypes = List<String>()
+    @objc dynamic var secure: Int = 0
+}
+
+class tableExternalSites: Object {
+
+    @objc dynamic var account = ""
+    @objc dynamic var icon = ""
+    @objc dynamic var idExternalSite: Int = 0
+    @objc dynamic var lang = ""
+    @objc dynamic var name = ""
+    @objc dynamic var type = ""
+    @objc dynamic var url = ""
+}
+
+class tableGPS: Object {
+
+    @objc dynamic var latitude = ""
+    @objc dynamic var location = ""
+    @objc dynamic var longitude = ""
+    @objc dynamic var placemarkAdministrativeArea = ""
+    @objc dynamic var placemarkCountry = ""
+    @objc dynamic var placemarkLocality = ""
+    @objc dynamic var placemarkPostalCode = ""
+    @objc dynamic var placemarkThoroughfare = ""
+}
+
+class tableLocalFile: Object {
+
+    @objc dynamic var account = ""
+    @objc dynamic var etag = ""
+    @objc dynamic var exifDate: NSDate?
+    @objc dynamic var exifLatitude = ""
+    @objc dynamic var exifLongitude = ""
+    @objc dynamic var exifLensModel: String?
+    @objc dynamic var favorite: Bool = false
+    @objc dynamic var fileName = ""
+    @objc dynamic var ocId = ""
+    @objc dynamic var offline: Bool = false
+
+    override static func primaryKey() -> String {
+        return "ocId"
+    }
+}
+
+class tablePhotoLibrary: Object {
+
+    @objc dynamic var account = ""
+    @objc dynamic var assetLocalIdentifier = ""
+    @objc dynamic var creationDate: NSDate?
+    @objc dynamic var idAsset = ""
+    @objc dynamic var modificationDate: NSDate?
+    @objc dynamic var mediaType: Int = 0
+
+    override static func primaryKey() -> String {
+        return "idAsset"
+    }
+}
+
+class tableTag: Object {
+
+    @objc dynamic var account = ""
+    @objc dynamic var ocId = ""
+    @objc dynamic var tagIOS: Data?
+
+    override static func primaryKey() -> String {
+        return "ocId"
+    }
+}
+
+class tableTip: Object {
+
+    @Persisted(primaryKey: true) var tipName = ""
+}
+
+class tableTrash: Object {
+
+    @objc dynamic var account = ""
+    @objc dynamic var classFile = ""
+    @objc dynamic var contentType = ""
+    @objc dynamic var date = NSDate()
+    @objc dynamic var directory: Bool = false
+    @objc dynamic var fileId = ""
+    @objc dynamic var fileName = ""
+    @objc dynamic var filePath = ""
+    @objc dynamic var hasPreview: Bool = false
+    @objc dynamic var iconName = ""
+    @objc dynamic var size: Int64 = 0
+    @objc dynamic var trashbinFileName = ""
+    @objc dynamic var trashbinOriginalLocation = ""
+    @objc dynamic var trashbinDeletionTime = NSDate()
+
+    override static func primaryKey() -> String {
+        return "fileId"
+    }
+}
+
+class tableUserStatus: Object {
+
+    @objc dynamic var account = ""
+    @objc dynamic var clearAt: NSDate?
+    @objc dynamic var clearAtTime: String?
+    @objc dynamic var clearAtType: String?
+    @objc dynamic var icon: String?
+    @objc dynamic var id: String?
+    @objc dynamic var message: String?
+    @objc dynamic var predefined: Bool = false
+    @objc dynamic var status: String?
+    @objc dynamic var userId: String?
+}

+ 75 - 0
iOSClient/Data/NCElementsJSON.swift

@@ -0,0 +1,75 @@
+//
+//  NCElementsJSON.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 14/05/2020.
+//  Copyright © 2020 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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
+
+@objc class NCElementsJSON: NSObject {
+    @objc static let shared: NCElementsJSON = {
+        let instance = NCElementsJSON()
+        return instance
+    }()
+
+    @objc public let capabilitiesVersionString: Array = ["ocs", "data", "version", "string"]
+    @objc public let capabilitiesVersionMajor: Array = ["ocs", "data", "version", "major"]
+
+    @objc public let capabilitiesFileSharingApiEnabled: Array = ["ocs", "data", "capabilities", "files_sharing", "api_enabled"]
+    @objc public let capabilitiesFileSharingPubPasswdEnforced: Array = ["ocs", "data", "capabilities", "files_sharing", "public", "password", "enforced"]
+    @objc public let capabilitiesFileSharingPubExpireDateEnforced: Array = ["ocs", "data", "capabilities", "files_sharing", "public", "expire_date", "enforced"]
+    @objc public let capabilitiesFileSharingPubExpireDateDays: Array = ["ocs", "data", "capabilities", "files_sharing", "public", "expire_date", "days"]
+    @objc public let capabilitiesFileSharingInternalExpireDateEnforced: Array = ["ocs", "data", "capabilities", "files_sharing", "public", "expire_date_internal", "enforced"]
+    @objc public let capabilitiesFileSharingInternalExpireDateDays: Array = ["ocs", "data", "capabilities", "files_sharing", "public", "expire_date_internal", "days"]
+    @objc public let capabilitiesFileSharingRemoteExpireDateEnforced: Array = ["ocs", "data", "capabilities", "files_sharing", "public", "expire_date_remote", "enforced"]
+    @objc public let capabilitiesFileSharingRemoteExpireDateDays: Array = ["ocs", "data", "capabilities", "files_sharing", "public", "expire_date_remote", "days"]
+    @objc public let capabilitiesFileSharingDefaultPermissions: Array = ["ocs", "data", "capabilities", "files_sharing", "default_permissions"]
+    @objc public let capabilitiesFileSharingSendPasswordMail: Array = ["ocs", "data", "capabilities", "files_sharing", "sharebymail", "send_password_by_mail"]
+
+    @objc public let capabilitiesThemingColor: Array = ["ocs", "data", "capabilities", "theming", "color"]
+    @objc public let capabilitiesThemingColorElement: Array = ["ocs", "data", "capabilities", "theming", "color-element"]
+    @objc public let capabilitiesThemingColorText: Array = ["ocs", "data", "capabilities", "theming", "color-text"]
+    @objc public let capabilitiesThemingName: Array = ["ocs", "data", "capabilities", "theming", "name"]
+    @objc public let capabilitiesThemingSlogan: Array = ["ocs", "data", "capabilities", "theming", "slogan"]
+
+    @objc public let capabilitiesWebDavRoot: Array = ["ocs", "data", "capabilities", "core", "webdav-root"]
+
+    @objc public let capabilitiesE2EEEnabled: Array = ["ocs", "data", "capabilities", "end-to-end-encryption", "enabled"]
+    @objc public let capabilitiesE2EEApiVersion: Array = ["ocs", "data", "capabilities", "end-to-end-encryption", "api-version"]
+
+    @objc public let capabilitiesExternalSitesExists: Array = ["ocs", "data", "capabilities", "external"]
+
+    @objc public let capabilitiesRichdocumentsMimetypes: Array = ["ocs", "data", "capabilities", "richdocuments", "mimetypes"]
+
+    @objc public let capabilitiesActivity: Array = ["ocs", "data", "capabilities", "activity", "apiv2"]
+
+    @objc public let capabilitiesNotification: Array = ["ocs", "data", "capabilities", "notifications", "ocs-endpoints"]
+
+    @objc public let capabilitiesFilesUndelete: Array = ["ocs", "data", "capabilities", "files", "undelete"]
+    @objc public let capabilitiesFilesLockVersion: Array = ["ocs", "data", "capabilities", "files", "locking"] // NC 24
+    @objc public let capabilitiesFilesComments: Array = ["ocs", "data", "capabilities", "files", "comments"] // NC 20
+
+    @objc public let capabilitiesHWCEnabled: Array = ["ocs", "data", "capabilities", "handwerkcloud", "enabled"]
+
+    @objc public let capabilitiesUserStatusEnabled: Array = ["ocs", "data", "capabilities", "user_status", "enabled"]
+    @objc public let capabilitiesUserStatusSupportsEmoji: Array = ["ocs", "data", "capabilities", "user_status", "supports_emoji"]
+
+    @objc public let capabilitiesGroupfoldersEnabled: Array = ["ocs", "data", "capabilities", "groupfolders", "hasAccessibleGroupFolders"]
+}

+ 483 - 0
iOSClient/Data/NCManageDatabase+Account.swift

@@ -0,0 +1,483 @@
+//
+//  NCManageDatabase+Account.swift
+//  Nextcloud
+//
+//  Created by Henrik Storch on 30.11.21.
+//  Copyright © 2021 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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 Foundation
+import RealmSwift
+import NextcloudKit
+
+class tableAccount: Object, NCUserBaseUrl {
+
+    @objc dynamic var account = ""
+    @objc dynamic var active: Bool = false
+    @objc dynamic var address = ""
+    @objc dynamic var alias = ""
+    @objc dynamic var autoUpload: Bool = false
+    @objc dynamic var autoUploadCreateSubfolder: Bool = false
+    @objc dynamic var autoUploadSubfolderGranularity: Int64 = 1
+    @objc dynamic var autoUploadDirectory = ""
+    @objc dynamic var autoUploadFileName = ""
+    @objc dynamic var autoUploadFull: Bool = false
+    @objc dynamic var autoUploadImage: Bool = false
+    @objc dynamic var autoUploadVideo: Bool = false
+    @objc dynamic var autoUploadWWAnPhoto: Bool = false
+    @objc dynamic var autoUploadWWAnVideo: Bool = false
+    @objc dynamic var backend = ""
+    @objc dynamic var backendCapabilitiesSetDisplayName: Bool = false
+    @objc dynamic var backendCapabilitiesSetPassword: Bool = false
+    @objc dynamic var businessSize: String = ""
+    @objc dynamic var businessType = ""
+    @objc dynamic var city = ""
+    @objc dynamic var country = ""
+    @objc dynamic var displayName = ""
+    @objc dynamic var email = ""
+    @objc dynamic var enabled: Bool = false
+    @objc dynamic var groups = ""
+    @objc dynamic var language = ""
+    @objc dynamic var lastLogin: Int64 = 0
+    @objc dynamic var locale = ""
+    @objc dynamic var mediaPath = ""
+    @objc dynamic var organisation = ""
+    @objc dynamic var password = ""
+    @objc dynamic var phone = ""
+    @objc dynamic var quota: Int64 = 0
+    @objc dynamic var quotaFree: Int64 = 0
+    @objc dynamic var quotaRelative: Double = 0
+    @objc dynamic var quotaTotal: Int64 = 0
+    @objc dynamic var quotaUsed: Int64 = 0
+    @objc dynamic var role = ""
+    @objc dynamic var storageLocation = ""
+    @objc dynamic var subadmin = ""
+    @objc dynamic var twitter = ""
+    @objc dynamic var urlBase = ""
+    @objc dynamic var user = ""
+    @objc dynamic var userId = ""
+    @objc dynamic var userStatusClearAt: NSDate?
+    @objc dynamic var userStatusIcon: String?
+    @objc dynamic var userStatusMessage: String?
+    @objc dynamic var userStatusMessageId: String?
+    @objc dynamic var userStatusMessageIsPredefined: Bool = false
+    @objc dynamic var userStatusStatus: String?
+    @objc dynamic var userStatusStatusIsUserDefined: Bool = false
+    @objc dynamic var website = ""
+    @objc dynamic var zip = ""
+
+    // HC
+    @objc dynamic var hcIsTrial: Bool = false
+    @objc dynamic var hcTrialExpired: Bool = false
+    @objc dynamic var hcTrialRemainingSec: Int64 = 0
+    @objc dynamic var hcTrialEndTime: NSDate?
+    @objc dynamic var hcAccountRemoveExpired: Bool = false
+    @objc dynamic var hcAccountRemoveRemainingSec: Int64 = 0
+    @objc dynamic var hcAccountRemoveTime: NSDate?
+    @objc dynamic var hcNextGroupExpirationGroup = ""
+    @objc dynamic var hcNextGroupExpirationGroupExpired: Bool = false
+    @objc dynamic var hcNextGroupExpirationExpiresTime: NSDate?
+    @objc dynamic var hcNextGroupExpirationExpires = ""
+
+    override static func primaryKey() -> String {
+        return "account"
+    }
+}
+
+extension NCManageDatabase {
+
+    @objc func addAccount(_ account: String, urlBase: String, user: String, password: String) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                let addObject = tableAccount()
+
+                addObject.account = account
+
+                // Brand
+                if NCBrandOptions.shared.use_default_auto_upload {
+
+                    addObject.autoUpload = true
+                    addObject.autoUploadImage = true
+                    addObject.autoUploadVideo = true
+                    addObject.autoUploadWWAnVideo = true
+                }
+
+                CCUtility.setPassword(account, password: password)
+
+                addObject.urlBase = urlBase
+                addObject.user = user
+                addObject.userId = user
+
+                realm.add(addObject, update: .all)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    @objc func updateAccount(_ account: tableAccount) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                realm.add(account, update: .all)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    @objc func deleteAccount(_ account: String) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                let result = realm.objects(tableAccount.self).filter("account == %@", account)
+                realm.delete(result)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    @objc func getActiveAccount() -> tableAccount? {
+
+        let realm = try! Realm()
+
+        guard let result = realm.objects(tableAccount.self).filter("active == true").first else {
+            return nil
+        }
+
+        return tableAccount.init(value: result)
+    }
+
+    @objc func getAccounts() -> [String]? {
+
+        let realm = try! Realm()
+
+        let results = realm.objects(tableAccount.self).sorted(byKeyPath: "account", ascending: true)
+
+        if results.count > 0 {
+            return Array(results.map { $0.account })
+        }
+
+        return nil
+    }
+
+    @objc func getAccount(predicate: NSPredicate) -> tableAccount? {
+
+        let realm = try! Realm()
+
+        guard let result = realm.objects(tableAccount.self).filter(predicate).first else {
+            return nil
+        }
+
+        return tableAccount.init(value: result)
+    }
+
+    @objc func getAllAccount() -> [tableAccount] {
+
+        let realm = try! Realm()
+
+        let sorted = [SortDescriptor(keyPath: "active", ascending: false), SortDescriptor(keyPath: "user", ascending: true)]
+        let results = realm.objects(tableAccount.self).sorted(by: sorted)
+
+        return Array(results.map { tableAccount.init(value: $0) })
+    }
+
+    @objc func getAllAccountOrderAlias() -> [tableAccount] {
+
+        let realm = try! Realm()
+
+        let sorted = [SortDescriptor(keyPath: "active", ascending: false), SortDescriptor(keyPath: "alias", ascending: true), SortDescriptor(keyPath: "user", ascending: true)]
+        let results = realm.objects(tableAccount.self).sorted(by: sorted)
+
+        return Array(results.map { tableAccount.init(value: $0) })
+    }
+
+    @objc func getAccountAutoUploadFileName() -> String {
+
+        let realm = try! Realm()
+
+        guard let result = realm.objects(tableAccount.self).filter("active == true").first else {
+            return ""
+        }
+
+        if result.autoUploadFileName.count > 0 {
+            return result.autoUploadFileName
+        } else {
+            return NCBrandOptions.shared.folderDefaultAutoUpload
+        }
+    }
+
+    @objc func getAccountAutoUploadDirectory(urlBase: String, userId: String, account: String) -> String {
+
+        let realm = try! Realm()
+
+        guard let result = realm.objects(tableAccount.self).filter("active == true").first else {
+            return ""
+        }
+
+        if result.autoUploadDirectory.count > 0 {
+            // FIX change webdav -> /dav/files/
+            if result.autoUploadDirectory.contains("/webdav") {
+                return NCUtilityFileSystem.shared.getHomeServer(urlBase: urlBase, userId: userId)
+            } else {
+                return result.autoUploadDirectory
+            }
+        } else {
+            return NCUtilityFileSystem.shared.getHomeServer(urlBase: urlBase, userId: userId)
+        }
+    }
+
+    @objc func getAccountAutoUploadPath(urlBase: String, userId: String, account: String) -> String {
+
+        let cameraFileName = self.getAccountAutoUploadFileName()
+        let cameraDirectory = self.getAccountAutoUploadDirectory(urlBase: urlBase, userId: userId, account: account)
+
+        let folderPhotos = CCUtility.stringAppendServerUrl(cameraDirectory, addFileName: cameraFileName)!
+
+        return folderPhotos
+    }
+
+    @objc func getAccountAutoUploadSubfolderGranularity() -> Int64 {
+
+        let realm = try! Realm()
+
+        guard let result = realm.objects(tableAccount.self).filter("active == true").first else {
+            return 1
+        }
+
+        return result.autoUploadSubfolderGranularity
+    }
+    
+    @discardableResult
+    @objc func setAccountActive(_ account: String) -> tableAccount? {
+
+        let realm = try! Realm()
+        var accountReturn = tableAccount()
+
+        do {
+            try realm.write {
+
+                let results = realm.objects(tableAccount.self)
+                for result in results {
+                    if result.account == account {
+                        result.active = true
+                        accountReturn = result
+                    } else {
+                        result.active = false
+                    }
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+            return nil
+        }
+
+        return tableAccount.init(value: accountReturn)
+    }
+
+    @objc func removePasswordAccount(_ account: String) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+
+                if let result = realm.objects(tableAccount.self).filter("account == %@", account).first {
+                    result.password = "********"
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    @objc func setAccountAutoUploadProperty(_ property: String, state: Bool) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                if let result = realm.objects(tableAccount.self).filter("active == true").first {
+                    if (tableAccount().objectSchema.properties.contains { $0.name == property }) {
+                        result[property] = state
+                    }
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    @objc func setAccountAutoUploadGranularity(_ property: String, state: Int64) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                if let result = realm.objects(tableAccount.self).filter("active == true").first {
+                    result.autoUploadSubfolderGranularity = state
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+    
+    @objc func setAccountAutoUploadFileName(_ fileName: String?) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                if let result = realm.objects(tableAccount.self).filter("active == true").first {
+                    if let fileName = fileName {
+                        result.autoUploadFileName = fileName
+                    } else {
+                        result.autoUploadFileName = self.getAccountAutoUploadFileName()
+                    }
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    @objc func setAccountAutoUploadDirectory(_ serverUrl: String?, urlBase: String, userId: String, account: String) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                if let result = realm.objects(tableAccount.self).filter("active == true").first {
+                    if let serverUrl = serverUrl {
+                        result.autoUploadDirectory = serverUrl
+                    } else {
+                        result.autoUploadDirectory = self.getAccountAutoUploadDirectory(urlBase: urlBase, userId: userId, account: account)
+                    }
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    @objc func setAccountUserProfile(account: String, userProfile: NKUserProfile) -> tableAccount? {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                if let result = realm.objects(tableAccount.self).filter("account == %@", account).first {
+                    result.address = userProfile.address
+                    result.backend = userProfile.backend
+                    result.backendCapabilitiesSetDisplayName = userProfile.backendCapabilitiesSetDisplayName
+                    result.backendCapabilitiesSetPassword = userProfile.backendCapabilitiesSetPassword
+                    result.displayName = userProfile.displayName
+                    result.email = userProfile.email
+                    result.enabled = userProfile.enabled
+                    result.groups = userProfile.groups.joined(separator: ",")
+                    result.language = userProfile.language
+                    result.lastLogin = userProfile.lastLogin
+                    result.locale = userProfile.locale
+                    result.organisation = userProfile.organisation
+                    result.phone = userProfile.phone
+                    result.quota = userProfile.quota
+                    result.quotaFree = userProfile.quotaFree
+                    result.quotaRelative = userProfile.quotaRelative
+                    result.quotaTotal = userProfile.quotaTotal
+                    result.quotaUsed = userProfile.quotaUsed
+                    result.storageLocation = userProfile.storageLocation
+                    result.subadmin = userProfile.subadmin.joined(separator: ",")
+                    result.twitter = userProfile.twitter
+                    result.userId = userProfile.userId
+                    result.website = userProfile.website
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+
+        if let result = realm.objects(tableAccount.self).filter("account == %@", account).first {
+            return tableAccount.init(value: result)
+        } else {
+            return nil
+        }
+    }
+
+    @objc func setAccountMediaPath(_ path: String, account: String) {
+
+        let realm = try! Realm()
+        do {
+            try realm.write {
+                if let result = realm.objects(tableAccount.self).filter("account == %@", account).first {
+                    result.mediaPath = path
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    @objc func setAccountUserStatus(userStatusClearAt: NSDate?, userStatusIcon: String?, userStatusMessage: String?, userStatusMessageId: String?, userStatusMessageIsPredefined: Bool, userStatusStatus: String?, userStatusStatusIsUserDefined: Bool, account: String) {
+
+        let realm = try! Realm()
+        do {
+            try realm.write {
+                if let result = realm.objects(tableAccount.self).filter("account == %@", account).first {
+                    result.userStatusClearAt = userStatusClearAt
+                    result.userStatusIcon = userStatusIcon
+                    result.userStatusMessage = userStatusMessage
+                    result.userStatusMessageId = userStatusMessageId
+                    result.userStatusMessageIsPredefined = userStatusMessageIsPredefined
+                    result.userStatusStatus = userStatusStatus
+                    result.userStatusStatusIsUserDefined = userStatusStatusIsUserDefined
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    @objc func setAccountAlias(_ alias: String?) {
+
+        let realm = try! Realm()
+        let alias = alias?.trimmingCharacters(in: .whitespacesAndNewlines)
+
+        do {
+            try realm.write {
+                if let result = realm.objects(tableAccount.self).filter("active == true").first {
+                    if let alias = alias {
+                        result.alias = alias
+                    } else {
+                        result.alias = ""
+                    }
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+}

+ 320 - 0
iOSClient/Data/NCManageDatabase+Activity.swift

@@ -0,0 +1,320 @@
+//
+//  NCManageDatabase+Activity.swift
+//  Nextcloud
+//
+//  Created by Henrik Storch on 30.11.21.
+//  Copyright © 2021 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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 Foundation
+import RealmSwift
+import NextcloudKit
+import SwiftyJSON
+
+class tableActivity: Object, DateCompareable {
+    var dateKey: Date { date as Date }
+
+    @objc dynamic var account = ""
+    @objc dynamic var idPrimaryKey = ""
+    @objc dynamic var action = "Activity"
+    @objc dynamic var date = NSDate()
+    @objc dynamic var idActivity: Int = 0
+    @objc dynamic var app = ""
+    @objc dynamic var type = ""
+    @objc dynamic var user = ""
+    @objc dynamic var subject = ""
+    @objc dynamic var subjectRich = ""
+    let subjectRichItem = List<tableActivitySubjectRich>()
+    @objc dynamic var icon = ""
+    @objc dynamic var link = ""
+    @objc dynamic var message = ""
+    @objc dynamic var objectType = ""
+    @objc dynamic var objectId: Int = 0
+    @objc dynamic var objectName = ""
+    @objc dynamic var note = ""
+    @objc dynamic var selector = ""
+    @objc dynamic var verbose: Bool = false
+
+    override static func primaryKey() -> String {
+        return "idPrimaryKey"
+    }
+}
+
+class tableActivityLatestId: Object {
+
+    @objc dynamic var account = ""
+    @objc dynamic var activityFirstKnown: Int = 0
+    @objc dynamic var activityLastGiven: Int = 0
+
+    override static func primaryKey() -> String {
+        return "account"
+    }
+}
+
+class tableActivityPreview: Object {
+
+    @objc dynamic var account = ""
+    @objc dynamic var filename = ""
+    @objc dynamic var idPrimaryKey = ""
+    @objc dynamic var idActivity: Int = 0
+    @objc dynamic var source = ""
+    @objc dynamic var link = ""
+    @objc dynamic var mimeType = ""
+    @objc dynamic var fileId: Int = 0
+    @objc dynamic var view = ""
+    @objc dynamic var isMimeTypeIcon: Bool = false
+
+    override static func primaryKey() -> String {
+        return "idPrimaryKey"
+    }
+}
+
+class tableActivitySubjectRich: Object {
+
+    @objc dynamic var account = ""
+    @objc dynamic var idActivity: Int = 0
+    @objc dynamic var idPrimaryKey = ""
+    @objc dynamic var id = ""
+    @objc dynamic var key = ""
+    @objc dynamic var link = ""
+    @objc dynamic var name = ""
+    @objc dynamic var path = ""
+    @objc dynamic var type = ""
+
+    override static func primaryKey() -> String {
+        return "idPrimaryKey"
+    }
+}
+
+extension NCManageDatabase {
+    
+    func addActivity(_ activities: [NKActivity], account: String) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+
+                for activity in activities {
+
+                    let addObjectActivity = tableActivity()
+
+                    addObjectActivity.account = account
+                    addObjectActivity.idActivity = activity.idActivity
+                    addObjectActivity.idPrimaryKey = account + String(activity.idActivity)
+                    addObjectActivity.date = activity.date
+                    addObjectActivity.app = activity.app
+                    addObjectActivity.type = activity.type
+                    addObjectActivity.user = activity.user
+                    addObjectActivity.subject = activity.subject
+
+                    if let subjectRich = activity.subjectRich,
+                       let json = JSON(subjectRich).array {
+
+                        addObjectActivity.subjectRich = json[0].stringValue
+                        if json.count > 1,
+                           let dict = json[1].dictionary {
+
+                            for (key, value) in dict {
+                                let addObjectActivitySubjectRich = tableActivitySubjectRich()
+                                let dict = value as JSON
+                                addObjectActivitySubjectRich.account = account
+
+                                if dict["id"].intValue > 0 {
+                                    addObjectActivitySubjectRich.id = String(dict["id"].intValue)
+                                } else {
+                                    addObjectActivitySubjectRich.id = dict["id"].stringValue
+                                }
+
+                                addObjectActivitySubjectRich.name = dict["name"].stringValue
+                                addObjectActivitySubjectRich.idPrimaryKey = account
+                                + String(activity.idActivity)
+                                + addObjectActivitySubjectRich.id
+                                + addObjectActivitySubjectRich.name
+
+                                addObjectActivitySubjectRich.key = key
+                                addObjectActivitySubjectRich.idActivity = activity.idActivity
+                                addObjectActivitySubjectRich.link = dict["link"].stringValue
+                                addObjectActivitySubjectRich.path = dict["path"].stringValue
+                                addObjectActivitySubjectRich.type = dict["type"].stringValue
+
+                                realm.add(addObjectActivitySubjectRich, update: .all)
+                            }
+                        }
+                    }
+
+                    if let previews = activity.previews,
+                       let json = JSON(previews).array {
+                        for preview in json {
+                            let addObjectActivityPreview = tableActivityPreview()
+
+                            addObjectActivityPreview.account = account
+                            addObjectActivityPreview.idActivity = activity.idActivity
+                            addObjectActivityPreview.fileId = preview["fileId"].intValue
+                            addObjectActivityPreview.filename = preview["filename"].stringValue
+                            addObjectActivityPreview.idPrimaryKey = account + String(activity.idActivity) + String(addObjectActivityPreview.fileId)
+                            addObjectActivityPreview.source = preview["source"].stringValue
+                            addObjectActivityPreview.link = preview["link"].stringValue
+                            addObjectActivityPreview.mimeType = preview["mimeType"].stringValue
+                            addObjectActivityPreview.view = preview["view"].stringValue
+                            addObjectActivityPreview.isMimeTypeIcon = preview["isMimeTypeIcon"].boolValue
+
+                            realm.add(addObjectActivityPreview, update: .all)
+                        }
+                    }
+
+                    addObjectActivity.icon = activity.icon
+                    addObjectActivity.link = activity.link
+                    addObjectActivity.message = activity.message
+                    addObjectActivity.objectType = activity.objectType
+                    addObjectActivity.objectId = activity.objectId
+                    addObjectActivity.objectName = activity.objectName
+
+                    realm.add(addObjectActivity, update: .all)
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func getActivity(predicate: NSPredicate, filterFileId: String?) -> (all: [tableActivity], filter: [tableActivity]) {
+
+        let realm = try! Realm()
+
+        let results = realm.objects(tableActivity.self).filter(predicate).sorted(byKeyPath: "idActivity", ascending: false)
+        let allActivity = Array(results.map(tableActivity.init))
+        guard let filterFileId = filterFileId else {
+            return (all: allActivity, filter: allActivity)
+        }
+
+        // comments are loaded seperately, see NCManageDatabase.getComments
+        let filtered = allActivity.filter({ String($0.objectId) == filterFileId && $0.type != "comments" })
+        return (all: allActivity, filter: filtered)
+    }
+
+    func getActivitySubjectRich(account: String, idActivity: Int, key: String) -> tableActivitySubjectRich? {
+
+        let realm = try! Realm()
+
+        let results = realm.objects(tableActivitySubjectRich.self).filter("account == %@ && idActivity == %d && key == %@", account, idActivity, key).first
+
+        return results.map { tableActivitySubjectRich.init(value: $0) }
+    }
+
+    func getActivitySubjectRich(account: String, idActivity: Int, id: String) -> tableActivitySubjectRich? {
+
+        let realm = try! Realm()
+
+        let results = realm.objects(tableActivitySubjectRich.self).filter("account == %@ && idActivity == %d && id == %@", account, idActivity, id)
+        var activitySubjectRich = results.first
+        if results.count == 2 {
+            for result in results {
+                if result.key == "newfile" {
+                    activitySubjectRich = result
+                }
+            }
+        }
+
+        return activitySubjectRich.map { tableActivitySubjectRich.init(value: $0) }
+    }
+
+    func getActivityPreview(account: String, idActivity: Int, orderKeysId: [String]) -> [tableActivityPreview] {
+
+        let realm = try! Realm()
+
+        var results: [tableActivityPreview] = []
+
+        for id in orderKeysId {
+            if let result = realm.objects(tableActivityPreview.self).filter("account == %@ && idActivity == %d && fileId == %d", account, idActivity, Int(id) ?? 0).first {
+                results.append(result)
+            }
+        }
+
+        return results
+    }
+
+   func updateLatestActivityId(activityFirstKnown: Int, activityLastGiven: Int, account: String) {
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                let newRecentActivity = tableActivityLatestId()
+                newRecentActivity.activityFirstKnown = activityFirstKnown
+                newRecentActivity.activityLastGiven = activityLastGiven
+                newRecentActivity.account = account
+                realm.add(newRecentActivity, update: .all)
+            }
+        } catch {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func getLatestActivityId(account: String) -> tableActivityLatestId? {
+
+        let realm = try! Realm()
+        return realm.objects(tableActivityLatestId.self).filter("account == %@", account).first
+    }
+    
+    // MARK: -
+    // MARK: Table Comments
+
+    func addComments(_ comments: [NKComments], account: String, objectId: String) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+
+                let results = realm.objects(tableComments.self).filter("account == %@ AND objectId == %@", account, objectId)
+                realm.delete(results)
+
+                for comment in comments {
+
+                    let object = tableComments()
+
+                    object.account = account
+                    object.actorDisplayName = comment.actorDisplayName
+                    object.actorId = comment.actorId
+                    object.actorType = comment.actorType
+                    object.creationDateTime = comment.creationDateTime as NSDate
+                    object.isUnread = comment.isUnread
+                    object.message = comment.message
+                    object.messageId = comment.messageId
+                    object.objectId = comment.objectId
+                    object.objectType = comment.objectType
+                    object.path = comment.path
+                    object.verb = comment.verb
+
+                    realm.add(object, update: .all)
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func getComments(account: String, objectId: String) -> [tableComments] {
+
+        let realm = try! Realm()
+
+        let results = realm.objects(tableComments.self).filter("account == %@ AND objectId == %@", account, objectId).sorted(byKeyPath: "creationDateTime", ascending: false)
+
+        return Array(results.map(tableComments.init))
+    }
+}

+ 134 - 0
iOSClient/Data/NCManageDatabase+Avatar.swift

@@ -0,0 +1,134 @@
+//
+//  NCManageDatabase+Avatar.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 20/01/23.
+//  Copyright © 2023 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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 Foundation
+import RealmSwift
+import NextcloudKit
+
+class tableAvatar: Object {
+
+    @objc dynamic var date = NSDate()
+    @objc dynamic var etag = ""
+    @objc dynamic var fileName = ""
+    @objc dynamic var loaded: Bool = false
+
+    override static func primaryKey() -> String {
+        return "fileName"
+    }
+}
+
+extension NCManageDatabase {
+
+    func addAvatar(fileName: String, etag: String) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+
+                // Add new
+                let addObject = tableAvatar()
+
+                addObject.date = NSDate()
+                addObject.etag = etag
+                addObject.fileName = fileName
+                addObject.loaded = true
+
+                realm.add(addObject, update: .all)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func getTableAvatar(fileName: String) -> tableAvatar? {
+
+        let realm = try! Realm()
+
+        guard let result = realm.objects(tableAvatar.self).filter("fileName == %@", fileName).first else {
+            return nil
+        }
+
+        return tableAvatar.init(value: result)
+    }
+
+    func clearAllAvatarLoaded() {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+
+                let results = realm.objects(tableAvatar.self)
+                for result in results {
+                    result.loaded = false
+                    realm.add(result, update: .all)
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    @discardableResult
+    func setAvatarLoaded(fileName: String) -> UIImage? {
+
+        let realm = try! Realm()
+        let fileNameLocalPath = String(CCUtility.getDirectoryUserData()) + "/" + fileName
+        var image: UIImage?
+
+        do {
+            try realm.write {
+                if let result = realm.objects(tableAvatar.self).filter("fileName == %@", fileName).first {
+                    if let imageAvatar = UIImage(contentsOfFile: fileNameLocalPath) {
+                        result.loaded = true
+                        image = imageAvatar
+                    } else {
+                        realm.delete(result)
+                    }
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+
+        return image
+    }
+
+    func getImageAvatarLoaded(fileName: String) -> UIImage? {
+
+        let realm = try! Realm()
+        let fileNameLocalPath = String(CCUtility.getDirectoryUserData()) + "/" + fileName
+
+        let result = realm.objects(tableAvatar.self).filter("fileName == %@", fileName).first
+        if result == nil {
+            NCUtilityFileSystem.shared.deleteFile(filePath: fileNameLocalPath)
+            return nil
+        } else if result?.loaded == false {
+            return nil
+        }
+
+        return UIImage(contentsOfFile: fileNameLocalPath)
+    }
+
+}

+ 123 - 0
iOSClient/Data/NCManageDatabase+DashboardWidget.swift

@@ -0,0 +1,123 @@
+//
+//  NCManageDatabase+DashboardWidget.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 09/09/22.
+//  Copyright © 2022 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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 Foundation
+import RealmSwift
+import NextcloudKit
+
+class tableDashboardWidget: Object {
+
+    @Persisted(primaryKey: true) var index = ""
+    @Persisted var account = ""
+    @Persisted var id = ""
+    @Persisted var title = ""
+    @Persisted var order: Int = 0
+    @Persisted var iconClass: String?
+    @Persisted var iconUrl: String?
+    @Persisted var widgetUrl: String?
+    @Persisted var itemIconsRound: Bool = false
+}
+
+class tableDashboardWidgetButton: Object {
+
+    @Persisted(primaryKey: true) var index = ""
+    @Persisted var account = ""
+    @Persisted var id = ""
+    @Persisted var type = ""
+    @Persisted var text = ""
+    @Persisted var link = ""
+}
+
+extension NCManageDatabase {
+
+    func getDashboardWidget(account: String, id: String) -> (tableDashboardWidget?, [tableDashboardWidgetButton]?) {
+     
+        let realm = try! Realm()
+        guard let resultDashboard = realm.objects(tableDashboardWidget.self).filter("account == %@ AND id == %@", account, id).first else {
+            return (nil, nil)
+        }
+        let resultsButton = realm.objects(tableDashboardWidgetButton.self).filter("account == %@ AND id == %@", account, id).sorted(byKeyPath: "type", ascending: true)
+        
+        return (tableDashboardWidget.init(value: resultDashboard), Array(resultsButton.map { tableDashboardWidgetButton.init(value: $0) }))
+    }
+
+    func getDashboardWidgetApplications(account: String) -> [tableDashboardWidget] {
+
+        let realm = try! Realm()
+        let sortProperties = [SortDescriptor(keyPath: "order", ascending: true), SortDescriptor(keyPath: "title", ascending: true)]
+        let results = realm.objects(tableDashboardWidget.self).filter("account == %@", account).sorted(by: sortProperties)
+
+        return Array(results.map { tableDashboardWidget.init(value: $0) })
+    }
+    
+    func addDashboardWidget(account: String, dashboardWidgets: [NCCDashboardWidget]) {
+        
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                
+                let resultDashboard = realm.objects(tableDashboardWidget.self).filter("account == %@", account)
+                realm.delete(resultDashboard)
+                
+                let resultDashboardButton = realm.objects(tableDashboardWidgetButton.self).filter("account == %@", account)
+                realm.delete(resultDashboardButton)
+                
+                for widget in dashboardWidgets {
+                    
+                    let addObject = tableDashboardWidget()
+                    
+                    addObject.index = account + " " + widget.id
+                    addObject.account = account
+                    addObject.id = widget.id
+                    addObject.title = widget.title
+                    addObject.order = widget.order
+                    addObject.iconClass = widget.iconClass
+                    addObject.iconUrl = widget.iconUrl
+                    addObject.widgetUrl = widget.widgetUrl
+                    addObject.itemIconsRound = widget.itemIconsRound
+
+                    if let buttons = widget.button {
+                        for button in buttons {
+                            
+                            let addObject = tableDashboardWidgetButton()
+                            
+                            addObject.account = account
+                            addObject.id = widget.id
+                            addObject.type = button.type
+                            addObject.text = button.text
+                            addObject.link = button.link
+                            addObject.index = account + " " + widget.id + " " + button.type
+                            
+                            realm.add(addObject, update: .all)
+                        }
+                    }
+                    
+                    realm.add(addObject, update: .all)
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+}

+ 240 - 0
iOSClient/Data/NCManageDatabase+Directory.swift

@@ -0,0 +1,240 @@
+//
+//  NCManageDatabase+Directory.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 26/11/22.
+//  Copyright © 2022 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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 Foundation
+import RealmSwift
+import NextcloudKit
+
+class tableDirectory: Object {
+
+    @objc dynamic var account = ""
+    @objc dynamic var colorFolder: String?
+    @objc dynamic var e2eEncrypted: Bool = false
+    @objc dynamic var etag = ""
+    @objc dynamic var favorite: Bool = false
+    @objc dynamic var fileId = ""
+    @objc dynamic var ocId = ""
+    @objc dynamic var offline: Bool = false
+    @objc dynamic var permissions = ""
+    @objc dynamic var richWorkspace: String?
+    @objc dynamic var serverUrl = ""
+
+    override static func primaryKey() -> String {
+        return "ocId"
+    }
+}
+
+extension NCManageDatabase {
+
+    func addDirectory(encrypted: Bool, favorite: Bool, ocId: String, fileId: String, etag: String? = nil, permissions: String? = nil, serverUrl: String, account: String) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                var addObject = tableDirectory()
+                let result = realm.objects(tableDirectory.self).filter("ocId == %@", ocId).first
+
+                if result != nil {
+                    addObject = result!
+                } else {
+                    addObject.ocId = ocId
+                }
+
+                addObject.account = account
+                addObject.e2eEncrypted = encrypted
+                addObject.favorite = favorite
+                addObject.fileId = fileId
+                if let etag = etag {
+                    addObject.etag = etag
+                }
+                if let permissions = permissions {
+                    addObject.permissions = permissions
+                }
+                addObject.serverUrl = serverUrl
+
+                realm.add(addObject, update: .all)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func deleteDirectoryAndSubDirectory(serverUrl: String, account: String) {
+
+        let realm = try! Realm()
+
+        let results = realm.objects(tableDirectory.self).filter("account == %@ AND serverUrl BEGINSWITH %@", account, serverUrl)
+
+        // Delete table Metadata & LocalFile
+        for result in results {
+
+            self.deleteMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", result.account, result.serverUrl))
+            self.deleteLocalFile(predicate: NSPredicate(format: "ocId == %@", result.ocId))
+        }
+
+        // Delete table Dirrectory
+        do {
+            try realm.write {
+                realm.delete(results)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func setDirectory(serverUrl: String, serverUrlTo: String? = nil, etag: String? = nil, ocId: String? = nil, fileId: String? = nil, encrypted: Bool, richWorkspace: String? = nil, account: String) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+
+                guard let result = realm.objects(tableDirectory.self).filter("account == %@ AND serverUrl == %@", account, serverUrl).first else {
+                    return
+                }
+
+                let directory = tableDirectory.init(value: result)
+
+                realm.delete(result)
+
+                directory.e2eEncrypted = encrypted
+                if let etag = etag {
+                    directory.etag = etag
+                }
+                if let ocId = ocId {
+                    directory.ocId = ocId
+                }
+                if let fileId = fileId {
+                    directory.fileId = fileId
+                }
+                if let serverUrlTo = serverUrlTo {
+                    directory.serverUrl = serverUrlTo
+                }
+                if let richWorkspace = richWorkspace {
+                    directory.richWorkspace = richWorkspace
+                }
+
+                realm.add(directory, update: .all)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func getTableDirectory(predicate: NSPredicate) -> tableDirectory? {
+
+        let realm = try! Realm()
+
+        guard let result = realm.objects(tableDirectory.self).filter(predicate).first else {
+            return nil
+        }
+
+        return tableDirectory.init(value: result)
+    }
+
+    func getTablesDirectory(predicate: NSPredicate, sorted: String, ascending: Bool) -> [tableDirectory]? {
+
+        let realm = try! Realm()
+
+        let results = realm.objects(tableDirectory.self).filter(predicate).sorted(byKeyPath: sorted, ascending: ascending)
+
+        if results.count > 0 {
+            return Array(results.map { tableDirectory.init(value: $0) })
+        } else {
+            return nil
+        }
+    }
+
+    func renameDirectory(ocId: String, serverUrl: String) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                let result = realm.objects(tableDirectory.self).filter("ocId == %@", ocId).first
+                result?.serverUrl = serverUrl
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func setDirectory(serverUrl: String, offline: Bool, account: String) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                let result = realm.objects(tableDirectory.self).filter("account == %@ AND serverUrl == %@", account, serverUrl).first
+                result?.offline = offline
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    @discardableResult
+    func setDirectory(serverUrl: String, richWorkspace: String?, account: String) -> tableDirectory? {
+
+        let realm = try! Realm()
+        var result: tableDirectory?
+
+        do {
+            try realm.write {
+                result = realm.objects(tableDirectory.self).filter("account == %@ AND serverUrl == %@", account, serverUrl).first
+                result?.richWorkspace = richWorkspace
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+
+        if let result = result {
+            return tableDirectory.init(value: result)
+        } else {
+            return nil
+        }
+    }
+
+    @discardableResult
+    func setDirectory(serverUrl: String, colorFolder: String?, account: String) -> tableDirectory? {
+
+        let realm = try! Realm()
+        var result: tableDirectory?
+
+        do {
+            try realm.write {
+                result = realm.objects(tableDirectory.self).filter("account == %@ AND serverUrl == %@", account, serverUrl).first
+                result?.colorFolder = colorFolder
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+
+        if let result = result {
+            return tableDirectory.init(value: result)
+        } else {
+            return nil
+        }
+    }
+}

+ 261 - 0
iOSClient/Data/NCManageDatabase+E2EE.swift

@@ -0,0 +1,261 @@
+//
+//  NCManageDatabase+E2EE.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 23/01/23.
+//  Copyright © 2023 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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 Foundation
+import RealmSwift
+import NextcloudKit
+import SwiftyJSON
+
+class tableE2eEncryption: Object {
+
+    @objc dynamic var account = ""
+    @objc dynamic var authenticationTag: String = ""
+    @objc dynamic var blob = "files"
+    @objc dynamic var fileName = ""
+    @objc dynamic var fileNameIdentifier = ""
+    @objc dynamic var fileNamePath = ""
+    @objc dynamic var key = ""
+    @objc dynamic var initializationVector = ""
+    @objc dynamic var metadataKey = ""
+    @objc dynamic var metadataKeyFiledrop = ""
+    @objc dynamic var metadataKeyIndex: Int = 0
+    @objc dynamic var metadataVersion: Double = 0
+    @objc dynamic var mimeType = ""
+    @objc dynamic var serverUrl = ""
+
+    override static func primaryKey() -> String {
+        return "fileNamePath"
+    }
+}
+
+class tableE2eEncryptionLock: Object {
+
+    @objc dynamic var account = ""
+    @objc dynamic var date = NSDate()
+    @objc dynamic var fileId = ""
+    @objc dynamic var serverUrl = ""
+    @objc dynamic var e2eToken = ""
+
+    override static func primaryKey() -> String {
+        return "fileId"
+    }
+}
+
+class tableE2eMetadata: Object {
+
+    @Persisted(primaryKey: true) var serverUrl = ""
+    @Persisted var account = ""
+    @Persisted var metadataKey = ""
+    @Persisted var version: Double = 0
+}
+
+extension NCManageDatabase {
+
+    // MARK: -
+    // MARK: Table e2e Encryption
+
+    @objc func addE2eEncryption(_ e2e: tableE2eEncryption) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                realm.add(e2e, update: .all)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    @objc func deleteE2eEncryption(predicate: NSPredicate) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+
+                let results = realm.objects(tableE2eEncryption.self).filter(predicate)
+                realm.delete(results)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    @objc func getE2eEncryption(predicate: NSPredicate) -> tableE2eEncryption? {
+
+        let realm = try! Realm()
+
+        guard let result = realm.objects(tableE2eEncryption.self).filter(predicate).sorted(byKeyPath: "metadataKeyIndex", ascending: false).first else {
+            return nil
+        }
+
+        return tableE2eEncryption.init(value: result)
+    }
+
+    @objc func getE2eEncryptions(predicate: NSPredicate) -> [tableE2eEncryption]? {
+
+        guard self.getActiveAccount() != nil else {
+            return nil
+        }
+
+        let realm = try! Realm()
+
+        let results: Results<tableE2eEncryption>
+
+        results = realm.objects(tableE2eEncryption.self).filter(predicate)
+
+        if results.count > 0 {
+            return Array(results.map { tableE2eEncryption.init(value: $0) })
+        } else {
+            return nil
+        }
+    }
+
+    @objc func renameFileE2eEncryption(serverUrl: String, fileNameIdentifier: String, newFileName: String, newFileNamePath: String) {
+
+        guard let activeAccount = self.getActiveAccount() else {
+            return
+        }
+
+        let realm = try! Realm()
+
+        realm.beginWrite()
+
+        guard let result = realm.objects(tableE2eEncryption.self).filter("account == %@ AND serverUrl == %@ AND fileNameIdentifier == %@", activeAccount.account, serverUrl, fileNameIdentifier).first else {
+            realm.cancelWrite()
+            return
+        }
+
+        let object = tableE2eEncryption.init(value: result)
+
+        realm.delete(result)
+
+        object.fileName = newFileName
+        object.fileNamePath = newFileNamePath
+
+        realm.add(object)
+
+        do {
+            try realm.commitWrite()
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    // MARK: -
+    // MARK: Table e2e Encryption Lock
+
+    @objc func getE2ETokenLock(account: String, serverUrl: String) -> tableE2eEncryptionLock? {
+
+        let realm = try! Realm()
+
+        guard let result = realm.objects(tableE2eEncryptionLock.self).filter("account == %@ AND serverUrl == %@", account, serverUrl).first else {
+            return nil
+        }
+
+        return tableE2eEncryptionLock.init(value: result)
+    }
+
+    @objc func getE2EAllTokenLock(account: String) -> [tableE2eEncryptionLock] {
+
+        let realm = try! Realm()
+
+        let results = realm.objects(tableE2eEncryptionLock.self).filter("account == %@", account)
+
+        if results.count > 0 {
+            return Array(results.map { tableE2eEncryptionLock.init(value: $0) })
+        } else {
+            return []
+        }
+    }
+
+    @objc func setE2ETokenLock(account: String, serverUrl: String, fileId: String, e2eToken: String) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                let addObject = tableE2eEncryptionLock()
+
+                addObject.account = account
+                addObject.fileId = fileId
+                addObject.serverUrl = serverUrl
+                addObject.e2eToken = e2eToken
+
+                realm.add(addObject, update: .all)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    @objc func deleteE2ETokenLock(account: String, serverUrl: String) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                if let result = realm.objects(tableE2eEncryptionLock.self).filter("account == %@ AND serverUrl == %@", account, serverUrl).first {
+                    realm.delete(result)
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    // MARK: -
+    // MARK: Table e2ee Metadata
+
+    func getE2eMetadata(account: String, serverUrl: String) -> tableE2eMetadata? {
+
+        let realm = try! Realm()
+
+        guard let result = realm.objects(tableE2eMetadata.self).filter("account == %@ AND serverUrl == %@", account, serverUrl).first else {
+            return nil
+        }
+
+        return tableE2eMetadata.init(value: result)
+    }
+
+    func setE2eMetadata(account: String, serverUrl: String, metadataKey: String, version: Double) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                let addObject = tableE2eMetadata()
+
+                addObject.account = account
+                addObject.metadataKey = metadataKey
+                addObject.serverUrl = serverUrl
+                addObject.version = version
+
+                realm.add(addObject, update: .all)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+}

+ 94 - 0
iOSClient/Data/NCManageDatabase+Groupfolders.swift

@@ -0,0 +1,94 @@
+//
+//  NCManageDatabase+LayoutForView.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 28/11/22.
+//  Copyright © 2022 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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 Foundation
+import RealmSwift
+import NextcloudKit
+import SwiftyJSON
+
+class TableGroupfolders: Object {
+
+    @Persisted var account = ""
+    @Persisted var acl: Bool = false
+    @Persisted var groups: List<TableGroupfoldersGroups>
+    @Persisted var id: Int = 0
+    @Persisted var manage: Data?
+    @Persisted var mountPoint = ""
+    @Persisted var quota: Int = 0
+    @Persisted var size: Int = 0
+}
+
+class TableGroupfoldersGroups: Object {
+
+    @Persisted var account = ""
+    @Persisted var group = ""
+    @Persisted var permission: Int = 0
+
+    convenience init(account: String, group: String, permission: Int) {
+        self.init()
+
+        self.account = account
+        self.group = group
+        self.permission = permission
+    }
+}
+
+extension NCManageDatabase {
+
+    func addGroupfolders(account: String, groupfolders: [NKGroupfolders]) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+
+                let tableGroupfolders = realm.objects(TableGroupfolders.self).filter("account == %@", account)
+                realm.delete(tableGroupfolders)
+
+                let tableGroupfoldersGroups = realm.objects(TableGroupfoldersGroups.self).filter("account == %@", account)
+                realm.delete(tableGroupfoldersGroups)
+
+                for groupfolder in groupfolders {
+
+                    let obj = TableGroupfolders()
+
+                    obj.account = account
+                    obj.acl = groupfolder.acl
+                    for group in groupfolder.groups ?? [:] {
+                        let objGroups = TableGroupfoldersGroups(account: account, group: group.key, permission: (group.value as? Int ?? 0))
+                        obj.groups.append(objGroups)
+                    }
+                    obj.id = groupfolder.id
+                    obj.manage = groupfolder.manage
+                    obj.mountPoint = groupfolder.mountPoint
+                    obj.quota = groupfolder.quota
+                    obj.size = groupfolder.size
+
+                    realm.add(obj)
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+}

+ 128 - 0
iOSClient/Data/NCManageDatabase+LayoutForView.swift

@@ -0,0 +1,128 @@
+//
+//  NCManageDatabase+LayoutForView.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 28/11/22.
+//  Copyright © 2022 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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 Foundation
+import RealmSwift
+import NextcloudKit
+
+class NCDBLayoutForView: Object {
+
+    @Persisted(primaryKey: true) var index = ""
+    @Persisted var account = ""
+    @Persisted var keyStore = ""
+    @Persisted var layout: String = NCGlobal.shared.layoutList
+    @Persisted var sort: String = "fileName"
+    @Persisted var ascending: Bool = true
+    @Persisted var groupBy: String = "none"
+    @Persisted var directoryOnTop: Bool = true
+    @Persisted var titleButtonHeader: String = "_sorted_by_name_a_z_"
+    @Persisted var itemForLine: Int = 3
+}
+
+extension NCManageDatabase {
+
+    @discardableResult
+    func setLayoutForView(account: String, key: String, serverUrl: String, layout: String? = nil, sort: String? = nil, ascending: Bool? = nil, groupBy: String? = nil, directoryOnTop: Bool? = nil, titleButtonHeader: String? = nil, itemForLine: Int? = nil) -> NCDBLayoutForView? {
+
+        let realm = try! Realm()
+
+        var keyStore = key
+        if !serverUrl.isEmpty { keyStore = serverUrl}
+        let index = account + " " + keyStore
+
+        var addObject = NCDBLayoutForView()
+
+        do {
+            try realm.write {
+                if let result = realm.objects(NCDBLayoutForView.self).filter("index == %@", index).first {
+                    addObject = result
+                } else {
+                    addObject.index = index
+                }
+                addObject.account = account
+                addObject.keyStore = keyStore
+                if let layout = layout {
+                    addObject.layout = layout
+                }
+                if let sort = sort {
+                    addObject.sort = sort
+                }
+                if let sort = sort {
+                    addObject.sort = sort
+                }
+                if let ascending = ascending {
+                    addObject.ascending = ascending
+                }
+                if let groupBy = groupBy {
+                    addObject.groupBy = groupBy
+                }
+                if let directoryOnTop = directoryOnTop {
+                    addObject.directoryOnTop = directoryOnTop
+                }
+                if let titleButtonHeader = titleButtonHeader {
+                    addObject.titleButtonHeader = titleButtonHeader
+                }
+                if let itemForLine = itemForLine {
+                    addObject.itemForLine = itemForLine
+                }
+                realm.add(addObject, update: .all)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+
+        return NCDBLayoutForView.init(value: addObject)
+    }
+
+    @discardableResult
+    func setLayoutForView(layoutForView: NCDBLayoutForView) -> NCDBLayoutForView? {
+
+        let realm = try! Realm()
+        let result = NCDBLayoutForView.init(value: layoutForView)
+
+        do {
+            try realm.write {
+                realm.add(result, update: .all)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+            return nil
+        }
+        return NCDBLayoutForView.init(value: result)
+    }
+
+    func getLayoutForView(account: String, key: String, serverUrl: String) -> NCDBLayoutForView? {
+
+        let realm = try! Realm()
+
+        var keyStore = key
+        if !serverUrl.isEmpty { keyStore = serverUrl}
+        let index = account + " " + keyStore
+
+        if let result = realm.objects(NCDBLayoutForView.self).filter("index == %@", index).first {
+            return NCDBLayoutForView.init(value: result)
+        } else {
+            return setLayoutForView(account: account, key: key, serverUrl: serverUrl)
+        }
+    }
+}

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

@@ -0,0 +1,1127 @@
+//
+//  NCManageDatabase+Metadata.swift
+//  Nextcloud
+//
+//  Created by Henrik Storch on 30.11.21.
+//  Copyright © 2021 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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 Foundation
+import RealmSwift
+import NextcloudKit
+
+class tableMetadata: Object, NCUserBaseUrl {
+    override func isEqual(_ object: Any?) -> Bool {
+        if let object = object as? tableMetadata {
+            return self.fileId == object.fileId && self.account == object.account
+                   && self.path == object.path && self.fileName == object.fileName
+        } else {
+            return false
+        }
+    }
+
+    @objc dynamic var account = ""
+    @objc dynamic var assetLocalIdentifier = ""
+    @objc dynamic var checksums = ""
+    @objc dynamic var chunk: Bool = false
+    @objc dynamic var classFile = ""
+    @objc dynamic var commentsUnread: Bool = false
+    @objc dynamic var contentType = ""
+    @objc dynamic var creationDate = NSDate()
+    @objc dynamic var dataFingerprint = ""
+    @objc dynamic var date = NSDate()
+    @objc dynamic var directory: Bool = false
+    @objc dynamic var deleteAssetLocalIdentifier: Bool = false
+    @objc dynamic var downloadURL = ""
+    @objc dynamic var e2eEncrypted: Bool = false
+    @objc dynamic var edited: Bool = false
+    @objc dynamic var etag = ""
+    @objc dynamic var etagResource = ""
+    @objc dynamic var favorite: Bool = false
+    @objc dynamic var fileId = ""
+    @objc dynamic var fileName = ""
+    @objc dynamic var fileNameView = ""
+    @objc dynamic var hasPreview: Bool = false
+    @objc dynamic var iconName = ""
+    @objc dynamic var iconUrl = ""
+    @objc dynamic var isExtractFile: Bool = false
+    @objc dynamic var livePhoto: Bool = false
+    @objc dynamic var mountType = ""
+    @objc dynamic var name = ""                                             // for unifiedSearch is the provider.id
+    @objc dynamic var note = ""
+    @objc dynamic var ocId = ""
+    @objc dynamic var ownerId = ""
+    @objc dynamic var ownerDisplayName = ""
+    @objc public var lock = false
+    @objc public var lockOwner = ""
+    @objc public var lockOwnerEditor = ""
+    @objc public var lockOwnerType = 0
+    @objc public var lockOwnerDisplayName = ""
+    @objc public var lockTime: Date?
+    @objc public var lockTimeOut: Date?
+    @objc dynamic var path = ""
+    @objc dynamic var permissions = ""
+    @objc dynamic var quotaUsedBytes: Int64 = 0
+    @objc dynamic var quotaAvailableBytes: Int64 = 0
+    @objc dynamic var resourceType = ""
+    @objc dynamic var richWorkspace: String?
+    @objc dynamic var serverUrl = ""
+    @objc dynamic var session = ""
+    @objc dynamic var sessionError = ""
+    @objc dynamic var sessionSelector = ""
+    @objc dynamic var sessionTaskIdentifier: Int = 0
+    @objc dynamic var sharePermissionsCollaborationServices: Int = 0
+    let sharePermissionsCloudMesh = List<String>()
+    let shareType = List<Int>()
+    @objc dynamic var size: Int64 = 0
+    @objc dynamic var status: Int = 0
+    @objc dynamic var subline: String?
+    let tags = List<String>()
+    @objc dynamic var trashbinFileName = ""
+    @objc dynamic var trashbinOriginalLocation = ""
+    @objc dynamic var trashbinDeletionTime = NSDate()
+    @objc dynamic var uploadDate = NSDate()
+    @objc dynamic var url = ""
+    @objc dynamic var urlBase = ""
+    @objc dynamic var user = ""
+    @objc dynamic var userId = ""
+
+    override static func primaryKey() -> String {
+        return "ocId"
+    }
+}
+
+extension tableMetadata {
+
+    var fileExtension: String {
+        (fileNameView as NSString).pathExtension
+    }
+
+    var fileNoExtension: String {
+        (fileNameView as NSString).deletingPathExtension
+    }
+
+    var isRenameable: Bool {
+        if lock {
+            return false
+        }
+        if !isDirectoryE2EE && e2eEncrypted {
+            return false
+        }
+        return true
+    }
+    
+    var isPrintable: Bool {
+        if isDocumentViewableOnly {
+            return false
+        }
+        if ["application/pdf", "com.adobe.pdf"].contains(contentType) || contentType.hasPrefix("text/") || classFile == NKCommon.TypeClassFile.image.rawValue {
+            return true
+        }
+        return false
+    }
+
+    var isSavebleInCameraRoll: Bool {
+        return (classFile == NKCommon.TypeClassFile.image.rawValue && contentType != "image/svg+xml") || classFile == NKCommon.TypeClassFile.video.rawValue
+    }
+
+    var isDocumentViewableOnly: Bool {
+        sharePermissionsCollaborationServices == NCGlobal.shared.permissionReadShare && classFile == NKCommon.TypeClassFile.document.rawValue
+    }
+
+    var isSavebleAsImage: Bool {
+        classFile == NKCommon.TypeClassFile.image.rawValue && contentType != "image/svg+xml"
+    }
+
+    var isCopyableInPasteboard: Bool {
+        !isDocumentViewableOnly && !directory
+    }
+
+    var isCopyableMovable: Bool {
+        !isDocumentViewableOnly && !isDirectoryE2EE && !e2eEncrypted
+    }
+
+    var isModifiableWithQuickLook: Bool {
+        if directory || isDocumentViewableOnly || isDirectoryE2EE {
+            return false
+        }
+        return contentType == "com.adobe.pdf" || contentType == "application/pdf" || classFile == NKCommon.TypeClassFile.image.rawValue
+    }
+
+    var isDeletable: Bool {
+        if !isDirectoryE2EE && e2eEncrypted {
+            return false
+        }
+        return true
+    }
+
+    var isSettableOnOffline: Bool {
+        return session.isEmpty && !isDocumentViewableOnly
+    }
+
+    var canOpenIn: Bool {
+        return session.isEmpty && !isDocumentViewableOnly && !directory && !NCBrandOptions.shared.disable_openin_file
+    }
+
+    var isDirectoySettableE2EE: Bool {
+        return directory && size == 0 && !e2eEncrypted && CCUtility.isEnd(toEndEnabled: account)
+    }
+
+    var isSharable: Bool {
+        let sharing = NCManageDatabase.shared.getCapabilitiesServerBool(account: account, elements: NCElementsJSON.shared.capabilitiesFileSharingApiEnabled, exists: false)
+        if !sharing { return false }
+        if !e2eEncrypted && !isDirectoryE2EE { return true }
+        let serverVersionMajor = NCManageDatabase.shared.getCapabilitiesServerInt(account: account, elements: NCElementsJSON.shared.capabilitiesVersionMajor)
+        if serverVersionMajor >= NCGlobal.shared.nextcloudVersion26 && directory {
+            // E2EE DIRECTORY SECURE FILE DROP (SHARE AVAILABLE)
+            return true
+        } else {
+            return false
+        }
+    }
+
+    var isDirectoryUnsettableE2EE: Bool {
+        return !isDirectoryE2EE && directory && size == 0 && e2eEncrypted && CCUtility.isEnd(toEndEnabled: account)
+    }
+
+    var canOpenExternalEditor: Bool {
+        if isDocumentViewableOnly {
+            return false
+        }
+
+        let editors = NCUtility.shared.isDirectEditing(account: account, contentType: contentType)
+        let isRichDocument = NCUtility.shared.isRichDocument(self)
+
+        return classFile == NKCommon.TypeClassFile.document.rawValue && editors.contains(NCGlobal.shared.editorText) && ((editors.contains(NCGlobal.shared.editorOnlyoffice) || isRichDocument))
+    }
+
+    var isDownloadUpload: Bool {
+        status == NCGlobal.shared.metadataStatusInDownload || status == NCGlobal.shared.metadataStatusDownloading || status == NCGlobal.shared.metadataStatusInUpload || status == NCGlobal.shared.metadataStatusUploading
+    }
+
+    var isDownload: Bool {
+        status == NCGlobal.shared.metadataStatusInDownload || status == NCGlobal.shared.metadataStatusDownloading
+    }
+
+    var isUpload: Bool {
+        status == NCGlobal.shared.metadataStatusInUpload || status == NCGlobal.shared.metadataStatusUploading
+    }
+
+    @objc var isDirectoryE2EE: Bool {
+        NCUtility.shared.isDirectoryE2EE(serverUrl: serverUrl, account: account, urlBase: urlBase, userId: userId)
+    }
+
+    /// Returns false if the user is lokced out of the file. I.e. The file is locked but by somone else
+    func canUnlock(as user: String) -> Bool {
+        return !lock || (lockOwner == user && lockOwnerType == 0)
+    }
+}
+
+extension NCManageDatabase {
+
+    func convertFileToMetadata(_ file: NKFile, isDirectoryE2EE: Bool) -> tableMetadata {
+
+        let metadata = tableMetadata()
+
+        metadata.account = file.account
+        metadata.checksums = file.checksums
+        metadata.commentsUnread = file.commentsUnread
+        metadata.contentType = file.contentType
+        if let date = file.creationDate {
+            metadata.creationDate = date
+        } else {
+            metadata.creationDate = file.date
+        }
+        metadata.dataFingerprint = file.dataFingerprint
+        metadata.date = file.date
+        metadata.directory = file.directory
+        metadata.downloadURL = file.downloadURL
+        metadata.e2eEncrypted = file.e2eEncrypted
+        metadata.etag = file.etag
+        metadata.favorite = file.favorite
+        metadata.fileId = file.fileId
+        metadata.fileName = file.fileName
+        metadata.fileNameView = file.fileName
+        metadata.hasPreview = file.hasPreview
+        metadata.iconName = file.iconName
+        metadata.mountType = file.mountType
+        metadata.name = file.name
+        metadata.note = file.note
+        metadata.ocId = file.ocId
+        metadata.ownerId = file.ownerId
+        metadata.ownerDisplayName = file.ownerDisplayName
+        metadata.lock = file.lock
+        metadata.lockOwner = file.lockOwner
+        metadata.lockOwnerEditor = file.lockOwnerEditor
+        metadata.lockOwnerType = file.lockOwnerType
+        metadata.lockOwnerDisplayName = file.lockOwnerDisplayName
+        metadata.lockTime = file.lockTime
+        metadata.lockTimeOut = file.lockTimeOut
+        metadata.path = file.path
+        metadata.permissions = file.permissions
+        metadata.quotaUsedBytes = file.quotaUsedBytes
+        metadata.quotaAvailableBytes = file.quotaAvailableBytes
+        metadata.richWorkspace = file.richWorkspace
+        metadata.resourceType = file.resourceType
+        metadata.serverUrl = file.serverUrl
+        metadata.sharePermissionsCollaborationServices = file.sharePermissionsCollaborationServices
+        for element in file.sharePermissionsCloudMesh {
+            metadata.sharePermissionsCloudMesh.append(element)
+        }
+        for element in file.shareType {
+            metadata.shareType.append(element)
+        }
+        for element in file.tags {
+            metadata.tags.append(element)
+        }
+        metadata.size = file.size
+        metadata.classFile = file.classFile
+        //FIXME: iOS 12.0,* don't detect UTI text/markdown, text/x-markdown
+        if (metadata.contentType == "text/markdown" || metadata.contentType == "text/x-markdown") && metadata.classFile == NKCommon.TypeClassFile.unknow.rawValue {
+            metadata.classFile = NKCommon.TypeClassFile.document.rawValue
+        }
+        if let date = file.uploadDate {
+            metadata.uploadDate = date
+        } else {
+            metadata.uploadDate = file.date
+        }
+        metadata.urlBase = file.urlBase
+        metadata.user = file.user
+        metadata.userId = file.userId
+
+        // E2EE find the fileName for fileNameView
+        if isDirectoryE2EE || file.e2eEncrypted {
+            if let tableE2eEncryption = NCManageDatabase.shared.getE2eEncryption(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileNameIdentifier == %@", file.account, file.serverUrl, file.fileName)) {
+                metadata.fileNameView = tableE2eEncryption.fileName
+                let results = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: metadata.fileNameView, mimeType: file.contentType, directory: file.directory)
+                metadata.contentType = results.mimeType
+                metadata.iconName = results.iconName
+                metadata.classFile = results.classFile
+            }
+        }
+
+        return metadata
+    }
+
+    func convertFilesToMetadatas(_ files: [NKFile], useMetadataFolder: Bool, completion: @escaping (_ metadataFolder: tableMetadata, _ metadatasFolder: [tableMetadata], _ metadatas: [tableMetadata]) -> Void) {
+
+        var counter: Int = 0
+        var isDirectoryE2EE: Bool = false
+        let listServerUrl = ThreadSafeDictionary<String,Bool>()
+
+        var metadataFolder = tableMetadata()
+        var metadataFolders: [tableMetadata] = []
+        var metadatas: [tableMetadata] = []
+
+        for file in files {
+
+            if let key = listServerUrl[file.serverUrl] {
+                isDirectoryE2EE = key
+            } else {
+                isDirectoryE2EE = NCUtility.shared.isDirectoryE2EE(file: file)
+                listServerUrl[file.serverUrl] = isDirectoryE2EE
+            }
+
+            let metadata = convertFileToMetadata(file, isDirectoryE2EE: isDirectoryE2EE)
+
+            if counter == 0 && useMetadataFolder {
+                metadataFolder = tableMetadata.init(value: metadata)
+            } else {
+                metadatas.append(metadata)
+                if metadata.directory {
+                    metadataFolders.append(metadata)
+                }
+            }
+
+            counter += 1
+        }
+
+        //
+        // Detect Live photo
+        //
+        var metadataOutput: [tableMetadata] = []
+        metadatas = metadatas.sorted(by: {$0.fileNameView < $1.fileNameView})
+
+        for index in metadatas.indices {
+            let metadata = metadatas[index]
+            if index < metadatas.count - 1,
+                metadata.fileNoExtension == metadatas[index+1].fileNoExtension,
+                ((metadata.classFile == NKCommon.TypeClassFile.image.rawValue && metadatas[index+1].classFile == NKCommon.TypeClassFile.video.rawValue) || (metadata.classFile == NKCommon.TypeClassFile.video.rawValue && metadatas[index+1].classFile == NKCommon.TypeClassFile.image.rawValue)){
+                metadata.livePhoto = true
+                metadatas[index+1].livePhoto = true
+            }
+            metadataOutput.append(metadata)
+        }
+
+        completion(metadataFolder, metadataFolders, metadataOutput)
+    }
+
+    func createMetadata(account: String, user: String, userId: String, fileName: String, fileNameView: String, ocId: String, serverUrl: String, urlBase: String, url: String, contentType: String, isLivePhoto: Bool = false, isUrl: Bool = false, name: String = NCGlobal.shared.appName, subline: String? = nil, iconName: String? = nil, iconUrl: String? = nil) -> tableMetadata {
+
+        let metadata = tableMetadata()
+        if isUrl {
+            metadata.contentType = "text/uri-list"
+            if let iconName = iconName {
+                metadata.iconName = iconName
+            } else {
+                metadata.iconName = NKCommon.TypeClassFile.url.rawValue
+            }
+            metadata.classFile = NKCommon.TypeClassFile.url.rawValue
+        } else {
+            let (mimeType, classFile, iconName, _, _, _) = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileName, mimeType: contentType, directory: false)
+            metadata.contentType = mimeType
+            metadata.iconName = iconName
+            metadata.classFile = classFile
+            //FIXME: iOS 12.0,* don't detect UTI text/markdown, text/x-markdown
+            if classFile == NKCommon.TypeClassFile.unknow.rawValue && (mimeType == "text/x-markdown" || mimeType == "text/markdown") {
+                metadata.iconName = NKCommon.TypeIconFile.txt.rawValue
+                metadata.classFile = NKCommon.TypeClassFile.document.rawValue
+            }
+        }
+        if let iconUrl = iconUrl {
+            metadata.iconUrl = iconUrl
+        }
+
+        let fileName = fileName.trimmingCharacters(in: .whitespacesAndNewlines)
+
+        metadata.account = account
+        metadata.chunk = false
+        metadata.creationDate = Date() as NSDate
+        metadata.date = Date() as NSDate
+        metadata.hasPreview = true
+        metadata.etag = ocId
+        metadata.fileName = fileName
+        metadata.fileNameView = fileName
+        metadata.livePhoto = isLivePhoto
+        metadata.name = name
+        metadata.ocId = ocId
+        metadata.permissions = "RGDNVW"
+        metadata.serverUrl = serverUrl
+        metadata.subline = subline
+        metadata.uploadDate = Date() as NSDate
+        metadata.url = url
+        metadata.urlBase = urlBase
+        metadata.user = user
+        metadata.userId = userId
+        
+        if !metadata.urlBase.isEmpty, metadata.serverUrl.hasPrefix(metadata.urlBase) {
+            metadata.path = String(metadata.serverUrl.dropFirst(metadata.urlBase.count)) + "/"
+        }
+
+        return metadata
+    }
+
+    @discardableResult
+    func addMetadata(_ metadata: tableMetadata) -> tableMetadata? {
+
+        let realm = try! Realm()
+        let result = tableMetadata.init(value: metadata)
+
+        do {
+            try realm.write {
+                realm.add(result, update: .all)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+            return nil
+        }
+        return tableMetadata.init(value: result)
+    }
+
+    func addMetadatas(_ metadatas: [tableMetadata]) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                for metadata in metadatas {
+                    realm.add(metadata, update: .all)
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func deleteMetadata(predicate: NSPredicate) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                let results = realm.objects(tableMetadata.self).filter(predicate)
+                realm.delete(results)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func moveMetadata(ocId: String, serverUrlTo: String) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                if let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first {
+                    result.serverUrl = serverUrlTo
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func renameMetadata(fileNameTo: String, ocId: String) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                if let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first {
+                    let resultsType = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileNameTo, mimeType: "", directory: result.directory)
+                    result.fileName = fileNameTo
+                    result.fileNameView = fileNameTo
+                    result.iconName = resultsType.iconName
+                    result.contentType = resultsType.mimeType
+                    result.classFile = resultsType.classFile
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    @discardableResult
+    func updateMetadatas(_ metadatas: [tableMetadata], metadatasResult: [tableMetadata], addCompareLivePhoto: Bool = true, addExistsInLocal: Bool = false, addCompareEtagLocal: Bool = false, addDirectorySynchronized: Bool = false) -> (metadatasUpdate: [tableMetadata], metadatasLocalUpdate: [tableMetadata], metadatasDelete: [tableMetadata]) {
+
+        let realm = try! Realm()
+        var ocIdsUdate: [String] = []
+        var ocIdsLocalUdate: [String] = []
+        var metadatasDelete: [tableMetadata] = []
+        var metadatasUpdate: [tableMetadata] = []
+        var metadatasLocalUpdate: [tableMetadata] = []
+
+        do {
+            try realm.write {
+
+                // DELETE
+                for metadataResult in metadatasResult {
+                    if metadatas.firstIndex(where: { $0.ocId == metadataResult.ocId }) == nil {
+                        if let result = realm.objects(tableMetadata.self).filter(NSPredicate(format: "ocId == %@", metadataResult.ocId)).first {
+                            metadatasDelete.append(tableMetadata.init(value: result))
+                            realm.delete(result)
+                        }
+                    }
+                }
+
+                // UPDATE/NEW
+                for metadata in metadatas {
+
+                    if let result = metadatasResult.first(where: { $0.ocId == metadata.ocId }) {
+                        // update
+                        // Workaround: check lock bc no etag changes if lock runs out in directory
+                        // https://github.com/nextcloud/server/issues/8477
+                        if result.status == NCGlobal.shared.metadataStatusNormal &&
+                            (result.etag != metadata.etag ||
+                             result.fileNameView != metadata.fileNameView ||
+                             result.date != metadata.date ||
+                             result.permissions != metadata.permissions ||
+                             result.hasPreview != metadata.hasPreview ||
+                             result.note != metadata.note ||
+                             result.lock != metadata.lock ||
+                             result.shareType != metadata.shareType ||
+                             result.sharePermissionsCloudMesh != metadata.sharePermissionsCloudMesh ||
+                             result.sharePermissionsCollaborationServices != metadata.sharePermissionsCollaborationServices ||
+                             result.favorite != metadata.favorite ||
+                             result.tags != metadata.tags) {
+                            ocIdsUdate.append(metadata.ocId)
+                            realm.add(tableMetadata.init(value: metadata), update: .all)
+                        } else if result.status == NCGlobal.shared.metadataStatusNormal && addCompareLivePhoto && result.livePhoto != metadata.livePhoto {
+                            ocIdsUdate.append(metadata.ocId)
+                            realm.add(tableMetadata.init(value: metadata), update: .all)
+                        }
+                    } else {
+                        // new
+                        ocIdsUdate.append(metadata.ocId)
+                        realm.add(tableMetadata.init(value: metadata), update: .all)
+                    }
+
+                    if metadata.directory && !ocIdsUdate.contains(metadata.ocId) {
+                        let table = realm.objects(tableDirectory.self).filter(NSPredicate(format: "ocId == %@", metadata.ocId)).first
+                        if table?.etag != metadata.etag {
+                            ocIdsUdate.append(metadata.ocId)
+                        }
+                    }
+
+                    // Local
+                    if !metadata.directory && (addExistsInLocal || addCompareEtagLocal) {
+                        let localFile = realm.objects(tableLocalFile.self).filter(NSPredicate(format: "ocId == %@", metadata.ocId)).first
+                        if addCompareEtagLocal && localFile != nil && localFile?.etag != metadata.etag {
+                            ocIdsLocalUdate.append(metadata.ocId)
+                        }
+                        if addExistsInLocal && (localFile == nil || localFile?.etag != metadata.etag) && !ocIdsLocalUdate.contains(metadata.ocId) {
+                            ocIdsLocalUdate.append(metadata.ocId)
+                        }
+                    }
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+
+        for ocId in ocIdsUdate {
+            if let result = realm.objects(tableMetadata.self).filter(NSPredicate(format: "ocId == %@", ocId)).first {
+                metadatasUpdate.append(tableMetadata.init(value: result))
+            }
+        }
+
+        for ocId in ocIdsLocalUdate {
+            if let result = realm.objects(tableMetadata.self).filter(NSPredicate(format: "ocId == %@", ocId)).first {
+                metadatasLocalUpdate.append(tableMetadata.init(value: result))
+            }
+        }
+
+        return (metadatasUpdate, metadatasLocalUpdate, metadatasDelete)
+    }
+
+    func setMetadataSession(ocId: String, newFileName: String? = nil, session: String? = nil, sessionError: String? = nil, sessionSelector: String? = nil, sessionTaskIdentifier: Int? = nil, status: Int? = nil, etag: String? = nil) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first
+                if let newFileName = newFileName {
+                    result?.fileName = newFileName
+                    result?.fileNameView = newFileName
+                }
+                if let session = session {
+                    result?.session = session
+                }
+                if let sessionError = sessionError {
+                    result?.sessionError = sessionError
+                }
+                if let sessionSelector = sessionSelector {
+                    result?.sessionSelector = sessionSelector
+                }
+                if let sessionTaskIdentifier = sessionTaskIdentifier {
+                    result?.sessionTaskIdentifier = sessionTaskIdentifier
+                }
+                if let status = status {
+                    result?.status = status
+                }
+                if let etag = etag {
+                    result?.etag = etag
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    @discardableResult
+    func setMetadataStatus(ocId: String, status: Int) -> tableMetadata? {
+
+        let realm = try! Realm()
+        var result: tableMetadata?
+
+        do {
+            try realm.write {
+                result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first
+                result?.status = status
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+
+        if let result = result {
+            return tableMetadata.init(value: result)
+        } else {
+            return nil
+        }
+    }
+
+    func setMetadataEtagResource(ocId: String, etagResource: String?) {
+
+        let realm = try! Realm()
+        var result: tableMetadata?
+        guard let etagResource = etagResource else { return }
+
+        do {
+            try realm.write {
+                result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first
+                result?.etagResource = etagResource
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func setMetadataFavorite(ocId: String, favorite: Bool) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first
+                result?.favorite = favorite
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func updateMetadatasFavorite(account: String, metadatas: [tableMetadata]) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                let results = realm.objects(tableMetadata.self).filter("account == %@ AND favorite == true", account)
+                for result in results {
+                    result.favorite = false
+                }
+                for metadata in metadatas {
+                    realm.add(metadata, update: .all)
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func setMetadataEncrypted(ocId: String, encrypted: Bool) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first
+                result?.e2eEncrypted = encrypted
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func setMetadataFileNameView(serverUrl: String, fileName: String, newFileNameView: String, account: String) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                let result = realm.objects(tableMetadata.self).filter("account == %@ AND serverUrl == %@ AND fileName == %@", account, serverUrl, fileName).first
+                result?.fileNameView = newFileNameView
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func getMetadata(predicate: NSPredicate) -> tableMetadata? {
+
+        let realm = try! Realm()
+        realm.refresh()
+
+        guard let result = realm.objects(tableMetadata.self).filter(predicate).first else {
+            return nil
+        }
+
+        return tableMetadata.init(value: result)
+    }
+
+    func getMetadata(predicate: NSPredicate, sorted: String, ascending: Bool) -> tableMetadata? {
+
+        let realm = try! Realm()
+        realm.refresh()
+
+        guard let result = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: sorted, ascending: ascending).first else {
+            return nil
+        }
+
+        return tableMetadata.init(value: result)
+    }
+
+    func getMetadatasViewer(predicate: NSPredicate, sorted: String, ascending: Bool) -> [tableMetadata]? {
+
+        let realm = try! Realm()
+        realm.refresh()
+
+        let results: Results<tableMetadata>
+        var finals: [tableMetadata] = []
+
+        if (tableMetadata().objectSchema.properties.contains { $0.name == sorted }) {
+            results = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: sorted, ascending: ascending)
+        } else {
+            results = realm.objects(tableMetadata.self).filter(predicate)
+        }
+
+        // For Live Photo
+        var fileNameImages: [String] = []
+        let filtered = results.filter { $0.classFile.contains(NKCommon.TypeClassFile.image.rawValue) }
+        filtered.forEach { print($0)
+            let fileName = ($0.fileNameView as NSString).deletingPathExtension
+            fileNameImages.append(fileName)
+        }
+
+        for result in results {
+
+            let ext = (result.fileNameView as NSString).pathExtension.uppercased()
+            let fileName = (result.fileNameView as NSString).deletingPathExtension
+
+            if !(ext == "MOV" && fileNameImages.contains(fileName)) {
+                finals.append(result)
+            }
+        }
+
+        if finals.count > 0 {
+            return Array(finals.map { tableMetadata.init(value: $0) })
+        } else {
+            return nil
+        }
+    }
+
+    func getMetadatas(predicate: NSPredicate) -> [tableMetadata] {
+
+        let realm = try! Realm()
+        realm.refresh()
+
+        let results = realm.objects(tableMetadata.self).filter(predicate)
+
+        return Array(results.map { tableMetadata.init(value: $0) })
+    }
+
+    func getAdvancedMetadatas(predicate: NSPredicate, page: Int = 0, limit: Int = 0, sorted: String, ascending: Bool) -> [tableMetadata] {
+
+        let realm = try! Realm()
+        realm.refresh()
+        var metadatas: [tableMetadata] = []
+
+        let results = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: sorted, ascending: ascending)
+
+        if results.count > 0 {
+            if page == 0 || limit == 0 {
+                return Array(results.map { tableMetadata.init(value: $0) })
+            } else {
+
+                let nFrom = (page - 1) * limit
+                let nTo = nFrom + (limit - 1)
+
+                for n in nFrom...nTo {
+                    if n == results.count {
+                        break
+                    }
+                    metadatas.append(tableMetadata.init(value: results[n]))
+                }
+            }
+        }
+        return metadatas
+    }
+
+    func getMetadataAtIndex(predicate: NSPredicate, sorted: String, ascending: Bool, index: Int) -> tableMetadata? {
+
+        let realm = try! Realm()
+        realm.refresh()
+
+        let results = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: sorted, ascending: ascending)
+
+        if results.count > 0  && results.count > index {
+            return tableMetadata.init(value: results[index])
+        } else {
+            return nil
+        }
+    }
+
+    func getMetadataFromOcId(_ ocId: String?) -> tableMetadata? {
+
+        let realm = try! Realm()
+        realm.refresh()
+
+        guard let ocId = ocId else { return nil }
+        guard let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first else { return nil }
+
+        return tableMetadata.init(value: result)
+    }
+
+    func getMetadataFolder(account: String, urlBase: String, userId: String, serverUrl: String) -> tableMetadata? {
+
+        let realm = try! Realm()
+        realm.refresh()
+        var serverUrl = serverUrl
+        var fileName = ""
+
+        let serverUrlHome = NCUtilityFileSystem.shared.getHomeServer(urlBase: urlBase, userId: userId)
+        if serverUrlHome == serverUrl {
+            fileName = "."
+            serverUrl = ".."
+        } else {
+            fileName = (serverUrl as NSString).lastPathComponent
+            if let path = NCUtilityFileSystem.shared.deleteLastPath(serverUrlPath: serverUrl) {
+                serverUrl = path
+            }
+        }
+
+        guard let result = realm.objects(tableMetadata.self).filter("account == %@ AND serverUrl == %@ AND fileName == %@", account, serverUrl, fileName).first else { return nil }
+
+        return tableMetadata.init(value: result)
+    }
+
+    func getTableMetadatasDirectoryFavoriteIdentifierRank(account: String) -> [String: NSNumber] {
+
+        var listIdentifierRank: [String: NSNumber] = [:]
+        let realm = try! Realm()
+        var counter = 10 as Int64
+
+        let results = realm.objects(tableMetadata.self).filter("account == %@ AND directory == true AND favorite == true", account).sorted(byKeyPath: "fileNameView", ascending: true)
+
+        for result in results {
+            counter += 1
+            listIdentifierRank[result.ocId] = NSNumber(value: Int64(counter))
+        }
+
+        return listIdentifierRank
+    }
+
+    @objc func clearMetadatasUpload(account: String) {
+
+        let realm = try! Realm()
+        realm.refresh()
+
+        do {
+            try realm.write {
+
+                let results = realm.objects(tableMetadata.self).filter("account == %@ AND (status == %d OR status == %@)", account, NCGlobal.shared.metadataStatusWaitUpload, NCGlobal.shared.metadataStatusUploadError)
+                realm.delete(results)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func readMarkerMetadata(account: String, fileId: String) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                let results = realm.objects(tableMetadata.self).filter("account == %@ AND fileId == %@", account, fileId)
+                for result in results {
+                    result.commentsUnread = false
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func getAssetLocalIdentifiersUploaded(account: String) -> [String] {
+
+        let realm = try! Realm()
+        realm.refresh()
+
+        var assetLocalIdentifiers: [String] = []
+
+        let results = realm.objects(tableMetadata.self).filter("account == %@ AND assetLocalIdentifier != '' AND deleteAssetLocalIdentifier == true", account)
+        for result in results {
+            assetLocalIdentifiers.append(result.assetLocalIdentifier)
+        }
+
+        return assetLocalIdentifiers
+    }
+
+    func clearAssetLocalIdentifiers(_ assetLocalIdentifiers: [String], account: String) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                let results = realm.objects(tableMetadata.self).filter("account == %@ AND assetLocalIdentifier IN %@", account, assetLocalIdentifiers)
+                for result in results {
+                    result.assetLocalIdentifier = ""
+                    result.deleteAssetLocalIdentifier = false
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func getMetadataLivePhoto(metadata: tableMetadata) -> tableMetadata? {
+
+        let realm = try! Realm()
+        var classFile = metadata.classFile
+        var fileName = (metadata.fileNameView as NSString).deletingPathExtension
+
+        if !metadata.livePhoto || !CCUtility.getLivePhoto() {
+            return nil
+        }
+
+        if classFile == NKCommon.TypeClassFile.image.rawValue {
+            classFile = NKCommon.TypeClassFile.video.rawValue
+            fileName = fileName + ".mov"
+        } else {
+            classFile = NKCommon.TypeClassFile.image.rawValue
+            fileName = fileName + ".jpg"
+        }
+
+        guard let result = realm.objects(tableMetadata.self).filter(NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileNameView CONTAINS[cd] %@ AND ocId != %@ AND classFile == %@", metadata.account, metadata.serverUrl, fileName, metadata.ocId, classFile)).first else {
+            return nil
+        }
+
+        return tableMetadata.init(value: result)
+    }
+
+    func getMetadatasMedia(predicate: NSPredicate, livePhoto: Bool) -> [tableMetadata] {
+
+        let realm = try! Realm()
+        var metadatas: [tableMetadata] = []
+
+        do {
+            try realm.write {
+                let sortProperties = [SortDescriptor(keyPath: "serverUrl", ascending: false), SortDescriptor(keyPath:  "fileNameView", ascending: false)]
+                let results = realm.objects(tableMetadata.self).filter(predicate).sorted(by: sortProperties)
+                if livePhoto {
+                    for index in results.indices {
+                        let metadata = results[index]
+                        if index < results.count - 1, metadata.fileNoExtension == results[index+1].fileNoExtension {
+                            if !metadata.livePhoto {
+                                metadata.livePhoto = true
+                            }
+                            if !results[index+1].livePhoto {
+                                results[index+1].livePhoto = true
+                            }
+                        }
+                        if metadata.livePhoto {
+                            if metadata.classFile == NKCommon.TypeClassFile.image.rawValue {
+                                metadatas.append(tableMetadata.init(value: metadata))
+                            }
+                            continue
+                        } else {
+                            metadatas.append(tableMetadata.init(value: metadata))
+                        }
+                    }
+                } else {
+                    metadatas = Array(results.map { tableMetadata.init(value: $0) })
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+
+        return metadatas
+    }
+
+    func isMetadataShareOrMounted(metadata: tableMetadata, metadataFolder: tableMetadata?) -> Bool {
+
+        var isShare = false
+        var isMounted = false
+
+        if metadataFolder != nil {
+
+            isShare = metadata.permissions.contains(NCGlobal.shared.permissionShared) && !metadataFolder!.permissions.contains(NCGlobal.shared.permissionShared)
+            isMounted = metadata.permissions.contains(NCGlobal.shared.permissionMounted) && !metadataFolder!.permissions.contains(NCGlobal.shared.permissionMounted)
+
+        } else if let directory = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", metadata.account, metadata.serverUrl)) {
+
+            isShare = metadata.permissions.contains(NCGlobal.shared.permissionShared) && !directory.permissions.contains(NCGlobal.shared.permissionShared)
+            isMounted = metadata.permissions.contains(NCGlobal.shared.permissionMounted) && !directory.permissions.contains(NCGlobal.shared.permissionMounted)
+        }
+
+        if isShare || isMounted {
+            return true
+        } else {
+            return false
+        }
+    }
+
+    func isDownloadMetadata(_ metadata: tableMetadata, download: Bool) -> Bool {
+
+        let localFile = getTableLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
+        let fileSize = CCUtility.fileProviderStorageSize(metadata.ocId, fileNameView: metadata.fileNameView)
+        if (localFile != nil || download) && (localFile?.etag != metadata.etag || fileSize == 0) {
+            return true
+        }
+        return false
+    }
+
+    func getMetadataConflict(account: String, serverUrl: String, fileNameView: String) -> tableMetadata? {
+
+        // verify exists conflict
+        let fileNameExtension = (fileNameView as NSString).pathExtension.lowercased()
+        let fileNameNoExtension = (fileNameView as NSString).deletingPathExtension
+        var fileNameConflict = fileNameView
+
+        if fileNameExtension == "heic" && CCUtility.getFormatCompatibility() {
+            fileNameConflict = fileNameNoExtension + ".jpg"
+        }
+        return getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileNameView == %@", account, serverUrl, fileNameConflict))
+    }
+
+    func getSubtitles(account: String, serverUrl: String, fileName: String) -> (all:[tableMetadata], existing:[tableMetadata]) {
+
+        let realm = try! Realm()
+        let nameOnly = (fileName as NSString).deletingPathExtension + "."
+        var metadatas: [tableMetadata] = []
+
+        let results = realm.objects(tableMetadata.self).filter("account == %@ AND serverUrl == %@ AND fileName BEGINSWITH[c] %@ AND fileName ENDSWITH[c] '.srt'", account, serverUrl, nameOnly)
+        for result in results {
+            if CCUtility.fileProviderStorageExists(result) {
+                metadatas.append(result)
+            }
+        }
+        return(Array(results.map { tableMetadata.init(value: $0) }), Array(metadatas.map { tableMetadata.init(value: $0) }))
+    }
+
+    func getNumMetadatasInUpload() -> Int {
+
+        let realm = try! Realm()
+
+        let num = realm.objects(tableMetadata.self).filter(NSPredicate(format: "status == %i || status == %i",  NCGlobal.shared.metadataStatusInUpload, NCGlobal.shared.metadataStatusUploading)).count
+
+        return num
+    }
+
+    func getMetadataFromDirectory(account: String, serverUrl: String) -> tableMetadata? {
+
+        let realm = try! Realm()
+
+        guard let directory = realm.objects(tableDirectory.self).filter("account == %@ AND serverUrl == %@", account, serverUrl).first else { return nil }
+        guard let result = realm.objects(tableMetadata.self).filter("ocId == %@", directory.ocId).first else { return nil }
+
+        return tableMetadata.init(value: result)
+    }
+
+    func getMetadatasFromGroupfolders(account: String, urlBase: String, userId: String) -> [tableMetadata] {
+
+        let realm = try! Realm()
+        var metadatas: [tableMetadata] = []
+        let homeServerUrl = NCUtilityFileSystem.shared.getHomeServer(urlBase: urlBase, userId: userId)
+
+        let groupfolders = realm.objects(TableGroupfolders.self).filter("account == %@", account)
+        for groupfolder in groupfolders {
+            let serverUrlFileName = homeServerUrl + groupfolder.mountPoint
+            if let directory = realm.objects(tableDirectory.self).filter("account == %@ AND serverUrl == %@", account, serverUrlFileName).first,
+               let metadata = realm.objects(tableMetadata.self).filter("ocId == %@", directory.ocId).first {
+                metadatas.append(tableMetadata(value: metadata))
+            }
+        }
+
+        return metadatas
+    }
+}

+ 233 - 0
iOSClient/Data/NCManageDatabase+Share.swift

@@ -0,0 +1,233 @@
+//
+//  NCManageDatabase+Share.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 20/01/23.
+//  Copyright © 2023 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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 Foundation
+import RealmSwift
+import NextcloudKit
+
+typealias tableShare = tableShareV2
+class tableShareV2: Object {
+
+    @objc dynamic var account = ""
+    @objc dynamic var canEdit: Bool = false
+    @objc dynamic var canDelete: Bool = false
+    @objc dynamic var date: NSDate?
+    @objc dynamic var displaynameFileOwner = ""
+    @objc dynamic var displaynameOwner = ""
+    @objc dynamic var expirationDate: NSDate?
+    @objc dynamic var fileName = ""
+    @objc dynamic var fileParent: Int = 0
+    @objc dynamic var fileSource: Int = 0
+    @objc dynamic var fileTarget = ""
+    @objc dynamic var hideDownload: Bool = false
+    @objc dynamic var idShare: Int = 0
+    @objc dynamic var itemSource: Int = 0
+    @objc dynamic var itemType = ""
+    @objc dynamic var label = ""
+    @objc dynamic var mailSend: Bool = false
+    @objc dynamic var mimeType = ""
+    @objc dynamic var note = ""
+    @objc dynamic var parent: String = ""
+    @objc dynamic var password: String = ""
+    @objc dynamic var path = ""
+    @objc dynamic var permissions: Int = 0
+    @objc dynamic var primaryKey = ""
+    @objc dynamic var sendPasswordByTalk: Bool = false
+    @objc dynamic var serverUrl = ""
+    @objc dynamic var shareType: Int = 0
+    @objc dynamic var shareWith = ""
+    @objc dynamic var shareWithDisplayname = ""
+    @objc dynamic var storage: Int = 0
+    @objc dynamic var storageId = ""
+    @objc dynamic var token = ""
+    @objc dynamic var uidFileOwner = ""
+    @objc dynamic var uidOwner = ""
+    @objc dynamic var url = ""
+    @objc dynamic var userClearAt: NSDate?
+    @objc dynamic var userIcon = ""
+    @objc dynamic var userMessage = ""
+    @objc dynamic var userStatus = ""
+
+    override static func primaryKey() -> String {
+        return "primaryKey"
+    }
+}
+
+extension NCManageDatabase {
+
+    func addShare(account: String, home: String, shares: [NKShare]) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+
+                for share in shares {
+
+                    let serverUrlPath = home + share.path
+                    guard let serverUrl = NCUtilityFileSystem.shared.deleteLastPath(serverUrlPath: serverUrlPath, home: home) else {
+                        continue
+                    }
+
+                    let object = tableShare()
+
+                    object.account = account
+                    if let fileName = share.path.components(separatedBy: "/").last {
+                        object.fileName = fileName
+                    }
+                    object.serverUrl = serverUrl
+
+                    object.canEdit = share.canEdit
+                    object.canDelete = share.canDelete
+                    object.date = share.date
+                    object.displaynameFileOwner = share.displaynameFileOwner
+                    object.displaynameOwner = share.displaynameOwner
+                    object.expirationDate = share.expirationDate
+                    object.fileParent = share.fileParent
+                    object.fileSource = share.fileSource
+                    object.fileTarget = share.fileTarget
+                    object.hideDownload = share.hideDownload
+                    object.idShare = share.idShare
+                    object.itemSource = share.itemSource
+                    object.itemType = share.itemType
+                    object.label = share.label
+                    object.mailSend = share.mailSend
+                    object.mimeType = share.mimeType
+                    object.note = share.note
+                    object.parent = share.parent
+                    object.password = share.password
+                    object.path = share.path
+                    object.permissions = share.permissions
+                    object.primaryKey = account + " " + String(share.idShare)
+                    object.sendPasswordByTalk = share.sendPasswordByTalk
+                    object.shareType = share.shareType
+                    object.shareWith = share.shareWith
+                    object.shareWithDisplayname = share.shareWithDisplayname
+                    object.storage = share.storage
+                    object.storageId = share.storageId
+                    object.token = share.token
+                    object.uidOwner = share.uidOwner
+                    object.uidFileOwner = share.uidFileOwner
+                    object.url = share.url
+                    object.userClearAt = share.userClearAt
+                    object.userIcon = share.userIcon
+                    object.userMessage = share.userMessage
+                    object.userStatus = share.userStatus
+
+                    realm.add(object, update: .all)
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func getTableShares(account: String) -> [tableShare] {
+
+        let realm = try! Realm()
+
+        let sortProperties = [SortDescriptor(keyPath: "shareType", ascending: false), SortDescriptor(keyPath: "idShare", ascending: false)]
+        let results = realm.objects(tableShare.self).filter("account == %@", account).sorted(by: sortProperties)
+
+        return Array(results.map { tableShare.init(value: $0) })
+    }
+
+    func getTableShares(metadata: tableMetadata) -> (firstShareLink: tableShare?, share: [tableShare]?) {
+
+        let realm = try! Realm()
+
+        let sortProperties = [SortDescriptor(keyPath: "shareType", ascending: false), SortDescriptor(keyPath: "idShare", ascending: false)]
+        let firstShareLink = realm.objects(tableShare.self).filter("account == %@ AND serverUrl == %@ AND fileName == %@ AND shareType == 3", metadata.account, metadata.serverUrl, metadata.fileName).first
+
+        if let firstShareLink = firstShareLink {
+            let results = realm.objects(tableShare.self).filter("account == %@ AND serverUrl == %@ AND fileName == %@ AND idShare != %d", metadata.account, metadata.serverUrl, metadata.fileName, firstShareLink.idShare).sorted(by: sortProperties)
+            return(firstShareLink: tableShare.init(value: firstShareLink), share: Array(results.map { tableShare.init(value: $0) }))
+        } else {
+            let results = realm.objects(tableShare.self).filter("account == %@ AND serverUrl == %@ AND fileName == %@", metadata.account, metadata.serverUrl, metadata.fileName).sorted(by: sortProperties)
+            return(firstShareLink: firstShareLink, share: Array(results.map { tableShare.init(value: $0) }))
+        }
+    }
+
+    func getTableShare(account: String, idShare: Int) -> tableShare? {
+
+        let realm = try! Realm()
+
+        guard let result = realm.objects(tableShare.self).filter("account = %@ AND idShare = %d", account, idShare).first else {
+            return nil
+        }
+
+        return tableShare.init(value: result)
+    }
+
+    func getTableShares(account: String, serverUrl: String) -> [tableShare] {
+
+        let realm = try! Realm()
+
+        let sortProperties = [SortDescriptor(keyPath: "shareType", ascending: false), SortDescriptor(keyPath: "idShare", ascending: false)]
+        let results = realm.objects(tableShare.self).filter("account == %@ AND serverUrl == %@", account, serverUrl).sorted(by: sortProperties)
+
+        return Array(results.map { tableShare.init(value: $0) })
+    }
+
+    func getTableShares(account: String, serverUrl: String, fileName: String) -> [tableShare] {
+
+        let realm = try! Realm()
+
+        let sortProperties = [SortDescriptor(keyPath: "shareType", ascending: false), SortDescriptor(keyPath: "idShare", ascending: false)]
+        let results = realm.objects(tableShare.self).filter("account == %@ AND serverUrl == %@ AND fileName == %@", account, serverUrl, fileName).sorted(by: sortProperties)
+
+        return Array(results.map { tableShare.init(value: $0) })
+    }
+
+    func deleteTableShare(account: String, idShare: Int) {
+
+        let realm = try! Realm()
+
+        realm.beginWrite()
+
+        let result = realm.objects(tableShare.self).filter("account == %@ AND idShare == %d", account, idShare)
+        realm.delete(result)
+
+        do {
+            try realm.commitWrite()
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func deleteTableShare(account: String) {
+
+        let realm = try! Realm()
+
+        realm.beginWrite()
+
+        let result = realm.objects(tableShare.self).filter("account == %@", account)
+        realm.delete(result)
+
+        do {
+            try realm.commitWrite()
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+}

+ 143 - 0
iOSClient/Data/NCManageDatabase+Video.swift

@@ -0,0 +1,143 @@
+//
+//  NCManageDatabase+Video.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 15/03/22.
+//  Copyright © 2022 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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 Foundation
+import RealmSwift
+import NextcloudKit
+
+class tableVideo: Object {
+
+    @objc dynamic var account = ""
+    @objc dynamic var ocId = ""
+    @objc dynamic var position: Float = 0
+    @objc dynamic var codecNameVideo: String?
+    @objc dynamic var codecNameAudio: String?
+    @objc dynamic var codecAudioChannelLayout: String?
+    @objc dynamic var codecAudioLanguage: String?
+    @objc dynamic var codecMaxCompatibility: Bool = false
+    @objc dynamic var codecQuality: String?
+
+    override static func primaryKey() -> String {
+        return "ocId"
+    }
+}
+
+extension NCManageDatabase {
+
+    func addVideo(metadata: tableMetadata, position: Float) {
+
+        if metadata.livePhoto { return }
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                if let result = realm.objects(tableVideo.self).filter("account == %@ AND ocId == %@", metadata.account, metadata.ocId).first {
+
+                    result.position = position
+                    realm.add(result, update: .all)
+
+                } else {
+
+                    let addObject = tableVideo()
+
+                    addObject.account = metadata.account
+                    addObject.ocId = metadata.ocId
+                    addObject.position = position
+                    realm.add(addObject, update: .all)
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func addVideoCodec(metadata: tableMetadata, codecNameVideo: String?, codecNameAudio: String?, codecAudioChannelLayout: String?, codecAudioLanguage: String?, codecMaxCompatibility: Bool, codecQuality: String?) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                if let result = realm.objects(tableVideo.self).filter("account == %@ AND ocId == %@", metadata.account, metadata.ocId).first {
+                    if let codecNameVideo = codecNameVideo { result.codecNameVideo = codecNameVideo }
+                    if let codecNameAudio = codecNameAudio { result.codecNameAudio = codecNameAudio }
+                    if let codecAudioChannelLayout = codecAudioChannelLayout { result.codecAudioChannelLayout = codecAudioChannelLayout }
+                    if let codecAudioLanguage = codecAudioLanguage { result.codecAudioLanguage = codecAudioLanguage }
+                    result.codecMaxCompatibility = codecMaxCompatibility
+                    if let codecQuality = codecQuality { result.codecQuality = codecQuality }
+                    realm.add(result, update: .all)
+                } else {
+                    let addObject = tableVideo()
+                    addObject.account = metadata.account
+                    addObject.ocId = metadata.ocId
+                    addObject.codecNameVideo = codecNameVideo
+                    addObject.codecNameAudio = codecNameAudio
+                    addObject.codecAudioChannelLayout = codecAudioChannelLayout
+                    addObject.codecAudioLanguage = codecAudioLanguage
+                    addObject.codecMaxCompatibility = codecMaxCompatibility
+                    addObject.codecQuality = codecQuality
+                    realm.add(addObject, update: .all)
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func getVideo(metadata: tableMetadata?) -> tableVideo? {
+        guard let metadata = metadata else { return nil }
+
+        let realm = try! Realm()
+        guard let result = realm.objects(tableVideo.self).filter("account == %@ AND ocId == %@", metadata.account, metadata.ocId).first else {
+            return nil
+        }
+
+        return tableVideo.init(value: result)
+    }
+
+    func getVideoPosition(metadata: tableMetadata) -> Float? {
+
+        if metadata.livePhoto { return nil }
+        let realm = try! Realm()
+
+        guard let result = realm.objects(tableVideo.self).filter("account == %@ AND ocId == %@", metadata.account, metadata.ocId).first else {
+            return nil
+        }
+
+        if result.position == 0 { return nil }
+        return result.position
+    }
+
+    func deleteVideo(metadata: tableMetadata) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                let result = realm.objects(tableVideo.self).filter("account == %@ AND ocId == %@", metadata.account, metadata.ocId)
+                realm.delete(result)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+}

+ 1086 - 0
iOSClient/Data/NCManageDatabase.swift

@@ -0,0 +1,1086 @@
+//
+//  NCManageDatabase.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 06/05/17.
+//  Copyright © 2017 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//  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
+import RealmSwift
+import NextcloudKit
+import SwiftyJSON
+import CoreMedia
+import Photos
+
+class NCManageDatabase: NSObject {
+    @objc static let shared: NCManageDatabase = {
+        let instance = NCManageDatabase()
+        return instance
+    }()
+
+    override init() {
+
+        let dirGroup = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: NCBrandOptions.shared.capabilitiesGroups)
+        let databaseFileUrlPath = dirGroup?.appendingPathComponent(NCGlobal.shared.appDatabaseNextcloud + "/" + databaseName)
+
+        let bundleUrl: URL = Bundle.main.bundleURL
+        let bundlePathExtension: String = bundleUrl.pathExtension
+        let isAppex: Bool = bundlePathExtension == "appex"
+
+        if let databaseFilePath = databaseFileUrlPath?.path {
+            if FileManager.default.fileExists(atPath: databaseFilePath) {
+                NextcloudKit.shared.nkCommonInstance.writeLog("DATABASE FOUND in " + databaseFilePath)
+            } else {
+                NextcloudKit.shared.nkCommonInstance.writeLog("DATABASE NOT FOUND in " + databaseFilePath)
+            }
+        }
+
+        // Disable file protection for directory DB
+        // https://docs.mongodb.com/realm/sdk/ios/examples/configure-and-open-a-realm/#std-label-ios-open-a-local-realm
+        if let folderPathURL = dirGroup?.appendingPathComponent(NCGlobal.shared.appDatabaseNextcloud) {
+            let folderPath = folderPathURL.path
+            do {
+                try FileManager.default.setAttributes([FileAttributeKey.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication], ofItemAtPath: folderPath)
+            } catch {
+                print("Dangerous error")
+            }
+        }
+
+        if isAppex {
+
+            // App Extension config
+
+            let config = Realm.Configuration(
+                fileURL: dirGroup?.appendingPathComponent(NCGlobal.shared.appDatabaseNextcloud + "/" + databaseName),
+                schemaVersion: databaseSchemaVersion,
+                objectTypes: [tableMetadata.self, tableLocalFile.self, tableDirectory.self, tableTag.self, tableAccount.self, tableCapabilities.self, tablePhotoLibrary.self, tableE2eEncryption.self, tableE2eEncryptionLock.self, tableShare.self, tableChunk.self, tableAvatar.self, tableDashboardWidget.self, tableDashboardWidgetButton.self, NCDBLayoutForView.self]
+            )
+
+            Realm.Configuration.defaultConfiguration = config
+
+        } else {
+
+            // App config
+
+            let configCompact = Realm.Configuration(
+
+                fileURL: databaseFileUrlPath,
+                schemaVersion: databaseSchemaVersion,
+
+                migrationBlock: { migration, oldSchemaVersion in
+
+                    if oldSchemaVersion < 255 {
+                        migration.deleteData(forType: tableActivity.className())
+                        migration.deleteData(forType: tableActivityLatestId.className())
+                        migration.deleteData(forType: tableActivityPreview.className())
+                        migration.deleteData(forType: tableActivitySubjectRich.className())
+                        migration.deleteData(forType: tableDirectory.className())
+                        migration.deleteData(forType: tableMetadata.className())
+                    }
+
+                }, shouldCompactOnLaunch: { totalBytes, usedBytes in
+
+                    // totalBytes refers to the size of the file on disk in bytes (data + free space)
+                    // usedBytes refers to the number of bytes used by data in the file
+
+                    // Compact if the file is over 100MB in size and less than 50% 'used'
+                    let oneHundredMB = 100 * 1024 * 1024
+                    return (totalBytes > oneHundredMB) && (Double(usedBytes) / Double(totalBytes)) < 0.5
+                }
+            )
+
+            do {
+                _ = try Realm(configuration: configCompact)
+            } catch {
+                if let databaseFileUrlPath = databaseFileUrlPath {
+                    do {
+#if !EXTENSION
+                        let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_database_corrupt_")
+                        NCContentPresenter.shared.showError(error: error, priority: .max)
+#endif
+                        NextcloudKit.shared.nkCommonInstance.writeLog("DATABASE CORRUPT: removed")
+                        try FileManager.default.removeItem(at: databaseFileUrlPath)
+                    } catch {}
+                }
+            }
+
+            let config = Realm.Configuration(
+                fileURL: dirGroup?.appendingPathComponent(NCGlobal.shared.appDatabaseNextcloud + "/" + databaseName),
+                schemaVersion: databaseSchemaVersion
+            )
+
+            Realm.Configuration.defaultConfiguration = config
+        }
+
+        // Verify Database, if corrupt remove it
+        do {
+            _ = try Realm()
+        } catch {
+            if let databaseFileUrlPath = databaseFileUrlPath {
+                do {
+#if !EXTENSION
+                    let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_database_corrupt_")
+                    NCContentPresenter.shared.showError(error: error, priority: .max)
+#endif
+                    NextcloudKit.shared.nkCommonInstance.writeLog("DATABASE CORRUPT: removed")
+                    try FileManager.default.removeItem(at: databaseFileUrlPath)
+                } catch {}
+            }
+        }
+
+        // Open Real
+        _ = try! Realm()
+    }
+
+    // MARK: -
+    // MARK: Utility Database
+
+    @objc func clearTable(_ table: Object.Type, account: String? = nil) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                var results: Results<Object>
+
+                if let account = account {
+                    results = realm.objects(table).filter("account == %@", account)
+                } else {
+                    results = realm.objects(table)
+                }
+
+                realm.delete(results)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    @objc func clearDatabase(account: String?, removeAccount: Bool) {
+
+        self.clearTable(tableActivity.self, account: account)
+        self.clearTable(tableActivityLatestId.self, account: account)
+        self.clearTable(tableActivityPreview.self, account: account)
+        self.clearTable(tableActivitySubjectRich.self, account: account)
+        self.clearTable(tableAvatar.self)
+        self.clearTable(tableCapabilities.self, account: account)
+        self.clearTable(tableChunk.self, account: account)
+        self.clearTable(tableComments.self, account: account)
+        self.clearTable(tableDashboardWidget.self, account: account)
+        self.clearTable(tableDashboardWidgetButton.self, account: account)
+        self.clearTable(tableDirectEditingCreators.self, account: account)
+        self.clearTable(tableDirectEditingEditors.self, account: account)
+        self.clearTable(tableDirectory.self, account: account)
+        self.clearTable(tableE2eEncryption.self, account: account)
+        self.clearTable(tableE2eEncryptionLock.self, account: account)
+        self.clearTable(tableExternalSites.self, account: account)
+        self.clearTable(tableGPS.self, account: nil)
+        self.clearTable(TableGroupfolders.self, account: account)
+        self.clearTable(TableGroupfoldersGroups.self, account: account)
+        self.clearTable(NCDBLayoutForView.self, account: account)
+        self.clearTable(tableLocalFile.self, account: account)
+        self.clearTable(tableMetadata.self, account: account)
+        self.clearTable(tablePhotoLibrary.self, account: account)
+        self.clearTable(tableShare.self, account: account)
+        self.clearTable(tableTag.self, account: account)
+        self.clearTable(tableTip.self)
+        self.clearTable(tableTrash.self, account: account)
+        self.clearTable(tableUserStatus.self, account: account)
+        self.clearTable(tableVideo.self, account: account)
+
+        if removeAccount {
+            self.clearTable(tableAccount.self, account: account)
+        }
+    }
+
+    @objc func removeDB() {
+
+        let realmURL = Realm.Configuration.defaultConfiguration.fileURL!
+        let realmURLs = [
+            realmURL,
+            realmURL.appendingPathExtension("lock"),
+            realmURL.appendingPathExtension("note"),
+            realmURL.appendingPathExtension("management")
+        ]
+        for URL in realmURLs {
+            do {
+                try FileManager.default.removeItem(at: URL)
+            } catch let error {
+                NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+            }
+        }
+    }
+
+    @objc func getThreadConfined(_ object: Object) -> Any {
+
+        // id tradeReference = [[NCManageDatabase shared] getThreadConfined:metadata];
+        return ThreadSafeReference(to: object)
+    }
+
+    @objc func putThreadConfined(_ tableRef: Any) -> Object? {
+
+        // tableMetadata *metadataThread = (tableMetadata *)[[NCManageDatabase shared] putThreadConfined:tradeReference];
+        let realm = try! Realm()
+
+        return realm.resolve(tableRef as! ThreadSafeReference<Object>)
+    }
+
+    @objc func isTableInvalidated(_ object: Object) -> Bool {
+
+        return object.isInvalidated
+    }
+
+    // MARK: -
+    // MARK: Table Avatar
+
+    
+    // MARK: -
+    // MARK: Table Capabilities
+
+    func addCapabilitiesJSon(_ data: Data, account: String) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                let addObject = tableCapabilities()
+
+                addObject.account = account
+                addObject.jsondata = data
+
+                realm.add(addObject, update: .all)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func getCapabilities(account: String) -> String? {
+
+        let realm = try! Realm()
+
+        guard let result = realm.objects(tableCapabilities.self).filter("account == %@", account).first else {
+            return nil
+        }
+        guard let jsondata = result.jsondata else {
+            return nil
+        }
+
+        let json = JSON(jsondata)
+
+        return json.rawString()?.replacingOccurrences(of: "\\/", with: "/")
+    }
+
+    @objc func getCapabilitiesServerString(account: String, elements: [String]) -> String? {
+
+        let realm = try! Realm()
+
+        guard let result = realm.objects(tableCapabilities.self).filter("account == %@", account).first else {
+            return nil
+        }
+        guard let jsondata = result.jsondata else {
+            return nil
+        }
+
+        let json = JSON(jsondata)
+        return json[elements].string
+    }
+
+    func getCapabilitiesServerInt(account: String, elements: [String]) -> Int {
+
+        let realm = try! Realm()
+
+        guard let result = realm.objects(tableCapabilities.self).filter("account == %@", account).first,
+              let jsondata = result.jsondata else {
+            return 0
+        }
+
+        let json = JSON(jsondata)
+        return json[elements].intValue
+    }
+
+    @objc func getCapabilitiesServerBool(account: String, elements: [String], exists: Bool) -> Bool {
+
+        let realm = try! Realm()
+
+        guard let result = realm.objects(tableCapabilities.self).filter("account == %@", account).first else {
+            return false
+        }
+        guard let jsondata = result.jsondata else {
+            return false
+        }
+
+        let json = JSON(jsondata)
+        if exists {
+            return json[elements].exists()
+        } else {
+            return json[elements].boolValue
+        }
+    }
+
+    func getCapabilitiesServerArray(account: String, elements: [String]) -> [String]? {
+
+        let realm = try! Realm()
+        var resultArray: [String] = []
+
+        guard let result = realm.objects(tableCapabilities.self).filter("account == %@", account).first else {
+            return nil
+        }
+        guard let jsondata = result.jsondata else {
+            return nil
+        }
+
+        let json = JSON(jsondata)
+
+        if let results = json[elements].array {
+            for result in results {
+                resultArray.append(result.string ?? "")
+            }
+            return resultArray
+        }
+
+        return nil
+    }
+
+    // MARK: -
+    // MARK: Table Chunk
+
+    func getChunkFolder(account: String, ocId: String) -> String {
+
+        let realm = try! Realm()
+
+        if let result = realm.objects(tableChunk.self).filter("account == %@ AND ocId == %@", account, ocId).first {
+            return result.chunkFolder
+        }
+
+        return NSUUID().uuidString
+    }
+
+    func getChunks(account: String, ocId: String) -> [String] {
+
+        let realm = try! Realm()
+        var filesNames: [String] = []
+
+        let results = realm.objects(tableChunk.self).filter("account == %@ AND ocId == %@", account, ocId).sorted(byKeyPath: "fileName", ascending: true)
+        for result in results {
+            filesNames.append(result.fileName)
+        }
+
+        return filesNames
+    }
+
+    func addChunks(account: String, ocId: String, chunkFolder: String, fileNames: [String]) {
+
+        let realm = try! Realm()
+        var size: Int64 = 0
+
+        do {
+            try realm.write {
+
+                for fileName in fileNames {
+
+                    let object = tableChunk()
+                    size += NCUtilityFileSystem.shared.getFileSize(filePath: CCUtility.getDirectoryProviderStorageOcId(ocId, fileNameView: fileName)!)
+
+                    object.account = account
+                    object.chunkFolder = chunkFolder
+                    object.fileName = fileName
+                    object.index = ocId + fileName
+                    object.ocId = ocId
+                    object.size = size
+
+                    realm.add(object, update: .all)
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func getChunk(account: String, fileName: String) -> tableChunk? {
+
+        let realm = try! Realm()
+
+        if let result = realm.objects(tableChunk.self).filter("account == %@ AND fileName == %@", account, fileName).first {
+            return tableChunk.init(value: result)
+        } else {
+            return nil
+        }
+    }
+
+    func deleteChunk(account: String, ocId: String, fileName: String) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+
+                let result = realm.objects(tableChunk.self).filter(NSPredicate(format: "account == %@ AND ocId == %@ AND fileName == %@", account, ocId, fileName))
+                realm.delete(result)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func deleteChunks(account: String, ocId: String) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+
+                let result = realm.objects(tableChunk.self).filter(NSPredicate(format: "account == %@ AND ocId == %@", account, ocId))
+                realm.delete(result)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    // MARK: -
+    // MARK: Table Direct Editing
+
+    func addDirectEditing(account: String, editors: [NKEditorDetailsEditors], creators: [NKEditorDetailsCreators]) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+
+                let resultsCreators = realm.objects(tableDirectEditingCreators.self).filter("account == %@", account)
+                realm.delete(resultsCreators)
+
+                let resultsEditors = realm.objects(tableDirectEditingEditors.self).filter("account == %@", account)
+                realm.delete(resultsEditors)
+
+                for creator in creators {
+
+                    let addObject = tableDirectEditingCreators()
+
+                    addObject.account = account
+                    addObject.editor = creator.editor
+                    addObject.ext = creator.ext
+                    addObject.identifier = creator.identifier
+                    addObject.mimetype = creator.mimetype
+                    addObject.name = creator.name
+                    addObject.templates = creator.templates
+
+                    realm.add(addObject)
+                }
+
+                for editor in editors {
+
+                    let addObject = tableDirectEditingEditors()
+
+                    addObject.account = account
+                    for mimeType in editor.mimetypes {
+                        addObject.mimetypes.append(mimeType)
+                    }
+                    addObject.name = editor.name
+                    if editor.name.lowercased() == NCGlobal.shared.editorOnlyoffice {
+                        addObject.editor = NCGlobal.shared.editorOnlyoffice
+                    } else {
+                        addObject.editor = NCGlobal.shared.editorText
+                    }
+                    for mimeType in editor.optionalMimetypes {
+                        addObject.optionalMimetypes.append(mimeType)
+                    }
+                    addObject.secure = editor.secure
+
+                    realm.add(addObject)
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func getDirectEditingCreators(account: String) -> [tableDirectEditingCreators]? {
+
+        let realm = try! Realm()
+        let results = realm.objects(tableDirectEditingCreators.self).filter("account == %@", account)
+
+        if results.count > 0 {
+            return Array(results.map { tableDirectEditingCreators.init(value: $0) })
+        } else {
+            return nil
+        }
+    }
+
+    func getDirectEditingCreators(predicate: NSPredicate) -> [tableDirectEditingCreators]? {
+
+        let realm = try! Realm()
+
+        let results = realm.objects(tableDirectEditingCreators.self).filter(predicate)
+
+        if results.count > 0 {
+            return Array(results.map { tableDirectEditingCreators.init(value: $0) })
+        } else {
+            return nil
+        }
+    }
+
+    func getDirectEditingEditors(account: String) -> [tableDirectEditingEditors]? {
+
+        let realm = try! Realm()
+        let results = realm.objects(tableDirectEditingEditors.self).filter("account == %@", account)
+
+        if results.count > 0 {
+            return Array(results.map { tableDirectEditingEditors.init(value: $0) })
+        } else {
+            return nil
+        }
+    }
+
+    // MARK: -
+    // MARK: Table External Sites
+
+    func addExternalSites(_ externalSite: NKExternalSite, account: String) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                let addObject = tableExternalSites()
+
+                addObject.account = account
+                addObject.idExternalSite = externalSite.idExternalSite
+                addObject.icon = externalSite.icon
+                addObject.lang = externalSite.lang
+                addObject.name = externalSite.name
+                addObject.url = externalSite.url
+                addObject.type = externalSite.type
+
+                realm.add(addObject)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func deleteExternalSites(account: String) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                let results = realm.objects(tableExternalSites.self).filter("account == %@", account)
+                realm.delete(results)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func getAllExternalSites(account: String) -> [tableExternalSites]? {
+
+        let realm = try! Realm()
+
+        let results = realm.objects(tableExternalSites.self).filter("account == %@", account).sorted(byKeyPath: "idExternalSite", ascending: true)
+
+        if results.count > 0 {
+            return Array(results.map { tableExternalSites.init(value: $0) })
+        } else {
+            return nil
+        }
+    }
+
+    // MARK: -
+    // MARK: Table GPS
+
+    @objc func addGeocoderLocation(_ location: String, placemarkAdministrativeArea: String, placemarkCountry: String, placemarkLocality: String, placemarkPostalCode: String, placemarkThoroughfare: String, latitude: String, longitude: String) {
+
+        let realm = try! Realm()
+
+        realm.beginWrite()
+
+        // Verify if exists
+        guard realm.objects(tableGPS.self).filter("latitude == %@ AND longitude == %@", latitude, longitude).first == nil else {
+            realm.cancelWrite()
+            return
+        }
+
+        // Add new GPS
+        let addObject = tableGPS()
+
+        addObject.latitude = latitude
+        addObject.location = location
+        addObject.longitude = longitude
+        addObject.placemarkAdministrativeArea = placemarkAdministrativeArea
+        addObject.placemarkCountry = placemarkCountry
+        addObject.placemarkLocality = placemarkLocality
+        addObject.placemarkPostalCode = placemarkPostalCode
+        addObject.placemarkThoroughfare = placemarkThoroughfare
+
+        realm.add(addObject)
+
+        do {
+            try realm.commitWrite()
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    @objc func getLocationFromGeoLatitude(_ latitude: String, longitude: String) -> String? {
+
+        let realm = try! Realm()
+
+        let result = realm.objects(tableGPS.self).filter("latitude == %@ AND longitude == %@", latitude, longitude).first
+        return result?.location
+    }
+
+    // MARK: -
+    // MARK: Table LocalFile
+
+    func addLocalFile(metadata: tableMetadata) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+
+                let addObject = getTableLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) ?? tableLocalFile()
+
+                addObject.account = metadata.account
+                addObject.etag = metadata.etag
+                addObject.exifDate = NSDate()
+                addObject.exifLatitude = "-1"
+                addObject.exifLongitude = "-1"
+                addObject.ocId = metadata.ocId
+                addObject.fileName = metadata.fileName
+
+                realm.add(addObject, update: .all)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func addLocalFile(account: String, etag: String, ocId: String, fileName: String) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+
+                let addObject = tableLocalFile()
+
+                addObject.account = account
+                addObject.etag = etag
+                addObject.exifDate = NSDate()
+                addObject.exifLatitude = "-1"
+                addObject.exifLongitude = "-1"
+                addObject.ocId = ocId
+                addObject.fileName = fileName
+
+                realm.add(addObject, update: .all)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func deleteLocalFile(predicate: NSPredicate) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                let results = realm.objects(tableLocalFile.self).filter(predicate)
+                realm.delete(results)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func setLocalFile(ocId: String, fileName: String?, etag: String?) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                let result = realm.objects(tableLocalFile.self).filter("ocId == %@", ocId).first
+                if let fileName = fileName {
+                    result?.fileName = fileName
+                }
+                if let etag = etag {
+                    result?.etag = etag
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    @objc func setLocalFile(ocId: String, exifDate: NSDate?, exifLatitude: String, exifLongitude: String, exifLensModel: String?) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                if let result = realm.objects(tableLocalFile.self).filter("ocId == %@", ocId).first {
+                    result.exifDate = exifDate
+                    result.exifLatitude = exifLatitude
+                    result.exifLongitude = exifLongitude
+                    if exifLensModel?.count ?? 0 > 0 {
+                        result.exifLensModel = exifLensModel
+                    }
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func getTableLocalFile(account: String) -> [tableLocalFile] {
+
+        let realm = try! Realm()
+
+        let results = realm.objects(tableLocalFile.self).filter("account == %@", account)
+        return Array(results.map { tableLocalFile.init(value: $0) })
+    }
+
+    func getTableLocalFile(predicate: NSPredicate) -> tableLocalFile? {
+
+        let realm = try! Realm()
+
+        guard let result = realm.objects(tableLocalFile.self).filter(predicate).first else {
+            return nil
+        }
+
+        return tableLocalFile.init(value: result)
+    }
+
+    func getTableLocalFiles(predicate: NSPredicate, sorted: String, ascending: Bool) -> [tableLocalFile] {
+
+        let realm = try! Realm()
+
+        let results = realm.objects(tableLocalFile.self).filter(predicate).sorted(byKeyPath: sorted, ascending: ascending)
+        return Array(results.map { tableLocalFile.init(value: $0) })
+    }
+
+    func setLocalFile(ocId: String, offline: Bool) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                let result = realm.objects(tableLocalFile.self).filter("ocId == %@", ocId).first
+                result?.offline = offline
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    // MARK: -
+    // MARK: Table Photo Library
+
+    @discardableResult
+    func addPhotoLibrary(_ assets: [PHAsset], account: String) -> Bool {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+
+                for asset in assets {
+
+                    var creationDateString = ""
+                    let addObject = tablePhotoLibrary()
+
+                    addObject.account = account
+                    addObject.assetLocalIdentifier = asset.localIdentifier
+                    addObject.mediaType = asset.mediaType.rawValue
+                    if let creationDate = asset.creationDate {
+                        addObject.creationDate = creationDate as NSDate
+                        creationDateString = String(describing: creationDate)
+                    }
+                    if let modificationDate = asset.modificationDate {
+                        addObject.modificationDate = modificationDate as NSDate
+                    }
+                    addObject.idAsset = account + asset.localIdentifier + creationDateString
+
+                    realm.add(addObject, update: .all)
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+            return false
+        }
+
+        return true
+    }
+
+    func getPhotoLibraryIdAsset(image: Bool, video: Bool, account: String) -> [String]? {
+
+        let realm = try! Realm()
+        var predicate = NSPredicate()
+
+        if image && video {
+
+            predicate = NSPredicate(format: "account == %@ AND (mediaType == %d OR mediaType == %d)", account, PHAssetMediaType.image.rawValue, PHAssetMediaType.video.rawValue)
+
+        } else if image {
+
+            predicate = NSPredicate(format: "account == %@ AND mediaType == %d", account, PHAssetMediaType.image.rawValue)
+
+        } else if video {
+
+            predicate = NSPredicate(format: "account == %@ AND mediaType == %d", account, PHAssetMediaType.video.rawValue)
+        }
+
+        let results = realm.objects(tablePhotoLibrary.self).filter(predicate)
+
+        let idsAsset = results.map { $0.idAsset }
+
+        return Array(idsAsset)
+    }
+    
+    // MARK: -
+    // MARK: Table Tag
+
+    func addTag(_ ocId: String, tagIOS: Data?, account: String) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+
+                // Add new
+                let addObject = tableTag()
+
+                addObject.account = account
+                addObject.ocId = ocId
+                addObject.tagIOS = tagIOS
+
+                realm.add(addObject, update: .all)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func deleteTag(_ ocId: String) {
+
+        let realm = try! Realm()
+
+        realm.beginWrite()
+
+        let result = realm.objects(tableTag.self).filter("ocId == %@", ocId)
+        realm.delete(result)
+
+        do {
+            try realm.commitWrite()
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func getTags(predicate: NSPredicate) -> [tableTag] {
+
+        let realm = try! Realm()
+
+        let results = realm.objects(tableTag.self).filter(predicate)
+
+        return Array(results.map { tableTag.init(value: $0) })
+    }
+
+    func getTag(predicate: NSPredicate) -> tableTag? {
+
+        let realm = try! Realm()
+
+        guard let result = realm.objects(tableTag.self).filter(predicate).first else {
+            return nil
+        }
+
+        return tableTag.init(value: result)
+    }
+
+    // MARK: -
+    // MARK: Table Tip
+
+    func tipExists(_ tipName: String) -> Bool {
+
+        let realm = try! Realm()
+
+        guard (realm.objects(tableTip.self).where {
+            $0.tipName == tipName
+        }.first) == nil else {
+            return true
+        }
+
+        return false
+    }
+
+    func addTip(_ tipName: String) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                let addObject = tableTip()
+                addObject.tipName = tipName
+                realm.add(addObject, update: .all)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    // MARK: -
+    // MARK: Table Trash
+
+    func addTrash(account: String, items: [NKTrash]) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+                for trash in items {
+                    let object = tableTrash()
+
+                    object.account = account
+                    object.contentType = trash.contentType
+                    object.date = trash.date
+                    object.directory = trash.directory
+                    object.fileId = trash.fileId
+                    object.fileName = trash.fileName
+                    object.filePath = trash.filePath
+                    object.hasPreview = trash.hasPreview
+                    object.iconName = trash.iconName
+                    object.size = trash.size
+                    object.trashbinDeletionTime = trash.trashbinDeletionTime
+                    object.trashbinFileName = trash.trashbinFileName
+                    object.trashbinOriginalLocation = trash.trashbinOriginalLocation
+                    object.classFile = trash.classFile
+
+                    realm.add(object, update: .all)
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func deleteTrash(filePath: String?, account: String) {
+
+        let realm = try! Realm()
+        var predicate = NSPredicate()
+
+        do {
+            try realm.write {
+
+                if filePath == nil {
+                    predicate = NSPredicate(format: "account == %@", account)
+                } else {
+                    predicate = NSPredicate(format: "account == %@ AND filePath == %@", account, filePath!)
+                }
+
+                let result = realm.objects(tableTrash.self).filter(predicate)
+                realm.delete(result)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func deleteTrash(fileId: String?, account: String) {
+
+        let realm = try! Realm()
+        var predicate = NSPredicate()
+
+        do {
+            try realm.write {
+
+                if fileId == nil {
+                    predicate = NSPredicate(format: "account == %@", account)
+                } else {
+                    predicate = NSPredicate(format: "account == %@ AND fileId == %@", account, fileId!)
+                }
+
+                let result = realm.objects(tableTrash.self).filter(predicate)
+                realm.delete(result)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func getTrash(filePath: String, sort: String?, ascending: Bool?, account: String) -> [tableTrash]? {
+
+        let realm = try! Realm()
+        let sort = sort ?? "date"
+        let ascending = ascending ?? false
+
+        let results = realm.objects(tableTrash.self).filter("account == %@ AND filePath == %@", account, filePath).sorted(byKeyPath: sort, ascending: ascending)
+
+        return Array(results.map { tableTrash.init(value: $0) })
+    }
+
+    func getTrashItem(fileId: String, account: String) -> tableTrash? {
+
+        let realm = try! Realm()
+
+        guard let result = realm.objects(tableTrash.self).filter("account == %@ AND fileId == %@", account, fileId).first else {
+            return nil
+        }
+
+        return tableTrash.init(value: result)
+    }
+
+    // MARK: -
+    // MARK: Table UserStatus
+
+    func addUserStatus(_ userStatuses: [NKUserStatus], account: String, predefined: Bool) {
+
+        let realm = try! Realm()
+
+        do {
+            try realm.write {
+
+                let results = realm.objects(tableUserStatus.self).filter("account == %@ AND predefined == %@", account, predefined)
+                realm.delete(results)
+
+                for userStatus in userStatuses {
+
+                    let object = tableUserStatus()
+
+                    object.account = account
+                    object.clearAt = userStatus.clearAt
+                    object.clearAtTime = userStatus.clearAtTime
+                    object.clearAtType = userStatus.clearAtType
+                    object.icon = userStatus.icon
+                    object.id = userStatus.id
+                    object.message = userStatus.message
+                    object.predefined = userStatus.predefined
+                    object.status = userStatus.status
+                    object.userId = userStatus.userId
+
+                    realm.add(object)
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+}

+ 690 - 0
iOSClient/Diagnostics/NCCapabilitiesViewController.storyboard

@@ -0,0 +1,690 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="vTK-Er-kbZ">
+    <device id="retina6_1" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
+        <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"/>
+    </dependencies>
+    <scenes>
+        <!--Capabilities View Controller-->
+        <scene sceneID="UF0-FW-gHK">
+            <objects>
+                <viewController id="7oH-vf-YqN" customClass="NCCapabilitiesViewController" customModule="Nextcloud" customModuleProvider="target" sceneMemberID="viewController">
+                    <view key="view" contentMode="scaleToFill" id="Aja-Mn-6Wc">
+                        <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <subviews>
+                            <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="hTE-ys-qsF">
+                                <rect key="frame" x="0.0" y="88" width="414" height="774"/>
+                                <subviews>
+                                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Sms-Ez-fLO" userLabel="View Capabilities">
+                                        <rect key="frame" x="5" y="5" width="404" height="600"/>
+                                        <subviews>
+                                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jdW-oZ-cH8" userLabel="FileSharing">
+                                                <rect key="frame" x="0.0" y="0.0" width="404" height="50"/>
+                                                <subviews>
+                                                    <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="share" translatesAutoresizingMaskIntoConstraints="NO" id="G9c-Nd-Ikl">
+                                                        <rect key="frame" x="0.0" y="10" width="30" height="30"/>
+                                                        <constraints>
+                                                            <constraint firstAttribute="width" constant="30" id="2jt-3Q-W9U"/>
+                                                            <constraint firstAttribute="height" constant="30" id="qw0-LB-a0S"/>
+                                                        </constraints>
+                                                    </imageView>
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="File sharing" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Olg-V3-URE">
+                                                        <rect key="frame" x="40" y="16" width="234" height="18"/>
+                                                        <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                                                        <nil key="textColor"/>
+                                                        <nil key="highlightedColor"/>
+                                                    </label>
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Available" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="SbT-rU-lJ8">
+                                                        <rect key="frame" x="279" y="12.5" width="120" height="25"/>
+                                                        <color key="backgroundColor" systemColor="systemGray4Color"/>
+                                                        <constraints>
+                                                            <constraint firstAttribute="height" constant="25" id="Yf6-Er-ibu"/>
+                                                            <constraint firstAttribute="width" constant="120" id="bqY-hB-VuU"/>
+                                                        </constraints>
+                                                        <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                                                        <nil key="textColor"/>
+                                                        <nil key="highlightedColor"/>
+                                                    </label>
+                                                </subviews>
+                                                <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                                                <constraints>
+                                                    <constraint firstAttribute="height" constant="50" id="486-j7-SKp"/>
+                                                    <constraint firstItem="SbT-rU-lJ8" firstAttribute="centerY" secondItem="jdW-oZ-cH8" secondAttribute="centerY" id="4Ao-MF-e1N"/>
+                                                    <constraint firstItem="G9c-Nd-Ikl" firstAttribute="leading" secondItem="jdW-oZ-cH8" secondAttribute="leading" id="Bnm-Im-c7M"/>
+                                                    <constraint firstItem="G9c-Nd-Ikl" firstAttribute="centerY" secondItem="jdW-oZ-cH8" secondAttribute="centerY" id="Jxk-cZ-ezx"/>
+                                                    <constraint firstItem="Olg-V3-URE" firstAttribute="centerY" secondItem="jdW-oZ-cH8" secondAttribute="centerY" id="RXs-zW-MT4"/>
+                                                    <constraint firstItem="Olg-V3-URE" firstAttribute="leading" secondItem="G9c-Nd-Ikl" secondAttribute="trailing" constant="10" id="XZo-QH-gpr"/>
+                                                    <constraint firstItem="SbT-rU-lJ8" firstAttribute="leading" secondItem="Olg-V3-URE" secondAttribute="trailing" constant="5" id="wW2-QT-gGz"/>
+                                                    <constraint firstAttribute="trailing" secondItem="SbT-rU-lJ8" secondAttribute="trailing" constant="5" id="z63-kb-OIf"/>
+                                                </constraints>
+                                            </view>
+                                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="mSC-JU-xuk" userLabel="external Site">
+                                                <rect key="frame" x="0.0" y="50" width="404" height="50"/>
+                                                <subviews>
+                                                    <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="externalsites" translatesAutoresizingMaskIntoConstraints="NO" id="JWO-C0-32L">
+                                                        <rect key="frame" x="0.0" y="10" width="30" height="30"/>
+                                                        <constraints>
+                                                            <constraint firstAttribute="height" constant="30" id="cuQ-hf-WGC"/>
+                                                            <constraint firstAttribute="width" constant="30" id="hun-lk-Hyf"/>
+                                                        </constraints>
+                                                    </imageView>
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="External site" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="g2c-tp-kiW">
+                                                        <rect key="frame" x="40" y="16" width="234" height="18"/>
+                                                        <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                                                        <nil key="textColor"/>
+                                                        <nil key="highlightedColor"/>
+                                                    </label>
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Available" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ivv-te-kaP">
+                                                        <rect key="frame" x="279" y="12.5" width="120" height="25"/>
+                                                        <color key="backgroundColor" systemColor="systemGray4Color"/>
+                                                        <constraints>
+                                                            <constraint firstAttribute="width" constant="120" id="KRE-Ez-UvG"/>
+                                                            <constraint firstAttribute="height" constant="25" id="xhy-Qg-h6R"/>
+                                                        </constraints>
+                                                        <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                                                        <nil key="textColor"/>
+                                                        <nil key="highlightedColor"/>
+                                                    </label>
+                                                </subviews>
+                                                <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                                                <constraints>
+                                                    <constraint firstAttribute="trailing" secondItem="ivv-te-kaP" secondAttribute="trailing" constant="5" id="4K1-tJ-Cun"/>
+                                                    <constraint firstItem="JWO-C0-32L" firstAttribute="centerY" secondItem="mSC-JU-xuk" secondAttribute="centerY" id="FLD-bX-ETy"/>
+                                                    <constraint firstItem="ivv-te-kaP" firstAttribute="leading" secondItem="g2c-tp-kiW" secondAttribute="trailing" constant="5" id="VuF-Q1-hEp"/>
+                                                    <constraint firstItem="g2c-tp-kiW" firstAttribute="leading" secondItem="JWO-C0-32L" secondAttribute="trailing" constant="10" id="fWw-rx-nFV"/>
+                                                    <constraint firstAttribute="height" constant="50" id="pLI-AP-DaV"/>
+                                                    <constraint firstItem="JWO-C0-32L" firstAttribute="leading" secondItem="mSC-JU-xuk" secondAttribute="leading" id="pyK-ZG-7fZ"/>
+                                                    <constraint firstItem="g2c-tp-kiW" firstAttribute="centerY" secondItem="mSC-JU-xuk" secondAttribute="centerY" id="rtm-fS-6ec"/>
+                                                    <constraint firstItem="ivv-te-kaP" firstAttribute="centerY" secondItem="mSC-JU-xuk" secondAttribute="centerY" id="wCm-7E-nJ3"/>
+                                                </constraints>
+                                            </view>
+                                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="nVq-4i-FNy" userLabel="end to end encryption">
+                                                <rect key="frame" x="0.0" y="100" width="404" height="50"/>
+                                                <subviews>
+                                                    <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="lock" translatesAutoresizingMaskIntoConstraints="NO" id="S7m-5Z-ktw">
+                                                        <rect key="frame" x="0.0" y="10" width="30" height="30"/>
+                                                        <constraints>
+                                                            <constraint firstAttribute="height" constant="30" id="edD-t9-G1B"/>
+                                                            <constraint firstAttribute="width" constant="30" id="nPu-CX-Ilf"/>
+                                                        </constraints>
+                                                    </imageView>
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="End-to-End Encryption" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5cZ-yL-XdC">
+                                                        <rect key="frame" x="40" y="16" width="234" height="18"/>
+                                                        <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                                                        <nil key="textColor"/>
+                                                        <nil key="highlightedColor"/>
+                                                    </label>
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Available" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="M82-8U-M4Q">
+                                                        <rect key="frame" x="279" y="12.5" width="120" height="25"/>
+                                                        <color key="backgroundColor" systemColor="systemGray4Color"/>
+                                                        <constraints>
+                                                            <constraint firstAttribute="height" constant="25" id="L0G-30-siH"/>
+                                                            <constraint firstAttribute="width" constant="120" id="z0Z-Hz-edq"/>
+                                                        </constraints>
+                                                        <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                                                        <nil key="textColor"/>
+                                                        <nil key="highlightedColor"/>
+                                                    </label>
+                                                </subviews>
+                                                <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                                                <constraints>
+                                                    <constraint firstItem="5cZ-yL-XdC" firstAttribute="leading" secondItem="S7m-5Z-ktw" secondAttribute="trailing" constant="10" id="HOl-vJ-SDL"/>
+                                                    <constraint firstAttribute="height" constant="50" id="X1d-TH-FfQ"/>
+                                                    <constraint firstItem="S7m-5Z-ktw" firstAttribute="centerY" secondItem="nVq-4i-FNy" secondAttribute="centerY" id="cUj-Mb-Gza"/>
+                                                    <constraint firstItem="5cZ-yL-XdC" firstAttribute="centerY" secondItem="nVq-4i-FNy" secondAttribute="centerY" id="fKw-6d-s4Z"/>
+                                                    <constraint firstItem="M82-8U-M4Q" firstAttribute="centerY" secondItem="nVq-4i-FNy" secondAttribute="centerY" id="nnA-zZ-9te"/>
+                                                    <constraint firstItem="S7m-5Z-ktw" firstAttribute="leading" secondItem="nVq-4i-FNy" secondAttribute="leading" id="sX7-dg-Goj"/>
+                                                    <constraint firstItem="M82-8U-M4Q" firstAttribute="leading" secondItem="5cZ-yL-XdC" secondAttribute="trailing" constant="5" id="vjo-Od-7Gr"/>
+                                                    <constraint firstAttribute="trailing" secondItem="M82-8U-M4Q" secondAttribute="trailing" constant="5" id="zg8-Za-Rla"/>
+                                                </constraints>
+                                            </view>
+                                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="lGp-bh-Ysz" userLabel="Activity">
+                                                <rect key="frame" x="0.0" y="150" width="404" height="50"/>
+                                                <subviews>
+                                                    <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="activity" translatesAutoresizingMaskIntoConstraints="NO" id="tl5-S1-p9X">
+                                                        <rect key="frame" x="0.0" y="10" width="30" height="30"/>
+                                                        <constraints>
+                                                            <constraint firstAttribute="height" constant="30" id="Hb3-9B-Zsk"/>
+                                                            <constraint firstAttribute="width" constant="30" id="Zmw-Ck-dGs"/>
+                                                        </constraints>
+                                                    </imageView>
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Activity" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="SIY-IW-w6O">
+                                                        <rect key="frame" x="40" y="16" width="51" height="18"/>
+                                                        <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                                                        <nil key="textColor"/>
+                                                        <nil key="highlightedColor"/>
+                                                    </label>
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Available" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dyb-1O-dIA">
+                                                        <rect key="frame" x="279" y="12.5" width="120" height="25"/>
+                                                        <color key="backgroundColor" systemColor="systemGray4Color"/>
+                                                        <constraints>
+                                                            <constraint firstAttribute="height" constant="25" id="C3a-Ec-xJv"/>
+                                                            <constraint firstAttribute="width" constant="120" id="IB5-Hq-R4j"/>
+                                                        </constraints>
+                                                        <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                                                        <nil key="textColor"/>
+                                                        <nil key="highlightedColor"/>
+                                                    </label>
+                                                </subviews>
+                                                <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                                                <constraints>
+                                                    <constraint firstAttribute="trailing" secondItem="dyb-1O-dIA" secondAttribute="trailing" constant="5" id="0fh-7J-NUN"/>
+                                                    <constraint firstAttribute="height" constant="50" id="OMI-4q-89Q"/>
+                                                    <constraint firstItem="dyb-1O-dIA" firstAttribute="centerY" secondItem="lGp-bh-Ysz" secondAttribute="centerY" id="OPb-RB-lic"/>
+                                                    <constraint firstItem="SIY-IW-w6O" firstAttribute="leading" secondItem="tl5-S1-p9X" secondAttribute="trailing" constant="10" id="Q9g-cC-zme"/>
+                                                    <constraint firstItem="tl5-S1-p9X" firstAttribute="leading" secondItem="lGp-bh-Ysz" secondAttribute="leading" id="cFK-we-PT6"/>
+                                                    <constraint firstItem="tl5-S1-p9X" firstAttribute="centerY" secondItem="lGp-bh-Ysz" secondAttribute="centerY" id="fyW-bz-dN0"/>
+                                                    <constraint firstItem="SIY-IW-w6O" firstAttribute="centerY" secondItem="lGp-bh-Ysz" secondAttribute="centerY" id="xCh-5R-1WK"/>
+                                                </constraints>
+                                            </view>
+                                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="UPC-L1-VKj" userLabel="Notification">
+                                                <rect key="frame" x="0.0" y="200" width="404" height="50"/>
+                                                <subviews>
+                                                    <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="notification" translatesAutoresizingMaskIntoConstraints="NO" id="cgb-3g-trc">
+                                                        <rect key="frame" x="0.0" y="10" width="30" height="30"/>
+                                                        <constraints>
+                                                            <constraint firstAttribute="height" constant="30" id="3RZ-6t-QNW"/>
+                                                            <constraint firstAttribute="width" constant="30" id="Rpr-ox-Bhc"/>
+                                                        </constraints>
+                                                    </imageView>
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Notification" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="W6x-fO-A6L">
+                                                        <rect key="frame" x="40" y="16" width="79" height="18"/>
+                                                        <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                                                        <nil key="textColor"/>
+                                                        <nil key="highlightedColor"/>
+                                                    </label>
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Available" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WAg-Hw-sQS">
+                                                        <rect key="frame" x="279" y="12.5" width="120" height="25"/>
+                                                        <color key="backgroundColor" systemColor="systemGray4Color"/>
+                                                        <constraints>
+                                                            <constraint firstAttribute="height" constant="25" id="NEz-tu-8Rk"/>
+                                                            <constraint firstAttribute="width" constant="120" id="zjs-JE-2aB"/>
+                                                        </constraints>
+                                                        <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                                                        <nil key="textColor"/>
+                                                        <nil key="highlightedColor"/>
+                                                    </label>
+                                                </subviews>
+                                                <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                                                <constraints>
+                                                    <constraint firstItem="cgb-3g-trc" firstAttribute="centerY" secondItem="UPC-L1-VKj" secondAttribute="centerY" id="28g-Vd-Pdh"/>
+                                                    <constraint firstItem="W6x-fO-A6L" firstAttribute="centerY" secondItem="UPC-L1-VKj" secondAttribute="centerY" id="Ah1-f7-KrB"/>
+                                                    <constraint firstAttribute="trailing" secondItem="WAg-Hw-sQS" secondAttribute="trailing" constant="5" id="BpJ-RQ-B1k"/>
+                                                    <constraint firstItem="WAg-Hw-sQS" firstAttribute="centerY" secondItem="UPC-L1-VKj" secondAttribute="centerY" id="OGo-Ns-IU0"/>
+                                                    <constraint firstAttribute="height" constant="50" id="VOs-so-anE"/>
+                                                    <constraint firstItem="cgb-3g-trc" firstAttribute="leading" secondItem="UPC-L1-VKj" secondAttribute="leading" id="e2z-tZ-5bX"/>
+                                                    <constraint firstItem="W6x-fO-A6L" firstAttribute="leading" secondItem="cgb-3g-trc" secondAttribute="trailing" constant="10" id="zGE-fg-e3h"/>
+                                                </constraints>
+                                            </view>
+                                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="8nf-zJ-Qas" userLabel="Notification">
+                                                <rect key="frame" x="0.0" y="250" width="404" height="50"/>
+                                                <subviews>
+                                                    <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="delete" translatesAutoresizingMaskIntoConstraints="NO" id="8Zc-9W-n27">
+                                                        <rect key="frame" x="0.0" y="10" width="30" height="30"/>
+                                                        <constraints>
+                                                            <constraint firstAttribute="width" constant="30" id="GE1-Yd-rOr"/>
+                                                            <constraint firstAttribute="height" constant="30" id="aQW-Uy-znK"/>
+                                                        </constraints>
+                                                    </imageView>
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Deleted files" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="EVN-t6-jzB">
+                                                        <rect key="frame" x="40" y="16" width="85.5" height="18"/>
+                                                        <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                                                        <nil key="textColor"/>
+                                                        <nil key="highlightedColor"/>
+                                                    </label>
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Available" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="8hg-qK-qvz">
+                                                        <rect key="frame" x="279" y="12.5" width="120" height="25"/>
+                                                        <color key="backgroundColor" systemColor="systemGray4Color"/>
+                                                        <constraints>
+                                                            <constraint firstAttribute="width" constant="120" id="Ns2-PG-H7R"/>
+                                                            <constraint firstAttribute="height" constant="25" id="b2x-qE-hf6"/>
+                                                        </constraints>
+                                                        <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                                                        <nil key="textColor"/>
+                                                        <nil key="highlightedColor"/>
+                                                    </label>
+                                                </subviews>
+                                                <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                                                <constraints>
+                                                    <constraint firstItem="8Zc-9W-n27" firstAttribute="leading" secondItem="8nf-zJ-Qas" secondAttribute="leading" id="0gl-yZ-NHn"/>
+                                                    <constraint firstItem="EVN-t6-jzB" firstAttribute="centerY" secondItem="8nf-zJ-Qas" secondAttribute="centerY" id="5dv-S0-9bY"/>
+                                                    <constraint firstItem="8Zc-9W-n27" firstAttribute="centerY" secondItem="8nf-zJ-Qas" secondAttribute="centerY" id="9H1-hH-E2o"/>
+                                                    <constraint firstAttribute="height" constant="50" id="NJt-Ob-qAJ"/>
+                                                    <constraint firstAttribute="trailing" secondItem="8hg-qK-qvz" secondAttribute="trailing" constant="5" id="VS2-55-BeI"/>
+                                                    <constraint firstItem="8hg-qK-qvz" firstAttribute="centerY" secondItem="8nf-zJ-Qas" secondAttribute="centerY" id="ceB-3o-Qpt"/>
+                                                    <constraint firstItem="EVN-t6-jzB" firstAttribute="leading" secondItem="8Zc-9W-n27" secondAttribute="trailing" constant="10" id="rDk-Z6-y2x"/>
+                                                </constraints>
+                                            </view>
+                                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ZNB-jF-9zg" userLabel="direct editing">
+                                                <rect key="frame" x="0.0" y="300" width="404" height="50"/>
+                                                <subviews>
+                                                    <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="text" translatesAutoresizingMaskIntoConstraints="NO" id="iCB-2A-phO">
+                                                        <rect key="frame" x="0.0" y="10" width="30" height="30"/>
+                                                        <constraints>
+                                                            <constraint firstAttribute="width" constant="30" id="Ww6-2V-i4r"/>
+                                                            <constraint firstAttribute="height" constant="30" id="kdP-OK-U2a"/>
+                                                        </constraints>
+                                                    </imageView>
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Text" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FOv-Ld-QFB">
+                                                        <rect key="frame" x="40" y="16" width="29" height="18"/>
+                                                        <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                                                        <nil key="textColor"/>
+                                                        <nil key="highlightedColor"/>
+                                                    </label>
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Available" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uiz-H8-p3D">
+                                                        <rect key="frame" x="279" y="12.5" width="120" height="25"/>
+                                                        <color key="backgroundColor" systemColor="systemGray4Color"/>
+                                                        <constraints>
+                                                            <constraint firstAttribute="height" constant="25" id="Eqf-R1-eKB"/>
+                                                            <constraint firstAttribute="width" constant="120" id="i0a-tu-JgU"/>
+                                                        </constraints>
+                                                        <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                                                        <nil key="textColor"/>
+                                                        <nil key="highlightedColor"/>
+                                                    </label>
+                                                </subviews>
+                                                <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                                                <constraints>
+                                                    <constraint firstItem="uiz-H8-p3D" firstAttribute="centerY" secondItem="ZNB-jF-9zg" secondAttribute="centerY" id="0dR-3w-1sQ"/>
+                                                    <constraint firstAttribute="height" constant="50" id="4UX-0n-U4U"/>
+                                                    <constraint firstAttribute="trailing" secondItem="uiz-H8-p3D" secondAttribute="trailing" constant="5" id="Rma-z3-oCf"/>
+                                                    <constraint firstItem="iCB-2A-phO" firstAttribute="leading" secondItem="ZNB-jF-9zg" secondAttribute="leading" id="cF1-co-eQB"/>
+                                                    <constraint firstItem="iCB-2A-phO" firstAttribute="centerY" secondItem="ZNB-jF-9zg" secondAttribute="centerY" id="gSQ-hN-iBx"/>
+                                                    <constraint firstItem="FOv-Ld-QFB" firstAttribute="leading" secondItem="iCB-2A-phO" secondAttribute="trailing" constant="10" id="kiE-oZ-ky6"/>
+                                                    <constraint firstItem="FOv-Ld-QFB" firstAttribute="centerY" secondItem="ZNB-jF-9zg" secondAttribute="centerY" id="vHG-Fk-kEj"/>
+                                                </constraints>
+                                            </view>
+                                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="dhs-06-3RT" userLabel="Collabora">
+                                                <rect key="frame" x="0.0" y="350" width="404" height="50"/>
+                                                <subviews>
+                                                    <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="collabora" translatesAutoresizingMaskIntoConstraints="NO" id="GXZ-Rj-WCE">
+                                                        <rect key="frame" x="0.0" y="10" width="30" height="30"/>
+                                                        <constraints>
+                                                            <constraint firstAttribute="height" constant="30" id="kcH-Br-yZB"/>
+                                                            <constraint firstAttribute="width" constant="30" id="ros-hl-Jec"/>
+                                                        </constraints>
+                                                    </imageView>
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Collabora" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="IJ7-i8-AvX">
+                                                        <rect key="frame" x="40" y="16" width="65.5" height="18"/>
+                                                        <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                                                        <nil key="textColor"/>
+                                                        <nil key="highlightedColor"/>
+                                                    </label>
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Available" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="30w-oC-gZl">
+                                                        <rect key="frame" x="279" y="12.5" width="120" height="25"/>
+                                                        <color key="backgroundColor" systemColor="systemGray4Color"/>
+                                                        <constraints>
+                                                            <constraint firstAttribute="height" constant="25" id="E6x-vP-Fs9"/>
+                                                            <constraint firstAttribute="width" constant="120" id="nGc-gZ-jaG"/>
+                                                        </constraints>
+                                                        <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                                                        <nil key="textColor"/>
+                                                        <nil key="highlightedColor"/>
+                                                    </label>
+                                                </subviews>
+                                                <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                                                <constraints>
+                                                    <constraint firstItem="30w-oC-gZl" firstAttribute="centerY" secondItem="dhs-06-3RT" secondAttribute="centerY" id="0G9-Hl-zZn"/>
+                                                    <constraint firstItem="IJ7-i8-AvX" firstAttribute="centerY" secondItem="dhs-06-3RT" secondAttribute="centerY" id="8Rv-kP-XDs"/>
+                                                    <constraint firstItem="GXZ-Rj-WCE" firstAttribute="centerY" secondItem="dhs-06-3RT" secondAttribute="centerY" id="DiJ-1E-bIC"/>
+                                                    <constraint firstItem="IJ7-i8-AvX" firstAttribute="leading" secondItem="GXZ-Rj-WCE" secondAttribute="trailing" constant="10" id="M7l-uB-RhH"/>
+                                                    <constraint firstAttribute="trailing" secondItem="30w-oC-gZl" secondAttribute="trailing" constant="5" id="XrQ-4B-UnP"/>
+                                                    <constraint firstItem="GXZ-Rj-WCE" firstAttribute="leading" secondItem="dhs-06-3RT" secondAttribute="leading" id="lCd-tJ-nvi"/>
+                                                    <constraint firstAttribute="height" constant="50" id="xgw-IO-enA"/>
+                                                </constraints>
+                                            </view>
+                                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="LTt-2C-rPb" userLabel="OnlyOffice">
+                                                <rect key="frame" x="0.0" y="400" width="404" height="50"/>
+                                                <subviews>
+                                                    <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="onlyoffice" translatesAutoresizingMaskIntoConstraints="NO" id="xvv-h0-9bM">
+                                                        <rect key="frame" x="0.0" y="10" width="30" height="30"/>
+                                                        <constraints>
+                                                            <constraint firstAttribute="width" constant="30" id="bUx-KW-ZGu"/>
+                                                            <constraint firstAttribute="height" constant="30" id="wKp-TM-dCw"/>
+                                                        </constraints>
+                                                    </imageView>
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="ONLYOFFICE" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Tq3-cS-wup">
+                                                        <rect key="frame" x="40" y="16" width="89.5" height="18"/>
+                                                        <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                                                        <nil key="textColor"/>
+                                                        <nil key="highlightedColor"/>
+                                                    </label>
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Available" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ucV-YG-5ht">
+                                                        <rect key="frame" x="279" y="12.5" width="120" height="25"/>
+                                                        <color key="backgroundColor" systemColor="systemGray4Color"/>
+                                                        <constraints>
+                                                            <constraint firstAttribute="height" constant="25" id="8Xe-4g-RxY"/>
+                                                            <constraint firstAttribute="width" constant="120" id="m3G-CE-aso"/>
+                                                        </constraints>
+                                                        <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                                                        <nil key="textColor"/>
+                                                        <nil key="highlightedColor"/>
+                                                    </label>
+                                                </subviews>
+                                                <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                                                <constraints>
+                                                    <constraint firstItem="Tq3-cS-wup" firstAttribute="leading" secondItem="xvv-h0-9bM" secondAttribute="trailing" constant="10" id="G34-W8-fhW"/>
+                                                    <constraint firstAttribute="height" constant="50" id="Glo-gX-j6q"/>
+                                                    <constraint firstItem="xvv-h0-9bM" firstAttribute="centerY" secondItem="LTt-2C-rPb" secondAttribute="centerY" id="mYd-TV-lVh"/>
+                                                    <constraint firstItem="ucV-YG-5ht" firstAttribute="centerY" secondItem="LTt-2C-rPb" secondAttribute="centerY" id="qmu-3J-9Xj"/>
+                                                    <constraint firstAttribute="trailing" secondItem="ucV-YG-5ht" secondAttribute="trailing" constant="5" id="tva-Qw-rYM"/>
+                                                    <constraint firstItem="xvv-h0-9bM" firstAttribute="leading" secondItem="LTt-2C-rPb" secondAttribute="leading" id="vkH-IQ-IUb"/>
+                                                    <constraint firstItem="Tq3-cS-wup" firstAttribute="centerY" secondItem="LTt-2C-rPb" secondAttribute="centerY" id="xlI-tP-AMe"/>
+                                                </constraints>
+                                            </view>
+                                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="2Pp-Kb-YMc" userLabel="User Status">
+                                                <rect key="frame" x="0.0" y="450" width="404" height="50"/>
+                                                <subviews>
+                                                    <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="userStatusAway" translatesAutoresizingMaskIntoConstraints="NO" id="Nxp-H1-Pob">
+                                                        <rect key="frame" x="0.0" y="10" width="30" height="30"/>
+                                                        <constraints>
+                                                            <constraint firstAttribute="height" constant="30" id="qzU-OP-bvd"/>
+                                                            <constraint firstAttribute="width" constant="30" id="t5O-mN-Xyd"/>
+                                                        </constraints>
+                                                    </imageView>
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="User Status" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="LID-IE-L9Z">
+                                                        <rect key="frame" x="40" y="16" width="80.5" height="18"/>
+                                                        <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                                                        <nil key="textColor"/>
+                                                        <nil key="highlightedColor"/>
+                                                    </label>
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Available" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5Sj-5h-jgb">
+                                                        <rect key="frame" x="279" y="12.5" width="120" height="25"/>
+                                                        <color key="backgroundColor" systemColor="systemGray4Color"/>
+                                                        <constraints>
+                                                            <constraint firstAttribute="width" constant="120" id="IJ3-gv-UjC"/>
+                                                            <constraint firstAttribute="height" constant="25" id="wiA-JQ-GDr"/>
+                                                        </constraints>
+                                                        <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                                                        <nil key="textColor"/>
+                                                        <nil key="highlightedColor"/>
+                                                    </label>
+                                                </subviews>
+                                                <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                                                <constraints>
+                                                    <constraint firstItem="LID-IE-L9Z" firstAttribute="leading" secondItem="Nxp-H1-Pob" secondAttribute="trailing" constant="10" id="4Pl-0O-5J4"/>
+                                                    <constraint firstItem="Nxp-H1-Pob" firstAttribute="leading" secondItem="2Pp-Kb-YMc" secondAttribute="leading" id="9yK-6i-taC"/>
+                                                    <constraint firstAttribute="height" constant="50" id="UUO-ej-PcK"/>
+                                                    <constraint firstAttribute="trailing" secondItem="5Sj-5h-jgb" secondAttribute="trailing" constant="5" id="iu9-2g-hKA"/>
+                                                    <constraint firstItem="LID-IE-L9Z" firstAttribute="centerY" secondItem="2Pp-Kb-YMc" secondAttribute="centerY" id="lFh-r6-Yvh"/>
+                                                    <constraint firstItem="Nxp-H1-Pob" firstAttribute="centerY" secondItem="2Pp-Kb-YMc" secondAttribute="centerY" id="ocY-iN-t73"/>
+                                                    <constraint firstItem="5Sj-5h-jgb" firstAttribute="centerY" secondItem="2Pp-Kb-YMc" secondAttribute="centerY" id="peY-Iv-kLA"/>
+                                                </constraints>
+                                            </view>
+                                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="S24-Wc-fps" userLabel="Comments">
+                                                <rect key="frame" x="0.0" y="498" width="404" height="50"/>
+                                                <subviews>
+                                                    <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="comments" translatesAutoresizingMaskIntoConstraints="NO" id="cYf-px-izj">
+                                                        <rect key="frame" x="0.0" y="10" width="30" height="30"/>
+                                                        <constraints>
+                                                            <constraint firstAttribute="width" constant="30" id="gdA-CQ-Wu0"/>
+                                                            <constraint firstAttribute="height" constant="30" id="xGI-Zv-SQC"/>
+                                                        </constraints>
+                                                    </imageView>
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Comments" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="AEw-vE-bsf">
+                                                        <rect key="frame" x="40" y="16" width="74.5" height="18"/>
+                                                        <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                                                        <nil key="textColor"/>
+                                                        <nil key="highlightedColor"/>
+                                                    </label>
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Available" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lVT-MG-7kN">
+                                                        <rect key="frame" x="279" y="12.5" width="120" height="25"/>
+                                                        <color key="backgroundColor" systemColor="systemGray4Color"/>
+                                                        <constraints>
+                                                            <constraint firstAttribute="width" constant="120" id="TPY-VV-LmQ"/>
+                                                            <constraint firstAttribute="height" constant="25" id="WwT-hU-yOT"/>
+                                                        </constraints>
+                                                        <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                                                        <nil key="textColor"/>
+                                                        <nil key="highlightedColor"/>
+                                                    </label>
+                                                </subviews>
+                                                <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                                                <constraints>
+                                                    <constraint firstAttribute="height" constant="50" id="3iW-tC-sQm"/>
+                                                    <constraint firstItem="AEw-vE-bsf" firstAttribute="leading" secondItem="cYf-px-izj" secondAttribute="trailing" constant="10" id="6bS-1j-tEi"/>
+                                                    <constraint firstItem="lVT-MG-7kN" firstAttribute="centerY" secondItem="S24-Wc-fps" secondAttribute="centerY" id="6zR-QZ-UEn"/>
+                                                    <constraint firstItem="cYf-px-izj" firstAttribute="leading" secondItem="S24-Wc-fps" secondAttribute="leading" id="Ahj-Nc-bRa"/>
+                                                    <constraint firstItem="cYf-px-izj" firstAttribute="centerY" secondItem="S24-Wc-fps" secondAttribute="centerY" id="BO8-tt-rX5"/>
+                                                    <constraint firstItem="AEw-vE-bsf" firstAttribute="centerY" secondItem="S24-Wc-fps" secondAttribute="centerY" id="ete-pJ-l2X"/>
+                                                    <constraint firstAttribute="trailing" secondItem="lVT-MG-7kN" secondAttribute="trailing" constant="5" id="zCZ-du-XT5"/>
+                                                </constraints>
+                                            </view>
+                                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="npG-0C-m3A" userLabel="Lock">
+                                                <rect key="frame" x="0.0" y="550" width="404" height="50"/>
+                                                <subviews>
+                                                    <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="lock" translatesAutoresizingMaskIntoConstraints="NO" id="eAC-Li-gKO">
+                                                        <rect key="frame" x="0.0" y="10" width="30" height="30"/>
+                                                        <constraints>
+                                                            <constraint firstAttribute="height" constant="30" id="JsP-Q6-6yG"/>
+                                                            <constraint firstAttribute="width" constant="30" id="WUk-lh-3Vb"/>
+                                                        </constraints>
+                                                    </imageView>
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Lock file" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="SCj-6t-sXN">
+                                                        <rect key="frame" x="40" y="16" width="57.5" height="18"/>
+                                                        <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                                                        <nil key="textColor"/>
+                                                        <nil key="highlightedColor"/>
+                                                    </label>
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Available" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="db5-st-O6M">
+                                                        <rect key="frame" x="279" y="12.5" width="120" height="25"/>
+                                                        <color key="backgroundColor" systemColor="systemGray4Color"/>
+                                                        <constraints>
+                                                            <constraint firstAttribute="width" constant="120" id="Vfm-Rm-l7N"/>
+                                                            <constraint firstAttribute="height" constant="25" id="oam-XV-KqH"/>
+                                                        </constraints>
+                                                        <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                                                        <nil key="textColor"/>
+                                                        <nil key="highlightedColor"/>
+                                                    </label>
+                                                </subviews>
+                                                <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                                                <constraints>
+                                                    <constraint firstItem="db5-st-O6M" firstAttribute="centerY" secondItem="npG-0C-m3A" secondAttribute="centerY" id="0X7-Jw-OjP"/>
+                                                    <constraint firstItem="eAC-Li-gKO" firstAttribute="leading" secondItem="npG-0C-m3A" secondAttribute="leading" id="1yz-QU-lXY"/>
+                                                    <constraint firstAttribute="height" constant="50" id="6GQ-cE-Nz3"/>
+                                                    <constraint firstItem="SCj-6t-sXN" firstAttribute="centerY" secondItem="npG-0C-m3A" secondAttribute="centerY" id="Ocs-LT-OYb"/>
+                                                    <constraint firstItem="eAC-Li-gKO" firstAttribute="centerY" secondItem="npG-0C-m3A" secondAttribute="centerY" id="SlK-BL-Nba"/>
+                                                    <constraint firstAttribute="trailing" secondItem="db5-st-O6M" secondAttribute="trailing" constant="5" id="Upi-tI-m1R"/>
+                                                    <constraint firstItem="SCj-6t-sXN" firstAttribute="leading" secondItem="eAC-Li-gKO" secondAttribute="trailing" constant="10" id="dgu-xW-E8T"/>
+                                                </constraints>
+                                            </view>
+                                        </subviews>
+                                        <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                                        <constraints>
+                                            <constraint firstItem="lGp-bh-Ysz" firstAttribute="leading" secondItem="Sms-Ez-fLO" secondAttribute="leading" id="0qE-PV-O2f"/>
+                                            <constraint firstAttribute="trailing" secondItem="8nf-zJ-Qas" secondAttribute="trailing" id="0vv-HQ-Qqx"/>
+                                            <constraint firstAttribute="height" constant="600" id="6nU-Cb-MzH"/>
+                                            <constraint firstItem="mSC-JU-xuk" firstAttribute="leading" secondItem="Sms-Ez-fLO" secondAttribute="leading" id="9Nq-du-3ah"/>
+                                            <constraint firstItem="S24-Wc-fps" firstAttribute="leading" secondItem="Sms-Ez-fLO" secondAttribute="leading" id="Aad-cm-E1T"/>
+                                            <constraint firstAttribute="trailing" secondItem="S24-Wc-fps" secondAttribute="trailing" id="Ajf-er-GBr"/>
+                                            <constraint firstItem="ZNB-jF-9zg" firstAttribute="leading" secondItem="Sms-Ez-fLO" secondAttribute="leading" id="GdE-lt-vZC"/>
+                                            <constraint firstAttribute="trailing" secondItem="nVq-4i-FNy" secondAttribute="trailing" id="HSG-Ia-fYc"/>
+                                            <constraint firstItem="npG-0C-m3A" firstAttribute="top" secondItem="S24-Wc-fps" secondAttribute="bottom" constant="2" id="IQe-Xv-U7D"/>
+                                            <constraint firstItem="S24-Wc-fps" firstAttribute="top" secondItem="2Pp-Kb-YMc" secondAttribute="bottom" constant="-2" id="ML8-GT-9hb"/>
+                                            <constraint firstAttribute="trailing" secondItem="2Pp-Kb-YMc" secondAttribute="trailing" id="NG8-Nu-ic6"/>
+                                            <constraint firstItem="LTt-2C-rPb" firstAttribute="top" secondItem="dhs-06-3RT" secondAttribute="bottom" id="Nm6-NH-AC9"/>
+                                            <constraint firstItem="npG-0C-m3A" firstAttribute="leading" secondItem="Sms-Ez-fLO" secondAttribute="leading" id="QH1-s3-UQ7"/>
+                                            <constraint firstItem="UPC-L1-VKj" firstAttribute="leading" secondItem="Sms-Ez-fLO" secondAttribute="leading" id="QcN-sd-pHM"/>
+                                            <constraint firstItem="LTt-2C-rPb" firstAttribute="leading" secondItem="Sms-Ez-fLO" secondAttribute="leading" id="SUy-Mo-oAO"/>
+                                            <constraint firstItem="jdW-oZ-cH8" firstAttribute="top" secondItem="Sms-Ez-fLO" secondAttribute="top" id="UBW-Mx-NTs"/>
+                                            <constraint firstItem="8nf-zJ-Qas" firstAttribute="top" secondItem="UPC-L1-VKj" secondAttribute="bottom" id="UqS-Lx-6mL"/>
+                                            <constraint firstItem="dhs-06-3RT" firstAttribute="leading" secondItem="Sms-Ez-fLO" secondAttribute="leading" id="UwG-6b-pEk"/>
+                                            <constraint firstItem="nVq-4i-FNy" firstAttribute="top" secondItem="mSC-JU-xuk" secondAttribute="bottom" id="VfU-sj-S9y"/>
+                                            <constraint firstAttribute="trailing" secondItem="npG-0C-m3A" secondAttribute="trailing" id="bLv-wr-d5v"/>
+                                            <constraint firstAttribute="trailing" secondItem="lGp-bh-Ysz" secondAttribute="trailing" id="bUd-8w-D8k"/>
+                                            <constraint firstItem="UPC-L1-VKj" firstAttribute="top" secondItem="lGp-bh-Ysz" secondAttribute="bottom" id="br5-nz-w7h"/>
+                                            <constraint firstItem="8nf-zJ-Qas" firstAttribute="leading" secondItem="Sms-Ez-fLO" secondAttribute="leading" id="dsm-XA-dZD"/>
+                                            <constraint firstItem="jdW-oZ-cH8" firstAttribute="leading" secondItem="Sms-Ez-fLO" secondAttribute="leading" id="eNi-cO-UFd"/>
+                                            <constraint firstItem="nVq-4i-FNy" firstAttribute="leading" secondItem="Sms-Ez-fLO" secondAttribute="leading" id="efi-mm-hvP"/>
+                                            <constraint firstAttribute="trailing" secondItem="LTt-2C-rPb" secondAttribute="trailing" id="erZ-V1-p76"/>
+                                            <constraint firstAttribute="trailing" secondItem="UPC-L1-VKj" secondAttribute="trailing" id="f2Y-D9-fMV"/>
+                                            <constraint firstItem="2Pp-Kb-YMc" firstAttribute="leading" secondItem="Sms-Ez-fLO" secondAttribute="leading" id="fQm-C9-02f"/>
+                                            <constraint firstAttribute="trailing" secondItem="dhs-06-3RT" secondAttribute="trailing" id="ijj-gU-Gdk"/>
+                                            <constraint firstItem="2Pp-Kb-YMc" firstAttribute="top" secondItem="LTt-2C-rPb" secondAttribute="bottom" id="jbS-we-vDH"/>
+                                            <constraint firstAttribute="trailing" secondItem="jdW-oZ-cH8" secondAttribute="trailing" id="k81-ap-Nwy"/>
+                                            <constraint firstItem="lGp-bh-Ysz" firstAttribute="top" secondItem="nVq-4i-FNy" secondAttribute="bottom" id="l7m-bA-t7c"/>
+                                            <constraint firstItem="dhs-06-3RT" firstAttribute="top" secondItem="ZNB-jF-9zg" secondAttribute="bottom" id="mJv-Ta-ZbY"/>
+                                            <constraint firstAttribute="trailing" secondItem="mSC-JU-xuk" secondAttribute="trailing" id="pBU-xQ-0qV"/>
+                                            <constraint firstAttribute="trailing" secondItem="ZNB-jF-9zg" secondAttribute="trailing" id="v5H-SV-MvM"/>
+                                            <constraint firstItem="ZNB-jF-9zg" firstAttribute="top" secondItem="8nf-zJ-Qas" secondAttribute="bottom" id="w6t-hz-cPZ"/>
+                                            <constraint firstItem="mSC-JU-xuk" firstAttribute="top" secondItem="jdW-oZ-cH8" secondAttribute="bottom" id="y2u-2j-cif"/>
+                                        </constraints>
+                                    </view>
+                                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ogC-ph-Xdr" userLabel="View Link">
+                                        <rect key="frame" x="5" y="620" width="404" height="40"/>
+                                        <subviews>
+                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Home server" textAlignment="natural" lineBreakMode="characterWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PD5-8h-ZLm">
+                                                <rect key="frame" x="40" y="0.0" width="359" height="40"/>
+                                                <constraints>
+                                                    <constraint firstAttribute="height" constant="40" id="hBZ-cQ-ZHg"/>
+                                                </constraints>
+                                                <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                                                <nil key="textColor"/>
+                                                <nil key="highlightedColor"/>
+                                            </label>
+                                            <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="home" translatesAutoresizingMaskIntoConstraints="NO" id="pyf-iS-YYP">
+                                                <rect key="frame" x="0.0" y="5" width="30" height="30"/>
+                                                <constraints>
+                                                    <constraint firstAttribute="height" constant="30" id="LkI-Fd-YBH"/>
+                                                    <constraint firstAttribute="width" constant="30" id="SVh-sE-fdn"/>
+                                                </constraints>
+                                            </imageView>
+                                        </subviews>
+                                        <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                                        <constraints>
+                                            <constraint firstAttribute="trailing" secondItem="PD5-8h-ZLm" secondAttribute="trailing" constant="5" id="71i-FN-PmV"/>
+                                            <constraint firstItem="pyf-iS-YYP" firstAttribute="leading" secondItem="ogC-ph-Xdr" secondAttribute="leading" id="D7Q-nx-Lcr"/>
+                                            <constraint firstAttribute="height" constant="40" id="HZv-dy-zeu"/>
+                                            <constraint firstItem="PD5-8h-ZLm" firstAttribute="top" secondItem="ogC-ph-Xdr" secondAttribute="top" id="m3v-SO-f43"/>
+                                            <constraint firstItem="PD5-8h-ZLm" firstAttribute="leading" secondItem="pyf-iS-YYP" secondAttribute="trailing" constant="10" id="pD9-5x-n4a"/>
+                                            <constraint firstItem="pyf-iS-YYP" firstAttribute="top" secondItem="ogC-ph-Xdr" secondAttribute="top" constant="5" id="uht-S6-R6J"/>
+                                        </constraints>
+                                    </view>
+                                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="OO4-N7-9vp" userLabel="View JSON">
+                                        <rect key="frame" x="0.0" y="670" width="414" height="40.5"/>
+                                        <subviews>
+                                            <textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="RIO-6X-GG1">
+                                                <rect key="frame" x="5" y="5" width="404" height="30.5"/>
+                                                <color key="backgroundColor" systemColor="systemGray4Color"/>
+                                                <color key="textColor" systemColor="labelColor"/>
+                                                <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                                                <textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
+                                            </textView>
+                                        </subviews>
+                                        <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                                        <constraints>
+                                            <constraint firstItem="RIO-6X-GG1" firstAttribute="leading" secondItem="OO4-N7-9vp" secondAttribute="leading" constant="5" id="63w-yC-mGi"/>
+                                            <constraint firstAttribute="trailing" secondItem="RIO-6X-GG1" secondAttribute="trailing" constant="5" id="9cj-M6-K3f"/>
+                                            <constraint firstItem="RIO-6X-GG1" firstAttribute="top" secondItem="OO4-N7-9vp" secondAttribute="top" constant="5" id="ODH-TK-qMG"/>
+                                            <constraint firstAttribute="bottom" secondItem="RIO-6X-GG1" secondAttribute="bottom" constant="5" id="ajP-yK-idK"/>
+                                        </constraints>
+                                    </view>
+                                </subviews>
+                                <constraints>
+                                    <constraint firstItem="ogC-ph-Xdr" firstAttribute="top" secondItem="hTE-ys-qsF" secondAttribute="top" constant="620" id="9yP-Qs-EjJ"/>
+                                    <constraint firstItem="OO4-N7-9vp" firstAttribute="top" secondItem="hTE-ys-qsF" secondAttribute="top" constant="670" id="A9O-TK-Vz6" userLabel="View JSON.top = top + 550"/>
+                                    <constraint firstAttribute="trailing" secondItem="ogC-ph-Xdr" secondAttribute="trailing" constant="5" id="JLe-vC-Oyq"/>
+                                    <constraint firstAttribute="bottom" secondItem="OO4-N7-9vp" secondAttribute="bottom" id="MpX-OZ-MDh"/>
+                                    <constraint firstItem="OO4-N7-9vp" firstAttribute="width" secondItem="hTE-ys-qsF" secondAttribute="width" id="PWW-C3-Qcw"/>
+                                    <constraint firstItem="OO4-N7-9vp" firstAttribute="leading" secondItem="hTE-ys-qsF" secondAttribute="leading" id="Q4I-CY-qdY"/>
+                                    <constraint firstAttribute="trailing" secondItem="OO4-N7-9vp" secondAttribute="trailing" id="cmw-yc-oJP"/>
+                                    <constraint firstAttribute="trailing" secondItem="Sms-Ez-fLO" secondAttribute="trailing" constant="5" id="coa-ak-YgW"/>
+                                    <constraint firstItem="ogC-ph-Xdr" firstAttribute="leading" secondItem="hTE-ys-qsF" secondAttribute="leading" constant="5" id="dBH-cz-20F"/>
+                                    <constraint firstItem="Sms-Ez-fLO" firstAttribute="leading" secondItem="hTE-ys-qsF" secondAttribute="leading" constant="5" id="rIR-0f-qtc"/>
+                                    <constraint firstItem="Sms-Ez-fLO" firstAttribute="top" secondItem="hTE-ys-qsF" secondAttribute="top" constant="5" id="tsB-Td-qaK"/>
+                                </constraints>
+                            </scrollView>
+                        </subviews>
+                        <viewLayoutGuide key="safeArea" id="uBS-U0-Obf"/>
+                        <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                        <constraints>
+                            <constraint firstItem="uBS-U0-Obf" firstAttribute="trailing" secondItem="hTE-ys-qsF" secondAttribute="trailing" id="CvM-Hx-Hy6"/>
+                            <constraint firstItem="hTE-ys-qsF" firstAttribute="top" secondItem="uBS-U0-Obf" secondAttribute="top" id="Lar-wg-a7V"/>
+                            <constraint firstItem="hTE-ys-qsF" firstAttribute="leading" secondItem="uBS-U0-Obf" secondAttribute="leading" id="Mye-Fv-DDH"/>
+                            <constraint firstItem="uBS-U0-Obf" firstAttribute="bottom" secondItem="hTE-ys-qsF" secondAttribute="bottom" id="aJj-P3-LeK"/>
+                        </constraints>
+                    </view>
+                    <navigationItem key="navigationItem" id="fnR-ht-Muv"/>
+                    <connections>
+                        <outlet property="homeImage" destination="pyf-iS-YYP" id="gAh-bM-f5J"/>
+                        <outlet property="homeServer" destination="PD5-8h-ZLm" id="Hge-8o-YY1"/>
+                        <outlet property="imageActivity" destination="tl5-S1-p9X" id="pT5-X7-R2q"/>
+                        <outlet property="imageCollabora" destination="GXZ-Rj-WCE" id="cOv-xH-ZKC"/>
+                        <outlet property="imageComments" destination="cYf-px-izj" id="lU6-Vu-AKk"/>
+                        <outlet property="imageDeletedFiles" destination="8Zc-9W-n27" id="XYP-JN-JFc"/>
+                        <outlet property="imageEndToEndEncryption" destination="S7m-5Z-ktw" id="0Pv-Yt-YJB"/>
+                        <outlet property="imageExternalSite" destination="JWO-C0-32L" id="JKi-n1-5IQ"/>
+                        <outlet property="imageFileSharing" destination="G9c-Nd-Ikl" id="Wha-2g-8o0"/>
+                        <outlet property="imageLockFile" destination="eAC-Li-gKO" id="hHH-Pl-uOW"/>
+                        <outlet property="imageNotification" destination="cgb-3g-trc" id="fa5-99-76C"/>
+                        <outlet property="imageOnlyOffice" destination="xvv-h0-9bM" id="tw2-is-KHy"/>
+                        <outlet property="imageText" destination="iCB-2A-phO" id="uit-Ku-oOF"/>
+                        <outlet property="imageUserStatus" destination="Nxp-H1-Pob" id="LQg-7M-CNd"/>
+                        <outlet property="statusActivity" destination="dyb-1O-dIA" id="BfC-ZI-Wcr"/>
+                        <outlet property="statusCollabora" destination="30w-oC-gZl" id="wnj-OE-UQZ"/>
+                        <outlet property="statusComments" destination="lVT-MG-7kN" id="7tn-cz-EhW"/>
+                        <outlet property="statusDeletedFiles" destination="8hg-qK-qvz" id="xtt-gI-H0a"/>
+                        <outlet property="statusEndToEndEncryption" destination="M82-8U-M4Q" id="S9e-h3-GpF"/>
+                        <outlet property="statusExternalSite" destination="ivv-te-kaP" id="qzS-eo-Dq3"/>
+                        <outlet property="statusFileSharing" destination="SbT-rU-lJ8" id="zqA-0V-TLr"/>
+                        <outlet property="statusLockFile" destination="db5-st-O6M" id="dRt-zm-Qd7"/>
+                        <outlet property="statusNotification" destination="WAg-Hw-sQS" id="T5C-Ch-11o"/>
+                        <outlet property="statusOnlyOffice" destination="ucV-YG-5ht" id="11e-La-p9K"/>
+                        <outlet property="statusText" destination="uiz-H8-p3D" id="wLb-D2-MNS"/>
+                        <outlet property="statusUserStatus" destination="5Sj-5h-jgb" id="EXQ-7b-UCf"/>
+                        <outlet property="textView" destination="RIO-6X-GG1" id="lPs-Ev-hQf"/>
+                    </connections>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="0OV-l8-tBJ" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="910.14492753623199" y="133.25892857142856"/>
+        </scene>
+        <!--Navigation Controller-->
+        <scene sceneID="YbF-Ed-KAp">
+            <objects>
+                <navigationController automaticallyAdjustsScrollViewInsets="NO" id="vTK-Er-kbZ" sceneMemberID="viewController">
+                    <toolbarItems/>
+                    <navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="f2L-7c-zw8">
+                        <rect key="frame" x="0.0" y="44" width="414" height="44"/>
+                        <autoresizingMask key="autoresizingMask"/>
+                    </navigationBar>
+                    <nil name="viewControllers"/>
+                    <connections>
+                        <segue destination="7oH-vf-YqN" kind="relationship" relationship="rootViewController" id="ZBj-sH-5gE"/>
+                    </connections>
+                </navigationController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="RAt-Xm-BSR" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="0.0" y="133.25892857142856"/>
+        </scene>
+    </scenes>
+    <resources>
+        <image name="activity" width="512" height="512"/>
+        <image name="collabora" width="425" height="425"/>
+        <image name="comments" width="425" height="425"/>
+        <image name="delete" width="425" height="425"/>
+        <image name="externalsites" width="425" height="425"/>
+        <image name="home" width="120" height="120"/>
+        <image name="lock" width="24" height="24"/>
+        <image name="notification" width="512" height="512"/>
+        <image name="onlyoffice" width="425" height="425"/>
+        <image name="share" width="512" height="512"/>
+        <image name="text" width="425" height="425"/>
+        <image name="userStatusAway" width="425" height="425"/>
+        <systemColor name="labelColor">
+            <color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+        </systemColor>
+        <systemColor name="systemBackgroundColor">
+            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+        </systemColor>
+        <systemColor name="systemGray4Color">
+            <color red="0.81960784313725488" green="0.81960784313725488" blue="0.83921568627450982" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+        </systemColor>
+    </resources>
+</document>

+ 350 - 0
iOSClient/Diagnostics/NCCapabilitiesViewController.swift

@@ -0,0 +1,350 @@
+//
+//  NCCapabilitiesViewController.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 28/07/2020.
+//  Copyright © 2020 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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
+import NextcloudKit
+
+class NCCapabilitiesViewController: UIViewController, UIDocumentInteractionControllerDelegate {
+
+    @IBOutlet weak var textView: UITextView!
+
+    @IBOutlet weak var imageFileSharing: UIImageView!
+    @IBOutlet weak var statusFileSharing: UILabel!
+
+    @IBOutlet weak var imageExternalSite: UIImageView!
+    @IBOutlet weak var statusExternalSite: UILabel!
+
+    @IBOutlet weak var imageEndToEndEncryption: UIImageView!
+    @IBOutlet weak var statusEndToEndEncryption: UILabel!
+
+    @IBOutlet weak var imageActivity: UIImageView!
+    @IBOutlet weak var statusActivity: UILabel!
+
+    @IBOutlet weak var imageNotification: UIImageView!
+    @IBOutlet weak var statusNotification: UILabel!
+
+    @IBOutlet weak var imageDeletedFiles: UIImageView!
+    @IBOutlet weak var statusDeletedFiles: UILabel!
+
+    @IBOutlet weak var imageUserStatus: UIImageView!
+    @IBOutlet weak var statusUserStatus: UILabel!
+
+    @IBOutlet weak var imageComments: UIImageView!
+    @IBOutlet weak var statusComments: UILabel!
+
+    @IBOutlet weak var imageText: UIImageView!
+    @IBOutlet weak var statusText: UILabel!
+
+    @IBOutlet weak var imageCollabora: UIImageView!
+    @IBOutlet weak var statusCollabora: UILabel!
+
+    @IBOutlet weak var imageOnlyOffice: UIImageView!
+    @IBOutlet weak var statusOnlyOffice: UILabel!
+
+    @IBOutlet weak var homeImage: UIImageView!
+    @IBOutlet weak var homeServer: UILabel!
+
+    @IBOutlet weak var imageLockFile: UIImageView!
+    @IBOutlet weak var statusLockFile: UILabel!
+
+    private let appDelegate = UIApplication.shared.delegate as! AppDelegate
+    private var documentController: UIDocumentInteractionController?
+    private var account: String = ""
+    private var capabilitiesText = ""
+    // private var timer: Timer?
+
+    // MARK: - View Life Cycle
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+
+        self.title = NSLocalizedString("_capabilities_", comment: "")
+
+        let shareImage = UIImage(named: "shareFill")!.image(color: .gray, size: 25)
+        self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: shareImage, style: UIBarButtonItem.Style.plain, target: self, action: #selector(share))
+        self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: NSLocalizedString("_done_", comment: ""), style: UIBarButtonItem.Style.plain, target: self, action: #selector(close))
+
+        textView.layer.cornerRadius = 15
+        textView.layer.masksToBounds = true
+        textView.backgroundColor = .secondarySystemBackground
+
+        statusFileSharing.layer.cornerRadius = 12.5
+        statusFileSharing.layer.borderWidth = 0.5
+        statusFileSharing.layer.borderColor = UIColor.systemGray.cgColor
+        statusFileSharing.layer.masksToBounds = true
+        statusFileSharing.backgroundColor = .secondarySystemBackground
+
+        statusExternalSite.layer.cornerRadius = 12.5
+        statusExternalSite.layer.borderWidth = 0.5
+        statusExternalSite.layer.borderColor = UIColor.systemGray.cgColor
+        statusExternalSite.layer.masksToBounds = true
+        statusExternalSite.backgroundColor = .secondarySystemBackground
+
+        statusEndToEndEncryption.layer.cornerRadius = 12.5
+        statusEndToEndEncryption.layer.borderWidth = 0.5
+        statusEndToEndEncryption.layer.borderColor = UIColor.systemGray.cgColor
+        statusEndToEndEncryption.layer.masksToBounds = true
+        statusEndToEndEncryption.backgroundColor = .secondarySystemBackground
+
+        statusActivity.layer.cornerRadius = 12.5
+        statusActivity.layer.borderWidth = 0.5
+        statusActivity.layer.borderColor = UIColor.systemGray.cgColor
+        statusActivity.layer.masksToBounds = true
+        statusActivity.backgroundColor = .secondarySystemBackground
+
+        statusNotification.layer.cornerRadius = 12.5
+        statusNotification.layer.borderWidth = 0.5
+        statusNotification.layer.borderColor = UIColor.systemGray.cgColor
+        statusNotification.layer.masksToBounds = true
+        statusNotification.backgroundColor = .secondarySystemBackground
+
+        statusDeletedFiles.layer.cornerRadius = 12.5
+        statusDeletedFiles.layer.borderWidth = 0.5
+        statusDeletedFiles.layer.borderColor = UIColor.systemGray.cgColor
+        statusDeletedFiles.layer.masksToBounds = true
+        statusDeletedFiles.backgroundColor = .secondarySystemBackground
+
+        statusText.layer.cornerRadius = 12.5
+        statusText.layer.borderWidth = 0.5
+        statusText.layer.borderColor = UIColor.systemGray.cgColor
+        statusText.layer.masksToBounds = true
+        statusText.backgroundColor = .secondarySystemBackground
+
+        statusCollabora.layer.cornerRadius = 12.5
+        statusCollabora.layer.borderWidth = 0.5
+        statusCollabora.layer.borderColor = UIColor.systemGray.cgColor
+        statusCollabora.layer.masksToBounds = true
+        statusCollabora.backgroundColor = .secondarySystemBackground
+
+        statusOnlyOffice.layer.cornerRadius = 12.5
+        statusOnlyOffice.layer.borderWidth = 0.5
+        statusOnlyOffice.layer.borderColor = UIColor.systemGray.cgColor
+        statusOnlyOffice.layer.masksToBounds = true
+        statusOnlyOffice.backgroundColor = .secondarySystemBackground
+
+        statusUserStatus.layer.cornerRadius = 12.5
+        statusUserStatus.layer.borderWidth = 0.5
+        statusUserStatus.layer.borderColor = UIColor.systemGray.cgColor
+        statusUserStatus.layer.masksToBounds = true
+        statusUserStatus.backgroundColor = .secondarySystemBackground
+
+        statusComments.layer.cornerRadius = 12.5
+        statusComments.layer.borderWidth = 0.5
+        statusComments.layer.borderColor = UIColor.systemGray.cgColor
+        statusComments.layer.masksToBounds = true
+        statusComments.backgroundColor = .secondarySystemBackground
+
+        statusLockFile.layer.cornerRadius = 12.5
+        statusLockFile.layer.borderWidth = 0.5
+        statusLockFile.layer.borderColor = UIColor.systemGray.cgColor
+        statusLockFile.layer.masksToBounds = true
+        statusLockFile.backgroundColor = .secondarySystemBackground
+
+        imageFileSharing.image = UIImage(named: "share")!.image(color: UIColor.systemGray, size: 50)
+        imageExternalSite.image = NCUtility.shared.loadImage(named: "network", color: UIColor.systemGray)
+        imageEndToEndEncryption.image = NCUtility.shared.loadImage(named: "lock", color: UIColor.systemGray)
+        imageActivity.image = UIImage(named: "bolt")!.image(color: UIColor.systemGray, size: 50)
+        imageNotification.image = NCUtility.shared.loadImage(named: "bell", color: UIColor.systemGray)
+        imageDeletedFiles.image = NCUtility.shared.loadImage(named: "trash", color: UIColor.systemGray)
+        imageText.image = UIImage(named: "text")!.image(color: UIColor.systemGray, size: 50)
+        imageCollabora.image = UIImage(named: "collabora")!.image(color: UIColor.systemGray, size: 50)
+        imageOnlyOffice.image = UIImage(named: "onlyoffice")!.image(color: UIColor.systemGray, size: 50)
+        imageUserStatus.image = UIImage(named: "userStatusAway")!.image(color: UIColor.systemGray, size: 50)
+        imageComments.image = UIImage(named: "comments")!.image(color: UIColor.systemGray, size: 50)
+        imageLockFile.image = UIImage(named: "lock")!.image(color: UIColor.systemGray, size: 50)
+
+        guard let activeAccount = NCManageDatabase.shared.getActiveAccount() else { return }
+        self.account = activeAccount.account
+
+        if let text = NCManageDatabase.shared.getCapabilities(account: activeAccount.account) {
+            capabilitiesText = text
+            updateCapabilities()
+        } else {
+            let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_capabilities_found_")
+            NCContentPresenter.shared.showError(error: error, priority: .max)
+
+            DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
+                self.dismiss(animated: true, completion: nil)
+            }
+        }
+
+        homeImage.image = UIImage(named: "home")!.image(color: UIColor.systemGray, size: 50)
+        homeServer.text = NCUtilityFileSystem.shared.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) + "/"
+    }
+
+    @objc func updateCapabilities() {
+
+        NextcloudKit.shared.getCapabilities { account, data, error in
+            if error == .success && data != nil {
+                NCManageDatabase.shared.addCapabilitiesJSon(data!, account: account)
+
+                // EDITORS
+                let serverVersionMajor = NCManageDatabase.shared.getCapabilitiesServerInt(account: account, elements: NCElementsJSON.shared.capabilitiesVersionMajor)
+                if serverVersionMajor >= NCGlobal.shared.nextcloudVersion18 {
+                    NextcloudKit.shared.NCTextObtainEditorDetails { account, editors, creators, data, error in
+                        if error == .success && account == self.appDelegate.account {
+                            NCManageDatabase.shared.addDirectEditing(account: account, editors: editors, creators: creators)
+                            self.readCapabilities()
+                        }
+                        if self.view.window != nil {
+                            // self.timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(self.updateCapabilities), userInfo: nil, repeats: false)
+                        }
+                    }
+                } else {
+                    if self.view.window != nil {
+                        // self.timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(self.updateCapabilities), userInfo: nil, repeats: false)
+                    }
+                }
+
+                if let text = NCManageDatabase.shared.getCapabilities(account: account) {
+                    self.capabilitiesText = text
+                }
+                self.readCapabilities()
+            }
+        }
+
+        readCapabilities()
+    }
+
+    @objc func share() {
+        // timer?.invalidate()
+        self.dismiss(animated: true) {
+            let fileURL = NSURL.fileURL(withPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent("capabilities.txt")
+            do {
+                try self.capabilitiesText.write(to: fileURL, atomically: true, encoding: .utf8)
+
+                if let view = self.appDelegate.window?.rootViewController?.view {
+                    self.documentController = UIDocumentInteractionController(url: fileURL)
+                    self.documentController?.delegate = self
+                    self.documentController?.presentOptionsMenu(from: CGRect.zero, in: view, animated: true)
+                }
+            } catch { }
+        }
+    }
+
+    @objc func close() {
+        // timer?.invalidate()
+        self.dismiss(animated: true, completion: nil)
+    }
+
+    func readCapabilities() {
+
+        textView.text = capabilitiesText
+
+        if NCManageDatabase.shared.getCapabilitiesServerBool(account: account, elements: NCElementsJSON.shared.capabilitiesFileSharingApiEnabled, exists: false) {
+            statusFileSharing.text = "✓ " + NSLocalizedString("_available_", comment: "")
+        } else {
+            statusFileSharing.text = NSLocalizedString("_not_available_", comment: "")
+        }
+
+        if NCManageDatabase.shared.getCapabilitiesServerBool(account: account, elements: NCElementsJSON.shared.capabilitiesExternalSitesExists, exists: true) {
+            statusExternalSite.text = "✓ " + NSLocalizedString("_available_", comment: "")
+        } else {
+            statusExternalSite.text = NSLocalizedString("_not_available_", comment: "")
+        }
+
+        let isE2EEEnabled = NCManageDatabase.shared.getCapabilitiesServerBool(account: account, elements: NCElementsJSON.shared.capabilitiesE2EEEnabled, exists: false)
+        // let versionE2EE = NCManageDatabase.shared.getCapabilitiesServerString(account: account, elements: NCElementsJSON.shared.capabilitiesE2EEApiVersion)
+
+        if isE2EEEnabled {
+            statusEndToEndEncryption.text = "✓ " + NSLocalizedString("_available_", comment: "")
+        } else {
+            statusEndToEndEncryption.text = NSLocalizedString("_not_available_", comment: "")
+        }
+
+        let activity = NCManageDatabase.shared.getCapabilitiesServerArray(account: account, elements: NCElementsJSON.shared.capabilitiesActivity)
+        if activity != nil {
+            statusActivity.text = "✓ " + NSLocalizedString("_available_", comment: "")
+        } else {
+            statusActivity.text = NSLocalizedString("_not_available_", comment: "")
+        }
+
+        let notification = NCManageDatabase.shared.getCapabilitiesServerArray(account: account, elements: NCElementsJSON.shared.capabilitiesNotification)
+        if notification != nil {
+            statusNotification.text = "✓ " + NSLocalizedString("_available_", comment: "")
+        } else {
+            statusNotification.text = NSLocalizedString("_not_available_", comment: "")
+        }
+
+        let deleteFiles = NCManageDatabase.shared.getCapabilitiesServerBool(account: account, elements: NCElementsJSON.shared.capabilitiesFilesUndelete, exists: false)
+        if deleteFiles {
+            statusDeletedFiles.text = "✓ " + NSLocalizedString("_available_", comment: "")
+        } else {
+            statusDeletedFiles.text = NSLocalizedString("_not_available_", comment: "")
+        }
+
+        var textEditor = false
+        var onlyofficeEditors = false
+        if let editors = NCManageDatabase.shared.getDirectEditingEditors(account: account) {
+            for editor in editors {
+                if editor.editor == NCGlobal.shared.editorText {
+                    textEditor = true
+                } else if editor.editor == NCGlobal.shared.editorOnlyoffice {
+                    onlyofficeEditors = true
+                }
+            }
+        }
+
+        if textEditor {
+            statusText.text = "✓ " + NSLocalizedString("_available_", comment: "")
+        } else {
+            statusText.text = NSLocalizedString("_not_available_", comment: "")
+        }
+
+        let richdocumentsMimetypes = NCManageDatabase.shared.getCapabilitiesServerArray(account: account, elements: NCElementsJSON.shared.capabilitiesRichdocumentsMimetypes)
+        if richdocumentsMimetypes != nil {
+            statusCollabora.text = "✓ " + NSLocalizedString("_available_", comment: "")
+        } else {
+            statusCollabora.text = NSLocalizedString("_not_available_", comment: "")
+        }
+
+        if onlyofficeEditors {
+            statusOnlyOffice.text = "✓ " + NSLocalizedString("_available_", comment: "")
+        } else {
+            statusOnlyOffice.text = NSLocalizedString("_not_available_", comment: "")
+        }
+
+        let userStatus = NCManageDatabase.shared.getCapabilitiesServerBool(account: account, elements: NCElementsJSON.shared.capabilitiesUserStatusEnabled, exists: false)
+        if userStatus {
+            statusUserStatus.text = "✓ " + NSLocalizedString("_available_", comment: "")
+        } else {
+            statusUserStatus.text = NSLocalizedString("_not_available_", comment: "")
+        }
+
+        let comments = NCManageDatabase.shared.getCapabilitiesServerBool(account: account, elements: NCElementsJSON.shared.capabilitiesFilesComments, exists: false)
+        if comments {
+            statusComments.text = "✓ " + NSLocalizedString("_available_", comment: "")
+        } else {
+            statusComments.text = NSLocalizedString("_not_available_", comment: "")
+        }
+
+        let hasLockCapability = NCManageDatabase.shared.getCapabilitiesServerInt(account: appDelegate.account, elements: NCElementsJSON.shared.capabilitiesFilesLockVersion) >= 1
+        if hasLockCapability {
+            statusLockFile.text = "✓ " + NSLocalizedString("_available_", comment: "")
+        } else {
+            statusLockFile.text = NSLocalizedString("_not_available_", comment: "")
+        }
+
+        print("end.")
+    }
+}

+ 132 - 0
iOSClient/EmptyView/NCEmptyDataSet.swift

@@ -0,0 +1,132 @@
+//
+//  NCEmptyDataSet.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 19/10/2020.
+//  Copyright © 2020 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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
+
+public protocol NCEmptyDataSetDelegate: AnyObject {
+    func emptyDataSetView(_ view: NCEmptyView)
+}
+
+// optional func
+public extension NCEmptyDataSetDelegate {
+    func emptyDataSetView(_ view: NCEmptyView) {}
+}
+
+class NCEmptyDataSet: NSObject {
+
+    private var emptyView: NCEmptyView?
+    private var timer: Timer?
+    private var numberItemsForSections: Int = 0
+    private weak var delegate: NCEmptyDataSetDelegate?
+
+    private var fillBackgroundName: String = ""
+    private var fillBackgroundView = UIImageView()
+
+    private var centerXAnchor: NSLayoutConstraint?
+    private var centerYAnchor: NSLayoutConstraint?
+
+
+    init(view: UIView, offset: CGFloat = 0, delegate: NCEmptyDataSetDelegate?) {
+        super.init()
+
+        if let emptyView = UINib(nibName: "NCEmptyView", bundle: nil).instantiate(withOwner: self, options: nil).first as? NCEmptyView {
+
+            self.delegate = delegate
+            self.emptyView = emptyView
+
+            emptyView.isHidden = true
+            emptyView.translatesAutoresizingMaskIntoConstraints = false
+
+//            emptyView.backgroundColor = .red
+//            emptyView.isHidden = false
+
+            emptyView.emptyTitle.sizeToFit()
+            emptyView.emptyDescription.sizeToFit()
+
+            view.addSubview(emptyView)
+
+            emptyView.widthAnchor.constraint(equalToConstant: 350).isActive = true
+            emptyView.heightAnchor.constraint(equalToConstant: 250).isActive = true
+
+            if let view = view.superview {
+                centerXAnchor = emptyView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
+                centerYAnchor = emptyView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: offset)
+            } else {
+                centerXAnchor = emptyView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
+                centerYAnchor = emptyView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: offset)
+            }
+
+            centerXAnchor?.isActive = true
+            centerYAnchor?.isActive = true
+        }
+    }
+
+    func setOffset(_ offset: CGFloat) {
+
+        centerYAnchor?.constant = offset
+    }
+
+    func numberOfItemsInSection(_ num: Int, section: Int) {
+
+        if section == 0 {
+            numberItemsForSections = num
+        } else {
+            numberItemsForSections += num
+        }
+
+        if let emptyView = emptyView {
+
+            self.delegate?.emptyDataSetView(emptyView)
+
+            if !(timer?.isValid ?? false) && emptyView.isHidden == true {
+                timer = Timer.scheduledTimer(timeInterval: 0.3, target: self, selector: #selector(timerHandler(_:)), userInfo: nil, repeats: false)
+            }
+
+            if numberItemsForSections > 0 {
+                self.emptyView?.isHidden = true
+            }
+        }
+    }
+
+    @objc func timerHandler(_ timer: Timer) {
+
+        if numberItemsForSections == 0 {
+            self.emptyView?.isHidden = false
+        } else {
+            self.emptyView?.isHidden = true
+        }
+    }
+}
+
+public class NCEmptyView: UIView {
+
+    @IBOutlet weak var emptyImage: UIImageView!
+    @IBOutlet weak var emptyTitle: UILabel!
+    @IBOutlet weak var emptyDescription: UILabel!
+
+    public override func awakeFromNib() {
+        super.awakeFromNib()
+
+        emptyTitle.textColor = .label
+    }
+}

+ 67 - 0
iOSClient/EmptyView/NCEmptyView.xib

@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+    <device id="retina3_5" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
+        <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"/>
+    </dependencies>
+    <objects>
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+        <view contentMode="scaleToFill" id="iN0-l3-epB" customClass="NCEmptyView" customModule="Nextcloud" customModuleProvider="target">
+            <rect key="frame" x="0.0" y="0.0" width="350" height="250"/>
+            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+            <subviews>
+                <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="W3d-Us-kU4">
+                    <rect key="frame" x="100" y="0.0" width="150" height="150"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="150" id="A8B-y7-Fre"/>
+                        <constraint firstAttribute="width" constant="150" id="g0C-P6-l3d"/>
+                    </constraints>
+                </imageView>
+                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="crs-DO-owR">
+                    <rect key="frame" x="20" y="180" width="310" height="24"/>
+                    <fontDescription key="fontDescription" type="boldSystem" pointSize="20"/>
+                    <color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                    <nil key="highlightedColor"/>
+                </label>
+                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="D4p-sI-mNB">
+                    <rect key="frame" x="20" y="224" width="310" height="17"/>
+                    <constraints>
+                        <constraint firstAttribute="height" relation="lessThanOrEqual" constant="50" id="u7B-jW-bWI"/>
+                    </constraints>
+                    <fontDescription key="fontDescription" name=".AppleSystemUIFont" family=".AppleSystemUIFont" pointSize="14"/>
+                    <color key="textColor" systemColor="systemGrayColor"/>
+                    <nil key="highlightedColor"/>
+                </label>
+            </subviews>
+            <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
+            <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+            <constraints>
+                <constraint firstItem="crs-DO-owR" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="20" id="CMU-Tp-bUM"/>
+                <constraint firstItem="W3d-Us-kU4" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" id="Fyb-so-iAw"/>
+                <constraint firstItem="D4p-sI-mNB" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="20" id="egV-G4-wax"/>
+                <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="crs-DO-owR" secondAttribute="trailing" constant="20" id="hHl-iN-Gev"/>
+                <constraint firstItem="crs-DO-owR" firstAttribute="top" secondItem="W3d-Us-kU4" secondAttribute="bottom" constant="30" id="hLN-L6-0gH"/>
+                <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="D4p-sI-mNB" secondAttribute="trailing" constant="20" id="imv-AK-mqu"/>
+                <constraint firstItem="W3d-Us-kU4" firstAttribute="centerX" secondItem="vUN-kp-3ea" secondAttribute="centerX" id="kma-1Q-c3Q"/>
+                <constraint firstItem="D4p-sI-mNB" firstAttribute="top" secondItem="crs-DO-owR" secondAttribute="bottom" constant="20" id="zbi-5P-raN"/>
+            </constraints>
+            <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
+            <connections>
+                <outlet property="emptyDescription" destination="D4p-sI-mNB" id="4Rw-TK-oGc"/>
+                <outlet property="emptyImage" destination="W3d-Us-kU4" id="xtd-nV-OUc"/>
+                <outlet property="emptyTitle" destination="crs-DO-owR" id="IkU-6d-P64"/>
+            </connections>
+            <point key="canvasLocation" x="-146.25" y="32.5"/>
+        </view>
+    </objects>
+    <resources>
+        <systemColor name="systemGrayColor">
+            <color red="0.55686274509803924" green="0.55686274509803924" blue="0.57647058823529407" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+        </systemColor>
+    </resources>
+</document>

+ 40 - 0
iOSClient/Extensions/Array+Extension.swift

@@ -0,0 +1,40 @@
+//
+//  Array+Extension.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 16/08/22.
+//  Copyright © 2022 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//  Found in Internet
+//
+//  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 Foundation
+
+// https://stackoverflow.com/questions/33861036/unique-objects-inside-a-array-swift/45023247#45023247
+extension Array {
+
+    func unique<T: Hashable>(map: ((Element) -> (T))) -> [Element] {
+        var set = Set<T>() // the unique list kept in a Set for fast retrieval
+        var arrayOrdered = [Element]() // keeping the unique list of elements but ordered
+        for value in self where !set.contains(map(value)) {
+            set.insert(map(value))
+            arrayOrdered.append(value)
+        }
+
+        return arrayOrdered
+    }
+}

+ 41 - 0
iOSClient/Extensions/Data+Extension.swift

@@ -0,0 +1,41 @@
+//
+//  Data+Extension.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 24/01/23.
+//  Copyright © 2023 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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 Foundation
+
+extension Data {
+
+    func printJson() {
+        do {
+            let json = try JSONSerialization.jsonObject(with: self, options: [])
+            let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
+            guard let jsonString = String(data: data, encoding: .utf8) else {
+                print("Inavlid data")
+                return
+            }
+            print(jsonString)
+        } catch {
+            print("Error: \(error.localizedDescription)")
+        }
+    }
+}

+ 33 - 0
iOSClient/Extensions/DateFormatter+Extension.swift

@@ -0,0 +1,33 @@
+//
+//  DateFormatter+Extension.swift
+//  Nextcloud
+//
+//  Created by Henrik Storch on 18.03.22.
+//  Copyright © 2022 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 Foundation
+
+extension DateFormatter {
+    static let shareExpDate: DateFormatter = {
+        let dateFormatter = DateFormatter()
+        dateFormatter.formatterBehavior = .behavior10_4
+        dateFormatter.dateStyle = .medium
+        return dateFormatter
+    }()
+}

+ 38 - 0
iOSClient/Extensions/NSMutableAttributedString+Extension.swift

@@ -0,0 +1,38 @@
+//
+//  NSMutableAttributedString+Extension.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 26/05/21.
+//  Copyright © 2021 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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 Foundation
+import UIKit
+
+extension NSMutableAttributedString {
+
+    func setColor(color: UIColor, font: UIFont? = nil, forText stringValue: String) {
+
+        let range: NSRange = self.mutableString.range(of: stringValue, options: .caseInsensitive)
+
+        self.addAttribute(NSAttributedString.Key.foregroundColor, value: color, range: range)
+        if let font = font {
+            self.addAttribute(NSAttributedString.Key.font, value: font, range: range)
+        }
+    }
+}

+ 33 - 0
iOSClient/Extensions/NSNotificationCenter+MainThread.h

@@ -0,0 +1,33 @@
+//
+//  NSNotificationCenter+MainThread.h
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 28/05/17.
+//  Copyright (c) 2017 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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 <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
+
+@interface NSNotificationCenter (MainThread)
+
+- (void)postNotificationOnMainThread:(NSNotification *)notification;
+- (void)postNotificationOnMainThreadName:(NSString *)aName object:(id)anObject;
+- (void)postNotificationOnMainThreadName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo;
+
+@end

+ 45 - 0
iOSClient/Extensions/NSNotificationCenter+MainThread.m

@@ -0,0 +1,45 @@
+//
+//  NSNotificationCenter+MainThread.m
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 28/05/17.
+//  Copyright (c) 2017 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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 "NSNotificationCenter+MainThread.h"
+
+@implementation NSNotificationCenter (MainThread)
+
+- (void)postNotificationOnMainThread:(NSNotification *)notification
+{
+    [self performSelectorOnMainThread:@selector(postNotification:) withObject:notification waitUntilDone:YES];
+}
+
+- (void)postNotificationOnMainThreadName:(NSString *)aName object:(id)anObject
+{
+    NSNotification *notification = [NSNotification notificationWithName:aName object:anObject];
+    [self postNotificationOnMainThread:notification];
+}
+
+- (void)postNotificationOnMainThreadName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo
+{
+    NSNotification *notification = [NSNotification notificationWithName:aName object:anObject userInfo:aUserInfo];
+    [self postNotificationOnMainThread:notification];
+}
+
+@end

+ 34 - 0
iOSClient/Extensions/NotificationCenter+MainThread.swift

@@ -0,0 +1,34 @@
+//
+//  NotificationCenter+MainThread.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 16/06/2020.
+//  Copyright © 2020 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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 Foundation
+import UIKit
+
+extension NotificationCenter {
+
+    func postOnMainThread(name: String, object anObject: Any? = nil, userInfo aUserInfo: [AnyHashable: Any]? = nil, second: Double = 0) {
+        DispatchQueue.main.asyncAfter(deadline: .now() + second) {
+            NotificationCenter.default.post(name: Notification.Name(rawValue: name), object: anObject, userInfo: aUserInfo)
+        }
+    }
+}

+ 92 - 0
iOSClient/Extensions/String+Extension.swift

@@ -0,0 +1,92 @@
+//
+//  String+Extension.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 22/12/20.
+//  Copyright © 2020 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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 Foundation
+import UIKit
+import CryptoKit
+
+extension String {
+
+    var alphanumeric: String {
+        return self.components(separatedBy: CharacterSet.alphanumerics.inverted).joined().lowercased()
+    }
+
+    public var uppercaseInitials: String? {
+        let initials = self.components(separatedBy: .whitespaces)
+            .reduce("", {
+                guard $0.count < 2, let nextLetter = $1.first else { return $0 }
+                return $0 + nextLetter.uppercased()
+            })
+        return initials.isEmpty ? nil : initials
+    }
+
+    func formatSecondsToString(_ seconds: TimeInterval) -> String {
+        if seconds.isNaN {
+            return "00:00:00"
+        }
+        let sec = Int(seconds.truncatingRemainder(dividingBy: 60))
+        let min = Int(seconds.truncatingRemainder(dividingBy: 3600) / 60)
+        let hour = Int(seconds / 3600)
+        return String(format: "%02d:%02d:%02d", hour, min, sec)
+    }
+
+    func md5() -> String {
+        let digest = Insecure.MD5.hash(data: self.data(using: .utf8) ?? Data())
+        return digest.map {
+            String(format: "%02hhx", $0)
+        }.joined()
+    }
+
+    /* DEPRECATED iOS 13
+    func md5() -> String {
+        // https://stackoverflow.com/a/32166735/9506784
+
+        let length = Int(CC_MD5_DIGEST_LENGTH)
+        let messageData = self.data(using: .utf8) ?? Data()
+        var digestData = Data(count: length)
+
+        _ = digestData.withUnsafeMutableBytes { digestBytes -> UInt8 in
+            messageData.withUnsafeBytes { messageBytes -> UInt8 in
+                if let messageBytesBaseAddress = messageBytes.baseAddress, let digestBytesBlindMemory = digestBytes.bindMemory(to: UInt8.self).baseAddress {
+                    let messageLength = CC_LONG(messageData.count)
+                    CC_MD5(messageBytesBaseAddress, messageLength, digestBytesBlindMemory)
+                }
+                return 0
+            }
+        }
+
+        return digestData.map { String(format: "%02hhx", $0) }.joined()
+    }
+    */
+
+    var urlEncoded: String? {
+        // +        for historical reason, most web servers treat + as a replacement of whitespace
+        // ?, &     mark query pararmeter which should not be part of a url string, but added seperately
+        let urlAllowedCharSet = CharacterSet.urlQueryAllowed.subtracting(["+", "?", "&"])
+        return addingPercentEncoding(withAllowedCharacters: urlAllowedCharSet)
+    }
+}
+
+extension StringProtocol {
+    var firstUppercased: String { lowercased().prefix(1).uppercased() + dropFirst() }
+}

+ 101 - 0
iOSClient/Extensions/UIAlertController+Extension.swift

@@ -0,0 +1,101 @@
+//
+//  UIAlertController+Extension.swift
+//  Nextcloud
+//
+//  Created by Henrik Storch on 27.01.22.
+//  Copyright © 2022 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
+import NextcloudKit
+
+extension UIAlertController {
+    /// Creates a alert controller with a textfield, asking to create a new folder
+    /// - Parameters:
+    ///   - serverUrl: Server url of the location where the folder should be created
+    ///   - urlBase: UrlBase object
+    ///   - completion: If not` nil` it overrides the default behavior which shows an error using `NCContentPresenter`
+    /// - Returns: The presentable alert controller
+    static func createFolder(serverUrl: String, urlBase: NCUserBaseUrl, markE2ee: Bool = false, completion: ((_ error: NKError) -> Void)? = nil) -> UIAlertController {
+        let alertController = UIAlertController(title: NSLocalizedString("_create_folder_", comment: ""), message: nil, preferredStyle: .alert)
+
+        let okAction = UIAlertAction(title: NSLocalizedString("_save_", comment: ""), style: .default, handler: { _ in
+            guard let fileNameFolder = alertController.textFields?.first?.text else { return }
+            if markE2ee {
+                Task {
+                    let error = await NCNetworkingE2EECreateFolder.shared.createFolderAndMarkE2EE(fileName: fileNameFolder, serverUrl: serverUrl, account: urlBase.account)
+                    if error != .success {
+                        NCContentPresenter.shared.showError(error: error)
+                    }
+                }
+            } else {
+                NCNetworking.shared.createFolder(fileName: fileNameFolder, serverUrl: serverUrl, account: urlBase.account, urlBase: urlBase.urlBase, userId: urlBase.userId, overwrite: false, withPush: true) { error in
+                    if let completion = completion {
+                        completion(error)
+                    } else if error != .success {
+                        NCContentPresenter.shared.showError(error: error)
+                    } // else: successful, no action
+                }
+            }
+        })
+
+        // text field is initially empty, no action
+        okAction.isEnabled = false
+        let cancelAction = UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel)
+
+        alertController.addTextField { textField in
+            textField.autocapitalizationType = .words
+        }
+
+        // only allow saving if folder name exists
+        NotificationCenter.default.addObserver(
+            forName: UITextField.textDidChangeNotification,
+            object: alertController.textFields?.first,
+            queue: .main) { _ in
+                guard let text = alertController.textFields?.first?.text,
+                      let folderName = CCUtility.removeForbiddenCharactersServer(text)?.trimmingCharacters(in: .whitespaces) else { return }
+                okAction.isEnabled = !folderName.isEmpty && folderName != "." && folderName != ".."
+            }
+
+        alertController.addAction(cancelAction)
+        alertController.addAction(okAction)
+        return alertController
+    }
+
+    static func withTextField(titleKey: String, textFieldConfiguration: ((UITextField) -> Void)?, completion: @escaping (String?) -> Void) -> UIAlertController {
+        let alertController = UIAlertController(title: NSLocalizedString(titleKey, comment: ""), message: "", preferredStyle: .alert)
+        alertController.addTextField { textField in
+            textFieldConfiguration?(textField)
+        }
+        alertController.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .default) { _ in })
+        let okAction = UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .default) { _ in
+            completion(alertController.textFields?.first?.text)
+        }
+
+        alertController.addAction(okAction)
+        return alertController
+    }
+
+    static func password(titleKey: String, completion: @escaping (String?) -> Void) -> UIAlertController {
+        return .withTextField(titleKey: titleKey, textFieldConfiguration: { textField in
+            textField.isSecureTextEntry = true
+            textField.placeholder = NSLocalizedString("_password_", comment: "")
+        }, completion: completion)
+
+    }
+}

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

@@ -0,0 +1,34 @@
+//
+//  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
+        }
+    }
+}

+ 55 - 0
iOSClient/Extensions/UIBarButton+Extension.swift

@@ -0,0 +1,55 @@
+//
+//  UIBarButton+Extension.swift
+//  Nextcloud
+//
+//  Created by Henrik Storch on 27.01.22.
+//  Copyright © 2022 Henrik Storch. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//  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
+
+private var actionKey: Void?
+
+extension UIBarButtonItem {
+    // https://stackoverflow.com/a/36983811/9506784
+    private var _action: () -> Void {
+        get {
+            return objc_getAssociatedObject(self, &actionKey) as? () -> Void ?? { }
+        }
+        set {
+            objc_setAssociatedObject(self, &actionKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
+        }
+    }
+
+    convenience init(title: String?, style: UIBarButtonItem.Style, action: @escaping () -> Void) {
+        self.init(title: title, style: style, target: nil, action: #selector(pressed))
+        self.target = self
+        self._action = action
+    }
+
+    convenience init(image: UIImage?, style: UIBarButtonItem.Style, action: @escaping () -> Void) {
+        self.init(image: image, style: style, target: nil, action: #selector(pressed))
+        self.target = self
+        self._action = action
+    }
+
+    @objc private func pressed(sender: UIBarButtonItem) {
+        _action()
+    }
+}

+ 148 - 0
iOSClient/Extensions/UIColor+Extension.swift

@@ -0,0 +1,148 @@
+//
+//  UIColor+Extension.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 04/02/2020.
+//  Copyright © 2020 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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 Foundation
+import UIKit
+
+extension UIColor {
+
+    var hexString: String {
+
+        let cgColorInRGB = cgColor.converted(to: CGColorSpace(name: CGColorSpace.sRGB)!, intent: .defaultIntent, options: nil)!
+        let colorRef = cgColorInRGB.components
+        let r = colorRef?[0] ?? 0
+        let g = colorRef?[1] ?? 0
+        let b = ((colorRef?.count ?? 0) > 2 ? colorRef?[2] : g) ?? 0
+        let a = cgColor.alpha
+
+        var color = String(
+            format: "#%02lX%02lX%02lX",
+            lroundf(Float(r * 255)),
+            lroundf(Float(g * 255)),
+            lroundf(Float(b * 255))
+        )
+
+        if a < 1 {
+            color += String(format: "%02lX", lroundf(Float(a * 255)))
+        }
+
+        return color
+    }
+
+    @objc convenience init?(hex: String) {
+
+        let r, g, b, a: CGFloat
+
+        if hex.hasPrefix("#") {
+
+            let start = hex.index(hex.startIndex, offsetBy: 1)
+            let hexColor = String(hex[start...])
+            let scanner = Scanner(string: hexColor)
+            var hexNumber: UInt64 = 0
+
+            if hexColor.count == 6 && scanner.scanHexInt64(&hexNumber) {
+
+                r = CGFloat((hexNumber & 0xff0000) >> 16) / 255.0
+                g = CGFloat((hexNumber & 0x00ff00) >> 8) / 255.0
+                b = CGFloat(hexNumber & 0x0000ff) / 255.0
+
+                self.init(red: r, green: g, blue: b, alpha: 1)
+                return
+
+            } else if hexColor.count == 7 && scanner.scanHexInt64(&hexNumber) {
+
+                r = CGFloat((hexNumber & 0xff000000) >> 24) / 255
+                g = CGFloat((hexNumber & 0x00ff0000) >> 16) / 255
+                b = CGFloat((hexNumber & 0x0000ff00) >> 8) / 255
+                a = CGFloat(hexNumber & 0x000000ff) / 255
+
+                self.init(red: r, green: g, blue: b, alpha: a)
+                return
+            }
+        }
+
+        return nil
+    }
+
+    @objc func lighter(by percentage: CGFloat = 30.0) -> UIColor? {
+        return self.adjust(by: abs(percentage) )
+    }
+
+    @objc func darker(by percentage: CGFloat = 30.0) -> UIColor? {
+        return self.adjust(by: -1 * abs(percentage) )
+    }
+
+    func adjust(by percentage: CGFloat = 30.0) -> UIColor? {
+        var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0
+        if self.getRed(&red, green: &green, blue: &blue, alpha: &alpha) {
+            return UIColor(red: min(red + percentage / 100, 1.0),
+                           green: min(green + percentage / 100, 1.0),
+                           blue: min(blue + percentage / 100, 1.0),
+                           alpha: alpha)
+        } else {
+            return nil
+        }
+    }
+
+    @objc func isTooLight() -> Bool {
+
+        var white: CGFloat = 0.0
+        self.getWhite(&white, alpha: nil)
+        if white == 1 { return true }
+
+        guard let components = cgColor.components, components.count > 2 else {return false}
+        let brightness = ((components[0] * 299) + (components[1] * 587) + (components[2] * 114)) / 1000
+        return (brightness > 0.95)
+    }
+
+    @objc func isTooDark() -> Bool {
+
+        var white: CGFloat = 0.0
+        self.getWhite(&white, alpha: nil)
+        if white == 0 { return true }
+
+        guard let components = cgColor.components, components.count > 2 else {return false}
+        let brightness = ((components[0] * 299) + (components[1] * 587) + (components[2] * 114)) / 1000
+        return (brightness < 0.05)
+    }
+
+    func isLight(threshold: Float = 0.7) -> Bool {
+        let originalCGColor = self.cgColor
+
+        // Now we need to convert it to the RGB colorspace. UIColor.white / UIColor.black are greyscale and not RGB.
+        // If you don't do this then you will crash when accessing components index 2 below when evaluating greyscale colors.
+        let RGBCGColor = originalCGColor.converted(to: CGColorSpaceCreateDeviceRGB(), intent: .defaultIntent, options: nil)
+        guard let components = RGBCGColor?.components else { return false }
+        guard components.count >= 3 else { return false }
+
+        let brightness = Float(((components[0] * 299) + (components[1] * 587) + (components[2] * 114)) / 1000)
+        return (brightness > threshold)
+    }
+
+    func image(_ size: CGSize = CGSize(width: 1, height: 1)) -> UIImage {
+        return UIGraphicsImageRenderer(size: size).image { rendererContext in
+            self.setFill()
+            rendererContext.fill(CGRect(origin: .zero, size: size))
+        }
+    }
+}

+ 48 - 0
iOSClient/Extensions/UIControl+Extension.swift

@@ -0,0 +1,48 @@
+//
+//  UIControl+Extension.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 16/08/21.
+//  Copyright © 2021 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//  Found in Internet
+//
+//  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 Foundation
+
+public class ActionClosure {
+
+    public let selector: Selector
+    private let closure: (_ sendersender: Any?) -> Void
+
+    init(_ attachObj: AnyObject, closure: @escaping (_ sender: Any?) -> Void) {
+        self.closure = closure
+        self.selector = #selector(target(_ :))
+        objc_setAssociatedObject(attachObj, UUID().uuidString, self, .OBJC_ASSOCIATION_RETAIN)
+    }
+
+    @objc func target(_ sender: Any?) {
+        closure(sender)
+    }
+}
+
+public extension UIControl {
+    func action(for event: UIControl.Event, _ closure: @escaping (_ object: Any?) -> Void) {
+        let actionClosure = ActionClosure(self, closure: closure)
+        self.addTarget(actionClosure, action: actionClosure.selector, for: event)
+    }
+}

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

@@ -0,0 +1,38 @@
+//
+//  UIDevice+Extension.swift
+//  Nextcloud
+//
+//  Created by Federico Malagoni on 23/02/22.
+//  Copyright © 2022 Federico Malagoni. All rights reserved.
+//
+//  Author Federico Malagoni <federico.malagoni@astrairidium.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 Foundation
+
+extension UIDevice {
+
+    var hasNotch: Bool {
+        if #available(iOS 11.0, *) {
+            if UIApplication.shared.windows.isEmpty { return false }
+            let top = UIApplication.shared.windows[0].safeAreaInsets.top
+            return top > 20
+        } else {
+            // Fallback on earlier versions
+            return false
+        }
+    }
+}

+ 31 - 0
iOSClient/Extensions/UIFont+Extension.swift

@@ -0,0 +1,31 @@
+//
+//  UIFont+Extension.swift
+//  Nextcloud
+//
+//  Created by Federico Malagoni on 23/02/22.
+//  Copyright © 2022 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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 UIFont {
+
+    static func incosolataMedium(size: CGFloat) -> UIFont {
+        return UIFont(name: "Inconsolata-Medium", size: size)!
+    }
+}

+ 231 - 0
iOSClient/Extensions/UIImage+Extension.swift

@@ -0,0 +1,231 @@
+//
+//  UIImage+Extensions.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 27/11/2019.
+//  Copyright © 2019 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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 Foundation
+import UIKit
+import Accelerate
+
+extension UIImage {
+
+    @objc func resizeImage(size: CGSize, isAspectRation: Bool = true) -> UIImage? {
+
+        let originRatio = self.size.width / self.size.height
+        let newRatio = size.width / size.height
+        var newSize = size
+
+        if isAspectRation {
+            if originRatio < newRatio {
+                newSize.height = size.height
+                newSize.width = size.height * originRatio
+            } else {
+                newSize.width = size.width
+                newSize.height = size.width / originRatio
+            }
+        }
+
+        UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)
+        self.draw(in: CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height))
+        let newImage = UIGraphicsGetImageFromCurrentImageContext()
+        UIGraphicsEndImageContext()
+        if let image = newImage {
+            return image
+        }
+        return self
+    }
+
+    func fixedOrientation() -> UIImage? {
+
+        guard imageOrientation != UIImage.Orientation.up else {
+            // This is default orientation, don't need to do anything
+            return self.copy() as? UIImage
+        }
+
+        guard let cgImage = self.cgImage else {
+            // CGImage is not available
+            return nil
+        }
+
+        guard let colorSpace = cgImage.colorSpace,
+              let ctx = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else {
+            return nil // Not able to create CGContext
+        }
+
+        var transform: CGAffineTransform = CGAffineTransform.identity
+
+        switch imageOrientation {
+        case .down, .downMirrored:
+            transform = transform.translatedBy(x: size.width, y: size.height)
+            transform = transform.rotated(by: CGFloat.pi)
+        case .left, .leftMirrored:
+            transform = transform.translatedBy(x: size.width, y: 0)
+            transform = transform.rotated(by: CGFloat.pi / 2.0)
+        case .right, .rightMirrored:
+            transform = transform.translatedBy(x: 0, y: size.height)
+            transform = transform.rotated(by: CGFloat.pi / -2.0)
+        case .up, .upMirrored:
+            break
+        @unknown default:
+            break
+        }
+
+        // Flip image one more time if needed to, this is to prevent flipped image
+        switch imageOrientation {
+        case .upMirrored, .downMirrored:
+            transform = transform.translatedBy(x: size.width, y: 0)
+            transform = transform.scaledBy(x: -1, y: 1)
+        case .leftMirrored, .rightMirrored:
+            transform = transform.translatedBy(x: size.height, y: 0)
+            transform = transform.scaledBy(x: -1, y: 1)
+        case .up, .down, .left, .right:
+            break
+        @unknown default:
+            break
+        }
+
+        ctx.concatenate(transform)
+
+        switch imageOrientation {
+        case .left, .leftMirrored, .right, .rightMirrored:
+            ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.height, height: size.width))
+        default:
+            ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
+        }
+
+        guard let newCGImage = ctx.makeImage() else { return nil }
+        return UIImage(cgImage: newCGImage, scale: 1, orientation: .up)
+    }
+
+    @objc func image(color: UIColor, size: CGFloat) -> UIImage {
+
+        let size = CGSize(width: size, height: size)
+
+        UIGraphicsBeginImageContextWithOptions(size, false, self.scale)
+        color.setFill()
+
+        let context = UIGraphicsGetCurrentContext()
+        context?.translateBy(x: 0, y: size.height)
+        context?.scaleBy(x: 1.0, y: -1.0)
+        context?.setBlendMode(CGBlendMode.normal)
+
+        let rect = CGRect(origin: .zero, size: size)
+        guard let cgImage = self.cgImage else { return self }
+        context?.clip(to: rect, mask: cgImage)
+        context?.fill(rect)
+
+        let newImage = UIGraphicsGetImageFromCurrentImageContext() ?? self
+        UIGraphicsEndImageContext()
+
+        return newImage
+    }
+
+    func isEqualToImage(image: UIImage?) -> Bool {
+        if image == nil { return false }
+        let data1: NSData = self.pngData()! as NSData
+        let data2: NSData = image!.pngData()! as NSData
+        return data1.isEqual(data2)
+    }
+
+    class func imageWithView(_ view: UIView) -> UIImage {
+        UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0)
+        defer { UIGraphicsEndImageContext() }
+        view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
+        return UIGraphicsGetImageFromCurrentImageContext() ?? UIImage()
+    }
+
+    func image(alpha: CGFloat) -> UIImage? {
+        UIGraphicsBeginImageContextWithOptions(size, false, scale)
+        draw(at: .zero, blendMode: .normal, alpha: alpha)
+        let newImage = UIGraphicsGetImageFromCurrentImageContext()
+        UIGraphicsEndImageContext()
+        return newImage
+    }
+
+    /// Downsamles a image using ImageIO. Has better memory perfomance than redrawing using UIKit
+    ///
+    /// - [Source](https://swiftsenpai.com/development/reduce-uiimage-memory-footprint/)
+    /// - [Original Source, WWDC18](https://developer.apple.com/videos/play/wwdc2018/416/?time=1352)
+    /// - Parameters:
+    ///   - imageURL: The URL path of the image
+    ///   - pointSize: The target point size
+    ///   - scale: The point to pixel scale (Pixeld per point)
+    /// - Returns: The downsampled image, if successful
+    static func downsample(imageAt imageURL: URL, to pointSize: CGSize, scale: CGFloat = UIScreen.main.scale) -> UIImage? {
+
+        // Create an CGImageSource that represent an image
+        let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
+        guard let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL, imageSourceOptions) else { return nil }
+
+        // Calculate the desired dimension
+        let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale
+
+        // Perform downsampling
+        let downsampleOptions = [
+            kCGImageSourceCreateThumbnailFromImageAlways: true,
+            kCGImageSourceShouldCacheImmediately: true,
+            kCGImageSourceCreateThumbnailWithTransform: true,
+            kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels
+        ] as CFDictionary
+        guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else { return nil }
+
+        // Return the downsampled image as UIImage
+        return UIImage(cgImage: downsampledImage)
+    }
+
+    // Source:
+    // https://stackoverflow.com/questions/27092354/rotating-uiimage-in-swift/47402811#47402811
+
+    func rotate(radians: Float) -> UIImage? {
+        var newSize = CGRect(origin: CGPoint.zero, size: self.size).applying(CGAffineTransform(rotationAngle: CGFloat(radians))).size
+        // Trim off the extremely small float value to prevent core graphics from rounding it up
+        newSize.width = floor(newSize.width)
+        newSize.height = floor(newSize.height)
+
+        UIGraphicsBeginImageContextWithOptions(newSize, true, self.scale)
+        let context = UIGraphicsGetCurrentContext()!
+
+        // Move origin to middle
+        context.translateBy(x: newSize.width / 2, y: newSize.height / 2)
+        // Rotate around middle
+        context.rotate(by: CGFloat(radians))
+        // Draw the image at its center
+        self.draw(in: CGRect(x: -self.size.width / 2, y: -self.size.height / 2, width: self.size.width, height: self.size.height))
+
+        let newImage = UIGraphicsGetImageFromCurrentImageContext()
+        UIGraphicsEndImageContext()
+
+        return newImage
+    }
+
+    func colorizeFolder(metadata: tableMetadata, tableDirectory: tableDirectory? = nil) -> UIImage {
+        let serverUrl = metadata.serverUrl + "/" + metadata.fileName
+        var image = self
+        if let tableDirectory = tableDirectory {
+            if let hex = tableDirectory.colorFolder, let color = UIColor(hex: hex) {
+                image = self.withTintColor(color, renderingMode: .alwaysOriginal)
+            }
+        } else if let tableDirectory = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", metadata.account, serverUrl)), let hex = tableDirectory.colorFolder, let color = UIColor(hex: hex) {
+            image = self.withTintColor(color, renderingMode: .alwaysOriginal)
+        }
+        return image
+    }
+}

+ 53 - 0
iOSClient/Extensions/UIImage+animatedGIF.h

@@ -0,0 +1,53 @@
+//
+//  UIImage+animatedGIF
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 28/05/17.
+//  Copyright (c) 2017 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//  Found in Internet
+//
+//  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 <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
+#import <ImageIO/ImageIO.h>
+
+@interface UIImage (animatedGIF)
+
+/*
+    UIImage *animation = [UIImage animatedImageWithAnimatedGIFData:theData];
+    
+    I interpret `theData` as a GIF.  I create an animated `UIImage` using the source images in the GIF.
+    
+    The GIF stores a separate duration for each frame, in units of centiseconds (hundredths of a second).  However, a `UIImage` only has a single, total `duration` property, which is a floating-point number.
+    
+    To handle this mismatch, I add each source image (from the GIF) to `animation` a varying number of times to match the ratios between the frame durations in the GIF.
+    
+    For example, suppose the GIF contains three frames.  Frame 0 has duration 3.  Frame 1 has duration 9.  Frame 2 has duration 15.  I divide each duration by the greatest common denominator of all the durations, which is 3, and add each frame the resulting number of times.  Thus `animation` will contain frame 0 3/3 = 1 time, then frame 1 9/3 = 3 times, then frame 2 15/3 = 5 times.  I set `animation.duration` to (3+9+15)/100 = 0.27 seconds.
+*/
++ (UIImage *)animatedImageWithAnimatedGIFData:(NSData *)theData;
+
+/*
+    UIImage *image = [UIImage animatedImageWithAnimatedGIFURL:theURL];
+    
+    I interpret the contents of `theURL` as a GIF.  I create an animated `UIImage` using the source images in the GIF.
+    
+    I operate exactly like `+[UIImage animatedImageWithAnimatedGIFData:]`, except that I read the data from `theURL`.  If `theURL` is not a `file:` URL, you probably want to call me on a background thread or GCD queue to avoid blocking the main thread.
+*/
++ (UIImage *)animatedImageWithAnimatedGIFURL:(NSURL *)theURL;
+
+@end

+ 142 - 0
iOSClient/Extensions/UIImage+animatedGIF.m

@@ -0,0 +1,142 @@
+//
+//  UIImage+animatedGIF
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 28/05/17.
+//  Copyright (c) 2017 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//  Found in Internet
+//
+//  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 "UIImage+animatedGIF.h"
+
+#if __has_feature(objc_arc)
+#define toCF (__bridge CFTypeRef)
+#define fromCF (__bridge id)
+#else
+#define toCF (CFTypeRef)
+#define fromCF (id)
+#endif
+
+@implementation UIImage (animatedGIF)
+
+static int delayCentisecondsForImageAtIndex(CGImageSourceRef const source, size_t const i) {
+    int delayCentiseconds = 1;
+    CFDictionaryRef const properties = CGImageSourceCopyPropertiesAtIndex(source, i, NULL);
+    if (properties) {
+        CFDictionaryRef const gifProperties = CFDictionaryGetValue(properties, kCGImagePropertyGIFDictionary);
+        if (gifProperties) {
+            NSNumber *number = fromCF CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFUnclampedDelayTime);
+            if (number == NULL || [number doubleValue] == 0) {
+                number = fromCF CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFDelayTime);
+            }
+            if ([number doubleValue] > 0) {
+                // Even though the GIF stores the delay as an integer number of centiseconds, ImageIO “helpfully” converts that to seconds for us.
+                delayCentiseconds = (int)lrint([number doubleValue] * 100);
+            }
+        }
+        CFRelease(properties);
+    }
+    return delayCentiseconds;
+}
+
+static void createImagesAndDelays(CGImageSourceRef source, size_t count, CGImageRef imagesOut[count], int delayCentisecondsOut[count]) {
+    for (size_t i = 0; i < count; ++i) {
+        imagesOut[i] = CGImageSourceCreateImageAtIndex(source, i, NULL);
+        delayCentisecondsOut[i] = delayCentisecondsForImageAtIndex(source, i);
+    }
+}
+
+static int sum(size_t const count, int const *const values) {
+    int theSum = 0;
+    for (size_t i = 0; i < count; ++i) {
+        theSum += values[i];
+    }
+    return theSum;
+}
+
+static int pairGCD(int a, int b) {
+    if (a < b)
+        return pairGCD(b, a);
+    while (true) {
+        int const r = a % b;
+        if (r == 0)
+            return b;
+        a = b;
+        b = r;
+    }
+}
+
+static int vectorGCD(size_t const count, int const *const values) {
+    int gcd = values[0];
+    for (size_t i = 1; i < count; ++i) {
+        // Note that after I process the first few elements of the vector, `gcd` will probably be smaller than any remaining element.  By passing the smaller value as the second argument to `pairGCD`, I avoid making it swap the arguments.
+        gcd = pairGCD(values[i], gcd);
+    }
+    return gcd;
+}
+
+static NSArray *frameArray(size_t const count, CGImageRef const images[count], int const delayCentiseconds[count], int const totalDurationCentiseconds) {
+    int const gcd = vectorGCD(count, delayCentiseconds);
+    size_t const frameCount = totalDurationCentiseconds / gcd;
+    UIImage *frames[frameCount];
+    for (size_t i = 0, f = 0; i < count; ++i) {
+        UIImage *const frame = [UIImage imageWithCGImage:images[i]];
+        for (size_t j = delayCentiseconds[i] / gcd; j > 0; --j) {
+            frames[f++] = frame;
+        }
+    }
+    return [NSArray arrayWithObjects:frames count:frameCount];
+}
+
+static void releaseImages(size_t const count, CGImageRef const images[count]) {
+    for (size_t i = 0; i < count; ++i) {
+        CGImageRelease(images[i]);
+    }
+}
+
+static UIImage *animatedImageWithAnimatedGIFImageSource(CGImageSourceRef const source) {
+    size_t const count = CGImageSourceGetCount(source);
+    CGImageRef images[count];
+    int delayCentiseconds[count]; // in centiseconds
+    createImagesAndDelays(source, count, images, delayCentiseconds);
+    int const totalDurationCentiseconds = sum(count, delayCentiseconds);
+    NSArray *const frames = frameArray(count, images, delayCentiseconds, totalDurationCentiseconds);
+    UIImage *const animation = [UIImage animatedImageWithImages:frames duration:(NSTimeInterval)totalDurationCentiseconds / 100.0];
+    releaseImages(count, images);
+    return animation;
+}
+
+static UIImage *animatedImageWithAnimatedGIFReleasingImageSource(CGImageSourceRef CF_RELEASES_ARGUMENT source) {
+    if (source) {
+        UIImage *const image = animatedImageWithAnimatedGIFImageSource(source);
+        CFRelease(source);
+        return image;
+    } else {
+        return nil;
+    }
+}
+
++ (UIImage *)animatedImageWithAnimatedGIFData:(NSData *)data {
+    return animatedImageWithAnimatedGIFReleasingImageSource(CGImageSourceCreateWithData(toCF data, NULL));
+}
+
++ (UIImage *)animatedImageWithAnimatedGIFURL:(NSURL *)url {
+    return animatedImageWithAnimatedGIFReleasingImageSource(CGImageSourceCreateWithURL(toCF url, NULL));
+}
+
+@end

+ 79 - 0
iOSClient/Extensions/UINavigationController+Extension.swift

@@ -0,0 +1,79 @@
+//
+//  UINavigationController+Extension.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 02/08/2022.
+//  Copyright © 2022 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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 Foundation
+
+extension UINavigationController {
+
+    // https://stackoverflow.com/questions/6131205/how-to-find-topmost-view-controller-on-ios
+    override func topMostViewController() -> UIViewController {
+        return self.visibleViewController!.topMostViewController()
+    }
+
+    func setFileAppreance() {
+
+        navigationBar.tintColor = .systemBlue
+
+        let standardAppearance = UINavigationBarAppearance()
+        standardAppearance.configureWithDefaultBackground()
+
+        standardAppearance.largeTitleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.label]
+        standardAppearance.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.label]
+        standardAppearance.backgroundColor = .systemGray6
+        navigationBar.standardAppearance = standardAppearance
+
+        let scrollEdgeAppearance = UINavigationBarAppearance()
+        scrollEdgeAppearance.configureWithDefaultBackground()
+
+        scrollEdgeAppearance.backgroundColor = .systemBackground
+        scrollEdgeAppearance.shadowColor = .clear
+        scrollEdgeAppearance.shadowImage = UIImage()
+        navigationBar.scrollEdgeAppearance = scrollEdgeAppearance
+    }
+
+    func setGroupeAppreance() {
+
+        navigationBar.tintColor = .systemBlue
+
+        let standardAppearance = UINavigationBarAppearance()
+        standardAppearance.configureWithDefaultBackground()
+
+        standardAppearance.largeTitleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.label]
+        standardAppearance.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.label]
+        standardAppearance.backgroundColor = .systemGray6
+        navigationBar.standardAppearance = standardAppearance
+
+        let scrollEdgeAppearance = UINavigationBarAppearance()
+        scrollEdgeAppearance.configureWithDefaultBackground()
+
+        scrollEdgeAppearance.backgroundColor = .systemGroupedBackground
+        scrollEdgeAppearance.shadowColor = .clear
+        scrollEdgeAppearance.shadowImage = UIImage()
+        navigationBar.scrollEdgeAppearance = scrollEdgeAppearance
+    }
+
+    func setMediaAppreance() {
+
+        setNavigationBarHidden(true, animated: false)
+    }
+}

+ 32 - 0
iOSClient/Extensions/UITabBarController+Extension.swift

@@ -0,0 +1,32 @@
+//
+//  UITabBarController+Extension.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 02/08/2022.
+//  Copyright © 2022 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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 Foundation
+
+extension UITabBarController {
+
+    // https://stackoverflow.com/questions/6131205/how-to-find-topmost-view-controller-on-ios
+    override func topMostViewController() -> UIViewController {
+        return self.selectedViewController!.topMostViewController()
+    }
+}

+ 77 - 0
iOSClient/Extensions/UIToolbar+Extension.swift

@@ -0,0 +1,77 @@
+//
+//  UIToolbar+Extension.swift
+//  Nextcloud
+//
+//  Created by Henrik Storch on 18.03.22.
+//  Copyright © 2022 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 UIToolbar {
+    static func toolbar(onClear: (() -> Void)?, completion: @escaping () -> Void) -> UIToolbar {
+        let toolbar = UIToolbar()
+        toolbar.sizeToFit()
+        var buttons: [UIBarButtonItem] = []
+
+        if let onClear = onClear {
+            let clearButton = UIBarButtonItem(title: NSLocalizedString("_clear_", comment: ""), style: .plain) {
+                onClear()
+            }
+            buttons.append(clearButton)
+        }
+        buttons.append(UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: nil, action: nil))
+        let doneButton = UIBarButtonItem(title: NSLocalizedString("_done_", comment: ""), style: .done) {
+            completion()
+        }
+        buttons.append(doneButton)
+        toolbar.setItems(buttons, animated: false)
+        return toolbar
+    }
+
+    // by default inputAccessoryView does not respect safeArea
+    var wrappedSafeAreaContainer: UIView {
+        let view = InputBarWrapper()
+        view.addSubview(self)
+        self.translatesAutoresizingMaskIntoConstraints = false
+        NSLayoutConstraint.activate([
+            self.topAnchor.constraint(equalTo: view.topAnchor),
+            self.leftAnchor.constraint(equalTo: view.leftAnchor),
+            self.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
+            self.rightAnchor.constraint(equalTo: view.rightAnchor)
+        ])
+        return view
+    }
+}
+
+// https://stackoverflow.com/a/67985180/9506784
+class InputBarWrapper: UIView {
+
+    var desiredHeight: CGFloat = 0 {
+        didSet { invalidateIntrinsicContentSize() }
+    }
+
+    override var intrinsicContentSize: CGSize { CGSize(width: 0, height: desiredHeight) }
+
+    required init?(coder aDecoder: NSCoder) { fatalError() }
+
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        autoresizingMask = .flexibleHeight
+    }
+}

+ 48 - 0
iOSClient/Extensions/UIView+Extension.swift

@@ -0,0 +1,48 @@
+//
+//  UIView+Extension.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 14/12/2022.
+//  Copyright © 2020 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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 Foundation
+import UIKit
+
+extension UIView {
+
+    // Source
+    // https://stackoverflow.com/questions/18680028/prevent-screen-capture-in-an-ios-app/67054892#67054892
+    //
+    // private weak var scrollView: UIScrollView! (it's an outlet)
+    // self.view.preventScreenshot(for: self.scrollView)
+    //
+    func preventScreenshot(for view: UIView) {
+        let textField = UITextField()
+        textField.isSecureTextEntry = true
+        textField.isUserInteractionEnabled = false
+        guard let hiddenView = textField.layer.sublayers?.first?.delegate as? UIView else {
+            return
+        }
+        hiddenView.subviews.forEach { $0.removeFromSuperview() }
+        hiddenView.translatesAutoresizingMaskIntoConstraints = false
+        self.addSubview(hiddenView)
+        hiddenView.fillSuperview()
+        hiddenView.addSubview(view)
+    }
+}

+ 64 - 0
iOSClient/Extensions/UIViewController+Extension.swift

@@ -0,0 +1,64 @@
+//
+//  UIViewController+Extension.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 02/08/2022.
+//  Copyright © 2022 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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 Foundation
+
+extension UIViewController {
+
+    // https://stackoverflow.com/questions/6131205/how-to-find-topmost-view-controller-on-ios
+    @objc func topMostViewController() -> UIViewController {
+        // Handling Modal views
+        if let presentedViewController = self.presentedViewController {
+            return presentedViewController.topMostViewController()
+        }
+        // Handling UIViewController's added as subviews to some other views.
+        else {
+            for view in self.view.subviews {
+                // Key property which most of us are unaware of / rarely use.
+                if let subViewController = view.next {
+                    if subViewController is UIViewController {
+                        if let viewController = subViewController as? UIViewController {
+                            return viewController.topMostViewController()
+                        }
+                    }
+                }
+            }
+            return self
+        }
+    }
+
+    // https://stackoverflow.com/questions/23620276/how-to-check-if-a-view-controller-is-presented-modally-or-pushed-on-a-navigation
+    var isModal: Bool {
+        if let index = navigationController?.viewControllers.firstIndex(of: self), index > 0 {
+            return false
+        } else if presentingViewController != nil {
+            return true
+        } else if navigationController?.presentingViewController?.presentedViewController == navigationController {
+            return true
+        } else if tabBarController?.presentingViewController is UITabBarController {
+            return true
+        } else {
+            return false
+        }
+    }
+}

+ 16 - 0
iOSClient/Extensions/View+Extension.swift

@@ -0,0 +1,16 @@
+//
+//  View+Extension.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 29/12/22.
+//  Copyright © 2022 Marino Faggiana. All rights reserved.
+//
+
+import SwiftUI
+
+extension View {
+
+    func complexModifier<V: View>(@ViewBuilder _ closure: (Self) -> V) -> some View {
+        closure(self)
+    }
+}

+ 53 - 0
iOSClient/Favorites/NCFavorite.storyboard

@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17156" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="EFX-fO-Oip">
+    <device id="retina5_9" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <scenes>
+        <!--Favorite-->
+        <scene sceneID="X4W-6b-l7s">
+            <objects>
+                <viewController storyboardIdentifier="NCFavorite.storyboard" extendedLayoutIncludesOpaqueBars="YES" id="EFX-fO-Oip" customClass="NCFavorite" customModule="Nextcloud" customModuleProvider="target" sceneMemberID="viewController">
+                    <view key="view" contentMode="scaleToFill" id="QEs-gO-Cmp">
+                        <rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <subviews>
+                            <collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="Zaz-Cl-qpZ">
+                                <rect key="frame" x="0.0" y="0.0" width="375" height="813"/>
+                                <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                <collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="0.0" minimumInteritemSpacing="0.0" id="fF1-wd-0xN">
+                                    <size key="itemSize" width="0.0" height="0.0"/>
+                                    <size key="headerReferenceSize" width="0.0" height="0.0"/>
+                                    <size key="footerReferenceSize" width="0.0" height="0.0"/>
+                                    <inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
+                                </collectionViewFlowLayout>
+                                <cells/>
+                                <connections>
+                                    <outlet property="dataSource" destination="EFX-fO-Oip" id="2On-qP-zuG"/>
+                                    <outlet property="delegate" destination="EFX-fO-Oip" id="s3n-CL-8X2"/>
+                                </connections>
+                            </collectionView>
+                        </subviews>
+                        <viewLayoutGuide key="safeArea" id="Meh-VD-wWh"/>
+                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                        <constraints>
+                            <constraint firstItem="Zaz-Cl-qpZ" firstAttribute="leading" secondItem="Meh-VD-wWh" secondAttribute="leading" id="1bp-sm-u0X"/>
+                            <constraint firstItem="Meh-VD-wWh" firstAttribute="trailing" secondItem="Zaz-Cl-qpZ" secondAttribute="trailing" id="aNd-UL-hmu"/>
+                            <constraint firstItem="Meh-VD-wWh" firstAttribute="bottom" secondItem="Zaz-Cl-qpZ" secondAttribute="bottom" constant="-35" id="aNr-tf-2AH"/>
+                            <constraint firstItem="Zaz-Cl-qpZ" firstAttribute="top" secondItem="QEs-gO-Cmp" secondAttribute="top" id="tji-wt-R7s"/>
+                        </constraints>
+                    </view>
+                    <connections>
+                        <outlet property="collectionView" destination="Zaz-Cl-qpZ" id="8oA-Gx-z7T"/>
+                    </connections>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="JJ0-Le-6eT" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="256.80000000000001" y="228.32512315270938"/>
+        </scene>
+    </scenes>
+</document>

+ 125 - 0
iOSClient/Favorites/NCFavorite.swift

@@ -0,0 +1,125 @@
+//
+//  NCFavorite.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 26/08/2020.
+//  Copyright © 2020 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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
+import NextcloudKit
+
+class NCFavorite: NCCollectionViewCommon {
+
+    // MARK: - View Life Cycle
+
+    required init?(coder aDecoder: NSCoder) {
+        super.init(coder: aDecoder)
+
+        titleCurrentFolder = NSLocalizedString("_favorites_", comment: "")
+        layoutKey = NCGlobal.shared.layoutViewFavorite
+        enableSearchBar = false
+        headerMenuButtonsCommand = false
+        headerMenuButtonsView = true
+        headerRichWorkspaceDisable = true
+        emptyImage = UIImage(named: "star.fill")?.image(color: NCBrandColor.shared.yellowFavorite, size: UIScreen.main.bounds.width)
+        emptyTitle = "_favorite_no_files_"
+        emptyDescription = "_tutorial_favorite_view_"
+    }
+
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+
+        navigationController?.setFileAppreance()
+    }
+
+    // MARK: - DataSource + NC Endpoint
+
+    override func reloadDataSource(forced: Bool = true) {
+        super.reloadDataSource()
+
+        DispatchQueue.global().async {
+            var metadatas: [tableMetadata] = []
+
+            if self.serverUrl.isEmpty {
+                metadatas = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND favorite == true", self.appDelegate.account))
+            } else {
+                metadatas = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", self.appDelegate.account, self.serverUrl))
+            }
+
+            self.dataSource = NCDataSource(metadatas: metadatas,
+                                           account: self.appDelegate.account,
+                                           sort: self.layoutForView?.sort,
+                                           ascending: self.layoutForView?.ascending,
+                                           directoryOnTop: self.layoutForView?.directoryOnTop,
+                                           favoriteOnTop: true,
+                                           filterLivePhoto: true,
+                                           groupByField: self.groupByField,
+                                           providers: self.providers,
+                                           searchResults: self.searchResults)
+
+            DispatchQueue.main.async {
+                self.refreshControl.endRefreshing()
+                self.collectionView.reloadData()
+            }
+        }
+    }
+
+    override func reloadDataSourceNetwork(forced: Bool = false) {
+        super.reloadDataSourceNetwork(forced: forced)
+
+        isReloadDataSourceNetworkInProgress = true
+        collectionView?.reloadData()
+
+        if serverUrl.isEmpty {
+
+            NCNetworking.shared.listingFavoritescompletion(selector: NCGlobal.shared.selectorListingFavorite) { _, _, error in
+                if error != .success {
+                    NCContentPresenter.shared.showError(error: error)
+                }
+
+                DispatchQueue.main.async {
+                    self.refreshControl.endRefreshing()
+                    self.isReloadDataSourceNetworkInProgress = false
+                    self.reloadDataSource()
+                }
+            }
+
+        } else {
+
+            networkReadFolder(forced: forced) { tableDirectory, metadatas, metadatasUpdate, metadatasDelete, error in
+                if error == .success, let metadatas = metadatas {
+                    for metadata in metadatas where (!metadata.directory && NCManageDatabase.shared.isDownloadMetadata(metadata, download: false)) {
+                        NCOperationQueue.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorDownloadFile)
+                    }
+                }
+
+                DispatchQueue.main.async {
+                    self.refreshControl.endRefreshing()
+                    self.isReloadDataSourceNetworkInProgress = false
+                    self.richWorkspaceText = tableDirectory?.richWorkspace
+                    if metadatasUpdate?.count ?? 0 > 0 || metadatasDelete?.count ?? 0 > 0 || forced {
+                        self.reloadDataSource()
+                    } else {
+                        self.collectionView?.reloadData()
+                    }
+                }
+            }
+        }
+    }
+}

+ 53 - 0
iOSClient/Files/NCFiles.storyboard

@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17156" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="EFX-fO-Oip">
+    <device id="retina5_9" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <scenes>
+        <!--Files-->
+        <scene sceneID="X4W-6b-l7s">
+            <objects>
+                <viewController storyboardIdentifier="NCFiles.storyboard" extendedLayoutIncludesOpaqueBars="YES" id="EFX-fO-Oip" customClass="NCFiles" customModule="Nextcloud" customModuleProvider="target" sceneMemberID="viewController">
+                    <view key="view" contentMode="scaleToFill" id="QEs-gO-Cmp">
+                        <rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <subviews>
+                            <collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="Zaz-Cl-qpZ">
+                                <rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
+                                <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                <collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="0.0" minimumInteritemSpacing="0.0" id="fF1-wd-0xN">
+                                    <size key="itemSize" width="0.0" height="0.0"/>
+                                    <size key="headerReferenceSize" width="0.0" height="0.0"/>
+                                    <size key="footerReferenceSize" width="0.0" height="0.0"/>
+                                    <inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
+                                </collectionViewFlowLayout>
+                                <cells/>
+                                <connections>
+                                    <outlet property="dataSource" destination="EFX-fO-Oip" id="2On-qP-zuG"/>
+                                    <outlet property="delegate" destination="EFX-fO-Oip" id="s3n-CL-8X2"/>
+                                </connections>
+                            </collectionView>
+                        </subviews>
+                        <viewLayoutGuide key="safeArea" id="Meh-VD-wWh"/>
+                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                        <constraints>
+                            <constraint firstItem="Zaz-Cl-qpZ" firstAttribute="leading" secondItem="Meh-VD-wWh" secondAttribute="leading" id="1bp-sm-u0X"/>
+                            <constraint firstItem="Meh-VD-wWh" firstAttribute="trailing" secondItem="Zaz-Cl-qpZ" secondAttribute="trailing" id="aNd-UL-hmu"/>
+                            <constraint firstItem="Meh-VD-wWh" firstAttribute="bottom" secondItem="Zaz-Cl-qpZ" secondAttribute="bottom" constant="-34" id="aNr-tf-2AH"/>
+                            <constraint firstItem="Zaz-Cl-qpZ" firstAttribute="top" secondItem="QEs-gO-Cmp" secondAttribute="top" id="tji-wt-R7s"/>
+                        </constraints>
+                    </view>
+                    <connections>
+                        <outlet property="collectionView" destination="Zaz-Cl-qpZ" id="8oA-Gx-z7T"/>
+                    </connections>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="JJ0-Le-6eT" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="256.80000000000001" y="228.32512315270938"/>
+        </scene>
+    </scenes>
+</document>

+ 198 - 0
iOSClient/Files/NCFiles.swift

@@ -0,0 +1,198 @@
+//
+//  NCFiles.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 26/09/2020.
+//  Copyright © 2020 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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
+import NextcloudKit
+
+class NCFiles: NCCollectionViewCommon {
+
+    internal var isRoot: Bool = true
+    internal var fileNameBlink: String?
+    internal var fileNameOpen: String?
+
+    // MARK: - View Life Cycle
+
+    required init?(coder aDecoder: NSCoder) {
+        super.init(coder: aDecoder)
+
+        titleCurrentFolder = NCBrandOptions.shared.brand
+        layoutKey = NCGlobal.shared.layoutViewFiles
+        enableSearchBar = true
+        headerMenuButtonsCommand = true
+        headerMenuButtonsView = true
+        headerRichWorkspaceDisable = false
+        emptyImage = UIImage(named: "folder")?.image(color: NCBrandColor.shared.brandElement, size: UIScreen.main.bounds.width)
+        emptyTitle = "_files_no_files_"
+        emptyDescription = "_no_file_pull_down_"
+    }
+
+    override func viewWillAppear(_ animated: Bool) {
+
+        if isRoot {
+            serverUrl = NCUtilityFileSystem.shared.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId)
+            titleCurrentFolder = getNavigationTitle()
+        }
+        super.viewWillAppear(animated)
+
+        navigationController?.setFileAppreance()
+    }
+
+    override func viewWillDisappear(_ animated: Bool) {
+        super.viewWillDisappear(animated)
+
+        fileNameBlink = nil
+        fileNameOpen = nil
+    }
+
+    // MARK: - NotificationCenter
+
+    override func initialize() {
+
+        if isRoot {
+            serverUrl = NCUtilityFileSystem.shared.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId)
+            titleCurrentFolder = getNavigationTitle()
+        }
+        super.initialize()
+
+        /*
+        if let userInfo = notification.userInfo as NSDictionary?, userInfo["atStart"] as? Int == 1 {
+            return
+        }
+        */
+
+        reloadDataSource(forced: false)
+        reloadDataSourceNetwork()
+    }
+
+    // MARK: - DataSource + NC Endpoint
+    //
+    // forced: do no make the etag of directory test (default)
+    //
+
+    override func reloadDataSource(forced: Bool = true) {
+        super.reloadDataSource()
+
+        DispatchQueue.main.async { self.refreshControl.endRefreshing() }
+        DispatchQueue.global().async {
+            guard !self.isSearchingMode, !self.appDelegate.account.isEmpty, !self.appDelegate.urlBase.isEmpty, !self.serverUrl.isEmpty else { return }
+
+            let metadatas = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", self.appDelegate.account, self.serverUrl))
+            if self.metadataFolder == nil {
+                self.metadataFolder = NCManageDatabase.shared.getMetadataFolder(account: self.appDelegate.account, urlBase: self.appDelegate.urlBase, userId: self.appDelegate.userId, serverUrl: self.serverUrl)
+            }
+            let directory = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", self.appDelegate.account, self.serverUrl))
+            let metadataTransfer = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "status != %i AND serverUrl == %@", NCGlobal.shared.metadataStatusNormal, self.serverUrl))
+            self.richWorkspaceText = directory?.richWorkspace
+
+            // FORCED false: test the directory.etag
+            if !forced, let directory = directory, directory.etag == self.dataSource.directory?.etag, metadataTransfer == nil, self.fileNameBlink == nil, self.fileNameOpen == nil {
+                return
+            }
+
+            self.dataSource = NCDataSource(
+                metadatas: metadatas,
+                account: self.appDelegate.account,
+                directory: directory,
+                sort: self.layoutForView?.sort,
+                ascending: self.layoutForView?.ascending,
+                directoryOnTop: self.layoutForView?.directoryOnTop,
+                favoriteOnTop: true,
+                filterLivePhoto: true,
+                groupByField: self.groupByField,
+                providers: self.providers,
+                searchResults: self.searchResults)
+
+            DispatchQueue.main.async {
+                self.collectionView.reloadData()
+                if !self.dataSource.metadatas.isEmpty {
+                    self.blinkCell(fileName: self.fileNameBlink)
+                    self.openFile(fileName: self.fileNameOpen)
+                    self.fileNameBlink = nil
+                    self.fileNameOpen = nil
+                }
+            }
+        }
+    }
+
+    override func reloadDataSourceNetwork(forced: Bool = false) {
+        super.reloadDataSourceNetwork(forced: forced)
+        guard !isSearchingMode else {
+            networkSearch()
+            return
+        }
+        isReloadDataSourceNetworkInProgress = true
+        collectionView?.reloadData()
+
+        networkReadFolder(forced: forced) { tableDirectory, metadatas, metadatasUpdate, metadatasDelete, error in
+            if error == .success {
+                for metadata in metadatas ?? [] where !metadata.directory && NCManageDatabase.shared.isDownloadMetadata(metadata, download: false) {
+                    NCOperationQueue.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorDownloadFile)
+                }
+            }
+
+            self.isReloadDataSourceNetworkInProgress = false
+            self.richWorkspaceText = tableDirectory?.richWorkspace
+
+            if metadatasUpdate?.count ?? 0 > 0 || metadatasDelete?.count ?? 0 > 0 || forced {
+                self.reloadDataSource()
+            } else if self.dataSource.getMetadataSourceForAllSections().isEmpty {
+                DispatchQueue.main.async {
+                    self.collectionView.reloadData()
+                }
+            }
+        }
+    }
+
+    func blinkCell(fileName: String?) {
+
+        if let fileName = fileName, let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName == %@", self.appDelegate.account, self.serverUrl, fileName)) {
+            let (indexPath, _) = self.dataSource.getIndexPathMetadata(ocId: metadata.ocId)
+            if let indexPath = indexPath {
+                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+                    UIView.animate(withDuration: 0.3) {
+                        self.collectionView.scrollToItem(at: indexPath, at: .centeredVertically, animated: false)
+                    } completion: { _ in
+                        if let cell = self.collectionView.cellForItem(at: indexPath) {
+                            cell.backgroundColor = .darkGray
+                            UIView.animate(withDuration: 2) {
+                                cell.backgroundColor = .clear
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    func openFile(fileName: String?) {
+
+        if let fileName = fileName, let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName == %@", self.appDelegate.account, self.serverUrl, fileName)) {
+            let (indexPath, _) = self.dataSource.getIndexPathMetadata(ocId: metadata.ocId)
+            if let indexPath = indexPath {
+                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+                    self.collectionView(self.collectionView, didSelectItemAt: indexPath)
+                }
+            }
+        }
+    }
+}

BIN
iOSClient/Font/Inconsolata/Inconsolata-Black.ttf


BIN
iOSClient/Font/Inconsolata/Inconsolata-Bold.ttf


BIN
iOSClient/Font/Inconsolata/Inconsolata-ExtraBold.ttf


BIN
iOSClient/Font/Inconsolata/Inconsolata-ExtraLight.ttf


BIN
iOSClient/Font/Inconsolata/Inconsolata-Light.ttf


BIN
iOSClient/Font/Inconsolata/Inconsolata-Medium.ttf


BIN
iOSClient/Font/Inconsolata/Inconsolata-Regular.ttf


BIN
iOSClient/Font/Inconsolata/Inconsolata-SemiBold.ttf


+ 59 - 0
iOSClient/GUI/ComponentView.swift

@@ -0,0 +1,59 @@
+//
+//  ComponentView.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 05/01/23.
+//  Copyright © 2023 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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 SwiftUI
+
+struct TextFieldClearButton: ViewModifier {
+
+    @Binding var text: String
+
+    func body(content: Content) -> some View {
+        HStack {
+            content
+            if !text.isEmpty {
+                Button(
+                    action: { self.text = "" },
+                    label: {
+                        Image(systemName: "xmark.circle.fill")
+                            .foregroundColor(Color(UIColor.placeholderText))
+                    }
+                ).buttonStyle(BorderlessButtonStyle())
+            }
+        }
+    }
+}
+
+struct ButtonRounded: ButtonStyle {
+
+    var disabled = false
+
+    func makeBody(configuration: Configuration) -> some View {
+        configuration.label
+            .padding(.horizontal, 40)
+            .padding(.vertical, 10)
+            .background(disabled ? Color(UIColor.placeholderText) : Color(NCBrandColor.shared.brand))
+            .foregroundColor(disabled ? Color(UIColor.placeholderText) : Color(NCBrandColor.shared.brandText))
+            .clipShape(Capsule())
+            .opacity(configuration.isPressed ? 0.5 : 1.0)
+    }
+}

+ 98 - 0
iOSClient/GUI/HUDView.swift

@@ -0,0 +1,98 @@
+//
+//  HUDView.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 02/01/23.
+//  Copyright © 2023 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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 SwiftUI
+
+struct HUDView: View {
+
+    @Binding var showHUD: Bool
+    @State var textLabel: String
+    @State var image: String
+
+    var body: some View {
+        Button(action: {
+            withAnimation {
+                self.showHUD = false
+            }
+        }) {
+            Label(textLabel, systemImage: image)
+                .foregroundColor(.white)
+                .padding(.horizontal, 10)
+                .padding(14)
+                .background(
+                    Blur(style: .regular)
+                        .clipShape(Capsule())
+                        .shadow(color: Color(.black).opacity(0.22), radius: 12, x: 0, y: 5)
+                    )
+        }.buttonStyle(PlainButtonStyle())
+    }
+}
+
+struct Blur: UIViewRepresentable {
+
+    var style: UIBlurEffect.Style
+
+    func makeUIView(context: Context) -> UIVisualEffectView {
+        let effectView = UIVisualEffectView(effect: UIBlurEffect(style: style))
+        effectView.backgroundColor = NCBrandColor.shared.brand
+        return effectView
+    }
+
+    func updateUIView(_ uiView: UIVisualEffectView, context: Context) {
+        uiView.effect = UIBlurEffect(style: style)
+    }
+}
+
+struct ContentView: View {
+
+    @State private var showHUD = false
+    @Namespace var hudAnimation
+
+    func dismissHUDAfterTime() {
+        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
+            self.showHUD = false
+        }
+    }
+
+    var body: some View {
+        GeometryReader { geo in
+            ZStack(alignment: .top) {
+                NavigationView {
+                    Button("Save image") {
+                        self.showHUD.toggle()
+                    }
+                    .navigationTitle("Content View")
+                }
+                HUDView(showHUD: $showHUD, textLabel: NSLocalizedString("_wait_", comment: ""), image: "doc.badge.arrow.up")
+                    .offset(y: showHUD ? (geo.size.height / 2) : -200)
+                    .animation(.easeOut)
+            }
+        }
+    }
+}
+
+struct HUDView_Previews: PreviewProvider {
+    static var previews: some View {
+        ContentView()
+    }
+}

+ 53 - 0
iOSClient/Groupfolders/NCGroupfolders.storyboard

@@ -0,0 +1,53 @@
+<?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="EFX-fO-Oip">
+    <device id="retina5_9" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <scenes>
+        <!--Groupfolders-->
+        <scene sceneID="X4W-6b-l7s">
+            <objects>
+                <viewController storyboardIdentifier="NCGroupfolders.storyboard" extendedLayoutIncludesOpaqueBars="YES" id="EFX-fO-Oip" customClass="NCGroupfolders" customModule="Nextcloud" customModuleProvider="target" sceneMemberID="viewController">
+                    <view key="view" contentMode="scaleToFill" id="QEs-gO-Cmp">
+                        <rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <subviews>
+                            <collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="Zaz-Cl-qpZ">
+                                <rect key="frame" x="0.0" y="0.0" width="375" height="813"/>
+                                <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                <collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="0.0" minimumInteritemSpacing="0.0" id="fF1-wd-0xN">
+                                    <size key="itemSize" width="0.0" height="0.0"/>
+                                    <size key="headerReferenceSize" width="0.0" height="0.0"/>
+                                    <size key="footerReferenceSize" width="0.0" height="0.0"/>
+                                    <inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
+                                </collectionViewFlowLayout>
+                                <cells/>
+                                <connections>
+                                    <outlet property="dataSource" destination="EFX-fO-Oip" id="2On-qP-zuG"/>
+                                    <outlet property="delegate" destination="EFX-fO-Oip" id="s3n-CL-8X2"/>
+                                </connections>
+                            </collectionView>
+                        </subviews>
+                        <viewLayoutGuide key="safeArea" id="Meh-VD-wWh"/>
+                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                        <constraints>
+                            <constraint firstItem="Zaz-Cl-qpZ" firstAttribute="leading" secondItem="Meh-VD-wWh" secondAttribute="leading" id="1bp-sm-u0X"/>
+                            <constraint firstItem="Meh-VD-wWh" firstAttribute="trailing" secondItem="Zaz-Cl-qpZ" secondAttribute="trailing" id="aNd-UL-hmu"/>
+                            <constraint firstItem="Meh-VD-wWh" firstAttribute="bottom" secondItem="Zaz-Cl-qpZ" secondAttribute="bottom" constant="-35" id="aNr-tf-2AH"/>
+                            <constraint firstItem="Zaz-Cl-qpZ" firstAttribute="top" secondItem="QEs-gO-Cmp" secondAttribute="top" id="tji-wt-R7s"/>
+                        </constraints>
+                    </view>
+                    <connections>
+                        <outlet property="collectionView" destination="Zaz-Cl-qpZ" id="8oA-Gx-z7T"/>
+                    </connections>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="JJ0-Le-6eT" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="256.80000000000001" y="228.32512315270938"/>
+        </scene>
+    </scenes>
+</document>

+ 156 - 0
iOSClient/Groupfolders/NCGroupfolders.swift

@@ -0,0 +1,156 @@
+//
+//  NCGroupfolders.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 14/04/2023.
+//  Copyright © 2023 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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
+import NextcloudKit
+
+class NCGroupfolders: NCCollectionViewCommon {
+
+    // MARK: - View Life Cycle
+
+    required init?(coder aDecoder: NSCoder) {
+        super.init(coder: aDecoder)
+
+        titleCurrentFolder = NSLocalizedString("_group_folders_", comment: "")
+        layoutKey = NCGlobal.shared.layoutViewGroupfolders
+        enableSearchBar = false
+        headerMenuButtonsCommand = false
+        headerMenuButtonsView = true
+        headerRichWorkspaceDisable = true
+        emptyImage = UIImage(named: "folder_group")?.image(color: NCBrandColor.shared.brandElement, size: UIScreen.main.bounds.width)
+        emptyTitle = "_files_no_files_"
+        emptyDescription = "_tutorial_groupfolders_view_"
+    }
+
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+
+        navigationController?.setFileAppreance()
+
+        NotificationCenter.default.addObserver(self, selector: #selector(readFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterOperationReadFile), object: nil)
+    }
+
+    override func viewWillDisappear(_ animated: Bool) {
+        super.viewWillDisappear(animated)
+
+        NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterOperationReadFile), object: nil)
+    }
+
+    // MARK: - NotificationCenter
+
+    @objc func readFile(_ notification: NSNotification) {
+
+        guard let userInfo = notification.userInfo as NSDictionary?,
+              let ocId = userInfo["ocId"] as? String,
+              let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId)
+        else {
+            return
+        }
+
+        dataSource.addMetadata(metadata)
+        self.collectionView?.reloadData()
+    }
+
+    // MARK: - DataSource + NC Endpoint
+
+    override func reloadDataSource(forced: Bool = true) {
+        super.reloadDataSource()
+
+        DispatchQueue.global().async {
+
+            var metadatas: [tableMetadata] = []
+
+            if self.serverUrl.isEmpty {
+                metadatas = NCManageDatabase.shared.getMetadatasFromGroupfolders(account: self.appDelegate.account, urlBase: self.appDelegate.urlBase, userId: self.appDelegate.userId)
+            } else {
+                metadatas = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", self.appDelegate.account, self.serverUrl))
+            }
+
+            self.dataSource = NCDataSource(
+                metadatas: metadatas,
+                account: self.appDelegate.account,
+                sort: self.layoutForView?.sort,
+                ascending: self.layoutForView?.ascending,
+                directoryOnTop: self.layoutForView?.directoryOnTop,
+                favoriteOnTop: true,
+                filterLivePhoto: true,
+                groupByField: self.groupByField,
+                providers: self.providers,
+                searchResults: self.searchResults)
+
+            DispatchQueue.main.async {
+                self.refreshControl.endRefreshing()
+                self.collectionView.reloadData()
+            }
+        }
+    }
+
+    override func reloadDataSourceNetwork(forced: Bool = false) {
+        super.reloadDataSourceNetwork(forced: forced)
+
+        isReloadDataSourceNetworkInProgress = true
+        collectionView?.reloadData()
+
+        if serverUrl.isEmpty {
+
+            let homeServerUrl = NCUtilityFileSystem.shared.getHomeServer(urlBase: self.appDelegate.urlBase, userId: self.appDelegate.userId)
+            let options = NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)
+
+            NextcloudKit.shared.getGroupfolders(options: options) { account, results, _, error in
+
+                DispatchQueue.main.async {
+                    self.refreshControl.endRefreshing()
+                    self.isReloadDataSourceNetworkInProgress = false
+                }
+
+                if error == .success, let groupfolders = results {
+                    NCManageDatabase.shared.addGroupfolders(account: account, groupfolders: groupfolders)
+                    for groupfolder in groupfolders {
+                        let serverUrlFileName = homeServerUrl + groupfolder.mountPoint
+                        if NCManageDatabase.shared.getMetadataFromDirectory(account: self.appDelegate.account, serverUrl: serverUrlFileName) == nil {
+                            NCOperationQueue.shared.readFile(serverUrlFileName: serverUrlFileName)
+                        }
+                    }
+                } else if error != .success {
+                    NCContentPresenter.shared.showError(error: error)
+                }
+                self.reloadDataSource()
+            }
+        } else {
+
+            networkReadFolder(forced: forced) { _, _, metadatasUpdate, metadatasDelete, _ in
+
+                DispatchQueue.main.async {
+                    self.refreshControl.endRefreshing()
+                    self.isReloadDataSourceNetworkInProgress = false
+
+                    if !(metadatasUpdate?.isEmpty ?? true) || !(metadatasDelete?.isEmpty ?? true) || forced {
+                        self.reloadDataSource()
+                    } else {
+                        self.collectionView?.reloadData()
+                    }
+                }
+            }
+        }
+    }
+}

+ 6 - 0
iOSClient/Images.xcassets/Contents.json

@@ -0,0 +1,6 @@
+{
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

+ 26 - 0
iOSClient/Images.xcassets/MenuGroupByAlphabetic.imageset/Contents.json

@@ -0,0 +1,26 @@
+{
+  "images" : [
+    {
+      "filename" : "MenuGroupByAlphabetic.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "MenuGroupByAlphabetic@3x-1.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "MenuGroupByAlphabetic@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  },
+  "properties" : {
+    "preserves-vector-representation" : true
+  }
+}

BIN
iOSClient/Images.xcassets/MenuGroupByAlphabetic.imageset/MenuGroupByAlphabetic.png


BIN
iOSClient/Images.xcassets/MenuGroupByAlphabetic.imageset/MenuGroupByAlphabetic@3x-1.png


BIN
iOSClient/Images.xcassets/MenuGroupByAlphabetic.imageset/MenuGroupByAlphabetic@3x.png


+ 26 - 0
iOSClient/Images.xcassets/MenuGroupByDate.imageset/Contents.json

@@ -0,0 +1,26 @@
+{
+  "images" : [
+    {
+      "filename" : "MenuGroupByDate.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "MenuGroupByDate@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "MenuGroupByDate@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  },
+  "properties" : {
+    "preserves-vector-representation" : true
+  }
+}

BIN
iOSClient/Images.xcassets/MenuGroupByDate.imageset/MenuGroupByDate.png


BIN
iOSClient/Images.xcassets/MenuGroupByDate.imageset/MenuGroupByDate@2x.png


BIN
iOSClient/Images.xcassets/MenuGroupByDate.imageset/MenuGroupByDate@3x.png


+ 26 - 0
iOSClient/Images.xcassets/MenuGroupByFile.imageset/Contents.json

@@ -0,0 +1,26 @@
+{
+  "images" : [
+    {
+      "filename" : "MenuGroupByFile.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "MenuGroupByFile@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "MenuGroupByFile@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  },
+  "properties" : {
+    "preserves-vector-representation" : true
+  }
+}

BIN
iOSClient/Images.xcassets/MenuGroupByFile.imageset/MenuGroupByFile.png


BIN
iOSClient/Images.xcassets/MenuGroupByFile.imageset/MenuGroupByFile@2x.png


BIN
iOSClient/Images.xcassets/MenuGroupByFile.imageset/MenuGroupByFile@3x.png


+ 26 - 0
iOSClient/Images.xcassets/MenuOrderByFileName.imageset/Contents.json

@@ -0,0 +1,26 @@
+{
+  "images" : [
+    {
+      "filename" : "MenuOrderByFileName.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "MenuOrderByFileName@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "MenuOrderByFileName@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  },
+  "properties" : {
+    "preserves-vector-representation" : true
+  }
+}

BIN
iOSClient/Images.xcassets/MenuOrderByFileName.imageset/MenuOrderByFileName.png


BIN
iOSClient/Images.xcassets/MenuOrderByFileName.imageset/MenuOrderByFileName@2x.png


BIN
iOSClient/Images.xcassets/MenuOrderByFileName.imageset/MenuOrderByFileName@3x.png


+ 26 - 0
iOSClient/Images.xcassets/MenuOrdeyByDate.imageset/Contents.json

@@ -0,0 +1,26 @@
+{
+  "images" : [
+    {
+      "filename" : "MenuOrdeyByDate.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "MenuOrdeyByDate@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "MenuOrdeyByDate@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  },
+  "properties" : {
+    "preserves-vector-representation" : true
+  }
+}

BIN
iOSClient/Images.xcassets/MenuOrdeyByDate.imageset/MenuOrdeyByDate.png


BIN
iOSClient/Images.xcassets/MenuOrdeyByDate.imageset/MenuOrdeyByDate@2x.png


BIN
iOSClient/Images.xcassets/MenuOrdeyByDate.imageset/MenuOrdeyByDate@3x.png


+ 26 - 0
iOSClient/Images.xcassets/MenuOrdinamentoAscendente.imageset/Contents.json

@@ -0,0 +1,26 @@
+{
+  "images" : [
+    {
+      "filename" : "MenuOrdinamentoAscendente.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "MenuOrdinamentoAscendente@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "MenuOrdinamentoAscendente@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  },
+  "properties" : {
+    "preserves-vector-representation" : true
+  }
+}

BIN
iOSClient/Images.xcassets/MenuOrdinamentoAscendente.imageset/MenuOrdinamentoAscendente.png


BIN
iOSClient/Images.xcassets/MenuOrdinamentoAscendente.imageset/MenuOrdinamentoAscendente@2x.png


BIN
iOSClient/Images.xcassets/MenuOrdinamentoAscendente.imageset/MenuOrdinamentoAscendente@3x.png


+ 26 - 0
iOSClient/Images.xcassets/MenuOrdinamentoDiscendente.imageset/Contents.json

@@ -0,0 +1,26 @@
+{
+  "images" : [
+    {
+      "filename" : "MenuOrdinamentoDiscendente.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "MenuOrdinamentoDiscendente@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "MenuOrdinamentoDiscendente@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  },
+  "properties" : {
+    "preserves-vector-representation" : true
+  }
+}

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