From b0b22b2fd5aed391c20dded21c4e7d4af4fa3dd7 Mon Sep 17 00:00:00 2001 From: mazevedo Date: Wed, 23 Oct 2024 13:34:14 -0300 Subject: [PATCH 01/10] Add share action to YiR coordinator delegate --- .../Year in Review/WMFYearInReview.swift | 2 +- .../WMFYearInReviewViewModel.swift | 30 +++++++++++++++---- Wikipedia/Code/YearInReviewCoordinator.swift | 11 +++++++ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReview.swift b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReview.swift index 8049946db9..7549373a82 100644 --- a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReview.swift +++ b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReview.swift @@ -69,7 +69,7 @@ public struct WMFYearInReview: View { ToolbarItem(placement: .bottomBar) { HStack(alignment: .center) { Button(action: { - // TODO: Implement share + viewModel.handleShare(for: currentSlide) }) { HStack(alignment: .center, spacing: 6) { if let uiImage = WMFSFSymbolIcon.for(symbol: .share, font: .semiboldHeadline) { diff --git a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift index a5f26b1afd..a375f6cdab 100644 --- a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift +++ b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift @@ -5,16 +5,24 @@ public class WMFYearInReviewViewModel: ObservableObject { @Published var isFirstSlide = true let localizedStrings: LocalizedStrings var slides: [YearInReviewSlideContent] - - public init(localizedStrings: LocalizedStrings, slides: [YearInReviewSlideContent]) { + @ObservedObject var appEnvironment = WMFAppEnvironment.current + + var theme: WMFTheme { + return appEnvironment.theme + } + + weak var coordinatorDelegate: YearInReviewCoordinatorDelegate? + + public init(localizedStrings: LocalizedStrings, slides: [YearInReviewSlideContent], coordinatorDelegate: YearInReviewCoordinatorDelegate?) { self.localizedStrings = localizedStrings self.slides = slides + self.coordinatorDelegate = coordinatorDelegate } - + public func getStarted() { isFirstSlide = false } - + public struct LocalizedStrings { let donateButtonTitle: String let doneButtonTitle: String @@ -24,7 +32,7 @@ public class WMFYearInReviewViewModel: ObservableObject { let firstSlideSubtitle: String let firstSlideCTA: String let firstSlideHide: String - + public init(donateButtonTitle: String, doneButtonTitle: String, shareButtonTitle: String, nextButtonTitle: String, firstSlideTitle: String, firstSlideSubtitle: String, firstSlideCTA: String, firstSlideHide: String) { self.donateButtonTitle = donateButtonTitle self.doneButtonTitle = doneButtonTitle @@ -43,7 +51,7 @@ public struct YearInReviewSlideContent: SlideShowProtocol { public let title: String let informationBubbleText: String? public let subtitle: String - + public init(imageName: String, title: String, informationBubbleText: String?, subtitle: String) { self.imageName = imageName self.title = title @@ -51,3 +59,13 @@ public struct YearInReviewSlideContent: SlideShowProtocol { self.subtitle = subtitle } } + +// temp - waiting for the donate action to be merged + +public protocol YearInReviewCoordinatorDelegate: AnyObject { + func handleYearInReviewAction(_ action: YearInReviewCoordinatorAction) +} + +public enum YearInReviewCoordinatorAction { + case share(image: UIImage) +} diff --git a/Wikipedia/Code/YearInReviewCoordinator.swift b/Wikipedia/Code/YearInReviewCoordinator.swift index 082d1e735b..330899a57c 100644 --- a/Wikipedia/Code/YearInReviewCoordinator.swift +++ b/Wikipedia/Code/YearInReviewCoordinator.swift @@ -182,3 +182,14 @@ final class YearInReviewCoordinator: NSObject, Coordinator { navigationController.present(hostingController, animated: true, completion: nil) } } + +extension YearInReviewCoordinator: YearInReviewCoordinatorDelegate { + func handleYearInReviewAction(_ action: WMFComponents.YearInReviewCoordinatorAction) { + + switch action { + case let .share(image): + print("") + + } + } +} From 583eb3b1d390d3dd62bf451fa9d5aebc21d086db Mon Sep 17 00:00:00 2001 From: mazevedo Date: Wed, 23 Oct 2024 16:30:14 -0300 Subject: [PATCH 02/10] Add YiR Share view and hosting controller --- .../WMFYearInReviewShareableSlideView.swift | 64 +++++++++++++ ...InReviewShareableSlideViewController.swift | 33 +++++++ .../WMFYearInReviewViewModel.swift | 16 ++-- Wikipedia/Code/YearInReviewCoordinator.swift | 89 +++++++++++-------- 4 files changed, 156 insertions(+), 46 deletions(-) create mode 100644 WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift create mode 100644 WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideViewController.swift diff --git a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift new file mode 100644 index 0000000000..9458204bdc --- /dev/null +++ b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift @@ -0,0 +1,64 @@ +import SwiftUI + +struct WMFYearInReviewShareableSlideView: View { + + @ObservedObject var appEnvironment = WMFAppEnvironment.current + + var theme: WMFTheme { + return appEnvironment.theme + } + + var viewModel: WMFYearInReviewViewModel + var slide: Int + var body: some View { + //TODO: Font size + //TODO: Smaller screens ? + VStack { + VStack(alignment: .leading, spacing: 16) { + Image(viewModel.slides[slide].imageName, bundle: .module) + .frame(maxWidth: .infinity, alignment: .center) + Text(viewModel.slides[slide].title) + .font(Font(WMFFont.for(.boldTitle1))) + .foregroundStyle(Color(uiColor: theme.text)) + Text(viewModel.slides[slide].subtitle) + .font(Font(WMFFont.for(.title3))) + .foregroundStyle(Color(uiColor: theme.text)) + } + .padding(60) + Spacer() + HStack { + Image("globe", bundle: .module) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 50, height: 50) + .padding(.leading, 15) + + VStack(alignment: .leading) { + Text("#WikipediaYearinReview") + .font(.system(size: 18, weight: .bold)) + .foregroundColor(.blue) + + if let username = viewModel.username { + Text("User:\(username)") // TODO: Localize it + .font(.system(size: 14)) + .foregroundColor(.black) + } + } + .padding(.leading, 10) + + Spacer() + } + .padding() + .background(Color.white) + .cornerRadius(15) + .shadow(color: Color.gray.opacity(0.4), radius: 10, x: 0, y: 5) + .padding(.horizontal, 20) + .frame(height: 80) + + Spacer() + } + .background(Color(.systemBackground)) + .edgesIgnoringSafeArea(.all) + } + +} diff --git a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideViewController.swift b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideViewController.swift new file mode 100644 index 0000000000..d8318a2362 --- /dev/null +++ b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideViewController.swift @@ -0,0 +1,33 @@ +import SwiftUI +import UIKit + +fileprivate final class WMFYearInReviewShareableSlideHostingController: WMFComponentHostingController { + + init(viewModel: WMFYearInReviewViewModel, slide: Int) { + super.init(rootView: WMFYearInReviewShareableSlideView(viewModel: viewModel, slide: slide)) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +public final class WMFYearInReviewShareableSlideViewController: WMFCanvasViewController { + fileprivate let hostingController: WMFYearInReviewShareableSlideHostingController + private let viewModel: WMFYearInReviewViewModel + public init(viewModel: WMFYearInReviewViewModel, slide: Int) { + self.viewModel = viewModel + self.hostingController = WMFYearInReviewShareableSlideHostingController(viewModel: viewModel, slide: slide) + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func viewDidLoad() { + super.viewDidLoad() + addComponent(hostingController, pinToEdges: true) + // Share activity + } +} diff --git a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift index a375f6cdab..079ce90607 100644 --- a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift +++ b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift @@ -5,17 +5,14 @@ public class WMFYearInReviewViewModel: ObservableObject { @Published var isFirstSlide = true let localizedStrings: LocalizedStrings var slides: [YearInReviewSlideContent] - @ObservedObject var appEnvironment = WMFAppEnvironment.current - - var theme: WMFTheme { - return appEnvironment.theme - } + let username: String? weak var coordinatorDelegate: YearInReviewCoordinatorDelegate? - public init(localizedStrings: LocalizedStrings, slides: [YearInReviewSlideContent], coordinatorDelegate: YearInReviewCoordinatorDelegate?) { + public init(localizedStrings: LocalizedStrings, slides: [YearInReviewSlideContent], username: String?, coordinatorDelegate: YearInReviewCoordinatorDelegate?) { self.localizedStrings = localizedStrings self.slides = slides + self.username = username self.coordinatorDelegate = coordinatorDelegate } @@ -44,6 +41,11 @@ public class WMFYearInReviewViewModel: ObservableObject { self.firstSlideHide = firstSlideHide } } + + func handleShare(for slide: Int) { + coordinatorDelegate?.handleYearInReviewAction(.share) + } + } public struct YearInReviewSlideContent: SlideShowProtocol { @@ -67,5 +69,5 @@ public protocol YearInReviewCoordinatorDelegate: AnyObject { } public enum YearInReviewCoordinatorAction { - case share(image: UIImage) + case share } diff --git a/Wikipedia/Code/YearInReviewCoordinator.swift b/Wikipedia/Code/YearInReviewCoordinator.swift index 330899a57c..07ea5ecfa6 100644 --- a/Wikipedia/Code/YearInReviewCoordinator.swift +++ b/Wikipedia/Code/YearInReviewCoordinator.swift @@ -8,99 +8,99 @@ import WMFData final class YearInReviewCoordinator: NSObject, Coordinator { let theme: Theme let dataStore: MWKDataStore - + var navigationController: UINavigationController private weak var viewModel: WMFYearInReviewViewModel? private let targetRects = WMFProfileViewTargetRects() let dataController: WMFYearInReviewDataController - + // Collective base numbers that will change let collectiveNumArticlesText = WMFLocalizedString("year-in-review-2024-Wikipedia-num-articles", value: "63.69 million articles", comment: "Total number of articles across Wikipedia. This text will be inserted into paragraph text displayed in Wikipedia Year in Review slides for 2024.") - + let collectiveNumLanguagesText = WMFLocalizedString("year-in-review-2024-Wikipedia-num-languages", value: "332 active languages", comment: "Number of active languages available on Wikipedia. This text will be inserted into paragraph text displayed in Wikipedia Year in Review slides for 2024.") - + let collectiveNumViewsText = WMFLocalizedString("year-in-review-2024-Wikipedia-num-views", value: "1.4 billion times", comment: "Number of article views on Wikipedia. This text will be inserted into paragraph text displayed in Wikipedia Year in Review slides for 2024.") - + let collectiveNumEditsText = WMFLocalizedString("year-in-review-2024-Wikipedia-num-edits", value: "460,300 edits", comment: "Number of edits made on Wikipedia. This text will be inserted into paragraph text displayed in Wikipedia Year in Review slides for 2024.") - + let collectiveNumEditsPerMinuteText = WMFLocalizedString("year-in-review-2024-Wikipedia-num-edits-per-minute", value: "342 edits per minute", comment: "Number of edits per minute made on Wikipedia. This text will be inserted into paragraph text displayed in Wikipedia Year in Review slides for 2024.") - + public init(navigationController: UINavigationController, theme: Theme, dataStore: MWKDataStore, dataController: WMFYearInReviewDataController) { self.navigationController = navigationController self.theme = theme self.dataStore = dataStore self.dataController = dataController } - + var baseSlide1Title: String { WMFLocalizedString("year-in-review-base-reading-title", value: "Reading brought us together", comment: "Year in review, collective reading article count slide title") } - + var baseSlide1Subtitle: String { let format = WMFLocalizedString("year-in-review-base-reading-subtitle", value: "Wikipedia had %1$@ across over %2$@ this year. You joined millions in expanding knowledge and exploring diverse topics.", comment: "Year in review, collective reading count slide subtitle. %1$@ is replaced with text representing the number of articles available across Wikipedia, e.g. \"63.69 million articles\". %2$@ is replaced with text representing the number of active languages available on Wikipedia, e.g. \"332 active languages\"") return String.localizedStringWithFormat(format, collectiveNumArticlesText, collectiveNumLanguagesText) } - + var baseSlide2Title: String { let format = WMFLocalizedString("year-in-review-base-viewed-title", value: "We have viewed Wikipedia articles %1$@.", comment: "Year in review, collective article view count slide title. %1$@ is replaced with the text representing the number of article views across Wikipedia, e.g. \"1.4 billion times\".") return String.localizedStringWithFormat(format, collectiveNumViewsText) } - + var baseSlide2Subtitle: String { let format = WMFLocalizedString("year-in-review-base-viewed-subtitle", value: "iOS app users have viewed Wikipedia articles %1$@. For people around the world, Wikipedia is the first stop when answering a question, looking up information for school or work, or learning a new fact.", comment: "Year in review, collective article view count subtitle, %1$@ is replaced with the number of article views text, e.g. \"1.4 billion times\"") return String.localizedStringWithFormat(format, collectiveNumViewsText) } - + var baseSlide3Title: String { let format = WMFLocalizedString("year-in-review-base-editors-title", value: "Editors on the iOS app made more than %1$@", comment: "Year in review, collective edits count slide title, %1$@ is replaced with the number of edits text, e.g. \"460,300 edits\".") return String.localizedStringWithFormat(format, collectiveNumEditsText) } - + var baseSlide3Subtitle: String { let format = WMFLocalizedString("year-in-review-base-editors-subtitle", value: "Wikipedia's community of volunteer editors made more than %1$@ on the iOS app so far this year. The heart and soul of Wikipedia is our global community of volunteer contributors, donors, and billions of readers like yourself – all united to share unlimited access to reliable information.", comment: "Year in review, collective edits count slide subtitle, %1$@ is replaced with the number of edits text, e.g. \"460,300 edits\"") return String.localizedStringWithFormat(format, collectiveNumEditsText) } - + var baseSlide4Title: String { let format = WMFLocalizedString("year-in-review-base-edits-title", value: "Wikipedia was edited %1$@", comment: "Year in review, collective edits per minute slide title, %1$@ is replaced with the number of edits per minute text, e.g. \"342 times per minute\".") return String.localizedStringWithFormat(format, collectiveNumEditsPerMinuteText) } - + var baseSlide4Subtitle: String { let format = WMFLocalizedString("year-in-review-base-edits-subtitle", value: "This year, Wikipedia was edited at an average rate of %1$@. Articles are collaboratively created and improved using reliable sources. Each edit plays a crucial role in improving and expanding Wikipedia.", comment: "Year in review, collective edits per minute slide subtitle, %1$@ is replaced with the number of edits per minute text, e.g. \"342 times per minute\"") return String.localizedStringWithFormat(format, collectiveNumEditsPerMinuteText) } - + func personalizedSlide1Title(readCount: Int) -> String { let format = WMFLocalizedString("year-in-review-personalized-reading-title- format", value: "You read {{PLURAL:%1$d|%1$d article|%1$d articles}} this year", comment: "Year in review, personalized reading article count slide title for users that read articles. %1$d is replaced with the number of articles the user read.") return String.localizedStringWithFormat(format, readCount) } - + func personalizedSlide1Subtitle(readCount: Int) -> String { let format = WMFLocalizedString("year-in-review-personalized-reading-subtitle-format", value: "You read {{PLURAL:%1$d|%1$d article|%1$d articles}} this year. This year Wikipedia had %2$@ available across over %3$@ this year. You joined millions in expanding knowledge and exploring diverse topics.", comment: "Year in review, personalized reading article count slide subtitle for users that read articles. %1$d is replaced with the number of articles the user read. %2$@ is replaced with the number of articles available across Wikipedia, for example, \"63.59 million articles\". %3$@ is replaced with the number of active languages available on Wikipedia, for example \"332 active languages\"") return String.localizedStringWithFormat(format, readCount, collectiveNumArticlesText, collectiveNumLanguagesText) } - + private struct PersonalizedSlides { let readCount: YearInReviewSlideContent? let editCount: YearInReviewSlideContent? } - + private func getPersonalizedSlides() -> PersonalizedSlides { - + guard let dataController = try? WMFYearInReviewDataController(), let report = try? dataController.fetchYearInReviewReport(forYear: 2024) else { return PersonalizedSlides(readCount: nil, editCount: nil) } - + var readCountSlide: YearInReviewSlideContent? = nil var editCountSlide: YearInReviewSlideContent? = nil - + for slide in report.slides { switch slide.id { case .readCount: if slide.display == true, - let data = slide.data { + let data = slide.data { let decoder = JSONDecoder() if let readCount = try? decoder.decode(Int.self, from: data) { readCountSlide = YearInReviewSlideContent( @@ -112,25 +112,25 @@ final class YearInReviewCoordinator: NSObject, Coordinator { break } } - + return PersonalizedSlides(readCount: readCountSlide, editCount: editCountSlide) } - + func start() { - + var firstSlide = YearInReviewSlideContent( imageName: "heart_yir", title: baseSlide1Title, informationBubbleText: nil, // Purposefully not translated due to numbers subtitle: baseSlide1Subtitle) - + let personalizedSlides = getPersonalizedSlides() - + if let readCountSlide = personalizedSlides.readCount { firstSlide = readCountSlide } - + let slides: [YearInReviewSlideContent] = [ firstSlide, YearInReviewSlideContent( @@ -149,7 +149,7 @@ final class YearInReviewCoordinator: NSObject, Coordinator { informationBubbleText: nil, subtitle: baseSlide4Subtitle) ] - + let localizedStrings = WMFYearInReviewViewModel.LocalizedStrings.init( donateButtonTitle: WMFLocalizedString("year-in-review-donate", value: "Donate", comment: "Year in review donate button"), doneButtonTitle: WMFLocalizedString("year-in-review-done", value: "Done", comment: "Year in review done button"), @@ -160,35 +160,46 @@ final class YearInReviewCoordinator: NSObject, Coordinator { firstSlideCTA: WMFLocalizedString("year-in-review-get-started", value: "Get Started", comment: "Button to continue to year in review"), firstSlideHide: WMFLocalizedString("year-in-review-hide", value: "Hide this feature", comment: "Button to hide year in review feature") ) - - let viewModel = WMFYearInReviewViewModel(localizedStrings: localizedStrings, slides: slides) + + let viewModel = WMFYearInReviewViewModel(localizedStrings: localizedStrings, slides: slides, username: dataStore.authenticationManager.authStatePermanentUsername, coordinatorDelegate: self) var yirview = WMFYearInReview(viewModel: viewModel) - + yirview.donePressed = { [weak self] in self?.navigationController.dismiss(animated: true, completion: nil) } - + self.viewModel = viewModel - let finalView = yirview.environmentObject(targetRects) + let finalView = yirview.environmentObject(targetRects) let hostingController = UIHostingController(rootView: finalView) hostingController.modalPresentationStyle = .pageSheet - + if let sheetPresentationController = hostingController.sheetPresentationController { sheetPresentationController.detents = [.large()] sheetPresentationController.prefersGrabberVisible = false } - + navigationController.present(hostingController, animated: true, completion: nil) } + + private func dismissYearInReview(completion: @escaping () -> Void) { + navigationController.dismiss(animated: true) { + completion() + } + } } extension YearInReviewCoordinator: YearInReviewCoordinatorDelegate { func handleYearInReviewAction(_ action: WMFComponents.YearInReviewCoordinatorAction) { switch action { - case let .share(image): - print("") + case .share: + dismissYearInReview { + guard let viewModel = self.viewModel else { return } + let shareController = WMFYearInReviewShareableSlideViewController(viewModel: viewModel, slide: 2) // get slide number + self.navigationController.present(shareController, animated: true, completion: nil) + } + } } From b46f7a71747a9a91c4a0b3111ba2b87a5e3f5868 Mon Sep 17 00:00:00 2001 From: mazevedo Date: Thu, 24 Oct 2024 18:30:25 -0300 Subject: [PATCH 03/10] Ajust YiR shareable view --- .../WMFYearInReviewShareableSlideView.swift | 29 +++++++------------ .../WMFYearInReviewViewModel.swift | 4 +-- Wikipedia/Code/YearInReviewCoordinator.swift | 4 +-- 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift index 9458204bdc..07b95f3ce3 100644 --- a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift +++ b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift @@ -11,9 +11,8 @@ struct WMFYearInReviewShareableSlideView: View { var viewModel: WMFYearInReviewViewModel var slide: Int var body: some View { - //TODO: Font size - //TODO: Smaller screens ? VStack { + Spacer() VStack(alignment: .leading, spacing: 16) { Image(viewModel.slides[slide].imageName, bundle: .module) .frame(maxWidth: .infinity, alignment: .center) @@ -24,41 +23,35 @@ struct WMFYearInReviewShareableSlideView: View { .font(Font(WMFFont.for(.title3))) .foregroundStyle(Color(uiColor: theme.text)) } - .padding(60) + .padding(28) Spacer() HStack { Image("globe", bundle: .module) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 50, height: 50) - .padding(.leading, 15) VStack(alignment: .leading) { Text("#WikipediaYearinReview") - .font(.system(size: 18, weight: .bold)) - .foregroundColor(.blue) + .font(Font(WMFFont.for(.boldTitle3))) + .foregroundStyle(Color(uiColor: theme.link)) if let username = viewModel.username { - Text("User:\(username)") // TODO: Localize it - .font(.system(size: 14)) - .foregroundColor(.black) + Text("User:\(username)")// TODO: Localize it + .font(Font(WMFFont.for(.georgiaTitle3))) + .foregroundStyle(Color(uiColor: theme.text)) } } - .padding(.leading, 10) - Spacer() } .padding() - .background(Color.white) - .cornerRadius(15) + .background(Color(uiColor: theme.paperBackground)) + .cornerRadius(12) .shadow(color: Color.gray.opacity(0.4), radius: 10, x: 0, y: 5) - .padding(.horizontal, 20) + .padding(.horizontal, 24) .frame(height: 80) - - Spacer() } - .background(Color(.systemBackground)) - .edgesIgnoringSafeArea(.all) + .background(Color(uiColor: theme.paperBackground)) } } diff --git a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift index 079ce90607..6284309468 100644 --- a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift +++ b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift @@ -43,7 +43,7 @@ public class WMFYearInReviewViewModel: ObservableObject { } func handleShare(for slide: Int) { - coordinatorDelegate?.handleYearInReviewAction(.share) + coordinatorDelegate?.handleYearInReviewAction(.share(slide: slide)) } } @@ -69,5 +69,5 @@ public protocol YearInReviewCoordinatorDelegate: AnyObject { } public enum YearInReviewCoordinatorAction { - case share + case share(slide: Int) } diff --git a/Wikipedia/Code/YearInReviewCoordinator.swift b/Wikipedia/Code/YearInReviewCoordinator.swift index 07ea5ecfa6..baee2b6250 100644 --- a/Wikipedia/Code/YearInReviewCoordinator.swift +++ b/Wikipedia/Code/YearInReviewCoordinator.swift @@ -193,10 +193,10 @@ extension YearInReviewCoordinator: YearInReviewCoordinatorDelegate { func handleYearInReviewAction(_ action: WMFComponents.YearInReviewCoordinatorAction) { switch action { - case .share: + case .share(let slide): dismissYearInReview { guard let viewModel = self.viewModel else { return } - let shareController = WMFYearInReviewShareableSlideViewController(viewModel: viewModel, slide: 2) // get slide number + let shareController = WMFYearInReviewShareableSlideViewController(viewModel: viewModel, slide: slide) self.navigationController.present(shareController, animated: true, completion: nil) } From f79553dcdb89f2cc61339f25322e7e3be9d54e11 Mon Sep 17 00:00:00 2001 From: mazevedo Date: Thu, 24 Oct 2024 20:03:54 -0300 Subject: [PATCH 04/10] Add localized strings --- .../WMFYearInReviewShareableSlideView.swift | 2 +- .../WMFYearInReviewViewModel.swift | 13 +++++++++++-- .../Extensions/View+SnapshotUIImage.swift | 0 Wikipedia/Code/YearInReviewCoordinator.swift | 8 +++++--- .../en.lproj/Localizable.strings | 1 + .../qqq.lproj/Localizable.strings | 1 + .../en.lproj/Localizable.strings | Bin 493634 -> 493932 bytes 7 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 WMFComponents/Sources/WMFComponents/Extensions/View+SnapshotUIImage.swift diff --git a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift index 07b95f3ce3..8e45fc1097 100644 --- a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift +++ b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift @@ -37,7 +37,7 @@ struct WMFYearInReviewShareableSlideView: View { .foregroundStyle(Color(uiColor: theme.link)) if let username = viewModel.username { - Text("User:\(username)")// TODO: Localize it + Text("\(viewModel.localizedStrings.usernameTitle):\(username)") .font(Font(WMFFont.for(.georgiaTitle3))) .foregroundStyle(Color(uiColor: theme.text)) } diff --git a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift index 6284309468..089c6ca615 100644 --- a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift +++ b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift @@ -6,13 +6,17 @@ public class WMFYearInReviewViewModel: ObservableObject { let localizedStrings: LocalizedStrings var slides: [YearInReviewSlideContent] let username: String? + let shareLink: String + let hashtag = "#WikipediaYearInReview" weak var coordinatorDelegate: YearInReviewCoordinatorDelegate? - public init(localizedStrings: LocalizedStrings, slides: [YearInReviewSlideContent], username: String?, coordinatorDelegate: YearInReviewCoordinatorDelegate?) { + public init(isFirstSlide: Bool = true, localizedStrings: LocalizedStrings, slides: [YearInReviewSlideContent], username: String?, shareLink: String, coordinatorDelegate: YearInReviewCoordinatorDelegate?) { + self.isFirstSlide = isFirstSlide self.localizedStrings = localizedStrings self.slides = slides self.username = username + self.shareLink = shareLink self.coordinatorDelegate = coordinatorDelegate } @@ -29,8 +33,10 @@ public class WMFYearInReviewViewModel: ObservableObject { let firstSlideSubtitle: String let firstSlideCTA: String let firstSlideHide: String + let shareText: String + let usernameTitle: String - public init(donateButtonTitle: String, doneButtonTitle: String, shareButtonTitle: String, nextButtonTitle: String, firstSlideTitle: String, firstSlideSubtitle: String, firstSlideCTA: String, firstSlideHide: String) { + public init(donateButtonTitle: String, doneButtonTitle: String, shareButtonTitle: String, nextButtonTitle: String, firstSlideTitle: String, firstSlideSubtitle: String, firstSlideCTA: String, firstSlideHide: String, shareText: String, usernameTitle: String) { self.donateButtonTitle = donateButtonTitle self.doneButtonTitle = doneButtonTitle self.shareButtonTitle = shareButtonTitle @@ -39,7 +45,10 @@ public class WMFYearInReviewViewModel: ObservableObject { self.firstSlideSubtitle = firstSlideSubtitle self.firstSlideCTA = firstSlideCTA self.firstSlideHide = firstSlideHide + self.shareText = shareText + self.usernameTitle = usernameTitle } + } func handleShare(for slide: Int) { diff --git a/WMFComponents/Sources/WMFComponents/Extensions/View+SnapshotUIImage.swift b/WMFComponents/Sources/WMFComponents/Extensions/View+SnapshotUIImage.swift new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Wikipedia/Code/YearInReviewCoordinator.swift b/Wikipedia/Code/YearInReviewCoordinator.swift index baee2b6250..84fd9e05cb 100644 --- a/Wikipedia/Code/YearInReviewCoordinator.swift +++ b/Wikipedia/Code/YearInReviewCoordinator.swift @@ -158,10 +158,12 @@ final class YearInReviewCoordinator: NSObject, Coordinator { firstSlideTitle: WMFLocalizedString("year-in-review-title", value: "Explore your Wikipedia Year in Review", comment: "Year in review page title"), firstSlideSubtitle: WMFLocalizedString("year-in-review-subtitle", value: "See insights about which articles you read on the Wikipedia app and the edits you made. Share your journey and discover what stood out for you this year. Your reading history is kept protected. Reading insights are calculated using locally stored data on your device.", comment: "Year in review page information"), firstSlideCTA: WMFLocalizedString("year-in-review-get-started", value: "Get Started", comment: "Button to continue to year in review"), - firstSlideHide: WMFLocalizedString("year-in-review-hide", value: "Hide this feature", comment: "Button to hide year in review feature") + firstSlideHide: WMFLocalizedString("year-in-review-hide", value: "Hide this feature", comment: "Button to hide year in review feature"), + shareText: WMFLocalizedString("year-in-review-share-text", value: "Here's my Wikipedia year in review. Created with the Wikipedia iOS app", comment: "Text shared the Year In Review slides"), + usernameTitle: CommonStrings.userTitle ) - - let viewModel = WMFYearInReviewViewModel(localizedStrings: localizedStrings, slides: slides, username: dataStore.authenticationManager.authStatePermanentUsername, coordinatorDelegate: self) + let appShareLink = "app link TBD" // TODO: Get from Shay + let viewModel = WMFYearInReviewViewModel(localizedStrings: localizedStrings, slides: slides, username: dataStore.authenticationManager.authStatePermanentUsername, shareLink: appShareLink, coordinatorDelegate: self) var yirview = WMFYearInReview(viewModel: viewModel) diff --git a/Wikipedia/Localizations/en.lproj/Localizable.strings b/Wikipedia/Localizations/en.lproj/Localizable.strings index f9dedfb6ed..2679b6f43e 100644 --- a/Wikipedia/Localizations/en.lproj/Localizable.strings +++ b/Wikipedia/Localizations/en.lproj/Localizable.strings @@ -1413,5 +1413,6 @@ "year-in-review-personalized-reading-subtitle-format" = "You read {{PLURAL:$1|$1 article|$1 articles}} this year. This year Wikipedia had $2 available across over $3 this year. You joined millions in expanding knowledge and exploring diverse topics."; "year-in-review-personalized-reading-title- format" = "You read {{PLURAL:$1|$1 article|$1 articles}} this year"; "year-in-review-share" = "Share"; +"year-in-review-share-text" = "Here's my Wikipedia year in review. Created with the Wikipedia iOS app"; "year-in-review-subtitle" = "See insights about which articles you read on the Wikipedia app and the edits you made. Share your journey and discover what stood out for you this year. Your reading history is kept protected. Reading insights are calculated using locally stored data on your device."; "year-in-review-title" = "Explore your Wikipedia Year in Review"; diff --git a/Wikipedia/Localizations/qqq.lproj/Localizable.strings b/Wikipedia/Localizations/qqq.lproj/Localizable.strings index 69ec5bcf2c..a27d41ef95 100644 --- a/Wikipedia/Localizations/qqq.lproj/Localizable.strings +++ b/Wikipedia/Localizations/qqq.lproj/Localizable.strings @@ -1413,5 +1413,6 @@ "year-in-review-personalized-reading-subtitle-format" = "Year in review, personalized reading article count slide subtitle for users that read articles. $1 is replaced with the number of articles the user read. $2 is replaced with the number of articles available across Wikipedia, for example, \"63.59 million articles\". $3 is replaced with the number of active languages available on Wikipedia, for example \"332 active languages\""; "year-in-review-personalized-reading-title- format" = "Year in review, personalized reading article count slide title for users that read articles. $1 is replaced with the number of articles the user read."; "year-in-review-share" = "Year in review share button"; +"year-in-review-share-text" = "Text shared the Year In Review slides"; "year-in-review-subtitle" = "Year in review page information"; "year-in-review-title" = "Year in review page title"; diff --git a/Wikipedia/iOS Native Localizations/en.lproj/Localizable.strings b/Wikipedia/iOS Native Localizations/en.lproj/Localizable.strings index 8bbb71da56ca2d2ad174088aac2274ce69feef49..b770e17aea394df53985ac5059f6cbaf0ae93fe2 100644 GIT binary patch delta 148 zcmX>!LGH~YxrP?T7N!>F7M2#)7Pc+y_gW?|aOax7EQy& zp3@ygS%s$u#If^DS4m)3<17X$0ZFA!Ke&lel-~oW63AgtXD9}$&Yk|yidlU60x?!O pUOgb+87!B`P%_z3LvnhYD4PgtCWAji@brZbnZ>u?Yhh1e1^^beC!YWS delta 39 vcmaDeN$$`DxrP?T7N!>F7M2#)7Pc+y_gbd+iL)w9Khwd^vE87J-G&(eE^rOy From 8db761f368fa8af31b328f3a7410f5d28434f75e Mon Sep 17 00:00:00 2001 From: mazevedo Date: Thu, 24 Oct 2024 20:06:32 -0300 Subject: [PATCH 05/10] Add extension to export View as Image, add show UIActivityViewController upon viewDidAppear in WMFYearInReviewShareableSlideViewController to allow slide sharing --- .../WMFYearInReviewShareableSlideView.swift | 1 + ...InReviewShareableSlideViewController.swift | 45 ++++++++++++++++++- .../Extensions/View+SnapshotUIImage.swift | 19 ++++++++ Wikipedia/Code/YearInReviewCoordinator.swift | 3 -- 4 files changed, 64 insertions(+), 4 deletions(-) diff --git a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift index 8e45fc1097..e3d8b007ed 100644 --- a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift +++ b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift @@ -51,6 +51,7 @@ struct WMFYearInReviewShareableSlideView: View { .padding(.horizontal, 24) .frame(height: 80) } + .padding(.bottom, 70) .background(Color(uiColor: theme.paperBackground)) } diff --git a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideViewController.swift b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideViewController.swift index d8318a2362..14888541d7 100644 --- a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideViewController.swift +++ b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideViewController.swift @@ -28,6 +28,49 @@ public final class WMFYearInReviewShareableSlideViewController: WMFCanvasViewCon public override func viewDidLoad() { super.viewDidLoad() addComponent(hostingController, pinToEdges: true) - // Share activity + } + + public override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(true) + share() + } + + private func share() { + let screenSize = UIScreen.main.bounds.size + let snapshot = hostingController.rootView.snapshot(with: screenSize) + let text = "\(viewModel.localizedStrings.shareText) (\(viewModel.shareLink))\(viewModel.hashtag)" + + let activityItems: [Any] = [ShareActivityImageItemProvider(image: snapshot), text] + + let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil) + activityController.excludedActivityTypes = [.print, .assignToContact, .addToReadingList] + + activityController.completionWithItemsHandler = { (activityType, completed, returnedItems, error) in + self.dismiss(animated: true, completion: nil) + } + + if let popover = activityController.popoverPresentationController { + popover.sourceRect = self.hostingController.view.bounds + popover.sourceView = self.hostingController.view + } + + self.present(activityController, animated: true, completion: nil) + } +} + +fileprivate class ShareActivityImageItemProvider: UIActivityItemProvider, @unchecked Sendable { + let image: UIImage + + required init(image: UIImage) { + self.image = image + super.init(placeholderItem: image) + } + + override var item: Any { + let type = activityType ?? .message + switch type { + default: + return image + } } } diff --git a/WMFComponents/Sources/WMFComponents/Extensions/View+SnapshotUIImage.swift b/WMFComponents/Sources/WMFComponents/Extensions/View+SnapshotUIImage.swift index e69de29bb2..e0f7fb5b56 100644 --- a/WMFComponents/Sources/WMFComponents/Extensions/View+SnapshotUIImage.swift +++ b/WMFComponents/Sources/WMFComponents/Extensions/View+SnapshotUIImage.swift @@ -0,0 +1,19 @@ +import UIKit +import SwiftUI + +extension View { + /// Captures a snapshot of the SwiftUI view as a UIImage with a given size. + /// - Parameter size: The desired size for the snapshot + /// - Returns: A snapshot of the SwiftUI view as a UIImage. + func snapshot(with size: CGSize) -> UIImage { + // Create a UIHostingController hosting the SwiftUI view. + let controller = UIHostingController(rootView: self.frame(width: size.width, height: size.height)) + let view = controller.view + view?.bounds = CGRect(origin: .zero, size: size) + view?.backgroundColor = .clear + let renderer = UIGraphicsImageRenderer(size: size) + return renderer.image { _ in + view?.drawHierarchy(in: view!.bounds, afterScreenUpdates: true) + } + } +} diff --git a/Wikipedia/Code/YearInReviewCoordinator.swift b/Wikipedia/Code/YearInReviewCoordinator.swift index 84fd9e05cb..b457fe17e2 100644 --- a/Wikipedia/Code/YearInReviewCoordinator.swift +++ b/Wikipedia/Code/YearInReviewCoordinator.swift @@ -193,7 +193,6 @@ final class YearInReviewCoordinator: NSObject, Coordinator { extension YearInReviewCoordinator: YearInReviewCoordinatorDelegate { func handleYearInReviewAction(_ action: WMFComponents.YearInReviewCoordinatorAction) { - switch action { case .share(let slide): dismissYearInReview { @@ -201,8 +200,6 @@ extension YearInReviewCoordinator: YearInReviewCoordinatorDelegate { let shareController = WMFYearInReviewShareableSlideViewController(viewModel: viewModel, slide: slide) self.navigationController.present(shareController, animated: true, completion: nil) } - - } } } From dda3be9382fad6bd1fc4bd14df55d4b89be6199e Mon Sep 17 00:00:00 2001 From: mazevedo Date: Fri, 25 Oct 2024 14:28:29 -0300 Subject: [PATCH 06/10] Delete share hosting controller, skip preview step, use fixed size for shred YiR slide --- .../WMFYearInReviewShareableSlideView.swift | 19 +++-- ...InReviewShareableSlideViewController.swift | 76 ------------------- .../WMFYearInReviewViewModel.swift | 26 +++++-- Wikipedia/Code/YearInReviewCoordinator.swift | 32 +++++--- 4 files changed, 53 insertions(+), 100 deletions(-) delete mode 100644 WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideViewController.swift diff --git a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift index e3d8b007ed..fa1fb67183 100644 --- a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift +++ b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift @@ -8,18 +8,22 @@ struct WMFYearInReviewShareableSlideView: View { return appEnvironment.theme } - var viewModel: WMFYearInReviewViewModel var slide: Int + var slideImage: String + var slideTitle: String + var slideSubtitle: String + var username: String? + var body: some View { VStack { Spacer() VStack(alignment: .leading, spacing: 16) { - Image(viewModel.slides[slide].imageName, bundle: .module) + Image(slideImage, bundle: .module) .frame(maxWidth: .infinity, alignment: .center) - Text(viewModel.slides[slide].title) + Text(slideTitle) .font(Font(WMFFont.for(.boldTitle1))) .foregroundStyle(Color(uiColor: theme.text)) - Text(viewModel.slides[slide].subtitle) + Text(slideSubtitle) .font(Font(WMFFont.for(.title3))) .foregroundStyle(Color(uiColor: theme.text)) } @@ -32,12 +36,12 @@ struct WMFYearInReviewShareableSlideView: View { .frame(width: 50, height: 50) VStack(alignment: .leading) { - Text("#WikipediaYearinReview") + Text("#WikipediaYearInReview") .font(Font(WMFFont.for(.boldTitle3))) .foregroundStyle(Color(uiColor: theme.link)) - if let username = viewModel.username { - Text("\(viewModel.localizedStrings.usernameTitle):\(username)") + if let username { + Text(username) .font(Font(WMFFont.for(.georgiaTitle3))) .foregroundStyle(Color(uiColor: theme.text)) } @@ -53,6 +57,7 @@ struct WMFYearInReviewShareableSlideView: View { } .padding(.bottom, 70) .background(Color(uiColor: theme.paperBackground)) + .frame(width: 402, height: 847) // Fixed iPhone 16 size } } diff --git a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideViewController.swift b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideViewController.swift deleted file mode 100644 index 14888541d7..0000000000 --- a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideViewController.swift +++ /dev/null @@ -1,76 +0,0 @@ -import SwiftUI -import UIKit - -fileprivate final class WMFYearInReviewShareableSlideHostingController: WMFComponentHostingController { - - init(viewModel: WMFYearInReviewViewModel, slide: Int) { - super.init(rootView: WMFYearInReviewShareableSlideView(viewModel: viewModel, slide: slide)) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -public final class WMFYearInReviewShareableSlideViewController: WMFCanvasViewController { - fileprivate let hostingController: WMFYearInReviewShareableSlideHostingController - private let viewModel: WMFYearInReviewViewModel - public init(viewModel: WMFYearInReviewViewModel, slide: Int) { - self.viewModel = viewModel - self.hostingController = WMFYearInReviewShareableSlideHostingController(viewModel: viewModel, slide: slide) - super.init() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - public override func viewDidLoad() { - super.viewDidLoad() - addComponent(hostingController, pinToEdges: true) - } - - public override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(true) - share() - } - - private func share() { - let screenSize = UIScreen.main.bounds.size - let snapshot = hostingController.rootView.snapshot(with: screenSize) - let text = "\(viewModel.localizedStrings.shareText) (\(viewModel.shareLink))\(viewModel.hashtag)" - - let activityItems: [Any] = [ShareActivityImageItemProvider(image: snapshot), text] - - let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil) - activityController.excludedActivityTypes = [.print, .assignToContact, .addToReadingList] - - activityController.completionWithItemsHandler = { (activityType, completed, returnedItems, error) in - self.dismiss(animated: true, completion: nil) - } - - if let popover = activityController.popoverPresentationController { - popover.sourceRect = self.hostingController.view.bounds - popover.sourceView = self.hostingController.view - } - - self.present(activityController, animated: true, completion: nil) - } -} - -fileprivate class ShareActivityImageItemProvider: UIActivityItemProvider, @unchecked Sendable { - let image: UIImage - - required init(image: UIImage) { - self.image = image - super.init(placeholderItem: image) - } - - override var item: Any { - let type = activityType ?? .message - switch type { - default: - return image - } - } -} diff --git a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift index 089c6ca615..846f4d0210 100644 --- a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift +++ b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift @@ -3,11 +3,11 @@ import SwiftUI public class WMFYearInReviewViewModel: ObservableObject { @Published var isFirstSlide = true - let localizedStrings: LocalizedStrings + public let localizedStrings: LocalizedStrings var slides: [YearInReviewSlideContent] let username: String? - let shareLink: String - let hashtag = "#WikipediaYearInReview" + public let shareLink: String + public let hashtag = "#WikipediaYearInReview" weak var coordinatorDelegate: YearInReviewCoordinatorDelegate? @@ -33,8 +33,8 @@ public class WMFYearInReviewViewModel: ObservableObject { let firstSlideSubtitle: String let firstSlideCTA: String let firstSlideHide: String - let shareText: String - let usernameTitle: String + public let shareText: String + public let usernameTitle: String public init(donateButtonTitle: String, doneButtonTitle: String, shareButtonTitle: String, nextButtonTitle: String, firstSlideTitle: String, firstSlideSubtitle: String, firstSlideCTA: String, firstSlideHide: String, shareText: String, usernameTitle: String) { self.donateButtonTitle = donateButtonTitle @@ -51,8 +51,20 @@ public class WMFYearInReviewViewModel: ObservableObject { } + func getFomattedUsername() -> String? { + if let username { + return "\(localizedStrings.usernameTitle):\(username)" + } + return nil + } + func handleShare(for slide: Int) { - coordinatorDelegate?.handleYearInReviewAction(.share(slide: slide)) + + let view = WMFYearInReviewShareableSlideView(slide: slide, slideImage: slides[slide].imageName, slideTitle: slides[slide].title, slideSubtitle: slides[slide].subtitle, username: getFomattedUsername()) + let size = CGSize(width: 402, height: 874) + let shareView = view.snapshot(with: size) // always iphone sized + + coordinatorDelegate?.handleYearInReviewAction(.share(image: shareView)) } } @@ -78,5 +90,5 @@ public protocol YearInReviewCoordinatorDelegate: AnyObject { } public enum YearInReviewCoordinatorAction { - case share(slide: Int) + case share(image: UIImage) } diff --git a/Wikipedia/Code/YearInReviewCoordinator.swift b/Wikipedia/Code/YearInReviewCoordinator.swift index b457fe17e2..6ec47e4e25 100644 --- a/Wikipedia/Code/YearInReviewCoordinator.swift +++ b/Wikipedia/Code/YearInReviewCoordinator.swift @@ -184,21 +184,33 @@ final class YearInReviewCoordinator: NSObject, Coordinator { navigationController.present(hostingController, animated: true, completion: nil) } - private func dismissYearInReview(completion: @escaping () -> Void) { - navigationController.dismiss(animated: true) { - completion() - } - } } extension YearInReviewCoordinator: YearInReviewCoordinatorDelegate { func handleYearInReviewAction(_ action: WMFComponents.YearInReviewCoordinatorAction) { switch action { - case .share(let slide): - dismissYearInReview { - guard let viewModel = self.viewModel else { return } - let shareController = WMFYearInReviewShareableSlideViewController(viewModel: viewModel, slide: slide) - self.navigationController.present(shareController, animated: true, completion: nil) + case .share(let image): + + guard let viewModel else { return } + + let text = "\(viewModel.localizedStrings.shareText) (\(viewModel.shareLink))\(viewModel.hashtag)" + + let activityItems: [Any] = [ShareAFactActivityImageItemProvider(image: image), text] + + let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil) + activityController.excludedActivityTypes = [.print, .assignToContact, .addToReadingList] + + activityController.completionWithItemsHandler = { (activityType, completed, returnedItems, error) in + self.navigationController.dismiss(animated: true, completion: nil) + } + + if let visibleVC = self.navigationController.visibleViewController { + if let popover = activityController.popoverPresentationController { + popover.sourceRect = visibleVC.view.bounds + popover.sourceView = visibleVC.view + } + + visibleVC.present(activityController, animated: true, completion: nil) } } } From d9dd032458cec9baa194231de3c70d4a181e8f86 Mon Sep 17 00:00:00 2001 From: mazevedo Date: Fri, 25 Oct 2024 15:32:38 -0300 Subject: [PATCH 07/10] Remove popover arrow, remove unecessary size argument on snapshot creation, fix coordinator retention --- .../WMFYearInReviewViewModel.swift | 5 +-- .../Extensions/View+SnapshotUIImage.swift | 36 ++++++++++--------- Wikipedia/Code/YearInReviewCoordinator.swift | 5 +-- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift index 968af15687..7b582579b3 100644 --- a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift +++ b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift @@ -17,6 +17,7 @@ public class WMFYearInReviewViewModel: ObservableObject { self.slides = slides self.username = username self.shareLink = shareLink + self.coordinatorDelegate = coordinatorDelegate } public func getStarted() { @@ -60,8 +61,8 @@ public class WMFYearInReviewViewModel: ObservableObject { func handleShare(for slide: Int) { let view = WMFYearInReviewShareableSlideView(slide: slide, slideImage: slides[slide].imageName, slideTitle: slides[slide].title, slideSubtitle: slides[slide].subtitle, username: getFomattedUsername()) - let size = CGSize(width: 402, height: 874) - let shareView = view.snapshot(with: size) // always iphone sized + + let shareView = view.snapshot() coordinatorDelegate?.handleYearInReviewAction(.share(image: shareView)) } diff --git a/WMFComponents/Sources/WMFComponents/Extensions/View+SnapshotUIImage.swift b/WMFComponents/Sources/WMFComponents/Extensions/View+SnapshotUIImage.swift index e0f7fb5b56..bd903cdf13 100644 --- a/WMFComponents/Sources/WMFComponents/Extensions/View+SnapshotUIImage.swift +++ b/WMFComponents/Sources/WMFComponents/Extensions/View+SnapshotUIImage.swift @@ -1,19 +1,23 @@ import UIKit import SwiftUI -extension View { - /// Captures a snapshot of the SwiftUI view as a UIImage with a given size. - /// - Parameter size: The desired size for the snapshot - /// - Returns: A snapshot of the SwiftUI view as a UIImage. - func snapshot(with size: CGSize) -> UIImage { - // Create a UIHostingController hosting the SwiftUI view. - let controller = UIHostingController(rootView: self.frame(width: size.width, height: size.height)) - let view = controller.view - view?.bounds = CGRect(origin: .zero, size: size) - view?.backgroundColor = .clear - let renderer = UIGraphicsImageRenderer(size: size) - return renderer.image { _ in - view?.drawHierarchy(in: view!.bounds, afterScreenUpdates: true) - } - } -} + extension View { + /// Captures a snapshot of the SwiftUI view as a UIImage. + /// - Returns: A snapshot of the SwiftUI view as a UIImage. + func snapshot() -> UIImage { + // Create a UIHostingController hosting the SwiftUI view. + let controller = UIHostingController(rootView: self) + + // Make sure the view is laid out correctly. + let view = controller.view + let targetSize = controller.view.intrinsicContentSize + view?.bounds = CGRect(origin: .zero, size: targetSize) + view?.backgroundColor = .clear + + // Render the view into an image. + let renderer = UIGraphicsImageRenderer(size: targetSize) + return renderer.image { _ in + view?.drawHierarchy(in: view!.bounds, afterScreenUpdates: true) + } + } + } diff --git a/Wikipedia/Code/YearInReviewCoordinator.swift b/Wikipedia/Code/YearInReviewCoordinator.swift index 87cf0ca36a..897c4a3232 100644 --- a/Wikipedia/Code/YearInReviewCoordinator.swift +++ b/Wikipedia/Code/YearInReviewCoordinator.swift @@ -244,14 +244,11 @@ extension YearInReviewCoordinator: YearInReviewCoordinatorDelegate { let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil) activityController.excludedActivityTypes = [.print, .assignToContact, .addToReadingList] - activityController.completionWithItemsHandler = { (activityType, completed, returnedItems, error) in - self.navigationController.dismiss(animated: true, completion: nil) - } - if let visibleVC = self.navigationController.visibleViewController { if let popover = activityController.popoverPresentationController { popover.sourceRect = visibleVC.view.bounds popover.sourceView = visibleVC.view + popover.permittedArrowDirections = [] } visibleVC.present(activityController, animated: true, completion: nil) From 3d8f6e7fb95594ca771d5177a28541f865ce05f3 Mon Sep 17 00:00:00 2001 From: mazevedo Date: Fri, 25 Oct 2024 16:01:02 -0300 Subject: [PATCH 08/10] Fix font size, pass hashtag to view model --- .../WMFYearInReviewShareableSlideView.swift | 11 +++++------ .../Year in Review/WMFYearInReviewViewModel.swift | 11 ++++------- .../Extensions/View+SnapshotUIImage.swift | 2 -- Wikipedia/Code/YearInReviewCoordinator.swift | 3 ++- 4 files changed, 11 insertions(+), 16 deletions(-) diff --git a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift index fa1fb67183..e294fe416c 100644 --- a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift +++ b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift @@ -21,10 +21,10 @@ struct WMFYearInReviewShareableSlideView: View { Image(slideImage, bundle: .module) .frame(maxWidth: .infinity, alignment: .center) Text(slideTitle) - .font(Font(WMFFont.for(.boldTitle1))) + .font(Font(WMFFont.for(.boldTitle1, compatibleWith: UITraitCollection(preferredContentSizeCategory: .medium)))) .foregroundStyle(Color(uiColor: theme.text)) Text(slideSubtitle) - .font(Font(WMFFont.for(.title3))) + .font(Font(WMFFont.for(.title3, compatibleWith: UITraitCollection(preferredContentSizeCategory: .medium)))) .foregroundStyle(Color(uiColor: theme.text)) } .padding(28) @@ -34,15 +34,14 @@ struct WMFYearInReviewShareableSlideView: View { .resizable() .aspectRatio(contentMode: .fit) .frame(width: 50, height: 50) - VStack(alignment: .leading) { Text("#WikipediaYearInReview") - .font(Font(WMFFont.for(.boldTitle3))) + .font(Font(WMFFont.for(.boldTitle3, compatibleWith: UITraitCollection(preferredContentSizeCategory: .medium)))) .foregroundStyle(Color(uiColor: theme.link)) if let username { Text(username) - .font(Font(WMFFont.for(.georgiaTitle3))) + .font(Font(WMFFont.for(.georgiaTitle3, compatibleWith: UITraitCollection(preferredContentSizeCategory: .medium)))) .foregroundStyle(Color(uiColor: theme.text)) } } @@ -57,7 +56,7 @@ struct WMFYearInReviewShareableSlideView: View { } .padding(.bottom, 70) .background(Color(uiColor: theme.paperBackground)) - .frame(width: 402, height: 847) // Fixed iPhone 16 size + .frame(width: 402, height: 847) // Fixed iPhone 16 size for iPad as well } } diff --git a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift index 7b582579b3..4e5644d90a 100644 --- a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift +++ b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift @@ -7,16 +7,17 @@ public class WMFYearInReviewViewModel: ObservableObject { var slides: [YearInReviewSlideContent] let username: String? public let shareLink: String - public let hashtag = "#WikipediaYearInReview" + public let hashtag: String weak var coordinatorDelegate: YearInReviewCoordinatorDelegate? @Published public var isLoading: Bool = false - public init(isFirstSlide: Bool = true, localizedStrings: LocalizedStrings, slides: [YearInReviewSlideContent], username: String?, shareLink: String, coordinatorDelegate: YearInReviewCoordinatorDelegate?) { + public init(isFirstSlide: Bool = true, localizedStrings: LocalizedStrings, slides: [YearInReviewSlideContent], username: String?, shareLink: String, hashtag: String, coordinatorDelegate: YearInReviewCoordinatorDelegate?) { self.isFirstSlide = isFirstSlide self.localizedStrings = localizedStrings self.slides = slides self.username = username self.shareLink = shareLink + self.hashtag = hashtag self.coordinatorDelegate = coordinatorDelegate } @@ -59,14 +60,10 @@ public class WMFYearInReviewViewModel: ObservableObject { } func handleShare(for slide: Int) { - let view = WMFYearInReviewShareableSlideView(slide: slide, slideImage: slides[slide].imageName, slideTitle: slides[slide].title, slideSubtitle: slides[slide].subtitle, username: getFomattedUsername()) - - let shareView = view.snapshot() - + let shareView = view.snapshot() coordinatorDelegate?.handleYearInReviewAction(.share(image: shareView)) } - } public struct YearInReviewSlideContent: SlideShowProtocol { diff --git a/WMFComponents/Sources/WMFComponents/Extensions/View+SnapshotUIImage.swift b/WMFComponents/Sources/WMFComponents/Extensions/View+SnapshotUIImage.swift index bd903cdf13..175777b129 100644 --- a/WMFComponents/Sources/WMFComponents/Extensions/View+SnapshotUIImage.swift +++ b/WMFComponents/Sources/WMFComponents/Extensions/View+SnapshotUIImage.swift @@ -8,13 +8,11 @@ import SwiftUI // Create a UIHostingController hosting the SwiftUI view. let controller = UIHostingController(rootView: self) - // Make sure the view is laid out correctly. let view = controller.view let targetSize = controller.view.intrinsicContentSize view?.bounds = CGRect(origin: .zero, size: targetSize) view?.backgroundColor = .clear - // Render the view into an image. let renderer = UIGraphicsImageRenderer(size: targetSize) return renderer.image { _ in view?.drawHierarchy(in: view!.bounds, afterScreenUpdates: true) diff --git a/Wikipedia/Code/YearInReviewCoordinator.swift b/Wikipedia/Code/YearInReviewCoordinator.swift index 897c4a3232..66c86a73e9 100644 --- a/Wikipedia/Code/YearInReviewCoordinator.swift +++ b/Wikipedia/Code/YearInReviewCoordinator.swift @@ -200,7 +200,8 @@ final class YearInReviewCoordinator: NSObject, Coordinator { ) let appShareLink = "app link TBD" // TODO: Get from Shay - let viewModel = WMFYearInReviewViewModel(localizedStrings: localizedStrings, slides: slides, username: dataStore.authenticationManager.authStatePermanentUsername, shareLink: appShareLink, coordinatorDelegate: self) + let hashtag = "#WikipediaYearInReview" + let viewModel = WMFYearInReviewViewModel(localizedStrings: localizedStrings, slides: slides, username: dataStore.authenticationManager.authStatePermanentUsername, shareLink: appShareLink, hashtag: hashtag, coordinatorDelegate: self) var yirview = WMFYearInReview(viewModel: viewModel) From ee7c71b73c7f78d1fc894faa9923f6671f892b2d Mon Sep 17 00:00:00 2001 From: mazevedo Date: Fri, 25 Oct 2024 16:03:30 -0300 Subject: [PATCH 09/10] Pass hashtag into view --- .../Year in Review/WMFYearInReviewShareableSlideView.swift | 3 ++- .../Components/Year in Review/WMFYearInReviewViewModel.swift | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift index e294fe416c..0d303c2584 100644 --- a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift +++ b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewShareableSlideView.swift @@ -12,6 +12,7 @@ struct WMFYearInReviewShareableSlideView: View { var slideImage: String var slideTitle: String var slideSubtitle: String + var hashtag: String var username: String? var body: some View { @@ -35,7 +36,7 @@ struct WMFYearInReviewShareableSlideView: View { .aspectRatio(contentMode: .fit) .frame(width: 50, height: 50) VStack(alignment: .leading) { - Text("#WikipediaYearInReview") + Text(hashtag) .font(Font(WMFFont.for(.boldTitle3, compatibleWith: UITraitCollection(preferredContentSizeCategory: .medium)))) .foregroundStyle(Color(uiColor: theme.link)) diff --git a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift index 4e5644d90a..348986156e 100644 --- a/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift +++ b/WMFComponents/Sources/WMFComponents/Components/Year in Review/WMFYearInReviewViewModel.swift @@ -60,7 +60,7 @@ public class WMFYearInReviewViewModel: ObservableObject { } func handleShare(for slide: Int) { - let view = WMFYearInReviewShareableSlideView(slide: slide, slideImage: slides[slide].imageName, slideTitle: slides[slide].title, slideSubtitle: slides[slide].subtitle, username: getFomattedUsername()) + let view = WMFYearInReviewShareableSlideView(slide: slide, slideImage: slides[slide].imageName, slideTitle: slides[slide].title, slideSubtitle: slides[slide].subtitle, hashtag: hashtag, username: getFomattedUsername()) let shareView = view.snapshot() coordinatorDelegate?.handleYearInReviewAction(.share(image: shareView)) } From c7d86da75576f6ea2d483d9d3548477e024f95d2 Mon Sep 17 00:00:00 2001 From: mazevedo Date: Fri, 25 Oct 2024 16:16:44 -0300 Subject: [PATCH 10/10] Add correct share link for campaign --- Wikipedia/Code/YearInReviewCoordinator.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wikipedia/Code/YearInReviewCoordinator.swift b/Wikipedia/Code/YearInReviewCoordinator.swift index 66c86a73e9..75d591f58d 100644 --- a/Wikipedia/Code/YearInReviewCoordinator.swift +++ b/Wikipedia/Code/YearInReviewCoordinator.swift @@ -199,7 +199,7 @@ final class YearInReviewCoordinator: NSObject, Coordinator { usernameTitle: CommonStrings.userTitle ) - let appShareLink = "app link TBD" // TODO: Get from Shay + let appShareLink = "https://apps.apple.com/app/apple-store/id324715238?pt=208305&ct=yir_2024_share&mt=8" let hashtag = "#WikipediaYearInReview" let viewModel = WMFYearInReviewViewModel(localizedStrings: localizedStrings, slides: slides, username: dataStore.authenticationManager.authStatePermanentUsername, shareLink: appShareLink, hashtag: hashtag, coordinatorDelegate: self)