Some Post-demo Cleanups (#200)

* Remove redundant string

* Use placeholder avatar on home screen

* Add initial home screen ui test

* Fix settings screen PR remarks

* Remove UIKit alert from home screen sign out

* Remove UIKit alert from soft logout clear all data

* Add reference screenshots for home screen UI tests

* Formatting fixes

* Add clearing room method to client proxy

* Clear room proxies on screen dismiss

* Fix retain cycle in room view model

* Do not go into authentication state immediately

* Define sizes for user and room avatars on different screens

* Use defined avatar sizes everywhere

* Disable image disk caching

* Rename rounded corner shape

* Fix text color of placeholder avatars

* Fix PR reviews on formatted body text

* Fix merge conflict

* Remove shouldShowSenderDetails everywhere and just use it from inGroupState

* Remove redundant linter disablings

* Fix PR remarks

* Rename media provider size parameter
This commit is contained in:
ismailgulek
2022-09-23 12:21:41 +03:00
committed by GitHub
parent b9f8fb0b6f
commit bdc83dac27
54 changed files with 307 additions and 204 deletions
@@ -21,6 +21,8 @@ struct SoftLogoutScreen: View {
// MARK: Private
@State private var showingClearDataConfirmation = false
/// The focus state of the password text field.
@FocusState private var isPasswordFocused: Bool
@@ -150,6 +152,14 @@ struct SoftLogoutScreen: View {
}
.buttonStyle(.elementAction(.xLarge, color: .element.alert))
.accessibilityIdentifier("clearDataButton")
.alert(ElementL10n.softLogoutClearDataDialogTitle,
isPresented: $showingClearDataConfirmation) {
Button(ElementL10n.actionSignOut,
role: .destructive,
action: clearData)
} message: {
Text(ElementL10n.softLogoutClearDataDialogContent)
}
}
}
@@ -39,6 +39,8 @@ enum HomeScreenViewAction {
}
struct HomeScreenViewState: BindableState {
var userID: String
var userDisplayName: String?
var userAvatar: UIImage?
var showSessionVerificationBanner = false
@@ -33,7 +33,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
roomSummaryProvider = userSession.clientProxy.roomSummaryProvider
self.attributedStringBuilder = attributedStringBuilder
super.init(initialViewState: HomeScreenViewState())
super.init(initialViewState: HomeScreenViewState(userID: userSession.userID))
userSession.callbacks
.receive(on: DispatchQueue.main)
@@ -61,13 +61,19 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
Task {
if case let .success(userAvatarURLString) = await userSession.clientProxy.loadUserAvatarURLString() {
if case let .success(avatar) = await userSession.mediaProvider.loadImageFromURLString(userAvatarURLString, size: MediaProviderDefaultAvatarSize) {
if case let .success(avatar) = await userSession.mediaProvider.loadImageFromURLString(userAvatarURLString, avatarSize: .user(on: .home)) {
state.userAvatar = avatar
}
}
await updateRooms()
}
Task {
if case let .success(userDisplayName) = await userSession.clientProxy.loadUserDisplayName() {
state.userDisplayName = userDisplayName
}
}
}
// MARK: - Public
@@ -101,7 +107,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
if let avatarURLString = summary.avatarURLString {
Task {
if case let .success(image) = await userSession.mediaProvider.loadImageFromURLString(avatarURLString, size: MediaProviderDefaultAvatarSize) {
if case let .success(image) = await userSession.mediaProvider.loadImageFromURLString(avatarURLString, avatarSize: .room(on: .home)) {
if let index = state.rooms.firstIndex(of: room) {
room.avatar = image
state.rooms[index] = room
@@ -116,7 +122,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
var rooms = [HomeScreenRoom]()
for summary in self.roomSummaryProvider.roomSummaries {
let avatarImage = await self.userSession.mediaProvider.imageFromURLString(summary.avatarURLString, size: MediaProviderDefaultAvatarSize)
let avatarImage = await self.userSession.mediaProvider.imageFromURLString(summary.avatarURLString, avatarSize: .room(on: .home))
var timestamp: String?
if let lastMessageTimestamp = summary.lastMessageTimestamp {
@@ -17,6 +17,7 @@
import SwiftUI
struct HomeScreen: View {
@State private var showingLogoutConfirmation = false
@ObservedObject var context: HomeScreenViewModel.Context
// MARK: Views
@@ -50,7 +51,7 @@ struct HomeScreen: View {
.transition(.slide)
.animation(.elementDefault, value: context.viewState.showSessionVerificationBanner)
.ignoresSafeArea(.all, edges: .bottom)
.navigationTitle(ElementL10n.homeScreenAllChats)
.navigationTitle(ElementL10n.allChats)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
userMenuButton
@@ -75,7 +76,9 @@ struct HomeScreen: View {
}
}
Section {
Button(role: .destructive, action: signOut) {
Button(role: .destructive) {
showingLogoutConfirmation = true
} label: {
Label(ElementL10n.actionSignOut, systemImage: "rectangle.portrait.and.arrow.right")
}
}
@@ -84,23 +87,33 @@ struct HomeScreen: View {
.animation(.elementDefault, value: context.viewState.userAvatar)
.transition(.opacity)
}
.alert(ElementL10n.actionSignOut,
isPresented: $showingLogoutConfirmation) {
Button(ElementL10n.actionSignOut,
role: .destructive,
action: signOut)
} message: {
Text(ElementL10n.actionSignOutConfirmationSimple)
}
}
@ViewBuilder
private var userAvatarImageView: some View {
userAvatarImage
.resizable()
.scaledToFill()
.frame(width: 32, height: 32, alignment: .center)
.frame(width: AvatarSize.user(on: .home).value, height: AvatarSize.user(on: .home).value, alignment: .center)
.clipShape(Circle())
.accessibilityIdentifier("userAvatarImage")
}
private var userAvatarImage: Image {
@ViewBuilder
private var userAvatarImage: some View {
if let avatar = context.viewState.userAvatar {
return Image(uiImage: avatar)
Image(uiImage: avatar)
.resizable()
.scaledToFill()
} else {
return .empty
PlaceholderAvatarImage(text: context.viewState.userDisplayName ?? context.viewState.userID,
contentId: context.viewState.userID)
}
}
@@ -17,7 +17,7 @@
import SwiftUI
struct HomeScreenRoomCell: View {
@ScaledMetric private var avatarSize = 44.0
@ScaledMetric private var avatarSize = AvatarSize.room(on: .home).value
let room: HomeScreenRoom
let context: HomeScreenViewModel.Context
@@ -40,6 +40,7 @@ enum RoomScreenViewAction {
case sendMessage
case sendReaction(key: String, eventID: String)
case cancelReply
case viewDisappeared
}
struct RoomScreenViewState: BindableState {
@@ -89,6 +89,10 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
MXLog.warning("React with \(key) failed. Not implemented.")
case .cancelReply:
state.composerMode = .default
case .viewDisappeared:
cancellables.forEach { $0.cancel() }
cancellables.removeAll()
state.contextMenuBuilder = nil
}
}
@@ -42,7 +42,7 @@ struct RoomHeaderView: View {
.accessibilityIdentifier("encryptionBadgeIcon")
}
}
.frame(width: 32.0, height: 32.0)
.frame(width: AvatarSize.room(on: .timeline).value, height: AvatarSize.room(on: .timeline).value)
}
@ViewBuilder private var roomAvatarImage: some View {
@@ -41,6 +41,9 @@ struct RoomScreen: View {
}
}
.alert(item: $context.alertInfo) { $0.alert }
.onDisappear {
context.send(viewAction: .viewDisappeared)
}
}
private func sendMessage() {
@@ -144,7 +144,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
.background(Color.element.systemGray6) // Demo time!
.cornerRadius(12, inGroupState: timelineItem.inGroupState) // Demo time!
// .overlay(
// RoundedCorner(radius: 18, inGroupState: timelineItem.inGroupState)
// RoundedCornerShape(radius: 18, inGroupState: timelineItem.inGroupState)
// .stroke(Color.element.systemGray5)
// )
}
@@ -58,7 +58,6 @@ struct EmoteRoomTimelineView_Previews: PreviewProvider {
EmoteRoomTimelineItem(id: UUID().uuidString,
text: text,
timestamp: timestamp,
shouldShowSenderDetails: true,
inGroupState: .single,
isOutgoing: false,
senderId: senderId)
@@ -26,25 +26,13 @@ struct FormattedBodyText: View {
VStack(alignment: .leading, spacing: 8.0) {
ForEach(attributedComponents, id: \.self) { component in
if component.isBlockquote {
if isOutgoing {
Text(component.attributedString)
.fixedSize(horizontal: false, vertical: true)
.foregroundColor(.element.primaryContent)
.padding(EdgeInsets(top: 6, leading: 12, bottom: 6, trailing: 12))
.clipped()
.background(Color.element.systemGray4)
.cornerRadius(13)
} else {
Text(component.attributedString)
.fixedSize(horizontal: false, vertical: true)
.foregroundColor(.element.primaryContent)
.padding(EdgeInsets(top: 8, leading: 12, bottom: 8, trailing: 12))
.clipped()
.overlay(
RoundedRectangle(cornerRadius: 13)
.stroke(Color.element.systemGray5)
)
}
Text(component.attributedString.mergingAttributes(blockquoteAttributes))
.fixedSize(horizontal: false, vertical: true)
.foregroundColor(.element.primaryContent)
.padding(EdgeInsets(top: 6, leading: 12, bottom: 6, trailing: 12))
.clipped()
.background(Color.element.systemGray4)
.cornerRadius(13)
} else {
Text(component.attributedString)
.fixedSize(horizontal: false, vertical: true)
@@ -54,6 +42,12 @@ struct FormattedBodyText: View {
}
.tint(.element.accent)
}
private var blockquoteAttributes: AttributeContainer {
var container = AttributeContainer()
container.font = .element.caption1
return container
}
}
extension FormattedBodyText {
@@ -62,7 +62,6 @@ struct ImageRoomTimelineView_Previews: PreviewProvider {
ImageRoomTimelineView(timelineItem: ImageRoomTimelineItem(id: UUID().uuidString,
text: "Some image",
timestamp: "Now",
shouldShowSenderDetails: false,
inGroupState: .single,
isOutgoing: false,
senderId: "Bob",
@@ -72,7 +71,6 @@ struct ImageRoomTimelineView_Previews: PreviewProvider {
ImageRoomTimelineView(timelineItem: ImageRoomTimelineItem(id: UUID().uuidString,
text: "Some other image",
timestamp: "Now",
shouldShowSenderDetails: false,
inGroupState: .single,
isOutgoing: false,
senderId: "Bob",
@@ -82,7 +80,6 @@ struct ImageRoomTimelineView_Previews: PreviewProvider {
ImageRoomTimelineView(timelineItem: ImageRoomTimelineItem(id: UUID().uuidString,
text: "Blurhashed image",
timestamp: "Now",
shouldShowSenderDetails: false,
inGroupState: .single,
isOutgoing: false,
senderId: "Bob",
@@ -59,7 +59,6 @@ struct NoticeRoomTimelineView_Previews: PreviewProvider {
NoticeRoomTimelineItem(id: UUID().uuidString,
text: text,
timestamp: timestamp,
shouldShowSenderDetails: true,
inGroupState: .single,
isOutgoing: false,
senderId: senderId)
@@ -25,7 +25,7 @@ struct PlaceholderAvatarImage: View {
bgColor
Text(textForImage)
.padding(4)
.foregroundColor(.element.background)
.foregroundColor(.white)
.font(.title2.bold())
}
.aspectRatio(1, contentMode: .fill)
@@ -51,7 +51,6 @@ struct RedactedRoomTimelineView_Previews: PreviewProvider {
RedactedRoomTimelineItem(id: UUID().uuidString,
text: text,
timestamp: timestamp,
shouldShowSenderDetails: true,
inGroupState: .single,
isOutgoing: false,
senderId: senderId)
@@ -43,26 +43,22 @@ struct TextRoomTimelineView_Previews: PreviewProvider {
VStack(alignment: .leading, spacing: 20.0) {
TextRoomTimelineView(timelineItem: itemWith(text: "Short loin ground round tongue hamburger, fatback salami shoulder. Beef turkey sausage kielbasa strip steak. Alcatra capicola pig tail pancetta chislic.",
timestamp: "Now",
shouldShowSenderDetails: true,
isOutgoing: false,
senderId: "Bob"))
TextRoomTimelineView(timelineItem: itemWith(text: "Some other text",
timestamp: "Later",
shouldShowSenderDetails: true,
isOutgoing: true,
senderId: "Anne"))
TextRoomTimelineView(timelineItem: itemWith(text: "Short loin ground round tongue hamburger, fatback salami shoulder. Beef turkey sausage kielbasa strip steak. Alcatra capicola pig tail pancetta chislic.",
timestamp: "Now",
shouldShowSenderDetails: true,
isOutgoing: false,
senderId: "Bob"))
.timelineStyle(.plain)
TextRoomTimelineView(timelineItem: itemWith(text: "Some other text",
timestamp: "Later",
shouldShowSenderDetails: true,
isOutgoing: true,
senderId: "Anne"))
.timelineStyle(.plain)
@@ -70,11 +66,10 @@ struct TextRoomTimelineView_Previews: PreviewProvider {
.padding(.horizontal, 8)
}
private static func itemWith(text: String, timestamp: String, shouldShowSenderDetails: Bool, isOutgoing: Bool, senderId: String) -> TextRoomTimelineItem {
private static func itemWith(text: String, timestamp: String, isOutgoing: Bool, senderId: String) -> TextRoomTimelineItem {
TextRoomTimelineItem(id: UUID().uuidString,
text: text,
timestamp: timestamp,
shouldShowSenderDetails: shouldShowSenderDetails,
inGroupState: .single,
isOutgoing: isOutgoing,
senderId: senderId)
@@ -20,7 +20,7 @@ import SwiftUI
struct TimelineSenderAvatarView: View {
let timelineItem: EventBasedTimelineItemProtocol
@ScaledMetric private var avatarSize = 32
@ScaledMetric private var avatarSize = AvatarSize.user(on: .timeline).value
var body: some View {
ZStack(alignment: .center) {
@@ -73,7 +73,7 @@ final class SettingsCoordinator: Coordinator, Presentable {
case .crash:
self.parameters.bugReportService.crash()
case .logout:
self.confirmSignOut()
self.callback?(.logout)
}
}
}
@@ -118,19 +118,6 @@ final class SettingsCoordinator: Coordinator, Presentable {
}
}
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?.callback?(.logout)
})
navigationRouter.present(alert, animated: true)
}
/// Show an activity indicator whilst loading.
/// - Parameters:
/// - label: The label to show on the indicator.
@@ -40,11 +40,13 @@ class SettingsViewModel: SettingsViewModelType, SettingsViewModelProtocol {
Task {
if case let .success(userAvatarURLString) = await userSession.clientProxy.loadUserAvatarURLString() {
if case let .success(avatar) = await userSession.mediaProvider.loadImageFromURLString(userAvatarURLString, size: MediaProviderDefaultAvatarSize) {
if case let .success(avatar) = await userSession.mediaProvider.loadImageFromURLString(userAvatarURLString, avatarSize: .user(on: .settings)) {
state.userAvatar = avatar
}
}
}
Task {
if case let .success(userDisplayName) = await self.userSession.clientProxy.loadUserDisplayName() {
state.userDisplayName = userDisplayName
}
@@ -19,10 +19,11 @@ import SwiftUI
struct SettingsScreen: View {
// MARK: Private
@State private var showingLogoutConfirmation = false
@Environment(\.colorScheme) private var colorScheme
@ObservedObject private var settings = ElementSettings.shared
@ScaledMetric private var avatarSize = 60.0
@ScaledMetric private var avatarSize = AvatarSize.user(on: .settings).value
@ScaledMetric private var menuIconSize = 30.0
private let listRowInsets = EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16)
@@ -169,7 +170,7 @@ struct SettingsScreen: View {
private var logoutSection: some View {
Section {
Button(action: logout) {
Button { showingLogoutConfirmation = true } label: {
HStack {
Image(systemName: "rectangle.portrait.and.arrow.right")
.foregroundColor(.element.systemGray)
@@ -186,6 +187,14 @@ struct SettingsScreen: View {
.listRowInsets(listRowInsets)
.foregroundColor(.element.primaryContent)
.accessibilityIdentifier("logoutButton")
.alert(ElementL10n.actionSignOut,
isPresented: $showingLogoutConfirmation) {
Button(ElementL10n.actionSignOut,
role: .destructive,
action: logout)
} message: {
Text(ElementL10n.actionSignOutConfirmationSimple)
}
} footer: {
versionText
.frame(maxWidth: .infinity)
@@ -194,12 +203,10 @@ struct SettingsScreen: View {
private var closeButton: some View {
Button(action: close) {
HStack {
Image(systemName: "xmark")
.font(.title3.bold())
.foregroundColor(.element.secondaryContent)
.padding(4)
}
Image(systemName: "xmark")
.font(.title3.bold())
.foregroundColor(.element.secondaryContent)
.padding(4)
}
.accessibilityIdentifier("closeButton")
}