Skip to content

T418955 challenge widget#5723

Merged
mazevedofs merged 20 commits intoreading-challengefrom
T418955_challenge-widget
Mar 17, 2026
Merged

T418955 challenge widget#5723
mazevedofs merged 20 commits intoreading-challengefrom
T418955_challenge-widget

Conversation

@l-olson1214
Copy link
Collaborator

@l-olson1214 l-olson1214 commented Mar 11, 2026

Phabricator:
https://phabricator.wikimedia.org/T418955

Notes

  • NOTE: colors/strings/fonts/etc to be fixed in future tickets since this is skeleton

[x] Reading challenge widget is visible for Logged-in and logged-out users
[x] Tapping anywhere other than a CTA on the widget opens the Explore Feed
[x] If explore feed is disabled, go to Search*** waiting on answer
[x] Create small and large version of Widget
[x] Large widget should be able to support up to 2 individual, clickable CTAs that lead to Search and Random

Test Steps

  1. You can install the widget and play around with it, try out the different types by sending different ones.

Screenshots/Videos

@l-olson1214 l-olson1214 requested a review from Copilot March 11, 2026 19:26
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new “Reading Challenge” widget and the supporting assets/deep-link plumbing so the widget can open new destinations (Activity tab and Random article) from the app.

Changes:

  • Added a new ReadingChallengeWidget to the widget bundle and project.
  • Extended NSUserActivity deep-link handling and app routing to support wikipedia://activity and wikipedia://random.
  • Added Reading Challenge artwork to WMFComponents and added a new SFSymbol wrapper (flame.fill) for widget UI.

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
WMFComponents/Sources/WMFComponents/Style/WMFIcon.swift Adds flameFill SFSymbol support for widget UI.
WMFComponents/Sources/WMFComponents/Assets.xcassets/readingChallenge/globe1.imageset/Contents.json Adds new Reading Challenge globe asset image set metadata.
WMFComponents/Sources/WMFComponents/Assets.xcassets/readingChallenge/Contents.json Adds asset catalog container metadata for Reading Challenge assets.
Wikipedia/Code/WMFAppViewController.m Routes new NSUserActivity types for Activity tab and Random to appropriate app navigation.
Wikipedia/Code/NSUserActivity+WMFExtensions.m Adds new activity types and wikipedia-scheme parsing for Activity/Random.
Wikipedia/Code/NSUserActivity+WMFExtensions.h Extends the WMFUserActivityType enum and declares the new Activity tab activity creator.
Wikipedia.xcodeproj/project.pbxproj Adds ReadingChallengeWidget.swift to the project and widget target sources.
Widgets/Widgets/ReadingChallengeWidget.swift Introduces the new widget implementation + placeholder display sets and deep-link URLs.
Widgets/Extension/Widgets.swift Registers ReadingChallengeWidget() in the widget bundle.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +305 to +308
host = @"Activity";
break;
case WMFUserActivityTypeRandom:
host = @"Random";
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wmf_baseURLComponentsForActivityOfType: uses capitalized hosts ("Activity" / "Random"), but wmf_activityForWikipediaScheme: matches lowercase hosts ("activity" / "random"). This inconsistency can break deep links generated via wmf_baseURLForActivityOfType: / wmf_URLForActivityOfType:. Use lowercase hosts here to match the URL parsing logic (and existing conventions like "saved", "search", etc.).

Suggested change
host = @"Activity";
break;
case WMFUserActivityTypeRandom:
host = @"Random";
host = @"activity";
break;
case WMFUserActivityTypeRandom:
host = @"random";

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to work on simulator

Comment on lines +128 to +144

var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: ReadingChallengeProvider()) { entry in
let state = ReadingChallengeState.notEnrolled
WMFReadingChallengeWidgetView(
viewModel: WMFReadingChallengeWidgetViewModel(
localizedStrings: WMFReadingChallengeWidgetViewModel.LocalizedStrings(
title: "3 days"
),
displaySet: .random(for: state),
state: state
)
)
.widgetURL(URL(string: widgetURL))
}
.configurationDisplayName("Reading Challenge")
.description("Track your reading challenge progress.")
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This widget configuration uses hard-coded, non-localized user-facing strings (display name and description). Other widgets in this target use WMFLocalizedString / CommonStrings for these fields; this should be localized before shipping.

Suggested change
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: ReadingChallengeProvider()) { entry in
let state = ReadingChallengeState.notEnrolled
WMFReadingChallengeWidgetView(
viewModel: WMFReadingChallengeWidgetViewModel(
localizedStrings: WMFReadingChallengeWidgetViewModel.LocalizedStrings(
title: "3 days"
),
displaySet: .random(for: state),
state: state
)
)
.widgetURL(URL(string: widgetURL))
}
.configurationDisplayName("Reading Challenge")
.description("Track your reading challenge progress.")
private let localizedStrings = WMFReadingChallengeWidgetViewModel.LocalizedStrings(
title: WMFLocalizedString(
"reading-challenge-widget-placeholder-title",
value: "3 days",
comment: "Placeholder title text shown in the Reading Challenge widget preview, representing a three-day reading streak."
)
)
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: ReadingChallengeProvider()) { entry in
let state = ReadingChallengeState.notEnrolled
WMFReadingChallengeWidgetView(
viewModel: WMFReadingChallengeWidgetViewModel(
localizedStrings: localizedStrings,
displaySet: .random(for: state),
state: state
)
)
.widgetURL(URL(string: widgetURL))
}
.configurationDisplayName(
WMFLocalizedString(
"reading-challenge-widget-display-name",
value: "Reading Challenge",
comment: "Display name for the Reading Challenge home screen widget, shown in the iOS widget gallery."
)
)
.description(
WMFLocalizedString(
"reading-challenge-widget-description",
value: "Track your reading challenge progress.",
comment: "Description for the Reading Challenge home screen widget, shown in the iOS widget gallery."
)
)

