Skip to content

Commit

Permalink
[MBL-854] Gate Creator Dashboard Behind Feature Flag (#1828)
Browse files Browse the repository at this point in the history
* Gate tab view controller instantiations and tab data creation behind feature flag

pre-planning

* update RootViewModelTests

the cases where the flag is true is currently not testable due to RemoteConfig's OBJ-C initializer not being available. Still looking into this.

* add override to MockRemoteConfigValue

RemoteConfigValue has no exposed initializer in the public API from Firebase so this is how can change boolValue for testing.

* update tests now that we can effectively control the flag's boolValue

* gate access to switch to dashboard from RootTabBarViewController

* gate access to the creator dashboard project activities

* gate access to creator dashboard creator messages + formatting

* gate access to creator dashboard from the project page

* refactor if/else and add suffix to test

* formatting

* refresh the tabbar when toggling the creator dashboard feature flag

* corrected dashboard tabs/vcs inconsistency.

* removed unused test - because the tabs are no longer based on the feature flag.

---------

Co-authored-by: Mubarak Sadoon <msadoon@gmail.com>
  • Loading branch information
scottkicks and msadoon committed Jun 29, 2023
1 parent e629b80 commit ea00943
Show file tree
Hide file tree
Showing 5 changed files with 292 additions and 114 deletions.
Expand Up @@ -370,6 +370,10 @@ public final class ProjectPageViewController: UIViewController, MessageBannerVie
self.viewModel.outputs.goToDashboard
.observeForControllerAction()
.observeValues { [weak self] param in
guard featureCreatorDashboardEnabled() else {
return
}

self?.goToDashboard(param: param)
}

Expand Down
100 changes: 68 additions & 32 deletions Kickstarter-iOS/Features/RootTabBar/RootTabBarViewController.swift
Expand Up @@ -167,6 +167,10 @@ public final class RootTabBarViewController: UITabBarController, MessageBannerVi
}

public func switchToDashboard(project param: Param?) {
guard featureCreatorDashboardEnabled() else {
return
}

self.viewModel.inputs.switchToDashboard(project: param)
}

Expand Down Expand Up @@ -213,6 +217,10 @@ public final class RootTabBarViewController: UITabBarController, MessageBannerVi
}

public func switchToCreatorMessageThread(projectId: Param, messageThread: MessageThread) {
guard featureCreatorDashboardEnabled() else {
return
}

self.switchToDashboard(project: nil)

guard
Expand All @@ -226,6 +234,10 @@ public final class RootTabBarViewController: UITabBarController, MessageBannerVi
}

public func switchToProjectActivities(projectId: Param) {
guard featureCreatorDashboardEnabled() else {
return
}

self.switchToDashboard(project: nil)

guard
Expand All @@ -248,44 +260,68 @@ public final class RootTabBarViewController: UITabBarController, MessageBannerVi
case let .search(index):
_ = tabBarItem(atIndex: index) ?|> searchTabBarItemStyle
case let .dashboard(index):
_ = tabBarItem(atIndex: index) ?|> dashboardTabBarItemStyle
let featureFlaggedTabBarItemStyle = self
.isDashboardViewControllerDisplayable() ? dashboardTabBarItemStyle :
profileTabBarItemStyle(isLoggedIn: data.isLoggedIn, isMember: data.isMember)
_ = tabBarItem(atIndex: index) ?|> featureFlaggedTabBarItemStyle
case let .profile(avatarUrl, index):
_ = tabBarItem(atIndex: index)
?|> profileTabBarItemStyle(isLoggedIn: data.isLoggedIn, isMember: data.isMember)

guard
data.isLoggedIn == true,
let avatarUrl = avatarUrl,
let dir = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first
else { return }

let hash = avatarUrl.absoluteString.hashValue
let imagePath = "\(dir)/tabbar-avatar-image-\(hash).dat"
let imageUrl = URL(fileURLWithPath: imagePath)

if let imageData = try? Data(contentsOf: imageUrl) {
let (defaultImage, selectedImage) = tabbarAvatarImageFromData(imageData)
_ = self.tabBarItem(atIndex: index)
?|> profileTabBarItemStyle(isLoggedIn: true, isMember: data.isMember)
?|> UITabBarItem.lens.image .~ defaultImage
?|> UITabBarItem.lens.selectedImage .~ selectedImage
} else {
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig, delegate: nil, delegateQueue: .main)
let dataTask = session.dataTask(with: avatarUrl) { [weak self] avatarData, _, _ in
guard let avatarData = avatarData else { return }
try? avatarData.write(to: imageUrl, options: [.atomic])

let (defaultImage, selectedImage) = tabbarAvatarImageFromData(avatarData)
_ = self?.tabBarItem(atIndex: index)
?|> profileTabBarItemStyle(isLoggedIn: true, isMember: data.isMember)
?|> UITabBarItem.lens.image .~ defaultImage
?|> UITabBarItem.lens.selectedImage .~ selectedImage
}
dataTask.resume()
}
setProfileImage(with: data, avatarUrl: avatarUrl, index: index)
}
}
}

