Various tweaks (#381)

* Add the message delivery status to the plain timeline styler and display it outside of the main content

* Fix glitchy timeline scroll to bottom button

* Fixed bubbled reactions padding

* Simplified the emoji picker, double tapping a timeline item directly opens it now and added a context menu option. Linked it to rust side reaction sending

* Fix the sliding sync cold cache: treat invalidated rooms as filled

* Make splash screen view full screen

* Fix the offline indicator popping up when first setting up the network monitor

* Expose presentationDetents on the NavigationStackCoordinator and start using them for the EmojiPicker

* Fix link tint color

* Linked TimelineReactionsView reaction sending

* Remove now unused/unnecessary classes

* Add changelog

* Fix formatting issue
This commit is contained in:
Stefan Ceriu 2022-12-21 11:18:45 +02:00 committed by GitHub
parent 5b9d45e461
commit 71f1f044c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 170 additions and 534 deletions

View File

@ -29,7 +29,6 @@
09AAF04B27732046C755D914 /* SoftLogoutViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32C5DAA1773F57653BF1C4F9 /* SoftLogoutViewModelTests.swift */; };
09C83DDDB07C28364F325209 /* MockRoomTimelineController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52D7074991B3267B26D89B22 /* MockRoomTimelineController.swift */; };
0AE0AB1952F186EB86719B4F /* HomeScreenRoomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */; };
0B1F80C2BF7D223159FBA82C /* ImageAnonymizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6045E825AE900A92D61FEFF0 /* ImageAnonymizerTests.swift */; };
0BEFE400B4802FE8C9DB39B3 /* FilePreviewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62BDF0FF4F59AF6EA858B70B /* FilePreviewViewModel.swift */; };
0BFA67AFD757EE2BA569836A /* ScrollViewAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53482ECA4B6633961EC224F5 /* ScrollViewAdapter.swift */; };
0C38C3E771B472E27295339D /* SessionVerificationModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BB9A17AC512A7EF4B106E5 /* SessionVerificationModels.swift */; };
@ -208,7 +207,6 @@
6CA81428F0970785CDCC5E86 /* UserNotificationToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F31A4E5941ACBA4BB9FEF94C /* UserNotificationToastView.swift */; };
6D046D653DA28ADF1E6E59A4 /* BackgroundTaskServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE73D571D4F9C36DD45255A /* BackgroundTaskServiceProtocol.swift */; };
6E6E0AAF6C44C0B117EBBE5A /* SlidingSyncViewProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41F3B445BD6EF1C751806B22 /* SlidingSyncViewProxy.swift */; };
6EA61FCA55D950BDE326A1A7 /* ImageAnonymizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12A626D74BBE9F4A60763B45 /* ImageAnonymizer.swift */; };
6F2AB43A1EFAD8A97AF41A15 /* DeviceKit in Frameworks */ = {isa = PBXBuildFile; productRef = A7CA6F33C553805035C3B114 /* DeviceKit */; };
6FC10A00D268FCD48B631E37 /* ViewFrameReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFF7BF82A950B91BC5469E91 /* ViewFrameReader.swift */; };
7002C55A4C917F3715765127 /* MediaProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C888BCD78E2A55DCE364F160 /* MediaProviderProtocol.swift */; };
@ -303,7 +301,6 @@
9CCC77C31CB399661A034739 /* UserProperties+Element.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A6C4BE591FE5C38CE9C7EF3 /* UserProperties+Element.swift */; };
9D2E03DB175A6AB14589076D /* AppAuth in Frameworks */ = {isa = PBXBuildFile; productRef = AA4E1BEB4E9BC2467006E12B /* AppAuth */; };
9D9690D2FD4CD26FF670620F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C75EF87651B00A176AB08E97 /* AppDelegate.swift */; };
9DAF683E0CD7D70C8862EC98 /* TimelineItemReactionsMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD61385D39FD330AFB928E6A /* TimelineItemReactionsMenuView.swift */; };
9DC5FB22B8F86C3B51E907C1 /* HomeScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D6E4C37E9F0E53D3DF951AC /* HomeScreenUITests.swift */; };
9E8AE387FD03E4F1C1B8815A /* SessionVerificationStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = C06FCD42EEFEFC220F14EAC5 /* SessionVerificationStateMachine.swift */; };
9F41FF9C53F7A6EAEA6259C9 /* InviteFriendsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F7AB0A148FCCAC28681C190 /* InviteFriendsCoordinator.swift */; };
@ -383,7 +380,6 @@
C94A6048C654B01163AE1BF1 /* VideoPlayerViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5282B7A2DCD076AD2CF27F46 /* VideoPlayerViewModelProtocol.swift */; };
CA45758F08DF42D41D8A4B29 /* FilePreviewViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF38B69D2C331A499276F400 /* FilePreviewViewModelTests.swift */; };
CB137BFB3E083C33E398A6CB /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 0DD568A494247444A4B56031 /* Kingfisher */; };
CB326BAB54E9B68658909E36 /* Benchmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EAD710A2C16EFF7C3EA16F /* Benchmark.swift */; };
CB498F4E27AA0545DCEF0F6F /* DeviceKit in Frameworks */ = {isa = PBXBuildFile; productRef = 4003BC24B24C9E63D3304177 /* DeviceKit */; };
CB99B0FA38A4AC596F38CC13 /* KeychainControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E94DCFEE803E5ABAE8ACCE /* KeychainControllerProtocol.swift */; };
CBF64DE774298D773DBD5354 /* VideoPlayerScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB634B42CFE667112369D57 /* VideoPlayerScreen.swift */; };
@ -447,7 +443,6 @@
F06CE9132855E81EBB6DDC32 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 800631D7250B7F93195035F1 /* KeychainAccess */; };
F0F82C3C848C865C3098AA52 /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 67E7A6F388D3BF85767609D9 /* Sentry */; };
F257F964493A9CD02A6F720C /* OnboardingPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DF2717AB91060260E5F4781 /* OnboardingPageView.swift */; };
F3B941FE3BBAA3320F18E73F /* EmojiPickerSearchFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C615E29435BC651DD2E95B67 /* EmojiPickerSearchFieldView.swift */; };
F425C3F85BFF28C9AC593F52 /* MockNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96561CC53F7C1E24D4C292E4 /* MockNotificationManager.swift */; };
F508683B76EF7B23BB2CBD6D /* TimelineItemPlainStylerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94BCC8A9C73C1F838122C645 /* TimelineItemPlainStylerView.swift */; };
F61AFA8BF2E739FBC30472F5 /* NotificationServiceProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCD5FEE195446A9E458DDDAF /* NotificationServiceProxyProtocol.swift */; };
@ -561,7 +556,6 @@
1215A4FC53D2319E81AE8970 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
1222DB76B917EB8A55365BA5 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
124D85E85505B6B81845235F /* fy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fy; path = fy.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
12A626D74BBE9F4A60763B45 /* ImageAnonymizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageAnonymizer.swift; sourceTree = "<group>"; };
13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
1423AB065857FA546444DB15 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; };
142808B69851451AC32A2CEA /* RoomSummaryDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryDetails.swift; sourceTree = "<group>"; };
@ -675,7 +669,6 @@
4990FDBDA96B88E214F92F48 /* SettingsModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsModels.swift; sourceTree = "<group>"; };
49D2C8E66E83EA578A7F318A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
49E751D7EDB6043238111D90 /* UNNotificationRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNNotificationRequest.swift; sourceTree = "<group>"; };
49EAD710A2C16EFF7C3EA16F /* Benchmark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Benchmark.swift; sourceTree = "<group>"; };
4A57A4AFA6A068668AFBD070 /* UIActivityViewControllerWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIActivityViewControllerWrapper.swift; sourceTree = "<group>"; };
4B362E695A7103C11F64B185 /* AnalyticsSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettings.swift; sourceTree = "<group>"; };
4B40B7F6FCCE2D8C242492D9 /* ga */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ga; path = ga.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -716,7 +709,6 @@
5F4134FEFE4EB55759017408 /* UserSessionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionProtocol.swift; sourceTree = "<group>"; };
5FF214969B25BFCBF87B908B /* bn-BD */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "bn-BD"; path = "bn-BD.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
6033779EB37259F27F938937 /* ClientProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientProxyProtocol.swift; sourceTree = "<group>"; };
6045E825AE900A92D61FEFF0 /* ImageAnonymizerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageAnonymizerTests.swift; sourceTree = "<group>"; };
607974D08BD2AF83725D817A /* RoomMessageProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMessageProtocol.swift; sourceTree = "<group>"; };
616197D81103330BF2ADD559 /* gl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gl; path = gl.lproj/Localizable.strings; sourceTree = "<group>"; };
624244C398804ADC885239AA /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -866,7 +858,6 @@
ACA11F7F50A4A3887A18CA5A /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
ACB6C5E4950B6C9842F35A38 /* RoomTimelineViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineViewProvider.swift; sourceTree = "<group>"; };
AD378D580A41E42560C60E9C /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = "<group>"; };
AD61385D39FD330AFB928E6A /* TimelineItemReactionsMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemReactionsMenuView.swift; sourceTree = "<group>"; };
AD6C07DA7D3FF193F7419F55 /* BugReportCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportCoordinator.swift; sourceTree = "<group>"; };
ADB3A7BCE745626EC61EF3C3 /* FilePreviewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewCoordinator.swift; sourceTree = "<group>"; };
ADCB8A232D3A8FB3E16A7303 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -912,7 +903,6 @@
C3F652E88106B855A2A55ADE /* FilePreviewViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewViewModelProtocol.swift; sourceTree = "<group>"; };
C483956FA3D665E3842E319A /* SettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreen.swift; sourceTree = "<group>"; };
C55D7E514F9DE4E3D72FDCAD /* SessionVerificationControllerProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationControllerProxy.swift; sourceTree = "<group>"; };
C615E29435BC651DD2E95B67 /* EmojiPickerSearchFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerSearchFieldView.swift; sourceTree = "<group>"; };
C687844F60BFF532D49A994C /* AnalyticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsTests.swift; sourceTree = "<group>"; };
C6FEA87EA3752203065ECE27 /* BugReportUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportUITests.swift; sourceTree = "<group>"; };
C75EF87651B00A176AB08E97 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
@ -1269,7 +1259,6 @@
children = (
AB785716B9212C093704E767 /* EmojiPickerHeaderView.swift */,
00245D40CD90FD71D6A05239 /* EmojiPickerScreen.swift */,
C615E29435BC651DD2E95B67 /* EmojiPickerSearchFieldView.swift */,
);
path = View;
sourceTree = "<group>";
@ -1677,7 +1666,6 @@
F3648F2FADEF2672D6A0D489 /* FileCacheTests.swift */,
DF38B69D2C331A499276F400 /* FilePreviewViewModelTests.swift */,
505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */,
6045E825AE900A92D61FEFF0 /* ImageAnonymizerTests.swift */,
FDB9C37196A4C79F24CE80C6 /* KeychainControllerTests.swift */,
C070FD43DC6BF4E50217965A /* LocalizationTests.swift */,
3DC1943ADE6A62ED5129D7C8 /* LoggingTests.swift */,
@ -2139,9 +2127,7 @@
isa = PBXGroup;
children = (
E24B88AD3D1599E8CB1376E0 /* AvatarSize.swift */,
49EAD710A2C16EFF7C3EA16F /* Benchmark.swift */,
E5272BC4A60B6AD7553BACA1 /* BlurHashDecode.swift */,
12A626D74BBE9F4A60763B45 /* ImageAnonymizer.swift */,
6A580295A56B55A856CC4084 /* InfoPlistReader.swift */,
6AD1A853D605C2146B0DC028 /* MatrixEntityRegex.swift */,
C789E7BFC066CF39B8AE0974 /* NetworkMonitor.swift */,
@ -2192,7 +2178,6 @@
isa = PBXGroup;
children = (
D5AC06FC11B6638F7BF1670E /* TimelineDeliveryStatusView.swift */,
AD61385D39FD330AFB928E6A /* TimelineItemReactionsMenuView.swift */,
351E89CE2ED9B73C5CC47955 /* TimelineReactionsView.swift */,
);
path = Supplementary;
@ -2837,7 +2822,6 @@
7E7DF1867F98B0D10A6C0A63 /* FileCacheTests.swift in Sources */,
CA45758F08DF42D41D8A4B29 /* FilePreviewViewModelTests.swift in Sources */,
F6F49E37272AD7397CD29A01 /* HomeScreenViewModelTests.swift in Sources */,
0B1F80C2BF7D223159FBA82C /* ImageAnonymizerTests.swift in Sources */,
A23B8B27A1436A1049EEF68E /* InfoPlistReader.swift in Sources */,
EEC40663922856C65D1E0DF5 /* KeychainControllerTests.swift in Sources */,
0033481EE363E4914295F188 /* LocalizationTests.swift in Sources */,
@ -2903,7 +2887,6 @@
D876EC0FED3B6D46C806912A /* AvatarSize.swift in Sources */,
E0A4DCA633D174EB43AD599F /* BackgroundTaskProtocol.swift in Sources */,
6D046D653DA28ADF1E6E59A4 /* BackgroundTaskServiceProtocol.swift in Sources */,
CB326BAB54E9B68658909E36 /* Benchmark.swift in Sources */,
38546A6010A2CF240EC9AF73 /* BindableState.swift in Sources */,
B6DF6B6FA8734B70F9BF261E /* BlurHashDecode.swift in Sources */,
A32517FB1CA0BBCE2BC75249 /* BugReportCoordinator.swift in Sources */,
@ -2938,7 +2921,6 @@
748F482FEF4E04D61C39AAD7 /* EmojiPickerScreenModels.swift in Sources */,
2C5E832434EE94E21AB3B238 /* EmojiPickerScreenViewModel.swift in Sources */,
1D69E31913DF66426985909B /* EmojiPickerScreenViewModelProtocol.swift in Sources */,
F3B941FE3BBAA3320F18E73F /* EmojiPickerSearchFieldView.swift in Sources */,
FBF09B6C900415800DDF2A21 /* EmojiProvider.swift in Sources */,
6647430A45B4A8E692909A8F /* EmoteRoomTimelineItem.swift in Sources */,
68AC3C84E2B438036B174E30 /* EmoteRoomTimelineView.swift in Sources */,
@ -2963,7 +2945,6 @@
DE4F8C4E0F1DB4832F09DE97 /* HomeScreenViewModel.swift in Sources */,
56F0A22972A3BB519DA2261C /* HomeScreenViewModelProtocol.swift in Sources */,
03D684A3AE85A23B3DA3B43F /* Image.swift in Sources */,
6EA61FCA55D950BDE326A1A7 /* ImageAnonymizer.swift in Sources */,
BA31448FBD9697F8CB9A83CD /* ImageCache.swift in Sources */,
DDB80FD2753FEAAE43CC2AAE /* ImageRoomTimelineItem.swift in Sources */,
D5EA4C6C80579279770D5804 /* ImageRoomTimelineView.swift in Sources */,
@ -3127,7 +3108,6 @@
01CB8ACFA5E143E89C168CA8 /* TimelineItemContextMenu.swift in Sources */,
F508683B76EF7B23BB2CBD6D /* TimelineItemPlainStylerView.swift in Sources */,
440123E29E2F9B001A775BBE /* TimelineItemProxy.swift in Sources */,
9DAF683E0CD7D70C8862EC98 /* TimelineItemReactionsMenuView.swift in Sources */,
9B582B3EEFEA615D4A6FBF1A /* TimelineReactionsView.swift in Sources */,
ABF3FAB234AD3565B214309B /* TimelineSenderAvatarView.swift in Sources */,
69BCBB4FB2DC3D61A28D3FD8 /* TimelineStyle.swift in Sources */,

View File

@ -13,6 +13,7 @@
"room_timeline_style_bubbled_long_description" = "Bubbled Timeline";
"room_timeline_permalink_creation_failure" = "Failed creating the permalink";
"room_timeline_backpagination_failure" = "Failed loading messages";
"room_timeline_replying_to" = "Replying to %@";
"room_timeline_editing" = "Editing";

View File

@ -377,6 +377,8 @@ class NavigationStackCoordinator: ObservableObject, CoordinatorProtocol, CustomS
}
}
var presentationDetents: Set<PresentationDetent> = []
// The currently presented sheet coordinator
// Sheets will be presented through the NavigationSplitCoordinator if provided
var sheetCoordinator: (any CoordinatorProtocol)? {
@ -489,7 +491,8 @@ class NavigationStackCoordinator: ObservableObject, CoordinatorProtocol, CustomS
// MARK: - CoordinatorProtocol
func toPresentable() -> AnyView {
AnyView(NavigationStackCoordinatorView(navigationStackCoordinator: self))
AnyView(NavigationStackCoordinatorView(navigationStackCoordinator: self)
.presentationDetents(presentationDetents))
}
// MARK: - CustomStringConvertible

View File

@ -30,6 +30,8 @@ extension ElementL10n {
public static let roomDetailsAboutSectionTitle = ElementL10n.tr("Untranslated", "room_details_about_section_title")
/// Info
public static let roomDetailsTitle = ElementL10n.tr("Untranslated", "room_details_title")
/// Failed loading messages
public static let roomTimelineBackpaginationFailure = ElementL10n.tr("Untranslated", "room_timeline_backpagination_failure")
/// Editing
public static let roomTimelineEditing = ElementL10n.tr("Untranslated", "room_timeline_editing")
/// Failed creating the permalink

View File

@ -1,70 +0,0 @@
//
// Copyright 2022 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
struct Benchmark {
private static var trackingIdentifiers = [String: CFAbsoluteTime]()
public static var trackingEnabled = false
static func startTrackingForIdentifier(_ identifier: String, message: String? = nil) {
guard trackingEnabled else {
return
}
let startTime = CFAbsoluteTimeGetCurrent()
trackingIdentifiers[identifier] = startTime
if let message {
MXLog.verbose("\(message).")
}
}
static func logElapsedDurationForIdentifier(_ identifier: String, message: String? = nil) {
guard trackingEnabled else {
return
}
guard let start = trackingIdentifiers[identifier] else {
assertionFailure("⏰ Invalid tracking identifier")
return
}
let elapsedTime = CFAbsoluteTimeGetCurrent() - start
if let message {
MXLog.verbose("\(message). Elapsed time: \(elapsedTime.round(to: 4)) seconds.")
} else {
MXLog.verbose("⏰ Elapsed time: \(elapsedTime.round(to: 4)) seconds.")
}
}
static func endTrackingForIdentifier(_ identifier: String, message: String? = nil) {
guard trackingEnabled else {
return
}
logElapsedDurationForIdentifier(identifier, message: message)
trackingIdentifiers[identifier] = nil
}
}
private extension Double {
func round(to places: Int) -> Double {
let divisor = pow(10.0, Double(places))
return (self * divisor).rounded() / divisor
}
}

View File

@ -1,123 +0,0 @@
//
// Copyright 2022 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
import UIKit
import Vision
enum ImageAnonymizerError: Error {
case noCgImageBased
}
enum ImageAnonymizer {
private static var allowedTextItems: [String] = [
"#",
"@",
"%",
"&",
"+",
"-",
"_",
"\"",
"?",
"*"
]
static func anonymizedImage(from image: UIImage,
confidenceLevel: Float = 0.5,
fillColor: UIColor = .red) async throws -> UIImage {
guard let cgImage = image.cgImage else {
throw ImageAnonymizerError.noCgImageBased
}
// create a handler with cgImage
let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
var observations: [VNDetectedObjectObservation] = []
// create a text request
let textRequest = VNRecognizeTextRequest { request, error in
guard let results = request.results as? [VNRecognizedTextObservation],
error == nil else {
return
}
observations.append(contentsOf: results)
}
textRequest.recognitionLevel = .accurate
textRequest.revision = VNRecognizeTextRequestRevision2
// create a face request
let faceRequest = VNDetectFaceRectanglesRequest { request, error in
guard let results = request.results as? [VNFaceObservation],
error == nil else {
return
}
observations.append(contentsOf: results)
}
// revision3 doesn't work!
faceRequest.revision = VNDetectFaceRectanglesRequestRevision2
#if targetEnvironment(simulator)
// Avoid `Could not create inference context` errors on Apple Silicon
// https://www.caseyliss.com/2022/6/20/feedback-is-broken-stop-trying-to-make-radar-happen
faceRequest.usesCPUOnly = true
#endif
// perform requests
try handler.perform([
textRequest,
faceRequest
])
return render(image: image,
confidenceLevel: confidenceLevel,
fillColor: fillColor,
observations: observations)
}
private static func render(image: UIImage,
confidenceLevel: Float,
fillColor: UIColor,
observations: [VNDetectedObjectObservation]) -> UIImage {
let size = image.size
let result = UIGraphicsImageRenderer(size: size).image { rendererContext in
// first draw self
image.draw(in: CGRect(origin: .zero, size: size))
// set fill color
fillColor.setFill()
for observation in observations {
guard observation.confidence >= confidenceLevel else {
// ensure observation's confidence level
continue
}
if let textObservation = observation as? VNRecognizedTextObservation,
let text = textObservation.topCandidates(1).first?.string {
if Double(text) != nil || Self.allowedTextItems.contains(text) {
continue
}
}
let box = observation.boundingBox
// boc is normalized (and in starts from the lower left corner)
// convert it to a rect in the image
let rect = CGRect(x: box.minX * size.width,
y: size.height - box.maxY * size.height,
width: box.width * size.width,
height: box.height * size.height)
rendererContext.fill(rect)
}
}
return result
}
}

View File

@ -34,7 +34,7 @@ class NetworkMonitor {
init() {
queue = DispatchQueue(label: "io.element.elementx.networkmonitor")
pathMonitor = NWPathMonitor()
reachabilityPublisher = CurrentValueSubject<Bool, Never>(pathMonitor.currentPath.status == .satisfied)
reachabilityPublisher = CurrentValueSubject<Bool, Never>(true)
pathMonitor.pathUpdateHandler = { [weak self] path in
DispatchQueue.main.async {

View File

@ -53,23 +53,11 @@ class BugReportViewModel: BugReportViewModelType, BugReportViewModelProtocol {
private func submitBugReport() async {
callback?(.submitStarted)
do {
var files: [URL] = []
if let screenshot = state.screenshot {
let anonymized = try await ImageAnonymizer.anonymizedImage(from: screenshot)
let tmpUrl = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("screenshot").appendingPathExtension("png")
// remove old screenshot if exists
if FileManager.default.fileExists(atPath: tmpUrl.path) {
try FileManager.default.removeItem(at: tmpUrl)
}
try anonymized.dataForPNGRepresentation().write(to: tmpUrl)
files.append(tmpUrl)
}
let result = try await bugReportService.submitBugReport(text: context.reportText,
includeLogs: context.sendingLogsEnabled,
includeCrashLog: true,
githubLabels: ServiceLocator.shared.settings.bugReportGHLabels,
files: files)
files: [])
MXLog.info("SubmitBugReport succeeded, result: \(result.reportUrl)")
callback?(.submitFinished)
} catch {

View File

@ -23,6 +23,7 @@ struct EmojiPickerScreenCoordinatorParameters {
enum EmojiPickerScreenCoordinatorAction {
case emojiSelected(emoji: String, itemId: String)
case dismiss
}
final class EmojiPickerScreenCoordinator: CoordinatorProtocol {
@ -44,12 +45,13 @@ final class EmojiPickerScreenCoordinator: CoordinatorProtocol {
switch action {
case let .emojiSelected(emoji: emoji):
self.callback?(.emojiSelected(emoji: emoji, itemId: self.parameters.itemId))
case .dismiss:
self.callback?(.dismiss)
}
}
}
func toPresentable() -> AnyView {
AnyView(EmojiPickerScreen(context: viewModel.context)
.presentationDetents([.medium, .large]))
AnyView(EmojiPickerScreen(context: viewModel.context))
}
}

View File

@ -18,6 +18,7 @@ import Foundation
enum EmojiPickerScreenViewModelAction {
case emojiSelected(emoji: String)
case dismiss
}
struct EmojiPickerScreenViewState: BindableState {
@ -27,6 +28,7 @@ struct EmojiPickerScreenViewState: BindableState {
enum EmojiPickerScreenViewAction {
case search(searchString: String)
case emojiTapped(emoji: EmojiPickerEmojiViewData)
case dismiss
}
struct EmojiPickerEmojiCategoryViewData: Identifiable {

View File

@ -39,6 +39,8 @@ class EmojiPickerScreenViewModel: EmojiPickerScreenViewModelType, EmojiPickerScr
state.categories = convert(emojiCategories: categories)
case let .emojiTapped(emoji: emoji):
callback?(.emojiSelected(emoji: emoji.value))
case .dismiss:
callback?(.dismiss)
}
}

View File

@ -21,39 +21,49 @@ struct EmojiPickerScreen: View {
@State var searchString = ""
var body: some View {
VStack {
Text(ElementL10n.reactions)
.padding(.top, 20)
EmojiPickerSearchFieldView(searchString: $searchString)
.padding(.horizontal, 10)
ScrollView {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 45))], spacing: 3) {
ForEach(context.viewState.categories) { category in
Section(header: EmojiPickerHeaderView(title: category.name)
.padding(.horizontal, 13)
.padding(.top, 10)) {
ForEach(category.emojis) { emoji in
Text(emoji.value)
.frame(width: 45, height: 45)
.onTapGesture {
context.send(viewAction: .emojiTapped(emoji: emoji))
}
}
ScrollView {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 45))], spacing: 3) {
ForEach(context.viewState.categories) { category in
Section(header: EmojiPickerHeaderView(title: category.name)
.padding(.horizontal, 13)
.padding(.top, 10)) {
ForEach(category.emojis) { emoji in
Text(emoji.value)
.frame(width: 45, height: 45)
.onTapGesture {
context.send(viewAction: .emojiTapped(emoji: emoji))
}
}
}
}
}
}
}
.navigationTitle(ElementL10n.reactions)
.navigationBarTitleDisplayMode(.inline)
.toolbar { toolbar }
.searchable(text: $searchString)
.onChange(of: searchString) { _ in
context.send(viewAction: .search(searchString: searchString))
}
}
@ToolbarContentBuilder
var toolbar: some ToolbarContent {
ToolbarItem(placement: .cancellationAction) {
Button { context.send(viewAction: .dismiss) } label: {
Text(ElementL10n.actionCancel)
}
.accessibilityIdentifier("dismissButton")
}
}
}
// MARK: - Previews
struct EmojiPickerScreen_Previews: PreviewProvider {
static var previews: some View {
EmojiPickerScreen(context: EmojiPickerScreenViewModel(emojiProvider: EmojiProvider()).context)
NavigationStack {
EmojiPickerScreen(context: EmojiPickerScreenViewModel(emojiProvider: EmojiProvider()).context)
}
}
}

View File

@ -1,45 +0,0 @@
//
// Copyright 2022 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
struct EmojiPickerSearchFieldView: View {
@Binding var searchString: String
@FocusState private var isSearchFocused: Bool
var body: some View {
HStack {
Image(systemName: "magnifyingglass")
TextField(ElementL10n.search, text: $searchString)
.focused($isSearchFocused)
if isSearchFocused {
Spacer()
Button {
searchString = ""
isSearchFocused = false
} label: {
Text(ElementL10n.actionCancel)
}
}
}
}
}
struct EmojiPickerSearchFieldView_Previews: PreviewProvider {
static var previews: some View {
EmojiPickerSearchFieldView(searchString: .constant(""))
}
}

View File

@ -19,7 +19,10 @@ import SwiftUI
struct SplashScreenCoordinator: CoordinatorProtocol {
func toPresentable() -> AnyView {
AnyView(
Image(asset: Asset.Images.appLogo)
ZStack {
Image(asset: Asset.Images.appLogo)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
)
}
}

View File

@ -107,6 +107,9 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
let timelineController = parameters?.timelineController else {
fatalError()
}
let emojiPickerNavigationStackCoordinator = NavigationStackCoordinator()
let params = EmojiPickerScreenCoordinatorParameters(emojiProvider: emojiProvider,
itemId: itemId)
let coordinator = EmojiPickerScreenCoordinator(parameters: params)
@ -118,10 +121,15 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
Task {
await timelineController.sendReaction(emoji, for: itemId)
}
case .dismiss:
self?.navigationStackCoordinator.setSheetCoordinator(nil)
}
}
navigationStackCoordinator.setSheetCoordinator(coordinator)
emojiPickerNavigationStackCoordinator.setRootCoordinator(coordinator)
emojiPickerNavigationStackCoordinator.presentationDetents = [.medium, .large]
navigationStackCoordinator.setSheetCoordinator(emojiPickerNavigationStackCoordinator)
}
private func displayRoomDetails() {

View File

@ -33,7 +33,6 @@ enum RoomScreenComposerMode: Equatable {
enum RoomScreenViewAction {
case headerTapped
case displayEmojiPicker(itemId: String)
case emojiTapped(emoji: String, itemId: String)
case paginateBackwards
case itemAppeared(id: String)
case itemDisappeared(id: String)
@ -43,7 +42,6 @@ enum RoomScreenViewAction {
case sendReaction(key: String, eventID: String)
case cancelReply
case cancelEdit
case displayReactionsMenuForItemId(itemId: String)
}
struct RoomScreenViewState: BindableState {
@ -54,7 +52,6 @@ struct RoomScreenViewState: BindableState {
var isBackPaginating = false
var showLoading = false
var bindings: RoomScreenViewStateBindings
var displayReactionsMenuForItemId = ""
var contextMenuBuilder: (@MainActor (_ itemId: String) -> TimelineItemContextMenu)?

View File

@ -102,20 +102,14 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
MXLog.warning("Link clicked: \(url)")
case .sendMessage:
await sendCurrentMessage()
case .sendReaction(let key, _):
#warning("Reaction implementation awaiting SDK support.")
MXLog.warning("React with \(key) failed. Not implemented.")
case .sendReaction(let emoji, let itemId):
await timelineController.sendReaction(emoji, for: itemId)
case .displayEmojiPicker(let itemId):
callback?(.displayEmojiPicker(itemId: itemId))
case .displayReactionsMenuForItemId(let itemId):
state.displayReactionsMenuForItemId = itemId
case .cancelReply:
state.composerMode = .default
case .cancelEdit:
state.composerMode = .default
case .emojiTapped(let emoji, let itemId):
await timelineController.sendReaction(emoji, for: itemId)
state.displayReactionsMenuForItemId = ""
}
}
@ -128,8 +122,10 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
private func paginateBackwards() async {
switch await timelineController.paginateBackwards(Constants.backPaginationPageSize) {
case .failure:
displayError(.alert(ElementL10n.roomTimelineBackpaginationFailure))
default:
#warning("Treat errors")
break
}
}
@ -201,7 +197,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
}
var actions: [TimelineItemContextMenuAction] = [
.copy, .quote, .copyPermalink, .reply
.react, .copy, .quote, .copyPermalink, .reply
]
if item.isEditable {
@ -215,6 +211,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
return .init(actions: actions)
}
// swiftlint:disable:next cyclomatic_complexity
private func processContentMenuAction(_ action: TimelineItemContextMenuAction, itemId: String) {
guard let timelineItem = timelineController.timelineItems.first(where: { $0.id == itemId }),
let item = timelineItem as? EventBasedTimelineItemProtocol else {
@ -222,6 +219,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
}
switch action {
case .react:
callback?(.displayEmojiPicker(itemId: item.id))
case .copy:
UIPasteboard.general.string = item.text
case .edit:

View File

@ -27,28 +27,25 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
@ScaledMetric private var senderNameVerticalPadding = 3
var body: some View {
VStack(alignment: alignment, spacing: -12) {
if !timelineItem.isOutgoing {
header
.zIndex(1)
}
VStack(alignment: alignment) {
if timelineItem.isOutgoing {
HStack {
Spacer()
styledContentWithReactions
if timelineItem.isOutgoing {
TimelineDeliveryStatusView(deliveryStatus: timelineItem.properties.deliveryStatus)
.padding(.top, 6)
}
}
.padding(.trailing, 16)
.padding(.leading, 56)
} else {
styledContentWithReactions
.padding(.leading, 24)
.padding(.trailing, 56)
ZStack(alignment: .trailingFirstTextBaseline) {
VStack(alignment: alignment, spacing: -12) {
if !timelineItem.isOutgoing {
header
.zIndex(1)
}
HStack {
if timelineItem.isOutgoing {
Spacer()
}
styledContentWithReactions
}
.padding(.horizontal, 16.0)
}
if timelineItem.isOutgoing {
TimelineDeliveryStatusView(deliveryStatus: timelineItem.properties.deliveryStatus)
}
}
}
@ -85,7 +82,6 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
alignment: alignment) { key in
context.send(viewAction: .sendReaction(key: key, eventID: timelineItem.id))
}
.padding(.horizontal, 12)
}
}
}

View File

@ -28,7 +28,15 @@ struct TimelineItemPlainStylerView<Content: View>: View {
header
VStack(alignment: .leading, spacing: 4) {
content()
HStack(alignment: .firstTextBaseline) {
content()
Spacer()
if timelineItem.isOutgoing {
TimelineDeliveryStatusView(deliveryStatus: timelineItem.properties.deliveryStatus)
}
}
supplementaryViews
}
}

View File

@ -43,17 +43,18 @@ struct TimelineDeliveryStatusView: View {
}
var body: some View {
if showDeliveryStatus {
Image(systemName: systemImageName)
.task {
if case .sent = deliveryStatus {
try? await Task.sleep(nanoseconds: 1_000_000_000)
withAnimation {
showDeliveryStatus = false
}
Image(systemName: systemImageName)
.resizable()
.frame(width: 12.0, height: 12.0)
.opacity(showDeliveryStatus ? 1.0 : 0.0)
.task {
if case .sent = deliveryStatus {
try? await Task.sleep(nanoseconds: 1_000_000_000)
withAnimation {
showDeliveryStatus = false
}
}
}
}
}
}

View File

@ -1,56 +0,0 @@
//
// Copyright 2022 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
struct TimelineItemReactionsMenuView: View {
private let emojis = ["👍🏼", "👎🏼", "😄", "🙏🏼", "😇"]
var onEmojiSelected: ((String) -> Void)?
var onDisplayEmojiPicker: (() -> Void)?
var body: some View {
HStack {
HStack(spacing: 10) {
ForEach(emojis, id: \.self) { emoji in
Button {
onEmojiSelected?(emoji)
} label: {
Text(emoji)
}
}
}
.padding(10)
.background(.gray)
.cornerRadius(15)
HStack(spacing: 10) {
Text("")
}
.padding(10)
.background(.gray)
.cornerRadius(15)
.onTapGesture {
onDisplayEmojiPicker?()
}
}
}
}
struct TimelineItemReactionsMenuView_Previews: PreviewProvider {
static var previews: some View {
TimelineItemReactionsMenuView(onDisplayEmojiPicker: nil)
}
}

View File

@ -51,7 +51,8 @@ struct FormattedBodyText: View {
}
}
}
.tint(.element.accent)
// Should be .element.accent but that's currently black
.tint(.blue)
}
private var blockquoteAttributes: AttributeContainer {

View File

@ -22,6 +22,7 @@ struct TimelineItemContextMenuActions {
}
enum TimelineItemContextMenuAction: Identifiable, Hashable {
case react
case copy
case edit
case quote
@ -59,6 +60,10 @@ public struct TimelineItemContextMenu: View {
private func viewsForActions(_ actions: [TimelineItemContextMenuAction]) -> some View {
ForEach(actions, id: \.self) { item in
switch item {
case .react:
Button { callback(item) } label: {
Label(ElementL10n.reactions, systemImage: "face.smiling")
}
case .copy:
Button { callback(item) } label: {
Label(ElementL10n.actionCopy, systemImage: "doc.on.doc")

View File

@ -69,13 +69,7 @@ class TimelineTableViewController: UIViewController {
applySnapshot()
}
}
var displayReactionsMenuForItemId = "" {
didSet {
tableView.reloadData()
}
}
var contextMenuBuilder: (@MainActor (_ itemId: String) -> TimelineItemContextMenu)?
@Binding private var scrollToBottomButtonVisible: Bool
@ -193,7 +187,6 @@ class TimelineTableViewController: UIViewController {
// A local reference to avoid capturing self in the cell configuration.
let coordinator = self.coordinator
let opacity = self.opacity(for: timelineItem)
let displayReactionsMenuForItemId = self.displayReactionsMenuForItemId
let contextMenuBuilder = self.contextMenuBuilder
cell.item = timelineItem
@ -201,38 +194,28 @@ class TimelineTableViewController: UIViewController {
if case .backPaginationIndicator = timelineItem {
timelineItem
} else {
VStack {
if displayReactionsMenuForItemId == timelineItem.id {
TimelineItemReactionsMenuView { emoji in
coordinator.send(viewAction: .emojiTapped(emoji: emoji, itemId: timelineItem.id))
} onDisplayEmojiPicker: {
coordinator.send(viewAction: .displayEmojiPicker(itemId: timelineItem.id))
}
timelineItem
.frame(maxWidth: .infinity, alignment: .leading)
.opacity(opacity)
.contextMenu {
contextMenuBuilder?(timelineItem.id)
}
.onAppear {
coordinator.send(viewAction: .itemAppeared(id: timelineItem.id))
}
.onDisappear {
coordinator.send(viewAction: .itemDisappeared(id: timelineItem.id))
}
.environment(\.openURL, OpenURLAction { url in
coordinator.send(viewAction: .linkClicked(url: url))
return .systemAction
})
.onTapGesture(count: 2) {
coordinator.send(viewAction: .displayEmojiPicker(itemId: timelineItem.id))
}
.onTapGesture {
coordinator.send(viewAction: .itemTapped(id: timelineItem.id))
}
timelineItem
.frame(maxWidth: .infinity, alignment: .leading)
.opacity(opacity)
.contextMenu {
contextMenuBuilder?(timelineItem.id)
}
.onAppear {
coordinator.send(viewAction: .itemAppeared(id: timelineItem.id))
}
.onDisappear {
coordinator.send(viewAction: .itemDisappeared(id: timelineItem.id))
}
.environment(\.openURL, OpenURLAction { url in
coordinator.send(viewAction: .linkClicked(url: url))
return .systemAction
})
.onTapGesture(count: 2) {
coordinator.send(viewAction: .displayReactionsMenuForItemId(itemId: timelineItem.id))
}
.onTapGesture {
coordinator.send(viewAction: .itemTapped(id: timelineItem.id))
}
}
}
}
.margins(.all, self.timelineStyle.rowInsets)
@ -375,15 +358,9 @@ class TimelineTableViewController: UIViewController {
extension TimelineTableViewController: UITableViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let isAtBottom = isAtBottom()
// Dispatches fix runtime warnings about making changes during a view update.
if !scrollToBottomButtonVisible, isAtBottom {
DispatchQueue.main.async { self.scrollToBottomButtonVisible = true }
} else if scrollToBottomButtonVisible, !isAtBottom {
DispatchQueue.main.async { self.scrollToBottomButtonVisible = false }
}
DispatchQueue.main.async { self.scrollToBottomButtonVisible = self.isAtBottom() }
paginateBackwardsPublisher.send(())
}

View File

@ -65,9 +65,6 @@ struct TimelineView: UIViewControllerRepresentable {
if tableViewController.composerMode != context.viewState.composerMode {
tableViewController.composerMode = context.viewState.composerMode
}
if tableViewController.displayReactionsMenuForItemId != context.viewState.displayReactionsMenuForItemId {
tableViewController.displayReactionsMenuForItemId = context.viewState.displayReactionsMenuForItemId
}
// Doesn't have an equatable conformance :(
tableViewController.contextMenuBuilder = context.viewState.contextMenuBuilder

View File

@ -95,8 +95,6 @@ class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol {
func login(username: String, password: String, initialDeviceName: String?, deviceId: String?) async -> Result<UserSessionProtocol, AuthenticationServiceError> {
do {
Benchmark.startTrackingForIdentifier("Login", message: "Started new login")
let client = try await Task.dispatch(on: .global()) {
try self.authenticationService.login(username: username,
password: password,
@ -104,11 +102,8 @@ class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol {
deviceId: deviceId)
}
Benchmark.endTrackingForIdentifier("Login", message: "Finished login")
return await userSession(for: client)
} catch {
Benchmark.endTrackingForIdentifier("Login", message: "Login failed")
MXLog.error("Failed logging in with error: \(error)")
guard let error = error as? AuthenticationError else { return .failure(.failedLoggingIn) }

View File

@ -27,7 +27,7 @@ class RoomProxy: RoomProxyProtocol {
private let serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomproxy.serial")
private var sendMessageBgTask: BackgroundTaskProtocol?
private var sendMessageBackgroundTask: BackgroundTaskProtocol?
private var memberAvatars = [String: String]()
private var memberDisplayNames = [String: String]()
@ -151,10 +151,7 @@ class RoomProxy: RoomProxyProtocol {
do {
let outcome: PaginationOutcome = try await Task.dispatch(on: .global()) {
Benchmark.startTrackingForIdentifier("BackPagination \(id)", message: "Backpaginating \(count) message(s) in room \(id)")
let outcome = try self.room.paginateBackwards(limit: UInt16(count))
Benchmark.endTrackingForIdentifier("BackPagination \(id)", message: "Finished backpaginating \(count) message(s) in room \(id)")
return outcome
try self.room.paginateBackwards(limit: UInt16(count))
}
update(backPaginationOutcome: outcome)
return .success(())
@ -164,9 +161,9 @@ class RoomProxy: RoomProxyProtocol {
}
func sendMessage(_ message: String, inReplyToEventId: String? = nil) async -> Result<Void, RoomProxyError> {
sendMessageBgTask = backgroundTaskService.startBackgroundTask(withName: "SendMessage", isReusable: true)
sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: "SendMessage", isReusable: true)
defer {
sendMessageBgTask?.stop()
sendMessageBackgroundTask?.stop()
}
let transactionId = genTransactionId()
@ -187,15 +184,25 @@ class RoomProxy: RoomProxyProtocol {
}
func sendReaction(_ reaction: String, for eventId: String) async -> Result<Void, RoomProxyError> {
await Task.dispatch(on: .global()) {
.success(())
sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: "SendMessage", isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
return await Task.dispatch(on: .global()) {
do {
try self.room.sendReaction(eventId: eventId, key: reaction)
return .success(())
} catch {
return .failure(.failedSendingReaction)
}
}
}
func editMessage(_ newMessage: String, originalEventId: String) async -> Result<Void, RoomProxyError> {
sendMessageBgTask = backgroundTaskService.startBackgroundTask(withName: "SendMessage", isReusable: true)
sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: "SendMessage", isReusable: true)
defer {
sendMessageBgTask?.stop()
sendMessageBackgroundTask?.stop()
}
let transactionId = genTransactionId()
@ -205,7 +212,7 @@ class RoomProxy: RoomProxyProtocol {
try self.room.edit(newMsg: newMessage, originalEventId: originalEventId, txnId: transactionId)
return .success(())
} catch {
return .failure(.failedSendingMessage)
return .failure(.failedEditingMessage)
}
}
}

View File

@ -23,6 +23,8 @@ enum RoomProxyError: Error {
case failedRetrievingMemberAvatarURL
case failedRetrievingMemberDisplayName
case failedSendingMessage
case failedSendingReaction
case failedEditingMessage
case failedRedactingEvent
case failedAddingTimelineListener
case failedRetrievingMembers

View File

@ -161,8 +161,8 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
return buildEmptyRoomSummary()
case .filled(let roomId):
return buildRoomSummaryForIdentifier(roomId)
case .invalidated:
return buildEmptyRoomSummary()
case .invalidated(let roomId):
return buildRoomSummaryForIdentifier(roomId)
}
}

View File

@ -64,7 +64,7 @@ enum RoomTimelineItemFixtures {
senderId: "",
senderDisplayName: "Helena"),
TextRoomTimelineItem(id: UUID().uuidString,
text: "And John's speech was amazing!",
text: "And John's speech was amazing!And John's speech was amazing!And John's speech was amazing!",
timestamp: "5 PM",
inGroupState: .beginning,
isOutgoing: true,

View File

@ -100,8 +100,6 @@ class UserSessionStore: UserSessionStoreProtocol {
// MARK: - Private
private func restorePreviousLogin(_ credentials: KeychainCredentials) async -> Result<ClientProxyProtocol, UserSessionStoreError> {
Benchmark.startTrackingForIdentifier("Login", message: "Started restoring previous login")
let builder = ClientBuilder()
.basePath(path: baseDirectory.path)
.username(username: credentials.userID)

View File

@ -1,63 +0,0 @@
//
// Copyright 2022 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.
//
@testable import ElementX
import XCTest
enum ImageAnonymizerTestsError: String, Error {
case screenshotNotFound
}
class ImageAnonymizerTests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func sampleScreenshot() throws -> UIImage {
let bundle = Bundle(for: classForCoder)
guard let path = bundle.path(forResource: "sample_screenshot", ofType: "png"),
let image = UIImage(contentsOfFile: path) else {
throw ImageAnonymizerTestsError.screenshotNotFound
}
return image
}
func testImageAnonymizationConfidenceLevel() async throws {
let image = try sampleScreenshot()
let anonymized5 = try await ImageAnonymizer.anonymizedImage(from: image)
let anonymized1 = try await ImageAnonymizer.anonymizedImage(from: image, confidenceLevel: 0.1)
// comparing colors is a complicated process, just compare images for now
XCTAssertNotEqual(image, anonymized5)
XCTAssertNotEqual(anonymized1, anonymized5)
}
func testImageAnonymizationFillColor() async throws {
let image = try sampleScreenshot()
let anonymizedRed = try await ImageAnonymizer.anonymizedImage(from: image)
let anonymizedBlue = try await ImageAnonymizer.anonymizedImage(from: image, fillColor: .blue)
// comparing colors is a complicated process, just compare images for now
XCTAssertNotEqual(image, anonymizedRed)
XCTAssertNotEqual(anonymizedBlue, anonymizedRed)
}
}

View File

@ -0,0 +1,9 @@
* moved the message delivery status outside of the main content and added it to the plain timeline as well
* fixed glithcy scroll to bottom timeline button
* simplified the emoji picker, double tapping a timeline item directly opens it now and added a context menu option. Linked it to rust side reaction sending
* fixed cold cache seemingly not working (invalid rooms treated as empty)
* made splash screen full screen
* fixed connectivity indicator starting off as offline
* added presentation detents on the NavigationStackCoordinator as they're not inherited from the child
* fixed timeline item link tint colors
* removed some unnecessary classes