mirror of
https://github.com/mastodon/mastodon-ios.git
synced 2025-01-26 15:24:54 +01:00
Merge branch 'release/0.8.6' into main
This commit is contained in:
commit
6952fd2148
@ -70,15 +70,16 @@
|
|||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"discard": "Discard",
|
"discard": "Discard",
|
||||||
"try_again": "Try Again",
|
"try_again": "Try Again",
|
||||||
"take_photo": "Take photo",
|
"take_photo": "Take Photo",
|
||||||
"save_photo": "Save photo",
|
"save_photo": "Save Photo",
|
||||||
|
"copy_photo": "Copy Photo",
|
||||||
"sign_in": "Sign In",
|
"sign_in": "Sign In",
|
||||||
"sign_up": "Sign Up",
|
"sign_up": "Sign Up",
|
||||||
"see_more": "See More",
|
"see_more": "See More",
|
||||||
"preview": "Preview",
|
"preview": "Preview",
|
||||||
"share": "Share",
|
"share": "Share",
|
||||||
"share_user": "Share %s",
|
"share_user": "Share %s",
|
||||||
"share_post": "Share post",
|
"share_post": "Share Post",
|
||||||
"open_in_safari": "Open in Safari",
|
"open_in_safari": "Open in Safari",
|
||||||
"find_people": "Find people to follow",
|
"find_people": "Find people to follow",
|
||||||
"manually_search": "Manually search instead",
|
"manually_search": "Manually search instead",
|
||||||
|
@ -3894,7 +3894,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 30;
|
CURRENT_PROJECT_VERSION = 31;
|
||||||
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
|
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
|
||||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = Mastodon/Info.plist;
|
INFOPLIST_FILE = Mastodon/Info.plist;
|
||||||
@ -3902,7 +3902,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.8.5;
|
MARKETING_VERSION = 0.8.6;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
@ -3921,7 +3921,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 30;
|
CURRENT_PROJECT_VERSION = 31;
|
||||||
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
|
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
|
||||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = Mastodon/Info.plist;
|
INFOPLIST_FILE = Mastodon/Info.plist;
|
||||||
@ -3929,7 +3929,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.8.5;
|
MARKETING_VERSION = 0.8.6;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
@ -4249,7 +4249,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 30;
|
CURRENT_PROJECT_VERSION = 31;
|
||||||
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
|
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
|
||||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = Mastodon/Info.plist;
|
INFOPLIST_FILE = Mastodon/Info.plist;
|
||||||
@ -4257,7 +4257,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.8.5;
|
MARKETING_VERSION = 0.8.6;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
@ -4363,7 +4363,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
|
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 30;
|
CURRENT_PROJECT_VERSION = 31;
|
||||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = NotificationService/Info.plist;
|
INFOPLIST_FILE = NotificationService/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
@ -4371,7 +4371,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.8.5;
|
MARKETING_VERSION = 0.8.6;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
@ -4482,7 +4482,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 30;
|
CURRENT_PROJECT_VERSION = 31;
|
||||||
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
|
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
|
||||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = Mastodon/Info.plist;
|
INFOPLIST_FILE = Mastodon/Info.plist;
|
||||||
@ -4490,7 +4490,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.8.5;
|
MARKETING_VERSION = 0.8.6;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
@ -4596,7 +4596,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
|
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 30;
|
CURRENT_PROJECT_VERSION = 31;
|
||||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = NotificationService/Info.plist;
|
INFOPLIST_FILE = NotificationService/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
@ -4604,7 +4604,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.8.5;
|
MARKETING_VERSION = 0.8.6;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
@ -4650,7 +4650,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
|
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 30;
|
CURRENT_PROJECT_VERSION = 31;
|
||||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = NotificationService/Info.plist;
|
INFOPLIST_FILE = NotificationService/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
@ -4658,7 +4658,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.8.5;
|
MARKETING_VERSION = 0.8.6;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
@ -4673,7 +4673,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
|
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 30;
|
CURRENT_PROJECT_VERSION = 31;
|
||||||
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = NotificationService/Info.plist;
|
INFOPLIST_FILE = NotificationService/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
@ -4681,7 +4681,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.8.5;
|
MARKETING_VERSION = 0.8.6;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
<key>CoreDataStack.xcscheme_^#shared#^_</key>
|
<key>CoreDataStack.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>20</integer>
|
<integer>21</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>Mastodon - ASDK.xcscheme_^#shared#^_</key>
|
<key>Mastodon - ASDK.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
@ -37,7 +37,7 @@
|
|||||||
<key>NotificationService.xcscheme_^#shared#^_</key>
|
<key>NotificationService.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>21</integer>
|
<integer>20</integer>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>SuppressBuildableAutocreation</key>
|
<key>SuppressBuildableAutocreation</key>
|
||||||
|
@ -1005,7 +1005,8 @@ extension StatusSection {
|
|||||||
private static func setupStatusMoreButtonMenu(
|
private static func setupStatusMoreButtonMenu(
|
||||||
cell: StatusTableViewCell,
|
cell: StatusTableViewCell,
|
||||||
dependency: NeedsDependency,
|
dependency: NeedsDependency,
|
||||||
status: Status) {
|
status: Status
|
||||||
|
) {
|
||||||
|
|
||||||
guard let userProvider = dependency as? UserProvider else { fatalError() }
|
guard let userProvider = dependency as? UserProvider else { fatalError() }
|
||||||
|
|
||||||
|
@ -110,6 +110,8 @@ internal enum L10n {
|
|||||||
internal static let confirm = L10n.tr("Localizable", "Common.Controls.Actions.Confirm")
|
internal static let confirm = L10n.tr("Localizable", "Common.Controls.Actions.Confirm")
|
||||||
/// Continue
|
/// Continue
|
||||||
internal static let `continue` = L10n.tr("Localizable", "Common.Controls.Actions.Continue")
|
internal static let `continue` = L10n.tr("Localizable", "Common.Controls.Actions.Continue")
|
||||||
|
/// Copy Photo
|
||||||
|
internal static let copyPhoto = L10n.tr("Localizable", "Common.Controls.Actions.CopyPhoto")
|
||||||
/// Delete
|
/// Delete
|
||||||
internal static let delete = L10n.tr("Localizable", "Common.Controls.Actions.Delete")
|
internal static let delete = L10n.tr("Localizable", "Common.Controls.Actions.Delete")
|
||||||
/// Discard
|
/// Discard
|
||||||
@ -144,7 +146,7 @@ internal enum L10n {
|
|||||||
}
|
}
|
||||||
/// Save
|
/// Save
|
||||||
internal static let save = L10n.tr("Localizable", "Common.Controls.Actions.Save")
|
internal static let save = L10n.tr("Localizable", "Common.Controls.Actions.Save")
|
||||||
/// Save photo
|
/// Save Photo
|
||||||
internal static let savePhoto = L10n.tr("Localizable", "Common.Controls.Actions.SavePhoto")
|
internal static let savePhoto = L10n.tr("Localizable", "Common.Controls.Actions.SavePhoto")
|
||||||
/// See More
|
/// See More
|
||||||
internal static let seeMore = L10n.tr("Localizable", "Common.Controls.Actions.SeeMore")
|
internal static let seeMore = L10n.tr("Localizable", "Common.Controls.Actions.SeeMore")
|
||||||
@ -152,7 +154,7 @@ internal enum L10n {
|
|||||||
internal static let settings = L10n.tr("Localizable", "Common.Controls.Actions.Settings")
|
internal static let settings = L10n.tr("Localizable", "Common.Controls.Actions.Settings")
|
||||||
/// Share
|
/// Share
|
||||||
internal static let share = L10n.tr("Localizable", "Common.Controls.Actions.Share")
|
internal static let share = L10n.tr("Localizable", "Common.Controls.Actions.Share")
|
||||||
/// Share post
|
/// Share Post
|
||||||
internal static let sharePost = L10n.tr("Localizable", "Common.Controls.Actions.SharePost")
|
internal static let sharePost = L10n.tr("Localizable", "Common.Controls.Actions.SharePost")
|
||||||
/// Share %@
|
/// Share %@
|
||||||
internal static func shareUser(_ p1: Any) -> String {
|
internal static func shareUser(_ p1: Any) -> String {
|
||||||
@ -164,7 +166,7 @@ internal enum L10n {
|
|||||||
internal static let signUp = L10n.tr("Localizable", "Common.Controls.Actions.SignUp")
|
internal static let signUp = L10n.tr("Localizable", "Common.Controls.Actions.SignUp")
|
||||||
/// Skip
|
/// Skip
|
||||||
internal static let skip = L10n.tr("Localizable", "Common.Controls.Actions.Skip")
|
internal static let skip = L10n.tr("Localizable", "Common.Controls.Actions.Skip")
|
||||||
/// Take photo
|
/// Take Photo
|
||||||
internal static let takePhoto = L10n.tr("Localizable", "Common.Controls.Actions.TakePhoto")
|
internal static let takePhoto = L10n.tr("Localizable", "Common.Controls.Actions.TakePhoto")
|
||||||
/// Try Again
|
/// Try Again
|
||||||
internal static let tryAgain = L10n.tr("Localizable", "Common.Controls.Actions.TryAgain")
|
internal static let tryAgain = L10n.tr("Localizable", "Common.Controls.Actions.TryAgain")
|
||||||
|
@ -189,6 +189,31 @@ extension StatusTableViewCellDelegate where Self: StatusProvider {
|
|||||||
})
|
})
|
||||||
.store(in: &self.context.disposeBag)
|
.store(in: &self.context.disposeBag)
|
||||||
}
|
}
|
||||||
|
let copyPhotoAction = UIAction(
|
||||||
|
title: L10n.Common.Controls.Actions.copyPhoto,
|
||||||
|
image: UIImage(systemName: "doc.on.doc"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off
|
||||||
|
) { [weak self] _ in
|
||||||
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: copy photo", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.attachment(of: status, index: i)
|
||||||
|
.setFailureType(to: Error.self)
|
||||||
|
.compactMap { attachment -> AnyPublisher<UIImage, Error>? in
|
||||||
|
guard let attachment = attachment, let url = URL(string: attachment.url) else { return nil }
|
||||||
|
return self.context.photoLibraryService.copyImage(url: url)
|
||||||
|
}
|
||||||
|
.switchToLatest()
|
||||||
|
.sink(receiveCompletion: { completion in
|
||||||
|
switch completion {
|
||||||
|
case .failure(let error):
|
||||||
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: copy photo fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
|
||||||
|
case .finished:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}, receiveValue: { _ in
|
||||||
|
// do nothing
|
||||||
|
})
|
||||||
|
.store(in: &self.context.disposeBag)
|
||||||
|
}
|
||||||
let shareAction = UIAction(
|
let shareAction = UIAction(
|
||||||
title: L10n.Common.Controls.Actions.share, image: UIImage(systemName: "square.and.arrow.up")!, identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off
|
title: L10n.Common.Controls.Actions.share, image: UIImage(systemName: "square.and.arrow.up")!, identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off
|
||||||
) { [weak self] _ in
|
) { [weak self] _ in
|
||||||
@ -210,7 +235,7 @@ extension StatusTableViewCellDelegate where Self: StatusProvider {
|
|||||||
})
|
})
|
||||||
.store(in: &self.context.disposeBag)
|
.store(in: &self.context.disposeBag)
|
||||||
}
|
}
|
||||||
let children = [savePhotoAction, shareAction]
|
let children = [savePhotoAction, copyPhotoAction, shareAction]
|
||||||
return UIMenu(title: "", image: nil, children: children)
|
return UIMenu(title: "", image: nil, children: children)
|
||||||
}
|
}
|
||||||
contextMenuConfiguration.indexPath = indexPath
|
contextMenuConfiguration.indexPath = indexPath
|
||||||
|
@ -30,6 +30,7 @@ Please check your internet connection.";
|
|||||||
"Common.Controls.Actions.Cancel" = "Cancel";
|
"Common.Controls.Actions.Cancel" = "Cancel";
|
||||||
"Common.Controls.Actions.Confirm" = "Confirm";
|
"Common.Controls.Actions.Confirm" = "Confirm";
|
||||||
"Common.Controls.Actions.Continue" = "Continue";
|
"Common.Controls.Actions.Continue" = "Continue";
|
||||||
|
"Common.Controls.Actions.CopyPhoto" = "Copy Photo";
|
||||||
"Common.Controls.Actions.Delete" = "Delete";
|
"Common.Controls.Actions.Delete" = "Delete";
|
||||||
"Common.Controls.Actions.Discard" = "Discard";
|
"Common.Controls.Actions.Discard" = "Discard";
|
||||||
"Common.Controls.Actions.Done" = "Done";
|
"Common.Controls.Actions.Done" = "Done";
|
||||||
@ -46,16 +47,16 @@ Please check your internet connection.";
|
|||||||
"Common.Controls.Actions.Reply" = "Reply";
|
"Common.Controls.Actions.Reply" = "Reply";
|
||||||
"Common.Controls.Actions.ReportUser" = "Report %@";
|
"Common.Controls.Actions.ReportUser" = "Report %@";
|
||||||
"Common.Controls.Actions.Save" = "Save";
|
"Common.Controls.Actions.Save" = "Save";
|
||||||
"Common.Controls.Actions.SavePhoto" = "Save photo";
|
"Common.Controls.Actions.SavePhoto" = "Save Photo";
|
||||||
"Common.Controls.Actions.SeeMore" = "See More";
|
"Common.Controls.Actions.SeeMore" = "See More";
|
||||||
"Common.Controls.Actions.Settings" = "Settings";
|
"Common.Controls.Actions.Settings" = "Settings";
|
||||||
"Common.Controls.Actions.Share" = "Share";
|
"Common.Controls.Actions.Share" = "Share";
|
||||||
"Common.Controls.Actions.SharePost" = "Share post";
|
"Common.Controls.Actions.SharePost" = "Share Post";
|
||||||
"Common.Controls.Actions.ShareUser" = "Share %@";
|
"Common.Controls.Actions.ShareUser" = "Share %@";
|
||||||
"Common.Controls.Actions.SignIn" = "Sign In";
|
"Common.Controls.Actions.SignIn" = "Sign In";
|
||||||
"Common.Controls.Actions.SignUp" = "Sign Up";
|
"Common.Controls.Actions.SignUp" = "Sign Up";
|
||||||
"Common.Controls.Actions.Skip" = "Skip";
|
"Common.Controls.Actions.Skip" = "Skip";
|
||||||
"Common.Controls.Actions.TakePhoto" = "Take photo";
|
"Common.Controls.Actions.TakePhoto" = "Take Photo";
|
||||||
"Common.Controls.Actions.TryAgain" = "Try Again";
|
"Common.Controls.Actions.TryAgain" = "Try Again";
|
||||||
"Common.Controls.Actions.UnblockDomain" = "Unblock %@";
|
"Common.Controls.Actions.UnblockDomain" = "Unblock %@";
|
||||||
"Common.Controls.Friendship.Block" = "Block";
|
"Common.Controls.Friendship.Block" = "Block";
|
||||||
|
@ -30,6 +30,7 @@ Please check your internet connection.";
|
|||||||
"Common.Controls.Actions.Cancel" = "Cancel";
|
"Common.Controls.Actions.Cancel" = "Cancel";
|
||||||
"Common.Controls.Actions.Confirm" = "Confirm";
|
"Common.Controls.Actions.Confirm" = "Confirm";
|
||||||
"Common.Controls.Actions.Continue" = "Continue";
|
"Common.Controls.Actions.Continue" = "Continue";
|
||||||
|
"Common.Controls.Actions.CopyPhoto" = "Copy Photo";
|
||||||
"Common.Controls.Actions.Delete" = "Delete";
|
"Common.Controls.Actions.Delete" = "Delete";
|
||||||
"Common.Controls.Actions.Discard" = "Discard";
|
"Common.Controls.Actions.Discard" = "Discard";
|
||||||
"Common.Controls.Actions.Done" = "Done";
|
"Common.Controls.Actions.Done" = "Done";
|
||||||
@ -46,16 +47,16 @@ Please check your internet connection.";
|
|||||||
"Common.Controls.Actions.Reply" = "Reply";
|
"Common.Controls.Actions.Reply" = "Reply";
|
||||||
"Common.Controls.Actions.ReportUser" = "Report %@";
|
"Common.Controls.Actions.ReportUser" = "Report %@";
|
||||||
"Common.Controls.Actions.Save" = "Save";
|
"Common.Controls.Actions.Save" = "Save";
|
||||||
"Common.Controls.Actions.SavePhoto" = "Save photo";
|
"Common.Controls.Actions.SavePhoto" = "Save Photo";
|
||||||
"Common.Controls.Actions.SeeMore" = "See More";
|
"Common.Controls.Actions.SeeMore" = "See More";
|
||||||
"Common.Controls.Actions.Settings" = "Settings";
|
"Common.Controls.Actions.Settings" = "Settings";
|
||||||
"Common.Controls.Actions.Share" = "Share";
|
"Common.Controls.Actions.Share" = "Share";
|
||||||
"Common.Controls.Actions.SharePost" = "Share post";
|
"Common.Controls.Actions.SharePost" = "Share Post";
|
||||||
"Common.Controls.Actions.ShareUser" = "Share %@";
|
"Common.Controls.Actions.ShareUser" = "Share %@";
|
||||||
"Common.Controls.Actions.SignIn" = "Sign In";
|
"Common.Controls.Actions.SignIn" = "Sign In";
|
||||||
"Common.Controls.Actions.SignUp" = "Sign Up";
|
"Common.Controls.Actions.SignUp" = "Sign Up";
|
||||||
"Common.Controls.Actions.Skip" = "Skip";
|
"Common.Controls.Actions.Skip" = "Skip";
|
||||||
"Common.Controls.Actions.TakePhoto" = "Take photo";
|
"Common.Controls.Actions.TakePhoto" = "Take Photo";
|
||||||
"Common.Controls.Actions.TryAgain" = "Try Again";
|
"Common.Controls.Actions.TryAgain" = "Try Again";
|
||||||
"Common.Controls.Actions.UnblockDomain" = "Unblock %@";
|
"Common.Controls.Actions.UnblockDomain" = "Unblock %@";
|
||||||
"Common.Controls.Friendship.Block" = "Block";
|
"Common.Controls.Friendship.Block" = "Block";
|
||||||
|
@ -226,6 +226,24 @@ extension MediaPreviewViewController: MediaPreviewImageViewControllerDelegate {
|
|||||||
case .local(let meta):
|
case .local(let meta):
|
||||||
context.photoLibraryService.save(image: meta.image, withNotificationFeedback: true)
|
context.photoLibraryService.save(image: meta.image, withNotificationFeedback: true)
|
||||||
}
|
}
|
||||||
|
case .copyPhoto:
|
||||||
|
switch viewController.viewModel.item {
|
||||||
|
case .status(let meta):
|
||||||
|
context.photoLibraryService.copyImage(url: meta.url)
|
||||||
|
.sink { completion in
|
||||||
|
switch completion {
|
||||||
|
case .failure(let error):
|
||||||
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: copy photo fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
|
||||||
|
case .finished:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} receiveValue: { _ in
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
.store(in: &context.disposeBag)
|
||||||
|
case .local(let meta):
|
||||||
|
context.photoLibraryService.copy(image: meta.image, withNotificationFeedback: true)
|
||||||
|
}
|
||||||
case .share:
|
case .share:
|
||||||
let applicationActivities: [UIActivity] = [
|
let applicationActivities: [UIActivity] = [
|
||||||
SafariActivity(sceneCoordinator: self.coordinator)
|
SafariActivity(sceneCoordinator: self.coordinator)
|
||||||
@ -236,6 +254,7 @@ extension MediaPreviewViewController: MediaPreviewImageViewControllerDelegate {
|
|||||||
)
|
)
|
||||||
activityViewController.popoverPresentationController?.sourceView = viewController.previewImageView.imageView
|
activityViewController.popoverPresentationController?.sourceView = viewController.previewImageView.imageView
|
||||||
self.present(activityViewController, animated: true, completion: nil)
|
self.present(activityViewController, animated: true, completion: nil)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,6 +133,14 @@ extension MediaPreviewImageViewController: UIContextMenuInteractionDelegate {
|
|||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.delegate?.mediaPreviewImageViewController(self, contextMenuActionPerform: .savePhoto)
|
self.delegate?.mediaPreviewImageViewController(self, contextMenuActionPerform: .savePhoto)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let copyAction = UIAction(
|
||||||
|
title: L10n.Common.Controls.Actions.copyPhoto, image: UIImage(systemName: "doc.on.doc")!, identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off
|
||||||
|
) { [weak self] _ in
|
||||||
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: copy photo", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.delegate?.mediaPreviewImageViewController(self, contextMenuActionPerform: .copyPhoto)
|
||||||
|
}
|
||||||
|
|
||||||
let shareAction = UIAction(
|
let shareAction = UIAction(
|
||||||
title: L10n.Common.Controls.Actions.share, image: UIImage(systemName: "square.and.arrow.up")!, identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off
|
title: L10n.Common.Controls.Actions.share, image: UIImage(systemName: "square.and.arrow.up")!, identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off
|
||||||
@ -145,6 +153,7 @@ extension MediaPreviewImageViewController: UIContextMenuInteractionDelegate {
|
|||||||
let actionProvider: UIContextMenuActionProvider = { elements -> UIMenu? in
|
let actionProvider: UIContextMenuActionProvider = { elements -> UIMenu? in
|
||||||
return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [
|
return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [
|
||||||
saveAction,
|
saveAction,
|
||||||
|
copyAction,
|
||||||
shareAction
|
shareAction
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
@ -162,6 +171,7 @@ extension MediaPreviewImageViewController: UIContextMenuInteractionDelegate {
|
|||||||
extension MediaPreviewImageViewController {
|
extension MediaPreviewImageViewController {
|
||||||
enum ContextMenuAction {
|
enum ContextMenuAction {
|
||||||
case savePhoto
|
case savePhoto
|
||||||
|
case copyPhoto
|
||||||
case share
|
case share
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,13 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Combine
|
||||||
|
|
||||||
final class SawToothView: UIView {
|
final class SawToothView: UIView {
|
||||||
static let widthUint = 8
|
static let widthUint = 8
|
||||||
|
|
||||||
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
_init()
|
_init()
|
||||||
@ -22,7 +25,19 @@ final class SawToothView: UIView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func _init() {
|
func _init() {
|
||||||
backgroundColor = Asset.Colors.Background.secondarySystemBackground.color
|
setupBackgroundColor(theme: ThemeService.shared.currentTheme.value)
|
||||||
|
ThemeService.shared.currentTheme
|
||||||
|
.receive(on: RunLoop.main)
|
||||||
|
.sink { [weak self] theme in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.setupBackgroundColor(theme: theme)
|
||||||
|
}
|
||||||
|
.store(in: &disposeBag)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupBackgroundColor(theme: Theme) {
|
||||||
|
backgroundColor = theme.secondarySystemBackgroundColor
|
||||||
|
setNeedsDisplay()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func draw(_ rect: CGRect) {
|
override func draw(_ rect: CGRect) {
|
||||||
@ -37,7 +52,7 @@ final class SawToothView: UIView {
|
|||||||
}
|
}
|
||||||
bezierPath.addLine(to: CGPoint(x: 0, y: bottomY))
|
bezierPath.addLine(to: CGPoint(x: 0, y: bottomY))
|
||||||
bezierPath.close()
|
bezierPath.close()
|
||||||
Asset.Colors.Background.systemBackground.color.setFill()
|
ThemeService.shared.currentTheme.value.systemBackgroundColor.setFill()
|
||||||
bezierPath.fill()
|
bezierPath.fill()
|
||||||
bezierPath.lineWidth = 0
|
bezierPath.lineWidth = 0
|
||||||
bezierPath.stroke()
|
bezierPath.stroke()
|
||||||
|
@ -16,13 +16,14 @@ class TimelineLoaderTableViewCell: UITableViewCell {
|
|||||||
static let labelFont = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .medium))
|
static let labelFont = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .medium))
|
||||||
|
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
private var _disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
let stackView = UIStackView()
|
let stackView = UIStackView()
|
||||||
|
|
||||||
let loadMoreButton: UIButton = {
|
let loadMoreButton: UIButton = {
|
||||||
let button = HighlightDimmableButton()
|
let button = HighlightDimmableButton()
|
||||||
button.titleLabel?.font = TimelineLoaderTableViewCell.labelFont
|
button.titleLabel?.font = TimelineLoaderTableViewCell.labelFont
|
||||||
button.backgroundColor = Asset.Colors.Background.systemBackground.color
|
|
||||||
button.setTitleColor(Asset.Colors.brandBlue.color, for: .normal)
|
button.setTitleColor(Asset.Colors.brandBlue.color, for: .normal)
|
||||||
button.setTitle(L10n.Common.Controls.Timeline.Loader.loadMissingPosts, for: .normal)
|
button.setTitle(L10n.Common.Controls.Timeline.Loader.loadMissingPosts, for: .normal)
|
||||||
button.setTitle("", for: .disabled)
|
button.setTitle("", for: .disabled)
|
||||||
@ -114,6 +115,19 @@ class TimelineLoaderTableViewCell: UITableViewCell {
|
|||||||
loadMoreButton.isHidden = true
|
loadMoreButton.isHidden = true
|
||||||
loadMoreLabel.isHidden = true
|
loadMoreLabel.isHidden = true
|
||||||
activityIndicatorView.isHidden = true
|
activityIndicatorView.isHidden = true
|
||||||
|
|
||||||
|
setupBackgroundColor(theme: ThemeService.shared.currentTheme.value)
|
||||||
|
ThemeService.shared.currentTheme
|
||||||
|
.receive(on: RunLoop.main)
|
||||||
|
.sink { [weak self] theme in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.setupBackgroundColor(theme: theme)
|
||||||
|
}
|
||||||
|
.store(in: &_disposeBag)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupBackgroundColor(theme: Theme) {
|
||||||
|
loadMoreButton.backgroundColor = theme.systemBackgroundColor
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import os.log
|
|||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import Photos
|
import Photos
|
||||||
import AlamofireImage
|
import Nuke
|
||||||
|
|
||||||
final class PhotoLibraryService: NSObject {
|
final class PhotoLibraryService: NSObject {
|
||||||
|
|
||||||
@ -26,39 +26,51 @@ extension PhotoLibraryService {
|
|||||||
extension PhotoLibraryService {
|
extension PhotoLibraryService {
|
||||||
|
|
||||||
func saveImage(url: URL) -> AnyPublisher<UIImage, Error> {
|
func saveImage(url: URL) -> AnyPublisher<UIImage, Error> {
|
||||||
|
guard PHPhotoLibrary.authorizationStatus(for: .addOnly) != .denied else {
|
||||||
|
return Fail(error: PhotoLibraryError.noPermission).eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
return processImage(url: url)
|
||||||
|
.handleEvents(receiveOutput: { image in
|
||||||
|
self.save(image: image)
|
||||||
|
})
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyImage(url: URL) -> AnyPublisher<UIImage, Error> {
|
||||||
|
return processImage(url: url)
|
||||||
|
.handleEvents(receiveOutput: { image in
|
||||||
|
UIPasteboard.general.image = image
|
||||||
|
})
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
func processImage(url: URL) -> AnyPublisher<UIImage, Error> {
|
||||||
let impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light)
|
let impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light)
|
||||||
let notificationFeedbackGenerator = UINotificationFeedbackGenerator()
|
let notificationFeedbackGenerator = UINotificationFeedbackGenerator()
|
||||||
|
|
||||||
return Future<UIImage, Error> { promise in
|
return ImagePipeline.shared.imagePublisher(with: url)
|
||||||
guard PHPhotoLibrary.authorizationStatus(for: .addOnly) != .denied else {
|
.handleEvents(receiveSubscription: { _ in
|
||||||
promise(.failure(PhotoLibraryError.noPermission))
|
impactFeedbackGenerator.impactOccurred()
|
||||||
return
|
}, receiveOutput: { response in
|
||||||
}
|
self.save(image: response.image)
|
||||||
|
}, receiveCompletion: { completion in
|
||||||
ImageDownloader.default.download(URLRequest(url: url), completion: { [weak self] response in
|
switch completion {
|
||||||
guard let self = self else { return }
|
|
||||||
switch response.result {
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: download image %s fail: %s", ((#file as NSString).lastPathComponent), #line, #function, url.debugDescription, error.localizedDescription)
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: download image %s fail: %s", ((#file as NSString).lastPathComponent), #line, #function, url.debugDescription, error.localizedDescription)
|
||||||
promise(.failure(error))
|
|
||||||
case .success(let image):
|
notificationFeedbackGenerator.notificationOccurred(.error)
|
||||||
|
case .finished:
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: download image %s success", ((#file as NSString).lastPathComponent), #line, #function, url.debugDescription)
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: download image %s success", ((#file as NSString).lastPathComponent), #line, #function, url.debugDescription)
|
||||||
self.save(image: image)
|
|
||||||
promise(.success(image))
|
notificationFeedbackGenerator.notificationOccurred(.success)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
.map { response in
|
||||||
.handleEvents(receiveSubscription: { _ in
|
return response.image
|
||||||
impactFeedbackGenerator.impactOccurred()
|
|
||||||
}, receiveCompletion: { completion in
|
|
||||||
switch completion {
|
|
||||||
case .failure:
|
|
||||||
notificationFeedbackGenerator.notificationOccurred(.error)
|
|
||||||
case .finished:
|
|
||||||
notificationFeedbackGenerator.notificationOccurred(.success)
|
|
||||||
}
|
}
|
||||||
})
|
.mapError { error in error as Error }
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
func save(image: UIImage, withNotificationFeedback: Bool = false) {
|
func save(image: UIImage, withNotificationFeedback: Bool = false) {
|
||||||
@ -75,6 +87,16 @@ extension PhotoLibraryService {
|
|||||||
notificationFeedbackGenerator.notificationOccurred(.success)
|
notificationFeedbackGenerator.notificationOccurred(.success)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func copy(image: UIImage, withNotificationFeedback: Bool = false) {
|
||||||
|
UIPasteboard.general.image = image
|
||||||
|
|
||||||
|
// assert no error
|
||||||
|
if withNotificationFeedback {
|
||||||
|
let notificationFeedbackGenerator = UINotificationFeedbackGenerator()
|
||||||
|
notificationFeedbackGenerator.notificationOccurred(.success)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func image(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
|
@objc private func image(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
|
||||||
// TODO: notify banner
|
// TODO: notify banner
|
||||||
|
Loading…
Reference in New Issue
Block a user