fileprivate func setProfileImage(with data: TabBarItemsData, avatarUrl: URL?, index: Int) {
guard
data.isLoggedIn == true,
let avatarUrl = avatarUrl,
let dir = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first
else { return }

let hash = avatarUrl.absoluteString.hashValue
let imagePath = "\(dir)/tabbar-avatar-image-\(hash).dat"
let imageUrl = URL(fileURLWithPath: imagePath)

if let imageData = try? Data(contentsOf: imageUrl) {
let (defaultImage, selectedImage) = tabbarAvatarImageFromData(imageData)
_ = self.tabBarItem(atIndex: index)
?|> profileTabBarItemStyle(isLoggedIn: true, isMember: data.isMember)
?|> UITabBarItem.lens.image .~ defaultImage
?|> UITabBarItem.lens.selectedImage .~ selectedImage
} else {
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig, delegate: nil, delegateQueue: .main)
let dataTask = session.dataTask(with: avatarUrl) { [weak self] avatarData, _, _ in
guard let avatarData = avatarData else { return }
try? avatarData.write(to: imageUrl, options: [.atomic])

let (defaultImage, selectedImage) = tabbarAvatarImageFromData(avatarData)
_ = self?.tabBarItem(atIndex: index)
?|> profileTabBarItemStyle(isLoggedIn: true, isMember: data.isMember)
?|> UITabBarItem.lens.image .~ defaultImage
?|> UITabBarItem.lens.selectedImage .~ selectedImage
}
dataTask.resume()
}
}

fileprivate func isDashboardViewControllerDisplayable() -> Bool {
guard let navigationControllers = self.viewControllers as? [UINavigationController] else {
return false
}

var foundDashboardViewController = false

for navController in navigationControllers {
if let dashboardVC = navController.viewControllers.first as? DashboardViewController {
foundDashboardViewController = true
break
}
}

return foundDashboardViewController
}

fileprivate func tabBarItem(atIndex index: Int) -> UITabBarItem? {
Expand Down
36 changes: 17 additions & 19 deletions Library/RemoteConfig/RemoteConfigFeature+HelpersTests.swift
Expand Up @@ -12,25 +12,23 @@ final class RemoteConfigFeatureHelpersTests: TestCase {
}
}

/** FIXME: RemoteConfigValue is not initializing because its' OBJC intiliazer is not available
func testConsentManagementDialog_RemoteConfig_FeatureFlag_True() {
let mockRemoteConfigClient = MockRemoteConfigClient()
|> \.features .~ [RemoteConfigFeature.consentManagementDialogEnabled.rawValue: true]
withEnvironment(remoteConfigClient: mockRemoteConfigClient) {
XCTAssertTrue(featureConsentManagementDialogEnabled())
}
}
func testFacebookDeprecation_RemoteConfig_FeatureFlag_True() {
let mockRemoteConfigClient = MockRemoteConfigClient()
|> \.features .~ [RemoteConfigFeature.facebookLoginInterstitialEnabled.rawValue: true]
withEnvironment(remoteConfigClient: mockRemoteConfigClient) {
XCTAssertTrue(featureFacebookLoginInterstitialEnabled())
}
}
*/
func testConsentManagementDialog_RemoteConfig_FeatureFlag_True() {
let mockRemoteConfigClient = MockRemoteConfigClient()
|> \.features .~ [RemoteConfigFeature.consentManagementDialogEnabled.rawValue: true]

withEnvironment(remoteConfigClient: mockRemoteConfigClient) {
XCTAssertTrue(featureConsentManagementDialogEnabled())
}
}

func testFacebookDeprecation_RemoteConfig_FeatureFlag_True() {
let mockRemoteConfigClient = MockRemoteConfigClient()
|> \.features .~ [RemoteConfigFeature.facebookLoginInterstitialEnabled.rawValue: true]

withEnvironment(remoteConfigClient: mockRemoteConfigClient) {
XCTAssertTrue(featureFacebookLoginInterstitialEnabled())
}
}

func testFacebookDeprecation_RemoteConfig_FeatureFlag_False() {
let mockRemoteConfigClient = MockRemoteConfigClient()
Expand Down
23 changes: 16 additions & 7 deletions Library/ViewModels/RootViewModel.swift
Expand Up @@ -488,21 +488,30 @@ private func generateStandardViewControllers() -> [RootViewControllerData] {

private func generatePersonalizedViewControllers(userState: (isMember: Bool, isLoggedIn: Bool))
-> [RootViewControllerData] {
return [.dashboard(isMember: userState.isMember), .profile(isLoggedIn: userState.isLoggedIn)]
if featureCreatorDashboardEnabled() {
return [.dashboard(isMember: userState.isMember), .profile(isLoggedIn: userState.isLoggedIn)]
}

return [.profile(isLoggedIn: userState.isLoggedIn)]
}

private func tabData(forUser user: User?) -> TabBarItemsData {
let isMember =
(user?.stats.memberProjectsCount ?? 0) > 0
let items: [TabBarItem] = isMember
? [
.home(index: 0), .activity(index: 1), .search(index: 2), .dashboard(index: 3),
.profile(avatarUrl: (user?.avatar.small).flatMap(URL.init(string:)), index: 4)
]
: [
let items: [TabBarItem]

switch isMember {
case false:
items = [
.home(index: 0), .activity(index: 1), .search(index: 2),
.profile(avatarUrl: (user?.avatar.small).flatMap(URL.init(string:)), index: 3)
]
case true:
items = [
.home(index: 0), .activity(index: 1), .search(index: 2), .dashboard(index: 3),
.profile(avatarUrl: (user?.avatar.small).flatMap(URL.init(string:)), index: 4)
]
}

return TabBarItemsData(
items: items,
Expand Down

0 comments on commit ea00943

Please sign in to comment.