Implement all emoji text message boosting

This commit is contained in:
Stefan Ceriu
2023-06-27 12:34:04 +03:00
committed by Stefan Ceriu
parent abe1df2524
commit 59fe200d8b
5 changed files with 151 additions and 12 deletions

View File

@@ -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 */,

View 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
}
}

View File

@@ -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 {

View File

@@ -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)
}
}
}

View 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)
}
}