RTL and LTR mixed languages fix for optimal timestamp support (#1055)

* fix

* fixed RTL and LTR timestamp for all the cases! also improved the testing

* changelog

* better and less convoluted solution

* Update ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift

Co-authored-by: Alfonso Grillo <alfogrillo@gmail.com>

* pr suggestion and removed prefix in reality it creates more issues than improvements, and is not really needed

---------

Co-authored-by: Alfonso Grillo <alfogrillo@gmail.com>
This commit is contained in:
Mauro
2023-06-09 13:49:02 +02:00
committed by GitHub
parent 7d14cfe2c5
commit de3997f6c4
8 changed files with 83 additions and 14 deletions

View File

@@ -617,6 +617,7 @@
EE6933C935080B4E0348A58B /* EmojiMartCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5C3AACCAA82392D08924496 /* EmojiMartCategory.swift */; };
EE8491AD81F47DF3C192497B /* DecorationTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 184CF8C196BE143AE226628D /* DecorationTimelineItemProtocol.swift */; };
EE8A37E2A1A77DE5CF941632 /* StateRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED983D4DCA5AFA6E1ED96099 /* StateRoomTimelineView.swift */; };
EEAE954289DE813A61656AE0 /* LayoutDirection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14D83B2B7CD5501A0089EFC /* LayoutDirection.swift */; };
EEB9C1555C63B93CA9C372C2 /* EmojiPickerScreenHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B5E29E9A22F45534FBD5B58 /* EmojiPickerScreenHeaderView.swift */; };
EEC40663922856C65D1E0DF5 /* KeychainControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB9C37196A4C79F24CE80C6 /* KeychainControllerTests.swift */; };
EF7924005216B8189898F370 /* BackgroundTaskProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA028DCD4157F9A1F999827 /* BackgroundTaskProtocol.swift */; };
@@ -1164,6 +1165,7 @@
C08E9043618AE5B0BF7B07E1 /* TemplateScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreenViewModelTests.swift; sourceTree = "<group>"; };
C0900BBF0A5D5D775E917C70 /* EventBasedMessageTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBasedMessageTimelineItemProtocol.swift; sourceTree = "<group>"; };
C1198B925F4A88DA74083662 /* OnboardingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewModel.swift; sourceTree = "<group>"; };
C14D83B2B7CD5501A0089EFC /* LayoutDirection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutDirection.swift; sourceTree = "<group>"; };
C2886615BEBAE33A0AA4D5F8 /* RoomScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenModels.swift; sourceTree = "<group>"; };
C2E9B841EE4878283ECDB554 /* InviteUsersScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreen.swift; sourceTree = "<group>"; };
C2F079B5DBD0D85FEA687AAE /* SDKGeneratedMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDKGeneratedMocks.swift; sourceTree = "<group>"; };
@@ -1878,6 +1880,7 @@
04DF593C3F7AF4B2FBAEB05D /* FileManager.swift */,
E26747B3154A5DBC3A7E24A5 /* Image.swift */,
4E2245243369B99216C7D84E /* ImageCache.swift */,
C14D83B2B7CD5501A0089EFC /* LayoutDirection.swift */,
2AFEF3AC64B1358083F76B8B /* List.swift */,
F72EFC8C634469F9262659C7 /* NSItemProvider.swift */,
95BAC0F6C9644336E9567EE6 /* NSRegularExpresion.swift */,
@@ -3935,6 +3938,7 @@
E3CA565A4B9704F191B191F0 /* JoinedRoomSize+MemberCount.swift in Sources */,
1FE593ECEC40A43789105D80 /* KeychainController.swift in Sources */,
CB99B0FA38A4AC596F38CC13 /* KeychainControllerProtocol.swift in Sources */,
EEAE954289DE813A61656AE0 /* LayoutDirection.swift in Sources */,
42B084FDE621FBEE433AF444 /* LegalInformationScreen.swift in Sources */,
9EBDC79CAC9B63A0D626E333 /* LegalInformationScreenCoordinator.swift in Sources */,
F40B097470D3110DFDB1FAAA /* LegalInformationScreenModels.swift in Sources */,

View File

@@ -0,0 +1,30 @@
//
// Copyright 2023 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 LayoutDirection {
var isolateLayoutUnicodeString: String {
switch self {
case .leftToRight:
return "\u{2066}"
case .rightToLeft:
return "\u{2067}"
default:
return ""
}
}
}

View File

