From 5ef8c99d5333d360d703e98b5651c6442a22fc43 Mon Sep 17 00:00:00 2001 From: TSI-amrutwaghmare <96108296+TSI-amrutwaghmare@users.noreply.github.com> Date: Wed, 18 Oct 2023 14:58:19 +0530 Subject: [PATCH 01/11] NMC 1984 - Privacy policy customisation added --- .../dataPrivacy.imageset/Contents.json | 23 ++ .../dataPrivacy.imageset/default copy@2x.png | Bin 0 -> 1262 bytes .../dataPrivacy.imageset/default copy@3x.png | Bin 0 -> 2037 bytes .../dataPrivacy.imageset/default copy@4x.png | Bin 0 -> 2624 bytes .../NCCollectionViewCommon.swift | 21 ++ .../AnalysisDataCollectionSwitch.swift | 34 +++ .../Settings/AnalysisDataCollectionSwitch.xib | 73 ++++++ ...InitialPrivacySettingsViewController.swift | 170 +++++++++++++ iOSClient/Settings/NCSettings.storyboard | 236 ++++++++++++++++++ .../PrivacyPolicyViewController.swift | 62 +++++ .../PrivacySettingsViewController.swift | 133 ++++++++++ .../RequiredDataCollectionSwitch.swift | 34 +++ .../Settings/RequiredDataCollectionSwitch.xib | 73 ++++++ .../SaveSettingsCustomButtonCell.swift | 46 ++++ .../Settings/SaveSettingsCustomButtonCell.xib | 46 ++++ 15 files changed, 951 insertions(+) create mode 100644 iOSClient/Images.xcassets/dataPrivacy.imageset/Contents.json create mode 100644 iOSClient/Images.xcassets/dataPrivacy.imageset/default copy@2x.png create mode 100644 iOSClient/Images.xcassets/dataPrivacy.imageset/default copy@3x.png create mode 100644 iOSClient/Images.xcassets/dataPrivacy.imageset/default copy@4x.png create mode 100644 iOSClient/Settings/AnalysisDataCollectionSwitch.swift create mode 100644 iOSClient/Settings/AnalysisDataCollectionSwitch.xib create mode 100644 iOSClient/Settings/InitialPrivacySettingsViewController.swift create mode 100644 iOSClient/Settings/NCSettings.storyboard create mode 100644 iOSClient/Settings/PrivacyPolicyViewController.swift create mode 100644 iOSClient/Settings/PrivacySettingsViewController.swift create mode 100644 iOSClient/Settings/RequiredDataCollectionSwitch.swift create mode 100644 iOSClient/Settings/RequiredDataCollectionSwitch.xib create mode 100644 iOSClient/Settings/SaveSettingsCustomButtonCell.swift create mode 100644 iOSClient/Settings/SaveSettingsCustomButtonCell.xib diff --git a/iOSClient/Images.xcassets/dataPrivacy.imageset/Contents.json b/iOSClient/Images.xcassets/dataPrivacy.imageset/Contents.json new file mode 100644 index 0000000000..4fb0d4c09c --- /dev/null +++ b/iOSClient/Images.xcassets/dataPrivacy.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "default copy@2x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "default copy@3x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "default copy@4x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOSClient/Images.xcassets/dataPrivacy.imageset/default copy@2x.png b/iOSClient/Images.xcassets/dataPrivacy.imageset/default copy@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..40c3e7d47f2c2fdbdc87713870b113c36d2bbc07 GIT binary patch literal 1262 zcmVPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NGSV=@dRA>e5naPV4R}{wkIf1Ao z5ETSbL~s#eB7z#VK@nFX?NU*9iDKMmoppq41lN*(AQ}`zT`5kjAWpQPIDkV2g9?$D zIHdFYy6Y4tRrgxAhVFLZf$!Z@x6b#Sb8ZdqRlUBx4>qwY(Cae&@bK_d^p=4&4oY#( z;YzR9d#%YXtW^UPJewj`11oq1{`88%Ub6oZu%^z_&>t0?=Hkd&&H*Ulfk>}IXf=^ zHFF*e($JrbB?~q{@mGla3{)k0LCheqVlTuJ5i?@h!e}w9DJ{~>(Y?`6EzAwHb$$KM zVhzL&-$U41UBgvGCEnO*NQ?9ZP(z0KAwhBl>n+71>g@m<+rh$8;22FPSC&(`a|@*l zb|i)cCwFaxj>oE6CUu-|+~qzrFz!oUNH``(fb(`UI1YZzAb)cq_tC!Be}>~V4(FfJR5@$+P2$AQZ2>bM$`k6pySyWly_A8*&^eiBL^ zYrr2aJ%mS%75WJL0PX>^pK$b!XvE|L&6(Z9*$1kgg?;WvrdK&4{53E;93v2*yUw5m z7RHe#m@5|V!%nj??H&4uUGVN$TxlNCB1e3RA3Rdb?{#q@S55ep55;^{Du4+BCjsR) zTM~+k&cV84Oh&y1DH-!8Tuf8Q0Rf1ro?!(9M$cNa=3iA@uRodq|6Ac2md zYw$YRq>*VcjF@hp`uC@MLwL_Tv$r^VQtN0HN1|QewPnsmz5lHI!KE0WSZ{iS9ov;Jc1*$zz#o1U^bhzRl&%fU zW&0H4O^>hrMg4KvCh=V0f3sU*y4a5Zy`>i9Yp!VG>c8fRHHz2-^sSj{cM04wu~iqc zQ_vK!16%_!yTuM&Kl$TkLFWC~q`-clZ*P4e>D9`QU?T|rsrY_W9jJnWCxa=0U7bz+ Y2SkK6zRJ=a>Hq)$07*qoM6N<$g2I134*&oF literal 0 HcmV?d00001 diff --git a/iOSClient/Images.xcassets/dataPrivacy.imageset/default copy@3x.png b/iOSClient/Images.xcassets/dataPrivacy.imageset/default copy@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..74d9dbb7dd8e51db1844e2102dff7045c235a1fb GIT binary patch literal 2037 zcmVPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS?Ur9tkRCodHn{TXFa~#LJLd_y{ zMZ^}8G@*Y&>~7Q(tF1iI^x!h{$e*z+p5dAC#0+a>nR$@4qHI=AG}TQcim5Bg5W|(4 zq+7SH*Zb$3+d1F+J?DG=pL_Xzw)g%1{d_*}@B4TDpL2%{Xfe=HnCjx)#P&uKXRdo`_-@;VxLbTq3Vu!*MCJ=&*Ao7jC7i1>&b3@t6YaAr> zN5B_tD}o&^B3)*I5M&$#d@)pB3x?((^)>6=B=mfaQEme|7&pKIy1cPQLC#hxI%xZtM*YmjV75QM;MOM?^-sy!oh_8- z1YTK=vqQ?dJV3??6P5V8bb< z18rdvOYoMk zfq7rZaPMmRmmt(Pl(krG#~>DeJ1f==>jGekBQeycv2EU5^;K@Q2*iSQ&$J4ROt50j zS>^d8m|pCZS_EPV&w+DNEJbtWQ@K?x5L?1Uta4t4mCo~gGq`4VsaznawgKBQ$F&Oi z$hT6fL!hxaR+z^(Q`$NN%ChzNi_7Ed$4FJyA<)mM7=|ZTe&JY$KwHCWPSw&2#7eDl zfw(Hx^XkqFtK5gL9(26PFeFDpzNWp-p~d&d*qu*Mya z_A)-3xu0jrVezqXx2>_y)g_Sa1Aq(qymMAq_)bFH03HT9yJ>4V1a!7~3`}N@?)Zx` z8~0d&T+f3_6-cW_fWIkp0IuR7Y!k?~Mp0{+YYW=%PYjf{B!S@pUA1xlF^;iIARPlt z8*O$BqPC6ucX9k(0!iKDMgV;Wl*%}J3@)V_d(4F&)g8PtbId z$Y(@*B^c4Gk4X}hxvk(^i@(_w5{W^VIKKavRP?mbY0MkrI|a&RE=SicCVzd&VOpEn z5z$^u|K$;5!R|9?tbn+L(Jr?FsLy4Sn4n99B-Un`l>g=z!j}aU_p3`S$wFOBYysc< z4hc(bNpZWCEsSnwzHTD>DQv4RFUX9M$; zE_(WeFZRB%GxN9sZZIhF3*R2+RS;O=h(J0t?Ef zp&VW=ijW}GwopH2F4s)$WbG*5>vlK%O+s$9|otGN|$vpp?%N@5l57ToCjV4 zQ3r5_5}Quz%Rp;f&OtfGqOJ$}#}wPO3*An8pBXa-<>J$jW|3Ee*MaTciSCf;x@I+) znI^}7VlImw10DrifoXbn{1g~l=VAxUvt)$O^BR5FsVnX^ytCR~r!dXj2^IJUOCpgG TJQa3{00000NkvXXu0mjfv1qa| literal 0 HcmV?d00001 diff --git a/iOSClient/Images.xcassets/dataPrivacy.imageset/default copy@4x.png b/iOSClient/Images.xcassets/dataPrivacy.imageset/default copy@4x.png new file mode 100644 index 0000000000000000000000000000000000000000..682122c3f21e9c196b2df4c47179f97e87b9788b GIT binary patch literal 2624 zcmV-G3cvMPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91V4wp41ONa40RR91U;qFB0I4%yP5=N2s!2paRCodHok^?|RTPGKf+#3Y zFc_SIKtPQX4iU%bMx#d5#5e#NbYWt2;l@Oa!Hq*q+!&2R^3~5tNQk{Px7a_Pn~Wa^m(Y+^X4A0R zN*X}14}j(wpt0a%!cwo?+d3ljH-yih(3EYVqXuaJ1wR5}>7hhLSMi62CW}99sh@)n z6D(jUIUk&t;AECn>{;9K`w2*!qLcetzLyg322gAzcM3?iZWVrfDHis@8(Fm&sNgF0 zM|sC;`03oD^GVtioo5aPRmf_b>Ku|bWf}amkvNNU14!(CysGF^KvfC-h;J1--NHZf zO^C5gl58-OQKox{8go>WQMfPKU_J&5;eD@*Tb~AmzHHkK!N$YMhmc<5Lb|LCO*sZ1 zX+yNsVQas#ejCS=i&Jf#jN};7WgD;wH|gO-Teyy)f5~^dh17cCphIce6dkq>D#I&= z2K+<<>7hhRJtEjyCaJo{(vTs<(j$+wAzB{k6Tj{T=y37{NDICI0)0M&W4EO#gBQ90Iza|H`Xn8#|W3) zdS){XTn6+=JblTdmnX}>Ti|)F!J~M_1eb7Pyxp_hgoKN_&l6$x19ySnL7vD)wzcft zHZq}Wu=i=GDOm78Ee?s<3*Iut&e3U%_Lxe=#vypn+5$B&C+1*Kr1&XajjiJKWIxL^ zQ0ubaAk>G}0O|Ko{!M;}F)NKzVmZs?JCx4G%hi9h593H|6iH}mNZGkUngcX}6Z0Rg zMe(sy15EY<;bGjhBv!Wgs*QZeSCmrAl|sSHFE2RGG%TbqlmEiHm>ZxsPN5{EuTY%! z`iZ##hX>3wq_2RqGTvzhjZ(~+^96!~`;fjI!nvEoya%jzPb1edq_5myJ_a#2;5(m0 za%}r@*0*hS%ng_yuqy8d^cB=q%nf)mV5aW`^cB=qv<=`+@>4R^%;U168F`w$9Hm6t zfTUUv1*qD?0erOIMcIc=MKd7o3%1w}dcB~jRBRlA2dyno3v=R*24CCaXXw|k?x+@b z!N1z#R}pBFCD^dw!CD*=_h_)v65pi$0p-LNH^ILWW0)y8y3MY^e9i%y-*1wGOv;ct zPNF~H?{x5tgWnghnar4#4z^`YY`NQNRXiSDU2W<1*y3LON5bJG@F@5c`~fzArQmUJ zn!m^5BZxN^m}E_Hua=QS`P>uJ~5;tOvV6 zqkP5}{rQ?qkqubZIFQfsC7e?HKaOrB&mfx)KY zn|v-tsXwPsSQy^}l)Fhv`8JXwiY7^IG(|%<$vDVC$=D0QcHk<;CNNmV*E&~GE`={! z89T`mXe;{t9a8rr_H3384;-|j9=Y#&xh_^6qX6mG6 zslP+yEI#J|Iki0=)@@)&~-Eih2`9`i_Ch1Ypu9w>4@ax~*+gCb(fc}WpxPGd;+ zik5DtUv(_>nuCeG?JhF)>9W+G;B(k9`PALC*YhmwbvU*i|F3Z>W)UNT4hNfoNiOj5 zi38g%Z1P>TPQN*D7RX#+A*6Fzud^czy$(S<;L=mRcP#;?Tl6+{;S-?EVF~?No?~sf zen5@9ejMV}fQ0007P8anzR#{~yN0&7uT<^KCCSm&fH!@)<@-;21~ptq@< zleRessQ2Nb98G^4V>kMj19GyvVk>wBm}G%{)WBFn(hE$9qkFry3<-9Lg@n{zC*#^k z2MDCxGV;DYKnOP%kkD=TDhru8ZeBXiihSo-a@Nh{0kvjCz^tDSO{tYk4Z+BFilz8c zKTrdO<`4w!1-6@!XeUB7XW{;hTo(e9XtILt#_JaX&o(hibTjH_R82(knURs_Bh^H8 z@CvD{y+9ATMmV^M+C(`3$!cQ-ANlsE%%w@F;*I9R@DV~Sp{ z8$0HFAg&2#7Dky{pr_Rif#wn57bD$$O5MZ^sg^}~@8Sq-H(bg@?+^4MCq9ALm13A7 zPsYc(nGxKd%;2w8H#U!m>>C81$VuRy$SC*If%iQ*+@I?=j`afxXv+5Tj6`;`F1oEq zoPfC5L{nLO-gmUGMG!p}{AtSs{Vs54`xrJy7MFw*3#0U*9n(B2@^28qP*#mo{F-}dgeE~;VTkjU*>nI`LWeO=w0nj{s)0E(3*ntF42nCv6* zLP9ryZCP0->t13R{Ddc;K)&-t7Y_sTwJM2S2y8ELSk*7T^66Gk`p#KC5?}I}Cz`?@ z(|o1yyAKH-55CH)M6zCXR~BWVvA{Lf5^Tp8jnz@(NK{iCv!1iFx=C~sbREc@e=3{A zR%2Y{F${-xR1rgvCFyA(>xQkhfVv^lP1Aq=1etWnI@!32uJL^)lZ8k-E*})&81O3a zk)0U73v{0$=(BV_@;b)0AKArKq&nD84E;-}6+RU#Xsf>=aaA2TbzBSzqt}~yBI$Ej z!rVzVV)Y;x-f@Mgn=nNh3Dk&npsc9y4}-(%&ZFbwQM3br?xI%#Us=n5`#`twWw!*1 zs7p)zJnC8SH?WBwbI%33WNq71*0T9VJ)Xkq9zq3v1#AU+Xf_|*0seD&8g(+APL6{2 if>E8GN0)yaEAS7aJVK+!9r$4Y0000P$ literal 0 HcmV?d00001 diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index ad09d5b6f2..bbd0fbcc57 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -226,6 +226,14 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS let dropInteraction = UIDropInteraction(delegate: self) self.navigationController?.navigationItem.leftBarButtonItems?.first?.customView?.addInteraction(dropInteraction) + + if(!UserDefaults.standard.bool(forKey: "isInitialPrivacySettingsShowed") || isApplicationUpdated()){ + redirectToPrivacyViewController() + + //set current app version + let appVersion = Bundle.main.infoDictionary?["CFBundleInfoDictionaryVersion"] as? String + UserDefaults.standard.set(appVersion, forKey: "CurrentAppVersion") + } registerForTraitChanges([UITraitUserInterfaceStyle.self]) { [weak self] (view: NCCollectionViewCommon, _) in guard let self else { return } @@ -315,6 +323,19 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS removeImageCache(metadatas: self.dataSource.getMetadatas()) } + + func isApplicationUpdated() -> Bool{ + let appVersion = Bundle.main.infoDictionary?["CFBundleInfoDictionaryVersion"] as? String ?? "" + let currentVersion = UserDefaults.standard.string(forKey: "CurrentAppVersion") + return currentVersion != appVersion + } + + func redirectToPrivacyViewController(){ + let storyBoard: UIStoryboard = UIStoryboard(name: "NCSettings", bundle: nil) + let newViewController = storyBoard.instantiateViewController(withIdentifier: "privacySettingsNavigation") as! UINavigationController + newViewController.modalPresentationStyle = .fullScreen + self.present(newViewController, animated: true, completion: nil) + } func presentationControllerDidDismiss( _ presentationController: UIPresentationController) { let viewController = presentationController.presentedViewController diff --git a/iOSClient/Settings/AnalysisDataCollectionSwitch.swift b/iOSClient/Settings/AnalysisDataCollectionSwitch.swift new file mode 100644 index 0000000000..e24216220f --- /dev/null +++ b/iOSClient/Settings/AnalysisDataCollectionSwitch.swift @@ -0,0 +1,34 @@ +// +// AnalysisDataCollectionSwitch.swift +// Nextcloud +// +// Created by A200073704 on 25/04/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import UIKit + +class AnalysisDataCollectionSwitch: XLFormBaseCell { + + @IBOutlet weak var cellLabel: UILabel! + @IBOutlet weak var analysisDataCollectionSwitchControl: UISwitch! + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + analysisDataCollectionSwitchControl.addTarget(self, action: #selector(switchChanged), for: UIControl.Event.valueChanged) + } + + override func configure() { + super.configure() + } + + override func update() { + super.update() + } + + @objc func switchChanged(mySwitch: UISwitch) { + self.rowDescriptor.value = mySwitch.isOn + } +} + diff --git a/iOSClient/Settings/AnalysisDataCollectionSwitch.xib b/iOSClient/Settings/AnalysisDataCollectionSwitch.xib new file mode 100644 index 0000000000..45530193f6 --- /dev/null +++ b/iOSClient/Settings/AnalysisDataCollectionSwitch.xib @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOSClient/Settings/InitialPrivacySettingsViewController.swift b/iOSClient/Settings/InitialPrivacySettingsViewController.swift new file mode 100644 index 0000000000..2cd30a4242 --- /dev/null +++ b/iOSClient/Settings/InitialPrivacySettingsViewController.swift @@ -0,0 +1,170 @@ +// +// InitialPrivacySettingsViewController.swift +// Nextcloud +// +// Created by A200073704 on 25/04/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import Foundation +import AppTrackingTransparency +import AdSupport +import UIKit + +class InitialPrivacySettingsViewController: UIViewController { + + @IBOutlet weak var dataPrivacyImage: UIImageView! + @IBOutlet weak var acceptButton: UIButton! + @IBOutlet weak var privacySettingsHelpText: UITextView! + @IBOutlet weak var privacySettingsTitle: UILabel! + @IBOutlet weak var widthPriavacyHelpView: NSLayoutConstraint! + var privacyHelpText = "" + + override func viewDidLoad() { + super.viewDidLoad() + + privacySettingsTitle.text = NSLocalizedString("_privacy_settings_title_", comment: "") + privacyHelpText = NSLocalizedString("_privacy_help_text_after_login_", comment: "") + privacySettingsHelpText.text = privacyHelpText + dataPrivacyImage.image = UIImage(named: "dataPrivacy")!.image(color: NCBrandColor.shared.brand, size: 60) + privacySettingsHelpText.delegate = self + privacySettingsHelpText.textColor = .label + privacySettingsHelpText.hyperLink(originalText: privacyHelpText, + linkTextsAndTypes: [NSLocalizedString("_key_privacy_help_", comment: ""): LinkType.privacyPolicy.rawValue, + NSLocalizedString("_key_reject_help_", comment: ""): LinkType.reject.rawValue, + NSLocalizedString("_key_settings_help_", comment: ""): LinkType.settings.rawValue]) + + acceptButton.backgroundColor = NCBrandColor.shared.brand + acceptButton.tintColor = UIColor.white + acceptButton.layer.cornerRadius = 5 + acceptButton.layer.borderWidth = 1 + acceptButton.layer.borderColor = NCBrandColor.shared.brand.cgColor + acceptButton.setTitle(NSLocalizedString("_accept_button_title_", comment: ""), for: .normal) + privacySettingsHelpText.centerText() + privacySettingsHelpText.font = UIFont(name: privacySettingsHelpText.font!.fontName, size: 16) + self.navigationItem.leftBarButtonItem?.tintColor = NCBrandColor.shared.brand + } + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.navigationBar.isHidden = true + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + self.navigationController?.navigationBar.isHidden = false + } + + override func viewDidLayoutSubviews(){ + if UIDevice.current.userInterfaceIdiom == .pad { + widthPriavacyHelpView.constant = UIScreen.main.bounds.width - 100 + } + } + + @IBAction func onAcceptButtonClicked(_ sender: Any) { + requestPermission() + } + + //NEWLY ADDED PERMISSIONS FOR iOS 14 + func requestPermission() { + UserDefaults.standard.set(true, forKey: "isInitialPrivacySettingsShowed") + UserDefaults.standard.set(true, forKey: "isAnalysisDataCollectionSwitchOn") + if #available(iOS 14, *) { + ATTrackingManager.requestTrackingAuthorization { status in + switch status { + case .authorized: + // Tracking authorization dialog was shown + // and we are authorized + print("Authorized") + // Now that we are authorized we can get the IDFA + print(ASIdentifierManager.shared().advertisingIdentifier) + case .denied: + UserDefaults.standard.set(true, forKey: "isInitialPrivacySettingsShowed") + UserDefaults.standard.set(false, forKey: "isAnalysisDataCollectionSwitchOn") + print("Denied") + case .notDetermined: + // Tracking authorization dialog has not been shown + print("Not Determined") + case .restricted: + print("Restricted") + @unknown default: + print("Unknown") + } + } + } else { + UserDefaults.standard.set(true, forKey: "isInitialPrivacySettingsShowed") + UserDefaults.standard.set(true, forKey: "isAnalysisDataCollectionSwitchOn") + } + self.dismiss(animated: true, completion: nil) + } +} +// MARK: - UITextViewDelegate +extension InitialPrivacySettingsViewController: UITextViewDelegate { + func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool { + if let linkType = LinkType(rawValue: URL.absoluteString) { + // TODO: handle linktype here with switch or similar. + switch linkType { + case LinkType.privacyPolicy: + //let storyBoard: UIStoryboard = UIStoryboard(name: "NCSettings", bundle: nil) + let privacyViewController = PrivacyPolicyViewController() + self.navigationController?.pushViewController(privacyViewController, animated: true) + case LinkType.reject: + UserDefaults.standard.set(false, forKey: "isAnalysisDataCollectionSwitchOn") + UserDefaults.standard.set(true, forKey: "isInitialPrivacySettingsShowed") + self.dismiss(animated: true, completion: nil) + case LinkType.settings: + let privacySettingsViewController = PrivacySettingsViewController() + UserDefaults.standard.set(true, forKey: "showSettingsButton") + self.navigationController?.pushViewController(privacySettingsViewController, animated: true) + } + print("handle link:: \(linkType)") + } + return false + } +} + +public extension UITextView { + + func hyperLink(originalText: String, linkTextsAndTypes: [String: String]) { + + let style = NSMutableParagraphStyle() + style.alignment = .left + + let attributedOriginalText = NSMutableAttributedString(string: originalText) + + let fullRange = NSRange(location: 0, length: attributedOriginalText.length) + attributedOriginalText.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.label, range: fullRange) + for linkTextAndType in linkTextsAndTypes { + let linkRange = attributedOriginalText.mutableString.range(of: linkTextAndType.key) + attributedOriginalText.addAttribute(NSAttributedString.Key.link, value: linkTextAndType.value, range: linkRange) + attributedOriginalText.addAttribute(NSAttributedString.Key.paragraphStyle, value: style, range: fullRange) + attributedOriginalText.addAttribute(NSAttributedString.Key.foregroundColor, value: NCBrandColor.shared.brand, range: linkRange) + attributedOriginalText.addAttribute(NSAttributedString.Key.font, value: UIFont.systemFont(ofSize: 10), range: fullRange) + } + + self.linkTextAttributes = [NSAttributedString.Key.foregroundColor: NCBrandColor.shared.brand] + self.attributedText = attributedOriginalText + } + + func centerText() { + self.textAlignment = .justified + let fittingSize = CGSize(width: 300, height: CGFloat.greatestFiniteMagnitude) + let size = sizeThatFits(fittingSize) + let topOffset = (bounds.size.height - size.height * zoomScale) / 2 + let positiveTopOffset = max(1, topOffset) + contentOffset.y = -positiveTopOffset + } +} + +enum LinkType: String { + case reject + case privacyPolicy + case settings +} + + diff --git a/iOSClient/Settings/NCSettings.storyboard b/iOSClient/Settings/NCSettings.storyboard new file mode 100644 index 0000000000..19a320515b --- /dev/null +++ b/iOSClient/Settings/NCSettings.storyboard @@ -0,0 +1,236 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOSClient/Settings/PrivacyPolicyViewController.swift b/iOSClient/Settings/PrivacyPolicyViewController.swift new file mode 100644 index 0000000000..141c444fca --- /dev/null +++ b/iOSClient/Settings/PrivacyPolicyViewController.swift @@ -0,0 +1,62 @@ +// +// PrivacyPolicyViewController.swift +// Nextcloud +// +// Created by A200073704 on 25/04/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import Foundation +import UIKit +import WebKit + +class PrivacyPolicyViewController: UIViewController, WKNavigationDelegate, WKUIDelegate { + + var myWebView = WKWebView() + + override func viewDidLoad() { + super.viewDidLoad() + + self.title = NSLocalizedString("_privacy_policy_", comment: "") + + myWebView = WKWebView(frame: CGRect(x:0, y:0, width: UIScreen.main.bounds.width, height:UIScreen.main.bounds.height)) + myWebView.uiDelegate = self + myWebView.navigationDelegate = self + myWebView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + self.view.addSubview(myWebView) + + //1. Load web site into my web view + let myURL = URL(string: "https://static.magentacloud.de/privacy/datenschutzhinweise_app.htm") + let myURLRequest:URLRequest = URLRequest(url: myURL!) + NCActivityIndicator.shared.start() + myWebView.load(myURLRequest) + self.navigationController?.navigationBar.tintColor = NCBrandColor.shared.brand + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + myWebView = WKWebView(frame: CGRect(x:0, y:0, width: UIScreen.main.bounds.width, height:UIScreen.main.bounds.height)) + } + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + NCActivityIndicator.shared.stop() + } + + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + if navigationAction.navigationType == .linkActivated { + if let url = navigationAction.request.url, + UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url) + decisionHandler(.cancel) + } else { + decisionHandler(.allow) + } + } else { + decisionHandler(.allow) + } + } +} diff --git a/iOSClient/Settings/PrivacySettingsViewController.swift b/iOSClient/Settings/PrivacySettingsViewController.swift new file mode 100644 index 0000000000..3e60dd6212 --- /dev/null +++ b/iOSClient/Settings/PrivacySettingsViewController.swift @@ -0,0 +1,133 @@ +// +// PrivacySettingsViewController.swift +// Nextcloud +// +// Created by A200073704 on 25/04/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import Foundation +import AppTrackingTransparency +import AdSupport + +class PrivacySettingsViewController: XLFormViewController{ + + @objc public var isShowSettingsButton: Bool = false + + override func viewDidLoad() { + super.viewDidLoad() + self.title = NSLocalizedString("_privacy_settings_title_", comment: "") + + NotificationCenter.default.addObserver(self, selector: #selector(changeTheming), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterChangeTheming), object: nil) + + let nib = UINib(nibName: "CustomSectionHeader", bundle: nil) + self.tableView.register(nib, forHeaderFooterViewReuseIdentifier: "customSectionHeader") + isShowSettingsButton = UserDefaults.standard.bool(forKey: "showSettingsButton") + self.navigationController?.navigationBar.tintColor = NCBrandColor.shared.brand + changeTheming() + } + + @objc func changeTheming() { + tableView.backgroundColor = .systemGroupedBackground + tableView.separatorColor = .none + tableView.separatorColor = .clear + tableView.reloadData() + initializeForm() + } + + //MARK: XLForm + func initializeForm() { + let form : XLFormDescriptor = XLFormDescriptor() as XLFormDescriptor + form.rowNavigationOptions = XLFormRowNavigationOptions.stopDisableRow + + var section : XLFormSectionDescriptor + var row : XLFormRowDescriptor + + // Section: Destination Folder + + section = XLFormSectionDescriptor.formSection(withTitle: NSLocalizedString("", comment: "").uppercased()) + section.footerTitle = " " + form.addFormSection(section) + + section = XLFormSectionDescriptor.formSection(withTitle: NSLocalizedString("", comment: "").uppercased()) + section.footerTitle = NSLocalizedString("_privacy_settings_help_text_", comment: "") + form.addFormSection(section) + + //custom cell + section = XLFormSectionDescriptor.formSection(withTitle: "") + section.footerTitle = NSLocalizedString("_required_data_collection_help_text_", comment: "") + form.addFormSection(section) + + XLFormViewController.cellClassesForRowDescriptorTypes()["RequiredDataCollectionCustomCellType"] = RequiredDataCollectionSwitch.self + + row = XLFormRowDescriptor(tag: "ButtonDestinationFolder", rowType: "RequiredDataCollectionCustomCellType", title: "") + row.cellConfig["requiredDataCollectionSwitchControl.onTintColor"] = NCBrandColor.shared.brand + row.cellConfig["cellLabel.textAlignment"] = NSTextAlignment.left.rawValue + row.cellConfig["cellLabel.font"] = UIFont.systemFont(ofSize: 15.0) + row.cellConfig["cellLabel.textColor"] = UIColor.label //photos + row.cellConfig["cellLabel.text"] = NSLocalizedString("_required_data_collection_", comment: "") + section.addFormRow(row) + + section = XLFormSectionDescriptor.formSection(withTitle: NSLocalizedString("", comment: "").uppercased()) + section.footerTitle = NSLocalizedString("_analysis_data_acqusition_help_text_", comment: "") + form.addFormSection(section) + + XLFormViewController.cellClassesForRowDescriptorTypes()["AnalysisDataCollectionCustomCellType"] = AnalysisDataCollectionSwitch.self + + row = XLFormRowDescriptor(tag: "AnalysisDataCollectionSwitch", rowType: "AnalysisDataCollectionCustomCellType", title: "") + row.cellConfig["analysisDataCollectionSwitchControl.onTintColor"] = NCBrandColor.shared.brand + row.cellConfig["cellLabel.textAlignment"] = NSTextAlignment.left.rawValue + row.cellConfig["cellLabel.font"] = UIFont.systemFont(ofSize: 15.0) + row.cellConfig["cellLabel.textColor"] = UIColor.label //photos + row.cellConfig["cellLabel.text"] = NSLocalizedString("_analysis_data_acqusition_", comment: "") + if(UserDefaults.standard.bool(forKey: "isAnalysisDataCollectionSwitchOn")){ + row.cellConfigAtConfigure["analysisDataCollectionSwitchControl.on"] = 1 + }else { + row.cellConfigAtConfigure["analysisDataCollectionSwitchControl.on"] = 0 + } + + section.addFormRow(row) + + XLFormViewController.cellClassesForRowDescriptorTypes()["SaveSettingsButton"] = SaveSettingsCustomButtonCell.self + + section = XLFormSectionDescriptor.formSection(withTitle: "") + form.addFormSection(section) + + row = XLFormRowDescriptor(tag: "SaveSettingsButton", rowType: "SaveSettingsButton", title: "") + row.cellConfig["backgroundColor"] = UIColor.clear + + if(isShowSettingsButton){ + section.addFormRow(row) + } + + self.form = form + } + + override func formRowDescriptorValueHasChanged(_ formRow: XLFormRowDescriptor!, oldValue: Any!, newValue: Any!) { + super.formRowDescriptorValueHasChanged(formRow, oldValue: oldValue, newValue: newValue) + + if formRow.tag == "SaveSettingsButton" { + print("save settings clicked") + //TODO save button state and leave the page + self.navigationController?.popViewController(animated: true) + + } + if formRow.tag == "AnalysisDataCollectionSwitch"{ + if (formRow.value! as AnyObject).boolValue { + if #available(iOS 14, *) { + ATTrackingManager.requestTrackingAuthorization(completionHandler: { (status) in + if status == .denied { + guard let url = URL(string: UIApplication.openSettingsURLString) else { + return + } + if UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url, options: [:]) + } + } + }) + } + } + UserDefaults.standard.set((formRow.value! as AnyObject).boolValue, forKey: "isAnalysisDataCollectionSwitchOn") + } + } +} diff --git a/iOSClient/Settings/RequiredDataCollectionSwitch.swift b/iOSClient/Settings/RequiredDataCollectionSwitch.swift new file mode 100644 index 0000000000..f80aa97e89 --- /dev/null +++ b/iOSClient/Settings/RequiredDataCollectionSwitch.swift @@ -0,0 +1,34 @@ +// +// RequiredDataCollectionSwitch.swift +// Nextcloud +// +// Created by A200073704 on 25/04/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import UIKit + + +class RequiredDataCollectionSwitch: XLFormBaseCell { + + @IBOutlet weak var cellLabel: UILabel! + @IBOutlet weak var requiredDataCollectionSwitchControl: UISwitch! + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + //requiredDataCollectionSwitchControl.addTarget(self, action: #selector(switchChanged), for: UIControl.Event.valueChanged) + + } + + override func configure() { + super.configure() + + requiredDataCollectionSwitchControl.isOn = true + requiredDataCollectionSwitchControl.isEnabled = false + } + + override func update() { + super.update() + } +} diff --git a/iOSClient/Settings/RequiredDataCollectionSwitch.xib b/iOSClient/Settings/RequiredDataCollectionSwitch.xib new file mode 100644 index 0000000000..66156c6e06 --- /dev/null +++ b/iOSClient/Settings/RequiredDataCollectionSwitch.xib @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOSClient/Settings/SaveSettingsCustomButtonCell.swift b/iOSClient/Settings/SaveSettingsCustomButtonCell.swift new file mode 100644 index 0000000000..6205727723 --- /dev/null +++ b/iOSClient/Settings/SaveSettingsCustomButtonCell.swift @@ -0,0 +1,46 @@ +// +// SaveSettingsCustomButtonCell.swift +// Nextcloud +// +// Created by A200073704 on 25/04/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import UIKit + + +class SaveSettingsCustomButtonCell: XLFormButtonCell { + + @IBOutlet weak var saveSettingsButton: UIButton! + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + self.selectionStyle = .none + self.separatorInset = UIEdgeInsets(top: 0, left: .greatestFiniteMagnitude, bottom: 0, right: .greatestFiniteMagnitude) + saveSettingsButton.setTitle(NSLocalizedString("_save_settings_", comment: ""), for: .normal) + saveSettingsButton.addTarget(self, action: #selector(saveButtonClicked), for: .touchUpInside) + + } + + override func configure() { + super.configure() + saveSettingsButton.backgroundColor = NCBrandColor.shared.brand + saveSettingsButton.tintColor = UIColor.white + saveSettingsButton.layer.cornerRadius = 5 + saveSettingsButton.layer.borderWidth = 1 + saveSettingsButton.layer.borderColor = NCBrandColor.shared.brand.cgColor + + } + + override func update() { + super.update() + + } + + @objc func saveButtonClicked(sender: UIButton) { + self.rowDescriptor.value = sender + + } + +} diff --git a/iOSClient/Settings/SaveSettingsCustomButtonCell.xib b/iOSClient/Settings/SaveSettingsCustomButtonCell.xib new file mode 100644 index 0000000000..0dd197da31 --- /dev/null +++ b/iOSClient/Settings/SaveSettingsCustomButtonCell.xib @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From d65bb831f3dfb543390b435b44343ba7ab7d047a Mon Sep 17 00:00:00 2001 From: TSI-amrutwaghmare <96108296+TSI-amrutwaghmare@users.noreply.github.com> Date: Wed, 18 Oct 2023 14:58:46 +0530 Subject: [PATCH 02/11] NMC 1984 - Unit test added for privacy policy view controller --- .../PrivacyPolicyTest.swift | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 Tests/NextcloudUnitTests/PrivacyPolicyTest.swift diff --git a/Tests/NextcloudUnitTests/PrivacyPolicyTest.swift b/Tests/NextcloudUnitTests/PrivacyPolicyTest.swift new file mode 100644 index 0000000000..f6265f48e7 --- /dev/null +++ b/Tests/NextcloudUnitTests/PrivacyPolicyTest.swift @@ -0,0 +1,154 @@ +// +// PrivacyPolicyTest.swift +// NextcloudTests +// +// Created by A200073704 on 27/04/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +@testable import Nextcloud +import XCTest +import NextcloudKit +import XLForm + + + class PrivacyPolicyTest: XCTestCase { + + var viewController: InitialPrivacySettingsViewController? + var privacySettingsView = PrivacySettingsViewController() + + override func setUpWithError() throws { + + // To Create an instance of UIStoryboard + let storyboard = UIStoryboard(name: "NCSettings", bundle: nil) + + // To Instantiate UIViewController with Storyboard ID + viewController = storyboard.instantiateViewController(withIdentifier: "privacyPolicyViewController") as? InitialPrivacySettingsViewController + + // Outlets are connected + let _ = viewController?.view + + // Make the viewDidLoad() execute. + viewController?.loadViewIfNeeded() + + } + + override func tearDownWithError() throws { + viewController = nil + } + + func testPrivacyPolicyViewControllerIsOpen() { + + // Check that the InitialPrivacyPolicyViewController gets opened + let storyboard = UIStoryboard(name: "NCSettings", bundle: nil) + if let privacyPolicyViewController = storyboard.instantiateViewController(withIdentifier: "privacyPolicyViewController") as? InitialPrivacySettingsViewController { + let navigationController = UINavigationController(rootViewController: privacyPolicyViewController) + + privacyPolicyViewController.loadViewIfNeeded() + + XCTAssertTrue(navigationController.topViewController is InitialPrivacySettingsViewController, "Privacy policy view controller should be open") + } + } + + func testTextViewHasCorrectText() { + + //Check that the text displayed is correct + let expectedText = NSLocalizedString("_privacy_help_text_after_login_", comment: "") + viewController?.privacySettingsHelpText?.text = expectedText + + let actualText = viewController?.privacySettingsHelpText?.text + XCTAssertEqual(actualText, expectedText, "The text view does not have the expected text") + } + + func testHasAcceptButton() { + + // Check that view has the accept button + let acceptButton = viewController?.acceptButton + + XCTAssertNotNil(acceptButton, "View controller does not have an accept button") + + } + + func testSettingsLinkTypeNavigatesToPrivacySettingsViewController() { + + // Simulate tapping the "Settings" link type + let linkType = LinkType.settings + + UserDefaults.standard.set(true, forKey: "showSettingsButton") + viewController?.privacySettingsHelpText.hyperLink(originalText: viewController?.privacyHelpText ?? "", linkTextsAndTypes: [NSLocalizedString("_key_settings_help_", comment: ""): linkType.rawValue]) + + // Check that the correct view controller was pushed onto the navigation stack + XCTAssertNotNil(viewController?.navigationController?.visibleViewController is PrivacySettingsViewController) + } + + func testPrivacyPolicyLinkType_NavigatesToPrivacyPolicyViewController() { + + // Simulate tapping the "Privacy Policy" link type + let linkType = LinkType.privacyPolicy + + viewController?.privacySettingsHelpText.hyperLink(originalText: viewController?.privacyHelpText ?? "", linkTextsAndTypes: [NSLocalizedString("_key_privacy_help_", comment: ""): linkType.rawValue]) + + // Check that the correct view controller was pushed onto the navigation + XCTAssertNotNil(viewController?.navigationController?.visibleViewController is PrivacyPolicyViewController) + } + + func testCorrectImagePresentOnInitialPrivacySettingsViewController() { + + // Check that the image view has the correct image + let expectedImage = UIImage(named: "dataPrivacy") + XCTAssertNotNil(expectedImage) + } + + func testAcceptButtonHasBackgroundColor() { + + // Check that the accept button has the correct background color + let expectedColor = NCBrandColor.shared.brand + XCTAssertEqual(viewController?.acceptButton.backgroundColor, expectedColor) + + } + + func testShowSaveSettingsButton() { + + privacySettingsView.isShowSettingsButton = UserDefaults.standard.bool(forKey: "showSettingsButton") + + XCTAssertTrue(privacySettingsView.isShowSettingsButton) + + } + + func testRequiredDataCollectionSectionExists() { + let form : XLFormDescriptor = XLFormDescriptor() as XLFormDescriptor + form.rowNavigationOptions = XLFormRowNavigationOptions.stopDisableRow + + var section : XLFormSectionDescriptor + var row : XLFormRowDescriptor + + // the section with the title "Required Data Collection" + row = XLFormRowDescriptor(tag: "ButtonDestinationFolder", rowType: "RequiredDataCollectionCustomCellType", title: "") + section = XLFormSectionDescriptor.formSection(withTitle: "") + section.footerTitle = NSLocalizedString("_required_data_collection_help_text_", comment: "") + + // Verify that section was found + XCTAssertNotNil(row, "Expected 'Required Data Collection' section to exist in form.") + } + + func testAnalysisDataCollectionSection() { + + let form : XLFormDescriptor = XLFormDescriptor() as XLFormDescriptor + form.rowNavigationOptions = XLFormRowNavigationOptions.stopDisableRow + var section : XLFormSectionDescriptor + var row : XLFormRowDescriptor + + // row with tag "AnalysisDataCollectionSwitch" + row = XLFormRowDescriptor(tag: "AnalysisDataCollectionSwitch", rowType: "AnalysisDataCollectionCustomCellType", title: "") + section = XLFormSectionDescriptor.formSection(withTitle: "") + section.footerTitle = NSLocalizedString("_analysis_data_acqusition_help_text_", comment: "") + + // Assert that the row exists + XCTAssertNotNil(row, "Expected row with tag 'AnalysisDataCollectionSwitch' to exist in form.") + + // Verify the switch is off + XCTAssertFalse(UserDefaults.standard.bool(forKey: "isAnalysisDataCollectionSwitchOn"), "Expected isAnalysisDataCollectionSwitchOn to be false.") + } + + +} From 2157f1da359073729e5a433492f80c54cbc6a479 Mon Sep 17 00:00:00 2001 From: TSI-amrutwaghmare <96108296+TSI-amrutwaghmare@users.noreply.github.com> Date: Mon, 4 Dec 2023 12:01:04 +0530 Subject: [PATCH 03/11] NMC 1984 - privacySettingsViewController file removed to avoid conflicts --- .../PrivacySettingsViewController.swift | 133 ------------------ 1 file changed, 133 deletions(-) diff --git a/iOSClient/Settings/PrivacySettingsViewController.swift b/iOSClient/Settings/PrivacySettingsViewController.swift index 3e60dd6212..e69de29bb2 100644 --- a/iOSClient/Settings/PrivacySettingsViewController.swift +++ b/iOSClient/Settings/PrivacySettingsViewController.swift @@ -1,133 +0,0 @@ -// -// PrivacySettingsViewController.swift -// Nextcloud -// -// Created by A200073704 on 25/04/23. -// Copyright © 2023 Marino Faggiana. All rights reserved. -// - -import Foundation -import AppTrackingTransparency -import AdSupport - -class PrivacySettingsViewController: XLFormViewController{ - - @objc public var isShowSettingsButton: Bool = false - - override func viewDidLoad() { - super.viewDidLoad() - self.title = NSLocalizedString("_privacy_settings_title_", comment: "") - - NotificationCenter.default.addObserver(self, selector: #selector(changeTheming), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterChangeTheming), object: nil) - - let nib = UINib(nibName: "CustomSectionHeader", bundle: nil) - self.tableView.register(nib, forHeaderFooterViewReuseIdentifier: "customSectionHeader") - isShowSettingsButton = UserDefaults.standard.bool(forKey: "showSettingsButton") - self.navigationController?.navigationBar.tintColor = NCBrandColor.shared.brand - changeTheming() - } - - @objc func changeTheming() { - tableView.backgroundColor = .systemGroupedBackground - tableView.separatorColor = .none - tableView.separatorColor = .clear - tableView.reloadData() - initializeForm() - } - - //MARK: XLForm - func initializeForm() { - let form : XLFormDescriptor = XLFormDescriptor() as XLFormDescriptor - form.rowNavigationOptions = XLFormRowNavigationOptions.stopDisableRow - - var section : XLFormSectionDescriptor - var row : XLFormRowDescriptor - - // Section: Destination Folder - - section = XLFormSectionDescriptor.formSection(withTitle: NSLocalizedString("", comment: "").uppercased()) - section.footerTitle = " " - form.addFormSection(section) - - section = XLFormSectionDescriptor.formSection(withTitle: NSLocalizedString("", comment: "").uppercased()) - section.footerTitle = NSLocalizedString("_privacy_settings_help_text_", comment: "") - form.addFormSection(section) - - //custom cell - section = XLFormSectionDescriptor.formSection(withTitle: "") - section.footerTitle = NSLocalizedString("_required_data_collection_help_text_", comment: "") - form.addFormSection(section) - - XLFormViewController.cellClassesForRowDescriptorTypes()["RequiredDataCollectionCustomCellType"] = RequiredDataCollectionSwitch.self - - row = XLFormRowDescriptor(tag: "ButtonDestinationFolder", rowType: "RequiredDataCollectionCustomCellType", title: "") - row.cellConfig["requiredDataCollectionSwitchControl.onTintColor"] = NCBrandColor.shared.brand - row.cellConfig["cellLabel.textAlignment"] = NSTextAlignment.left.rawValue - row.cellConfig["cellLabel.font"] = UIFont.systemFont(ofSize: 15.0) - row.cellConfig["cellLabel.textColor"] = UIColor.label //photos - row.cellConfig["cellLabel.text"] = NSLocalizedString("_required_data_collection_", comment: "") - section.addFormRow(row) - - section = XLFormSectionDescriptor.formSection(withTitle: NSLocalizedString("", comment: "").uppercased()) - section.footerTitle = NSLocalizedString("_analysis_data_acqusition_help_text_", comment: "") - form.addFormSection(section) - - XLFormViewController.cellClassesForRowDescriptorTypes()["AnalysisDataCollectionCustomCellType"] = AnalysisDataCollectionSwitch.self - - row = XLFormRowDescriptor(tag: "AnalysisDataCollectionSwitch", rowType: "AnalysisDataCollectionCustomCellType", title: "") - row.cellConfig["analysisDataCollectionSwitchControl.onTintColor"] = NCBrandColor.shared.brand - row.cellConfig["cellLabel.textAlignment"] = NSTextAlignment.left.rawValue - row.cellConfig["cellLabel.font"] = UIFont.systemFont(ofSize: 15.0) - row.cellConfig["cellLabel.textColor"] = UIColor.label //photos - row.cellConfig["cellLabel.text"] = NSLocalizedString("_analysis_data_acqusition_", comment: "") - if(UserDefaults.standard.bool(forKey: "isAnalysisDataCollectionSwitchOn")){ - row.cellConfigAtConfigure["analysisDataCollectionSwitchControl.on"] = 1 - }else { - row.cellConfigAtConfigure["analysisDataCollectionSwitchControl.on"] = 0 - } - - section.addFormRow(row) - - XLFormViewController.cellClassesForRowDescriptorTypes()["SaveSettingsButton"] = SaveSettingsCustomButtonCell.self - - section = XLFormSectionDescriptor.formSection(withTitle: "") - form.addFormSection(section) - - row = XLFormRowDescriptor(tag: "SaveSettingsButton", rowType: "SaveSettingsButton", title: "") - row.cellConfig["backgroundColor"] = UIColor.clear - - if(isShowSettingsButton){ - section.addFormRow(row) - } - - self.form = form - } - - override func formRowDescriptorValueHasChanged(_ formRow: XLFormRowDescriptor!, oldValue: Any!, newValue: Any!) { - super.formRowDescriptorValueHasChanged(formRow, oldValue: oldValue, newValue: newValue) - - if formRow.tag == "SaveSettingsButton" { - print("save settings clicked") - //TODO save button state and leave the page - self.navigationController?.popViewController(animated: true) - - } - if formRow.tag == "AnalysisDataCollectionSwitch"{ - if (formRow.value! as AnyObject).boolValue { - if #available(iOS 14, *) { - ATTrackingManager.requestTrackingAuthorization(completionHandler: { (status) in - if status == .denied { - guard let url = URL(string: UIApplication.openSettingsURLString) else { - return - } - if UIApplication.shared.canOpenURL(url) { - UIApplication.shared.open(url, options: [:]) - } - } - }) - } - } - UserDefaults.standard.set((formRow.value! as AnyObject).boolValue, forKey: "isAnalysisDataCollectionSwitchOn") - } - } -} From d9477aae7c3dec24b4e5b7a396c8e424dcb997e5 Mon Sep 17 00:00:00 2001 From: TSI-amrutwaghmare <96108296+TSI-amrutwaghmare@users.noreply.github.com> Date: Tue, 26 Dec 2023 13:29:37 +0530 Subject: [PATCH 04/11] NMC 1984 - Privacy setting view controller added --- .../PrivacySettingsViewController.swift | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/iOSClient/Settings/PrivacySettingsViewController.swift b/iOSClient/Settings/PrivacySettingsViewController.swift index e69de29bb2..b7883ad800 100644 --- a/iOSClient/Settings/PrivacySettingsViewController.swift +++ b/iOSClient/Settings/PrivacySettingsViewController.swift @@ -0,0 +1,147 @@ +// +// PrivacySettingsViewController.swift +// Nextcloud +// +// Created by A200073704 on 25/04/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import Foundation +import AppTrackingTransparency +import AdSupport + +class PrivacySettingsViewController: XLFormViewController{ + + @objc public var isShowSettingsButton: Bool = false + + override func viewDidLoad() { + super.viewDidLoad() + self.title = NSLocalizedString("_privacy_settings_title_", comment: "") + + NotificationCenter.default.addObserver(self, selector: #selector(changeTheming), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterChangeTheming), object: nil) + + let nib = UINib(nibName: "CustomSectionHeader", bundle: nil) + self.tableView.register(nib, forHeaderFooterViewReuseIdentifier: "customSectionHeader") + isShowSettingsButton = UserDefaults.standard.bool(forKey: "showSettingsButton") + self.navigationController?.navigationBar.tintColor = NCBrandColor.shared.brand + changeTheming() + } + + @objc func changeTheming() { + tableView.backgroundColor = .systemGroupedBackground + tableView.separatorColor = .none + tableView.separatorColor = .clear + tableView.reloadData() + initializeForm() + } + + + + //MARK: XLForm + + func initializeForm() { + + let form : XLFormDescriptor = XLFormDescriptor() as XLFormDescriptor + form.rowNavigationOptions = XLFormRowNavigationOptions.stopDisableRow + + var section : XLFormSectionDescriptor + var row : XLFormRowDescriptor + + // Section: Destination Folder + + section = XLFormSectionDescriptor.formSection(withTitle: NSLocalizedString("", comment: "").uppercased()) + section.footerTitle = " " + form.addFormSection(section) + + section = XLFormSectionDescriptor.formSection(withTitle: NSLocalizedString("", comment: "").uppercased()) + section.footerTitle = NSLocalizedString("_privacy_settings_help_text_", comment: "") + form.addFormSection(section) + + + //custom cell + section = XLFormSectionDescriptor.formSection(withTitle: "") + section.footerTitle = NSLocalizedString("_required_data_collection_help_text_", comment: "") + form.addFormSection(section) + + + XLFormViewController.cellClassesForRowDescriptorTypes()["RequiredDataCollectionCustomCellType"] = RequiredDataCollectionSwitch.self + + + row = XLFormRowDescriptor(tag: "ButtonDestinationFolder", rowType: "RequiredDataCollectionCustomCellType", title: "") + row.cellConfig["requiredDataCollectionSwitchControl.onTintColor"] = NCBrandColor.shared.brand + row.cellConfig["cellLabel.textAlignment"] = NSTextAlignment.left.rawValue + row.cellConfig["cellLabel.font"] = UIFont.systemFont(ofSize: 15.0) + row.cellConfig["cellLabel.textColor"] = UIColor.label //photos + row.cellConfig["cellLabel.text"] = NSLocalizedString("_required_data_collection_", comment: "") + section.addFormRow(row) + + section = XLFormSectionDescriptor.formSection(withTitle: NSLocalizedString("", comment: "").uppercased()) + section.footerTitle = NSLocalizedString("_analysis_data_acqusition_help_text_", comment: "") + form.addFormSection(section) + + XLFormViewController.cellClassesForRowDescriptorTypes()["AnalysisDataCollectionCustomCellType"] = AnalysisDataCollectionSwitch.self + + + row = XLFormRowDescriptor(tag: "AnalysisDataCollectionSwitch", rowType: "AnalysisDataCollectionCustomCellType", title: "") + row.cellConfig["analysisDataCollectionSwitchControl.onTintColor"] = NCBrandColor.shared.brand + row.cellConfig["cellLabel.textAlignment"] = NSTextAlignment.left.rawValue + row.cellConfig["cellLabel.font"] = UIFont.systemFont(ofSize: 15.0) + row.cellConfig["cellLabel.textColor"] = UIColor.label //photos + row.cellConfig["cellLabel.text"] = NSLocalizedString("_analysis_data_acqusition_", comment: "") + if(UserDefaults.standard.bool(forKey: "isAnalysisDataCollectionSwitchOn")){ + row.cellConfigAtConfigure["analysisDataCollectionSwitchControl.on"] = 1 + }else { + row.cellConfigAtConfigure["analysisDataCollectionSwitchControl.on"] = 0 + } + + section.addFormRow(row) + + + XLFormViewController.cellClassesForRowDescriptorTypes()["SaveSettingsButton"] = SaveSettingsCustomButtonCell.self + + section = XLFormSectionDescriptor.formSection(withTitle: "") + form.addFormSection(section) + + + row = XLFormRowDescriptor(tag: "SaveSettingsButton", rowType: "SaveSettingsButton", title: "") + row.cellConfig["backgroundColor"] = UIColor.clear + + if(isShowSettingsButton){ + section.addFormRow(row) + } + + + self.form = form + } + + + override func formRowDescriptorValueHasChanged(_ formRow: XLFormRowDescriptor!, oldValue: Any!, newValue: Any!) { + super.formRowDescriptorValueHasChanged(formRow, oldValue: oldValue, newValue: newValue) + + if formRow.tag == "SaveSettingsButton" { + print("save settings clicked") + //TODO save button state and leave the page + self.navigationController?.popViewController(animated: true) + + } + if formRow.tag == "AnalysisDataCollectionSwitch"{ + if (formRow.value! as AnyObject).boolValue { + if #available(iOS 14, *) { + ATTrackingManager.requestTrackingAuthorization(completionHandler: { (status) in + if status == .denied { + guard let url = URL(string: UIApplication.openSettingsURLString) else { + return + } + if UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url, options: [:]) + } + } + }) + } + } + UserDefaults.standard.set((formRow.value! as AnyObject).boolValue, forKey: "isAnalysisDataCollectionSwitchOn") + } + + } + +} From 650ff61c151928d206210c0e268324956b20986d Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Wed, 9 Apr 2025 14:10:39 +0530 Subject: [PATCH 05/11] NMC 1984 - Privacy policy customisation changes added --- Nextcloud.xcodeproj/project.pbxproj | 49 ++ ...mon+CollectionViewDelegateFlowLayout.swift | 43 ++ .../NCCollectionViewCommon.swift | 651 ++++++++++++++++-- 3 files changed, 679 insertions(+), 64 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 8dd4c5545f..25a0172e9a 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -85,6 +85,20 @@ AFCE353527E4ED5900FEA6C2 /* DateFormatter+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353427E4ED5900FEA6C2 /* DateFormatter+Extension.swift */; }; AFCE353727E4ED7B00FEA6C2 /* NCShareCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353627E4ED7B00FEA6C2 /* NCShareCells.swift */; }; AFCE353927E5DE0500FEA6C2 /* Shareable.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353827E5DE0400FEA6C2 /* Shareable.swift */; }; + AFCE353927E5DE0500FEA6C2 /* NCShare+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353827E5DE0400FEA6C2 /* NCShare+Helper.swift */; }; + B54315362DA64EB100981E7E /* PrivacyPolicyTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54315352DA64EB100981E7E /* PrivacyPolicyTest.swift */; }; + B54315412DA669C700981E7E /* InitialPrivacySettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54315392DA669C700981E7E /* InitialPrivacySettingsViewController.swift */; }; + B54315422DA669C700981E7E /* RequiredDataCollectionSwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = B543153D2DA669C700981E7E /* RequiredDataCollectionSwitch.swift */; }; + B54315432DA669C700981E7E /* PrivacySettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B543153C2DA669C700981E7E /* PrivacySettingsViewController.swift */; }; + B54315442DA669C700981E7E /* AnalysisDataCollectionSwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54315372DA669C700981E7E /* AnalysisDataCollectionSwitch.swift */; }; + B54315452DA669C700981E7E /* PrivacyPolicyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B543153B2DA669C700981E7E /* PrivacyPolicyViewController.swift */; }; + B54315462DA669C700981E7E /* SaveSettingsCustomButtonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B543153F2DA669C700981E7E /* SaveSettingsCustomButtonCell.swift */; }; + B54315472DA669C700981E7E /* AnalysisDataCollectionSwitch.xib in Resources */ = {isa = PBXBuildFile; fileRef = B54315382DA669C700981E7E /* AnalysisDataCollectionSwitch.xib */; }; + B54315482DA669C700981E7E /* NCSettings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B543153A2DA669C700981E7E /* NCSettings.storyboard */; }; + B54315492DA669C700981E7E /* SaveSettingsCustomButtonCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B54315402DA669C700981E7E /* SaveSettingsCustomButtonCell.xib */; }; + B543154A2DA669C700981E7E /* RequiredDataCollectionSwitch.xib in Resources */ = {isa = PBXBuildFile; fileRef = B543153E2DA669C700981E7E /* RequiredDataCollectionSwitch.xib */; }; + C04E2F232A17BB4D001BAD85 /* FilesIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E2F222A17BB4D001BAD85 /* FilesIntegrationTests.swift */; }; + D575039F27146F93008DC9DC /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A0D1342591FBC5008F8A13 /* String+Extension.swift */; }; D5B6AA7827200C7200D49C24 /* NCActivityTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */; }; F310B1EF2BA862F1001C42F5 /* NCViewerMedia+VisionKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = F310B1EE2BA862F1001C42F5 /* NCViewerMedia+VisionKit.swift */; }; F321DA8A2B71205A00DDA0E6 /* NCTrashSelectTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F321DA892B71205A00DDA0E6 /* NCTrashSelectTabBar.swift */; }; @@ -1221,6 +1235,18 @@ AFCE353427E4ED5900FEA6C2 /* DateFormatter+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+Extension.swift"; sourceTree = ""; }; AFCE353627E4ED7B00FEA6C2 /* NCShareCells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareCells.swift; sourceTree = ""; }; AFCE353827E5DE0400FEA6C2 /* Shareable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shareable.swift; sourceTree = ""; }; + AFCE353827E5DE0400FEA6C2 /* NCShare+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCShare+Helper.swift"; sourceTree = ""; }; + B54315352DA64EB100981E7E /* PrivacyPolicyTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyPolicyTest.swift; sourceTree = ""; }; + B54315372DA669C700981E7E /* AnalysisDataCollectionSwitch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalysisDataCollectionSwitch.swift; sourceTree = ""; }; + B54315382DA669C700981E7E /* AnalysisDataCollectionSwitch.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AnalysisDataCollectionSwitch.xib; sourceTree = ""; }; + B54315392DA669C700981E7E /* InitialPrivacySettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitialPrivacySettingsViewController.swift; sourceTree = ""; }; + B543153A2DA669C700981E7E /* NCSettings.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCSettings.storyboard; sourceTree = ""; }; + B543153B2DA669C700981E7E /* PrivacyPolicyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyPolicyViewController.swift; sourceTree = ""; }; + B543153C2DA669C700981E7E /* PrivacySettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacySettingsViewController.swift; sourceTree = ""; }; + B543153D2DA669C700981E7E /* RequiredDataCollectionSwitch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequiredDataCollectionSwitch.swift; sourceTree = ""; }; + B543153E2DA669C700981E7E /* RequiredDataCollectionSwitch.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RequiredDataCollectionSwitch.xib; sourceTree = ""; }; + B543153F2DA669C700981E7E /* SaveSettingsCustomButtonCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveSettingsCustomButtonCell.swift; sourceTree = ""; }; + B54315402DA669C700981E7E /* SaveSettingsCustomButtonCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SaveSettingsCustomButtonCell.xib; sourceTree = ""; }; C0046CDA2A17B98400D87C9D /* NextcloudUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NextcloudUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; C04E2F202A17BB4D001BAD85 /* NextcloudIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NextcloudIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCActivityTableViewCell.swift; sourceTree = ""; }; @@ -2024,6 +2050,8 @@ isa = PBXGroup; children = ( AA52EB452D42AC5A0089C348 /* Placeholder.swift */, + B54315352DA64EB100981E7E /* PrivacyPolicyTest.swift */, + AF8ED1FB2757821000B8DBC4 /* NextcloudUnitTests.swift */, ); path = NextcloudUnitTests; sourceTree = ""; @@ -2518,6 +2546,16 @@ F76882042C0DD1E7001CF441 /* Settings */ = { isa = PBXGroup; children = ( + B54315372DA669C700981E7E /* AnalysisDataCollectionSwitch.swift */, + B54315382DA669C700981E7E /* AnalysisDataCollectionSwitch.xib */, + B54315392DA669C700981E7E /* InitialPrivacySettingsViewController.swift */, + B543153A2DA669C700981E7E /* NCSettings.storyboard */, + B543153B2DA669C700981E7E /* PrivacyPolicyViewController.swift */, + B543153C2DA669C700981E7E /* PrivacySettingsViewController.swift */, + B543153D2DA669C700981E7E /* RequiredDataCollectionSwitch.swift */, + B543153E2DA669C700981E7E /* RequiredDataCollectionSwitch.xib */, + B543153F2DA669C700981E7E /* SaveSettingsCustomButtonCell.swift */, + B54315402DA669C700981E7E /* SaveSettingsCustomButtonCell.xib */, F768820B2C0DD1E7001CF441 /* Settings */, F76882162C0DD1E7001CF441 /* AutoUpload */, F389C9F32CEE381E00049762 /* SelectAlbum */, @@ -3941,6 +3979,11 @@ 3704EB2A23D5A58400455C5B /* NCMenu.storyboard in Resources */, AF93471C27E2361E002537EE /* NCShareHeader.xib in Resources */, F75D901F2D2BE12E003E740B /* NCRecommendationsCell.xib in Resources */, + B54315472DA669C700981E7E /* AnalysisDataCollectionSwitch.xib in Resources */, + B54315482DA669C700981E7E /* NCSettings.storyboard in Resources */, + B54315492DA669C700981E7E /* SaveSettingsCustomButtonCell.xib in Resources */, + B543154A2DA669C700981E7E /* RequiredDataCollectionSwitch.xib in Resources */, + F76032A0252F0F8E0015A421 /* NCTransferCell.xib in Resources */, F7F4F10527ECDBDB008676F9 /* Inconsolata-SemiBold.ttf in Resources */, F7A48415297028FC00BD1B49 /* Nextcloud Hub.png in Resources */, F74C0437253F1CDC009762AB /* NCShares.storyboard in Resources */, @@ -4457,6 +4500,12 @@ F72408332B8A27C900F128E2 /* NCMedia+Command.swift in Sources */, F755CB402B8CB13C00CE27E9 /* NCMediaLayout.swift in Sources */, F73EF7B72B0224AB0087E6E9 /* NCManageDatabase+ExternalSites.swift in Sources */, + B54315412DA669C700981E7E /* InitialPrivacySettingsViewController.swift in Sources */, + B54315422DA669C700981E7E /* RequiredDataCollectionSwitch.swift in Sources */, + B54315432DA669C700981E7E /* PrivacySettingsViewController.swift in Sources */, + B54315442DA669C700981E7E /* AnalysisDataCollectionSwitch.swift in Sources */, + B54315452DA669C700981E7E /* PrivacyPolicyViewController.swift in Sources */, + B54315462DA669C700981E7E /* SaveSettingsCustomButtonCell.swift in Sources */, AF4BF61927562A4B0081CEEF /* NCManageDatabase+Metadata.swift in Sources */, F78A18B623CDD07D00F681F3 /* NCViewerRichWorkspaceWebView.swift in Sources */, AFA2AC8527849604008E1EA7 /* NCActivityCommentView.swift in Sources */, diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegateFlowLayout.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegateFlowLayout.swift index 06795639bc..42bbbd6038 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegateFlowLayout.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegateFlowLayout.swift @@ -13,4 +13,47 @@ extension NCCollectionViewCommon: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize { return sizeForFooterInSection(section: section) } + + func getHeaderHeight() -> CGFloat { + + var size: CGFloat = 0 + // transfer in progress + if headerMenuTransferView, + let metadata = NCManageDatabase.shared.getMetadataFromOcId(NCNetworking.shared.transferInForegorund?.ocId), + metadata.isTransferInForeground { + if !isSearchingMode { + size += NCGlobal.shared.heightHeaderTransfer + } + } else { + NCNetworking.shared.transferInForegorund = nil + } + + if headerMenuButtonsView { + size += NCGlobal.shared.heightButtonsView + } + + return size + } + + func getHeaderHeight(section: Int) -> (heightHeaderCommands: CGFloat, heightHeaderRichWorkspace: CGFloat, heightHeaderSection: CGFloat) { + + var headerRichWorkspace: CGFloat = 0 + + if let richWorkspaceText = richWorkspaceText, showDescription { + let trimmed = richWorkspaceText.trimmingCharacters(in: .whitespaces) + if !trimmed.isEmpty && !isSearchingMode { + headerRichWorkspace = UIScreen.main.bounds.size.height / 6 + } + } + + if isSearchingMode || layoutForView?.layout == NCGlobal.shared.layoutGrid || dataSource.numberOfSections() > 1 { + if section == 0 { + return (getHeaderHeight(), headerRichWorkspace, NCGlobal.shared.heightSection) + } else { + return (0, 0, NCGlobal.shared.heightSection) + } + } else { + return (getHeaderHeight(), headerRichWorkspace, 0) + } + } } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index bbd0fbcc57..1ba3a8dbee 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -9,7 +9,7 @@ import NextcloudKit import EasyTipView import LucidBanner -class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate, NCListCellDelegate, NCGridCellDelegate, NCPhotoCellDelegate, NCSectionFirstHeaderDelegate, NCSectionFooterDelegate, NCSectionFirstHeaderEmptyDataDelegate, NCAccountSettingsModelDelegate, NCTransferDelegate, UIAdaptivePresentationControllerDelegate, UIContextMenuInteractionDelegate { +class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate, NCListCellDelegate, NCGridCellDelegate, NCPhotoCellDelegate, NCSectionHeaderMenuDelegate, NCSectionFooterDelegate, NCSectionFirstHeaderEmptyDataDelegate, NCAccountSettingsModelDelegate, UIAdaptivePresentationControllerDelegate, UIContextMenuInteractionDelegate, NCEmptyDataSetDelegate { @IBOutlet weak var collectionView: UICollectionView! @@ -58,10 +58,17 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS var titleCurrentFolder = "" var titlePreviusFolder: String? var enableSearchBar: Bool = false + let maxImageGrid: CGFloat = 7 + var headerMenu: NCSectionHeaderMenu? + var headerMenuTransferView = false + var headerMenuButtonsView: Bool = true var headerRichWorkspaceDisable: Bool = false + + var groupByField = "name" var emptyImageName: String? var emptyImageColors: [UIColor]? + var emptyImage: UIImage? var emptyTitle: String = "" var emptyDescription: String = "" @@ -80,6 +87,11 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS var numberOfColumns: Int = 0 var lastNumberOfColumns: Int = 0 + var isTransitioning: Bool = false + var selectableDataSource: [RealmSwiftObject] { dataSource.getMetadataSourceForAllSections() } + var pushed: Bool = false + var emptyDataSet: NCEmptyDataSet? + let heightHeaderRecommendations: CGFloat = 160 let heightHeaderSection: CGFloat = 30 @@ -164,8 +176,11 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS view.backgroundColor = .systemBackground collectionView.backgroundColor = .systemBackground - refreshControl.tintColor = .clear - + refreshControl.tintColor = .gray + + listLayout = NCListLayout() + gridLayout = NCGridLayout() + if enableSearchBar { searchController = UISearchController(searchResultsController: nil) searchController?.searchResultsUpdater = self @@ -174,7 +189,8 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS searchController?.searchBar.delegate = self searchController?.searchBar.autocapitalizationType = .none navigationItem.searchController = searchController - navigationItem.hidesSearchBarWhenScrolling = true + navigationItem.hidesSearchBarWhenScrolling = false + navigationItem.backBarButtonItem = UIBarButtonItem(title: NSLocalizedString("_back_", comment: ""), style: .plain, target: nil, action: nil) } // Cell @@ -184,12 +200,8 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS collectionView.register(UINib(nibName: "NCTransferCell", bundle: nil), forCellWithReuseIdentifier: "transferCell") // Header - collectionView.register(UINib(nibName: "NCSectionFirstHeader", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "sectionFirstHeader") - collectionView.register(UINib(nibName: "NCSectionFirstHeader", bundle: nil), forSupplementaryViewOfKind: mediaSectionHeader, withReuseIdentifier: "sectionFirstHeader") + collectionView.register(UINib(nibName: "NCSectionHeaderMenu", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "sectionHeaderMenu") collectionView.register(UINib(nibName: "NCSectionHeader", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "sectionHeader") - collectionView.register(UINib(nibName: "NCSectionHeader", bundle: nil), forSupplementaryViewOfKind: mediaSectionHeader, withReuseIdentifier: "sectionHeader") - collectionView.register(UINib(nibName: "NCSectionFirstHeaderEmptyData", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "sectionFirstHeaderEmptyData") - collectionView.register(UINib(nibName: "NCSectionFirstHeaderEmptyData", bundle: nil), forSupplementaryViewOfKind: mediaSectionHeader, withReuseIdentifier: "sectionFirstHeaderEmptyData") // Footer collectionView.register(UINib(nibName: "NCSectionFooter", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: "sectionFooter") @@ -209,6 +221,9 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS self.mainNavigationController?.resetPlusButtonAlpha() } } + + // Empty + emptyDataSet = NCEmptyDataSet(view: collectionView, offset: getHeaderHeight(), delegate: self) let longPressedGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPressCollecationView(_:))) longPressedGesture.minimumPressDuration = 0.5 @@ -258,6 +273,11 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS navigationController?.navigationBar.topItem?.title = titlePreviusFolder } navigationItem.title = titleCurrentFolder + navigationController?.setNavigationBarAppearance() + navigationController?.navigationBar.prefersLargeTitles = true + navigationController?.setNavigationBarHidden(false, animated: true) + + appDelegate.activeViewController = self if tabBarSelect == nil { tabBarSelect = NCCollectionViewCommonSelectTabBar(controller: self.controller, viewController: self, delegate: self) @@ -265,12 +285,12 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS isEditMode = false - Task { - await (self.navigationController as? NCMainNavigationController)?.setNavigationLeftItems() - await (self.navigationController as? NCMainNavigationController)?.setNavigationRightItems() - } + /// Magentacloud branding changes hide user account button on left navigation bar +// setNavigationLeftItems() + setNavigationRightItems() layoutForView = database.getLayoutForView(account: session.account, key: layoutKey, serverUrl: serverUrl) + gridLayout.column = CGFloat(layoutForView?.columnGrid ?? 3) if isLayoutList { collectionView?.collectionViewLayout = listLayout self.layoutType = global.layoutList @@ -297,6 +317,30 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS NotificationCenter.default.addObserver(self, selector: #selector(applicationWillResignActive(_:)), name: UIApplication.willResignActiveNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(closeRichWorkspaceWebView), name: NSNotification.Name(rawValue: global.notificationCenterCloseRichWorkspaceWebView), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(changeStatusFolderE2EE(_:)), name: NSNotification.Name(rawValue: global.notificationCenterChangeStatusFolderE2EE), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(changeLayout(_:)), name: NSNotification.Name(rawValue: global.notificationCenterChangeLayout), object: nil) + + NotificationCenter.default.addObserver(self, selector: #selector(deleteFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterDeleteFile), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(copyMoveFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterCopyMoveFile), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(renameFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterRenameFile), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(createFolder(_:)), name: NSNotification.Name(rawValue: global.notificationCenterCreateFolder), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(favoriteFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterFavoriteFile), object: nil) + + NotificationCenter.default.addObserver(self, selector: #selector(downloadStartFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterDownloadStartFile), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(downloadedFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterDownloadedFile), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(downloadCancelFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterDownloadCancelFile), object: nil) + + NotificationCenter.default.addObserver(self, selector: #selector(uploadStartFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterUploadStartFile), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(uploadedFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterUploadedFile), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(uploadedLivePhoto(_:)), name: NSNotification.Name(rawValue: global.notificationCenterUploadedLivePhoto), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(uploadCancelFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterUploadCancelFile), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(updateShare(_:)), name: NSNotification.Name(rawValue: global.notificationCenterUpdateShare), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(triggerProgressTask(_:)), name: NSNotification.Name(rawValue: global.notificationCenterProgressTask), object: nil) + + // FIXME: iPAD PDF landscape mode iOS 16 + DispatchQueue.main.async { + self.collectionView?.collectionViewLayout.invalidateLayout() + } } override func viewWillDisappear(_ animated: Bool) { @@ -304,7 +348,8 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS self.networking.cancelUnifiedSearchFiles() dismissTip() - + pushed = false + toggleSelect(isOn: false) // Cancel Queue & Retrieves Properties self.networking.downloadThumbnailQueue.cancelAll() self.networking.unifiedSearchQueue.cancelAll() @@ -320,17 +365,38 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS NotificationCenter.default.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil) NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterCloseRichWorkspaceWebView), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterChangeStatusFolderE2EE), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterChangeLayout), object: nil) + + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterDeleteFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterCopyMoveFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterCopyFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterMoveFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterRenameFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterCreateFolder), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterFavoriteFile), object: nil) + + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterDownloadStartFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterDownloadedFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterDownloadCancelFile), object: nil) + + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterUploadStartFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterUploadedFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterUploadedLivePhoto), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterUploadCancelFile), object: nil) + + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterUpdateShare), object: nil) removeImageCache(metadatas: self.dataSource.getMetadatas()) } - func isApplicationUpdated() -> Bool{ + func isApplicationUpdated() -> Bool { let appVersion = Bundle.main.infoDictionary?["CFBundleInfoDictionaryVersion"] as? String ?? "" let currentVersion = UserDefaults.standard.string(forKey: "CurrentAppVersion") return currentVersion != appVersion } - func redirectToPrivacyViewController(){ + func redirectToPrivacyViewController() { let storyBoard: UIStoryboard = UIStoryboard(name: "NCSettings", bundle: nil) let newViewController = storyBoard.instantiateViewController(withIdentifier: "privacySettingsNavigation") as! UINavigationController newViewController.modalPresentationStyle = .fullScreen @@ -531,8 +597,352 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS self.present(alertController, animated: true) } + self.collectionView.collectionViewLayout.invalidateLayout() + +// (self.navigationController as? NCMainNavigationController)?.setNavigationRightItems() + } + + @objc func reloadDataSource(_ notification: NSNotification) { + if let userInfo = notification.userInfo as? NSDictionary { + if let serverUrl = userInfo["serverUrl"] as? String { + if serverUrl != self.serverUrl { + return + } + } + + if let clearDataSource = userInfo["clearDataSource"] as? Bool, clearDataSource { + self.dataSource.removeAll() + } + } + + reloadDataSource() + } + + @objc func getServerData(_ notification: NSNotification) { + if let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String { + if serverUrl != self.serverUrl { + return + } + } + + getServerData() + } + + @objc func reloadHeader(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let account = userInfo["account"] as? String, + account == session.account + else { return } + + self.collectionView.reloadData() + } + + @objc func changeStatusFolderE2EE(_ notification: NSNotification) { + reloadDataSource() + } + + @objc func closeRichWorkspaceWebView() { + reloadDataSource() + } + + @objc func deleteFile(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let error = userInfo["error"] as? NKError else { return } + + if error == .success { + if isSearchingMode { + return networkSearch() + } + + if isRecommendationActived { + Task.detached { + await NCNetworking.shared.createRecommendations(session: self.session) + } + } + } else { + NCContentPresenter().showError(error: error) + } + + reloadDataSource() + } + + @objc func copyMoveFile(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String, + let account = userInfo["account"] as? String, + account == session.account else { return } + + if isSearchingMode { + return networkSearch() + } + + if isRecommendationActived { + Task.detached { + await NCNetworking.shared.createRecommendations(session: self.session) + } + } + + if serverUrl == self.serverUrl { + reloadDataSource() + } + } + + @objc func renameFile(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let account = userInfo["account"] as? String, + let serverUrl = userInfo["serverUrl"] as? String, + let error = userInfo["error"] as? NKError, + account == session.account + else { return } + + if error == .success { + if isSearchingMode { + return networkSearch() + } + + if isRecommendationActived { + Task.detached { + await NCNetworking.shared.createRecommendations(session: self.session) + } + } + } + + if serverUrl == self.serverUrl { + if error != .success { + NCContentPresenter().showError(error: error) + } + reloadDataSource() + } else { + collectionView.reloadData() + } + } + + @objc func createFolder(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let ocId = userInfo["ocId"] as? String, + let account = userInfo["account"] as? String, + account == session.account, + let withPush = userInfo["withPush"] as? Bool, + let metadata = database.getMetadataFromOcId(ocId) + else { return } + + if isSearchingMode { + return networkSearch() + } + + if metadata.serverUrl + "/" + metadata.fileName == self.serverUrl { + reloadDataSource() + } else if withPush, metadata.serverUrl == self.serverUrl { + reloadDataSource() + if let sceneIdentifier = userInfo["sceneIdentifier"] as? String { + if sceneIdentifier == controller?.sceneIdentifier { + pushMetadata(metadata) + } + } else { + pushMetadata(metadata) + } + } + } + + @objc func favoriteFile(_ notification: NSNotification) { + if isSearchingMode { + return networkSearch() + } + + if self is NCFavorite { + return reloadDataSource() + } + + guard let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String, + serverUrl == self.serverUrl + else { return } + + reloadDataSource() + } + + @objc func downloadStartFile(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String, + let account = userInfo["account"] as? String + else { return } + + if account == self.session.account, serverUrl == self.serverUrl { + reloadDataSource() + } else { + collectionView?.reloadData() + } + } + + @objc func downloadedFile(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String, + let account = userInfo["account"] as? String + else { return } + + if account == self.session.account, serverUrl == self.serverUrl { + reloadDataSource() + } else { + collectionView?.reloadData() + } + } + + @objc func downloadCancelFile(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String, + let account = userInfo["account"] as? String + else { return } + + if account == self.session.account, serverUrl == self.serverUrl { + reloadDataSource() + } else { + collectionView?.reloadData() + } + } + + @objc func uploadStartFile(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let ocId = userInfo["ocId"] as? String, + let serverUrl = userInfo["serverUrl"] as? String, + let account = userInfo["account"] as? String, + !isSearchingMode, + let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) + else { return } + + // Header view trasfer + if metadata.isTransferInForeground { + NCNetworking.shared.transferInForegorund = NCNetworking.TransferInForegorund(ocId: ocId, progress: 0) + DispatchQueue.main.async { self.collectionView?.reloadData() } + } + + if account == self.session.account, serverUrl == self.serverUrl { + reloadDataSource() + } else { + collectionView?.reloadData() + } + } + + @objc func uploadedFile(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String, + let account = userInfo["account"] as? String + else { return } + + if account == self.session.account, serverUrl == self.serverUrl { + reloadDataSource() + } else { + collectionView?.reloadData() + } + } + + @objc func uploadedLivePhoto(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String, + let account = userInfo["account"] as? String + else { return } + + if account == self.session.account, serverUrl == self.serverUrl { + reloadDataSource() + } else { + collectionView?.reloadData() + } + } + + @objc func uploadCancelFile(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String, + let account = userInfo["account"] as? String + else { return } + + if account == self.session.account, serverUrl == self.serverUrl { + reloadDataSource() + } else { + collectionView?.reloadData() + } + } + + @objc func updateShare(_ notification: NSNotification) { + if isSearchingMode { + networkSearch() + } else { + self.dataSource.removeAll() + getServerData() + } + } + + @objc func triggerProgressTask(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let progressNumber = userInfo["progress"] as? NSNumber, + let totalBytes = userInfo["totalBytes"] as? Int64, + let totalBytesExpected = userInfo["totalBytesExpected"] as? Int64, + let ocId = userInfo["ocId"] as? String, + let ocIdTransfer = userInfo["ocIdTransfer"] as? String, + let session = userInfo["session"] as? String + else { return } + + let chunk: Int = userInfo["chunk"] as? Int ?? 0 + let e2eEncrypted: Bool = userInfo["e2eEncrypted"] as? Bool ?? false + + let transfer = NCTransferProgress.shared.append(NCTransferProgress.Transfer(ocId: ocId, ocIdTransfer: ocIdTransfer, session: session, chunk: chunk, e2eEncrypted: e2eEncrypted, progressNumber: progressNumber, totalBytes: totalBytes, totalBytesExpected: totalBytesExpected)) + + // HEADER +// if self.headerMenuTransferView, transfer.session.contains("upload") { +// self.sectionFirstHeader?.setViewTransfer(isHidden: false, progress: transfer.progressNumber.floatValue) +// self.sectionFirstHeaderEmptyData?.setViewTransfer(isHidden: false, progress: transfer.progressNumber.floatValue) +// } + + DispatchQueue.main.async { + if self.headerMenuTransferView && (chunk > 0 || e2eEncrypted) { + if NCNetworking.shared.transferInForegorund?.ocId == ocId { + NCNetworking.shared.transferInForegorund?.progress = progressNumber.floatValue + } else { + NCNetworking.shared.transferInForegorund = NCNetworking.TransferInForegorund(ocId: ocId, progress: progressNumber.floatValue) + self.collectionView.reloadData() + } + self.headerMenu?.progressTransfer.progress = transfer.progressNumber.floatValue + } else { + guard let indexPath = self.dataSource.getIndexPathMetadata(ocId: ocId).indexPath, + let cell = self.collectionView?.cellForItem(at: indexPath), + let cell = cell as? NCCellProtocol else { return } + if progressNumber.floatValue == 1 && !(cell is NCTransferCell) { + cell.fileProgressView?.isHidden = true + cell.fileProgressView?.progress = .zero + cell.setButtonMore(named: NCGlobal.shared.buttonMoreMore, image: NCImageCache.images.buttonMore) + if let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) { + cell.writeInfoDateSize(date: metadata.date, size: metadata.size) + } else { + cell.fileInfoLabel?.text = "" + cell.fileSubinfoLabel?.text = "" + } + } else { + cell.fileProgressView?.isHidden = false + cell.fileProgressView?.progress = progressNumber.floatValue + cell.setButtonMore(named: NCGlobal.shared.buttonMoreStop, image: NCImageCache.images.buttonStop) + let status = userInfo["status"] as? Int ?? NCGlobal.shared.metadataStatusNormal + if status == NCGlobal.shared.metadataStatusDownloading { + cell.fileInfoLabel?.text = self.utilityFileSystem.transformedSize(totalBytesExpected) + cell.fileSubinfoLabel?.text = self.infoLabelsSeparator + "↓ " + self.utilityFileSystem.transformedSize(totalBytes) + } else if status == NCGlobal.shared.metadataStatusUploading { + if totalBytes > 0 { + cell.fileInfoLabel?.text = self.utilityFileSystem.transformedSize(totalBytesExpected) + cell.fileSubinfoLabel?.text = self.infoLabelsSeparator + "↑ " + self.utilityFileSystem.transformedSize(totalBytes) + } else { + cell.fileInfoLabel?.text = self.utilityFileSystem.transformedSize(totalBytesExpected) + cell.fileSubinfoLabel?.text = self.infoLabelsSeparator + "↑ …" + } + } + } + } + } } + + // MARK: - Layout + func setNavigationLeftItems() { + navigationItem.title = titleCurrentFolder + } + func getNavigationTitle() -> String { let tblAccount = self.database.getTableAccount(predicate: NSPredicate(format: "account == %@", session.account)) if let tblAccount, @@ -578,6 +988,36 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS self.navigationItem.title = self.titleCurrentFolder } + // MARK: - Empty + + func emptyDataSetView(_ view: NCEmptyView) { + + self.emptyDataSet?.setOffset(getHeaderHeight()) + if isSearchingMode { + view.emptyImage.image = UIImage(named: "search")?.image(color: .gray, size: UIScreen.main.bounds.width) + if self.dataSourceTask?.state == .running { + view.emptyTitle.text = NSLocalizedString("_search_in_progress_", comment: "") + } else { + view.emptyTitle.text = NSLocalizedString("_search_no_record_found_", comment: "") + } + view.emptyDescription.text = NSLocalizedString("_search_instruction_", comment: "") + } else if self.dataSourceTask?.state == .running { + view.emptyImage.image = UIImage(named: "networkInProgress")?.image(color: .gray, size: UIScreen.main.bounds.width) + view.emptyTitle.text = NSLocalizedString("_request_in_progress_", comment: "") + view.emptyDescription.text = "" + } else { + if serverUrl.isEmpty { + view.emptyImage.image = emptyImage + view.emptyTitle.text = NSLocalizedString(emptyTitle, comment: "") + view.emptyDescription.text = NSLocalizedString(emptyDescription, comment: "") + } else { + view.emptyImage.image = UIImage(named: "folder_nmcloud") + view.emptyTitle.text = NSLocalizedString("_files_no_files_", comment: "") + view.emptyDescription.text = NSLocalizedString("_no_file_pull_down_", comment: "") + } + } + } + // MARK: - SEARCH func searchController(enabled: Bool) { @@ -630,23 +1070,35 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // MARK: - TAP EVENT - func tapMoreListItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { - tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: image, sender: sender) + func tapMoreListItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, image: UIImage?, sender: Any) { + tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, namedButtonMore: namedButtonMore, image: image, sender: sender) } - func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { - tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: image, sender: sender) + func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, image: UIImage?, sender: Any) { + tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, namedButtonMore: namedButtonMore, image: image, sender: sender) } func tapShareListItem(with ocId: String, ocIdTransfer: String, sender: Any) { + if isEditMode { return } guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } NCCreate().createShare(viewController: self, metadata: metadata, page: .sharing) +// NCDownloadAction.shared.openShare(viewController: self, metadata: metadata, page: .sharing) + TealiumHelper.shared.trackEvent(title: "magentacloud-app.filebrowser.sharing", data: ["": ""]) + appDelegate.adjust.trackEvent(TriggerEvent(Sharing.rawValue)) } - func tapMoreGridItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { + func tapMoreGridItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, image: UIImage?, sender: Any) { + if isEditMode { return } guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } - toggleMenu(metadata: metadata, image: image, sender: sender) +// toggleMenu(metadata: metadata, image: image) + if namedButtonMore == NCGlobal.shared.buttonMoreMore || namedButtonMore == NCGlobal.shared.buttonMoreLock { + toggleMenu(metadata: metadata, image: image) + } else if namedButtonMore == NCGlobal.shared.buttonMoreStop { + Task { + await cancelSession(metadata: metadata) + } + } } func tapRichWorkspace(_ sender: Any) { @@ -674,16 +1126,50 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS didSelectMetadata(metadata, withOcIds: false) } - func longPressListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + func tapButtonSwitch(_ sender: Any) { + guard !isTransitioning else { return } + isTransitioning = true - func longPressGridItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + guard let layoutForView = NCManageDatabase.shared.getLayoutForView(account: session.account, key: layoutKey, serverUrl: serverUrl) else { return } - func longPressMoreListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + if layoutForView.layout == NCGlobal.shared.layoutGrid { + layoutForView.layout = NCGlobal.shared.layoutList + } else { + layoutForView.layout = NCGlobal.shared.layoutGrid + } + self.layoutForView = NCManageDatabase.shared.setLayoutForView(layoutForView: layoutForView) + self.collectionView.reloadData() + self.collectionView.collectionViewLayout.invalidateLayout() + self.collectionView.setCollectionViewLayout(layoutForView.layout == NCGlobal.shared.layoutList ? self.listLayout : self.gridLayout, animated: true) {_ in self.isTransitioning = false } + } - func longPressPhotoItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + func tapButtonOrder(_ sender: Any) { + +// if let titleButtonHeader = NCKeychain().getTitleButtonHeader(account: session.account), !titleButtonHeader.isEmpty { +// layoutForView?.titleButtonHeader = titleButtonHeader +// } +// NCManageDatabase.shared.setLayoutForView(layoutForView: layoutForView!) + + let sortMenu = NCSortMenu() + sortMenu.toggleMenu(viewController: self, account: session.account, key: layoutKey, sortButton: sender as? UIButton, serverUrl: serverUrl) + } - func longPressMoreGridItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + func longPressPhotoItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + + func longPressListItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) { } + + func tapShareListItem(with objectId: String, indexPath: IndexPath, sender: Any) { } + func longPressMoreListItem(with ocId: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) { } + + func longPressGridItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) { } + + func longPressMoreGridItem(with ocId: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) { } + + func tapButtonTransfer(_ sender: Any) { } + + func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { } + @objc func longPressCollecationView(_ gestureRecognizer: UILongPressGestureRecognizer) { openMenuItems(with: nil, gestureRecognizer: gestureRecognizer) } @@ -807,11 +1293,27 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // MARK: - DataSource - @MainActor - func reloadDataSource() async { - if !isSearchingMode { - Task.detached { - if await self.isRecommendationActived() { + @objc func reloadDataSource() async { + + // get auto upload folder + autoUploadFileName = NCManageDatabase.shared.getAccountAutoUploadFileName() + autoUploadDirectory = NCManageDatabase.shared.getAccountAutoUploadDirectory(urlBase: session.urlBase, userId: session.userId, account: session.account) + + // get layout for view + layoutForView = NCManageDatabase.shared.getLayoutForView(account: session.account, key: layoutKey, serverUrl: serverUrl) + // set GroupField for Grid + if !isSearchingMode && layoutForView?.layout == NCGlobal.shared.layoutGrid { + groupByField = "classFile" + } else { + groupByField = "name" + } + + if isSearchingMode { + isDirectoryEncrypted = false + } else { + isDirectoryEncrypted = NCUtilityFileSystem().isDirectoryE2EE(session: session, serverUrl: serverUrl) + if isRecommendationActived { + Task.detached { await self.networking.createRecommendations(session: self.session, serverUrl: self.serverUrl, collectionView: self.collectionView) } } @@ -823,7 +1325,11 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS animations: { self.collectionView.reloadData() }, completion: nil) - await (self.navigationController as? NCMainNavigationController)?.updateRightMenu() + (self.navigationController as? NCMainNavigationController)?.updateRightMenu() + self.refreshControl.endRefreshing() + self.collectionView.reloadData() + self.setNavigationRightItems() + } } func getServerData(forced: Bool = false) async { } @@ -961,39 +1467,56 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } } - // MARK: - Header size - - func getHeaderHeight(section: Int) -> (heightHeaderRichWorkspace: CGFloat, - heightHeaderRecommendations: CGFloat, - heightHeaderSection: CGFloat) { - var heightHeaderRichWorkspace: CGFloat = 0 - var heightHeaderRecommendations: CGFloat = 0 - var heightHeaderSection: CGFloat = 0 + func pushViewController(viewController: UIViewController) { + if pushed { return } - if showDescription, - !isSearchingMode, - let richWorkspaceText = self.richWorkspaceText, - !richWorkspaceText.trimmingCharacters(in: .whitespaces).isEmpty { - heightHeaderRichWorkspace = UIScreen.main.bounds.size.height / 6 - } - - if isRecommendationActived, - !isSearchingMode, - NCPreferences().showRecommendedFiles, - !self.database.getRecommendedFiles(account: self.session.account).isEmpty { - heightHeaderRecommendations = self.heightHeaderRecommendations - heightHeaderSection = self.heightHeaderSection - } + pushed = true + navigationController?.pushViewController(viewController, animated: true) + } + + // MARK: - Header size - if isSearchingMode || layoutForView?.groupBy != "none" || self.dataSource.numberOfSections() > 1 { - if section == 0 { - return (heightHeaderRichWorkspace, heightHeaderRecommendations, self.heightHeaderSection) - } else { - return (0, 0, self.heightHeaderSection) - } - } else { - return (heightHeaderRichWorkspace, heightHeaderRecommendations, heightHeaderSection) +// func getHeaderHeight(section: Int) -> (heightHeaderRichWorkspace: CGFloat, +// heightHeaderRecommendations: CGFloat, +// heightHeaderSection: CGFloat) { +// var heightHeaderRichWorkspace: CGFloat = 0 +// var heightHeaderRecommendations: CGFloat = 0 +// var heightHeaderSection: CGFloat = 0 +// +// if showDescription, +// !isSearchingMode, +// let richWorkspaceText = self.richWorkspaceText, +// !richWorkspaceText.trimmingCharacters(in: .whitespaces).isEmpty { +// heightHeaderRichWorkspace = UIScreen.main.bounds.size.height / 6 +// } +// +// if isRecommendationActived, +// !isSearchingMode, +// NCKeychain().showRecommendedFiles, +// !self.database.getRecommendedFiles(account: self.session.account).isEmpty { +// heightHeaderRecommendations = self.heightHeaderRecommendations +// heightHeaderSection = self.heightHeaderSection +// } +// +// if isSearchingMode || layoutForView?.groupBy != "none" || self.dataSource.numberOfSections() > 1 { +// if section == 0 { +// return (heightHeaderRichWorkspace, heightHeaderRecommendations, self.heightHeaderSection) +// } else { +// return (0, 0, self.heightHeaderSection) +// } +// } else { +// return (heightHeaderRichWorkspace, heightHeaderRecommendations, heightHeaderSection) +// } +// } + + func isHeaderMenuTransferViewEnabled() -> [tableMetadata]? { + if headerMenuTransferView, + NCNetworking.shared.isOnline, + let results = database.getResultsMetadatas(predicate: NSPredicate(format: "status IN %@", [global.metadataStatusWaitUpload, global.metadataStatusUploading])), + !results.isEmpty { + return Array(results) } + return nil } func sizeForHeaderInSection(section: Int) -> CGSize { @@ -1002,7 +1525,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS let isIphone = UIDevice.current.userInterfaceIdiom == .phone if self.dataSource.isEmpty() { - height = utility.getHeightHeaderEmptyData(view: view, portraitOffset: emptyDataPortaitOffset, landscapeOffset: emptyDataLandscapeOffset) + height = utility.getHeightHeaderEmptyData(view: view, portraitOffset: emptyDataPortaitOffset, landscapeOffset: emptyDataLandscapeOffset, isHeaderMenuTransferViewEnabled: isHeaderMenuTransferViewEnabled() != nil) } else if isEditMode || (isLandscape && isIphone) { return CGSize.zero } else { From 3273c1505dc695f822c8aa73a8f0f8780cbe405a Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Tue, 30 Sep 2025 22:00:01 +0530 Subject: [PATCH 06/11] NMC 1984 - Privacy policy customisation added --- .../NCCollectionViewCommon.swift | 547 +++++++++--------- 1 file changed, 271 insertions(+), 276 deletions(-) diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index 1ba3a8dbee..cb54bb2153 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -4,12 +4,14 @@ import UIKit import SwiftUI +import Realm import RealmSwift import NextcloudKit import EasyTipView import LucidBanner +import MoEngageInApps -class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate, NCListCellDelegate, NCGridCellDelegate, NCPhotoCellDelegate, NCSectionHeaderMenuDelegate, NCSectionFooterDelegate, NCSectionFirstHeaderEmptyDataDelegate, NCAccountSettingsModelDelegate, UIAdaptivePresentationControllerDelegate, UIContextMenuInteractionDelegate, NCEmptyDataSetDelegate { +class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate, NCListCellDelegate, NCGridCellDelegate, NCPhotoCellDelegate, NCSectionHeaderMenuDelegate, NCSectionFooterDelegate, NCSectionFirstHeaderEmptyDataDelegate, NCAccountSettingsModelDelegate, NCTransferDelegate, UIAdaptivePresentationControllerDelegate, UIContextMenuInteractionDelegate, NCEmptyDataSetDelegate { @IBOutlet weak var collectionView: UICollectionView! @@ -19,7 +21,6 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS let utilityFileSystem = NCUtilityFileSystem() let imageCache = NCImageCache.shared var dataSource = NCCollectionViewDataSource() - let networking = NCNetworking.shared let appDelegate = (UIApplication.shared.delegate as? AppDelegate)! var pinchGesture: UIPinchGestureRecognizer = UIPinchGestureRecognizer() @@ -30,7 +31,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS var backgroundImageView = UIImageView() var serverUrl: String = "" var isEditMode = false - var isDirectoryE2EE = false + var isDirectoryEncrypted = false var fileSelect: [String] = [] var metadataFolder: tableMetadata? var richWorkspaceText: String? @@ -39,7 +40,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS var isSearchingMode: Bool = false var networkSearchInProgress: Bool = false var layoutForView: NCDBLayoutForView? - var searchDataSourceTask: URLSessionTask? + var dataSourceTask: URLSessionTask? var providers: [NKSearchProvider]? var searchResults: [NKSearchResult]? var listLayout = NCListLayout() @@ -50,19 +51,22 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS var tabBarSelect: NCCollectionViewCommonSelectTabBar? var attributesZoomIn: UIMenuElement.Attributes = [] var attributesZoomOut: UIMenuElement.Attributes = [] - var tipViewAccounts: EasyTipView? var syncMetadatasTask: Task? + let maxImageGrid: CGFloat = 7 +// var headerMenu: NCSectionFirstHeader? + var tipViewAccounts: EasyTipView? + var tipViewAutoUpload: EasyTipView? + var headerMenu: NCSectionHeaderMenu? + var headerMenuTransferView = false + var headerMenuButtonsView: Bool = true + var headerRichWorkspaceDisable: Bool = false + // DECLARE var layoutKey = "" var titleCurrentFolder = "" var titlePreviusFolder: String? var enableSearchBar: Bool = false - let maxImageGrid: CGFloat = 7 - var headerMenu: NCSectionHeaderMenu? - var headerMenuTransferView = false - var headerMenuButtonsView: Bool = true - var headerRichWorkspaceDisable: Bool = false var groupByField = "name" @@ -95,7 +99,6 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS let heightHeaderRecommendations: CGFloat = 160 let heightHeaderSection: CGFloat = 30 - @MainActor var session: NCSession.Session { NCSession.shared.getSession(controller: tabBarController) } @@ -113,29 +116,30 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } var showDescription: Bool { - !headerRichWorkspaceDisable && NCPreferences().showDescription + !headerRichWorkspaceDisable && NCKeychain().showDescription } var isRecommendationActived: Bool { - let capabilities = NCNetworking.shared.capabilities[session.account] ?? NKCapabilities.Capabilities() - return self.serverUrl == self.utilityFileSystem.getHomeServer(session: self.session) && capabilities.recommendations + self.serverUrl == self.utilityFileSystem.getHomeServer(session: self.session) && + NCCapabilities.shared.getCapabilities(account: self.session.account).capabilityRecommendations } var infoLabelsSeparator: String { layoutForView?.layout == global.layoutList ? " - " : "" } - @MainActor var controller: NCMainTabBarController? { self.tabBarController as? NCMainTabBarController } - var mainNavigationController: NCMainNavigationController? { - self.navigationController as? NCMainNavigationController + var defaultPredicate: NSPredicate { + let predicate = NSPredicate(format: "account == %@ AND serverUrl == %@ AND NOT (status IN %@) AND NOT (livePhotoFile != '' AND classFile == %@)", session.account, self.serverUrl, NCGlobal.shared.metadataStatusHideInView, NKCommon.TypeClassFile.video.rawValue) + return predicate } - var sceneIdentifier: String { - (self.tabBarController as? NCMainTabBarController)?.sceneIdentifier ?? "" + var personalFilesOnlyPredicate: NSPredicate { + let predicate = NSPredicate(format: "account == %@ AND serverUrl == %@ AND (ownerId == %@ || ownerId == '') AND mountType == '' AND NOT (status IN %@) AND NOT (livePhotoFile != '' AND classFile == %@)", session.account, self.serverUrl, session.userId, global.metadataStatusHideInView, NKCommon.TypeClassFile.video.rawValue) + return predicate } var isNumberOfItemsInAllSectionsNull: Bool { @@ -170,6 +174,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS override func viewDidLoad() { super.viewDidLoad() + tabBarSelect = NCCollectionViewCommonSelectTabBar(controller: self.controller, delegate: self) self.navigationController?.presentationController?.delegate = self collectionView.alwaysBounceVertical = true collectionView.accessibilityIdentifier = "NCCollectionViewCommon" @@ -209,16 +214,16 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS collectionView.refreshControl = refreshControl refreshControl.action(for: .valueChanged) { _ in - Task { @MainActor in - // Perform async server forced - await self.getServerData(forced: true) - - // Stop the refresh control after data is loaded - self.refreshControl.endRefreshing() - - // Wait 1.5 seconds before resetting the button alpha - try? await Task.sleep(nanoseconds: 1_500_000_000) - self.mainNavigationController?.resetPlusButtonAlpha() + self.dataSource.removeAll() + self.getServerData() + if self.isRecommendationActived { + Task.detached { + await NCNetworking.shared.createRecommendations(session: self.session) + } + } + self.refreshControl.endRefreshing() + DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { + self.resetPlusButtonAlpha() } } @@ -249,17 +254,12 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS let appVersion = Bundle.main.infoDictionary?["CFBundleInfoDictionaryVersion"] as? String UserDefaults.standard.set(appVersion, forKey: "CurrentAppVersion") } - - registerForTraitChanges([UITraitUserInterfaceStyle.self]) { [weak self] (view: NCCollectionViewCommon, _) in - guard let self else { return } - - self.sectionFirstHeader?.setRichWorkspaceColor(style: view.traitCollection.userInterfaceStyle) - } - - NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: self.global.notificationCenterChangeTheming), object: nil, queue: .main) { [weak self] _ in - guard let self else { return } - self.collectionView.reloadData() - } + + NotificationCenter.default.addObserver(self, selector: #selector(changeTheming(_:)), name: NSNotification.Name(rawValue: global.notificationCenterChangeTheming), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(reloadDataSource(_:)), name: NSNotification.Name(rawValue: global.notificationCenterReloadDataSource), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(getServerData(_:)), name: NSNotification.Name(rawValue: global.notificationCenterGetServerData), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(reloadHeader(_:)), name: NSNotification.Name(rawValue: global.notificationCenterReloadHeader), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(updateIcons), name: NSNotification.Name(rawValue: global.notificationCenterUpdateIcons), object: nil) DispatchQueue.main.async { self.collectionView?.collectionViewLayout.invalidateLayout() @@ -269,6 +269,8 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) + AnalyticsHelper.shared.displayInAppNotification() + if titlePreviusFolder != nil { navigationController?.navigationBar.topItem?.title = titlePreviusFolder } @@ -278,11 +280,12 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS navigationController?.setNavigationBarHidden(false, animated: true) appDelegate.activeViewController = self + appDelegate.account = session.account + appDelegate.urlBase = session.urlBase + appDelegate.userId = session.userId + appDelegate.user = session.user - if tabBarSelect == nil { - tabBarSelect = NCCollectionViewCommonSelectTabBar(controller: self.controller, viewController: self, delegate: self) - } - + NCKeychain().setAccountName(account: session.account) isEditMode = false /// Magentacloud branding changes hide user account button on left navigation bar @@ -311,9 +314,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - Task { - await NCNetworking.shared.transferDispatcher.addDelegate(self) - } + NCNetworking.shared.transferDelegate = self NotificationCenter.default.addObserver(self, selector: #selector(applicationWillResignActive(_:)), name: UIApplication.willResignActiveNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(closeRichWorkspaceWebView), name: NSNotification.Name(rawValue: global.notificationCenterCloseRichWorkspaceWebView), object: nil) @@ -346,22 +347,20 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - self.networking.cancelUnifiedSearchFiles() + NCNetworking.shared.cancelUnifiedSearchFiles() dismissTip() pushed = false toggleSelect(isOn: false) // Cancel Queue & Retrieves Properties - self.networking.downloadThumbnailQueue.cancelAll() - self.networking.unifiedSearchQueue.cancelAll() - searchDataSourceTask?.cancel() + NCNetworking.shared.downloadThumbnailQueue.cancelAll() + NCNetworking.shared.unifiedSearchQueue.cancelAll() + dataSourceTask?.cancel() } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) - Task { - await NCNetworking.shared.transferDispatcher.removeDelegate(self) - } + NCNetworking.shared.transferDelegate = nil NotificationCenter.default.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil) NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterCloseRichWorkspaceWebView), object: nil) @@ -376,6 +375,11 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterCreateFolder), object: nil) NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterFavoriteFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterDeleteFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterMoveFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterCopyFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterCreateFolder), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterFavoriteFile), object: nil) NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterDownloadStartFile), object: nil) NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterDownloadedFile), object: nil) NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterDownloadCancelFile), object: nil) @@ -387,7 +391,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterUpdateShare), object: nil) - removeImageCache(metadatas: self.dataSource.getMetadatas()) + dataSource.removeImageCache() } func isApplicationUpdated() -> Bool { @@ -428,8 +432,14 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS return true } - // MARK: - Transfer Delegate + override func viewWillLayoutSubviews() { + super.viewWillLayoutSubviews() + if let frame = tabBarController?.tabBar.frame { + tabBarSelect?.hostingController?.view.frame = frame + } + } + func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String) { } func transferChange(status: String, @@ -535,68 +545,57 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // MARK: - NotificationCenter @objc func applicationWillResignActive(_ notification: NSNotification) { - mainNavigationController?.resetPlusButtonAlpha() + self.resetPlusButtonAlpha() + self.refreshControl.endRefreshing() } - @objc func closeRichWorkspaceWebView() { - Task { - await self.reloadDataSource() + @objc func reloadAvatar(_ notification: NSNotification) { + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + self.showTipAccounts() } + guard let userInfo = notification.userInfo as NSDictionary?, + let error = userInfo["error"] as? NKError, + error.errorCode != global.errorNotModified else { return } + /// Magentacloud branding changes hide user account button on left navigation bar + setNavigationLeftItems() } - // MARK: - Layout - - func changeLayout(layoutForView: NCDBLayoutForView) { - let homeServer = utilityFileSystem.getHomeServer(urlBase: session.urlBase, userId: session.userId) - let numFoldersLayoutsForView = self.database.getLayoutsForView(keyStore: layoutForView.keyStore)?.count ?? 1 - - func changeLayout(withSubFolders: Bool) { - if self.layoutForView?.layout == layoutForView.layout { - self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView, withSubFolders: withSubFolders) - Task { - await self.reloadDataSource() - } - return - } - - self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView, withSubFolders: withSubFolders) - layoutForView.layout = layoutForView.layout - self.layoutType = layoutForView.layout - - collectionView.reloadData() - - switch layoutForView.layout { - case global.layoutList: - self.collectionView.setCollectionViewLayout(self.listLayout, animated: true) - case global.layoutGrid: - self.collectionView.setCollectionViewLayout(self.gridLayout, animated: true) - case global.layoutPhotoSquare, global.layoutPhotoRatio: - self.collectionView.setCollectionViewLayout(self.mediaLayout, animated: true) - default: - break - } + @objc func changeTheming(_ notification: NSNotification) { + self.reloadDataSource() + } - self.collectionView.collectionViewLayout.invalidateLayout() + @objc func changeLayout(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let account = userInfo["account"] as? String, + let serverUrl = userInfo["serverUrl"] as? String, + let layoutForView = userInfo["layoutForView"] as? NCDBLayoutForView, + account == session.account, + serverUrl == self.serverUrl + else { return } - Task { - await (self.navigationController as? NCMainNavigationController)?.updateRightMenu() - } + if self.layoutForView?.layout == layoutForView.layout { + self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView) + self.reloadDataSource() + return } - if serverUrl == homeServer || numFoldersLayoutsForView == 1 { - changeLayout(withSubFolders: false) - } else { - let alertController = UIAlertController(title: NSLocalizedString("_propagate_layout_", comment: ""), message: nil, preferredStyle: .alert) + self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView) + layoutForView.layout = layoutForView.layout + self.layoutType = layoutForView.layout - alertController.addAction(UIAlertAction(title: NSLocalizedString("_yes_", comment: ""), style: .default, handler: { _ in - changeLayout(withSubFolders: true) - })) - alertController.addAction(UIAlertAction(title: NSLocalizedString("_no_", comment: ""), style: .default, handler: { _ in - changeLayout(withSubFolders: false) - })) + collectionView.reloadData() - self.present(alertController, animated: true) + switch layoutForView.layout { + case global.layoutList: + self.collectionView.setCollectionViewLayout(self.listLayout, animated: true) + case global.layoutGrid: + self.collectionView.setCollectionViewLayout(self.gridLayout, animated: true) + case global.layoutPhotoSquare, global.layoutPhotoRatio: + self.collectionView.setCollectionViewLayout(self.mediaLayout, animated: true) + default: + break } + self.collectionView.collectionViewLayout.invalidateLayout() // (self.navigationController as? NCMainNavigationController)?.setNavigationRightItems() @@ -861,16 +860,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS collectionView?.reloadData() } } - - @objc func updateShare(_ notification: NSNotification) { - if isSearchingMode { - networkSearch() - } else { - self.dataSource.removeAll() - getServerData() - } - } - + @objc func triggerProgressTask(_ notification: NSNotification) { guard let userInfo = notification.userInfo as NSDictionary?, let progressNumber = userInfo["progress"] as? NSNumber, @@ -885,14 +875,8 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS let e2eEncrypted: Bool = userInfo["e2eEncrypted"] as? Bool ?? false let transfer = NCTransferProgress.shared.append(NCTransferProgress.Transfer(ocId: ocId, ocIdTransfer: ocIdTransfer, session: session, chunk: chunk, e2eEncrypted: e2eEncrypted, progressNumber: progressNumber, totalBytes: totalBytes, totalBytesExpected: totalBytesExpected)) - - // HEADER -// if self.headerMenuTransferView, transfer.session.contains("upload") { -// self.sectionFirstHeader?.setViewTransfer(isHidden: false, progress: transfer.progressNumber.floatValue) -// self.sectionFirstHeaderEmptyData?.setViewTransfer(isHidden: false, progress: transfer.progressNumber.floatValue) -// } - DispatchQueue.main.async { +// DispatchQueue.main.async { if self.headerMenuTransferView && (chunk > 0 || e2eEncrypted) { if NCNetworking.shared.transferInForegorund?.ocId == ocId { NCNetworking.shared.transferInForegorund?.progress = progressNumber.floatValue @@ -902,7 +886,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } self.headerMenu?.progressTransfer.progress = transfer.progressNumber.floatValue } else { - guard let indexPath = self.dataSource.getIndexPathMetadata(ocId: ocId).indexPath, + guard let indexPath = self.dataSource.getIndexPathMetadata(ocId: ocId), let cell = self.collectionView?.cellForItem(at: indexPath), let cell = cell as? NCCellProtocol else { return } if progressNumber.floatValue == 1 && !(cell is NCTransferCell) { @@ -934,8 +918,22 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } } } +// } + } + + @objc func updateShare(_ notification: NSNotification) { + if isSearchingMode { + networkSearch() + } else { +// self.dataSource.removeAll() + getServerData() } } + + @objc func updateIcons() { + collectionView.reloadData() +// reloadDataSource() + } // MARK: - Layout @@ -944,15 +942,15 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } func getNavigationTitle() -> String { - let tblAccount = self.database.getTableAccount(predicate: NSPredicate(format: "account == %@", session.account)) - if let tblAccount, - !tblAccount.alias.isEmpty { - return tblAccount.alias + let tableAccount = self.database.getTableAccount(predicate: NSPredicate(format: "account == %@", session.account)) + if let tableAccount, + !tableAccount.alias.isEmpty { + return tableAccount.alias } return NCBrandOptions.shared.brand } - func accountSettingsDidDismiss(tblAccount: tableAccount?, controller: NCMainTabBarController?) { } + func accountSettingsDidDismiss(tableAccount: tableAccount?, controller: NCMainTabBarController?) { } @MainActor func startGUIGetServerData() { @@ -987,6 +985,10 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS self.navigationItem.titleView = nil self.navigationItem.title = self.titleCurrentFolder } + + func resetPlusButtonAlpha(animated: Bool = true) { } + + func isHiddenPlusButton(_ isHidden: Bool) { } // MARK: - Empty @@ -1027,7 +1029,6 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS searchController?.searchBar.alpha = 1 } else { searchController?.searchBar.alpha = 0.3 - } } @@ -1039,13 +1040,9 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS isSearchingMode = true self.providers?.removeAll() self.dataSource.removeAll() - Task { - await self.reloadDataSource() - } + self.reloadDataSource() // TIP dismissTip() - // - mainNavigationController?.hiddenPlusButton(true) } func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { @@ -1055,17 +1052,12 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { - self.networking.cancelUnifiedSearchFiles() - + NCNetworking.shared.cancelUnifiedSearchFiles() self.isSearchingMode = false self.literalSearch = "" self.providers?.removeAll() self.dataSource.removeAll() - Task { - await self.reloadDataSource() - } - // - mainNavigationController?.hiddenPlusButton(false) + self.reloadDataSource() } // MARK: - TAP EVENT @@ -1073,6 +1065,10 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS func tapMoreListItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, image: UIImage?, sender: Any) { tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, namedButtonMore: namedButtonMore, image: image, sender: sender) } + + func tapMoreListItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { + tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: image, sender: sender) + } func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, image: UIImage?, sender: Any) { tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, namedButtonMore: namedButtonMore, image: image, sender: sender) @@ -1100,6 +1096,16 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } } } + + func tapMoreGridItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { + guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } +// toggleMenu(metadata: metadata, image: image) + Task { + await cancelSession(metadata: metadata) + } + } + + func longPressMoreListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } func tapRichWorkspace(_ sender: Any) { if let navigationController = UIStoryboard(name: "NCViewerRichWorkspace", bundle: nil).instantiateInitialViewController() as? UINavigationController { @@ -1114,8 +1120,8 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } } - func tapRecommendationsButtonMenu(with metadata: tableMetadata, image: UIImage?, sender: Any?) { - toggleMenu(metadata: metadata, image: image, sender: sender) + func tapRecommendationsButtonMenu(with metadata: tableMetadata, image: UIImage?) { + toggleMenu(metadata: metadata, image: image) } func tapButtonSection(_ sender: Any, metadataForSection: NCMetadataForSection?) { @@ -1144,32 +1150,28 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } func tapButtonOrder(_ sender: Any) { - -// if let titleButtonHeader = NCKeychain().getTitleButtonHeader(account: session.account), !titleButtonHeader.isEmpty { -// layoutForView?.titleButtonHeader = titleButtonHeader -// } -// NCManageDatabase.shared.setLayoutForView(layoutForView: layoutForView!) - let sortMenu = NCSortMenu() sortMenu.toggleMenu(viewController: self, account: session.account, key: layoutKey, sortButton: sender as? UIButton, serverUrl: serverUrl) } - func longPressPhotoItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + func tapButtonTransfer(_ sender: Any) { } - func longPressListItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) { } + func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { } - func tapShareListItem(with objectId: String, indexPath: IndexPath, sender: Any) { } + func longPressListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + + func longPressListItem(with objectId: String, indexPath: IndexPath, gestureRecognizer: UILongPressGestureRecognizer) { + } + func longPressListItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) { } func longPressMoreListItem(with ocId: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) { } + + func longPressPhotoItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } func longPressGridItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) { } func longPressMoreGridItem(with ocId: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) { } - func tapButtonTransfer(_ sender: Any) { } - - func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { } - @objc func longPressCollecationView(_ gestureRecognizer: UILongPressGestureRecognizer) { openMenuItems(with: nil, gestureRecognizer: gestureRecognizer) } @@ -1191,7 +1193,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS becomeFirstResponder() if !serverUrl.isEmpty { - listMenuItems.append(UIMenuItem(title: NSLocalizedString("_paste_file_", comment: ""), action: #selector(pasteFilesMenu(_:)))) + listMenuItems.append(UIMenuItem(title: NSLocalizedString("_paste_file_", comment: ""), action: #selector(pasteFilesMenu))) } if !listMenuItems.isEmpty { @@ -1200,16 +1202,23 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } } + // MARK: - Transfer Delegate + + func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String) { } + + func tranferChange(status: String, metadata: tableMetadata, error: NKError) { } + // MARK: - Menu Item override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { - if #selector(pasteFilesMenu(_:)) == action { + + if #selector(pasteFilesMenu) == action { if !UIPasteboard.general.items.isEmpty, !(metadataFolder?.e2eEncrypted ?? false) { return true } - } else if #selector(copyMenuFile(_:)) == action { + } else if #selector(copyMenuFile) == action { return true - } else if #selector(moveMenuFile(_:)) == action { + } else if #selector(moveMenuFile) == action { return true } @@ -1293,12 +1302,12 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // MARK: - DataSource - @objc func reloadDataSource() async { + @objc func reloadDataSource() { // get auto upload folder autoUploadFileName = NCManageDatabase.shared.getAccountAutoUploadFileName() autoUploadDirectory = NCManageDatabase.shared.getAccountAutoUploadDirectory(urlBase: session.urlBase, userId: session.userId, account: session.account) - + // get layout for view layoutForView = NCManageDatabase.shared.getLayoutForView(account: session.account, key: layoutKey, serverUrl: serverUrl) // set GroupField for Grid @@ -1307,32 +1316,28 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } else { groupByField = "name" } - if isSearchingMode { isDirectoryEncrypted = false } else { isDirectoryEncrypted = NCUtilityFileSystem().isDirectoryE2EE(session: session, serverUrl: serverUrl) - if isRecommendationActived { - Task.detached { - await self.networking.createRecommendations(session: self.session, serverUrl: self.serverUrl, collectionView: self.collectionView) - } - } } - UIView.transition(with: self.collectionView, - duration: 0.20, - options: .transitionCrossDissolve, - animations: { self.collectionView.reloadData() }, - completion: nil) - - (self.navigationController as? NCMainNavigationController)?.updateRightMenu() + DispatchQueue.main.async { +// UIView.transition(with: self.collectionView, +// duration: 0.20, +// options: .transitionCrossDissolve, +// animations: { self.collectionView.reloadData() }, +// completion: nil) +// +// (self.navigationController as? NCMainNavigationController)?.setNavigationRightItems() self.refreshControl.endRefreshing() self.collectionView.reloadData() self.setNavigationRightItems() } } - func getServerData(forced: Bool = false) async { } + func getServerData() { + } @objc func networkSearch() { guard !networkSearchInProgress else { @@ -1341,68 +1346,45 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS guard !session.account.isEmpty, let literalSearch = literalSearch, !literalSearch.isEmpty else { - return + return self.refreshControl.endRefreshing() } - let capabilities = NCNetworking.shared.capabilities[session.account] ?? NKCapabilities.Capabilities() self.networkSearchInProgress = true self.dataSource.removeAll() - Task { - await self.reloadDataSource() - } - - if capabilities.serverVersionMajor >= global.nextcloudVersion20 { - self.networking.unifiedSearchFiles(literal: literalSearch, account: session.account) { task in - self.searchDataSourceTask = task - Task { - await self.reloadDataSource() - } - } providers: { account, searchProviders in + self.refreshControl.beginRefreshing() + self.reloadDataSource() + + if NCCapabilities.shared.getCapabilities(account: session.account).capabilityServerVersionMajor >= global.nextcloudVersion20 { + NCNetworking.shared.unifiedSearchFiles(literal: literalSearch, account: session.account) { task in + self.dataSourceTask = task + self.reloadDataSource() + } providers: { _, searchProviders in self.providers = searchProviders self.searchResults = [] - self.dataSource = NCCollectionViewDataSource(metadatas: [], - layoutForView: self.layoutForView, - providers: self.providers, - searchResults: self.searchResults, - account: account) + self.dataSource = NCCollectionViewDataSource(metadatas: [], layoutForView: self.layoutForView, providers: self.providers, searchResults: self.searchResults) } update: { _, _, searchResult, metadatas in guard let metadatas, !metadatas.isEmpty, self.isSearchingMode, let searchResult else { return } - self.networking.unifiedSearchQueue.addOperation(NCCollectionViewUnifiedSearch(collectionViewCommon: self, metadatas: metadatas, searchResult: searchResult)) + NCNetworking.shared.unifiedSearchQueue.addOperation(NCCollectionViewUnifiedSearch(collectionViewCommon: self, metadatas: metadatas, searchResult: searchResult)) } completion: { _, _ in - Task { - await self.reloadDataSource() - } + self.refreshControl.endRefreshing() + self.reloadDataSource() self.networkSearchInProgress = false } } else { - self.networking.searchFiles(literal: literalSearch, account: session.account) { task in - self.searchDataSourceTask = task - Task { - await self.reloadDataSource() - } + NCNetworking.shared.searchFiles(literal: literalSearch, account: session.account) { task in + self.dataSourceTask = task + self.reloadDataSource() } completion: { metadatasSearch, error in - Task { - guard let metadatasSearch, - error == .success, - self.isSearchingMode - else { - self.networkSearchInProgress = false - await self.reloadDataSource() - return - } - let ocId = metadatasSearch.map { $0.ocId } - let metadatas = await self.database.getMetadatasAsync(predicate: NSPredicate(format: "ocId IN %@", ocId), - withLayout: self.layoutForView, - withAccount: self.session.account) - - self.dataSource = NCCollectionViewDataSource(metadatas: metadatas, - layoutForView: self.layoutForView, - providers: self.providers, - searchResults: self.searchResults, - account: self.session.account) - self.networkSearchInProgress = false - await self.reloadDataSource() + DispatchQueue.main.async { + self.refreshControl.endRefreshing() + self.reloadDataSource() } + guard let metadatasSearch, error == .success, self.isSearchingMode else { return } + let ocId = metadatasSearch.map { $0.ocId } + let metadatas = self.database.getResultsMetadatasPredicate(NSPredicate(format: "ocId IN %@", ocId), layoutForView: self.layoutForView) + + self.dataSource = NCCollectionViewDataSource(metadatas: metadatas, layoutForView: self.layoutForView, providers: self.providers, searchResults: self.searchResults) + self.networkSearchInProgress = false } } } @@ -1413,11 +1395,9 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS metadataForSection.unifiedSearchInProgress = true self.collectionView?.reloadData() - self.networking.unifiedSearchFilesProvider(id: lastSearchResult.id, term: term, limit: 5, cursor: cursor, account: session.account) { task in - self.searchDataSourceTask = task - Task { - await self.reloadDataSource() - } + NCNetworking.shared.unifiedSearchFilesProvider(id: lastSearchResult.id, term: term, limit: 5, cursor: cursor, account: session.account) { task in + self.dataSourceTask = task + self.reloadDataSource() } completion: { _, searchResult, metadatas, error in if error != .success { Task {@MainActor in @@ -1442,15 +1422,8 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // MARK: - Push metadata func pushMetadata(_ metadata: tableMetadata) { - guard let navigationCollectionViewCommon = self.controller?.navigationCollectionViewCommon else { - return - } - let serverUrlPush = utilityFileSystem.createServerUrl(serverUrl: metadata.serverUrl, fileName: metadata.fileName) - - // Set Last Opening Date - Task { - await database.setDirectoryLastOpeningDateAsync(ocId: metadata.ocId) - } + guard let navigationCollectionViewCommon = self.controller?.navigationCollectionViewCommon else { return } + let serverUrlPush = utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: metadata.fileName) if let viewController = navigationCollectionViewCommon.first(where: { $0.navigationController == self.navigationController && $0.serverUrl == serverUrlPush})?.viewController, viewController.isViewLoaded { navigationController?.pushViewController(viewController, animated: true) @@ -1466,38 +1439,55 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } } } - + func pushViewController(viewController: UIViewController) { if pushed { return } pushed = true navigationController?.pushViewController(viewController, animated: true) } - + // MARK: - Header size -// func getHeaderHeight(section: Int) -> (heightHeaderRichWorkspace: CGFloat, -// heightHeaderRecommendations: CGFloat, -// heightHeaderSection: CGFloat) { -// var heightHeaderRichWorkspace: CGFloat = 0 -// var heightHeaderRecommendations: CGFloat = 0 -// var heightHeaderSection: CGFloat = 0 +// func getHeaderHeight(section: Int) -> (heightHeaderCommands: CGFloat, heightHeaderRichWorkspace: CGFloat, heightHeaderSection: CGFloat) { +// var headerRichWorkspace: CGFloat = 0 // -// if showDescription, -// !isSearchingMode, -// let richWorkspaceText = self.richWorkspaceText, -// !richWorkspaceText.trimmingCharacters(in: .whitespaces).isEmpty { -// heightHeaderRichWorkspace = UIScreen.main.bounds.size.height / 6 -// } +// func getHeaderHeight() -> CGFloat { +// var size: CGFloat = 0 // -// if isRecommendationActived, -// !isSearchingMode, -// NCKeychain().showRecommendedFiles, -// !self.database.getRecommendedFiles(account: self.session.account).isEmpty { -// heightHeaderRecommendations = self.heightHeaderRecommendations -// heightHeaderSection = self.heightHeaderSection +// if isHeaderMenuTransferViewEnabled() != nil { +// if !isSearchingMode { +// size += global.heightHeaderTransfer +// } +// } +// if headerMenuButtonsView { +// size += NCGlobal.shared.heightButtonsView +// } +// return size // } // +//// func getHeaderHeight(section: Int) -> (heightHeaderRichWorkspace: CGFloat, +//// heightHeaderRecommendations: CGFloat, +//// heightHeaderSection: CGFloat) { +//// var heightHeaderRichWorkspace: CGFloat = 0 +//// var heightHeaderRecommendations: CGFloat = 0 +//// var heightHeaderSection: CGFloat = 0 +//// +//// if showDescription, +//// !isSearchingMode, +//// let richWorkspaceText = self.richWorkspaceText, +//// !richWorkspaceText.trimmingCharacters(in: .whitespaces).isEmpty { +//// heightHeaderRichWorkspace = UIScreen.main.bounds.size.height / 6 +//// } +//// +//// if isRecommendationActived, +//// !isSearchingMode, +//// NCKeychain().showRecommendedFiles, +//// !self.database.getRecommendedFiles(account: self.session.account).isEmpty { +//// heightHeaderRecommendations = self.heightHeaderRecommendations +//// heightHeaderSection = self.heightHeaderSection +//// } +// // if isSearchingMode || layoutForView?.groupBy != "none" || self.dataSource.numberOfSections() > 1 { // if section == 0 { // return (heightHeaderRichWorkspace, heightHeaderRecommendations, self.heightHeaderSection) @@ -1506,6 +1496,15 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // } // } else { // return (heightHeaderRichWorkspace, heightHeaderRecommendations, heightHeaderSection) +//// +//// if isSearchingMode || layoutForView?.groupBy != "none" || self.dataSource.numberOfSections() > 1 { +//// if section == 0 { +//// return (heightHeaderRichWorkspace, heightHeaderRecommendations, self.heightHeaderSection) +//// } else { +//// return (0, 0, self.heightHeaderSection) +//// } +//// } else { +//// return (heightHeaderRichWorkspace, heightHeaderRecommendations, heightHeaderSection) // } // } @@ -1529,8 +1528,8 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } else if isEditMode || (isLandscape && isIphone) { return CGSize.zero } else { - let (heightHeaderRichWorkspace, heightHeaderRecommendations, heightHeaderSection) = getHeaderHeight(section: section) - height = heightHeaderRichWorkspace + heightHeaderRecommendations + heightHeaderSection + let (heightHeaderCommands, heightHeaderRichWorkspace, heightHeaderSection) = getHeaderHeight(section: section) + height = heightHeaderCommands + heightHeaderRichWorkspace + heightHeaderSection } return CGSize(width: collectionView.frame.width, height: height) @@ -1539,25 +1538,21 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // MARK: - Footer size func sizeForFooterInSection(section: Int) -> CGSize { - guard let controller else { - return CGSize.zero - } let sections = dataSource.numberOfSections() - let bottomAreaInsets: CGFloat = controller.tabBar.safeAreaInsets.bottom == 0 ? 34 : 0 - let height = controller.tabBar.frame.height + bottomAreaInsets - - if isEditMode { - return CGSize(width: collectionView.frame.width, height: 90 + height) - } - - if isSearchingMode { - return CGSize(width: collectionView.frame.width, height: 50) - } + let metadataForSection = self.dataSource.getMetadataForSection(section) + let isPaginated = metadataForSection?.lastSearchResult?.isPaginated ?? false + let metadatasCount: Int = metadataForSection?.lastSearchResult?.entries.count ?? 0 + var size = CGSize(width: collectionView.frame.width, height: 0) if section == sections - 1 { - return CGSize(width: collectionView.frame.width, height: height) + size.height += 85 } else { - return CGSize(width: collectionView.frame.width, height: 0) + size.height += 1 + } + + if isSearchingMode && isPaginated && metadatasCount > 0 { + size.height += 30 } + return size } } From 333a86da9aec0b4f0857dcb451bd15181c698594 Mon Sep 17 00:00:00 2001 From: TSI-amrutwaghmare <96108296+TSI-amrutwaghmare@users.noreply.github.com> Date: Wed, 18 Oct 2023 14:58:19 +0530 Subject: [PATCH 07/11] NMC 1984 - Privacy policy customisation added --- .../NCCollectionViewCommon.swift | 24 +++++++++++++++++++ .../PrivacySettingsViewController.swift | 11 +-------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index cb54bb2153..bbec1fb300 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -254,6 +254,17 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS let appVersion = Bundle.main.infoDictionary?["CFBundleInfoDictionaryVersion"] as? String UserDefaults.standard.set(appVersion, forKey: "CurrentAppVersion") } + + registerForTraitChanges([UITraitUserInterfaceStyle.self]) { [weak self] (view: NCCollectionViewCommon, _) in + guard let self else { return } + + self.sectionFirstHeader?.setRichWorkspaceColor(style: view.traitCollection.userInterfaceStyle) + } + + NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: self.global.notificationCenterChangeTheming), object: nil, queue: .main) { [weak self] _ in + guard let self else { return } + self.collectionView.reloadData() + } NotificationCenter.default.addObserver(self, selector: #selector(changeTheming(_:)), name: NSNotification.Name(rawValue: global.notificationCenterChangeTheming), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(reloadDataSource(_:)), name: NSNotification.Name(rawValue: global.notificationCenterReloadDataSource), object: nil) @@ -406,6 +417,19 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS newViewController.modalPresentationStyle = .fullScreen self.present(newViewController, animated: true, completion: nil) } + + func isApplicationUpdated() -> Bool{ + let appVersion = Bundle.main.infoDictionary?["CFBundleInfoDictionaryVersion"] as? String ?? "" + let currentVersion = UserDefaults.standard.string(forKey: "CurrentAppVersion") + return currentVersion != appVersion + } + + func redirectToPrivacyViewController(){ + let storyBoard: UIStoryboard = UIStoryboard(name: "NCSettings", bundle: nil) + let newViewController = storyBoard.instantiateViewController(withIdentifier: "privacySettingsNavigation") as! UINavigationController + newViewController.modalPresentationStyle = .fullScreen + self.present(newViewController, animated: true, completion: nil) + } func presentationControllerDidDismiss( _ presentationController: UIPresentationController) { let viewController = presentationController.presentedViewController diff --git a/iOSClient/Settings/PrivacySettingsViewController.swift b/iOSClient/Settings/PrivacySettingsViewController.swift index b7883ad800..685bcef50a 100644 --- a/iOSClient/Settings/PrivacySettingsViewController.swift +++ b/iOSClient/Settings/PrivacySettingsViewController.swift @@ -12,7 +12,7 @@ import AdSupport class PrivacySettingsViewController: XLFormViewController{ - @objc public var isShowSettingsButton: Bool = false + @objc public var isShowSettingsButton: Bool = false override func viewDidLoad() { super.viewDidLoad() @@ -34,13 +34,9 @@ class PrivacySettingsViewController: XLFormViewController{ tableView.reloadData() initializeForm() } - - //MARK: XLForm - func initializeForm() { - let form : XLFormDescriptor = XLFormDescriptor() as XLFormDescriptor form.rowNavigationOptions = XLFormRowNavigationOptions.stopDisableRow @@ -57,7 +53,6 @@ class PrivacySettingsViewController: XLFormViewController{ section.footerTitle = NSLocalizedString("_privacy_settings_help_text_", comment: "") form.addFormSection(section) - //custom cell section = XLFormSectionDescriptor.formSection(withTitle: "") section.footerTitle = NSLocalizedString("_required_data_collection_help_text_", comment: "") @@ -66,7 +61,6 @@ class PrivacySettingsViewController: XLFormViewController{ XLFormViewController.cellClassesForRowDescriptorTypes()["RequiredDataCollectionCustomCellType"] = RequiredDataCollectionSwitch.self - row = XLFormRowDescriptor(tag: "ButtonDestinationFolder", rowType: "RequiredDataCollectionCustomCellType", title: "") row.cellConfig["requiredDataCollectionSwitchControl.onTintColor"] = NCBrandColor.shared.brand row.cellConfig["cellLabel.textAlignment"] = NSTextAlignment.left.rawValue @@ -81,7 +75,6 @@ class PrivacySettingsViewController: XLFormViewController{ XLFormViewController.cellClassesForRowDescriptorTypes()["AnalysisDataCollectionCustomCellType"] = AnalysisDataCollectionSwitch.self - row = XLFormRowDescriptor(tag: "AnalysisDataCollectionSwitch", rowType: "AnalysisDataCollectionCustomCellType", title: "") row.cellConfig["analysisDataCollectionSwitchControl.onTintColor"] = NCBrandColor.shared.brand row.cellConfig["cellLabel.textAlignment"] = NSTextAlignment.left.rawValue @@ -96,13 +89,11 @@ class PrivacySettingsViewController: XLFormViewController{ section.addFormRow(row) - XLFormViewController.cellClassesForRowDescriptorTypes()["SaveSettingsButton"] = SaveSettingsCustomButtonCell.self section = XLFormSectionDescriptor.formSection(withTitle: "") form.addFormSection(section) - row = XLFormRowDescriptor(tag: "SaveSettingsButton", rowType: "SaveSettingsButton", title: "") row.cellConfig["backgroundColor"] = UIColor.clear From 10ae17c2bcfbe4b13b3a4cca0770082840e10f89 Mon Sep 17 00:00:00 2001 From: TSI-amrutwaghmare <96108296+TSI-amrutwaghmare@users.noreply.github.com> Date: Tue, 26 Dec 2023 13:29:37 +0530 Subject: [PATCH 08/11] NMC 1984 - Privacy setting view controller added --- iOSClient/Settings/PrivacySettingsViewController.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/iOSClient/Settings/PrivacySettingsViewController.swift b/iOSClient/Settings/PrivacySettingsViewController.swift index 685bcef50a..5e56a6bd8d 100644 --- a/iOSClient/Settings/PrivacySettingsViewController.swift +++ b/iOSClient/Settings/PrivacySettingsViewController.swift @@ -34,9 +34,11 @@ class PrivacySettingsViewController: XLFormViewController{ tableView.reloadData() initializeForm() } - + //MARK: XLForm + func initializeForm() { + let form : XLFormDescriptor = XLFormDescriptor() as XLFormDescriptor form.rowNavigationOptions = XLFormRowNavigationOptions.stopDisableRow From db8f30a861e458b1da12328cb3151ba38acad995 Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Wed, 9 Apr 2025 14:10:39 +0530 Subject: [PATCH 09/11] NMC 1984 - Privacy policy customisation changes added --- .../NCCollectionViewCommon.swift | 484 +++++++++++++++--- 1 file changed, 421 insertions(+), 63 deletions(-) diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index bbec1fb300..43f6a929d0 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -67,7 +67,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS var titleCurrentFolder = "" var titlePreviusFolder: String? var enableSearchBar: Bool = false - + let maxImageGrid: CGFloat = 7 var groupByField = "name" var emptyImageName: String? @@ -418,13 +418,13 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS self.present(newViewController, animated: true, completion: nil) } - func isApplicationUpdated() -> Bool{ + func isApplicationUpdated() -> Bool { let appVersion = Bundle.main.infoDictionary?["CFBundleInfoDictionaryVersion"] as? String ?? "" let currentVersion = UserDefaults.standard.string(forKey: "CurrentAppVersion") return currentVersion != appVersion } - func redirectToPrivacyViewController(){ + func redirectToPrivacyViewController() { let storyBoard: UIStoryboard = UIStoryboard(name: "NCSettings", bundle: nil) let newViewController = storyBoard.instantiateViewController(withIdentifier: "privacySettingsNavigation") as! UINavigationController newViewController.modalPresentationStyle = .fullScreen @@ -959,6 +959,399 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // reloadDataSource() } + // MARK: - Layout + + func changeLayout(layoutForView: NCDBLayoutForView) { + let homeServer = utilityFileSystem.getHomeServer(urlBase: session.urlBase, userId: session.userId) + let numFoldersLayoutsForView = self.database.getLayoutsForView(keyStore: layoutForView.keyStore)?.count ?? 1 + + func changeLayout(withSubFolders: Bool) { + if self.layoutForView?.layout == layoutForView.layout { + self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView, withSubFolders: withSubFolders) + Task { + await self.reloadDataSource() + } + return + } + + self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView, withSubFolders: withSubFolders) + layoutForView.layout = layoutForView.layout + self.layoutType = layoutForView.layout + + collectionView.reloadData() + + switch layoutForView.layout { + case global.layoutList: + self.collectionView.setCollectionViewLayout(self.listLayout, animated: true) + case global.layoutGrid: + self.collectionView.setCollectionViewLayout(self.gridLayout, animated: true) + case global.layoutPhotoSquare, global.layoutPhotoRatio: + self.collectionView.setCollectionViewLayout(self.mediaLayout, animated: true) + default: + break + } + + self.collectionView.collectionViewLayout.invalidateLayout() + + Task { + await (self.navigationController as? NCMainNavigationController)?.updateRightMenu() + } + } + + if serverUrl == homeServer || numFoldersLayoutsForView == 1 { + changeLayout(withSubFolders: false) + } else { + let alertController = UIAlertController(title: NSLocalizedString("_propagate_layout_", comment: ""), message: nil, preferredStyle: .alert) + + alertController.addAction(UIAlertAction(title: NSLocalizedString("_yes_", comment: ""), style: .default, handler: { _ in + changeLayout(withSubFolders: true) + })) + alertController.addAction(UIAlertAction(title: NSLocalizedString("_no_", comment: ""), style: .default, handler: { _ in + changeLayout(withSubFolders: false) + })) + + self.present(alertController, animated: true) + } + self.collectionView.collectionViewLayout.invalidateLayout() + +// (self.navigationController as? NCMainNavigationController)?.setNavigationRightItems() + } + + @objc func reloadDataSource(_ notification: NSNotification) { + if let userInfo = notification.userInfo as? NSDictionary { + if let serverUrl = userInfo["serverUrl"] as? String { + if serverUrl != self.serverUrl { + return + } + } + + if let clearDataSource = userInfo["clearDataSource"] as? Bool, clearDataSource { + self.dataSource.removeAll() + } + } + + reloadDataSource() + } + + @objc func getServerData(_ notification: NSNotification) { + if let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String { + if serverUrl != self.serverUrl { + return + } + } + + getServerData() + } + + @objc func reloadHeader(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let account = userInfo["account"] as? String, + account == session.account + else { return } + + self.collectionView.reloadData() + } + + @objc func changeStatusFolderE2EE(_ notification: NSNotification) { + reloadDataSource() + } + + @objc func closeRichWorkspaceWebView() { + reloadDataSource() + } + + @objc func deleteFile(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let error = userInfo["error"] as? NKError else { return } + + if error == .success { + if isSearchingMode { + return networkSearch() + } + + if isRecommendationActived { + Task.detached { + await NCNetworking.shared.createRecommendations(session: self.session) + } + } + } else { + NCContentPresenter().showError(error: error) + } + + reloadDataSource() + } + + @objc func copyMoveFile(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String, + let account = userInfo["account"] as? String, + account == session.account else { return } + + if isSearchingMode { + return networkSearch() + } + + if isRecommendationActived { + Task.detached { + await NCNetworking.shared.createRecommendations(session: self.session) + } + } + + if serverUrl == self.serverUrl { + reloadDataSource() + } + } + + @objc func renameFile(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let account = userInfo["account"] as? String, + let serverUrl = userInfo["serverUrl"] as? String, + let error = userInfo["error"] as? NKError, + account == session.account + else { return } + + if error == .success { + if isSearchingMode { + return networkSearch() + } + + if isRecommendationActived { + Task.detached { + await NCNetworking.shared.createRecommendations(session: self.session) + } + } + } + + if serverUrl == self.serverUrl { + if error != .success { + NCContentPresenter().showError(error: error) + } + reloadDataSource() + } else { + collectionView.reloadData() + } + } + + @objc func createFolder(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let ocId = userInfo["ocId"] as? String, + let account = userInfo["account"] as? String, + account == session.account, + let withPush = userInfo["withPush"] as? Bool, + let metadata = database.getMetadataFromOcId(ocId) + else { return } + + if isSearchingMode { + return networkSearch() + } + + if metadata.serverUrl + "/" + metadata.fileName == self.serverUrl { + reloadDataSource() + } else if withPush, metadata.serverUrl == self.serverUrl { + reloadDataSource() + if let sceneIdentifier = userInfo["sceneIdentifier"] as? String { + if sceneIdentifier == controller?.sceneIdentifier { + pushMetadata(metadata) + } + } else { + pushMetadata(metadata) + } + } + } + + @objc func favoriteFile(_ notification: NSNotification) { + if isSearchingMode { + return networkSearch() + } + + if self is NCFavorite { + return reloadDataSource() + } + + guard let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String, + serverUrl == self.serverUrl + else { return } + + reloadDataSource() + } + + @objc func downloadStartFile(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String, + let account = userInfo["account"] as? String + else { return } + + if account == self.session.account, serverUrl == self.serverUrl { + reloadDataSource() + } else { + collectionView?.reloadData() + } + } + + @objc func downloadedFile(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String, + let account = userInfo["account"] as? String + else { return } + + if account == self.session.account, serverUrl == self.serverUrl { + reloadDataSource() + } else { + collectionView?.reloadData() + } + } + + @objc func downloadCancelFile(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String, + let account = userInfo["account"] as? String + else { return } + + if account == self.session.account, serverUrl == self.serverUrl { + reloadDataSource() + } else { + collectionView?.reloadData() + } + } + + @objc func uploadStartFile(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let ocId = userInfo["ocId"] as? String, + let serverUrl = userInfo["serverUrl"] as? String, + let account = userInfo["account"] as? String, + !isSearchingMode, + let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) + else { return } + + // Header view trasfer + if metadata.isTransferInForeground { + NCNetworking.shared.transferInForegorund = NCNetworking.TransferInForegorund(ocId: ocId, progress: 0) + DispatchQueue.main.async { self.collectionView?.reloadData() } + } + + if account == self.session.account, serverUrl == self.serverUrl { + reloadDataSource() + } else { + collectionView?.reloadData() + } + } + + @objc func uploadedFile(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String, + let account = userInfo["account"] as? String + else { return } + + if account == self.session.account, serverUrl == self.serverUrl { + reloadDataSource() + } else { + collectionView?.reloadData() + } + } + + @objc func uploadedLivePhoto(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String, + let account = userInfo["account"] as? String + else { return } + + if account == self.session.account, serverUrl == self.serverUrl { + reloadDataSource() + } else { + collectionView?.reloadData() + } + } + + @objc func uploadCancelFile(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let serverUrl = userInfo["serverUrl"] as? String, + let account = userInfo["account"] as? String + else { return } + + if account == self.session.account, serverUrl == self.serverUrl { + reloadDataSource() + } else { + collectionView?.reloadData() + } + } + + @objc func updateShare(_ notification: NSNotification) { + if isSearchingMode { + networkSearch() + } else { + self.dataSource.removeAll() + getServerData() + } + } + + @objc func triggerProgressTask(_ notification: NSNotification) { + guard let userInfo = notification.userInfo as NSDictionary?, + let progressNumber = userInfo["progress"] as? NSNumber, + let totalBytes = userInfo["totalBytes"] as? Int64, + let totalBytesExpected = userInfo["totalBytesExpected"] as? Int64, + let ocId = userInfo["ocId"] as? String, + let ocIdTransfer = userInfo["ocIdTransfer"] as? String, + let session = userInfo["session"] as? String + else { return } + + let chunk: Int = userInfo["chunk"] as? Int ?? 0 + let e2eEncrypted: Bool = userInfo["e2eEncrypted"] as? Bool ?? false + + let transfer = NCTransferProgress.shared.append(NCTransferProgress.Transfer(ocId: ocId, ocIdTransfer: ocIdTransfer, session: session, chunk: chunk, e2eEncrypted: e2eEncrypted, progressNumber: progressNumber, totalBytes: totalBytes, totalBytesExpected: totalBytesExpected)) + + // HEADER +// if self.headerMenuTransferView, transfer.session.contains("upload") { +// self.sectionFirstHeader?.setViewTransfer(isHidden: false, progress: transfer.progressNumber.floatValue) +// self.sectionFirstHeaderEmptyData?.setViewTransfer(isHidden: false, progress: transfer.progressNumber.floatValue) +// } + + DispatchQueue.main.async { + if self.headerMenuTransferView && (chunk > 0 || e2eEncrypted) { + if NCNetworking.shared.transferInForegorund?.ocId == ocId { + NCNetworking.shared.transferInForegorund?.progress = progressNumber.floatValue + } else { + NCNetworking.shared.transferInForegorund = NCNetworking.TransferInForegorund(ocId: ocId, progress: progressNumber.floatValue) + self.collectionView.reloadData() + } + self.headerMenu?.progressTransfer.progress = transfer.progressNumber.floatValue + } else { + guard let indexPath = self.dataSource.getIndexPathMetadata(ocId: ocId).indexPath, + let cell = self.collectionView?.cellForItem(at: indexPath), + let cell = cell as? NCCellProtocol else { return } + if progressNumber.floatValue == 1 && !(cell is NCTransferCell) { + cell.fileProgressView?.isHidden = true + cell.fileProgressView?.progress = .zero + cell.setButtonMore(named: NCGlobal.shared.buttonMoreMore, image: NCImageCache.images.buttonMore) + if let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) { + cell.writeInfoDateSize(date: metadata.date, size: metadata.size) + } else { + cell.fileInfoLabel?.text = "" + cell.fileSubinfoLabel?.text = "" + } + } else { + cell.fileProgressView?.isHidden = false + cell.fileProgressView?.progress = progressNumber.floatValue + cell.setButtonMore(named: NCGlobal.shared.buttonMoreStop, image: NCImageCache.images.buttonStop) + let status = userInfo["status"] as? Int ?? NCGlobal.shared.metadataStatusNormal + if status == NCGlobal.shared.metadataStatusDownloading { + cell.fileInfoLabel?.text = self.utilityFileSystem.transformedSize(totalBytesExpected) + cell.fileSubinfoLabel?.text = self.infoLabelsSeparator + "↓ " + self.utilityFileSystem.transformedSize(totalBytes) + } else if status == NCGlobal.shared.metadataStatusUploading { + if totalBytes > 0 { + cell.fileInfoLabel?.text = self.utilityFileSystem.transformedSize(totalBytesExpected) + cell.fileSubinfoLabel?.text = self.infoLabelsSeparator + "↑ " + self.utilityFileSystem.transformedSize(totalBytes) + } else { + cell.fileInfoLabel?.text = self.utilityFileSystem.transformedSize(totalBytesExpected) + cell.fileSubinfoLabel?.text = self.infoLabelsSeparator + "↑ …" + } + } + } + } + } + } + // MARK: - Layout func setNavigationLeftItems() { @@ -1196,6 +1589,10 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS func longPressMoreGridItem(with ocId: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) { } + func tapButtonTransfer(_ sender: Any) { } + + func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { } + @objc func longPressCollecationView(_ gestureRecognizer: UILongPressGestureRecognizer) { openMenuItems(with: nil, gestureRecognizer: gestureRecognizer) } @@ -1331,7 +1728,6 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // get auto upload folder autoUploadFileName = NCManageDatabase.shared.getAccountAutoUploadFileName() autoUploadDirectory = NCManageDatabase.shared.getAccountAutoUploadDirectory(urlBase: session.urlBase, userId: session.userId, account: session.account) - // get layout for view layoutForView = NCManageDatabase.shared.getLayoutForView(account: session.account, key: layoutKey, serverUrl: serverUrl) // set GroupField for Grid @@ -1354,6 +1750,20 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // completion: nil) // // (self.navigationController as? NCMainNavigationController)?.setNavigationRightItems() + if isRecommendationActived { + Task.detached { + await self.networking.createRecommendations(session: self.session, serverUrl: self.serverUrl, collectionView: self.collectionView) + } + } + } + + UIView.transition(with: self.collectionView, + duration: 0.20, + options: .transitionCrossDissolve, + animations: { self.collectionView.reloadData() }, + completion: nil) + + (self.navigationController as? NCMainNavigationController)?.updateRightMenu() self.refreshControl.endRefreshing() self.collectionView.reloadData() self.setNavigationRightItems() @@ -1471,66 +1881,14 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS navigationController?.pushViewController(viewController, animated: true) } - // MARK: - Header size + func pushViewController(viewController: UIViewController) { + if pushed { return } -// func getHeaderHeight(section: Int) -> (heightHeaderCommands: CGFloat, heightHeaderRichWorkspace: CGFloat, heightHeaderSection: CGFloat) { -// var headerRichWorkspace: CGFloat = 0 -// -// func getHeaderHeight() -> CGFloat { -// var size: CGFloat = 0 -// -// if isHeaderMenuTransferViewEnabled() != nil { -// if !isSearchingMode { -// size += global.heightHeaderTransfer -// } -// } -// if headerMenuButtonsView { -// size += NCGlobal.shared.heightButtonsView -// } -// return size -// } -// -//// func getHeaderHeight(section: Int) -> (heightHeaderRichWorkspace: CGFloat, -//// heightHeaderRecommendations: CGFloat, -//// heightHeaderSection: CGFloat) { -//// var heightHeaderRichWorkspace: CGFloat = 0 -//// var heightHeaderRecommendations: CGFloat = 0 -//// var heightHeaderSection: CGFloat = 0 -//// -//// if showDescription, -//// !isSearchingMode, -//// let richWorkspaceText = self.richWorkspaceText, -//// !richWorkspaceText.trimmingCharacters(in: .whitespaces).isEmpty { -//// heightHeaderRichWorkspace = UIScreen.main.bounds.size.height / 6 -//// } -//// -//// if isRecommendationActived, -//// !isSearchingMode, -//// NCKeychain().showRecommendedFiles, -//// !self.database.getRecommendedFiles(account: self.session.account).isEmpty { -//// heightHeaderRecommendations = self.heightHeaderRecommendations -//// heightHeaderSection = self.heightHeaderSection -//// } -// -// if isSearchingMode || layoutForView?.groupBy != "none" || self.dataSource.numberOfSections() > 1 { -// if section == 0 { -// return (heightHeaderRichWorkspace, heightHeaderRecommendations, self.heightHeaderSection) -// } else { -// return (0, 0, self.heightHeaderSection) -// } -// } else { -// return (heightHeaderRichWorkspace, heightHeaderRecommendations, heightHeaderSection) -//// -//// if isSearchingMode || layoutForView?.groupBy != "none" || self.dataSource.numberOfSections() > 1 { -//// if section == 0 { -//// return (heightHeaderRichWorkspace, heightHeaderRecommendations, self.heightHeaderSection) -//// } else { -//// return (0, 0, self.heightHeaderSection) -//// } -//// } else { -//// return (heightHeaderRichWorkspace, heightHeaderRecommendations, heightHeaderSection) -// } -// } + pushed = true + navigationController?.pushViewController(viewController, animated: true) + } + + // MARK: - Header size func isHeaderMenuTransferViewEnabled() -> [tableMetadata]? { if headerMenuTransferView, From c202fa6b99b5fd712cf79fb1fd3f81596627884e Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Tue, 30 Sep 2025 22:00:01 +0530 Subject: [PATCH 10/11] NMC 1984 - Privacy policy customisation added --- .../NCCollectionViewCommon.swift | 139 +++++++----------- 1 file changed, 54 insertions(+), 85 deletions(-) diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index 43f6a929d0..e545e2e949 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -67,7 +67,6 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS var titleCurrentFolder = "" var titlePreviusFolder: String? var enableSearchBar: Bool = false - let maxImageGrid: CGFloat = 7 var groupByField = "name" var emptyImageName: String? @@ -411,19 +410,6 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS return currentVersion != appVersion } - func redirectToPrivacyViewController() { - let storyBoard: UIStoryboard = UIStoryboard(name: "NCSettings", bundle: nil) - let newViewController = storyBoard.instantiateViewController(withIdentifier: "privacySettingsNavigation") as! UINavigationController - newViewController.modalPresentationStyle = .fullScreen - self.present(newViewController, animated: true, completion: nil) - } - - func isApplicationUpdated() -> Bool { - let appVersion = Bundle.main.infoDictionary?["CFBundleInfoDictionaryVersion"] as? String ?? "" - let currentVersion = UserDefaults.standard.string(forKey: "CurrentAppVersion") - return currentVersion != appVersion - } - func redirectToPrivacyViewController() { let storyBoard: UIStoryboard = UIStoryboard(name: "NCSettings", bundle: nil) let newViewController = storyBoard.instantiateViewController(withIdentifier: "privacySettingsNavigation") as! UINavigationController @@ -708,7 +694,17 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS if serverUrl == self.serverUrl { reloadDataSource() + } + + @objc func reloadAvatar(_ notification: NSNotification) { + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + self.showTipAccounts() } + guard let userInfo = notification.userInfo as NSDictionary?, + let error = userInfo["error"] as? NKError, + error.errorCode != global.errorNotModified else { return } + /// Magentacloud branding changes hide user account button on left navigation bar + setNavigationLeftItems() } @objc func renameFile(_ notification: NSNotification) { @@ -965,53 +961,30 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS let homeServer = utilityFileSystem.getHomeServer(urlBase: session.urlBase, userId: session.userId) let numFoldersLayoutsForView = self.database.getLayoutsForView(keyStore: layoutForView.keyStore)?.count ?? 1 - func changeLayout(withSubFolders: Bool) { - if self.layoutForView?.layout == layoutForView.layout { - self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView, withSubFolders: withSubFolders) - Task { - await self.reloadDataSource() - } - return - } - - self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView, withSubFolders: withSubFolders) - layoutForView.layout = layoutForView.layout - self.layoutType = layoutForView.layout - - collectionView.reloadData() - - switch layoutForView.layout { - case global.layoutList: - self.collectionView.setCollectionViewLayout(self.listLayout, animated: true) - case global.layoutGrid: - self.collectionView.setCollectionViewLayout(self.gridLayout, animated: true) - case global.layoutPhotoSquare, global.layoutPhotoRatio: - self.collectionView.setCollectionViewLayout(self.mediaLayout, animated: true) - default: - break - } - self.collectionView.collectionViewLayout.invalidateLayout() - - Task { - await (self.navigationController as? NCMainNavigationController)?.updateRightMenu() - } + if self.layoutForView?.layout == layoutForView.layout { + self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView) + self.reloadDataSource() + return } - if serverUrl == homeServer || numFoldersLayoutsForView == 1 { - changeLayout(withSubFolders: false) - } else { - let alertController = UIAlertController(title: NSLocalizedString("_propagate_layout_", comment: ""), message: nil, preferredStyle: .alert) + self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView) + layoutForView.layout = layoutForView.layout + self.layoutType = layoutForView.layout - alertController.addAction(UIAlertAction(title: NSLocalizedString("_yes_", comment: ""), style: .default, handler: { _ in - changeLayout(withSubFolders: true) - })) - alertController.addAction(UIAlertAction(title: NSLocalizedString("_no_", comment: ""), style: .default, handler: { _ in - changeLayout(withSubFolders: false) - })) + collectionView.reloadData() - self.present(alertController, animated: true) + switch layoutForView.layout { + case global.layoutList: + self.collectionView.setCollectionViewLayout(self.listLayout, animated: true) + case global.layoutGrid: + self.collectionView.setCollectionViewLayout(self.gridLayout, animated: true) + case global.layoutPhotoSquare, global.layoutPhotoRatio: + self.collectionView.setCollectionViewLayout(self.mediaLayout, animated: true) + default: + break } + self.collectionView.collectionViewLayout.invalidateLayout() // (self.navigationController as? NCMainNavigationController)?.setNavigationRightItems() @@ -1276,16 +1249,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS collectionView?.reloadData() } } - - @objc func updateShare(_ notification: NSNotification) { - if isSearchingMode { - networkSearch() - } else { - self.dataSource.removeAll() - getServerData() - } - } - + @objc func triggerProgressTask(_ notification: NSNotification) { guard let userInfo = notification.userInfo as NSDictionary?, let progressNumber = userInfo["progress"] as? NSNumber, @@ -1300,14 +1264,8 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS let e2eEncrypted: Bool = userInfo["e2eEncrypted"] as? Bool ?? false let transfer = NCTransferProgress.shared.append(NCTransferProgress.Transfer(ocId: ocId, ocIdTransfer: ocIdTransfer, session: session, chunk: chunk, e2eEncrypted: e2eEncrypted, progressNumber: progressNumber, totalBytes: totalBytes, totalBytesExpected: totalBytesExpected)) - - // HEADER -// if self.headerMenuTransferView, transfer.session.contains("upload") { -// self.sectionFirstHeader?.setViewTransfer(isHidden: false, progress: transfer.progressNumber.floatValue) -// self.sectionFirstHeaderEmptyData?.setViewTransfer(isHidden: false, progress: transfer.progressNumber.floatValue) -// } - DispatchQueue.main.async { +// DispatchQueue.main.async { if self.headerMenuTransferView && (chunk > 0 || e2eEncrypted) { if NCNetworking.shared.transferInForegorund?.ocId == ocId { NCNetworking.shared.transferInForegorund?.progress = progressNumber.floatValue @@ -1317,7 +1275,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } self.headerMenu?.progressTransfer.progress = transfer.progressNumber.floatValue } else { - guard let indexPath = self.dataSource.getIndexPathMetadata(ocId: ocId).indexPath, + guard let indexPath = self.dataSource.getIndexPathMetadata(ocId: ocId), let cell = self.collectionView?.cellForItem(at: indexPath), let cell = cell as? NCCellProtocol else { return } if progressNumber.floatValue == 1 && !(cell is NCTransferCell) { @@ -1349,8 +1307,22 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } } } +// } + } + + @objc func updateShare(_ notification: NSNotification) { + if isSearchingMode { + networkSearch() + } else { +// self.dataSource.removeAll() + getServerData() } } + + @objc func updateIcons() { + collectionView.reloadData() +// reloadDataSource() + } // MARK: - Layout @@ -1589,10 +1561,6 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS func longPressMoreGridItem(with ocId: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) { } - func tapButtonTransfer(_ sender: Any) { } - - func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { } - @objc func longPressCollecationView(_ gestureRecognizer: UILongPressGestureRecognizer) { openMenuItems(with: nil, gestureRecognizer: gestureRecognizer) } @@ -1757,13 +1725,14 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } } - UIView.transition(with: self.collectionView, - duration: 0.20, - options: .transitionCrossDissolve, - animations: { self.collectionView.reloadData() }, - completion: nil) - - (self.navigationController as? NCMainNavigationController)?.updateRightMenu() + DispatchQueue.main.async { +// UIView.transition(with: self.collectionView, +// duration: 0.20, +// options: .transitionCrossDissolve, +// animations: { self.collectionView.reloadData() }, +// completion: nil) +// +// (self.navigationController as? NCMainNavigationController)?.setNavigationRightItems() self.refreshControl.endRefreshing() self.collectionView.reloadData() self.setNavigationRightItems() @@ -1887,7 +1856,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS pushed = true navigationController?.pushViewController(viewController, animated: true) } - + // MARK: - Header size func isHeaderMenuTransferViewEnabled() -> [tableMetadata]? { From 489f6d6773f16c5bf4f9c5ac0a5c24eeaf04ab13 Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Wed, 17 Dec 2025 18:29:46 +0530 Subject: [PATCH 11/11] NMC 1984 - Privacy policy customisation added --- .../FileProviderData.swift | 2 +- .../Data/NCManageDatabase+LocalFile.swift | 40 +- .../Data/NCManageDatabase+Metadata.swift | 66 + ...mon+CollectionViewDelegateFlowLayout.swift | 43 - .../NCCollectionViewCommon+SyncMetadata.swift | 14 +- .../NCCollectionViewCommon.swift | 1524 +++++------------ .../NCBackgroundLocationUploadManager.swift | 32 +- iOSClient/NCGlobal.swift | 59 +- .../E2EE/NCNetworkingE2EEUpload.swift | 2 +- iOSClient/Networking/NCConfigServer.swift | 25 +- .../Networking/NCNetworking+Download.swift | 62 +- .../Networking/NCNetworking+LivePhoto.swift | 25 +- .../NCNetworking+Synchronization.swift | 93 + iOSClient/Networking/NCNetworking+Task.swift | 25 +- .../Networking/NCNetworking+Upload.swift | 21 +- .../Networking/NCNetworking+WebDAV.swift | 69 +- iOSClient/Networking/NCNetworking.swift | 34 +- .../Networking/NCNetworkingProcess.swift | 1 - iOSClient/Networking/NCService.swift | 25 +- .../AnalysisDataCollectionSwitch.swift | 1 + ...InitialPrivacySettingsViewController.swift | 1 + .../PrivacySettingsViewController.swift | 1 + .../RequiredDataCollectionSwitch.swift | 2 +- .../SaveSettingsCustomButtonCell.swift | 1 + .../en.lproj/Localizable.strings | 769 ++++++++- 25 files changed, 1728 insertions(+), 1209 deletions(-) create mode 100644 iOSClient/Networking/NCNetworking+Synchronization.swift diff --git a/File Provider Extension/FileProviderData.swift b/File Provider Extension/FileProviderData.swift index 9c36bb5bf0..a19d3197b2 100644 --- a/File Provider Extension/FileProviderData.swift +++ b/File Provider Extension/FileProviderData.swift @@ -32,7 +32,7 @@ class FileProviderData: NSObject { case workingSet } - // MARK: - + // MARK: - @discardableResult func setupAccount(domain: NSFileProviderDomain? = nil, diff --git a/iOSClient/Data/NCManageDatabase+LocalFile.swift b/iOSClient/Data/NCManageDatabase+LocalFile.swift index a4308b0ec3..c0c9205813 100644 --- a/iOSClient/Data/NCManageDatabase+LocalFile.swift +++ b/iOSClient/Data/NCManageDatabase+LocalFile.swift @@ -29,14 +29,17 @@ extension NCManageDatabase { // MARK: - Realm Write - /// Adds or updates multiple local file entries corresponding to the given metadata array. - /// Uses async Realm read + single write transaction. Assumes `tableLocalFile` has a primary key. /// - Parameters: - /// - metadatas: Array of `tableMetadata` to map into `tableLocalFile`. - /// - offline: Optional override for the `offline` flag applied to all items. - func addLocalFilesAsync(metadatas: [tableMetadata], offline: Bool? = nil) async { - guard !metadatas.isEmpty else { - return + /// - metadata: The `tableMetadata` containing file details. + /// - offline: Optional flag to mark the file as available offline. + /// - Returns: Nothing. Realm write is performed asynchronously. + func addLocalFileAsync(metadata: tableMetadata, offline: Bool? = nil) async { + // Read (non-blocking): safely detach from Realm thread + let existing: tableLocalFile? = performRealmRead { realm in + realm.objects(tableLocalFile.self) + .filter(NSPredicate(format: "ocId == %@", metadata.ocId)) + .first + .map { tableLocalFile(value: $0) } } // Extract ocIds for efficient lookup @@ -59,20 +62,19 @@ extension NCManageDatabase { // Reuse existing object or create a new one let local = existingMap[metadata.ocId] ?? tableLocalFile() - local.account = metadata.account - local.etag = metadata.etag - local.exifDate = NSDate() - local.exifLatitude = "-1" - local.exifLongitude = "-1" - local.ocId = metadata.ocId - local.fileName = metadata.fileName + 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 - if let offline { - local.offline = offline - } - - realm.add(local, update: .all) + if let offline { + addObject.offline = offline } + + realm.add(addObject, update: .all) } } diff --git a/iOSClient/Data/NCManageDatabase+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift index f03ef6cf8d..448c2a8143 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -139,10 +139,37 @@ extension tableMetadata { (fileNameView as NSString).deletingPathExtension } + var isRenameable: Bool { + if !NCMetadataPermissions.canRename(self) { + return false + } + 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 == NKTypeClassFile.image.rawValue { + return true + } + return false + } + var isSavebleInCameraRoll: Bool { return (classFile == NKTypeClassFile.image.rawValue && contentType != "image/svg+xml") || classFile == NKTypeClassFile.video.rawValue } + var isDocumentViewableOnly: Bool { + sharePermissionsCollaborationServices == NCPermissions().permissionReadShare && classFile == NKTypeClassFile.document.rawValue + } + var isAudioOrVideo: Bool { return classFile == NKTypeClassFile.audio.rawValue || classFile == NKTypeClassFile.video.rawValue } @@ -333,6 +360,17 @@ extension tableMetadata { return !lock || (lockOwner == user && lockOwnerType == 0) } + // Return if is sharable + func isSharable() -> Bool { + guard let capabilities = NCNetworking.shared.capabilities[account] else { + return false + } + if !capabilities.fileSharingApiEnabled || (capabilities.e2EEEnabled && isDirectoryE2EE), !e2eEncrypted { + return false + } + return !e2eEncrypted + } + /// Returns a detached (unmanaged) deep copy of the current `tableMetadata` object. /// /// - Note: The Realm `List` properties containing primitive types (e.g., `tags`, `shareType`) are copied automatically @@ -875,6 +913,34 @@ extension NCManageDatabase { .map { $0.detachedCopy() } } ?? [] } + + func getMediaMetadatas(predicate: NSPredicate, sorted: String? = nil, ascending: Bool = false) -> ThreadSafeArray? { + + do { + let realm = try Realm() + if let sorted { + var results: [tableMetadata] = [] + switch sorted {//NCPreferences().mediaSortDate { + case "date": + results = realm.objects(tableMetadata.self).filter(predicate).sorted { ($0.date as Date) > ($1.date as Date) } + case "creationDate": + results = realm.objects(tableMetadata.self).filter(predicate).sorted { ($0.creationDate as Date) > ($1.creationDate as Date) } + case "uploadDate": + results = realm.objects(tableMetadata.self).filter(predicate).sorted { ($0.uploadDate as Date) > ($1.uploadDate as Date) } + default: + let results = realm.objects(tableMetadata.self).filter(predicate) + return ThreadSafeArray(results.map { tableMetadata.init(value: $0) }) + } + return ThreadSafeArray(results.map { tableMetadata.init(value: $0) }) + } else { + let results = realm.objects(tableMetadata.self).filter(predicate) + return ThreadSafeArray(results.map { tableMetadata.init(value: $0) }) + } + } catch let error as NSError { +// NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)") + } + return nil + } func getMetadatas(predicate: NSPredicate, sortedByKeyPath: String, diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegateFlowLayout.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegateFlowLayout.swift index 42bbbd6038..06795639bc 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegateFlowLayout.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegateFlowLayout.swift @@ -13,47 +13,4 @@ extension NCCollectionViewCommon: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize { return sizeForFooterInSection(section: section) } - - func getHeaderHeight() -> CGFloat { - - var size: CGFloat = 0 - // transfer in progress - if headerMenuTransferView, - let metadata = NCManageDatabase.shared.getMetadataFromOcId(NCNetworking.shared.transferInForegorund?.ocId), - metadata.isTransferInForeground { - if !isSearchingMode { - size += NCGlobal.shared.heightHeaderTransfer - } - } else { - NCNetworking.shared.transferInForegorund = nil - } - - if headerMenuButtonsView { - size += NCGlobal.shared.heightButtonsView - } - - return size - } - - func getHeaderHeight(section: Int) -> (heightHeaderCommands: CGFloat, heightHeaderRichWorkspace: CGFloat, heightHeaderSection: CGFloat) { - - var headerRichWorkspace: CGFloat = 0 - - if let richWorkspaceText = richWorkspaceText, showDescription { - let trimmed = richWorkspaceText.trimmingCharacters(in: .whitespaces) - if !trimmed.isEmpty && !isSearchingMode { - headerRichWorkspace = UIScreen.main.bounds.size.height / 6 - } - } - - if isSearchingMode || layoutForView?.layout == NCGlobal.shared.layoutGrid || dataSource.numberOfSections() > 1 { - if section == 0 { - return (getHeaderHeight(), headerRichWorkspace, NCGlobal.shared.heightSection) - } else { - return (0, 0, NCGlobal.shared.heightSection) - } - } else { - return (getHeaderHeight(), headerRichWorkspace, 0) - } - } } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SyncMetadata.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SyncMetadata.swift index 365f38d923..d054be3f44 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SyncMetadata.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SyncMetadata.swift @@ -21,7 +21,7 @@ extension NCCollectionViewCommon { // If a sync task is already running, do not start a new one if let task = syncMetadatasTask, !task.isCancelled { - nkLog(tag: global.logTagSpeedUpSyncMetadata, emoji: .info, message: "Exit: Another sync is already running. Skipping this one.", consoleOnly: true) + nkLog(tag: global.logSpeedUpSyncMetadata, emoji: .info, message: "Exit: Another sync is already running. Skipping this one.", consoleOnly: true) return } @@ -45,9 +45,9 @@ extension NCCollectionViewCommon { func stopSyncMetadata() { if let task = syncMetadatasTask { if task.isCancelled { - nkLog(tag: global.logTagSpeedUpSyncMetadata, emoji: .stop, message: "Sync Metadata for \(self.serverUrl) was already cancelled.", consoleOnly: true) + nkLog(tag: global.logSpeedUpSyncMetadata, emoji: .stop, message: "Sync Metadata for \(self.serverUrl) was already cancelled.", consoleOnly: true) } else { - nkLog(tag: global.logTagSpeedUpSyncMetadata, emoji: .stop, message: "Stopping active Sync Metadata for \(self.serverUrl).", consoleOnly: true) + nkLog(tag: global.logSpeedUpSyncMetadata, emoji: .stop, message: "Stopping active Sync Metadata for \(self.serverUrl).", consoleOnly: true) } } @@ -74,7 +74,7 @@ extension NCCollectionViewCommon { return } let identifier = self.serverUrl + "_syncMetadata" - nkLog(tag: global.logTagSpeedUpSyncMetadata, emoji: .start, message: "Start Sync Metadata for \(self.serverUrl)") + nkLog(tag: global.logSpeedUpSyncMetadata, emoji: .start, message: "Start Sync Metadata for \(self.serverUrl)") // Always cancel and clear all tracked URLSessionTask on any exit path defer { @@ -85,7 +85,7 @@ extension NCCollectionViewCommon { // If a readFile for this serverUrl is already in-flight, do nothing if await networking.networkingTasks.isReading(identifier: identifier) { - nkLog(tag: global.logTagSpeedUpSyncMetadata, emoji: .debug, message: "ReadFile for this \(self.serverUrl) is already in-flight.", consoleOnly: true) + nkLog(tag: global.logSpeedUpSyncMetadata, emoji: .debug, message: "ReadFile for this \(self.serverUrl) is already in-flight.", consoleOnly: true) return } @@ -151,9 +151,9 @@ extension NCCollectionViewCommon { // If this folder failed, skip it but keep processing others if resultsReadFolder.error == .success { - nkLog(tag: global.logTagSpeedUpSyncMetadata, emoji: .network, message: "Read correctly: \(serverUrl)", consoleOnly: true) + nkLog(tag: global.logSpeedUpSyncMetadata, emoji: .network, message: "Read correctly: \(serverUrl)", consoleOnly: true) } else { - nkLog(tag: global.logTagSpeedUpSyncMetadata, emoji: .error, message: "Read failed for \(serverUrl) with error: \(resultsReadFolder.error.errorDescription)") + nkLog(tag: global.logSpeedUpSyncMetadata, emoji: .error, message: "Read failed for \(serverUrl) with error: \(resultsReadFolder.error.errorDescription)") return } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index e545e2e949..2595fb1348 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -11,7 +11,7 @@ import EasyTipView import LucidBanner import MoEngageInApps -class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate, NCListCellDelegate, NCGridCellDelegate, NCPhotoCellDelegate, NCSectionHeaderMenuDelegate, NCSectionFooterDelegate, NCSectionFirstHeaderEmptyDataDelegate, NCAccountSettingsModelDelegate, NCTransferDelegate, UIAdaptivePresentationControllerDelegate, UIContextMenuInteractionDelegate, NCEmptyDataSetDelegate { +class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate, NCListCellDelegate, NCGridCellDelegate, NCPhotoCellDelegate, NCSectionFirstHeaderDelegate, NCSectionFooterDelegate, NCSectionFirstHeaderEmptyDataDelegate, NCAccountSettingsModelDelegate, NCTransferDelegate, UIAdaptivePresentationControllerDelegate, UIContextMenuInteractionDelegate { @IBOutlet weak var collectionView: UICollectionView! @@ -21,6 +21,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS let utilityFileSystem = NCUtilityFileSystem() let imageCache = NCImageCache.shared var dataSource = NCCollectionViewDataSource() + let networking = NCNetworking.shared let appDelegate = (UIApplication.shared.delegate as? AppDelegate)! var pinchGesture: UIPinchGestureRecognizer = UIPinchGestureRecognizer() @@ -31,7 +32,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS var backgroundImageView = UIImageView() var serverUrl: String = "" var isEditMode = false - var isDirectoryEncrypted = false + var isDirectoryE2EE = false var fileSelect: [String] = [] var metadataFolder: tableMetadata? var richWorkspaceText: String? @@ -40,7 +41,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS var isSearchingMode: Bool = false var networkSearchInProgress: Bool = false var layoutForView: NCDBLayoutForView? - var dataSourceTask: URLSessionTask? + var searchDataSourceTask: URLSessionTask? var providers: [NKSearchProvider]? var searchResults: [NKSearchResult]? var listLayout = NCListLayout() @@ -56,22 +57,18 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // var headerMenu: NCSectionFirstHeader? var tipViewAccounts: EasyTipView? - var tipViewAutoUpload: EasyTipView? - var headerMenu: NCSectionHeaderMenu? - var headerMenuTransferView = false - var headerMenuButtonsView: Bool = true - var headerRichWorkspaceDisable: Bool = false - + var selectableDataSource: [RealmSwiftObject] { dataSource.getMetadataSourceForAllSections() } + // DECLARE var layoutKey = "" var titleCurrentFolder = "" var titlePreviusFolder: String? var enableSearchBar: Bool = false var groupByField = "name" + var headerRichWorkspaceDisable: Bool = false var emptyImageName: String? var emptyImageColors: [UIColor]? - var emptyImage: UIImage? var emptyTitle: String = "" var emptyDescription: String = "" @@ -90,14 +87,10 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS var numberOfColumns: Int = 0 var lastNumberOfColumns: Int = 0 - var isTransitioning: Bool = false - var selectableDataSource: [RealmSwiftObject] { dataSource.getMetadataSourceForAllSections() } - var pushed: Bool = false - var emptyDataSet: NCEmptyDataSet? - let heightHeaderRecommendations: CGFloat = 160 let heightHeaderSection: CGFloat = 30 + @MainActor var session: NCSession.Session { NCSession.shared.getSession(controller: tabBarController) } @@ -115,30 +108,29 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } var showDescription: Bool { - !headerRichWorkspaceDisable && NCKeychain().showDescription + !headerRichWorkspaceDisable && NCPreferences().showDescription } var isRecommendationActived: Bool { - self.serverUrl == self.utilityFileSystem.getHomeServer(session: self.session) && - NCCapabilities.shared.getCapabilities(account: self.session.account).capabilityRecommendations + let capabilities = NCNetworking.shared.capabilities[session.account] ?? NKCapabilities.Capabilities() + return self.serverUrl == self.utilityFileSystem.getHomeServer(session: self.session) && capabilities.recommendations } var infoLabelsSeparator: String { layoutForView?.layout == global.layoutList ? " - " : "" } + @MainActor var controller: NCMainTabBarController? { self.tabBarController as? NCMainTabBarController } - var defaultPredicate: NSPredicate { - let predicate = NSPredicate(format: "account == %@ AND serverUrl == %@ AND NOT (status IN %@) AND NOT (livePhotoFile != '' AND classFile == %@)", session.account, self.serverUrl, NCGlobal.shared.metadataStatusHideInView, NKCommon.TypeClassFile.video.rawValue) - return predicate + var mainNavigationController: NCMainNavigationController? { + self.navigationController as? NCMainNavigationController } - var personalFilesOnlyPredicate: NSPredicate { - let predicate = NSPredicate(format: "account == %@ AND serverUrl == %@ AND (ownerId == %@ || ownerId == '') AND mountType == '' AND NOT (status IN %@) AND NOT (livePhotoFile != '' AND classFile == %@)", session.account, self.serverUrl, session.userId, global.metadataStatusHideInView, NKCommon.TypeClassFile.video.rawValue) - return predicate + var sceneIdentifier: String { + (self.tabBarController as? NCMainTabBarController)?.sceneIdentifier ?? "" } var isNumberOfItemsInAllSectionsNull: Bool { @@ -173,18 +165,14 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS override func viewDidLoad() { super.viewDidLoad() - tabBarSelect = NCCollectionViewCommonSelectTabBar(controller: self.controller, delegate: self) self.navigationController?.presentationController?.delegate = self collectionView.alwaysBounceVertical = true collectionView.accessibilityIdentifier = "NCCollectionViewCommon" view.backgroundColor = .systemBackground collectionView.backgroundColor = .systemBackground - refreshControl.tintColor = .gray - - listLayout = NCListLayout() - gridLayout = NCGridLayout() - + refreshControl.tintColor = .clear + if enableSearchBar { searchController = UISearchController(searchResultsController: nil) searchController?.searchResultsUpdater = self @@ -193,8 +181,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS searchController?.searchBar.delegate = self searchController?.searchBar.autocapitalizationType = .none navigationItem.searchController = searchController - navigationItem.hidesSearchBarWhenScrolling = false - navigationItem.backBarButtonItem = UIBarButtonItem(title: NSLocalizedString("_back_", comment: ""), style: .plain, target: nil, action: nil) + navigationItem.hidesSearchBarWhenScrolling = true } // Cell @@ -204,8 +191,12 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS collectionView.register(UINib(nibName: "NCTransferCell", bundle: nil), forCellWithReuseIdentifier: "transferCell") // Header - collectionView.register(UINib(nibName: "NCSectionHeaderMenu", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "sectionHeaderMenu") + collectionView.register(UINib(nibName: "NCSectionFirstHeader", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "sectionFirstHeader") + collectionView.register(UINib(nibName: "NCSectionFirstHeader", bundle: nil), forSupplementaryViewOfKind: mediaSectionHeader, withReuseIdentifier: "sectionFirstHeader") collectionView.register(UINib(nibName: "NCSectionHeader", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "sectionHeader") + collectionView.register(UINib(nibName: "NCSectionHeader", bundle: nil), forSupplementaryViewOfKind: mediaSectionHeader, withReuseIdentifier: "sectionHeader") + collectionView.register(UINib(nibName: "NCSectionFirstHeaderEmptyData", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "sectionFirstHeaderEmptyData") + collectionView.register(UINib(nibName: "NCSectionFirstHeaderEmptyData", bundle: nil), forSupplementaryViewOfKind: mediaSectionHeader, withReuseIdentifier: "sectionFirstHeaderEmptyData") // Footer collectionView.register(UINib(nibName: "NCSectionFooter", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: "sectionFooter") @@ -213,21 +204,18 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS collectionView.refreshControl = refreshControl refreshControl.action(for: .valueChanged) { _ in - self.dataSource.removeAll() - self.getServerData() - if self.isRecommendationActived { - Task.detached { - await NCNetworking.shared.createRecommendations(session: self.session) - } - } - self.refreshControl.endRefreshing() - DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { - self.resetPlusButtonAlpha() + Task { @MainActor in + // Perform async server forced + await self.getServerData(forced: true) + + // Stop the refresh control after data is loaded + self.refreshControl.endRefreshing() + + // Wait 1.5 seconds before resetting the button alpha + try? await Task.sleep(nanoseconds: 1_500_000_000) + self.mainNavigationController?.resetPlusButtonAlpha() } } - - // Empty - emptyDataSet = NCEmptyDataSet(view: collectionView, offset: getHeaderHeight(), delegate: self) let longPressedGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPressCollecationView(_:))) longPressedGesture.minimumPressDuration = 0.5 @@ -245,7 +233,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS let dropInteraction = UIDropInteraction(delegate: self) self.navigationController?.navigationItem.leftBarButtonItems?.first?.customView?.addInteraction(dropInteraction) - + if(!UserDefaults.standard.bool(forKey: "isInitialPrivacySettingsShowed") || isApplicationUpdated()){ redirectToPrivacyViewController() @@ -265,10 +253,17 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS self.collectionView.reloadData() } - NotificationCenter.default.addObserver(self, selector: #selector(changeTheming(_:)), name: NSNotification.Name(rawValue: global.notificationCenterChangeTheming), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(reloadDataSource(_:)), name: NSNotification.Name(rawValue: global.notificationCenterReloadDataSource), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(getServerData(_:)), name: NSNotification.Name(rawValue: global.notificationCenterGetServerData), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(reloadHeader(_:)), name: NSNotification.Name(rawValue: global.notificationCenterReloadHeader), object: nil) + registerForTraitChanges([UITraitUserInterfaceStyle.self]) { [weak self] (view: NCCollectionViewCommon, _) in + guard let self else { return } + + self.sectionFirstHeader?.setRichWorkspaceColor(style: view.traitCollection.userInterfaceStyle) + } + + NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: self.global.notificationCenterChangeTheming), object: nil, queue: .main) { [weak self] _ in + guard let self else { return } + self.collectionView.reloadData() + } + NotificationCenter.default.addObserver(self, selector: #selector(updateIcons), name: NSNotification.Name(rawValue: global.notificationCenterUpdateIcons), object: nil) DispatchQueue.main.async { @@ -279,31 +274,23 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - AnalyticsHelper.shared.displayInAppNotification() - if titlePreviusFolder != nil { navigationController?.navigationBar.topItem?.title = titlePreviusFolder } navigationItem.title = titleCurrentFolder - navigationController?.setNavigationBarAppearance() - navigationController?.navigationBar.prefersLargeTitles = true - navigationController?.setNavigationBarHidden(false, animated: true) - - appDelegate.activeViewController = self - appDelegate.account = session.account - appDelegate.urlBase = session.urlBase - appDelegate.userId = session.userId - appDelegate.user = session.user - NCKeychain().setAccountName(account: session.account) + if tabBarSelect == nil { + tabBarSelect = NCCollectionViewCommonSelectTabBar(controller: self.controller, viewController: self, delegate: self) + } + isEditMode = false - /// Magentacloud branding changes hide user account button on left navigation bar -// setNavigationLeftItems() - setNavigationRightItems() + Task { + await (self.navigationController as? NCMainNavigationController)?.setNavigationLeftItems() + await (self.navigationController as? NCMainNavigationController)?.setNavigationRightItems() + } layoutForView = database.getLayoutForView(account: session.account, key: layoutKey, serverUrl: serverUrl) - gridLayout.column = CGFloat(layoutForView?.columnGrid ?? 3) if isLayoutList { collectionView?.collectionViewLayout = listLayout self.layoutType = global.layoutList @@ -324,97 +311,37 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - NCNetworking.shared.transferDelegate = self + Task { + await NCNetworking.shared.transferDispatcher.addDelegate(self) + } NotificationCenter.default.addObserver(self, selector: #selector(applicationWillResignActive(_:)), name: UIApplication.willResignActiveNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(closeRichWorkspaceWebView), name: NSNotification.Name(rawValue: global.notificationCenterCloseRichWorkspaceWebView), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(changeStatusFolderE2EE(_:)), name: NSNotification.Name(rawValue: global.notificationCenterChangeStatusFolderE2EE), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(changeLayout(_:)), name: NSNotification.Name(rawValue: global.notificationCenterChangeLayout), object: nil) - - NotificationCenter.default.addObserver(self, selector: #selector(deleteFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterDeleteFile), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(copyMoveFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterCopyMoveFile), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(renameFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterRenameFile), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(createFolder(_:)), name: NSNotification.Name(rawValue: global.notificationCenterCreateFolder), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(favoriteFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterFavoriteFile), object: nil) - - NotificationCenter.default.addObserver(self, selector: #selector(downloadStartFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterDownloadStartFile), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(downloadedFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterDownloadedFile), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(downloadCancelFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterDownloadCancelFile), object: nil) - - NotificationCenter.default.addObserver(self, selector: #selector(uploadStartFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterUploadStartFile), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(uploadedFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterUploadedFile), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(uploadedLivePhoto(_:)), name: NSNotification.Name(rawValue: global.notificationCenterUploadedLivePhoto), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(uploadCancelFile(_:)), name: NSNotification.Name(rawValue: global.notificationCenterUploadCancelFile), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(updateShare(_:)), name: NSNotification.Name(rawValue: global.notificationCenterUpdateShare), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(triggerProgressTask(_:)), name: NSNotification.Name(rawValue: global.notificationCenterProgressTask), object: nil) - - // FIXME: iPAD PDF landscape mode iOS 16 - DispatchQueue.main.async { - self.collectionView?.collectionViewLayout.invalidateLayout() - } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - NCNetworking.shared.cancelUnifiedSearchFiles() + self.networking.cancelUnifiedSearchFiles() dismissTip() - pushed = false - toggleSelect(isOn: false) + // Cancel Queue & Retrieves Properties - NCNetworking.shared.downloadThumbnailQueue.cancelAll() - NCNetworking.shared.unifiedSearchQueue.cancelAll() - dataSourceTask?.cancel() + self.networking.downloadThumbnailQueue.cancelAll() + self.networking.unifiedSearchQueue.cancelAll() + searchDataSourceTask?.cancel() } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) - NCNetworking.shared.transferDelegate = nil + Task { + await NCNetworking.shared.transferDispatcher.removeDelegate(self) + } NotificationCenter.default.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil) NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterCloseRichWorkspaceWebView), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterChangeStatusFolderE2EE), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterChangeLayout), object: nil) - - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterDeleteFile), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterCopyMoveFile), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterCopyFile), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterMoveFile), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterRenameFile), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterCreateFolder), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterFavoriteFile), object: nil) - - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterDeleteFile), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterMoveFile), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterCopyFile), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterCreateFolder), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterFavoriteFile), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterDownloadStartFile), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterDownloadedFile), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterDownloadCancelFile), object: nil) - - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterUploadStartFile), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterUploadedFile), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterUploadedLivePhoto), object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterUploadCancelFile), object: nil) - - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: global.notificationCenterUpdateShare), object: nil) - - dataSource.removeImageCache() - } - - func isApplicationUpdated() -> Bool { - let appVersion = Bundle.main.infoDictionary?["CFBundleInfoDictionaryVersion"] as? String ?? "" - let currentVersion = UserDefaults.standard.string(forKey: "CurrentAppVersion") - return currentVersion != appVersion - } - func redirectToPrivacyViewController() { - let storyBoard: UIStoryboard = UIStoryboard(name: "NCSettings", bundle: nil) - let newViewController = storyBoard.instantiateViewController(withIdentifier: "privacySettingsNavigation") as! UINavigationController - newViewController.modalPresentationStyle = .fullScreen - self.present(newViewController, animated: true, completion: nil) + removeImageCache(metadatas: self.dataSource.getMetadatas()) } func presentationControllerDidDismiss( _ presentationController: UIPresentationController) { @@ -441,12 +368,200 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS override var canBecomeFirstResponder: Bool { return true } + + @objc func updateIcons() { + collectionView.reloadData() + } - override func viewWillLayoutSubviews() { - super.viewWillLayoutSubviews() + func isApplicationUpdated() -> Bool { + let appVersion = Bundle.main.infoDictionary?["CFBundleInfoDictionaryVersion"] as? String ?? "" + let currentVersion = UserDefaults.standard.string(forKey: "CurrentAppVersion") + return currentVersion != appVersion + } + + func redirectToPrivacyViewController() { + let storyBoard: UIStoryboard = UIStoryboard(name: "NCSettings", bundle: nil) + let newViewController = storyBoard.instantiateViewController(withIdentifier: "privacySettingsNavigation") as? UINavigationController + newViewController?.modalPresentationStyle = .fullScreen + self.present(newViewController!, animated: true, completion: nil) + } + + // MARK: - Transfer Delegate - if let frame = tabBarController?.tabBar.frame { - tabBarSelect?.hostingController?.view.frame = frame + func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String) { } + + func transferChange(status: String, metadatasError: [tableMetadata: NKError]) { + switch status { + // DELETE + case self.global.networkingStatusDelete: + let errorForThisServer = metadatasError.first { entry in + let (key, value) = entry + return key.serverUrl == self.serverUrl && value != .success + }?.value + + let needLoadDataSource = metadatasError.contains { entry in + let (key, value) = entry + return key.serverUrl == self.serverUrl && value == .success + } + + if let error = errorForThisServer { + NCContentPresenter().showError(error: error) + } + + if self.isSearchingMode { + self.networkSearch() + } else if needLoadDataSource { + Task { + await self.reloadDataSource() + } + } else { + Task.detached { + if await self.isRecommendationActived() { + await self.networking.createRecommendations(session: self.session, serverUrl: self.serverUrl, collectionView: self.collectionView) + } + } + } + default: + break + } + } + + func transferChange(status: String, metadata: tableMetadata, error: NKError) { + guard session.account == metadata.account else { return } + + if error != .success { + NCContentPresenter().showError(error: error) + } + + DispatchQueue.main.async { + switch status { + // UPLOADED, UPLOADED LIVEPHOTO + case self.global.networkingStatusUploaded, self.global.networkingStatusUploadedLivePhoto: + self.debouncer.call { + if self.isSearchingMode { + self.networkSearch() + } else if self.serverUrl == metadata.serverUrl { + Task { + await self.reloadDataSource() + } + } + } + // DOWNLOAD + case self.global.networkingStatusDownloading: + Task { + if metadata.serverUrl == self.serverUrl { + await self.reloadDataSource() + } + } + case self.global.networkingStatusDownloaded: + Task { + if metadata.serverUrl == self.serverUrl { + await self.reloadDataSource() + } + } + case self.global.networkingStatusDownloadCancel: + Task { + if metadata.serverUrl == self.serverUrl { + await self.reloadDataSource() + } + } + // CREATE FOLDER + case self.global.networkingStatusCreateFolder: + if metadata.serverUrl == self.serverUrl, metadata.sessionSelector != self.global.selectorUploadAutoUpload { + self.pushMetadata(metadata) + } + // RENAME + case self.global.networkingStatusRename: + self.debouncer.call { + if self.isSearchingMode { + self.networkSearch() + } else if self.serverUrl == metadata.serverUrl { + Task { + await self.reloadDataSource() + } + } + } + // FAVORITE + case self.global.networkingStatusFavorite: + self.debouncer.call { + if self.isSearchingMode { + self.networkSearch() + } else if self is NCFavorite { + Task { + await self.reloadDataSource() + } + } else if self.serverUrl == metadata.serverUrl { + Task { + await self.reloadDataSource() + } + } + } + default: + break + } + } + } + + func transferReloadData(serverUrl: String?, status: Int?) { + self.debouncer.call { + if self.isSearchingMode { + guard status != self.global.metadataStatusWaitDelete, + status != self.global.metadataStatusWaitRename, + status != self.global.metadataStatusWaitMove, + status != self.global.metadataStatusWaitCopy, + status != self.global.metadataStatusWaitFavorite else { + return + } + self.networkSearch() + } else if ( self.serverUrl == serverUrl) || serverUrl == nil { + Task { + await self.reloadDataSource() + } + } + } + } + + func transferRequestData(serverUrl: String?) { + self.debouncer.call { + if self.isSearchingMode { + self.networkSearch() + } else if ( self.serverUrl == serverUrl) || serverUrl == nil { + Task { + await self.getServerData() + } + } + } + } + + func transferCopy(metadata: tableMetadata, destination: String, error: NKError) { + if error != .success { + NCContentPresenter().showError(error: error) + } + + if isSearchingMode { + return networkSearch() + } + + if metadata.serverUrl == self.serverUrl || destination == self.serverUrl { + Task { + await self.reloadDataSource() + } + } + } + + func transferMove(metadata: tableMetadata, destination: String, error: NKError) { + if error != .success { + NCContentPresenter().showError(error: error) + } + + if isSearchingMode { + return networkSearch() + } + + if metadata.serverUrl == self.serverUrl || destination == self.serverUrl { + Task { + await self.reloadDataSource() + } } } @@ -515,831 +630,120 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS await self.reloadDataSource() } default: - break - } - } - } - } - - func transferReloadData(serverUrl: String?, requestData: Bool, status: Int?) { - Task { - await self.debouncer.call { - if requestData { - if self.isSearchingMode { - self.networkSearch() - } else if ( self.serverUrl == serverUrl) || serverUrl == nil { - Task { - await self.getServerData() - } - } - } else { - if self.isSearchingMode { - guard status != self.global.metadataStatusWaitDelete, - status != self.global.metadataStatusWaitRename, - status != self.global.metadataStatusWaitMove, - status != self.global.metadataStatusWaitCopy, - status != self.global.metadataStatusWaitFavorite else { - return - } - self.networkSearch() - } else if ( self.serverUrl == serverUrl) || serverUrl == nil { - Task { - await self.reloadDataSource() - } - } - } - } - } - } - - // MARK: - NotificationCenter - - @objc func applicationWillResignActive(_ notification: NSNotification) { - self.resetPlusButtonAlpha() - self.refreshControl.endRefreshing() - } - - @objc func reloadAvatar(_ notification: NSNotification) { - DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { - self.showTipAccounts() - } - guard let userInfo = notification.userInfo as NSDictionary?, - let error = userInfo["error"] as? NKError, - error.errorCode != global.errorNotModified else { return } - /// Magentacloud branding changes hide user account button on left navigation bar - setNavigationLeftItems() - } - - @objc func changeTheming(_ notification: NSNotification) { - self.reloadDataSource() - } - - @objc func changeLayout(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let account = userInfo["account"] as? String, - let serverUrl = userInfo["serverUrl"] as? String, - let layoutForView = userInfo["layoutForView"] as? NCDBLayoutForView, - account == session.account, - serverUrl == self.serverUrl - else { return } - - if self.layoutForView?.layout == layoutForView.layout { - self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView) - self.reloadDataSource() - return - } - - self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView) - layoutForView.layout = layoutForView.layout - self.layoutType = layoutForView.layout - - collectionView.reloadData() - - switch layoutForView.layout { - case global.layoutList: - self.collectionView.setCollectionViewLayout(self.listLayout, animated: true) - case global.layoutGrid: - self.collectionView.setCollectionViewLayout(self.gridLayout, animated: true) - case global.layoutPhotoSquare, global.layoutPhotoRatio: - self.collectionView.setCollectionViewLayout(self.mediaLayout, animated: true) - default: - break - } - - self.collectionView.collectionViewLayout.invalidateLayout() - -// (self.navigationController as? NCMainNavigationController)?.setNavigationRightItems() - } - - @objc func reloadDataSource(_ notification: NSNotification) { - if let userInfo = notification.userInfo as? NSDictionary { - if let serverUrl = userInfo["serverUrl"] as? String { - if serverUrl != self.serverUrl { - return - } - } - - if let clearDataSource = userInfo["clearDataSource"] as? Bool, clearDataSource { - self.dataSource.removeAll() - } - } - - reloadDataSource() - } - - @objc func getServerData(_ notification: NSNotification) { - if let userInfo = notification.userInfo as NSDictionary?, - let serverUrl = userInfo["serverUrl"] as? String { - if serverUrl != self.serverUrl { - return - } - } - - getServerData() - } - - @objc func reloadHeader(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let account = userInfo["account"] as? String, - account == session.account - else { return } - - self.collectionView.reloadData() - } - - @objc func changeStatusFolderE2EE(_ notification: NSNotification) { - reloadDataSource() - } - - @objc func closeRichWorkspaceWebView() { - reloadDataSource() - } - - @objc func deleteFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let error = userInfo["error"] as? NKError else { return } - - if error == .success { - if isSearchingMode { - return networkSearch() - } - - if isRecommendationActived { - Task.detached { - await NCNetworking.shared.createRecommendations(session: self.session) - } - } - } else { - NCContentPresenter().showError(error: error) - } - - reloadDataSource() - } - - @objc func copyMoveFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let serverUrl = userInfo["serverUrl"] as? String, - let account = userInfo["account"] as? String, - account == session.account else { return } - - if isSearchingMode { - return networkSearch() - } - - if isRecommendationActived { - Task.detached { - await NCNetworking.shared.createRecommendations(session: self.session) - } - } - - if serverUrl == self.serverUrl { - reloadDataSource() - } - - @objc func reloadAvatar(_ notification: NSNotification) { - DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { - self.showTipAccounts() - } - guard let userInfo = notification.userInfo as NSDictionary?, - let error = userInfo["error"] as? NKError, - error.errorCode != global.errorNotModified else { return } - /// Magentacloud branding changes hide user account button on left navigation bar - setNavigationLeftItems() - } - - @objc func renameFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let account = userInfo["account"] as? String, - let serverUrl = userInfo["serverUrl"] as? String, - let error = userInfo["error"] as? NKError, - account == session.account - else { return } - - if error == .success { - if isSearchingMode { - return networkSearch() - } - - if isRecommendationActived { - Task.detached { - await NCNetworking.shared.createRecommendations(session: self.session) - } - } - } - - if serverUrl == self.serverUrl { - if error != .success { - NCContentPresenter().showError(error: error) - } - reloadDataSource() - } else { - collectionView.reloadData() - } - } - - @objc func createFolder(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let ocId = userInfo["ocId"] as? String, - let account = userInfo["account"] as? String, - account == session.account, - let withPush = userInfo["withPush"] as? Bool, - let metadata = database.getMetadataFromOcId(ocId) - else { return } - - if isSearchingMode { - return networkSearch() - } - - if metadata.serverUrl + "/" + metadata.fileName == self.serverUrl { - reloadDataSource() - } else if withPush, metadata.serverUrl == self.serverUrl { - reloadDataSource() - if let sceneIdentifier = userInfo["sceneIdentifier"] as? String { - if sceneIdentifier == controller?.sceneIdentifier { - pushMetadata(metadata) - } - } else { - pushMetadata(metadata) - } - } - } - - @objc func favoriteFile(_ notification: NSNotification) { - if isSearchingMode { - return networkSearch() - } - - if self is NCFavorite { - return reloadDataSource() - } - - guard let userInfo = notification.userInfo as NSDictionary?, - let serverUrl = userInfo["serverUrl"] as? String, - serverUrl == self.serverUrl - else { return } - - reloadDataSource() - } - - @objc func downloadStartFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let serverUrl = userInfo["serverUrl"] as? String, - let account = userInfo["account"] as? String - else { return } - - if account == self.session.account, serverUrl == self.serverUrl { - reloadDataSource() - } else { - collectionView?.reloadData() - } - } - - @objc func downloadedFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let serverUrl = userInfo["serverUrl"] as? String, - let account = userInfo["account"] as? String - else { return } - - if account == self.session.account, serverUrl == self.serverUrl { - reloadDataSource() - } else { - collectionView?.reloadData() - } - } - - @objc func downloadCancelFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let serverUrl = userInfo["serverUrl"] as? String, - let account = userInfo["account"] as? String - else { return } - - if account == self.session.account, serverUrl == self.serverUrl { - reloadDataSource() - } else { - collectionView?.reloadData() - } - } - - @objc func uploadStartFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let ocId = userInfo["ocId"] as? String, - let serverUrl = userInfo["serverUrl"] as? String, - let account = userInfo["account"] as? String, - !isSearchingMode, - let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) - else { return } - - // Header view trasfer - if metadata.isTransferInForeground { - NCNetworking.shared.transferInForegorund = NCNetworking.TransferInForegorund(ocId: ocId, progress: 0) - DispatchQueue.main.async { self.collectionView?.reloadData() } - } - - if account == self.session.account, serverUrl == self.serverUrl { - reloadDataSource() - } else { - collectionView?.reloadData() - } - } - - @objc func uploadedFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let serverUrl = userInfo["serverUrl"] as? String, - let account = userInfo["account"] as? String - else { return } - - if account == self.session.account, serverUrl == self.serverUrl { - reloadDataSource() - } else { - collectionView?.reloadData() - } - } - - @objc func uploadedLivePhoto(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let serverUrl = userInfo["serverUrl"] as? String, - let account = userInfo["account"] as? String - else { return } - - if account == self.session.account, serverUrl == self.serverUrl { - reloadDataSource() - } else { - collectionView?.reloadData() - } - } - - @objc func uploadCancelFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let serverUrl = userInfo["serverUrl"] as? String, - let account = userInfo["account"] as? String - else { return } - - if account == self.session.account, serverUrl == self.serverUrl { - reloadDataSource() - } else { - collectionView?.reloadData() - } - } - - @objc func triggerProgressTask(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let progressNumber = userInfo["progress"] as? NSNumber, - let totalBytes = userInfo["totalBytes"] as? Int64, - let totalBytesExpected = userInfo["totalBytesExpected"] as? Int64, - let ocId = userInfo["ocId"] as? String, - let ocIdTransfer = userInfo["ocIdTransfer"] as? String, - let session = userInfo["session"] as? String - else { return } - - let chunk: Int = userInfo["chunk"] as? Int ?? 0 - let e2eEncrypted: Bool = userInfo["e2eEncrypted"] as? Bool ?? false - - let transfer = NCTransferProgress.shared.append(NCTransferProgress.Transfer(ocId: ocId, ocIdTransfer: ocIdTransfer, session: session, chunk: chunk, e2eEncrypted: e2eEncrypted, progressNumber: progressNumber, totalBytes: totalBytes, totalBytesExpected: totalBytesExpected)) - -// DispatchQueue.main.async { - if self.headerMenuTransferView && (chunk > 0 || e2eEncrypted) { - if NCNetworking.shared.transferInForegorund?.ocId == ocId { - NCNetworking.shared.transferInForegorund?.progress = progressNumber.floatValue - } else { - NCNetworking.shared.transferInForegorund = NCNetworking.TransferInForegorund(ocId: ocId, progress: progressNumber.floatValue) - self.collectionView.reloadData() - } - self.headerMenu?.progressTransfer.progress = transfer.progressNumber.floatValue - } else { - guard let indexPath = self.dataSource.getIndexPathMetadata(ocId: ocId), - let cell = self.collectionView?.cellForItem(at: indexPath), - let cell = cell as? NCCellProtocol else { return } - if progressNumber.floatValue == 1 && !(cell is NCTransferCell) { - cell.fileProgressView?.isHidden = true - cell.fileProgressView?.progress = .zero - cell.setButtonMore(named: NCGlobal.shared.buttonMoreMore, image: NCImageCache.images.buttonMore) - if let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) { - cell.writeInfoDateSize(date: metadata.date, size: metadata.size) - } else { - cell.fileInfoLabel?.text = "" - cell.fileSubinfoLabel?.text = "" - } - } else { - cell.fileProgressView?.isHidden = false - cell.fileProgressView?.progress = progressNumber.floatValue - cell.setButtonMore(named: NCGlobal.shared.buttonMoreStop, image: NCImageCache.images.buttonStop) - let status = userInfo["status"] as? Int ?? NCGlobal.shared.metadataStatusNormal - if status == NCGlobal.shared.metadataStatusDownloading { - cell.fileInfoLabel?.text = self.utilityFileSystem.transformedSize(totalBytesExpected) - cell.fileSubinfoLabel?.text = self.infoLabelsSeparator + "↓ " + self.utilityFileSystem.transformedSize(totalBytes) - } else if status == NCGlobal.shared.metadataStatusUploading { - if totalBytes > 0 { - cell.fileInfoLabel?.text = self.utilityFileSystem.transformedSize(totalBytesExpected) - cell.fileSubinfoLabel?.text = self.infoLabelsSeparator + "↑ " + self.utilityFileSystem.transformedSize(totalBytes) - } else { - cell.fileInfoLabel?.text = self.utilityFileSystem.transformedSize(totalBytesExpected) - cell.fileSubinfoLabel?.text = self.infoLabelsSeparator + "↑ …" - } - } - } - } -// } - } - - @objc func updateShare(_ notification: NSNotification) { - if isSearchingMode { - networkSearch() - } else { -// self.dataSource.removeAll() - getServerData() - } - } - - @objc func updateIcons() { - collectionView.reloadData() -// reloadDataSource() - } - - // MARK: - Layout - - func changeLayout(layoutForView: NCDBLayoutForView) { - let homeServer = utilityFileSystem.getHomeServer(urlBase: session.urlBase, userId: session.userId) - let numFoldersLayoutsForView = self.database.getLayoutsForView(keyStore: layoutForView.keyStore)?.count ?? 1 - - - if self.layoutForView?.layout == layoutForView.layout { - self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView) - self.reloadDataSource() - return - } - - self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView) - layoutForView.layout = layoutForView.layout - self.layoutType = layoutForView.layout - - collectionView.reloadData() - - switch layoutForView.layout { - case global.layoutList: - self.collectionView.setCollectionViewLayout(self.listLayout, animated: true) - case global.layoutGrid: - self.collectionView.setCollectionViewLayout(self.gridLayout, animated: true) - case global.layoutPhotoSquare, global.layoutPhotoRatio: - self.collectionView.setCollectionViewLayout(self.mediaLayout, animated: true) - default: - break - } - - self.collectionView.collectionViewLayout.invalidateLayout() - -// (self.navigationController as? NCMainNavigationController)?.setNavigationRightItems() - } - - @objc func reloadDataSource(_ notification: NSNotification) { - if let userInfo = notification.userInfo as? NSDictionary { - if let serverUrl = userInfo["serverUrl"] as? String { - if serverUrl != self.serverUrl { - return + break } } - - if let clearDataSource = userInfo["clearDataSource"] as? Bool, clearDataSource { - self.dataSource.removeAll() - } } - - reloadDataSource() } - @objc func getServerData(_ notification: NSNotification) { - if let userInfo = notification.userInfo as NSDictionary?, - let serverUrl = userInfo["serverUrl"] as? String { - if serverUrl != self.serverUrl { - return + func transferReloadData(serverUrl: String?, requestData: Bool, status: Int?) { + Task { + await self.debouncer.call { + if requestData { + if self.isSearchingMode { + self.networkSearch() + } else if ( self.serverUrl == serverUrl) || serverUrl == nil { + Task { + await self.getServerData() + } + } + } else { + if self.isSearchingMode { + guard status != self.global.metadataStatusWaitDelete, + status != self.global.metadataStatusWaitRename, + status != self.global.metadataStatusWaitMove, + status != self.global.metadataStatusWaitCopy, + status != self.global.metadataStatusWaitFavorite else { + return + } + self.networkSearch() + } else if ( self.serverUrl == serverUrl) || serverUrl == nil { + Task { + await self.reloadDataSource() + } + } + } } } - - getServerData() } - @objc func reloadHeader(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let account = userInfo["account"] as? String, - account == session.account - else { return } - - self.collectionView.reloadData() - } + // MARK: - NotificationCenter - @objc func changeStatusFolderE2EE(_ notification: NSNotification) { - reloadDataSource() + @objc func applicationWillResignActive(_ notification: NSNotification) { + mainNavigationController?.resetPlusButtonAlpha() } @objc func closeRichWorkspaceWebView() { - reloadDataSource() - } - - @objc func deleteFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let error = userInfo["error"] as? NKError else { return } - - if error == .success { - if isSearchingMode { - return networkSearch() - } - - if isRecommendationActived { - Task.detached { - await NCNetworking.shared.createRecommendations(session: self.session) - } - } - } else { - NCContentPresenter().showError(error: error) - } - - reloadDataSource() - } - - @objc func copyMoveFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let serverUrl = userInfo["serverUrl"] as? String, - let account = userInfo["account"] as? String, - account == session.account else { return } - - if isSearchingMode { - return networkSearch() - } - - if isRecommendationActived { - Task.detached { - await NCNetworking.shared.createRecommendations(session: self.session) - } - } - - if serverUrl == self.serverUrl { - reloadDataSource() + Task { + await self.reloadDataSource() } } - @objc func renameFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let account = userInfo["account"] as? String, - let serverUrl = userInfo["serverUrl"] as? String, - let error = userInfo["error"] as? NKError, - account == session.account - else { return } + // MARK: - Layout - if error == .success { - if isSearchingMode { - return networkSearch() - } + func changeLayout(layoutForView: NCDBLayoutForView) { + let homeServer = utilityFileSystem.getHomeServer(urlBase: session.urlBase, userId: session.userId) + let numFoldersLayoutsForView = self.database.getLayoutsForView(keyStore: layoutForView.keyStore)?.count ?? 1 - if isRecommendationActived { - Task.detached { - await NCNetworking.shared.createRecommendations(session: self.session) + func changeLayout(withSubFolders: Bool) { + if self.layoutForView?.layout == layoutForView.layout { + self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView, withSubFolders: withSubFolders) + Task { + await self.reloadDataSource() } + return } - } - - if serverUrl == self.serverUrl { - if error != .success { - NCContentPresenter().showError(error: error) - } - reloadDataSource() - } else { - collectionView.reloadData() - } - } - @objc func createFolder(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let ocId = userInfo["ocId"] as? String, - let account = userInfo["account"] as? String, - account == session.account, - let withPush = userInfo["withPush"] as? Bool, - let metadata = database.getMetadataFromOcId(ocId) - else { return } + self.layoutForView = self.database.setLayoutForView(layoutForView: layoutForView, withSubFolders: withSubFolders) + layoutForView.layout = layoutForView.layout + self.layoutType = layoutForView.layout - if isSearchingMode { - return networkSearch() - } + collectionView.reloadData() - if metadata.serverUrl + "/" + metadata.fileName == self.serverUrl { - reloadDataSource() - } else if withPush, metadata.serverUrl == self.serverUrl { - reloadDataSource() - if let sceneIdentifier = userInfo["sceneIdentifier"] as? String { - if sceneIdentifier == controller?.sceneIdentifier { - pushMetadata(metadata) - } - } else { - pushMetadata(metadata) + switch layoutForView.layout { + case global.layoutList: + self.collectionView.setCollectionViewLayout(self.listLayout, animated: true) + case global.layoutGrid: + self.collectionView.setCollectionViewLayout(self.gridLayout, animated: true) + case global.layoutPhotoSquare, global.layoutPhotoRatio: + self.collectionView.setCollectionViewLayout(self.mediaLayout, animated: true) + default: + break } - } - } - - @objc func favoriteFile(_ notification: NSNotification) { - if isSearchingMode { - return networkSearch() - } - - if self is NCFavorite { - return reloadDataSource() - } - - guard let userInfo = notification.userInfo as NSDictionary?, - let serverUrl = userInfo["serverUrl"] as? String, - serverUrl == self.serverUrl - else { return } - - reloadDataSource() - } - @objc func downloadStartFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let serverUrl = userInfo["serverUrl"] as? String, - let account = userInfo["account"] as? String - else { return } + self.collectionView.collectionViewLayout.invalidateLayout() - if account == self.session.account, serverUrl == self.serverUrl { - reloadDataSource() - } else { - collectionView?.reloadData() - } - } - - @objc func downloadedFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let serverUrl = userInfo["serverUrl"] as? String, - let account = userInfo["account"] as? String - else { return } - - if account == self.session.account, serverUrl == self.serverUrl { - reloadDataSource() - } else { - collectionView?.reloadData() - } - } - - @objc func downloadCancelFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let serverUrl = userInfo["serverUrl"] as? String, - let account = userInfo["account"] as? String - else { return } - - if account == self.session.account, serverUrl == self.serverUrl { - reloadDataSource() - } else { - collectionView?.reloadData() - } - } - - @objc func uploadStartFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let ocId = userInfo["ocId"] as? String, - let serverUrl = userInfo["serverUrl"] as? String, - let account = userInfo["account"] as? String, - !isSearchingMode, - let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) - else { return } - - // Header view trasfer - if metadata.isTransferInForeground { - NCNetworking.shared.transferInForegorund = NCNetworking.TransferInForegorund(ocId: ocId, progress: 0) - DispatchQueue.main.async { self.collectionView?.reloadData() } - } - - if account == self.session.account, serverUrl == self.serverUrl { - reloadDataSource() - } else { - collectionView?.reloadData() - } - } - - @objc func uploadedFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let serverUrl = userInfo["serverUrl"] as? String, - let account = userInfo["account"] as? String - else { return } - - if account == self.session.account, serverUrl == self.serverUrl { - reloadDataSource() - } else { - collectionView?.reloadData() + Task { + await (self.navigationController as? NCMainNavigationController)?.updateRightMenu() + } } - } - @objc func uploadedLivePhoto(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let serverUrl = userInfo["serverUrl"] as? String, - let account = userInfo["account"] as? String - else { return } - - if account == self.session.account, serverUrl == self.serverUrl { - reloadDataSource() + if serverUrl == homeServer || numFoldersLayoutsForView == 1 { + changeLayout(withSubFolders: false) } else { - collectionView?.reloadData() - } - } - - @objc func uploadCancelFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let serverUrl = userInfo["serverUrl"] as? String, - let account = userInfo["account"] as? String - else { return } + let alertController = UIAlertController(title: NSLocalizedString("_propagate_layout_", comment: ""), message: nil, preferredStyle: .alert) - if account == self.session.account, serverUrl == self.serverUrl { - reloadDataSource() - } else { - collectionView?.reloadData() - } - } - - @objc func triggerProgressTask(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, - let progressNumber = userInfo["progress"] as? NSNumber, - let totalBytes = userInfo["totalBytes"] as? Int64, - let totalBytesExpected = userInfo["totalBytesExpected"] as? Int64, - let ocId = userInfo["ocId"] as? String, - let ocIdTransfer = userInfo["ocIdTransfer"] as? String, - let session = userInfo["session"] as? String - else { return } - - let chunk: Int = userInfo["chunk"] as? Int ?? 0 - let e2eEncrypted: Bool = userInfo["e2eEncrypted"] as? Bool ?? false - - let transfer = NCTransferProgress.shared.append(NCTransferProgress.Transfer(ocId: ocId, ocIdTransfer: ocIdTransfer, session: session, chunk: chunk, e2eEncrypted: e2eEncrypted, progressNumber: progressNumber, totalBytes: totalBytes, totalBytesExpected: totalBytesExpected)) - -// DispatchQueue.main.async { - if self.headerMenuTransferView && (chunk > 0 || e2eEncrypted) { - if NCNetworking.shared.transferInForegorund?.ocId == ocId { - NCNetworking.shared.transferInForegorund?.progress = progressNumber.floatValue - } else { - NCNetworking.shared.transferInForegorund = NCNetworking.TransferInForegorund(ocId: ocId, progress: progressNumber.floatValue) - self.collectionView.reloadData() - } - self.headerMenu?.progressTransfer.progress = transfer.progressNumber.floatValue - } else { - guard let indexPath = self.dataSource.getIndexPathMetadata(ocId: ocId), - let cell = self.collectionView?.cellForItem(at: indexPath), - let cell = cell as? NCCellProtocol else { return } - if progressNumber.floatValue == 1 && !(cell is NCTransferCell) { - cell.fileProgressView?.isHidden = true - cell.fileProgressView?.progress = .zero - cell.setButtonMore(named: NCGlobal.shared.buttonMoreMore, image: NCImageCache.images.buttonMore) - if let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) { - cell.writeInfoDateSize(date: metadata.date, size: metadata.size) - } else { - cell.fileInfoLabel?.text = "" - cell.fileSubinfoLabel?.text = "" - } - } else { - cell.fileProgressView?.isHidden = false - cell.fileProgressView?.progress = progressNumber.floatValue - cell.setButtonMore(named: NCGlobal.shared.buttonMoreStop, image: NCImageCache.images.buttonStop) - let status = userInfo["status"] as? Int ?? NCGlobal.shared.metadataStatusNormal - if status == NCGlobal.shared.metadataStatusDownloading { - cell.fileInfoLabel?.text = self.utilityFileSystem.transformedSize(totalBytesExpected) - cell.fileSubinfoLabel?.text = self.infoLabelsSeparator + "↓ " + self.utilityFileSystem.transformedSize(totalBytes) - } else if status == NCGlobal.shared.metadataStatusUploading { - if totalBytes > 0 { - cell.fileInfoLabel?.text = self.utilityFileSystem.transformedSize(totalBytesExpected) - cell.fileSubinfoLabel?.text = self.infoLabelsSeparator + "↑ " + self.utilityFileSystem.transformedSize(totalBytes) - } else { - cell.fileInfoLabel?.text = self.utilityFileSystem.transformedSize(totalBytesExpected) - cell.fileSubinfoLabel?.text = self.infoLabelsSeparator + "↑ …" - } - } - } - } -// } - } + alertController.addAction(UIAlertAction(title: NSLocalizedString("_yes_", comment: ""), style: .default, handler: { _ in + changeLayout(withSubFolders: true) + })) + alertController.addAction(UIAlertAction(title: NSLocalizedString("_no_", comment: ""), style: .default, handler: { _ in + changeLayout(withSubFolders: false) + })) - @objc func updateShare(_ notification: NSNotification) { - if isSearchingMode { - networkSearch() - } else { -// self.dataSource.removeAll() - getServerData() + self.present(alertController, animated: true) } } - @objc func updateIcons() { - collectionView.reloadData() -// reloadDataSource() - } - - // MARK: - Layout - - func setNavigationLeftItems() { - navigationItem.title = titleCurrentFolder - } - func getNavigationTitle() -> String { - let tableAccount = self.database.getTableAccount(predicate: NSPredicate(format: "account == %@", session.account)) - if let tableAccount, - !tableAccount.alias.isEmpty { - return tableAccount.alias + let tblAccount = self.database.getTableAccount(predicate: NSPredicate(format: "account == %@", session.account)) + if let tblAccount, + !tblAccount.alias.isEmpty { + return tblAccount.alias } return NCBrandOptions.shared.brand } - func accountSettingsDidDismiss(tableAccount: tableAccount?, controller: NCMainTabBarController?) { } + func accountSettingsDidDismiss(tblAccount: tableAccount?, controller: NCMainTabBarController?) { } @MainActor func startGUIGetServerData() { @@ -1407,8 +811,29 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS view.emptyDescription.text = NSLocalizedString("_no_file_pull_down_", comment: "") } } + + let spinner = UIActivityIndicatorView(style: .medium) + spinner.startAnimating() + + let container = UIView() + container.translatesAutoresizingMaskIntoConstraints = false + container.addSubview(spinner) + + spinner.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + spinner.centerXAnchor.constraint(equalTo: container.centerXAnchor), + spinner.centerYAnchor.constraint(equalTo: container.centerYAnchor) + ]) + + self.navigationItem.titleView = container } - + + @MainActor + func restoreDefaultTitle() { + self.navigationItem.titleView = nil + self.navigationItem.title = self.titleCurrentFolder + } + // MARK: - SEARCH func searchController(enabled: Bool) { @@ -1418,6 +843,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS searchController?.searchBar.alpha = 1 } else { searchController?.searchBar.alpha = 0.3 + } } @@ -1429,9 +855,13 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS isSearchingMode = true self.providers?.removeAll() self.dataSource.removeAll() - self.reloadDataSource() + Task { + await self.reloadDataSource() + } // TIP dismissTip() + // + mainNavigationController?.hiddenPlusButton(true) } func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { @@ -1441,30 +871,30 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { - NCNetworking.shared.cancelUnifiedSearchFiles() + self.networking.cancelUnifiedSearchFiles() + self.isSearchingMode = false self.literalSearch = "" self.providers?.removeAll() self.dataSource.removeAll() - self.reloadDataSource() + Task { + await self.reloadDataSource() + } + // + mainNavigationController?.hiddenPlusButton(false) } // MARK: - TAP EVENT - func tapMoreListItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, image: UIImage?, sender: Any) { - tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, namedButtonMore: namedButtonMore, image: image, sender: sender) - } - func tapMoreListItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: image, sender: sender) } - func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, image: UIImage?, sender: Any) { - tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, namedButtonMore: namedButtonMore, image: image, sender: sender) + func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { + tapMoreGridItem(with: ocId, ocIdTransfer: ocIdTransfer, image: image, sender: sender) } func tapShareListItem(with ocId: String, ocIdTransfer: String, sender: Any) { - if isEditMode { return } guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } NCCreate().createShare(viewController: self, metadata: metadata, page: .sharing) @@ -1473,29 +903,11 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS appDelegate.adjust.trackEvent(TriggerEvent(Sharing.rawValue)) } - func tapMoreGridItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, image: UIImage?, sender: Any) { - if isEditMode { return } - guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } -// toggleMenu(metadata: metadata, image: image) - if namedButtonMore == NCGlobal.shared.buttonMoreMore || namedButtonMore == NCGlobal.shared.buttonMoreLock { - toggleMenu(metadata: metadata, image: image) - } else if namedButtonMore == NCGlobal.shared.buttonMoreStop { - Task { - await cancelSession(metadata: metadata) - } - } - } - func tapMoreGridItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { guard let metadata = self.database.getMetadataFromOcId(ocId) else { return } -// toggleMenu(metadata: metadata, image: image) - Task { - await cancelSession(metadata: metadata) - } + toggleMenu(metadata: metadata, image: image, sender: sender) } - func longPressMoreListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } - func tapRichWorkspace(_ sender: Any) { if let navigationController = UIStoryboard(name: "NCViewerRichWorkspace", bundle: nil).instantiateInitialViewController() as? UINavigationController { if let viewerRichWorkspace = navigationController.topViewController as? NCViewerRichWorkspace { @@ -1509,8 +921,8 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } } - func tapRecommendationsButtonMenu(with metadata: tableMetadata, image: UIImage?) { - toggleMenu(metadata: metadata, image: image) + func tapRecommendationsButtonMenu(with metadata: tableMetadata, image: UIImage?, sender: Any?) { + toggleMenu(metadata: metadata, image: image, sender: sender) } func tapButtonSection(_ sender: Any, metadataForSection: NCMetadataForSection?) { @@ -1521,46 +933,16 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS didSelectMetadata(metadata, withOcIds: false) } - func tapButtonSwitch(_ sender: Any) { - guard !isTransitioning else { return } - isTransitioning = true - - guard let layoutForView = NCManageDatabase.shared.getLayoutForView(account: session.account, key: layoutKey, serverUrl: serverUrl) else { return } - - if layoutForView.layout == NCGlobal.shared.layoutGrid { - layoutForView.layout = NCGlobal.shared.layoutList - } else { - layoutForView.layout = NCGlobal.shared.layoutGrid - } - self.layoutForView = NCManageDatabase.shared.setLayoutForView(layoutForView: layoutForView) - self.collectionView.reloadData() - self.collectionView.collectionViewLayout.invalidateLayout() - self.collectionView.setCollectionViewLayout(layoutForView.layout == NCGlobal.shared.layoutList ? self.listLayout : self.gridLayout, animated: true) {_ in self.isTransitioning = false } - } - - func tapButtonOrder(_ sender: Any) { - let sortMenu = NCSortMenu() - sortMenu.toggleMenu(viewController: self, account: session.account, key: layoutKey, sortButton: sender as? UIButton, serverUrl: serverUrl) - } - - func tapButtonTransfer(_ sender: Any) { } - - func tapMorePhotoItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { } - func longPressListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } - func longPressListItem(with objectId: String, indexPath: IndexPath, gestureRecognizer: UILongPressGestureRecognizer) { - } - func longPressListItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) { } + func longPressGridItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } - func longPressMoreListItem(with ocId: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) { } + func longPressMoreListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } func longPressPhotoItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } - - func longPressGridItem(with ocId: String, ocIdTransfer: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) { } - - func longPressMoreGridItem(with ocId: String, namedButtonMore: String, gestureRecognizer: UILongPressGestureRecognizer) { } - + + func longPressMoreGridItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { } + @objc func longPressCollecationView(_ gestureRecognizer: UILongPressGestureRecognizer) { openMenuItems(with: nil, gestureRecognizer: gestureRecognizer) } @@ -1582,7 +964,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS becomeFirstResponder() if !serverUrl.isEmpty { - listMenuItems.append(UIMenuItem(title: NSLocalizedString("_paste_file_", comment: ""), action: #selector(pasteFilesMenu))) + listMenuItems.append(UIMenuItem(title: NSLocalizedString("_paste_file_", comment: ""), action: #selector(pasteFilesMenu(_:)))) } if !listMenuItems.isEmpty { @@ -1591,23 +973,16 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } } - // MARK: - Transfer Delegate - - func transferProgressDidUpdate(progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String) { } - - func tranferChange(status: String, metadata: tableMetadata, error: NKError) { } - // MARK: - Menu Item override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { - - if #selector(pasteFilesMenu) == action { + if #selector(pasteFilesMenu(_:)) == action { if !UIPasteboard.general.items.isEmpty, !(metadataFolder?.e2eEncrypted ?? false) { return true } - } else if #selector(copyMenuFile) == action { + } else if #selector(copyMenuFile(_:)) == action { return true - } else if #selector(moveMenuFile) == action { + } else if #selector(moveMenuFile(_:)) == action { return true } @@ -1739,8 +1114,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } } - func getServerData() { - } + func getServerData(forced: Bool = false) async { } @objc func networkSearch() { guard !networkSearchInProgress else { @@ -1749,45 +1123,68 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS guard !session.account.isEmpty, let literalSearch = literalSearch, !literalSearch.isEmpty else { - return self.refreshControl.endRefreshing() + return } + let capabilities = NCNetworking.shared.capabilities[session.account] ?? NKCapabilities.Capabilities() self.networkSearchInProgress = true self.dataSource.removeAll() - self.refreshControl.beginRefreshing() - self.reloadDataSource() - - if NCCapabilities.shared.getCapabilities(account: session.account).capabilityServerVersionMajor >= global.nextcloudVersion20 { - NCNetworking.shared.unifiedSearchFiles(literal: literalSearch, account: session.account) { task in - self.dataSourceTask = task - self.reloadDataSource() - } providers: { _, searchProviders in + Task { + await self.reloadDataSource() + } + + if capabilities.serverVersionMajor >= global.nextcloudVersion20 { + self.networking.unifiedSearchFiles(literal: literalSearch, account: session.account) { task in + self.searchDataSourceTask = task + Task { + await self.reloadDataSource() + } + } providers: { account, searchProviders in self.providers = searchProviders self.searchResults = [] - self.dataSource = NCCollectionViewDataSource(metadatas: [], layoutForView: self.layoutForView, providers: self.providers, searchResults: self.searchResults) + self.dataSource = NCCollectionViewDataSource(metadatas: [], + layoutForView: self.layoutForView, + providers: self.providers, + searchResults: self.searchResults, + account: account) } update: { _, _, searchResult, metadatas in guard let metadatas, !metadatas.isEmpty, self.isSearchingMode, let searchResult else { return } - NCNetworking.shared.unifiedSearchQueue.addOperation(NCCollectionViewUnifiedSearch(collectionViewCommon: self, metadatas: metadatas, searchResult: searchResult)) + self.networking.unifiedSearchQueue.addOperation(NCCollectionViewUnifiedSearch(collectionViewCommon: self, metadatas: metadatas, searchResult: searchResult)) } completion: { _, _ in - self.refreshControl.endRefreshing() - self.reloadDataSource() + Task { + await self.reloadDataSource() + } self.networkSearchInProgress = false } } else { - NCNetworking.shared.searchFiles(literal: literalSearch, account: session.account) { task in - self.dataSourceTask = task - self.reloadDataSource() + self.networking.searchFiles(literal: literalSearch, account: session.account) { task in + self.searchDataSourceTask = task + Task { + await self.reloadDataSource() + } } completion: { metadatasSearch, error in - DispatchQueue.main.async { - self.refreshControl.endRefreshing() - self.reloadDataSource() + Task { + guard let metadatasSearch, + error == .success, + self.isSearchingMode + else { + self.networkSearchInProgress = false + await self.reloadDataSource() + return + } + let ocId = metadatasSearch.map { $0.ocId } + let metadatas = await self.database.getMetadatasAsync(predicate: NSPredicate(format: "ocId IN %@", ocId), + withLayout: self.layoutForView, + withAccount: self.session.account) + + self.dataSource = NCCollectionViewDataSource(metadatas: metadatas, + layoutForView: self.layoutForView, + providers: self.providers, + searchResults: self.searchResults, + account: self.session.account) + self.networkSearchInProgress = false + await self.reloadDataSource() } - guard let metadatasSearch, error == .success, self.isSearchingMode else { return } - let ocId = metadatasSearch.map { $0.ocId } - let metadatas = self.database.getResultsMetadatasPredicate(NSPredicate(format: "ocId IN %@", ocId), layoutForView: self.layoutForView) - - self.dataSource = NCCollectionViewDataSource(metadatas: metadatas, layoutForView: self.layoutForView, providers: self.providers, searchResults: self.searchResults) - self.networkSearchInProgress = false } } } @@ -1798,9 +1195,11 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS metadataForSection.unifiedSearchInProgress = true self.collectionView?.reloadData() - NCNetworking.shared.unifiedSearchFilesProvider(id: lastSearchResult.id, term: term, limit: 5, cursor: cursor, account: session.account) { task in - self.dataSourceTask = task - self.reloadDataSource() + self.networking.unifiedSearchFilesProvider(id: lastSearchResult.id, term: term, limit: 5, cursor: cursor, account: session.account) { task in + self.searchDataSourceTask = task + Task { + await self.reloadDataSource() + } } completion: { _, searchResult, metadatas, error in if error != .success { Task {@MainActor in @@ -1825,8 +1224,15 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // MARK: - Push metadata func pushMetadata(_ metadata: tableMetadata) { - guard let navigationCollectionViewCommon = self.controller?.navigationCollectionViewCommon else { return } - let serverUrlPush = utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: metadata.fileName) + guard let navigationCollectionViewCommon = self.controller?.navigationCollectionViewCommon else { + return + } + let serverUrlPush = utilityFileSystem.createServerUrl(serverUrl: metadata.serverUrl, fileName: metadata.fileName) + + // Set Last Opening Date + Task { + await database.setDirectoryLastOpeningDateAsync(ocId: metadata.ocId) + } if let viewController = navigationCollectionViewCommon.first(where: { $0.navigationController == self.navigationController && $0.serverUrl == serverUrlPush})?.viewController, viewController.isViewLoaded { navigationController?.pushViewController(viewController, animated: true) @@ -1842,13 +1248,6 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS } } } - - func pushViewController(viewController: UIViewController) { - if pushed { return } - - pushed = true - navigationController?.pushViewController(viewController, animated: true) - } func pushViewController(viewController: UIViewController) { if pushed { return } @@ -1866,7 +1265,6 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS !results.isEmpty { return Array(results) } - return nil } func sizeForHeaderInSection(section: Int) -> CGSize { @@ -1875,12 +1273,12 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS let isIphone = UIDevice.current.userInterfaceIdiom == .phone if self.dataSource.isEmpty() { - height = utility.getHeightHeaderEmptyData(view: view, portraitOffset: emptyDataPortaitOffset, landscapeOffset: emptyDataLandscapeOffset, isHeaderMenuTransferViewEnabled: isHeaderMenuTransferViewEnabled() != nil) + height = utility.getHeightHeaderEmptyData(view: view, portraitOffset: emptyDataPortaitOffset, landscapeOffset: emptyDataLandscapeOffset) } else if isEditMode || (isLandscape && isIphone) { return CGSize.zero } else { - let (heightHeaderCommands, heightHeaderRichWorkspace, heightHeaderSection) = getHeaderHeight(section: section) - height = heightHeaderCommands + heightHeaderRichWorkspace + heightHeaderSection + let (heightHeaderRichWorkspace, heightHeaderRecommendations, heightHeaderSection) = getHeaderHeight(section: section) + height = heightHeaderRichWorkspace + heightHeaderRecommendations + heightHeaderSection } return CGSize(width: collectionView.frame.width, height: height) @@ -1889,21 +1287,25 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS // MARK: - Footer size func sizeForFooterInSection(section: Int) -> CGSize { + guard let controller else { + return CGSize.zero + } let sections = dataSource.numberOfSections() - let metadataForSection = self.dataSource.getMetadataForSection(section) - let isPaginated = metadataForSection?.lastSearchResult?.isPaginated ?? false - let metadatasCount: Int = metadataForSection?.lastSearchResult?.entries.count ?? 0 - var size = CGSize(width: collectionView.frame.width, height: 0) + let bottomAreaInsets: CGFloat = controller.tabBar.safeAreaInsets.bottom == 0 ? 34 : 0 + let height = controller.tabBar.frame.height + bottomAreaInsets - if section == sections - 1 { - size.height += 85 - } else { - size.height += 1 + if isEditMode { + return CGSize(width: collectionView.frame.width, height: 90 + height) + } + + if isSearchingMode { + return CGSize(width: collectionView.frame.width, height: 50) } - if isSearchingMode && isPaginated && metadatasCount > 0 { - size.height += 30 + if section == sections - 1 { + return CGSize(width: collectionView.frame.width, height: height) + } else { + return CGSize(width: collectionView.frame.width, height: 0) } - return size } } diff --git a/iOSClient/NCBackgroundLocationUploadManager.swift b/iOSClient/NCBackgroundLocationUploadManager.swift index 3bea5f4204..5fd5caf392 100644 --- a/iOSClient/NCBackgroundLocationUploadManager.swift +++ b/iOSClient/NCBackgroundLocationUploadManager.swift @@ -1,6 +1,10 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2025 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later +// +// NCBackgroundLocationUploadManager.swift +// Nextcloud +// +// Created by Marino Faggiana on 06/06/25. +// Copyright © 2025 Marino Faggiana. All rights reserved. +// import CoreLocation import NextcloudKit @@ -102,23 +106,21 @@ class NCBackgroundLocationUploadManager: NSObject, CLLocationManagerDelegate { } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { - // Must work only in background guard isAppInBackground else { return } // Open Realm - guard NCManageDatabase.shared.openRealmBackground() else { - nkLog(tag: self.global.logTagLocation, emoji: .error, message: "Failed to open Realm in Location Manager") - return - } - - let appDelegate = (UIApplication.shared.delegate as? AppDelegate)! - let location = locations.last - nkLog(tag: self.global.logTagLocation, emoji: .start, message: "Triggered by location change: \(location?.coordinate.latitude ?? 0), \(location?.coordinate.longitude ?? 0)") - - Task.detached { - await appDelegate.backgroundSync() + if database.openRealmBackground() { + let appDelegate = (UIApplication.shared.delegate as? AppDelegate)! + let location = locations.last + nkLog(tag: self.global.logTagLocation, emoji: .start, message: "Triggered by location change: \(location?.coordinate.latitude ?? 0), \(location?.coordinate.longitude ?? 0)") + + Task.detached { + if let tblAccount = await self.database.getActiveTableAccountAsync() { + await appDelegate.backgroundSync(tblAccount: tblAccount) + } + } } } diff --git a/iOSClient/NCGlobal.swift b/iOSClient/NCGlobal.swift index 51df24b68c..b445f506ab 100644 --- a/iOSClient/NCGlobal.swift +++ b/iOSClient/NCGlobal.swift @@ -60,13 +60,14 @@ final class NCGlobal: Sendable { // Intro selector // let introLogin: Int = 0 + let introSignup: Int = 1 let introSignUpWithProvider: Int = 1 // Avatar // let avatarSize: Int = 128 * Int(UIScreen.main.scale) let avatarSizeRounded: Int = 128 - + // Preview size // let size1024: CGSize = CGSize(width: 1024, height: 1024) @@ -134,7 +135,35 @@ final class NCGlobal: Sendable { // let buttonMoreMore = "more" let buttonMoreLock = "moreLock" - + let buttonMoreStop = "stop" + + // Standard height sections header/footer + // + let heightButtonsView: CGFloat = 50 + let heightHeaderTransfer: CGFloat = 50 + let heightSection: CGFloat = 30 + let heightFooter: CGFloat = 1 + let heightFooterButton: CGFloat = 30 + let endHeightFooter: CGFloat = 85 + + + // Text - OnlyOffice - Collabora - QuickLook + // + let editorText = "text" + let editorOnlyoffice = "onlyoffice" + let editorCollabora = "collabora" + let editorQuickLook = "quicklook" + + let onlyofficeDocx = "onlyoffice_docx" + let onlyofficeXlsx = "onlyoffice_xlsx" + let onlyofficePptx = "onlyoffice_pptx" + + // Template + // + let templateDocument = "document" + let templateSpreadsheet = "spreadsheet" + let templatePresentation = "presentation" + // Rich Workspace // let fileNameRichWorkspace = "Readme.md" @@ -217,6 +246,8 @@ final class NCGlobal: Sendable { let selectorSaveAsScan = "saveAsScan" let selectorOpenDetail = "openDetail" let selectorSynchronizationOffline = "synchronizationOffline" + let selectorPrint = "print" + let selectorDeleteFile = "deleteFile" // Metadata : Status // @@ -247,7 +278,6 @@ final class NCGlobal: Sendable { let metadataStatusForScreenAwake = [-1, -2, 1, 2] let metadataStatusHideInView = [1, 2, 3, 11] let metadataStatusWaitWebDav = [10, 11, 12, 13, 14, 15] - let metadataStatusTransfers = [-2, -3, 2, 3, 10, 11, 12, 13, 14, 15] let metadatasStatusInWaiting = [-1, 1, 10, 11, 12, 13, 14, 15] let metadatasStatusInProgress = [-2, 2] @@ -264,6 +294,8 @@ final class NCGlobal: Sendable { let notificationCenterChangeTheming = "changeTheming" // userInfo: account let notificationCenterRichdocumentGrabFocus = "richdocumentGrabFocus" let notificationCenterReloadDataNCShare = "reloadDataNCShare" + let notificationCenterDidCreateShareLink = "didCreateShareLink" + let notificationCenterCloseRichWorkspaceWebView = "closeRichWorkspaceWebView" let notificationCenterReloadAvatar = "reloadAvatar" let notificationCenterClearCache = "clearCache" @@ -271,6 +303,8 @@ final class NCGlobal: Sendable { let notificationCenterServerDidUpdate = "serverDidUpdate" // userInfo: account let notificationCenterNetworkReachability = "networkReachability" + let notificationCenterRenameFile = "renameFile" // userInfo: serverUrl, account, error + let notificationCenterMenuSearchTextPDF = "menuSearchTextPDF" let notificationCenterMenuGotToPageInPDF = "menuGotToPageInPDF" @@ -285,6 +319,8 @@ final class NCGlobal: Sendable { let notificationCenterPlayerIsPlaying = "playerIsPlaying" let notificationCenterPlayerStoppedPlaying = "playerStoppedPlaying" + let notificationCenterFavoriteStatusChanged = "favoriteStatusChanged" + // Networking Status let networkingStatusCreateFolder = "statusCreateFolder" let networkingStatusDelete = "statusDelete" @@ -299,8 +335,9 @@ final class NCGlobal: Sendable { let networkingStatusUploading = "statusUploading" let networkingStatusUploaded = "statusUploaded" - let networkingStatusReloadAvatar = "statusReloadAvatar" + let networkingStatusReloadAvatar = "statusReloadAvatar" + let notificationCenterUpdateIcons = "updateIcons" // TIP // @@ -383,6 +420,20 @@ final class NCGlobal: Sendable { // let taskDescriptionRetrievesProperties = "retrievesProperties" let taskDescriptionSynchronization = "synchronization" + let taskDescriptionDeleteFileOrFolder = "deleteFileOrFolder" + + // MoEngage App Version + // + let moEngageAppVersion = 854 + + // Filename Mask and Type + // + let keyFileNameMask = "fileNameMask" + let keyFileNameType = "fileNameType" + let keyFileNameAutoUploadMask = "fileNameAutoUploadMask" + let keyFileNameAutoUploadType = "fileNameAutoUploadType" + let keyFileNameOriginal = "fileNameOriginal" + let keyFileNameOriginalAutoUpload = "fileNameOriginalAutoUpload" // LOG TAG // diff --git a/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift b/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift index fc10b27b2a..d16f3e9f16 100644 --- a/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift +++ b/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift @@ -196,7 +196,7 @@ class NCNetworkingE2EEUpload: NSObject { metadata.status = NCGlobal.shared.metadataStatusNormal await self.database.addMetadataAsync(metadata) - await self.database.addLocalFilesAsync(metadatas: [metadata]) + await self.database.addLocalFileAsync(metadata: metadata) utility.createImageFileFrom(metadata: metadata) await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in diff --git a/iOSClient/Networking/NCConfigServer.swift b/iOSClient/Networking/NCConfigServer.swift index 2214a85b0a..0d466c370c 100644 --- a/iOSClient/Networking/NCConfigServer.swift +++ b/iOSClient/Networking/NCConfigServer.swift @@ -1,6 +1,25 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2022 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later +// +// NCConfigServer.swift +// Nextcloud +// +// Created by Marino Faggiana on 05/12/22. +// Copyright © 2022 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// +// 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 . +// import Foundation import UIKit diff --git a/iOSClient/Networking/NCNetworking+Download.swift b/iOSClient/Networking/NCNetworking+Download.swift index 9f459e484c..5178cf296e 100644 --- a/iOSClient/Networking/NCNetworking+Download.swift +++ b/iOSClient/Networking/NCNetworking+Download.swift @@ -1,6 +1,25 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2024 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later +// +// NCNetworking+Download.swift +// Nextcloud +// +// Created by Marino Faggiana on 07/02/24. +// Copyright © 2024 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// +// 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 . +// import UIKit import NextcloudKit @@ -329,3 +348,40 @@ extension NCNetworking { } } } + +class NCOperationDownload: ConcurrentOperation, @unchecked Sendable { + var metadata: tableMetadata + var selector: String + + init(metadata: tableMetadata, selector: String) { + self.metadata = tableMetadata.init(value: metadata) + self.selector = selector + } + + override func start() { + guard !isCancelled else { return self.finish() } + + metadata.session = NCNetworking.shared.sessionDownload + metadata.sessionError = "" + metadata.sessionSelector = selector + metadata.sessionTaskIdentifier = 0 + metadata.status = NCGlobal.shared.metadataStatusWaitDownload + +// let metadata = NCManageDatabase.shared.addMetadata(metadata) + +// NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true) { +// } completion: { _, _ in +// self.finish() +// } + Task { + await download(withSelector: self.selector) + } + } + + private func download(withSelector selector: String = "") async { + await NCNetworking.shared.downloadFile(metadata: metadata) { _ in + self.finish() + } taskHandler: { _ in } + + } +} diff --git a/iOSClient/Networking/NCNetworking+LivePhoto.swift b/iOSClient/Networking/NCNetworking+LivePhoto.swift index e861b76b81..072b3b087f 100644 --- a/iOSClient/Networking/NCNetworking+LivePhoto.swift +++ b/iOSClient/Networking/NCNetworking+LivePhoto.swift @@ -1,6 +1,25 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2024 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later +// +// NCNetworking+LivePhoto.swift +// Nextcloud +// +// Created by Marino Faggiana on 07/02/24. +// Copyright © 2024 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// +// 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 . +// import UIKit import NextcloudKit diff --git a/iOSClient/Networking/NCNetworking+Synchronization.swift b/iOSClient/Networking/NCNetworking+Synchronization.swift new file mode 100644 index 0000000000..270c19864b --- /dev/null +++ b/iOSClient/Networking/NCNetworking+Synchronization.swift @@ -0,0 +1,93 @@ +// +// NCNetworking+Synchronization.swift +// Nextcloud +// +// Created by Marino Faggiana on 07/02/24. +// Copyright © 2024 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// +// 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 . +// + +import UIKit +import NextcloudKit + +extension NCNetworking { + internal func synchronization(account: String, serverUrl: String, userId: String, urlBase: String, metadatasInDownload: [tableMetadata]?) async { + let showHiddenFiles = NCPreferences().getShowHiddenFiles(account: account) + let options = NKRequestOptions(timeout: 300, taskDescription: NCGlobal.shared.taskDescriptionSynchronization, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue) + + nkLog(tag: self.global.logTagSync, emoji: .start, message: "Start read infinite folder: \(serverUrl)") + + let results = await NextcloudKit.shared.readFileOrFolderAsync(serverUrlFileName: serverUrl, depth: "infinity", showHiddenFiles: showHiddenFiles, account: account, options: options) { task in + Task { + let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: account, + path: serverUrl, + name: "readFileOrFolder") + await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) + } + } + + if results.error == .success, let files = results.files { + nkLog(tag: self.global.logTagSync, emoji: .success, message: "Read infinite folder: \(serverUrl)") + + for file in files { + if file.directory { + let metadata = await NCManageDatabase.shared.convertFileToMetadataAsync(file) + await NCManageDatabase.shared.createDirectory(metadata: metadata) + } else { + if await isFileDifferent(ocId: file.ocId, fileName: file.fileName, etag: file.etag, metadatasInDownload: metadatasInDownload, userId: userId, urlBase: urlBase) { + let metadata = await NCManageDatabase.shared.convertFileToMetadataAsync(file) + metadata.session = self.sessionDownloadBackground + metadata.sessionSelector = NCGlobal.shared.selectorSynchronizationOffline + metadata.sessionTaskIdentifier = 0 + metadata.sessionError = "" + metadata.status = NCGlobal.shared.metadataStatusWaitDownload + metadata.sessionDate = Date() + + await NCManageDatabase.shared.addMetadataAsync(metadata) + + nkLog(tag: self.global.logTagSync, emoji: .start, message: "File download: \(file.serverUrl)/\(file.fileName)") + } + } + } + } else { + nkLog(tag: self.global.logTagSync, emoji: .error, message: "Read infinite folder: \(serverUrl), error: \(results.error.errorCode)") + } + + nkLog(tag: self.global.logTagSync, emoji: .stop, message: "Stop read infinite folder: \(serverUrl)") + } + + internal func isFileDifferent(ocId: String, + fileName: String, + etag: String, + metadatasInDownload: [tableMetadata]?, + userId: String, + urlBase: String) async -> Bool { + let match = metadatasInDownload?.contains { $0.ocId == ocId } ?? false + if match { + return false + } + + guard let localFile = await NCManageDatabase.shared.getTableLocalFileAsync(predicate: NSPredicate(format: "ocId == %@", ocId)) else { + return true + } + let fileNamePath = self.utilityFileSystem.getDirectoryProviderStorageOcId(ocId, fileName: fileName, userId: userId, urlBase: urlBase) + let size = await self.utilityFileSystem.fileSizeAsync(atPath: fileNamePath) + let isDifferent = (localFile.etag != etag) || size == 0 + + return isDifferent + } +} diff --git a/iOSClient/Networking/NCNetworking+Task.swift b/iOSClient/Networking/NCNetworking+Task.swift index 2887838214..4ab593f620 100644 --- a/iOSClient/Networking/NCNetworking+Task.swift +++ b/iOSClient/Networking/NCNetworking+Task.swift @@ -1,6 +1,25 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2024 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later +// +// NCNetworking+Task.swift +// Nextcloud +// +// Created by Marino Faggiana on 24/08/24. +// Copyright © 2024 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// +// 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 . +// import Foundation import UIKit diff --git a/iOSClient/Networking/NCNetworking+Upload.swift b/iOSClient/Networking/NCNetworking+Upload.swift index 93ea6b9597..f7cb42f268 100644 --- a/iOSClient/Networking/NCNetworking+Upload.swift +++ b/iOSClient/Networking/NCNetworking+Upload.swift @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2024 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later - import UIKit import NextcloudKit import Alamofire @@ -39,6 +35,22 @@ extension NCNetworking { } taskHandler(task) } progressHandler: { progress in + Task { + guard await self.progressQuantizer.shouldEmit(serverUrlFileName: serverUrlFileName, fraction: progress.fractionCompleted) else { + return + } + + if let metadata { + await NCManageDatabase.shared.setMetadataProgress(ocId: metadata.ocId, progress: progress.fractionCompleted) + await self.transferDispatcher.notifyAllDelegates { delegate in + delegate.transferProgressDidUpdate(progress: Float(progress.fractionCompleted), + totalBytes: progress.totalUnitCount, + totalBytesExpected: progress.completedUnitCount, + fileName: metadata.fileName, + serverUrl: metadata.serverUrl) + } + } + } progressHandler(progress.completedUnitCount, progress.totalUnitCount, progress.fractionCompleted) } @@ -332,6 +344,7 @@ extension NCNetworking { error: self.global.diagnosticProblemsUploadServerError) } } + await NCManageDatabase.shared.updateBadge() } // MARK: - diff --git a/iOSClient/Networking/NCNetworking+WebDAV.swift b/iOSClient/Networking/NCNetworking+WebDAV.swift index 2d3d19b5e7..ba4b604f31 100644 --- a/iOSClient/Networking/NCNetworking+WebDAV.swift +++ b/iOSClient/Networking/NCNetworking+WebDAV.swift @@ -1,6 +1,25 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2024 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later +// +// NCNetworking+WebDAV.swift +// Nextcloud +// +// Created by Marino Faggiana on 07/02/24. +// Copyright © 2024 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// +// 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 . +// import UIKit import NextcloudKit @@ -538,6 +557,50 @@ extension NCNetworking { } } } + + func renameMetadata(_ metadata: tableMetadata, + fileNameNew: String, + indexPath: IndexPath, + viewController: UIViewController?, + completion: @escaping (_ error: NKError) -> Void) { + + let permission = NCMetadataPermissions.permissionsContainsString(metadata.permissions, permissions: NCMetadataPermissions.permissionCanRename) + if (!metadata.permissions.isEmpty && permission == false) || + (metadata.status != global.metadataStatusNormal && metadata.status != global.metadataStatusWaitRename) { +// return NCContentPresenter().showInfo(error: NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_permission_modify_file_")) +// let error = await NCNetworkingE2EERename().rename(metadata: metadata, fileNameNew: fileNameNew) +// DispatchQueue.main.async { completion(error) } + completion(NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_permission_modify_file_")) + } + + if metadata.isDirectoryE2EE { +#if !EXTENSION + if isOffline { +// return NCContentPresenter().showInfo(error: NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_offline_not_allowed_")) + completion(NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_offline_not_allowed_")) + } + Task { + let error = await NCNetworkingE2EERename().rename(metadata: metadata, fileNameNew: fileNameNew) + if error != .success { +// NCContentPresenter().showError(error: error) + completion(error) + } + } +#endif + } else { + Task { + await self.transferDispatcher.notifyAllDelegatesAsync { delegate in + let status = self.global.metadataStatusWaitRename + await NCManageDatabase.shared.renameMetadata(fileNameNew: fileNameNew, ocId: metadata.ocId, status: status) + delegate.transferReloadData(serverUrl: metadata.serverUrl, status: status) + } + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterRenameFile, userInfo: ["serverUrl": metadata.serverUrl, "account": metadata.account, "error": NKError(errorCode: 0, errorDescription: ""), "ocId": metadata.ocId, "indexPath": indexPath]) + + } + + completion(NKError(errorCode: 0, errorDescription: "")) + } + } func renameFileOrFolder(metadata: tableMetadata) async -> NKError { let serverUrlFileNameSource = metadata.serverUrlFileName diff --git a/iOSClient/Networking/NCNetworking.swift b/iOSClient/Networking/NCNetworking.swift index 3f6cffe649..55268f9665 100644 --- a/iOSClient/Networking/NCNetworking.swift +++ b/iOSClient/Networking/NCNetworking.swift @@ -1,6 +1,25 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2019 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later +// +// NCNetworking.swift +// Nextcloud +// +// Created by Marino Faggiana on 23/10/19. +// Copyright © 2019 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// +// 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 . +// #if !EXTENSION_FILE_PROVIDER_EXTENSION import OpenSSL @@ -151,11 +170,11 @@ actor NetworkingTasks { $0.identifier == identifier && $0.task.state == .running } active.append((identifier, task)) - nkLog(tag: NCGlobal.shared.logTagNetworkingTasks, emoji: .start, message: "Start task for identifier: \(identifier)", consoleOnly: true) + nkLog(tag: NCGlobal.shared.logNetworkingTasks, emoji: .start, message: "Start task for identifier: \(identifier)", consoleOnly: true) } /// create a Identifier - /// + /// func createIdentifier(account: String? = nil, path: String? = nil, name: String) -> String { if let account, let path { @@ -176,7 +195,7 @@ actor NetworkingTasks { for element in active where element.identifier == identifier { element.task.cancel() - nkLog(tag: NCGlobal.shared.logTagNetworkingTasks, emoji: .cancel, message: "Cancel task for identifier: \(identifier)", consoleOnly: true) + nkLog(tag: NCGlobal.shared.logNetworkingTasks, emoji: .cancel, message: "Cancel task for identifier: \(identifier)", consoleOnly: true) } active.removeAll { $0.identifier == identifier @@ -189,7 +208,7 @@ actor NetworkingTasks { func cancelAll() { active.forEach { $0.task.cancel() - nkLog(tag: NCGlobal.shared.logTagNetworkingTasks, emoji: .cancel, message: "Cancel task with identifier: \($0.identifier)", consoleOnly: true) + nkLog(tag: NCGlobal.shared.logNetworkingTasks, emoji: .cancel, message: "Cancel task with identifier: \($0.identifier)", consoleOnly: true) } active.removeAll() } @@ -289,6 +308,7 @@ class NCNetworking: @unchecked Sendable, NextcloudKitDelegate { let saveLivePhotoQueue = Queuer(name: "saveLivePhotoQueue", maxConcurrentOperationCount: 1, qualityOfService: .default) let downloadAvatarQueue = Queuer(name: "downloadAvatarQueue", maxConcurrentOperationCount: 10, qualityOfService: .default) #endif + let downloadQueue = Queuer(name: "downloadQueue", maxConcurrentOperationCount: NCBrandOptions.shared.httpMaximumConnectionsPerHostInDownload, qualityOfService: .default) // MARK: - init diff --git a/iOSClient/Networking/NCNetworkingProcess.swift b/iOSClient/Networking/NCNetworkingProcess.swift index aa94633a71..ff91b2b1b8 100644 --- a/iOSClient/Networking/NCNetworkingProcess.swift +++ b/iOSClient/Networking/NCNetworkingProcess.swift @@ -245,7 +245,6 @@ actor NCNetworkingProcess { await startTimer(interval: minInterval) } } else { - // Remove upload asset await removeUploadedAssetsIfNeeded() // Set Live Photo diff --git a/iOSClient/Networking/NCService.swift b/iOSClient/Networking/NCService.swift index ebc3aa58ff..ac657cf195 100644 --- a/iOSClient/Networking/NCService.swift +++ b/iOSClient/Networking/NCService.swift @@ -1,6 +1,25 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2018 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later +// +// NCService.swift +// Nextcloud +// +// Created by Marino Faggiana on 14/03/18. +// Copyright © 2018 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// +// 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 . +// import UIKit @preconcurrency import NextcloudKit diff --git a/iOSClient/Settings/AnalysisDataCollectionSwitch.swift b/iOSClient/Settings/AnalysisDataCollectionSwitch.swift index e24216220f..58f2d55d8d 100644 --- a/iOSClient/Settings/AnalysisDataCollectionSwitch.swift +++ b/iOSClient/Settings/AnalysisDataCollectionSwitch.swift @@ -7,6 +7,7 @@ // import UIKit +import XLForm class AnalysisDataCollectionSwitch: XLFormBaseCell { diff --git a/iOSClient/Settings/InitialPrivacySettingsViewController.swift b/iOSClient/Settings/InitialPrivacySettingsViewController.swift index 2cd30a4242..8ae7b2e186 100644 --- a/iOSClient/Settings/InitialPrivacySettingsViewController.swift +++ b/iOSClient/Settings/InitialPrivacySettingsViewController.swift @@ -10,6 +10,7 @@ import Foundation import AppTrackingTransparency import AdSupport import UIKit +import XLForm class InitialPrivacySettingsViewController: UIViewController { diff --git a/iOSClient/Settings/PrivacySettingsViewController.swift b/iOSClient/Settings/PrivacySettingsViewController.swift index 5e56a6bd8d..b3993620ba 100644 --- a/iOSClient/Settings/PrivacySettingsViewController.swift +++ b/iOSClient/Settings/PrivacySettingsViewController.swift @@ -9,6 +9,7 @@ import Foundation import AppTrackingTransparency import AdSupport +import XLForm class PrivacySettingsViewController: XLFormViewController{ diff --git a/iOSClient/Settings/RequiredDataCollectionSwitch.swift b/iOSClient/Settings/RequiredDataCollectionSwitch.swift index f80aa97e89..0d5a34732f 100644 --- a/iOSClient/Settings/RequiredDataCollectionSwitch.swift +++ b/iOSClient/Settings/RequiredDataCollectionSwitch.swift @@ -7,7 +7,7 @@ // import UIKit - +import XLForm class RequiredDataCollectionSwitch: XLFormBaseCell { diff --git a/iOSClient/Settings/SaveSettingsCustomButtonCell.swift b/iOSClient/Settings/SaveSettingsCustomButtonCell.swift index 6205727723..1332b1f92a 100644 --- a/iOSClient/Settings/SaveSettingsCustomButtonCell.swift +++ b/iOSClient/Settings/SaveSettingsCustomButtonCell.swift @@ -7,6 +7,7 @@ // import UIKit +import XLForm class SaveSettingsCustomButtonCell: XLFormButtonCell { diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 46bf73d7a3..9b4708e1f5 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -20,6 +20,7 @@ // along with this program. If not, see . // +"_itunes_" = "iTunes"; "_cancel_" = "Cancel"; "_edit_" = "Edit"; "_tap_to_cancel_" = "Tap to cancel"; @@ -27,66 +28,123 @@ "_cancel_request_" = "Do you want to cancel?"; "_upload_file_" = "Upload file"; "_download_file_" = "Download file"; +"_loading_" = "Loading"; +"_loading_with_points_" = "Loading …"; +"_loading_num_" = "Loading file %i"; +"_loading_autoupload_" = "Auto uploading"; +"_uploading_" = "Uploading"; +"_synchronization_" = "Synchronization"; "_delete_" = "Delete"; +"_delete_file_n_" = "Delete file %i of %i"; "_rename_" = "Rename"; "_rename_file_" = "Rename file"; "_rename_folder_" = "Rename folder"; "_move_" = "Move"; +"_move_file_n_" = "Move file %i of %i"; +"_creating_sharing_" = "Creating share"; +"_updating_sharing_" = "Updating share"; +"_removing_sharing_" = "Removing share"; "_add_" = "Add"; +"_login_" = "Log in"; "_save_" = "Save"; "_warning_" = "Warning"; "_error_" = "Error"; "_no_" = "No"; "_yes_" = "Yes"; "_select_" = "Select"; +"_deselect_" = "Deselect"; "_select_all_" = "Select all"; "_upload_" = "Upload"; "_home_" = "Files"; "_files_" = "Files"; +//"_files_" = "Files"; +"_home_dir_" = "Home"; +"_file_to_upload_" = "File to upload"; +"_destination_" = "Destination"; "_ok_" = "OK"; +"_beta_version_" = "Beta version"; +"_function_in_testing_" = "Function in testing, please send information about any problems you run into."; "_done_" = "Done"; "_clear_" = "Clear"; +"_passcode_too_short_" = "Passcode too short, at least 4 characters required"; "_selected_" = "Selected"; +"_scan_fingerprint_" = "Scan fingerprint to authenticate"; "_no_active_account_" = "No account found"; "_info_" = "Info"; "_warning_" = "Warning"; +"_email_" = "Email"; +"_save_exit_" = "Do you want to exit without saving?"; "_video_" = "Video"; "_overwrite_" = "Overwrite"; +"_transfers_in_queue_" = "Transfers in progress, please wait …"; +"_too_errors_upload_" = "Too many errors, please verify the problem"; "_create_" = "Create"; "_create_folder_" = "Create folder"; "_create_folder_e2ee_" = "Create encrypted folder"; +"_create_folder_on_" = "Create folder on"; "_close_" = "Close"; +"_postpone_" = "Postpone"; "_remove_" = "Remove"; "_file_not_found_" = "File not found"; "_continue_" = "Continue"; "_continue_editing_" = "Continue editing"; +"_continue_request_" = "Do you want to continue?"; "_auto_upload_folder_" = "Auto upload"; +"_gallery_" = "Gallery"; "_photo_" = "Photo"; "_audio_" = "Audio"; "_unknown_" = "Unknown"; +"_additional_view_options_" = "Additional view options"; +"_next_" = "Next"; "_success_" = "Success"; +"_initialization_" = "Initialization"; +"_experimental_" = "Experimental"; +"_select_dir_media_tab_" = "Select as folder \"Media\""; +"_error_creation_file_" = "Oops! Could not create the file"; +"_save_path_" = "Storage path"; +"_save_settings_" = "Save settings"; "_mode_filename_" = "Filename mode"; "_warning_owncloud_" = "You are connected to an ownCloud server. This is untested and unsupported, use at your own risk. To upgrade to Nextcloud, see https://nextcloud.com/migration."; "_warning_unsupported_" = "You are using an unsupported version of Nextcloud. For the safety of your data we strongly recommend updating to the latest stable Nextcloud."; "_restore_" = "Restore"; +"_camera_roll_" = "Camera roll"; "_tap_here_to_change_" = "Tap here to change"; "_no_albums_" = "No albums"; "_denied_album_" = "This app does not have access to \"Photos\". You can enable access in Privacy Settings."; "_denied_camera_" = "This app does not have access to the \"Camera\". You can enable access in Privacy Settings."; +"_start_" = "Start"; "_force_start_" = "Force the start"; "_account_not_available_" = "The account %@ of %@ does not exist, please add it to be able to read the file %@."; "_the_account_" = "The account"; "_does_not_exist_" = "does not exist"; +"_purchase_" = "Purchase"; +"_account_not_exists_" = "The account %@ of %@ does not exist"; +"_account_not_available_" = "The account %@ of %@ does not exist, please add it to be able to read the file %@"; +"_the_account_" = "The account"; +"_does_not_exist_" = "does not exist"; +"_error_parameter_schema_" = "Wrong parameters, impossible to continue"; +"_comments_" = "Comments"; "_sharing_" = "Sharing"; +//"_details_" = "Share"; "_details_" = "Details"; +"_sub_details_" = "Subscription Details"; +"_subscriptions_" = "Subscriptions"; +"_dark_mode_" = "Dark mode"; +"_dark_mode_detect_" = "Detect iOS dark mode"; +"_screen_" = "Screen"; +"_wipe_account_" = "Account is wiped from server"; +"_appconfig_view_title_" = "Account configuration in progress …"; "_no_permission_add_file_" = "You don't have permission to add files."; "_no_permission_delete_file_" = "You don't have permission to delete files."; "_no_permission_modify_file_" = "You don't have permission to modify files."; "_no_permission_favorite_file_" = "You don't have permission to add the file to your favorites."; +"_request_upload_new_ver_" = "The file has been modified, do you want to upload the new version?"; "_add_folder_info_" = "Add folder info"; "_back_" = "Back"; "_search_" = "Search"; "_of_" = "of"; +"_internal_modify_" = "Edit with internal editor"; +"_database_corrupt_" = "Oops something went wrong, please enter your credentials but don't worry, your files have remained secure"; "_livephoto_save_" = "Save Live Photo to Photo Album"; "_livephoto_save_error_" = "Error during save of Live Photo."; "_livephoto_no_" = "Disable Live Photo"; @@ -102,9 +160,14 @@ "_copy_" = "Copy"; "_now_" = "Now"; "_wait_" = "Please wait …"; +"_attention_" = "Attention"; "_recent_" = "Recent"; "_view_in_folder_" = "View in folder"; "_leave_share_" = "Leave this share"; +"_premium_" = "Premium"; +"_professional_" = "Professional"; +"_current_" = "Current"; +"_buy_" = "Buy"; "_disabled_" = "Disabled"; "_compact_" = "Compact"; "_normal_" = "Normal"; @@ -134,24 +197,50 @@ "_locked_by_" = "Locked by %@"; "_file_locked_no_override_" = "This file is locked. It cannot be overridden."; "_lock_no_permissions_selected_" = "Not allowed for some selected files or folders."; +/* Remove a file from a list, don't delete it entirely */ "_remove_file_" = "Remove file"; + +/* Delete file and put it into the trash */ "_delete_file_" = "Delete file"; "_delete_folder_" = "Delete folder"; +"_delete_photo_" = "Delete photo"; +"_delete_video_" = "Delete video"; +"_automatic_Download_Image_" = "Use images in full resolution"; +"_automatic_Download_Image_footer_" = "When viewing images always download, if not available locally, the images in full resolution"; "_size_" = "Size"; -"_set_user_status_" = "Online status"; +//"_set_user_status_" = "Online status"; "_set_user_status_message_" = "Status message"; +"_file_size_" = "Exported file size"; +"_dimension_" = "Dimension"; +"_duration_" = "Duration"; +"_model_" = "Model"; +"_set_user_status_" = "Set user status"; "_open_settings_" = "Open settings"; +"_rename_ext_title_" = "Change file type?"; +"_rename_ext_message_" = "This file may behave differently if you change it from .%@ to %@"; +"_use_" = "Use"; +"_keep_" = "Keep"; +"_account_request_" = "Request account"; "_settings_account_request_" = "Request account at startup"; +"_print_" = "Print"; "_alias_" = "Alias"; -"_alias_placeholder_" = "Write alias"; -"_alias_footer_" = "Give your accounts a descriptive name such as Home, Office, School…"; +"_alias_placeholder_" = "Write the alias"; +"_alias_footer_" = "Give your account names a descriptive name such as Home, Office, School …"; +"_chunk_size_mb_" = "Chunk size in MB"; +"_chunk_footer_title_" = "Chunked file upload (0 is disabled)\nImportant: the chunked upload works only when the app is \"active\"."; "_privacy_legal_" = "Privacy and Legal Policy"; "_source_code_" = "Get source code"; "_account_select_" = "Select the account"; "_account_select_to_add_" = "Select the account to add"; +"_host_insert_" = "Insert the host name, for example:"; +"_certificate_not_found_" = "File %@ in documents directory not found."; +"_copy_failed_" = "Copy failed"; +"_certificate_installed_" = "Certificate installed"; "_remove_local_account_" = "Remove local account"; "_want_delete_account_" = "Do you want to remove local account?"; "_prevent_http_redirection_"= "The redirection in HTTP is not permitted."; +"_pdf_vertical_" = "PDF vertical display"; +"_pdf_horizontal_" = "PDF horizontal display"; "_single_file_conflict_title_" = "File conflict"; "_multi_file_conflict_title_" = "%@ File conflicts"; "_replace_action_title_" = "Replace"; @@ -169,6 +258,33 @@ "_change_lock_passcode_" = "Change passcode"; "_lock_cannot_disable_mdm_" = "Disabling the passcode lock is not permitted by your configuration profile."; +//TOPasscodeSettingsViewController.m +"_enter_passcode_" = "Enter Passcode"; +"_passcodes_didnt_match_try_again_" = "Passcodes didn't match. Try again."; +"_passcode_options_" = "Passcode Options"; +//"_next_" = "Next"; +"_enter_your_passcode_" = "Enter your passcode"; +"_enter_a_new_passcode_" = "Enter a new passcode"; +"_confirm_new_passcode_" = "Confirm new passcode"; +"_4_digit_numeric_code_" = "4-Digit Numeric Code"; +"_6_digit_numeric_code_" = "6-Digit Numeric Code"; +"_custom_numeric_code_" = "Custom Numeric Code"; +"_custom_alphanumeric_code_" = "Custom Alphanumeric Code"; + +//TOPasscodeSettingsWarningLabel.m +"_1_failed_passcode_attempt" = "1 Failed Passcode Attempt"; +"_failed_passcode_attempts" = "%d Failed Passcode Attempts"; + +/* Background of the file listing view */ +"_use_as_background_" = "Use it as a background"; + +/* Background of the file listing view */ +"_background_" = "Background"; + +"_dark_mode_" = "Dark mode"; +"_default_color_" = "Use the default color"; +"_as_default_color_" = "Use as default color"; + // MARK: User Status /* User status */ @@ -215,50 +331,149 @@ "_hours_" = "Hours"; "_minutes_" = "Minutes"; - +"_network_available_" = "Network available."; "_network_not_available_" = "Network unavailable."; +"_file_too_big_" = "File too large to be encrypted/decrypted"; +"_file_too_big_max_100_" = "File too large (max 100 kb.)"; +"_...loading..._" = "Loading …"; +"_download_plist_" = " "; +"_no_reuploadfile_" = "Could not find nor resend file. Delete the upload and reload the file to upload it."; "_file_already_exists_" = "Unable to complete the operation, a file with the same name exists."; +//"_file_already_exists_" = "Unable to complete the operation, a file with the same name exists."; +"_read_file_error_" = "Could not read the file"; +"_write_file_error_" = "Could not write the file"; "_files_lock_error_" = "There was an error changing the lock of this file."; "_more_" = "More"; "_notifications_" = "Notifications"; +"_logout_" = "Log out"; "_quota_space_unlimited_" = "Unlimited"; "_quota_space_unknown_" = "Unknown"; "_quota_using_" = "You are using %@ of %@"; +//"_quota_using_" = "%@ "; +"_quota_using_of_" = "of %@"; +"_quota_using_percentage_" = "Memory to %@ occupied"; "_acknowledgements_" = "Acknowledgements"; "_settings_" = "Settings"; +"_passcode_" = "Password"; "_enter_password_" = "Enter password …"; "_lock_" = "Lock"; "_lock_active_" = "Lock: On"; "_lock_not_active_" = "Lock: Off"; "_lock_protection_no_screen_" = "Do not ask at startup"; +"_lock_protection_no_screen_footer_" = "Use \"Do not ask at startup\" for the encryption option."; "_enable_touch_face_id_" = "Enable Touch/Face ID"; +"_face_id_" = "Face ID"; +"_touch_id_" = "Touch ID"; +"_security_" = "Security"; +"_data_protection_" = "Data protection"; +"_privacy_settings_" = "Privacy Settings"; +"_used_opensource_software_" = "OpenSource software used"; +"_service_" = "Service"; +"_imprint_" = "Imprint"; +"_magentacloud_version_" = "MagentaCLOUD Version"; +"_your_secure_cloud_storage_" = "Your secure cloud storage"; +"_url_" = "URL"; +"_username_" = "Username"; +"_change_credentials_" = "Change your credentials"; "_wifi_only_" = "Only use Wi-Fi connection"; "_settings_autoupload_" = "Auto upload photos"; +//"_settings_autoupload_" = "Auto upload"; +"_app_version_" = "Application version"; +"_app_in_use_" = "Application in use"; +"_contact_by_email_" = "Contact us by email"; "_clear_cache_" = "Clear cache"; "_clear_cache_footer_" = "Clear downloaded and offline files from the app"; -"_exit_" = "Reset application"; +"_exit_" = "Logout"; "_exit_footer_" = "Remove all accounts and local data from the app."; +"_funct_not_enabled_" = "Functionality not enabled"; +"_passcode_activate_" = "Password lock on"; +"_disabling_passcode_" = "Removing password lock"; "_want_exit_" = "Attention! Will be reset to initial state. Continue?"; +"_proceed_" = "Proceed"; +"_delete_cache_" = "Delete cache"; "_want_delete_cache_" = "Do you want to delete the cache (this also removes the transfers in progress)?"; +"_want_delete_thumbnails_" = "Do you want to delete all thumbnails too?"; +"_mail_deleted_" = "Email deleted"; +"_mail_saved_" = "Email saved"; +"_mail_sent_" = "Email sent"; +"_mail_failure_" = "Could not send email: %@"; +"_information_req_" = "Information request"; +"_write_in_english_" = "Kindly write to us in English"; +"_credentials_" = "Credentials"; +"_manage_account_" = "Manage account"; +"_change_password_" = "Change password"; "_add_account_" = "Add account"; -"_want_delete_" = "You will delete the following: "; +//"_want_delete_" = "You will delete the following: "; +//"_want_leave_share_" = "You will leave the following shares: "; +"_delete_account_" = "Remove account"; +"_delete_active_account_" = "Remove active account"; +"_want_delete_" = "Do you really want to delete?"; +"_want_leave_share_" = "You will leave the following shares: "; +"_delete_account_" = "Remove account"; +"_delete_active_account_" = "Remove active account"; +"_want_delete_" = "Do you really want to delete?"; "_want_leave_share_" = "You will leave the following shares: "; "_no_delete_" = "No, do not delete"; "_yes_delete_" = "Yes, delete"; +"_remove_cache_" = "Deleting cache, please wait …"; +"_optimizations_" = "Optimizations"; +"_synchronizations_" = "Synchronized folders"; +"_version_server_" = "Server version"; +"_help_" = "Help"; +"_change_simply_passcode_" = "Change password type"; +"_quota_" = "Quota"; +"_available_" = "Available"; +"_not_available_" = "Not available"; +"_accounts_" = "Accounts"; "_information_" = "Information"; +"_personal_information_" = "Personal info"; +"_user_full_name_" = "Full name"; +"_user_address_" = "Address"; +"_user_phone_" = "Phone number"; +"_user_email_" = "Email"; +"_user_web_" = "Website"; +"_user_twitter_" = "Twitter"; +"_user_job_" = "Job"; +"_user_businesssize_" = "Business size"; +"_user_businesstype_" = "Business type"; +"_user_city_" = "City"; +"_user_country_" = "Country"; +"_user_company_" = "Company"; +"_user_role_" = "Role"; +"_user_zip_" = "Zip"; +"_user_owner_" = "Owner"; +"_user_employee_" = "Employee"; +"_user_contractor_" = "Contractor"; +"_user_editprofile_" = "Edit profile"; "_select_offline_warning_" = "Making multiple files and folders available offline may take a while and use a lot of memory while doing so."; "_advanced_" = "Advanced"; "_permissions_" = "Permissions"; "_custom_permissions_" = "Custom permissions"; "_disable_files_app_" = "Disable Files App integration"; "_disable_files_app_footer_" = "Do not permit the access of files via the iOS Files application."; +"_trial_" = "Trial"; +"_trial_expired_day_" = "Days remaining"; "_time_remaining_" = "%@ remaining"; +"_disableLocalCacheAfterUpload_footer_" = "After uploading the file, do not keep it in the local cache"; +"_disableLocalCacheAfterUpload_" = "Disable local cache"; "_autoupload_" = "Auto upload photos/videos"; "_autoupload_select_folder_" = "Select the \"Auto upload\" folder"; +"_autoupload_error_select_folder_" = "Select a valid folder for the \"Auto upload\""; +"_autoupload_background_" = "Auto upload in the background"; "_autoupload_photos_" = "Auto upload photos"; "_autoupload_videos_" = "Auto upload videos"; "_autoupload_favorites_" = "Auto upload favorites only"; -"_autoupload_description_" = "Choose whether new photos or videos will be automatically uploaded to your server."; +//"_autoupload_description_" = "New photos/videos will be automatically uploaded to your MagentaCLOUD"; +"_autoupload_description_" = "Choose whether new photos or videos will be automatically uploaded to your MagentaCLOUD."; +"_autoupload_description_background_" = "This option requires the use of GPS to trigger the detection of new photos/videos in the camera roll once the location changes significantly"; +"_autoupload_background_title_" = "Limitations"; +"_autoupload_background_msg_" = "Due to iOS restrictions, it is not yet possible to perform background processes, unless GPS services are activated. Once the cell in the cellular network is changed, the system wakes up for a short time and checks for new photos to upload to the cloud."; +"_autoupload_change_location_" = "Change folder"; +"_autoupload_location_now_" = "Folder"; +"_autoupload_location_default_" = "Restore default folder"; +"_autoupload_change_location_footer_" = "Change folder used for \"Automatic upload of camera photos\" (if the option is enabled)"; +"_autoupload_not_select_home_" = "Select a folder"; +"_autoupload_save_album_" = "Copy photo or video into the photo album"; "_autoupload_fullphotos_" = "Upload the whole camera roll"; "_start_autoupload_" = "Turn on auto upload"; "_stop_autoupload_" = "Turn off auto upload"; @@ -268,47 +483,82 @@ "_autoupload_subfolder_granularity_" = "Subfolder Granularity"; "_filenamemask_" = "Change filename mask"; "_filenamemask_footer_" = "By default, when a file is uploaded, it automatically gets the following format: yy-mm-dd hh-mm-ss plus a 4-digit counter. If you do not like this format, you can change it here except for the final 4-digit counter, which cannot be omitted."; +"_autoupload_filenamemask_" = "Change filename mask"; +"_autoupload_filenamemask_footer_" = "Change the automatic filename mask"; "_autoupload_current_folder_" = "Currently selected folder"; +"_help_tutorial_" = "Tutorial"; +"_help_intro_" = "Introduction to Nextcloud"; +"_help_activity_verbose_" = "Detailed Activity feed"; +"_help_activity_mail_" = "Send activity via email"; +"_help_activity_clear_" = "Clear activity"; "_show_hidden_files_" = "Show hidden files"; "_format_compatibility_" = "Most Compatible"; "_format_compatibility_footer_" = "\"Most compatible\" will save photos as JPEG, if possible."; +"_terms_" = "Terms of Service"; "_privacy_" = "Privacy"; +"_privacy_policy_" = "Privacy Policy"; "_privacy_footer_" = "This app uses a service for the analysis of a crash. Your personal information is not sent with the report. If you want disable it, please change the setting \"Disable crash reporter\" to ON."; "_crashservice_title_" = "Disable crash reporter"; "_crashservice_alert_" = "This option requires a restart of the app to take effect."; "_upload_mov_livephoto_" = "Live Photo"; "_upload_mov_livephoto_footer_" = "\"Live Photo\" will save the selected photo in \"Live Photo\" format, if possible."; +"_view_capabilities_" = "View the capabilities"; "_capabilities_" = "Capabilities"; +"_no_capabilities_found_" = "Capabilities not found"; "_capabilities_footer_" = "Display the packages used by the app if they are installed and available on the server."; "_diagnostics_" = "Diagnostics"; "_diagnostics_footer_" = "Changing log level requires a restart of the app to take effect"; "_view_log_" = "View log file"; "_clear_log_" = "Clear log file"; +"_level_log_" = "Set Log level (disabled, standard, maximum)"; "_set_log_level_" = "Set Log level"; "_log_file_clear_alert_" = "Log file cleared \n successfully!"; "_connect_server_anyway_" = "Do you want to connect to the server anyway?"; "_server_is_trusted_" = "Do you consider this server trusted?"; "_connection_error_" = "Connection error"; +"_serverstatus_error_" = "Connection to server failure, verify your server address or network status"; +"_add_your_nextcloud_" = "Add your account"; "_login_url_" = "Server address https:// …"; +"_login_bottom_label_" = "Don't have a server yet?\nChoose one of the providers."; +"_error_multidomain_" = "Address not allowed, only the following domains are valid:"; +"_account_already_exists_" = "The account %@ already exists"; +"_traditional_login_" = "Revert to old login method"; +"_web_login_" = "Revert to web login method"; "_login_url_error_" = "URL error, please verify your server URL"; +"_webflow_not_available_" = "Web login not available, use the old login method"; "_favorites_" = "Favorites"; "_favorite_short_" = "Favorite"; +"_favorite_" = "Favorite"; +"_unfavorite_" = "Unfavorite"; +"_no_files_uploaded_" = "No files uploaded"; "_tutorial_favorite_view_" = "Files and folders you mark as favorites will show up here"; "_tutorial_offline_view_" = "Files copied here will be available offline.\n\nThey will be synchronized with your cloud."; "_tutorial_groupfolders_view_" = "No Group folders yet"; +"_tutorial_local_view_" = "You'll find the unpacked files from your cloud.\n\nConnect to iTunes to share these files."; "_more_" = "More"; "_favorite_no_files_" = "No favorites yet"; +"_pull_down_" = "Pull down to refresh"; +"_no_photo_load_" = "No photo or video"; +"_tutorial_autoupload_view_" = "You can enable auto uploads from \"Settings\""; "_no_date_information_" = "No date information"; "_no_camera_information_" = "No camera information"; "_no_lens_information_" = "No lens information"; "_today_" = "Today"; "_yesterday_" = "Yesterday"; +"_time_" = "Time: %@\n\n%@"; +"_location_not_enabled_" = "Location Services not enabled"; +"_location_not_enabled_msg_" = "Please go to \"Settings\" and turn on \"Location Services\""; "_access_photo_not_enabled_" = "Access to Photos is not enabled"; -"_access_photo_not_enabled_msg_" = "Please go to Settings → Apps → Nextcloud → Photos and enable Photo Library Access"; +//"_access_photo_not_enabled_msg_" = "Please go to \"Settings\" and turn on \"Photo Access\""; +"_access_photo_not_enabled_msg_" = "Please go to Settings → Apps → MagentaCLOUD → Photos and enable Photo Library Access"; "_access_background_app_refresh_denied_" = "\"Background App Refresh\" is denied. Please enable it in \"Settings\" otherwise, new photos or videos will not be detected when the application is in the background"; +"_access_photo_location_not_enabled_" = "Access to Photos and Location not enabled"; +"_access_photo_location_not_enabled_msg_" = "Please go to \"Settings\" and turn on \"Photo Access\" and \"Location Services\""; "_new_photos_starting_" = "Only photos or videos starting %@ will be uploaded."; "_tutorial_photo_view_" = "No photos or videos uploaded yet"; +"_create_full_upload_" = "Creating archive … May take a long time. During this process, keep the application active during the transfer as well."; "_error_createsubfolders_upload_" = "Error creating subfolders"; +"_activate_autoupload_" = "Enable auto upload"; "_remove_photo_CameraRoll_" = "Remove from camera roll"; "_remove_photo_CameraRoll_desc_" = "\"Remove from camera roll\" after uploads, a confirmation message will be displayed to delete the uploaded photos or videos from the camera roll. The deleted photos or videos will still be available in the iOS Photos Trash for 30 days."; "_never_" = "never"; @@ -319,25 +569,88 @@ "_hours_ago_" = "%d hours ago"; "_a_day_ago_" = "a day ago"; "_days_ago_" = "%d days ago"; +"_over_30_days_" = "over 30 days"; +"_connection_internet_offline_" = "The internet connection appears to be offline or Wi-Fi is required"; +"_insert_password_" = "Enter password"; +//"_update_in_progress_" = "Version upgrade, please wait …"; "_forbidden_characters_" = "Forbidden characters: %@"; "_cannot_send_mail_error_" = "No account is set up, or wrong email address entered."; "_open_url_error_" = "Cannot open the URL for this action."; +"_photo_camera_" = "Photos"; "_media_" = "Media"; +"_unzip_in_progress_" = "Extraction in progress on local storage …"; +"_file_unpacked_" = "File unpacked on local storage"; +"_file_saved_local_" = "File saved on local storage."; +"_file_not_present_" = "Error: File not present, please reload."; "_order_by_" = "Sort by"; "_name_" = "Name"; "_date_" = "Date"; "_size_" = "Size"; +"_order_by_name_a_z_" = "Sort by name (from A to Z)"; +"_sorted_by_name_a_z_" = "Sorted by name (from A to Z)"; +"_order_by_name_z_a_" = "Sort by name (from Z to A)"; +"_sorted_by_name_z_a_" = "Sorted by name (from Z to A)"; +"_order_by_date_more_recent_" = "Sort by newest"; +"_sorted_by_date_more_recent_" = "Sorted by newest"; +"_order_by_date_less_recent_" = "Sort by oldest"; +"_sorted_by_date_less_recent_" = "Sorted by oldest"; +"_order_by_size_smallest_" = "Sort by smallest"; +"_sorted_by_size_smallest_" = "Sorted by smallest"; +"_order_by_size_largest_" = "Sort by largest"; +"_sorted_by_size_largest_" = "Sorted by largest"; "_delete_selected_files_" = "Delete files"; +"_move_selected_files_" = "Move files"; +"_move_or_copy_selected_files_" = "Move or copy files"; +"_download_selected_files_" = "Download files"; +"_download_selected_files_folders_" = "Download files and folders"; +"_error_operation_canc_" = "Error: Operation canceled."; +"_only_lock_passcode_" = "Available only with Lock password activated. Activate it in the \"Settings\"."; +"_go_to_app_settings_" = "Go to app settings"; +"_passcode_protection_" = "Password protection"; "_remove_favorites_" = "Remove from favorites"; +"_remove_offline_" = "Remove from offline"; "_add_favorites_" = "Add to favorites"; +"_add_offline_" = "Add to offline"; +"_remove_passcode_" = "Remove password protection"; +"_protect_passcode_" = "Protect with password"; "_share_" = "Share"; +"_reload_" = "Reload"; +"_open_in_" = "Open in …"; +"_open_" = "Open …"; "_remove_local_file_" = "Remove locally"; +"_add_local_" = "Add to local storage"; +"_comm_erro_pull_down_" = "Attention: Communication error with the server. Pull down to refresh."; +"_file_not_downloaded_" = "file not downloaded"; +"_file_not_uploaded_" = "file not uploaded"; "_folders_" = "folders"; "_folder_" = "folder"; "_files_" = "files"; "_file_" = "file"; +"_folder_blocked_" = "Folder blocked"; +"_downloading_progress_" = "Initiating download of files …"; "_no_file_pull_down_" = "Upload a file or pull down to refresh"; "_no_file_no_permission_to_create_" = "You don't have permission to create or upload files in this folder."; +"_browse_images_" = "Browse images"; +"_synchronized_folder_" = "Keep the folder synchronized"; +"_remove_synchronized_folder_" = "Turn off the synchronization"; +"_synchronized_confirm_" = "After enabling the synchronization, all files in the folder will be synchronized with the server, continue?"; +"_offline_folder_confirm_" = "After enabling the offline folder, all files in it will be synchronized with the server, continue?"; +"_file_not_found_reload_" = "File not found, pull down to refresh"; +"_title_section_download_" = "DOWNLOAD"; +"_download_" = "Download"; +"_title_section_upload_" = "UPLOAD"; +"_group_alphabetic_yes_" = "✓ Group alphabetically"; +"_group_alphabetic_no_" = "Group alphabetically"; +"_group_typefile_yes_" = "✓ Group by file type"; +"_group_typefile_no_" = "Group by file type"; +"_group_date_yes_" = "✓ Group by date"; +"_group_date_no_" = "Group by date"; +"_element_" = "element"; +"_elements_" = "elements"; +"_tite_footer_upload_wwan_" = " Wi-Fi network required, %lu %@ to upload"; +"_tite_footer_upload_" = "%lu %@ to upload"; +"_tite_footer_download_wwan_" = " Wi-Fi network required, %lu %@ to download"; +"_tite_footer_download_" = "%lu %@ to download"; "_limited_dimension_" = "Maximum size reached"; "_save_selected_files_" = "Save to photo gallery"; "_file_not_saved_cameraroll_" = "Error: File not saved in photo album"; @@ -346,24 +659,36 @@ "_directory_on_top_" = "Sort folders before files"; "_show_description_" = "Show folder description"; "_show_recommended_files_" = "Show recommendations"; +"_no_description_available_" = "No description available for this folder"; +"_folder_automatic_upload_" = "Folder for \"Auto upload\""; "_search_no_record_found_" = "No result"; "_search_in_progress_" = "Search in progress …"; "_search_instruction_" = "Search for file (minimum 2 characters)"; "_files_no_files_" = "No files in here"; "_files_no_folders_" = "No folders in here"; "_request_in_progress_" = "Request to server in progress …"; -"_personal_files_only_" = "Personal files only"; +"_personal_files_only_" = "Personal files only"; "audio" = "AUDIO"; +"compress" = "COMPRESS"; "directory" = "FOLDERS"; "document" = "DOCUMENTS"; "image" = "IMAGES"; "template" = "TEMPLATES"; +"unknow" = "UNKNOWN"; "video" = "VIDEO"; +"_file_del_only_local_" = "File not present on server"; +"_copy_file_" = "Copy"; "_paste_file_" = "Paste"; +"_open_quicklook_" = "Open with Quick Look"; +"_search_this_folder_" = "Search in this folder"; +"_search_all_folders_" = "Search in all folders"; +"_search_sub_folder_" = "Search here and in subfolders"; +"_theming_is_light_" = "Server theming too brightly coloured, not applicable"; "_cancel_all_task_" = "Cancel all transfers"; "_status_wait_download_" = "Waiting for download"; +"_status_in_download_" = "In download"; "_status_downloading_" = "Downloading"; "_status_wait_upload_" = "Waiting to upload"; "_status_wait_create_folder_" = "Waiting to create the folder"; @@ -372,12 +697,19 @@ "_status_wait_favorite_" = "Waiting to change favorite"; "_status_wait_copy_" = "Waiting to copy"; "_status_wait_move_" = "Waiting to move"; +"_status_in_upload_" = "In upload"; "_status_uploading_" = "Uploading"; "_status_upload_error_" = "Error, waiting to upload"; "_select_media_folder_" = "Set Media folder"; "_media_viewimage_show_" = "Show only images"; "_media_viewvideo_show_" = "Show only video"; "_media_show_all_" = "Show both"; +"_media_by_created_date_" = "Sort by created date"; +"_media_by_upload_date_" = "Sort by upload date"; +"_media_by_modified_date_" = "Sort by modified date"; +"_insert_password_pfd_" = "Secured PDF. Enter password"; +"_password_pdf_error_" = "Wrong password"; +"_error_download_photobrowser_" = "Error: Unable to download photo"; "_good_morning_" = "Good morning"; "_good_day_" = "Good day"; "_good_afternoon_" = "Good afternoon"; @@ -389,78 +721,197 @@ // MARK: Share "_share_link_" = "Share link"; +//"_share_link_" = "Share link"; +"_share_link_" = "Link"; +"_share_link_button_" = "Send link to …"; "_share_link_name_" = "Link name"; "_password_" = "Password"; "_share_password_" = "Password protected link"; +"_share_expirationdate_" = "Set expiration date for link"; "_date_" = "Date"; +"_share_title_" = "Share"; +"_add_sharee_" = "Add users or groups"; +"_add_sharee_footer_" = "You can share this resource by adding users or groups. To remove a share, remove all users and groups"; +"_find_sharee_title_" = "Search"; +"_find_sharee_" = "Search for user or group …"; +"_find_sharee_footer_" = "Enter part of the name of the user or group to search for (at least 2 characters) followed by \"Return\", select the users that should be allowed to access the share followed by \"Done\" to confirm"; +"_user_is_group_" = "(Group)"; +"_direct_sharee_title_" = "Share"; +"_direct_sharee_footer_" = "If you already know the name, enter it, then select the share type and press \"Done\" to confirm"; +"_direct_sharee_" = "Enter the username …"; "_user_sharee_footer_" = "Tap to change permissions"; +"_share_type_title_" = "Type of share"; +"_share_type_user_" = "User"; +"_share_type_group_" = "Group"; +"_share_type_remote_" = "Remote"; "_enforce_password_protection_" = "Enforce password protection"; +"_password_obligatory_" = "Enforce password protection enabled, password obligatory"; "_shared_with_you_by_" = "Shared with you by"; -"_shareLinksearch_placeholder_" = "Name, email, or Federated Cloud ID …"; +"_share_reshare_allowed_" = "Resharing is allowed."; +"_share_reshare_not_allowed_" = "Resharing is not allowed."; +"_sharing_message_" = "You can create links or send shares by mail. If you invite MagentaCLOUD users, you have more opportunities for collaboration."; +"_create_link_" = "Create Link"; +"personal_share_by_mail" = "Personal share by mail"; +"_your_shares_" = "Your Shares"; +"_share_linklabel_" = "Link '%@'"; +"_share_link_folder_" = "Link to folder"; +"_share_link_file_" = "Link to file"; +"no_shares_created" = "No shares created yet."; +"_advance_permissions_" = "Advanced permissions"; +"_send_new_email_" = "Send new email"; +"_apply_changes_" = "Apply changes"; +"_send_share_" = "Send share"; +"_PERMISSIONS_" = "PERMISSIONS"; +"share_editing_message" = "There are no editing functions for files unless you share them with MagentaCLOUD users."; +"_file_drop_message_" = "With File drop, only uploading is allowed. Only you can see files and folders that have been uploaded."; +"_custom_link_label" = "Your custom link label"; +"_set_password_" = "Set password"; +"_share_expiration_date_placeholder_"= "Expiration date for this share"; +"_share_download_limit_" = "Download Limit"; +"_share_download_limit_placeholder_" = "Enter download limit"; +"_share_download_limit_alert_empty_" = "Download limit cannot be empty."; +"_share_download_limit_alert_zero_" = "Download limit should be greater than 0."; +"_share_download_limit_alert_invalid_" = "Please enter a valid number for the download limit."; +"_share_remaining_download_" = "Downloads:"; +"_share_read_only_" = "View only"; +"_share_editing_" = "Can edit"; +"_share_file_drop_" = "Filedrop only"; +"_share_hide_download_" = "Prevent download"; +//"_share_note_recipient_" = "YOUR MESSAGE"; +"_share_note_recipient_" = "Note to recipient"; +"_shareLinksearch_placeholder_" = "Contact name or email address"; +"_shareLinksearch_mail_placeholder_" = "Type a name or an email and press Enter"; "_new_comment_" = "New comment …"; "_edit_comment_" = "Edit comment"; "_delete_comment_" = "Delete comment"; -"_share_read_only_" = "View only"; -"_share_editing_" = "Can edit"; +"_share_reshare_allowed_" = "Resharing is allowed."; +"_share_reshare_not_allowed_" = "Resharing is not allowed."; +"_sharing_message_" = "You can create links or send shares by mail. If you invite MagentaCLOUD users, you have more opportunities for collaboration."; +"_create_link_" = "Create Link"; +"personal_share_by_mail" = "Personal share by mail"; +"_your_shares_" = "Your Shares"; +"_share_linklabel_" = "Link '%@'"; +"_share_link_folder_" = "Link to folder"; +"_share_link_file_" = "Link to file"; +"no_shares_created" = "No shares created yet."; +"_advance_permissions_" = "Advanced permissions"; +"_send_new_email_" = "Send new email"; +"_apply_changes_" = "Apply changes"; +"_send_share_" = "Send share"; +"_PERMISSIONS_" = "PERMISSIONS"; +"share_editing_message" = "There are no editing functions for files unless you share them with MagentaCLOUD users."; +"_file_drop_message_" = "With File drop, only uploading is allowed. Only you can see files and folders that have been uploaded."; +"_custom_link_label" = "Your custom link label"; +"_set_password_" = "Set password"; +"_share_download_limit_" = "Download Limit"; +"_share_download_limit_placeholder_" = "Enter download limit"; +"_share_download_limit_alert_empty_" = "Download limit cannot be empty."; +"_share_download_limit_alert_zero_" = "Download limit should be greater than 0."; +"_share_allow_editing_" = "Allow editing"; "_share_allow_upload_" = "Allow upload and editing"; -"_share_file_drop_" = "File request"; "_share_secure_file_drop_" = "Secure file drop (upload only)"; -"_share_hide_download_" = "Hide download"; "_share_allowed_downloads_" = "Allowed downloads"; "_share_limit_download_" = "Limit downloads"; "_remaining_" = "%d remaining"; "_share_password_protect_" = "Password protection"; "_share_expiration_date_" = "Set expiration date"; -"_share_note_recipient_" = "Note to recipient"; +"_share_delete_sharelink_" = "Delete link"; "_share_add_sharelink_" = "Add another link"; "_share_can_read_" = "Read"; -"_share_can_reshare_" = "Share"; -"_share_can_create_" = "Create"; -"_share_can_change_" = "Edit"; -"_share_can_delete_" = "Delete"; -"_share_can_download_" = "Allow download and sync"; -"_share_unshare_" = "Delete share"; +"_share_can_reshare_" = "Allow resharing"; +"_share_can_create_" = "Allow creating"; +"_share_can_change_" = "Allow editing"; +"_share_can_delete_" = "Allow deleting"; +"_share_can_download_" = "Allow download"; +"_share_unshare_" = "Unshare"; "_share_internal_link_" = "Internal link"; +"_share_internal_link_des_" = "Only works for users with access to this file/folder"; +"_share_reshare_disabled_" = "You are not allowed to reshare this file/folder"; +"_share_reshare_restricted_" = "Note: You only have limited permission to reshare this file/folder"; +"_share_can_download_" = "Allow download"; "_share_internal_link_des_" = "Only works for users with access to this file/folder."; "_share_reshare_disabled_" = "You are not allowed to reshare this file/folder."; "_share_reshare_restricted_" = "Note: You only have limited permission to reshare this file/folder."; +"_create_new_link_" = "Create new link"; +"_share_send_link_by_mail_" = "Send link by mail"; +"_share_or_" = "or"; +"_share_copy_link_" = "Copy link"; +"_share_shared_with_" = "Shared with"; +"_share_quick_permission_everyone_can_just_upload_" = "Everyone can just upload"; +"_share_quick_permission_everyone_can_edit_" = "Everyone can edit"; +"_share_quick_permission_everyone_can_only_view_" = "Everyone can only view"; +"_share_quick_permission_everyone_can_just_upload_short_" = "Just upload"; +"_share_quick_permission_everyone_can_edit_short_" = "Can edit"; +"_share_quick_permission_everyone_can_only_view_short_" = "Only view"; +"_share_received_shares_text_" = "This file / folder was shared with you by"; +"_share_no_shares_text_" = "You have not yet shared your file/folder. Share to give others access"; +"_share_size_" = "Size: "; +"_share_modified_" = "Modified: "; +"_share_created_" = "Created: "; +"_share_uploaded_" = "Uploaded: "; +"_share_details_" = "Details"; +"_share_via_link_menu_password_label_" = "Password protect (%1$s)"; +"_share_link_empty_exp_date_" = "You must select expiration date."; +"_share_link_empty_note_message_" = "Please enter note."; "_remote_" = "Remote"; "_remote_group_" = "Remote group"; "_conversation_" = "Conversation"; +//"_share_can_reshare_" = "Allow resharing"; +//"_share_can_create_" = "Allow creating"; +//"_share_can_change_" = "Allow editing"; +//"_share_can_delete_" = "Allow deleting"; +//"_share_unshare_" = "Unshare"; +//"_share_can_download_" = "Allow download"; "_no_transfer_" = "No transfers yet"; "_no_transfer_sub_" = "Uploads and downloads from this device will show up here"; +"_no_activity_" = "No activity yet"; "_no_activity_footer_" = "No more activities to load"; "_transfers_" = "Transfers"; "_activity_" = "Activity"; +"_activity_file_not_present_" = "File no longer present"; "_trash_file_not_found_" = "It seems that the file is not in the Trash. Go to the Trash to update it and try again."; "_list_shares_" = "Shares"; "_list_shares_no_files_" = "No shares yet"; "_tutorial_list_shares_view_" = "Files and folders you share will show up here"; +"_create_synchronization_" = "Create synchronization"; "_offline_" = "Offline"; +"_local_storage_" = "Local storage"; +"_local_storage_no_record_" = "No files yet"; "_upload_photos_videos_" = "Upload photos or videos"; "_upload_file_" = "Upload file"; +"_upload_file_text_" = "Create text file"; "_create_nextcloudtext_document_" = "Create text document"; +"_save_document_picker_" = "Save here"; +"_destination_folder_" = "Destination folder"; "_use_folder_auto_upload_" = "Use the \"Auto upload\" folder as destination"; +"_rename_filename_" = "Rename"; "_filename_" = "Filename"; "_enter_filename_" = "Enter filename …"; "_default_preview_filename_footer_" = "Example preview of filename: IMG_0001.JPG"; "_filename_header_" = "Enter filename"; "_preview_filename_" = "Example preview of filename. You can use the mask %@ for date/time."; "_add_filenametype_" = "Specify type in filename"; +"_filenametype_photo_video_" = "Photo/Video"; "_maintain_original_filename_" = "Maintain original filename"; +"_modify_photo_" = "Modify photo"; "_notifications_" = "Notifications"; +"_no_notification_" = "No notifications yet"; +"_autoupload_filename_title_" = "Auto upload filename"; "_untitled_" = "Untitled"; +"_untitled_txt_" = "Untitled.txt"; +"_text_upload_title_" = "Upload text file"; "_e2e_settings_title_" = "Encryption"; "_e2e_settings_" = "End-to-end encryption"; "_e2e_settings_start_" = "Start end-to-end encryption"; +"_e2e_settings_not_available_" = "End-to-end encryption not available"; "_e2e_settings_activated_" = "End-to-end encryption activated"; "_e2e_server_disabled_" = "End-to-end encryption app disabled on server"; "_e2e_settings_view_passphrase_" = "All 12 words together make a very strong password, letting only you view and make use of your encrypted files. Please write it down and keep it somewhere safe."; "_e2e_settings_read_passphrase_" = "Read passphrase"; "_e2e_settings_lock_not_active_" = "Lock not active. Go to \"Settings\" and activate it."; "_e2e_settings_the_passphrase_is_" = "The passphrase is:"; -"_e2e_passphrase_request_title_" = "Request passphrase"; +"_e2e_passphrase_request_title_" = "Enter passphrase"; //"Request passphrase"; "_e2e_passphrase_request_message_" = "Insert the 12 words"; "_e2e_settings_remove_" = "Remove the encryption locally"; "_e2e_settings_remove_message_" = "Confirm removal of encryption along with the passphrase."; @@ -470,23 +921,79 @@ "_e2e_goto_settings_for_enable_" = "This is an encrypted directory, go to \"Settings\" and enable end-to-end encryption"; "_e2e_error_" = "An internal end-to-end encryption error occurred."; "_e2e_in_upload_" = "Upload in progress, please wait for all files to be transferred."; +"_e2e_delete_folder_not_permitted_" = "Deletion of the directory marked as \"encrypted\" is not allowed"; +"_e2e_error_encode_metadata_" = "Serious internal error in encoding metadata"; +"_e2e_error_decode_metadata_" = "Serious internal error in decoding metadata"; +"_e2e_error_create_encrypted_" = "Could not create encrypted file"; +"_e2e_error_update_metadata_" = "Update metadata error"; +"_e2e_error_store_metadata_" = "Could not save metadata"; +"_e2e_error_send_metadata_" = "Could not send metadata"; +"_e2e_error_delete_metadata_" = "Could not delete metadata"; +"_e2e_error_get_metadata_" = "Could not fetch metadata"; +"_e2e_error_not_enabled_" = "Serious internal error. End-to-end encryption not enabled"; +"_e2e_error_record_not_found_" = "Serious internal error. Records not found"; +"_e2e_error_unlock_" = "Could not unlock folder"; +"_e2e_error_lock_" = "Could not lock folder"; +"_e2e_error_delete_mark_folder_" = "Decrypt marked folder"; +"_e2e_error_mark_folder_" = "Encrypt folder"; +"_e2e_error_directory_not_empty_" = "The directory is not empty"; +"_e2e_error_not_move_" = "It is not possible move files to encrypted directory"; +"_e2e_error_not_versionwriteable_" = "The E2EE version of the server is not compatible with this client"; +"_start_e2e_encryption_1_" = "To set up end-to-end encryption, they must first set up the PIN lock to prevent unauthorised people from accessing your key."; +"_start_e2e_encryption_2_" = "After starting the encryption, a randomly generated word sequence (passphrase) of 12 words is displayed. This remains in this app and can be displayed again. Nevertheless, we recommend that you write down the passphrase."; +"_start_e2e_encryption_3_" = "The passphrase is your personal password with which you can access encrypted data in your MagentaCLOUD or enable access to these files on other devices such as your PC."; +"_read_passphrase_description_" = "Here you can display the passphrase again and also copy it. You need the passphrase if you want to decrypt the data on another device with access to MagentaCLOUD, for example your PC or another smartphone or tablet."; +"_remove_passphrase_desc_1_" = "You can remove the passphrase on this device. This will not affect the encrypted content, but this device will no longer be able to decrypt your data."; +"_remove_passphrase_desc_2_" = "You can re-enter the passphrase here at any time to ensure access to your encrypted content from this device."; +"_e2e_error_incorrect_passphrase_" = "Wrong password?"; +"_e2e_error_passphrase_title" = "Error while decrypting."; "_scans_document_" = "Scan document"; -"_scanned_images_" = "Scanned images"; +//"_scanned_images_" = "Scanned images"; +"_scanned_images_" = "Scanned documents"; "_scan_document_pdf_page_" = "Page"; "_scan_label_document_zone_" = "Tap or drag images down for document creation"; "_filter_document_" = "Document"; "_filter_original_" = "Original"; +"_filter_bn_" = "Black & White"; +"_filter_grayscale_" = "Grayscale"; "_quality_image_title_" = "Preview image quality"; +"_quality_high_" = "Large file size of high quality"; +"_quality_medium_" = "Average file size of medium quality"; +"_quality_low_" = "Small file size of low quality"; +"_file_type_" = "File type"; +"_pdf_password_" = "PDF Password"; "_file_creation_" = "File creation"; "_delete_all_scanned_images_" = "Delete all scanned images"; "_text_recognition_" = "Text recognition"; "_all_files_" = "All files"; "_personal_files_" = "Personal Files"; +/* The title on the navigation bar of the Scanning screen. */ +"wescan.scanning.title" = "Scanning"; +/* The "Next" button on the right side of the navigation bar on the Edit screen. */ +"wescan.edit.button.next" = "Next"; +/* The title on the navigation bar of the Edit screen. */ +"wescan.edit.title" = "Edit Scan"; +/* The "Done" button on the right side of the navigation bar on the Review screen. */ +"wescan.review.button.done" = "Done"; +/* The title on the navigation bar of the Review screen. */ +"wescan.review.title" = "Review"; + "_trash_view_" = "Deleted files"; +"_trash_restore_all_" = "Restore all files"; +"_trash_delete_all_" = "Empty trash"; +"_trash_delete_permanently_" = "Delete permanently"; +"_trash_delete_all_description_" = "Do you want to empty the trash bin?";"_trash_no_trash_" = "No files deleted"; "_empty_trash_" = "Empty trash"; +"_trash_restore_all_" = "Restore all files"; +"_trash_delete_all_" = "Empty trash"; +"_trash_delete_permanently_" = "Delete permanently"; +"_trash_delete_all_description_" = "Do you want to empty the trash bin?"; "_trash_no_trash_" = "No files deleted"; "_trash_no_trash_description_" = "You can restore deleted files from here"; +"_trash_restore_selected_" = "Restore selected files"; +"_trash_delete_selected_" = "Delete selected files"; +"_recover_" = "Recover"; "_confirm_delete_selected_" = "Are you sure you want to delete the selected items?"; "_manage_file_offline_" = "Manage offline files"; "_set_available_offline_" = "Set as available offline"; @@ -502,7 +1009,62 @@ "_log_in_" = "Log in"; "_sign_up_" = "Sign up with provider"; "_host_your_own_server" = "Host your own server"; +"_unauthorized_" = "Unauthorized"; +"_bad_username_password_" = "Wrong username or password"; +"_cancelled_by_user" = "Transfer canceled"; +"_error_folder_destiny_is_the_same_" = "It is not possible to move the folder into itself"; +"_error_not_permission_" = "You don't have permission to complete the operation"; +"_error_path_" = "Unable to open this file or folder. Please make sure it exists"; +"_file_upload_not_exitst_" = "The file that you want to upload does not exist"; +"_forbidden_characters_from_server_" = "The name contains at least one invalid character"; +"_error_not_modified_" = "Resource not modified"; +"_not_connected_internet_" = "Server connection error"; +"_not_possible_connect_to_server_" = "It is not possible to connect to the server at this time"; +"_not_possible_create_folder_" = "Folder could not be created"; +"_server_down_" = "Could not establish contact with server"; +"_time_out_" = "Timeout, try again"; +"_unknow_response_server_" = "Unexpected response from server"; +"_user_authentication_required_" = "User authentication required"; +"_file_directory_locked_" = "File or directory locked"; +"_ssl_certificate_untrusted_" = "The certificate for this server is invalid"; +"_ssl_certificate_changed_" = "The certificate for this server seems to have changed"; +"_internal_server_" = "Internal server error"; +"_file_already_exists_" = "Could not complete the operation, a file with the same name exists"; +"_file_folder_not_exists_" = "The source file wasn't found at the specified path"; +"_folder_contents_nochanged_" = "The folder contents have not changed"; +"_images_invalid_converted_" = "The image is invalid and cannot be converted to a thumbnail"; +"_method_not_expected_" = "Unexpected request method"; +"_reauthenticate_user_" = "Access expired, log in again"; +"_server_error_retry_" = "The server is temporarily unavailable"; +"_too_many_files_" = "Too many files would be involved in this operation"; +"_too_many_request_" = "Sending too many requests caused the rate limit to be reached"; +"_user_over_quota_" = "Storage quota is reached"; +"_ssl_connection_error_" = "Connection SSL error, try again"; +"_bad_request_" = "Bad request"; +"_webdav_locked_" = "WebDAV Locked: Trying to access locked resource"; +"_error_user_not_available_" = "The user is no longer available"; +"_server_response_error_" = "Server response content error"; +"_no_nextcloud_found_" = "Server not found"; +"_error_decompressing_" = "Error during decompressing. Unknown compression method or the file is corrupt"; +"_error_json_decoding_" = "Serious internal error in decoding metadata (The data couldn't be read because it isn't in the correct format.)"; +"_error_check_remote_user_" = "Server responded with an error. Please log in again"; +"_request_entity_too_large_" = "The file is too large"; +"_not_possible_download_" = "It is not possible to download the file"; +"_not_possible_upload_" = "It is not possible to upload the file"; +"_error_files_upload_" = "Error uploading files"; +"_method_not_allowed_" = "The requested method is not supported"; "_invalid_url_" = "Invalid server URL"; +"_invalid_literal_" = "Invalid search string"; +"_invalid_date_format_" = "Invalid date format"; +"_invalid_data_format_" = "Invalid data format"; +"_error_decode_xml_" = "Invalid response, error decode XML"; +"_internal_generic_error_" = "internal error"; +"_editor_unknown_" = "Failed to open file: Editor is unknown"; +"_err_file_not_found_" = "File not found, removed"; +"_err_e2ee_app_version_" = "The app version of end-to-end encryption is not compatible with the server, please update your server"; +"_err_permission_microphone_" = "Please allow Microphone usage from Settings"; +"_err_permission_photolibrary_" = "Please allow Photos from Settings"; +"_err_permission_locationmanager_" = "Please allow Location - Always from Settings"; "_ssl_certificate_untrusted_" = "The certificate for this server is invalid."; "_ssl_certificate_changed_" = "The certificate for this server seems to have changed."; "_file_already_exists_" = "Could not complete the operation, a file with the same name exists."; @@ -510,11 +1072,20 @@ "_internal_generic_error_" = "internal error."; "_err_permission_microphone_" = "Please allow Microphone usage from Settings."; "_err_permission_photolibrary_" = "Please allow Photos from Settings."; +//"_ssl_certificate_untrusted_" = "The certificate for this server is invalid."; +//"_ssl_certificate_changed_" = "The certificate for this server seems to have changed."; +//"_file_already_exists_" = "Could not complete the operation, a file with the same name exists."; +//"_error_files_upload_" = "Error uploading files."; +//"_internal_generic_error_" = "internal error."; +//"_err_permission_microphone_" = "Please allow Microphone usage from Settings."; +//"_err_permission_photolibrary_" = "Please allow Photos from Settings."; "_qrcode_not_authorized_" = "This app is not authorized to use the Back Camera."; "_qrcode_not_supported_" = "QR code not supported by the current device."; "_create_voice_memo_" = "Create voice memo"; "_voice_memo_start_" = "Tap to start"; "_voice_memo_stop_" = "Tap to stop"; +"_voice_memo_filename_" = "Voice memo"; +"_voice_memo_title_" = "Upload voice memo"; "Enter Passcode" = "Enter Passcode"; "Enter a new passcode" = "Enter a new passcode"; "Confirm new passcode" = "Confirm new passcode"; @@ -542,10 +1113,15 @@ "_3_months_" = "3 months"; "_1_month_" = "1 month"; "_1_week_" = "1 week"; +"_1_day_" = "1 day"; "_monthly_" = "Monthly"; "_yearly_" = "Yearly"; +"_weekly_" = "Weekly"; "_daily_" = "Daily"; -"_used_space_" = "Used space:"; +"_day_" = "Day"; +"_used_space_" = "Used space"; +"_open_in_onlyoffice_" = "Open in ONLYOFFICE"; +"_open_in_collabora_" = "Open with Collabora Online"; "_login_address_detail_" = "The link to your %@ web interface when you open it in the browser."; "_go_to_page_" = "Go to page"; "_page_" = "Page"; @@ -553,16 +1129,26 @@ "_invalid_page_" = "Invalid Page"; "_the_entered_page_number_does_not_exist_" = "The entered page number does not exist."; "_error_something_wrong_" = "Something went wrong"; +"_resolution_" = "Resolution"; "_try_download_full_resolution_" = "Download full resolution image"; "_full_resolution_image_info_" = "This may reveal more information about the photo."; "_download_audio_" = "Download the audio locally"; "_copied_path_" = "Copied path"; +"_copy_path_" = "Copy path"; +"_certificates_" = "Certificates"; "_privacy_screen_" = "Splash screen when app inactive"; +"_saving_" = "Saving …"; +"_video_not_streamed_" = "The server does not allow video streaming, do you want to download it?"; +"_video_not_streamed_e2ee_" = "The server does not allow video streaming because it is encrypted, do you want to download it?"; +"_scan_" = "Scan"; "_in_" = "in"; "_enter_passphrase_" = "Enter passphrase (12 words)"; "_show_more_results_" = "Show more results"; "_waiting_for_" = "Waiting for:"; +"_waiting_" = "Waiting …"; "_reachable_wifi_" = "network reachable via Wi-Fi or cable"; +"_ITMS-90076_" = "Due to a change in the Nextcloud application identifier, the settings and password for accessing your cloud are reset, so please re-enter your account data and check your Settings. We are sorry about that."; +"_password_not_present_" = "Please re-insert your credentials."; "_copy_passphrase_" = "Copy passphrase"; "_ok_copy_passphrase_" = "OK and copy passphrase"; "_select_color_" = "Select the color"; @@ -570,10 +1156,17 @@ "_description_dashboardwidget_" = "Having the Dashboard always at your fingertips has never been easier."; "_description_fileswidget_" = "View your recent files and use the toolbar to speed up your operations."; "_description_toolbarwidget_" = "A toolbar to speed up your operations."; +"_no_data_available_" = "No data available"; +"_widget_available_nc25_" = "Widget only available starting with server version 25"; +"_keep_running_" = "Keep the app running for a better user experience"; +"_recent_activity_" = "Recent activity"; "_title_lockscreenwidget_" = "Status"; "_description_lockscreenwidget_" = "Keep an eye on available space and recent activity"; - +"_no_items_" = "No items"; +"_check_back_later_" = "Check back later"; +"_exporting_video_" = "Exporting video … Tap to cancel."; "_keep_running_" = "Keep the app running for a better user experience."; +//"_keep_running_" = "Keep the app running for a better user experience."; "_apps_nextcloud_detect_" = "Detected %@ apps"; "_add_existing_account_" = "Other %@ Apps has been detected, do you want to add an existing account?"; "_status_in_progress_" = "Status reading in progress …"; @@ -588,16 +1181,35 @@ "_mobile_config_" = "Download the configuration profile"; "_calendar_contacts_footer_warning_" = "Configuration profile can only be downloaded if Safari is set as default browser."; "_calendar_contacts_footer_" = "After downloading the profile you can install it from Settings."; +"_preview_" = "Preview"; +"_crop_" = "Crop"; "_modify_image_desc_" = "Tap on a file to modify or rename."; "_message_disable_livephoto_" = "This image is a Live Photo, changing it will lose the Live Photo effect."; +//"_modify_image_desc_" = "Tap the image for modify"; +//"_message_disable_livephoto_" = "This image is a Live Photo, changing it will lose the Live effect"; "_enable_livephoto_" = "Enable Live Photo"; "_disable_livephoto_" = "Disable Live Photo"; "_undo_modify_" = "Undo modifying"; +"_unauthorizedFilesPasscode_" = "Files app cannot be used with an activated passcode"; +"_disableFilesApp_" = "Files app cannot be used because it is disabled"; +"_reset_application_done_" = "Reset application, done."; "_rename_already_exists_" = "A file with this name already exists."; +"_created_" = "Created"; +"_recipients_" = "Recipients"; +"_are_sure_" = "Are you sure?"; +"_creation_" = "Creation"; +"_modified_" = "Modified"; "_group_folders_" = "Group folders"; "_play_from_files_" = "Play movie from a file"; "_play_from_url_" = "Play movie from URL"; "_valid_video_url_" = "Insert a valid URL"; +"_deletion_progess_" = "Deletion in progress"; +"_copying_progess_" = "Copying in progress"; +"_moving_progess_" = "Moving in progress"; +"_chunk_enough_memory_" = "It seems there is not enough space to send the file"; +"_chunk_create_folder_" = "The file could not be sent, please check the server log"; +"_chunk_files_null_" = "The file for sending could not be created"; +"_chunk_file_null_" = "The file could not be sent"; "_chunk_move_" = "The sent file could not be reassembled, please check the server log."; "_download_image_" = "Download image"; "_download_video_" = "Download video"; @@ -606,6 +1218,9 @@ "_reset_wrong_passcode_desc_" = "Use \"Reset application\" to remove all accounts and local data after %d failed code entry attempts."; "_deviceOwnerAuthentication_" = "The biometric sensor has been temporarily disabled due to multiple failed attempts. Enter the device passcode to re-enable the sensor."; "_virus_detect_" = "Virus detected. Upload cannot be completed!"; +"_zoom_" = "Zoom"; +"_zoom_in_" = "Zoom in"; +"_zoom_out_" = "Zoom out"; "_select_photos_" = "Select photos"; "_selected_photo_" = "selected photo"; "_selected_photos_" = "selected photos"; @@ -621,12 +1236,15 @@ "_account_settings_" = "Account settings"; "_users_" = "Users"; "_users_footer_" = "Every time the app is reactivated, the account will be requested."; +"_additional_view_options_" = "Additional view options"; "_while_charging_" = "While charging"; +"_downloading_" = "Downloading"; +"_additional_options_" = "Additional options"; "_keep_screen_awake_" = "Keep screen awake\nwhile transferring files"; "_error_not_found_" = "The requested resource could not be found"; "_error_conflict_" = "The request could not be completed due to a conflict with the current state of the resource"; "_error_precondition_" = "The server does not meet one of the preconditions of the requester"; -"_downloading_" = "Downloading"; + "_additional_options_" = "Additional options"; "_unauthorizedFilesPasscode_" = "Files app cannot be used with an activated passcode"; "_disableFilesApp_" = "Files app cannot be used because it is disabled"; @@ -637,12 +1255,32 @@ "_recent_activity_" = "Recent activity"; "_maintenance_mode_" = "The server is currently in maintenance mode, which may take a while."; "_account_disabled_" = "Account disabled"; + +//Video +"_select_trace_" = "Select the trace"; +"_video_processing_" = "Video processing"; +"_video_being_processed_" = "Video being processed …"; +"_downloading_" = "Downloading"; +"_download_error_" = "Download error"; +"_subtitle_" = "Subtitle"; +"_dts_to_ac3_" = "The DTS is not supported, it requires a conversion to Dolby Digital"; +"_reuired_conversion_" = "This video takes a long time to convert."; +"_stay_app_foreground_" = "Keep the app in the foreground …"; +"_conversion_available_" = "The conversion is always available on menu"; +"_video_format_not_recognized_" = "This video needs to be processed to be played, do you want to do it now?"; +"_video_must_download_" = "This video needs to be downloaded and processed to be played, do you want to do it now?"; +"_conversion_max_compatibility_" = "Max compatibility, the conversion can take much longer"; +"_video_tap_for_close_" = "A slight pressure to close the processing"; +"_subtitle_not_found_" = "Subtitle not found"; +"_disable_" = "Disable"; +"_subtitle_not_dowloaded_" = "There are subtitles not downloaded locally"; "_user_" = "User"; "_add_subtitle_" = "Add an external subtitle"; "_add_audio_" = "Add an external audio"; "_upload_foreground_msg_" = "Do not close %@ to complete the transfer …"; "_upload_background_msg_" = "Files upload in progress …"; "_create_folder_error_" = "An error has occurred while creating the folder:\n%@.\n\nPlease resolve the issue as soon as possible.\n\nAll uploads are suspended until the problem is resolved.\n"; +"_rename_file_error_" = "An error has occurred while renaming the file:\n%@."; "_creating_dir_progress_" = "Creating directories in progress … keep the application active."; "_creating_db_photo_progress_" = "Creating photo archive in progress … keep the application active."; "_account_unauthorized_" = "Warning, %@, you are not authorized, your account has been deleted, if you have changed your password, re-authenticate."; @@ -656,7 +1294,7 @@ "_recommended_files_" = "Recommended Files"; "_propagate_layout_" = "Apply the following change to all subfolders"; "_version_mismatch_error_" = "Version change: please open the main app to update the data."; -"_check_auto_upload_" = "Automatic photo upload is currently disabled.\nWith Nextcloud iOS you can automatically back up your photos and keep them safe on your server.\nJust open Settings → Auto Upload, choose the options you prefer, and turn it on with the \"%@\" button."; +"_check_auto_upload_" = "Automatic photo upload is currently disabled.\nWith MagentaCLOUD iOS you can automatically back up your photos and keep them safe on your server.\nJust open Settings → Auto Upload, choose the options you prefer, and turn it on with the \"%@\" button."; "_cancel_all_request_" = "Are you sure you want to cancel all transfers?"; "_cancel_all_" = "Cancel All"; "_dismiss_" = "Dismiss"; @@ -683,6 +1321,7 @@ "_tip_open_mediadetail_" = "Swipe up to show the details"; "_tip_autoupload_button_" = "Configure the options as you prefer (folder, Wi-Fi, new content, subfolders) and tap ‘Turn on auto uploading’. You can stop it at any time, adjust the settings, and enable it again."; +"_tip_autoupload_" = "You can now Auto Upload specific Albums from your Photos. You can enable Auto Upload here"; // MARK: Accessibility @@ -695,15 +1334,42 @@ You can stop it at any time, adjust the settings, and enable it again."; "_on_" = "On"; // a11y: On/Off "_off_" = "Off"; +"_grid_view_" = "Show grid view"; +"_list_view_" = "Show list view"; "_list_" = "List"; "_icons_" = "Icons"; +// MARK: Plan customer +"_leave_plan_title" = "We're sorry to see you go"; +"_leave_plan_description" = "You'll no longer have access to:"; +"_current_plan_" = "Current Plan"; +"_billing_plan_" = "Billing Plan"; +"_keep_plan_" = "Keep Plan"; +"_leave_plan_" = "Leave Plan"; +"_change_plan_" = "Change Plan"; +"_manage_plan_" = "Manage Plan"; +"_purchase_plan_" = "Purchase Plan"; +"_restore_plan_" = "Restore Purchased Plan"; +"_purchase_plan_description_" = "Purchases have been restored"; +"_choose_plan_" = "You should choose a plan in order to purchase it."; +"_already_plan_" = "The selected plan has already been bought."; +"_change_billing_" = "Change Billing"; +"_payment_method_" = "Payment Method"; + +// MARK: Mantis library +"Mantis.Done" = "Done"; +"Mantis.Cancel" = "Cancel"; +"Mantis.Reset" = "Reset"; +"Mantis.Original" = "Original"; +"Mantis.Square" = "Square"; + // MARK: Assistant "_assistant_task_unknown_" = "Unknown"; "_assistant_task_scheduled_" = "Scheduled"; "_assistant_task_in_progress_" = "In Progress"; "_assistant_task_completed_" = "Completed"; "_assistant_task_failed_" = "Failed"; +"_all_" = "All"; "_input_" = "Input"; "_output_" = "Output"; "_task_details_" = "Task details"; @@ -750,6 +1416,55 @@ You can stop it at any time, adjust the settings, and enable it again."; "_auto_upload_all_photos_warning_message_" = "This can take some time to process depending on the amount of photos."; "_item_with_same_name_already_exists_" = "An item with the same name already exists."; +// MARK: Privacy Policy +"_privacy_settings_title_" = "Privacy Settings"; +"_privacy_help_text_after_login_" = "This app uses Cookies and similiar technolgies (tools). By clicking Accept, you accept the processing and also the Transfer of your data to third parties. The data will be used for Analysis, retargeting and to Display personalized Content and Advertising on sites and third-party sites. You can find further informatin, including Information on data processing by third-party Providers, in the settings and in our Privacy Policy.You can reject the use of the Tools or customize them at any time in the Settings."; +"_key_privacy_help_" = "Privacy Policy"; +"_key_reject_help_" = "reject"; +"_key_settings_help_" = "Settings"; +"_accept_button_title_" = "Accept"; +"_privacy_settings_help_text_" = "To optimize your app, we collect anonymous data. For this we use software solutions of different partners. We would like to give you full transparency and decision-making power over the processin and collection of your anonymized usage data. You can also change your settings at any time later in the app settings under data protection. Please note, however, that data collection makes a considerable contribution to the optimization of this app and you prevent this optimization by preventing data transmission."; +"_required_data_collection_" = "Required data collection"; +"_required_data_collection_help_text_" = "The collection of this data is necessary to be able to use essential functions of the app."; +"_analysis_data_acqusition_" = "Analysis-data acqusition for the design"; +"_analysis_data_acqusition_help_text_" = "This data helps us to optimize the app usage for you and to identify system crashes and errors more quickly."; + +// MARK: Collabora +"_prefix_upload_path_" = "MagentaCLOUD/"; +"_please_enter_file_name_" = "Please enter the file name"; + +// MARK: Scan +"_location_" = "Location"; +"_prefix_upload_path_" = "MagentaCLOUD/"; + +"_save_with_text_recognition_" = "SAVE WITH TEXT RECOGNITION (OCR)"; +"_pdf_with_ocr_" = "PDF (OCR)"; +"_text_file_ocr_" = "Textfile (txt)"; +"_save_without_text_recognition_" = "SAVE WITHOUT TEXT RECOGNITION"; +"_pdf_" = "PDF"; +"_jpg_" = "JPG"; +"_png_" = "PNG"; +"_set_password_" = "Set password"; +"_no_password_warn_" = "Please enter a password for the PDF you want to create or disable the function."; +"_saved_info_alert_" = "Saving will take some time, especially if you have selected several pages and file formats."; +"_no_file_type_selection_error_" = "Please select at least one filetype"; +"_no_internet_alert_message_" = "A data connection is not currently allowed."; +"_no_internet_alert_title_" = "Connection error"; +"_auto_upload_help_text_" = "With this option, you upload your photos or videos to the same folder that you selected for \"Automatic upload.\""; +"_item_with_same_name_already_exists_" = "An item with the same name already exists."; + +// MARK: Dashboard +"_shared_" = "Shared"; +"_recieved_" = "Received"; + +// MARK: App Updater +"update_available" = "Update available"; +"update_description" = "MagentaCLOUD version %@ is now available"; +"update" = "Update"; +"not_now" = "Not Now"; + +"_prompt_insert_file_name" = "Please enter filename"; + // MARK: Migration Multi Domains "_preparing_migration_" = "Preparing migration …";