From 17dce25e071599f2712306866952876b1309b4d3 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Sat, 22 Apr 2023 15:46:34 -0400 Subject: [PATCH 01/27] =?UTF-8?q?Fix=20=E2=80=9Chide=20sensitive=20content?= =?UTF-8?q?=E2=80=9D=20button=20being=20hidden=20(#1024)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MastodonUI/View/Content/StatusView+ViewModel.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift index 0bdcb6243..f466fd819 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift @@ -311,14 +311,15 @@ extension StatusView.ViewModel { // statusView.spoilerBannerView.label.reset() } - if statusView.style == .editHistory, let spoilerContent = spoilerContent, !spoilerContent.string.isEmpty { + if statusView.style == .editHistory { + statusView.setContentSensitiveeToggleButtonDisplay(isDisplay: false) + } + if let spoilerContent = spoilerContent, !spoilerContent.string.isEmpty { statusView.historyContentWarningLabel.configure(content: spoilerContent) statusView.historyContentWarningAdaptiveMarginContainerView.isHidden = statusView.style != .editHistory - statusView.setContentSensitiveeToggleButtonDisplay(isDisplay: false) } else { statusView.historyContentWarningLabel.reset() statusView.historyContentWarningAdaptiveMarginContainerView.isHidden = true - statusView.setContentSensitiveeToggleButtonDisplay(isDisplay: false) } let paragraphStyle = statusView.contentMetaText.paragraphStyle From a99f3a152bc607d07fe9da77dba82a9403c20105 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 2 May 2023 20:20:13 +0200 Subject: [PATCH 02/27] Change text for alternative server-button (IOS-153) (#1030) --- Localization/app.json | 2 +- .../Sources/MastodonLocalization/Generated/Strings.swift | 4 ++-- .../Resources/Base.lproj/Localizable.strings | 4 ++-- .../Resources/en.lproj/Localizable.strings | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Localization/app.json b/Localization/app.json index 7bd3dfca7..5e63124f9 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -264,7 +264,7 @@ "log_in": "Log In", "learn_more": "Learn more", "join_default_server": "Join mastodon.social", - "pick_server": "Pick my own Server", + "pick_server": "Pick another server", "separator": { "or": "or" }, diff --git a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift index 313eaa65a..e1c6db802 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift +++ b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift @@ -1496,8 +1496,8 @@ public enum L10n { public static let learnMore = L10n.tr("Localizable", "Scene.Welcome.LearnMore", fallback: "Learn more") /// Log In public static let logIn = L10n.tr("Localizable", "Scene.Welcome.LogIn", fallback: "Log In") - /// Pick my own Server - public static let pickServer = L10n.tr("Localizable", "Scene.Welcome.PickServer", fallback: "Pick my own Server") + /// Pick another server + public static let pickServer = L10n.tr("Localizable", "Scene.Welcome.PickServer", fallback: "Pick another server") public enum Education { public enum A11Y { public enum WhatIsMastodon { diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings index 125a0969b..9be911ddb 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings @@ -527,7 +527,7 @@ uploaded to Mastodon."; "Scene.Welcome.JoinDefaultServer" = "Join mastodon.social"; "Scene.Welcome.LearnMore" = "Learn more"; "Scene.Welcome.LogIn" = "Log In"; -"Scene.Welcome.PickServer" = "Pick my own Server"; +"Scene.Welcome.PickServer" = "Pick another server"; "Scene.Welcome.Separator.Or" = "or"; "Widget.Common.UnsupportedWidgetFamily" = "Sorry but this Widget family is unsupported."; "Widget.Common.UserNotLoggedIn" = "Please open Mastodon to log in to an Account."; @@ -542,4 +542,4 @@ uploaded to Mastodon."; "Widget.MultipleFollowers.ConfigurationDescription" = "Show number of followers for multiple accounts."; "Widget.MultipleFollowers.ConfigurationDisplayName" = "Multiple followers"; "Widget.MultipleFollowers.MockUser.AccountName" = "another@follower.social"; -"Widget.MultipleFollowers.MockUser.DisplayName" = "Another follower"; \ No newline at end of file +"Widget.MultipleFollowers.MockUser.DisplayName" = "Another follower"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings index 125a0969b..9be911ddb 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings @@ -527,7 +527,7 @@ uploaded to Mastodon."; "Scene.Welcome.JoinDefaultServer" = "Join mastodon.social"; "Scene.Welcome.LearnMore" = "Learn more"; "Scene.Welcome.LogIn" = "Log In"; -"Scene.Welcome.PickServer" = "Pick my own Server"; +"Scene.Welcome.PickServer" = "Pick another server"; "Scene.Welcome.Separator.Or" = "or"; "Widget.Common.UnsupportedWidgetFamily" = "Sorry but this Widget family is unsupported."; "Widget.Common.UserNotLoggedIn" = "Please open Mastodon to log in to an Account."; @@ -542,4 +542,4 @@ uploaded to Mastodon."; "Widget.MultipleFollowers.ConfigurationDescription" = "Show number of followers for multiple accounts."; "Widget.MultipleFollowers.ConfigurationDisplayName" = "Multiple followers"; "Widget.MultipleFollowers.MockUser.AccountName" = "another@follower.social"; -"Widget.MultipleFollowers.MockUser.DisplayName" = "Another follower"; \ No newline at end of file +"Widget.MultipleFollowers.MockUser.DisplayName" = "Another follower"; From d3680618da0965ab4f28e9130fb988e71f3b0f22 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 21 Mar 2023 23:06:08 +0100 Subject: [PATCH 03/27] Add relevant base-classes for Hashtag-Widget (IOS-37) --- Mastodon.xcodeproj/project.pbxproj | 16 +++ Mastodon/Info.plist | 3 +- MastodonIntent/Info.plist | 2 + .../WidgetExtension.intentdefinition | 115 +++++++++++++++++- .../Variants/Hashtag/HashtagWidget.swift | 41 +++++++ .../Variants/Hashtag/HashtagWidgetView.swift | 23 ++++ 6 files changed, 198 insertions(+), 2 deletions(-) create mode 100644 WidgetExtension/Variants/Hashtag/HashtagWidget.swift create mode 100644 WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 17a4dce35..c8893f7b4 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -150,6 +150,8 @@ D8A6AB6C291C5136003AB663 /* MastodonLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A6AB6B291C5136003AB663 /* MastodonLoginViewController.swift */; }; D8E5C346296DAB84007E76A7 /* DataSourceFacade+Status+History.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E5C345296DAB84007E76A7 /* DataSourceFacade+Status+History.swift */; }; D8E5C349296DB8A3007E76A7 /* StatusEditHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E5C348296DB8A3007E76A7 /* StatusEditHistoryViewController.swift */; }; + D8F8A03A29CA5C15000195DD /* HashtagWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F8A03929CA5C15000195DD /* HashtagWidgetView.swift */; }; + D8F8A03C29CA5CB6000195DD /* HashtagWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F8A03B29CA5CB6000195DD /* HashtagWidget.swift */; }; DB0009A626AEE5DC009B9D2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = DB0009A926AEE5DC009B9D2D /* Intents.intentdefinition */; settings = {ATTRIBUTES = (codegen, ); }; }; DB0009A726AEE5DC009B9D2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = DB0009A926AEE5DC009B9D2D /* Intents.intentdefinition */; }; DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140CE25C42AEE00F9F3CF /* OSLog.swift */; }; @@ -794,6 +796,8 @@ D8A6FE6629325F5900666A47 /* Localizable.stringsdict */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; path = Localizable.stringsdict; sourceTree = ""; }; D8E5C345296DAB84007E76A7 /* DataSourceFacade+Status+History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Status+History.swift"; sourceTree = ""; }; D8E5C348296DB8A3007E76A7 /* StatusEditHistoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditHistoryViewController.swift; sourceTree = ""; }; + D8F8A03929CA5C15000195DD /* HashtagWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagWidgetView.swift; sourceTree = ""; }; + D8F8A03B29CA5CB6000195DD /* HashtagWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagWidget.swift; sourceTree = ""; }; DB0009A826AEE5DC009B9D2D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/Intents.intentdefinition; sourceTree = ""; }; DB0009AD26AEE5E4009B9D2D /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Intents.strings; sourceTree = ""; }; DB0140CE25C42AEE00F9F3CF /* OSLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = ""; }; @@ -1461,6 +1465,7 @@ 2A86A14329892700007F1062 /* Variants */ = { isa = PBXGroup; children = ( + D8F8A03829CA5C02000195DD /* Hashtag */, 2A86A14429892709007F1062 /* FollowersCount */, 2A86A14729892B1B007F1062 /* MultiFollowersCount */, 2A9D0662298C045000BF38CB /* LatestFollowers */, @@ -1833,6 +1838,15 @@ path = "Edit History"; sourceTree = ""; }; + D8F8A03829CA5C02000195DD /* Hashtag */ = { + isa = PBXGroup; + children = ( + D8F8A03929CA5C15000195DD /* HashtagWidgetView.swift */, + D8F8A03B29CA5CB6000195DD /* HashtagWidget.swift */, + ); + path = Hashtag; + sourceTree = ""; + }; DB01409B25C40BB600F9F3CF /* Onboarding */ = { isa = PBXGroup; children = ( @@ -3510,7 +3524,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D8F8A03A29CA5C15000195DD /* HashtagWidgetView.swift in Sources */, 2A33062D2987DBFA001D4C51 /* FollowersCountHistory.swift in Sources */, + D8F8A03C29CA5CB6000195DD /* HashtagWidget.swift in Sources */, 2A9D0666298C05A800BF38CB /* LatestFollowersWidgetView.swift in Sources */, 2AB5011E299243FB00346092 /* WidgetExtension.intentdefinition in Sources */, 2A86A14B2989326E007F1062 /* MultiFollowersCountWidgetView.swift in Sources */, diff --git a/Mastodon/Info.plist b/Mastodon/Info.plist index 0e0deb2cc..a525ccb6f 100644 --- a/Mastodon/Info.plist +++ b/Mastodon/Info.plist @@ -62,8 +62,9 @@ NSUserActivityTypes FollowersCountIntent - MultiFollowersCountIntent + HashtagIntent LatestFollowersIntent + MultiFollowersCountIntent SendPostIntent UIApplicationSceneManifest diff --git a/MastodonIntent/Info.plist b/MastodonIntent/Info.plist index 06804cb31..2a05311bb 100644 --- a/MastodonIntent/Info.plist +++ b/MastodonIntent/Info.plist @@ -31,6 +31,8 @@ IntentsSupported FollowersCountIntent + HashtagIntent + LatestFollowersIntent MultiFollowersCountIntent SendPostIntent diff --git a/WidgetExtension/Base.lproj/WidgetExtension.intentdefinition b/WidgetExtension/Base.lproj/WidgetExtension.intentdefinition index b4f48b63f..4543a7380 100644 --- a/WidgetExtension/Base.lproj/WidgetExtension.intentdefinition +++ b/WidgetExtension/Base.lproj/WidgetExtension.intentdefinition @@ -9,7 +9,7 @@ INIntentDefinitionNamespace 88xZPY INIntentDefinitionSystemVersion - 22D49 + 22D68 INIntentDefinitionToolsBuildVersion 14C18 INIntentDefinitionToolsVersion @@ -348,8 +348,38 @@ INIntentIneligibleForSuggestions + INIntentLastParameterTag + 1 INIntentName LatestFollowers + INIntentParameters + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Parameter + INIntentParameterDisplayNameID + sFTGjN + INIntentParameterDisplayPriority + 1 + INIntentParameterMetadata + + INIntentParameterMetadataCapitalization + Sentences + INIntentParameterMetadataDefaultValueID + Cnairs + + INIntentParameterName + parameter + INIntentParameterSupportsResolution + + INIntentParameterTag + 1 + INIntentParameterType + String + + INIntentResponse INIntentResponseCodes @@ -375,6 +405,89 @@ INIntentVerb View + + INIntentCategory + information + INIntentDescription + Hashtag + INIntentDescriptionID + A1rwKl + INIntentEligibleForWidgets + + INIntentIneligibleForSuggestions + + INIntentLastParameterTag + 1 + INIntentName + Hashtag + INIntentParameters + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Hashtag + INIntentParameterDisplayNameID + GTUbZg + INIntentParameterDisplayPriority + 1 + INIntentParameterMetadata + + INIntentParameterMetadataCapitalization + Sentences + INIntentParameterMetadataDefaultValueID + YdkgW1 + + INIntentParameterName + hashtag + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterSupportsResolution + + INIntentParameterTag + 1 + INIntentParameterType + String + + + INIntentResponse + + INIntentResponseCodes + + + INIntentResponseCodeName + success + INIntentResponseCodeSuccess + + + + INIntentResponseCodeName + failure + + + + INIntentTitle + Hashtag + INIntentTitleID + OcUp1W + INIntentType + Custom + INIntentVerb + View + INTypes diff --git a/WidgetExtension/Variants/Hashtag/HashtagWidget.swift b/WidgetExtension/Variants/Hashtag/HashtagWidget.swift new file mode 100644 index 000000000..18ba7e66c --- /dev/null +++ b/WidgetExtension/Variants/Hashtag/HashtagWidget.swift @@ -0,0 +1,41 @@ +// Copyright © 2023 Mastodon gGmbH. All rights reserved. + +import WidgetKit +import SwiftUI + +struct HashtagWidgetProvider: IntentTimelineProvider { + func placeholder(in context: Context) -> HashtagWidgetTimelineEntry { + .init(date: Date()) + } + + func getSnapshot(for configuration: Intent, in context: Context, completion: @escaping (HashtagWidgetTimelineEntry) -> Void) { + //TODO: Implement + } + + func getTimeline(for configuration: HashtagIntent, in context: Context, completion: @escaping (Timeline) -> Void) { + //TODO: Implement + } +} + +struct HashtagWidgetTimelineEntry: TimelineEntry { + var date: Date + //TODO: implement, add relevant information +} + +struct HashtagWidget: Widget { + + private var availableFamilies: [WidgetFamily] { + if #available(iOS 16, *) { + return [.systemMedium, .systemLarge, .accessoryRectangular] + } else { + return [.systemMedium, .systemLarge] + } + } + + var body: some WidgetConfiguration { + IntentConfiguration(kind: "Hashtag", intent: HashtagIntent.self, provider: HashtagWidgetProvider()) { entry in + HashtagWidgetView() + } + .supportedFamilies(availableFamilies) + } +} diff --git a/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift b/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift new file mode 100644 index 000000000..efbaf2e7f --- /dev/null +++ b/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift @@ -0,0 +1,23 @@ +// Copyright © 2023 Mastodon gGmbH. All rights reserved. + +import SwiftUI + +struct HashtagWidgetView: View { + var body: some View { + //TODO: Lockscreen has a different design + HStack { + VStack { + Text("Username") + Text("@user@mastodon.social") + } + Text("Toot") + VStack { + Image(systemName: "arrow.2.squarepath") + Text("Reblog Count") + Image(systemName: "star") + Text("Star Count") + Text("#Hashtag") + } + } + } +} From 96b432346c907adf9212370d4999c7ca267572c3 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Wed, 22 Mar 2023 22:04:59 +0100 Subject: [PATCH 04/27] Fix intentdefintion (IOS-37) --- .../WidgetExtension.intentdefinition | 58 +++++++++---------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/WidgetExtension/Base.lproj/WidgetExtension.intentdefinition b/WidgetExtension/Base.lproj/WidgetExtension.intentdefinition index 4543a7380..aca5db658 100644 --- a/WidgetExtension/Base.lproj/WidgetExtension.intentdefinition +++ b/WidgetExtension/Base.lproj/WidgetExtension.intentdefinition @@ -352,34 +352,6 @@ 1 INIntentName LatestFollowers - INIntentParameters - - - INIntentParameterConfigurable - - INIntentParameterDisplayName - Parameter - INIntentParameterDisplayNameID - sFTGjN - INIntentParameterDisplayPriority - 1 - INIntentParameterMetadata - - INIntentParameterMetadataCapitalization - Sentences - INIntentParameterMetadataDefaultValueID - Cnairs - - INIntentParameterName - parameter - INIntentParameterSupportsResolution - - INIntentParameterTag - 1 - INIntentParameterType - String - - INIntentResponse INIntentResponseCodes @@ -417,7 +389,7 @@ INIntentIneligibleForSuggestions INIntentLastParameterTag - 1 + 5 INIntentName Hashtag INIntentParameters @@ -445,6 +417,10 @@ INIntentParameterPromptDialogCustom + INIntentParameterPromptDialogFormatString + Hashtag + INIntentParameterPromptDialogFormatStringID + zbXop9 INIntentParameterPromptDialogType Configuration @@ -454,11 +430,31 @@ INIntentParameterPromptDialogType Primary + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + There are ${count} options matching ‘${hashtag}’. + INIntentParameterPromptDialogFormatStringID + oQ0WOE + INIntentParameterPromptDialogType + DisambiguationIntroduction + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + Just to confirm, you wanted ‘${hashtag}’? + INIntentParameterPromptDialogFormatStringID + REztzm + INIntentParameterPromptDialogType + Confirmation + - INIntentParameterSupportsResolution + INIntentParameterSupportsDynamicEnumeration INIntentParameterTag - 1 + 5 INIntentParameterType String From 4adf58015c266f90527f2c804af970b48f802ffb Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Wed, 22 Mar 2023 22:05:15 +0100 Subject: [PATCH 05/27] Show widget in bundle (IOS-37) --- WidgetExtension/Variants/Hashtag/HashtagWidget.swift | 2 ++ WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift | 6 +++--- WidgetExtension/WidgetExtensionBundle.swift | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/WidgetExtension/Variants/Hashtag/HashtagWidget.swift b/WidgetExtension/Variants/Hashtag/HashtagWidget.swift index 18ba7e66c..fa3f74204 100644 --- a/WidgetExtension/Variants/Hashtag/HashtagWidget.swift +++ b/WidgetExtension/Variants/Hashtag/HashtagWidget.swift @@ -36,6 +36,8 @@ struct HashtagWidget: Widget { IntentConfiguration(kind: "Hashtag", intent: HashtagIntent.self, provider: HashtagWidgetProvider()) { entry in HashtagWidgetView() } + .configurationDisplayName("Hashtag") + .description("Show a Hashtag") .supportedFamilies(availableFamilies) } } diff --git a/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift b/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift index efbaf2e7f..e053e6837 100644 --- a/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift +++ b/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift @@ -5,13 +5,13 @@ import SwiftUI struct HashtagWidgetView: View { var body: some View { //TODO: Lockscreen has a different design - HStack { - VStack { + VStack { + HStack { Text("Username") Text("@user@mastodon.social") } Text("Toot") - VStack { + HStack { Image(systemName: "arrow.2.squarepath") Text("Reblog Count") Image(systemName: "star") diff --git a/WidgetExtension/WidgetExtensionBundle.swift b/WidgetExtension/WidgetExtensionBundle.swift index 61e383660..cf85726c9 100644 --- a/WidgetExtension/WidgetExtensionBundle.swift +++ b/WidgetExtension/WidgetExtensionBundle.swift @@ -9,5 +9,6 @@ struct WidgetExtensionBundle: WidgetBundle { FollowersCountWidget() MultiFollowersCountWidget() LatestFollowersWidget() + HashtagWidget() } } From d1c6c5523ad06d58f0b94b59cb972094e77a4d81 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Wed, 22 Mar 2023 23:36:41 +0100 Subject: [PATCH 06/27] Implement (static) widget (IOS-37) --- .../Variants/Hashtag/HashtagWidget.swift | 42 ++++++++++++++-- .../Variants/Hashtag/HashtagWidgetView.swift | 48 +++++++++++++++---- 2 files changed, 75 insertions(+), 15 deletions(-) diff --git a/WidgetExtension/Variants/Hashtag/HashtagWidget.swift b/WidgetExtension/Variants/Hashtag/HashtagWidget.swift index fa3f74204..9adfdc899 100644 --- a/WidgetExtension/Variants/Hashtag/HashtagWidget.swift +++ b/WidgetExtension/Variants/Hashtag/HashtagWidget.swift @@ -5,21 +5,44 @@ import SwiftUI struct HashtagWidgetProvider: IntentTimelineProvider { func placeholder(in context: Context) -> HashtagWidgetTimelineEntry { - .init(date: Date()) + .placeholder } - func getSnapshot(for configuration: Intent, in context: Context, completion: @escaping (HashtagWidgetTimelineEntry) -> Void) { - //TODO: Implement + func getSnapshot(for configuration: HashtagIntent, in context: Context, completion: @escaping (HashtagWidgetTimelineEntry) -> Void) { + loadMostRecentHashtag(for: configuration, in: context, completion: completion) } func getTimeline(for configuration: HashtagIntent, in context: Context, completion: @escaping (Timeline) -> Void) { - //TODO: Implement + loadMostRecentHashtag(for: configuration, in: context) { entry in + completion(Timeline(entries: [entry], policy: .after(.now))) + } + } +} + +extension HashtagWidgetProvider { + func loadMostRecentHashtag(for configuration: HashtagIntent, in context: Context, completion: @escaping (HashtagWidgetTimelineEntry) -> Void ) { + let hashtagTimelineEntry = HashtagWidgetTimelineEntry.placeholder + completion(hashtagTimelineEntry) } } struct HashtagWidgetTimelineEntry: TimelineEntry { var date: Date //TODO: implement, add relevant information + var hashtag: HashtagEntry + + static var placeholder: Self { + HashtagWidgetTimelineEntry( + date: .now, + hashtag: HashtagEntry( + accountName: "John Mastodon", + account: "@johnmastodon@mastodon.social", + content: "Caturday is the best day of the week #CatsOfMastodon", + reblogCount: 13, + favoriteCount: 12, + hashtag: "#CatsOfMastodon") + ) + } } struct HashtagWidget: Widget { @@ -34,10 +57,19 @@ struct HashtagWidget: Widget { var body: some WidgetConfiguration { IntentConfiguration(kind: "Hashtag", intent: HashtagIntent.self, provider: HashtagWidgetProvider()) { entry in - HashtagWidgetView() + HashtagWidgetView(entry: entry) } .configurationDisplayName("Hashtag") .description("Show a Hashtag") .supportedFamilies(availableFamilies) } } + +struct HashtagEntry { + var accountName: String + var account: String + var content: String + var reblogCount: Int + var favoriteCount: Int + var hashtag: String +} diff --git a/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift b/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift index e053e6837..a5548a2de 100644 --- a/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift +++ b/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift @@ -3,21 +3,49 @@ import SwiftUI struct HashtagWidgetView: View { + + var entry: HashtagWidgetProvider.Entry + var body: some View { //TODO: Lockscreen has a different design - VStack { + VStack(alignment: .leading, spacing: 0) { HStack { - Text("Username") - Text("@user@mastodon.social") + Text(entry.hashtag.accountName) + .font(.caption) + .fontWeight(.semibold) + .foregroundColor(.secondary) + Text(entry.hashtag.account) + .lineLimit(1) + .font(.caption) + .foregroundColor(.secondary) + Text("18h") //TODO: Implement + .font(.caption) + .foregroundColor(.secondary) } - Text("Toot") - HStack { - Image(systemName: "arrow.2.squarepath") - Text("Reblog Count") - Image(systemName: "star") - Text("Star Count") - Text("#Hashtag") + + Text(entry.hashtag.content) + Spacer() + HStack(alignment: .center, spacing: 16) { + HStack(spacing: 0) { + Image(systemName: "arrow.2.squarepath") + .foregroundColor(.secondary) + Text("\(entry.hashtag.reblogCount)") + .font(.caption) + .foregroundColor(.secondary) + } + HStack(spacing: 0) { + Image(systemName: "star") + .foregroundColor(.secondary) + Text("\(entry.hashtag.favoriteCount)") + .font(.caption) + .foregroundColor(.secondary) + } + Text(entry.hashtag.hashtag) + .font(.caption) + .fontWeight(.semibold) + .foregroundColor(.secondary) } } + .padding(EdgeInsets(top: 12, leading: 29, bottom: 12, trailing: 29)) } } From 48ab745b5c161def63054703f99dce18132959fa Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Sun, 26 Mar 2023 14:55:34 +0200 Subject: [PATCH 07/27] Set indention to 4 spaces --- Mastodon.xcodeproj/project.pbxproj | 1 + 1 file changed, 1 insertion(+) diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index c8893f7b4..e27e02499 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -2058,6 +2058,7 @@ DB98335F25C93B0400AD9700 /* Recovered References */, D8A6FE6029325F5900666A47 /* Localization */, ); + indentWidth = 4; sourceTree = ""; tabWidth = 4; }; From 50945f5285b4a90cde49668b74e0b3e6ef1b9cba Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Sun, 26 Mar 2023 16:10:46 +0200 Subject: [PATCH 08/27] Add widget for lockscreen (IOS-37) --- .../Variants/Hashtag/HashtagWidgetView.swift | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift b/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift index a5548a2de..a2bda5753 100644 --- a/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift +++ b/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift @@ -1,13 +1,26 @@ // Copyright © 2023 Mastodon gGmbH. All rights reserved. import SwiftUI +import MastodonLocalization struct HashtagWidgetView: View { var entry: HashtagWidgetProvider.Entry - + + @Environment(\.widgetFamily) var family + var body: some View { - //TODO: Lockscreen has a different design + switch family { + case .systemMedium, .systemLarge: + viewForMediumWidget() + case .accessoryRectangular: + viewForRectangularAccessory() + default: + Text(L10n.Widget.Common.unsupportedWidgetFamily) + } + } + + private func viewForMediumWidget() -> some View { VStack(alignment: .leading, spacing: 0) { HStack { Text(entry.hashtag.accountName) @@ -48,4 +61,27 @@ struct HashtagWidgetView: View { } .padding(EdgeInsets(top: 12, leading: 29, bottom: 12, trailing: 29)) } + + private func viewForRectangularAccessory() -> some View { + VStack(alignment: .leading, spacing: 1) { + HStack(spacing: 3) { + Image("BrandIcon") + .foregroundColor(.secondary) + Text("|") + .foregroundColor(.secondary) + .font(.system(size: UIFontMetrics.default.scaledValue(for: 9))) + Text(entry.hashtag.hashtag) + .foregroundColor(.secondary) + .font(.system(size: UIFontMetrics.default.scaledValue(for: 13))) + .fontWeight(.heavy) + + } + Text(entry.hashtag.content) + .foregroundColor(.primary) + .font(.system(size: UIFontMetrics.default.scaledValue(for: 16))) + .fontWeight(.medium) + .lineLimit(3) + Spacer() + } + } } From 7b59fdf3bcc553e64c777fd27b55022c2f42665f Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Sun, 26 Mar 2023 18:49:06 +0200 Subject: [PATCH 09/27] Implement first working version of widget (IOS-37) There's still plenty of stuff to do, like: Show text (instead of source) and configure the hashtag ... you know the paretro principle) --- .../Variants/Hashtag/HashtagWidget.swift | 96 ++++++++++++++++++- .../Variants/Hashtag/HashtagWidgetView.swift | 4 +- 2 files changed, 95 insertions(+), 5 deletions(-) diff --git a/WidgetExtension/Variants/Hashtag/HashtagWidget.swift b/WidgetExtension/Variants/Hashtag/HashtagWidget.swift index 9adfdc899..c7ccfd49a 100644 --- a/WidgetExtension/Variants/Hashtag/HashtagWidget.swift +++ b/WidgetExtension/Variants/Hashtag/HashtagWidget.swift @@ -2,6 +2,7 @@ import WidgetKit import SwiftUI +import MastodonSDK struct HashtagWidgetProvider: IntentTimelineProvider { func placeholder(in context: Context) -> HashtagWidgetTimelineEntry { @@ -21,17 +22,70 @@ struct HashtagWidgetProvider: IntentTimelineProvider { extension HashtagWidgetProvider { func loadMostRecentHashtag(for configuration: HashtagIntent, in context: Context, completion: @escaping (HashtagWidgetTimelineEntry) -> Void ) { - let hashtagTimelineEntry = HashtagWidgetTimelineEntry.placeholder - completion(hashtagTimelineEntry) + + guard + let authBox = WidgetExtension.appContext + .authenticationService + .mastodonAuthenticationBoxes + .first + else { + if context.isPreview { + return completion(.placeholder) + } else { + return completion(.unconfigured) + } + } + + Task { + let desiredHashtag: String + + if let hashtag = configuration.hashtag { + desiredHashtag = hashtag + } else { + return completion(.notFound) + } + + do { + let mostRecentStatuses = try await WidgetExtension.appContext + .apiService + .hashtagTimeline(domain: authBox.domain, limit: 1, hashtag: desiredHashtag, authenticationBox: authBox) + .value + + if let mostRecentStatus = mostRecentStatuses.first { + + let hashtagEntry = HashtagEntry( + accountName: mostRecentStatus.account.displayNameWithFallback, + account: mostRecentStatus.account.acct, + content: mostRecentStatus.content ?? "-", + reblogCount: mostRecentStatus.reblogsCount, + favoriteCount: mostRecentStatus.favouritesCount, + hashtag: "#\(desiredHashtag)", + timestamp: mostRecentStatus.createdAt + ) + + let hashtagTimelineEntry = HashtagWidgetTimelineEntry( + date: mostRecentStatus.createdAt, + hashtag: hashtagEntry + ) + + completion(hashtagTimelineEntry) + } + } catch { + completion(.notFound) + } + } + + + } } struct HashtagWidgetTimelineEntry: TimelineEntry { var date: Date - //TODO: implement, add relevant information var hashtag: HashtagEntry static var placeholder: Self { + //TODO: @zeitschlag Add Localization HashtagWidgetTimelineEntry( date: .now, hashtag: HashtagEntry( @@ -40,8 +94,40 @@ struct HashtagWidgetTimelineEntry: TimelineEntry { content: "Caturday is the best day of the week #CatsOfMastodon", reblogCount: 13, favoriteCount: 12, - hashtag: "#CatsOfMastodon") + hashtag: "#CatsOfMastodon", + timestamp: .now.addingTimeInterval(-3600 * 18) ) + ) + } + + static var notFound: Self { + HashtagWidgetTimelineEntry( + date: .now, + hashtag: HashtagEntry( + accountName: "Not Found", + account: "404", + content: "Couldn't find a status, sorryyyyyyy", + reblogCount: 0, + favoriteCount: 0, + hashtag: "", + timestamp: .now + ) + ) + } + + static var unconfigured: Self { + HashtagWidgetTimelineEntry( + date: .now, + hashtag: HashtagEntry( + accountName: "Unconfigured", + account: "@unconfigured@mastodon.social", + content: "Caturday is the best day of the week #CatsOfMastodon", + reblogCount: 14, + favoriteCount: 13, + hashtag: "#CatsOfMastodon", + timestamp: .now.addingTimeInterval(-3600 * 18) + ) + ) } } @@ -59,6 +145,7 @@ struct HashtagWidget: Widget { IntentConfiguration(kind: "Hashtag", intent: HashtagIntent.self, provider: HashtagWidgetProvider()) { entry in HashtagWidgetView(entry: entry) } + //TODO: @zeitschlag Add Localization .configurationDisplayName("Hashtag") .description("Show a Hashtag") .supportedFamilies(availableFamilies) @@ -72,4 +159,5 @@ struct HashtagEntry { var reblogCount: Int var favoriteCount: Int var hashtag: String + var timestamp: Date } diff --git a/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift b/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift index a2bda5753..74281a4f2 100644 --- a/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift +++ b/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift @@ -31,12 +31,14 @@ struct HashtagWidgetView: View { .lineLimit(1) .font(.caption) .foregroundColor(.secondary) - Text("18h") //TODO: Implement + Text(entry.hashtag.timestamp.localizedShortTimeAgo(since: .now)) .font(.caption) .foregroundColor(.secondary) } + //TODO: Check MetaLabelRepresentable, maybe it's a way to color Hashtags? Text(entry.hashtag.content) + Spacer() HStack(alignment: .center, spacing: 16) { HStack(spacing: 0) { From 741456ede6790aefde9e70649dcde8b20dfcda2a Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 27 Mar 2023 22:38:24 +0200 Subject: [PATCH 10/27] Limit account-name to one line (IOS-37) --- WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift b/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift index 74281a4f2..2c21a4aaf 100644 --- a/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift +++ b/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift @@ -27,6 +27,7 @@ struct HashtagWidgetView: View { .font(.caption) .fontWeight(.semibold) .foregroundColor(.secondary) + .lineLimit(1) Text(entry.hashtag.account) .lineLimit(1) .font(.caption) From 24b1602972421ddc32d78281752a6b67503e0fb0 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 27 Mar 2023 22:41:36 +0200 Subject: [PATCH 11/27] Search online for hashtags (IOS-37) --- Mastodon.xcodeproj/project.pbxproj | 4 +++ .../Handler/HashtagIntentHandler.swift | 34 +++++++++++++++++++ MastodonIntent/IntentHandler.swift | 2 ++ .../WidgetExtension.intentdefinition | 22 ++---------- 4 files changed, 42 insertions(+), 20 deletions(-) create mode 100644 MastodonIntent/Handler/HashtagIntentHandler.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index e27e02499..8e68c405a 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -150,6 +150,7 @@ D8A6AB6C291C5136003AB663 /* MastodonLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A6AB6B291C5136003AB663 /* MastodonLoginViewController.swift */; }; D8E5C346296DAB84007E76A7 /* DataSourceFacade+Status+History.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E5C345296DAB84007E76A7 /* DataSourceFacade+Status+History.swift */; }; D8E5C349296DB8A3007E76A7 /* StatusEditHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E5C348296DB8A3007E76A7 /* StatusEditHistoryViewController.swift */; }; + D8F0372C29D232730027DE2E /* HashtagIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F0372B29D232730027DE2E /* HashtagIntentHandler.swift */; }; D8F8A03A29CA5C15000195DD /* HashtagWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F8A03929CA5C15000195DD /* HashtagWidgetView.swift */; }; D8F8A03C29CA5CB6000195DD /* HashtagWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F8A03B29CA5CB6000195DD /* HashtagWidget.swift */; }; DB0009A626AEE5DC009B9D2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = DB0009A926AEE5DC009B9D2D /* Intents.intentdefinition */; settings = {ATTRIBUTES = (codegen, ); }; }; @@ -796,6 +797,7 @@ D8A6FE6629325F5900666A47 /* Localizable.stringsdict */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; path = Localizable.stringsdict; sourceTree = ""; }; D8E5C345296DAB84007E76A7 /* DataSourceFacade+Status+History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Status+History.swift"; sourceTree = ""; }; D8E5C348296DB8A3007E76A7 /* StatusEditHistoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditHistoryViewController.swift; sourceTree = ""; }; + D8F0372B29D232730027DE2E /* HashtagIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagIntentHandler.swift; sourceTree = ""; }; D8F8A03929CA5C15000195DD /* HashtagWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagWidgetView.swift; sourceTree = ""; }; D8F8A03B29CA5CB6000195DD /* HashtagWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagWidget.swift; sourceTree = ""; }; DB0009A826AEE5DC009B9D2D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/Intents.intentdefinition; sourceTree = ""; }; @@ -2341,6 +2343,7 @@ DBB8AB4526AECDE200F6D281 /* SendPostIntentHandler.swift */, 2AE202AB297FE19100F66E55 /* FollowersCountIntentHandler.swift */, 2A86A14529892944007F1062 /* MultiFollowersCountIntentHandler.swift */, + D8F0372B29D232730027DE2E /* HashtagIntentHandler.swift */, ); path = Handler; sourceTree = ""; @@ -3979,6 +3982,7 @@ 2AE202AC297FE19100F66E55 /* FollowersCountIntentHandler.swift in Sources */, DB64BA452851F23000ADF1B7 /* MastodonAuthentication+Fetch.swift in Sources */, DB64BA482851F29300ADF1B7 /* Account+Fetch.swift in Sources */, + D8F0372C29D232730027DE2E /* HashtagIntentHandler.swift in Sources */, 2A86A14629892944007F1062 /* MultiFollowersCountIntentHandler.swift in Sources */, DB8FABCA26AEC7B2008E5AF4 /* IntentHandler.swift in Sources */, ); diff --git a/MastodonIntent/Handler/HashtagIntentHandler.swift b/MastodonIntent/Handler/HashtagIntentHandler.swift new file mode 100644 index 000000000..eaaf6c349 --- /dev/null +++ b/MastodonIntent/Handler/HashtagIntentHandler.swift @@ -0,0 +1,34 @@ +// Copyright © 2023 Mastodon gGmbH. All rights reserved. + +import Foundation +import Intents + +class HashtagIntentHandler: INExtension, HashtagIntentHandling { + func provideHashtagOptionsCollection(for intent: HashtagIntent, searchTerm: String?) async throws -> INObjectCollection { + + guard let authenticationBox = WidgetExtension.appContext + .authenticationService + .mastodonAuthenticationBoxes + .first + else { + return INObjectCollection(items: []) + } + + var results: [NSString] = [] + + if let searchTerm, searchTerm.isEmpty == false { + let searchResults = try await WidgetExtension.appContext + .apiService + .search(query: .init(q: searchTerm, type: .hashtags), authenticationBox: authenticationBox) + .value + .hashtags.compactMap { $0.name as NSString } + results = searchResults + + } else { + //TODO: Show hashtags I follow + } + + return INObjectCollection(items: results) + + } +} diff --git a/MastodonIntent/IntentHandler.swift b/MastodonIntent/IntentHandler.swift index c8fb67d4c..692af7ee3 100644 --- a/MastodonIntent/IntentHandler.swift +++ b/MastodonIntent/IntentHandler.swift @@ -19,6 +19,8 @@ class IntentHandler: INExtension { return FollowersCountIntentHandler() case is MultiFollowersCountIntent: return MultiFollowersCountIntentHandler() + case is HashtagIntent: + return HashtagIntentHandler() default: return self } diff --git a/WidgetExtension/Base.lproj/WidgetExtension.intentdefinition b/WidgetExtension/Base.lproj/WidgetExtension.intentdefinition index aca5db658..667efed07 100644 --- a/WidgetExtension/Base.lproj/WidgetExtension.intentdefinition +++ b/WidgetExtension/Base.lproj/WidgetExtension.intentdefinition @@ -430,29 +430,11 @@ INIntentParameterPromptDialogType Primary - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - There are ${count} options matching ‘${hashtag}’. - INIntentParameterPromptDialogFormatStringID - oQ0WOE - INIntentParameterPromptDialogType - DisambiguationIntroduction - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Just to confirm, you wanted ‘${hashtag}’? - INIntentParameterPromptDialogFormatStringID - REztzm - INIntentParameterPromptDialogType - Confirmation - INIntentParameterSupportsDynamicEnumeration + INIntentParameterSupportsSearch + INIntentParameterTag 5 INIntentParameterType From 48a14bc63cd24c3730426e985be6c992a078a746 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 27 Mar 2023 22:57:11 +0200 Subject: [PATCH 12/27] Show followed hashtags by default (IOS-37) --- MastodonIntent/Handler/HashtagIntentHandler.swift | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/MastodonIntent/Handler/HashtagIntentHandler.swift b/MastodonIntent/Handler/HashtagIntentHandler.swift index eaaf6c349..f6f330606 100644 --- a/MastodonIntent/Handler/HashtagIntentHandler.swift +++ b/MastodonIntent/Handler/HashtagIntentHandler.swift @@ -2,6 +2,7 @@ import Foundation import Intents +import MastodonSDK class HashtagIntentHandler: INExtension, HashtagIntentHandling { func provideHashtagOptionsCollection(for intent: HashtagIntent, searchTerm: String?) async throws -> INObjectCollection { @@ -21,11 +22,21 @@ class HashtagIntentHandler: INExtension, HashtagIntentHandling { .apiService .search(query: .init(q: searchTerm, type: .hashtags), authenticationBox: authenticationBox) .value - .hashtags.compactMap { $0.name as NSString } + .hashtags + .compactMap { $0.name as NSString } + results = searchResults } else { - //TODO: Show hashtags I follow + let followedTags = try await WidgetExtension.appContext.apiService.getFollowedTags( + domain: authenticationBox.domain, + query: Mastodon.API.Account.FollowedTagsQuery(limit: nil), + authenticationBox: authenticationBox) + .value + .compactMap { $0.name as NSString } + + results = followedTags + } return INObjectCollection(items: results) From 49307a316fe7f96ca65c701f0c2efa9724f6b0e9 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 28 Mar 2023 23:05:47 +0200 Subject: [PATCH 13/27] Add localization (IOS-37) --- Localization/app.json | 6 ++++++ .../Sources/MastodonLocalization/Generated/Strings.swift | 8 ++++++++ .../Resources/Base.lproj/Localizable.strings | 2 ++ .../Resources/en.lproj/Localizable.strings | 2 ++ WidgetExtension/Variants/Hashtag/HashtagWidget.swift | 6 +++--- 5 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Localization/app.json b/Localization/app.json index 5e63124f9..36275e994 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -870,6 +870,12 @@ "configuration_description": "Show latest followers.", "title": "Latest followers", "last_update": "Last update: %s" + }, + "hashtag": { + "configuration": { + "display_name": "Hashtag", + "description": "Shows a recent status with the selected hashtag." + } } } } diff --git a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift index e1c6db802..fb0b497c3 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift +++ b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift @@ -1543,6 +1543,14 @@ public enum L10n { /// FOLLOWERS public static let title = L10n.tr("Localizable", "Widget.FollowersCount.Title", fallback: "FOLLOWERS") } + public enum Hashtag { + public enum Configuration { + /// Shows a recent status with the selected hashtag + public static let description = L10n.tr("Localizable", "Widget.Hashtag.Configuration.Description", fallback: "Shows a recent status with the selected hashtag") + /// Hashtag + public static let displayName = L10n.tr("Localizable", "Widget.Hashtag.Configuration.DisplayName", fallback: "Hashtag") + } + } public enum LatestFollowers { /// Show latest followers. public static let configurationDescription = L10n.tr("Localizable", "Widget.LatestFollowers.ConfigurationDescription", fallback: "Show latest followers.") diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings index 9be911ddb..6f7d0c8f4 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings @@ -543,3 +543,5 @@ uploaded to Mastodon."; "Widget.MultipleFollowers.ConfigurationDisplayName" = "Multiple followers"; "Widget.MultipleFollowers.MockUser.AccountName" = "another@follower.social"; "Widget.MultipleFollowers.MockUser.DisplayName" = "Another follower"; +"Widget.Hashtag.Configuration.Description" = "Shows a recent status with the selected hashtag"; +"Widget.Hashtag.Configuration.DisplayName" = "Hashtag"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings index 9be911ddb..6f7d0c8f4 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings @@ -543,3 +543,5 @@ uploaded to Mastodon."; "Widget.MultipleFollowers.ConfigurationDisplayName" = "Multiple followers"; "Widget.MultipleFollowers.MockUser.AccountName" = "another@follower.social"; "Widget.MultipleFollowers.MockUser.DisplayName" = "Another follower"; +"Widget.Hashtag.Configuration.Description" = "Shows a recent status with the selected hashtag"; +"Widget.Hashtag.Configuration.DisplayName" = "Hashtag"; diff --git a/WidgetExtension/Variants/Hashtag/HashtagWidget.swift b/WidgetExtension/Variants/Hashtag/HashtagWidget.swift index c7ccfd49a..7d5b5218f 100644 --- a/WidgetExtension/Variants/Hashtag/HashtagWidget.swift +++ b/WidgetExtension/Variants/Hashtag/HashtagWidget.swift @@ -3,6 +3,7 @@ import WidgetKit import SwiftUI import MastodonSDK +import MastodonLocalization struct HashtagWidgetProvider: IntentTimelineProvider { func placeholder(in context: Context) -> HashtagWidgetTimelineEntry { @@ -145,9 +146,8 @@ struct HashtagWidget: Widget { IntentConfiguration(kind: "Hashtag", intent: HashtagIntent.self, provider: HashtagWidgetProvider()) { entry in HashtagWidgetView(entry: entry) } - //TODO: @zeitschlag Add Localization - .configurationDisplayName("Hashtag") - .description("Show a Hashtag") + .configurationDisplayName(L10n.Widget.Hashtag.Configuration.displayName) + .description(L10n.Widget.Hashtag.Configuration.description) .supportedFamilies(availableFamilies) } } From f5fca28cb0944f2eb4b2bcc563c745215522ed3b Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Thu, 30 Mar 2023 21:55:46 +0200 Subject: [PATCH 14/27] Show status-text in widget (IOS-37) --- .../Variants/Hashtag/HashtagWidgetView.swift | 55 ++++++++++++++++--- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift b/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift index 2c21a4aaf..c09c22826 100644 --- a/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift +++ b/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift @@ -37,8 +37,7 @@ struct HashtagWidgetView: View { .foregroundColor(.secondary) } - //TODO: Check MetaLabelRepresentable, maybe it's a way to color Hashtags? - Text(entry.hashtag.content) + Text(statusHTML: entry.hashtag.content) Spacer() HStack(alignment: .center, spacing: 16) { @@ -73,18 +72,58 @@ struct HashtagWidgetView: View { Text("|") .foregroundColor(.secondary) .font(.system(size: UIFontMetrics.default.scaledValue(for: 9))) - Text(entry.hashtag.hashtag) + Text(statusHTML: entry.hashtag.content) .foregroundColor(.secondary) .font(.system(size: UIFontMetrics.default.scaledValue(for: 13))) .fontWeight(.heavy) } - Text(entry.hashtag.content) - .foregroundColor(.primary) - .font(.system(size: UIFontMetrics.default.scaledValue(for: 16))) - .fontWeight(.medium) - .lineLimit(3) + Text(statusHTML: entry.hashtag.content) Spacer() } } } + +/// Inspired by: https://swiftuirecipes.com/blog/swiftui-text-with-html-via-nsattributedstring +extension Text { + init(statusHTML htmlString: String) { + let fullHTML = """ + + + + + + + \(htmlString) + + +""" + + let attributedString: NSAttributedString + if let data = fullHTML.data(using: .unicode), + let attrString = try? NSAttributedString(data: data, + options: [.documentType: NSAttributedString.DocumentType.html], + documentAttributes: nil) { + attributedString = attrString + } else { + attributedString = NSAttributedString() + } + + self.init(AttributedString(attributedString)) // uses the NSAttributedString initializer + } +} From 8b1316814a72a0b80fa7423fde3f37374cfc8a85 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Sat, 22 Apr 2023 23:00:55 +0200 Subject: [PATCH 15/27] Filter for content warnings (IOS-37) --- .../WidgetExtension.intentdefinition | 48 +++++++++++++++++-- .../Variants/Hashtag/HashtagWidget.swift | 15 +++++- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/WidgetExtension/Base.lproj/WidgetExtension.intentdefinition b/WidgetExtension/Base.lproj/WidgetExtension.intentdefinition index 667efed07..9a6dae161 100644 --- a/WidgetExtension/Base.lproj/WidgetExtension.intentdefinition +++ b/WidgetExtension/Base.lproj/WidgetExtension.intentdefinition @@ -11,9 +11,9 @@ INIntentDefinitionSystemVersion 22D68 INIntentDefinitionToolsBuildVersion - 14C18 + 14E222b INIntentDefinitionToolsVersion - 14.2 + 14.3 INIntents @@ -389,7 +389,7 @@ INIntentIneligibleForSuggestions INIntentLastParameterTag - 5 + 7 INIntentName Hashtag INIntentParameters @@ -440,6 +440,48 @@ INIntentParameterType String + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Ignore content warnings + INIntentParameterDisplayNameID + xcBHPA + INIntentParameterDisplayPriority + 2 + INIntentParameterMetadata + + INIntentParameterMetadataFalseDisplayName + false + INIntentParameterMetadataFalseDisplayNameID + wftYbm + INIntentParameterMetadataTrueDisplayName + true + INIntentParameterMetadataTrueDisplayNameID + QkKsLf + + INIntentParameterName + ignoreContentWarnings + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterTag + 7 + INIntentParameterType + Boolean + INIntentResponse diff --git a/WidgetExtension/Variants/Hashtag/HashtagWidget.swift b/WidgetExtension/Variants/Hashtag/HashtagWidget.swift index 7d5b5218f..28566e10f 100644 --- a/WidgetExtension/Variants/Hashtag/HashtagWidget.swift +++ b/WidgetExtension/Variants/Hashtag/HashtagWidget.swift @@ -49,10 +49,17 @@ extension HashtagWidgetProvider { do { let mostRecentStatuses = try await WidgetExtension.appContext .apiService - .hashtagTimeline(domain: authBox.domain, limit: 1, hashtag: desiredHashtag, authenticationBox: authBox) + .hashtagTimeline(domain: authBox.domain, limit: 40, hashtag: desiredHashtag, authenticationBox: authBox) .value - if let mostRecentStatus = mostRecentStatuses.first { + let filteredStatuses: [Mastodon.Entity.Status] + if configuration.ignoreContentWarnings?.boolValue == true { + filteredStatuses = mostRecentStatuses + } else { + filteredStatuses = mostRecentStatuses.filter { $0.sensitive == false } + } + + if let mostRecentStatus = filteredStatuses.first { let hashtagEntry = HashtagEntry( accountName: mostRecentStatus.account.displayNameWithFallback, @@ -70,6 +77,10 @@ extension HashtagWidgetProvider { ) completion(hashtagTimelineEntry) + } else { + let noStatusFound = HashtagWidgetTimelineEntry.notFound + + completion(noStatusFound) } } catch { completion(.notFound) From 1829793ac5acff6c7448502d8ab545fc4585d080 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Sat, 22 Apr 2023 23:20:32 +0200 Subject: [PATCH 16/27] Locale notfound/placeholder-texts (IOS-37) --- Localization/app.json | 10 +++++ .../Generated/Strings.swift | 18 +++++++++ .../Resources/Base.lproj/Localizable.strings | 8 ++++ .../Resources/en.lproj/Localizable.strings | 8 ++++ .../Variants/Hashtag/HashtagWidget.swift | 40 +++++++++---------- 5 files changed, 64 insertions(+), 20 deletions(-) diff --git a/Localization/app.json b/Localization/app.json index 36275e994..c58b8798d 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -875,6 +875,16 @@ "configuration": { "display_name": "Hashtag", "description": "Shows a recent status with the selected hashtag." + }, + "not_found": { + "account_name": "John Mastodon", + "account": "@johnMastodon@no-such.account", + "content": "Sorry, we didn't find a status with #%@. Please try a #DifferentHashtag or check the Widget-settings." + }, + "placeholder": { + "account_name": "John Mastodon", + "account": "@johnMastodon@no-such.account", + "content": "This is how a status with a #hashtag would look like. Of course you can decide about the #hashtag" } } } diff --git a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift index fb0b497c3..4131a6bab 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift +++ b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift @@ -1550,6 +1550,24 @@ public enum L10n { /// Hashtag public static let displayName = L10n.tr("Localizable", "Widget.Hashtag.Configuration.DisplayName", fallback: "Hashtag") } + public enum NotFound { + /// @johnMastodon@no-such.account + public static let account = L10n.tr("Localizable", "Widget.Hashtag.NotFound.Account", fallback: "@johnMastodon@no-such.account") + /// John Mastodon + public static let accountName = L10n.tr("Localizable", "Widget.Hashtag.NotFound.AccountName", fallback: "John Mastodon") + /// Sorry, we didn't find a status with #%@. Please try a #DifferentHashtag or check the Widget-settings. + public static func content(_ p1: Any) -> String { + return L10n.tr("Localizable", "Widget.Hashtag.NotFound.Content", String(describing: p1), fallback: "Sorry, we didn't find a status with #%@. Please try a #DifferentHashtag or check the Widget-settings.") + } + } + public enum Placeholder { + /// @johnMastodon@no-such.account + public static let account = L10n.tr("Localizable", "Widget.Hashtag.Placeholder.Account", fallback: "@johnMastodon@no-such.account") + /// John Mastodon + public static let accountName = L10n.tr("Localizable", "Widget.Hashtag.Placeholder.AccountName", fallback: "John Mastodon") + /// This is how a status with a #hashtag would look like. Of course you can decide about the #hashtag + public static let content = L10n.tr("Localizable", "Widget.Hashtag.Placeholder.Content", fallback: "This is how a status with a #hashtag would look like. Of course you can decide about the #hashtag") + } } public enum LatestFollowers { /// Show latest followers. diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings index 6f7d0c8f4..df3e00d39 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings @@ -545,3 +545,11 @@ uploaded to Mastodon."; "Widget.MultipleFollowers.MockUser.DisplayName" = "Another follower"; "Widget.Hashtag.Configuration.Description" = "Shows a recent status with the selected hashtag"; "Widget.Hashtag.Configuration.DisplayName" = "Hashtag"; + +"Widget.Hashtag.NotFound.AccountName" = "John Mastodon"; +"Widget.Hashtag.NotFound.Account" = "@johnMastodon@no-such.account"; +"Widget.Hashtag.NotFound.Content" = "Sorry, we didn't find a status with #%@. Please try a #DifferentHashtag or check the Widget-settings."; + +"Widget.Hashtag.Placeholder.AccountName" = "John Mastodon"; +"Widget.Hashtag.Placeholder.Account" = "@johnMastodon@no-such.account"; +"Widget.Hashtag.Placeholder.Content" = "This is how a status with a #hashtag would look like. Of course you can decide about the #hashtag"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings index 6f7d0c8f4..df3e00d39 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings @@ -545,3 +545,11 @@ uploaded to Mastodon."; "Widget.MultipleFollowers.MockUser.DisplayName" = "Another follower"; "Widget.Hashtag.Configuration.Description" = "Shows a recent status with the selected hashtag"; "Widget.Hashtag.Configuration.DisplayName" = "Hashtag"; + +"Widget.Hashtag.NotFound.AccountName" = "John Mastodon"; +"Widget.Hashtag.NotFound.Account" = "@johnMastodon@no-such.account"; +"Widget.Hashtag.NotFound.Content" = "Sorry, we didn't find a status with #%@. Please try a #DifferentHashtag or check the Widget-settings."; + +"Widget.Hashtag.Placeholder.AccountName" = "John Mastodon"; +"Widget.Hashtag.Placeholder.Account" = "@johnMastodon@no-such.account"; +"Widget.Hashtag.Placeholder.Content" = "This is how a status with a #hashtag would look like. Of course you can decide about the #hashtag"; diff --git a/WidgetExtension/Variants/Hashtag/HashtagWidget.swift b/WidgetExtension/Variants/Hashtag/HashtagWidget.swift index 28566e10f..22162f9a1 100644 --- a/WidgetExtension/Variants/Hashtag/HashtagWidget.swift +++ b/WidgetExtension/Variants/Hashtag/HashtagWidget.swift @@ -37,14 +37,15 @@ extension HashtagWidgetProvider { } } - Task { - let desiredHashtag: String + let desiredHashtag: String - if let hashtag = configuration.hashtag { - desiredHashtag = hashtag - } else { - return completion(.notFound) - } + if let hashtag = configuration.hashtag { + desiredHashtag = hashtag + } else { + return completion(.notFound("hashtag")) + } + + Task { do { let mostRecentStatuses = try await WidgetExtension.appContext @@ -78,12 +79,12 @@ extension HashtagWidgetProvider { completion(hashtagTimelineEntry) } else { - let noStatusFound = HashtagWidgetTimelineEntry.notFound + let noStatusFound = HashtagWidgetTimelineEntry.notFound(desiredHashtag) completion(noStatusFound) } } catch { - completion(.notFound) + completion(.notFound(desiredHashtag)) } } @@ -97,31 +98,30 @@ struct HashtagWidgetTimelineEntry: TimelineEntry { var hashtag: HashtagEntry static var placeholder: Self { - //TODO: @zeitschlag Add Localization HashtagWidgetTimelineEntry( date: .now, hashtag: HashtagEntry( - accountName: "John Mastodon", - account: "@johnmastodon@mastodon.social", - content: "Caturday is the best day of the week #CatsOfMastodon", + accountName: L10n.Widget.Hashtag.Placeholder.accountName, + account: L10n.Widget.Hashtag.Placeholder.account, + content: L10n.Widget.Hashtag.Placeholder.content, reblogCount: 13, favoriteCount: 12, - hashtag: "#CatsOfMastodon", - timestamp: .now.addingTimeInterval(-3600 * 18) + hashtag: "#hashtag", + timestamp: .now.addingTimeInterval(-3600 * 12) ) ) } - static var notFound: Self { + static func notFound(_ hashtag: String? = nil) -> Self { HashtagWidgetTimelineEntry( date: .now, hashtag: HashtagEntry( - accountName: "Not Found", - account: "404", - content: "Couldn't find a status, sorryyyyyyy", + accountName: L10n.Widget.Hashtag.NotFound.accountName, + account: L10n.Widget.Hashtag.NotFound.account, + content: L10n.Widget.Hashtag.NotFound.content(hashtag ?? "hashtag"), reblogCount: 0, favoriteCount: 0, - hashtag: "", + hashtag: hashtag ?? "", timestamp: .now ) ) From fee94c6a8dbd6eefce3e86e0522b00dc8cfea7b4 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 2 May 2023 18:44:20 +0200 Subject: [PATCH 17/27] Show hashtag instead of content on lockscreen Widget isn't working on lockscreen atm as we can't edit the widget to set the hashtag --- .../FollowersCount/FollowersCountWidgetView.swift | 2 +- .../Variants/Hashtag/HashtagWidgetView.swift | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/WidgetExtension/Variants/FollowersCount/FollowersCountWidgetView.swift b/WidgetExtension/Variants/FollowersCount/FollowersCountWidgetView.swift index ab0f29d57..2ad348c71 100644 --- a/WidgetExtension/Variants/FollowersCount/FollowersCountWidgetView.swift +++ b/WidgetExtension/Variants/FollowersCount/FollowersCountWidgetView.swift @@ -127,7 +127,7 @@ struct FollowersCountWidgetView: View { .padding(.top, 16) } - private func viewForAccessoryRectangular(_ account :FollowersEntryAccountable) -> some View { + private func viewForAccessoryRectangular(_ account: FollowersEntryAccountable) -> some View { HStack(spacing: 0) { VStack(alignment: .leading, spacing: 0) { HStack(alignment: .center) { diff --git a/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift b/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift index c09c22826..52b3a75ed 100644 --- a/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift +++ b/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift @@ -72,13 +72,14 @@ struct HashtagWidgetView: View { Text("|") .foregroundColor(.secondary) .font(.system(size: UIFontMetrics.default.scaledValue(for: 9))) - Text(statusHTML: entry.hashtag.content) + Text(statusHTML: entry.hashtag.hashtag) .foregroundColor(.secondary) .font(.system(size: UIFontMetrics.default.scaledValue(for: 13))) .fontWeight(.heavy) - } - Text(statusHTML: entry.hashtag.content) + Text(statusHTML: entry.hashtag.content, fontSize: 12) + .foregroundColor(.primary) + .lineLimit(3) Spacer() } } @@ -86,7 +87,7 @@ struct HashtagWidgetView: View { /// Inspired by: https://swiftuirecipes.com/blog/swiftui-text-with-html-via-nsattributedstring extension Text { - init(statusHTML htmlString: String) { + init(statusHTML htmlString: String, fontSize: Int = 16) { let fullHTML = """ @@ -94,7 +95,7 @@ extension Text { @@ -119,16 +119,16 @@ extension Text { """ - let attributedString: NSAttributedString - if let data = fullHTML.data(using: .unicode), - let attrString = try? NSAttributedString(data: data, - options: [.documentType: NSAttributedString.DocumentType.html], - documentAttributes: nil) { - attributedString = attrString - } else { - attributedString = NSAttributedString() - } + let attributedString: NSAttributedString + if let data = fullHTML.data(using: .unicode), + let attrString = try? NSAttributedString(data: data, + options: [.documentType: NSAttributedString.DocumentType.html], + documentAttributes: nil) { + attributedString = attrString + } else { + attributedString = NSAttributedString() + } - self.init(AttributedString(attributedString)) // uses the NSAttributedString initializer - } + self.init(AttributedString(attributedString)) // uses the NSAttributedString initializer + } } From 05c35e9bd4a26a7f57e3123af9940e76d46eec5e Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 21 Apr 2023 23:24:48 +0200 Subject: [PATCH 20/27] Set timezone for joined-dates to UTC (#1015) Fixes #1015 --- Mastodon/Diffable/Profile/ProfileFieldSection.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Mastodon/Diffable/Profile/ProfileFieldSection.swift b/Mastodon/Diffable/Profile/ProfileFieldSection.swift index 02fb75bad..351c87df5 100644 --- a/Mastodon/Diffable/Profile/ProfileFieldSection.swift +++ b/Mastodon/Diffable/Profile/ProfileFieldSection.swift @@ -48,6 +48,7 @@ extension ProfileFieldSection { let formatter = DateFormatter() formatter.dateStyle = .medium formatter.timeStyle = .none + formatter.timeZone = TimeZone(identifier: "UTC") value = formatter.string(from: date) emojiMeta = [:] verified = false From 4066b2603094d7b07d1ae4948b335de454aa059b Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Thu, 13 Apr 2023 22:12:51 +0200 Subject: [PATCH 21/27] Put actions in scrollView (#1009) --- .../Toolbar/ComposeContentToolbarView.swift | 294 +++++++++--------- 1 file changed, 147 insertions(+), 147 deletions(-) diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Toolbar/ComposeContentToolbarView.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Toolbar/ComposeContentToolbarView.swift index b480834c2..90f42822b 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Toolbar/ComposeContentToolbarView.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Toolbar/ComposeContentToolbarView.swift @@ -18,8 +18,6 @@ protocol ComposeContentToolbarViewDelegate: AnyObject { struct ComposeContentToolbarView: View { - let logger = Logger(subsystem: "ComposeContentToolbarView", category: "View") - static var toolbarHeight: CGFloat { 48 } @ObservedObject var viewModel: ViewModel @@ -32,160 +30,162 @@ struct ComposeContentToolbarView: View { var body: some View { HStack(spacing: .zero) { - ForEach(ComposeContentToolbarView.ViewModel.Action.allCases, id: \.self) { action in - switch action { - case .attachment: - Menu { - ForEach(ComposeContentToolbarView.ViewModel.AttachmentAction.allCases, id: \.self) { attachmentAction in - Button { - logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public), \(attachmentAction.title)") - viewModel.delegate?.composeContentToolbarView(viewModel, attachmentMenuDidPressed: attachmentAction) - } label: { - Label { - Text(attachmentAction.title) - } icon: { - Image(uiImage: attachmentAction.image) + ScrollView(.horizontal) { + HStack(spacing: .zero) { + ForEach(ComposeContentToolbarView.ViewModel.Action.allCases, id: \.self) { action in + switch action { + case .attachment: + Menu { + ForEach(ComposeContentToolbarView.ViewModel.AttachmentAction.allCases, id: \.self) { attachmentAction in + Button { + viewModel.delegate?.composeContentToolbarView(viewModel, attachmentMenuDidPressed: attachmentAction) + } label: { + Label { + Text(attachmentAction.title) + } icon: { + Image(uiImage: attachmentAction.image) + } + } + } + } label: { + label(for: action) + .opacity(viewModel.isAttachmentButtonEnabled ? 1.0 : 0.5) } - } - } - } label: { - label(for: action) - .opacity(viewModel.isAttachmentButtonEnabled ? 1.0 : 0.5) - } - .disabled(!viewModel.isAttachmentButtonEnabled) - .frame(width: 48, height: 48) - case .visibility: - Menu { - Picker(selection: $viewModel.visibility) { - ForEach(viewModel.allVisibilities, id: \.self) { visibility in - Label { - Text(visibility.title) - } icon: { - Image(uiImage: visibility.image) + .disabled(!viewModel.isAttachmentButtonEnabled) + .frame(width: 48, height: 48) + case .visibility: + Menu { + Picker(selection: $viewModel.visibility) { + ForEach(viewModel.allVisibilities, id: \.self) { visibility in + Label { + Text(visibility.title) + } icon: { + Image(uiImage: visibility.image) + } + } + } label: { + Text(viewModel.visibility.title) + } + } label: { + label(for: viewModel.visibility.image) + .accessibilityLabel(L10n.Scene.Compose.Keyboard.selectVisibilityEntry(viewModel.visibility.title)) + .opacity(viewModel.isVisibilityButtonEnabled ? 1.0 : 0.5) } - } - } label: { - Text(viewModel.visibility.title) - } - } label: { - label(for: viewModel.visibility.image) - .accessibilityLabel(L10n.Scene.Compose.Keyboard.selectVisibilityEntry(viewModel.visibility.title)) - .opacity(viewModel.isVisibilityButtonEnabled ? 1.0 : 0.5) - } - .disabled(!viewModel.isVisibilityButtonEnabled) - .frame(width: 48, height: 48) - case .poll: - Button { - logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(String(describing: action))") - viewModel.delegate?.composeContentToolbarView(viewModel, toolbarItemDidPressed: action) - } label: { - label(for: action) - .opacity(viewModel.isPollButtonEnabled ? 1.0 : 0.5) - } - .disabled(!viewModel.isPollButtonEnabled) - .frame(width: 48, height: 48) - case .language: - Menu { - Section {} // workaround a bug where the “Suggested” section doesn’t appear - if !viewModel.suggestedLanguages.isEmpty { - Section(L10n.Scene.Compose.Language.suggested) { - ForEach(viewModel.suggestedLanguages.compactMap(Language.init(id:))) { lang in - Toggle(isOn: languageBinding(for: lang.id)) { - Text(lang.label) + .disabled(!viewModel.isVisibilityButtonEnabled) + .frame(width: 48, height: 48) + case .poll: + Button { + viewModel.delegate?.composeContentToolbarView(viewModel, toolbarItemDidPressed: action) + } label: { + label(for: action) + .opacity(viewModel.isPollButtonEnabled ? 1.0 : 0.5) + } + .disabled(!viewModel.isPollButtonEnabled) + .frame(width: 48, height: 48) + case .language: + Menu { + Section {} // workaround a bug where the “Suggested” section doesn’t appear + if !viewModel.suggestedLanguages.isEmpty { + Section(L10n.Scene.Compose.Language.suggested) { + ForEach(viewModel.suggestedLanguages.compactMap(Language.init(id:))) { lang in + Toggle(isOn: languageBinding(for: lang.id)) { + Text(lang.label) + } + } + } + } + let recent = viewModel.recentLanguages.filter { !viewModel.suggestedLanguages.contains($0) } + if !recent.isEmpty { + Section(L10n.Scene.Compose.Language.recent) { + ForEach(recent.compactMap(Language.init(id:))) { lang in + Toggle(isOn: languageBinding(for: lang.id)) { + Text(lang.label) + } + } + } + } + if !(recent + viewModel.suggestedLanguages).contains(viewModel.language) { + Toggle(isOn: languageBinding(for: viewModel.language)) { + Text(Language(id: viewModel.language)?.label ?? AttributedString("\(viewModel.language)")) + } + } + Button(L10n.Scene.Compose.Language.other) { + showingLanguagePicker = true + } + } label: { + let font: SwiftUI.Font = { + if #available(iOS 16, *) { + return .system(size: 11, weight: .semibold).width(viewModel.language.count == 3 ? .compressed : .standard) + } else { + return .system(size: 11, weight: .semibold) + } + }() + + Text(viewModel.language) + .font(font) + .textCase(.uppercase) + .padding(.horizontal, 4) + .minimumScaleFactor(0.5) + .frame(width: 24, height: 24, alignment: .center) + .overlay { RoundedRectangle(cornerRadius: 7).inset(by: 3).stroke(lineWidth: 1.5) } + .accessibilityLabel(L10n.Scene.Compose.Language.title) + .accessibilityValue(Text(Language(id: viewModel.language)?.label ?? AttributedString("\(viewModel.language)"))) + .foregroundColor(Color(Asset.Scene.Compose.buttonTint.color)) + .overlay(alignment: .topTrailing) { + Group { + if let suggested = viewModel.highConfidenceSuggestedLanguage, + suggested != viewModel.language, + !didChangeLanguage { + Circle().fill(.blue) + .frame(width: 8, height: 8) + } + } + .transition(.opacity) + .animation(.default, value: [viewModel.highConfidenceSuggestedLanguage, viewModel.language]) + } + // fixes weird appearance when drawing at low opacity (eg when pressed) + .drawingGroup() + } + .frame(width: 48, height: 48) + .popover(isPresented: $showingLanguagePicker) { + let picker = LanguagePicker { newLanguage in + viewModel.language = newLanguage + didChangeLanguage = true + showingLanguagePicker = false + } + if verticalSizeClass == .regular && horizontalSizeClass == .regular { + // explicitly size picker when it’s a popover + picker.frame(width: 400, height: 500) + } else { + picker } } - } - } - let recent = viewModel.recentLanguages.filter { !viewModel.suggestedLanguages.contains($0) } - if !recent.isEmpty { - Section(L10n.Scene.Compose.Language.recent) { - ForEach(recent.compactMap(Language.init(id:))) { lang in - Toggle(isOn: languageBinding(for: lang.id)) { - Text(lang.label) - } + default: + Button { + viewModel.delegate?.composeContentToolbarView(viewModel, toolbarItemDidPressed: action) + } label: { + label(for: action) } - } - } - if !(recent + viewModel.suggestedLanguages).contains(viewModel.language) { - Toggle(isOn: languageBinding(for: viewModel.language)) { - Text(Language(id: viewModel.language)?.label ?? AttributedString("\(viewModel.language)")) - } - } - Button(L10n.Scene.Compose.Language.other) { - showingLanguagePicker = true - } - } label: { - let font: SwiftUI.Font = { - if #available(iOS 16, *) { - return .system(size: 11, weight: .semibold).width(viewModel.language.count == 3 ? .compressed : .standard) - } else { - return .system(size: 11, weight: .semibold) - } - }() - - Text(viewModel.language) - .font(font) - .textCase(.uppercase) - .padding(.horizontal, 4) - .minimumScaleFactor(0.5) - .frame(width: 24, height: 24, alignment: .center) - .overlay { RoundedRectangle(cornerRadius: 7).inset(by: 3).stroke(lineWidth: 1.5) } - .accessibilityLabel(L10n.Scene.Compose.Language.title) - .accessibilityValue(Text(Language(id: viewModel.language)?.label ?? AttributedString("\(viewModel.language)"))) - .foregroundColor(Color(Asset.Scene.Compose.buttonTint.color)) - .overlay(alignment: .topTrailing) { - Group { - if let suggested = viewModel.highConfidenceSuggestedLanguage, - suggested != viewModel.language, - !didChangeLanguage { - Circle().fill(.blue) - .frame(width: 8, height: 8) - } - } - .transition(.opacity) - .animation(.default, value: [viewModel.highConfidenceSuggestedLanguage, viewModel.language]) - } - // fixes weird appearance when drawing at low opacity (eg when pressed) - .drawingGroup() - } - .frame(width: 48, height: 48) - .popover(isPresented: $showingLanguagePicker) { - let picker = LanguagePicker { newLanguage in - viewModel.language = newLanguage - didChangeLanguage = true - showingLanguagePicker = false - } - if verticalSizeClass == .regular && horizontalSizeClass == .regular { - // explicitly size picker when it’s a popover - picker.frame(width: 400, height: 500) - } else { - picker + .frame(width: 48, height: 48) } } - default: - Button { - logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(String(describing: action))") - viewModel.delegate?.composeContentToolbarView(viewModel, toolbarItemDidPressed: action) - } label: { - label(for: action) - } - .frame(width: 48, height: 48) } } - Spacer() - let count: Int = { - if viewModel.isContentWarningActive { - return viewModel.contentWeightedLength + viewModel.contentWarningWeightedLength - } else { - return viewModel.contentWeightedLength - } - }() - let remains = viewModel.maxTextInputLimit - count - let isOverflow = remains < 0 - Text("\(remains)") - .foregroundColor(Color(isOverflow ? UIColor.systemRed : UIColor.secondaryLabel)) - .font(.system(size: isOverflow ? 18 : 16, weight: isOverflow ? .medium : .regular)) - .accessibilityLabel(L10n.A11y.Plural.Count.charactersLeft(remains)) + Spacer() + let count: Int = { + if viewModel.isContentWarningActive { + return viewModel.contentWeightedLength + viewModel.contentWarningWeightedLength + } else { + return viewModel.contentWeightedLength + } + }() + let remains = viewModel.maxTextInputLimit - count + let isOverflow = remains < 0 + Text("\(remains)") + .foregroundColor(Color(isOverflow ? UIColor.systemRed : UIColor.secondaryLabel)) + .font(.system(size: isOverflow ? 18 : 16, weight: isOverflow ? .medium : .regular)) + .accessibilityLabel(L10n.A11y.Plural.Count.charactersLeft(remains)) + } .padding(.leading, 4) // 4 + 12 = 16 .padding(.trailing, 16) From e6e691b7aa342458673b2e968287f4dce72e2544 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Thu, 13 Apr 2023 22:39:01 +0200 Subject: [PATCH 22/27] Scroll enabled for large display zoom only (#1009) --- .../Toolbar/ComposeContentToolbarView.swift | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Toolbar/ComposeContentToolbarView.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Toolbar/ComposeContentToolbarView.swift index 90f42822b..71f7c9cb5 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Toolbar/ComposeContentToolbarView.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Toolbar/ComposeContentToolbarView.swift @@ -21,6 +21,7 @@ struct ComposeContentToolbarView: View { static var toolbarHeight: CGFloat { 48 } @ObservedObject var viewModel: ViewModel + let isZoomed = (UIScreen.main.scale != UIScreen.main.nativeScale) @State private var showingLanguagePicker = false @State private var didChangeLanguage = false @@ -30,7 +31,7 @@ struct ComposeContentToolbarView: View { var body: some View { HStack(spacing: .zero) { - ScrollView(.horizontal) { + ScrollView(isZoomed ? .horizontal : []) { HStack(spacing: .zero) { ForEach(ComposeContentToolbarView.ViewModel.Action.allCases, id: \.self) { action in switch action { @@ -171,20 +172,21 @@ struct ComposeContentToolbarView: View { } } } - Spacer() - let count: Int = { - if viewModel.isContentWarningActive { - return viewModel.contentWeightedLength + viewModel.contentWarningWeightedLength - } else { - return viewModel.contentWeightedLength - } - }() - let remains = viewModel.maxTextInputLimit - count - let isOverflow = remains < 0 - Text("\(remains)") - .foregroundColor(Color(isOverflow ? UIColor.systemRed : UIColor.secondaryLabel)) - .font(.system(size: isOverflow ? 18 : 16, weight: isOverflow ? .medium : .regular)) - .accessibilityLabel(L10n.A11y.Plural.Count.charactersLeft(remains)) + + Spacer() + let count: Int = { + if viewModel.isContentWarningActive { + return viewModel.contentWeightedLength + viewModel.contentWarningWeightedLength + } else { + return viewModel.contentWeightedLength + } + }() + let remains = viewModel.maxTextInputLimit - count + let isOverflow = remains < 0 + Text("\(remains)") + .foregroundColor(Color(isOverflow ? UIColor.systemRed : UIColor.secondaryLabel)) + .font(.system(size: isOverflow ? 18 : 16, weight: isOverflow ? .medium : .regular)) + .accessibilityLabel(L10n.A11y.Plural.Count.charactersLeft(remains)) } .padding(.leading, 4) // 4 + 12 = 16 From da8da2ae4bd25f9f88dfcaf91137220641df5295 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 5 May 2023 12:10:57 +0200 Subject: [PATCH 23/27] Use better strings for placeholders (IOS-152) Co-authored-by: Jed Fox --- Localization/app.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Localization/app.json b/Localization/app.json index c58b8798d..62846ef1a 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -874,17 +874,17 @@ "hashtag": { "configuration": { "display_name": "Hashtag", - "description": "Shows a recent status with the selected hashtag." + "description": "Shows a recent post with the selected hashtag." }, "not_found": { "account_name": "John Mastodon", "account": "@johnMastodon@no-such.account", - "content": "Sorry, we didn't find a status with #%@. Please try a #DifferentHashtag or check the Widget-settings." + "content": "Sorry, we couldn’t find any posts with the hashtag #%@. Please try a #DifferentHashtag or check the widget settings." }, "placeholder": { "account_name": "John Mastodon", "account": "@johnMastodon@no-such.account", - "content": "This is how a status with a #hashtag would look like. Of course you can decide about the #hashtag" + "content": "This is how a post with a #hashtag would look. Pick whichever #hashtag you want in the widget settings." } } } From d406dcd5532ade55db16a5c8b11547ac928f8f45 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 5 May 2023 12:29:46 +0200 Subject: [PATCH 24/27] Use better strings in strings-files, too (IOS-152) --- .../MastodonLocalization/Generated/Strings.swift | 12 ++++++------ .../Resources/Base.lproj/Localizable.strings | 6 +++--- .../Resources/en.lproj/Localizable.strings | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift index 4131a6bab..6a426e8d2 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift +++ b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift @@ -1545,8 +1545,8 @@ public enum L10n { } public enum Hashtag { public enum Configuration { - /// Shows a recent status with the selected hashtag - public static let description = L10n.tr("Localizable", "Widget.Hashtag.Configuration.Description", fallback: "Shows a recent status with the selected hashtag") + /// Shows a recent post with the selected hashtag + public static let description = L10n.tr("Localizable", "Widget.Hashtag.Configuration.Description", fallback: "Shows a recent post with the selected hashtag") /// Hashtag public static let displayName = L10n.tr("Localizable", "Widget.Hashtag.Configuration.DisplayName", fallback: "Hashtag") } @@ -1555,9 +1555,9 @@ public enum L10n { public static let account = L10n.tr("Localizable", "Widget.Hashtag.NotFound.Account", fallback: "@johnMastodon@no-such.account") /// John Mastodon public static let accountName = L10n.tr("Localizable", "Widget.Hashtag.NotFound.AccountName", fallback: "John Mastodon") - /// Sorry, we didn't find a status with #%@. Please try a #DifferentHashtag or check the Widget-settings. + /// Sorry, we couldn’t find any posts with the hashtag #%@. Please try a #DifferentHashtag or check the widget settings public static func content(_ p1: Any) -> String { - return L10n.tr("Localizable", "Widget.Hashtag.NotFound.Content", String(describing: p1), fallback: "Sorry, we didn't find a status with #%@. Please try a #DifferentHashtag or check the Widget-settings.") + return L10n.tr("Localizable", "Widget.Hashtag.NotFound.Content", String(describing: p1), fallback: "Sorry, we couldn’t find any posts with the hashtag #%@. Please try a #DifferentHashtag or check the widget settings") } } public enum Placeholder { @@ -1565,8 +1565,8 @@ public enum L10n { public static let account = L10n.tr("Localizable", "Widget.Hashtag.Placeholder.Account", fallback: "@johnMastodon@no-such.account") /// John Mastodon public static let accountName = L10n.tr("Localizable", "Widget.Hashtag.Placeholder.AccountName", fallback: "John Mastodon") - /// This is how a status with a #hashtag would look like. Of course you can decide about the #hashtag - public static let content = L10n.tr("Localizable", "Widget.Hashtag.Placeholder.Content", fallback: "This is how a status with a #hashtag would look like. Of course you can decide about the #hashtag") + /// This is how a post with a #hashtag would look. Pick whichever #hashtag you want in the widget settings + public static let content = L10n.tr("Localizable", "Widget.Hashtag.Placeholder.Content", fallback: "This is how a post with a #hashtag would look. Pick whichever #hashtag you want in the widget settings") } } public enum LatestFollowers { diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings index df3e00d39..41f663c4a 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings @@ -543,13 +543,13 @@ uploaded to Mastodon."; "Widget.MultipleFollowers.ConfigurationDisplayName" = "Multiple followers"; "Widget.MultipleFollowers.MockUser.AccountName" = "another@follower.social"; "Widget.MultipleFollowers.MockUser.DisplayName" = "Another follower"; -"Widget.Hashtag.Configuration.Description" = "Shows a recent status with the selected hashtag"; +"Widget.Hashtag.Configuration.Description" = "Shows a recent post with the selected hashtag"; "Widget.Hashtag.Configuration.DisplayName" = "Hashtag"; "Widget.Hashtag.NotFound.AccountName" = "John Mastodon"; "Widget.Hashtag.NotFound.Account" = "@johnMastodon@no-such.account"; -"Widget.Hashtag.NotFound.Content" = "Sorry, we didn't find a status with #%@. Please try a #DifferentHashtag or check the Widget-settings."; +"Widget.Hashtag.NotFound.Content" = "Sorry, we couldn’t find any posts with the hashtag #%@. Please try a #DifferentHashtag or check the widget settings"; "Widget.Hashtag.Placeholder.AccountName" = "John Mastodon"; "Widget.Hashtag.Placeholder.Account" = "@johnMastodon@no-such.account"; -"Widget.Hashtag.Placeholder.Content" = "This is how a status with a #hashtag would look like. Of course you can decide about the #hashtag"; +"Widget.Hashtag.Placeholder.Content" = "This is how a post with a #hashtag would look. Pick whichever #hashtag you want in the widget settings"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings index df3e00d39..41f663c4a 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings @@ -543,13 +543,13 @@ uploaded to Mastodon."; "Widget.MultipleFollowers.ConfigurationDisplayName" = "Multiple followers"; "Widget.MultipleFollowers.MockUser.AccountName" = "another@follower.social"; "Widget.MultipleFollowers.MockUser.DisplayName" = "Another follower"; -"Widget.Hashtag.Configuration.Description" = "Shows a recent status with the selected hashtag"; +"Widget.Hashtag.Configuration.Description" = "Shows a recent post with the selected hashtag"; "Widget.Hashtag.Configuration.DisplayName" = "Hashtag"; "Widget.Hashtag.NotFound.AccountName" = "John Mastodon"; "Widget.Hashtag.NotFound.Account" = "@johnMastodon@no-such.account"; -"Widget.Hashtag.NotFound.Content" = "Sorry, we didn't find a status with #%@. Please try a #DifferentHashtag or check the Widget-settings."; +"Widget.Hashtag.NotFound.Content" = "Sorry, we couldn’t find any posts with the hashtag #%@. Please try a #DifferentHashtag or check the widget settings"; "Widget.Hashtag.Placeholder.AccountName" = "John Mastodon"; "Widget.Hashtag.Placeholder.Account" = "@johnMastodon@no-such.account"; -"Widget.Hashtag.Placeholder.Content" = "This is how a status with a #hashtag would look like. Of course you can decide about the #hashtag"; +"Widget.Hashtag.Placeholder.Content" = "This is how a post with a #hashtag would look. Pick whichever #hashtag you want in the widget settings"; From 0fbe54d368b664bbf1418701f290d45eb6fb3ffd Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 5 May 2023 15:15:37 +0200 Subject: [PATCH 25/27] Use colors from assets (IOS-152) --- .../Extension/UIColor+Extensions.swift | 20 ++++++++++ .../Colors/Blurple.colorset/Contents.json | 38 +++++++++++++++++++ .../Assets.xcassets/Colors/Contents.json | 9 +++++ .../Colors/TextColor.colorset/Contents.json | 38 +++++++++++++++++++ .../Variants/Hashtag/HashtagWidgetView.swift | 4 +- 5 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 MastodonSDK/Sources/MastodonUI/Extension/UIColor+Extensions.swift create mode 100644 WidgetExtension/Assets.xcassets/Colors/Blurple.colorset/Contents.json create mode 100644 WidgetExtension/Assets.xcassets/Colors/Contents.json create mode 100644 WidgetExtension/Assets.xcassets/Colors/TextColor.colorset/Contents.json diff --git a/MastodonSDK/Sources/MastodonUI/Extension/UIColor+Extensions.swift b/MastodonSDK/Sources/MastodonUI/Extension/UIColor+Extensions.swift new file mode 100644 index 000000000..c43aed3d8 --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/Extension/UIColor+Extensions.swift @@ -0,0 +1,20 @@ +// Copyright © 2023 Mastodon gGmbH. All rights reserved. + +import UIKit + +extension UIColor { + public var hexValue: String { + let components = cgColor.components + + let red: CGFloat = components?[0] ?? 0.0 + let green: CGFloat = components?[1] ?? 0.0 + let blue: CGFloat = components?[2] ?? 0.0 + + return String( + format: "#%02lX%02lX%02lX", + lroundf(Float(red * 255)), + lroundf(Float(green * 255)), + lroundf(Float(blue * 255)) + ) + } +} diff --git a/WidgetExtension/Assets.xcassets/Colors/Blurple.colorset/Contents.json b/WidgetExtension/Assets.xcassets/Colors/Blurple.colorset/Contents.json new file mode 100644 index 000000000..09bdab5a3 --- /dev/null +++ b/WidgetExtension/Assets.xcassets/Colors/Blurple.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.984", + "green" : "0.173", + "red" : "0.333" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.980", + "green" : "0.541", + "red" : "0.522" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WidgetExtension/Assets.xcassets/Colors/Contents.json b/WidgetExtension/Assets.xcassets/Colors/Contents.json new file mode 100644 index 000000000..6e965652d --- /dev/null +++ b/WidgetExtension/Assets.xcassets/Colors/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/WidgetExtension/Assets.xcassets/Colors/TextColor.colorset/Contents.json b/WidgetExtension/Assets.xcassets/Colors/TextColor.colorset/Contents.json new file mode 100644 index 000000000..d89071918 --- /dev/null +++ b/WidgetExtension/Assets.xcassets/Colors/TextColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.000", + "green" : "0.000", + "red" : "0.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift b/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift index 22ec056b3..da1d6bc7b 100644 --- a/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift +++ b/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift @@ -91,8 +91,8 @@ struct HashtagWidgetView: View { extension Text { init(statusHTML htmlString: String, fontSize: Int = 16, fontWeight: Int = 400, colorScheme: ColorScheme = .light) { - let textColor = (colorScheme == .light) ? "#000000" : "#FFFFFF" - let accentColor = (colorScheme == .light) ? "#563ACC" : "#858AFA" + let textColor = (UIColor(named: "Colors/TextColor") ?? UIColor.gray).hexValue + let accentColor = (UIColor(named: "Colors/Blurple") ?? UIColor.purple).hexValue let fullHTML = """ From f3becbec3ffeded992dcbf4432f9107b184876f5 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 5 May 2023 15:19:46 +0200 Subject: [PATCH 26/27] Show raw html in case of something went wrong (IOS-152) Co-authored-by: Jed Fox --- WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift b/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift index da1d6bc7b..d9f9d4556 100644 --- a/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift +++ b/WidgetExtension/Variants/Hashtag/HashtagWidgetView.swift @@ -126,7 +126,7 @@ extension Text { documentAttributes: nil) { attributedString = attrString } else { - attributedString = NSAttributedString() + attributedString = NSAttributedString(string: htmlString) } self.init(AttributedString(attributedString)) // uses the NSAttributedString initializer From 413a29208f689c4118bc9253556e57b21a394ac3 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 5 May 2023 15:35:15 +0200 Subject: [PATCH 27/27] Update hashtag every 15 minutes (IOS-152) --- WidgetExtension/Variants/Hashtag/HashtagWidget.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WidgetExtension/Variants/Hashtag/HashtagWidget.swift b/WidgetExtension/Variants/Hashtag/HashtagWidget.swift index 22162f9a1..86e9f485c 100644 --- a/WidgetExtension/Variants/Hashtag/HashtagWidget.swift +++ b/WidgetExtension/Variants/Hashtag/HashtagWidget.swift @@ -16,7 +16,7 @@ struct HashtagWidgetProvider: IntentTimelineProvider { func getTimeline(for configuration: HashtagIntent, in context: Context, completion: @escaping (Timeline) -> Void) { loadMostRecentHashtag(for: configuration, in: context) { entry in - completion(Timeline(entries: [entry], policy: .after(.now))) + completion(Timeline(entries: [entry], policy: .after(.now.addingTimeInterval(60 * 15)))) } } }