diff --git a/Localization/app.json b/Localization/app.json index a9eede0de..fa2af0935 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -129,14 +129,6 @@ "media_content_warning": "Tap anywhere to reveal", "poll": { "vote": "Vote", - "vote_count": { - "single": "%d vote", - "multiple": "%d votes" - }, - "voter_count": { - "single": "%d voter", - "multiple": "%d voters" - }, "time_left": "%s left", "closed": "Closed" }, @@ -200,12 +192,6 @@ "count_favorites": "%s favorites" } } - }, - "countable": { - "photo": { - "single": "photo", - "multiple": "photos" - } } }, "scene": { @@ -386,8 +372,6 @@ "direct": "Only people I mention" }, "auto_complete": { - "single_people_talking": "%ld people talking", - "multiple_people_talking": "%ld people talking", "space_to_add": "Space to add" }, "accessibility": { @@ -411,7 +395,6 @@ } }, "profile": { - "subtitle": "%s posts", "dashboard": { "posts": "posts", "following": "following", @@ -499,15 +482,7 @@ }, "thread": { "back_title": "Post", - "title": "Post from %s", - "reblog": { - "single": "%s reblog", - "multiple": "%s reblogs" - }, - "favorite": { - "single": "%s favorite", - "multiple": "%s favorites" - } + "title": "Post from %s" }, "settings": { "title": "Settings", @@ -548,6 +523,9 @@ "signout": "Sign Out" } }, + "footer": { + "mastodon_description": "Mastodon is open source software. You can contribute or report issues on GitHub at %s (%s)" + }, "keyboard": { "close_settings_window": "Close Settings Window" } diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 67f158fad..527aed0fd 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -55,7 +55,6 @@ 2D34D9DB261494120081BFC0 /* APIService+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D34D9DA261494120081BFC0 /* APIService+Search.swift */; }; 2D34D9E226149C920081BFC0 /* SearchRecommendTagsCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D34D9E126149C920081BFC0 /* SearchRecommendTagsCollectionViewCell.swift */; }; 2D35237A26256D920031AF25 /* NotificationSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D35237926256D920031AF25 /* NotificationSection.swift */; }; - 2D35238126256F690031AF25 /* NotificationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D35238026256F690031AF25 /* NotificationTableViewCell.swift */; }; 2D364F7225E66D7500204FDC /* MastodonResendEmailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D364F7125E66D7500204FDC /* MastodonResendEmailViewController.swift */; }; 2D364F7825E66D8300204FDC /* MastodonResendEmailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D364F7725E66D8300204FDC /* MastodonResendEmailViewModel.swift */; }; 2D38F1C625CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1C525CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift */; }; @@ -242,6 +241,8 @@ DB427DED25BAA00100D1B89D /* MastodonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DEC25BAA00100D1B89D /* MastodonTests.swift */; }; DB427DF825BAA00100D1B89D /* MastodonUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DF725BAA00100D1B89D /* MastodonUITests.swift */; }; DB44384F25E8C1FA008912A2 /* CALayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB44384E25E8C1FA008912A2 /* CALayer.swift */; }; + DB443CD22694326A00159B29 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = DB443CD0269415D200159B29 /* Localizable.stringsdict */; }; + DB443CD42694627B00159B29 /* AppearanceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB443CD32694627B00159B29 /* AppearanceView.swift */; }; DB44767B260B3B8C00B66B82 /* CustomEmojiPickerInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB44767A260B3B8C00B66B82 /* CustomEmojiPickerInputView.swift */; }; DB447681260B3ED600B66B82 /* CustomEmojiPickerSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB447680260B3ED600B66B82 /* CustomEmojiPickerSection.swift */; }; DB44768B260B3F2100B66B82 /* CustomEmojiPickerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB44768A260B3F2100B66B82 /* CustomEmojiPickerItem.swift */; }; @@ -665,7 +666,6 @@ 2D34D9DA261494120081BFC0 /* APIService+Search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Search.swift"; sourceTree = ""; }; 2D34D9E126149C920081BFC0 /* SearchRecommendTagsCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchRecommendTagsCollectionViewCell.swift; sourceTree = ""; }; 2D35237926256D920031AF25 /* NotificationSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSection.swift; sourceTree = ""; }; - 2D35238026256F690031AF25 /* NotificationTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationTableViewCell.swift; sourceTree = ""; }; 2D364F7125E66D7500204FDC /* MastodonResendEmailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonResendEmailViewController.swift; sourceTree = ""; }; 2D364F7725E66D8300204FDC /* MastodonResendEmailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonResendEmailViewModel.swift; sourceTree = ""; }; 2D38F1C525CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentOffsetAdjustableTimelineViewControllerDelegate.swift; sourceTree = ""; }; @@ -875,6 +875,9 @@ DB427DF725BAA00100D1B89D /* MastodonUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonUITests.swift; sourceTree = ""; }; DB427DF925BAA00100D1B89D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; DB44384E25E8C1FA008912A2 /* CALayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CALayer.swift; sourceTree = ""; }; + DB443CCF269415D200159B29 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; }; + DB443CD1269415D800159B29 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ar; path = ar.lproj/Localizable.stringsdict; sourceTree = ""; }; + DB443CD32694627B00159B29 /* AppearanceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceView.swift; sourceTree = ""; }; DB44767A260B3B8C00B66B82 /* CustomEmojiPickerInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEmojiPickerInputView.swift; sourceTree = ""; }; DB447680260B3ED600B66B82 /* CustomEmojiPickerSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEmojiPickerSection.swift; sourceTree = ""; }; DB44768A260B3F2100B66B82 /* CustomEmojiPickerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEmojiPickerItem.swift; sourceTree = ""; }; @@ -1359,7 +1362,6 @@ 2D35237F26256F470031AF25 /* TableViewCell */ = { isa = PBXGroup; children = ( - 2D35238026256F690031AF25 /* NotificationTableViewCell.swift */, 2D24E11C2626D8B100A59D4F /* NotificationStatusTableViewCell.swift */, ); path = TableViewCell; @@ -1722,6 +1724,7 @@ children = ( 5B90C458262599800002E742 /* Cell */, 5B90C45C262599800002E742 /* SettingsSectionHeader.swift */, + DB443CD32694627B00159B29 /* AppearanceView.swift */, ); path = View; sourceTree = ""; @@ -1831,6 +1834,7 @@ 164F0EBB267D4FE400249499 /* BoopSound.caf */, DB427DDE25BAA00100D1B89D /* Assets.xcassets */, DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */, + DB443CD0269415D200159B29 /* Localizable.stringsdict */, DB3D100F25BAA75E00EAA174 /* Localizable.strings */, DB2B3ABE25E37E15007045F9 /* InfoPlist.strings */, ); @@ -2933,6 +2937,7 @@ files = ( 164F0EBC267D4FE400249499 /* BoopSound.caf in Resources */, DB427DE225BAA00100D1B89D /* LaunchScreen.storyboard in Resources */, + DB443CD22694326A00159B29 /* Localizable.stringsdict in Resources */, DB3D100D25BAA75E00EAA174 /* Localizable.strings in Resources */, DB427DDF25BAA00100D1B89D /* Assets.xcassets in Resources */, DB427DDD25BAA00100D1B89D /* Main.storyboard in Resources */, @@ -3198,6 +3203,7 @@ 2D571B2F26004EC000540450 /* NavigationBarProgressView.swift in Sources */, DBD376AA2692EA4F007FEC24 /* Theme.swift in Sources */, 0FAA101225E105390017CCDE /* PrimaryActionButton.swift in Sources */, + DB443CD42694627B00159B29 /* AppearanceView.swift in Sources */, DB8AF53025C13561002E6C99 /* AppContext.swift in Sources */, DB92CF7225E7BB98002C1017 /* PollOptionTableViewCell.swift in Sources */, DB72602725E36A6F00235243 /* MastodonServerRulesViewModel.swift in Sources */, @@ -3273,7 +3279,6 @@ DBF9814A265E24F500E4BA07 /* ProfileFieldCollectionViewHeaderFooterView.swift in Sources */, 2D939AB525EDD8A90076FA61 /* String.swift in Sources */, DB4481B925EE289600BEFB67 /* UITableView.swift in Sources */, - 2D35238126256F690031AF25 /* NotificationTableViewCell.swift in Sources */, DBE3CDBB261C427900430CC6 /* TimelineHeaderTableViewCell.swift in Sources */, DBCBCBFF2680AE98000F5B51 /* AsyncHomeTimelineViewModel.swift in Sources */, 0FAA101C25E10E760017CCDE /* UIFont.swift in Sources */, @@ -3749,6 +3754,16 @@ name = LaunchScreen.storyboard; sourceTree = ""; }; + DB443CD0269415D200159B29 /* Localizable.stringsdict */ = { + isa = PBXVariantGroup; + children = ( + DB443CCF269415D200159B29 /* en */, + DB443CD1269415D800159B29 /* ar */, + ); + name = Localizable.stringsdict; + path = /Users/mainasuk/Developer/Mastodon/Mastodon/Resources; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ @@ -3879,7 +3894,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 29; + CURRENT_PROJECT_VERSION = 30; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -3887,7 +3902,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.8.4; + MARKETING_VERSION = 0.8.5; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3906,7 +3921,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 29; + CURRENT_PROJECT_VERSION = 30; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -3914,7 +3929,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.8.4; + MARKETING_VERSION = 0.8.5; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -4234,7 +4249,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 29; + CURRENT_PROJECT_VERSION = 30; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -4242,7 +4257,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.8.4; + MARKETING_VERSION = 0.8.5; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -4348,7 +4363,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 29; + CURRENT_PROJECT_VERSION = 30; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4356,7 +4371,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 0.8.4; + MARKETING_VERSION = 0.8.5; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -4467,7 +4482,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 29; + CURRENT_PROJECT_VERSION = 30; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -4475,7 +4490,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.8.4; + MARKETING_VERSION = 0.8.5; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -4581,7 +4596,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 29; + CURRENT_PROJECT_VERSION = 30; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4589,7 +4604,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 0.8.4; + MARKETING_VERSION = 0.8.5; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -4635,7 +4650,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 29; + CURRENT_PROJECT_VERSION = 30; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4643,7 +4658,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 0.8.4; + MARKETING_VERSION = 0.8.5; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -4658,7 +4673,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 29; + CURRENT_PROJECT_VERSION = 30; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4666,7 +4681,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 0.8.4; + MARKETING_VERSION = 0.8.5; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -4814,7 +4829,7 @@ repositoryURL = "https://github.com/TwidereProject/MetaTextView.git"; requirement = { kind = exactVersion; - version = 1.2.4; + version = 1.2.5; }; }; DB0E2D2C26833FF600865C3C /* XCRemoteSwiftPackageReference "Nuke-FLAnimatedImage-Plugin" */ = { diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index 0f3da3a13..ecde1bcc5 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,7 +12,7 @@ CoreDataStack.xcscheme_^#shared#^_ orderHint - 21 + 20 Mastodon - ASDK.xcscheme_^#shared#^_ @@ -37,7 +37,7 @@ NotificationService.xcscheme_^#shared#^_ orderHint - 20 + 21 SuppressBuildableAutocreation diff --git a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved index 817624a3c..e143e8a71 100644 --- a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -114,8 +114,8 @@ "repositoryURL": "https://github.com/TwidereProject/MetaTextView.git", "state": { "branch": null, - "revision": "28e53130d16f12e0eeb479d83b77a0a718ef2088", - "version": "1.2.4" + "revision": "9ba4027ed0a88185ce95bb1773620c2ceaa9f3bb", + "version": "1.2.5" } }, { diff --git a/Mastodon/Diffiable/Section/AutoCompleteSection.swift b/Mastodon/Diffiable/Section/AutoCompleteSection.swift index 8de32a284..c5e8891a0 100644 --- a/Mastodon/Diffiable/Section/AutoCompleteSection.swift +++ b/Mastodon/Diffiable/Section/AutoCompleteSection.swift @@ -55,11 +55,7 @@ extension AutoCompleteSection { .prefix(2) .compactMap { Int($0.accounts) } .reduce(0, +) - if count > 1 { - return L10n.Scene.Compose.AutoComplete.multiplePeopleTalking(count) - } else { - return L10n.Scene.Compose.AutoComplete.singlePeopleTalking(count) - } + return L10n.Plural.peopleTalking(count) }() cell.avatarImageView.isHidden = true } diff --git a/Mastodon/Diffiable/Section/NotificationSection.swift b/Mastodon/Diffiable/Section/NotificationSection.swift index 42bef444d..77098a935 100644 --- a/Mastodon/Diffiable/Section/NotificationSection.swift +++ b/Mastodon/Diffiable/Section/NotificationSection.swift @@ -46,10 +46,6 @@ extension NotificationSection { ), into: cell.avatarImageView ) - cell.avatarImageView.gesture().sink { [weak cell] _ in - cell?.delegate?.userAvatarDidPressed(notification: notification) - } - .store(in: &cell.disposeBag) cell.actionImageView.image = UIImage( systemName: notification.notificationType.actionImageName, withConfiguration: UIImage.SymbolConfiguration( diff --git a/Mastodon/Diffiable/Section/StatusSection.swift b/Mastodon/Diffiable/Section/StatusSection.swift index 50e084529..d0d37a456 100644 --- a/Mastodon/Diffiable/Section/StatusSection.swift +++ b/Mastodon/Diffiable/Section/StatusSection.swift @@ -479,21 +479,13 @@ extension StatusSection { cell.threadMetaView.dateLabel.accessibilityLabel = DateFormatter.localizedString(from: status.createdAt, dateStyle: .medium, timeStyle: .short) let reblogCountTitle: String = { let count = status.reblogsCount.intValue - if count > 1 { - return L10n.Scene.Thread.Reblog.multiple(String(count)) - } else { - return L10n.Scene.Thread.Reblog.single(String(count)) - } + return L10n.Plural.Count.reblog(count) }() cell.threadMetaView.reblogButton.setTitle(reblogCountTitle, for: .normal) let favoriteCountTitle: String = { let count = status.favouritesCount.intValue - if count > 1 { - return L10n.Scene.Thread.Favorite.multiple(String(count)) - } else { - return L10n.Scene.Thread.Favorite.single(String(count)) - } + return L10n.Plural.Count.favorite(count) }() cell.threadMetaView.favoriteButton.setTitle(favoriteCountTitle, for: .normal) @@ -832,18 +824,10 @@ extension StatusSection { cell.statusView.pollVoteCountLabel.text = { if poll.multiple { let count = poll.votersCount?.intValue ?? 0 - if count > 1 { - return L10n.Common.Controls.Status.Poll.VoterCount.single(count) - } else { - return L10n.Common.Controls.Status.Poll.VoterCount.multiple(count) - } + return L10n.Plural.Count.voter(count) } else { let count = poll.votesCount.intValue - if count > 1 { - return L10n.Common.Controls.Status.Poll.VoteCount.single(count) - } else { - return L10n.Common.Controls.Status.Poll.VoteCount.multiple(count) - } + return L10n.Plural.Count.vote(count) } }() if poll.expired { diff --git a/Mastodon/Generated/Strings.swift b/Mastodon/Generated/Strings.swift index e34ec45f5..163b13da7 100644 --- a/Mastodon/Generated/Strings.swift +++ b/Mastodon/Generated/Strings.swift @@ -299,26 +299,6 @@ internal enum L10n { } /// Vote internal static let vote = L10n.tr("Localizable", "Common.Controls.Status.Poll.Vote") - internal enum VoteCount { - /// %d votes - internal static func multiple(_ p1: Int) -> String { - return L10n.tr("Localizable", "Common.Controls.Status.Poll.VoteCount.Multiple", p1) - } - /// %d vote - internal static func single(_ p1: Int) -> String { - return L10n.tr("Localizable", "Common.Controls.Status.Poll.VoteCount.Single", p1) - } - } - internal enum VoterCount { - /// %d voters - internal static func multiple(_ p1: Int) -> String { - return L10n.tr("Localizable", "Common.Controls.Status.Poll.VoterCount.Multiple", p1) - } - /// %d voter - internal static func single(_ p1: Int) -> String { - return L10n.tr("Localizable", "Common.Controls.Status.Poll.VoterCount.Single", p1) - } - } } internal enum Tag { /// Email @@ -400,14 +380,6 @@ internal enum L10n { } } } - internal enum Countable { - internal enum Photo { - /// photos - internal static let multiple = L10n.tr("Localizable", "Common.Countable.Photo.Multiple") - /// photo - internal static let single = L10n.tr("Localizable", "Common.Countable.Photo.Single") - } - } } internal enum Scene { @@ -459,14 +431,6 @@ internal enum L10n { internal static let video = L10n.tr("Localizable", "Scene.Compose.Attachment.Video") } internal enum AutoComplete { - /// %ld people talking - internal static func multiplePeopleTalking(_ p1: Int) -> String { - return L10n.tr("Localizable", "Scene.Compose.AutoComplete.MultiplePeopleTalking", p1) - } - /// %ld people talking - internal static func singlePeopleTalking(_ p1: Int) -> String { - return L10n.tr("Localizable", "Scene.Compose.AutoComplete.SinglePeopleTalking", p1) - } /// Space to add internal static let spaceToAdd = L10n.tr("Localizable", "Scene.Compose.AutoComplete.SpaceToAdd") } @@ -634,10 +598,6 @@ internal enum L10n { } } internal enum Profile { - /// %@ posts - internal static func subtitle(_ p1: Any) -> String { - return L10n.tr("Localizable", "Scene.Profile.Subtitle", String(describing: p1)) - } internal enum Dashboard { /// followers internal static let followers = L10n.tr("Localizable", "Scene.Profile.Dashboard.Followers") @@ -950,6 +910,12 @@ internal enum L10n { internal enum Settings { /// Settings internal static let title = L10n.tr("Localizable", "Scene.Settings.Title") + internal enum Footer { + /// Mastodon is open source software. You can contribute or report issues on GitHub at %@ (%@) + internal static func mastodonDescription(_ p1: Any, _ p2: Any) -> String { + return L10n.tr("Localizable", "Scene.Settings.Footer.MastodonDescription", String(describing: p1), String(describing: p2)) + } + } internal enum Keyboard { /// Close Settings Window internal static let closeSettingsWindow = L10n.tr("Localizable", "Scene.Settings.Keyboard.CloseSettingsWindow") @@ -1026,32 +992,43 @@ internal enum L10n { internal static func title(_ p1: Any) -> String { return L10n.tr("Localizable", "Scene.Thread.Title", String(describing: p1)) } - internal enum Favorite { - /// %@ favorites - internal static func multiple(_ p1: Any) -> String { - return L10n.tr("Localizable", "Scene.Thread.Favorite.Multiple", String(describing: p1)) - } - /// %@ favorite - internal static func single(_ p1: Any) -> String { - return L10n.tr("Localizable", "Scene.Thread.Favorite.Single", String(describing: p1)) - } - } - internal enum Reblog { - /// %@ reblogs - internal static func multiple(_ p1: Any) -> String { - return L10n.tr("Localizable", "Scene.Thread.Reblog.Multiple", String(describing: p1)) - } - /// %@ reblog - internal static func single(_ p1: Any) -> String { - return L10n.tr("Localizable", "Scene.Thread.Reblog.Single", String(describing: p1)) - } - } } internal enum Welcome { /// Social networking\nback in your hands. internal static let slogan = L10n.tr("Localizable", "Scene.Welcome.Slogan") } } + + internal enum Plural { + /// Plural format key: "%#@count_people_talking@" + internal static func peopleTalking(_ p1: Int) -> String { + return L10n.tr("Localizable", "plural.people_talking", p1) + } + internal enum Count { + /// Plural format key: "%#@favorite_count@" + internal static func favorite(_ p1: Int) -> String { + return L10n.tr("Localizable", "plural.count.favorite", p1) + } + /// Plural format key: "%#@reblog_count@" + internal static func reblog(_ p1: Int) -> String { + return L10n.tr("Localizable", "plural.count.reblog", p1) + } + /// Plural format key: "%#@vote_count@" + internal static func vote(_ p1: Int) -> String { + return L10n.tr("Localizable", "plural.count.vote", p1) + } + /// Plural format key: "%#@voter_count@" + internal static func voter(_ p1: Int) -> String { + return L10n.tr("Localizable", "plural.count.voter", p1) + } + internal enum MetricFormatted { + /// Plural format key: "%@ %#@post_count@" + internal static func post(_ p1: Any, _ p2: Int) -> String { + return L10n.tr("Localizable", "plural.count.metric_formatted.post", String(describing: p1), p2) + } + } + } + } } // swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length // swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces diff --git a/Mastodon/Resources/Assets.xcassets/Asset/email.imageset/Contents.json b/Mastodon/Resources/Assets.xcassets/Asset/email.imageset/Contents.json index ae4670472..ba3df05b6 100644 --- a/Mastodon/Resources/Assets.xcassets/Asset/email.imageset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/Asset/email.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "mastodon.with.mail.svg", + "filename" : "c1 1.svg", "idiom" : "universal" } ], diff --git a/Mastodon/Resources/Assets.xcassets/Asset/email.imageset/mastodon.with.mail.svg b/Mastodon/Resources/Assets.xcassets/Asset/email.imageset/c1 1.svg similarity index 100% rename from Mastodon/Resources/Assets.xcassets/Asset/email.imageset/mastodon.with.mail.svg rename to Mastodon/Resources/Assets.xcassets/Asset/email.imageset/c1 1.svg diff --git a/Mastodon/Resources/ar.lproj/Localizable.strings b/Mastodon/Resources/ar.lproj/Localizable.strings index 3e2394724..82745b786 100644 --- a/Mastodon/Resources/ar.lproj/Localizable.strings +++ b/Mastodon/Resources/ar.lproj/Localizable.strings @@ -101,10 +101,6 @@ Please check your internet connection."; "Common.Controls.Status.Poll.Closed" = "Closed"; "Common.Controls.Status.Poll.TimeLeft" = "%@ left"; "Common.Controls.Status.Poll.Vote" = "Vote"; -"Common.Controls.Status.Poll.VoteCount.Multiple" = "%d votes"; -"Common.Controls.Status.Poll.VoteCount.Single" = "%d vote"; -"Common.Controls.Status.Poll.VoterCount.Multiple" = "%d voters"; -"Common.Controls.Status.Poll.VoterCount.Single" = "%d voter"; "Common.Controls.Status.ShowPost" = "Show Post"; "Common.Controls.Status.ShowUserProfile" = "Show user profile"; "Common.Controls.Status.Tag.Email" = "Email"; @@ -140,8 +136,6 @@ Your account looks like this to them."; "Common.Controls.Timeline.Loader.ShowMoreReplies" = "Show more replies"; "Common.Controls.Timeline.Timestamp.Now" = "Now"; "Common.Controls.Timeline.Timestamp.TimeAgo" = "%@ ago"; -"Common.Countable.Photo.Multiple" = "photos"; -"Common.Countable.Photo.Single" = "photo"; "Scene.Compose.Accessibility.AppendAttachment" = "Append attachment"; "Scene.Compose.Accessibility.AppendPoll" = "Append poll"; "Scene.Compose.Accessibility.CustomEmojiPicker" = "Custom emoji picker"; @@ -157,8 +151,6 @@ uploaded to Mastodon."; "Scene.Compose.Attachment.DescriptionVideo" = "Describe what’s happening for low vision people..."; "Scene.Compose.Attachment.Photo" = "photo"; "Scene.Compose.Attachment.Video" = "video"; -"Scene.Compose.AutoComplete.MultiplePeopleTalking" = "%ld people talking"; -"Scene.Compose.AutoComplete.SinglePeopleTalking" = "%ld people talking"; "Scene.Compose.AutoComplete.SpaceToAdd" = "Space to add"; "Scene.Compose.ComposeAction" = "Publish"; "Scene.Compose.ContentInputPlaceholder" = "Type or paste what’s on your mind"; @@ -235,7 +227,6 @@ tap the link to confirm your account."; "Scene.Profile.SegmentedControl.Media" = "Media"; "Scene.Profile.SegmentedControl.Posts" = "Posts"; "Scene.Profile.SegmentedControl.Replies" = "Replies"; -"Scene.Profile.Subtitle" = "%@ posts"; "Scene.PublicTimeline.Title" = "Public"; "Scene.Register.Error.Item.Agreement" = "Agreement"; "Scene.Register.Error.Item.Email" = "Email"; @@ -319,6 +310,7 @@ any server."; "Scene.ServerRules.Subtitle" = "These rules are set by the admins of %@."; "Scene.ServerRules.TermsOfService" = "terms of service"; "Scene.ServerRules.Title" = "Some ground rules."; +"Scene.Settings.Footer.MastodonDescription" = "Mastodon is open source software. You can contribute or report issues on GitHub at %@ (%@)"; "Scene.Settings.Keyboard.CloseSettingsWindow" = "Close Settings Window"; "Scene.Settings.Section.Appearance.Automatic" = "Automatic"; "Scene.Settings.Section.Appearance.Dark" = "Always Dark"; @@ -345,10 +337,6 @@ any server."; "Scene.SuggestionAccount.FollowExplain" = "When you follow someone, you’ll see their posts in your home feed."; "Scene.SuggestionAccount.Title" = "Find People to Follow"; "Scene.Thread.BackTitle" = "Post"; -"Scene.Thread.Favorite.Multiple" = "%@ favorites"; -"Scene.Thread.Favorite.Single" = "%@ favorite"; -"Scene.Thread.Reblog.Multiple" = "%@ reblogs"; -"Scene.Thread.Reblog.Single" = "%@ reblog"; "Scene.Thread.Title" = "Post from %@"; "Scene.Welcome.Slogan" = "Social networking back in your hands."; \ No newline at end of file diff --git a/Mastodon/Resources/ar.lproj/Localizable.stringsdict b/Mastodon/Resources/ar.lproj/Localizable.stringsdict new file mode 100644 index 000000000..e9d64796f --- /dev/null +++ b/Mastodon/Resources/ar.lproj/Localizable.stringsdict @@ -0,0 +1,138 @@ + + + + + plural.count.metric_formatted.post + + NSStringLocalizedFormatKey + %@ %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + posts + one + post + few + posts + many + posts + other + posts + + + plural.count.favorite + + NSStringLocalizedFormatKey + %#@favorite_count@ + favorite_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 favorites + one + 1 favorite + few + %ld favorites + many + %ld favorites + other + %ld favorites + + + plural.count.reblog + + NSStringLocalizedFormatKey + %#@reblog_count@ + reblog_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 reblogs + one + 1 reblog + few + %ld reblogs + many + %ld reblogs + other + %ld reblogs + + + plural.count.vote + + NSStringLocalizedFormatKey + %#@vote_count@ + vote_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 votes + one + 1 vote + few + %ld votes + many + %ld votes + other + %ld votes + + + plural.count.voter + + NSStringLocalizedFormatKey + %#@voter_count@ + voter_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 voters + one + 1 voter + few + %ld voters + many + %ld voters + other + %ld voters + + + plural.people_talking + + NSStringLocalizedFormatKey + %#@count_people_talking@ + count_people_talking + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 people talking + one + 1 people talking + few + %ld people talking + many + %ld people talking + other + %ld people talking + + + + diff --git a/Mastodon/Resources/en.lproj/Localizable.strings b/Mastodon/Resources/en.lproj/Localizable.strings index 3e2394724..82745b786 100644 --- a/Mastodon/Resources/en.lproj/Localizable.strings +++ b/Mastodon/Resources/en.lproj/Localizable.strings @@ -101,10 +101,6 @@ Please check your internet connection."; "Common.Controls.Status.Poll.Closed" = "Closed"; "Common.Controls.Status.Poll.TimeLeft" = "%@ left"; "Common.Controls.Status.Poll.Vote" = "Vote"; -"Common.Controls.Status.Poll.VoteCount.Multiple" = "%d votes"; -"Common.Controls.Status.Poll.VoteCount.Single" = "%d vote"; -"Common.Controls.Status.Poll.VoterCount.Multiple" = "%d voters"; -"Common.Controls.Status.Poll.VoterCount.Single" = "%d voter"; "Common.Controls.Status.ShowPost" = "Show Post"; "Common.Controls.Status.ShowUserProfile" = "Show user profile"; "Common.Controls.Status.Tag.Email" = "Email"; @@ -140,8 +136,6 @@ Your account looks like this to them."; "Common.Controls.Timeline.Loader.ShowMoreReplies" = "Show more replies"; "Common.Controls.Timeline.Timestamp.Now" = "Now"; "Common.Controls.Timeline.Timestamp.TimeAgo" = "%@ ago"; -"Common.Countable.Photo.Multiple" = "photos"; -"Common.Countable.Photo.Single" = "photo"; "Scene.Compose.Accessibility.AppendAttachment" = "Append attachment"; "Scene.Compose.Accessibility.AppendPoll" = "Append poll"; "Scene.Compose.Accessibility.CustomEmojiPicker" = "Custom emoji picker"; @@ -157,8 +151,6 @@ uploaded to Mastodon."; "Scene.Compose.Attachment.DescriptionVideo" = "Describe what’s happening for low vision people..."; "Scene.Compose.Attachment.Photo" = "photo"; "Scene.Compose.Attachment.Video" = "video"; -"Scene.Compose.AutoComplete.MultiplePeopleTalking" = "%ld people talking"; -"Scene.Compose.AutoComplete.SinglePeopleTalking" = "%ld people talking"; "Scene.Compose.AutoComplete.SpaceToAdd" = "Space to add"; "Scene.Compose.ComposeAction" = "Publish"; "Scene.Compose.ContentInputPlaceholder" = "Type or paste what’s on your mind"; @@ -235,7 +227,6 @@ tap the link to confirm your account."; "Scene.Profile.SegmentedControl.Media" = "Media"; "Scene.Profile.SegmentedControl.Posts" = "Posts"; "Scene.Profile.SegmentedControl.Replies" = "Replies"; -"Scene.Profile.Subtitle" = "%@ posts"; "Scene.PublicTimeline.Title" = "Public"; "Scene.Register.Error.Item.Agreement" = "Agreement"; "Scene.Register.Error.Item.Email" = "Email"; @@ -319,6 +310,7 @@ any server."; "Scene.ServerRules.Subtitle" = "These rules are set by the admins of %@."; "Scene.ServerRules.TermsOfService" = "terms of service"; "Scene.ServerRules.Title" = "Some ground rules."; +"Scene.Settings.Footer.MastodonDescription" = "Mastodon is open source software. You can contribute or report issues on GitHub at %@ (%@)"; "Scene.Settings.Keyboard.CloseSettingsWindow" = "Close Settings Window"; "Scene.Settings.Section.Appearance.Automatic" = "Automatic"; "Scene.Settings.Section.Appearance.Dark" = "Always Dark"; @@ -345,10 +337,6 @@ any server."; "Scene.SuggestionAccount.FollowExplain" = "When you follow someone, you’ll see their posts in your home feed."; "Scene.SuggestionAccount.Title" = "Find People to Follow"; "Scene.Thread.BackTitle" = "Post"; -"Scene.Thread.Favorite.Multiple" = "%@ favorites"; -"Scene.Thread.Favorite.Single" = "%@ favorite"; -"Scene.Thread.Reblog.Multiple" = "%@ reblogs"; -"Scene.Thread.Reblog.Single" = "%@ reblog"; "Scene.Thread.Title" = "Post from %@"; "Scene.Welcome.Slogan" = "Social networking back in your hands."; \ No newline at end of file diff --git a/Mastodon/Resources/en.lproj/Localizable.stringsdict b/Mastodon/Resources/en.lproj/Localizable.stringsdict new file mode 100644 index 000000000..b002a41c3 --- /dev/null +++ b/Mastodon/Resources/en.lproj/Localizable.stringsdict @@ -0,0 +1,138 @@ + + + + + plural.count.metric_formatted.post + + NSStringLocalizedFormatKey + %@ %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + posts + one + post + few + posts + many + posts + other + posts + + + plural.count.favorite + + NSStringLocalizedFormatKey + %#@favorite_count@ + favorite_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 favorites + one + 1 favorite + few + %ld favorites + many + %ld favorites + other + %ld favorites + + + plural.count.reblog + + NSStringLocalizedFormatKey + %#@reblog_count@ + reblog_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 reblogs + one + 1 reblog + few + %ld reblogs + many + %ld reblogs + other + %ld reblogs + + + plural.count.vote + + NSStringLocalizedFormatKey + %#@vote_count@ + vote_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 votes + one + 1 vote + few + %ld votes + many + %ld votes + other + %ld votes + + + plural.count.voter + + NSStringLocalizedFormatKey + %#@voter_count@ + voter_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 voters + one + 1 voter + few + %ld voters + many + %ld voters + other + %ld voters + + + plural.people_talking + + NSStringLocalizedFormatKey + %#@count_people_talking@ + count_people_talking + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 people talking + one + 1 people talking + few + %ld people talking + many + %ld people talking + other + %ld people talking + + + + diff --git a/Mastodon/Scene/Compose/ComposeViewController.swift b/Mastodon/Scene/Compose/ComposeViewController.swift index a3c56b3e2..f399907b3 100644 --- a/Mastodon/Scene/Compose/ComposeViewController.swift +++ b/Mastodon/Scene/Compose/ComposeViewController.swift @@ -144,13 +144,12 @@ extension ComposeViewController { self.title = title } .store(in: &disposeBag) - view.backgroundColor = ThemeService.shared.currentTheme.value.systemElevatedBackgroundColor + self.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.view.backgroundColor = theme.systemElevatedBackgroundColor - self.tableView.backgroundColor = theme.systemElevatedBackgroundColor + self.setupBackgroundColor(theme: theme) } .store(in: &disposeBag) navigationItem.leftBarButtonItem = cancelBarButtonItem @@ -607,6 +606,11 @@ extension ComposeViewController { // }) } + private func setupBackgroundColor(theme: Theme) { + view.backgroundColor = theme.systemElevatedBackgroundColor + tableView.backgroundColor = theme.systemElevatedBackgroundColor + } + } extension ComposeViewController { @@ -809,7 +813,11 @@ extension ComposeViewController: ComposeToolbarViewDelegate { } func composeToolbarView(_ composeToolbarView: ComposeToolbarView, pollButtonDidPressed sender: UIButton) { + // toggle poll composing state viewModel.isPollComposing.value.toggle() + + // cancel custom picker input + viewModel.isCustomEmojiComposing.value = false // setup initial poll option if needs if viewModel.isPollComposing.value, viewModel.pollOptionAttributes.value.isEmpty { @@ -831,6 +839,9 @@ extension ComposeViewController: ComposeToolbarViewDelegate { } func composeToolbarView(_ composeToolbarView: ComposeToolbarView, contentWarningButtonDidPressed sender: UIButton) { + // cancel custom picker input + viewModel.isCustomEmojiComposing.value = false + // restore first responder for text editor when content warning dismiss if viewModel.isContentWarningComposing.value { if contentWarningEditorTextView()?.isFirstResponder == true { @@ -860,20 +871,45 @@ extension ComposeViewController { let repliedToCellFrame = viewModel.repliedToCellFrame.value guard repliedToCellFrame != .zero else { return } - let throttle = viewModel.repliedToCellFrame.value.height - scrollView.adjustedContentInset.top - // print("\(throttle) - \(scrollView.contentOffset.y)") + + // try to find some patterns: + // print(""" + // repliedToCellFrame: \(viewModel.repliedToCellFrame.value.height) + // scrollView.contentOffset.y: \(scrollView.contentOffset.y) + // scrollView.contentSize.height: \(scrollView.contentSize.height) + // scrollView.frame: \(scrollView.frame) + // scrollView.adjustedContentInset.top: \(scrollView.adjustedContentInset.top) + // scrollView.adjustedContentInset.bottom: \(scrollView.adjustedContentInset.bottom) + // """) switch viewModel.collectionViewState.value { case .fold: - if scrollView.contentOffset.y < throttle { + os_log("%{public}s[%{public}ld], %{public}s: fold", ((#file as NSString).lastPathComponent), #line, #function) + guard velocity.y < 0 else { return } + let offsetY = scrollView.contentOffset.y + scrollView.adjustedContentInset.top + if offsetY < -44 { + tableView.contentInset.top = 0 + targetContentOffset.pointee = CGPoint(x: 0, y: -scrollView.adjustedContentInset.top) viewModel.collectionViewState.value = .expand } - os_log("%{public}s[%{public}ld], %{public}s: fold", ((#file as NSString).lastPathComponent), #line, #function) case .expand: - if scrollView.contentOffset.y > -44 { + os_log("%{public}s[%{public}ld], %{public}s: expand", ((#file as NSString).lastPathComponent), #line, #function) + guard velocity.y > 0 else { return } + // check if top across + let topOffset = (scrollView.contentOffset.y + scrollView.adjustedContentInset.top) - repliedToCellFrame.height + + // check if bottom bounce + let bottomOffsetY = scrollView.contentOffset.y + (scrollView.frame.height - scrollView.adjustedContentInset.bottom) + let bottomOffset = bottomOffsetY - scrollView.contentSize.height + + if topOffset > 44 { + // do not interrupt user scrolling + viewModel.collectionViewState.value = .fold + } else if bottomOffset > 44 { + tableView.contentInset.top = -repliedToCellFrame.height + targetContentOffset.pointee = CGPoint(x: 0, y: -repliedToCellFrame.height) viewModel.collectionViewState.value = .fold - os_log("%{public}s[%{public}ld], %{public}s: expand", ((#file as NSString).lastPathComponent), #line, #function) } } } @@ -910,7 +946,8 @@ extension ComposeViewController: UICollectionViewDelegate { extension ComposeViewController: UIAdaptivePresentationControllerDelegate { func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle { - return traitCollection.userInterfaceIdiom == .pad ? .formSheet : .automatic + return .fullScreen + //return traitCollection.userInterfaceIdiom == .pad ? .formSheet : .automatic } func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool { diff --git a/Mastodon/Scene/Compose/ComposeViewModel+Diffable.swift b/Mastodon/Scene/Compose/ComposeViewModel+Diffable.swift index fa38afcfe..59de0d60a 100644 --- a/Mastodon/Scene/Compose/ComposeViewModel+Diffable.swift +++ b/Mastodon/Scene/Compose/ComposeViewModel+Diffable.swift @@ -253,6 +253,8 @@ extension ComposeViewModel: UITableViewDataSource { cell.statusContentWarningEditorView.alpha = 0 UIView.animate(withDuration: 0.33, delay: 0, options: [.curveEaseOut]) { cell.statusContentWarningEditorView.alpha = 1 + tableView.beginUpdates() + tableView.endUpdates() } completion: { _ in // do nothing } diff --git a/Mastodon/Scene/Compose/ComposeViewModel.swift b/Mastodon/Scene/Compose/ComposeViewModel.swift index 15dd0ef3b..69b9836ac 100644 --- a/Mastodon/Scene/Compose/ComposeViewModel.swift +++ b/Mastodon/Scene/Compose/ComposeViewModel.swift @@ -29,7 +29,7 @@ final class ComposeViewModel: NSObject { let selectedStatusVisibility: CurrentValueSubject let activeAuthentication: CurrentValueSubject let activeAuthenticationBox: CurrentValueSubject - let traitCollectionDidChangePublisher = CurrentValueSubject(Void()) // use CurrentValueSubject to make intial event emit + let traitCollectionDidChangePublisher = CurrentValueSubject(Void()) // use CurrentValueSubject to make initial event emit let repliedToCellFrame = CurrentValueSubject(.zero) let autoCompleteRetryLayoutTimes = CurrentValueSubject(0) let autoCompleteInfo = CurrentValueSubject(nil) diff --git a/Mastodon/Scene/Compose/View/StatusContentWarningEditorView.swift b/Mastodon/Scene/Compose/View/StatusContentWarningEditorView.swift index d782f7024..1d7ae65e3 100644 --- a/Mastodon/Scene/Compose/View/StatusContentWarningEditorView.swift +++ b/Mastodon/Scene/Compose/View/StatusContentWarningEditorView.swift @@ -13,7 +13,7 @@ final class StatusContentWarningEditorView: UIView { // default hidden let containerBackgroundView: UIView = { let view = UIView() - view.backgroundColor = Asset.Colors.Background.secondarySystemBackground.color + view.backgroundColor = ThemeService.shared.currentTheme.value.secondarySystemBackgroundColor return view }() @@ -57,29 +57,43 @@ extension StatusContentWarningEditorView { containerBackgroundView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 1024), containerBackgroundView.bottomAnchor.constraint(equalTo: bottomAnchor), ]) - - iconImageView.translatesAutoresizingMaskIntoConstraints = false - addSubview(iconImageView) + + let containerStackView = UIStackView() + containerStackView.axis = .horizontal + containerStackView.translatesAutoresizingMaskIntoConstraints = false + addSubview(containerStackView) NSLayoutConstraint.activate([ - iconImageView.centerYAnchor.constraint(equalTo: centerYAnchor), - iconImageView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), - iconImageView.widthAnchor.constraint(equalToConstant: StatusView.avatarImageSize.width).priority(.defaultHigh), // center alignment to avatar - ]) - iconImageView.setContentHuggingPriority(.required - 2, for: .horizontal) - - textView.translatesAutoresizingMaskIntoConstraints = false - addSubview(textView) - NSLayoutConstraint.activate([ - textView.centerYAnchor.constraint(equalTo: centerYAnchor), - textView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor, constant: 6).priority(.required - 1), - textView.leadingAnchor.constraint(equalTo: iconImageView.trailingAnchor, constant: StatusView.avatarToLabelSpacing - 4), // align to name label. minus magic 4pt to remove addition inset - textView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), - bottomAnchor.constraint(greaterThanOrEqualTo: textView.bottomAnchor, constant: 6).priority(.required - 1), - //textView.heightAnchor.constraint(greaterThanOrEqualToConstant: 44).priority(.defaultHigh), + containerStackView.topAnchor.constraint(equalTo: topAnchor, constant: 6), + containerStackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), + containerStackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), + bottomAnchor.constraint(equalTo: containerStackView.bottomAnchor, constant: 6), ]) - textView.setContentHuggingPriority(.required - 1, for: .vertical) - textView.setContentCompressionResistancePriority(.required - 1, for: .vertical) + containerStackView.addArrangedSubview(iconImageView) + iconImageView.setContentHuggingPriority(.required - 1, for: .horizontal) + containerStackView.addArrangedSubview(textView) + +// iconImageView.translatesAutoresizingMaskIntoConstraints = false +// addSubview(iconImageView) +// NSLayoutConstraint.activate([ +// iconImageView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), +// iconImageView.widthAnchor.constraint(equalToConstant: StatusView.avatarImageSize.width).priority(.defaultHigh), // center alignment to avatar +// ]) +// iconImageView.setContentHuggingPriority(.required - 2, for: .horizontal) +// +// textView.translatesAutoresizingMaskIntoConstraints = false +// addSubview(textView) +// NSLayoutConstraint.activate([ +// textView.centerYAnchor.constraint(equalTo: centerYAnchor), +// textView.topAnchor.constraint(equalTo: topAnchor, constant: 6), +// textView.leadingAnchor.constraint(equalTo: iconImageView.trailingAnchor, constant: StatusView.avatarToLabelSpacing - 4), // align to name label. minus magic 4pt to remove addition inset +// textView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), +// bottomAnchor.constraint(equalTo: textView.bottomAnchor, constant: 6), +// textView.heightAnchor.constraint(greaterThanOrEqualToConstant: 44).priority(.defaultHigh), +// ]) +// +// textView.setContentHuggingPriority(.required - 1, for: .vertical) +// textView.setContentCompressionResistancePriority(.required - 1, for: .vertical) } } diff --git a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift index 858a7b0f2..c0de5c532 100644 --- a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift +++ b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift @@ -58,7 +58,8 @@ extension HashtagTimelineViewController { title = "#\(viewModel.hashtag)" titleView.update(title: viewModel.hashtag, subtitle: nil, emojiDict: [:]) navigationItem.titleView = titleView - + + view.backgroundColor = ThemeService.shared.currentTheme.value.secondarySystemBackgroundColor ThemeService.shared.currentTheme .receive(on: RunLoop.main) .sink { [weak self] theme in diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift index 69eff4a82..7c1f79dbd 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift @@ -31,7 +31,11 @@ extension HomeTimelineViewController { guard let self = self else { return } self.showWelcomeAction(action) }, - UIAction(title: "Show Or Remove EmptyView", image: UIImage(systemName: "clear"), attributes: []) { [weak self] action in + UIAction(title: "Show Confirm Email", image: UIImage(systemName: "envelope"), attributes: []) { [weak self] action in + guard let self = self else { return } + self.showConfirmEmail(action) + }, + UIAction(title: "Toggle EmptyView", image: UIImage(systemName: "clear"), attributes: []) { [weak self] action in guard let self = self else { return } if self.emptyView.superview != nil { self.emptyView.removeFromSuperview() @@ -310,6 +314,11 @@ extension HomeTimelineViewController { @objc private func showWelcomeAction(_ sender: UIAction) { coordinator.present(scene: .welcome, from: self, transition: .modal(animated: true, completion: nil)) } + + @objc private func showConfirmEmail(_ sender: UIAction) { + let mastodonConfirmEmailViewModel = MastodonConfirmEmailViewModel() + coordinator.present(scene: .mastodonConfirmEmail(viewModel: mastodonConfirmEmailViewModel), from: nil, transition: .modal(animated: true, completion: nil)) + } @objc private func showPublicTimelineAction(_ sender: UIAction) { coordinator.present(scene: .publicTimeline, from: self, transition: .show) diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift index 333a42ae0..fcd35cec1 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift @@ -80,8 +80,8 @@ extension HomeTimelineViewController { override func viewDidLoad() { super.viewDidLoad() - title = L10n.Scene.HomeTimeline.title + view.backgroundColor = ThemeService.shared.currentTheme.value.secondarySystemBackgroundColor ThemeService.shared.currentTheme .receive(on: RunLoop.main) .sink { [weak self] theme in diff --git a/Mastodon/Scene/Notification/NotificationViewController.swift b/Mastodon/Scene/Notification/NotificationViewController.swift index 144d06b8a..f383781e9 100644 --- a/Mastodon/Scene/Notification/NotificationViewController.swift +++ b/Mastodon/Scene/Notification/NotificationViewController.swift @@ -12,6 +12,7 @@ import GameplayKit import MastodonSDK import OSLog import UIKit +import ActiveLabel import Meta import MetaTextView @@ -47,7 +48,8 @@ final class NotificationViewController: UIViewController, NeedsDependency { extension NotificationViewController { override func viewDidLoad() { super.viewDidLoad() - + + view.backgroundColor = ThemeService.shared.currentTheme.value.secondarySystemBackgroundColor ThemeService.shared.currentTheme .receive(on: RunLoop.main) .sink { [weak self] theme in @@ -277,6 +279,35 @@ extension NotificationViewController: ContentOffsetAdjustableTimelineViewControl // MARK: - NotificationTableViewCellDelegate extension NotificationViewController: NotificationTableViewCellDelegate { + func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, avatarImageViewDidPressed imageView: UIImageView) { + guard let diffableDataSource = viewModel.diffableDataSource else { return } + guard let indexPath = tableView.indexPath(for: cell) else { return } + guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } + switch item { + case .notification(let objectID, _): + guard let notification = try? viewModel.fetchedResultsController.managedObjectContext.existingObject(with: objectID) as? MastodonNotification else { return } + let viewModel = ProfileViewModel(context: context, optionalMastodonUser: notification.account) + coordinator.present(scene: .profile(viewModel: viewModel), from: self, transition: .show) + default: + break + } + } + + func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, authorNameLabelDidPressed label: ActiveLabel) { + guard let diffableDataSource = viewModel.diffableDataSource else { return } + guard let indexPath = tableView.indexPath(for: cell) else { return } + guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } + switch item { + case .notification(let objectID, _): + guard let notification = try? viewModel.fetchedResultsController.managedObjectContext.existingObject(with: objectID) as? MastodonNotification else { return } + let viewModel = ProfileViewModel(context: context, optionalMastodonUser: notification.account) + coordinator.present(scene: .profile(viewModel: viewModel), from: self, transition: .show) + default: + break + } + } + + func notificationTableViewCell(_ cell: NotificationStatusTableViewCell, notification: MastodonNotification, acceptButtonDidPressed button: UIButton) { viewModel.acceptFollowRequest(notification: notification) } @@ -284,13 +315,6 @@ extension NotificationViewController: NotificationTableViewCellDelegate { func notificationTableViewCell(_ cell: NotificationStatusTableViewCell, notification: MastodonNotification, rejectButtonDidPressed button: UIButton) { viewModel.rejectFollowRequest(notification: notification) } - - func userAvatarDidPressed(notification: MastodonNotification) { - let viewModel = ProfileViewModel(context: context, optionalMastodonUser: notification.account) - DispatchQueue.main.async { - self.coordinator.present(scene: .profile(viewModel: viewModel), from: self, transition: .show) - } - } func userNameLabelDidPressed(notification: MastodonNotification) { let viewModel = CachedProfileViewModel(context: context, mastodonUser: notification.account) diff --git a/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift b/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift index 14a0ea5e0..75342b6c0 100644 --- a/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift +++ b/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift @@ -5,8 +5,10 @@ // Created by sxiaojian on 2021/4/14. // +import os.log import Combine import Foundation +import CoreDataStack import UIKit import ActiveLabel import MetaTextView @@ -14,6 +16,23 @@ import Meta import FLAnimatedImage import Nuke +protocol NotificationTableViewCellDelegate: AnyObject { + var context: AppContext! { get } + func parent() -> UIViewController + + func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, avatarImageViewDidPressed imageView: UIImageView) + func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, authorNameLabelDidPressed label: ActiveLabel) + + func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, revealContentWarningButtonDidPressed button: UIButton) + func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView) + func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, playerContainerView: PlayerContainerView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView) + func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta) + + func notificationTableViewCell(_ cell: NotificationStatusTableViewCell, notification: MastodonNotification, acceptButtonDidPressed button: UIButton) + func notificationTableViewCell(_ cell: NotificationStatusTableViewCell, notification: MastodonNotification, rejectButtonDidPressed button: UIButton) + +} + final class NotificationStatusTableViewCell: UITableViewCell, StatusCell { static let actionImageBorderWidth: CGFloat = 2 @@ -219,13 +238,12 @@ extension NotificationStatusTableViewCell { statusView.bottomAnchor.constraint(equalTo: statusContainerView.layoutMarginsGuide.bottomAnchor), ]) + 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.statusContainerView.backgroundColor = UIColor(dynamicProvider: { traitCollection in - return traitCollection.userInterfaceStyle == .light ? theme.systemBackgroundColor : theme.tertiarySystemGroupedBackgroundColor - }) + self.setupBackgroundColor(theme: theme) } .store(in: &disposeBag) // remove item don't display @@ -246,7 +264,14 @@ extension NotificationStatusTableViewCell { ]) statusView.delegate = self - + + let avatarImageViewTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer + avatarImageViewTapGestureRecognizer.addTarget(self, action: #selector(NotificationStatusTableViewCell.avatarImageViewTapGestureRecognizerHandler(_:))) + avatarImageView.addGestureRecognizer(avatarImageViewTapGestureRecognizer) + let authorNameLabelTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer + authorNameLabelTapGestureRecognizer.addTarget(self, action: #selector(NotificationStatusTableViewCell.authorNameLabelTapGestureRecognizerHandler(_:))) + nameLabel.addGestureRecognizer(authorNameLabelTapGestureRecognizer) + resetSeparatorLineLayout() } @@ -260,6 +285,28 @@ extension NotificationStatusTableViewCell { } +extension NotificationStatusTableViewCell { + + private func setupBackgroundColor(theme: Theme) { + statusContainerView.backgroundColor = UIColor(dynamicProvider: { traitCollection in + return traitCollection.userInterfaceStyle == .light ? theme.systemBackgroundColor : theme.tertiarySystemGroupedBackgroundColor + }) + } + +} + +extension NotificationStatusTableViewCell { + @objc private func avatarImageViewTapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + delegate?.notificationStatusTableViewCell(self, avatarImageViewDidPressed: avatarImageView) + } + + @objc private func authorNameLabelTapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + delegate?.notificationStatusTableViewCell(self, authorNameLabelDidPressed: nameLabel) + } +} + // MARK: - StatusViewDelegate extension NotificationStatusTableViewCell: StatusViewDelegate { diff --git a/Mastodon/Scene/Notification/TableViewCell/NotificationTableViewCell.swift b/Mastodon/Scene/Notification/TableViewCell/NotificationTableViewCell.swift deleted file mode 100644 index 8bdaaa146..000000000 --- a/Mastodon/Scene/Notification/TableViewCell/NotificationTableViewCell.swift +++ /dev/null @@ -1,265 +0,0 @@ -// -// NotificationTableViewCell.swift -// Mastodon -// -// Created by sxiaojian on 2021/4/13. -// - -import Combine -import CoreDataStack -import Foundation -import UIKit -import Meta -import MetaTextView -import ActiveLabel -import FLAnimatedImage -import Nuke - -protocol NotificationTableViewCellDelegate: AnyObject { - var context: AppContext! { get } - - func parent() -> UIViewController - - func userAvatarDidPressed(notification: MastodonNotification) - func userNameLabelDidPressed(notification: MastodonNotification) - - func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, revealContentWarningButtonDidPressed button: UIButton) - func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView) - func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, playerContainerView: PlayerContainerView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView) - func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta) - - func notificationTableViewCell(_ cell: NotificationStatusTableViewCell, notification: MastodonNotification, acceptButtonDidPressed button: UIButton) - func notificationTableViewCell(_ cell: NotificationStatusTableViewCell, notification: MastodonNotification, rejectButtonDidPressed button: UIButton) - -} - -//final class NotificationTableViewCell: UITableViewCell { -// static let actionImageBorderWidth: CGFloat = 2 -// -// var disposeBag = Set() -// -// var delegate: NotificationTableViewCellDelegate? -// -// var avatarImageViewTask: ImageTask? -// let avatarImageView: UIImageView = { -// let imageView = FLAnimatedImageView() -// imageView.layer.cornerRadius = 4 -// imageView.layer.cornerCurve = .continuous -// imageView.clipsToBounds = true -// return imageView -// }() -// -// let actionImageView: UIImageView = { -// let imageView = UIImageView() -// imageView.tintColor = Asset.Colors.Background.systemBackground.color -// return imageView -// }() -// -// let actionImageBackground: UIView = { -// let view = UIView() -// view.layer.cornerRadius = (24 + NotificationTableViewCell.actionImageBorderWidth) / 2 -// view.layer.cornerCurve = .continuous -// view.clipsToBounds = true -// view.layer.borderWidth = NotificationTableViewCell.actionImageBorderWidth -// view.layer.borderColor = Asset.Colors.Background.systemBackground.color.cgColor -// view.tintColor = Asset.Colors.Background.systemBackground.color -// return view -// }() -// -// let avatarContainer: UIView = { -// let view = UIView() -// return view -// }() -// -// let actionLabel: UILabel = { -// let label = UILabel() -// label.textColor = Asset.Colors.Label.secondary.color -// label.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .regular), maximumPointSize: 20) -// label.lineBreakMode = .byTruncatingTail -// return label -// }() -// -// let nameLabel: ActiveLabel = { -// let label = ActiveLabel() -// label.textColor = Asset.Colors.brandBlue.color -// label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .semibold), maximumPointSize: 20) -// label.lineBreakMode = .byTruncatingTail -// return label -// }() -// -// let acceptButton: UIButton = { -// let button = UIButton(type: .custom) -// let actionImage = UIImage(systemName: "checkmark.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 28, weight: .semibold))?.withRenderingMode(.alwaysTemplate) -// button.setImage(actionImage, for: .normal) -// button.tintColor = Asset.Colors.Label.secondary.color -// return button -// }() -// -// let rejectButton: UIButton = { -// let button = UIButton(type: .custom) -// let actionImage = UIImage(systemName: "xmark.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 28, weight: .semibold))?.withRenderingMode(.alwaysTemplate) -// button.setImage(actionImage, for: .normal) -// button.tintColor = Asset.Colors.Label.secondary.color -// return button -// }() -// -// let buttonStackView = UIStackView() -// -// let separatorLine = UIView.separatorLine -// -// var separatorLineToEdgeLeadingLayoutConstraint: NSLayoutConstraint! -// var separatorLineToEdgeTrailingLayoutConstraint: NSLayoutConstraint! -// -// var separatorLineToMarginLeadingLayoutConstraint: NSLayoutConstraint! -// var separatorLineToMarginTrailingLayoutConstraint: NSLayoutConstraint! -// -// override func prepareForReuse() { -// super.prepareForReuse() -// avatarImageViewTask?.cancel() -// avatarImageViewTask = nil -// disposeBag.removeAll() -// } -// -// override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { -// super.init(style: style, reuseIdentifier: reuseIdentifier) -// configure() -// } -// -// required init?(coder: NSCoder) { -// super.init(coder: coder) -// configure() -// } -//} -// -//extension NotificationTableViewCell { -// func configure() { -// backgroundColor = Asset.Colors.Background.systemBackground.color -// selectedBackgroundView = { -// let view = UIView() -// view.backgroundColor = Asset.Colors.Background.Cell.highlight.color -// return view -// }() -// -// let containerStackView = UIStackView() -// containerStackView.axis = .vertical -// containerStackView.alignment = .fill -// containerStackView.layoutMargins = UIEdgeInsets(top: 14, left: 0, bottom: 12, right: 0) -// containerStackView.isLayoutMarginsRelativeArrangement = true -// containerStackView.translatesAutoresizingMaskIntoConstraints = false -// contentView.addSubview(containerStackView) -// NSLayoutConstraint.activate([ -// containerStackView.topAnchor.constraint(equalTo: contentView.topAnchor), -// containerStackView.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor), -// contentView.readableContentGuide.trailingAnchor.constraint(equalTo: containerStackView.trailingAnchor), -// contentView.bottomAnchor.constraint(equalTo: containerStackView.bottomAnchor), -// ]) -// -// let horizontalStackView = UIStackView() -// horizontalStackView.translatesAutoresizingMaskIntoConstraints = false -// horizontalStackView.axis = .horizontal -// horizontalStackView.spacing = 6 -// -// horizontalStackView.addArrangedSubview(avatarContainer) -// avatarContainer.translatesAutoresizingMaskIntoConstraints = false -// NSLayoutConstraint.activate([ -// avatarContainer.heightAnchor.constraint(equalToConstant: 47).priority(.required - 1), -// avatarContainer.widthAnchor.constraint(equalToConstant: 47).priority(.required - 1) -// ]) -// -// avatarContainer.addSubview(avatarImageView) -// avatarImageView.translatesAutoresizingMaskIntoConstraints = false -// NSLayoutConstraint.activate([ -// avatarImageView.heightAnchor.constraint(equalToConstant: 35).priority(.required - 1), -// avatarImageView.widthAnchor.constraint(equalToConstant: 35).priority(.required - 1), -// avatarImageView.topAnchor.constraint(equalTo: avatarContainer.topAnchor), -// avatarImageView.leadingAnchor.constraint(equalTo: avatarContainer.leadingAnchor) -// ]) -// -// avatarContainer.addSubview(actionImageBackground) -// actionImageBackground.translatesAutoresizingMaskIntoConstraints = false -// NSLayoutConstraint.activate([ -// actionImageBackground.heightAnchor.constraint(equalToConstant: 24 + NotificationTableViewCell.actionImageBorderWidth).priority(.required - 1), -// actionImageBackground.widthAnchor.constraint(equalToConstant: 24 + NotificationTableViewCell.actionImageBorderWidth).priority(.required - 1), -// actionImageBackground.bottomAnchor.constraint(equalTo: avatarContainer.bottomAnchor), -// actionImageBackground.trailingAnchor.constraint(equalTo: avatarContainer.trailingAnchor) -// ]) -// -// avatarContainer.addSubview(actionImageView) -// actionImageView.translatesAutoresizingMaskIntoConstraints = false -// NSLayoutConstraint.activate([ -// actionImageView.centerXAnchor.constraint(equalTo: actionImageBackground.centerXAnchor), -// actionImageView.centerYAnchor.constraint(equalTo: actionImageBackground.centerYAnchor) -// ]) -// -// nameLabel.translatesAutoresizingMaskIntoConstraints = false -// horizontalStackView.addArrangedSubview(nameLabel) -// actionLabel.translatesAutoresizingMaskIntoConstraints = false -// horizontalStackView.addArrangedSubview(actionLabel) -// nameLabel.setContentCompressionResistancePriority(.required - 1, for: .horizontal) -// nameLabel.setContentHuggingPriority(.required - 1, for: .horizontal) -// actionLabel.setContentHuggingPriority(.defaultLow, for: .horizontal) -// -// containerStackView.addArrangedSubview(horizontalStackView) -// -// buttonStackView.translatesAutoresizingMaskIntoConstraints = false -// buttonStackView.axis = .horizontal -// buttonStackView.distribution = .fillEqually -// acceptButton.translatesAutoresizingMaskIntoConstraints = false -// rejectButton.translatesAutoresizingMaskIntoConstraints = false -// buttonStackView.addArrangedSubview(acceptButton) -// buttonStackView.addArrangedSubview(rejectButton) -// containerStackView.addArrangedSubview(buttonStackView) -// -// separatorLine.translatesAutoresizingMaskIntoConstraints = false -// contentView.addSubview(separatorLine) -// separatorLineToEdgeLeadingLayoutConstraint = separatorLine.leadingAnchor.constraint(equalTo: contentView.leadingAnchor) -// separatorLineToEdgeTrailingLayoutConstraint = separatorLine.trailingAnchor.constraint(equalTo: contentView.trailingAnchor) -// separatorLineToMarginLeadingLayoutConstraint = separatorLine.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor) -// separatorLineToMarginTrailingLayoutConstraint = separatorLine.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor) -// NSLayoutConstraint.activate([ -// separatorLine.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), -// separatorLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: contentView)), -// ]) -// resetSeparatorLineLayout() -// } -// -// override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { -// super.traitCollectionDidChange(previousTraitCollection) -// -// actionImageBackground.layer.borderColor = Asset.Colors.Background.systemBackground.color.cgColor -// resetSeparatorLineLayout() -// } -//} -// -//extension NotificationTableViewCell { -// -// private func resetSeparatorLineLayout() { -// separatorLineToEdgeLeadingLayoutConstraint.isActive = false -// separatorLineToEdgeTrailingLayoutConstraint.isActive = false -// separatorLineToMarginLeadingLayoutConstraint.isActive = false -// separatorLineToMarginTrailingLayoutConstraint.isActive = false -// -// if traitCollection.userInterfaceIdiom == .phone { -// // to edge -// NSLayoutConstraint.activate([ -// separatorLineToEdgeLeadingLayoutConstraint, -// separatorLineToEdgeTrailingLayoutConstraint, -// ]) -// } else { -// if traitCollection.horizontalSizeClass == .compact { -// // to edge -// NSLayoutConstraint.activate([ -// separatorLineToEdgeLeadingLayoutConstraint, -// separatorLineToEdgeTrailingLayoutConstraint, -// ]) -// } else { -// // to margin -// NSLayoutConstraint.activate([ -// separatorLineToMarginLeadingLayoutConstraint, -// separatorLineToMarginTrailingLayoutConstraint, -// ]) -// } -// } -// } -// -//} diff --git a/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewModel.swift b/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewModel.swift index 9fbd24eda..7ddcefbbf 100644 --- a/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewModel.swift +++ b/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewModel.swift @@ -37,4 +37,17 @@ final class MastodonConfirmEmailViewModel { self.userToken = userToken self.updateCredentialQuery = updateCredentialQuery } + + #if DEBUG + init() { + self.context = AppContext.shared + self.email = "example.com" + self.authenticateInfo = AuthenticationViewModel.AuthenticateInfo( + domain: "", + application: Mastodon.Entity.Application(name: "", website: nil, vapidKey: nil, redirectURI: nil, clientID: "clientID", clientSecret: "clientSecret") + )! + self.userToken = Mastodon.Entity.Token(accessToken: "", tokenType: "", scope: "", createdAt: Date()) + self.updateCredentialQuery = Mastodon.API.Account.UpdateCredentialQuery(discoverable: nil, bot: nil, displayName: nil, note: nil, avatar: nil, header: nil, locked: nil, source: nil, fieldsAttributes: nil) + } + #endif } diff --git a/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift b/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift index bf109888d..24cfe90a3 100644 --- a/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift +++ b/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift @@ -88,6 +88,7 @@ extension ProfileHeaderViewController { override func viewDidLoad() { super.viewDidLoad() + view.backgroundColor = ThemeService.shared.currentTheme.value.systemGroupedBackgroundColor ThemeService.shared.currentTheme .receive(on: RunLoop.main) .sink { [weak self] theme in @@ -222,13 +223,18 @@ extension ProfileHeaderViewController { } .store(in: &disposeBag) - Publishers.CombineLatest( + Publishers.CombineLatest3( viewModel.isEditing, - viewModel.displayProfileInfo.fields + viewModel.displayProfileInfo.fields, + viewModel.needsFiledCollectionViewHidden ) .receive(on: RunLoop.main) - .sink { [weak self] isEditing, fields in + .sink { [weak self] isEditing, fields, needsHidden in guard let self = self else { return } + guard !needsHidden else { + self.profileHeaderView.fieldCollectionView.isHidden = true + return + } self.profileHeaderView.fieldCollectionView.isHidden = isEditing ? false : fields.isEmpty } .store(in: &disposeBag) diff --git a/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift b/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift index 453aa2e43..9a0145751 100644 --- a/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift +++ b/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift @@ -22,6 +22,7 @@ final class ProfileHeaderViewModel { let isEditing = CurrentValueSubject(false) let viewDidAppear = CurrentValueSubject(false) let needsSetupBottomShadow = CurrentValueSubject(true) + let needsFiledCollectionViewHidden = CurrentValueSubject(false) let isTitleViewContentOffsetSet = CurrentValueSubject(false) let emojiDict = CurrentValueSubject([:]) let accountForEdit = CurrentValueSubject(nil) diff --git a/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift b/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift index 2ba4cef86..509e6739d 100644 --- a/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift +++ b/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift @@ -218,7 +218,7 @@ final class ProfileHeaderView: UIView { collectionView.isScrollEnabled = false return collectionView }() - var fieldCollectionViewHeightLaoutConstraint: NSLayoutConstraint! + var fieldCollectionViewHeightLayoutConstraint: NSLayoutConstraint! var fieldCollectionViewHeightObservation: NSKeyValueObservation? override init(frame: CGRect) { @@ -239,6 +239,8 @@ final class ProfileHeaderView: UIView { extension ProfileHeaderView { private func _init() { + backgroundColor = ThemeService.shared.currentTheme.value.systemGroupedBackgroundColor + fieldCollectionView.backgroundColor = ThemeService.shared.currentTheme.value.profileFieldCollectionViewBackgroundColor ThemeService.shared.currentTheme .receive(on: RunLoop.main) .sink { [weak self] theme in @@ -427,17 +429,17 @@ extension ProfileHeaderView { fieldCollectionView.translatesAutoresizingMaskIntoConstraints = false metaContainerStackView.addArrangedSubview(fieldCollectionView) - fieldCollectionViewHeightLaoutConstraint = fieldCollectionView.heightAnchor.constraint(equalToConstant: 44).priority(.defaultHigh) + fieldCollectionViewHeightLayoutConstraint = fieldCollectionView.heightAnchor.constraint(equalToConstant: 44).priority(.defaultHigh) NSLayoutConstraint.activate([ - fieldCollectionViewHeightLaoutConstraint, + fieldCollectionViewHeightLayoutConstraint, ]) fieldCollectionViewHeightObservation = fieldCollectionView.observe(\.contentSize, options: .new, changeHandler: { [weak self] tableView, _ in guard let self = self else { return } guard self.fieldCollectionView.contentSize.height != .zero else { - self.fieldCollectionViewHeightLaoutConstraint.constant = 44 + self.fieldCollectionViewHeightLayoutConstraint.constant = 44 return } - self.fieldCollectionViewHeightLaoutConstraint.constant = self.fieldCollectionView.contentSize.height + self.fieldCollectionViewHeightLayoutConstraint.constant = self.fieldCollectionView.contentSize.height }) bringSubviewToFront(bannerContainerView) diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index e5104c4cb..fc29d308f 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -141,7 +141,8 @@ extension ProfileViewController { override func viewDidLoad() { super.viewDidLoad() - + + view.backgroundColor = ThemeService.shared.currentTheme.value.secondarySystemBackgroundColor ThemeService.shared.currentTheme .receive(on: RunLoop.main) .sink { [weak self] theme in @@ -322,7 +323,7 @@ extension ProfileViewController { self.titleView.isHidden = true return } - let subtitle = L10n.Scene.Profile.subtitle(formattedStatusCount) + let subtitle = L10n.Plural.Count.MetricFormatted.post(formattedStatusCount, statusesCount) self.titleView.update(title: title, subtitle: subtitle, emojiDict: emojiDict) self.titleView.isHidden = false } @@ -495,7 +496,8 @@ extension ProfileViewController { let isNeedSetHidden = isBlocking || isBlockedBy || suspended self.profileHeaderViewController.viewModel.needsSetupBottomShadow.value = !isNeedSetHidden self.profileHeaderViewController.profileHeaderView.bioContainerView.isHidden = isNeedSetHidden - self.profileHeaderViewController.pageSegmentedControl.isHidden = isNeedSetHidden + self.profileHeaderViewController.viewModel.needsFiledCollectionViewHidden.value = isNeedSetHidden + self.profileHeaderViewController.pageSegmentedControl.isEnabled = !isNeedSetHidden self.viewModel.needsPagePinToTop.value = isNeedSetHidden } .store(in: &disposeBag) @@ -530,7 +532,7 @@ extension ProfileViewController { self.profileHeaderViewController.profileHeaderView.statusDashboardView.followersDashboardMeterView.accessibilityLabel = L10n.Scene.Profile.Dashboard.Accessibility.countFollowers(count ?? 0) } .store(in: &disposeBag) - viewModel.needsPaingEnabled + viewModel.needsPagingEnabled .receive(on: RunLoop.main) .sink { [weak self] needsPaingEnabled in guard let self = self else { return } diff --git a/Mastodon/Scene/Profile/ProfileViewModel.swift b/Mastodon/Scene/Profile/ProfileViewModel.swift index 45a2386be..5e8874c22 100644 --- a/Mastodon/Scene/Profile/ProfileViewModel.swift +++ b/Mastodon/Scene/Profile/ProfileViewModel.swift @@ -63,7 +63,7 @@ class ProfileViewModel: NSObject { let isMeBarButtonItemsHidden = CurrentValueSubject(true) let needsPagePinToTop = CurrentValueSubject(false) - let needsPaingEnabled = CurrentValueSubject(true) + let needsPagingEnabled = CurrentValueSubject(true) let needsImageOverlayBlurred = CurrentValueSubject(false) init(context: AppContext, optionalMastodonUser mastodonUser: MastodonUser?) { @@ -161,7 +161,7 @@ class ProfileViewModel: NSObject { isBlockingOrBlocked .map { !$0 } - .assign(to: \.value, on: needsPaingEnabled) + .assign(to: \.value, on: needsPagingEnabled) .store(in: &disposeBag) isBlockingOrBlocked diff --git a/Mastodon/Scene/Search/SearchViewController.swift b/Mastodon/Scene/Search/SearchViewController.swift index 06b5eeced..f3638e58b 100644 --- a/Mastodon/Scene/Search/SearchViewController.swift +++ b/Mastodon/Scene/Search/SearchViewController.swift @@ -135,14 +135,12 @@ extension SearchViewController { navigationItem.compactAppearance = barAppearance navigationItem.scrollEdgeAppearance = barAppearance + 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.view.backgroundColor = theme.systemGroupedBackgroundColor - self.searchHeader.backgroundColor = theme.systemGroupedBackgroundColor - self.searchingTableView.backgroundColor = theme.systemBackgroundColor - self.statusBar.backgroundColor = theme.navigationBarBackgroundColor + self.setupBackgroundColor(theme: theme) } .store(in: &disposeBag) @@ -171,6 +169,13 @@ extension SearchViewController { viewModel.viewDidAppeared.send() } + private func setupBackgroundColor(theme: Theme) { + view.backgroundColor = theme.systemGroupedBackgroundColor + searchHeader.backgroundColor = theme.systemGroupedBackgroundColor + searchingTableView.backgroundColor = theme.systemBackgroundColor + statusBar.backgroundColor = theme.navigationBarBackgroundColor + } + func setupSearchBar() { searchBar.delegate = self view.addSubview(searchBar) diff --git a/Mastodon/Scene/Settings/SettingsViewController.swift b/Mastodon/Scene/Settings/SettingsViewController.swift index 8c74bbcfa..89514a064 100644 --- a/Mastodon/Scene/Settings/SettingsViewController.swift +++ b/Mastodon/Scene/Settings/SettingsViewController.swift @@ -102,7 +102,13 @@ class SettingsViewController: UIViewController, NeedsDependency { tableView.register(SettingsLinkTableViewCell.self, forCellReuseIdentifier: String(describing: SettingsLinkTableViewCell.self)) return tableView }() - + + let tableFooterActiveLabel: ActiveLabel = { + let label = ActiveLabel(style: .default) + label.adjustsFontForContentSizeCategory = true + label.textAlignment = .center + return label + }() lazy var tableFooterView: UIView = { // init with a frame to fix a conflict ('UIView-Encapsulated-Layout-Height' UIStackView:0x7ffe41e47da0.height == 0) let view = UIStackView(frame: CGRect(x: 0, y: 0, width: 320, height: 320)) @@ -110,14 +116,9 @@ class SettingsViewController: UIViewController, NeedsDependency { view.layoutMargins = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20) view.axis = .vertical view.alignment = .center - - let label = ActiveLabel(style: .default) - label.adjustsFontForContentSizeCategory = true - label.textAlignment = .center - label.configure(content: "Mastodon is open source software. You can contribute or report issues on GitHub at tootsuite/mastodon (v3.3.0).", emojiDict: [:]) - label.delegate = self - - view.addArrangedSubview(label) + + tableFooterActiveLabel.delegate = self + view.addArrangedSubview(tableFooterActiveLabel) return view }() @@ -190,30 +191,26 @@ class SettingsViewController: UIViewController, NeedsDependency { } } .store(in: &disposeBag) + + viewModel.currentInstance + .receive(on: RunLoop.main) + .sink { [weak self] instance in + guard let self = self else { return } + let version = instance?.version ?? "-" + let link = #"tootsuite/mastodon"# + let content = L10n.Scene.Settings.Footer.mastodonDescription(link, version) + self.tableFooterActiveLabel.configure(content: content, emojiDict: [:]) + } + .store(in: &disposeBag) } private func setupView() { - self.view.backgroundColor = UIColor(dynamicProvider: { traitCollection in - switch traitCollection.userInterfaceLevel { - case .elevated where traitCollection.userInterfaceStyle == .dark: - return ThemeService.shared.currentTheme.value.systemElevatedBackgroundColor - default: - return ThemeService.shared.currentTheme.value.secondarySystemBackgroundColor - } - }) + 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.view.backgroundColor = UIColor(dynamicProvider: { traitCollection in - switch traitCollection.userInterfaceLevel { - case .elevated where traitCollection.userInterfaceStyle == .dark: - return theme.systemElevatedBackgroundColor - default: - return theme.secondarySystemBackgroundColor - } - }) - + self.setupBackgroundColor(theme: theme) } .store(in: &disposeBag) @@ -229,6 +226,17 @@ class SettingsViewController: UIViewController, NeedsDependency { updateSectionHeaderStackViewLayout() } + + private func setupBackgroundColor(theme: Theme) { + view.backgroundColor = UIColor(dynamicProvider: { traitCollection in + switch traitCollection.userInterfaceLevel { + case .elevated where traitCollection.userInterfaceStyle == .dark: + return theme.systemElevatedBackgroundColor + default: + return theme.secondarySystemBackgroundColor + } + }) + } private func setupNavigation() { navigationController?.navigationBar.prefersLargeTitles = true diff --git a/Mastodon/Scene/Settings/SettingsViewModel.swift b/Mastodon/Scene/Settings/SettingsViewModel.swift index 15aaf66ff..f212a591f 100644 --- a/Mastodon/Scene/Settings/SettingsViewModel.swift +++ b/Mastodon/Scene/Settings/SettingsViewModel.swift @@ -32,6 +32,7 @@ class SettingsViewModel { /// - does not has one /// - does not find subscription for selected trigger when change trigger let createSubscriptionSubject = PassthroughSubject<(triggerBy: String, values: [Bool?]), Never>() + let currentInstance = CurrentValueSubject(nil) /// update a subscription when: /// - change switch for specified alerts @@ -55,6 +56,26 @@ class SettingsViewModel { self.processDataSource(setting) }) .store(in: &disposeBag) + + context.authenticationService.activeMastodonAuthenticationBox + .compactMap { $0?.domain } + .map { context.apiService.instance(domain: $0) } + .switchToLatest() + .sink { [weak self] completion in + guard let self = self else { return } + switch completion { + case .failure(let error): + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: fetch instance fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription) + self.currentInstance.value = nil + case .finished: + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: fetch instance success", ((#file as NSString).lastPathComponent), #line, #function) + + } + } receiveValue: { [weak self] response in + guard let self = self else { return } + self.currentInstance.value = response.value + } + .store(in: &disposeBag) } deinit { diff --git a/Mastodon/Scene/Settings/View/AppearanceView.swift b/Mastodon/Scene/Settings/View/AppearanceView.swift new file mode 100644 index 000000000..dfac265cd --- /dev/null +++ b/Mastodon/Scene/Settings/View/AppearanceView.swift @@ -0,0 +1,102 @@ +// +// AppearanceView.swift +// Mastodon +// +// Created by MainasuK Cirno on 2021-7-6. +// + +import UIKit + +class AppearanceView: UIView { + lazy var imageView: UIImageView = { + let view = UIImageView() + // accessibility + view.accessibilityIgnoresInvertColors = true + return view + }() + lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 12, weight: .regular) + label.textColor = Asset.Colors.Label.primary.color + label.textAlignment = .center + return label + }() + lazy var checkBox: UIButton = { + let button = UIButton() + button.isUserInteractionEnabled = false + button.setImage(UIImage(systemName: "circle"), for: .normal) + button.setImage(UIImage(systemName: "checkmark.circle.fill"), for: .selected) + button.imageView?.preferredSymbolConfiguration = UIImage.SymbolConfiguration(textStyle: .body) + button.imageView?.tintColor = Asset.Colors.Label.secondary.color + button.imageView?.contentMode = .scaleAspectFill + return button + }() + lazy var stackView: UIStackView = { + let view = UIStackView() + view.axis = .vertical + view.spacing = 10 + view.distribution = .equalSpacing + return view + }() + + var selected: Bool = false { + didSet { + checkBox.isSelected = selected + if selected { + checkBox.imageView?.tintColor = Asset.Colors.brandBlue.color + } else { + checkBox.imageView?.tintColor = Asset.Colors.Label.secondary.color + } + } + } + + // MARK: - Methods + init(image: UIImage?, title: String) { + super.init(frame: .zero) + setupUI() + + imageView.image = image + titleLabel.text = title + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Private methods + private func setupUI() { + stackView.addArrangedSubview(imageView) + stackView.addArrangedSubview(titleLabel) + stackView.addArrangedSubview(checkBox) + + addSubview(stackView) + translatesAutoresizingMaskIntoConstraints = false + stackView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + stackView.topAnchor.constraint(equalTo: self.topAnchor), + stackView.leadingAnchor.constraint(equalTo: self.leadingAnchor), + stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor), + stackView.trailingAnchor.constraint(equalTo: self.trailingAnchor), + imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 218.0 / 100.0), + ]) + } + + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + super.touchesBegan(touches, with: event) + self.alpha = 0.5 + } + + override func touchesEnded(_ touches: Set, with event: UIEvent?) { + super.touchesEnded(touches, with: event) + UIView.animate(withDuration: 0.33) { + self.alpha = 1 + } + } + + override func touchesCancelled(_ touches: Set, with event: UIEvent?) { + super.touchesCancelled(touches, with: event) + UIView.animate(withDuration: 0.33) { + self.alpha = 1 + } + } +} diff --git a/Mastodon/Scene/Settings/View/Cell/SettingsAppearanceTableViewCell.swift b/Mastodon/Scene/Settings/View/Cell/SettingsAppearanceTableViewCell.swift index 1cafd046e..d1785b7b5 100644 --- a/Mastodon/Scene/Settings/View/Cell/SettingsAppearanceTableViewCell.swift +++ b/Mastodon/Scene/Settings/View/Cell/SettingsAppearanceTableViewCell.swift @@ -12,81 +12,6 @@ protocol SettingsAppearanceTableViewCellDelegate: AnyObject { func settingsAppearanceCell(_ cell: SettingsAppearanceTableViewCell, didSelectAppearanceMode appearanceMode: SettingsItem.AppearanceMode) } -class AppearanceView: UIView { - lazy var imageView: UIImageView = { - let view = UIImageView() - // accessibility - view.accessibilityIgnoresInvertColors = true - return view - }() - lazy var titleLabel: UILabel = { - let label = UILabel() - label.font = .systemFont(ofSize: 12, weight: .regular) - label.textColor = Asset.Colors.Label.primary.color - label.textAlignment = .center - return label - }() - lazy var checkBox: UIButton = { - let button = UIButton() - button.isUserInteractionEnabled = false - button.setImage(UIImage(systemName: "circle"), for: .normal) - button.setImage(UIImage(systemName: "checkmark.circle.fill"), for: .selected) - button.imageView?.preferredSymbolConfiguration = UIImage.SymbolConfiguration(textStyle: .body) - button.imageView?.tintColor = Asset.Colors.Label.secondary.color - button.imageView?.contentMode = .scaleAspectFill - return button - }() - lazy var stackView: UIStackView = { - let view = UIStackView() - view.axis = .vertical - view.spacing = 10 - view.distribution = .equalSpacing - return view - }() - - var selected: Bool = false { - didSet { - checkBox.isSelected = selected - if selected { - checkBox.imageView?.tintColor = Asset.Colors.brandBlue.color - } else { - checkBox.imageView?.tintColor = Asset.Colors.Label.secondary.color - } - } - } - - // MARK: - Methods - init(image: UIImage?, title: String) { - super.init(frame: .zero) - setupUI() - - imageView.image = image - titleLabel.text = title - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Private methods - private func setupUI() { - stackView.addArrangedSubview(imageView) - stackView.addArrangedSubview(titleLabel) - stackView.addArrangedSubview(checkBox) - - addSubview(stackView) - translatesAutoresizingMaskIntoConstraints = false - stackView.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - stackView.topAnchor.constraint(equalTo: self.topAnchor), - stackView.leadingAnchor.constraint(equalTo: self.leadingAnchor), - stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor), - stackView.trailingAnchor.constraint(equalTo: self.trailingAnchor), - imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 218.0 / 100.0), - ]) - } -} - class SettingsAppearanceTableViewCell: UITableViewCell { var disposeBag = Set() diff --git a/Mastodon/Scene/Share/View/Content/StatusView.swift b/Mastodon/Scene/Share/View/Content/StatusView.swift index 64697b599..d813c2219 100644 --- a/Mastodon/Scene/Share/View/Content/StatusView.swift +++ b/Mastodon/Scene/Share/View/Content/StatusView.swift @@ -165,7 +165,7 @@ final class StatusView: UIView { let label = UILabel() label.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 12, weight: .regular)) label.textColor = Asset.Colors.Label.secondary.color - label.text = L10n.Common.Controls.Status.Poll.VoteCount.single(0) + label.text = L10n.Plural.Count.vote(0) return label }() let pollStatusDotLabel: UILabel = { diff --git a/Mastodon/Scene/Thread/ThreadViewController.swift b/Mastodon/Scene/Thread/ThreadViewController.swift index 99c448801..71a7de9ee 100644 --- a/Mastodon/Scene/Thread/ThreadViewController.swift +++ b/Mastodon/Scene/Thread/ThreadViewController.swift @@ -51,7 +51,8 @@ extension ThreadViewController { override func viewDidLoad() { super.viewDidLoad() - + + view.backgroundColor = ThemeService.shared.currentTheme.value.secondarySystemBackgroundColor ThemeService.shared.currentTheme .receive(on: RunLoop.main) .sink { [weak self] theme in diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Application.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Application.swift index d7d4dda4d..e6f6300e6 100644 --- a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Application.swift +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Application.swift @@ -17,7 +17,7 @@ extension Mastodon.Entity { /// # Reference /// [Document](https://docs.joinmastodon.org/entities/application/) public struct Application: Codable { - + public let name: String public let website: String? @@ -27,6 +27,15 @@ extension Mastodon.Entity { public let redirectURI: String? // undocumented public let clientID: String? public let clientSecret: String? + + public init(name: String, website: String?, vapidKey: String?, redirectURI: String?, clientID: String?, clientSecret: String?) { + self.name = name + self.website = website + self.vapidKey = vapidKey + self.redirectURI = redirectURI + self.clientID = clientID + self.clientSecret = clientSecret + } enum CodingKeys: String, CodingKey { case name diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Token.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Token.swift index e7f18b518..ff305e104 100644 --- a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Token.swift +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Token.swift @@ -17,10 +17,18 @@ extension Mastodon.Entity { /// # Reference /// [Document](https://docs.joinmastodon.org/entities/token/) public struct Token: Codable { + public let accessToken: String public let tokenType: String public let scope: String public let createdAt: Date + + public init(accessToken: String, tokenType: String, scope: String, createdAt: Date) { + self.accessToken = accessToken + self.tokenType = tokenType + self.scope = scope + self.createdAt = createdAt + } enum CodingKeys: String, CodingKey { case accessToken = "access_token" diff --git a/swiftgen.yml b/swiftgen.yml index e74c53562..e086533fb 100644 --- a/swiftgen.yml +++ b/swiftgen.yml @@ -1,5 +1,7 @@ strings: - inputs: Mastodon/Resources/en.lproj/Localizable.strings + inputs: + - Mastodon/Resources/en.lproj/Localizable.strings + - Mastodon/Resources/en.lproj/Localizable.stringsdict outputs: - templateName: structured-swift5 output: Mastodon/Generated/Strings.swift @@ -7,4 +9,4 @@ xcassets: inputs: Mastodon/Resources/Assets.xcassets outputs: templateName: swift5 - output: Mastodon/Generated/Assets.swift \ No newline at end of file + output: Mastodon/Generated/Assets.swift