Skip to content

Commit da09d05

Browse files
authored
Merge pull request #5747 from woocommerce/issue/5509-handle-actions-in-settings
Hub Menu: handle the remaining actions, like Switch Store, Settings and WooCommerce Admin
2 parents cc6036e + 78d8c49 commit da09d05

File tree

6 files changed

+131
-21
lines changed

6 files changed

+131
-21
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import SwiftUI
2+
3+
/// SwiftUI conformance for `SettingsViewController`
4+
///
5+
struct SettingsView: UIViewControllerRepresentable {
6+
7+
typealias UIViewControllerType = SettingsViewController
8+
9+
class Coordinator {
10+
var parentObserver: NSKeyValueObservation?
11+
}
12+
13+
/// This is a UIKit solution for fixing Navigation Title and Bar Button Items ignored in NavigationView.
14+
/// This solution doesn't require making internal changes to the destination `UIViewController`
15+
/// and should be called once, when wrapped.
16+
/// Solution proposed here: https://stackoverflow.com/a/68567095/7241994
17+
///
18+
func makeUIViewController(context: Self.Context) -> SettingsViewController {
19+
let viewController = SettingsViewController()
20+
context.coordinator.parentObserver = viewController.observe(\.parent, changeHandler: { vc, _ in
21+
vc.parent?.navigationItem.title = vc.title
22+
vc.parent?.navigationItem.rightBarButtonItems = vc.navigationItem.rightBarButtonItems
23+
})
24+
return viewController
25+
}
26+
27+
func updateUIViewController(_ uiViewController: SettingsViewController, context: Context) {
28+
// nothing to do here
29+
}
30+
31+
func makeCoordinator() -> Self.Coordinator { Coordinator() }
32+
}

WooCommerce/Classes/ViewRelated/Hub Menu/HubMenu.swift

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,28 @@
11
import SwiftUI
2+
import Kingfisher
23

