From 264668a663d6f1366ddde48f132cafab658b0516 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Wed, 18 Nov 2020 15:43:14 +0800 Subject: [PATCH] Widgets simplified code added large widgets localised the counts updated technotes --- NetNewsWire.xcodeproj/project.pbxproj | 16 +++++- Shared/Widget/WidgetDataEncoder.swift | 11 ++-- Technotes/Widgets.md | 5 +- Widget/Localizable.stringsdict | 60 +++++++++++++++++++++ Widget/Widget Views/StarredWidget.swift | 69 +++++++----------------- Widget/Widget Views/TodayWidget.swift | 69 +++++++----------------- Widget/Widget Views/UnreadWidget.swift | 71 +++++++------------------ Widget/WidgetBundle.swift | 6 +-- 8 files changed, 142 insertions(+), 165 deletions(-) create mode 100644 Widget/Localizable.stringsdict diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 127657480..41fcd9420 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -85,6 +85,7 @@ 17A1598624E3DEDD005DA32A /* RSDatabase in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 17A1598424E3DEDD005DA32A /* RSDatabase */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 17A1598824E3DEDD005DA32A /* RSParser in Frameworks */ = {isa = PBXBuildFile; productRef = 17A1598724E3DEDD005DA32A /* RSParser */; }; 17A1598924E3DEDD005DA32A /* RSParser in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 17A1598724E3DEDD005DA32A /* RSParser */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + 17D0682C2564F47E00C0B37E /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 17D0682B2564F47E00C0B37E /* Localizable.stringsdict */; }; 17D232A824AFF10A0005F075 /* AddWebFeedModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D232A724AFF10A0005F075 /* AddWebFeedModel.swift */; }; 17D232A924AFF10A0005F075 /* AddWebFeedModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D232A724AFF10A0005F075 /* AddWebFeedModel.swift */; }; 17D5F17124B0BC6700375168 /* SidebarToolbarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D5F17024B0BC6700375168 /* SidebarToolbarModel.swift */; }; @@ -1523,6 +1524,7 @@ 1799E6CC24C320D600511E91 /* InspectorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorModel.swift; sourceTree = ""; }; 179DBBA2B22A659F81EED6F9 /* AccountsNewsBlurWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsNewsBlurWindowController.swift; sourceTree = ""; }; 17B223DB24AC24D2001E4592 /* TimelineLayoutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineLayoutView.swift; sourceTree = ""; }; + 17D0682B2564F47E00C0B37E /* Localizable.stringsdict */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; path = Localizable.stringsdict; sourceTree = ""; }; 17D232A724AFF10A0005F075 /* AddWebFeedModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedModel.swift; sourceTree = ""; }; 17D5F17024B0BC6700375168 /* SidebarToolbarModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarToolbarModel.swift; sourceTree = ""; }; 17E4DBD524BFC53E00FE462A /* AdvancedPreferencesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedPreferencesModel.swift; sourceTree = ""; }; @@ -2266,6 +2268,7 @@ 176813F82564BB2C00D98635 /* Widget */ = { isa = PBXGroup; children = ( + 17D0683F2564F7AD00C0B37E /* Helpers */, 176814822564C02A00D98635 /* NetNewsWire_iOS_WidgetExtension.entitlements */, 176814122564BC8A00D98635 /* WidgetBundle.swift */, 1768142C2564BCA800D98635 /* TimelineProvider.swift */, @@ -2274,6 +2277,7 @@ 176814792564BE3C00D98635 /* Resources */, 176813FB2564BB2D00D98635 /* Assets.xcassets */, 176813FD2564BB2D00D98635 /* Info.plist */, + 17D0682B2564F47E00C0B37E /* Localizable.stringsdict */, ); path = Widget; sourceTree = ""; @@ -2385,6 +2389,13 @@ path = Add; sourceTree = ""; }; + 17D0683F2564F7AD00C0B37E /* Helpers */ = { + isa = PBXGroup; + children = ( + ); + path = Helpers; + sourceTree = ""; + }; 510289CE2451BA1E00426DDF /* Twitter */ = { isa = PBXGroup; children = ( @@ -4232,6 +4243,7 @@ files = ( 176813FC2564BB2D00D98635 /* Assets.xcassets in Resources */, 1768147B2564BE5400D98635 /* widget-sample.json in Resources */, + 17D0682C2564F47E00C0B37E /* Localizable.stringsdict in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4475,7 +4487,7 @@ /* Begin PBXShellScriptBuildPhase section */ 515D50802326D02600EE1167 /* Run Script: Verify No Build Settings */ = { isa = PBXShellScriptBuildPhase; - buildActionMask = 12; + buildActionMask = 8; files = ( ); inputFileListPaths = ( @@ -4487,7 +4499,7 @@ ); outputPaths = ( ); - runOnlyForDeploymentPostprocessing = 0; + runOnlyForDeploymentPostprocessing = 1; shellPath = /bin/sh; shellScript = "xcrun -sdk macosx swiftc -target x86_64-macosx10.11 buildscripts/VerifyNoBuildSettings.swift -o $CONFIGURATION_TEMP_DIR/VerifyNoBS\n$CONFIGURATION_TEMP_DIR/VerifyNoBS ${PROJECT_NAME}.xcodeproj/project.pbxproj\n\n\nif [ $? -ne 0 ]\nthen\n echo \"error: Build Setting were found in the project.pbxproj file. Most likely you didn't intend to change this file and should revert it.\"\n exit 1\nfi\n"; }; diff --git a/Shared/Widget/WidgetDataEncoder.swift b/Shared/Widget/WidgetDataEncoder.swift index a3fb6f92e..7679af2c7 100644 --- a/Shared/Widget/WidgetDataEncoder.swift +++ b/Shared/Widget/WidgetDataEncoder.swift @@ -20,7 +20,6 @@ struct WidgetDataEncoder { static func encodeWidgetData() { os_log(.info, "Starting widget data encoding") - let task = UIApplication.shared.beginBackgroundTask(withName: taskIdentifier, expirationHandler: nil) do { // Unread Articles let unreadArticles = try SmartFeedsController.shared.unreadFeed.fetchArticles().sorted(by: { $0.datePublished! > $1.datePublished! }) @@ -33,7 +32,7 @@ struct WidgetDataEncoder { feedIcon: article.iconImage()?.image.dataRepresentation(), pubDate: article.datePublished!.description) unread.append(latestArticle) - if unread.count == 3 { break } + if unread.count == 8 { break } } // Starred Articles @@ -47,7 +46,7 @@ struct WidgetDataEncoder { feedIcon: article.iconImage()?.image.dataRepresentation(), pubDate: article.datePublished!.description) starred.append(latestArticle) - if starred.count == 3 { break } + if starred.count == 8 { break } } // Today Articles @@ -61,7 +60,7 @@ struct WidgetDataEncoder { feedIcon: article.iconImage()?.image.dataRepresentation(), pubDate: article.datePublished!.description) today.append(latestArticle) - if today.count == 3 { break } + if today.count == 8 { break } } let latestData = WidgetData(currentUnreadCount: SmartFeedsController.shared.unreadFeed.unreadCount, @@ -80,13 +79,11 @@ struct WidgetDataEncoder { try FileManager.default.removeItem(at: dataURL!) } try encodedData.write(to: dataURL!) - + print(latestData.unreadArticles.count) WidgetCenter.shared.reloadAllTimelines() os_log(.info, "Finished encoding widget data") - UIApplication.shared.endBackgroundTask(task) } catch { os_log(.error, "%@", error.localizedDescription) - UIApplication.shared.endBackgroundTask(task) } } diff --git a/Technotes/Widgets.md b/Technotes/Widgets.md index 4ab09ed3d..628461506 100644 --- a/Technotes/Widgets.md +++ b/Technotes/Widgets.md @@ -1,9 +1,10 @@ # Widgets on iOS -There are _currently_ four widgets available for iOS: +There are _currently_ seven widgets available for iOS: -- 1x small widget that displays the current count of each of the Smart Feeds. +- 1x small widget that displays the current count of each of the Smart Feeds - 3x medium widgets—one for each of the smart feeds. +- 3x large widgets—bigger versions of the medium widgets ## Widget Data The widget does not have access to the parent app's database. To surface data to the widget, a small amount of article data is encoded to JSON (see `WidgetDataEncoder`) and saved to the AppGroup container. diff --git a/Widget/Localizable.stringsdict b/Widget/Localizable.stringsdict new file mode 100644 index 000000000..da0030943 --- /dev/null +++ b/Widget/Localizable.stringsdict @@ -0,0 +1,60 @@ + + + + + UnreadCount + + NSStringLocalizedFormatKey + %#@unread_count@ + unread_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + zero + No more unread articles + one + + 1 more unread article + other + + %u more unread articles + + + StarredCount + + NSStringLocalizedFormatKey + %#@starred_count@ + starred_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + zero + No more starred articles + one + + 1 more starred article + other + + %u more starred articles + + + TodayCount + + NSStringLocalizedFormatKey + %#@today_count@ + today_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + zero + No more recent articles + one + + 1 more recent article + other + + %u more recent articles + + + + diff --git a/Widget/Widget Views/StarredWidget.swift b/Widget/Widget Views/StarredWidget.swift index 2a14fd500..3704a8a7f 100644 --- a/Widget/Widget Views/StarredWidget.swift +++ b/Widget/Widget Views/StarredWidget.swift @@ -16,20 +16,6 @@ struct StarredWidgetView : View { var entry: Provider.Entry var body: some View { - switch family { - case .systemSmall: - mediumWidget - case .systemMedium: - mediumWidget - case .systemLarge: - mediumWidget - @unknown default: - mediumWidget - } - } - - @ViewBuilder - var mediumWidget: some View { if entry.widgetData.starredArticles.count == 0 { inboxZero } @@ -49,7 +35,7 @@ struct StarredWidgetView : View { Spacer() HStack { Spacer() - unreadCountText + countText } } } @@ -75,43 +61,28 @@ struct StarredWidgetView : View { .cornerRadius(4) } - var unreadCountText: some View { - if entry.widgetData.currentStarredCount > 3 { - let count = entry.widgetData.currentStarredCount - 3 - let formatter = NumberFormatter() - formatter.locale = Locale.current - formatter.numberStyle = .decimal - let formattedCount = formatter.string(from: NSNumber(value: count)) - var str = "" - if count == 1 { - str = "+ \(formattedCount!) more starred article..." - } else { - str = "+ \(formattedCount!) more starred articles..." - } - return Text(str) - .font(.caption2) - .bold() - .foregroundColor(.accentColor) + var countText: some View { + var count = entry.widgetData.currentStarredCount + if family == .systemLarge { + count = count - 8 } else { - let formatter = NumberFormatter() - formatter.locale = Locale.current - formatter.numberStyle = .decimal - let formattedCount = formatter.string(from: NSNumber(value: entry.widgetData.currentStarredCount)) - var str = "" - if entry.widgetData.currentStarredCount == 1 { - str = "\(formattedCount!) starred article" - } else { - str = "\(formattedCount!) starred articles" - } - return Text(str) - .font(.caption2) - .bold() - .foregroundColor(.accentColor) + count = count - 3 } + if count < 0 { count = 0 } + let formatString = NSLocalizedString("StarredCount", + comment: "Starred Count Format") + let str = String.localizedStringWithFormat(formatString, UInt(count)) + return Text(str) + .font(.caption2) + .bold() + .foregroundColor(.accentColor) } func maxCount() -> Int { - return entry.widgetData.starredArticles.count > 3 ? 3 : entry.widgetData.starredArticles.count + if family == .systemLarge { + return entry.widgetData.currentStarredCount > 8 ? 8 : entry.widgetData.currentStarredCount + } + return entry.widgetData.currentStarredCount > 3 ? 3 : entry.widgetData.currentStarredCount } var inboxZero: some View { @@ -120,8 +91,6 @@ struct StarredWidgetView : View { Text("#StarredZero") .italic() .font(Font.system(.subheadline, design: .serif)) - .fixedSize(horizontal: false, vertical: true) - .padding(.bottom, 4) Spacer() HStack { @@ -133,7 +102,7 @@ struct StarredWidgetView : View { Text("You've not starred any artices.") .font(.caption2) .foregroundColor(.gray) - }.padding(.bottom, 8) + } }.padding() } diff --git a/Widget/Widget Views/TodayWidget.swift b/Widget/Widget Views/TodayWidget.swift index 1294ab2ad..c4211cc20 100644 --- a/Widget/Widget Views/TodayWidget.swift +++ b/Widget/Widget Views/TodayWidget.swift @@ -16,20 +16,6 @@ struct TodayWidgetView : View { var entry: Provider.Entry var body: some View { - switch family { - case .systemSmall: - mediumWidget - case .systemMedium: - mediumWidget - case .systemLarge: - mediumWidget - @unknown default: - mediumWidget - } - } - - @ViewBuilder - var mediumWidget: some View { if entry.widgetData.todayArticles.count == 0 { inboxZero } @@ -49,7 +35,7 @@ struct TodayWidgetView : View { Spacer() HStack { Spacer() - unreadCountText + countText } } } @@ -75,42 +61,27 @@ struct TodayWidgetView : View { .cornerRadius(4) } - var unreadCountText: some View { - if entry.widgetData.currentTodayCount > 3 { - let count = entry.widgetData.currentTodayCount - 3 - let formatter = NumberFormatter() - formatter.locale = Locale.current - formatter.numberStyle = .decimal - let formattedCount = formatter.string(from: NSNumber(value: count)) - var str = "" - if count == 1 { - str = "+ \(formattedCount!) more recent article..." - } else { - str = "+ \(formattedCount!) more recent articles..." - } - return Text(str) - .font(.caption2) - .bold() - .foregroundColor(.accentColor) + var countText: some View { + var count = entry.widgetData.currentTodayCount + if family == .systemLarge { + count = count - 8 } else { - let formatter = NumberFormatter() - formatter.locale = Locale.current - formatter.numberStyle = .decimal - let formattedCount = formatter.string(from: NSNumber(value: entry.widgetData.currentTodayCount)) - var str = "" - if entry.widgetData.currentTodayCount == 1 { - str = "\(formattedCount!) recent article" - } else { - str = "\(formattedCount!) recent articles" - } - return Text(str) - .font(.caption2) - .bold() - .foregroundColor(.accentColor) + count = count - 3 } + if count < 0 { count = 0 } + let formatString = NSLocalizedString("TodayCount", + comment: "Today Count Format") + let str = String.localizedStringWithFormat(formatString, UInt(count)) + return Text(str) + .font(.caption2) + .bold() + .foregroundColor(.accentColor) } func maxCount() -> Int { + if family == .systemLarge { + return entry.widgetData.todayArticles.count > 8 ? 8 : entry.widgetData.todayArticles.count + } return entry.widgetData.todayArticles.count > 3 ? 3 : entry.widgetData.todayArticles.count } @@ -120,8 +91,6 @@ struct TodayWidgetView : View { Text("#TodayZero") .italic() .font(Font.system(.subheadline, design: .serif)) - .fixedSize(horizontal: false, vertical: true) - .padding(.bottom, 4) Spacer() HStack { @@ -130,10 +99,10 @@ struct TodayWidgetView : View { .frame(width: 15, height: 15, alignment: .center) .cornerRadius(4) - Text("There're no recent articles to read.") + Text("There are no recent articles to read.") .font(.caption2) .foregroundColor(.gray) - }.padding(.bottom, 8) + } }.padding() } diff --git a/Widget/Widget Views/UnreadWidget.swift b/Widget/Widget Views/UnreadWidget.swift index 0062e1659..55d361d07 100644 --- a/Widget/Widget Views/UnreadWidget.swift +++ b/Widget/Widget Views/UnreadWidget.swift @@ -16,21 +16,7 @@ struct UnreadWidgetView : View { var entry: Provider.Entry var body: some View { - switch family { - case .systemSmall: - mediumWidget - case .systemMedium: - mediumWidget - case .systemLarge: - mediumWidget - @unknown default: - mediumWidget - } - } - - @ViewBuilder - var mediumWidget: some View { - if entry.widgetData.unreadArticles.count == 0 { + if entry.widgetData.currentUnreadCount == 0 { inboxZero } else { @@ -49,13 +35,13 @@ struct UnreadWidgetView : View { Spacer() HStack { Spacer() - unreadCountText + countText } } } } .padding() - .widgetURL(URL(string: "nnw-widget://showunread")!) + .widgetURL(WidgetDeepLink.unread.url) } } @@ -75,42 +61,27 @@ struct UnreadWidgetView : View { .cornerRadius(4) } - var unreadCountText: some View { - if entry.widgetData.currentUnreadCount > 3 { - let count = entry.widgetData.currentUnreadCount - 3 - let formatter = NumberFormatter() - formatter.locale = Locale.current - formatter.numberStyle = .decimal - let formattedCount = formatter.string(from: NSNumber(value: count)) - var str = "" - if count == 1 { - str = "+ \(formattedCount!) more unread article..." - } else { - str = "+ \(formattedCount!) more unread articles..." - } - return Text(str) - .font(.caption2) - .bold() - .foregroundColor(.accentColor) + var countText: some View { + var count = entry.widgetData.currentUnreadCount + if family == .systemLarge { + count = count - 8 } else { - let formatter = NumberFormatter() - formatter.locale = Locale.current - formatter.numberStyle = .decimal - let formattedCount = formatter.string(from: NSNumber(value: entry.widgetData.currentUnreadCount)) - var str = "" - if entry.widgetData.currentUnreadCount == 1 { - str = "\(formattedCount!) unread article" - } else { - str = "\(formattedCount!) unread articles" - } - return Text(str) - .font(.caption2) - .bold() - .foregroundColor(.accentColor) + count = count - 3 } + if count < 0 { count = 0 } + let formatString = NSLocalizedString("UnreadCount", + comment: "Unread Count Format") + let str = String.localizedStringWithFormat(formatString, UInt(count)) + return Text(str) + .font(.caption2) + .bold() + .foregroundColor(.accentColor) } func maxCount() -> Int { + if family == .systemLarge { + return entry.widgetData.unreadArticles.count > 8 ? 8 : entry.widgetData.unreadArticles.count + } return entry.widgetData.unreadArticles.count > 3 ? 3 : entry.widgetData.unreadArticles.count } @@ -120,8 +91,6 @@ struct UnreadWidgetView : View { Text("#UnreadZero") .italic() .font(Font.system(.subheadline, design: .serif)) - .fixedSize(horizontal: false, vertical: true) - .padding(.bottom, 4) Spacer() HStack { @@ -133,7 +102,7 @@ struct UnreadWidgetView : View { Text("There's nothing to read right now.") .font(.caption2) .foregroundColor(.gray) - }.padding(.bottom, 8) + } }.padding() } diff --git a/Widget/WidgetBundle.swift b/Widget/WidgetBundle.swift index 85ee0e481..95cce7236 100644 --- a/Widget/WidgetBundle.swift +++ b/Widget/WidgetBundle.swift @@ -24,7 +24,7 @@ struct UnreadWidget: Widget { }) .configurationDisplayName("Your Unread Articles") .description("A sneak peak at what's left unread.") - .supportedFamilies([.systemMedium]) + .supportedFamilies([.systemMedium, .systemLarge]) } } @@ -42,7 +42,7 @@ struct TodayWidget: Widget { }) .configurationDisplayName("Your Today Articles") .description("A sneak peak at recently published articles.") - .supportedFamilies([.systemMedium]) + .supportedFamilies([.systemMedium, .systemLarge]) } } @@ -60,7 +60,7 @@ struct StarredWidget: Widget { }) .configurationDisplayName("Your Starred Articles") .description("A sneak peak at your starred articles.") - .supportedFamilies([.systemMedium]) + .supportedFamilies([.systemMedium, .systemLarge]) } }