Use the Emoji/Map/Poll view models. (#4458)

* Toggle emojis in the EmojiPickerScreenViewModel.

* Send locations in the StaticLocationScreen.

* Send polls in the PollFormScreen.
This commit is contained in:
Doug
2025-09-03 16:52:28 +01:00
committed by GitHub
parent 441f5ae975
commit eda37eb626
21 changed files with 398 additions and 255 deletions
@@ -9,13 +9,13 @@ import Combine
import SwiftUI
struct EmojiPickerScreenCoordinatorParameters {
let emojiProvider: EmojiProviderProtocol
let itemID: TimelineItemIdentifier
let selectedEmojis: Set<String>
let emojiProvider: EmojiProviderProtocol
let timelineController: TimelineControllerProtocol
}
enum EmojiPickerScreenCoordinatorAction {
case emojiSelected(emoji: String, itemID: TimelineItemIdentifier)
case dismiss
}
@@ -33,7 +33,10 @@ final class EmojiPickerScreenCoordinator: CoordinatorProtocol {
init(parameters: EmojiPickerScreenCoordinatorParameters) {
self.parameters = parameters
viewModel = EmojiPickerScreenViewModel(emojiProvider: parameters.emojiProvider)
viewModel = EmojiPickerScreenViewModel(itemID: parameters.itemID,
selectedEmojis: parameters.selectedEmojis,
emojiProvider: parameters.emojiProvider,
timelineController: parameters.timelineController)
}
func start() {
@@ -42,8 +45,6 @@ final class EmojiPickerScreenCoordinator: CoordinatorProtocol {
guard let self else { return }
switch action {
case let .emojiSelected(emoji: emoji):
actionsSubject.send(.emojiSelected(emoji: emoji, itemID: self.parameters.itemID))
case .dismiss:
actionsSubject.send(.dismiss)
}
@@ -52,6 +53,6 @@ final class EmojiPickerScreenCoordinator: CoordinatorProtocol {
}
func toPresentable() -> AnyView {
AnyView(EmojiPickerScreen(context: viewModel.context, selectedEmojis: parameters.selectedEmojis))
AnyView(EmojiPickerScreen(context: viewModel.context))
}
}
@@ -8,12 +8,12 @@
import Foundation
enum EmojiPickerScreenViewModelAction {
case emojiSelected(emoji: String)
case dismiss
}
struct EmojiPickerScreenViewState: BindableState {
var categories: [EmojiPickerEmojiCategoryViewData]
var selectedEmojis: Set<String>
}
enum EmojiPickerScreenViewAction {
@@ -11,17 +11,20 @@ import SwiftUI
typealias EmojiPickerScreenViewModelType = StateStoreViewModelV2<EmojiPickerScreenViewState, EmojiPickerScreenViewAction>
class EmojiPickerScreenViewModel: EmojiPickerScreenViewModelType, EmojiPickerScreenViewModelProtocol {
private var actionsSubject: PassthroughSubject<EmojiPickerScreenViewModelAction, Never> = .init()
private let itemID: TimelineItemIdentifier
private let emojiProvider: EmojiProviderProtocol
private let timelineController: TimelineControllerProtocol
private var actionsSubject: PassthroughSubject<EmojiPickerScreenViewModelAction, Never> = .init()
var actions: AnyPublisher<EmojiPickerScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
private let emojiProvider: EmojiProviderProtocol
init(emojiProvider: EmojiProviderProtocol) {
let initialViewState = EmojiPickerScreenViewState(categories: [])
init(itemID: TimelineItemIdentifier, selectedEmojis: Set<String>, emojiProvider: EmojiProviderProtocol, timelineController: TimelineControllerProtocol) {
let initialViewState = EmojiPickerScreenViewState(categories: [], selectedEmojis: selectedEmojis)
self.itemID = itemID
self.emojiProvider = emojiProvider
self.timelineController = timelineController
super.init(initialViewState: initialViewState)
loadEmojis()
}
@@ -36,8 +39,7 @@ class EmojiPickerScreenViewModel: EmojiPickerScreenViewModelType, EmojiPickerScr
state.categories = convert(emojiCategories: categories)
}
case let .emojiTapped(emoji: emoji):
emojiProvider.markEmojiAsFrequentlyUsed(emoji.value)
actionsSubject.send(.emojiSelected(emoji: emoji.value))
Task { await selectEmoji(emoji) }
case .dismiss:
actionsSubject.send(.dismiss)
}
@@ -62,4 +64,17 @@ class EmojiPickerScreenViewModel: EmojiPickerScreenViewModelType, EmojiPickerScr
return EmojiPickerEmojiCategoryViewData(id: emojiCategory.id, emojis: emojisViewData)
}
}
private func selectEmoji(_ emoji: EmojiPickerEmojiViewData) async {
MXLog.debug("Selected \(emoji) for \(itemID)")
emojiProvider.markEmojiAsFrequentlyUsed(emoji.value)
guard case let .event(_, eventOrTransactionID) = itemID else { fatalError("Attempted to react to a virtual item.") }
// There aren't any local echoes when the toggle redacts, so dismiss the screen early
// until we have them: https://github.com/matrix-org/matrix-rust-sdk/issues/4162
actionsSubject.send(.dismiss)
await timelineController.toggleReaction(emoji.value, to: eventOrTransactionID)
}
}
@@ -11,7 +11,6 @@ import SwiftUI
struct EmojiPickerScreen: View {
let context: EmojiPickerScreenViewModel.Context
var selectedEmojis = Set<String>()
@State var searchString = ""
@State private var isSearching = false
@@ -64,7 +63,7 @@ struct EmojiPickerScreen: View {
}
private func accessibilityLabel(for emoji: String) -> String {
if selectedEmojis.contains(emoji) {
if context.viewState.selectedEmojis.contains(emoji) {
return L10n.a11yRemoveReaction(emoji)
} else {
return L10n.a11yAddReaction(emoji)
@@ -72,7 +71,7 @@ struct EmojiPickerScreen: View {
}
private func emojiBackgroundColor(for emoji: String) -> Color {
if selectedEmojis.contains(emoji) {
if context.viewState.selectedEmojis.contains(emoji) {
return .compound.bgActionPrimaryRest
} else {
return .clear
@@ -101,22 +100,28 @@ struct EmojiPickerScreen: View {
// MARK: - Previews
struct EmojiPickerScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = EmojiPickerScreenViewModel(emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings))
static let viewModel = EmojiPickerScreenViewModel(itemID: .randomEvent,
selectedEmojis: ["😀", "😄"],
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
timelineController: MockTimelineController())
static var previews: some View {
EmojiPickerScreen(context: viewModel.context, selectedEmojis: ["😀", "😄"])
EmojiPickerScreen(context: viewModel.context)
.previewDisplayName("Screen")
.snapshotPreferences(expect: viewModel.context.observe(\.viewState.categories).map { !$0.isEmpty }.eraseToStream())
}
}
struct EmojiPickerScreenSheet_Previews: PreviewProvider {
static let viewModel = EmojiPickerScreenViewModel(emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings))
static let viewModel = EmojiPickerScreenViewModel(itemID: .randomEvent,
selectedEmojis: ["😀", "😄"],
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
timelineController: MockTimelineController())
static var previews: some View {
Text("Timeline view")
.sheet(isPresented: .constant(true)) {
EmojiPickerScreen(context: viewModel.context, selectedEmojis: ["😀", "😄"])
EmojiPickerScreen(context: viewModel.context)
}
.previewDisplayName("Sheet")
}