@@ -64,15 +64,12 @@ extension String {
}
extension String {
static func generateBreakableWhitespaceEnd(whitespaceCount: Int, isRTL: Bool) -> String {
static func generateBreakableWhitespaceEnd(whitespaceCount: Int, layoutDirection: LayoutDirection) -> String {
guard whitespaceCount > 0 else {
return ""
}
var whiteSpaces = ""
if isRTL {
whiteSpaces = "\u{202e}"
}
var whiteSpaces = layoutDirection.isolateLayoutUnicodeString
// fixed size whitespace of size 1/3 em per character
whiteSpaces += String(repeating: "\u{2004}", count: whitespaceCount)

View File

@@ -64,6 +64,18 @@ struct TimelineItemStyler_Previews: PreviewProvider {
return result
}()
static let ltrString = TextRoomTimelineItem(id: UUID().uuidString, timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "house!"))
static let rtlString = TextRoomTimelineItem(id: UUID().uuidString, timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "באמת!"))
static let ltrStringThatContainsRtl = TextRoomTimelineItem(id: UUID().uuidString, timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "house! -- באמת‏! -- house!"))
static let rtlStringThatContainsLtr = TextRoomTimelineItem(id: UUID().uuidString, timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "באמת‏! -- house! -- באמת!"))
static let ltrStringThatFinishesInRtl = TextRoomTimelineItem(id: UUID().uuidString, timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "house! -- באמת!"))
static let rtlStringThatFinishesInLtr = TextRoomTimelineItem(id: UUID().uuidString, timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "באמת‏! -- house!"))
static var testView: some View {
VStack {
TextRoomTimelineView(timelineItem: sent)
@@ -74,13 +86,37 @@ struct TimelineItemStyler_Previews: PreviewProvider {
}
}
static var languagesTestView: some View {
VStack {
TextRoomTimelineView(timelineItem: ltrString)
TextRoomTimelineView(timelineItem: rtlString)
TextRoomTimelineView(timelineItem: ltrStringThatContainsRtl)
TextRoomTimelineView(timelineItem: rtlStringThatContainsLtr)
TextRoomTimelineView(timelineItem: ltrStringThatFinishesInRtl)
TextRoomTimelineView(timelineItem: rtlStringThatFinishesInLtr)
}
}
static var previews: some View {
testView
.environmentObject(viewModel.context)
.environment(\.timelineStyle, .bubbles)
.previewDisplayName("Bubbles")
testView
.environmentObject(viewModel.context)
.environment(\.timelineStyle, .plain)
.previewDisplayName("Plain")
languagesTestView
.environmentObject(viewModel.context)
.environment(\.timelineStyle, .bubbles)
.previewDisplayName("Bubbles LTR with different layout languages")
languagesTestView
.environmentObject(viewModel.context)
.environment(\.timelineStyle, .bubbles)
.environment(\.layoutDirection, .rightToLeft)
.previewDisplayName("Bubbles RTL with different layout languages")
}
}

View File

@@ -36,7 +36,7 @@ struct FormattedBodyText: View {
// These is needed to create the slightly off inlined timestamp effect
private var additionalWhitespacesSuffix: String {
.generateBreakableWhitespaceEnd(whitespaceCount: additionalWhitespacesCount, isRTL: layoutDirection == .rightToLeft)
.generateBreakableWhitespaceEnd(whitespaceCount: additionalWhitespacesCount, layoutDirection: layoutDirection)
}
var body: some View {

View File

@@ -61,23 +61,23 @@ class StringTests: XCTestCase {
func testGenerateBreakableWhitespaceEnd() {
var count = 5
var result = String(repeating: "\u{2004}", count: count) + "\u{2800}"
XCTAssertEqual(String.generateBreakableWhitespaceEnd(whitespaceCount: count, isRTL: false), result)
var result = "\u{2066}" + String(repeating: "\u{2004}", count: count) + "\u{2800}"
XCTAssertEqual(String.generateBreakableWhitespaceEnd(whitespaceCount: count, layoutDirection: .leftToRight), result)
count = 3
result = String(repeating: "\u{2004}", count: count) + "\u{2800}"
XCTAssertEqual(String.generateBreakableWhitespaceEnd(whitespaceCount: count, isRTL: false), result)
result = "\u{2066}" + String(repeating: "\u{2004}", count: count) + "\u{2800}"
XCTAssertEqual(String.generateBreakableWhitespaceEnd(whitespaceCount: count, layoutDirection: .leftToRight), result)
count = 0
result = ""
XCTAssertEqual(String.generateBreakableWhitespaceEnd(whitespaceCount: count, isRTL: false), result)
XCTAssertEqual(String.generateBreakableWhitespaceEnd(whitespaceCount: count, layoutDirection: .leftToRight), result)
count = 4
result = "\u{202e}" + String(repeating: "\u{2004}", count: count) + "\u{2800}"
XCTAssertEqual(String.generateBreakableWhitespaceEnd(whitespaceCount: count, isRTL: true), result)
result = "\u{2067}" + String(repeating: "\u{2004}", count: count) + "\u{2800}"
XCTAssertEqual(String.generateBreakableWhitespaceEnd(whitespaceCount: count, layoutDirection: .rightToLeft), result)
count = 0
result = ""
XCTAssertEqual(String.generateBreakableWhitespaceEnd(whitespaceCount: count, isRTL: true), result)
XCTAssertEqual(String.generateBreakableWhitespaceEnd(whitespaceCount: count, layoutDirection: .rightToLeft), result)
}
}

1
changelog.d/1052.feature Normal file
View File

@@ -0,0 +1 @@
Read Receipts with avatars will be displayed at the bottom of the messages (only for Nightly, can be enabled in developer settings).

View File

@@ -0,0 +1 @@
Improved timestamp rendering for RTL and bidirectional mixed text.