mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-06 04:18:31 +00:00
Home screen - User options menu (#185)
* Add user options menu properties to home screen * Implement home screen new callbacks * Add user menu button on home screen * Add changelog * Fix unit tests * Fix user menu button layout, make menu sectioned * Remove user display name from home screen classes
This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
02D8DF8EB7537EB4E9019DDB /* EventBasedTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218AB05B4E3889731959C5F1 /* EventBasedTimelineItemProtocol.swift */; };
|
02D8DF8EB7537EB4E9019DDB /* EventBasedTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218AB05B4E3889731959C5F1 /* EventBasedTimelineItemProtocol.swift */; };
|
||||||
03B8FEA668A5B76A93113BB1 /* MemberDetailProviderManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C2ABC1A9B62BDB3D216E7FD /* MemberDetailProviderManager.swift */; };
|
03B8FEA668A5B76A93113BB1 /* MemberDetailProviderManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C2ABC1A9B62BDB3D216E7FD /* MemberDetailProviderManager.swift */; };
|
||||||
03CB204C52F18E24A5C3D219 /* UITestsAppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967873B9E11828B67F64C89A /* UITestsAppCoordinator.swift */; };
|
03CB204C52F18E24A5C3D219 /* UITestsAppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967873B9E11828B67F64C89A /* UITestsAppCoordinator.swift */; };
|
||||||
|
03D684A3AE85A23B3DA3B43F /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = E26747B3154A5DBC3A7E24A5 /* Image.swift */; };
|
||||||
04A16B45228F7678A027C079 /* RoomHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 422724361B6555364C43281E /* RoomHeaderView.swift */; };
|
04A16B45228F7678A027C079 /* RoomHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 422724361B6555364C43281E /* RoomHeaderView.swift */; };
|
||||||
05776B005C57E92582F0CF08 /* BuildSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F87116470221880017CF522 /* BuildSettings.swift */; };
|
05776B005C57E92582F0CF08 /* BuildSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F87116470221880017CF522 /* BuildSettings.swift */; };
|
||||||
059173B3C77056C406906B6D /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = D4DA544B2520BFA65D6DB4BB /* target.yml */; };
|
059173B3C77056C406906B6D /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = D4DA544B2520BFA65D6DB4BB /* target.yml */; };
|
||||||
@@ -757,6 +758,7 @@
|
|||||||
E0FCA0957FAA0E15A9F5579D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Untranslated.stringsdict; sourceTree = "<group>"; };
|
E0FCA0957FAA0E15A9F5579D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Untranslated.stringsdict; sourceTree = "<group>"; };
|
||||||
E157152B11E347F735C3FD6E /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = tr; path = tr.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
E157152B11E347F735C3FD6E /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = tr; path = tr.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
E18CF12478983A5EB390FB26 /* MessageComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageComposer.swift; sourceTree = "<group>"; };
|
E18CF12478983A5EB390FB26 /* MessageComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageComposer.swift; sourceTree = "<group>"; };
|
||||||
|
E26747B3154A5DBC3A7E24A5 /* Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = "<group>"; };
|
||||||
E3B97591B2D3D4D67553506D /* AnalyticsClientProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsClientProtocol.swift; sourceTree = "<group>"; };
|
E3B97591B2D3D4D67553506D /* AnalyticsClientProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsClientProtocol.swift; sourceTree = "<group>"; };
|
||||||
E3E29F98CF0E960689A410E3 /* SettingsUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsUITests.swift; sourceTree = "<group>"; };
|
E3E29F98CF0E960689A410E3 /* SettingsUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsUITests.swift; sourceTree = "<group>"; };
|
||||||
E45C57120F28F8D619150219 /* sr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sr; path = sr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
E45C57120F28F8D619150219 /* sr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sr; path = sr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
@@ -1074,6 +1076,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
B6E89E530A8E92EC44301CA1 /* Bundle.swift */,
|
B6E89E530A8E92EC44301CA1 /* Bundle.swift */,
|
||||||
|
E26747B3154A5DBC3A7E24A5 /* Image.swift */,
|
||||||
40B21E611DADDEF00307E7AC /* String.swift */,
|
40B21E611DADDEF00307E7AC /* String.swift */,
|
||||||
227AC5D71A4CE43512062243 /* URL.swift */,
|
227AC5D71A4CE43512062243 /* URL.swift */,
|
||||||
);
|
);
|
||||||
@@ -2317,6 +2320,7 @@
|
|||||||
8810A2A30A68252EBB54EE05 /* HomeScreenModels.swift in Sources */,
|
8810A2A30A68252EBB54EE05 /* HomeScreenModels.swift in Sources */,
|
||||||
DE4F8C4E0F1DB4832F09DE97 /* HomeScreenViewModel.swift in Sources */,
|
DE4F8C4E0F1DB4832F09DE97 /* HomeScreenViewModel.swift in Sources */,
|
||||||
56F0A22972A3BB519DA2261C /* HomeScreenViewModelProtocol.swift in Sources */,
|
56F0A22972A3BB519DA2261C /* HomeScreenViewModelProtocol.swift in Sources */,
|
||||||
|
03D684A3AE85A23B3DA3B43F /* Image.swift in Sources */,
|
||||||
6EA61FCA55D950BDE326A1A7 /* ImageAnonymizer.swift in Sources */,
|
6EA61FCA55D950BDE326A1A7 /* ImageAnonymizer.swift in Sources */,
|
||||||
2E59008365E01F0AFB3A6B24 /* ImageRoomMessage.swift in Sources */,
|
2E59008365E01F0AFB3A6B24 /* ImageRoomMessage.swift in Sources */,
|
||||||
DDB80FD2753FEAAE43CC2AAE /* ImageRoomTimelineItem.swift in Sources */,
|
DDB80FD2753FEAAE43CC2AAE /* ImageRoomTimelineItem.swift in Sources */,
|
||||||
|
|||||||
@@ -234,8 +234,12 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
|||||||
self.stateMachine.processEvent(.showRoomScreen(roomId: roomIdentifier))
|
self.stateMachine.processEvent(.showRoomScreen(roomId: roomIdentifier))
|
||||||
case .presentSettings:
|
case .presentSettings:
|
||||||
self.stateMachine.processEvent(.showSettingsScreen)
|
self.stateMachine.processEvent(.showSettingsScreen)
|
||||||
|
case .presentBugReport:
|
||||||
|
self.presentBugReportScreen()
|
||||||
case .verifySession:
|
case .verifySession:
|
||||||
self.stateMachine.processEvent(.showSessionVerificationScreen)
|
self.stateMachine.processEvent(.showSessionVerificationScreen)
|
||||||
|
case .signOut:
|
||||||
|
self.confirmSignOut()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,6 +339,19 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
|||||||
navigationRouter.present(alert, animated: true)
|
navigationRouter.present(alert, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func confirmSignOut() {
|
||||||
|
let alert = UIAlertController(title: ElementL10n.actionSignOut,
|
||||||
|
message: ElementL10n.actionSignOutConfirmationSimple,
|
||||||
|
preferredStyle: .alert)
|
||||||
|
|
||||||
|
alert.addAction(UIAlertAction(title: ElementL10n.actionCancel, style: .cancel))
|
||||||
|
alert.addAction(UIAlertAction(title: ElementL10n.actionSignOut, style: .destructive) { [weak self] _ in
|
||||||
|
self?.stateMachine.processEvent(.attemptSignOut)
|
||||||
|
})
|
||||||
|
|
||||||
|
navigationRouter.present(alert, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
private func processScreenshotDetection(image: UIImage?, error: Error?) {
|
private func processScreenshotDetection(image: UIImage?, error: Error?) {
|
||||||
MXLog.debug("Detected screenshot: \(String(describing: image)), error: \(String(describing: error))")
|
MXLog.debug("Detected screenshot: \(String(describing: image)), error: \(String(describing: error))")
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ class AppCoordinatorStateMachine {
|
|||||||
machine.addRoutes(event: .succeededRestoringSession, transitions: [.restoringSession => .homeScreen])
|
machine.addRoutes(event: .succeededRestoringSession, transitions: [.restoringSession => .homeScreen])
|
||||||
machine.addRoutes(event: .failedRestoringSession, transitions: [.restoringSession => .signedOut])
|
machine.addRoutes(event: .failedRestoringSession, transitions: [.restoringSession => .signedOut])
|
||||||
|
|
||||||
machine.addRoutes(event: .attemptSignOut, transitions: [.settingsScreen => .signingOut])
|
machine.addRoutes(event: .attemptSignOut, transitions: [.any => .signingOut])
|
||||||
|
|
||||||
machine.addRoutes(event: .succeededSigningOut, transitions: [.signingOut => .signedOut])
|
machine.addRoutes(event: .succeededSigningOut, transitions: [.signingOut => .signedOut])
|
||||||
machine.addRoutes(event: .failedSigningOut, transitions: [.signingOut => .settingsScreen])
|
machine.addRoutes(event: .failedSigningOut, transitions: [.signingOut => .settingsScreen])
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
extension Image {
|
||||||
|
/// Empty image view
|
||||||
|
static let empty = Image(uiImage: .init(ciImage: .empty()))
|
||||||
|
}
|
||||||
@@ -26,7 +26,9 @@ struct HomeScreenCoordinatorParameters {
|
|||||||
enum HomeScreenCoordinatorAction {
|
enum HomeScreenCoordinatorAction {
|
||||||
case presentRoom(roomIdentifier: String)
|
case presentRoom(roomIdentifier: String)
|
||||||
case presentSettings
|
case presentSettings
|
||||||
|
case presentBugReport
|
||||||
case verifySession
|
case verifySession
|
||||||
|
case signOut
|
||||||
}
|
}
|
||||||
|
|
||||||
final class HomeScreenCoordinator: Coordinator, Presentable {
|
final class HomeScreenCoordinator: Coordinator, Presentable {
|
||||||
@@ -53,8 +55,7 @@ final class HomeScreenCoordinator: Coordinator, Presentable {
|
|||||||
init(parameters: HomeScreenCoordinatorParameters) {
|
init(parameters: HomeScreenCoordinatorParameters) {
|
||||||
self.parameters = parameters
|
self.parameters = parameters
|
||||||
|
|
||||||
viewModel = HomeScreenViewModel(initialDisplayName: parameters.userSession.userID,
|
viewModel = HomeScreenViewModel(attributedStringBuilder: parameters.attributedStringBuilder)
|
||||||
attributedStringBuilder: parameters.attributedStringBuilder)
|
|
||||||
|
|
||||||
let view = HomeScreen(context: viewModel.context)
|
let view = HomeScreen(context: viewModel.context)
|
||||||
hostingController = UIHostingController(rootView: view)
|
hostingController = UIHostingController(rootView: view)
|
||||||
@@ -65,8 +66,8 @@ final class HomeScreenCoordinator: Coordinator, Presentable {
|
|||||||
switch action {
|
switch action {
|
||||||
case .selectRoom(let roomIdentifier):
|
case .selectRoom(let roomIdentifier):
|
||||||
self.callback?(.presentRoom(roomIdentifier: roomIdentifier))
|
self.callback?(.presentRoom(roomIdentifier: roomIdentifier))
|
||||||
case .tapUserAvatar:
|
case .userMenu(let action):
|
||||||
self.callback?(.presentSettings)
|
self.processUserMenuAction(action)
|
||||||
case .verifySession:
|
case .verifySession:
|
||||||
self.callback?(.verifySession)
|
self.callback?(.verifySession)
|
||||||
}
|
}
|
||||||
@@ -100,10 +101,6 @@ final class HomeScreenCoordinator: Coordinator, Presentable {
|
|||||||
self.viewModel.updateWithUserAvatar(avatar)
|
self.viewModel.updateWithUserAvatar(avatar)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if case let .success(userDisplayName) = await parameters.userSession.clientProxy.loadUserDisplayName() {
|
|
||||||
self.viewModel.updateWithUserDisplayName(userDisplayName)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,4 +133,26 @@ final class HomeScreenCoordinator: Coordinator, Presentable {
|
|||||||
|
|
||||||
viewModel.updateWithRoomSummaries(roomSummaries)
|
viewModel.updateWithRoomSummaries(roomSummaries)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func processUserMenuAction(_ action: HomeScreenViewUserMenuAction) {
|
||||||
|
switch action {
|
||||||
|
case .settings:
|
||||||
|
callback?(.presentSettings)
|
||||||
|
case .inviteFriends:
|
||||||
|
presentInviteFriends()
|
||||||
|
case .feedback:
|
||||||
|
callback?(.presentBugReport)
|
||||||
|
case .signOut:
|
||||||
|
callback?(.signOut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func presentInviteFriends() {
|
||||||
|
guard let permalink = try? PermalinkBuilder.permalinkTo(userIdentifier: parameters.userSession.userID).absoluteString else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let shareText = ElementL10n.inviteFriendsText(ElementInfoPlist.cfBundleName, permalink)
|
||||||
|
let vc = UIActivityViewController(activityItems: [shareText], applicationActivities: nil)
|
||||||
|
hostingController.present(vc, animated: true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,19 +19,25 @@ import UIKit
|
|||||||
|
|
||||||
enum HomeScreenViewModelAction {
|
enum HomeScreenViewModelAction {
|
||||||
case selectRoom(roomIdentifier: String)
|
case selectRoom(roomIdentifier: String)
|
||||||
case tapUserAvatar
|
case userMenu(action: HomeScreenViewUserMenuAction)
|
||||||
case verifySession
|
case verifySession
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum HomeScreenViewUserMenuAction {
|
||||||
|
case settings
|
||||||
|
case inviteFriends
|
||||||
|
case feedback
|
||||||
|
case signOut
|
||||||
|
}
|
||||||
|
|
||||||
enum HomeScreenViewAction {
|
enum HomeScreenViewAction {
|
||||||
case loadRoomData(roomIdentifier: String)
|
case loadRoomData(roomIdentifier: String)
|
||||||
case selectRoom(roomIdentifier: String)
|
case selectRoom(roomIdentifier: String)
|
||||||
case tapUserAvatar
|
case userMenu(action: HomeScreenViewUserMenuAction)
|
||||||
case verifySession
|
case verifySession
|
||||||
}
|
}
|
||||||
|
|
||||||
struct HomeScreenViewState: BindableState {
|
struct HomeScreenViewState: BindableState {
|
||||||
var userDisplayName: String
|
|
||||||
var userAvatar: UIImage?
|
var userAvatar: UIImage?
|
||||||
|
|
||||||
var showSessionVerificationBanner = false
|
var showSessionVerificationBanner = false
|
||||||
|
|||||||
@@ -39,11 +39,10 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
|
|
||||||
// MARK: - Setup
|
// MARK: - Setup
|
||||||
|
|
||||||
init(initialDisplayName: String, attributedStringBuilder: AttributedStringBuilderProtocol) {
|
init(attributedStringBuilder: AttributedStringBuilderProtocol) {
|
||||||
self.attributedStringBuilder = attributedStringBuilder
|
self.attributedStringBuilder = attributedStringBuilder
|
||||||
|
|
||||||
super.init(initialViewState: HomeScreenViewState(userDisplayName: initialDisplayName,
|
super.init(initialViewState: HomeScreenViewState(isLoadingRooms: true))
|
||||||
isLoadingRooms: true))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
@@ -54,8 +53,8 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
loadRoomDataForIdentifier(roomIdentifier)
|
loadRoomDataForIdentifier(roomIdentifier)
|
||||||
case .selectRoom(let roomIdentifier):
|
case .selectRoom(let roomIdentifier):
|
||||||
callback?(.selectRoom(roomIdentifier: roomIdentifier))
|
callback?(.selectRoom(roomIdentifier: roomIdentifier))
|
||||||
case .tapUserAvatar:
|
case .userMenu(let action):
|
||||||
callback?(.tapUserAvatar)
|
callback?(.userMenu(action: action))
|
||||||
case .verifySession:
|
case .verifySession:
|
||||||
callback?(.verifySession)
|
callback?(.verifySession)
|
||||||
}
|
}
|
||||||
@@ -113,11 +112,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
func updateWithUserAvatar(_ avatar: UIImage) {
|
func updateWithUserAvatar(_ avatar: UIImage) {
|
||||||
state.userAvatar = avatar
|
state.userAvatar = avatar
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateWithUserDisplayName(_ displayName: String) {
|
|
||||||
state.userDisplayName = displayName
|
|
||||||
}
|
|
||||||
|
|
||||||
func showSessionVerificationBanner() {
|
func showSessionVerificationBanner() {
|
||||||
state.showSessionVerificationBanner = true
|
state.showSessionVerificationBanner = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ protocol HomeScreenViewModelProtocol {
|
|||||||
var context: HomeScreenViewModelType.Context { get }
|
var context: HomeScreenViewModelType.Context { get }
|
||||||
|
|
||||||
func updateWithUserAvatar(_ avatar: UIImage)
|
func updateWithUserAvatar(_ avatar: UIImage)
|
||||||
func updateWithUserDisplayName(_ displayName: String)
|
|
||||||
func updateWithRoomSummaries(_ roomSummaries: [RoomSummaryProtocol])
|
func updateWithRoomSummaries(_ roomSummaries: [RoomSummaryProtocol])
|
||||||
|
|
||||||
func showSessionVerificationBanner()
|
func showSessionVerificationBanner()
|
||||||
|
|||||||
@@ -69,39 +69,71 @@ struct HomeScreen: View {
|
|||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .navigationBarLeading) {
|
ToolbarItem(placement: .navigationBarLeading) {
|
||||||
Button { context.send(viewAction: .tapUserAvatar) } label: {
|
userMenuButton
|
||||||
HStack {
|
|
||||||
userAvatarImage
|
|
||||||
.animation(.elementDefault, value: context.viewState.userAvatar)
|
|
||||||
.transition(.opacity)
|
|
||||||
|
|
||||||
userDisplayNameView
|
|
||||||
.animation(.elementDefault, value: context.viewState.userDisplayName)
|
|
||||||
.transition(.opacity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var userAvatarImage: some View {
|
private var userMenuButton: some View {
|
||||||
if let avatar = context.viewState.userAvatar {
|
Menu {
|
||||||
Image(uiImage: avatar)
|
Section {
|
||||||
.resizable()
|
Button(action: settings) {
|
||||||
.scaledToFill()
|
Label(ElementL10n.settingsUserSettings, systemImage: "gearshape")
|
||||||
.frame(width: 32, height: 32, alignment: .center)
|
}
|
||||||
.clipShape(Circle())
|
}
|
||||||
.accessibilityIdentifier("userAvatarImage")
|
Section {
|
||||||
|
Button(action: inviteFriends) {
|
||||||
|
Label(ElementL10n.inviteFriends, systemImage: "square.and.arrow.up")
|
||||||
|
}
|
||||||
|
Button(action: feedback) {
|
||||||
|
Label(ElementL10n.feedback, systemImage: "questionmark.circle")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Section {
|
||||||
|
Button(role: .destructive, action: signOut) {
|
||||||
|
Label(ElementL10n.actionSignOut, systemImage: "rectangle.portrait.and.arrow.right")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
userAvatarImageView
|
||||||
|
.animation(.elementDefault, value: context.viewState.userAvatar)
|
||||||
|
.transition(.opacity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var userDisplayNameView: some View {
|
@ViewBuilder
|
||||||
Text(context.viewState.userDisplayName)
|
private var userAvatarImageView: some View {
|
||||||
.font(.headline)
|
userAvatarImage
|
||||||
.fontWeight(.bold)
|
.resizable()
|
||||||
.foregroundColor(.primary)
|
.scaledToFill()
|
||||||
.accessibilityIdentifier("userDisplayNameView")
|
.frame(width: 32, height: 32, alignment: .center)
|
||||||
|
.clipShape(Circle())
|
||||||
|
.accessibilityIdentifier("userAvatarImage")
|
||||||
|
}
|
||||||
|
|
||||||
|
private var userAvatarImage: Image {
|
||||||
|
if let avatar = context.viewState.userAvatar {
|
||||||
|
return Image(uiImage: avatar)
|
||||||
|
} else {
|
||||||
|
return .empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func settings() {
|
||||||
|
context.send(viewAction: .userMenu(action: .settings))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func inviteFriends() {
|
||||||
|
context.send(viewAction: .userMenu(action: .inviteFriends))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func feedback() {
|
||||||
|
context.send(viewAction: .userMenu(action: .feedback))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func signOut() {
|
||||||
|
context.send(viewAction: .userMenu(action: .signOut))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,8 +204,7 @@ struct HomeScreen_Previews: PreviewProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var body: some View {
|
static var body: some View {
|
||||||
let viewModel = HomeScreenViewModel(initialDisplayName: "@username:server.com",
|
let viewModel = HomeScreenViewModel(attributedStringBuilder: AttributedStringBuilder())
|
||||||
attributedStringBuilder: AttributedStringBuilder())
|
|
||||||
|
|
||||||
let eventBrief = EventBrief(eventId: "id",
|
let eventBrief = EventBrief(eventId: "id",
|
||||||
senderId: "senderId",
|
senderId: "senderId",
|
||||||
@@ -187,7 +218,6 @@ struct HomeScreen_Previews: PreviewProvider {
|
|||||||
MockRoomSummary(displayName: "Omega", lastMessage: eventBrief)]
|
MockRoomSummary(displayName: "Omega", lastMessage: eventBrief)]
|
||||||
|
|
||||||
viewModel.updateWithRoomSummaries(roomSummaries)
|
viewModel.updateWithRoomSummaries(roomSummaries)
|
||||||
viewModel.updateWithUserDisplayName("username")
|
|
||||||
|
|
||||||
if let avatarImage = UIImage(systemName: "person.fill") {
|
if let avatarImage = UIImage(systemName: "person.fill") {
|
||||||
viewModel.updateWithUserAvatar(avatarImage)
|
viewModel.updateWithUserAvatar(avatarImage)
|
||||||
|
|||||||
@@ -23,8 +23,7 @@ class HomeScreenViewModelTests: XCTestCase {
|
|||||||
var context: HomeScreenViewModelType.Context!
|
var context: HomeScreenViewModelType.Context!
|
||||||
|
|
||||||
@MainActor override func setUpWithError() throws {
|
@MainActor override func setUpWithError() throws {
|
||||||
viewModel = HomeScreenViewModel(initialDisplayName: "@test:example.com",
|
viewModel = HomeScreenViewModel(attributedStringBuilder: AttributedStringBuilder())
|
||||||
attributedStringBuilder: AttributedStringBuilder())
|
|
||||||
context = viewModel.context
|
context = viewModel.context
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,14 +51,14 @@ class HomeScreenViewModelTests: XCTestCase {
|
|||||||
var correctResult = false
|
var correctResult = false
|
||||||
viewModel.callback = { result in
|
viewModel.callback = { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .tapUserAvatar:
|
case .userMenu(let action):
|
||||||
correctResult = true
|
correctResult = action == .settings
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context.send(viewAction: .tapUserAvatar)
|
context.send(viewAction: .userMenu(action: .settings))
|
||||||
await Task.yield()
|
await Task.yield()
|
||||||
XCTAssert(correctResult)
|
XCTAssert(correctResult)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
HomeScreen: Add user options menu to avatar and display name.
|
||||||
Reference in New Issue
Block a user