34
/// This view will be embedded inside the `HubMenuViewController`
45
/// and will be the entry point of the `Menu` Tab.
56
///
67
struct HubMenu: View {
78
@ObservedObject private var viewModel: HubMenuViewModel
8-
@State private var showViewStore = false
9-
@State private var showReviews = false
9+
@State private var showingWooCommerceAdmin = false
10+
@State private var showingViewStore = false
11+
@State private var showingReviews = false
1012
@State private var showingCoupons = false
1113

12-
init(siteID: Int64) {
13-
viewModel = HubMenuViewModel(siteID: siteID)
14+
init(siteID: Int64, navigationController: UINavigationController? = nil) {
15+
viewModel = HubMenuViewModel(siteID: siteID, navigationController: navigationController)
1416
}
1517

1618
var body: some View {
1719
VStack {
18-
TopBar(storeTitle: viewModel.storeTitle,
19-
storeURL: viewModel.storeURL.absoluteString)
20+
TopBar(avatarURL: viewModel.avatarURL,
21+
storeTitle: viewModel.storeTitle,
22+
storeURL: viewModel.storeURL.absoluteString) {
23+
viewModel.presentSwitchStore()
24+
}
25+
.padding([.leading, .trailing], Constants.padding)
2026

2127
ScrollView {
2228
let gridItemLayout = [GridItem(.adaptive(minimum: Constants.itemSize), spacing: Constants.itemSpacing)]
@@ -27,10 +33,12 @@ struct HubMenu: View {
2733
.frame(width: Constants.itemSize, height: Constants.itemSize)
2834
.onTapGesture {
2935
switch menu {
36+
case .woocommerceAdmin:
37+
showingWooCommerceAdmin = true
3038
case .viewStore:
31-
showViewStore = true
39+
showingViewStore = true
3240
case .reviews:
33-
showReviews = true
41+
showingReviews = true
3442
case .coupons:
3543
showingCoupons = true
3644
default:
@@ -46,10 +54,11 @@ struct HubMenu: View {
4654
.padding(Constants.padding)
4755
.background(Color(.listBackground))
4856
}
49-
.safariSheet(isPresented: $showViewStore, url: viewModel.storeURL)
57+
.safariSheet(isPresented: $showingWooCommerceAdmin, url: viewModel.woocommerceAdminURL)
58+
.safariSheet(isPresented: $showingViewStore, url: viewModel.storeURL)
5059
NavigationLink(destination:
5160
ReviewsView(siteID: viewModel.siteID),
52-
isActive: $showReviews) {
61+
isActive: $showingReviews) {
5362
EmptyView()
5463
}.hidden()
5564
NavigationLink(destination: CouponListView(siteID: viewModel.siteID), isActive: $showingCoupons) {
@@ -61,23 +70,40 @@ struct HubMenu: View {
6170
}
6271

6372
private struct TopBar: View {
73+
let avatarURL: URL?
6474
let storeTitle: String
6575
let storeURL: String?
76+
var switchStoreHandler: (() -> Void)?
6677

78+
@State private var showSettings = false
6779
@ScaledMetric var settingsSize: CGFloat = 28
6880
@ScaledMetric var settingsIconSize: CGFloat = 20
6981

7082
var body: some View {
71-
HStack() {
83+
HStack(spacing: Constants.padding) {
84+
if let avatarURL = avatarURL {
85+
VStack {
86+
KFImage(avatarURL)
87+
.resizable()
88+
.clipShape(Circle())
89+
.frame(width: Constants.avatarSize, height: Constants.avatarSize)
90+
Spacer()
91+
}
92+
.fixedSize()
93+
}
94+
7295
VStack(alignment: .leading,
7396
spacing: Constants.topBarSpacing) {
74-
Text(storeTitle).headlineStyle()
97+
Text(storeTitle)
98+
.headlineStyle()
99+
.lineLimit(1)
75100
if let storeURL = storeURL {
76101
Text(storeURL)
77102
.subheadlineStyle()
103+
.lineLimit(1)
78104
}
79105
Button(Localization.switchStore) {
80-
106+
switchStoreHandler?()
81107
}
82108
.linkStyle()
83109
}
@@ -97,13 +123,19 @@ struct HubMenu: View {
97123
}
98124
}
99125
.onTapGesture {
100-
// TODO-5509: implement tap
126+
showSettings = true
101127
}
102128
Spacer()
103129
}
104130
.fixedSize()
105131
}
106132
.padding([.top, .leading, .trailing], Constants.padding)
133+
134+
NavigationLink(destination:
135+
SettingsView(),
136+
isActive: $showSettings) {
137+
EmptyView()
138+
}.hidden()
107139
}
108140
}
109141

@@ -113,6 +145,7 @@ struct HubMenu: View {
113145
static let itemSize: CGFloat = 160
114146
static let padding: CGFloat = 16
115147
static let topBarSpacing: CGFloat = 2
148+
static let avatarSize: CGFloat = 40
116149
}
117150

118151
private enum Localization {

WooCommerce/Classes/ViewRelated/Hub Menu/HubMenuCoordinator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ final class HubMenuCoordinator: Coordinator {
5454

5555
/// Replaces `start()` because the menu tab's navigation stack could be updated multiple times when site ID changes.
5656
func activate(siteID: Int64) {
57-
navigationController.viewControllers = [HubMenuViewController(siteID: siteID)]
57+
navigationController.viewControllers = [HubMenuViewController(siteID: siteID, navigationController: navigationController)]
5858

5959
if observationToken == nil {
6060
observationToken = pushNotificationsManager.inactiveNotifications.subscribe { [weak self] in

WooCommerce/Classes/ViewRelated/Hub Menu/HubMenuViewController.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import UIKit
33

44
/// Displays a grid view of all available menu in the "Menu" tab (eg. View Store, Reviews, Coupons, etc...)
55
final class HubMenuViewController: UIHostingController<HubMenu> {
6-
init(siteID: Int64) {
7-
super.init(rootView: HubMenu(siteID: siteID))
6+
init(siteID: Int64, navigationController: UINavigationController?) {
7+
super.init(rootView: HubMenu(siteID: siteID, navigationController: navigationController))
88
configureNavigationBar()
99
configureTabBarItem()
1010
}

WooCommerce/Classes/ViewRelated/Hub Menu/HubMenuViewModel.swift

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Foundation
22
import UIKit
33
import SwiftUI
4+
import Combine
45
import Experiments
56

67
/// View model for `HubMenu`.
@@ -9,26 +10,65 @@ final class HubMenuViewModel: ObservableObject {
910

1011
let siteID: Int64
1112

12-
let storeTitle = ServiceLocator.stores.sessionManager.defaultSite?.name ?? Localization.myStore
13+
/// The view controller that will be used for presenting the `StorePickerViewController` via `StorePickerCoordinator`
14+
///
15+
private(set) unowned var navigationController: UINavigationController?
16+
17+
var avatarURL: URL? {
18+
guard let urlString = ServiceLocator.stores.sessionManager.defaultAccount?.gravatarUrl, let url = URL(string: urlString) else {
19+
return nil
20+
}
21+
return url
22+
}
23+
var storeTitle: String {
24+
ServiceLocator.stores.sessionManager.defaultSite?.name ?? Localization.myStore
25+
}
1326
var storeURL: URL {
1427
guard let urlString = ServiceLocator.stores.sessionManager.defaultSite?.url, let url = URL(string: urlString) else {
1528
return WooConstants.URLs.blog.asURL()
1629
}
17-
30+
return url
31+
}
32+
var woocommerceAdminURL: URL {
33+
guard let urlString = ServiceLocator.stores.sessionManager.defaultSite?.adminURL, let url = URL(string: urlString) else {
34+
return WooConstants.URLs.blog.asURL()
35+
}
1836
return url
1937
}
2038

2139
/// Child items
2240
///
2341
@Published private(set) var menuElements: [Menu] = []
2442

25-
init(siteID: Int64, featureFlagService: FeatureFlagService = ServiceLocator.featureFlagService) {
43+
private var storePickerCoordinator: StorePickerCoordinator?
44+
45+
private var cancellables = Set<AnyCancellable>()
46+
47+
init(siteID: Int64, navigationController: UINavigationController? = nil, featureFlagService: FeatureFlagService = ServiceLocator.featureFlagService) {
2648
self.siteID = siteID
27-
menuElements = [.woocommerceAdmin, .viewStore]
49+
self.navigationController = navigationController
50+
menuElements = [.woocommerceAdmin, .viewStore, .reviews]
2851
if featureFlagService.isFeatureFlagEnabled(.couponManagement) {
2952
menuElements.append(.coupons)
3053
}
3154
menuElements.append(.reviews)
55+
observeSiteForUIUpdates()
56+
}
57+
58+
/// Present the `StorePickerViewController` using the `StorePickerCoordinator`, passing the navigation controller from the entry point.
59+
///
60+
func presentSwitchStore() {
61+
//TODO-5509: add analytics events
62+
if let navigationController = navigationController {
63+
storePickerCoordinator = StorePickerCoordinator(navigationController, config: .switchingStores)
64+
storePickerCoordinator?.start()
65+
}
66+
}
67+
68+
private func observeSiteForUIUpdates() {
69+
ServiceLocator.stores.site.sink { site in
70+
// This will be useful in the future for updating some info of the screen depending on the store site info
71+
}.store(in: &cancellables)
3272
}
3373
}
3474

WooCommerce/WooCommerce.xcodeproj/project.pbxproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,7 @@
766766
45DB70662614CE3F0064A6CF /* Decimal+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45DB70652614CE3F0064A6CF /* Decimal+Helpers.swift */; };
767767
45DB706C26161F970064A6CF /* DecimalWooTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45DB706B26161F970064A6CF /* DecimalWooTests.swift */; };
768768
45DB7076261623410064A6CF /* ShippingLabelPackageDetailsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45DB7075261623410064A6CF /* ShippingLabelPackageDetailsViewModelTests.swift */; };
769+
45E1482527736D1E0099CF23 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E1482427736D1E0099CF23 /* SettingsView.swift */; };
769770
45E3C8F325E7D30300102E84 /* ShippingLabelAddress+Woo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E3C8F225E7D30300102E84 /* ShippingLabelAddress+Woo.swift */; };
770771
45E9A6E424DAE1EA00A600E8 /* ProductReviewsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E9A6E224DAE1EA00A600E8 /* ProductReviewsViewController.swift */; };
771772
45E9A6E524DAE1EA00A600E8 /* ProductReviewsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 45E9A6E324DAE1EA00A600E8 /* ProductReviewsViewController.xib */; };
@@ -2308,6 +2309,7 @@
23082309
45DB70652614CE3F0064A6CF /* Decimal+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Decimal+Helpers.swift"; sourceTree = "<group>"; };
23092310
45DB706B26161F970064A6CF /* DecimalWooTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecimalWooTests.swift; sourceTree = "<group>"; };
23102311
45DB7075261623410064A6CF /* ShippingLabelPackageDetailsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShippingLabelPackageDetailsViewModelTests.swift; sourceTree = "<group>"; };
2312+
45E1482427736D1E0099CF23 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
23112313
45E3C8F225E7D30300102E84 /* ShippingLabelAddress+Woo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ShippingLabelAddress+Woo.swift"; sourceTree = "<group>"; };
23122314
45E9A6E224DAE1EA00A600E8 /* ProductReviewsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductReviewsViewController.swift; sourceTree = "<group>"; };
23132315
45E9A6E324DAE1EA00A600E8 /* ProductReviewsViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ProductReviewsViewController.xib; sourceTree = "<group>"; };
@@ -6039,6 +6041,7 @@
60396041
027D4A8B2526FD1700108626 /* SettingsViewController.swift */,
60406042
BAE4F8422734325C00871344 /* SettingsViewModel.swift */,
60416043
027D4A8C2526FD1700108626 /* SettingsViewController.xib */,
6044+
45E1482427736D1E0099CF23 /* SettingsView.swift */,
60426045
);
60436046
path = Settings;
60446047
sourceTree = "<group>";
@@ -8085,6 +8088,7 @@
80858088
311D21E8264AEDB900102316 /* CardPresentModalScanningForReader.swift in Sources */,
80868089
2688643D25D470C000821BA5 /* EditAttributesViewModel.swift in Sources */,
80878090
31C21FA426D9949000916E2E /* SeveralReadersFoundViewController.swift in Sources */,
8091+
45E1482527736D1E0099CF23 /* SettingsView.swift in Sources */,
80888092
CC078531266E706300BA9AC1 /* ErrorTopBannerFactory.swift in Sources */,
80898093
DE8C94662646990000C94823 /* PluginListViewController.swift in Sources */,
80908094
B6E851F3276320C70041D1BA /* RefundFeesDetailsViewModel.swift in Sources */,
@@ -8158,6 +8162,7 @@
81588162
DEC51A9D274F8528009F3DF4 /* JetpackInstallStepsViewModel.swift in Sources */,
81598163
455DC3A327393C7E00D4644C /* OrderDatesFilterViewController.swift in Sources */,
81608164
45B6F4EF27592A4000C18782 /* ReviewsView.swift in Sources */,
8165+
028BAC4222F30B05008BB4AF /* OldStoreStatsV4PeriodViewController.swift in Sources */,
81618166
268FD44727580A81008FDF9B /* CollectOrderPaymentUseCase.swift in Sources */,
81628167
740987B321B87760000E4C80 /* FancyAnimatedButton+Woo.swift in Sources */,
81638168
45F627B6253603AE00894B86 /* Product+DownloadSettingsViewModels.swift in Sources */,

0 commit comments

Comments
 (0)