mirror of
https://github.com/mastodon/mastodon-ios.git
synced 2025-02-02 18:36:44 +01:00
Make the app work on iPad again (IOS-192)
Friends don't let friends use forced unwrapping.
This commit is contained in:
parent
6bcbc0ac07
commit
88c5cfa140
@ -16,7 +16,7 @@ extension HomeTimelineViewController: DataSourceProvider {
|
||||
}
|
||||
guard let indexPath = _indexPath else { return nil }
|
||||
|
||||
guard let item = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else {
|
||||
guard let item = viewModel?.diffableDataSource?.itemIdentifier(for: indexPath) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@ extension HomeTimelineViewController: DataSourceProvider {
|
||||
}
|
||||
|
||||
func update(status: MastodonStatus, intent: MastodonStatus.UpdateIntent) {
|
||||
viewModel.dataController.update(status: status, intent: intent)
|
||||
viewModel?.dataController.update(status: status, intent: intent)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
@ -25,8 +25,8 @@ final class HomeTimelineViewController: UIViewController, NeedsDependency, Media
|
||||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
var viewModel: HomeTimelineViewModel!
|
||||
|
||||
var viewModel: HomeTimelineViewModel?
|
||||
|
||||
let mediaPreviewTransitionController = MediaPreviewTransitionController()
|
||||
|
||||
let friendsAssetImageView: UIImageView = {
|
||||
@ -82,7 +82,7 @@ extension HomeTimelineViewController {
|
||||
title = L10n.Scene.HomeTimeline.title
|
||||
view.backgroundColor = .secondarySystemBackground
|
||||
|
||||
viewModel.$displaySettingBarButtonItem
|
||||
viewModel?.$displaySettingBarButtonItem
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] displaySettingBarButtonItem in
|
||||
guard let self = self else { return }
|
||||
@ -97,7 +97,7 @@ extension HomeTimelineViewController {
|
||||
navigationItem.titleView = titleView
|
||||
titleView.delegate = self
|
||||
|
||||
viewModel.homeTimelineNavigationBarTitleViewModel.state
|
||||
viewModel?.homeTimelineNavigationBarTitleViewModel.state
|
||||
.removeDuplicates()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] state in
|
||||
@ -106,7 +106,7 @@ extension HomeTimelineViewController {
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
viewModel.homeTimelineNavigationBarTitleViewModel.state
|
||||
viewModel?.homeTimelineNavigationBarTitleViewModel.state
|
||||
.removeDuplicates()
|
||||
.filter { $0 == .publishedButton }
|
||||
.receive(on: DispatchQueue.main)
|
||||
@ -137,27 +137,27 @@ extension HomeTimelineViewController {
|
||||
publishProgressView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
])
|
||||
|
||||
viewModel.tableView = tableView
|
||||
viewModel?.tableView = tableView
|
||||
tableView.delegate = self
|
||||
viewModel.setupDiffableDataSource(
|
||||
viewModel?.setupDiffableDataSource(
|
||||
tableView: tableView,
|
||||
statusTableViewCellDelegate: self,
|
||||
timelineMiddleLoaderTableViewCellDelegate: self
|
||||
)
|
||||
|
||||
// setup batch fetch
|
||||
viewModel.listBatchFetchViewModel.setup(scrollView: tableView)
|
||||
viewModel.listBatchFetchViewModel.shouldFetch
|
||||
viewModel?.listBatchFetchViewModel.setup(scrollView: tableView)
|
||||
viewModel?.listBatchFetchViewModel.shouldFetch
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
guard self.view.window != nil else { return }
|
||||
self.viewModel.loadOldestStateMachine.enter(HomeTimelineViewModel.LoadOldestState.Loading.self)
|
||||
self.viewModel?.loadOldestStateMachine.enter(HomeTimelineViewModel.LoadOldestState.Loading.self)
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
// bind refresh control
|
||||
viewModel.didLoadLatest
|
||||
viewModel?.didLoadLatest
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
@ -170,8 +170,8 @@ extension HomeTimelineViewController {
|
||||
|
||||
context.publisherService.statusPublishResult.receive(on: DispatchQueue.main).sink { result in
|
||||
if case .success(.edit(let status)) = result {
|
||||
self.viewModel.hasPendingStatusEditReload = true
|
||||
self.viewModel.dataController.update(status: .fromEntity(status.value), intent: .edit)
|
||||
self.viewModel?.hasPendingStatusEditReload = true
|
||||
self.viewModel?.dataController.update(status: .fromEntity(status.value), intent: .edit)
|
||||
}
|
||||
}.store(in: &disposeBag)
|
||||
|
||||
@ -204,7 +204,7 @@ extension HomeTimelineViewController {
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
viewModel.timelineIsEmpty
|
||||
viewModel?.timelineIsEmpty
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] isEmpty in
|
||||
if isEmpty {
|
||||
@ -218,9 +218,9 @@ extension HomeTimelineViewController {
|
||||
userDoesntFollowPeople = true
|
||||
}
|
||||
|
||||
if (self?.viewModel.presentedSuggestions == false) && userDoesntFollowPeople {
|
||||
if (self?.viewModel?.presentedSuggestions == false) && userDoesntFollowPeople {
|
||||
self?.findPeopleButtonPressed(self)
|
||||
self?.viewModel.presentedSuggestions = true
|
||||
self?.viewModel?.presentedSuggestions = true
|
||||
}
|
||||
} else {
|
||||
self?.emptyView.removeFromSuperview()
|
||||
@ -264,16 +264,16 @@ extension HomeTimelineViewController {
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
if let timestamp = viewModel.lastAutomaticFetchTimestamp {
|
||||
if let timestamp = viewModel?.lastAutomaticFetchTimestamp {
|
||||
let now = Date()
|
||||
if now.timeIntervalSince(timestamp) > 60 {
|
||||
self.viewModel.lastAutomaticFetchTimestamp = now
|
||||
self.viewModel.homeTimelineNeedRefresh.send()
|
||||
self.viewModel?.lastAutomaticFetchTimestamp = now
|
||||
self.viewModel?.homeTimelineNeedRefresh.send()
|
||||
} else {
|
||||
// do nothing
|
||||
}
|
||||
} else {
|
||||
self.viewModel.homeTimelineNeedRefresh.send()
|
||||
self.viewModel?.homeTimelineNeedRefresh.send()
|
||||
}
|
||||
}
|
||||
|
||||
@ -284,7 +284,7 @@ extension HomeTimelineViewController {
|
||||
// do nothing
|
||||
} completion: { _ in
|
||||
// fix AutoLayout cell height not update after rotate issue
|
||||
self.viewModel.cellFrameCache.removeAllObjects()
|
||||
self.viewModel?.cellFrameCache.removeAllObjects()
|
||||
self.tableView.reloadData()
|
||||
}
|
||||
}
|
||||
@ -356,7 +356,9 @@ extension HomeTimelineViewController {
|
||||
extension HomeTimelineViewController {
|
||||
|
||||
@objc private func findPeopleButtonPressed(_ sender: Any?) {
|
||||
let suggestionAccountViewModel = SuggestionAccountViewModel(context: context, authContext: viewModel.authContext)
|
||||
guard let authContext = viewModel?.authContext else { return }
|
||||
|
||||
let suggestionAccountViewModel = SuggestionAccountViewModel(context: context, authContext: authContext)
|
||||
suggestionAccountViewModel.delegate = viewModel
|
||||
_ = coordinator.present(
|
||||
scene: .suggestionAccount(viewModel: suggestionAccountViewModel),
|
||||
@ -366,7 +368,9 @@ extension HomeTimelineViewController {
|
||||
}
|
||||
|
||||
@objc private func manuallySearchButtonPressed(_ sender: UIButton) {
|
||||
let searchDetailViewModel = SearchDetailViewModel(authContext: viewModel.authContext)
|
||||
guard let authContext = viewModel?.authContext else { return }
|
||||
|
||||
let searchDetailViewModel = SearchDetailViewModel(authContext: authContext)
|
||||
_ = coordinator.present(scene: .searchDetail(viewModel: searchDetailViewModel), from: self, transition: .modal(animated: true, completion: nil))
|
||||
}
|
||||
|
||||
@ -377,16 +381,18 @@ extension HomeTimelineViewController {
|
||||
}
|
||||
|
||||
@objc private func refreshControlValueChanged(_ sender: RefreshControl) {
|
||||
guard viewModel.loadLatestStateMachine.enter(HomeTimelineViewModel.LoadLatestState.LoadingManually.self) else {
|
||||
guard let viewModel, viewModel.loadLatestStateMachine.enter(HomeTimelineViewModel.LoadLatestState.LoadingManually.self) else {
|
||||
sender.endRefreshing()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@objc func signOutAction(_ sender: UIAction) {
|
||||
guard let authContext = viewModel?.authContext else { return }
|
||||
|
||||
Task { @MainActor in
|
||||
try await context.authenticationService.signOutMastodonUser(authenticationBox: viewModel.authContext.mastodonAuthenticationBox)
|
||||
let userIdentifier = viewModel.authContext.mastodonAuthenticationBox
|
||||
try await context.authenticationService.signOutMastodonUser(authenticationBox: authContext.mastodonAuthenticationBox)
|
||||
let userIdentifier = authContext.mastodonAuthenticationBox
|
||||
FileManager.default.invalidateHomeTimelineCache(for: userIdentifier)
|
||||
FileManager.default.invalidateNotificationsAll(for: userIdentifier)
|
||||
FileManager.default.invalidateNotificationsMentions(for: userIdentifier)
|
||||
@ -400,7 +406,7 @@ extension HomeTimelineViewController {
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
switch scrollView {
|
||||
case tableView:
|
||||
viewModel.homeTimelineNavigationBarTitleViewModel.handleScrollViewDidScroll(scrollView)
|
||||
viewModel?.homeTimelineNavigationBarTitleViewModel.handleScrollViewDidScroll(scrollView)
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -411,7 +417,7 @@ extension HomeTimelineViewController {
|
||||
case tableView:
|
||||
|
||||
let indexPath = IndexPath(row: 0, section: 0)
|
||||
guard viewModel.diffableDataSource?.itemIdentifier(for: indexPath) != nil else {
|
||||
guard viewModel?.diffableDataSource?.itemIdentifier(for: indexPath) != nil else {
|
||||
return true
|
||||
}
|
||||
// save position
|
||||
@ -428,7 +434,7 @@ extension HomeTimelineViewController {
|
||||
private func savePositionBeforeScrollToTop() {
|
||||
// check save action interval
|
||||
// should not fast than 0.5s to prevent save when scrollToTop on-flying
|
||||
if let record = viewModel.scrollPositionRecord {
|
||||
if let record = viewModel?.scrollPositionRecord {
|
||||
let now = Date()
|
||||
guard now.timeIntervalSince(record.timestamp) > 0.5 else {
|
||||
// skip this save action
|
||||
@ -436,7 +442,7 @@ extension HomeTimelineViewController {
|
||||
}
|
||||
}
|
||||
|
||||
guard let diffableDataSource = viewModel.diffableDataSource else { return }
|
||||
guard let diffableDataSource = viewModel?.diffableDataSource else { return }
|
||||
guard let anchorIndexPaths = tableView.indexPathsForVisibleRows?.sorted() else { return }
|
||||
guard !anchorIndexPaths.isEmpty else { return }
|
||||
let anchorIndexPath = anchorIndexPaths[anchorIndexPaths.count / 2]
|
||||
@ -447,7 +453,7 @@ extension HomeTimelineViewController {
|
||||
let cellFrameInView = tableView.convert(anchorCell.frame, to: view)
|
||||
return cellFrameInView.origin.y
|
||||
}()
|
||||
viewModel.scrollPositionRecord = HomeTimelineViewModel.ScrollPositionRecord(
|
||||
viewModel?.scrollPositionRecord = HomeTimelineViewModel.ScrollPositionRecord(
|
||||
item: anchorItem,
|
||||
offset: offset,
|
||||
timestamp: Date()
|
||||
@ -462,19 +468,19 @@ extension HomeTimelineViewController {
|
||||
}
|
||||
|
||||
private func restorePositionWhenScrollToTop() {
|
||||
guard let diffableDataSource = self.viewModel.diffableDataSource else { return }
|
||||
guard let record = self.viewModel.scrollPositionRecord,
|
||||
guard let diffableDataSource = viewModel?.diffableDataSource else { return }
|
||||
guard let record = viewModel?.scrollPositionRecord,
|
||||
let indexPath = diffableDataSource.indexPath(for: record.item)
|
||||
else { return }
|
||||
|
||||
tableView.scrollToRow(at: indexPath, at: .middle, animated: true)
|
||||
viewModel.scrollPositionRecord = nil
|
||||
viewModel?.scrollPositionRecord = nil
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - AuthContextProvider
|
||||
extension HomeTimelineViewController: AuthContextProvider {
|
||||
var authContext: AuthContext { viewModel.authContext }
|
||||
var authContext: AuthContext { viewModel!.authContext }
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDelegate
|
||||
@ -507,7 +513,7 @@ extension HomeTimelineViewController: UITableViewDelegate, AutoGenerateTableView
|
||||
|
||||
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||||
if indexPath.row == tableView.numberOfRows(inSection: indexPath.section) - 1 {
|
||||
viewModel.timelineDidReachEnd()
|
||||
viewModel?.timelineDidReachEnd()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -515,12 +521,12 @@ extension HomeTimelineViewController: UITableViewDelegate, AutoGenerateTableView
|
||||
// MARK: - TimelineMiddleLoaderTableViewCellDelegate
|
||||
extension HomeTimelineViewController: TimelineMiddleLoaderTableViewCellDelegate {
|
||||
func timelineMiddleLoaderTableViewCell(_ cell: TimelineMiddleLoaderTableViewCell, loadMoreButtonDidPressed button: UIButton) {
|
||||
guard let diffableDataSource = viewModel.diffableDataSource else { return }
|
||||
guard let diffableDataSource = viewModel?.diffableDataSource else { return }
|
||||
guard let indexPath = tableView.indexPath(for: cell) else { return }
|
||||
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
|
||||
|
||||
Task {
|
||||
await viewModel.loadMore(item: item)
|
||||
await viewModel?.loadMore(item: item)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -531,6 +537,8 @@ extension HomeTimelineViewController: ScrollViewContainer {
|
||||
var scrollView: UIScrollView { return tableView }
|
||||
|
||||
func scrollToTop(animated: Bool) {
|
||||
guard let viewModel else { return }
|
||||
|
||||
if scrollView.contentOffset.y < scrollView.frame.height,
|
||||
viewModel.loadLatestStateMachine.canEnterState(HomeTimelineViewModel.LoadLatestState.Loading.self),
|
||||
(scrollView.contentOffset.y + scrollView.adjustedContentInset.top) == 0.0,
|
||||
@ -569,7 +577,7 @@ extension HomeTimelineViewController: HomeTimelineNavigationBarTitleViewDelegate
|
||||
func homeTimelineNavigationBarTitleView(_ titleView: HomeTimelineNavigationBarTitleView, buttonDidPressed sender: UIButton) {
|
||||
switch titleView.state {
|
||||
case .newPostButton:
|
||||
guard let diffableDataSource = viewModel.diffableDataSource else { return }
|
||||
guard let diffableDataSource = viewModel?.diffableDataSource else { return }
|
||||
let indexPath = IndexPath(row: 0, section: 0)
|
||||
guard diffableDataSource.itemIdentifier(for: indexPath) != nil else { return }
|
||||
|
||||
|
@ -21,7 +21,7 @@ final class NotificationViewController: TabmanViewController, NeedsDependency {
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
var observations = Set<NSKeyValueObservation>()
|
||||
|
||||
var viewModel: NotificationViewModel!
|
||||
var viewModel: NotificationViewModel?
|
||||
|
||||
let pageSegmentedControl = UISegmentedControl()
|
||||
|
||||
@ -38,7 +38,7 @@ final class NotificationViewController: TabmanViewController, NeedsDependency {
|
||||
animated: animated
|
||||
)
|
||||
|
||||
viewModel.currentPageIndex = index
|
||||
viewModel?.currentPageIndex = index
|
||||
}
|
||||
|
||||
}
|
||||
@ -49,7 +49,7 @@ extension NotificationViewController {
|
||||
|
||||
view.backgroundColor = .secondarySystemBackground
|
||||
|
||||
setupSegmentedControl(scopes: viewModel.scopes)
|
||||
setupSegmentedControl(scopes: APIService.MastodonNotificationScope.allCases)
|
||||
pageSegmentedControl.translatesAutoresizingMaskIntoConstraints = false
|
||||
navigationItem.titleView = pageSegmentedControl
|
||||
NSLayoutConstraint.activate([
|
||||
@ -58,7 +58,7 @@ extension NotificationViewController {
|
||||
pageSegmentedControl.addTarget(self, action: #selector(NotificationViewController.pageSegmentedControlValueChanged(_:)), for: .valueChanged)
|
||||
|
||||
dataSource = viewModel
|
||||
viewModel.$viewControllers
|
||||
viewModel?.$viewControllers
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] viewControllers in
|
||||
guard let self = self else { return }
|
||||
@ -68,11 +68,11 @@ extension NotificationViewController {
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
viewModel.viewControllers = viewModel.scopes.map { scope in
|
||||
viewModel?.viewControllers = APIService.MastodonNotificationScope.allCases.map { scope in
|
||||
createViewController(for: scope)
|
||||
}
|
||||
|
||||
viewModel.$currentPageIndex
|
||||
viewModel?.$currentPageIndex
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] currentPageIndex in
|
||||
guard let self = self else { return }
|
||||
@ -127,7 +127,7 @@ extension NotificationViewController {
|
||||
}
|
||||
|
||||
// set initial selection
|
||||
guard !pageSegmentedControl.isSelected else { return }
|
||||
guard let viewModel, !pageSegmentedControl.isSelected else { return }
|
||||
if viewModel.currentPageIndex < pageSegmentedControl.numberOfSegments {
|
||||
pageSegmentedControl.selectedSegmentIndex = viewModel.currentPageIndex
|
||||
} else {
|
||||
@ -136,12 +136,13 @@ extension NotificationViewController {
|
||||
}
|
||||
|
||||
private func createViewController(for scope: NotificationTimelineViewModel.Scope) -> UIViewController {
|
||||
guard let authContext = viewModel?.authContext else { return UITableViewController() }
|
||||
let viewController = NotificationTimelineViewController()
|
||||
viewController.context = context
|
||||
viewController.coordinator = coordinator
|
||||
viewController.viewModel = NotificationTimelineViewModel(
|
||||
context: context,
|
||||
authContext: viewModel.authContext,
|
||||
authContext: authContext,
|
||||
scope: scope
|
||||
)
|
||||
return viewController
|
||||
|
@ -22,7 +22,6 @@ final class NotificationViewModel {
|
||||
let viewDidLoad = PassthroughSubject<Void, Never>()
|
||||
|
||||
// output
|
||||
let scopes = NotificationTimelineViewModel.Scope.allCases
|
||||
@Published var viewControllers: [UIViewController] = []
|
||||
@Published var currentPageIndex = 0 {
|
||||
didSet {
|
||||
|
@ -39,9 +39,9 @@ final class ContentSplitViewController: UIViewController, NeedsDependency {
|
||||
|
||||
@Published var currentSupplementaryTab: Tab = .home
|
||||
private(set) lazy var mainTabBarController: MainTabBarController = {
|
||||
let mainTabBarController = MainTabBarController(context: context, coordinator: coordinator, authContext: authContext)
|
||||
let mainTabBarController = MainTabBarController(context: self.context, coordinator: self.coordinator, authContext: self.authContext)
|
||||
if let homeTimelineViewController = mainTabBarController.viewController(of: HomeTimelineViewController.self) {
|
||||
homeTimelineViewController.viewModel.displaySettingBarButtonItem = false
|
||||
homeTimelineViewController.viewModel?.displaySettingBarButtonItem = false
|
||||
}
|
||||
return mainTabBarController
|
||||
}()
|
||||
|
@ -26,7 +26,7 @@ final class SearchViewController: UIViewController, NeedsDependency {
|
||||
var searchTransitionController = SearchTransitionController()
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
var viewModel: SearchViewModel!
|
||||
var viewModel: SearchViewModel?
|
||||
|
||||
// use AutoLayout could set search bar margin automatically to
|
||||
// layout alongside with split mode button (on iPad)
|
||||
@ -37,7 +37,7 @@ final class SearchViewController: UIViewController, NeedsDependency {
|
||||
let searchBarTapPublisher = PassthroughSubject<String, Never>()
|
||||
|
||||
private(set) lazy var discoveryViewController: DiscoveryViewController? = {
|
||||
guard let authContext = viewModel.authContext else { return nil }
|
||||
guard let authContext = viewModel?.authContext else { return nil }
|
||||
let viewController = DiscoveryViewController()
|
||||
viewController.context = context
|
||||
viewController.coordinator = coordinator
|
||||
@ -70,7 +70,7 @@ extension SearchViewController {
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
viewModel.viewDidAppeared.send()
|
||||
viewModel?.viewDidAppeared.send()
|
||||
|
||||
// note:
|
||||
// need set alpha because (maybe) SDK forget set alpha back
|
||||
@ -110,7 +110,7 @@ extension SearchViewController {
|
||||
.sink { [weak self] initialText in
|
||||
guard let self = self else { return }
|
||||
// push to search detail
|
||||
guard let authContext = self.viewModel.authContext else { return }
|
||||
guard let authContext = self.viewModel?.authContext else { return }
|
||||
let searchDetailViewModel = SearchDetailViewModel(authContext: authContext, initialSearchText: initialText)
|
||||
searchDetailViewModel.needsBecomeFirstResponder = true
|
||||
self.navigationController?.delegate = self.searchTransitionController
|
||||
|
Loading…
x
Reference in New Issue
Block a user