Copilot uses AI. Check for mistakes.
Comment on lines +99 to +101
button2URL: URL(string: "wikipedia://random"),
button1Icon: "search",
button2Icon: "dice"
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

button1Icon / button2Icon are passed as "search" and "dice", but the widget view renders them with Image(icon) (asset lookup in the widget bundle). There are no matching assets in Widgets/Extension/Assets.xcassets, so these icons will fail to render. Either add the missing assets to the widget extension asset catalog or switch the view to use SF Symbols (e.g. Image(systemName:)) / WMFSFSymbolIcon.

Suggested change
button2URL: URL(string: "wikipedia://random"),
button1Icon: "search",
button2Icon: "dice"
button2URL: URL(string: "wikipedia://random")

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Button assests are not rendering

Comment on lines +25 to +26
let entry = ReadingChallengeEntry(date: Date())
let timeline = Timeline(entries: [entry], policy: .atEnd)
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getTimeline returns a single entry with policy .atEnd, so the widget will not refresh after the initial load. If this widget is meant to reflect reading challenge progress, schedule a refresh (.after(...)) consistent with other widgets or derive the next update time from the underlying reading challenge data.

Suggested change
let entry = ReadingChallengeEntry(date: Date())
let timeline = Timeline(entries: [entry], policy: .atEnd)
let currentDate = Date()
let entry = ReadingChallengeEntry(date: currentDate)
let refreshDate = Calendar.current.date(byAdding: .hour, value: 1, to: currentDate) ?? currentDate.addingTimeInterval(3600)
let timeline = Timeline(entries: [entry], policy: .after(refreshDate))

Copilot uses AI. Check for mistakes.
Comment on lines +129 to +139
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: ReadingChallengeProvider()) { entry in
let state = ReadingChallengeState.notEnrolled
WMFReadingChallengeWidgetView(
viewModel: WMFReadingChallengeWidgetViewModel(
localizedStrings: WMFReadingChallengeWidgetViewModel.LocalizedStrings(
title: "3 days"
),
displaySet: .random(for: state),
state: state
)
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The widget body always uses ReadingChallengeState.notEnrolled and hard-coded mock content (e.g. "3 days"), so it will never reflect the real user state/progress. Consider fetching the actual reading challenge state from the shared cache / data layer (similar to other widgets using WidgetController.shared) and building the displaySet from that.

Copilot uses AI. Check for mistakes.
…llenge/globe1.imageset/Contents.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@l-olson1214 l-olson1214 requested review from a team and tonisevener and removed request for a team March 11, 2026 20:43
@mazevedofs mazevedofs added the reading-challenge Reading challenge widget label Mar 12, 2026
@l-olson1214 l-olson1214 requested a review from mazevedofs March 12, 2026 18:06
Copy link
Collaborator

@mazevedofs mazevedofs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The button in the small widget is taking over the whole widget.

Image

color2: .gray,
image: "globe1",
title: "",
button1Title: "Search",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the buttons change dynamically? Otherwise, it would be better to have them be named in a more specific way, like randomButtonTitle for clarity

Comment on lines +99 to +101
button2URL: URL(string: "wikipedia://random"),
button1Icon: "search",
button2Icon: "dice"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Button assests are not rendering

.description("Track your reading challenge progress.")
.supportedFamilies([.systemSmall, .systemMedium])
.contentMarginsDisabled()
.containerBackgroundRemovable(false)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The content is not rendering on the device, you can add this modifier so we avoid the error from the screenshot

func clearWidgetContainerBackground() -> some View {

Image

Comment on lines +305 to +308
host = @"Activity";
break;
case WMFUserActivityTypeRandom:
host = @"Random";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to work on simulator

imageSource.source = WMFChangeImageSourceURLSizePrefix(imageSource.source, maxWidth)
if var imageSource = featuredContent.pictureOfTheDay?.originalImageSource {
imageSource.source = WMFChangeImageSourceURLSizePrefix(imageSource.source, Int(self.potdTargetImageSize.width))
featuredContent.pictureOfTheDay?.originalImageSource = imageSource
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these changes related to this Pr or the Picture of the Day fix?

@l-olson1214 l-olson1214 requested a review from mazevedofs March 17, 2026 16:45
@mazevedofs mazevedofs merged commit 8687fe3 into reading-challenge Mar 17, 2026
@mazevedofs mazevedofs deleted the T418955_challenge-widget branch March 17, 2026 16:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

reading-challenge Reading challenge widget

Development

Successfully merging this pull request may close these issues.

5 participants