Allow the mac app to chose room avatars directly from files instead of the photo library

This commit is contained in:
Stefan Ceriu
2026-04-25 11:46:40 +03:00
parent 0ec114d2f3
commit 53a54c1b90
20 changed files with 56 additions and 8 deletions
@@ -63,6 +63,7 @@
"action_call" = "Call";
"action_cancel" = "Cancel";
"action_cancel_for_now" = "Cancel for now";
"action_choose_file" = "Choose file";
"action_choose_photo" = "Choose photo";
"action_clear" = "Clear";
"action_close" = "Close";
@@ -63,6 +63,7 @@
"action_call" = "Call";
"action_cancel" = "Cancel";
"action_cancel_for_now" = "Cancel for now";
"action_choose_file" = "Choose file";
"action_choose_photo" = "Choose photo";
"action_clear" = "Clear";
"action_close" = "Close";
+2
View File
@@ -160,6 +160,8 @@ internal enum L10n {
internal static var actionCancel: String { return L10n.tr("Localizable", "action_cancel") }
/// Cancel for now
internal static var actionCancelForNow: String { return L10n.tr("Localizable", "action_cancel_for_now") }
/// Choose file
internal static var actionChooseFile: String { return L10n.tr("Localizable", "action_choose_file") }
/// Choose photo
internal static var actionChoosePhoto: String { return L10n.tr("Localizable", "action_choose_photo") }
/// Clear
@@ -281,6 +281,7 @@ enum A11yIdentifiers {
let roomName = "create_room-room_name"
let roomTopic = "create_room-room_topic"
let mediaPicker = "create_room-media_picker"
let filePicker = "create_room-file_picker"
}
struct PollFormScreen {
@@ -54,6 +54,8 @@ final class CreateRoomScreenCoordinator: CoordinatorProtocol {
actionsSubject.send(.displayMediaPickerWithMode(.init(source: .camera, selectionType: .single)))
case .displayMediaPicker:
actionsSubject.send(.displayMediaPickerWithMode(.init(source: .photoLibrary, selectionType: .single)))
case .displayFilePicker:
actionsSubject.send(.displayMediaPickerWithMode(.init(source: .documents(types: [.image]), selectionType: .single)))
case .dismiss:
actionsSubject.send(.dismiss)
}
@@ -21,6 +21,7 @@ enum CreateRoomScreenViewModelAction {
case createdRoom(JoinedRoomProxyProtocol, SpaceRoomListProxyProtocol?)
case displayMediaPicker
case displayCameraPicker
case displayFilePicker
case dismiss
}
@@ -114,6 +115,7 @@ enum CreateRoomScreenViewAction {
case createRoom
case displayCameraPicker
case displayMediaPicker
case displayFilePicker
case removeImage
case updateRoomName(String)
case updateAliasLocalPart(String)
@@ -90,6 +90,8 @@ class CreateRoomScreenViewModel: CreateRoomScreenViewModelType, CreateRoomScreen
actionsSubject.send(.displayCameraPicker)
case .displayMediaPicker:
actionsSubject.send(.displayMediaPicker)
case .displayFilePicker:
actionsSubject.send(.displayFilePicker)
case .removeImage:
state.avatarMediaInfo = nil
case .updateAliasLocalPart(let aliasLocalPart):
@@ -139,11 +139,17 @@ struct CreateRoomScreen: View {
Button(L10n.actionTakePhoto) {
context.send(viewAction: .displayCameraPicker)
}
Button(L10n.actionChoosePhoto) {
context.send(viewAction: .displayMediaPicker)
}
.accessibilityIdentifier(A11yIdentifiers.createRoomScreen.mediaPicker)
Button(L10n.actionChooseFile) {
context.send(viewAction: .displayFilePicker)
}
.accessibilityIdentifier(A11yIdentifiers.createRoomScreen.filePicker)
if context.viewState.avatarImage != nil {
Button(L10n.actionRemove, role: .destructive) {
context.send(viewAction: .removeImage)
@@ -7,6 +7,7 @@
//
import SwiftUI
import UniformTypeIdentifiers
enum DocumentPickerAction {
case selectedMediaAtURLs([URL])
@@ -16,19 +17,22 @@ enum DocumentPickerAction {
struct DocumentPicker: UIViewControllerRepresentable {
private let selectionType: MediaPickerScreenSelectionType
private let contentTypes: [UTType]
private let userIndicatorController: UserIndicatorControllerProtocol
private let callback: (DocumentPickerAction) -> Void
init(selectionType: MediaPickerScreenSelectionType,
contentTypes: [UTType],
userIndicatorController: UserIndicatorControllerProtocol,
callback: @escaping (DocumentPickerAction) -> Void) {
self.selectionType = selectionType
self.contentTypes = contentTypes
self.userIndicatorController = userIndicatorController
self.callback = callback
}
func makeUIViewController(context: Context) -> UIDocumentPickerViewController {
let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data])
let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: contentTypes)
documentPicker.delegate = context.coordinator
documentPicker.allowsMultipleSelection = switch selectionType {
@@ -7,16 +7,17 @@
//
import SwiftUI
import UniformTypeIdentifiers
struct MediaPickerScreenMode: Hashable {
let source: MediaPickerScreenSource
let selectionType: MediaPickerScreenSelectionType
}
enum MediaPickerScreenSource {
enum MediaPickerScreenSource: Hashable {
case camera
case photoLibrary
case documents
case documents(types: [UTType] = [.data])
}
enum MediaPickerScreenSelectionType {
@@ -79,10 +80,12 @@ class MediaPickerScreenCoordinator: CoordinatorProtocol {
self?.callback(.selectedMediaAtURLs(urls))
}
}
case .documents:
case .documents(let types):
// The document picker automatically dismisses everything on selection
// Strongly retain self in the callback to forward actions correctly
DocumentPicker(selectionType: mode.selectionType, userIndicatorController: userIndicatorController) { action in
DocumentPicker(selectionType: mode.selectionType,
contentTypes: types,
userIndicatorController: userIndicatorController) { action in
switch action {
case .cancel:
self.callback(.cancel)
@@ -52,6 +52,8 @@ final class RoomDetailsEditScreenCoordinator: CoordinatorProtocol {
self?.displayMediaPickerWithMode(.init(source: .camera, selectionType: .single))
case .displayMediaPicker:
self?.displayMediaPickerWithMode(.init(source: .photoLibrary, selectionType: .single))
case .displayFilePicker:
self?.displayMediaPickerWithMode(.init(source: .documents(types: [.image]), selectionType: .single))
}
}
.store(in: &cancellables)
@@ -13,6 +13,7 @@ enum RoomDetailsEditScreenViewModelAction {
case saveFinished
case displayCameraPicker
case displayMediaPicker
case displayFilePicker
}
struct RoomDetailsEditScreenViewState: BindableState {
@@ -81,5 +82,6 @@ enum RoomDetailsEditScreenViewAction {
case presentMediaSource
case displayCameraPicker
case displayMediaPicker
case displayFilePicker
case removeImage
}
@@ -69,6 +69,8 @@ class RoomDetailsEditScreenViewModel: RoomDetailsEditScreenViewModelType, RoomDe
actionsSubject.send(.displayCameraPicker)
case .displayMediaPicker:
actionsSubject.send(.displayMediaPicker)
case .displayFilePicker:
actionsSubject.send(.displayFilePicker)
case .removeImage:
state.avatarURL = nil
state.localMedia = nil
@@ -119,11 +119,19 @@ struct RoomDetailsEditScreen: View {
} label: {
Text(L10n.actionTakePhoto)
}
Button {
context.send(viewAction: .displayMediaPicker)
} label: {
Text(L10n.actionChoosePhoto)
}
Button {
context.send(viewAction: .displayFilePicker)
} label: {
Text(L10n.actionChooseFile)
}
if context.viewState.showDeleteImageAction {
Button(role: .destructive) {
context.send(viewAction: .removeImage)
@@ -126,7 +126,7 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
case .displayMediaPicker:
actionsSubject.send(.presentMediaUploadPicker(mode: .init(source: .photoLibrary, selectionType: .multiple)))
case .displayDocumentPicker:
actionsSubject.send(.presentMediaUploadPicker(mode: .init(source: .documents, selectionType: .multiple)))
actionsSubject.send(.presentMediaUploadPicker(mode: .init(source: .documents(), selectionType: .multiple)))
case .displayMediaPreview(let mediaPreviewViewModel):
roomViewModel.displayMediaPreview(mediaPreviewViewModel)
case .displayLocationPicker:
@@ -53,7 +53,7 @@ final class UserDetailsEditScreenCoordinator: CoordinatorProtocol {
case .displayMediaPicker:
displayMediaPickerWithMode(.init(source: .photoLibrary, selectionType: .single))
case .displayFilePicker:
displayMediaPickerWithMode(.init(source: .documents, selectionType: .single))
displayMediaPickerWithMode(.init(source: .documents(types: [.image]), selectionType: .single))
}
}
.store(in: &cancellables)
@@ -67,5 +67,6 @@ enum UserDetailsEditScreenViewAction {
case presentMediaSource
case displayCameraPicker
case displayMediaPicker
case displayFilePicker
case removeImage
}
@@ -77,6 +77,8 @@ class UserDetailsEditScreenViewModel: UserDetailsEditScreenViewModelType, UserDe
actionsSubject.send(.displayCameraPicker)
case .displayMediaPicker:
actionsSubject.send(.displayMediaPicker)
case .displayFilePicker:
actionsSubject.send(.displayFilePicker)
case .removeImage:
state.localMedia = nil
state.selectedAvatarURL = nil
@@ -96,12 +96,19 @@ struct UserDetailsEditScreen: View {
} label: {
Text(L10n.actionTakePhoto)
}
Button {
context.send(viewAction: .displayMediaPicker)
} label: {
Text(L10n.actionChoosePhoto)
}
Button {
context.send(viewAction: .displayFilePicker)
} label: {
Text(L10n.actionChooseFile)
}
if context.viewState.showDeleteImageAction {
Button(role: .destructive) {
context.send(viewAction: .removeImage)
@@ -114,7 +114,7 @@ final class ThreadTimelineScreenCoordinator: CoordinatorProtocol {
case .displayMediaPicker:
actionsSubject.send(.presentMediaUploadPicker(mode: .init(source: .photoLibrary, selectionType: .multiple)))
case .displayDocumentPicker:
actionsSubject.send(.presentMediaUploadPicker(mode: .init(source: .documents, selectionType: .multiple)))
actionsSubject.send(.presentMediaUploadPicker(mode: .init(source: .documents(), selectionType: .multiple)))
case .displayMediaPreview(let mediaPreviewViewModel):
viewModel.displayMediaPreview(mediaPreviewViewModel)
case .displayLocationPicker: