Skip to content
Merged
1 change: 1 addition & 0 deletions RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
23.3
-----
* [***] [Jetpack-only] Lock Screen Widgets are now available on Jetpack. Quickly view your site stats without unlocking your phone. [#21535]
* [*] Block editor: Fix the obscurred "Insert from URL" input for media blocks when using a device in landscape orientation. [https://github.com/wordpress-mobile/gutenberg-mobile/pull/6143]
* [**] Block editor: Updated placeholder text colors for block-based themes [https://github.com/wordpress-mobile/gutenberg-mobile/pull/6182]
* [**] Update the main site navigation on the My Site screen, making it easier to access the common site sections. The new shortcuts can be modified using the Personalize Home Tab screen. [21502](https://github.com/wordpress-mobile/WordPress-iOS/pull/21502).
Expand Down
37 changes: 31 additions & 6 deletions WordPress/Classes/Stores/StatsWidgetsStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,24 +35,32 @@ class StatsWidgetsStore {

/// Initialize the local cache for widgets, if it does not exist
@objc func initializeStatsWidgetsIfNeeded() {
UserDefaults(suiteName: WPAppGroupName)?.setValue(AccountHelper.isLoggedIn, forKey: AppConfiguration.Widget.Stats.userDefaultsLoggedInKey)
UserDefaults(suiteName: WPAppGroupName)?.setValue(AccountHelper.defaultSiteId, forKey: AppConfiguration.Widget.Stats.userDefaultsSiteIdKey)
storeCredentials()

var isReloadRequired = false

if !HomeWidgetTodayData.cacheDataExists() {
DDLogInfo("StatsWidgets: Writing initialization data into HomeWidgetTodayData.plist")
HomeWidgetTodayData.write(items: initializeHomeWidgetData(type: HomeWidgetTodayData.self))
WidgetCenter.shared.reloadTodayTimelines()
isReloadRequired = true
}

if !HomeWidgetThisWeekData.cacheDataExists() {
DDLogInfo("StatsWidgets: Writing initialization data into HomeWidgetThisWeekData.plist")
HomeWidgetThisWeekData.write(items: initializeHomeWidgetData(type: HomeWidgetThisWeekData.self))
WidgetCenter.shared.reloadThisWeekTimelines()
isReloadRequired = true
}

if !HomeWidgetAllTimeData.cacheDataExists() {
DDLogInfo("StatsWidgets: Writing initialization data into HomeWidgetAllTimeData.plist")
HomeWidgetAllTimeData.write(items: initializeHomeWidgetData(type: HomeWidgetAllTimeData.self))
WidgetCenter.shared.reloadAllTimeTimelines()
isReloadRequired = true
}

if isReloadRequired {
WidgetCenter.shared.reloadAllTimelines()
}
}

Expand Down Expand Up @@ -273,9 +281,8 @@ private extension StatsWidgetsStore {
HomeWidgetAllTimeData.delete()

userDefaults?.setValue(nil, forKey: AppConfiguration.Widget.Stats.userDefaultsSiteIdKey)
WidgetCenter.shared.reloadTodayTimelines()
WidgetCenter.shared.reloadThisWeekTimelines()
WidgetCenter.shared.reloadAllTimeTimelines()

WidgetCenter.shared.reloadAllTimelines()
}

/// Observes WPSigninDidFinishNotification and wordpressLoginFinishedJetpackLogin notifications and initializes the widget.
Expand Down Expand Up @@ -319,6 +326,24 @@ private extension StatsWidgetsStore {
}
}

private extension StatsWidgetsStore {
func storeCredentials() {
guard let token = AccountHelper.authToken else { return }

do {
try SFHFKeychainUtils.storeUsername(
AppConfiguration.Widget.Stats.keychainTokenKey,
andPassword: token,
forServiceName: AppConfiguration.Widget.Stats.keychainServiceName,
accessGroup: WPAppKeychainAccessGroup,
updateExisting: true
)
} catch {
DDLogDebug("Error while saving Widgets OAuth token: \(error)")
}
}
}


extension StatsViewController {
@objc func initializeStatsWidgetsIfNeeded() {
Expand Down
6 changes: 6 additions & 0 deletions WordPress/Classes/Utility/AccountHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ import Foundation
return account?.defaultBlog?.dotComID
}

static var authToken: String? {
let context = ContextManager.sharedInstance().mainContext
let account = try? WPAccount.lookupDefaultWordPressComAccount(in: context)
return account?.authToken
}

static func logBlogsAndAccounts(context: NSManagedObjectContext) {
let allBlogs = (try? BlogQuery().blogs(in: context)) ?? []
let blogsByAccount = Dictionary(grouping: allBlogs, by: { $0.account })
Expand Down
7 changes: 0 additions & 7 deletions WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ enum FeatureFlag: Int, CaseIterable {
case bloggingPrompts
case jetpackDisconnect
case debugMenu
case lockScreenWidget
case siteIconCreator
case statsNewInsights
case betaSiteDesigns
Expand All @@ -31,8 +30,6 @@ enum FeatureFlag: Int, CaseIterable {
return BuildConfiguration.current == .localDeveloper
case .debugMenu:
return BuildConfiguration.current ~= [.localDeveloper, .a8cBranchTest, .a8cPrereleaseTesting]
case .lockScreenWidget:
return false
case .siteIconCreator:
return BuildConfiguration.current != .appStore
case .statsNewInsights:
Expand Down Expand Up @@ -83,8 +80,6 @@ extension FeatureFlag {
return "Jetpack disconnect"
case .debugMenu:
return "Debug menu"
case .lockScreenWidget:
return "iOS 16 Widget in Lock Screen"
case .siteIconCreator:
return "Site Icon Creator"
case .statsNewInsights:
Expand Down Expand Up @@ -121,8 +116,6 @@ extension FeatureFlag: OverridableFlag {
switch self {
case .debugMenu:
return false
case .lockScreenWidget:
return false
default:
return true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ final class HomeWidgetDataFileReader: WidgetDataCacheReader {

return T.read()?[siteID]
}

func widgetData<T: HomeWidgetData>() -> [T]? {
return T.read()?.map { $0.value }
}
}
38 changes: 16 additions & 22 deletions WordPress/JetpackStatsWidgets/Helpers/WidgetDataReader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Foundation

protocol WidgetDataCacheReader {
func widgetData<T: HomeWidgetData>(for siteID: String) -> T?
func widgetData<T: HomeWidgetData>() -> [T]?
}

enum WidgetDataReadError: Error {
Expand Down Expand Up @@ -30,40 +31,33 @@ final class WidgetDataReader<T: HomeWidgetData> {
/// - configuration: Configuration of the Widget Site Selection Intent
/// - defaultSiteID: ID of the default site in the account
/// - Returns: Widget data
func widgetData(for configuration: SelectSiteIntent, defaultSiteID: Int?) -> T? {

/// If configuration.site.identifier has value but there's no widgetData, it means that this identifier comes from previously logged in account
if let selectedSite = configuration.site?.identifier,
let widgetData: T = cacheReader.widgetData(for: selectedSite) {
return widgetData
} else if let defaultSiteID = defaultSiteID {
return cacheReader.widgetData(for: String(defaultSiteID))
} else {
return nil
}
}

func widgetData(
for configuration: SelectSiteIntent,
defaultSiteID: Int?,
isJetpack: Bool
defaultSiteID: Int?
) -> Result<T, WidgetDataReadError> {
guard let defaults = userDefaults else {
return .failure(.noData)
}
guard let defaultSiteID = defaultSiteID else {

if let selectedSite = configuration.site?.identifier,
let widgetData: T = cacheReader.widgetData(for: selectedSite) {
return .success(widgetData)
} else if let defaultSiteID = defaultSiteID,
let widgetData: T = cacheReader.widgetData(for: String(defaultSiteID)) {
return .success(widgetData)
} else {
let loggedIn = defaults.bool(forKey: AppConfiguration.Widget.Stats.userDefaultsLoggedInKey)

if loggedIn {
return .failure(.noSite)
/// In rare cases there could be no default site and no defaultSiteId set
if let firstSiteData: T = cacheReader.widgetData()?.sorted(by: { $0.siteID < $1.siteID }).first {
return .success(firstSiteData)
} else {
return .failure(.noSite)
}
} else {
return .failure(.loggedOut)
}
}
guard let widgetData = widgetData(for: configuration, defaultSiteID: defaultSiteID) else {
return .failure(.noData)
}

return .success(widgetData)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,6 @@ struct LockScreenAllTimePostsBestViewsStatWidgetConfig: LockScreenStatsWidgetCon
typealias WidgetData = HomeWidgetAllTimeData
typealias ViewProvider = LockScreenMultiStatWidgetViewProvider

var supportFamilies: [WidgetFamily] {
guard AppConfiguration.isJetpack, FeatureFlag.lockScreenWidget.enabled else {
return []
}
return [.accessoryRectangular]
}

var displayName: String {
LocalizableStrings.allTimePostMostViewsWidgetPreviewTitle
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,6 @@ struct LockScreenAllTimeViewsStatWidgetConfig: LockScreenStatsWidgetConfig {
typealias WidgetData = HomeWidgetAllTimeData
typealias ViewProvider = LockScreenSingleStatWidgetViewProvider

var supportFamilies: [WidgetFamily] {
guard AppConfiguration.isJetpack, FeatureFlag.lockScreenWidget.enabled else {
return []
}
return [.accessoryRectangular]
}

var displayName: String {
LocalizableStrings.allTimeViewsWidgetPreviewTitle
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,6 @@ struct LockScreenAllTimeViewsVisitorsStatWidgetConfig: LockScreenStatsWidgetConf
typealias WidgetData = HomeWidgetAllTimeData
typealias ViewProvider = LockScreenMultiStatWidgetViewProvider

var supportFamilies: [WidgetFamily] {
guard AppConfiguration.isJetpack, FeatureFlag.lockScreenWidget.enabled else {
return []
}
return [.accessoryRectangular]
}

var displayName: String {
LocalizableStrings.allTimeViewsVisitorsWidgetPreviewTitle
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,13 @@ protocol LockScreenStatsWidgetConfig {
var placeholderContent: WidgetData { get }
var viewProvider: ViewProvider { get }
}

extension LockScreenStatsWidgetConfig {
var supportFamilies: [WidgetFamily] {
if #available(iOSApplicationExtension 16.0, *) {
return [.accessoryRectangular]
} else {
return []
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,6 @@ struct LockScreenTodayLikesCommentsStatWidgetConfig: LockScreenStatsWidgetConfig
typealias WidgetData = HomeWidgetTodayData
typealias ViewProvider = LockScreenMultiStatWidgetViewProvider<WidgetData>

var supportFamilies: [WidgetFamily] {
guard AppConfiguration.isJetpack, FeatureFlag.lockScreenWidget.enabled else {
return []
}
return [.accessoryRectangular]
}

var displayName: String {
LocalizableStrings.todayLikesCommentsWidgetPreviewTitle
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,6 @@ struct LockScreenTodayViewsStatWidgetConfig: LockScreenStatsWidgetConfig {
typealias WidgetData = HomeWidgetTodayData
typealias ViewProvider = LockScreenSingleStatWidgetViewProvider

var supportFamilies: [WidgetFamily] {
guard AppConfiguration.isJetpack, FeatureFlag.lockScreenWidget.enabled else {
return []
}
return [.accessoryRectangular]
}

var displayName: String {
LocalizableStrings.todayViewsWidgetPreviewTitle
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,6 @@ struct LockScreenTodayViewsVisitorsStatWidgetConfig: LockScreenStatsWidgetConfig
typealias WidgetData = HomeWidgetTodayData
typealias ViewProvider = LockScreenMultiStatWidgetViewProvider<WidgetData>

var supportFamilies: [WidgetFamily] {
guard AppConfiguration.isJetpack, FeatureFlag.lockScreenWidget.enabled else {
return []
}
return [.accessoryRectangular]
}

var displayName: String {
LocalizableStrings.todayViewsVisitorsWidgetPreviewTitle
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,18 @@ struct LockScreenSiteListProvider<T: HomeWidgetData>: IntentTimelineProvider {
}

func getSnapshot(for configuration: SelectSiteIntent, in context: Context, completion: @escaping (LockScreenStatsWidgetEntry<T>) -> Void) {
let content = widgetDataLoader.widgetData(for: configuration, defaultSiteID: defaultSiteID) ?? placeholderContent
completion(.siteSelected(content, context))
switch widgetDataLoader.widgetData(for: configuration, defaultSiteID: defaultSiteID) {
case .success(let widgetData):
completion(.siteSelected(widgetData, context))
case .failure:
completion(.siteSelected(placeholderContent, context))
}
}

func getTimeline(for configuration: SelectSiteIntent, in context: Context, completion: @escaping (Timeline<LockScreenStatsWidgetEntry<T>>) -> Void) {
switch widgetDataLoader.widgetData(
for: configuration,
defaultSiteID: defaultSiteID,
isJetpack: AppConfiguration.isJetpack
defaultSiteID: defaultSiteID
) {
case .success(let widgetData):
let date = Date()
Expand Down
12 changes: 7 additions & 5 deletions WordPress/JetpackStatsWidgets/SiteListProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,18 @@ struct SiteListProvider<T: HomeWidgetData>: IntentTimelineProvider {
}

func getSnapshot(for configuration: SelectSiteIntent, in context: Context, completion: @escaping (StatsWidgetEntry) -> Void) {

let content = widgetDataLoader.widgetData(for: configuration, defaultSiteID: defaultSiteID) ?? placeholderContent
completion(.siteSelected(content, context))
switch widgetDataLoader.widgetData(for: configuration, defaultSiteID: defaultSiteID) {
case .success(let widgetData):
completion(.siteSelected(widgetData, context))
case .failure:
completion(.siteSelected(placeholderContent, context))
}
}

func getTimeline(for configuration: SelectSiteIntent, in context: Context, completion: @escaping (Timeline<StatsWidgetEntry>) -> Void) {
switch widgetDataLoader.widgetData(
for: configuration,
defaultSiteID: defaultSiteID,
isJetpack: AppConfiguration.isJetpack
defaultSiteID: defaultSiteID
) {
case .success(let widgetData):
let date = Date()
Expand Down
Loading