Room's details edit screen (#956)

* Add RoomDetailsEditScreen template

* Add navigation to the edit screen

* Delete template code

* Start RoomDetailsEditScreen UI

* Manage power levels in RoomDetailsScreen

* Start RoomDetailsEditScreenViewModel logic

* Inject initial room state

* Add cancel action

* Expose set name/topic APIs in RoomProxy

* Refine RoomDetailsEditScreen UI

* Add save logic

* Add localisations

* Fix avatar image

* Update localisations

* Add “Add topic” button

* Add feature flag

* Add dismiss on save logic

* Reduce throttling

* Improve form logic

* Fix UT build errors

* Add media sheet

* Add media preprocessing

* Add LoadableEditableAvatarImage

* Add condition on delete image

* Add avatar save button logic

* Add remove avatar logic

* Cleanup

* Fix edit bug in DM

* Add upload avatar

* Add focus

* Add RoomDetailsViewModel UTs

* Fix button style

* Add UTs

* Add empty topic ui test

* Fix iPad sheet presentation

* Revert topic appearance in room’s details

* Address PR comments

* Add UI tests
This commit is contained in:
Alfonso Grillo
2023-05-26 15:47:12 +02:00
committed by GitHub
parent 524997438f
commit f2b7faa183
47 changed files with 1281 additions and 54 deletions
@@ -0,0 +1,176 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import SwiftUI
struct RoomDetailsEditScreen: View {
@ObservedObject var context: RoomDetailsEditScreenViewModel.Context
@FocusState private var focus: Focus?
private enum Focus {
case name
case topic
}
var body: some View {
mainContent
.scrollContentBackground(.hidden)
.background(Color.element.formBackground.ignoresSafeArea())
.scrollDismissesKeyboard(.immediately)
.navigationTitle(L10n.screenRoomDetailsEditRoomTitle)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button(L10n.actionCancel) {
context.send(viewAction: .cancel)
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button(L10n.actionSave) {
context.send(viewAction: .save)
focus = nil
}
.disabled(!context.viewState.canSave)
}
}
}
// MARK: - Private
private var mainContent: some View {
Form {
avatar
nameSection
topicSection
}
}
private var avatar: some View {
Button {
context.send(viewAction: .presentMediaSource)
} label: {
OverridableAvatarImage(overrideURL: context.viewState.localMedia?.thumbnailURL,
url: context.viewState.avatarURL,
name: context.viewState.initialName,
contentID: context.viewState.roomID,
avatarSize: .user(on: .memberDetails),
imageProvider: context.imageProvider)
.overlay(alignment: .bottomTrailing) {
if context.viewState.canEditAvatar {
avatarOverlayIcon
}
}
.confirmationDialog("", isPresented: $context.showMediaSheet) {
mediaActionSheet
}
}
.buttonStyle(.plain)
.disabled(!context.viewState.canEditAvatar)
.frame(maxWidth: .infinity, alignment: .center)
.listRowBackground(Color.clear)
}
private var nameSection: some View {
Section {
let canEditName = context.viewState.canEditName
TextField(L10n.commonRoomName,
text: $context.name,
prompt: canEditName ? Text(L10n.screenCreateRoomRoomNamePlaceholder) : nil,
axis: .horizontal)
.focused($focus, equals: .name)
.font(.compound.bodyLG)
.foregroundColor(.element.primaryContent)
.disabled(!canEditName)
.listRowBackground(canEditName ? nil : Color.clear)
.clipShape(RoundedRectangle(cornerRadius: 8))
} header: {
Text(L10n.commonRoomName)
.formSectionHeader()
}
.formSectionStyle()
}
private var topicSection: some View {
Section {
let canEditTopic = context.viewState.canEditTopic
TextField(L10n.commonTopic,
text: $context.topic,
prompt: canEditTopic ? Text(L10n.screenCreateRoomTopicPlaceholder) : nil,
axis: .vertical)
.focused($focus, equals: .topic)
.font(.compound.bodyLG)
.foregroundColor(.element.primaryContent)
.disabled(!canEditTopic)
.listRowBackground(canEditTopic ? nil : Color.clear)
.lineLimit(3, reservesSpace: true)
} header: {
Text(L10n.commonTopic)
.formSectionHeader()
}
.formSectionStyle()
}
private var avatarOverlayIcon: some View {
Image(systemName: "camera")
.padding(2)
.imageScale(.small)
.foregroundColor(.white)
.background {
Circle()
.foregroundColor(.black)
.aspectRatio(1, contentMode: .fill)
}
}
@ViewBuilder
private var mediaActionSheet: some View {
Button {
context.send(viewAction: .displayCameraPicker)
} label: {
Text(L10n.actionTakePhoto)
}
Button {
context.send(viewAction: .displayMediaPicker)
} label: {
Text(L10n.actionChoosePhoto)
}
if context.viewState.showDeleteImageAction {
Button(role: .destructive) {
context.send(viewAction: .removeImage)
} label: {
Text(L10n.actionRemove)
}
}
}
}
// MARK: - Previews
struct RoomDetailsEditScreen_Previews: PreviewProvider {
static let viewModel = RoomDetailsEditScreenViewModel(accountOwner: RoomMemberProxyMock.mockAlice,
mediaProvider: MockMediaProvider(),
roomProxy: RoomProxyMock(with: .init(name: "Room", displayName: "Room")),
userIndicatorController: MockUserIndicatorController())
static var previews: some View {
NavigationStack {
RoomDetailsEditScreen(context: viewModel.context)
}
}
}