Implement all emoji text message boosting
This commit is contained in:
committed by
Stefan Ceriu
parent
abe1df2524
commit
59fe200d8b
@@ -630,6 +630,7 @@
|
||||
E3291AD16D7A5CB14781819C /* UserNotificationCenterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45D8149FDDA0315CDC553B4B /* UserNotificationCenterProtocol.swift */; };
|
||||
E3CA565A4B9704F191B191F0 /* JoinedRoomSize+MemberCount.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBF9AEA706926DD0DA2B954C /* JoinedRoomSize+MemberCount.swift */; };
|
||||
E3E1E255DC8CB34BD8573E0D /* UserIndicatorControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A12D3B1BCF920880CA8BBB6B /* UserIndicatorControllerProtocol.swift */; };
|
||||
E45C9FA22BC13B477FD3B4AC /* EmojiDetection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D99730313BEBF08CDE81EE3 /* EmojiDetection.swift */; };
|
||||
E481C8FDCB6C089963C95344 /* DeviceKit in Frameworks */ = {isa = PBXBuildFile; productRef = BC01130651CB23340B899032 /* DeviceKit */; };
|
||||
E570117376826665640F0CFD /* SessionVerificationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B16CAF20C9AC874A210E2DCF /* SessionVerificationScreenViewModelProtocol.swift */; };
|
||||
E571163060CBE87D82CE24FD /* NSESettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9DFC0FBA0FC6FC4DC0FC9FC /* NSESettings.swift */; };
|
||||
@@ -681,6 +682,7 @@
|
||||
F54E2D6CAD96E1AC15BC526F /* MessageForwardingScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E60332509665C00179ACF6 /* MessageForwardingScreenViewModel.swift */; };
|
||||
F5D2270B5021D521C0D22E11 /* FlowCoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B9FCA1CFD07B8CF9BD21266 /* FlowCoordinatorProtocol.swift */; };
|
||||
F656F92A63D3DC1978D79427 /* Compound in Frameworks */ = {isa = PBXBuildFile; productRef = 07FEEEDB11543A7DED420F04 /* Compound */; };
|
||||
F697284B9B5F2C00CFEA3B12 /* EmojiDetectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A58E93D91DE3288010390DEE /* EmojiDetectionTests.swift */; };
|
||||
F6F49E37272AD7397CD29A01 /* HomeScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */; };
|
||||
F7567DD6635434E8C563BF85 /* AnalyticsClientProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3B97591B2D3D4D67553506D /* AnalyticsClientProtocol.swift */; };
|
||||
F777C6FEE7D106136E2ED2B2 /* MessageForwardingScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F6E6EDC4BBF962B2ED595A4 /* MessageForwardingScreenViewModelTests.swift */; };
|
||||
@@ -996,6 +998,7 @@
|
||||
5C7C7CFA6B2A62A685FF6CE3 /* DeveloperOptionsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
5D26A086A8278D39B5756D6F /* project.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = project.yml; sourceTree = "<group>"; };
|
||||
5D2D0A6F1ABC99D29462FB84 /* AuthenticationCoordinatorUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationCoordinatorUITests.swift; sourceTree = "<group>"; };
|
||||
5D99730313BEBF08CDE81EE3 /* EmojiDetection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiDetection.swift; sourceTree = "<group>"; };
|
||||
5DE8D25D6A91030175D52A20 /* RoomTimelineItemProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProperties.swift; sourceTree = "<group>"; };
|
||||
5EB2CAA266B921D128C35710 /* LegalInformationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
5F4134FEFE4EB55759017408 /* UserSessionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionProtocol.swift; sourceTree = "<group>"; };
|
||||
@@ -1165,6 +1168,7 @@
|
||||
A436057DBEA1A23CA8CB1FD7 /* UIFont+AttributedStringBuilder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIFont+AttributedStringBuilder.h"; sourceTree = "<group>"; };
|
||||
A4756C5A8C8649AD6C10C615 /* MockUserSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUserSession.swift; sourceTree = "<group>"; };
|
||||
A58DB8EFB91BE920762025D0 /* NCE.appex */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "wrapper.app-extension"; path = NCE.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
A58E93D91DE3288010390DEE /* EmojiDetectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiDetectionTests.swift; sourceTree = "<group>"; };
|
||||
A65F140F9FE5E8D4DAEFF354 /* RoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxy.swift; sourceTree = "<group>"; };
|
||||
A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogConfiguration.swift; sourceTree = "<group>"; };
|
||||
A73A07BAEDD74C48795A996A /* AsyncSequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncSequence.swift; sourceTree = "<group>"; };
|
||||
@@ -2357,6 +2361,7 @@
|
||||
3B5E97E9615A158C76B2AB77 /* DateTests.swift */,
|
||||
6D0A27607AB09784C8501B5C /* DeveloperOptionsScreenViewModelTests.swift */,
|
||||
DBFEAC3AC691CBB84983E275 /* ElementXTests.swift */,
|
||||
A58E93D91DE3288010390DEE /* EmojiDetectionTests.swift */,
|
||||
9BF9E3E6A23180EC05F06460 /* EmojiMartJSONLoaderTests.swift */,
|
||||
099F2D36C141D845A445B1E6 /* EmojiProviderTests.swift */,
|
||||
84B7A28A6606D58D1E38C55A /* ExpiringTaskRunnerTests.swift */,
|
||||
@@ -3040,6 +3045,7 @@
|
||||
AE52983FAFB4E0998C00EE8A /* CancellableTask.swift */,
|
||||
127A57D053CE8C87B5EFB089 /* Consumable.swift */,
|
||||
127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */,
|
||||
5D99730313BEBF08CDE81EE3 /* EmojiDetection.swift */,
|
||||
7B25F959A434BB9923A3223F /* ExpiringTaskRunner.swift */,
|
||||
6A580295A56B55A856CC4084 /* InfoPlistReader.swift */,
|
||||
6AD1A853D605C2146B0DC028 /* MatrixEntityRegex.swift */,
|
||||
@@ -3890,6 +3896,7 @@
|
||||
CD0088B763CD970CF1CBF8CB /* DateTests.swift in Sources */,
|
||||
864C69CF951BF36D25BE0C03 /* DeveloperOptionsScreenViewModelTests.swift in Sources */,
|
||||
9C45CE85325CD591DADBC4CA /* ElementXTests.swift in Sources */,
|
||||
F697284B9B5F2C00CFEA3B12 /* EmojiDetectionTests.swift in Sources */,
|
||||
501304F26B52DF7024011B6C /* EmojiMartJSONLoaderTests.swift in Sources */,
|
||||
25618589E0DE0F1E95FC7B5C /* EmojiProviderTests.swift in Sources */,
|
||||
71B62C48B8079D49F3FBC845 /* ExpiringTaskRunnerTests.swift in Sources */,
|
||||
@@ -4060,6 +4067,7 @@
|
||||
D8CFF02C2730EE5BC4F17ABF /* ElementToggleStyle.swift in Sources */,
|
||||
7C1A7B594B2F8143F0DD0005 /* ElementXAttributeScope.swift in Sources */,
|
||||
7361B011A79BF723D8C9782B /* EmojiCategory.swift in Sources */,
|
||||
E45C9FA22BC13B477FD3B4AC /* EmojiDetection.swift in Sources */,
|
||||
D5C805F49B2C75DC3793E780 /* EmojiItem.swift in Sources */,
|
||||
3A08584ECDD4A4541DBF21F8 /* EmojiLoaderProtocol.swift in Sources */,
|
||||
EE6933C935080B4E0348A58B /* EmojiMartCategory.swift in Sources */,
|
||||
|
||||
66
ElementX/Sources/Other/EmojiDetection.swift
Normal file
66
ElementX/Sources/Other/EmojiDetection.swift
Normal file
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// 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 Foundation
|
||||
|
||||
extension UnicodeScalar {
|
||||
var isZeroWidthJoiner: Bool {
|
||||
value == 8205
|
||||
}
|
||||
|
||||
var isKeycap: Bool {
|
||||
value == 8419
|
||||
}
|
||||
|
||||
var isNumber: Bool {
|
||||
switch value {
|
||||
case 48...57:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
var containsOnlyEmoji: Bool {
|
||||
guard !isEmpty else {
|
||||
return false
|
||||
}
|
||||
|
||||
var emojiMarkerCount = 0
|
||||
for scalar in unicodeScalars {
|
||||
let isEmojiMarker = scalar.properties.isEmoji ||
|
||||
scalar.properties.isEmojiPresentation ||
|
||||
scalar.isZeroWidthJoiner ||
|
||||
scalar.properties.isDefaultIgnorableCodePoint ||
|
||||
scalar.isKeycap
|
||||
|
||||
guard isEmojiMarker else {
|
||||
return false
|
||||
}
|
||||
|
||||
emojiMarkerCount += 1
|
||||
}
|
||||
|
||||
// Plain numbers like 0 return true for .isEmoji. We don't want that
|
||||
let markersRequiringSiblings = unicodeScalars.filter {
|
||||
$0.properties.isEmoji && $0.isNumber
|
||||
}
|
||||
|
||||
return markersRequiringSiblings.count != emojiMarkerCount && emojiMarkerCount == unicodeScalars.count
|
||||
}
|
||||
}
|
||||
@@ -22,16 +22,35 @@ struct FormattedBodyText: View {
|
||||
|
||||
private let attributedString: AttributedString
|
||||
private let additionalWhitespacesCount: Int
|
||||
private let boostEmojiSize: Bool
|
||||
|
||||
private var attributedComponents: [AttributedStringBuilderComponent] {
|
||||
var attributedString = attributedString
|
||||
attributedString.append(AttributedString(stringLiteral: additionalWhitespacesSuffix))
|
||||
return attributedString.formattedComponents
|
||||
var adjustedAttributedString = attributedString
|
||||
adjustedAttributedString.append(AttributedString(stringLiteral: additionalWhitespacesSuffix))
|
||||
|
||||
let string = String(attributedString.characters)
|
||||
|
||||
if boostEmojiSize,
|
||||
string.containsOnlyEmoji,
|
||||
let range = adjustedAttributedString.range(of: string) {
|
||||
adjustedAttributedString[range].font = .system(size: 48.0)
|
||||
}
|
||||
|
||||
return adjustedAttributedString.formattedComponents
|
||||
}
|
||||
|
||||
init(attributedString: AttributedString, additionalWhitespacesCount: Int = 0) {
|
||||
init(attributedString: AttributedString,
|
||||
additionalWhitespacesCount: Int = 0,
|
||||
boostEmojiSize: Bool = false) {
|
||||
self.attributedString = attributedString
|
||||
self.additionalWhitespacesCount = additionalWhitespacesCount
|
||||
self.boostEmojiSize = boostEmojiSize
|
||||
}
|
||||
|
||||
init(text: String, additionalWhitespacesCount: Int = 0, boostEmojiSize: Bool = false) {
|
||||
self.init(attributedString: AttributedString(text),
|
||||
additionalWhitespacesCount: additionalWhitespacesCount,
|
||||
boostEmojiSize: boostEmojiSize)
|
||||
}
|
||||
|
||||
// These is needed to create the slightly off inlined timestamp effect
|
||||
@@ -123,12 +142,6 @@ struct FormattedBodyText: View {
|
||||
}
|
||||
}
|
||||
|
||||
extension FormattedBodyText {
|
||||
init(text: String, additionalWhitespacesCount: Int = 0) {
|
||||
self.init(attributedString: AttributedString(text), additionalWhitespacesCount: additionalWhitespacesCount)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct FormattedBodyText_Previews: PreviewProvider {
|
||||
|
||||
@@ -24,9 +24,13 @@ struct TextRoomTimelineView: View, TextBasedRoomTimelineViewProtocol {
|
||||
var body: some View {
|
||||
TimelineStyler(timelineItem: timelineItem) {
|
||||
if let attributedString = timelineItem.content.formattedBody {
|
||||
FormattedBodyText(attributedString: attributedString, additionalWhitespacesCount: additionalWhitespaces)
|
||||
FormattedBodyText(attributedString: attributedString,
|
||||
additionalWhitespacesCount: additionalWhitespaces,
|
||||
boostEmojiSize: true)
|
||||
} else {
|
||||
FormattedBodyText(text: timelineItem.body, additionalWhitespacesCount: additionalWhitespaces)
|
||||
FormattedBodyText(text: timelineItem.body,
|
||||
additionalWhitespacesCount: additionalWhitespaces,
|
||||
boostEmojiSize: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
48
UnitTests/Sources/EmojiDetectionTests.swift
Normal file
48
UnitTests/Sources/EmojiDetectionTests.swift
Normal file
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// 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 XCTest
|
||||
|
||||
@testable import ElementX
|
||||
|
||||
class EmojiDetectionTests: XCTestCase {
|
||||
func testEmojiDetection() {
|
||||
XCTAssertTrue("👨👩👦".containsOnlyEmoji)
|
||||
XCTAssertTrue("1️⃣".containsOnlyEmoji)
|
||||
XCTAssertTrue("🚀".containsOnlyEmoji)
|
||||
XCTAssertTrue("👳🏾♂️".containsOnlyEmoji)
|
||||
XCTAssertTrue("🪩".containsOnlyEmoji)
|
||||
|
||||
XCTAssertTrue("👨👩👦1️⃣🚀👳🏾♂️🪩".containsOnlyEmoji)
|
||||
|
||||
XCTAssertFalse(" 👨👩👦".containsOnlyEmoji)
|
||||
XCTAssertFalse(" 👨👩👦 ".containsOnlyEmoji)
|
||||
XCTAssertFalse("👨👩👦 ".containsOnlyEmoji)
|
||||
XCTAssertFalse("Ciao 👨👩👦 peeps".containsOnlyEmoji)
|
||||
|
||||
XCTAssertFalse("0".containsOnlyEmoji)
|
||||
XCTAssertFalse("1".containsOnlyEmoji)
|
||||
XCTAssertFalse("5".containsOnlyEmoji)
|
||||
XCTAssertFalse("000".containsOnlyEmoji)
|
||||
|
||||
XCTAssertTrue("👍".containsOnlyEmoji)
|
||||
XCTAssertTrue("🫱🏼🫲🏾".containsOnlyEmoji)
|
||||
XCTAssertTrue("👁❤️🍝".containsOnlyEmoji)
|
||||
XCTAssertFalse("🙂 ".containsOnlyEmoji)
|
||||
XCTAssertFalse("Hello 👋".containsOnlyEmoji)
|
||||
XCTAssertFalse("Thanks".containsOnlyEmoji)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user