Skip to content

Commit

Permalink
Add full settings menu for beta features
Browse files Browse the repository at this point in the history
Fixes IOS-354
  • Loading branch information
whattherestimefor committed Jan 7, 2025
1 parent 1e6ae17 commit 4dd856f
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 21 deletions.
10 changes: 9 additions & 1 deletion Mastodon.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 60;
objectVersion = 70;
objects = {

/* Begin PBXBuildFile section */
Expand Down Expand Up @@ -1271,6 +1271,10 @@
FBD689B42CCBF09F00CE29F3 /* DonationCampaignViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DonationCampaignViewModel.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFileSystemSynchronizedRootGroup section */
FBC4A4F42D2DA424002E654B /* Beta Testing Settings */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = "Beta Testing Settings"; sourceTree = "<group>"; };
/* End PBXFileSystemSynchronizedRootGroup section */

/* Begin PBXFrameworksBuildPhase section */
2A64515A29642A8A00CD8B8A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
Expand Down Expand Up @@ -1760,6 +1764,7 @@
2A0BF97D2C062278004A1E29 /* Privacy and Safety */,
D80911062AC4BFD100EB4D15 /* Server Details */,
D8F917092A4B2AFF008A5370 /* About Mastodon */,
FBC4A4F42D2DA424002E654B /* Beta Testing Settings */,
D8318A7F2A4466D300C0FB73 /* SettingsCoordinator.swift */,
);
path = Settings;
Expand Down Expand Up @@ -3147,6 +3152,9 @@
2A64516829642A8B00CD8B8A /* PBXTargetDependency */,
2A728133297EA9D8004138C5 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
FBC4A4F42D2DA424002E654B /* Beta Testing Settings */,
);
name = Mastodon;
packageProductDependencies = (
357FEEAE29523D470021C9DC /* MastodonSDKDynamic */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright © 2025 Mastodon gGmbH. All rights reserved.

import UIKit

class BetaTestSettingsDiffableTableViewDataSource: UITableViewDiffableDataSource<BetaTestSettingsSectionType, BetaTestSetting> {
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
guard let settingsSectionType = sectionIdentifier(for: section) else { return nil }

return settingsSectionType.sectionTitle.uppercased()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// Copyright © 2025 Mastodon gGmbH. All rights reserved.

import UIKit
import MastodonSDK

struct BetaTestSettingsViewModel {
let useStagingForDonations: Bool
let testGroupedNotifications: Bool

init() {
useStagingForDonations = Mastodon.API.isTestingDonations
testGroupedNotifications = UserDefaults.standard.bool(forKey: BetaTestSetting.useGroupedNotifications.userDefaultsKey)
}

func byToggling(_ setting: BetaTestSetting) -> BetaTestSettingsViewModel {
switch setting {
case .useStagingForDonations:
Mastodon.API.toggleTestingDonations()
case .useGroupedNotifications:
UserDefaults.standard.set(!testGroupedNotifications, forKey: setting.userDefaultsKey)
case .clearPreviousDonationCampaigns:
assertionFailure("this is an action, not a setting")
break
}
return BetaTestSettingsViewModel()
}
}

enum BetaTestSettingsSectionType: Hashable {
case donations
case notifications

var sectionTitle: String {
switch self {
case .donations:
return "Donations"
case .notifications:
return "Notifications"
}
}
}

enum BetaTestSetting: Hashable {
case useStagingForDonations
case clearPreviousDonationCampaigns
case useGroupedNotifications

var labelText: String {
switch self {
case .useStagingForDonations:
return "Donations use test endpoint"
case .clearPreviousDonationCampaigns:
return "Clear donation history"
case .useGroupedNotifications:
return "Test grouped notifications"
}
}

var userDefaultsKey: String {
switch self {
case .useGroupedNotifications:
return "use_grouped_notifications"
case .useStagingForDonations, .clearPreviousDonationCampaigns:
return "UNEXPECTED_KEY"
}
}
}

fileprivate typealias BasicCell = UITableViewCell
fileprivate let basicCellReuseIdentifier = "basic_cell"

class BetaTestSettingsViewController: UIViewController {

let tableView: UITableView

var tableViewDataSource: BetaTestSettingsDiffableTableViewDataSource?

private var viewModel: BetaTestSettingsViewModel {
didSet {
loadFromViewModel(animated: true)
}
}

init() {
tableView = UITableView(frame: .zero, style: .insetGrouped)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.register(BasicCell.self, forCellReuseIdentifier: basicCellReuseIdentifier)
tableView.register(ToggleTableViewCell.self, forCellReuseIdentifier: ToggleTableViewCell.reuseIdentifier)

viewModel = BetaTestSettingsViewModel()

super.init(nibName: nil, bundle: nil)

tableView.delegate = self

let tableViewDataSource = BetaTestSettingsDiffableTableViewDataSource(tableView: tableView, cellProvider: { [weak self] tableView, indexPath, itemIdentifier in
guard let self else { return nil }
switch itemIdentifier {
case .useStagingForDonations:
guard let selectionCell = tableView.dequeueReusableCell(withIdentifier: ToggleTableViewCell.reuseIdentifier, for: indexPath) as? ToggleTableViewCell else { assertionFailure("unexpected cell type"); return nil }
selectionCell.label.text = itemIdentifier.labelText
selectionCell.toggle.isOn = self.viewModel.useStagingForDonations
selectionCell.toggle.removeTarget(self, action: nil, for: .valueChanged)
selectionCell.toggle.addTarget(self, action: #selector(didToggleDonationsStaging), for: .valueChanged)
return selectionCell
case .clearPreviousDonationCampaigns:
let cell = tableView.dequeueReusableCell(withIdentifier: basicCellReuseIdentifier, for: indexPath)
cell.textLabel?.text = itemIdentifier.labelText
cell.textLabel?.textColor = .red
return cell
case .useGroupedNotifications:
guard let selectionCell = tableView.dequeueReusableCell(withIdentifier: ToggleTableViewCell.reuseIdentifier, for: indexPath) as? ToggleTableViewCell else { assertionFailure("unexpected cell type"); return nil }
selectionCell.label.text = itemIdentifier.labelText
selectionCell.toggle.isOn = self.viewModel.testGroupedNotifications
selectionCell.toggle.removeTarget(self, action: nil, for: .valueChanged)
selectionCell.toggle.addTarget(self, action: #selector(didToggleGroupedNotifications), for: .valueChanged)
return selectionCell
}
})

tableView.dataSource = tableViewDataSource
self.tableViewDataSource = tableViewDataSource

view.backgroundColor = .systemGroupedBackground
view.addSubview(tableView)
tableView.pinTo(to: view)

title = "Beta Test Settings"
}

required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }

override func viewDidLoad() {
super.viewDidLoad()
loadFromViewModel(animated: false)
}

@objc func didToggleDonationsStaging(_ sender: UISwitch) {
viewModel = viewModel.byToggling(.useStagingForDonations)
}
@objc func didToggleGroupedNotifications(_ sender: UISwitch) {
viewModel = viewModel.byToggling(.useGroupedNotifications)
}

func loadFromViewModel(animated: Bool = true) {
var snapshot = NSDiffableDataSourceSnapshot<BetaTestSettingsSectionType, BetaTestSetting>()
snapshot.appendSections([.donations, .notifications])
snapshot.appendItems([.useStagingForDonations], toSection: .donations)
if viewModel.useStagingForDonations {
snapshot.appendItems([.useStagingForDonations, .clearPreviousDonationCampaigns], toSection: .donations)
}
snapshot.appendItems([.useGroupedNotifications], toSection: .notifications)
tableViewDataSource?.apply(snapshot, animatingDifferences: animated)
}
}

extension BetaTestSettingsViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let identifier = tableViewDataSource?.itemIdentifier(for: indexPath) else { return }
switch identifier {
case .useStagingForDonations, .useGroupedNotifications:
break
case .clearPreviousDonationCampaigns:
Mastodon.Entity.DonationCampaign.forgetPreviousCampaigns()
DispatchQueue.main.async {
self.tableView.deselectRow(at: indexPath, animated: true)
}
}
}
}
25 changes: 11 additions & 14 deletions Mastodon/Scene/Settings/Settings Overview/Settings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ enum SettingsEntry: Hashable {
case makeDonation
case manageDonations
case logout(accountName: String)
case toggleTestDonations
case clearPreviousDonationCampaigns
case manageBetaFeatures

var title: String {
switch self {
Expand All @@ -38,10 +37,8 @@ enum SettingsEntry: Hashable {
return L10n.Scene.Settings.Overview.aboutMastodon
case .logout(let accountName):
return L10n.Scene.Settings.Overview.logout(accountName)
case .toggleTestDonations:
return Mastodon.API.isTestingDonations ? "Donations use staging: ON" : "Donations use staging: OFF"
case .clearPreviousDonationCampaigns:
return "Clear Donation History"
case .manageBetaFeatures:
return "Beta Features"
}
}

Expand All @@ -51,16 +48,16 @@ enum SettingsEntry: Hashable {
return domain
case .general, .notifications, .privacySafety, .makeDonation, .manageDonations, .aboutMastodon, .logout(_):
return nil
case .toggleTestDonations, .clearPreviousDonationCampaigns:
case .manageBetaFeatures:
return nil
}
}

var accessoryType: UITableViewCell.AccessoryType {
switch self {
case .general, .notifications, .privacySafety, .serverDetails(_), .makeDonation, .manageDonations, .aboutMastodon, .logout(_):
case .general, .notifications, .privacySafety, .serverDetails(_), .manageDonations, .aboutMastodon, .logout(_), .manageBetaFeatures:
return .disclosureIndicator
case .toggleTestDonations, .clearPreviousDonationCampaigns:
case .makeDonation:
return .none
}
}
Expand All @@ -83,8 +80,8 @@ enum SettingsEntry: Hashable {
return UIImage(systemName: "info.circle.fill")
case .logout(_):
return nil
case .toggleTestDonations, .clearPreviousDonationCampaigns:
return nil
case .manageBetaFeatures:
return UIImage(systemName: "wrench.adjustable.fill")
}
}

Expand All @@ -104,8 +101,8 @@ enum SettingsEntry: Hashable {
return .systemPurple
case .logout(_):
return nil
case .toggleTestDonations, .clearPreviousDonationCampaigns:
return nil
case .manageBetaFeatures:
return .systemOrange
}

}
Expand All @@ -116,7 +113,7 @@ enum SettingsEntry: Hashable {
return .label
case .logout(_):
return .red
case .toggleTestDonations, .clearPreviousDonationCampaigns:
case .manageBetaFeatures:
return .systemIndigo
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class SettingsViewController: UIViewController {
baseSections.insert(.init(entries: [.makeDonation, .manageDonations]), at: baseSections.count - 1)
}
if isDebugOrTestflightOrSimulator {
baseSections.append(.init(entries: [.toggleTestDonations, .clearPreviousDonationCampaigns]))
baseSections.append(.init(entries: [.manageBetaFeatures]))
}
sections = baseSections

Expand Down
9 changes: 4 additions & 5 deletions Mastodon/Scene/Settings/SettingsCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,10 @@ extension SettingsCoordinator: SettingsViewControllerDelegate {
navigationController.pushViewController(aboutViewController, animated: true)
case .logout(_):
delegate?.logout(self)
case .toggleTestDonations:
Mastodon.API.toggleTestingDonations()
settingsViewController.tableView.reloadData()
case .clearPreviousDonationCampaigns:
Mastodon.Entity.DonationCampaign.forgetPreviousCampaigns()
case .manageBetaFeatures:
let betaTestSettingsViewController = BetaTestSettingsViewController()

navigationController.pushViewController(betaTestSettingsViewController, animated: true)
}
}
}
Expand Down

0 comments on commit 4dd856f

Please sign in to comment.