diff --git a/Assets/App Icon/Icon-Dark-1024x1024.png b/Assets/App Icon/Icon-Dark-1024x1024.png new file mode 100644 index 000000000..1125717f7 Binary files /dev/null and b/Assets/App Icon/Icon-Dark-1024x1024.png differ diff --git a/Assets/App Icon/Icon-Dark-70px@1x.png b/Assets/App Icon/Icon-Dark-70px@1x.png new file mode 100644 index 000000000..b0f48a9cd Binary files /dev/null and b/Assets/App Icon/Icon-Dark-70px@1x.png differ diff --git a/Assets/App Icon/Icon-Dark-70px@2x.png b/Assets/App Icon/Icon-Dark-70px@2x.png new file mode 100644 index 000000000..86f5dadd8 Binary files /dev/null and b/Assets/App Icon/Icon-Dark-70px@2x.png differ diff --git a/Assets/App Icon/Icon-Dark-70px@3x.png b/Assets/App Icon/Icon-Dark-70px@3x.png new file mode 100644 index 000000000..9430801a9 Binary files /dev/null and b/Assets/App Icon/Icon-Dark-70px@3x.png differ diff --git a/Assets/App Icon/Icon-Light-1024x1024.png b/Assets/App Icon/Icon-Light-1024x1024.png new file mode 100644 index 000000000..0170cf262 Binary files /dev/null and b/Assets/App Icon/Icon-Light-1024x1024.png differ diff --git a/Assets/App Icon/Icon-Light-70px@1x.png b/Assets/App Icon/Icon-Light-70px@1x.png new file mode 100644 index 000000000..dab426d12 Binary files /dev/null and b/Assets/App Icon/Icon-Light-70px@1x.png differ diff --git a/Assets/App Icon/Icon-Light-70px@2x.png b/Assets/App Icon/Icon-Light-70px@2x.png new file mode 100644 index 000000000..e5ba8b00e Binary files /dev/null and b/Assets/App Icon/Icon-Light-70px@2x.png differ diff --git a/Assets/App Icon/Icon-Light-70px@3x.png b/Assets/App Icon/Icon-Light-70px@3x.png new file mode 100644 index 000000000..4599b3de8 Binary files /dev/null and b/Assets/App Icon/Icon-Light-70px@3x.png differ diff --git a/Assets/App Icon/Icon-Tinted-1024x1024.png b/Assets/App Icon/Icon-Tinted-1024x1024.png new file mode 100644 index 000000000..65e77c351 Binary files /dev/null and b/Assets/App Icon/Icon-Tinted-1024x1024.png differ diff --git a/Assets/App Icon/piwigoIcon (lens alone).afdesign b/Assets/App Icon/Icon-squared.afdesign similarity index 100% rename from Assets/App Icon/piwigoIcon (lens alone).afdesign rename to Assets/App Icon/Icon-squared.afdesign diff --git a/Assets/App Icon/iOS 18 and iPadOS 18 App Icon.fig b/Assets/App Icon/iOS 18 and iPadOS 18 App Icon.fig new file mode 100644 index 000000000..c3c45dfa7 Binary files /dev/null and b/Assets/App Icon/iOS 18 and iPadOS 18 App Icon.fig differ diff --git a/Assets/App Icon/icon-1024.png b/Assets/App Icon/icon-1024.png deleted file mode 100644 index 3a7e07baf..000000000 Binary files a/Assets/App Icon/icon-1024.png and /dev/null differ diff --git a/Assets/App Icon/icon-20.png b/Assets/App Icon/icon-20.png deleted file mode 100644 index a2391bf83..000000000 Binary files a/Assets/App Icon/icon-20.png and /dev/null differ diff --git a/Assets/App Icon/icon-20@2x.png b/Assets/App Icon/icon-20@2x.png deleted file mode 100644 index 48c36585b..000000000 Binary files a/Assets/App Icon/icon-20@2x.png and /dev/null differ diff --git a/Assets/App Icon/icon-20@3x.png b/Assets/App Icon/icon-20@3x.png deleted file mode 100644 index 1f7f19d8c..000000000 Binary files a/Assets/App Icon/icon-20@3x.png and /dev/null differ diff --git a/Assets/App Icon/icon-29.png b/Assets/App Icon/icon-29.png deleted file mode 100644 index 5f43f81c4..000000000 Binary files a/Assets/App Icon/icon-29.png and /dev/null differ diff --git a/Assets/App Icon/icon-29@2x.png b/Assets/App Icon/icon-29@2x.png deleted file mode 100644 index 2e75b977a..000000000 Binary files a/Assets/App Icon/icon-29@2x.png and /dev/null differ diff --git a/Assets/App Icon/icon-29@3x.png b/Assets/App Icon/icon-29@3x.png deleted file mode 100644 index 29bc7de30..000000000 Binary files a/Assets/App Icon/icon-29@3x.png and /dev/null differ diff --git a/Assets/App Icon/icon-40.png b/Assets/App Icon/icon-40.png deleted file mode 100644 index 48c36585b..000000000 Binary files a/Assets/App Icon/icon-40.png and /dev/null differ diff --git a/Assets/App Icon/icon-40@2x.png b/Assets/App Icon/icon-40@2x.png deleted file mode 100644 index 89a56cb39..000000000 Binary files a/Assets/App Icon/icon-40@2x.png and /dev/null differ diff --git a/Assets/App Icon/icon-40@3x.png b/Assets/App Icon/icon-40@3x.png deleted file mode 100644 index be1897685..000000000 Binary files a/Assets/App Icon/icon-40@3x.png and /dev/null differ diff --git a/Assets/App Icon/icon-60.png b/Assets/App Icon/icon-60.png deleted file mode 100644 index 1f7f19d8c..000000000 Binary files a/Assets/App Icon/icon-60.png and /dev/null differ diff --git a/Assets/App Icon/icon-60@2x.png b/Assets/App Icon/icon-60@2x.png deleted file mode 100644 index be1897685..000000000 Binary files a/Assets/App Icon/icon-60@2x.png and /dev/null differ diff --git a/Assets/App Icon/icon-60@3x.png b/Assets/App Icon/icon-60@3x.png deleted file mode 100644 index b0255385f..000000000 Binary files a/Assets/App Icon/icon-60@3x.png and /dev/null differ diff --git a/Assets/App Icon/icon-76.png b/Assets/App Icon/icon-76.png deleted file mode 100644 index e4ccf890a..000000000 Binary files a/Assets/App Icon/icon-76.png and /dev/null differ diff --git a/Assets/App Icon/icon-76@2x.png b/Assets/App Icon/icon-76@2x.png deleted file mode 100644 index 5412bb2db..000000000 Binary files a/Assets/App Icon/icon-76@2x.png and /dev/null differ diff --git a/Assets/App Icon/icon-83.5@2x.png b/Assets/App Icon/icon-83.5@2x.png deleted file mode 100644 index 42d9089c2..000000000 Binary files a/Assets/App Icon/icon-83.5@2x.png and /dev/null differ diff --git a/Assets/App Icon/piwigo.org-icon-edited.svg b/Assets/App Icon/piwigo.org-icon-edited.svg new file mode 100644 index 000000000..0e03967a1 --- /dev/null +++ b/Assets/App Icon/piwigo.org-icon-edited.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/Assets/App Icon/piwigo.org-icon-original.svg b/Assets/App Icon/piwigo.org-icon-original.svg new file mode 100644 index 000000000..e869635d1 --- /dev/null +++ b/Assets/App Icon/piwigo.org-icon-original.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Assets/App Icon/piwigo.org-icon.afdesign b/Assets/App Icon/piwigo.org-icon.afdesign new file mode 100644 index 000000000..d6197c3b8 Binary files /dev/null and b/Assets/App Icon/piwigo.org-icon.afdesign differ diff --git a/Assets/App Icon/piwigoIcon-1024.png b/Assets/App Icon/piwigoIcon-1024.png deleted file mode 100644 index 3a7e07baf..000000000 Binary files a/Assets/App Icon/piwigoIcon-1024.png and /dev/null differ diff --git a/piwigo.xcodeproj/project.pbxproj b/piwigo.xcodeproj/project.pbxproj index 6f9135a7c..417527f32 100644 --- a/piwigo.xcodeproj/project.pbxproj +++ b/piwigo.xcodeproj/project.pbxproj @@ -160,6 +160,7 @@ AD2D959A27D7F14900AC1FD8 /* AppLockViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AD2D959927D7F14900AC1FD8 /* AppLockViewController.storyboard */; }; AD30FD3721316DA8009AA30E /* piwigoScreenshots.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD30FD3621316DA8009AA30E /* piwigoScreenshots.swift */; }; AD311FFA27CB7B730044AC5A /* LoginViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AD311FF927CB7B730044AC5A /* LoginViewController.storyboard */; }; + AD31A73F2C736A9800B37B19 /* ImagePreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD31A73E2C736A9800B37B19 /* ImagePreviewViewController.swift */; }; AD31F5F5242FA75C00BD65FC /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD31F5F4242FA75C00BD65FC /* AboutViewController.swift */; }; AD31F5F7242FABF300BD65FC /* ReleaseNotesViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AD31F5F6242FABF300BD65FC /* ReleaseNotesViewController.storyboard */; }; AD31F5F9242FB76900BD65FC /* ReleaseNotesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD31F5F8242FB76800BD65FC /* ReleaseNotesViewController.swift */; }; @@ -186,6 +187,8 @@ AD3DB4152773958A00017542 /* ImageDescriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD3DB4142773958A00017542 /* ImageDescriptionView.swift */; }; AD3EE6012B811349002B5CDB /* pwg.image.rotate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD3EE6002B811349002B5CDB /* pwg.image.rotate.swift */; }; AD3FF5F42682908000D2F52A /* UIImage+AppTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADCC996425B452670034CB4A /* UIImage+AppTools.swift */; }; + AD400B212C61334200DC9A61 /* PasteboardImagesViewController+FlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD400B202C61334200DC9A61 /* PasteboardImagesViewController+FlowLayout.swift */; }; + AD400B232C6137A200DC9A61 /* PasteboardImagesViewController+Select.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD400B222C6137A200DC9A61 /* PasteboardImagesViewController+Select.swift */; }; AD41530F27C6D35D00DE01CB /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD41530E27C6D35D00DE01CB /* LoginViewController.swift */; }; AD41531527C6D37D00DE01CB /* LoginNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD41531427C6D37C00DE01CB /* LoginNavigationController.swift */; }; AD42568C2ACB584F00D7CCC8 /* VideoControlsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = AD42568B2ACB584F00D7CCC8 /* VideoControlsView.xib */; }; @@ -250,11 +253,11 @@ AD6BA0F12BA5B04200755257 /* LocalImagesViewController+PHPhotoLibraryChangeObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6BA0F02BA5B04200755257 /* LocalImagesViewController+PHPhotoLibraryChangeObserver.swift */; }; AD6BA0F32BA5BDDC00755257 /* LocalImagesViewController+Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6BA0F22BA5BDDC00755257 /* LocalImagesViewController+Data.swift */; }; AD6BA0F52BA5C00500755257 /* LocalImagesViewController+HeaderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6BA0F42BA5C00500755257 /* LocalImagesViewController+HeaderDelegate.swift */; }; - AD6BA0F82BA5C18100755257 /* PasteboardImagesViewController+UICollectionViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6BA0F72BA5C18100755257 /* PasteboardImagesViewController+UICollectionViewDelegate.swift */; }; - AD6BA0FA2BA5C1D600755257 /* PasteboardImagesViewController+UICollectionViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6BA0F92BA5C1D600755257 /* PasteboardImagesViewController+UICollectionViewDataSource.swift */; }; + AD6BA0F82BA5C18100755257 /* PasteboardImagesViewController+Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6BA0F72BA5C18100755257 /* PasteboardImagesViewController+Delegate.swift */; }; + AD6BA0FA2BA5C1D600755257 /* PasteboardImagesViewController+DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6BA0F92BA5C1D600755257 /* PasteboardImagesViewController+DataSource.swift */; }; AD6BA0FC2BA5D01800755257 /* PasteboardImagesViewController+UploadSwitchDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6BA0FB2BA5D01800755257 /* PasteboardImagesViewController+UploadSwitchDelegate.swift */; }; AD6BA0FE2BA5D15900755257 /* PasteboardImagesViewController+Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6BA0FD2BA5D15900755257 /* PasteboardImagesViewController+Data.swift */; }; - AD6BA1002BA5D2FA00755257 /* PasteboardImagesViewController+HeaderReusableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6BA0FF2BA5D2FA00755257 /* PasteboardImagesViewController+HeaderReusableViewDelegate.swift */; }; + AD6BA1002BA5D2FA00755257 /* PasteboardImagesViewController+HeaderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6BA0FF2BA5D2FA00755257 /* PasteboardImagesViewController+HeaderDelegate.swift */; }; AD6BA1042BA5ED1A00755257 /* LocalImagePreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6BA1032BA5ED1A00755257 /* LocalImagePreviewViewController.swift */; }; AD6FD1FA24BEFFAA009AA3B7 /* UploadSwitchViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AD6FD1F924BEFFAA009AA3B7 /* UploadSwitchViewController.storyboard */; }; AD6FD1FC24BF0321009AA3B7 /* UploadSwitchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6FD1FB24BF0321009AA3B7 /* UploadSwitchViewController.swift */; }; @@ -757,6 +760,7 @@ AD30FD3621316DA8009AA30E /* piwigoScreenshots.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = piwigoScreenshots.swift; sourceTree = ""; }; AD30FD3821316DA8009AA30E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; AD311FF927CB7B730044AC5A /* LoginViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LoginViewController.storyboard; sourceTree = ""; }; + AD31A73E2C736A9800B37B19 /* ImagePreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePreviewViewController.swift; sourceTree = ""; }; AD31F5F4242FA75C00BD65FC /* AboutViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; AD31F5F6242FABF300BD65FC /* ReleaseNotesViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = ReleaseNotesViewController.storyboard; sourceTree = ""; }; AD31F5F8242FB76800BD65FC /* ReleaseNotesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReleaseNotesViewController.swift; sourceTree = ""; }; @@ -797,6 +801,8 @@ AD3FDDE91F2E23F700F71C81 /* da */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/About.strings; sourceTree = ""; }; AD3FDDEA1F2E23F700F71C81 /* da */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/ReleaseNotes.strings; sourceTree = ""; }; AD3FDDEB1F2E23F700F71C81 /* da */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/AppStore.strings; sourceTree = ""; }; + AD400B202C61334200DC9A61 /* PasteboardImagesViewController+FlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PasteboardImagesViewController+FlowLayout.swift"; sourceTree = ""; }; + AD400B222C6137A200DC9A61 /* PasteboardImagesViewController+Select.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PasteboardImagesViewController+Select.swift"; sourceTree = ""; }; AD41530E27C6D35D00DE01CB /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; AD41531427C6D37C00DE01CB /* LoginNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginNavigationController.swift; sourceTree = ""; }; AD42568B2ACB584F00D7CCC8 /* VideoControlsView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = VideoControlsView.xib; sourceTree = ""; }; @@ -863,11 +869,11 @@ AD6BA0F02BA5B04200755257 /* LocalImagesViewController+PHPhotoLibraryChangeObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LocalImagesViewController+PHPhotoLibraryChangeObserver.swift"; sourceTree = ""; }; AD6BA0F22BA5BDDC00755257 /* LocalImagesViewController+Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LocalImagesViewController+Data.swift"; sourceTree = ""; }; AD6BA0F42BA5C00500755257 /* LocalImagesViewController+HeaderDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LocalImagesViewController+HeaderDelegate.swift"; sourceTree = ""; }; - AD6BA0F72BA5C18100755257 /* PasteboardImagesViewController+UICollectionViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PasteboardImagesViewController+UICollectionViewDelegate.swift"; sourceTree = ""; }; - AD6BA0F92BA5C1D600755257 /* PasteboardImagesViewController+UICollectionViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PasteboardImagesViewController+UICollectionViewDataSource.swift"; sourceTree = ""; }; + AD6BA0F72BA5C18100755257 /* PasteboardImagesViewController+Delegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PasteboardImagesViewController+Delegate.swift"; sourceTree = ""; }; + AD6BA0F92BA5C1D600755257 /* PasteboardImagesViewController+DataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PasteboardImagesViewController+DataSource.swift"; sourceTree = ""; }; AD6BA0FB2BA5D01800755257 /* PasteboardImagesViewController+UploadSwitchDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PasteboardImagesViewController+UploadSwitchDelegate.swift"; sourceTree = ""; }; AD6BA0FD2BA5D15900755257 /* PasteboardImagesViewController+Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PasteboardImagesViewController+Data.swift"; sourceTree = ""; }; - AD6BA0FF2BA5D2FA00755257 /* PasteboardImagesViewController+HeaderReusableViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PasteboardImagesViewController+HeaderReusableViewDelegate.swift"; sourceTree = ""; }; + AD6BA0FF2BA5D2FA00755257 /* PasteboardImagesViewController+HeaderDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PasteboardImagesViewController+HeaderDelegate.swift"; sourceTree = ""; }; AD6BA1032BA5ED1A00755257 /* LocalImagePreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalImagePreviewViewController.swift; sourceTree = ""; }; AD6BA1052BA8E32700755257 /* DataModel 0D (Album).xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "DataModel 0D (Album).xcdatamodel"; sourceTree = ""; }; AD6FD1F924BEFFAA009AA3B7 /* UploadSwitchViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = UploadSwitchViewController.storyboard; sourceTree = ""; }; @@ -1443,6 +1449,7 @@ ADBC08CD244604EB00BF82E8 /* ImageFooterReusableView.swift */, AD211A7527A85EDB005D5E4F /* ImageCollectionViewCell.swift */, AD211A7727AB1FA8005D5E4F /* ImageCollectionViewCell.xib */, + AD31A73E2C736A9800B37B19 /* ImagePreviewViewController.swift */, ); path = ImageCollection; sourceTree = ""; @@ -1742,9 +1749,11 @@ isa = PBXGroup; children = ( AD6BA0FD2BA5D15900755257 /* PasteboardImagesViewController+Data.swift */, - AD6BA0F72BA5C18100755257 /* PasteboardImagesViewController+UICollectionViewDelegate.swift */, - AD6BA0F92BA5C1D600755257 /* PasteboardImagesViewController+UICollectionViewDataSource.swift */, - AD6BA0FF2BA5D2FA00755257 /* PasteboardImagesViewController+HeaderReusableViewDelegate.swift */, + AD6BA0F92BA5C1D600755257 /* PasteboardImagesViewController+DataSource.swift */, + AD400B202C61334200DC9A61 /* PasteboardImagesViewController+FlowLayout.swift */, + AD6BA0F72BA5C18100755257 /* PasteboardImagesViewController+Delegate.swift */, + AD400B222C6137A200DC9A61 /* PasteboardImagesViewController+Select.swift */, + AD6BA0FF2BA5D2FA00755257 /* PasteboardImagesViewController+HeaderDelegate.swift */, AD6BA0FB2BA5D01800755257 /* PasteboardImagesViewController+UploadSwitchDelegate.swift */, ); path = Extensions; @@ -2744,7 +2753,7 @@ attributes = { BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1250; - LastUpgradeCheck = 1530; + LastUpgradeCheck = 1600; ORGANIZATIONNAME = Piwigo.org; TargetAttributes = { AD084E452659A27100159FE1 = { @@ -3320,6 +3329,7 @@ AD12D6F62AA657080038BBFF /* VideoControlsView.swift in Sources */, ADEA269C285F8F2E00B6C9A0 /* ImageViewController+Favorites.swift in Sources */, ADF3EC0C2516515500379E53 /* Device+Model.swift in Sources */, + AD400B212C61334200DC9A61 /* PasteboardImagesViewController+FlowLayout.swift in Sources */, ADDB2A5E2B416A8400FDC1B1 /* EditImageParamsViewController+UITextField.swift in Sources */, AD7D415C2B4435AC0095A070 /* AutoUploadViewController+Tags.swift in Sources */, ADDB2A682B416DC600FDC1B1 /* EditImageParamsViewController+DatePicker.swift in Sources */, @@ -3327,7 +3337,7 @@ AD07CAC026DBF13800BEAEFB /* EditImageDatePickerTableViewCell.swift in Sources */, AD07CABC26DBDC4500BEAEFB /* EditImageTextFieldTableViewCell.swift in Sources */, AD7A0CC22C10FF4A008AB50F /* UIButton+AppTools.swift in Sources */, - AD6BA0F82BA5C18100755257 /* PasteboardImagesViewController+UICollectionViewDelegate.swift in Sources */, + AD6BA0F82BA5C18100755257 /* PasteboardImagesViewController+Delegate.swift in Sources */, AD07CABA26DBD82700BEAEFB /* EditImagePrivacyTableViewCell.swift in Sources */, AD9698242BE6548400520614 /* AlbumViewController+Discover.swift in Sources */, ADC592662C0333A2002B6F82 /* ImageHeaderReusableView.swift in Sources */, @@ -3373,7 +3383,7 @@ ADDFD45227EF4E7C00DB5081 /* UIView+AppTools.swift in Sources */, AD911272281060D9002FB78F /* UIWindow+AppTools.swift in Sources */, ADD77EF02A19339800F8C779 /* ExternalDisplaySceneDelegate.swift in Sources */, - AD6BA1002BA5D2FA00755257 /* PasteboardImagesViewController+HeaderReusableViewDelegate.swift in Sources */, + AD6BA1002BA5D2FA00755257 /* PasteboardImagesViewController+HeaderDelegate.swift in Sources */, ADBE0C232BE961E300F16A50 /* AlbumViewController+Edit.swift in Sources */, ADBDD9372441AB3800F19DFC /* LabelTableViewCell.swift in Sources */, ADEC6B032455A3520011527C /* LocalAlbumsProvider.swift in Sources */, @@ -3430,7 +3440,7 @@ AD07CAC426DD701600BEAEFB /* EditImageThumbTableViewCell.swift in Sources */, AD089D042BC2A5F800CE4A3E /* AlbumTableViewCell.swift in Sources */, AD0ED1002441C42200C69178 /* SliderTableViewCell.swift in Sources */, - AD6BA0FA2BA5C1D600755257 /* PasteboardImagesViewController+UICollectionViewDataSource.swift in Sources */, + AD6BA0FA2BA5C1D600755257 /* PasteboardImagesViewController+DataSource.swift in Sources */, ADACD29D2C34852800F8790B /* LogsViewController.swift in Sources */, ADBE0C272BE962D200F16A50 /* AlbumViewController+Favorite.swift in Sources */, AD43733F2B432862009BC454 /* SettingsViewController+UITableViewDelegate.swift in Sources */, @@ -3442,6 +3452,7 @@ AD9697D52BDE4B8900520614 /* PiwigoHUD.swift in Sources */, AD3369BF23F035F700F3FA80 /* TagSelectorViewController.swift in Sources */, ADEA26A8285FA2C300B6C9A0 /* ImageViewController+AlbumThumnail.swift in Sources */, + AD400B232C6137A200DC9A61 /* PasteboardImagesViewController+Select.swift in Sources */, ADD65C5B2BE777E8005AC3B8 /* AlbumViewController+Upload.swift in Sources */, ADF26FBF24C200040036E778 /* TagTableViewCell.swift in Sources */, ADF26FC024C200040036E778 /* TagsViewController.swift in Sources */, @@ -3489,6 +3500,7 @@ ADE03D2E25FCBA4B002F5C7B /* ColorPaletteViewControllerOld.swift in Sources */, AD6A0E782C445C750089A25E /* AlbumCollectionViewCell.swift in Sources */, AD9698262BE6661D00520614 /* AlbumViewController+Search.swift in Sources */, + AD31A73F2C736A9800B37B19 /* ImagePreviewViewController.swift in Sources */, ADDB2A6D2B416F5800FDC1B1 /* EditImageParamsViewController+UITableView.swift in Sources */, ADC69F25285DF50C009BF9AF /* TagSelectorViewController+Search.swift in Sources */, ADBE0C2D2BE963CD00F16A50 /* AlbumViewController+Delete.swift in Sources */, @@ -4289,7 +4301,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; @@ -4363,7 +4374,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; @@ -4419,7 +4429,6 @@ D82B8CDF1A67820A000E47CA /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; @@ -4491,7 +4500,6 @@ D82B8CE01A67820A000E47CA /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; diff --git a/piwigo.xcodeproj/xcshareddata/xcschemes/piwigo.xcscheme b/piwigo.xcodeproj/xcshareddata/xcschemes/piwigo.xcscheme index cc5a47a9b..d2680a74b 100644 --- a/piwigo.xcodeproj/xcshareddata/xcschemes/piwigo.xcscheme +++ b/piwigo.xcodeproj/xcshareddata/xcschemes/piwigo.xcscheme @@ -1,6 +1,6 @@ , + let imageIDs = array[0] as? Set, let albumId = array[1] as? Int32 else { debugPrint("Input parameter expected to be of type [[NSNumber], Int32]") return false } // IDs of the selected images which will be copied/moved to the selected album - inputImageIds = imageIds + inputImageIds = imageIDs if inputImageIds.isEmpty { debugPrint("List of image IDs should not be empty") return false } - inputImages = imageProvider.getImages(inContext: mainContext, withIds: imageIds) + inputImages = imageProvider.getImages(inContext: mainContext, withIds: imageIDs) nberOfImages = Int64(inputImages.count) if inputImages.isEmpty { debugPrint("No image in cache with these IDs: \(inputImageIds)") diff --git a/piwigo/Album/AlbumViewController.swift b/piwigo/Album/AlbumViewController.swift index 261bc6d34..f2867401d 100644 --- a/piwigo/Album/AlbumViewController.swift +++ b/piwigo/Album/AlbumViewController.swift @@ -15,7 +15,7 @@ import uploadKit enum pwgImageAction { case edit, delete, share case copyImages, moveImages - case addToFavorites, removeFromFavorites + case favorite, unfavorite case rotateImagesLeft, rotateImagesRight } @@ -92,17 +92,15 @@ class AlbumViewController: UIViewController var imageOfInterest = IndexPath(item: 0, section: 0) var indexOfImageToRestore = Int.min var isSelect = false - var touchedImageIds = [Int64]() - var selectedImageIds = Set() - var selectedImageIdsLoop = Set() - var selectedFavoriteIds = Set() - var selectedVideosIds = Set() - var totalNumberOfImages = 0 + var touchedImageIDs = [Int64]() + var selectedImageIDs = Set() + var selectedFavoriteIDs = Set() + var selectedVideosIDs = Set() var selectedSections = [Int : SelectButtonState]() // State of Select buttons // MARK: - Cached Values - private var timeCounter = CFAbsoluteTime(0) + var timeCounter = CFAbsoluteTime(0) lazy var thumbSize = pwgImageSize(rawValue: AlbumVars.shared.defaultAlbumThumbnailSize) ?? .medium lazy var albumCellSize: CGSize = getAlbumCellSize() lazy var albumPlaceHolder = UIImage(named: "placeholder")! @@ -129,7 +127,7 @@ class AlbumViewController: UIViewController // MARK: - Fetch // Number of images to download per page - var oldImageIds = Set() + var oldImageIDs = Set() var onPage = 0, lastPage = 0 lazy var perPage: Int = { return max(AlbumUtilities.numberOfImagesToDownloadPerPage(), 100) diff --git a/piwigo/Album/Extensions/AlbumViewController+Bars.swift b/piwigo/Album/Extensions/AlbumViewController+Bars.swift index 83e41a155..addb447b1 100644 --- a/piwigo/Album/Extensions/AlbumViewController+Bars.swift +++ b/piwigo/Album/Extensions/AlbumViewController+Bars.swift @@ -287,7 +287,8 @@ extension AlbumViewController } func updateBarsInSelectMode() { - let hasImagesSelected = !selectedImageIds.isEmpty + setTitleViewFromAlbumData(whileUpdating: false) + let hasImagesSelected = !selectedImageIDs.isEmpty cancelBarButton.isEnabled = true // User with admin or upload rights can do everything @@ -299,11 +300,9 @@ extension AlbumViewController shareBarButton.isEnabled = hasImagesSelected deleteBarButton.isEnabled = hasImagesSelected favoriteBarButton?.isEnabled = hasImagesSelected - let selected = selectedImageIds - let favorites = selectedFavoriteIds - let areFavorites = selected == favorites + let areFavorites = selectedImageIDs == selectedFavoriteIDs favoriteBarButton?.setFavoriteImage(for: areFavorites) - favoriteBarButton?.action = areFavorites ? #selector(removeFromFavorites) : #selector(addToFavorites) + favoriteBarButton?.action = areFavorites ? #selector(unfavoriteSelection) : #selector(favoriteSelection) if #available(iOS 14, *) { let children = [albumMenu(), imagesMenu()].compactMap({$0}) @@ -318,11 +317,9 @@ extension AlbumViewController /// — non-guest users can set favorites in addition shareBarButton.isEnabled = hasImagesSelected favoriteBarButton?.isEnabled = hasImagesSelected - let selected = selectedImageIds - let favorites = selectedFavoriteIds - let areFavorites = selected == favorites + let areFavorites = selectedImageIDs == selectedFavoriteIDs favoriteBarButton?.setFavoriteImage(for: areFavorites) - favoriteBarButton?.action = areFavorites ? #selector(removeFromFavorites) : #selector(addToFavorites) + favoriteBarButton?.action = areFavorites ? #selector(unfavoriteSelection) : #selector(favoriteSelection) } } @@ -377,17 +374,38 @@ extension AlbumViewController titleLabel.sizeToFit() // There is no subtitle in landscape mode on iPhone - var lastUpdated = "" + var subtitle = "" if !(UIDevice.current.userInterfaceIdiom == .phone && UIApplication.shared.statusBarOrientation.isLandscape) { if isUpdating { // Inform user that the app is fetching album data - lastUpdated = NSLocalizedString("categoryUpdating", comment: "Updating…") + subtitle = NSLocalizedString("categoryUpdating", comment: "Updating…") + } + else if isSelect { + let nberPhotos = selectedImageIDs.count + switch nberPhotos { + case 0: + subtitle = NSLocalizedString("selectImages", comment: "Select Photos") + case 1: + subtitle = NSLocalizedString("selectImageSelected", comment: "1 Photo Selected") + case 2...nberPhotos: + var nberPhotosStr = "" + if #available(iOS 16, *) { + nberPhotosStr = nberPhotos.formatted(.number) + } else { + let numberFormatter = NumberFormatter() + numberFormatter.numberStyle = NumberFormatter.Style.decimal + nberPhotosStr = numberFormatter.string(from: NSNumber(value: nberPhotos)) ?? String(nberPhotos) + } + subtitle = String(format: NSLocalizedString("selectImagesSelected", comment: "%@ Photos Selected"), nberPhotosStr) + default: + subtitle = "" + } } else if albumData.dateGetImages > TimeInterval(86400) { // i.e. a day after minimum date let dateGetImages = Date(timeIntervalSinceReferenceDate: albumData.dateGetImages) if Date().timeIntervalSinceReferenceDate - albumData.dateGetImages < 60 { - lastUpdated = NSLocalizedString("categoryUpdatedNow", comment: "Updated just now") + subtitle = NSLocalizedString("categoryUpdatedNow", comment: "Updated just now") } else { let calendar = Calendar.current let updatedDay = calendar.dateComponents([.day], from: dateGetImages) @@ -396,21 +414,19 @@ extension AlbumViewController // Album data updated today let time = DateFormatter.localizedString(from: dateGetImages, dateStyle: .none, timeStyle: .short) - lastUpdated = String(format: NSLocalizedString("categoryUpdatedAt", - comment: "Updated at…"), time) + subtitle = String(format: NSLocalizedString("categoryUpdatedAt", comment: "Updated at…"), time) } else { // Album data updated yesterday or before let date = DateFormatter.localizedString(from: dateGetImages, dateStyle: .short, timeStyle: .none) - lastUpdated = String(format: NSLocalizedString("categoryUpdatedOn", - comment: "Updated on…"), date) + subtitle = String(format: NSLocalizedString("categoryUpdatedOn", comment: "Updated on…"), date) } } } } // Prepare sub-title - if lastUpdated.isEmpty == false { + if subtitle.isEmpty == false { let subTitleLabel = UILabel(frame: CGRect(x: 0.0, y: titleLabel.frame.size.height, width: 0, height: 0)) subTitleLabel.backgroundColor = UIColor.clear subTitleLabel.textColor = .piwigoColorWhiteCream() @@ -421,7 +437,7 @@ extension AlbumViewController subTitleLabel.adjustsFontSizeToFitWidth = false subTitleLabel.lineBreakMode = .byTruncatingTail subTitleLabel.allowsDefaultTighteningForTruncation = true - subTitleLabel.text = lastUpdated + subTitleLabel.text = subtitle subTitleLabel.sizeToFit() var titleWidth = CGFloat(fmax(subTitleLabel.bounds.size.width, titleLabel.bounds.size.width)) diff --git a/piwigo/Album/Extensions/AlbumViewController+CopyMove.swift b/piwigo/Album/Extensions/AlbumViewController+CopyMove.swift index f56a42ee4..18ca2c92f 100644 --- a/piwigo/Album/Extensions/AlbumViewController+CopyMove.swift +++ b/piwigo/Album/Extensions/AlbumViewController+CopyMove.swift @@ -29,7 +29,7 @@ extension AlbumViewController // Disable buttons during action setEnableStateOfButtons(false) // Retrieve complete image data before copying images - initSelection(beforeAction: .copyImages) + initSelection(ofImagesWithIDs: selectedImageIDs, beforeAction: .copyImages, contextually: false) }) action.accessibilityIdentifier = "copy" return action @@ -44,7 +44,7 @@ extension AlbumViewController // Disable buttons during action setEnableStateOfButtons(false) // Retrieve complete image data before moving images - initSelection(beforeAction: .moveImages) + initSelection(ofImagesWithIDs: selectedImageIDs, beforeAction: .moveImages, contextually: false) }) action.accessibilityIdentifier = "move" return action @@ -69,14 +69,14 @@ extension AlbumViewController title: NSLocalizedString("copyImage_title", comment: "Copy to Album"), style: .default, handler: { [self] action in // Retrieve complete image data before copying images - initSelection(beforeAction: .copyImages) + initSelection(ofImagesWithIDs: selectedImageIDs, beforeAction: .copyImages, contextually: false) }) let moveAction = UIAlertAction( title: NSLocalizedString("moveImage_title", comment: "Move to Album"), style: .default, handler: { [self] action in // Retrieve complete image data before moving images - initSelection(beforeAction: .moveImages) + initSelection(ofImagesWithIDs: selectedImageIDs, beforeAction: .moveImages, contextually: false) }) // Add actions @@ -98,10 +98,10 @@ extension AlbumViewController } } - func copyImagesToAlbum() { + func copyToAlbum(imagesWithID imageIDs: Set) { let copySB = UIStoryboard(name: "SelectCategoryViewController", bundle: nil) guard let copyVC = copySB.instantiateViewController(withIdentifier: "SelectCategoryViewController") as? SelectCategoryViewController else { return } - let parameter: [Any] = [selectedImageIds, albumData.pwgID] + let parameter: [Any] = [imageIDs, albumData.pwgID] copyVC.user = user if copyVC.setInput(parameter: parameter, for: .copyImages) { copyVC.delegate = self // To re-enable toolbar @@ -109,10 +109,10 @@ extension AlbumViewController } } - func moveImagesToAlbum() { + func moveToAlbum(imagesWithID imageIDs: Set) { let moveSB = UIStoryboard(name: "SelectCategoryViewController", bundle: nil) guard let moveVC = moveSB.instantiateViewController(withIdentifier: "SelectCategoryViewController") as? SelectCategoryViewController else { return } - let parameter: [Any] = [selectedImageIds, albumData.pwgID] + let parameter: [Any] = [imageIDs, albumData.pwgID] moveVC.user = user if moveVC.setInput(parameter: parameter, for: .moveImages) { moveVC.delegate = self // To re-enable toolbar diff --git a/piwigo/Album/Extensions/AlbumViewController+DataSource.swift b/piwigo/Album/Extensions/AlbumViewController+DataSource.swift index eb4dd7d49..cd0d7d353 100644 --- a/piwigo/Album/Extensions/AlbumViewController+DataSource.swift +++ b/piwigo/Album/Extensions/AlbumViewController+DataSource.swift @@ -16,7 +16,7 @@ extension AlbumViewController: UICollectionViewDataSource // MARK: - Headers & Footers func attributedComment() -> NSMutableAttributedString { let desc = NSMutableAttributedString(attributedString: albumData.comment) - let wholeRange = NSRange(location: 0, length: desc.string.count) + let wholeRange = NSRange(location: 0, length: albumData.comment.string.count) let style = NSMutableParagraphStyle() style.alignment = NSTextAlignment.center let attributes = [ @@ -117,18 +117,19 @@ extension AlbumViewController: UICollectionViewDataSource let imageSection = indexPath.section - 1 var imagesInSection = [Image]() let nberOfImageInSection = collectionView.numberOfItems(inSection: indexPath.section) - if nberOfImageInSection <= 10 { + if nberOfImageInSection <= 20 { // Collect all images - for item in 0.. add/remove image from selection - if !selectedImageIds.contains(imageId) { - selectedImageIds.insert(imageId) + if !selectedImageIDs.contains(imageID) { + selectedImageIDs.insert(imageID) selectedCell.isSelection = true if selectedCell.isFavorite { - selectedFavoriteIds.insert(imageId) + selectedFavoriteIDs.insert(imageID) } if selectedCell.imageData.isVideo { - selectedVideosIds.insert(imageId) + selectedVideosIDs.insert(imageID) } } else { selectedCell.isSelection = false - selectedImageIds.remove(imageId) - selectedFavoriteIds.remove(imageId) - selectedVideosIds.remove(imageId) + selectedImageIDs.remove(imageID) + selectedFavoriteIDs.remove(imageID) + selectedVideosIDs.remove(imageID) } // Update nav buttons @@ -123,6 +123,21 @@ extension AlbumViewController: UICollectionViewDelegate return self.albumContextMenu(indexPath) } } + else if indexPath.section > 0, + let cell = collectionView.cellForItem(at: indexPath) as? ImageCollectionViewCell, + let imageData = cell.imageData { + // Return context menu configuration + let identifier = NSString(string: "\(imageData.pwgID)") + return UIContextMenuConfiguration(identifier: identifier, + previewProvider: { + // Create preview view controller + return ImagePreviewViewController(imageData: imageData) + }, + actionProvider: { suggestedActions in + // Present context menu + return self.imageContextMenu(forCell: cell, imageData: imageData, at: indexPath) + }) + } return nil } @@ -139,20 +154,59 @@ extension AlbumViewController: UICollectionViewDelegate return self.albumContextMenu(indexPath) }) } + else if indexPaths.count == 1, let indexPath = indexPaths.first, indexPath.section > 0, + let cell = collectionView.cellForItem(at: indexPath) as? ImageCollectionViewCell, + let imageData = cell.imageData { + // Return context menu configuration + return UIContextMenuConfiguration(identifier: nil, + previewProvider: { + // Create preview view controller + return ImagePreviewViewController(imageData: imageData) + }, + actionProvider: { suggestedActions in + // Present context menu + return self.imageContextMenu(forCell: cell, imageData: imageData, at: indexPath) + }) + } return nil } - + + + // MARK: - Album Context Menu @available(iOS 13.0, *) private func albumContextMenu(_ indexPath: IndexPath) -> UIMenu { - let renameAction = self.renameAlbumAction(indexPath) - let moveAction = self.moveAlbumAction(indexPath) - let deleteAction = self.deleteAlbumAction(indexPath) - return UIMenu(title: "", children: [renameAction, moveAction, deleteAction]) + let addPhotos = self.addPhotosMenu(indexPath) + let rename = self.renameAlbumAction(indexPath) + let move = self.moveAlbumAction(indexPath) + let delete = self.deleteAlbumMenu(indexPath) + return UIMenu(title: "", children: [addPhotos, rename, move, delete]) + } + + @available(iOS 13.0, *) + private func addPhotosMenu(_ indexPath: IndexPath) -> UIMenu { + let addPhotos = addPhotosAction(indexPath) + let menuId = UIMenu.Identifier("org.piwigo.addPhotos") + return UIMenu(identifier: menuId, options: UIMenu.Options.displayInline, children: [addPhotos]) + } + + @available(iOS 13.0, *) + private func addPhotosAction(_ indexPath: IndexPath) -> UIAction { + return UIAction(title: NSLocalizedString("categoryCellOption_addPhotos", comment: "Add Photos"), + image: UIImage(named: "imageUpload")) { action in + // Push album view + let albumData = self.albums.object(at: indexPath) + let albumSB = UIStoryboard(name: "AlbumViewController", bundle: nil) + guard let subAlbumVC = albumSB.instantiateViewController(withIdentifier: "AlbumViewController") as? AlbumViewController + else { preconditionFailure("Could not load AlbumViewController") } + subAlbumVC.categoryId = albumData.pwgID + self.pushAlbumView(subAlbumVC) { _ in } + subAlbumVC.checkPhotoLibraryAccess() + } } @available(iOS 13.0, *) private func renameAlbumAction(_ indexPath: IndexPath) -> UIAction { - return UIAction(title: NSLocalizedString("categoryCellOption_rename", comment: "Rename"), + return UIAction(title: NSLocalizedString("categoryCellOption_rename", comment: "Rename Album"), image: UIImage(systemName: "character.cursor.ibeam")) { action in guard let topViewController = self.navigationController else { return } @@ -165,7 +219,7 @@ extension AlbumViewController: UICollectionViewDelegate @available(iOS 13.0, *) private func moveAlbumAction(_ indexPath: IndexPath) -> UIAction { - return UIAction(title: NSLocalizedString("categoryCellOption_move", comment: "Move"), + return UIAction(title: NSLocalizedString("categoryCellOption_move", comment: "Move Album"), image: UIImage(systemName: "arrowshape.turn.up.left")) { action in let moveSB = UIStoryboard(name: "SelectCategoryViewController", bundle: nil) guard let moveVC = moveSB.instantiateViewController(withIdentifier: "SelectCategoryViewController") as? SelectCategoryViewController else { return } @@ -177,9 +231,16 @@ extension AlbumViewController: UICollectionViewDelegate } } + @available(iOS 13.0, *) + private func deleteAlbumMenu(_ indexPath: IndexPath) -> UIMenu { + let delete = deleteAlbumAction(indexPath) + let menuId = UIMenu.Identifier("org.piwigo.deleteAlbum") + return UIMenu(identifier: menuId, options: UIMenu.Options.displayInline, children: [delete]) + } + @available(iOS 13.0, *) private func deleteAlbumAction(_ indexPath: IndexPath) -> UIAction { - return UIAction(title: NSLocalizedString("categoryCellOption_delete", comment: "Delete"), + return UIAction(title: NSLocalizedString("categoryCellOption_delete", comment: "Delete Album"), image: UIImage(systemName: "trash"), attributes: .destructive) { action in guard let topViewController = self.navigationController @@ -190,4 +251,162 @@ extension AlbumViewController: UICollectionViewDelegate delete.displayAlert { _ in } } } + + + // MARK: - Image Context Menu + @available(iOS 13.0, *) + private func imageContextMenu(forCell cell: ImageCollectionViewCell, imageData: Image, + at indexPath: IndexPath) -> UIMenu { + var children = [UIMenuElement]() + if let imageID = cell.imageData?.pwgID { + // Guest cannot share images + if NetworkVars.userStatus != .guest { + children.append(shareImageAction(withID: imageID)) + } + + // pwg.users.favorites… methods available from Piwigo version 2.10 for registered users + let isGuest = NetworkVars.userStatus == .guest + let versionTooOld = NetworkVars.pwgVersion.compare("2.10.0", options: .numeric) == .orderedAscending + if isGuest == false, versionTooOld == false { + if cell.isFavorite { + children.append(unfavoriteImageAction(withID: imageID)) + } else { + children.append(favoriteImageAction(withID: imageID)) + } + } + + // Only identified users can select images + if NetworkVars.userStatus != .guest { + if self.selectedImageIDs.contains(imageID) { + // Image not selected ► Propose to select it + children.append(deselectImageAction(forCell: cell, imageID: imageID, at: indexPath)) + } else { + // Image selected ► Propose to deselect it + children.append(selectImageAction(forCell: cell, imageID: imageID, at: indexPath)) + } + } + + // User with admin or upload rights can delete images + if user.hasUploadRights(forCatID: categoryId) { + children.append(deleteImageMenu(forImageID: imageID)) + } + } + return UIMenu(title: "", children: children) + } + + @available(iOS 13.0, *) + private func shareImageAction(withID imageID: Int64) -> UIAction { + return UIAction(title: NSLocalizedString("categoryImageList_share", comment: "Share"), + image: UIImage(systemName: "square.and.arrow.up")) { _ in + self.initSelection(ofImagesWithIDs: Set([imageID]), beforeAction: .share, contextually: true) + } + } + + @available(iOS 13.0, *) + private func favoriteImageAction(withID imageID: Int64) -> UIAction { + return UIAction(title: NSLocalizedString("categoryImageList_favorite", comment: "Favorite"), + image: UIImage(systemName: "heart")) { _ in + self.initSelection(ofImagesWithIDs: Set([imageID]), beforeAction: .favorite, contextually: true) + } + } + + @available(iOS 13.0, *) + private func unfavoriteImageAction(withID imageID: Int64) -> UIAction { + return UIAction(title: NSLocalizedString("categoryImageList_unfavorite", comment: "Unfavorite"), + image: UIImage(systemName: "heart.slash")) { _ in + self.initSelection(ofImagesWithIDs: Set([imageID]), beforeAction: .unfavorite, contextually: true) + } + } + + @available(iOS 13.0, *) + private func selectImageAction(forCell cell: ImageCollectionViewCell, imageID: Int64, + at indexPath: IndexPath) -> UIAction { + // Image not selected ► Propose to select it + return UIAction(title: NSLocalizedString("categoryImageList_selectButton", comment: "Select"), + image: UIImage(systemName: "checkmark.circle")) { _ in + // Select image + self.selectedImageIDs.insert(imageID) + cell.isSelection = true + if cell.isFavorite { + self.selectedFavoriteIDs.insert(imageID) + } + if cell.imageData.isVideo { + self.selectedVideosIDs.insert(imageID) + } + + // Check if the selection mode is active + if self.isSelect { + // Update the navigation bar and title view + self.updateBarsInSelectMode() + } else { + // Enable the selection mode + self.isSelect = true + self.hideButtons() + self.initBarsInSelectMode() + } + + // Update state of Select button if needed + let selectState = self.updateSelectButton(ofSection: indexPath.section) + let indexPath = IndexPath(item: 0, section: indexPath.section) + if let header = self.collectionView.supplementaryView(forElementKind: UICollectionView.elementKindSectionHeader, at: indexPath) as? ImageHeaderReusableView { + header.selectButton.setTitle(forState: selectState) + } else if let header = self.collectionView.supplementaryView(forElementKind: UICollectionView.elementKindSectionHeader, at: indexPath) as? ImageOldHeaderReusableView { + header.selectButton.setTitle(forState: selectState) + } + } + } + + @available(iOS 13.0, *) + private func deselectImageAction(forCell cell: ImageCollectionViewCell, imageID: Int64, + at indexPath: IndexPath) -> UIAction { + // Image selected ► Propose to deselect it + var image: UIImage? + if #available(iOS 16, *) { + image = UIImage(systemName: "checkmark.circle.badge.xmark") + } else { + image = UIImage(systemName: "checkmark.circle") + } + return UIAction(title: NSLocalizedString("categoryImageList_deselectButton", comment: "Deselect"), + image: image) { _ in + // Deselect image + cell.isSelection = false + self.selectedImageIDs.remove(imageID) + self.selectedFavoriteIDs.remove(imageID) + self.selectedVideosIDs.remove(imageID) + + // Check if the selection mode should be disabled + if self.selectedImageIDs.isEmpty { + // Disable the selection mode + self.cancelSelect() + } else { + // Update the navigation bar and title view + self.updateBarsInSelectMode() + } + + // Update state of Select button if needed + let selectState = self.updateSelectButton(ofSection: indexPath.section) + let indexPath = IndexPath(item: 0, section: indexPath.section) + if let header = self.collectionView.supplementaryView(forElementKind: UICollectionView.elementKindSectionHeader, at: indexPath) as? ImageHeaderReusableView { + header.selectButton.setTitle(forState: selectState) + } else if let header = self.collectionView.supplementaryView(forElementKind: UICollectionView.elementKindSectionHeader, at: indexPath) as? ImageOldHeaderReusableView { + header.selectButton.setTitle(forState: selectState) + } + } + } + + @available(iOS 13.0, *) + private func deleteImageMenu(forImageID imageID: Int64) -> UIMenu { + let delete = deleteImageAction(forImageID: imageID) + let menuId = UIMenu.Identifier("org.piwigo.removeFromCameraRoll") + return UIMenu(identifier: menuId, options: UIMenu.Options.displayInline, children: [delete]) + } + + @available(iOS 13.0, *) + private func deleteImageAction(forImageID imageID: Int64) -> UIAction { + // Image selected ► Propose to deselect it + return UIAction(title: NSLocalizedString("deleteSingleImage_title", comment: "Delete Photo"), + image: UIImage(systemName: "trash"), attributes: .destructive) { _ in + self.initSelection(ofImagesWithIDs: Set([imageID]), beforeAction: .delete, contextually: true) + } + } } diff --git a/piwigo/Album/Extensions/AlbumViewController+Delete.swift b/piwigo/Album/Extensions/AlbumViewController+Delete.swift index 16895d3fc..c36367fac 100644 --- a/piwigo/Album/Extensions/AlbumViewController+Delete.swift +++ b/piwigo/Album/Extensions/AlbumViewController+Delete.swift @@ -19,27 +19,27 @@ extension AlbumViewController } - // MARK: - Delete Images + // MARK: - Delete or Remove Images @objc func deleteSelection() { - initSelection(beforeAction: .delete) + initSelection(ofImagesWithIDs: selectedImageIDs, beforeAction: .delete, contextually: false) } - func askDeleteConfirmation() { + func askDeleteConfirmation(forImagesWithID imageIDs: Set) { // Split orphaned and non-orphaned images var toRemove = Set() var toDelete = Set() - for selectedImageId in selectedImageIds { - guard let selectedImage = (images.fetchedObjects ?? []).first(where: {$0.pwgID == selectedImageId}) + for imageID in imageIDs { + guard let image = (images.fetchedObjects ?? []).first(where: {$0.pwgID == imageID}) else { continue } - if (selectedImage.albums ?? Set()).filter({$0.pwgID > 0}).count == 1 { - toDelete.insert(selectedImage) + if (image.albums ?? Set()).filter({$0.pwgID > 0}).count == 1 { + toDelete.insert(image) } else { - toRemove.insert(selectedImage) + toRemove.insert(image) } } let totalNberToDelete = toDelete.count + toRemove.count - // We cannot propose to remove images from a smart albums + // We cannot propose to remove images from a smart album if albumData.pwgID < 0 { toDelete.formUnion(toRemove) toRemove = [] @@ -52,7 +52,7 @@ extension AlbumViewController } else if let imageData = toDelete.first, imageData.isVideo { msg = NSLocalizedString("deleteSingleVideo_title", comment: "Are you sure you want to delete this video?") } else { - msg = NSLocalizedString("deleteSingleImage_message", comment: "Are you sure you want to delete this image?") + msg = NSLocalizedString("deleteSingleImage_message", comment: "Are you sure you want to delete this photo?") } let alert = UIAlertController(title: nil, message: msg, preferredStyle: .actionSheet) @@ -79,7 +79,7 @@ extension AlbumViewController // Display HUD during server update var msgHUD = "" - if totalNumberOfImages > 1 { + if imageIDs.count > 1 { msgHUD = NSLocalizedString("deleteSeveralImagesHUD_deleting", comment: "Deleting Photos/Videos…") } else if let imageData = toDelete.first, imageData.isVideo { msgHUD = NSLocalizedString("deleteSingleVideoHUD_deleting", comment: "Deleting Video…") @@ -100,8 +100,8 @@ extension AlbumViewController handler: { [self] action in // Display HUD during server update var msgHUD = "" - totalNumberOfImages = toRemove.count + (toDelete.isEmpty ? 0 : 1) - if totalNumberOfImages > 1 { + let totalNberOfImages = toRemove.count + (toDelete.isEmpty ? 0 : 1) + if totalNberOfImages > 1 { msgHUD = toDelete.isEmpty ? NSLocalizedString("removeSeveralImagesHUD_removing", comment: "Removing Photos/Videos…") : NSLocalizedString("deleteSeveralImagesHUD_deleting", comment: "Deleting Photos/Videos…") @@ -125,7 +125,7 @@ extension AlbumViewController } // Start removing images - removeImages(toRemove, andThenDelete: toDelete) + removeImages(toRemove, andThenDelete: toDelete, total: Float(totalNberToDelete)) }) alert.addAction(removeImagesAction) } @@ -142,10 +142,11 @@ extension AlbumViewController } } - func removeImages(_ toRemove: Set, andThenDelete toDelete: Set) { + func removeImages(_ toRemove: Set, andThenDelete toDelete: Set, total: Float) { var imagesToRemove = toRemove guard let imageData = imagesToRemove.first, - let albums = imageData.albums else { + let albums = imageData.albums + else { if toDelete.isEmpty { navigationController?.updateHUDwithSuccess() { [self] in // Save changes @@ -190,21 +191,22 @@ extension AlbumViewController imagesToRemove.removeFirst() // Update HUD - let ratio = Float(imagesToRemove.count) / Float(totalNumberOfImages) + let ratio = Float(imagesToRemove.count) / total navigationController?.updateHUD(withProgress: 1.0 - ratio) // Next image - removeImages(imagesToRemove, andThenDelete:toDelete) + removeImages(imagesToRemove, andThenDelete:toDelete, total: total) } failure: { [self] error in - self.removeImages(imagesToRemove, andThenDelete: toDelete, error: error) + self.removeImages(imagesToRemove, andThenDelete: toDelete, total: total, error: error) } } failure: { [self] error in - self.removeImages(imagesToRemove, andThenDelete: toDelete, error: error) + self.removeImages(imagesToRemove, andThenDelete: toDelete, total: total, error: error) } } - private func removeImages(_ toRemove: Set, andThenDelete toDelete: Set, error: NSError) { + private func removeImages(_ toRemove: Set, andThenDelete toDelete: Set, + total: Float, error: NSError) { // Session logout required? if let pwgError = error as? PwgSessionError, [.invalidCredentials, .incompatiblePwgVersion, .invalidURL, .authenticationFailed] @@ -234,7 +236,7 @@ extension AlbumViewController // Bypass image imagesToRemove.removeFirst() // Continue removing images - removeImages(imagesToRemove, andThenDelete:toDelete) + removeImages(imagesToRemove, andThenDelete:toDelete, total: total) } } else { dismissPiwigoError(withTitle: title, message: message, @@ -260,7 +262,7 @@ extension AlbumViewController do { try self.mainContext.save() } catch let error as NSError { - print("Could not save moved images \(error), \(error.userInfo)") + print("Could not save deleted images \(error), \(error.userInfo)") } // Hide HUD and deselect images navigationController?.hideHUD(afterDelay: pwgDelayHUD) { [self] in diff --git a/piwigo/Album/Extensions/AlbumViewController+Edit.swift b/piwigo/Album/Extensions/AlbumViewController+Edit.swift index 40a207551..b55c50c05 100644 --- a/piwigo/Album/Extensions/AlbumViewController+Edit.swift +++ b/piwigo/Album/Extensions/AlbumViewController+Edit.swift @@ -29,11 +29,11 @@ extension AlbumViewController // MARK: Edit Images Parameters @objc func editSelection() { - initSelection(beforeAction: .edit) + initSelection(ofImagesWithIDs: selectedImageIDs, beforeAction: .edit, contextually: false) } - func editImages() { - if selectedImageIds.isEmpty { + func editImages(withIDs imageIDs: Set) { + if imageIDs.isEmpty { // No image => End (should never happen) navigationController?.updateHUDwithSuccess() { [self] in navigationController?.hideHUD(afterDelay: pwgDelayHUD) { [self] in @@ -49,7 +49,7 @@ extension AlbumViewController else { preconditionFailure("Could not load EditImageParamsViewController") } editImageVC.user = user let albumImages = images.fetchedObjects ?? [] - editImageVC.images = albumImages.filter({selectedImageIds.contains($0.pwgID)}) + editImageVC.images = albumImages.filter({imageIDs.contains($0.pwgID)}) editImageVC.delegate = self pushView(editImageVC) } @@ -59,11 +59,11 @@ extension AlbumViewController // MARK: - EditImageParamsDelegate Methods extension AlbumViewController: EditImageParamsDelegate { - func didDeselectImage(withId imageId: Int64) { + func didDeselectImage(withID imageID: Int64) { // Deselect image - selectedImageIds.remove(imageId) - selectedFavoriteIds.remove(imageId) - selectedVideosIds.remove(imageId) + selectedImageIDs.remove(imageID) + selectedFavoriteIDs.remove(imageID) + selectedVideosIDs.remove(imageID) collectionView?.reloadData() } diff --git a/piwigo/Album/Extensions/AlbumViewController+Favorite.swift b/piwigo/Album/Extensions/AlbumViewController+Favorite.swift index b9253ce12..dae54071c 100644 --- a/piwigo/Album/Extensions/AlbumViewController+Favorite.swift +++ b/piwigo/Album/Extensions/AlbumViewController+Favorite.swift @@ -23,44 +23,52 @@ extension AlbumViewController } // Are the selected images favorites? - let areFavorites = selectedImageIds == selectedFavoriteIds + let areFavorites = selectedImageIDs == selectedFavoriteIDs let button = UIBarButtonItem.favoriteImageButton(areFavorites, target: self) - button.action = areFavorites ? #selector(removeFromFavorites) : #selector(addToFavorites) + button.action = areFavorites ? #selector(unfavoriteSelection) : #selector(favoriteSelection) return button } // MARK: - Add Images to Favorites - @objc func addToFavorites() { - initSelection(beforeAction: .addToFavorites) + @objc func favoriteSelection() { + initSelection(ofImagesWithIDs: selectedImageIDs, beforeAction: .favorite, contextually: false) } - func addImageToFavorites() { - guard let imageId = selectedImageIds.first else { + func favorite(imagesWithID someIDs: Set, total: Float, contextually: Bool) { + var remainingIDs = someIDs + guard let imageID = remainingIDs.first else { // Save changes // bckgContext.saveIfNeeded() // Close HUD with success navigationController?.updateHUDwithSuccess() { [self] in navigationController?.hideHUD(afterDelay: pwgDelayHUD) { [self] in - // Deselect images - cancelSelect() + // Deselect images if needed + if contextually { + setEnableStateOfButtons(true) + } else { + cancelSelect() + } } } return } // Get image data - guard let imageData = (images.fetchedObjects ?? []).first(where: {$0.pwgID == imageId}) else { + guard let imageData = (images.fetchedObjects ?? []).first(where: {$0.pwgID == imageID}) else { // Forget this image - selectedImageIds.removeFirst() - selectedFavoriteIds.remove(imageId) - selectedVideosIds.remove(imageId) + remainingIDs.removeFirst() + if contextually == false { + selectedImageIDs.remove(imageID) + selectedFavoriteIDs.remove(imageID) + selectedVideosIDs.remove(imageID) + } // Update HUD - navigationController?.updateHUD(withProgress: 1.0 - Float(selectedImageIds.count) / Float(totalNumberOfImages)) + navigationController?.updateHUD(withProgress: 1.0 - Float(remainingIDs.count) / total) // Next image - addImageToFavorites() + favorite(imagesWithID: remainingIDs, total: total, contextually: contextually) return } @@ -69,7 +77,7 @@ extension AlbumViewController ImageUtilities.addToFavorites(imageData) { [self] in DispatchQueue.main.async { [self] in // Update HUD - navigationController?.updateHUD(withProgress: 1.0 - Float(self.selectedImageIds.count) / Float(self.totalNumberOfImages)) + navigationController?.updateHUD(withProgress: 1.0 - Float(remainingIDs.count) / total) // Image added to favorites ► Add it in the background if let favAlbum = self.albumProvider.getAlbum(ofUser: self.user, withId: pwgSmartAlbum.favorites.rawValue) { @@ -82,20 +90,23 @@ extension AlbumViewController } // Next image - selectedImageIds.removeFirst() - selectedFavoriteIds.remove(imageId) - selectedVideosIds.remove(imageId) - addImageToFavorites() + remainingIDs.remove(imageID) + if contextually == false { + selectedImageIDs.remove(imageID) + selectedFavoriteIDs.remove(imageID) + selectedVideosIDs.remove(imageID) + } + favorite(imagesWithID: remainingIDs, total: total, contextually: contextually) } } failure: { [self] error in - self.addImageToFavoritesError(error) + self.favoriteError(error, contextually: contextually) } } failure: { [self] error in - self.addImageToFavoritesError(error) + self.favoriteError(error, contextually: contextually) } } - private func addImageToFavoritesError(_ error: NSError) { + private func favoriteError(_ error: NSError, contextually: Bool) { DispatchQueue.main.async { [self] in // Session logout required? if let pwgError = error as? PwgSessionError, @@ -111,7 +122,11 @@ extension AlbumViewController navigationController?.dismissPiwigoError(withTitle: title, message: message, errorMessage: error.localizedDescription) { [self] in navigationController?.hideHUD() { [self] in - updateBarsInSelectMode() + if contextually { + setEnableStateOfButtons(true) + } else { + updateBarsInSelectMode() + } } } } @@ -119,36 +134,44 @@ extension AlbumViewController // MARK: - Remove Images from Favorites - @objc func removeFromFavorites() { - initSelection(beforeAction: .removeFromFavorites) + @objc func unfavoriteSelection() { + initSelection(ofImagesWithIDs: selectedImageIDs, beforeAction: .unfavorite, contextually: false) } - func removeImageFromFavorites() { - guard let imageId = selectedImageIds.first else { + func unfavorite(imagesWithID someIDs: Set, total: Float, contextually: Bool) { + var remainingIDs = someIDs + guard let imageID = remainingIDs.first else { // Save changes // bckgContext.saveIfNeeded() // Close HUD with success navigationController?.updateHUDwithSuccess() { [self] in navigationController?.hideHUD(afterDelay: pwgDelayHUD) { [self] in - // Deselect images - cancelSelect() + // Deselect images if needed + if contextually { + setEnableStateOfButtons(true) + } else { + cancelSelect() + } } } return } // Get image data - guard let imageData = (images.fetchedObjects ?? []).first(where: {$0.pwgID == imageId}) else { - // Deselect this image - selectedImageIds.removeFirst() - selectedFavoriteIds.remove(imageId) - selectedVideosIds.remove(imageId) + guard let imageData = (images.fetchedObjects ?? []).first(where: {$0.pwgID == imageID}) else { + // Deselect this image if needed + remainingIDs.remove(imageID) + if contextually == false { + selectedImageIDs.remove(imageID) + selectedFavoriteIDs.remove(imageID) + selectedVideosIDs.remove(imageID) + } // Update HUD - navigationController?.updateHUD(withProgress: 1.0 - Float(selectedImageIds.count) / Float(totalNumberOfImages)) + navigationController?.updateHUD(withProgress: 1.0 - Float(remainingIDs.count) / total) // Next image - removeImageFromFavorites() + unfavorite(imagesWithID: remainingIDs, total: total, contextually: contextually) return } @@ -157,7 +180,7 @@ extension AlbumViewController ImageUtilities.removeFromFavorites(imageData) { [self] in DispatchQueue.main.async { [self] in // Update HUD - navigationController?.updateHUD(withProgress: 1.0 - Float(self.selectedImageIds.count) / Float(self.totalNumberOfImages)) + navigationController?.updateHUD(withProgress: 1.0 - Float(remainingIDs.count) / total) // Image removed from favorites ► Remove it in the foreground if let favAlbum = self.albumProvider.getAlbum(ofUser: self.user, withId: pwgSmartAlbum.favorites.rawValue) { @@ -170,20 +193,23 @@ extension AlbumViewController } // Next image - selectedImageIds.removeFirst() - selectedFavoriteIds.remove(imageId) - selectedVideosIds.remove(imageId) - removeImageFromFavorites() + remainingIDs.removeFirst() + if contextually == false { + selectedImageIDs.remove(imageID) + selectedFavoriteIDs.remove(imageID) + selectedVideosIDs.remove(imageID) + } + unfavorite(imagesWithID: remainingIDs, total: total, contextually: contextually) } } failure: { [unowned self] error in - self.removeFromFavoritesError(error) + self.unfavoriteError(error, contextually: contextually) } } failure: { [unowned self] error in - self.removeFromFavoritesError(error) + self.unfavoriteError(error, contextually: contextually) } } - private func removeFromFavoritesError(_ error: NSError) { + private func unfavoriteError(_ error: NSError, contextually: Bool) { DispatchQueue.main.async { [self] in // Session logout required? if let pwgError = error as? PwgSessionError, @@ -199,7 +225,11 @@ extension AlbumViewController navigationController?.dismissPiwigoError(withTitle: title, message: message, errorMessage: error.localizedDescription) { [unowned self] in navigationController?.hideHUD() { [unowned self] in - updateBarsInSelectMode() + if contextually { + setEnableStateOfButtons(true) + } else { + updateBarsInSelectMode() + } } } } diff --git a/piwigo/Album/Extensions/AlbumViewController+Fetch.swift b/piwigo/Album/Extensions/AlbumViewController+Fetch.swift index f7c30fe8c..201c35ee9 100644 --- a/piwigo/Album/Extensions/AlbumViewController+Fetch.swift +++ b/piwigo/Album/Extensions/AlbumViewController+Fetch.swift @@ -18,7 +18,7 @@ extension AlbumViewController // from main context before calling background tasks /// - takes 662 ms for 2500 photos on iPhone 14 Pro with derivatives inside Image instances /// - takes 51 ms for 2584 photos on iPhone 14 Pro with derivatives in Sizes instances - let oldImageIds = Set((images.fetchedObjects ?? []).map({$0.pwgID})) + let oldImageIDs = Set((images.fetchedObjects ?? []).map({$0.pwgID})) let query = albumData.query // Use the AlbumProvider to create the album data. On completion, @@ -29,19 +29,19 @@ extension AlbumViewController // The number of images is unknown when a smart album is created. // Use the ImageProvider to fetch image data. On completion, // handle general UI updates and error alerts on the main queue. - self.fetchImages(withInitialImageIds: oldImageIds, query: query, + self.fetchImages(withInitialImageIds: oldImageIDs, query: query, fromPage: 0, toPage: 0) { completion() } } else { - fetchAlbums(withInitialImageIds: oldImageIds, query: query) { + fetchAlbums(withInitialImageIds: oldImageIDs, query: query) { completion() } } } } - private func fetchAlbums(withInitialImageIds oldImageIds: Set, query: String, + private func fetchAlbums(withInitialImageIds oldImageIDs: Set, query: String, completion: @escaping () -> Void) { // Use the AlbumProvider to fetch album data. On completion, // handle general UI updates and error alerts on the main queue. @@ -63,7 +63,7 @@ extension AlbumViewController return } // ► Remove non-fetched images from album - self.removeImageWithIDs(oldImageIds) + self.removeImageWithIDs(oldImageIDs) // ► Remove current album from list of album being fetched AlbumVars.shared.isFetchingAlbumData.remove(self.categoryId) completion() @@ -74,7 +74,7 @@ extension AlbumViewController // handle general UI updates and error alerts on the main queue. let (quotient, remainder) = nbImages.quotientAndRemainder(dividingBy: Int64(self.perPage)) let lastPage = Int(quotient) + Int(remainder > 0 ? 1 : 0) - self.fetchImages(withInitialImageIds: oldImageIds, query: query, + self.fetchImages(withInitialImageIds: oldImageIDs, query: query, fromPage: 0, toPage: lastPage - 1, completion: completion) return @@ -94,7 +94,7 @@ extension AlbumViewController // MARK: - Fetch Image Data in the Background - func fetchImages(withInitialImageIds oldImageIds: Set, query: String, + func fetchImages(withInitialImageIds oldImageIDs: Set, query: String, fromPage onPage: Int, toPage lastPage: Int, completion: @escaping () -> Void) { // Use the ImageProvider to fetch image data. On completion, @@ -128,7 +128,7 @@ extension AlbumViewController } // Will not remove fetched images from album image list - let imageIds = oldImageIds.subtracting(fetchedImageIds) + let imageIDs = oldImageIDs.subtracting(fetchedImageIds) // Should we continue? if onPage < newLastPage, query == albumData.query { @@ -149,16 +149,16 @@ extension AlbumViewController // Is user editing the search string? if imageProvider.userDidCancelSearch { // Remove non-fetched images from album - removeImageWithIDs(imageIds) + removeImageWithIDs(imageIDs) // Store parameters - self.oldImageIds = imageIds + self.oldImageIDs = imageIDs self.onPage = onPage + 1 self.lastPage = newLastPage self.perPage = perPage return } // Load next page of images - self.fetchImages(withInitialImageIds: imageIds, query: query, + self.fetchImages(withInitialImageIds: imageIDs, query: query, fromPage: onPage + 1, toPage: newLastPage, completion: completion) return @@ -166,7 +166,7 @@ extension AlbumViewController // Done fetching images // ► Remove non-fetched images from album - removeImageWithIDs(imageIds) + removeImageWithIDs(imageIDs) // ► Remove current album from list of album being fetched AlbumVars.shared.isFetchingAlbumData.remove(self.categoryId) // ► Delete orphaned images in the background @@ -275,7 +275,7 @@ extension AlbumViewController // Remember which images belong to this album // from main context before calling background tasks - let oldImageIds = Set(album.images?.map({$0.pwgID}) ?? []) + let oldImageIDs = Set(album.images?.map({$0.pwgID}) ?? []) // Load favorites data in the background // Use the ImageProvider to fetch image data. On completion, @@ -283,16 +283,15 @@ extension AlbumViewController let albumNbImages = album.nbImages let (quotient, remainer) = albumNbImages.quotientAndRemainder(dividingBy: Int64(self.perPage)) let lastPage = Int(quotient) + Int(remainer) > 0 ? 1 : 0 - self.fetchFavorites(ofAlbum: album, imageIds: oldImageIds, + self.fetchFavorites(ofAlbum: album, imageIDs: oldImageIDs, fromPage: 0, toPage: lastPage, perPage: perPage) } - private func fetchFavorites(ofAlbum album: Album, imageIds: Set, + private func fetchFavorites(ofAlbum album: Album, imageIDs: Set, fromPage onPage: Int, toPage lastPage: Int, perPage: Int) { // Use the ImageProvider to fetch image data. On completion, // handle general UI updates and error alerts on the main queue. - imageProvider.fetchImages(ofAlbumWithId: album.pwgID, withQuery: "", - sort: .dateCreatedAscending, + imageProvider.fetchImages(ofAlbumWithId: album.pwgID, withQuery: "", sort: sortOption, fromPage: onPage, perPage: perPage) { [self] fetchedImageIds, totalCount, error in // Any error? if error != nil { @@ -315,12 +314,12 @@ extension AlbumViewController } // Will not remove fetched images from album image list - let newImageIds = imageIds.subtracting(fetchedImageIds) + let newImageIds = imageIDs.subtracting(fetchedImageIds) // Should we continue? if onPage < newLastPage { // Load next page of images - self.fetchFavorites(ofAlbum: album, imageIds: newImageIds, + self.fetchFavorites(ofAlbum: album, imageIDs: newImageIds, fromPage: onPage + 1, toPage: newLastPage, perPage: perPage) return } diff --git a/piwigo/Album/Extensions/AlbumViewController+FetchResults.swift b/piwigo/Album/Extensions/AlbumViewController+FetchResults.swift index a96b0fee7..475fbdaf7 100644 --- a/piwigo/Album/Extensions/AlbumViewController+FetchResults.swift +++ b/piwigo/Album/Extensions/AlbumViewController+FetchResults.swift @@ -110,7 +110,9 @@ extension AlbumViewController: NSFetchedResultsControllerDelegate else { return } indexPath.section += 1 // Deselect image - selectedImageIds.remove(image.pwgID) + selectedImageIDs.remove(image.pwgID) + selectedFavoriteIDs.remove(image.pwgID) + selectedVideosIDs.remove(image.pwgID) // Delete image updateOperations.append( BlockOperation { [weak self] in debugPrint("••> Delete image of album #\(self?.categoryId ?? Int32.min) at \(indexPath)") @@ -185,12 +187,28 @@ extension AlbumViewController: NSFetchedResultsControllerDelegate if indexPath.section == 0 { return } // Determine place names from first images - var imagesInSection: [Image] = [] - for item in 0.. CGSize { if AlbumVars.shared.displayAlbumDescriptions { - let albumWidth = AlbumUtilities.albumWidth(forView: collectionView, maxWidth: 384.0) + let albumWidth = AlbumUtilities.albumWidth(forView: collectionView, maxWidth: CGFloat(384)) // debugPrint("••> getAlbumCellSize: \(albumWidth) x 156.5 points") return CGSize(width: albumWidth, height: 156.5) } else { - let albumWidth = AlbumUtilities.albumWidth(forView: collectionView, maxWidth: 200.0) + let albumWidth = AlbumUtilities.albumWidth(forView: collectionView, maxWidth: CGFloat(200)) let albumHeight = albumWidth * 2 / 3 + 50 // debugPrint("••> getAlbumCellSize: \(albumWidth) x \(albumHeight) points") return CGSize(width: albumWidth, height: albumHeight) @@ -99,7 +99,7 @@ extension AlbumViewController: UICollectionViewDelegateFlowLayout default /* Images */: // Number of images shown at the bottom of the collection - guard section == images.sections?.count ?? 0 + guard categoryId != Int32.zero, section == images.sections?.count ?? 0 else { return CGSize.zero } } @@ -123,8 +123,8 @@ extension AlbumViewController: UICollectionViewDelegateFlowLayout if AlbumVars.shared.displayAlbumDescriptions { return UIEdgeInsets.zero } else { - return UIEdgeInsets(top: 0, left: AlbumUtilities.kAlbumMarginsSpacing, - bottom: 0, right: AlbumUtilities.kAlbumMarginsSpacing) + return UIEdgeInsets(top: CGFloat.zero, left: AlbumUtilities.kAlbumMarginsSpacing, + bottom: CGFloat.zero, right: AlbumUtilities.kAlbumMarginsSpacing) } default /* Images */: return UIEdgeInsets.zero diff --git a/piwigo/Album/Extensions/AlbumViewController+Menu.swift b/piwigo/Album/Extensions/AlbumViewController+Menu.swift index 61652ad59..e46ef1e6a 100644 --- a/piwigo/Album/Extensions/AlbumViewController+Menu.swift +++ b/piwigo/Album/Extensions/AlbumViewController+Menu.swift @@ -216,6 +216,25 @@ extension AlbumViewController let visitsSortAction = UIAlertAction(title: title, style: .default, handler: handler) alert.addAction(visitsSortAction) + // Sorted manually + if sortOption != .rankAscending { + let randomAction = UIAlertAction(title: NSLocalizedString("categorySort_manual", comment: "Manual Order"), + style: .default, handler: { [self] action in + if let allImages = images.fetchedObjects { + allImages.forEach { image in + debugPrint("Manual Sort: \(image.pwgID) -> \(image.rankManual == Int64.min ? "Unknown" : "Known")") + } + } + sortOption = .rankAscending + images.delegate = nil + images = data.images(sortedBy: .rankAscending) + images.delegate = self + let shouldFetch = images.fetchedObjects?.first(where: {$0.rankManual == Int64.min}) != nil + updateImageCollection(afterFetchingRanks: shouldFetch) + }) + alert.addAction(randomAction) + } + // Presents photos randomly if sortOption != .random { let randomAction = UIAlertAction(title: NSLocalizedString("categorySort_randomly", comment: "Randomly"), @@ -224,7 +243,8 @@ extension AlbumViewController images.delegate = nil images = data.images(sortedBy: .random) images.delegate = self - updateImageCollection() + let shouldFetch = images.fetchedObjects?.first(where: {$0.rankRandom == Int64.min}) != nil + updateImageCollection(afterFetchingRanks: shouldFetch) }) alert.addAction(randomAction) } @@ -243,10 +263,15 @@ extension AlbumViewController } } - func updateImageCollection() { - // Re-fetch image collection - try? images.performFetch() - collectionView?.reloadData() + func updateImageCollection(afterFetchingRanks shouldFetch: Bool = false) { + if shouldFetch { + // Some image ranks are unknown and must be retrieved + startFetchingAlbumAndImages(withHUD: true) + } else { + // Re-fetch image collection + try? images.performFetch() + collectionView?.reloadData() + } } } @@ -297,10 +322,10 @@ extension AlbumViewController: ImageHeaderDelegate let image = images.object(at: imageIndexPath) // Is this image already selected? - if selectedImageIds.contains(image.pwgID) { continue } + if selectedImageIDs.contains(image.pwgID) { continue } // Select this image - selectedImageIds.insert(image.pwgID) + selectedImageIDs.insert(image.pwgID) let indexPath = IndexPath(item: item, section: section) if let cell = collectionView?.cellForItem(at: indexPath) as? ImageCollectionViewCell { cell.isSelection = true @@ -317,10 +342,10 @@ extension AlbumViewController: ImageHeaderDelegate let image = images.object(at: imageIndexPath) // Is this image already deselected? - if selectedImageIds.contains(image.pwgID) == false { continue } + if selectedImageIDs.contains(image.pwgID) == false { continue } // Deselect this image - selectedImageIds.remove(image.pwgID) + selectedImageIDs.remove(image.pwgID) let indexPath = IndexPath(item: item, section: section) if let cell = collectionView?.cellForItem(at: indexPath) as? ImageCollectionViewCell { cell.isSelection = false @@ -356,9 +381,9 @@ extension AlbumViewController: ImageHeaderDelegate extension AlbumViewController { // MARK: - Menu - func updateCollectionAndMenu() { + func updateCollectionAndMenu(afterFetchingRanks shouldFetch: Bool = false) { // Re-fetch image collection - updateImageCollection() + updateImageCollection(afterFetchingRanks: shouldFetch) // Update menu var children = [UIMenu?]() @@ -382,7 +407,7 @@ extension AlbumViewController children: [defaultSortAction(), titleSortAction(), createdSortAction(), postedSortAction(), ratingSortAction(), visitsSortAction(), - randomSortAction()].compactMap({$0})) + manualSortAction(), randomSortAction()].compactMap({$0})) } func defaultSortAction() -> UIAction? { @@ -625,6 +650,33 @@ extension AlbumViewController return action } + func manualSortAction() -> UIAction? { + // Unavailable when presenting some smart albums + let unwantedAlbums = [pwgSmartAlbum.visits.rawValue, pwgSmartAlbum.best.rawValue] + if unwantedAlbums.contains(categoryId) { + return nil + } + + let actionId = UIAction.Identifier("org.piwigo.images.sort.manual") + let isActive = sortOption == .rankAscending + let action = UIAction(title: NSLocalizedString("categorySort_manual", comment: "Manual Order"), + image: isActive ? UIImage(systemName: "checkmark") : nil, + identifier: actionId, handler: { [self] action in + // Should sorting be changed? + if isActive { return } + + // Change image sorting + sortOption = .rankAscending + images.delegate = nil + images = data.images(sortedBy: .rankAscending) + images.delegate = self + let shouldFetch = images.fetchedObjects?.first(where: {$0.rankManual == Int64.min}) != nil + updateCollectionAndMenu(afterFetchingRanks: shouldFetch) + }) + action.accessibilityIdentifier = "ManualSort" + return action + } + func randomSortAction() -> UIAction? { // Unavailable when presenting some smart albums let unwantedAlbums = [pwgSmartAlbum.visits.rawValue, pwgSmartAlbum.best.rawValue] @@ -645,7 +697,8 @@ extension AlbumViewController images.delegate = nil images = data.images(sortedBy: .random) images.delegate = self - updateCollectionAndMenu() + let shouldFetch = images.fetchedObjects?.first(where: {$0.rankRandom == Int64.min}) != nil + updateCollectionAndMenu(afterFetchingRanks: shouldFetch) }) action.accessibilityIdentifier = "RandomSort" return action diff --git a/piwigo/Album/Extensions/AlbumViewController+Rotate.swift b/piwigo/Album/Extensions/AlbumViewController+Rotate.swift index 7726677ee..54136d333 100644 --- a/piwigo/Album/Extensions/AlbumViewController+Rotate.swift +++ b/piwigo/Album/Extensions/AlbumViewController+Rotate.swift @@ -15,7 +15,7 @@ import piwigoKit extension AlbumViewController { func rotateMenu() -> UIMenu? { - if selectedVideosIds.isEmpty { + if selectedVideosIDs.isEmpty { return UIMenu(title: NSLocalizedString("rotateImage_rotate", comment: "Rotate 90°…"), image: nil, identifier: UIMenu.Identifier("org.piwigo.images.rotate"), @@ -30,7 +30,7 @@ extension AlbumViewController image: UIImage(systemName: "rotate.right"), handler: { _ in // Rotate images right - self.rotateImagesRight() + self.rotateSelectionRight() }) action.accessibilityIdentifier = "Rotate Right" return action @@ -42,7 +42,7 @@ extension AlbumViewController image: UIImage(systemName: "rotate.left"), handler: { _ in // Rotate images left - self.rotateImagesLeft() + self.rotateSelectionLeft() }) action.accessibilityIdentifier = "Rotate Left" return action @@ -53,16 +53,17 @@ extension AlbumViewController extension AlbumViewController { // MARK: - Rotate Image - @objc func rotateImagesLeft() { - initSelection(beforeAction: .rotateImagesLeft) + @objc func rotateSelectionLeft() { + initSelection(ofImagesWithIDs: selectedImageIDs, beforeAction: .rotateImagesLeft, contextually: false) } - @objc func rotateImagesRight() { - initSelection(beforeAction: .rotateImagesRight) + @objc func rotateSelectionRight() { + initSelection(ofImagesWithIDs: selectedImageIDs, beforeAction: .rotateImagesRight, contextually: false) } - func rotateImages(by angle: Double) { - guard let imageId = selectedImageIds.first else { + func rotateImages(withID someIDs: Set, by angle: Double, total: Float) { + var remainingIDs = someIDs + guard let imageID = remainingIDs.first else { // Save changes // bckgContext.saveIfNeeded() // Close HUD with success @@ -78,20 +79,22 @@ extension AlbumViewController } // Get image data - guard let imageData = (images.fetchedObjects ?? []).first(where: {$0.pwgID == imageId}) else { + guard let imageData = (images.fetchedObjects ?? []).first(where: {$0.pwgID == imageID}) + else { // Forget this image - selectedImageIds.removeFirst() - selectedFavoriteIds.remove(imageId) - selectedVideosIds.remove(imageId) + remainingIDs.removeFirst() + selectedImageIDs.remove(imageID) + selectedFavoriteIDs.remove(imageID) + selectedVideosIDs.remove(imageID) // Update HUD DispatchQueue.main.async { - let progress: Float = 1 - Float(self.selectedImageIds.count) / Float(self.totalNumberOfImages) + let progress: Float = 1 - Float(remainingIDs.count) / total self.navigationController?.updateHUD(withProgress: progress) } // Next image - rotateImages(by: angle) + rotateImages(withID: remainingIDs, by: angle, total: total) return } @@ -107,10 +110,12 @@ extension AlbumViewController // Update HUD DispatchQueue.main.async { // Update progress indicator - let progress: Float = 1 - Float(self.selectedImageIds.count) / Float(self.totalNumberOfImages) + let progress: Float = 1 - Float(remainingIDs.count) / total self.navigationController?.updateHUD(withProgress: progress) + // Rotate cell image - for cell in (self.collectionView?.visibleCells ?? []) { + let visibleCells = self.collectionView?.visibleCells ?? [] + for cell in visibleCells { if let cell = cell as? ImageCollectionViewCell, cell.imageData.pwgID == imageID, let updatedImage = self.images.fetchedObjects?.filter({$0.pwgID == imageID}).first { cell.config(with: updatedImage, placeHolder: self.imagePlaceHolder, @@ -120,10 +125,11 @@ extension AlbumViewController } // Next image - selectedImageIds.removeFirst() - selectedFavoriteIds.remove(imageId) - selectedVideosIds.remove(imageId) - rotateImages(by: angle) + remainingIDs.removeFirst() + selectedImageIDs.remove(imageID) + selectedFavoriteIDs.remove(imageID) + selectedVideosIDs.remove(imageID) + rotateImages(withID: remainingIDs, by: angle, total: total) } failure: { [self] error in rotateImagesInDatabaseError(error) diff --git a/piwigo/Album/Extensions/AlbumViewController+Scroll.swift b/piwigo/Album/Extensions/AlbumViewController+Scroll.swift index c1b318058..815d4346b 100644 --- a/piwigo/Album/Extensions/AlbumViewController+Scroll.swift +++ b/piwigo/Album/Extensions/AlbumViewController+Scroll.swift @@ -14,7 +14,7 @@ extension AlbumViewController: UIScrollViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { // Show/hide navigation bar border - var topSpace = navigationController?.navigationBar.bounds.height ?? 0 + var topSpace: CGFloat = navigationController?.navigationBar.bounds.height ?? 0 if #available(iOS 13, *) { topSpace += view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0 } else { diff --git a/piwigo/Album/Extensions/AlbumViewController+Search.swift b/piwigo/Album/Extensions/AlbumViewController+Search.swift index bb714dbdc..db1e39ba6 100644 --- a/piwigo/Album/Extensions/AlbumViewController+Search.swift +++ b/piwigo/Album/Extensions/AlbumViewController+Search.swift @@ -143,7 +143,7 @@ extension AlbumViewController: UISearchBarDelegate // Did the query string change? if albumData.query == query { // Restart loading pages of images - self.fetchImages(withInitialImageIds: self.oldImageIds, query: query, + self.fetchImages(withInitialImageIds: self.oldImageIDs, query: query, fromPage: self.onPage, toPage: self.lastPage) { self.fetchCompleted() } diff --git a/piwigo/Album/Extensions/AlbumViewController+Select.swift b/piwigo/Album/Extensions/AlbumViewController+Select.swift index 4c6569b08..bf7741042 100644 --- a/piwigo/Album/Extensions/AlbumViewController+Select.swift +++ b/piwigo/Album/Extensions/AlbumViewController+Select.swift @@ -46,14 +46,14 @@ extension AlbumViewController // Rotate clockwise var title = NSLocalizedString("rotateImage_right", comment: "Clockwise") let rotateRightAction = UIAlertAction(title: title, style: .default) { [self] _ in - self.rotateImagesRight() + self.rotateSelectionRight() } alert.addAction(rotateRightAction) // Rotate counterclockwise title = NSLocalizedString("rotateImage_left", comment: "Counterclockwise") let rotateLeftAction = UIAlertAction(title: title, style: .default) { [self] _ in - self.rotateImagesLeft() + self.rotateSelectionLeft() } alert.addAction(rotateLeftAction) @@ -201,10 +201,10 @@ extension AlbumViewController } // Clear array of selected images - touchedImageIds = [] - selectedImageIds = Set() - selectedFavoriteIds = Set() - selectedVideosIds = Set() + touchedImageIDs = [] + selectedImageIDs = Set() + selectedFavoriteIDs = Set() + selectedVideosIDs = Set() for key in selectedSections.keys { selectedSections[key] = .select } @@ -239,7 +239,7 @@ extension AlbumViewController var nberOfSelectedImagesInSection = 0 for item in 0.., + beforeAction action: pwgImageAction, contextually: Bool) { + if imageIDs.isEmpty { return } // Disable buttons setEnableStateOfButtons(false) @@ -273,108 +274,129 @@ extension AlbumViewController .moveImages /* Move images to album */: // Remove images from which we already have complete data - selectedImageIdsLoop = selectedImageIds - let selectedImages = (images.fetchedObjects ?? []).filter({selectedImageIds.contains($0.pwgID)}) - for selectedImageId in selectedImageIds { - guard let selectedImage = selectedImages.first(where: {$0.pwgID == selectedImageId}) - else { continue } + var imageIDsToRetrieve = imageIDs + let selectedImages = (images.fetchedObjects ?? []).filter({imageIDs.contains($0.pwgID)}) + for imageID in imageIDs { + guard let selectedImage = selectedImages.first(where: {$0.pwgID == imageID}) + else { continue } if selectedImage.fileSize != Int64.zero { - selectedImageIdsLoop.remove(selectedImageId) + imageIDsToRetrieve.remove(imageID) } } // Should we retrieve data of some images? - if selectedImageIdsLoop.isEmpty { - doAction(action) + if imageIDsToRetrieve.isEmpty { + performAction(action, withImageIDs: imageIDs, contextually: contextually) } else { // Display HUD - totalNumberOfImages = selectedImageIdsLoop.count navigationController?.showHUD(withTitle: NSLocalizedString("loadingHUD_label", comment: "Loading…"), - inMode: totalNumberOfImages > 1 ? .determinate : .indeterminate) + inMode: imageIDsToRetrieve.count > 1 ? .determinate : .indeterminate) // Retrieve image data if needed PwgSession.checkSession(ofUser: user) { [self] in - retrieveImageData(beforeAction: action) + retrieveData(ofImagesWithID: imageIDsToRetrieve, among: imageIDs, + beforeAction: action, contextually: contextually) } failure: { [unowned self] error in - retrieveImageDataError(error) + retrieveImageDataError(error, contextually: contextually) } } - case .addToFavorites /* Add photos to favorites */, - .removeFromFavorites /* Remove photos from favorites */: + case .favorite /* Favorite photos */, + .unfavorite /* Unfavorite photos */: // Display HUD - totalNumberOfImages = selectedImageIds.count - let title = totalNumberOfImages > 1 ? NSLocalizedString("editImageDetailsHUD_updatingPlural", comment: "Updating Photos…") : NSLocalizedString("editImageDetailsHUD_updatingSingle", comment: "Updating Photo…") - navigationController?.showHUD(withTitle: title, inMode: totalNumberOfImages > 1 ? .determinate : .indeterminate) + let title = imageIDs.count > 1 ? + NSLocalizedString("editImageDetailsHUD_updatingPlural", comment: "Updating Photos…") : + NSLocalizedString("editImageDetailsHUD_updatingSingle", comment: "Updating Photo…") + navigationController?.showHUD(withTitle: title, inMode: imageIDs.count > 1 ? .determinate : .indeterminate) // Add or remove image from favorites - doAction(action) + performAction(action, withImageIDs: imageIDs, contextually: contextually) case .rotateImagesLeft /* Rotate photos 90° to left */, .rotateImagesRight /* Rotate photos 90° to right */: // Display HUD - totalNumberOfImages = selectedImageIds.count - let title = totalNumberOfImages > 1 ? NSLocalizedString("rotateSeveralImageHUD_rotating", comment: "Rotating Photos…") : NSLocalizedString("rotateSingleImageHUD_rotating", comment: "Rotating Photo…") - navigationController?.showHUD(withTitle: title, inMode: totalNumberOfImages > 1 ? .determinate : .indeterminate) + let title = imageIDs.count > 1 ? + NSLocalizedString("rotateSeveralImageHUD_rotating", comment: "Rotating Photos…") : + NSLocalizedString("rotateSingleImageHUD_rotating", comment: "Rotating Photo…") + navigationController?.showHUD(withTitle: title, inMode: imageIDs.count > 1 ? .determinate : .indeterminate) // Add or remove image from favorites - doAction(action) + performAction(action, withImageIDs: imageIDs, contextually: contextually) } } - private func doAction(_ action: pwgImageAction) { + private func performAction(_ action: pwgImageAction, withImageIDs imageIDs: Set, contextually: Bool) { switch action { case .edit /* Edit images parameters */: - editImages() + editImages(withIDs: imageIDs) case .delete /* Distinguish orphanes and ask for confirmation */: - askDeleteConfirmation() + askDeleteConfirmation(forImagesWithID: imageIDs) case .share /* Check Photo Library access rights */: - checkPhotoLibraryAccessBeforeShare() + // Display or update HUD + if navigationController?.isShowingHUD() ?? false { + navigationController?.updateHUD(title: NSLocalizedString("loadingHUD_label", comment: "Loading…"), + inMode: .indeterminate) + } else if selectedImageIDs.count > 200 { + navigationController?.showHUD(withTitle: NSLocalizedString("loadingHUD_label", comment: "Loading…"), + inMode: .indeterminate) + } + // Prepare items to share in background queue + DispatchQueue(label: "org.piwigo.share", qos: .userInitiated).async { + self.checkPhotoLibraryAccessBeforeSharing(imagesWithID: imageIDs, contextually: contextually) + } case .copyImages /* Copy images to Album */: - copyImagesToAlbum() + copyToAlbum(imagesWithID: imageIDs) case .moveImages /* Move images to album */: - moveImagesToAlbum() - case .addToFavorites: - addImageToFavorites() - case .removeFromFavorites: - removeImageFromFavorites() + moveToAlbum(imagesWithID: imageIDs) + case .favorite: + favorite(imagesWithID: imageIDs, total: Float(imageIDs.count), contextually: contextually) + case .unfavorite: + unfavorite(imagesWithID: imageIDs, total: Float(imageIDs.count), contextually: contextually) case .rotateImagesLeft: - rotateImages(by: 90.0) + rotateImages(withID: imageIDs, by: 90.0, total: Float(imageIDs.count)) case .rotateImagesRight: - rotateImages(by: -90.0) + rotateImages(withID: imageIDs, by: -90.0, total: Float(imageIDs.count)) } } - private func retrieveImageData(beforeAction action:pwgImageAction) { + private func retrieveData(ofImagesWithID someIDs: Set, among imageIDs: Set, + beforeAction action:pwgImageAction, contextually: Bool) { // Get image ID if any - guard let imageId = selectedImageIdsLoop.first else { + var remainingIDs = someIDs + guard let imageID = remainingIDs.first else { DispatchQueue.main.async { - self.navigationController?.hideHUD() { [self] in - doAction(action) + if action == .share { + // Update or display HUD + self.performAction(action, withImageIDs: imageIDs, contextually: contextually) + } else { + self.navigationController?.hideHUD() { [self] in + performAction(action, withImageIDs: imageIDs, contextually: contextually) + } } } return } // Image data are not complete when retrieved using pwg.categories.getImages - imageProvider.getInfos(forID: imageId, inCategoryId: self.albumData.pwgID) { [self] in + imageProvider.getInfos(forID: imageID, inCategoryId: self.albumData.pwgID) { [self] in // Image info retrieved - selectedImageIdsLoop.remove(imageId) + remainingIDs.remove(imageID) // Update HUD DispatchQueue.main.async { - let progress: Float = 1 - Float(self.selectedImageIdsLoop.count) / Float(self.totalNumberOfImages) + let progress: Float = Float(1) - Float(remainingIDs.count) / Float(imageIDs.count) self.navigationController?.updateHUD(withProgress: progress) } // Next image - retrieveImageData(beforeAction: action) + retrieveData(ofImagesWithID: remainingIDs, among: imageIDs, + beforeAction: action, contextually: contextually) } failure: { [unowned self] error in - retrieveImageDataError(error) + retrieveImageDataError(error, contextually: contextually) } } - private func retrieveImageDataError(_ error: NSError) { + private func retrieveImageDataError(_ error: NSError, contextually: Bool) { DispatchQueue.main.async { [self] in // Session logout required? if let pwgError = error as? PwgSessionError, @@ -390,7 +412,11 @@ extension AlbumViewController dismissPiwigoError(withTitle: title, message: message, errorMessage: error.localizedDescription) { [unowned self] in navigationController?.hideHUD() { [unowned self] in - updateBarsInSelectMode() + if contextually { + setEnableStateOfButtons(true) + } else { + updateBarsInSelectMode() + } } } } @@ -434,29 +460,29 @@ extension AlbumViewController: UIGestureRecognizerDelegate { // Get cell at touch position if let imageCell = collectionView.cellForItem(at: indexPath) as? ImageCollectionViewCell, - let imageId = imageCell.imageData?.pwgID + let imageID = imageCell.imageData?.pwgID { // Update the selection if not already done - if touchedImageIds.contains(imageId) { return } + if touchedImageIDs.contains(imageID) { return } // Store that the user touched this cell during this gesture - touchedImageIds.append(imageId) + touchedImageIDs.append(imageID) // Update the selection state - if !selectedImageIds.contains(imageId) { - selectedImageIds.insert(imageId) + if !selectedImageIDs.contains(imageID) { + selectedImageIDs.insert(imageID) imageCell.isSelection = true if imageCell.isFavorite { - selectedFavoriteIds.insert(imageId) + selectedFavoriteIDs.insert(imageID) } if imageCell.imageData.isVideo { - selectedVideosIds.insert(imageId) + selectedVideosIDs.insert(imageID) } } else { imageCell.isSelection = false - selectedImageIds.remove(imageId) - selectedFavoriteIds.remove(imageId) - selectedVideosIds.remove(imageId) + selectedImageIDs.remove(imageID) + selectedFavoriteIDs.remove(imageID) + selectedVideosIDs.remove(imageID) } // Update the navigation bar @@ -467,7 +493,7 @@ extension AlbumViewController: UIGestureRecognizerDelegate // Is this the end of the gesture? if gestureRecognizerState == .ended { // Clear list of touched images - touchedImageIds = [] + touchedImageIDs = [] // Update state of Select button if needed let selectState = updateSelectButton(ofSection: indexPath.section) diff --git a/piwigo/Album/Extensions/AlbumViewController+Share.swift b/piwigo/Album/Extensions/AlbumViewController+Share.swift index ef40e950f..0fe9a50d1 100644 --- a/piwigo/Album/Extensions/AlbumViewController+Share.swift +++ b/piwigo/Album/Extensions/AlbumViewController+Share.swift @@ -23,135 +23,176 @@ extension AlbumViewController // MARK: Share Images @objc func shareSelection() { - initSelection(beforeAction: .share) + initSelection(ofImagesWithIDs: selectedImageIDs, beforeAction: .share, contextually: false) } - func checkPhotoLibraryAccessBeforeShare() { + func checkPhotoLibraryAccessBeforeSharing(imagesWithID imageIDs: Set, contextually: Bool) { // Check autorisation to access Photo Library (camera roll) if #available(iOS 14, *) { PhotosFetch.shared.checkPhotoLibraryAuthorizationStatus( for: PHAccessLevel.addOnly, for: self, onAccess: { [self] in // User allowed to save image in camera roll - presentShareImageViewController(withCameraRollAccess: true) + shareImages(withID: imageIDs, withCameraRollAccess: true, contextually: contextually) }, onDeniedAccess: { [self] in // User not allowed to save image in camera roll - DispatchQueue.main.async { [self] in - presentShareImageViewController(withCameraRollAccess: false) - } + shareImages(withID: imageIDs, withCameraRollAccess: false, contextually: contextually) }) } else { // Fallback on earlier versions PhotosFetch.shared.checkPhotoLibraryAccessForViewController(nil) { [self] in // User allowed to save image in camera roll - presentShareImageViewController(withCameraRollAccess: true) + shareImages(withID: imageIDs, withCameraRollAccess: true, contextually: contextually) } onDeniedAccess: { [self] in // User not allowed to save image in camera roll - if Thread.isMainThread { - self.presentShareImageViewController(withCameraRollAccess: false) - } else { - DispatchQueue.main.async(execute: { [self] in - presentShareImageViewController(withCameraRollAccess: false) - }) - } + shareImages(withID: imageIDs, withCameraRollAccess: false, contextually: contextually) } } } - func presentShareImageViewController(withCameraRollAccess hasCameraRollAccess: Bool) { + func shareImages(withID imageIDs: Set, withCameraRollAccess hasCameraRollAccess: Bool, contextually: Bool) { // To exclude some activity types - var excludedActivityTypes = [UIActivity.ActivityType]() + var hasVideoItem = false + var totalSize = Int64.zero // Create new activity provider items to pass to the activity view controller - totalNumberOfImages = selectedImageIds.count var itemsToShare: [UIActivityItemProvider] = [] - for selectedImageId in selectedImageIds { - guard let selectedImage = (images.fetchedObjects ?? []).first(where: {$0.pwgID == selectedImageId}) - else { continue } - if selectedImage.isVideo { - // Case of a video - let videoItemProvider = ShareVideoActivityItemProvider(placeholderImage: selectedImage) - - // Use delegation to monitor the progress of the item method - videoItemProvider.delegate = self - - // Add to list of items to share - itemsToShare.append(videoItemProvider) - - // Exclude "assign to contact" activity - excludedActivityTypes.append(.assignToContact) - - } else { - // Case of an image - let imageItemProvider = ShareImageActivityItemProvider(placeholderImage: selectedImage) - - // Use delegation to monitor the progress of the item method - imageItemProvider.delegate = self - - // Add to list of items to share - itemsToShare.append(imageItemProvider) + + // Loop over images +// timeCounter = CFAbsoluteTimeGetCurrent() + for imageID in imageIDs { + autoreleasepool { + if let image = (images.fetchedObjects ?? []).first(where: {$0.pwgID == imageID}) { + if image.isVideo { + // Case of a video + let videoItemProvider = ShareVideoActivityItemProvider(placeholderImage: image, contextually: contextually) + + // Use delegation to monitor the progress of the item method + videoItemProvider.delegate = self + + // Add to list of items to share + itemsToShare.append(videoItemProvider) + + // To exclude some activities + hasVideoItem = true + totalSize += image.fileSize + } + else { + // Case of an image + let imageItemProvider = ShareImageActivityItemProvider(placeholderImage: image, contextually: contextually) + + // Use delegation to monitor the progress of the item method + imageItemProvider.delegate = self + + // Add to list of items to share + itemsToShare.append(imageItemProvider) + + // To exclude some activities + totalSize += image.fileSize + } + } } } +// let duration = (CFAbsoluteTimeGetCurrent() - timeCounter)*1000 +// print("••> completed in \(duration.rounded()) ms") - // Create an activity view controller with the activity provider item. - // ShareImageActivityItemProvider's superclass conforms to the UIActivityItemSource protocol - let activityViewController = UIActivityViewController(activityItems: itemsToShare, applicationActivities: nil) + // Close HUD if needed + DispatchQueue.main.async { + self.navigationController?.hideHUD { [self] in + // Check that the items size is acceptable for the device + let count = itemsToShare.count + let deviceMemory = UIDevice.current.modelMemorySize * 1024 * 1024 + if totalSize * 5 > deviceMemory { // i.e. 20% of available memory + let title = NSLocalizedString("shareFailError_title", comment: "Share Fail") + let message = NSLocalizedString("shareFailError_tooLarge", comment: "Selection too large to share") + let error = ByteCountFormatter.string(fromByteCount: totalSize, countStyle: .file) + self.navigationController?.dismissPiwigoError(withTitle: title, message: message, errorMessage: error ) { } + return + } + + // Create an activity view controller with the activity provider item. + // ShareImageActivityItemProvider's superclass conforms to the UIActivityItemSource protocol + let activityViewController = UIActivityViewController(activityItems: itemsToShare, applicationActivities: nil) - // Exclude camera roll activity if needed - if !hasCameraRollAccess { - // Exclude "camera roll" activity when the Photo Library is not accessible - excludedActivityTypes.append(.saveToCameraRoll) - } - activityViewController.excludedActivityTypes = Array(excludedActivityTypes) - - // Delete image/video files and remove observers after dismissing activity view controller - activityViewController.completionWithItemsHandler = { [self] activityType, completed, returnedItems, activityError in - // NSLog(@"Activity Type selected: %@", activityType); - if completed { - // NSLog(@"Selected activity was performed and returned error:%ld", (long)activityError.code); - // Delete shared files & remove observers - NotificationCenter.default.post(name: .pwgDidShare, object: nil) - - // Deselect images - cancelSelect() - - // Close HUD with success - presentedViewController?.updateHUDwithSuccess() { [self] in - presentedViewController?.hideHUD(afterDelay: pwgDelayHUD) { [self] in - // Close ActivityView - presentedViewController?.dismiss(animated: true) + // Exclude some activities if needed + var excludedActivityTypes = [UIActivity.ActivityType]() + if hasVideoItem || count > 1 { + excludedActivityTypes.append(.assignToContact) + if #available(iOS 16.4, *) { + excludedActivityTypes.append(.addToHomeScreen) } } - } else { - if activityType == nil { - // User dismissed the view controller without making a selection. - updateBarsInSelectMode() - } else { - // Check what to do with selection - if selectedImageIds.isEmpty { - cancelSelect() + if !hasCameraRollAccess { + excludedActivityTypes.append(.saveToCameraRoll) + } + if totalSize * 10 > deviceMemory { // i.e. 10% of available memory + excludedActivityTypes.append(.copyToPasteboard) + } + activityViewController.excludedActivityTypes = Array(excludedActivityTypes) + + // Delete image/video files and remove observers after dismissing activity view controller + activityViewController.completionWithItemsHandler = { [self] activityType, completed, returnedItems, activityError in + // NSLog(@"Activity Type selected: %@", activityType); + if completed { + // NSLog(@"Selected activity was performed and returned error:%ld", (long)activityError.code); + // Delete shared files & remove observers + NotificationCenter.default.post(name: .pwgDidShare, object: nil) + + // Deselect images if needed + if contextually { + setEnableStateOfButtons(true) + } else { + cancelSelect() + } + + // Close HUD with success + presentedViewController?.updateHUDwithSuccess() { [self] in + presentedViewController?.hideHUD(afterDelay: pwgDelayHUD) { [self] in + // Close ActivityView + presentedViewController?.dismiss(animated: true) + } + } } else { - setEnableStateOfButtons(true) - } + if activityType == nil { + // User dismissed the view controller without making a selection. + setEnableStateOfButtons(true) + } else { + // Check what to do with selection + if contextually { + setEnableStateOfButtons(true) + } else { + if selectedImageIDs.isEmpty { + cancelSelect() + } else { + setEnableStateOfButtons(true) + } + } - // Cancel download task - NotificationCenter.default.post(name: .pwgCancelDownload, object: nil) + // Cancel download task + NotificationCenter.default.post(name: .pwgCancelDownload, object: nil) - // Delete shared file & remove observers - NotificationCenter.default.post(name: .pwgDidShare, object: nil) + // Delete shared file & remove observers + NotificationCenter.default.post(name: .pwgDidShare, object: nil) - // Close ActivityView - presentedViewController?.dismiss(animated: true) + // Close ActivityView + presentedViewController?.dismiss(animated: true) + } + } } - } - } - // Present share image activity view controller - if let parent = parent as? AlbumViewController { - activityViewController.popoverPresentationController?.barButtonItem = parent.shareBarButton + // Present share image activity view controller + activityViewController.view.tag = count + if isSelect, contextually == false { + activityViewController.popoverPresentationController?.barButtonItem = shareBarButton + } else if let imageID = imageIDs.first, + let visibleCells = collectionView?.visibleCells, + let cell = visibleCells.first(where: { ($0 as? ImageCollectionViewCell)?.imageData.pwgID == imageID}) { + activityViewController.popoverPresentationController?.sourceView = cell.contentView + } + present(activityViewController, animated: true) + } } - present(activityViewController, animated: true) } @objc func cancelShareImages() { @@ -167,7 +208,8 @@ extension AlbumViewController: ShareImageActivityItemProviderDelegate func imageActivityItemProviderPreprocessingDidBegin(_ imageActivityItemProvider: UIActivityItemProvider?, withTitle title: String) { // Show HUD to let the user know the image is being downloaded in the background. - let detail = String(format: "%d / %d", totalNumberOfImages - selectedImageIds.count + 1, totalNumberOfImages) + let total = presentedViewController?.view.tag ?? 1 + let detail = total > 1 ? String(format: "%d / %d", total - selectedImageIDs.count + 1, total) : nil if presentedViewController?.isShowingHUD() ?? false { presentedViewController?.updateHUD(title: title, detail: detail) } else { @@ -185,26 +227,30 @@ extension AlbumViewController: ShareImageActivityItemProviderDelegate } func imageActivityItemProviderPreprocessingDidEnd(_ imageActivityItemProvider: UIActivityItemProvider?, - withImageId imageId: Int64) { + withImageID imageID: Int64, contextually: Bool) { // Check activity item provider guard let imageActivityItemProvider = imageActivityItemProvider else { return } // Close HUD if imageActivityItemProvider.isCancelled { presentedViewController?.hideHUD { } - } else if selectedImageIds.contains(imageId) { + } else if contextually == false, selectedImageIDs.contains(imageID) { // Remove image from selection - selectedImageIds.remove(imageId) - selectedFavoriteIds.remove(imageId) - selectedVideosIds.remove(imageId) + selectedImageIDs.remove(imageID) + selectedFavoriteIDs.remove(imageID) + selectedVideosIDs.remove(imageID) updateBarsInSelectMode() // Close HUD if last image - if selectedImageIds.count == 0 { + if selectedImageIDs.count == 0 { presentedViewController?.updateHUDwithSuccess { [self] in self.presentedViewController?.hideHUD(afterDelay: pwgDelayHUD) { } } } + } else if contextually { + presentedViewController?.updateHUDwithSuccess { [self] in + self.presentedViewController?.hideHUD(afterDelay: pwgDelayHUD) { } + } } } diff --git a/piwigo/Album/Extensions/AlbumViewController+Upload.swift b/piwigo/Album/Extensions/AlbumViewController+Upload.swift index b5f65be8b..603735df0 100644 --- a/piwigo/Album/Extensions/AlbumViewController+Upload.swift +++ b/piwigo/Album/Extensions/AlbumViewController+Upload.swift @@ -15,6 +15,13 @@ extension AlbumViewController // MARK: - Upload Actions @objc func didTapUploadImagesButton() { // Check autorisation to access Photo Library before uploading + checkPhotoLibraryAccess() + + // Hide CreateAlbum and UploadImages buttons + didCancelTapAddButton() + } + + func checkPhotoLibraryAccess() { if #available(iOS 14, *) { PhotosFetch.shared.checkPhotoLibraryAuthorizationStatus(for: PHAccessLevel.readWrite, for: self, onAccess: { [self] in // Open local albums view controller in new navigation controller @@ -27,9 +34,6 @@ extension AlbumViewController self.presentLocalAlbums() }, onDeniedAccess: { }) } - - // Hide CreateAlbum and UploadImages buttons - didCancelTapAddButton() } private func presentLocalAlbums() { diff --git a/piwigo/Album/ImageCollection/ImageFooterReusableView.swift b/piwigo/Album/ImageCollection/ImageFooterReusableView.swift index 6b43ad5a5..544fbe6c8 100644 --- a/piwigo/Album/ImageCollection/ImageFooterReusableView.swift +++ b/piwigo/Album/ImageCollection/ImageFooterReusableView.swift @@ -30,9 +30,6 @@ class ImageFooterReusableView: UICollectionReusableView { if let nberImagesLabel = nberImagesLabel { addSubview(nberImagesLabel) addConstraints(NSLayoutConstraint.constraintCenter(nberImagesLabel)!) - addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "|-16-[footer]-16-|", - options: [], metrics: nil, views: ["footer": nberImagesLabel - ])) } } diff --git a/piwigo/Album/ImageCollection/ImagePreviewViewController.swift b/piwigo/Album/ImageCollection/ImagePreviewViewController.swift new file mode 100644 index 000000000..997c6bd57 --- /dev/null +++ b/piwigo/Album/ImageCollection/ImagePreviewViewController.swift @@ -0,0 +1,96 @@ +// +// ImagePreviewViewController.swift +// piwigo +// +// Created by Eddy Lelièvre-Berna on 19/08/2024. +// Copyright © 2024 Piwigo.org. All rights reserved. +// + +import Foundation +import UIKit +import piwigoKit + +class ImagePreviewViewController: UIViewController +{ + private var aspectRatio = 1.0 + private let imageView = UIImageView() + + init(imageData: Image) { + super.init(nibName: nil, bundle: nil) + + // Retrieve image + let viewSize = view.bounds.size + let scale = view.traitCollection.displayScale + let sizes = imageData.sizes + aspectRatio = sizes.medium?.aspectRatio ?? sizes.thumb?.aspectRatio ?? 1.0 + var previewSize = pwgImageSize(rawValue: ImageVars.shared.defaultImagePreviewSize) ?? .medium + if imageData.isVideo, previewSize == .fullRes { + previewSize = .xxLarge + } + + // Check if we already have the high-resolution image in cache + if let wantedImage = imageData.cachedThumbnail(ofSize: previewSize) { + // Show high-resolution image in cache + let cachedImage = ImageUtilities.downsample(image: wantedImage, to: viewSize, scale: scale) + setImageView(with: cachedImage) + } else { + // Display thumbnail image which should be in cache + let placeHolder = UIImage(named: "unknownImage")! + let thumbSize = pwgImageSize(rawValue: AlbumVars.shared.defaultThumbnailSize) ?? .thumb + self.setImageView(with: imageData.cachedThumbnail(ofSize: thumbSize) ?? placeHolder) + + // Download high-resolution image + if let imageURL = ImageUtilities.getURL(imageData, ofMinSize: previewSize) { + PwgSession.shared.getImage(withID: imageData.pwgID, ofSize: previewSize, atURL: imageURL, + fromServer: imageData.server?.uuid, fileSize: imageData.fileSize, + placeHolder: placeHolder) { fractionCompleted in +// DispatchQueue.main.async { +// // Show download progress +// self.progressView.progress = fractionCompleted +// } + } completion: { cachedImageURL in + let cachedImage = ImageUtilities.downsample(imageAt: cachedImageURL, to: viewSize, scale: scale) + DispatchQueue.main.async { + // Hide progress view +// self.progressView.isHidden = true + // Replace thumbnail with high-resolution image + self.setImageView(with: cachedImage) + } + } failure: { _ in } + } + } + } + + private func setImageView(with image: UIImage) { + imageView.image = image + imageView.frame.size = image.size + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + imageView.clipsToBounds = true + imageView.contentMode = .scaleAspectFit + imageView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(imageView) + + NSLayoutConstraint.activate([ + imageView.leftAnchor.constraint(equalTo: view.leftAnchor), + imageView.rightAnchor.constraint(equalTo: view.rightAnchor), + imageView.topAnchor.constraint(equalTo: view.topAnchor), + imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + let width = view.bounds.width + let height = width * aspectRatio + preferredContentSize = CGSize(width: width, height: height) + } +} diff --git a/piwigo/Image/Extensions/ImageViewController+Edit.swift b/piwigo/Image/Extensions/ImageViewController+Edit.swift index 4693e2fc0..8e276627d 100644 --- a/piwigo/Image/Extensions/ImageViewController+Edit.swift +++ b/piwigo/Image/Extensions/ImageViewController+Edit.swift @@ -51,7 +51,7 @@ extension ImageViewController // MARK: - EditImageParamsDelegate Methods extension ImageViewController: EditImageParamsDelegate { - func didDeselectImage(withId imageId: Int64) { + func didDeselectImage(withID imageID: Int64) { // Should never be called when the properties of a single image are edited } diff --git a/piwigo/Image/Extensions/ImageViewController+Share.swift b/piwigo/Image/Extensions/ImageViewController+Share.swift index 5296fd942..fdb0d91a6 100644 --- a/piwigo/Image/Extensions/ImageViewController+Share.swift +++ b/piwigo/Image/Extensions/ImageViewController+Share.swift @@ -70,7 +70,7 @@ extension ImageViewController var itemsToShare: [AnyHashable] = [] if imageData.isVideo { // Case of a video - let videoItemProvider = ShareVideoActivityItemProvider(placeholderImage: imageData) + let videoItemProvider = ShareVideoActivityItemProvider(placeholderImage: imageData, contextually: false) // Use delegation to monitor the progress of the item method videoItemProvider.delegate = self @@ -83,7 +83,7 @@ extension ImageViewController } else { // Case of an image - let imageItemProvider = ShareImageActivityItemProvider(placeholderImage: imageData) + let imageItemProvider = ShareImageActivityItemProvider(placeholderImage: imageData, contextually: false) // Use delegation to monitor the progress of the item method imageItemProvider.delegate = self @@ -154,7 +154,9 @@ extension ImageViewController: ShareImageActivityItemProviderDelegate { func imageActivityItemProviderPreprocessingDidBegin(_ imageActivityItemProvider: UIActivityItemProvider?, withTitle title: String) { // Show HUD to let the user know the image is being downloaded in the background. - presentedViewController?.showHUD(withTitle: title, buttonTitle: NSLocalizedString("alertCancelButton", comment: "Cancel"), buttonTarget: self, buttonSelector: #selector(cancelShareImage), inMode: .determinate) + let cancelButton = NSLocalizedString("alertCancelButton", comment: "Cancel") + presentedViewController?.showHUD(withTitle: title, buttonTitle: cancelButton, buttonTarget: self, + buttonSelector: #selector(cancelShareImage), inMode: .determinate) } func imageActivityItemProvider(_ imageActivityItemProvider: UIActivityItemProvider?, preprocessingProgressDidUpdate progress: Float) { @@ -162,7 +164,7 @@ extension ImageViewController: ShareImageActivityItemProviderDelegate presentedViewController?.updateHUD(withProgress: progress) } - func imageActivityItemProviderPreprocessingDidEnd(_ imageActivityItemProvider: UIActivityItemProvider?, withImageId imageId: Int64) { + func imageActivityItemProviderPreprocessingDidEnd(_ imageActivityItemProvider: UIActivityItemProvider?, withImageID imageID: Int64, contextually:Bool) { // Close HUD if imageActivityItemProvider?.isCancelled ?? false { presentedViewController?.hideHUD { } diff --git a/piwigo/Image/ImageUtilities.swift b/piwigo/Image/ImageUtilities.swift index f859be0b3..f419da894 100644 --- a/piwigo/Image/ImageUtilities.swift +++ b/piwigo/Image/ImageUtilities.swift @@ -250,11 +250,21 @@ class ImageUtilities: NSObject { static func downsampledImage(from imageSource: CGImageSource, to pointSize: CGSize, scale: CGFloat) -> UIImage? { // The default display scale for a trait collection is 0.0 (indicating unspecified). // We therefore adopt a scale of 1.0 when the display scale is unspecified. - let maxDimensionInPixels = max(pointSize.width, pointSize.height) * max(scale, 1.0) + let options = [kCGImagePropertyPixelWidth: true, + kCGImagePropertyPixelHeight: true] as CFDictionary + var maxPixelSize = Float(max(pointSize.width, pointSize.height) * max(scale, 1.0)) + if let imageMetadata = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, options) as? [CFString : CFNumber] { + if let maxPixelWidth = imageMetadata[kCGImagePropertyPixelWidth] as NSNumber? { + maxPixelSize = min(maxPixelSize, maxPixelWidth.floatValue) + } + if let maxPixelHeight = imageMetadata[kCGImagePropertyPixelHeight] as NSNumber? { + maxPixelSize = min(maxPixelSize, maxPixelHeight.floatValue) + } + } let downsampleOptions = [kCGImageSourceCreateThumbnailFromImageAlways: true, kCGImageSourceShouldCacheImmediately: true, kCGImageSourceCreateThumbnailWithTransform: true, - kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels] as [CFString : Any] as CFDictionary + kCGImageSourceThumbnailMaxPixelSize: maxPixelSize] as [CFString : Any] as CFDictionary if let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions as CFDictionary) { return UIImage(cgImage: downsampledImage) @@ -269,7 +279,7 @@ class ImageUtilities: NSObject { } else { // Delete corrupted cached image file try? FileManager.default.removeItem(at: imageURL) - return UIImage(named: "placeholder")! + return UIImage(named: "unknownImage")! } } diff --git a/piwigo/Image/ImageViewController.storyboard b/piwigo/Image/ImageViewController.storyboard index 0980ee3ce..33436e94e 100644 --- a/piwigo/Image/ImageViewController.storyboard +++ b/piwigo/Image/ImageViewController.storyboard @@ -1,9 +1,9 @@ - + - + @@ -169,10 +169,10 @@ + - - + @@ -328,7 +328,7 @@ - + diff --git a/piwigo/Image/Parameters/Cells/EditImageThumbCollectionViewCell.swift b/piwigo/Image/Parameters/Cells/EditImageThumbCollectionViewCell.swift index 249d5bbb6..060ad4bd5 100644 --- a/piwigo/Image/Parameters/Cells/EditImageThumbCollectionViewCell.swift +++ b/piwigo/Image/Parameters/Cells/EditImageThumbCollectionViewCell.swift @@ -13,8 +13,8 @@ import UIKit import piwigoKit @objc protocol EditImageThumbnailDelegate: NSObjectProtocol { - func didDeselectImage(withId imageId: Int64) - func didRenameFileOfImage(withId imageId: Int64, andFilename fileName: String) + func didDeselectImage(withID imageID: Int64) + func didRenameFileOfImage(withId imageID: Int64, andFilename fileName: String) } class EditImageThumbCollectionViewCell: UICollectionViewCell @@ -34,7 +34,7 @@ class EditImageThumbCollectionViewCell: UICollectionViewCell @IBOutlet private weak var removeButtonView: UIView! @IBOutlet private weak var removeImageButton: UIButton! - private var imageId = Int64.zero + private var imageID = Int64.zero private var renameFileNameAction: UIAlertAction? private var oldFileName: String? @@ -81,7 +81,7 @@ class EditImageThumbCollectionViewCell: UICollectionViewCell else { return } // Store image ID - imageId = imageData.pwgID + imageID = imageData.pwgID // Image file name if imageData.fileName.isEmpty == false { @@ -206,7 +206,7 @@ class EditImageThumbCollectionViewCell: UICollectionViewCell topViewController?.showHUD(withTitle: NSLocalizedString("renameImageHUD_label", comment: "Renaming Original File…")) // Prepare parameters for renaming the image/video filename - let paramsDict: [String : Any] = ["image_id" : imageId, + let paramsDict: [String : Any] = ["image_id" : imageID, "file" : fileName, "single_value_mode" : "replace"] // Launch request @@ -242,7 +242,7 @@ class EditImageThumbCollectionViewCell: UICollectionViewCell imageFile.text = fileName // Update parent image view - delegate?.didRenameFileOfImage(withId: imageId, andFilename: fileName) + delegate?.didRenameFileOfImage(withId: imageID, andFilename: fileName) }) } } @@ -283,7 +283,7 @@ class EditImageThumbCollectionViewCell: UICollectionViewCell // MARK: - Remove Image from Selection @IBAction func removeImage() { // Notify this deselection to parent view - delegate?.didDeselectImage(withId: imageId) + delegate?.didDeselectImage(withID: imageID) } } diff --git a/piwigo/Image/Parameters/Cells/EditImageThumbTableViewCell.swift b/piwigo/Image/Parameters/Cells/EditImageThumbTableViewCell.swift index d2a24c24c..5466af001 100644 --- a/piwigo/Image/Parameters/Cells/EditImageThumbTableViewCell.swift +++ b/piwigo/Image/Parameters/Cells/EditImageThumbTableViewCell.swift @@ -12,7 +12,7 @@ import UIKit import piwigoKit @objc protocol EditImageThumbnailCellDelegate: NSObjectProtocol { - func didDeselectImage(withId imageId: Int64) + func didDeselectImage(withID imageID: Int64) func didRenameFileOfImage(_ imageData: Image) } @@ -97,19 +97,19 @@ extension EditImageThumbTableViewCell: UICollectionViewDelegateFlowLayout // MARK: - EditImageThumbnailDelegate Methods extension EditImageThumbTableViewCell: EditImageThumbnailDelegate { - @objc func didDeselectImage(withId imageId: Int64) { + @objc func didDeselectImage(withID imageID: Int64) { // Update data source - let newImages = images?.filter({ $0.pwgID != imageId }) + let newImages = images?.filter({ $0.pwgID != imageID }) images = newImages editImageThumbCollectionView.reloadData() // Deselect image in parent view - delegate?.didDeselectImage(withId: imageId) + delegate?.didDeselectImage(withID: imageID) } - @objc func didRenameFileOfImage(withId imageId: Int64, andFilename fileName: String) { + @objc func didRenameFileOfImage(withId imageID: Int64, andFilename fileName: String) { // Retrieve image data from cache - guard let imageToUpdate = images?.first(where: {$0.pwgID == imageId}) else { return } + guard let imageToUpdate = images?.first(where: {$0.pwgID == imageID}) else { return } // Update image in cache imageToUpdate.fileName = fileName diff --git a/piwigo/Image/Parameters/EditImageParamsViewController.swift b/piwigo/Image/Parameters/EditImageParamsViewController.swift index 1a6277ba5..9d0c174d1 100644 --- a/piwigo/Image/Parameters/EditImageParamsViewController.swift +++ b/piwigo/Image/Parameters/EditImageParamsViewController.swift @@ -13,7 +13,7 @@ import UIKit import piwigoKit @objc protocol EditImageParamsDelegate: NSObjectProtocol { - func didDeselectImage(withId imageId: Int64) + func didDeselectImage(withID imageID: Int64) func didChangeImageParameters(_ imageData: Image) func didFinishEditingParameters() } @@ -169,8 +169,8 @@ class EditImageParamsViewController: UIViewController preferredContentSize = CGSize(width: pwgPadSubViewWidth, height: ceil(mainScreenBounds.height * 2 / 3)) let navBarHeight = navigationController?.navigationBar.bounds.size.height ?? 0.0 - editImageParamsTableView.contentInset = UIEdgeInsets(top: 0.0, left: 0.0, - bottom: navBarHeight, right: 0.0) + editImageParamsTableView.contentInset = UIEdgeInsets(top: CGFloat.zero, left: CGFloat.zero, + bottom: navBarHeight, right: CGFloat.zero) } // Reload table view diff --git a/piwigo/Image/Parameters/Extensions/EditImageParamsViewController+Keyboard.swift b/piwigo/Image/Parameters/Extensions/EditImageParamsViewController+Keyboard.swift index 42e6b8268..250546270 100644 --- a/piwigo/Image/Parameters/Extensions/EditImageParamsViewController+Keyboard.swift +++ b/piwigo/Image/Parameters/Extensions/EditImageParamsViewController+Keyboard.swift @@ -27,7 +27,8 @@ extension EditImageParamsViewController let viewIntersection = convertedViewFrame.intersection(convertedKeyboardFrameEnd) if viewIntersection.height > 0 { // Extend the content view to allow full scrolling - editImageParamsTableView.contentInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: viewIntersection.height, right: 0.0) + editImageParamsTableView.contentInset = UIEdgeInsets(top: CGFloat.zero, left: CGFloat.zero, + bottom: viewIntersection.height, right: CGFloat.zero) } } diff --git a/piwigo/Image/Parameters/Extensions/EditImageParamsViewController+ThumbnailCell.swift b/piwigo/Image/Parameters/Extensions/EditImageParamsViewController+ThumbnailCell.swift index c8e2e39e1..833e9d8ca 100644 --- a/piwigo/Image/Parameters/Extensions/EditImageParamsViewController+ThumbnailCell.swift +++ b/piwigo/Image/Parameters/Extensions/EditImageParamsViewController+ThumbnailCell.swift @@ -12,7 +12,7 @@ import piwigoKit // MARK: - EditImageThumbnailCellDelegate Methods extension EditImageParamsViewController: EditImageThumbnailCellDelegate { - func didDeselectImage(withId imageId: Int64) { + func didDeselectImage(withID imageID: Int64) { // Hide picker if needed let indexPath = IndexPath(row: EditImageParamsOrder.datePicker.rawValue, section: 0) if hasDatePicker { @@ -25,7 +25,7 @@ extension EditImageParamsViewController: EditImageThumbnailCellDelegate // Update data source let timeInterval = commonDateCreated.timeIntervalSince(oldCreationDate) - images.removeAll(where: {$0.pwgID == imageId}) + images.removeAll(where: {$0.pwgID == imageID}) // Update common creation date if needed oldCreationDate = Date(timeIntervalSinceReferenceDate: images[0].dateCreated) @@ -35,7 +35,7 @@ extension EditImageParamsViewController: EditImageThumbnailCellDelegate editImageParamsTableView.reloadData() // Deselect image in album view - delegate?.didDeselectImage(withId: imageId) + delegate?.didDeselectImage(withID: imageID) } func didRenameFileOfImage(_ imageData: Image) { diff --git a/piwigo/Image/Share/ShareImageActivityItemProvider.swift b/piwigo/Image/Share/ShareImageActivityItemProvider.swift index c33782def..fbfc63b87 100644 --- a/piwigo/Image/Share/ShareImageActivityItemProvider.swift +++ b/piwigo/Image/Share/ShareImageActivityItemProvider.swift @@ -22,7 +22,7 @@ protocol ShareImageActivityItemProviderDelegate: NSObjectProtocol { func imageActivityItemProvider(_ imageActivityItemProvider: UIActivityItemProvider?, preprocessingProgressDidUpdate progress: Float) func imageActivityItemProviderPreprocessingDidEnd(_ imageActivityItemProvider: UIActivityItemProvider?, - withImageId imageId: Int64) + withImageID imageID: Int64, contextually: Bool) func showError(withTitle title: String, andMessage message: String?) } @@ -38,8 +38,10 @@ class ShareImageActivityItemProvider: UIActivityItemProvider { private var cachedFileURL: URL? // URL of cached image file private var imageFileURL: URL // URL of shared image file private var isCancelledByUser = false // Flag updated when pressing Cancel - - // MARK: - Progress Faction + private var contextually = false + + + // MARK: - Progress Fraction private var _progressFraction: Float = 0.0 private var progressFraction: Float { get { @@ -57,9 +59,12 @@ class ShareImageActivityItemProvider: UIActivityItemProvider { // MARK: - Placeholder Image - init(placeholderImage: Image) { + init(placeholderImage: Image, contextually: Bool) { // Store Piwigo image data for future use self.imageData = placeholderImage + + // Remember if this video is shared from a contextual menu + self.contextually = contextually // We use the thumbnail image stored in cache let size = pwgImageSize(rawValue: AlbumVars.shared.defaultThumbnailSize) ?? .thumb @@ -107,7 +112,8 @@ class ShareImageActivityItemProvider: UIActivityItemProvider { // Notify the delegate on the main thread that the processing is beginning. DispatchQueue.main.async(execute: { - self.delegate?.imageActivityItemProviderPreprocessingDidBegin(self, withTitle: NSLocalizedString("downloadingImage", comment: "Downloading Photo")) + let title = NSLocalizedString("downloadingImage", comment: "Downloading Photo") + self.delegate?.imageActivityItemProviderPreprocessingDidBegin(self, withTitle: title) }) // Get the maximum accepted image size (infinity for largest) @@ -333,7 +339,7 @@ class ShareImageActivityItemProvider: UIActivityItemProvider { private func preprocessingDidEnd() { // Notify the delegate on the main thread that the processing is cancelled. DispatchQueue.main.async(execute: { - self.delegate?.imageActivityItemProviderPreprocessingDidEnd(self, withImageId: self.imageData.pwgID) + self.delegate?.imageActivityItemProviderPreprocessingDidEnd(self, withImageID: self.imageData.pwgID, contextually: self.contextually) }) } diff --git a/piwigo/Image/Share/ShareUtilities.swift b/piwigo/Image/Share/ShareUtilities.swift index d87bd24d2..127de618d 100644 --- a/piwigo/Image/Share/ShareUtilities.swift +++ b/piwigo/Image/Share/ShareUtilities.swift @@ -245,13 +245,8 @@ class ShareUtilities { // MARK: - UIActivityType Extensions -extension UIActivity.ActivityType: Comparable { - - // Allows to compare and sort activity types - public static func < (lhs: UIActivity.ActivityType, rhs: UIActivity.ActivityType) -> Bool { - lhs.rawValue < rhs.rawValue - } - +extension UIActivity.ActivityType +{ // Return the maximum resolution accepted for some activity types func imageMaxSize() -> Int { // Get the maximum image size according to the activity type (infinity if no limit) diff --git a/piwigo/Image/Share/ShareVideoActivityItemProvider.swift b/piwigo/Image/Share/ShareVideoActivityItemProvider.swift index 3101bb513..1df1769f6 100644 --- a/piwigo/Image/Share/ShareVideoActivityItemProvider.swift +++ b/piwigo/Image/Share/ShareVideoActivityItemProvider.swift @@ -28,6 +28,7 @@ class ShareVideoActivityItemProvider: UIActivityItemProvider { private var cachedFileURL: URL? // URL of cached video file private var imageFileURL: URL // URL of shared video file private var isCancelledByUser = false // Flag updated when pressing Cancel + private var contextually = false // MARK: - Progress Faction @@ -48,9 +49,12 @@ class ShareVideoActivityItemProvider: UIActivityItemProvider { // MARK: - Placeholder Image - init(placeholderImage: Image) { + init(placeholderImage: Image, contextually: Bool) { // Store Piwigo image data for future use self.imageData = placeholderImage + + // Remember if this video is shared from a contextual menu + self.contextually = contextually // We use the thumbnail image stored in cache let size = pwgImageSize(rawValue: AlbumVars.shared.defaultThumbnailSize) ?? .thumb @@ -98,7 +102,8 @@ class ShareVideoActivityItemProvider: UIActivityItemProvider { // Notify the delegate on the main thread that the processing is beginning. DispatchQueue.main.async(execute: { - self.delegate?.imageActivityItemProviderPreprocessingDidBegin(self, withTitle: NSLocalizedString("downloadingVideo", comment: "Downloading Video")) + let title = NSLocalizedString("downloadingVideo", comment: "Downloading Video") + self.delegate?.imageActivityItemProviderPreprocessingDidBegin(self, withTitle: title) }) // Get the server ID and optimum available image size @@ -350,7 +355,7 @@ class ShareVideoActivityItemProvider: UIActivityItemProvider { private func preprocessingDidEnd() { // Notify the delegate on the main thread that the processing is cancelled. DispatchQueue.main.async(execute: { - self.delegate?.imageActivityItemProviderPreprocessingDidEnd(self, withImageId: self.imageData.pwgID) + self.delegate?.imageActivityItemProviderPreprocessingDidEnd(self, withImageID: self.imageData.pwgID, contextually: self.contextually) }) } diff --git a/piwigo/In-App Intents/fr.lproj/InAppIntents.strings b/piwigo/In-App Intents/fr.lproj/InAppIntents.strings index d0eb9890c..8d09931e1 100644 --- a/piwigo/In-App Intents/fr.lproj/InAppIntents.strings +++ b/piwigo/In-App Intents/fr.lproj/InAppIntents.strings @@ -2,19 +2,19 @@ "1yW2tL" = "${photos} photo(s) ajoutée(s) à la file d'attente."; -"61lMsg" = "Téléchargement automatique des photos"; +"61lMsg" = "Téléversement automatique des photos"; "7zXloB" = "photos"; -"85qLPb" = "Télécharger automatiquement des photos"; +"85qLPb" = "Téléverser automatiquement des photos"; "DYv5Ms" = "${error}"; -"OEvuBy" = "Téléchargement automatique des photos"; +"OEvuBy" = "Téléversement automatique des photos"; -"QlzJjd" = "Ajoute les photos récentes à la file d'attente de téléchargement en arrière-plan conformément aux paramètres de téléchargement automatique."; +"QlzJjd" = "Ajoute les photos récentes à la file d'attente de téléversement en arrière-plan conformément aux paramètres de téléversement automatique."; -"Vz5KMD" = "Ajoute les photos récentes à la file d'attente de téléchargement en arrière-plan."; +"Vz5KMD" = "Ajoute les photos récentes à la file d'attente de téléversement en arrière-plan."; "YtBidq" = "${photos} photo(s) ajoutée(s) à la file d'attente."; diff --git a/piwigo/Info.plist b/piwigo/Info.plist index cf3a504fe..d274c5d78 100644 --- a/piwigo/Info.plist +++ b/piwigo/Info.plist @@ -25,7 +25,7 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 585 + 591 INIntentsSupported AutoUploadIntent @@ -41,6 +41,8 @@ NSFaceIDUsageDescription Use Face ID instead of a passcode to access your albums. + NSLocalNetworkUsageDescription + Piwigo uses the local network to access your Piwigo server. NSPhotoLibraryUsageDescription so that it will be able to download images to Photos and upload images to your Piwigo. NSSiriUsageDescription diff --git a/piwigo/Login/LoginUtilities.swift b/piwigo/Login/LoginUtilities.swift index e180a764d..58e5ba2a9 100644 --- a/piwigo/Login/LoginUtilities.swift +++ b/piwigo/Login/LoginUtilities.swift @@ -115,7 +115,7 @@ class LoginUtilities: NSObject { } // Calculate number of thumbnails per row for that selection - let minNberOfImages = AlbumUtilities.imagesPerRowInPortrait(forMaxWidth: (pwgImageSize(rawValue: AlbumVars.shared.defaultThumbnailSize) ?? .thumb).minPoints) + let minNberOfImages: Int = AlbumUtilities.imagesPerRowInPortrait(forMaxWidth: (pwgImageSize(rawValue: AlbumVars.shared.defaultThumbnailSize) ?? .thumb).minPoints) // Make sure that default number fits inside selected range AlbumVars.shared.thumbnailsPerRowInPortrait = max(AlbumVars.shared.thumbnailsPerRowInPortrait, minNberOfImages); diff --git a/piwigo/Login/LoginViewController+Keyboard.swift b/piwigo/Login/LoginViewController+Keyboard.swift index c26725e9d..e9d099f3f 100644 --- a/piwigo/Login/LoginViewController+Keyboard.swift +++ b/piwigo/Login/LoginViewController+Keyboard.swift @@ -31,7 +31,8 @@ extension LoginViewController // Update vertical inset and offset if needed if missingHeight > CGFloat.zero { // Update vertical inset and offset - let insets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: missingHeight, right: 0.0) + let insets = UIEdgeInsets(top: CGFloat.zero, left: CGFloat.zero, + bottom: missingHeight, right: CGFloat.zero) scrollView.contentInset = insets let point = CGPointMake(0, oldVertOffset + missingHeight) scrollView.setContentOffset(point, animated: true) diff --git a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/Contents.json b/piwigo/Resources/Images.xcassets/AppIcon.appiconset/Contents.json index 0141b06f2..e91cbd33d 100644 --- a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/Contents.json +++ b/piwigo/Resources/Images.xcassets/AppIcon.appiconset/Contents.json @@ -1,116 +1,38 @@ { "images" : [ { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "icon-20@2x-1.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "icon-20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "icon-29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "icon-29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "icon-40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "icon-40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "icon-60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "icon-60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "icon-20.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "icon-20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "icon-29.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "icon-29@2x-1.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "icon-40.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "icon-40@2x-1.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "icon-76.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "icon-76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "icon-83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "icon-1024.png", - "scale" : "1x" + "filename" : "Icon-Light-1024x1024.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "Icon-Dark-1024x1024.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "filename" : "Icon-Tinted-1024x1024.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/Icon-Dark-1024x1024.png b/piwigo/Resources/Images.xcassets/AppIcon.appiconset/Icon-Dark-1024x1024.png new file mode 100644 index 000000000..1125717f7 Binary files /dev/null and b/piwigo/Resources/Images.xcassets/AppIcon.appiconset/Icon-Dark-1024x1024.png differ diff --git a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/Icon-Light-1024x1024.png b/piwigo/Resources/Images.xcassets/AppIcon.appiconset/Icon-Light-1024x1024.png new file mode 100644 index 000000000..0170cf262 Binary files /dev/null and b/piwigo/Resources/Images.xcassets/AppIcon.appiconset/Icon-Light-1024x1024.png differ diff --git a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/Icon-Tinted-1024x1024.png b/piwigo/Resources/Images.xcassets/AppIcon.appiconset/Icon-Tinted-1024x1024.png new file mode 100644 index 000000000..65e77c351 Binary files /dev/null and b/piwigo/Resources/Images.xcassets/AppIcon.appiconset/Icon-Tinted-1024x1024.png differ diff --git a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-1024.png b/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-1024.png deleted file mode 100644 index 3a7e07baf..000000000 Binary files a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-1024.png and /dev/null differ diff --git a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-20.png b/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-20.png deleted file mode 100644 index a2391bf83..000000000 Binary files a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-20.png and /dev/null differ diff --git a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-20@2x-1.png b/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-20@2x-1.png deleted file mode 100644 index 48c36585b..000000000 Binary files a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-20@2x-1.png and /dev/null differ diff --git a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-20@2x.png b/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-20@2x.png deleted file mode 100644 index 48c36585b..000000000 Binary files a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-20@2x.png and /dev/null differ diff --git a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-20@3x.png b/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-20@3x.png deleted file mode 100644 index 1f7f19d8c..000000000 Binary files a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-20@3x.png and /dev/null differ diff --git a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-29.png b/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-29.png deleted file mode 100644 index 5f43f81c4..000000000 Binary files a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-29.png and /dev/null differ diff --git a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-29@2x-1.png b/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-29@2x-1.png deleted file mode 100644 index 2e75b977a..000000000 Binary files a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-29@2x-1.png and /dev/null differ diff --git a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-29@2x.png b/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-29@2x.png deleted file mode 100644 index 2e75b977a..000000000 Binary files a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-29@2x.png and /dev/null differ diff --git a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-29@3x.png b/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-29@3x.png deleted file mode 100644 index 29bc7de30..000000000 Binary files a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-29@3x.png and /dev/null differ diff --git a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-40.png b/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-40.png deleted file mode 100644 index 48c36585b..000000000 Binary files a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-40.png and /dev/null differ diff --git a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-40@2x-1.png b/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-40@2x-1.png deleted file mode 100644 index 89a56cb39..000000000 Binary files a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-40@2x-1.png and /dev/null differ diff --git a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-40@2x.png b/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-40@2x.png deleted file mode 100644 index 89a56cb39..000000000 Binary files a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-40@2x.png and /dev/null differ diff --git a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-40@3x.png b/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-40@3x.png deleted file mode 100644 index be1897685..000000000 Binary files a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-40@3x.png and /dev/null differ diff --git a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-60@2x.png b/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-60@2x.png deleted file mode 100644 index be1897685..000000000 Binary files a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-60@2x.png and /dev/null differ diff --git a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-60@3x.png b/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-60@3x.png deleted file mode 100644 index b0255385f..000000000 Binary files a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-60@3x.png and /dev/null differ diff --git a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-76.png b/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-76.png deleted file mode 100644 index e4ccf890a..000000000 Binary files a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-76.png and /dev/null differ diff --git a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-76@2x.png b/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-76@2x.png deleted file mode 100644 index 5412bb2db..000000000 Binary files a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-76@2x.png and /dev/null differ diff --git a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-83.5@2x.png b/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-83.5@2x.png deleted file mode 100644 index 42d9089c2..000000000 Binary files a/piwigo/Resources/Images.xcassets/AppIcon.appiconset/icon-83.5@2x.png and /dev/null differ diff --git a/piwigo/Resources/Images.xcassets/AppIconShare.imageset/AppIconShare@1x.png b/piwigo/Resources/Images.xcassets/AppIconShare.imageset/AppIconShare@1x.png deleted file mode 100644 index 13d11c999..000000000 Binary files a/piwigo/Resources/Images.xcassets/AppIconShare.imageset/AppIconShare@1x.png and /dev/null differ diff --git a/piwigo/Resources/Images.xcassets/AppIconShare.imageset/AppIconShare@2x.png b/piwigo/Resources/Images.xcassets/AppIconShare.imageset/AppIconShare@2x.png deleted file mode 100644 index 06a0f30eb..000000000 Binary files a/piwigo/Resources/Images.xcassets/AppIconShare.imageset/AppIconShare@2x.png and /dev/null differ diff --git a/piwigo/Resources/Images.xcassets/AppIconShare.imageset/AppIconShare@3x.png b/piwigo/Resources/Images.xcassets/AppIconShare.imageset/AppIconShare@3x.png deleted file mode 100644 index ef7fc12c1..000000000 Binary files a/piwigo/Resources/Images.xcassets/AppIconShare.imageset/AppIconShare@3x.png and /dev/null differ diff --git a/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Contents.json b/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Contents.json index 4a8ce05e6..7fb6c2198 100644 --- a/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Contents.json +++ b/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Contents.json @@ -1,17 +1,83 @@ { "images" : [ { - "filename" : "AppIconShare@1x.png", + "filename" : "Icon-Light-70px@1x.png", "idiom" : "universal", "scale" : "1x" }, { - "filename" : "AppIconShare@2x.png", + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "light" + } + ], + "filename" : "Icon-Light-70px@1x 1.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "Icon-Dark-70px@1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Icon-Light-70px@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "light" + } + ], + "filename" : "Icon-Light-70px@2x 1.png", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "AppIconShare@3x.png", + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "Icon-Dark-70px@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Icon-Light-70px@3x.png", + "idiom" : "universal", + "scale" : "3x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "light" + } + ], + "filename" : "Icon-Light-70px@3x 1.png", + "idiom" : "universal", + "scale" : "3x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "Icon-Dark-70px@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Icon-Dark-70px@1x.png b/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Icon-Dark-70px@1x.png new file mode 100644 index 000000000..b0f48a9cd Binary files /dev/null and b/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Icon-Dark-70px@1x.png differ diff --git a/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Icon-Dark-70px@2x.png b/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Icon-Dark-70px@2x.png new file mode 100644 index 000000000..86f5dadd8 Binary files /dev/null and b/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Icon-Dark-70px@2x.png differ diff --git a/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Icon-Dark-70px@3x.png b/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Icon-Dark-70px@3x.png new file mode 100644 index 000000000..9430801a9 Binary files /dev/null and b/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Icon-Dark-70px@3x.png differ diff --git a/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Icon-Light-70px@1x 1.png b/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Icon-Light-70px@1x 1.png new file mode 100644 index 000000000..dab426d12 Binary files /dev/null and b/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Icon-Light-70px@1x 1.png differ diff --git a/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Icon-Light-70px@1x.png b/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Icon-Light-70px@1x.png new file mode 100644 index 000000000..dab426d12 Binary files /dev/null and b/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Icon-Light-70px@1x.png differ diff --git a/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Icon-Light-70px@2x 1.png b/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Icon-Light-70px@2x 1.png new file mode 100644 index 000000000..e5ba8b00e Binary files /dev/null and b/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Icon-Light-70px@2x 1.png differ diff --git a/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Icon-Light-70px@2x.png b/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Icon-Light-70px@2x.png new file mode 100644 index 000000000..e5ba8b00e Binary files /dev/null and b/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Icon-Light-70px@2x.png differ diff --git a/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Icon-Light-70px@3x 1.png b/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Icon-Light-70px@3x 1.png new file mode 100644 index 000000000..4599b3de8 Binary files /dev/null and b/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Icon-Light-70px@3x 1.png differ diff --git a/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Icon-Light-70px@3x.png b/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Icon-Light-70px@3x.png new file mode 100644 index 000000000..4599b3de8 Binary files /dev/null and b/piwigo/Resources/Images.xcassets/AppIconShare.imageset/Icon-Light-70px@3x.png differ diff --git a/piwigo/Resources/Images.xcassets/images/airplay.video.imageset/Contents.json b/piwigo/Resources/Images.xcassets/images/airplay.video.imageset/Contents.json new file mode 100644 index 000000000..29cb42973 --- /dev/null +++ b/piwigo/Resources/Images.xcassets/images/airplay.video.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "airplayvideo.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "airplayvideo@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "airplayvideo@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/piwigo/Resources/Images.xcassets/images/airplay.video.imageset/airplayvideo.png b/piwigo/Resources/Images.xcassets/images/airplay.video.imageset/airplayvideo.png new file mode 100644 index 000000000..d047177a1 Binary files /dev/null and b/piwigo/Resources/Images.xcassets/images/airplay.video.imageset/airplayvideo.png differ diff --git a/piwigo/Resources/Images.xcassets/images/airplay.video.imageset/airplayvideo@2x.png b/piwigo/Resources/Images.xcassets/images/airplay.video.imageset/airplayvideo@2x.png new file mode 100644 index 000000000..26d6c2318 Binary files /dev/null and b/piwigo/Resources/Images.xcassets/images/airplay.video.imageset/airplayvideo@2x.png differ diff --git a/piwigo/Resources/Images.xcassets/images/airplay.video.imageset/airplayvideo@3x.png b/piwigo/Resources/Images.xcassets/images/airplay.video.imageset/airplayvideo@3x.png new file mode 100644 index 000000000..5c983e8f7 Binary files /dev/null and b/piwigo/Resources/Images.xcassets/images/airplay.video.imageset/airplayvideo@3x.png differ diff --git a/piwigo/Resources/Images.xcassets/images/imageUpload.imageset/Contents.json b/piwigo/Resources/Images.xcassets/images/imageUpload.imageset/Contents.json index 358388c59..2d7f8d0c5 100644 --- a/piwigo/Resources/Images.xcassets/images/imageUpload.imageset/Contents.json +++ b/piwigo/Resources/Images.xcassets/images/imageUpload.imageset/Contents.json @@ -1,23 +1,26 @@ { "images" : [ { - "idiom" : "universal", "filename" : "upload@1x.png", + "idiom" : "universal", "scale" : "1x" }, { - "idiom" : "universal", "filename" : "upload@2x.png", + "idiom" : "universal", "scale" : "2x" }, { - "idiom" : "universal", "filename" : "upload@3x.png", + "idiom" : "universal", "scale" : "3x" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" } -} \ No newline at end of file +} diff --git a/piwigo/Resources/ar.lproj/InfoPlist.strings b/piwigo/Resources/ar.lproj/InfoPlist.strings index 73e9abc02..bbcea554c 100644 --- a/piwigo/Resources/ar.lproj/InfoPlist.strings +++ b/piwigo/Resources/ar.lproj/InfoPlist.strings @@ -8,3 +8,4 @@ "NSPhotoLibraryUsageDescription" = "بحيث أنها سوف تكون قادرة على تحميل الصور إلى الصور وتحميل الصور إلى Piwigo الخاص بك."; "NSSiriUsageDescription" = "حتى يتمكن Piwigo من تحميل الصور باستخدام Siri."; +"NSLocalNetworkUsageDescription" = "يستخدم Piwigo الشبكة المحلية للوصول إلى خادم Piwigo الخاص بك."; diff --git a/piwigo/Resources/ar.lproj/Localizable.strings b/piwigo/Resources/ar.lproj/Localizable.strings index 473de5054..205d9da37 100644 --- a/piwigo/Resources/ar.lproj/Localizable.strings +++ b/piwigo/Resources/ar.lproj/Localizable.strings @@ -155,6 +155,9 @@ "categoryImageList_selectButton" = "حدد"; "categoryImageList_deselectButton" = "Deselect"; "categoryImageList_noDataError" = "خطأ لا توجد بيانات"; +"categoryImageList_share" = "Share"; +"categoryImageList_favorite" = "Favorite"; +"categoryImageList_unfavorite" = "Unfavorite"; // Albums "Discover" "categoryDiscover_title" = "Discover"; @@ -223,9 +226,6 @@ "categoryUpload_otherAlbums" = "Other Albums"; "categoryUpload_pasteboard" = "Clipboard"; -"categoyUpload_loadSubCategories" = "تحميل"; -"categoryUpload_images" = "تحميل الصور"; - // Default album "setDefaultCategory_select" = "Please select an album or sub-album which will become the new root album."; "setDefaultCategory_title" = "Default Album"; @@ -244,6 +244,7 @@ "createAlbumError_message" = "فشل إنشاء ألبوم جديد"; // Album options (swipe actions) +"categoryCellOption_addPhotos" = "Add Photos"; "categoryCellOption_rename" = "إعادة التسمية"; "categoryCellOption_move" = "نقل"; "categoryCellOption_delete" = "حذف"; @@ -519,14 +520,6 @@ "imageUploadDetails_tags" = "العلامات:"; "imageUploadDetails_description" = "الوصف:"; -"imageUploadDetailsView_title" = "الصور"; -"imageUploadDetailsButton_title" = "تحميل"; -"imageUploadDetailsEdit_title" = "تعديل الصور للتحميل"; -"imageUploadDetailsUploading_title" = "الصور التي يتم تحميلها"; -"imageUploadDetailsCell_uploadComplete" = "اكتملت! جار الانتهاء..."; - -"imageUploadRemove" = "Removing uploaded photos"; - "imageUploadTableCell_waiting" = "Waiting..."; "imageUploadTableCell_preparing" = "Preparing..."; "imageUploadTableCell_prepared" = "Ready for upload..."; @@ -541,8 +534,6 @@ "imageUploadResumeSingle" = "Resume Upload"; "imageUploadResumeSeveral" = "Resume %@ Uploads"; -"imageUploadProgressBar_zero" = "جار التحميل 0/0"; -"imageUploadProgressBar_nonZero" = "جار التحميل %@/%@"; "imageUploadProgressBar_completed" = "اكتملت"; "imageUploadCompleted_title" = "اكتمل التحميل"; "imageUploadCompleted_message" = "صورة / فيديو تم تحميلها إلى خادم Piwigo الخاص بك."; @@ -618,6 +609,7 @@ "shareFailError_title" = "Share Fail"; "shareMetadataError_message" = "Cannot strip private metadata"; +"shareFailError_tooLarge" = "Selection too large to share"; // =========================================================================== // ==> LOCAL ALBUMS & IMAGES / Photo Library diff --git a/piwigo/Resources/da.lproj/InfoPlist.strings b/piwigo/Resources/da.lproj/InfoPlist.strings index a2e1121ca..ab07e5498 100644 --- a/piwigo/Resources/da.lproj/InfoPlist.strings +++ b/piwigo/Resources/da.lproj/InfoPlist.strings @@ -8,3 +8,4 @@ "NSPhotoLibraryUsageDescription" = "så den vil have mulighed for at gemme billeder i kamerarulle og uploade billeder til din Piwigo."; "NSSiriUsageDescription" = "så Piwigo kan uploade billeder med Siri."; +"NSLocalNetworkUsageDescription" = "Piwigo bruger det lokale netværk til at tilgå din Piwigo-server."; diff --git a/piwigo/Resources/da.lproj/Localizable.strings b/piwigo/Resources/da.lproj/Localizable.strings index 85ed57f4d..3138958d5 100644 --- a/piwigo/Resources/da.lproj/Localizable.strings +++ b/piwigo/Resources/da.lproj/Localizable.strings @@ -155,6 +155,9 @@ "categoryImageList_selectButton" = "Vælg"; "categoryImageList_deselectButton" = "Fravælg"; "categoryImageList_noDataError" = "Fejl ingen Data"; +"categoryImageList_share" = "Del"; +"categoryImageList_favorite" = "Tilføj til favoritter"; +"categoryImageList_unfavorite" = "Fjern fra favorit"; // Albums "Discover" "categoryDiscover_title" = "Opdag"; @@ -223,9 +226,6 @@ "categoryUpload_otherAlbums" = "Andre Albummer"; "categoryUpload_pasteboard" = "Udklipsholder"; -"categoyUpload_loadSubCategories" = "indlæs"; -"categoryUpload_images" = "Upload billeder"; - // Default album "setDefaultCategory_select" = "Vælg venligst et album eller underalbum, som vil blive det nye rodalbum."; "setDefaultCategory_title" = "Standardalbum"; @@ -244,9 +244,10 @@ "createAlbumError_message" = "Kunne ikke oprette nyt album"; // Album options (swipe actions) -"categoryCellOption_rename" = "Omdøb"; -"categoryCellOption_move" = "Flyt"; -"categoryCellOption_delete" = "Slet"; +"categoryCellOption_addPhotos" = "Tilføj fotos"; +"categoryCellOption_rename" = "Omdøb albummet"; +"categoryCellOption_move" = "Flyt album"; +"categoryCellOption_delete" = "Slet album"; // Album rename "renameCategory_title" = "Omdøb Album"; @@ -519,14 +520,6 @@ "imageUploadDetails_tags" = "Etiketter:"; "imageUploadDetails_description" = "Beskrivelse:"; -"imageUploadDetailsView_title" = "Billeder"; -"imageUploadDetailsButton_title" = "Upload"; -"imageUploadDetailsEdit_title" = "Vælg Billeder til Upload"; -"imageUploadDetailsUploading_title" = "Billeder der bliver Uploadet"; -"imageUploadDetailsCell_uploadComplete" = "Fuldført! Efterbehandling..."; - -"imageUploadRemove" = "Fjerner uploadede billeder"; - "imageUploadTableCell_waiting" = "Venter..."; "imageUploadTableCell_preparing" = "Forbereder..."; "imageUploadTableCell_prepared" = "Klar til upload..."; @@ -541,8 +534,6 @@ "imageUploadResumeSingle" = "Genoptag Upload"; "imageUploadResumeSeveral" = "Genoptag %@ Uploads"; -"imageUploadProgressBar_zero" = "Uploader 0/0"; -"imageUploadProgressBar_nonZero" = "Uploader %@/%@"; "imageUploadProgressBar_completed" = "Fuldført"; "imageUploadCompleted_title" = "Upload Fuldført"; "imageUploadCompleted_message" = "billede/video uploadet til Piwigo server."; @@ -618,6 +609,7 @@ "shareFailError_title" = "Deling mislykkedes"; "shareMetadataError_message" = "Kan ikke fjerne private metadata"; +"shareFailError_tooLarge" = "Udvalget er for stort til at dele"; // =========================================================================== // ==> LOCAL ALBUMS & IMAGES / Photo Library diff --git a/piwigo/Resources/de.lproj/InfoPlist.strings b/piwigo/Resources/de.lproj/InfoPlist.strings index 019837c48..85a430d6c 100644 --- a/piwigo/Resources/de.lproj/InfoPlist.strings +++ b/piwigo/Resources/de.lproj/InfoPlist.strings @@ -8,3 +8,4 @@ "NSPhotoLibraryUsageDescription" = "ermöglicht, Fotos auf herunterzuladen und Fotos auf Ihren Piwigo-Server hochzuladen."; "NSSiriUsageDescription" = "so dass Piwigo Bilder über Siri hochladen kann."; +"NSLocalNetworkUsageDescription" = "Piwigo verwendet das lokale Netzwerk, um auf Ihren Piwigo-Server zuzugreifen."; diff --git a/piwigo/Resources/de.lproj/Localizable.strings b/piwigo/Resources/de.lproj/Localizable.strings index 385c89017..22ef57137 100644 --- a/piwigo/Resources/de.lproj/Localizable.strings +++ b/piwigo/Resources/de.lproj/Localizable.strings @@ -155,6 +155,9 @@ "categoryImageList_selectButton" = "Wählen"; "categoryImageList_deselectButton" = "Auswahl aufheben"; "categoryImageList_noDataError" = "Fehler keine Daten"; +"categoryImageList_share" = "Teilen"; +"categoryImageList_favorite" = "Favorit"; +"categoryImageList_unfavorite" = "Aus Favoriten entfernen"; // Albums "Discover" "categoryDiscover_title" = "Entdecken"; @@ -223,9 +226,6 @@ "categoryUpload_otherAlbums" = "Andere Alben"; "categoryUpload_pasteboard" = "Zwischenablage"; -"categoyUpload_loadSubCategories" = "Laden"; -"categoryUpload_images" = "Lade Fotos hoch"; - // Default album "setDefaultCategory_select" = "Bitte wählen Sie ein Album oder Unter-Album das Ihr Standard Fotoalbum werden soll."; "setDefaultCategory_title" = "Standard-Album"; @@ -244,9 +244,10 @@ "createAlbumError_message" = "Erstellen eines neuen Albums fehlgeschlagen"; // Album options (swipe actions) -"categoryCellOption_rename" = "Umbenennen"; -"categoryCellOption_move" = "Verschieben"; -"categoryCellOption_delete" = "Löschen"; +"categoryCellOption_addPhotos" = "Fotos hinzufügen"; +"categoryCellOption_rename" = "Album umbenennen"; +"categoryCellOption_move" = "Album verschieben"; +"categoryCellOption_delete" = "Album löschen"; // Album rename "renameCategory_title" = "Album umbenennen"; @@ -519,14 +520,6 @@ "imageUploadDetails_tags" = "Schlagwörter:"; "imageUploadDetails_description" = "Beschreibung:"; -"imageUploadDetailsView_title" = "Fotos"; -"imageUploadDetailsButton_title" = "Hochladen"; -"imageUploadDetailsEdit_title" = "Bearbeite Fotos zum Hochladen"; -"imageUploadDetailsUploading_title" = "Fotos, die hochgeladen werden"; -"imageUploadDetailsCell_uploadComplete" = "Fertig! Abschliessen ..."; - -"imageUploadRemove" = "Hochgeladene Fotos entfernen"; - "imageUploadTableCell_waiting" = "Warten..."; "imageUploadTableCell_preparing" = "Vorbereiten..."; "imageUploadTableCell_prepared" = "Bereit zum Upload..."; @@ -541,8 +534,6 @@ "imageUploadResumeSingle" = "Fehlgeschlagenen Upload fortsetzen"; "imageUploadResumeSeveral" = "Fehlgeschlagene %@ Uploads fortsetzen"; -"imageUploadProgressBar_zero" = "Hochladen 0/0"; -"imageUploadProgressBar_nonZero" = "Hochladen %@/%@"; "imageUploadProgressBar_completed" = "Abgeschlossen"; "imageUploadCompleted_title" = "Erfolgreich hochgeladen"; "imageUploadCompleted_message" = "Fotos/Videos wurden auf Ihren Piwigo-Server hochgeladen."; @@ -618,6 +609,7 @@ "shareFailError_title" = "Teilen fehlgeschlagen"; "shareMetadataError_message" = "Private Metadaten können nicht entfernt werden"; +"shareFailError_tooLarge" = "Auswahl zu umfangreich, um geteilt zu werden"; // =========================================================================== // ==> LOCAL ALBUMS & IMAGES / Photo Library diff --git a/piwigo/Resources/en.lproj/InfoPlist.strings b/piwigo/Resources/en.lproj/InfoPlist.strings index 0bac4f7ab..d663b4161 100644 --- a/piwigo/Resources/en.lproj/InfoPlist.strings +++ b/piwigo/Resources/en.lproj/InfoPlist.strings @@ -8,3 +8,4 @@ "NSPhotoLibraryUsageDescription" = "so that it will be able to store photos in the Photos app and upload photos to your Piwigo."; "NSSiriUsageDescription" = "so that Piwigo will be able to upload images with Siri."; +"NSLocalNetworkUsageDescription" = "Piwigo uses the local network to access your Piwigo server."; diff --git a/piwigo/Resources/en.lproj/Localizable.strings b/piwigo/Resources/en.lproj/Localizable.strings index 241c28f46..60c1fd7c7 100644 --- a/piwigo/Resources/en.lproj/Localizable.strings +++ b/piwigo/Resources/en.lproj/Localizable.strings @@ -155,6 +155,9 @@ "categoryImageList_selectButton" = "Select"; "categoryImageList_deselectButton" = "Deselect"; "categoryImageList_noDataError" = "Error No Data"; +"categoryImageList_share" = "Share"; +"categoryImageList_favorite" = "Favorite"; +"categoryImageList_unfavorite" = "Unfavorite"; // Albums "Discover" "categoryDiscover_title" = "Discover"; @@ -223,9 +226,6 @@ "categoryUpload_otherAlbums" = "Other Albums"; "categoryUpload_pasteboard" = "Clipboard"; -"categoyUpload_loadSubCategories" = "load"; -"categoryUpload_images" = "Upload Photos"; - // Default album "setDefaultCategory_select" = "Please select an album or sub-album which will become the new root album."; "setDefaultCategory_title" = "Default Album"; @@ -244,9 +244,10 @@ "createAlbumError_message" = "Failed to create a new album"; // Album options (swipe actions) -"categoryCellOption_rename" = "Rename"; -"categoryCellOption_move" = "Move"; -"categoryCellOption_delete" = "Delete"; +"categoryCellOption_addPhotos" = "Add Photos"; +"categoryCellOption_rename" = "Rename Album"; +"categoryCellOption_move" = "Move Album"; +"categoryCellOption_delete" = "Delete Album"; // Album rename "renameCategory_title" = "Rename Album"; @@ -519,14 +520,6 @@ "imageUploadDetails_tags" = "Tags:"; "imageUploadDetails_description" = "Description:"; -"imageUploadDetailsView_title" = "Photos"; -"imageUploadDetailsButton_title" = "Upload"; -"imageUploadDetailsEdit_title" = "Edit Photos to Upload"; -"imageUploadDetailsUploading_title" = "Photos that are Being Uploaded"; -"imageUploadDetailsCell_uploadComplete" = "Completed! Finishing up..."; - -"imageUploadRemove" = "Removing uploaded photos"; - "imageUploadTableCell_waiting" = "Waiting..."; "imageUploadTableCell_preparing" = "Preparing..."; "imageUploadTableCell_prepared" = "Ready for upload..."; @@ -541,8 +534,6 @@ "imageUploadResumeSingle" = "Resume Upload"; "imageUploadResumeSeveral" = "Resume %@ Uploads"; -"imageUploadProgressBar_zero" = "Uploading 0/0"; -"imageUploadProgressBar_nonZero" = "Uploading %@/%@"; "imageUploadProgressBar_completed" = "Completed"; "imageUploadCompleted_title" = "Upload Completed"; "imageUploadCompleted_message" = "photo uploaded to your Piwigo server."; @@ -618,6 +609,7 @@ "shareFailError_title" = "Share Fail"; "shareMetadataError_message" = "Cannot strip private metadata"; +"shareFailError_tooLarge" = "Selection too large to share"; // =========================================================================== // ==> LOCAL ALBUMS & IMAGES / Photo Library diff --git a/piwigo/Resources/en.lproj/PrivacyPolicy.strings b/piwigo/Resources/en.lproj/PrivacyPolicy.strings index fa4191aad..252f08d59 100644 --- a/piwigo/Resources/en.lproj/PrivacyPolicy.strings +++ b/piwigo/Resources/en.lproj/PrivacyPolicy.strings @@ -60,4 +60,4 @@ "contact_address" = "Piwigo, 42 rue des Vignes, 21800 Quetigny (France)"; -"contact_email" = "ios@piwigo.org"; +"contact_email" = "piwigo@lelievre-berna.net"; diff --git a/piwigo/Resources/es.lproj/InfoPlist.strings b/piwigo/Resources/es.lproj/InfoPlist.strings index 092ad4ef2..1dc43f9d6 100644 --- a/piwigo/Resources/es.lproj/InfoPlist.strings +++ b/piwigo/Resources/es.lproj/InfoPlist.strings @@ -8,3 +8,4 @@ "NSPhotoLibraryUsageDescription" = "para que pueda almacenar fotos en la aplicación Fotos y subir fotos a su Piwigo."; "NSSiriUsageDescription" = "para que Piwigo pueda subir imágenes con Siri."; +"NSLocalNetworkUsageDescription" = "Piwigo utiliza la red local para acceder al servidor Piwigo."; diff --git a/piwigo/Resources/es.lproj/Localizable.strings b/piwigo/Resources/es.lproj/Localizable.strings index a339495be..ab6740b0f 100644 --- a/piwigo/Resources/es.lproj/Localizable.strings +++ b/piwigo/Resources/es.lproj/Localizable.strings @@ -155,6 +155,9 @@ "categoryImageList_selectButton" = "Seleccionar"; "categoryImageList_deselectButton" = "Deseleccionar"; "categoryImageList_noDataError" = "Error Sin datos"; +"categoryImageList_share" = "Compartir"; +"categoryImageList_favorite" = "Favorito"; +"categoryImageList_unfavorite" = "No favorito"; // Albums "Discover" "categoryDiscover_title" = "Descubre"; @@ -223,9 +226,6 @@ "categoryUpload_otherAlbums" = "Otros álbumes"; "categoryUpload_pasteboard" = "Portapapeles"; -"categoyUpload_loadSubCategories" = "cargar"; -"categoryUpload_images" = "Subir fotos"; - // Default album "setDefaultCategory_select" = "Seleccione un álbum o sub-álbum que se convertirá en el nuevo álbum raíz."; "setDefaultCategory_title" = "Álbum por defecto"; @@ -244,9 +244,10 @@ "createAlbumError_message" = "No se ha podido crear un álbum nuevo"; // Album options (swipe actions) -"categoryCellOption_rename" = "Cambiar el nombre"; -"categoryCellOption_move" = "Mover"; -"categoryCellOption_delete" = "Eliminar"; +"categoryCellOption_addPhotos" = "Añadir fotos"; +"categoryCellOption_rename" = "Renombrar álbum"; +"categoryCellOption_move" = "Mover álbum"; +"categoryCellOption_delete" = "Eliminar álbum"; // Album rename "renameCategory_title" = "Renombrar álbum"; @@ -519,14 +520,6 @@ "imageUploadDetails_tags" = "Tags:"; "imageUploadDetails_description" = "Descripción:"; -"imageUploadDetailsView_title" = "Fotos"; -"imageUploadDetailsButton_title" = "Subir"; -"imageUploadDetailsEdit_title" = "Editar fotos a subir"; -"imageUploadDetailsUploading_title" = "Fotos que se están subiendo"; -"imageUploadDetailsCell_uploadComplete" = "¡Completado! Terminando..."; - -"imageUploadRemove" = "Eliminando fotos subidas"; - "imageUploadTableCell_waiting" = "Esperando..."; "imageUploadTableCell_preparing" = "Preparando..."; "imageUploadTableCell_prepared" = "Listo para subir..."; @@ -541,8 +534,6 @@ "imageUploadResumeSingle" = "Reanudar la subida fallida"; "imageUploadResumeSeveral" = "Reanudar %@ subidas fallidas"; -"imageUploadProgressBar_zero" = "Subiendo 0/0"; -"imageUploadProgressBar_nonZero" = "Subiendo %@/%@"; "imageUploadProgressBar_completed" = "Terminado"; "imageUploadCompleted_title" = "Transferencia completada"; "imageUploadCompleted_message" = "foto subida a su servidor Piwigo."; @@ -618,6 +609,7 @@ "shareFailError_title" = "Error de compartición"; "shareMetadataError_message" = "No se pueden eliminar los metadatos privados"; +"shareFailError_tooLarge" = "Selección demasiado grande para compartir"; // =========================================================================== // ==> LOCAL ALBUMS & IMAGES / Photo Library diff --git a/piwigo/Resources/fr.lproj/InfoPlist.strings b/piwigo/Resources/fr.lproj/InfoPlist.strings index e03612ec7..25f9bced9 100644 --- a/piwigo/Resources/fr.lproj/InfoPlist.strings +++ b/piwigo/Resources/fr.lproj/InfoPlist.strings @@ -8,3 +8,4 @@ "NSPhotoLibraryUsageDescription" = "afin qu'il puisse télécharger des photos dans l'app Photos et téléverser des photos sur votre Piwigo."; "NSSiriUsageDescription" = "afin que Piwigo puisse télécharger des images avec Siri."; +"NSLocalNetworkUsageDescription" = "Piwigo utilise le réseau local pour accéder à votre serveur Piwigo."; diff --git a/piwigo/Resources/fr.lproj/Localizable.strings b/piwigo/Resources/fr.lproj/Localizable.strings index 1a5ad6c7c..26aa3f6bc 100644 --- a/piwigo/Resources/fr.lproj/Localizable.strings +++ b/piwigo/Resources/fr.lproj/Localizable.strings @@ -11,7 +11,7 @@ // Tab bar "tabBar_albums" = "Albums"; -"tabBar_upload" = "Envoyer"; +"tabBar_upload" = "Transférer"; "tabBar_preferences" = "Réglages"; // Alert buttons @@ -33,7 +33,7 @@ // Upload rights "uploadRights_title" = "Droits requis"; -"uploadRights_message" = "Vous devez avoir le droit de télécharger pour pouvoir envoyer des photos ou des vidéos."; +"uploadRights_message" = "Vous devez avoir les droits appropriés pour pouvoir transférer des photos ou des vidéos."; // Errors "internetErrorGeneral_title" = "Erreur de connexion"; @@ -155,6 +155,9 @@ "categoryImageList_selectButton" = "Sélectionner"; "categoryImageList_deselectButton" = "Désélectionner"; "categoryImageList_noDataError" = "Erreur aucune donnée"; +"categoryImageList_share" = "Partager"; +"categoryImageList_favorite" = "Ajouter aux favoris"; +"categoryImageList_unfavorite" = "Retirer des favoris"; // Albums "Discover" "categoryDiscover_title" = "Découvrir"; @@ -212,10 +215,10 @@ "categoryUpload_noSubAlbum" = "Il n'y a pas de sous-album qui puisse recevoir des photos"; "categoryUpload_chooseAlbum" = "Sélectionnez le sous-album recevant les photos"; "categoryUpload_LocalAlbums" = "Albums locaux"; -"categoryUpload_chooseLocalAlbum" = "Sélectionnez l'album local qui contient les photos à envoyer"; +"categoryUpload_chooseLocalAlbum" = "Sélectionnez l'album local qui contient les photos à transférer"; "categoryUpload_sharedAlbums" = "Albums partagés"; "categoryUpload_iCloudAlbums" = "Albums iCloud"; -"categoryUpload_chooseiCloudAlbum" = "Sélectionnez l'album iCloud qui contient les photos à envoyer"; +"categoryUpload_chooseiCloudAlbum" = "Sélectionnez l'album iCloud qui contient les photos à transférer"; "categoryUpload_syncedEvents" = "Événements iPhoto"; "categoryUpload_syncedAlbums" = "Albums iPhoto"; "categoryUpload_syncedFaces" = "Visages iPhoto"; @@ -223,9 +226,6 @@ "categoryUpload_otherAlbums" = "Autres albums"; "categoryUpload_pasteboard" = "Presse-papier"; -"categoyUpload_loadSubCategories" = "télécharge"; -"categoryUpload_images" = "Télécharger des photos"; - // Default album "setDefaultCategory_select" = "Veuillez sélectionner un album ou un sous-album qui deviendra le nouvel album racine."; "setDefaultCategory_title" = "Album par défaut"; @@ -244,9 +244,10 @@ "createAlbumError_message" = "Impossible de créer un nouvel album"; // Album options (swipe actions) -"categoryCellOption_rename" = "Renomer"; -"categoryCellOption_move" = "Déplacer"; -"categoryCellOption_delete" = "Effacer"; +"categoryCellOption_addPhotos" = "Ajouter des Photos"; +"categoryCellOption_rename" = "Renommer l'album"; +"categoryCellOption_move" = "Déplacer l'album"; +"categoryCellOption_delete" = "Supprimer l'album"; // Album rename "renameCategory_title" = "Renommer l'album"; @@ -507,11 +508,11 @@ "copySeveralImagesError_message" = "Impossible de copier certaines photos"; // Images upload -"imageUploadHeader" = "Veuillez sélectionner l'album ou le sous-album à partir duquel les photos et les vidéos de votre appareil seront téléchargées."; +"imageUploadHeader" = "Veuillez sélectionner l'album ou le sous-album à partir duquel les photos et les vidéos de votre appareil seront transférées."; "imageUploadHeaderTitle_images" = "Paramètres des photos"; "imageUploadHeaderText_images" = "Veuillez définir les paramètres à appliquer à la sélection des photos/vidéos"; -"imageUploadHeaderTitle_upload" = "Réglage des téléchargements"; -"imageUploadHeaderText_upload" = "Veuillez définir les paramètres de téléchargement à appliquer à la sélection des photos/vidéos"; +"imageUploadHeaderTitle_upload" = "Réglage des transferts"; +"imageUploadHeaderText_upload" = "Veuillez définir les paramètres de transfert à appliquer à la sélection des photos/vidéos"; "imageUploadDetails_title" = "Titre :"; "imageUploadDetails_author" = "Auteur :"; @@ -519,19 +520,11 @@ "imageUploadDetails_tags" = "Tags :"; "imageUploadDetails_description" = "Description :"; -"imageUploadDetailsView_title" = "Photos"; -"imageUploadDetailsButton_title" = "Envoyer"; -"imageUploadDetailsEdit_title" = "Éditer les photos à télécharger"; -"imageUploadDetailsUploading_title" = "Images en cours d'envoi"; -"imageUploadDetailsCell_uploadComplete" = "Envoyée ! Vérification..."; - -"imageUploadRemove" = "Suppression des photos téléchargées"; - "imageUploadTableCell_waiting" = "En attente..."; "imageUploadTableCell_preparing" = "En cours de préparation..."; -"imageUploadTableCell_prepared" = "Prêt à télécharger..."; -"imageUploadTableCell_uploading" = "Téléchargement..."; -"imageUploadTableCell_uploaded" = "Téléchargée"; +"imageUploadTableCell_prepared" = "Prêt pour le transfert…"; +"imageUploadTableCell_uploading" = "Transfert..."; +"imageUploadTableCell_uploaded" = "Transférée"; "imageUploadTableCell_finishing" = "Finalisation..."; "imageUploadClearFailedSingle" = "Effacer transfert échoué"; @@ -541,16 +534,14 @@ "imageUploadResumeSingle" = "Relancer le transfert"; "imageUploadResumeSeveral" = "Relancer %@ transferts"; -"imageUploadProgressBar_zero" = "Téléchargement 0/0"; -"imageUploadProgressBar_nonZero" = "Téléchargement %@/%@"; "imageUploadProgressBar_completed" = "Terminé"; -"imageUploadCompleted_title" = "Téléchargement Terminé"; -"imageUploadCompleted_message" = "photo envoyée à votre serveur Piwigo."; -"imageUploadCompleted_message>1" = "photos envoyées à votre serveur Piwigo."; +"imageUploadCompleted_title" = "Transfert terminé"; +"imageUploadCompleted_message" = "photo transférée à votre serveur Piwigo."; +"imageUploadCompleted_message>1" = "photos transférées à votre serveur Piwigo."; // Image upload — Errors -"uploadError_title" = "Echec du téléchargement"; -"uploadError_message" = "Echec du téléchargement de votre photo. Erreur : %@"; +"uploadError_title" = "Echec du Transfert"; +"uploadError_message" = "Echec du transfert de votre photo. Erreur : %@"; "uploadCancelled_title" = "Transfert annulé"; "uploadNoInternetNetwork" = "Pas de connexion Internet"; "uploadNoWiFiNetwork" = "Pas de connexion Wi-Fi"; @@ -559,19 +550,19 @@ "uploadSection_resumable" = "Transferts réamorçables"; "uploadSection_queue" = "File d'attente des transferts"; -"imageUploadError_title" = "Echec du téléchargement"; +"imageUploadError_title" = "Echec du transfert"; "imageUploadError_format" = "Format de fichier non accepté par le serveur Piwigo."; "imageUploadError_source" = "impossible de créer le source de la photo"; "imageUploadError_destination" = "impossible de créer la destination de la photo"; "imageUploadError_iCloud" = "Echec de la récupération de la photo. Erreur : %@"; -"audioUploadError_title" = "Echec du téléchargement"; +"audioUploadError_title" = "Echec du Transfert"; "audioUploadError_format" = "Désolé, les fichiers audio ne sont pas encore supportés par Piwigo Mobile."; -"videoUploadError_title" = "Echec du téléchargement"; -"videoUploadError_format" = "Désolé, les fichiers de vidéo ayant comme extension .%@ ne sont pas acceptés par le serveur Piwigo."; +"videoUploadError_title" = "Echec du Transfert"; +"videoUploadError_format" = "Désolé, les fichiers vidéo ayant comme extension .%@ ne sont pas acceptés par le serveur Piwigo."; "videoUploadError_iCloud" = "Echec de la récupération de la vidéo. Erreur : %@"; -"videoUploadError_export" = "Désolé, la vidéo n'a pas pu être récupérée pour être téléchargée vers votre Piwigo. Erreur : %@"; +"videoUploadError_export" = "Désolé, la vidéo n'a pas pu être récupérée pour être transférée à votre Piwigo. Erreur : %@"; "videoUploadError_unknown" = "Désolé, le transfert vers votre Piwigo de la vidéo a échoué pour une erreur inconnue lors de la conversion MP4. Erreur : %@"; "videoUploadCancelled_message" = "Le transfert de la vidéo vers votre Piwigo a été annulé."; @@ -618,6 +609,7 @@ "shareFailError_title" = "Échec du partage"; "shareMetadataError_message" = "Impossible de supprimer les métadonnées privées"; +"shareFailError_tooLarge" = "Sélection trop volumineuse pour être partagée"; // =========================================================================== // ==> LOCAL ALBUMS & IMAGES / Photo Library @@ -634,9 +626,9 @@ "localAlbums_photosNotAuthorized_msg" = "Impossible d’ouvrir la photothèque locale. Allez dans l’application Réglages et activez l’accès pour Piwigo."; // Local images — Explain -"localImages_reUploadTitle" = "Re-télécharger"; +"localImages_reUploadTitle" = "Re-transférer"; "localImages_deleteTitle" = "Retirer de la pellicule"; -"localImages_deleteMessage" = "Voulez-vous supprimer les photos ou vidéos téléchargées de la pellicule ? Les photos ou vidéos supprimées resteront disponibles dans la corbeille de l'app Photos pendant 30 jours."; +"localImages_deleteMessage" = "Voulez-vous supprimer de la pellicule les photos ou vidéos transférées? Les photos ou vidéos supprimées resteront disponibles dans la corbeille de l'app Photos pendant 30 jours."; // =========================================================================== // ==> SETTINGS @@ -723,57 +715,57 @@ "imageSizexFullRes" = "Haute résolution"; // Settings — Upload -"settingsHeader_upload" = "Téléchargements"; +"settingsHeader_upload" = "Transferts"; "settings_defaultAuthor" = "Auteur"; "settings_defaultAuthor>320px" = "Nom d'auteur"; "settings_defaultAuthorPlaceholder" = "Nom de l'auteur"; "settings_defaultPrivacy" = "Qui voit ?"; "settings_defaultPrivacy>414px" = "Veuillez sélectionner qui pourra visualiser les photos"; "settings_stripGPSdata" = "Suppr. métadonnées privées"; -"settings_stripGPSdata>375px" = "Supprimer les métadonnées privées avant envoi"; -"settings_photoResize" = "Réduire avant téléchargement"; +"settings_stripGPSdata>375px" = "Supprimer les métadonnées privées avant transfert"; +"settings_photoResize" = "Réduire avant transfert"; "settings_photoSize" = "Taille"; "settings_placeholderSize" = "Entrez une taille de photo de 5 à 100"; -"settings_photoCompress" = "Compresser avant Envoi"; -"settings_photoCompress>375px" = "Compresser la photo avant envoi"; +"settings_photoCompress" = "Compresser avant transfert"; +"settings_photoCompress>375px" = "Compresser la photo avant transfert"; "settings_photoQuality" = "Qualité"; "settings_placeholderQuality" = "Entrez une qualité de photo entre 50 et 98"; -"settings_prefixFilename>414px" = "Préfixer le nom du fichier photo avant téléchargement"; -"settings_prefixFilename>375px" = "Préfixer nom du fichier avant téléch."; +"settings_prefixFilename>414px" = "Préfixer le nom du fichier photo avant transfert"; +"settings_prefixFilename>375px" = "Préfixer nom du fichier avant transfert"; "settings_prefixFilename" = "Préfixer nom du fichier"; "settings_defaultPrefix" = "Préfixe"; "settings_defaultPrefix>320px" = "Préfixe du fichier"; "settings_defaultPrefixPlaceholder" = "Préfixe"; -"settings_deleteImage>375px" = "Effacer la photo après envoi"; +"settings_deleteImage>375px" = "Effacer la photo après transfert"; "settings_wifiOnly" = "Wi-Fi uniquement"; -"settings_deleteImage" = "Effacer après envoi"; +"settings_deleteImage" = "Effacer après transfert"; // Settings — Auto-Upload -"settings_autoUpload" = "Télécharger automatiquement"; -"settings_autoUpload>414px" = "Télécharger automatiquement des photos"; +"settings_autoUpload" = "Transferts auto"; +"settings_autoUpload>414px" = "Transferts auto. des photos"; "settings_autoUploadEnabled" = "Activé"; -"settings_autoUploadEnabledInfo" = "Les photos seront automatiquement téléchargées sur votre Piwigo."; -"settings_autoUploadEnabledInfoAll" = "Les photos et les vidéos seront automatiquement téléchargées sur votre Piwigo."; +"settings_autoUploadEnabledInfo" = "Les photos seront automatiquement téléversées sur votre Piwigo."; +"settings_autoUploadEnabledInfoAll" = "Les photos et les vidéos seront automatiquement téléversées sur votre Piwigo."; "settings_autoUploadDisabled" = "Désactivé"; -"settings_autoUploadDisabledInfo" = "Les photos ne seront pas automatiquement téléchargées sur votre Piwigo."; +"settings_autoUploadDisabledInfo" = "Les photos ne seront pas automatiquement téléversées sur votre Piwigo."; "settings_autoUploadSource" = "Source"; "settings_autoUploadSourceInvalid" = "Album source invalide"; -"settings_autoUploadSourceInfo" = "Veuillez sélectionner l'album ou le sous-album à partir duquel les photos et les vidéos de votre appareil seront automatiquement téléchargées."; +"settings_autoUploadSourceInfo" = "Veuillez sélectionner l'album ou le sous-album à partir duquel les photos et les vidéos de votre appareil seront automatiquement transférées."; "settings_autoUploadDestination" = "Destination"; "settings_autoUploadDestinationInvalid" = "Album de destination invalide"; -"settings_autoUploadDestinationInfo" = "Veuillez sélectionner l'album ou le sous-album dans lequel les photos et les vidéos seront automatiquement téléchargées."; -"AutoUploadError_Disabled" = "Le téléchargement automatique est désactivé dans les paramètres de l'application."; +"settings_autoUploadDestinationInfo" = "Veuillez sélectionner l'album ou le sous-album dans lequel les photos et les vidéos seront automatiquement transférées."; +"AutoUploadError_Disabled" = "Les transferts automatiques sont désactivés dans les paramètres de l'application."; "AutoUploadError_Failed" = "Plusieurs transferts ont échoué et la file d'attente est en pause. Veuillez vérifier dans l'application s'il vous plaît."; // Settings — Upload Photo Size "UploadPhotoSize_title" = "Taille maximale des photos"; -"UploadPhotoSize_header" = "Veuillez sélectionner la taille maximale des photos qui seront téléchargées."; +"UploadPhotoSize_header" = "Veuillez sélectionner la taille maximale des photos qui seront transférées."; "UploadPhotoSize_resolution" = "Résolution maximale des caméras intégrées :"; "UploadVideoSize_title" = "Taille maximale des vidéos"; -"UploadVideoSize_header" = "Veuillez sélectionner la taille maximale des vidéos qui seront téléchargées."; +"UploadVideoSize_header" = "Veuillez sélectionner la taille maximale des vidéos qui seront transférées."; "UploadVideoSize_resolution" = "Performances maximales des caméras intégrées :"; "UploadPhotoSize_original" = "Sans réduction"; -"UploadRequests_cache" = "Téléchargements"; +"UploadRequests_cache" = "Transferts"; // Settings — Privacy "settingsHeader_privacy" = "Confidentialité"; @@ -848,19 +840,19 @@ // Settings — Help "help01_header" = "Sélection multiple"; "help01_text" = "Faites glisser votre doigt de gauche à droite (ou de droite à gauche) puis vers le bas, etc."; -"help02_header" = "Téléchargement en arrière-plan"; -"help02_text" = "Sélectionnez les photos/vidéos, définissez les paramètres et appuyez sur Envoyer."; -"help02_text2" = "Branchez l'appareil à son chargeur et laissez iOS lancer les téléchargements quand cela est approprié."; +"help02_header" = "Transferts en arrière-plan"; +"help02_text" = "Sélectionnez les photos/vidéos, définissez les paramètres et appuyez sur Transférer."; +"help02_text2" = "Branchez l'appareil à son chargeur et laissez iOS lancer les transferts quand cela est approprié."; "help02_text3" = "(nécessite l'extension uploadAsync ou Piwigo 11)"; "help03_header" = "Administrateurs"; "help03_text" = "Créer, supprimer, déplacer et renommer des albums."; -"help04_text" = "Les photos téléchargées retirées de la pellicule restent disponibles dans la corbeille de l'app iOS Photos pendant 30 jours."; -"help05_header" = "Téléchargement des photos"; -"help05_text" = "Une fois les demandes de téléchargement soumises, vous pouvez parcourir les albums, soumettre d'autres demandes ou quitter l'app."; -"help05_text2" = "Appuyez sur le bouton gris présenté dans l'album racine pour gérer les téléchargements."; -"help06_header" = "Gestion des téléchargements"; -"help06_text" = "Surveillez, annulez et relancez les demandes de téléchargement globalement ou individuellement."; -"help07_header" = "Téléchargement auto"; +"help04_text" = "Les photos transférées retirées de la pellicule restent disponibles dans la corbeille de l'app iOS Photos pendant 30 jours."; +"help05_header" = "Transferts des photos"; +"help05_text" = "Une fois les demandes de transfert soumises, vous pouvez parcourir les albums, soumettre d'autres demandes ou quitter l'app."; +"help05_text2" = "Appuyez sur le bouton gris présenté dans l'album racine pour gérer les transferts."; +"help06_header" = "Gestion des Transferts"; +"help06_text" = "Surveillez, annulez et relancez les demandes de transfert globalement ou individuellement."; +"help07_header" = "Transferts Automatiques"; "help07_text" = "Sélectionnez les albums source et destination dans Piwigo, créez des automatisations dans Raccourcis."; "help08_header" = "Albums parents"; "help08_text" = "Appuyez longuement sur le bouton retour pour afficher la liste des albums parents."; diff --git a/piwigo/Resources/hu.lproj/InfoPlist.strings b/piwigo/Resources/hu.lproj/InfoPlist.strings index d1dd25166..555f7e65d 100644 --- a/piwigo/Resources/hu.lproj/InfoPlist.strings +++ b/piwigo/Resources/hu.lproj/InfoPlist.strings @@ -8,3 +8,4 @@ "NSPhotoLibraryUsageDescription" = "hogy tárolhasson képeket a Fotók alkalmazásban, és fel tudjon tölteni fotókat a saját Piwigodba."; "NSSiriUsageDescription" = "hogy a Piwigo képes legyen képeket feltölteni Sirivel."; +"NSLocalNetworkUsageDescription" = "A Piwigo a helyi hálózatot használja a Piwigo szerver eléréséhez."; diff --git a/piwigo/Resources/hu.lproj/Localizable.strings b/piwigo/Resources/hu.lproj/Localizable.strings index 75fc53646..959ad2e98 100644 --- a/piwigo/Resources/hu.lproj/Localizable.strings +++ b/piwigo/Resources/hu.lproj/Localizable.strings @@ -155,6 +155,9 @@ "categoryImageList_selectButton" = "Kiválaszt"; "categoryImageList_deselectButton" = "Elvet"; "categoryImageList_noDataError" = "Hiba: nincs adat"; +"categoryImageList_share" = "Megosztás"; +"categoryImageList_favorite" = "Kedvenc"; +"categoryImageList_unfavorite" = "Kedvencek"; // Albums "Discover" "categoryDiscover_title" = "Felfedezés"; @@ -223,9 +226,6 @@ "categoryUpload_otherAlbums" = "Egyéb albumok"; "categoryUpload_pasteboard" = "Vágólap"; -"categoyUpload_loadSubCategories" = "betöltés"; -"categoryUpload_images" = "Képek feltöltése"; - // Default album "setDefaultCategory_select" = "Válaszd ki az új főalbumot!"; "setDefaultCategory_title" = "Alapértelmezett Album"; @@ -244,9 +244,10 @@ "createAlbumError_message" = "Nem sikerült létrehozni az albumot"; // Album options (swipe actions) -"categoryCellOption_rename" = "Átnevezés"; -"categoryCellOption_move" = "Mozgatás"; -"categoryCellOption_delete" = "Törlés"; +"categoryCellOption_addPhotos" = "Fényképek hozzáadása"; +"categoryCellOption_rename" = "Album átnevezése"; +"categoryCellOption_move" = "Album áthelyezése"; +"categoryCellOption_delete" = "Album törlése"; // Album rename "renameCategory_title" = "Album átnevezése"; @@ -519,14 +520,6 @@ "imageUploadDetails_tags" = "Címkék:"; "imageUploadDetails_description" = "Leírás:"; -"imageUploadDetailsView_title" = "Fotók"; -"imageUploadDetailsButton_title" = "Feltöltés"; -"imageUploadDetailsEdit_title" = "Feltöltendő fotók szerkesztése"; -"imageUploadDetailsUploading_title" = "Fotók feltöltés alatt"; -"imageUploadDetailsCell_uploadComplete" = "Kész! Már csak pár pillanat..."; - -"imageUploadRemove" = "Feltöltött fotók eltávolítása"; - "imageUploadTableCell_waiting" = "Várakozás..."; "imageUploadTableCell_preparing" = "Előkészítés..."; "imageUploadTableCell_prepared" = "Feltöltésre kész..."; @@ -541,8 +534,6 @@ "imageUploadResumeSingle" = "Feltöltés folytatása"; "imageUploadResumeSeveral" = "%@ feltöltés folytatása"; -"imageUploadProgressBar_zero" = "Feltöltés 0/0"; -"imageUploadProgressBar_nonZero" = "Feltöltés %@/%@"; "imageUploadProgressBar_completed" = "Befejezve"; "imageUploadCompleted_title" = "A feltöltés befejeződött"; "imageUploadCompleted_message" = "fotó feltöltve a Piwigo szerveredre."; @@ -618,6 +609,7 @@ "shareFailError_title" = "Sikertelen megosztás"; "shareMetadataError_message" = "Nem sikerült eltávolítani a privát metaadatokat"; +"shareFailError_tooLarge" = "Túl nagy választék a megosztáshoz"; // =========================================================================== // ==> LOCAL ALBUMS & IMAGES / Photo Library diff --git a/piwigo/Resources/id.lproj/InfoPlist.strings b/piwigo/Resources/id.lproj/InfoPlist.strings index 0bac4f7ab..1270ad7c9 100644 --- a/piwigo/Resources/id.lproj/InfoPlist.strings +++ b/piwigo/Resources/id.lproj/InfoPlist.strings @@ -6,5 +6,6 @@ Copyright © 2017 Piwigo.org. All rights reserved. */ -"NSPhotoLibraryUsageDescription" = "so that it will be able to store photos in the Photos app and upload photos to your Piwigo."; +"NSPhotoLibraryUsageDescription" = "Piwigo menggunakan jaringan lokal untuk mengakses server Piwigo Anda."; "NSSiriUsageDescription" = "so that Piwigo will be able to upload images with Siri."; +"NSLocalNetworkUsageDescription" = "Piwigo uses the local network to access your Piwigo server."; diff --git a/piwigo/Resources/id.lproj/Localizable.strings b/piwigo/Resources/id.lproj/Localizable.strings index 6d1ec4d0e..0857af18c 100644 --- a/piwigo/Resources/id.lproj/Localizable.strings +++ b/piwigo/Resources/id.lproj/Localizable.strings @@ -155,6 +155,9 @@ "categoryImageList_selectButton" = "Pilih"; "categoryImageList_deselectButton" = "Deselect"; "categoryImageList_noDataError" = "Galat Tidak Ada Data"; +"categoryImageList_share" = "Share"; +"categoryImageList_favorite" = "Favorite"; +"categoryImageList_unfavorite" = "Unfavorite"; // Albums "Discover" "categoryDiscover_title" = "Discover"; @@ -223,9 +226,6 @@ "categoryUpload_otherAlbums" = "Other Albums"; "categoryUpload_pasteboard" = "Clipboard"; -"categoyUpload_loadSubCategories" = "muat"; -"categoryUpload_images" = "Unggah gambar"; - // Default album "setDefaultCategory_select" = "Please select an album or sub-album which will become the new root album."; "setDefaultCategory_title" = "Default Album"; @@ -244,6 +244,7 @@ "createAlbumError_message" = "Gagal untuk membuat album baru"; // Album options (swipe actions) +"categoryCellOption_addPhotos" = "Add Photos"; "categoryCellOption_rename" = "Ganti Nama"; "categoryCellOption_move" = "Pindah"; "categoryCellOption_delete" = "Hapus"; @@ -519,14 +520,6 @@ "imageUploadDetails_tags" = "Tag:"; "imageUploadDetails_description" = "Deskripsi:"; -"imageUploadDetailsView_title" = "Gambar"; -"imageUploadDetailsButton_title" = "Unggah"; -"imageUploadDetailsEdit_title" = "Sunting Gambar untuk Diunggah"; -"imageUploadDetailsUploading_title" = "Gambar yang Akan Diunggah"; -"imageUploadDetailsCell_uploadComplete" = "Lengkap! Selesaikan..."; - -"imageUploadRemove" = "Removing uploaded photos"; - "imageUploadTableCell_waiting" = "Waiting..."; "imageUploadTableCell_preparing" = "Preparing..."; "imageUploadTableCell_prepared" = "Ready for upload..."; @@ -541,8 +534,6 @@ "imageUploadResumeSingle" = "Resume Upload"; "imageUploadResumeSeveral" = "Resume %@ Uploads"; -"imageUploadProgressBar_zero" = "Mengunggah 0/0"; -"imageUploadProgressBar_nonZero" = "Mengunggah %@/%@"; "imageUploadProgressBar_completed" = "Selesai"; "imageUploadCompleted_title" = "Pengunggahan Selesai"; "imageUploadCompleted_message" = "gambar/video telah duinggah ke peladen Piwigo Anda."; @@ -618,6 +609,7 @@ "shareFailError_title" = "Share Fail"; "shareMetadataError_message" = "Cannot strip private metadata"; +"shareFailError_tooLarge" = "Selection too large to share"; // =========================================================================== // ==> LOCAL ALBUMS & IMAGES / Photo Library diff --git a/piwigo/Resources/it.lproj/InfoPlist.strings b/piwigo/Resources/it.lproj/InfoPlist.strings index 7db51c612..78a8aa294 100644 --- a/piwigo/Resources/it.lproj/InfoPlist.strings +++ b/piwigo/Resources/it.lproj/InfoPlist.strings @@ -8,3 +8,4 @@ "NSPhotoLibraryUsageDescription" = "in modo che sia in grado di scaricare immagini su Foto e caricare immagini sul tuo Piwigo."; "NSSiriUsageDescription" = "in modo che Piwigo sarà in grado di caricare immagini con Siri."; +"NSLocalNetworkUsageDescription" = "Piwigo utilizza la rete locale per accedere al tuo server Piwigo."; diff --git a/piwigo/Resources/it.lproj/Localizable.strings b/piwigo/Resources/it.lproj/Localizable.strings index 01d9c6936..2d7e2a712 100644 --- a/piwigo/Resources/it.lproj/Localizable.strings +++ b/piwigo/Resources/it.lproj/Localizable.strings @@ -155,6 +155,9 @@ "categoryImageList_selectButton" = "Seleziona"; "categoryImageList_deselectButton" = "Deseleziona"; "categoryImageList_noDataError" = "Errore dati"; +"categoryImageList_share" = "Condividi"; +"categoryImageList_favorite" = "Aggiungi ai preferiti"; +"categoryImageList_unfavorite" = "Rimuovi dai preferiti"; // Albums "Discover" "categoryDiscover_title" = "Scoprire"; @@ -223,9 +226,6 @@ "categoryUpload_otherAlbums" = "Altri Album"; "categoryUpload_pasteboard" = "Appunti"; -"categoyUpload_loadSubCategories" = "carica"; -"categoryUpload_images" = "Carica immagini"; - // Default album "setDefaultCategory_select" = "Seleziona un album o un sottoalbum che diventerà il nuovo album root."; "setDefaultCategory_title" = "Album predefinito"; @@ -244,9 +244,10 @@ "createAlbumError_message" = "Impossibile creare un nuovo album"; // Album options (swipe actions) -"categoryCellOption_rename" = "Rinomina"; -"categoryCellOption_move" = "Sposta"; -"categoryCellOption_delete" = "Elimina"; +"categoryCellOption_addPhotos" = "Aggiungi foto"; +"categoryCellOption_rename" = "Rinominare l'album"; +"categoryCellOption_move" = "Spostare l'album"; +"categoryCellOption_delete" = "Eliminare l'album"; // Album rename "renameCategory_title" = "Rinomina album"; @@ -519,14 +520,6 @@ "imageUploadDetails_tags" = "Tags:"; "imageUploadDetails_description" = "Descrizione:"; -"imageUploadDetailsView_title" = "Immagini"; -"imageUploadDetailsButton_title" = "Carica"; -"imageUploadDetailsEdit_title" = "Modificare le immagini da caricare"; -"imageUploadDetailsUploading_title" = "Immagini che vengono caricate"; -"imageUploadDetailsCell_uploadComplete" = "Completato! Finendo..."; - -"imageUploadRemove" = "Rimozione di immagini caricate"; - "imageUploadTableCell_waiting" = "Attendere..."; "imageUploadTableCell_preparing" = "Preparazione..."; "imageUploadTableCell_prepared" = "Pronto per il caricamento..."; @@ -541,8 +534,6 @@ "imageUploadResumeSingle" = "Riprendi Caricamento"; "imageUploadResumeSeveral" = "Riprendi %@ caricamenti"; -"imageUploadProgressBar_zero" = "Caricamento in corso 0/0"; -"imageUploadProgressBar_nonZero" = "Caricamento di %@/%@"; "imageUploadProgressBar_completed" = "Completato"; "imageUploadCompleted_title" = "Caricamento Completato"; "imageUploadCompleted_message" = "immagine/video caricati sul server di Piwigo."; @@ -618,6 +609,7 @@ "shareFailError_title" = "Condivisione Fallita"; "shareMetadataError_message" = "Impossibile eliminare i metadati privati"; +"shareFailError_tooLarge" = "Selezione troppo ampia per essere condivisa"; // =========================================================================== // ==> LOCAL ALBUMS & IMAGES / Photo Library diff --git a/piwigo/Resources/ja.lproj/InfoPlist.strings b/piwigo/Resources/ja.lproj/InfoPlist.strings index 34d797c44..4a180bf23 100644 --- a/piwigo/Resources/ja.lproj/InfoPlist.strings +++ b/piwigo/Resources/ja.lproj/InfoPlist.strings @@ -8,3 +8,4 @@ "NSPhotoLibraryUsageDescription" = "写真を写真アプリにダウンロードしたり、写真をPiwigoにアップロードしたりできますように。"; "NSSiriUsageDescription" = "piwigoがSiriで画像をアップロードできるようにします。"; +"NSLocalNetworkUsageDescription" = "Piwigoは、ローカルネットワークを使用してPiwigoサーバーにアクセスします。"; diff --git a/piwigo/Resources/ja.lproj/Localizable.strings b/piwigo/Resources/ja.lproj/Localizable.strings index c578be502..064a8df9b 100644 --- a/piwigo/Resources/ja.lproj/Localizable.strings +++ b/piwigo/Resources/ja.lproj/Localizable.strings @@ -155,6 +155,9 @@ "categoryImageList_selectButton" = "選択"; "categoryImageList_deselectButton" = "選択を解除"; "categoryImageList_noDataError" = "エラー データがありません"; +"categoryImageList_share" = "シェア"; +"categoryImageList_favorite" = "お気に入りに追加"; +"categoryImageList_unfavorite" = "お気に入りから削除する"; // Albums "Discover" "categoryDiscover_title" = "探す"; @@ -223,9 +226,6 @@ "categoryUpload_otherAlbums" = "その他のアルバム"; "categoryUpload_pasteboard" = "クリップボード"; -"categoyUpload_loadSubCategories" = "読み込む"; -"categoryUpload_images" = "写真をアップロードする"; - // Default album "setDefaultCategory_select" = "新しいアルバムになるアルバムまたはサブアルバムを選択してください。"; "setDefaultCategory_title" = "既定のアルバム"; @@ -244,9 +244,10 @@ "createAlbumError_message" = "新規アルバムの作成に失敗しました"; // Album options (swipe actions) -"categoryCellOption_rename" = "名称の変更"; -"categoryCellOption_move" = "移動"; -"categoryCellOption_delete" = "削除"; +"categoryCellOption_addPhotos" = "写真を追加"; +"categoryCellOption_rename" = "アルバム名の変更"; +"categoryCellOption_move" = "アルバムの移動"; +"categoryCellOption_delete" = "アルバム削除"; // Album rename "renameCategory_title" = "アルバムの名称を変更"; @@ -519,14 +520,6 @@ "imageUploadDetails_tags" = "タグ:"; "imageUploadDetails_description" = "説明:"; -"imageUploadDetailsView_title" = "写真"; -"imageUploadDetailsButton_title" = "アップロード"; -"imageUploadDetailsEdit_title" = "アップロードする写真を編集します"; -"imageUploadDetailsUploading_title" = "アップロード中の写真"; -"imageUploadDetailsCell_uploadComplete" = "完了!終了中..."; - -"imageUploadRemove" = "アップロードした写真を削除する"; - "imageUploadTableCell_waiting" = "待機中..."; "imageUploadTableCell_preparing" = "準備中..."; "imageUploadTableCell_prepared" = "アップロードの準備ができました..."; @@ -541,8 +534,6 @@ "imageUploadResumeSingle" = "失敗したアップロードを再開する"; "imageUploadResumeSeveral" = "%@ 件の失敗したアップロードを再開する"; -"imageUploadProgressBar_zero" = "アップロード中 0/0"; -"imageUploadProgressBar_nonZero" = "アップロード中 %@/%@"; "imageUploadProgressBar_completed" = "完了しました"; "imageUploadCompleted_title" = "アップロードが完了しました"; "imageUploadCompleted_message" = "写真/動画が、Piwigoサーバにアップロードされました。"; @@ -618,6 +609,7 @@ "shareFailError_title" = "共有に失敗しました"; "shareMetadataError_message" = "プライベートメタデータを取り除くことはできません"; +"shareFailError_tooLarge" = "共有するには大きすぎるセレクション"; // =========================================================================== // ==> LOCAL ALBUMS & IMAGES / Photo Library diff --git a/piwigo/Resources/nl.lproj/InfoPlist.strings b/piwigo/Resources/nl.lproj/InfoPlist.strings index a182657b5..53bf4f007 100644 --- a/piwigo/Resources/nl.lproj/InfoPlist.strings +++ b/piwigo/Resources/nl.lproj/InfoPlist.strings @@ -8,3 +8,4 @@ "NSPhotoLibraryUsageDescription" = "zodat het mogelijk is foto's naar Foto's te downloaden en foto's naar jouw Piwigo te uploaden."; "NSSiriUsageDescription" = "zodat Piwigo afbeeldingen kan uploaden met Siri."; +"NSLocalNetworkUsageDescription" = "Piwigo gebruikt het lokale netwerk voor toegang tot je Piwigo-server."; diff --git a/piwigo/Resources/nl.lproj/Localizable.strings b/piwigo/Resources/nl.lproj/Localizable.strings index 42b5ad2fd..3dd8c5a70 100644 --- a/piwigo/Resources/nl.lproj/Localizable.strings +++ b/piwigo/Resources/nl.lproj/Localizable.strings @@ -155,6 +155,9 @@ "categoryImageList_selectButton" = "Selecteer"; "categoryImageList_deselectButton" = "Deselecteren"; "categoryImageList_noDataError" = "Fout geen gegevens"; +"categoryImageList_share" = "Deel"; +"categoryImageList_favorite" = "Favoriet"; +"categoryImageList_unfavorite" = "Niet favoriet"; // Albums "Discover" "categoryDiscover_title" = "Ontdek"; @@ -223,9 +226,6 @@ "categoryUpload_otherAlbums" = "Andere albums"; "categoryUpload_pasteboard" = "Klembord"; -"categoyUpload_loadSubCategories" = "laden"; -"categoryUpload_images" = "Foto's uploaden"; - // Default album "setDefaultCategory_select" = "Selecteer een album of sub-album dat het nieuwe root album zal worden."; "setDefaultCategory_title" = "Standaard album"; @@ -244,9 +244,10 @@ "createAlbumError_message" = "Het aanmaken van een album is mislukt"; // Album options (swipe actions) -"categoryCellOption_rename" = "Naam wijzigen"; -"categoryCellOption_move" = "Verplaatsen"; -"categoryCellOption_delete" = "Verwijderen"; +"categoryCellOption_addPhotos" = "Foto's toevoegen"; +"categoryCellOption_rename" = "Album hernoemen"; +"categoryCellOption_move" = "Album verplaatsen"; +"categoryCellOption_delete" = "Album verwijderen"; // Album rename "renameCategory_title" = "Albumnaam wijzigen"; @@ -519,14 +520,6 @@ "imageUploadDetails_tags" = "Labels:"; "imageUploadDetails_description" = "Omschrijving:"; -"imageUploadDetailsView_title" = "Foto's"; -"imageUploadDetailsButton_title" = "Upload"; -"imageUploadDetailsEdit_title" = "Bewerk foto's vóór uploaden"; -"imageUploadDetailsUploading_title" = "Foto's die worden ge-upload"; -"imageUploadDetailsCell_uploadComplete" = "Klaar! Afronden..."; - -"imageUploadRemove" = "Geüploade foto's verwijderen"; - "imageUploadTableCell_waiting" = "Wachten..."; "imageUploadTableCell_preparing" = "Voorbereiden..."; "imageUploadTableCell_prepared" = "Klaar voor upload..."; @@ -541,8 +534,6 @@ "imageUploadResumeSingle" = "Upload hervatten"; "imageUploadResumeSeveral" = "Hervat %@ Uploads"; -"imageUploadProgressBar_zero" = "Bezig met uploaden 0/0"; -"imageUploadProgressBar_nonZero" = "Bezig met uploaden %@/%@"; "imageUploadProgressBar_completed" = "Klaar"; "imageUploadCompleted_title" = "Uploaden klaar"; "imageUploadCompleted_message" = "foto/video die ge-upload zijn naar jouw Piwigo-server."; @@ -618,6 +609,7 @@ "shareFailError_title" = "Delen mislukt"; "shareMetadataError_message" = "Kan metadata niet verwijderen"; +"shareFailError_tooLarge" = "Selectie te groot om te delen"; // =========================================================================== // ==> LOCAL ALBUMS & IMAGES / Photo Library diff --git a/piwigo/Resources/pl.lproj/InfoPlist.strings b/piwigo/Resources/pl.lproj/InfoPlist.strings index b251253b4..9271c4cab 100644 --- a/piwigo/Resources/pl.lproj/InfoPlist.strings +++ b/piwigo/Resources/pl.lproj/InfoPlist.strings @@ -8,3 +8,4 @@ "NSPhotoLibraryUsageDescription" = "dzięki temu będzie w stanie przechowywać zdjęcia w aplikacji Zdjęcia i przesłać je do Twojej galerii Piwigo."; "NSSiriUsageDescription" = "aby Piwigo mógł przesyłać zdjęcia z Siri."; +"NSLocalNetworkUsageDescription" = "Piwigo używa sieci lokalnej, aby uzyskać dostęp do serwera Piwigo."; diff --git a/piwigo/Resources/pl.lproj/Localizable.strings b/piwigo/Resources/pl.lproj/Localizable.strings index 480f1f3e9..4b80abda9 100644 --- a/piwigo/Resources/pl.lproj/Localizable.strings +++ b/piwigo/Resources/pl.lproj/Localizable.strings @@ -155,6 +155,9 @@ "categoryImageList_selectButton" = "Wybierz"; "categoryImageList_deselectButton" = "Odznacz"; "categoryImageList_noDataError" = "Błąd. Brak danych"; +"categoryImageList_share" = "Udostępnij"; +"categoryImageList_favorite" = "Dodaj do ulubionych"; +"categoryImageList_unfavorite" = "Usuń z ulubionych"; // Albums "Discover" "categoryDiscover_title" = "Odkrywaj"; @@ -223,9 +226,6 @@ "categoryUpload_otherAlbums" = "Inne albumy"; "categoryUpload_pasteboard" = "Schowek"; -"categoyUpload_loadSubCategories" = "ładowanie"; -"categoryUpload_images" = "Prześlij zdjęcia"; - // Default album "setDefaultCategory_select" = "Wybierz album lub podalbum, który stanie się nowym albumem głównym."; "setDefaultCategory_title" = "Album domyślny"; @@ -244,9 +244,10 @@ "createAlbumError_message" = "Nie można utworzyć nowego albumu"; // Album options (swipe actions) -"categoryCellOption_rename" = "Zmień\nnazwę"; -"categoryCellOption_move" = "Przenieść"; -"categoryCellOption_delete" = "Usuń"; +"categoryCellOption_addPhotos" = "Dodawanie zdjęć"; +"categoryCellOption_rename" = "Zmiana nazwy albumu"; +"categoryCellOption_move" = "Przenoszenie albumu"; +"categoryCellOption_delete" = "Usuwanie albumu"; // Album rename "renameCategory_title" = "Zmień nazwę albumu"; @@ -519,14 +520,6 @@ "imageUploadDetails_tags" = "Tagi:"; "imageUploadDetails_description" = "Opis:"; -"imageUploadDetailsView_title" = "Zdjęcia"; -"imageUploadDetailsButton_title" = "Wyślij"; -"imageUploadDetailsEdit_title" = "Edycja informacji o przesyłanym zdjęciu"; -"imageUploadDetailsUploading_title" = "Przesyłane zdjęcia"; -"imageUploadDetailsCell_uploadComplete" = "Zakończone! Finalizowanie..."; - -"imageUploadRemove" = "Usuwanie przesłanych zdjęć"; - "imageUploadTableCell_waiting" = "Oczekiwanie..."; "imageUploadTableCell_preparing" = "Przygotowywanie..."; "imageUploadTableCell_prepared" = "Gotowy do przesłania..."; @@ -541,8 +534,6 @@ "imageUploadResumeSingle" = "Wznów nieudane przesyłanie"; "imageUploadResumeSeveral" = "Wznów nieudane przesyłanie: %@"; -"imageUploadProgressBar_zero" = "Przesyłanie 0/0"; -"imageUploadProgressBar_nonZero" = "Przesyłanie %@/%@"; "imageUploadProgressBar_completed" = "Zakończono"; "imageUploadCompleted_title" = "Przesyłanie Zakończone"; "imageUploadCompleted_message" = "zdjęcie/film zostało przesłane do Twojego serwera Piwigo."; @@ -618,6 +609,7 @@ "shareFailError_title" = "Udostępnienie nie powiodło się"; "shareMetadataError_message" = "Nie można usunąć prywatnych metadanych"; +"shareFailError_tooLarge" = "Wybór zbyt duży, aby go udostępnić"; // =========================================================================== // ==> LOCAL ALBUMS & IMAGES / Photo Library diff --git a/piwigo/Resources/pl.lproj/ReleaseNotes.strings b/piwigo/Resources/pl.lproj/ReleaseNotes.strings index 2da12f2d6..3671a8d6e 100644 --- a/piwigo/Resources/pl.lproj/ReleaseNotes.strings +++ b/piwigo/Resources/pl.lproj/ReleaseNotes.strings @@ -6,7 +6,7 @@ Copyright © 2017 Piwigo.org. All rights reserved. */ -"v3.2.2_text" = "Wersja 3.2.2\n\n• Dodano opcję usuwania wybranych zdjęć/filmów zapisanych w urządzeniu.\n• Poprawki błędów"; +"v3.2.2_text" = "Wersja 3.2.2\n\n• Dodano opcję usuwania wybranych zdjęć/filmów zapisanych w urządzeniu\n• Poprawki błędów"; "v3.2.1_text" = "Wersja 3.2.1\n29 lipca 2024\n\n• Dodaje opcję wyświetlania albumów z ich opisami lub bez opisów\n• Poprawki błędów"; diff --git a/piwigo/Resources/ru.lproj/InfoPlist.strings b/piwigo/Resources/ru.lproj/InfoPlist.strings index 01c5fe0a6..c372a7088 100644 --- a/piwigo/Resources/ru.lproj/InfoPlist.strings +++ b/piwigo/Resources/ru.lproj/InfoPlist.strings @@ -8,3 +8,4 @@ "NSPhotoLibraryUsageDescription" = "чтобы было возможным хранить фотографии в приложении Фотографии и загружать фотографии в ваш Piwigo."; "NSSiriUsageDescription" = "чтобы Piwigo мог загружать изображения с помощью Siri."; +"NSLocalNetworkUsageDescription" = "Piwigo использует локальную сеть для доступа к вашему серверу Piwigo."; diff --git a/piwigo/Resources/ru.lproj/Localizable.strings b/piwigo/Resources/ru.lproj/Localizable.strings index ee743c34e..dd87ea0cf 100644 --- a/piwigo/Resources/ru.lproj/Localizable.strings +++ b/piwigo/Resources/ru.lproj/Localizable.strings @@ -155,6 +155,9 @@ "categoryImageList_selectButton" = "Выбрать"; "categoryImageList_deselectButton" = "Отменить выбор"; "categoryImageList_noDataError" = "Ошибка: нет данных"; +"categoryImageList_share" = "Поделиться"; +"categoryImageList_favorite" = "Добавить в избранное"; +"categoryImageList_unfavorite" = "Удалить из избранного"; // Albums "Discover" "categoryDiscover_title" = "Обзор"; @@ -223,9 +226,6 @@ "categoryUpload_otherAlbums" = "Другие Альбомы"; "categoryUpload_pasteboard" = "Буфер обмена"; -"categoyUpload_loadSubCategories" = "загрузить"; -"categoryUpload_images" = "Загрузить фото"; - // Default album "setDefaultCategory_select" = "Пожалуйста, выберите альбом или подальбом, который станет Вашим новым корневым альбомом."; "setDefaultCategory_title" = "Альбом по умолчанию"; @@ -244,9 +244,10 @@ "createAlbumError_message" = "Не удалось создать новый альбом"; // Album options (swipe actions) -"categoryCellOption_rename" = "Переименовать"; -"categoryCellOption_move" = "Переместить"; -"categoryCellOption_delete" = "Удалить"; +"categoryCellOption_addPhotos" = "Добавить фотографии"; +"categoryCellOption_rename" = "Переименовать альбом"; +"categoryCellOption_move" = "Переместить альбом"; +"categoryCellOption_delete" = "Удалить альбом"; // Album rename "renameCategory_title" = "Переименовать альбом"; @@ -519,14 +520,6 @@ "imageUploadDetails_tags" = "Тэги:"; "imageUploadDetails_description" = "Описание:"; -"imageUploadDetailsView_title" = "Фото"; -"imageUploadDetailsButton_title" = "Загрузка"; -"imageUploadDetailsEdit_title" = "Редактирование фотографий для загрузки"; -"imageUploadDetailsUploading_title" = "Изображения, которые были загружены"; -"imageUploadDetailsCell_uploadComplete" = "Завершено! Финишируем..."; - -"imageUploadRemove" = "Перенос загруженных изображений"; - "imageUploadTableCell_waiting" = "Ожидание..."; "imageUploadTableCell_preparing" = "Подготовка..."; "imageUploadTableCell_prepared" = "Готово к загрузке..."; @@ -541,8 +534,6 @@ "imageUploadResumeSingle" = "Возобновить Загрузку"; "imageUploadResumeSeveral" = "Возобновить %@ Загрузок"; -"imageUploadProgressBar_zero" = "Загрузка 0/0"; -"imageUploadProgressBar_nonZero" = "Загрузка %@/%@"; "imageUploadProgressBar_completed" = "Выполнено"; "imageUploadCompleted_title" = "Загрузка завершена"; "imageUploadCompleted_message" = "фото загружено на Ваш сервер Piwigo."; @@ -618,6 +609,7 @@ "shareFailError_title" = "Поделиться не удалось"; "shareMetadataError_message" = "Не удается разделить личные метаданные"; +"shareFailError_tooLarge" = "Выбор слишком велик, чтобы делиться им"; // =========================================================================== // ==> LOCAL ALBUMS & IMAGES / Photo Library diff --git a/piwigo/Resources/sv.lproj/InfoPlist.strings b/piwigo/Resources/sv.lproj/InfoPlist.strings index aaa63c40c..f043afba3 100644 --- a/piwigo/Resources/sv.lproj/InfoPlist.strings +++ b/piwigo/Resources/sv.lproj/InfoPlist.strings @@ -8,3 +8,4 @@ "NSPhotoLibraryUsageDescription" = "så att den kommer att kunna ladda ner bilder till Bilder-appen och ladda upp bilder till din Piwigo."; "NSSiriUsageDescription" = "så att Piwigo kan ladda upp bilder med Siri."; +"NSLocalNetworkUsageDescription" = "Piwigo använder det lokala nätverket för att komma åt din Piwigo-server."; diff --git a/piwigo/Resources/sv.lproj/Localizable.strings b/piwigo/Resources/sv.lproj/Localizable.strings index fa010af00..3a109ba25 100644 --- a/piwigo/Resources/sv.lproj/Localizable.strings +++ b/piwigo/Resources/sv.lproj/Localizable.strings @@ -155,6 +155,9 @@ "categoryImageList_selectButton" = "Välj"; "categoryImageList_deselectButton" = "Avmarkera"; "categoryImageList_noDataError" = "Fel inga Data"; +"categoryImageList_share" = "Aktie"; +"categoryImageList_favorite" = "Lägg till i favoriter"; +"categoryImageList_unfavorite" = "Ta bort från favorit"; // Albums "Discover" "categoryDiscover_title" = "Upptäck"; @@ -223,9 +226,6 @@ "categoryUpload_otherAlbums" = "Andra album"; "categoryUpload_pasteboard" = "Urklipp"; -"categoyUpload_loadSubCategories" = "ladda"; -"categoryUpload_images" = "Ladda upp bilder"; - // Default album "setDefaultCategory_select" = "Välj ett album eller underalbum som kommer att bli det nya rotalbumet."; "setDefaultCategory_title" = "Standardalbum"; @@ -244,9 +244,10 @@ "createAlbumError_message" = "Kunde inte skapa nytt album"; // Album options (swipe actions) -"categoryCellOption_rename" = "Byt namn"; -"categoryCellOption_move" = "Flytta"; -"categoryCellOption_delete" = "Radera"; +"categoryCellOption_addPhotos" = "Lägg till foton"; +"categoryCellOption_rename" = "Byt namn på album"; +"categoryCellOption_move" = "Flytta album"; +"categoryCellOption_delete" = "Radera album"; // Album rename "renameCategory_title" = "Byt namn på Album"; @@ -519,14 +520,6 @@ "imageUploadDetails_tags" = "Taggar:"; "imageUploadDetails_description" = "Beskrivning:"; -"imageUploadDetailsView_title" = "Bilder"; -"imageUploadDetailsButton_title" = "Ladda upp"; -"imageUploadDetailsEdit_title" = "Bearbeta bilder för uppladdning"; -"imageUploadDetailsUploading_title" = "Bilder som laddas upp"; -"imageUploadDetailsCell_uploadComplete" = "Klart! Färdigställer..."; - -"imageUploadRemove" = "Tar bort uppladdade bilder"; - "imageUploadTableCell_waiting" = "Väntar..."; "imageUploadTableCell_preparing" = "Förbereder..."; "imageUploadTableCell_prepared" = "Redo för uppladdning..."; @@ -541,8 +534,6 @@ "imageUploadResumeSingle" = "Återuppta uppladdning"; "imageUploadResumeSeveral" = "Återuppta %@ uppladdningar"; -"imageUploadProgressBar_zero" = "Laddar upp 0/0"; -"imageUploadProgressBar_nonZero" = "Ladda upp %@/%@"; "imageUploadProgressBar_completed" = "Klart"; "imageUploadCompleted_title" = "Uppladdning Slutförd"; "imageUploadCompleted_message" = "bilder/videor laddades upp till din Piwigoserver."; @@ -618,6 +609,7 @@ "shareFailError_title" = "Dela misslyckades"; "shareMetadataError_message" = "Kan inte ta bort privata metadata"; +"shareFailError_tooLarge" = "Urvalet är för stort för att dela"; // =========================================================================== // ==> LOCAL ALBUMS & IMAGES / Photo Library diff --git a/piwigo/Resources/zh-Hans.lproj/InfoPlist.strings b/piwigo/Resources/zh-Hans.lproj/InfoPlist.strings index 549a28421..69576f103 100644 --- a/piwigo/Resources/zh-Hans.lproj/InfoPlist.strings +++ b/piwigo/Resources/zh-Hans.lproj/InfoPlist.strings @@ -8,3 +8,4 @@ "NSPhotoLibraryUsageDescription" = "只有授予此权限,你才能将照片下载到本地图库或上传本地照片到你的Piwigo相册。"; "NSSiriUsageDescription" = "这样Piwigo将能够使用 Siri上传图像。"; +"NSLocalNetworkUsageDescription" = "Piwigo 使用本地网络访问 Piwigo 服务器。"; diff --git a/piwigo/Resources/zh-Hans.lproj/Localizable.strings b/piwigo/Resources/zh-Hans.lproj/Localizable.strings index bbfa1cf86..871feefc8 100644 --- a/piwigo/Resources/zh-Hans.lproj/Localizable.strings +++ b/piwigo/Resources/zh-Hans.lproj/Localizable.strings @@ -155,6 +155,9 @@ "categoryImageList_selectButton" = "选择"; "categoryImageList_deselectButton" = "反选"; "categoryImageList_noDataError" = "错误:没有数据"; +"categoryImageList_share" = "共享"; +"categoryImageList_favorite" = "添加到收藏夹"; +"categoryImageList_unfavorite" = "从收藏中移除"; // Albums "Discover" "categoryDiscover_title" = "探索"; @@ -223,9 +226,6 @@ "categoryUpload_otherAlbums" = "其他相册"; "categoryUpload_pasteboard" = "剪贴板"; -"categoyUpload_loadSubCategories" = "加载"; -"categoryUpload_images" = "上传图片"; - // Default album "setDefaultCategory_select" = "请选择一个相册或子相册作为新的根相册"; "setDefaultCategory_title" = "默认相册"; @@ -244,9 +244,10 @@ "createAlbumError_message" = "无法创建新相册"; // Album options (swipe actions) -"categoryCellOption_rename" = "重命名"; -"categoryCellOption_move" = "移动"; -"categoryCellOption_delete" = "删除"; +"categoryCellOption_addPhotos" = "添加照片"; +"categoryCellOption_rename" = "重新命名相册"; +"categoryCellOption_move" = "移动相册"; +"categoryCellOption_delete" = "删除相册"; // Album rename "renameCategory_title" = "重命名相册"; @@ -519,14 +520,6 @@ "imageUploadDetails_tags" = "标签:"; "imageUploadDetails_description" = "描述:"; -"imageUploadDetailsView_title" = "照片"; -"imageUploadDetailsButton_title" = "上传"; -"imageUploadDetailsEdit_title" = "编辑要上传的照片"; -"imageUploadDetailsUploading_title" = "正在被上传的照片"; -"imageUploadDetailsCell_uploadComplete" = "即将完成..."; - -"imageUploadRemove" = "移除已上传的照片"; - "imageUploadTableCell_waiting" = "请稍等..."; "imageUploadTableCell_preparing" = "正在准备..."; "imageUploadTableCell_prepared" = "已准备好上传..."; @@ -541,8 +534,6 @@ "imageUploadResumeSingle" = "继续上传"; "imageUploadResumeSeveral" = "继续剩余的 %@ 个上传"; -"imageUploadProgressBar_zero" = "正在上传 0/0"; -"imageUploadProgressBar_nonZero" = "正在上传 %@/%@"; "imageUploadProgressBar_completed" = "完成"; "imageUploadCompleted_title" = "上传完成"; "imageUploadCompleted_message" = "照片成功上传到你的Piwigo服务器"; @@ -618,6 +609,7 @@ "shareFailError_title" = "分享失败"; "shareMetadataError_message" = "无法删除私有元数据"; +"shareFailError_tooLarge" = "选择太多,无法共享"; // =========================================================================== // ==> LOCAL ALBUMS & IMAGES / Photo Library diff --git a/piwigo/Resources/zh-Hant.lproj/InfoPlist.strings b/piwigo/Resources/zh-Hant.lproj/InfoPlist.strings index 473fe67c5..070a6cbb6 100644 --- a/piwigo/Resources/zh-Hant.lproj/InfoPlist.strings +++ b/piwigo/Resources/zh-Hant.lproj/InfoPlist.strings @@ -8,3 +8,4 @@ "NSPhotoLibraryUsageDescription" = "允許Piwigo儲存照片至您的手機及上傳照片。"; "NSSiriUsageDescription" = "允許Piwigo使用Siri上傳照片。"; +"NSLocalNetworkUsageDescription" = "Piwigo 使用本機網路存取您的 Piwigo 伺服器。"; diff --git a/piwigo/Resources/zh-Hant.lproj/Localizable.strings b/piwigo/Resources/zh-Hant.lproj/Localizable.strings index 7999a6518..18c4fe39e 100644 --- a/piwigo/Resources/zh-Hant.lproj/Localizable.strings +++ b/piwigo/Resources/zh-Hant.lproj/Localizable.strings @@ -155,6 +155,9 @@ "categoryImageList_selectButton" = "選取"; "categoryImageList_deselectButton" = "取消選取"; "categoryImageList_noDataError" = "錯誤 無資料"; +"categoryImageList_share" = "分享到"; +"categoryImageList_favorite" = "加入我的最愛"; +"categoryImageList_unfavorite" = "從我的最愛移除"; // Albums "Discover" "categoryDiscover_title" = "探索"; @@ -223,9 +226,6 @@ "categoryUpload_otherAlbums" = "其他相簿"; "categoryUpload_pasteboard" = "剪貼簿"; -"categoyUpload_loadSubCategories" = "載入"; -"categoryUpload_images" = "上傳照片"; - // Default album "setDefaultCategory_select" = "請選擇一個相簿或子相簿來做為新的根相簿。"; "setDefaultCategory_title" = "預設相簿"; @@ -244,9 +244,10 @@ "createAlbumError_message" = "無法建立新相簿"; // Album options (swipe actions) -"categoryCellOption_rename" = "重新命名"; -"categoryCellOption_move" = "移動"; -"categoryCellOption_delete" = "刪除"; +"categoryCellOption_addPhotos" = "新增相片"; +"categoryCellOption_rename" = "重新命名相簿"; +"categoryCellOption_move" = "移動相簿"; +"categoryCellOption_delete" = "刪除相簿"; // Album rename "renameCategory_title" = "重新命名相簿"; @@ -519,14 +520,6 @@ "imageUploadDetails_tags" = "標籤:"; "imageUploadDetails_description" = "描述:"; -"imageUploadDetailsView_title" = "照片"; -"imageUploadDetailsButton_title" = "上傳"; -"imageUploadDetailsEdit_title" = "編輯要上傳的照片"; -"imageUploadDetailsUploading_title" = "正在被上傳的照片"; -"imageUploadDetailsCell_uploadComplete" = "完成! 正在收尾..."; - -"imageUploadRemove" = "移除以刪除的照片"; - "imageUploadTableCell_waiting" = "等待中..."; "imageUploadTableCell_preparing" = "準備中..."; "imageUploadTableCell_prepared" = "已經準備好上傳..."; @@ -541,8 +534,6 @@ "imageUploadResumeSingle" = "恢復上傳"; "imageUploadResumeSeveral" = "恢復%@個上傳"; -"imageUploadProgressBar_zero" = "上傳中 0/0"; -"imageUploadProgressBar_nonZero" = "上傳中 %@/%@"; "imageUploadProgressBar_completed" = "完成"; "imageUploadCompleted_title" = "上傳完成"; "imageUploadCompleted_message" = "張照片已上傳到您的Piwigo伺服器。"; @@ -618,6 +609,7 @@ "shareFailError_title" = "分享失敗"; "shareMetadataError_message" = "無法刪除私人中繼資料"; +"shareFailError_tooLarge" = "選擇太多,無法分享"; // =========================================================================== // ==> LOCAL ALBUMS & IMAGES / Photo Library diff --git a/piwigo/Settings.bundle/en.lproj/PrivacyPolicy.strings b/piwigo/Settings.bundle/en.lproj/PrivacyPolicy.strings index fa4191aad..252f08d59 100644 --- a/piwigo/Settings.bundle/en.lproj/PrivacyPolicy.strings +++ b/piwigo/Settings.bundle/en.lproj/PrivacyPolicy.strings @@ -60,4 +60,4 @@ "contact_address" = "Piwigo, 42 rue des Vignes, 21800 Quetigny (France)"; -"contact_email" = "ios@piwigo.org"; +"contact_email" = "piwigo@lelievre-berna.net"; diff --git a/piwigo/Settings/Extensions/SettingsViewController+UITableViewDelegate.swift b/piwigo/Settings/Extensions/SettingsViewController+UITableViewDelegate.swift index 70c8d8772..73720b033 100644 --- a/piwigo/Settings/Extensions/SettingsViewController+UITableViewDelegate.swift +++ b/piwigo/Settings/Extensions/SettingsViewController+UITableViewDelegate.swift @@ -430,7 +430,7 @@ extension SettingsViewController: UITableViewDelegate // Configure the fields of the interface. composeVC.setToRecipients([ - NSLocalizedString("contact_email", tableName: "PrivacyPolicy", bundle: Bundle.main, value: "", comment: "Contact email") + NSLocalizedString("contact_email", tableName: "PrivacyPolicy", bundle: Bundle.main, value: "", comment: "Contact email") ]) // Collect version and build numbers diff --git a/piwigo/Settings/Privacy/ShareMetadataViewController.swift b/piwigo/Settings/Privacy/ShareMetadataViewController.swift index 84e132b72..e4765dd63 100644 --- a/piwigo/Settings/Privacy/ShareMetadataViewController.swift +++ b/piwigo/Settings/Privacy/ShareMetadataViewController.swift @@ -219,8 +219,10 @@ class ShareMetadataViewController: UIViewController, UITableViewDelegate, UITabl activitiesNotSharing.append(activity) // Sort list of activities - activitiesSharingMetadata = activitiesSharing.sorted() - activitiesNotSharingMetadata = activitiesNotSharing.sorted() + activitiesSharingMetadata = activitiesSharing.map({$0.rawValue}).sorted() + .map({UIActivity.ActivityType(rawValue: $0)}) + activitiesNotSharingMetadata = activitiesNotSharing.map({$0.rawValue}).sorted() + .map({UIActivity.ActivityType(rawValue: $0)}) // Determine new indexPath of tapped activity let index = activitiesNotSharingMetadata.firstIndex(of: activity) @@ -247,8 +249,10 @@ class ShareMetadataViewController: UIViewController, UITableViewDelegate, UITabl activitiesSharing.append(activity) // Sort list of activities - activitiesSharingMetadata = activitiesSharing.sorted() - activitiesNotSharingMetadata = activitiesNotSharing.sorted() + activitiesSharingMetadata = activitiesSharing.map({$0.rawValue}).sorted() + .map({UIActivity.ActivityType(rawValue: $0)}) + activitiesNotSharingMetadata = activitiesNotSharing.map({$0.rawValue}).sorted() + .map({UIActivity.ActivityType(rawValue: $0)}) // Determine new indexPath of tapped activity let index = activitiesSharingMetadata.firstIndex(of: activity) @@ -362,8 +366,10 @@ class ShareMetadataViewController: UIViewController, UITableViewDelegate, UITabl activitiesNotSharing.append(pwgActivityTypeOther) } - activitiesSharingMetadata = activitiesSharing.sorted() - activitiesNotSharingMetadata = activitiesNotSharing.sorted() + activitiesSharingMetadata = activitiesSharing.map({$0.rawValue}).sorted() + .map({UIActivity.ActivityType(rawValue: $0)}) + activitiesNotSharingMetadata = activitiesNotSharing.map({$0.rawValue}).sorted() + .map({UIActivity.ActivityType(rawValue: $0)}) } private func switchActivity(_ activity: UIActivity.ActivityType, toState newState: Bool) { diff --git a/piwigo/Supporting Files/Macros/Device+Model.swift b/piwigo/Supporting Files/Macros/Device+Model.swift index d9d5f6389..1db0a207b 100644 --- a/piwigo/Supporting Files/Macros/Device+Model.swift +++ b/piwigo/Supporting Files/Macros/Device+Model.swift @@ -15,9 +15,9 @@ extension UIDevice { // MARK: - Identifier var identifier: String { - #if targetEnvironment(simulator) +#if targetEnvironment(simulator) let identifier = ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"]! - #else +#else var systemInfo = utsname() uname(&systemInfo) let machineMirror = Mirror(reflecting: systemInfo.machine) @@ -25,7 +25,7 @@ extension UIDevice { guard let value = element.value as? Int8 , value != 0 else { return identifier } return identifier + String(UnicodeScalar(UInt8(value))) } - #endif +#endif return identifier } @@ -129,7 +129,7 @@ extension UIDevice { return "iPhone 15 Pro" case "iPhone16,2": return "iPhone 15 Pro Max" - + // MARK: iPad case "iPad1,1": return "iPad" @@ -287,7 +287,7 @@ extension UIDevice { return "iPad Pro 13-inch (M4) (Wi-Fi)" case "iPad16,6": return "iPad Pro 13-inch (M4) (Wi-Fi + Cellular)" - + // MARK: iPod case "iPod1,1": return "iPod touch" @@ -303,7 +303,7 @@ extension UIDevice { return "iPod touch (6th generation)" case "iPod9,1": return "iPod touch (7th generation)" - + // MARK: Simulator case "i386", "x86_64": return "Simulator" @@ -311,12 +311,12 @@ extension UIDevice { return identifier } } - - + + // MARK: - Photo Resolutions var modelPhotoResolution: String { switch identifier { - + // MARK: iPhone case "iPhone1,1", "iPhone1,2": return "2 Mpx" @@ -325,48 +325,48 @@ extension UIDevice { case "iPhone3,1", "iPhone3,2", "iPhone3,3": return "5 Mpx" case "iPhone4,1", - "iPhone5,1", "iPhone5,2", "iPhone5,3", "iPhone5,4", - "iPhone6,1", "iPhone6,2", "iPhone7,1", "iPhone7,2": + "iPhone5,1", "iPhone5,2", "iPhone5,3", "iPhone5,4", + "iPhone6,1", "iPhone6,2", "iPhone7,1", "iPhone7,2": return "8 Mpx" case "iPhone8,1", "iPhone8,2", "iPhone8,4", - "iPhone9,1", "iPhone9,2", "iPhone9,3", "iPhone9,4", - "iPhone10,1", "iPhone10,2", "iPhone10,3", "iPhone10,4", "iPhone10,5", "iPhone10,6", - "iPhone11,2", "iPhone11,6", "iPhone11,8", - "iPhone12,1", "iPhone12,3", "iPhone12,5", "iPhone12,8", - "iPhone13,1", "iPhone13,2", "iPhone13,3", "iPhone13,4", - "iPhone14,2", "iPhone14,3", "iPhone14,4", "iPhone14,5", "iPhone14,6", - "iPhone14,7", "iPhone14,8": + "iPhone9,1", "iPhone9,2", "iPhone9,3", "iPhone9,4", + "iPhone10,1", "iPhone10,2", "iPhone10,3", "iPhone10,4", "iPhone10,5", "iPhone10,6", + "iPhone11,2", "iPhone11,6", "iPhone11,8", + "iPhone12,1", "iPhone12,3", "iPhone12,5", "iPhone12,8", + "iPhone13,1", "iPhone13,2", "iPhone13,3", "iPhone13,4", + "iPhone14,2", "iPhone14,3", "iPhone14,4", "iPhone14,5", "iPhone14,6", + "iPhone14,7", "iPhone14,8": return "12 Mpx" case "iPhone15,2", "iPhone15,3", "iPhone15,4", "iPhone15,5", - "iPhone16,1", "iPhone16,2": + "iPhone16,1", "iPhone16,2": return "48 Mpx" - + // MARK: iPad case "iPad1,1": return "" case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4": return "0.92 Mpx" case "iPad2,5", "iPad2,6", "iPad2,7", - "iPad3,1", "iPad3,2", "iPad3,3", "iPad3,4", "iPad3,5", "iPad3,6", - "iPad4,1", "iPad4,2", "iPad4,4", "iPad4,5", "iPad4,7", "iPad4,8": + "iPad3,1", "iPad3,2", "iPad3,3", "iPad3,4", "iPad3,5", "iPad3,6", + "iPad4,1", "iPad4,2", "iPad4,4", "iPad4,5", "iPad4,7", "iPad4,8": return "5 Mpx" case "iPad5,1", "iPad5,2", "iPad5,3", "iPad5,4", - "iPad6,7", "iPad6,8", "iPad6,11", "iPad6,12", - "iPad7,5", "iPad7,6", "iPad7,11", "iPad7,12", - "iPad11,1", "iPad11,2", "iPad11,3", "iPad11,4", "iPad11,6", "iPad11,7": + "iPad6,7", "iPad6,8", "iPad6,11", "iPad6,12", + "iPad7,5", "iPad7,6", "iPad7,11", "iPad7,12", + "iPad11,1", "iPad11,2", "iPad11,3", "iPad11,4", "iPad11,6", "iPad11,7": return "8 Mpx" case "iPad6,3", "iPad6,4", - "iPad7,1", "iPad7,2", "iPad7,3", "iPad7,4", - "iPad8,1", "iPad8,2", "iPad8,3", "iPad8,4", "iPad8,5", "iPad8,6", - "iPad8,7", "iPad8,8", "iPad8,9", "iPad8,10", "iPad8,11", "iPad8,12", - "iPad12,1", "iPad12,2", - "iPad13,1", "iPad13,2", "iPad13,4", "iPad13,5", "iPad13,6", "iPad13,7", "iPad13,8", - "iPad13,9", "iPad13,10", "iPad13,11", "iPad13,16", "iPad13,17", "iPad13,18", "iPad13,19", - "iPad14,1", "iPad14,2", "iPad14,3", "iPad14,4", "iPad14,5", "iPad14,6", - "iPad14,8", "iPad14,9", "iPad14,10", "iPad14,11", - "iPad16,3", "iPad16,4", "iPad16,5", "iPad16,6": + "iPad7,1", "iPad7,2", "iPad7,3", "iPad7,4", + "iPad8,1", "iPad8,2", "iPad8,3", "iPad8,4", "iPad8,5", "iPad8,6", + "iPad8,7", "iPad8,8", "iPad8,9", "iPad8,10", "iPad8,11", "iPad8,12", + "iPad12,1", "iPad12,2", + "iPad13,1", "iPad13,2", "iPad13,4", "iPad13,5", "iPad13,6", "iPad13,7", "iPad13,8", + "iPad13,9", "iPad13,10", "iPad13,11", "iPad13,16", "iPad13,17", "iPad13,18", "iPad13,19", + "iPad14,1", "iPad14,2", "iPad14,3", "iPad14,4", "iPad14,5", "iPad14,6", + "iPad14,8", "iPad14,9", "iPad14,10", "iPad14,11", + "iPad16,3", "iPad16,4", "iPad16,5", "iPad16,6": return "12 Mpx" - + // MARK: iPod case "iPod1,1", "iPod2,1", "iPod3,1": return "" @@ -376,7 +376,7 @@ extension UIDevice { return "5 Mpx" case "iPod7,1", "iPod9,1": return "8 Mpx" - + // MARK: Simulator case "i386", "x86_64": return "? Mpx" @@ -384,62 +384,61 @@ extension UIDevice { return "? Mpx" } } - - + + // MARK: - Video Capabilities var modelVideoCapabilities: String { switch identifier { - // MARK: iPhone case "iPhone1,1", "iPhone1,2", "iPhone2,1": return "VGA, 30 fps" case "iPhone3,1", "iPhone3,2", "iPhone3,3": return "HD, 30 fps" case "iPhone4,1", - "iPhone5,1", "iPhone5,2", "iPhone5,3", "iPhone5,4", - "iPhone6,1", "iPhone6,2", - "iPhone7,1", "iPhone7,2": + "iPhone5,1", "iPhone5,2", "iPhone5,3", "iPhone5,4", + "iPhone6,1", "iPhone6,2", + "iPhone7,1", "iPhone7,2": return "Full HD, 30 fps" case "iPhone8,1", "iPhone8,2", "iPhone8,4", - "iPhone9,1", "iPhone9,2", "iPhone9,3", "iPhone9,4": + "iPhone9,1", "iPhone9,2", "iPhone9,3", "iPhone9,4": return "4K, 30 fps" case "iPhone10,1", "iPhone10,2", "iPhone10,3", "iPhone10,4", "iPhone10,5", "iPhone10,6", - "iPhone11,2", "iPhone11,6", "iPhone11,8", - "iPhone12,1", "iPhone12,3", "iPhone12,5", "iPhone12,8", - "iPhone13,1", "iPhone13,2", "iPhone13,3", "iPhone13,4", - "iPhone14,2", "iPhone14,3", "iPhone14,4", "iPhone14,5", "iPhone14,6", "iPhone14,7", "iPhone14,8", - "iPhone15,2", "iPhone15,3", "iPhone15,4", "iPhone15,5", - "iPhone16,1", "iPhone16,2": + "iPhone11,2", "iPhone11,6", "iPhone11,8", + "iPhone12,1", "iPhone12,3", "iPhone12,5", "iPhone12,8", + "iPhone13,1", "iPhone13,2", "iPhone13,3", "iPhone13,4", + "iPhone14,2", "iPhone14,3", "iPhone14,4", "iPhone14,5", "iPhone14,6", "iPhone14,7", "iPhone14,8", + "iPhone15,2", "iPhone15,3", "iPhone15,4", "iPhone15,5", + "iPhone16,1", "iPhone16,2": return "4K, 60 fps" - + // MARK: iPad case "iPad1,1": return "" case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4": return "VGA, 30 fps" case "iPad2,5", "iPad2,6", "iPad2,7", - "iPad3,1", "iPad3,2", "iPad3,3", "iPad3,4", "iPad3,5", "iPad3,6", - "iPad4,1", "iPad4,2", "iPad4,4", "iPad4,5", "iPad4,7", "iPad4,8", - "iPad6,3", "iPad6,4": + "iPad3,1", "iPad3,2", "iPad3,3", "iPad3,4", "iPad3,5", "iPad3,6", + "iPad4,1", "iPad4,2", "iPad4,4", "iPad4,5", "iPad4,7", "iPad4,8", + "iPad6,3", "iPad6,4": return "HD, 30 fps" case "iPad5,1", "iPad5,2", "iPad5,3", "iPad5,4", - "iPad6,7", "iPad6,8", "iPad6,11", "iPad6,12", - "iPad7,5", "iPad7,6", "iPad7,11", "iPad7,12", - "iPad11,1", "iPad11,2", "iPad11,3", "iPad11,4", "iPad11,6", "iPad11,7": + "iPad6,7", "iPad6,8", "iPad6,11", "iPad6,12", + "iPad7,5", "iPad7,6", "iPad7,11", "iPad7,12", + "iPad11,1", "iPad11,2", "iPad11,3", "iPad11,4", "iPad11,6", "iPad11,7": return "Full HD, 30 fps" case "iPad7,1", "iPad7,2", "iPad7,3", "iPad7,4", - "iPad8,1", "iPad8,2", "iPad8,3", "iPad8,4", "iPad8,5", - "iPad8,6", "iPad8,7", "iPad8,8", "iPad8,9", "iPad8,10", "iPad8,11", "iPad8,12", - "iPad12,1", "iPad12,2", "iPad13,1", "iPad13,2": + "iPad8,1", "iPad8,2", "iPad8,3", "iPad8,4", "iPad8,5", + "iPad8,6", "iPad8,7", "iPad8,8", "iPad8,9", "iPad8,10", "iPad8,11", "iPad8,12", + "iPad12,1", "iPad12,2", "iPad13,1", "iPad13,2": return "4K, 30 fps" case "iPad13,4", "iPad13,5", "iPad13,6", "iPad13,7", - "iPad13,8", "iPad13,9", "iPad13,10", "iPad13,11", - "iPad13,16", "iPad13,17", "iPad13,18", "iPad13,19", - "iPad14,1", "iPad14,2", "iPad14,3", "iPad14,4", "iPad14,5", "iPad14,6", - "iPad14,8", "iPad14,9", "iPad14,10", "iPad14,11", - "iPad16,3", "iPad16,4", "iPad16,5", "iPad16,6": + "iPad13,8", "iPad13,9", "iPad13,10", "iPad13,11", + "iPad13,16", "iPad13,17", "iPad13,18", "iPad13,19", + "iPad14,1", "iPad14,2", "iPad14,3", "iPad14,4", "iPad14,5", "iPad14,6", + "iPad14,8", "iPad14,9", "iPad14,10", "iPad14,11", + "iPad16,3", "iPad16,4", "iPad16,5", "iPad16,6": return "4K, 60 fps" - + // MARK: iPod case "iPod1,1", "iPod2,1", "iPod3,1": return "" @@ -447,7 +446,7 @@ extension UIDevice { return "HD, 30 fps" case "iPod5,1", "iPod7,1", "iPod9,1": return "Full HD, 30 fps" - + // MARK: Simulator case "i386", "x86_64": return "? Mpx" @@ -455,4 +454,97 @@ extension UIDevice { return "? Mpx" } } + + // MARK: - Available Memory in MB + /// Returns the lowest value of the model when the memory size depends on the capacity + var modelMemorySize: Int64 { + switch identifier { + // MARK: iPhone + case "iPhone1,1", "iPhone1,2": + return 128 + case "iPhone2,1": + return 256 + case "iPhone3,1", "iPhone3,2", "iPhone3,3", + "iPhone4,1": + return 512 + case "iPhone5,1", "iPhone5,2", "iPhone5,3", "iPhone5,4", + "iPhone6,1", "iPhone6,2", + "iPhone7,1", "iPhone7,2": + return 1024 + case "iPhone8,1", "iPhone8,2", "iPhone8,4", + "iPhone9,1", "iPhone9,3", + "iPhone10,1", "iPhone10,4": + return 2048 + case "iPhone9,2", "iPhone9,4", + "iPhone10,2", "iPhone10,3", "iPhone10,5", "iPhone10,6", + "iPhone11,8", + "iPhone12,8": + return 3072 + case "iPhone11,2", "iPhone11,6", + "iPhone12,1", "iPhone12,3", "iPhone12,5", + "iPhone13,1", "iPhone13,2", + "iPhone14,4", "iPhone14,5", "iPhone14,6": + return 4096 + case "iPhone13,3", "iPhone13,4", + "iPhone14,2", "iPhone14,3", "iPhone14,7", "iPhone14,8", + "iPhone15,2", "iPhone15,3", "iPhone15,4", "iPhone15,5": + return 6144 + case "iPhone16,1", "iPhone16,2": + return 8192 + + // MARK: iPad + case "iPad1,1": + return 256 + case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4", "iPad2,5", "iPad2,6", "iPad2,7": + return 512 + case "iPad3,1", "iPad3,2", "iPad3,3", "iPad3,4", "iPad3,5", "iPad3,6", + "iPad4,1", "iPad4,2", "iPad4,4", "iPad4,5", "iPad4,7", "iPad4,8": + return 1024 + case "iPad5,1", "iPad5,2", "iPad5,3", "iPad5,4", + "iPad6,3", "iPad6,4", "iPad6,11", "iPad6,12", + "iPad7,5", "iPad7,6": + return 2048 + case "iPad11,1", "iPad11,2", "iPad11,3", "iPad11,4", + "iPad7,11", "iPad7,12", + "iPad11,6", "iPad11,7", + "iPad12,1", "iPad12,2": + return 3072 + case "iPad6,7", "iPad6,8", + "iPad13,1", "iPad13,2", + "iPad7,1", "iPad7,2", "iPad7,3", "iPad7,4", + "iPad8,1", "iPad8,3", "iPad8,5", "iPad8,7", + "iPad13,18", "iPad13,19", + "iPad14,1", "iPad14,2": + return 4096 + case "iPad8,9", "iPad8,10", "iPad8,11", "iPad8,12", + "iPad8,2", "iPad8,4", "iPad8,6", "iPad8,8": + return 6144 + case "iPad13,4", "iPad13,6", "iPad13,8", "iPad13,10", + "iPad13,16", "iPad13,17", + "iPad14,3", "iPad14,4", "iPad14,5", "iPad14,6", + "iPad14,8","iPad14,9", "iPad14,10", "iPad14,11", + "iPad16,3", "iPad16,4", "iPad16,5", "iPad16,6": + return 8192 + case "iPad13,5", "iPad13,7", "iPad13,9", "iPad13,11": + return 16384 + + // MARK: iPod + case "iPod1,1", "iPod2,1": + return 128 + case "iPod3,1", "iPod4,1": + return 256 + case "iPod5,1": + return 512 + case "iPod7,1": + return 1024 + case "iPod9,1": + return 2048 + + // MARK: Simulator + case "i386", "x86_64": + return 16384 + default: + return 16384 + } + } } diff --git a/piwigo/Supporting Files/SceneDelegate.swift b/piwigo/Supporting Files/SceneDelegate.swift index 735d6d043..2f1d28a71 100644 --- a/piwigo/Supporting Files/SceneDelegate.swift +++ b/piwigo/Supporting Files/SceneDelegate.swift @@ -49,8 +49,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // Get other existing scenes of the main screen let otherScenes = UIApplication.shared.connectedScenes - .filter({$0.session.role == .windowApplication}) - .filter({$0.session.persistentIdentifier != session.persistentIdentifier}) + .filter({($0.session.role == .windowApplication) && + ($0.session.persistentIdentifier != session.persistentIdentifier)}) // Determine the user activity from a new connection or from a session's state restoration. guard let userActivity = connectionOptions.userActivities.first ?? session.stateRestorationActivity else { @@ -81,8 +81,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // Did user create other scenes during the migration? let otherScenes = UIApplication.shared.connectedScenes - .filter({$0.session.role == .windowApplication}) - .filter({$0.session.persistentIdentifier != session.persistentIdentifier}) + .filter({($0.session.role == .windowApplication) && + ($0.session.persistentIdentifier != session.persistentIdentifier)}) otherScenes.forEach { scene in if let windowScene = (scene as? UIWindowScene) { // Replace migration with album view controller diff --git a/piwigo/Upload/PhotosFetch.swift b/piwigo/Upload/PhotosFetch.swift index 1b1c2c366..9d6aa31a1 100644 --- a/piwigo/Upload/PhotosFetch.swift +++ b/piwigo/Upload/PhotosFetch.swift @@ -44,9 +44,9 @@ class PhotosFetch: NSObject { if Thread.isMainThread { self.showPhotosLibraryAccessRestricted(in: viewController) } else { - DispatchQueue.main.async(execute: { + DispatchQueue.main.async { self.showPhotosLibraryAccessRestricted(in: viewController) - }) + } } } doWithoutAccess() @@ -63,9 +63,9 @@ class PhotosFetch: NSObject { if Thread.isMainThread { self.requestPhotoLibraryAccess(in: viewController) } else { - DispatchQueue.main.async(execute: { + DispatchQueue.main.async { self.requestPhotoLibraryAccess(in: viewController) - }) + } } } } @@ -74,9 +74,9 @@ class PhotosFetch: NSObject { if Thread.isMainThread { doWithAccess() } else { - DispatchQueue.main.async(execute: { + DispatchQueue.main.async { doWithAccess() - }) + } } } } @@ -92,9 +92,9 @@ class PhotosFetch: NSObject { if Thread.isMainThread { showPhotosLibraryAccessRestricted(in: viewController) } else { - DispatchQueue.main.async(execute: { + DispatchQueue.main.async { self.showPhotosLibraryAccessRestricted(in: viewController) - }) + } } } doWithoutAccess() @@ -111,9 +111,9 @@ class PhotosFetch: NSObject { if Thread.isMainThread { requestPhotoLibraryAccess(in: viewController) } else { - DispatchQueue.main.async(execute: { + DispatchQueue.main.async { self.requestPhotoLibraryAccess(in: viewController) - }) + } } } } @@ -122,9 +122,9 @@ class PhotosFetch: NSObject { if Thread.isMainThread { doWithAccess() } else { - DispatchQueue.main.async(execute: { + DispatchQueue.main.async { doWithAccess() - }) + } } @unknown default: print("unknown Photo Library authorization status") @@ -150,9 +150,9 @@ class PhotosFetch: NSObject { if Thread.isMainThread { self.showPhotosLibraryAccessRestricted(in: viewController) } else { - DispatchQueue.main.async(execute: { + DispatchQueue.main.async { self.showPhotosLibraryAccessRestricted(in: viewController) - }) + } } } // Exceute next steps @@ -163,9 +163,9 @@ class PhotosFetch: NSObject { if Thread.isMainThread { self.requestPhotoLibraryAccess(in: viewController) } else { - DispatchQueue.main.async(execute: { + DispatchQueue.main.async { self.requestPhotoLibraryAccess(in: viewController) - }) + } } } // Exceute next steps @@ -175,9 +175,9 @@ class PhotosFetch: NSObject { if Thread.isMainThread { doWithAccess() } else { - DispatchQueue.main.async(execute: { + DispatchQueue.main.async { doWithAccess() - }) + } } } }) @@ -187,9 +187,9 @@ class PhotosFetch: NSObject { if Thread.isMainThread { showPhotosLibraryAccessRestricted(in: viewController) } else { - DispatchQueue.main.async(execute: { + DispatchQueue.main.async { self.showPhotosLibraryAccessRestricted(in: viewController) - }) + } } } // Exceute next steps @@ -200,9 +200,9 @@ class PhotosFetch: NSObject { if Thread.isMainThread { requestPhotoLibraryAccess(in: viewController) } else { - DispatchQueue.main.async(execute: { + DispatchQueue.main.async { self.requestPhotoLibraryAccess(in: viewController) - }) + } } } // Exceute next steps @@ -212,9 +212,9 @@ class PhotosFetch: NSObject { if Thread.isMainThread { doWithAccess() } else { - DispatchQueue.main.async(execute: { + DispatchQueue.main.async { doWithAccess() - }) + } } } } diff --git a/piwigo/Upload/Pick Local Images/Extensions/LocalImagesViewController+Data.swift b/piwigo/Upload/Pick Local Images/Extensions/LocalImagesViewController+Data.swift index 1534b9253..cc251b63c 100644 --- a/piwigo/Upload/Pick Local Images/Extensions/LocalImagesViewController+Data.swift +++ b/piwigo/Upload/Pick Local Images/Extensions/LocalImagesViewController+Data.swift @@ -341,14 +341,14 @@ extension LocalImagesViewController switch UploadVars.localImagesSort { case .dateCreatedDescending: if let index = indexOfImageSortedByMonth[indexPath.section].first { - return index + indexPath.row + return index + indexPath.item } else { return 0 } case .dateCreatedAscending: let lastSection = indexOfImageSortedByMonth.endIndex - 1 if let index = indexOfImageSortedByMonth[lastSection - indexPath.section].last { - return index - indexPath.row + return index - indexPath.item } else { return 0 } @@ -359,14 +359,14 @@ extension LocalImagesViewController switch UploadVars.localImagesSort { case .dateCreatedDescending: if let index = indexOfImageSortedByWeek[indexPath.section].first { - return index + indexPath.row + return index + indexPath.item } else { return 0 } case .dateCreatedAscending: let lastSection = indexOfImageSortedByWeek.endIndex - 1 if let index = indexOfImageSortedByWeek[lastSection - indexPath.section].last { - return index - indexPath.row + return index - indexPath.item } else { return 0 } @@ -377,14 +377,14 @@ extension LocalImagesViewController switch UploadVars.localImagesSort { case .dateCreatedDescending: if let index = indexOfImageSortedByDay[indexPath.section].first { - return index + indexPath.row + return index + indexPath.item } else { return 0 } case .dateCreatedAscending: let lastSection = indexOfImageSortedByDay.endIndex - 1 if let index = indexOfImageSortedByDay[lastSection - indexPath.section].last { - return index - indexPath.row + return index - indexPath.item } else { return 0 } @@ -394,9 +394,9 @@ extension LocalImagesViewController case .none: switch UploadVars.localImagesSort { case .dateCreatedDescending: - return indexPath.row + return indexPath.item case .dateCreatedAscending: - return max(0, fetchedImages.count - 1 - indexPath.row) + return max(0, fetchedImages.count - 1 - indexPath.item) default: return 0 } diff --git a/piwigo/Upload/Pick Local Images/Extensions/LocalImagesViewController+DataSource.swift b/piwigo/Upload/Pick Local Images/Extensions/LocalImagesViewController+DataSource.swift index 60ecaadc4..1fcc95cd5 100644 --- a/piwigo/Upload/Pick Local Images/Extensions/LocalImagesViewController+DataSource.swift +++ b/piwigo/Upload/Pick Local Images/Extensions/LocalImagesViewController+DataSource.swift @@ -10,7 +10,7 @@ import Photos import UIKit import piwigoKit -//MARK: UICollectionViewDataSource Methods +// MARK: UICollectionViewDataSource Methods extension LocalImagesViewController: UICollectionViewDataSource { // MARK: - Headers & Footers diff --git a/piwigo/Upload/Pick Local Images/Extensions/LocalImagesViewController+Delegate.swift b/piwigo/Upload/Pick Local Images/Extensions/LocalImagesViewController+Delegate.swift index a8390922f..1c9fd1c7d 100644 --- a/piwigo/Upload/Pick Local Images/Extensions/LocalImagesViewController+Delegate.swift +++ b/piwigo/Upload/Pick Local Images/Extensions/LocalImagesViewController+Delegate.swift @@ -72,7 +72,7 @@ extension LocalImagesViewController: UICollectionViewDelegate let canDelete = (imageAsset.sourceType != .typeCloudShared) && (upload.isEmpty || [.finished, .moderated].contains(upload.first?.state)) - // Return nil or a + // Return preview and appropriate menu return UIContextMenuConfiguration(identifier: identifier, previewProvider: { [self] in // Create preview view controller @@ -93,8 +93,11 @@ extension LocalImagesViewController: UICollectionViewDelegate } else { children.append(self.statusAction(upload.first)) } + if self.reUploadAllowed { + children.append(self.uploaAction(forCell: cell, at: indexPath)) + } if canDelete { - children.append(self.deleteAction(forCell: cell, at: indexPath)) + children.append(self.deleteMenu(forCell: cell, at: indexPath)) } return UIMenu(title: "", children: children) }) @@ -144,8 +147,11 @@ extension LocalImagesViewController: UICollectionViewDelegate } else { children.append(self.statusAction(upload.first)) } + if self.reUploadAllowed { + children.append(self.uploaAction(forCell: cell, at: indexPath)) + } if canDelete { - children.append(self.deleteAction(forCell: cell, at: indexPath)) + children.append(self.deleteMenu(forCell: cell, at: indexPath)) } return UIMenu(title: "", children: children) }) @@ -230,48 +236,63 @@ extension LocalImagesViewController: UICollectionViewDelegate @available(iOS 13.0, *) private func uploaAction(forCell cell: LocalImageCollectionViewCell, at indexPath: IndexPath) -> UIAction { - return UIAction(title: NSLocalizedString("imageUploadDetailsButton_title", comment: "Upload"), - image: UIImage(contentsOfFile: "piwigo")) { action in - // Check that an upload request does not exist for that image + return UIAction(title: NSLocalizedString("tabBar_upload", comment: "Upload"), + image: UIImage(named: "imageUpload")) { action in + // Check that an upload request does not exist for that image (should never happen) if (self.uploads.fetchedObjects ?? []) .filter({$0.localIdentifier == cell.localIdentifier}).first != nil { return } - // Create an upload request for that image and update the corresponding cell + // Create an upload request for that image and add it to the upload queue let upload = UploadProperties(localIdentifier: cell.localIdentifier, category: self.categoryId) - cell.update(selected: true, state: .waiting) + self.uploadRequests.append(upload) + + // Disable buttons + self.cancelBarButton?.isEnabled = false + self.uploadBarButton?.isEnabled = false + self.actionBarButton?.isEnabled = false + self.trashBarButton?.isEnabled = false - // Append the upload to the queue - UploadManager.shared.backgroundQueue.async { - self.uploadProvider.importUploads(from: [upload]) { error in - guard let error = error else { - // Restart UploadManager activities - UploadManager.shared.backgroundQueue.async { - UploadManager.shared.isPaused = false - UploadManager.shared.findNextImageToUpload() - } - return - } - DispatchQueue.main.async { - self.dismissPiwigoError(withTitle: NSLocalizedString("CoreDataFetch_UploadCreateFailed", comment: "Failed to create a new Upload object."), message: error.localizedDescription) { - // Restart UploadManager activities - UploadManager.shared.backgroundQueue.async { - UploadManager.shared.isPaused = false - UploadManager.shared.findNextImageToUpload() - } - } + // Show upload parameter views + let uploadSwitchSB = UIStoryboard(name: "UploadSwitchViewController", bundle: nil) + if let uploadSwitchVC = uploadSwitchSB.instantiateViewController(withIdentifier: "UploadSwitchViewController") as? UploadSwitchViewController { + uploadSwitchVC.delegate = self + uploadSwitchVC.user = self.user + + // Will we propose to delete images after upload? + if let imageAsset = PHAsset.fetchAssets(withLocalIdentifiers: [cell.localIdentifier], + options: nil).firstObject { + // Only local images can be deleted + if imageAsset.sourceType != .typeCloudShared { + // Will allow user to delete images after upload + uploadSwitchVC.canDeleteImages = true } } + + // Push Edit view embedded in navigation controller + let navController = UINavigationController(rootViewController: uploadSwitchVC) + navController.modalPresentationStyle = .popover + navController.modalTransitionStyle = .coverVertical + navController.popoverPresentationController?.sourceView = self.localImagesCollection + navController.popoverPresentationController?.barButtonItem = self.uploadBarButton + navController.popoverPresentationController?.permittedArrowDirections = .up + self.navigationController?.present(navController, animated: true) } } } + @available(iOS 13.0, *) + private func deleteMenu(forCell cell: LocalImageCollectionViewCell, at indexPath: IndexPath) -> UIMenu { + let delete = deleteAction(forCell: cell, at: indexPath) + let menuId = UIMenu.Identifier("org.piwigo.removeFromCameraRoll") + return UIMenu(identifier: menuId, options: UIMenu.Options.displayInline, children: [delete]) + } + @available(iOS 13.0, *) private func deleteAction(forCell cell: LocalImageCollectionViewCell, at indexPath: IndexPath) -> UIAction { return UIAction(title: NSLocalizedString("localImages_deleteTitle", comment: "Remove from Camera Roll"), - image: UIImage(systemName: "trash"), - attributes: .destructive) { action in + image: UIImage(systemName: "trash"), attributes: .destructive) { action in // Get image identifier and check if this image has been uploaded if let upload = (self.uploads.fetchedObjects ?? []).filter({$0.localIdentifier == cell.localIdentifier}).first { // Delete uploaded image diff --git a/piwigo/Upload/Pick Local Images/Extensions/LocalImagesViewController+FlowLayout.swift b/piwigo/Upload/Pick Local Images/Extensions/LocalImagesViewController+FlowLayout.swift index 167548567..1f6b0dade 100644 --- a/piwigo/Upload/Pick Local Images/Extensions/LocalImagesViewController+FlowLayout.swift +++ b/piwigo/Upload/Pick Local Images/Extensions/LocalImagesViewController+FlowLayout.swift @@ -31,7 +31,7 @@ extension LocalImagesViewController: UICollectionViewDelegateFlowLayout } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { - return UIEdgeInsets(top: 10, left: 0, bottom: 10, right: 0) + return UIEdgeInsets(top: CGFloat(10), left: CGFloat.zero, bottom: CGFloat(10), right: CGFloat.zero) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { diff --git a/piwigo/Upload/Pick Local Images/Extensions/LocalImagesViewController+HeaderDelegate.swift b/piwigo/Upload/Pick Local Images/Extensions/LocalImagesViewController+HeaderDelegate.swift index eb19a8ead..653c5fd32 100644 --- a/piwigo/Upload/Pick Local Images/Extensions/LocalImagesViewController+HeaderDelegate.swift +++ b/piwigo/Upload/Pick Local Images/Extensions/LocalImagesViewController+HeaderDelegate.swift @@ -9,9 +9,9 @@ import UIKit import piwigoKit +// MARK: - LocalImagesHeaderDelegate Methods extension LocalImagesViewController: LocalImagesHeaderDelegate { - // MARK: - LocalImagesHeaderReusableView Delegate Methods func didSelectImagesOfSection(_ section: Int) { let nberOfImagesInSection = localImagesCollection.numberOfItems(inSection: section) // let start = CFAbsoluteTimeGetCurrent() diff --git a/piwigo/Upload/Pick Local Images/Extensions/LocalImagesViewController+UploadSwitchDelegate.swift b/piwigo/Upload/Pick Local Images/Extensions/LocalImagesViewController+UploadSwitchDelegate.swift index 1a1d61736..c10723466 100644 --- a/piwigo/Upload/Pick Local Images/Extensions/LocalImagesViewController+UploadSwitchDelegate.swift +++ b/piwigo/Upload/Pick Local Images/Extensions/LocalImagesViewController+UploadSwitchDelegate.swift @@ -11,13 +11,14 @@ import UIKit import piwigoKit import uploadKit +// MARK: - UploadSwitchDelegate Methods extension LocalImagesViewController: UploadSwitchDelegate { - // MARK: - UploadSwitchDelegate Methods @objc func didValidateUploadSettings(with imageParameters: [String : Any], _ uploadParameters: [String:Any]) { // Retrieve common image parameters and upload settings - for index in 0.. 0 { - UIApplication.shared.isIdleTimerDisabled = true - } + UIApplication.shared.isIdleTimerDisabled = (uploadRequests.isEmpty == false) // Add selected images to upload queue - let uploads = selectedImages.compactMap({$0}) UploadManager.shared.backgroundQueue.async { - self.uploadProvider.importUploads(from: uploads) { error in + self.uploadProvider.importUploads(from: self.uploadRequests) { error in + // Deselect cells and reset upload queue + DispatchQueue.main.async { + self.cancelSelect() + } + self.uploadRequests = [] + + // Error encountered? guard let error = error else { // Restart UploadManager activities UploadManager.shared.backgroundQueue.async { @@ -107,6 +112,9 @@ extension LocalImagesViewController: UploadSwitchDelegate // Update the navigation bar updateNavBar() + // Display help views only when uploads are launched + if (self.uploads.fetchedObjects ?? []).isEmpty { return } + // Display help views less than once a day let dateOfLastHelpView = AppVars.shared.dateOfLastHelpView let diff = Date().timeIntervalSinceReferenceDate - dateOfLastHelpView diff --git a/piwigo/Upload/Pick Local Images/LocalImageCollectionViewCell.swift b/piwigo/Upload/Pick Local Images/LocalImageCollectionViewCell.swift index 8dd962dc3..ff111b73c 100644 --- a/piwigo/Upload/Pick Local Images/LocalImageCollectionViewCell.swift +++ b/piwigo/Upload/Pick Local Images/LocalImageCollectionViewCell.swift @@ -32,6 +32,7 @@ class LocalImageCollectionViewCell: UICollectionViewCell { backgroundColor = .piwigoColorCellBackground() waitingActivity?.color = UIColor.white uploadingProgress?.trackTintColor = UIColor.white + playImg?.tintColor = UIColor.white } func configure(with imageAsset: PHAsset, thumbnailSize: CGSize) { @@ -73,7 +74,7 @@ class LocalImageCollectionViewCell: UICollectionViewCell { }) } - func configure(with image: UIImage, identifier: String, thumbnailSize: CGFloat) { + func configure(with image: UIImage, identifier: String) { // Configure icons configureIcons() diff --git a/piwigo/Upload/Pick Local Images/LocalImagePreviewViewController.swift b/piwigo/Upload/Pick Local Images/LocalImagePreviewViewController.swift index b9d6608a2..ca16085d4 100644 --- a/piwigo/Upload/Pick Local Images/LocalImagePreviewViewController.swift +++ b/piwigo/Upload/Pick Local Images/LocalImagePreviewViewController.swift @@ -12,10 +12,10 @@ import UIKit class LocalImagePreviewViewController: UIViewController { private var aspectRatio = 1.0 private let imageView = UIImageView() - + init(imageAsset: PHAsset, pixelSize: CGSize) { super.init(nibName: nil, bundle: nil) - + // Retrieve image aspectRatio = Double(imageAsset.pixelHeight) / Double(imageAsset.pixelWidth) let options = PHImageRequestOptions() @@ -36,25 +36,34 @@ class LocalImagePreviewViewController: UIViewController { }) } + init(image: UIImage) { + super.init(nibName: nil, bundle: nil) + self.imageView.image = image + } + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() - + imageView.clipsToBounds = true imageView.contentMode = .scaleAspectFit imageView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(imageView) - + NSLayoutConstraint.activate([ imageView.leftAnchor.constraint(equalTo: view.leftAnchor), imageView.rightAnchor.constraint(equalTo: view.rightAnchor), imageView.topAnchor.constraint(equalTo: view.topAnchor), imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) - + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + let width = view.bounds.width let height = width * aspectRatio preferredContentSize = CGSize(width: width, height: height) diff --git a/piwigo/Upload/Pick Local Images/LocalImagesViewController.swift b/piwigo/Upload/Pick Local Images/LocalImagesViewController.swift index 1403a9665..7a37581b4 100644 --- a/piwigo/Upload/Pick Local Images/LocalImagesViewController.swift +++ b/piwigo/Upload/Pick Local Images/LocalImagesViewController.swift @@ -63,7 +63,7 @@ class LocalImagesViewController: UIViewController }() - // MARK: - Cached Values + // MARK: - Variables and Cached Values let queue = OperationQueue() // Queue used to sort and cache things var fetchedImages: PHFetchResult! // Collection of images in selected non-empty local album var sortType: SectionType = .none // Images grouped by Day, Week, Month or None @@ -72,10 +72,11 @@ class LocalImagesViewController: UIViewController var indexOfImageSortedByDay: [IndexSet] = [] // Indices of images sorted day var indexedUploadsInQueue = [(String,pwgUploadState,Bool)?]() // Arrays of uploads at indices of fetched image - var selectedImages = [UploadProperties?]() // Array of images to upload + var selectedImages = [UploadProperties?]() // Array of images selected for upload var selectedSections = [SelectButtonState]() // State of Select buttons var imagesBeingTouched = [IndexPath]() // Array of indexPaths of touched images - + var uploadRequests = [UploadProperties]() // Array of images to upload + private var uploadsToDelete = [Upload]() lazy var imageCellSize: CGSize = getImageCellSize() @@ -91,9 +92,9 @@ class LocalImagesViewController: UIViewController @IBOutlet weak var segmentedControl: UISegmentedControl! - private var cancelBarButton: UIBarButtonItem! // For cancelling the selection of images + var cancelBarButton: UIBarButtonItem! // For cancelling the selection of images var uploadBarButton: UIBarButtonItem! // for uploading selected images - private var trashBarButton: UIBarButtonItem! // For deleting uploaded images on iPhone until iOS 13 + var trashBarButton: UIBarButtonItem! // For deleting uploaded images on iPhone until iOS 13 // on iPad (all iOS) var actionBarButton: UIBarButtonItem! // iPhone until iOS 13: // - for reversing the sort order @@ -180,7 +181,7 @@ class LocalImagesViewController: UIViewController var children: [UIMenuElement?] = [swapOrderAction(), groupMenu(), selectPhotosMenu(), reUploadAction()] if UIDevice.current.userInterfaceIdiom == .phone { - children.append(deleteAction()) + children.append(deleteMenu()) } let menu = UIMenu(title: "", children: children.compactMap({$0})) actionBarButton = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), menu: menu) @@ -457,7 +458,7 @@ class LocalImagesViewController: UIViewController var children: [UIMenuElement?] = [swapOrderAction(), groupMenu(), selectPhotosMenu(), reUploadAction()] if UIDevice.current.userInterfaceIdiom == .phone { - children.append(deleteAction()) + children.append(deleteMenu()) } let updatedMenu = actionBarButton?.menu?.replacingChildren(children.compactMap({$0})) actionBarButton?.menu = updatedMenu @@ -671,10 +672,10 @@ class LocalImagesViewController: UIViewController } - // MARK: - Re-upload & Delete Camera Roll Images + // MARK: - Re-Upload Photos @available(iOS 14, *) private func reUploadAction() -> UIAction? { - // Check if there are uploaded photos + // Check if there are already uploaded photos if !canDeleteUploadedImages() { return nil } // Propose option for re-uploading photos @@ -736,28 +737,12 @@ class LocalImagesViewController: UIViewController } self.updateNavBar() } - - private func canDeleteUploadedImages() -> Bool { - // Don't provide access to the Trash button until the preparation work is not done - if queue.operationCount > 0 { return false } - // Check if there are uploaded photos to delete - let indexedUploads = self.indexedUploadsInQueue.compactMap({$0}) - let completed = (uploads.fetchedObjects ?? []).filter({[.finished, .moderated].contains($0.state)}) - for index in 0.. UIAction? { - // Check if there are uploaded photos + private func deleteMenu() -> UIMenu? { + // Check if there are already uploaded photos that can be deleted if canDeleteUploadedImages() == false, canDeleteSelectedImages() == false { return nil } @@ -767,8 +752,8 @@ class LocalImagesViewController: UIViewController // Delete uploaded photos from the camera roll self.deleteUploadedImages() }) - delete.accessibilityIdentifier = "org.piwigo.removeFromCameraRoll" - return delete + let menuId = UIMenu.Identifier("org.piwigo.removeFromCameraRoll") + return UIMenu(identifier: menuId, options: UIMenu.Options.displayInline, children: [delete]) } @objc func deleteUploadedImages() { @@ -817,6 +802,22 @@ class LocalImagesViewController: UIViewController } } + private func canDeleteUploadedImages() -> Bool { + // Don't provide access to the Trash button until the preparation work is not done + if queue.operationCount > 0 { return false } + + // Check if there are uploaded photos to delete + let indexedUploads = self.indexedUploadsInQueue.compactMap({$0}) + let completed = (uploads.fetchedObjects ?? []).filter({[.finished, .moderated].contains($0.state)}) + for index in 0.. Bool { var hasImagesToDelete = false let imageIDs = selectedImages.compactMap({ $0?.localIdentifier }) @@ -834,7 +835,8 @@ class LocalImagesViewController: UIViewController // MARK: - Show Upload Options @objc func didTapUploadButton() { // Avoid potential crash (should never happen, but…) - if selectedImages.compactMap({ $0 }).isEmpty { return } + uploadRequests = selectedImages.compactMap({ $0 }) + if uploadRequests.isEmpty { return } // Disable buttons cancelBarButton?.isEnabled = false @@ -849,8 +851,8 @@ class LocalImagesViewController: UIViewController uploadSwitchVC.user = user // Will we propose to delete images after upload? - if let firstLocalIdentifer = selectedImages.compactMap({ $0 }).first?.localIdentifier { - if let imageAsset = PHAsset.fetchAssets(withLocalIdentifiers: [firstLocalIdentifer], options: nil).firstObject { + if let firstLocalID = uploadRequests.first?.localIdentifier { + if let imageAsset = PHAsset.fetchAssets(withLocalIdentifiers: [firstLocalID], options: nil).firstObject { // Only local images can be deleted if imageAsset.sourceType != .typeCloudShared { // Will allow user to delete images after upload diff --git a/piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+Data.swift b/piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+Data.swift index b8c6d20ff..a2e9fa6a5 100644 --- a/piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+Data.swift +++ b/piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+Data.swift @@ -75,7 +75,7 @@ extension PasteboardImagesViewController { } // Create an instance of the preparation method - let preparer = ObjectPreparation(pbObject, at: indexPath.row) + let preparer = ObjectPreparation(pbObject, at: indexPath.item) // Refresh the thumbnail of the cell and update upload cache preparer.completionBlock = { @@ -85,50 +85,50 @@ extension PasteboardImagesViewController { // Operation completed self.pendingOperations.preparationsInProgress.removeValue(forKey: indexPath) - // Update upload cache - if let upload = (self.uploads.fetchedObjects ?? []).first(where: {$0.md5Sum == pbObject.md5Sum}) { - self.indexedUploadsInQueue[indexPath.row] = (upload.localIdentifier, upload.md5Sum, upload.state) - } - // Update cell image if operation was successful - switch (pbObject.state) { - case .stored: + if pbObject.state == .stored { + // Set upload cache + if let upload = (self.uploads.fetchedObjects ?? []).first(where: {$0.md5Sum == pbObject.md5Sum}) { + self.indexedUploadsInQueue[indexPath.item] = (upload.localIdentifier, upload.md5Sum, upload.state) + } // Refresh the thumbnail of the cell DispatchQueue.main.async { if let cell = self.localImagesCollection.cellForItem(at: indexPath) as? LocalImageCollectionViewCell { + let uploadState = self.getUploadStateOfImage(at: indexPath.item, for: cell) + cell.update(selected: self.selectedImages[indexPath.item] != nil, state: uploadState) cell.cellImage.image = pbObject.image self.reloadInputViews() } } - case .failed: - if self.pendingOperations.preparationsInProgress.isEmpty { - var newSetOfObjects = [PasteboardObject]() - for index in 0.., didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) { @@ -192,6 +194,8 @@ extension PasteboardImagesViewController: NSFetchedResultsControllerDelegate // Deselect image selectedImages[index] = nil } + // Remove image from upload requests if needed + uploadRequests.removeAll(where: {$0.localIdentifier == upload.localIdentifier}) // Update corresponding cell updateCellAndSectionHeader(for: upload) case .move: @@ -216,7 +220,9 @@ extension PasteboardImagesViewController: NSFetchedResultsControllerDelegate func controllerDidChangeContent(_ controller: NSFetchedResultsController) { // print("••• PasteboardImagesViewController controller:didChangeContent...") // Update navigation bar - updateNavBar() + DispatchQueue.main.async { + self.updateNavBar() + } } func updateCellAndSectionHeader(for upload: Upload) { @@ -230,7 +236,7 @@ extension PasteboardImagesViewController: NSFetchedResultsControllerDelegate // The section will be refreshed only if the button content needs to be changed self.updateSelectButton() if let header = self.localImagesCollection.supplementaryView(forElementKind: UICollectionView.elementKindSectionHeader, at: IndexPath(item: 0, section: 0)) as? PasteboardImagesHeaderReusableView { - header.setButtonTitle(forState: self.sectionState) + header.selectButton.setTitle(forState: self.sectionState) } } } diff --git a/piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+UICollectionViewDataSource.swift b/piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+DataSource.swift similarity index 88% rename from piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+UICollectionViewDataSource.swift rename to piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+DataSource.swift index 0bd52b46d..ecb81b213 100644 --- a/piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+UICollectionViewDataSource.swift +++ b/piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+DataSource.swift @@ -1,5 +1,5 @@ // -// PasteboardImagesViewController+UICollectionViewDataSource.swift +// PasteboardImagesViewController+DataSource.swift // piwigo // // Created by Eddy Lelièvre-Berna on 16/03/2024. @@ -13,6 +13,7 @@ import UIKit import UniformTypeIdentifiers // Requires iOS 14 #endif +// MARK: UICollectionViewDataSource Methods extension PasteboardImagesViewController: UICollectionViewDataSource { // MARK: - Headers & Footers @@ -69,30 +70,10 @@ extension PasteboardImagesViewController: UICollectionViewDataSource // (the content of the pasteboard may not last forever) let identifier = pbObjects[indexPath.item].identifier - // Get thumbnail of image if available - var image: UIImage! = imagePlaceholder - if [.stored, .ready].contains(pbObjects[indexPath.row].state) { - image = pbObjects[indexPath.row].image - cell.md5sum = pbObjects[indexPath.row].md5Sum - } - else { - var imageType = "" - if #available(iOS 14.0, *) { - imageType = UTType.image.identifier - } else { - // Fallback on earlier version - imageType = kUTTypeImage as String - } - if let data = UIPasteboard.general.data(forPasteboardType: imageType, - inItemSet: IndexSet(integer: indexPath.row))?.first { - image = UIImage(data: data) ?? imagePlaceholder - cell.md5sum = "" - } - } - // Configure cell - let thumbnailSize = AlbumUtilities.imageSize(forView: self.localImagesCollection, imagesPerRowInPortrait: AlbumVars.shared.thumbnailsPerRowInPortrait, collectionType: .popup) - cell.configure(with: image, identifier: identifier, thumbnailSize: CGFloat(thumbnailSize)) + let (image, md5sum) = getImageAndMd5sumOfPbObject(atIndex: indexPath.item) + cell.configure(with: image, identifier: identifier) + cell.md5sum = md5sum // Add pan gesture recognition let imageSeriesRocognizer = UIPanGestureRecognizer(target: self, action: #selector(touchedImages(_:))) @@ -110,6 +91,30 @@ extension PasteboardImagesViewController: UICollectionViewDataSource return cell } + func getImageAndMd5sumOfPbObject(atIndex index: Int) -> (UIImage, String) { + var image: UIImage! = imagePlaceholder + var md5sum = "" + if [.stored, .ready].contains(pbObjects[index].state) { + image = pbObjects[index].image + md5sum = pbObjects[index].md5Sum + } + else { + var imageType = "" + if #available(iOS 14.0, *) { + imageType = UTType.image.identifier + } else { + // Fallback on earlier version + imageType = kUTTypeImage as String + } + if let data = UIPasteboard.general.data(forPasteboardType: imageType, + inItemSet: IndexSet(integer: index))?.first { + image = UIImage(data: data) ?? imagePlaceholder + md5sum = data.MD5checksum() + } + } + return (image, md5sum) + } + @objc func applyUploadProgress(_ notification: Notification) { if let visibleCells = localImagesCollection.visibleCells as? [LocalImageCollectionViewCell], let localIdentifier = notification.userInfo?["localIdentifier"] as? String, !localIdentifier.isEmpty, diff --git a/piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+Delegate.swift b/piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+Delegate.swift new file mode 100644 index 000000000..35df0a7dc --- /dev/null +++ b/piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+Delegate.swift @@ -0,0 +1,251 @@ +// +// PasteboardImagesViewController+Delegate.swift +// piwigo +// +// Created by Eddy Lelièvre-Berna on 16/03/2024. +// Copyright © 2024 Piwigo.org. All rights reserved. +// + +import UIKit +import piwigoKit + +// MARK: UICollectionViewDelegate Methods +extension PasteboardImagesViewController: UICollectionViewDelegate +{ + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + guard let cell = collectionView.cellForItem(at: indexPath) as? LocalImageCollectionViewCell else { + return + } + + // Get upload state of image + let uploadState = getUploadStateOfImage(at: indexPath.item, for: cell) + + // Update cell and selection + if let _ = selectedImages[indexPath.item] { + // Deselect the cell + selectedImages[indexPath.item] = nil + cell.update(selected: false, state: uploadState) + } else { + // Can we upload or re-upload this image? + if (uploadState == nil) || reUploadAllowed { + // Select the image + selectedImages[indexPath.item] = UploadProperties(localIdentifier: cell.localIdentifier, + category: categoryId) + cell.update(selected: true, state: uploadState) + } + } + + // Update navigation bar + updateNavBar() + + // Refresh cell + cell.reloadInputViews() + + // Update state of Select button if needed + updateSelectButton() + if let header = self.localImagesCollection.supplementaryView(forElementKind: UICollectionView.elementKindSectionHeader, at: IndexPath(item: 0, section: 0)) as? PasteboardImagesHeaderReusableView { + header.selectButton.setTitle(forState: sectionState) + } + } + + + // MARK: - Context Menus + @available(iOS, introduced: 13.0, deprecated: 16.0, message: "") + func collectionView(_ collectionView: UICollectionView, + contextMenuConfigurationForItemAt indexPath: IndexPath, + point: CGPoint) -> UIContextMenuConfiguration? { + if let cell = collectionView.cellForItem(at: indexPath) as? LocalImageCollectionViewCell { + // Get image identifier and corresponding upload request if it exists + let identifier = NSString(string: "\(cell.localIdentifier)") + let upload = (self.uploads.fetchedObjects ?? []).filter({$0.md5Sum == cell.md5sum}) + + // Get upload state + let uploadState = self.getUploadStateOfImage(at: indexPath.item, for: cell) + + // Return preview and appropriate menu + return UIContextMenuConfiguration(identifier: identifier, + previewProvider: { [self] in + // Create preview view controller + let (image, _) = self.getImageAndMd5sumOfPbObject(atIndex: indexPath.item) + return LocalImagePreviewViewController(image: image) + }, actionProvider: { suggestedActions in + var children = [UIMenuElement]() + if upload.isEmpty { + if self.selectedImages[indexPath.item] != nil { + // Image selected ► Propose to deselect it + children.append(self.deselectAction(forCell: cell, at: indexPath, + inUploadSate: uploadState)) + } else if (uploadState == nil) || self.reUploadAllowed { + // Image deselected ► Propose to select it + children.append(self.selectAction(forCell: cell, at: indexPath, + inUploadSate: uploadState)) + } + children.append(self.uploaAction(forCell: cell, at: indexPath)) + } else { + children.append(self.statusAction(upload.first)) + if self.reUploadAllowed { + children.append(self.uploaAction(forCell: cell, at: indexPath)) + } + } + return UIMenu(title: "", children: children) + }) + } + return nil + } + + @available(iOS 16.0, *) + func collectionView(_ collectionView: UICollectionView, + contextMenuConfigurationForItemsAt indexPaths: [IndexPath], + point: CGPoint) -> UIContextMenuConfiguration? { + if indexPaths.count == 1, let indexPath = indexPaths.first, + let cell = collectionView.cellForItem(at: indexPath) as? LocalImageCollectionViewCell { + // Get image identifier and corresponding upload request if it exists + let identifier = NSString(string: "\(cell.localIdentifier)") + let upload = (self.uploads.fetchedObjects ?? []).filter({$0.md5Sum == cell.md5sum}) + + // Get upload state + let uploadState = self.getUploadStateOfImage(at: indexPath.item, for: cell) + + // Return preview and appropriate menu + return UIContextMenuConfiguration(identifier: identifier, + previewProvider: { [self] in + // Create preview view controller + let (image, _) = self.getImageAndMd5sumOfPbObject(atIndex: indexPath.item) + return LocalImagePreviewViewController(image: image) + }, actionProvider: { suggestedActions in + var children = [UIMenuElement]() + if upload.isEmpty { + if self.selectedImages[indexPath.item] != nil { + // Image selected ► Propose to deselect it + children.append(self.deselectAction(forCell: cell, at: indexPath, + inUploadSate: uploadState)) + } else if (uploadState == nil) || self.reUploadAllowed { + // Image deselected ► Propose to select it + children.append(self.selectAction(forCell: cell, at: indexPath, + inUploadSate: uploadState)) + } + children.append(self.uploaAction(forCell: cell, at: indexPath)) + } else { + children.append(self.statusAction(upload.first)) + if self.reUploadAllowed { + children.append(self.uploaAction(forCell: cell, at: indexPath)) + } + } + return UIMenu(title: "", children: children) + }) + } + return nil + } + + @available(iOS 13.0, *) + private func statusAction(_ upload: Upload?) -> UIAction { + // Check if an upload request exists (should never happen) + guard let upload = upload else { + return UIAction(title: NSLocalizedString("errorHUD_label", comment: "Error"), + image: UIImage(systemName: "exclamationmark.triangle"), handler: { _ in }) + } + + // Show upload status + switch upload.state { + case .waiting, .preparing, .prepared, .uploading, .uploaded, .finishing: + return UIAction(title: upload.stateLabel, + image: UIImage(systemName: "timer"), handler: { _ in }) + case .preparingError, .preparingFail, .formatError, + .uploadingError, .uploadingFail, .finishingError, .finishingFail: + return UIAction(title: upload.stateLabel, + image: UIImage(systemName: "exclamationmark.triangle"), handler: { _ in }) + case .finished, .moderated: + return UIAction(title: NSLocalizedString("imageUploadCompleted_title", comment: "Upload Completed"), + image: UIImage(systemName: "checkmark"), handler: { _ in }) + } + } + + @available(iOS 13.0, *) + private func selectAction(forCell cell: LocalImageCollectionViewCell, at indexPath: IndexPath, + inUploadSate uploadState: pwgUploadState?) -> UIAction + { + // Image not selected and selectable ► Propose to select it + return UIAction(title: NSLocalizedString("categoryImageList_selectButton", comment: "Select"), + image: UIImage(systemName: "checkmark.circle")) { _ in + // Select the cell + self.selectedImages[indexPath.item] = UploadProperties(localIdentifier: cell.localIdentifier, + category: self.categoryId) + cell.update(selected: true, state: uploadState) + + // Update number of selected cells + self.updateNavBar() + + // Update state of Select button if needed + self.updateSelectButton() + let indexPath = IndexPath(item: 0, section: indexPath.section) + if let header = self.localImagesCollection.supplementaryView(forElementKind: UICollectionView.elementKindSectionHeader, at: indexPath) as? PasteboardImagesHeaderReusableView { + header.configure(with: self.sectionState) + } + } + } + + @available(iOS 13.0, *) + private func deselectAction(forCell cell: LocalImageCollectionViewCell, at indexPath: IndexPath, + inUploadSate uploadState: pwgUploadState?) -> UIAction + { + var image: UIImage? + if #available(iOS 16, *) { + image = UIImage(systemName: "checkmark.circle.badge.xmark") + } else { + image = UIImage(systemName: "checkmark.circle") + } + return UIAction(title: NSLocalizedString("categoryImageList_deselectButton", comment: "Deselect"), + image: image) { _ in + // Deselect the cell + self.selectedImages[indexPath.item] = nil + cell.update(selected: false, state: uploadState) + + // Update number of selected cells + self.updateNavBar() + + // Update state of Select button if needed + self.updateSelectButton() + let indexPath = IndexPath(item: 0, section: indexPath.section) + if let header = self.localImagesCollection.supplementaryView(forElementKind: UICollectionView.elementKindSectionHeader, at: indexPath) as? PasteboardImagesHeaderReusableView { + header.configure(with: self.sectionState) + } + } + } + + @available(iOS 13.0, *) + private func uploaAction(forCell cell: LocalImageCollectionViewCell, at indexPath: IndexPath) -> UIAction { + return UIAction(title: NSLocalizedString("tabBar_upload", comment: "Upload"), + image: UIImage(named: "imageUpload")) { action in + // Check that an upload request does not exist for that image (should never happen) + if (self.uploads.fetchedObjects ?? []).filter({$0.md5Sum == cell.md5sum}).first != nil { + return + } + + // Create an upload request for that image and add it to the upload queue + let upload = UploadProperties(localIdentifier: cell.localIdentifier, category: self.categoryId) + self.uploadRequests.append(upload) + + // Disable buttons + self.cancelBarButton?.isEnabled = false + self.uploadBarButton?.isEnabled = false + self.actionBarButton?.isEnabled = false + + // Show upload parameter views + let uploadSwitchSB = UIStoryboard(name: "UploadSwitchViewController", bundle: nil) + if let uploadSwitchVC = uploadSwitchSB.instantiateViewController(withIdentifier: "UploadSwitchViewController") as? UploadSwitchViewController { + uploadSwitchVC.delegate = self + uploadSwitchVC.user = self.user + uploadSwitchVC.canDeleteImages = false + + // Push Edit view embedded in navigation controller + let navController = UINavigationController(rootViewController: uploadSwitchVC) + navController.modalPresentationStyle = .popover + navController.modalTransitionStyle = .coverVertical + navController.popoverPresentationController?.sourceView = self.localImagesCollection + navController.popoverPresentationController?.barButtonItem = self.uploadBarButton + navController.popoverPresentationController?.permittedArrowDirections = .up + self.navigationController?.present(navController, animated: true) + } + } + } +} diff --git a/piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+FlowLayout.swift b/piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+FlowLayout.swift new file mode 100644 index 000000000..d36b6bf9f --- /dev/null +++ b/piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+FlowLayout.swift @@ -0,0 +1,43 @@ +// +// PasteboardImagesViewController+FlowLayout.swift +// piwigo +// +// Created by Eddy Lelièvre-Berna on 05/08/2024. +// Copyright © 2024 Piwigo.org. All rights reserved. +// + +import Foundation +import UIKit + +// MARK: UICollectionViewDelegateFlowLayout Methods +extension PasteboardImagesViewController: UICollectionViewDelegateFlowLayout +{ + func getImageCellSize() -> CGSize { + let nbImages = AlbumVars.shared.thumbnailsPerRowInPortrait // from Settings + let size = AlbumUtilities.imageSize(forView: localImagesCollection, imagesPerRowInPortrait: nbImages, collectionType: .popup) +// debugPrint("••> getImageCellSize: \(size) x \(size) points") + return CGSize(width: size, height: size) + } + + func collectionView(_ collectionView: UICollectionView, willDisplaySupplementaryView view: UICollectionReusableView, forElementKind elementKind: String, at indexPath: IndexPath) { + if (elementKind == UICollectionView.elementKindSectionHeader) || (elementKind == UICollectionView.elementKindSectionFooter) { + view.layer.zPosition = 0 // Below scroll indicator + } + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + return imageCellSize + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { + return UIEdgeInsets(top: 10, left: 0, bottom: 10, right: 0) + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + return CGFloat(AlbumUtilities.imageCellVerticalSpacing(forCollectionType: .popup)) + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + return CGFloat(AlbumUtilities.imageCellHorizontalSpacing(forCollectionType: .popup)) + } +} diff --git a/piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+HeaderReusableViewDelegate.swift b/piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+HeaderDelegate.swift similarity index 92% rename from piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+HeaderReusableViewDelegate.swift rename to piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+HeaderDelegate.swift index cd3f2c514..096409d60 100644 --- a/piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+HeaderReusableViewDelegate.swift +++ b/piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+HeaderDelegate.swift @@ -1,5 +1,5 @@ // -// PasteboardImagesViewController+HeaderReusableViewDelegate.swift +// PasteboardImagesViewController+HeaderDelegate.swift // piwigo // // Created by Eddy Lelièvre-Berna on 16/03/2024. @@ -9,9 +9,9 @@ import UIKit import piwigoKit +// MARK: - PasteboardImagesHeaderDelegate Methods extension PasteboardImagesViewController: PasteboardImagesHeaderDelegate { - // MARK: - PasteboardImagesHeaderReusableView Delegate Methods func didSelectImagesOfSection() { let nberOfImagesInSection = localImagesCollection.numberOfItems(inSection: 0) if sectionState == .select { @@ -50,7 +50,7 @@ extension PasteboardImagesViewController: PasteboardImagesHeaderDelegate let headers = localImagesCollection.visibleSupplementaryViews(ofKind: UICollectionView.elementKindSectionHeader) headers.forEach { header in if let header = header as? PasteboardImagesHeaderReusableView { - header.setButtonTitle(forState: sectionState) + header.selectButton.setTitle(forState: sectionState) } } } diff --git a/piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+Select.swift b/piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+Select.swift new file mode 100644 index 000000000..d3e751a6a --- /dev/null +++ b/piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+Select.swift @@ -0,0 +1,153 @@ +// +// PasteboardImagesViewController+Select.swift +// piwigo +// +// Created by Eddy Lelièvre-Berna on 05/08/2024. +// Copyright © 2024 Piwigo.org. All rights reserved. +// + +import Foundation +import UIKit +import piwigoKit + +// MARK: - Select Images +extension PasteboardImagesViewController +{ + @objc func cancelSelect() { + // Clear list of selected images + selectedImages = .init(repeating: nil, count: pbObjects.count) + + // Update navigation bar + updateNavBar() + + // Deselect visible cells + localImagesCollection.visibleCells.forEach { cell in + if let cell = cell as? LocalImageCollectionViewCell { + cell.update(selected: false) + } + } + + // Update button + let headers = localImagesCollection.visibleSupplementaryViews(ofKind: UICollectionView.elementKindSectionHeader) + headers.forEach { header in + if let header = header as? PasteboardImagesHeaderReusableView { + header.selectButton.setTitle(forState: .select) + } + } + } +} + + +// MARK: - UIGestureRecognizerDelegate Methods +extension PasteboardImagesViewController: UIGestureRecognizerDelegate +{ + func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + // Will interpret touches only in horizontal direction + if (gestureRecognizer is UIPanGestureRecognizer) { + let gPR = gestureRecognizer as? UIPanGestureRecognizer + let translation = gPR?.translation(in: localImagesCollection) + if abs(translation?.x ?? 0.0) > abs(translation?.y ?? 0.0) { + return true + } + } + return false + } + + @objc func touchedImages(_ gestureRecognizer: UIPanGestureRecognizer?) { + // To prevent a crash + if gestureRecognizer?.view == nil { + return + } + + // Point and direction + let point = gestureRecognizer?.location(in: localImagesCollection) + + // Get index path at touch position + guard let indexPath = localImagesCollection.indexPathForItem(at: point ?? CGPoint.zero) else { + return + } + + // Select/deselect the cell or scroll the view + if (gestureRecognizer?.state == .began) || (gestureRecognizer?.state == .changed) { + + // Get cell at touch position + guard let cell = localImagesCollection.cellForItem(at: indexPath) as? LocalImageCollectionViewCell else { + return + } + + // Update the selection if not already done + if !imagesBeingTouched.contains(indexPath) { + + // Store that the user touched this cell during this gesture + imagesBeingTouched.append(indexPath) + + // Get upload state of image + let uploadState = getUploadStateOfImage(at: indexPath.item, for: cell) + + // Update the selection state + if let _ = selectedImages[indexPath.item] { + selectedImages[indexPath.item] = nil + cell.update(selected: false, state: uploadState) + } else { + // Can we upload or re-upload this image? + if (uploadState == nil) || reUploadAllowed { + // Select the cell + selectedImages[indexPath.item] = UploadProperties(localIdentifier: cell.localIdentifier, + category: categoryId) + cell.update(selected: true, state: uploadState) + } + } + + // Update navigation bar + updateNavBar() + + // Refresh cell + cell.reloadInputViews() + } + } + + // Is this the end of the gesture? + if gestureRecognizer?.state == .ended { + // Clear list of touched images + imagesBeingTouched = [] + } + } + + func updateSelectButton() + { + // Number of images in section + let nberOfImages = localImagesCollection.numberOfItems(inSection: 0) + + // Job done if there is no image presented + if nberOfImages == 0 { + sectionState = .none + return + } + + // Can we calculate the number of images already in the upload queue? + if pendingOperations.preparationsInProgress.isEmpty == false { + // Keep Select button disabled + sectionState = .none + return + } + + // Number of images already in the upload queue + var nberOfImagesInUploadQueue = 0 + if reUploadAllowed == false { + nberOfImagesInUploadQueue = indexedUploadsInQueue.compactMap({ $0 }).count + } + + // Update state of Select button only if needed + let nberOfSelectedImages = selectedImages[0.. UIEdgeInsets { - return UIEdgeInsets(top: 10, left: 0, bottom: 10, right: 0) - } - - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { - return CGFloat(AlbumUtilities.imageCellVerticalSpacing(forCollectionType: .popup)) - } - - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { - return CGFloat(AlbumUtilities.imageCellHorizontalSpacing(forCollectionType: .popup)) - } - - - // MARK: - Items i.e. Images - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - // Calculate the optimum image size - let size = CGFloat(AlbumUtilities.imageSize(forView: collectionView, imagesPerRowInPortrait: AlbumVars.shared.thumbnailsPerRowInPortrait, collectionType: .popup)) - - return CGSize(width: size, height: size) - } - - func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - guard let cell = collectionView.cellForItem(at: indexPath) as? LocalImageCollectionViewCell else { - return - } - - // Get upload state of image - let uploadState = getUploadStateOfImage(at: indexPath.item, for: cell) - - // Update cell and selection - if let _ = selectedImages[indexPath.item] { - // Deselect the cell - selectedImages[indexPath.item] = nil - cell.update(selected: false, state: uploadState) - } else { - // Can we upload or re-upload this image? - if (uploadState == nil) || reUploadAllowed { - // Select the image - selectedImages[indexPath.item] = UploadProperties(localIdentifier: cell.localIdentifier, - category: categoryId) - cell.update(selected: true, state: uploadState) - } - } - - // Update navigation bar - updateNavBar() - - // Refresh cell - cell.reloadInputViews() - - // Update state of Select button if needed - updateSelectButton() - if let header = self.localImagesCollection.supplementaryView(forElementKind: UICollectionView.elementKindSectionHeader, at: IndexPath(item: 0, section: 0)) as? PasteboardImagesHeaderReusableView { - header.setButtonTitle(forState: sectionState) - } - } -} diff --git a/piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+UploadSwitchDelegate.swift b/piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+UploadSwitchDelegate.swift index 6af9844a5..114365573 100644 --- a/piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+UploadSwitchDelegate.swift +++ b/piwigo/Upload/Pick Pasteboard Images/Extensions/PasteboardImagesViewController+UploadSwitchDelegate.swift @@ -10,14 +10,15 @@ import Foundation import piwigoKit import uploadKit +// MARK: - UploadSwitchDelegate Methods extension PasteboardImagesViewController: UploadSwitchDelegate { - // MARK: - UploadSwitchDelegate Methods @objc func didValidateUploadSettings(with imageParameters: [String : Any], _ uploadParameters: [String:Any]) { // Retrieve common image parameters and upload settings - for index in 0.. Bool { - // Don't provide access to the Trash button until the preparation work is not done + // Don't provide access to the re-upload button until the preparation work is not done if !pendingOperations.preparationsInProgress.isEmpty { return false } - // Check if there are uploaded photos to delete + // Check if there are already uploaded photos let indexedUploads = self.indexedUploadsInQueue.compactMap({$0}) let completed = (uploads.fetchedObjects ?? []).filter({[.finished, .moderated].contains($0.state)}) for index in 0.. Bool { - // Will interpret touches only in horizontal direction - if (gestureRecognizer is UIPanGestureRecognizer) { - let gPR = gestureRecognizer as? UIPanGestureRecognizer - let translation = gPR?.translation(in: localImagesCollection) - if abs(translation?.x ?? 0.0) > abs(translation?.y ?? 0.0) { - return true - } - } - return false - } - - @objc func touchedImages(_ gestureRecognizer: UIPanGestureRecognizer?) { - // To prevent a crash - if gestureRecognizer?.view == nil { - return - } - - // Point and direction - let point = gestureRecognizer?.location(in: localImagesCollection) - - // Get index path at touch position - guard let indexPath = localImagesCollection.indexPathForItem(at: point ?? CGPoint.zero) else { - return - } - - // Select/deselect the cell or scroll the view - if (gestureRecognizer?.state == .began) || (gestureRecognizer?.state == .changed) { - - // Get cell at touch position - guard let cell = localImagesCollection.cellForItem(at: indexPath) as? LocalImageCollectionViewCell else { - return - } - - // Update the selection if not already done - if !imagesBeingTouched.contains(indexPath) { - - // Store that the user touched this cell during this gesture - imagesBeingTouched.append(indexPath) - - // Get upload state of image - let uploadState = getUploadStateOfImage(at: indexPath.item, for: cell) - - // Update the selection state - if let _ = selectedImages[indexPath.item] { - selectedImages[indexPath.item] = nil - cell.update(selected: false, state: uploadState) - } else { - // Can we upload or re-upload this image? - if (uploadState == nil) || reUploadAllowed { - // Select the cell - selectedImages[indexPath.item] = UploadProperties(localIdentifier: cell.localIdentifier, - category: categoryId) - cell.update(selected: true, state: uploadState) - } - } - - // Update navigation bar - updateNavBar() - - // Refresh cell - cell.reloadInputViews() - } - } - - // Is this the end of the gesture? - if gestureRecognizer?.state == .ended { - // Clear list of touched images - imagesBeingTouched = [] - } - } - - func updateSelectButton() { - - // Number of images in section - let nberOfImagesInSection = localImagesCollection.numberOfItems(inSection: 0) - - // Job done if there is no image presented - if nberOfImagesInSection == 0 { - sectionState = .none - return - } - - // Number of selected images - let nberOfSelectedImagesInSection = selectedImages[0.. UIImage! { var image: UIImage! = UIImage(named: "placeholder")! diff --git a/piwigo/Upload/Pick Photos Album/LocalAlbumsViewController.swift b/piwigo/Upload/Pick Photos Album/LocalAlbumsViewController.swift index 576bf8715..6d15d852e 100644 --- a/piwigo/Upload/Pick Photos Album/LocalAlbumsViewController.swift +++ b/piwigo/Upload/Pick Photos Album/LocalAlbumsViewController.swift @@ -183,14 +183,11 @@ class LocalAlbumsViewController: UIViewController, UITableViewDelegate, UITableV navigationController?.navigationBar.accessibilityIdentifier = "LocalAlbumsNav" // Check if there are photos/videos in the pasteboard - if let indexSet = UIPasteboard.general.itemSet(withPasteboardTypes: pasteboardTypes), - indexSet.count > 0, let _ = UIPasteboard.general.types(forItemSet: indexSet) { - hasImagesInPasteboard = true - } else { - hasImagesInPasteboard = false - } + let testTypes = UIPasteboard.general.contains(pasteboardTypes: pasteboardTypes) ? true : false + let nberPhotos = UIPasteboard.general.itemSet(withPasteboardTypes: pasteboardTypes)?.count ?? 0 + hasImagesInPasteboard = testTypes && (nberPhotos > 0) } - + // Set colors, fonts, etc. applyColorPalette() @@ -569,10 +566,10 @@ class LocalAlbumsViewController: UIViewController, UITableViewDelegate, UITableV switch albumType { case .pasteboard: let pasteboardImagesSB = UIStoryboard(name: "PasteboardImagesViewController", bundle: nil) - guard let localImagesVC = pasteboardImagesSB.instantiateViewController(withIdentifier: "PasteboardImagesViewController") as? PasteboardImagesViewController else { return } - localImagesVC.categoryId = categoryId - localImagesVC.user = user - navigationController?.pushViewController(localImagesVC, animated: true) + guard let pasteboardImagesVC = pasteboardImagesSB.instantiateViewController(withIdentifier: "PasteboardImagesViewController") as? PasteboardImagesViewController else { return } + pasteboardImagesVC.categoryId = categoryId + pasteboardImagesVC.user = user + navigationController?.pushViewController(pasteboardImagesVC, animated: true) return case .localAlbums: assetCollections = LocalAlbumsProvider.shared.localAlbums diff --git a/piwigo/Upload/Upload Options/UploadSwitchViewController.swift b/piwigo/Upload/Upload Options/UploadSwitchViewController.swift index 018ac70b4..36bf87541 100644 --- a/piwigo/Upload/Upload Options/UploadSwitchViewController.swift +++ b/piwigo/Upload/Upload Options/UploadSwitchViewController.swift @@ -171,7 +171,6 @@ class UploadSwitchViewController: UIViewController { @objc func cancelUpload() { // Return to local images view - delegate?.uploadSettingsDidDisappear() dismiss(animated: true) } diff --git a/piwigoKit/Data Cache/AttributeTransformers/DescriptionValueTransformer.swift b/piwigoKit/Data Cache/AttributeTransformers/DescriptionValueTransformer.swift index 5d8e97859..515812022 100644 --- a/piwigoKit/Data Cache/AttributeTransformers/DescriptionValueTransformer.swift +++ b/piwigoKit/Data Cache/AttributeTransformers/DescriptionValueTransformer.swift @@ -8,7 +8,6 @@ import Foundation -@objc(DescriptionValueTransformer) public class DescriptionValueTransformer: NSSecureUnarchiveFromDataTransformer { public override class func allowsReverseTransformation() -> Bool { diff --git a/piwigoKit/Data Cache/AttributeTransformers/RelativeURLValueTransformer.swift b/piwigoKit/Data Cache/AttributeTransformers/RelativeURLValueTransformer.swift index 05f442b1d..bbdb93a51 100644 --- a/piwigoKit/Data Cache/AttributeTransformers/RelativeURLValueTransformer.swift +++ b/piwigoKit/Data Cache/AttributeTransformers/RelativeURLValueTransformer.swift @@ -8,7 +8,6 @@ import Foundation -@objc(RelativeURLValueTransformer) public class RelativeURLValueTransformer: NSSecureUnarchiveFromDataTransformer { public override class func allowsReverseTransformation() -> Bool { diff --git a/piwigoKit/Data Cache/AttributeTransformers/ResolutionValueTransformer.swift b/piwigoKit/Data Cache/AttributeTransformers/ResolutionValueTransformer.swift index b16978c29..570fa7ac5 100644 --- a/piwigoKit/Data Cache/AttributeTransformers/ResolutionValueTransformer.swift +++ b/piwigoKit/Data Cache/AttributeTransformers/ResolutionValueTransformer.swift @@ -9,7 +9,6 @@ import Foundation import UIKit -@objc(ResolutionValueTransformer) public class ResolutionValueTransformer: NSSecureUnarchiveFromDataTransformer { public override class func allowsReverseTransformation() -> Bool { diff --git a/piwigoKit/Data Cache/Image Data/ImageProvider.swift b/piwigoKit/Data Cache/Image Data/ImageProvider.swift index cbbeb4724..467a3011d 100644 --- a/piwigoKit/Data Cache/Image Data/ImageProvider.swift +++ b/piwigoKit/Data Cache/Image Data/ImageProvider.swift @@ -208,7 +208,8 @@ public class ImageProvider: NSObject { */ public func didUploadImage(_ imageData: ImagesGetInfo, asVideo: Bool, inAlbumId albumId: Int32) { // Import the image data into Core Data. - try? self.importImages([imageData], inAlbum: albumId, withAlbumUpdate: true) + // The provided sort option will not change the rankManual/rankRandom values of Int64.min + try? self.importImages([imageData], inAlbum: albumId, withAlbumUpdate: true, sort: .albumDefault) } /** @@ -239,8 +240,9 @@ public class ImageProvider: NSObject { return } - // Import the imageJSON into Core Data. - try self.importImages([imageJSON.data], inAlbum: albumId) + // Import the imageJSON into Core Data + // The provided sort option will not change the rankManual/rankRandom values. + try self.importImages([imageJSON.data], inAlbum: albumId, sort: .albumDefault) completion() } @@ -265,12 +267,11 @@ public class ImageProvider: NSObject { private let batchSize = 25 private func importImages(_ imageArray: [ImagesGetInfo], inAlbum albumId: Int32, withAlbumUpdate: Bool = false, - sort: pwgImageSort = .dateCreatedDescending, - fromRank rank: Int64 = Int64.min) throws { + sort: pwgImageSort, fromRank rank: Int64 = Int64.min) throws { // We shall perform at least one import in case where // the user did delete all images guard imageArray.isEmpty == false else { - _ = importOneBatch([ImagesGetInfo](), inAlbum: albumId) + _ = importOneBatch([ImagesGetInfo](), inAlbum: albumId, sort: sort) return } @@ -315,8 +316,7 @@ public class ImageProvider: NSObject { */ private func importOneBatch(_ imagesBatch: [ImagesGetInfo], inAlbum albumId: Int32, withAlbumUpdate: Bool = false, - sort: pwgImageSort = .dateCreatedDescending, - fromRank startRank: Int64 = Int64.min) -> Bool { + sort: pwgImageSort, fromRank startRank: Int64 = Int64.min) -> Bool { // Initialisation var success = false diff --git a/piwigoKit/Data Cache/Image Data/ImageResolution.swift b/piwigoKit/Data Cache/Image Data/ImageResolution.swift index 586c643b4..d02ce0430 100644 --- a/piwigoKit/Data Cache/Image Data/ImageResolution.swift +++ b/piwigoKit/Data Cache/Image Data/ImageResolution.swift @@ -66,4 +66,12 @@ extension Resolution { public var maxSize: Int { return max(self.width, self.height) } + + public var aspectRatio: Double? { + if height != 0 { + return Double(height) / Double(width) + } else { + return nil + } + } } diff --git a/piwigoKit/Info.plist b/piwigoKit/Info.plist index 09bbd4068..fb3e9dacb 100644 --- a/piwigoKit/Info.plist +++ b/piwigoKit/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 3.2.1 + 3.2.2 CFBundleVersion - 587 + 594 diff --git a/piwigoKit/Network/Extensions/PwgSession+Utilities.swift b/piwigoKit/Network/Extensions/PwgSession+Utilities.swift index c94d27acb..e3127c256 100644 --- a/piwigoKit/Network/Extensions/PwgSession+Utilities.swift +++ b/piwigoKit/Network/Extensions/PwgSession+Utilities.swift @@ -78,6 +78,7 @@ extension PwgSession { func checkSession(ofUser user: User?, completion: @escaping () -> Void, failure: @escaping (NSError) -> Void) { +// debugPrint("••> seconds since last used: \(Date.timeIntervalSinceReferenceDate - (user?.lastUsed ?? 0.0))") if #available(iOSApplicationExtension 14.0, *) { logger.notice("Start checking session…") } diff --git a/piwigoKit/Network/PwgSession.swift b/piwigoKit/Network/PwgSession.swift index 580376b6b..91de694af 100644 --- a/piwigoKit/Network/PwgSession.swift +++ b/piwigoKit/Network/PwgSession.swift @@ -31,7 +31,7 @@ public class PwgSession: NSObject { config.networkServiceType = .responsiveData /// The foreground session should wait for connectivity to become available. - // config.waitsForConnectivity = true + config.waitsForConnectivity = true /// Connections should use the network when the user has specified Low Data Mode // if #available(iOSApplicationExtension 13.0, *) { diff --git a/uploadKit/Extensions/UploadManager+Video.swift b/uploadKit/Extensions/UploadManager+Video.swift index 7b48c0d1b..79d312524 100644 --- a/uploadKit/Extensions/UploadManager+Video.swift +++ b/uploadKit/Extensions/UploadManager+Video.swift @@ -271,28 +271,29 @@ extension UploadManager { // <<==== End of code for debugging // resultHandler performed on another thread! + let error = info?[PHImageErrorKey] as? Error if self.isExecutingBackgroundUploadTask { // print("\(self.dbg()) exits retrieveVideoAssetFrom in", queueName()) // Any error? - if info?[PHImageErrorKey] != nil { - completionHandler(nil, options, info?[PHImageErrorKey] as? Error) + guard let error = error else { + completionHandler(avasset, options, nil) return } - completionHandler(avasset, options, nil) + completionHandler(nil, options, error) } else { self.backgroundQueue.async { // print("\(self.dbg()) exits retrieveVideoAssetFrom in", queueName()) // Any error? - if info?[PHImageErrorKey] != nil { - completionHandler(nil, options, info?[PHImageErrorKey] as? Error) + guard let error = error else { + completionHandler(avasset, options, nil) return } - completionHandler(avasset, options, nil) + completionHandler(nil, options, error) } } }) } - + // MARK: - Export Video /// - Determine video size and reduce it if requested diff --git a/uploadKit/Info.plist b/uploadKit/Info.plist index 8bb9f4274..c509cbd27 100644 --- a/uploadKit/Info.plist +++ b/uploadKit/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.2.1 CFBundleVersion - 405 + 412 diff --git a/uploadKit/Supporting Files/ImageMetadata.swift b/uploadKit/Supporting Files/ImageMetadata.swift index cf939083a..ee36a1f85 100644 --- a/uploadKit/Supporting Files/ImageMetadata.swift +++ b/uploadKit/Supporting Files/ImageMetadata.swift @@ -139,7 +139,7 @@ extension CGImageMetadata { let prefix = CGImageMetadataTagCopyPrefix(tag)! let name = CGImageMetadataTagCopyName(tag)! let path = ((prefix as String) + ":" + (name as String)) as CFString - print("=> Tag: \(prefix):\(name)") +// debugPrint("=> Tag: \(prefix):\(name)") // Check presence of dictionary if let properties = dictOfKeys[prefix] { diff --git a/uploadKit/Supporting Files/VideoMetadata.swift b/uploadKit/Supporting Files/VideoMetadata.swift index 637c17728..a683ac0fc 100644 --- a/uploadKit/Supporting Files/VideoMetadata.swift +++ b/uploadKit/Supporting Files/VideoMetadata.swift @@ -16,10 +16,10 @@ extension Array where Element == AVMetadataItem { // Common Metadata Identifiers var locationID = AVMetadataIdentifier.commonIdentifierLocation var locations = AVMetadataItem.metadataItems(from: metadata, filteredByIdentifier: locationID) - if let location = locations.first { - if let position = location.stringValue { - print("position => \(position)") - } + if let _ = locations.first { +// if let position = location.stringValue { +// debugPrint("position => \(position)") +// } return true }