mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 10:33:45 +00:00
App Polish (#1098)
* App polish - Skeleton avatar colour on app switching. - Bug report icon and string. - Semi-bold font on room list timestamps. - Room attachments button spacing and alignment. - Replace link tint with semi-bold font in AnalyticsSettingsScreen. - Semi-bold font on Invites button. - Add padding to media attachment sheet. - Message composer header layout tweaks. - Remove indicator after changing homeserver with an MXID. - Change copy permalink string. - Formatting of link in Analytics Prompt. - Remove indicator after changing homeserver with an MXID. - Change copy permalink string. - Formatting of link in Analytics Prompt. - Only semi-bold the room timestamp when unread. - Tweak the colour of the date separator. - Add activity indicator to toast style user indicator.
This commit is contained in:
@@ -33,20 +33,29 @@ extension AttributedString {
|
||||
}
|
||||
}
|
||||
|
||||
/// Replaces the specified placeholder with the a string that links to the specified URL.
|
||||
/// Replaces the specified placeholder with a string that links to the specified URL.
|
||||
/// - Parameters:
|
||||
/// - linkPlaceholder: The text in the string that will be replaced. Make sure this is unique within the string.
|
||||
/// - string: The text for the link that will be substituted into the placeholder.
|
||||
/// - url: The URL that the link should open.
|
||||
mutating func replace(_ linkPlaceholder: String, with string: String, asLinkTo url: URL) {
|
||||
guard let range = range(of: linkPlaceholder) else {
|
||||
MXLog.failure("Failed to find the link placeholder to be replaced.")
|
||||
return
|
||||
}
|
||||
|
||||
// Replace the placeholder with a link.
|
||||
var replacement = AttributedString(string)
|
||||
replacement.link = url
|
||||
replace(linkPlaceholder, with: replacement)
|
||||
}
|
||||
|
||||
/// Replaces the specified placeholder with the supplied attributed string.
|
||||
/// - Parameters:
|
||||
/// - placeholder: The text in the string that will be replaced. Make sure this is unique within the string.
|
||||
/// - attributedString: The text for the link that will be substituted into the placeholder.
|
||||
mutating func replace(_ placeholder: String, with replacement: AttributedString) {
|
||||
guard let range = range(of: placeholder) else {
|
||||
MXLog.failure("Failed to find the placeholder to be replaced.")
|
||||
return
|
||||
}
|
||||
|
||||
// Replace the placeholder.
|
||||
replaceSubrange(range, with: replacement)
|
||||
}
|
||||
|
||||
@@ -63,4 +72,12 @@ extension AttributedString {
|
||||
}
|
||||
return newValue
|
||||
}
|
||||
|
||||
/// Makes the entire string bold by setting the presentation intent to strongly emphasized.
|
||||
///
|
||||
/// In practice, this is rendered as semibold for smaller font sizes and just so happens to nicely
|
||||
/// line up with the semibold → bold font switch used by compound.
|
||||
mutating func bold() {
|
||||
self[startIndex..<endIndex].inlinePresentationIntent = .stronglyEmphasized
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ protocol ProgressListener {
|
||||
var progressSubject: CurrentValueSubject<Double, Never> { get }
|
||||
}
|
||||
|
||||
protocol ProgressPublisher {
|
||||
protocol ProgressPublisher: AnyObject {
|
||||
var publisher: AnyPublisher<Double, Never> { get }
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -19,8 +19,10 @@ import SwiftUI
|
||||
struct FixedIconSizeLabelStyle: LabelStyle {
|
||||
@ScaledMetric private var iconSize = 24.0
|
||||
|
||||
var spacing: CGFloat = 16
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
HStack {
|
||||
HStack(spacing: spacing) {
|
||||
configuration
|
||||
.icon
|
||||
.frame(width: iconSize, height: iconSize)
|
||||
@@ -25,7 +25,7 @@ struct PlaceholderAvatarImage: View {
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
ZStack(alignment: .center) {
|
||||
bgColor
|
||||
backgroundColor
|
||||
|
||||
// This text's frame doesn't look right when redacted
|
||||
if redactionReasons != .placeholder {
|
||||
@@ -47,8 +47,8 @@ struct PlaceholderAvatarImage: View {
|
||||
self.contentID = contentID
|
||||
}
|
||||
|
||||
private var bgColor: Color {
|
||||
if redactionReasons == .placeholder {
|
||||
private var backgroundColor: Color {
|
||||
if redactionReasons.contains(.placeholder) {
|
||||
return Color(.systemGray4) // matches the default text redaction
|
||||
}
|
||||
|
||||
|
||||
@@ -18,41 +18,51 @@ import Combine
|
||||
import Foundation
|
||||
|
||||
enum UserIndicatorType: Equatable {
|
||||
case toast
|
||||
case modal(interactiveDismissDisabled: Bool)
|
||||
case toast(progress: UserIndicator.Progress?)
|
||||
case modal(progress: UserIndicator.Progress?, interactiveDismissDisabled: Bool)
|
||||
|
||||
static var modal: Self {
|
||||
.modal(interactiveDismissDisabled: false)
|
||||
}
|
||||
static var toast: Self { .toast(progress: .none) }
|
||||
static var modal: Self { .modal(progress: .indeterminate, interactiveDismissDisabled: false) }
|
||||
}
|
||||
|
||||
struct UserIndicator: Equatable, Identifiable {
|
||||
static func == (lhs: UserIndicator, rhs: UserIndicator) -> Bool {
|
||||
lhs.id == rhs.id &&
|
||||
lhs.type == rhs.type &&
|
||||
lhs.title == rhs.title &&
|
||||
lhs.iconName == rhs.iconName &&
|
||||
lhs.persistent == rhs.persistent
|
||||
}
|
||||
|
||||
enum LoaderType {
|
||||
case unknownProgress
|
||||
case progress(ProgressPublisher)
|
||||
enum Progress: Equatable {
|
||||
static func == (lhs: UserIndicator.Progress, rhs: UserIndicator.Progress) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.indeterminate, .indeterminate): return true
|
||||
case (.published(let lhsPublisher), .published(let rhsPublisher)): return lhsPublisher === rhsPublisher
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
case indeterminate
|
||||
case published(ProgressPublisher)
|
||||
}
|
||||
|
||||
var id: String = UUID().uuidString
|
||||
var type = UserIndicatorType.toast
|
||||
var type: UserIndicatorType = .toast
|
||||
var title: String
|
||||
var iconName: String?
|
||||
var persistent = false
|
||||
var loaderType: LoaderType? = .unknownProgress
|
||||
|
||||
// MARK: - Associated values from the type
|
||||
|
||||
var progress: Progress? {
|
||||
switch type {
|
||||
case .toast(let progress): return progress
|
||||
case .modal(let progress, _): return progress
|
||||
}
|
||||
}
|
||||
|
||||
var progressPublisher: AnyPublisher<Double, Never> {
|
||||
switch loaderType {
|
||||
case .none, .unknownProgress:
|
||||
return Empty().eraseToAnyPublisher()
|
||||
case .some(.progress(let progress)):
|
||||
return progress.publisher.eraseToAnyPublisher()
|
||||
switch type {
|
||||
case .toast(let progress), .modal(let progress, _):
|
||||
switch progress {
|
||||
case .none, .indeterminate:
|
||||
return Empty().eraseToAnyPublisher()
|
||||
case .some(.published(let progress)):
|
||||
return progress.publisher.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +70,7 @@ struct UserIndicator: Equatable, Identifiable {
|
||||
switch type {
|
||||
case .toast:
|
||||
return false
|
||||
case .modal(let interactiveDismissDisabled):
|
||||
case .modal(_, let interactiveDismissDisabled):
|
||||
return interactiveDismissDisabled
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,15 +24,17 @@ struct UserIndicatorModalView: View {
|
||||
var body: some View {
|
||||
ZStack {
|
||||
VStack(spacing: 12.0) {
|
||||
if case .unknownProgress = indicator.loaderType {
|
||||
if case .indeterminate = indicator.progress {
|
||||
ProgressView()
|
||||
} else if case .progress = indicator.loaderType {
|
||||
} else if case .published = indicator.progress {
|
||||
ProgressView(value: progressFraction)
|
||||
}
|
||||
|
||||
HStack {
|
||||
if let iconName = indicator.iconName {
|
||||
Image(systemName: iconName)
|
||||
.font(.compound.bodyLG)
|
||||
.foregroundColor(.compound.iconPrimary)
|
||||
}
|
||||
Text(indicator.title)
|
||||
.font(.compound.bodyLG)
|
||||
@@ -66,17 +68,17 @@ struct UserIndicatorModalView_Previews: PreviewProvider {
|
||||
iconName: "checkmark")
|
||||
)
|
||||
.previewDisplayName("Spinner")
|
||||
UserIndicatorModalView(indicator: UserIndicator(type: .modal,
|
||||
|
||||
UserIndicatorModalView(indicator: UserIndicator(type: .modal(progress: .published(ProgressTracker(initialValue: 0.5)),
|
||||
interactiveDismissDisabled: false),
|
||||
title: "Successfully logged in",
|
||||
iconName: "checkmark",
|
||||
loaderType: .progress(ProgressTracker(initialValue: 0.5)))
|
||||
iconName: "checkmark")
|
||||
)
|
||||
.previewDisplayName("Progress Bar")
|
||||
|
||||
UserIndicatorModalView(indicator: UserIndicator(type: .modal,
|
||||
UserIndicatorModalView(indicator: UserIndicator(type: .modal(progress: .none, interactiveDismissDisabled: false),
|
||||
title: "Successfully logged in",
|
||||
iconName: "checkmark",
|
||||
loaderType: .none)
|
||||
iconName: "checkmark")
|
||||
)
|
||||
.previewDisplayName("No progress")
|
||||
}
|
||||
|
||||
@@ -20,21 +20,27 @@ struct UserIndicatorToastView: View {
|
||||
let indicator: UserIndicator
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
HStack(spacing: 4) {
|
||||
if case .indeterminate = indicator.progress {
|
||||
ProgressView()
|
||||
.controlSize(.small)
|
||||
.tint(.compound.iconPrimary)
|
||||
}
|
||||
if let iconName = indicator.iconName {
|
||||
Image(systemName: iconName)
|
||||
.font(.compound.bodyMD)
|
||||
.foregroundColor(.compound.iconPrimary)
|
||||
}
|
||||
Text(indicator.title)
|
||||
.font(.compound.bodySM)
|
||||
.font(.compound.bodyMD)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
}
|
||||
.id(indicator.id)
|
||||
.padding(.horizontal, 12.0)
|
||||
.padding(.vertical, 10.0)
|
||||
.frame(minWidth: 150.0)
|
||||
.background(Color.compound.bgSubtlePrimary)
|
||||
.clipShape(RoundedCornerShape(radius: 24.0, corners: .allCorners))
|
||||
.shadow(color: .black.opacity(0.25), radius: 10.0, y: 4.0)
|
||||
.shadow(color: .black.opacity(0.1), radius: 6.0, y: 4.0)
|
||||
.transition(toastTransition)
|
||||
}
|
||||
|
||||
@@ -48,11 +54,14 @@ struct UserIndicatorToastView: View {
|
||||
|
||||
struct UserIndicatorToastView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
VStack(spacing: 30) {
|
||||
UserIndicatorToastView(indicator: UserIndicator(title: "Successfully logged in",
|
||||
iconName: "checkmark"))
|
||||
|
||||
UserIndicatorToastView(indicator: UserIndicator(title: "Toast without icon"))
|
||||
|
||||
UserIndicatorToastView(indicator: UserIndicator(type: .toast(progress: .indeterminate),
|
||||
title: "Syncing"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user