Files
letro-ios/ElementX/Sources/Screens/RoomSelectionScreen/View/RoomSelectionScreen.swift
Stefan Ceriu 07cf873484 Share extension (#3506)
* Setup simple share extension

* Switch the app url scheme to be the full bundle identifier

* Setup a share extension that show a SwiftUI view, uses rust tracing and redirects to the hosting aplication

* Move media as json through the custom scheme into the main app and deep link into the media upload preview screen

* Fix message forwarding and global search screen room summary provider filtering.

* Tweak the message forwarding and global search screen designs.

* Add a room selection screen to use after receiving a share request from the share extension

* Fix share extension entitlements

* Share the temporary directory between the main app and the extensions; rename the caches one.

* Remove the no longer needed notification avatar flipping fix.

* Extract the placeholder avatar image generator from the NSE

* Nest `AvatarSize` within the new `Avatars` enum

* Donate an `INSendMessageIntent` to the system every time we send a message so they appear as share suggestions

* Support suggestions in the share extension itself

* Improve sharing animations and fix presentation when room already on the stack

* Clear all routes when sharing without a preselected room.

* Fix broken unit tests

* Various initial tweaks following code review.

* Correctly clean up and dismiss the share extension for all paths.

* Move the share extension path to a constants enum

* Rename UserSessionFlowCoordinator specific share extension states and events

* Add UserSession and Room flow coordinator share route tests

* Tweak the share extension logic.
2024-11-13 14:02:47 +02:00

105 lines
3.7 KiB
Swift

//
// Copyright 2022-2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//
import Compound
import SwiftUI
struct RoomSelectionScreen: View {
@ObservedObject var context: RoomSelectionScreenViewModel.Context
var body: some View {
Form {
Section {
ForEach(context.viewState.rooms) { room in
RoomSelectionListRow(room: room,
isSelected: context.viewState.selectedRoomID == room.id,
context: context)
}
// Replace these with ScrollView's `scrollPosition` when dropping iOS 16.
} header: {
emptyRectangle
.onAppear {
context.send(viewAction: .reachedTop)
}
} footer: {
emptyRectangle
.onAppear {
context.send(viewAction: .reachedBottom)
}
}
}
.compoundList()
.navigationTitle(L10n.screenRoomlistMainSpaceTitle)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button(L10n.actionCancel) {
context.send(viewAction: .cancel)
}
}
ToolbarItem(placement: .confirmationAction) {
Button(L10n.actionShare) {
context.send(viewAction: .confirm)
}
.disabled(context.viewState.selectedRoomID == nil)
}
}
.searchController(query: $context.searchQuery, showsCancelButton: false)
.compoundSearchField()
.disableAutocorrection(true)
}
/// The greedy size of Rectangle can create an issue with the navigation bar when the search is highlighted, so is best to use a fixed frame instead of hidden() or EmptyView()
private var emptyRectangle: some View {
Rectangle()
.frame(width: 0, height: 0)
}
}
private struct RoomSelectionListRow: View {
@Environment(\.dynamicTypeSize) var dynamicTypeSize
let room: RoomSelectionRoom
let isSelected: Bool
let context: RoomSelectionScreenViewModel.Context
var body: some View {
ListRow(label: .avatar(title: room.title,
description: room.description,
icon: avatar),
kind: .selection(isSelected: isSelected) {
context.send(viewAction: .selectRoom(roomID: room.id))
})
}
@ViewBuilder @MainActor
var avatar: some View {
if dynamicTypeSize < .accessibility3 {
RoomAvatarImage(avatar: room.avatar,
avatarSize: .room(on: .roomSelection),
mediaProvider: context.mediaProvider)
.dynamicTypeSize(dynamicTypeSize < .accessibility1 ? dynamicTypeSize : .accessibility1)
.accessibilityHidden(true)
}
}
}
// MARK: - Previews
struct RoomSelectionScreen_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
let summaryProvider = RoomSummaryProviderMock(.init(state: .loaded(.mockRooms)))
let viewModel = RoomSelectionScreenViewModel(clientProxy: ClientProxyMock(.init()),
roomSummaryProvider: summaryProvider,
mediaProvider: MediaProviderMock(configuration: .init()))
NavigationStack {
RoomSelectionScreen(context: viewModel.context)
}
}
}