mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 21:39:12 +00:00
Suggestions view design improvements (#1955)
* snapshot tests * making the padding of the composer internal * setting the max width using the line fragment * setting the max width using the line fragment * Update ElementX/Sources/Screens/ComposerToolbar/View/CompletionSuggestionView.swift Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com> * updated tests and pr suggestions * better stencil --------- Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com>
This commit is contained in:
parent
f9ad44f96a
commit
1ba6fb0a60
@ -262,7 +262,7 @@
|
|||||||
{
|
{
|
||||||
"identity" : "swiftui-introspect",
|
"identity" : "swiftui-introspect",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/siteline/SwiftUI-Introspect.git",
|
"location" : "https://github.com/siteline/SwiftUI-Introspect",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "b94da693e57eaf79d16464b8b7c90d09cba4e290",
|
"revision" : "b94da693e57eaf79d16464b8b7c90d09cba4e290",
|
||||||
"version" : "0.9.2"
|
"version" : "0.9.2"
|
||||||
|
@ -51,6 +51,7 @@ enum UserAvatarSizeOnScreen {
|
|||||||
case inviteUsers
|
case inviteUsers
|
||||||
case readReceipt
|
case readReceipt
|
||||||
case editUserDetails
|
case editUserDetails
|
||||||
|
case suggestions
|
||||||
|
|
||||||
var value: CGFloat {
|
var value: CGFloat {
|
||||||
switch self {
|
switch self {
|
||||||
@ -60,6 +61,8 @@ enum UserAvatarSizeOnScreen {
|
|||||||
return 32
|
return 32
|
||||||
case .home:
|
case .home:
|
||||||
return 32
|
return 32
|
||||||
|
case .suggestions:
|
||||||
|
return 32
|
||||||
case .settings:
|
case .settings:
|
||||||
return 52
|
return 52
|
||||||
case .roomDetails:
|
case .roomDetails:
|
||||||
|
@ -25,5 +25,6 @@ enum PillConstants {
|
|||||||
/// Used by the WYSIWYG as the urlString value to identify @room mentions
|
/// Used by the WYSIWYG as the urlString value to identify @room mentions
|
||||||
static let composerAtRoomURLString = "#"
|
static let composerAtRoomURLString = "#"
|
||||||
|
|
||||||
static let maxWidth: CGFloat = 235
|
/// Used only to mock the max width in previews since the real max width is calculated by the line fragment width
|
||||||
|
static let mockMaxWidth: CGFloat = 235
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ final class PillTextAttachment: NSTextAttachment {
|
|||||||
let fontData = pillData.fontData
|
let fontData = pillData.fontData
|
||||||
// Align the pill text vertically with the surrounding text.
|
// Align the pill text vertically with the surrounding text.
|
||||||
rect.origin.y = fontData.descender + (fontData.lineHeight - rect.height) / 2.0
|
rect.origin.y = fontData.descender + (fontData.lineHeight - rect.height) / 2.0
|
||||||
|
rect.size.width = min(rect.size.width, lineFrag.width)
|
||||||
return rect
|
return rect
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,6 @@ struct PillView: View {
|
|||||||
.padding(.trailing, 6)
|
.padding(.trailing, 6)
|
||||||
.padding(.vertical, 1)
|
.padding(.vertical, 1)
|
||||||
.background { Capsule().foregroundColor(backgroundColor) }
|
.background { Capsule().foregroundColor(backgroundColor) }
|
||||||
.frame(maxWidth: PillConstants.maxWidth)
|
|
||||||
.onChange(of: context.viewState.displayText) { _ in
|
.onChange(of: context.viewState.displayText) { _ in
|
||||||
didChangeText()
|
didChangeText()
|
||||||
}
|
}
|
||||||
@ -55,18 +54,23 @@ struct PillView_Previews: PreviewProvider, TestablePreview {
|
|||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
PillView(imageProvider: mockMediaProvider,
|
PillView(imageProvider: mockMediaProvider,
|
||||||
context: PillContext.mock(type: .loadUser(isOwn: false))) { }
|
context: PillContext.mock(type: .loadUser(isOwn: false))) { }
|
||||||
|
.frame(maxWidth: PillConstants.mockMaxWidth)
|
||||||
.previewDisplayName("Loading")
|
.previewDisplayName("Loading")
|
||||||
PillView(imageProvider: mockMediaProvider,
|
PillView(imageProvider: mockMediaProvider,
|
||||||
context: PillContext.mock(type: .loadUser(isOwn: true))) { }
|
context: PillContext.mock(type: .loadUser(isOwn: true))) { }
|
||||||
|
.frame(maxWidth: PillConstants.mockMaxWidth)
|
||||||
.previewDisplayName("Loading Own")
|
.previewDisplayName("Loading Own")
|
||||||
PillView(imageProvider: mockMediaProvider,
|
PillView(imageProvider: mockMediaProvider,
|
||||||
context: PillContext.mock(type: .loadedUser(isOwn: false))) { }
|
context: PillContext.mock(type: .loadedUser(isOwn: false))) { }
|
||||||
|
.frame(maxWidth: PillConstants.mockMaxWidth)
|
||||||
.previewDisplayName("Loaded Long")
|
.previewDisplayName("Loaded Long")
|
||||||
PillView(imageProvider: mockMediaProvider,
|
PillView(imageProvider: mockMediaProvider,
|
||||||
context: PillContext.mock(type: .loadedUser(isOwn: true))) { }
|
context: PillContext.mock(type: .loadedUser(isOwn: true))) { }
|
||||||
|
.frame(maxWidth: PillConstants.mockMaxWidth)
|
||||||
.previewDisplayName("Loaded Long Own")
|
.previewDisplayName("Loaded Long Own")
|
||||||
PillView(imageProvider: mockMediaProvider,
|
PillView(imageProvider: mockMediaProvider,
|
||||||
context: PillContext.mock(type: .allUsers)) { }
|
context: PillContext.mock(type: .allUsers)) { }
|
||||||
|
.frame(maxWidth: PillConstants.mockMaxWidth)
|
||||||
.previewDisplayName("All Users")
|
.previewDisplayName("All Users")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,19 +23,11 @@ struct CompletionSuggestionView: View {
|
|||||||
|
|
||||||
private enum Constants {
|
private enum Constants {
|
||||||
static let topPadding: CGFloat = 8.0
|
static let topPadding: CGFloat = 8.0
|
||||||
static let listItemPadding: CGFloat = 4.0
|
static let listItemPadding: CGFloat = 6.0
|
||||||
static let lineSpacing: CGFloat = 10.0
|
// added by the list itself when presenting the divider
|
||||||
static let maxHeight: CGFloat = 300.0
|
static let listItemSpacing: CGFloat = 4.0
|
||||||
|
static let leadingPadding: CGFloat = 16.0
|
||||||
static let maxVisibleRows = 4
|
static let maxVisibleRows = 4
|
||||||
|
|
||||||
/*
|
|
||||||
As of iOS 16.0, SwiftUI's List uses `UICollectionView` instead
|
|
||||||
of `UITableView` internally, this value is an adjustment to apply
|
|
||||||
to the list items in order to be as close as possible as the
|
|
||||||
`UITableView` display.
|
|
||||||
*/
|
|
||||||
@available(iOS 16.0, *)
|
|
||||||
static let collectionViewPaddingCorrection: CGFloat = -5.0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Public
|
// MARK: Public
|
||||||
@ -73,16 +65,16 @@ struct CompletionSuggestionView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.modifier(ListItemPaddingModifier(isFirst: items.first?.id == item.id))
|
.modifier(ListItemPaddingModifier(isFirst: items.first?.id == item.id))
|
||||||
|
.listRowInsets(.init(top: 0, leading: Constants.leadingPadding, bottom: 0, trailing: 0))
|
||||||
}
|
}
|
||||||
.listStyle(PlainListStyle())
|
.listStyle(PlainListStyle())
|
||||||
.frame(height: min(Constants.maxHeight,
|
.frame(height: min(contentHeightForRowCount(Constants.maxVisibleRows),
|
||||||
min(contentHeightForRowCount(Constants.maxVisibleRows),
|
contentHeightForRowCount(items.count)))
|
||||||
contentHeightForRowCount(items.count))))
|
|
||||||
.background(Color.compound.bgCanvasDefault)
|
.background(Color.compound.bgCanvasDefault)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func contentHeightForRowCount(_ count: Int) -> CGFloat {
|
private func contentHeightForRowCount(_ count: Int) -> CGFloat {
|
||||||
(prototypeListItemFrame.height + (Constants.listItemPadding * 2) + Constants.lineSpacing) * CGFloat(count) + Constants.topPadding
|
(prototypeListItemFrame.height + Constants.listItemPadding * 2 + Constants.listItemSpacing) * CGFloat(count) - Constants.listItemSpacing / 2 + Constants.topPadding - Constants.listItemPadding
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct ListItemPaddingModifier: ViewModifier {
|
private struct ListItemPaddingModifier: ViewModifier {
|
||||||
@ -93,12 +85,8 @@ struct CompletionSuggestionView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
func body(content: Content) -> some View {
|
||||||
var topPadding: CGFloat = isFirst ? Constants.listItemPadding + Constants.topPadding : Constants.listItemPadding
|
var topPadding: CGFloat = isFirst ? Constants.topPadding : Constants.listItemPadding
|
||||||
var bottomPadding: CGFloat = Constants.listItemPadding
|
var bottomPadding: CGFloat = Constants.listItemPadding
|
||||||
if #available(iOS 16.0, *) {
|
|
||||||
topPadding += Constants.collectionViewPaddingCorrection
|
|
||||||
bottomPadding += Constants.collectionViewPaddingCorrection
|
|
||||||
}
|
|
||||||
|
|
||||||
return content
|
return content
|
||||||
.padding(.top, topPadding)
|
.padding(.top, topPadding)
|
||||||
|
@ -46,6 +46,8 @@ struct ComposerToolbar: View {
|
|||||||
bottomBar
|
bottomBar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.padding(.leading, 5)
|
||||||
|
.padding(.trailing, 8)
|
||||||
.background {
|
.background {
|
||||||
ViewFrameReader(frame: $frame)
|
ViewFrameReader(frame: $frame)
|
||||||
}
|
}
|
||||||
|
@ -21,13 +21,13 @@ struct MentionSuggestionItemView: View {
|
|||||||
let item: MentionSuggestionItem
|
let item: MentionSuggestionItem
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center, spacing: 16) {
|
||||||
LoadableAvatarImage(url: item.avatarURL,
|
LoadableAvatarImage(url: item.avatarURL,
|
||||||
name: item.displayName,
|
name: item.displayName,
|
||||||
contentID: item.id,
|
contentID: item.id,
|
||||||
avatarSize: .custom(42),
|
avatarSize: .user(on: .suggestions),
|
||||||
imageProvider: imageProvider)
|
imageProvider: imageProvider)
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
Text(item.displayName ?? item.id)
|
Text(item.displayName ?? item.id)
|
||||||
.font(.compound.bodyLG)
|
.font(.compound.bodyLG)
|
||||||
.foregroundColor(.compound.textPrimary)
|
.foregroundColor(.compound.textPrimary)
|
||||||
|
@ -35,8 +35,6 @@ struct RoomScreen: View {
|
|||||||
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
|
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
|
||||||
.safeAreaInset(edge: .bottom, spacing: 0) {
|
.safeAreaInset(edge: .bottom, spacing: 0) {
|
||||||
composerToolbar
|
composerToolbar
|
||||||
.padding(.leading, 5)
|
|
||||||
.padding(.trailing, 8)
|
|
||||||
.padding(.bottom, composerToolbarContext.composerActionsEnabled ? 8 : 12)
|
.padding(.bottom, composerToolbarContext.composerActionsEnabled ? 8 : 12)
|
||||||
.background {
|
.background {
|
||||||
if composerToolbarContext.composerActionsEnabled {
|
if composerToolbarContext.composerActionsEnabled {
|
||||||
|
@ -60,9 +60,7 @@ class PreviewTests: XCTestCase {
|
|||||||
file: file{% endif %},
|
file: file{% endif %},
|
||||||
testName: testName)
|
testName: testName)
|
||||||
|
|
||||||
if let failure,
|
if let failure {
|
||||||
!failure.contains("No reference was found on disk."),
|
|
||||||
!failure.contains("to test against the newly-recorded snapshot") {
|
|
||||||
XCTFail(failure)
|
XCTFail(failure)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BIN
UnitTests/__Snapshots__/PreviewTests/test_completionSuggestion.1.png
(Stored with Git LFS)
BIN
UnitTests/__Snapshots__/PreviewTests/test_completionSuggestion.1.png
(Stored with Git LFS)
Binary file not shown.
BIN
UnitTests/__Snapshots__/PreviewTests/test_completionSuggestion.2.png
(Stored with Git LFS)
BIN
UnitTests/__Snapshots__/PreviewTests/test_completionSuggestion.2.png
(Stored with Git LFS)
Binary file not shown.
BIN
UnitTests/__Snapshots__/PreviewTests/test_composerToolbar.1.png
(Stored with Git LFS)
BIN
UnitTests/__Snapshots__/PreviewTests/test_composerToolbar.1.png
(Stored with Git LFS)
Binary file not shown.
BIN
UnitTests/__Snapshots__/PreviewTests/test_composerToolbar.Voice-Message.png
(Stored with Git LFS)
BIN
UnitTests/__Snapshots__/PreviewTests/test_composerToolbar.Voice-Message.png
(Stored with Git LFS)
Binary file not shown.
BIN
UnitTests/__Snapshots__/PreviewTests/test_composerToolbar.With-Suggestions.png
(Stored with Git LFS)
BIN
UnitTests/__Snapshots__/PreviewTests/test_composerToolbar.With-Suggestions.png
(Stored with Git LFS)
Binary file not shown.
BIN
UnitTests/__Snapshots__/PreviewTests/test_mentionSuggestionItemView.1.png
(Stored with Git LFS)
BIN
UnitTests/__Snapshots__/PreviewTests/test_mentionSuggestionItemView.1.png
(Stored with Git LFS)
Binary file not shown.
BIN
UnitTests/__Snapshots__/PreviewTests/test_mentionSuggestionItemView.2.png
(Stored with Git LFS)
BIN
UnitTests/__Snapshots__/PreviewTests/test_mentionSuggestionItemView.2.png
(Stored with Git LFS)
Binary file not shown.
BIN
UnitTests/__Snapshots__/PreviewTests/test_secureBackupLogoutConfirmationScreen.1.png
(Stored with Git LFS)
Normal file
BIN
UnitTests/__Snapshots__/PreviewTests/test_secureBackupLogoutConfirmationScreen.1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user