mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 21:39:12 +00:00
Fixes vector-im/element-x-ios/issues/117 - Event permalink timeline action
* moved NSRegularExpression outside of the AttributedString builder into the MatrixEntityRegex * fixed eventId v3 regex * added permalink builders for users, room identifiers and aliases, and events * added timeline item permalink contextual menu actions and error alerts * added an app wide ServiceLocator and moved the top level userIndicatorPresenter to it. * added URL constructor that takes a StaticString and returns an non-optional * Include Unit and UI tests in the swiftlint search paths
This commit is contained in:
parent
4006cc6b80
commit
4660f096f8
@ -14,6 +14,8 @@ opt_in_rules:
|
|||||||
# paths to include during linting. `--path` is ignored if present.
|
# paths to include during linting. `--path` is ignored if present.
|
||||||
included:
|
included:
|
||||||
- ElementX
|
- ElementX
|
||||||
|
- UnitTests
|
||||||
|
- UITests
|
||||||
- Tools/Scripts/Templates
|
- Tools/Scripts/Templates
|
||||||
excluded:
|
excluded:
|
||||||
- IntegrationTests
|
- IntegrationTests
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
0602FA07557F580086782A9E /* UserIndicatorPresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FA072E995316CD18BC29313 /* UserIndicatorPresentationContext.swift */; };
|
0602FA07557F580086782A9E /* UserIndicatorPresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FA072E995316CD18BC29313 /* UserIndicatorPresentationContext.swift */; };
|
||||||
066A1E9B94723EE9F3038044 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EBB5D698CE9A25BB553A2D /* Strings.swift */; };
|
066A1E9B94723EE9F3038044 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EBB5D698CE9A25BB553A2D /* Strings.swift */; };
|
||||||
06E93B2E3B32740B40F47CC5 /* ElementNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF4B39D52CAE7D21D276ABEE /* ElementNavigationController.swift */; };
|
06E93B2E3B32740B40F47CC5 /* ElementNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF4B39D52CAE7D21D276ABEE /* ElementNavigationController.swift */; };
|
||||||
|
071A017E415AD378F2961B11 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 227AC5D71A4CE43512062243 /* URL.swift */; };
|
||||||
07240B7159A3990C4C2E8FFC /* LoginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D256FEE2F1AF1E51D39B622 /* LoginTests.swift */; };
|
07240B7159A3990C4C2E8FFC /* LoginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D256FEE2F1AF1E51D39B622 /* LoginTests.swift */; };
|
||||||
072BA9DBA932374CCA300125 /* MessageComposerTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE6C10032A77AE7DC5AA4C50 /* MessageComposerTextField.swift */; };
|
072BA9DBA932374CCA300125 /* MessageComposerTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE6C10032A77AE7DC5AA4C50 /* MessageComposerTextField.swift */; };
|
||||||
0B1F80C2BF7D223159FBA82C /* ImageAnonymizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6045E825AE900A92D61FEFF0 /* ImageAnonymizerTests.swift */; };
|
0B1F80C2BF7D223159FBA82C /* ImageAnonymizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6045E825AE900A92D61FEFF0 /* ImageAnonymizerTests.swift */; };
|
||||||
@ -62,6 +63,7 @@
|
|||||||
24906A1E82D0046655958536 /* MessageComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18CF12478983A5EB390FB26 /* MessageComposer.swift */; };
|
24906A1E82D0046655958536 /* MessageComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18CF12478983A5EB390FB26 /* MessageComposer.swift */; };
|
||||||
24BDDD09A90B8BFE3793F3AA /* ClientProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6033779EB37259F27F938937 /* ClientProxyProtocol.swift */; };
|
24BDDD09A90B8BFE3793F3AA /* ClientProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6033779EB37259F27F938937 /* ClientProxyProtocol.swift */; };
|
||||||
2797C9D9BA642370F1C85D78 /* Untranslated.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = F75DF9500D69A3AAF8339E69 /* Untranslated.stringsdict */; };
|
2797C9D9BA642370F1C85D78 /* Untranslated.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = F75DF9500D69A3AAF8339E69 /* Untranslated.stringsdict */; };
|
||||||
|
27E9263DA75E266690A37EB1 /* PermalinkBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FB31A32C93D94930B253FBF /* PermalinkBuilderTests.swift */; };
|
||||||
28410F3DE89C2C44E4F75C92 /* MockBugReportService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0E7BF8F7BB1021F889C6483 /* MockBugReportService.swift */; };
|
28410F3DE89C2C44E4F75C92 /* MockBugReportService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0E7BF8F7BB1021F889C6483 /* MockBugReportService.swift */; };
|
||||||
290FDB0FFDC2F1DDF660343E /* TestMeasurementParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C4048041C1A6B20CB97FD18 /* TestMeasurementParser.swift */; };
|
290FDB0FFDC2F1DDF660343E /* TestMeasurementParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C4048041C1A6B20CB97FD18 /* TestMeasurementParser.swift */; };
|
||||||
297CD0A27C87B0C50FF192EE /* RoomTimelineViewFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE384418EB1FEDFA62C9CD0 /* RoomTimelineViewFactoryProtocol.swift */; };
|
297CD0A27C87B0C50FF192EE /* RoomTimelineViewFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE384418EB1FEDFA62C9CD0 /* RoomTimelineViewFactoryProtocol.swift */; };
|
||||||
@ -86,6 +88,7 @@
|
|||||||
34966D4C1C2C6D37FE3F7F50 /* SettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DD2D50A7EAA4FC78417730E /* SettingsCoordinator.swift */; };
|
34966D4C1C2C6D37FE3F7F50 /* SettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DD2D50A7EAA4FC78417730E /* SettingsCoordinator.swift */; };
|
||||||
352C439BE0F75E101EF11FB1 /* RoomScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2886615BEBAE33A0AA4D5F8 /* RoomScreenModels.swift */; };
|
352C439BE0F75E101EF11FB1 /* RoomScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2886615BEBAE33A0AA4D5F8 /* RoomScreenModels.swift */; };
|
||||||
3588F34D05B4D731A73214C6 /* BugReportScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DED59F9EFF273BFA2055FFDF /* BugReportScreen.swift */; };
|
3588F34D05B4D731A73214C6 /* BugReportScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DED59F9EFF273BFA2055FFDF /* BugReportScreen.swift */; };
|
||||||
|
35C57543D245E82CBFE15DF0 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 227AC5D71A4CE43512062243 /* URL.swift */; };
|
||||||
35E975CFDA60E05362A7CF79 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 1222DB76B917EB8A55365BA5 /* target.yml */; };
|
35E975CFDA60E05362A7CF79 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 1222DB76B917EB8A55365BA5 /* target.yml */; };
|
||||||
368C8758FCD079E6AAA18C2C /* NoticeRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */; };
|
368C8758FCD079E6AAA18C2C /* NoticeRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */; };
|
||||||
36AC963F2F04069B7FF1AA0C /* UIConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6D88E8AFFBF2C1D589C0FA /* UIConstants.swift */; };
|
36AC963F2F04069B7FF1AA0C /* UIConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6D88E8AFFBF2C1D589C0FA /* UIConstants.swift */; };
|
||||||
@ -188,6 +191,7 @@
|
|||||||
7FA4227B2BAAA71560252866 /* UserIndicatorDismissal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D1532B5D9FB0C8461A1453 /* UserIndicatorDismissal.swift */; };
|
7FA4227B2BAAA71560252866 /* UserIndicatorDismissal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D1532B5D9FB0C8461A1453 /* UserIndicatorDismissal.swift */; };
|
||||||
7FED310F6AB7A70CBFB7C8A3 /* SettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = C483956FA3D665E3842E319A /* SettingsScreen.swift */; };
|
7FED310F6AB7A70CBFB7C8A3 /* SettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = C483956FA3D665E3842E319A /* SettingsScreen.swift */; };
|
||||||
8024BE37156FF0A95A7A3465 /* AnalyticsPromptUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF11DD57D9FACF2A757AB024 /* AnalyticsPromptUITests.swift */; };
|
8024BE37156FF0A95A7A3465 /* AnalyticsPromptUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF11DD57D9FACF2A757AB024 /* AnalyticsPromptUITests.swift */; };
|
||||||
|
80D00A7C62AAB44F54725C43 /* PermalinkBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F754E66A8970963B15B2A41E /* PermalinkBuilder.swift */; };
|
||||||
80E04BE80A89A78FBB4863BB /* UserIndicatorViewPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 193FB285430D3956B6E61E4D /* UserIndicatorViewPresentable.swift */; };
|
80E04BE80A89A78FBB4863BB /* UserIndicatorViewPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 193FB285430D3956B6E61E4D /* UserIndicatorViewPresentable.swift */; };
|
||||||
83E5054739949181CA981193 /* LoginCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD667C4BB98CF4F3FE2CE3B0 /* LoginCoordinator.swift */; };
|
83E5054739949181CA981193 /* LoginCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD667C4BB98CF4F3FE2CE3B0 /* LoginCoordinator.swift */; };
|
||||||
85AFBB433AD56704A880F8A0 /* FramePreferenceKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4798B3B7A1E8AE3901CEE8C6 /* FramePreferenceKey.swift */; };
|
85AFBB433AD56704A880F8A0 /* FramePreferenceKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4798B3B7A1E8AE3901CEE8C6 /* FramePreferenceKey.swift */; };
|
||||||
@ -416,6 +420,7 @@
|
|||||||
2112A6CFEA46E672D90EBF54 /* kab */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = kab; path = kab.lproj/Localizable.strings; sourceTree = "<group>"; };
|
2112A6CFEA46E672D90EBF54 /* kab */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = kab; path = kab.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
218AB05B4E3889731959C5F1 /* EventBasedTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBasedTimelineItemProtocol.swift; sourceTree = "<group>"; };
|
218AB05B4E3889731959C5F1 /* EventBasedTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBasedTimelineItemProtocol.swift; sourceTree = "<group>"; };
|
||||||
21BA866267F84BF4350B0CB7 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "pt-BR"; path = "pt-BR.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
21BA866267F84BF4350B0CB7 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "pt-BR"; path = "pt-BR.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||||
|
227AC5D71A4CE43512062243 /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = "<group>"; };
|
||||||
22B384D54464FA39C6C7F6E7 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ca; path = ca.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
22B384D54464FA39C6C7F6E7 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ca; path = ca.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
233D5F7E5E9F49ABF3413291 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = hr; path = hr.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
233D5F7E5E9F49ABF3413291 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = hr; path = hr.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
24A534A4619D8FEFB6439FCC /* SplashScreenPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreenPageView.swift; sourceTree = "<group>"; };
|
24A534A4619D8FEFB6439FCC /* SplashScreenPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreenPageView.swift; sourceTree = "<group>"; };
|
||||||
@ -553,6 +558,7 @@
|
|||||||
6EA1D2CBAEA5D0BD00B90D1B /* BindableState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BindableState.swift; sourceTree = "<group>"; };
|
6EA1D2CBAEA5D0BD00B90D1B /* BindableState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BindableState.swift; sourceTree = "<group>"; };
|
||||||
6F3DFE5B444F131648066F05 /* StateStoreViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateStoreViewModel.swift; sourceTree = "<group>"; };
|
6F3DFE5B444F131648066F05 /* StateStoreViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateStoreViewModel.swift; sourceTree = "<group>"; };
|
||||||
6FA072E995316CD18BC29313 /* UserIndicatorPresentationContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorPresentationContext.swift; sourceTree = "<group>"; };
|
6FA072E995316CD18BC29313 /* UserIndicatorPresentationContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorPresentationContext.swift; sourceTree = "<group>"; };
|
||||||
|
6FB31A32C93D94930B253FBF /* PermalinkBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermalinkBuilderTests.swift; sourceTree = "<group>"; };
|
||||||
6FC5015B9634698BDB8701AF /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = it; path = it.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
6FC5015B9634698BDB8701AF /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = it; path = it.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
71BC7CA1BC1041E93077BBA1 /* HomeScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenModels.swift; sourceTree = "<group>"; };
|
71BC7CA1BC1041E93077BBA1 /* HomeScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenModels.swift; sourceTree = "<group>"; };
|
||||||
71D52BAA5BADB06E5E8C295D /* Assets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Assets.swift; sourceTree = "<group>"; };
|
71D52BAA5BADB06E5E8C295D /* Assets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Assets.swift; sourceTree = "<group>"; };
|
||||||
@ -775,6 +781,7 @@
|
|||||||
F5C4AF6E3885730CD560311C /* ScreenshotDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotDetector.swift; sourceTree = "<group>"; };
|
F5C4AF6E3885730CD560311C /* ScreenshotDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotDetector.swift; sourceTree = "<group>"; };
|
||||||
F6A8C632CEF4600107792899 /* TextRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineItem.swift; sourceTree = "<group>"; };
|
F6A8C632CEF4600107792899 /* TextRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||||
F73FF1A33198F5FAE9D34B1F /* FormattedBodyText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormattedBodyText.swift; sourceTree = "<group>"; };
|
F73FF1A33198F5FAE9D34B1F /* FormattedBodyText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormattedBodyText.swift; sourceTree = "<group>"; };
|
||||||
|
F754E66A8970963B15B2A41E /* PermalinkBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermalinkBuilder.swift; sourceTree = "<group>"; };
|
||||||
F77C060C2ACC4CB7336A29E7 /* EmoteRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItem.swift; sourceTree = "<group>"; };
|
F77C060C2ACC4CB7336A29E7 /* EmoteRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||||
F9E785D5137510481733A3E8 /* TextRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineView.swift; sourceTree = "<group>"; };
|
F9E785D5137510481733A3E8 /* TextRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||||
FA154570F693D93513E584C1 /* RoomMessageFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMessageFactory.swift; sourceTree = "<group>"; };
|
FA154570F693D93513E584C1 /* RoomMessageFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMessageFactory.swift; sourceTree = "<group>"; };
|
||||||
@ -1060,6 +1067,7 @@
|
|||||||
children = (
|
children = (
|
||||||
B6E89E530A8E92EC44301CA1 /* Bundle.swift */,
|
B6E89E530A8E92EC44301CA1 /* Bundle.swift */,
|
||||||
40B21E611DADDEF00307E7AC /* String.swift */,
|
40B21E611DADDEF00307E7AC /* String.swift */,
|
||||||
|
227AC5D71A4CE43512062243 /* URL.swift */,
|
||||||
);
|
);
|
||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -1268,6 +1276,7 @@
|
|||||||
C070FD43DC6BF4E50217965A /* LocalizationTests.swift */,
|
C070FD43DC6BF4E50217965A /* LocalizationTests.swift */,
|
||||||
3DC1943ADE6A62ED5129D7C8 /* LoggingTests.swift */,
|
3DC1943ADE6A62ED5129D7C8 /* LoggingTests.swift */,
|
||||||
A05707BF550D770168A406DB /* LoginViewModelTests.swift */,
|
A05707BF550D770168A406DB /* LoginViewModelTests.swift */,
|
||||||
|
6FB31A32C93D94930B253FBF /* PermalinkBuilderTests.swift */,
|
||||||
93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */,
|
93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */,
|
||||||
F03C9D319676F3C0DC6B0203 /* ScreenshotDetectorTests.swift */,
|
F03C9D319676F3C0DC6B0203 /* ScreenshotDetectorTests.swift */,
|
||||||
EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */,
|
EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */,
|
||||||
@ -1619,6 +1628,7 @@
|
|||||||
1027BB9A852F445B7623897F /* ElementSettings.swift */,
|
1027BB9A852F445B7623897F /* ElementSettings.swift */,
|
||||||
12A626D74BBE9F4A60763B45 /* ImageAnonymizer.swift */,
|
12A626D74BBE9F4A60763B45 /* ImageAnonymizer.swift */,
|
||||||
6AD1A853D605C2146B0DC028 /* MatrixEntityRegex.swift */,
|
6AD1A853D605C2146B0DC028 /* MatrixEntityRegex.swift */,
|
||||||
|
F754E66A8970963B15B2A41E /* PermalinkBuilder.swift */,
|
||||||
BB3073CCD77D906B330BC1D6 /* Tests.swift */,
|
BB3073CCD77D906B330BC1D6 /* Tests.swift */,
|
||||||
44BBB96FAA2F0D53C507396B /* Extensions */,
|
44BBB96FAA2F0D53C507396B /* Extensions */,
|
||||||
8F9A844EB44B6AD7CA18FD96 /* HTMLParsing */,
|
8F9A844EB44B6AD7CA18FD96 /* HTMLParsing */,
|
||||||
@ -2191,6 +2201,7 @@
|
|||||||
0033481EE363E4914295F188 /* LocalizationTests.swift in Sources */,
|
0033481EE363E4914295F188 /* LocalizationTests.swift in Sources */,
|
||||||
149D1942DC005D0485FB8D93 /* LoggingTests.swift in Sources */,
|
149D1942DC005D0485FB8D93 /* LoggingTests.swift in Sources */,
|
||||||
1E59B77A0B2CE83DCC1B203C /* LoginViewModelTests.swift in Sources */,
|
1E59B77A0B2CE83DCC1B203C /* LoginViewModelTests.swift in Sources */,
|
||||||
|
27E9263DA75E266690A37EB1 /* PermalinkBuilderTests.swift in Sources */,
|
||||||
46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */,
|
46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */,
|
||||||
EA31DD9043B91ECB8E45A9A6 /* ScreenshotDetectorTests.swift in Sources */,
|
EA31DD9043B91ECB8E45A9A6 /* ScreenshotDetectorTests.swift in Sources */,
|
||||||
93875ADD456142D20823ED24 /* ServerSelectionViewModelTests.swift in Sources */,
|
93875ADD456142D20823ED24 /* ServerSelectionViewModelTests.swift in Sources */,
|
||||||
@ -2326,6 +2337,7 @@
|
|||||||
368C8758FCD079E6AAA18C2C /* NoticeRoomTimelineView.swift in Sources */,
|
368C8758FCD079E6AAA18C2C /* NoticeRoomTimelineView.swift in Sources */,
|
||||||
563A05B43207D00A6B698211 /* OIDCService.swift in Sources */,
|
563A05B43207D00A6B698211 /* OIDCService.swift in Sources */,
|
||||||
CD6A72B65D3B6076F4045C30 /* PHGPostHogConfiguration.swift in Sources */,
|
CD6A72B65D3B6076F4045C30 /* PHGPostHogConfiguration.swift in Sources */,
|
||||||
|
80D00A7C62AAB44F54725C43 /* PermalinkBuilder.swift in Sources */,
|
||||||
7D1DAAA364A9A29D554BD24E /* PlaceholderAvatarImage.swift in Sources */,
|
7D1DAAA364A9A29D554BD24E /* PlaceholderAvatarImage.swift in Sources */,
|
||||||
DF504B10A4918F971A57BEF2 /* PostHogAnalyticsClient.swift in Sources */,
|
DF504B10A4918F971A57BEF2 /* PostHogAnalyticsClient.swift in Sources */,
|
||||||
BF35062D06888FA80BD139FF /* Presentable.swift in Sources */,
|
BF35062D06888FA80BD139FF /* Presentable.swift in Sources */,
|
||||||
@ -2416,6 +2428,7 @@
|
|||||||
004561D297DC8B9786AE136F /* UITestScreenIdentifier.swift in Sources */,
|
004561D297DC8B9786AE136F /* UITestScreenIdentifier.swift in Sources */,
|
||||||
03CB204C52F18E24A5C3D219 /* UITestsAppCoordinator.swift in Sources */,
|
03CB204C52F18E24A5C3D219 /* UITestsAppCoordinator.swift in Sources */,
|
||||||
17CC4FB64F3A670F43ECBE5F /* UITestsRootView.swift in Sources */,
|
17CC4FB64F3A670F43ECBE5F /* UITestsRootView.swift in Sources */,
|
||||||
|
071A017E415AD378F2961B11 /* URL.swift in Sources */,
|
||||||
8775F46AE3234A5A5688C19D /* UserIndicator.swift in Sources */,
|
8775F46AE3234A5A5688C19D /* UserIndicator.swift in Sources */,
|
||||||
7FA4227B2BAAA71560252866 /* UserIndicatorDismissal.swift in Sources */,
|
7FA4227B2BAAA71560252866 /* UserIndicatorDismissal.swift in Sources */,
|
||||||
0602FA07557F580086782A9E /* UserIndicatorPresentationContext.swift in Sources */,
|
0602FA07557F580086782A9E /* UserIndicatorPresentationContext.swift in Sources */,
|
||||||
@ -2461,6 +2474,7 @@
|
|||||||
C4180F418235DAD9DD173951 /* TemplateScreenUITests.swift in Sources */,
|
C4180F418235DAD9DD173951 /* TemplateScreenUITests.swift in Sources */,
|
||||||
0ED951768EC443A8728DE1D7 /* TimelineStyle.swift in Sources */,
|
0ED951768EC443A8728DE1D7 /* TimelineStyle.swift in Sources */,
|
||||||
75D98001C5AC38B6A5CA897C /* UITestScreenIdentifier.swift in Sources */,
|
75D98001C5AC38B6A5CA897C /* UITestScreenIdentifier.swift in Sources */,
|
||||||
|
35C57543D245E82CBFE15DF0 /* URL.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
"room_timeline_style_plain_long_description" = "Plain Timeline";
|
"room_timeline_style_plain_long_description" = "Plain Timeline";
|
||||||
"room_timeline_style_bubbled_long_description" = "Bubbled Timeline";
|
"room_timeline_style_bubbled_long_description" = "Bubbled Timeline";
|
||||||
|
|
||||||
|
"room_timeline_permalink_creation_failure" = "Failed creating the permalink";
|
||||||
|
|
||||||
// MARK: - Authentication
|
// MARK: - Authentication
|
||||||
|
|
||||||
"authentication_login_title" = "Welcome back!";
|
"authentication_login_title" = "Welcome back!";
|
||||||
|
@ -18,6 +18,19 @@ import Combine
|
|||||||
import MatrixRustSDK
|
import MatrixRustSDK
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
struct ServiceLocator {
|
||||||
|
fileprivate static var serviceLocator: ServiceLocator?
|
||||||
|
static var shared: ServiceLocator {
|
||||||
|
guard let serviceLocator = serviceLocator else {
|
||||||
|
fatalError("The service locator should be setup at this point")
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceLocator
|
||||||
|
}
|
||||||
|
|
||||||
|
let userIndicatorPresenter: UserIndicatorTypePresenter
|
||||||
|
}
|
||||||
|
|
||||||
class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||||
private let window: UIWindow
|
private let window: UIWindow
|
||||||
|
|
||||||
@ -38,7 +51,6 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
|||||||
private let screenshotDetector: ScreenshotDetector
|
private let screenshotDetector: ScreenshotDetector
|
||||||
private let backgroundTaskService: BackgroundTaskServiceProtocol
|
private let backgroundTaskService: BackgroundTaskServiceProtocol
|
||||||
|
|
||||||
private var indicatorPresenter: UserIndicatorTypePresenterProtocol
|
|
||||||
private var loadingIndicator: UserIndicator?
|
private var loadingIndicator: UserIndicator?
|
||||||
private var statusIndicator: UserIndicator?
|
private var statusIndicator: UserIndicator?
|
||||||
|
|
||||||
@ -46,13 +58,8 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
|||||||
|
|
||||||
init() {
|
init() {
|
||||||
stateMachine = AppCoordinatorStateMachine()
|
stateMachine = AppCoordinatorStateMachine()
|
||||||
|
|
||||||
do {
|
bugReportService = BugReportService(withBaseURL: BuildSettings.bugReportServiceBaseURL, sentryURL: BuildSettings.bugReportSentryURL)
|
||||||
bugReportService = try BugReportService(withBaseUrlString: BuildSettings.bugReportServiceBaseUrlString,
|
|
||||||
sentryEndpoint: BuildSettings.bugReportSentryEndpoint)
|
|
||||||
} catch {
|
|
||||||
fatalError(error.localizedDescription)
|
|
||||||
}
|
|
||||||
|
|
||||||
splashViewController = SplashViewController()
|
splashViewController = SplashViewController()
|
||||||
mainNavigationController = ElementNavigationController(rootViewController: splashViewController)
|
mainNavigationController = ElementNavigationController(rootViewController: splashViewController)
|
||||||
@ -64,7 +71,7 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
|||||||
|
|
||||||
memberDetailProviderManager = MemberDetailProviderManager()
|
memberDetailProviderManager = MemberDetailProviderManager()
|
||||||
|
|
||||||
indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: mainNavigationController)
|
ServiceLocator.serviceLocator = ServiceLocator(userIndicatorPresenter: UserIndicatorTypePresenter(presentingViewController: mainNavigationController))
|
||||||
|
|
||||||
guard let bundleIdentifier = Bundle.main.bundleIdentifier else {
|
guard let bundleIdentifier = Bundle.main.bundleIdentifier else {
|
||||||
fatalError("Should have a valid bundle identifier at this point")
|
fatalError("Should have a valid bundle identifier at this point")
|
||||||
@ -256,6 +263,7 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
|||||||
attributedStringBuilder: AttributedStringBuilder())
|
attributedStringBuilder: AttributedStringBuilder())
|
||||||
|
|
||||||
let timelineController = RoomTimelineController(userId: userId,
|
let timelineController = RoomTimelineController(userId: userId,
|
||||||
|
roomId: roomIdentifier,
|
||||||
timelineProvider: RoomTimelineProvider(roomProxy: roomProxy),
|
timelineProvider: RoomTimelineProvider(roomProxy: roomProxy),
|
||||||
timelineItemFactory: timelineItemFactory,
|
timelineItemFactory: timelineItemFactory,
|
||||||
mediaProvider: userSession.mediaProvider,
|
mediaProvider: userSession.mediaProvider,
|
||||||
@ -409,7 +417,7 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
|||||||
// MARK: Toasts and loading indicators
|
// MARK: Toasts and loading indicators
|
||||||
|
|
||||||
private func showLoadingIndicator() {
|
private func showLoadingIndicator() {
|
||||||
loadingIndicator = indicatorPresenter.present(.loading(label: ElementL10n.loading, isInteractionBlocking: true))
|
loadingIndicator = ServiceLocator.shared.userIndicatorPresenter.present(.loading(label: ElementL10n.loading, isInteractionBlocking: true))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func hideLoadingIndicator() {
|
private func hideLoadingIndicator() {
|
||||||
@ -417,10 +425,10 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func showLoginErrorToast() {
|
private func showLoginErrorToast() {
|
||||||
statusIndicator = indicatorPresenter.present(.error(label: "Failed logging in"))
|
statusIndicator = ServiceLocator.shared.userIndicatorPresenter.present(.error(label: "Failed logging in"))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func showLogoutErrorToast() {
|
private func showLogoutErrorToast() {
|
||||||
statusIndicator = indicatorPresenter.present(.error(label: "Failed logging out"))
|
statusIndicator = ServiceLocator.shared.userIndicatorPresenter.present(.error(label: "Failed logging out"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,14 +23,14 @@ final class BuildSettings {
|
|||||||
|
|
||||||
// MARK: - Bug report
|
// MARK: - Bug report
|
||||||
|
|
||||||
static let bugReportServiceBaseUrlString = "https://riot.im/bugreports"
|
static let bugReportServiceBaseURL = URL(staticString: "https://riot.im/bugreports")
|
||||||
static let bugReportSentryEndpoint = "https://f39ac49e97714316965b777d9f3d6cd8@sentry.tools.element.io/44"
|
static let bugReportSentryURL = URL(staticString: "https://f39ac49e97714316965b777d9f3d6cd8@sentry.tools.element.io/44")
|
||||||
// Use the name allocated by the bug report server
|
// Use the name allocated by the bug report server
|
||||||
static let bugReportApplicationId = "riot-ios"
|
static let bugReportApplicationId = "riot-ios"
|
||||||
static let bugReportUISIId = "element-auto-uisi"
|
static let bugReportUISIId = "element-auto-uisi"
|
||||||
|
|
||||||
static let bugReportGHLabels = ["Element-X"]
|
static let bugReportGHLabels = ["Element-X"]
|
||||||
|
|
||||||
// MARK: - Analytics
|
// MARK: - Analytics
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
@ -57,4 +57,8 @@ final class BuildSettings {
|
|||||||
// MARK: - Room screen
|
// MARK: - Room screen
|
||||||
|
|
||||||
static let defaultRoomTimelineStyle: TimelineStyle = .bubbles
|
static let defaultRoomTimelineStyle: TimelineStyle = .bubbles
|
||||||
|
|
||||||
|
// MARK: - Other
|
||||||
|
|
||||||
|
static var permalinkBaseURL = URL(staticString: "https://matrix.to")
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,8 @@ extension ElementL10n {
|
|||||||
public static let authenticationServerInfoMatrixDescription = ElementL10n.tr("Untranslated", "authentication_server_info_matrix_description")
|
public static let authenticationServerInfoMatrixDescription = ElementL10n.tr("Untranslated", "authentication_server_info_matrix_description")
|
||||||
/// Choose your server to store your data
|
/// Choose your server to store your data
|
||||||
public static let authenticationServerInfoTitle = ElementL10n.tr("Untranslated", "authentication_server_info_title")
|
public static let authenticationServerInfoTitle = ElementL10n.tr("Untranslated", "authentication_server_info_title")
|
||||||
|
/// Failed creating the permalink
|
||||||
|
public static let roomTimelinePermalinkCreationFailure = ElementL10n.tr("Untranslated", "room_timeline_permalink_creation_failure")
|
||||||
/// Bubbled Timeline
|
/// Bubbled Timeline
|
||||||
public static let roomTimelineStyleBubbledLongDescription = ElementL10n.tr("Untranslated", "room_timeline_style_bubbled_long_description")
|
public static let roomTimelineStyleBubbledLongDescription = ElementL10n.tr("Untranslated", "room_timeline_style_bubbled_long_description")
|
||||||
/// Plain Timeline
|
/// Plain Timeline
|
||||||
|
27
ElementX/Sources/Other/Extensions/URL.swift
Normal file
27
ElementX/Sources/Other/Extensions/URL.swift
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
extension URL {
|
||||||
|
init(staticString: StaticString) {
|
||||||
|
guard let url = URL(string: "\(staticString)") else {
|
||||||
|
fatalError("The static string used to create this URL is invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
self = url
|
||||||
|
}
|
||||||
|
}
|
@ -22,24 +22,6 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
|||||||
private let temporaryCodeBlockMarkingColor = UIColor.cyan
|
private let temporaryCodeBlockMarkingColor = UIColor.cyan
|
||||||
private let linkColor = UIColor.blue
|
private let linkColor = UIColor.blue
|
||||||
|
|
||||||
private let userIdDetector: NSRegularExpression
|
|
||||||
private let roomIdDetector: NSRegularExpression
|
|
||||||
private let eventIdDetector: NSRegularExpression
|
|
||||||
private let roomAliasDetector: NSRegularExpression
|
|
||||||
private let linkDetector: NSDataDetector
|
|
||||||
|
|
||||||
init() {
|
|
||||||
do {
|
|
||||||
userIdDetector = try NSRegularExpression(pattern: MatrixEntityRegex.userId.rawValue, options: .caseInsensitive)
|
|
||||||
roomIdDetector = try NSRegularExpression(pattern: MatrixEntityRegex.roomId.rawValue, options: .caseInsensitive)
|
|
||||||
eventIdDetector = try NSRegularExpression(pattern: MatrixEntityRegex.eventId.rawValue, options: .caseInsensitive)
|
|
||||||
roomAliasDetector = try NSRegularExpression(pattern: MatrixEntityRegex.roomAlias.rawValue, options: .caseInsensitive)
|
|
||||||
linkDetector = try NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
|
|
||||||
} catch {
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fromPlain(_ string: String?) async -> AttributedString? {
|
func fromPlain(_ string: String?) async -> AttributedString? {
|
||||||
await Task.detached {
|
await Task.detached {
|
||||||
fromPlain(string)
|
fromPlain(string)
|
||||||
@ -181,11 +163,11 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
|||||||
let string = attributedString.string
|
let string = attributedString.string
|
||||||
let range = NSRange(location: 0, length: attributedString.string.count)
|
let range = NSRange(location: 0, length: attributedString.string.count)
|
||||||
|
|
||||||
var matches = userIdDetector.matches(in: string, options: [], range: range)
|
var matches = MatrixEntityRegex.userIdentifierRegex.matches(in: string, options: [], range: range)
|
||||||
matches.append(contentsOf: roomIdDetector.matches(in: string, options: [], range: range))
|
matches.append(contentsOf: MatrixEntityRegex.roomIdentifierRegex.matches(in: string, options: [], range: range))
|
||||||
matches.append(contentsOf: eventIdDetector.matches(in: string, options: [], range: range))
|
matches.append(contentsOf: MatrixEntityRegex.eventIdentifierRegex.matches(in: string, options: [], range: range))
|
||||||
matches.append(contentsOf: roomAliasDetector.matches(in: string, options: [], range: range))
|
matches.append(contentsOf: MatrixEntityRegex.roomAliasRegex.matches(in: string, options: [], range: range))
|
||||||
matches.append(contentsOf: linkDetector.matches(in: string, options: [], range: range))
|
matches.append(contentsOf: MatrixEntityRegex.linkRegex.matches(in: string, options: [], range: range))
|
||||||
|
|
||||||
guard matches.count > 0 else {
|
guard matches.count > 0 else {
|
||||||
return
|
return
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
// https://spec.matrix.org/latest/appendices/#identifier-grammar
|
||||||
enum MatrixEntityRegex: String {
|
enum MatrixEntityRegex: String {
|
||||||
case homeserver
|
case homeserver
|
||||||
case userId
|
case userId
|
||||||
@ -34,7 +35,56 @@ enum MatrixEntityRegex: String {
|
|||||||
case .roomId:
|
case .roomId:
|
||||||
return "![A-Z0-9]+:" + MatrixEntityRegex.homeserver.rawValue
|
return "![A-Z0-9]+:" + MatrixEntityRegex.homeserver.rawValue
|
||||||
case .eventId:
|
case .eventId:
|
||||||
return "\\$[A-Z0-9]+:" + MatrixEntityRegex.homeserver.rawValue
|
return "\\$[A-Z0-9\\/+]+"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// swiftlint:disable force_try
|
||||||
|
static var homeserverRegex = try! NSRegularExpression(pattern: MatrixEntityRegex.homeserver.rawValue, options: .caseInsensitive)
|
||||||
|
static var userIdentifierRegex = try! NSRegularExpression(pattern: MatrixEntityRegex.userId.rawValue, options: .caseInsensitive)
|
||||||
|
static var roomAliasRegex = try! NSRegularExpression(pattern: MatrixEntityRegex.roomAlias.rawValue, options: .caseInsensitive)
|
||||||
|
static var roomIdentifierRegex = try! NSRegularExpression(pattern: MatrixEntityRegex.roomId.rawValue, options: .caseInsensitive)
|
||||||
|
static var eventIdentifierRegex = try! NSRegularExpression(pattern: MatrixEntityRegex.eventId.rawValue, options: .caseInsensitive)
|
||||||
|
static var linkRegex = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
|
||||||
|
// swiftlint:enable force_try
|
||||||
|
|
||||||
|
static func isMatrixHomeserver(_ homeserver: String) -> Bool {
|
||||||
|
guard let match = userIdentifierRegex.firstMatch(in: homeserver, range: .init(location: 0, length: homeserver.count)) else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return match.range.length == homeserver.count
|
||||||
|
}
|
||||||
|
|
||||||
|
static func isMatrixUserIdentifier(_ identifier: String) -> Bool {
|
||||||
|
guard let match = userIdentifierRegex.firstMatch(in: identifier, range: .init(location: 0, length: identifier.count)) else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return match.range.length == identifier.count
|
||||||
|
}
|
||||||
|
|
||||||
|
static func isMatrixRoomAlias(_ alias: String) -> Bool {
|
||||||
|
guard let match = roomAliasRegex.firstMatch(in: alias, range: .init(location: 0, length: alias.count)) else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return match.range.length == alias.count
|
||||||
|
}
|
||||||
|
|
||||||
|
static func isMatrixRoomIdentifier(_ identifier: String) -> Bool {
|
||||||
|
guard let match = roomIdentifierRegex.firstMatch(in: identifier, range: .init(location: 0, length: identifier.count)) else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return match.range.length == identifier.count
|
||||||
|
}
|
||||||
|
|
||||||
|
static func isMatrixEventIdentifier(_ identifier: String) -> Bool {
|
||||||
|
guard let match = eventIdentifierRegex.firstMatch(in: identifier, range: .init(location: 0, length: identifier.count)) else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return match.range.length == identifier.count
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
102
ElementX/Sources/Other/PermalinkBuilder.swift
Normal file
102
ElementX/Sources/Other/PermalinkBuilder.swift
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
enum PermalinkBuilderError: Error {
|
||||||
|
case invalidUserIdentifier
|
||||||
|
case invalidRoomIdentifier
|
||||||
|
case invalidRoomAlias
|
||||||
|
case invalidEventIdentifier
|
||||||
|
case failedConstructingURL
|
||||||
|
case failedAddingPercentEncoding
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PermalinkBuilder {
|
||||||
|
static var uriComponentCharacterSet: CharacterSet = {
|
||||||
|
var charset = CharacterSet.alphanumerics
|
||||||
|
charset.insert(charactersIn: "-_.!~*'()")
|
||||||
|
return charset
|
||||||
|
}()
|
||||||
|
|
||||||
|
static func permalinkTo(userIdentifier: String) throws -> URL {
|
||||||
|
guard MatrixEntityRegex.isMatrixUserIdentifier(userIdentifier) else {
|
||||||
|
throw PermalinkBuilderError.invalidUserIdentifier
|
||||||
|
}
|
||||||
|
|
||||||
|
let urlString = "\(BuildSettings.permalinkBaseURL)/#/\(userIdentifier)"
|
||||||
|
|
||||||
|
guard let url = URL(string: urlString) else {
|
||||||
|
throw PermalinkBuilderError.failedConstructingURL
|
||||||
|
}
|
||||||
|
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
static func permalinkTo(roomIdentifier: String) throws -> URL {
|
||||||
|
guard MatrixEntityRegex.isMatrixRoomIdentifier(roomIdentifier) else {
|
||||||
|
throw PermalinkBuilderError.invalidRoomIdentifier
|
||||||
|
}
|
||||||
|
|
||||||
|
return try permalinkTo(roomIdentifierOrAlias: roomIdentifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func permalinkTo(roomAlias: String) throws -> URL {
|
||||||
|
guard MatrixEntityRegex.isMatrixRoomAlias(roomAlias) else {
|
||||||
|
throw PermalinkBuilderError.invalidRoomAlias
|
||||||
|
}
|
||||||
|
|
||||||
|
return try permalinkTo(roomIdentifierOrAlias: roomAlias)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func permalinkTo(eventIdentifier: String, roomIdentifier: String) throws -> URL {
|
||||||
|
guard MatrixEntityRegex.isMatrixEventIdentifier(eventIdentifier) else {
|
||||||
|
throw PermalinkBuilderError.invalidEventIdentifier
|
||||||
|
}
|
||||||
|
guard MatrixEntityRegex.isMatrixRoomIdentifier(roomIdentifier) else {
|
||||||
|
throw PermalinkBuilderError.invalidRoomIdentifier
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let roomId = roomIdentifier.addingPercentEncoding(withAllowedCharacters: uriComponentCharacterSet),
|
||||||
|
let eventId = eventIdentifier.addingPercentEncoding(withAllowedCharacters: uriComponentCharacterSet) else {
|
||||||
|
throw PermalinkBuilderError.failedAddingPercentEncoding
|
||||||
|
}
|
||||||
|
|
||||||
|
let urlString = "\(BuildSettings.permalinkBaseURL)/#/\(roomId)/\(eventId)"
|
||||||
|
|
||||||
|
guard let url = URL(string: urlString) else {
|
||||||
|
throw PermalinkBuilderError.failedConstructingURL
|
||||||
|
}
|
||||||
|
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
private static func permalinkTo(roomIdentifierOrAlias: String) throws -> URL {
|
||||||
|
guard let identifier = roomIdentifierOrAlias.addingPercentEncoding(withAllowedCharacters: uriComponentCharacterSet) else {
|
||||||
|
throw PermalinkBuilderError.failedAddingPercentEncoding
|
||||||
|
}
|
||||||
|
|
||||||
|
let urlString = "\(BuildSettings.permalinkBaseURL)/#/\(identifier)"
|
||||||
|
|
||||||
|
guard let url = URL(string: urlString) else {
|
||||||
|
throw PermalinkBuilderError.failedConstructingURL
|
||||||
|
}
|
||||||
|
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
}
|
@ -64,8 +64,10 @@ extension LoginHomeserver {
|
|||||||
|
|
||||||
/// A mock homeserver that supports only supports authentication via a single SSO provider.
|
/// A mock homeserver that supports only supports authentication via a single SSO provider.
|
||||||
static var mockOIDC: LoginHomeserver {
|
static var mockOIDC: LoginHomeserver {
|
||||||
// swiftlint:disable:next force_unwrapping
|
guard let issuerURL = URL(string: "https://auth.company.com") else {
|
||||||
let issuerURL = URL(string: "https://auth.company.com")!
|
fatalError("This shoud never fail parsing")
|
||||||
|
}
|
||||||
|
|
||||||
return LoginHomeserver(address: "company.com", loginMode: .oidc(issuerURL))
|
return LoginHomeserver(address: "company.com", loginMode: .oidc(issuerURL))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ enum RoomScreenViewModelAction { }
|
|||||||
enum TimelineItemContextMenuAction: Hashable {
|
enum TimelineItemContextMenuAction: Hashable {
|
||||||
case copy
|
case copy
|
||||||
case quote
|
case quote
|
||||||
|
case copyPermalink
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RoomScreenViewAction {
|
enum RoomScreenViewAction {
|
||||||
@ -49,4 +50,12 @@ struct RoomScreenViewState: BindableState {
|
|||||||
|
|
||||||
struct RoomScreenViewStateBindings {
|
struct RoomScreenViewStateBindings {
|
||||||
var composerText: String
|
var composerText: String
|
||||||
|
|
||||||
|
/// Information describing the currently displayed alert.
|
||||||
|
var alertInfo: AlertInfo<RoomScreenErrorType>?
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RoomScreenErrorType: Hashable {
|
||||||
|
/// A specific error message shown in an alert.
|
||||||
|
case alert(String)
|
||||||
}
|
}
|
||||||
|
@ -116,7 +116,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
return [.copy, .quote]
|
return [.copy, .quote, .copyPermalink]
|
||||||
}
|
}
|
||||||
|
|
||||||
private func processContentMenuAction(_ action: TimelineItemContextMenuAction, itemId: String) {
|
private func processContentMenuAction(_ action: TimelineItemContextMenuAction, itemId: String) {
|
||||||
@ -130,6 +130,22 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
UIPasteboard.general.string = item.text
|
UIPasteboard.general.string = item.text
|
||||||
case .quote:
|
case .quote:
|
||||||
state.bindings.composerText = "> \(item.text)"
|
state.bindings.composerText = "> \(item.text)"
|
||||||
|
case .copyPermalink:
|
||||||
|
do {
|
||||||
|
let permalink = try PermalinkBuilder.permalinkTo(eventIdentifier: item.id, roomIdentifier: timelineController.roomId)
|
||||||
|
UIPasteboard.general.url = permalink
|
||||||
|
} catch {
|
||||||
|
displayError(.alert(ElementL10n.roomTimelinePermalinkCreationFailure))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func displayError(_ type: RoomScreenErrorType) {
|
||||||
|
switch type {
|
||||||
|
case .alert(let message):
|
||||||
|
state.bindings.alertInfo = AlertInfo(id: type,
|
||||||
|
title: ElementL10n.dialogTitleError,
|
||||||
|
message: message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ struct RoomScreen: View {
|
|||||||
RoomHeaderView(context: context)
|
RoomHeaderView(context: context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.alert(item: $context.alertInfo) { $0.alert }
|
||||||
}
|
}
|
||||||
|
|
||||||
private func sendMessage() {
|
private func sendMessage() {
|
||||||
|
@ -25,11 +25,15 @@ public struct TimelineItemContextMenu: View {
|
|||||||
ForEach(contextMenuActions, id: \.self) { item in
|
ForEach(contextMenuActions, id: \.self) { item in
|
||||||
switch item {
|
switch item {
|
||||||
case .copy:
|
case .copy:
|
||||||
Button("Copy") {
|
Button(ElementL10n.actionCopy) {
|
||||||
callback(item)
|
callback(item)
|
||||||
}
|
}
|
||||||
case .quote:
|
case .quote:
|
||||||
Button("Quote") {
|
Button(ElementL10n.actionQuote) {
|
||||||
|
callback(item)
|
||||||
|
}
|
||||||
|
case .copyPermalink:
|
||||||
|
Button(ElementL10n.permalink) {
|
||||||
callback(item)
|
callback(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,8 +34,7 @@ class OIDCService {
|
|||||||
|
|
||||||
private var metadata: OIDServiceConfiguration?
|
private var metadata: OIDServiceConfiguration?
|
||||||
/// Redirect URI for the request. Must match the `client_uri` in reverse DNS format.
|
/// Redirect URI for the request. Must match the `client_uri` in reverse DNS format.
|
||||||
private let redirectURI = URL(string: "io.element:/callback")!
|
private var redirectURI = URL(staticString: "io.element:/callback")
|
||||||
// swiftlint:disable:previous force_unwrapping
|
|
||||||
|
|
||||||
/// Maintains a strong ref to the authorization session that's in progress.
|
/// Maintains a strong ref to the authorization session that's in progress.
|
||||||
private var session: OIDExternalUserAgentSession?
|
private var session: OIDExternalUserAgentSession?
|
||||||
|
@ -20,40 +20,29 @@ import MatrixRustSDK
|
|||||||
import Sentry
|
import Sentry
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
enum BugReportServiceError: Error {
|
|
||||||
case invalidBaseUrlString
|
|
||||||
case invalidSentryEndpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
class BugReportService: BugReportServiceProtocol {
|
class BugReportService: BugReportServiceProtocol {
|
||||||
private let baseURL: URL
|
private let baseURL: URL
|
||||||
private let sentryEndpoint: String
|
private let sentryURL: URL
|
||||||
private let applicationId: String
|
private let applicationId: String
|
||||||
private let session: URLSession
|
private let session: URLSession
|
||||||
private var lastCrashEventId: String?
|
private var lastCrashEventId: String?
|
||||||
|
|
||||||
init(withBaseUrlString baseUrlString: String,
|
init(withBaseURL baseURL: URL,
|
||||||
sentryEndpoint: String,
|
sentryURL: URL,
|
||||||
applicationId: String = BuildSettings.bugReportApplicationId,
|
applicationId: String = BuildSettings.bugReportApplicationId,
|
||||||
session: URLSession = .shared) throws {
|
session: URLSession = .shared) {
|
||||||
guard let url = URL(string: baseUrlString) else {
|
self.baseURL = baseURL
|
||||||
throw BugReportServiceError.invalidBaseUrlString
|
self.sentryURL = sentryURL
|
||||||
}
|
|
||||||
guard !sentryEndpoint.isEmpty else {
|
|
||||||
throw BugReportServiceError.invalidSentryEndpoint
|
|
||||||
}
|
|
||||||
baseURL = url
|
|
||||||
self.sentryEndpoint = sentryEndpoint
|
|
||||||
self.applicationId = applicationId
|
self.applicationId = applicationId
|
||||||
self.session = session
|
self.session = session
|
||||||
|
|
||||||
// enable SentrySDK
|
// enable SentrySDK
|
||||||
SentrySDK.start { options in
|
SentrySDK.start { options in
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
options.enabled = false
|
options.enabled = false
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
options.dsn = sentryEndpoint
|
options.dsn = sentryURL.absoluteString
|
||||||
|
|
||||||
// Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
|
// Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
|
||||||
// We recommend adjusting this value in production.
|
// We recommend adjusting this value in production.
|
||||||
|
@ -18,6 +18,8 @@ import Combine
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
||||||
|
let roomId = "MockRoomIdentifier"
|
||||||
|
|
||||||
let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()
|
let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()
|
||||||
|
|
||||||
var timelineItems: [RoomTimelineItemProtocol] = [SeparatorRoomTimelineItem(id: UUID().uuidString,
|
var timelineItems: [RoomTimelineItemProtocol] = [SeparatorRoomTimelineItem(id: UUID().uuidString,
|
||||||
|
@ -32,16 +32,19 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let roomId: String
|
||||||
let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()
|
let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()
|
||||||
|
|
||||||
private(set) var timelineItems = [RoomTimelineItemProtocol]()
|
private(set) var timelineItems = [RoomTimelineItemProtocol]()
|
||||||
|
|
||||||
init(userId: String,
|
init(userId: String,
|
||||||
|
roomId: String,
|
||||||
timelineProvider: RoomTimelineProviderProtocol,
|
timelineProvider: RoomTimelineProviderProtocol,
|
||||||
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
||||||
mediaProvider: MediaProviderProtocol,
|
mediaProvider: MediaProviderProtocol,
|
||||||
memberDetailProvider: MemberDetailProviderProtocol) {
|
memberDetailProvider: MemberDetailProviderProtocol) {
|
||||||
self.userId = userId
|
self.userId = userId
|
||||||
|
self.roomId = roomId
|
||||||
self.timelineProvider = timelineProvider
|
self.timelineProvider = timelineProvider
|
||||||
self.timelineItemFactory = timelineItemFactory
|
self.timelineItemFactory = timelineItemFactory
|
||||||
self.mediaProvider = mediaProvider
|
self.mediaProvider = mediaProvider
|
||||||
|
@ -28,6 +28,8 @@ enum RoomTimelineControllerError: Error {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
protocol RoomTimelineControllerProtocol {
|
protocol RoomTimelineControllerProtocol {
|
||||||
|
var roomId: String { get }
|
||||||
|
|
||||||
var timelineItems: [RoomTimelineItemProtocol] { get }
|
var timelineItems: [RoomTimelineItemProtocol] { get }
|
||||||
var callbacks: PassthroughSubject<RoomTimelineControllerCallback, Never> { get }
|
var callbacks: PassthroughSubject<RoomTimelineControllerCallback, Never> { get }
|
||||||
|
|
||||||
|
@ -87,3 +87,4 @@ targets:
|
|||||||
- path: ../../ElementX/Sources/Generated/InfoPlist.swift
|
- path: ../../ElementX/Sources/Generated/InfoPlist.swift
|
||||||
- path: ../../ElementX/Resources
|
- path: ../../ElementX/Resources
|
||||||
- path: ../../ElementX/Sources/Other/Extensions/Bundle.swift
|
- path: ../../ElementX/Sources/Other/Extensions/Bundle.swift
|
||||||
|
- path: ../../ElementX/Sources/Other/Extensions/URL.swift
|
||||||
|
@ -181,7 +181,7 @@ class AttributedStringBuilderTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testEventIdLink() async {
|
func testEventIdLink() async {
|
||||||
let eventId = "$eventidentifier:matrix.org"
|
let eventId = "$eventidentifier"
|
||||||
let string = "The event is \(eventId)."
|
let string = "The event is \(eventId)."
|
||||||
checkMatrixEntityLinkIn(attributedString: await attributedStringBuilder.fromHTML(string), expected: eventId)
|
checkMatrixEntityLinkIn(attributedString: await attributedStringBuilder.fromHTML(string), expected: eventId)
|
||||||
checkMatrixEntityLinkIn(attributedString: await attributedStringBuilder.fromPlain(string), expected: eventId)
|
checkMatrixEntityLinkIn(attributedString: await attributedStringBuilder.fromPlain(string), expected: eventId)
|
||||||
@ -254,10 +254,8 @@ class AttributedStringBuilderTests: XCTestCase {
|
|||||||
|
|
||||||
XCTAssertEqual(attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedString)?.count, 1)
|
XCTAssertEqual(attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedString)?.count, 1)
|
||||||
|
|
||||||
for run in attributedString.runs {
|
for run in attributedString.runs where run.elementX.blockquote ?? false {
|
||||||
if run.elementX.blockquote != nil {
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
XCTFail("Couldn't find blockquote")
|
XCTFail("Couldn't find blockquote")
|
||||||
@ -280,10 +278,8 @@ class AttributedStringBuilderTests: XCTestCase {
|
|||||||
|
|
||||||
XCTAssertEqual(attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedString)?.count, 3)
|
XCTAssertEqual(attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedString)?.count, 3)
|
||||||
|
|
||||||
for run in attributedString.runs {
|
for run in attributedString.runs where run.elementX.blockquote ?? false {
|
||||||
if run.elementX.blockquote != nil {
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
XCTFail("Couldn't find blockquote")
|
XCTFail("Couldn't find blockquote")
|
||||||
@ -310,10 +306,8 @@ class AttributedStringBuilderTests: XCTestCase {
|
|||||||
XCTAssertEqual(coalescedComponents.first?.attributedString.runs.count, 3, "Link not present in the component")
|
XCTAssertEqual(coalescedComponents.first?.attributedString.runs.count, 3, "Link not present in the component")
|
||||||
|
|
||||||
var foundBlockquoteAndLink = false
|
var foundBlockquoteAndLink = false
|
||||||
for run in attributedString.runs {
|
for run in attributedString.runs where run.elementX.blockquote ?? false && run.link != nil {
|
||||||
if run.elementX.blockquote != nil, run.link != nil {
|
foundBlockquoteAndLink = true
|
||||||
foundBlockquoteAndLink = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
XCTAssertNotNil(foundBlockquoteAndLink, "Couldn't find blockquote or link")
|
XCTAssertNotNil(foundBlockquoteAndLink, "Couldn't find blockquote or link")
|
||||||
@ -336,10 +330,8 @@ class AttributedStringBuilderTests: XCTestCase {
|
|||||||
XCTAssertEqual(attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedString)?.count, 1)
|
XCTAssertEqual(attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedString)?.count, 1)
|
||||||
|
|
||||||
var numberOfBlockquotes = 0
|
var numberOfBlockquotes = 0
|
||||||
for run in attributedString.runs {
|
for run in attributedString.runs where run.elementX.blockquote ?? false && run.link != nil {
|
||||||
if run.elementX.blockquote != nil, run.link != nil {
|
numberOfBlockquotes += 1
|
||||||
numberOfBlockquotes += 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
XCTAssertEqual(numberOfBlockquotes, 3, "Couldn't find all the blockquotes")
|
XCTAssertEqual(numberOfBlockquotes, 3, "Couldn't find all the blockquotes")
|
||||||
@ -365,10 +357,8 @@ class AttributedStringBuilderTests: XCTestCase {
|
|||||||
XCTAssertEqual(attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedString)?.count, 6)
|
XCTAssertEqual(attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedString)?.count, 6)
|
||||||
|
|
||||||
var numberOfBlockquotes = 0
|
var numberOfBlockquotes = 0
|
||||||
for run in attributedString.runs {
|
for run in attributedString.runs where run.elementX.blockquote ?? false && run.link != nil {
|
||||||
if run.elementX.blockquote != nil, run.link != nil {
|
numberOfBlockquotes += 1
|
||||||
numberOfBlockquotes += 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
XCTAssertEqual(numberOfBlockquotes, 3, "Couldn't find all the blockquotes")
|
XCTAssertEqual(numberOfBlockquotes, 3, "Couldn't find all the blockquotes")
|
||||||
@ -384,11 +374,9 @@ class AttributedStringBuilderTests: XCTestCase {
|
|||||||
|
|
||||||
XCTAssertEqual(attributedString.runs.count, 3)
|
XCTAssertEqual(attributedString.runs.count, 3)
|
||||||
|
|
||||||
for run in attributedString.runs {
|
for run in attributedString.runs where run.link != nil {
|
||||||
if run.link != nil {
|
XCTAssertEqual(run.link?.path, expected)
|
||||||
XCTAssertEqual(run.link?.path, expected)
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
XCTFail("Couldn't find expected value.")
|
XCTFail("Couldn't find expected value.")
|
||||||
|
@ -33,21 +33,21 @@ class BugReportServiceTests: XCTestCase {
|
|||||||
files: [])
|
files: [])
|
||||||
XCTAssertFalse(result.reportUrl.isEmpty)
|
XCTAssertFalse(result.reportUrl.isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testInitialStateWithRealService() throws {
|
func testInitialStateWithRealService() throws {
|
||||||
let service = try BugReportService(withBaseUrlString: "https://www.example.com",
|
let service = BugReportService(withBaseURL: URL(staticString: "https://www.example.com"),
|
||||||
sentryEndpoint: "mock_sentry_dsn",
|
sentryURL: URL(staticString: "https://1234@sentry.com/1234"),
|
||||||
applicationId: "mock_app_id",
|
applicationId: "mock_app_id",
|
||||||
session: .mock)
|
session: .mock)
|
||||||
XCTAssertFalse(service.crashedLastRun)
|
XCTAssertFalse(service.crashedLastRun)
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor func testSubmitBugReportWithRealService() async throws {
|
@MainActor func testSubmitBugReportWithRealService() async throws {
|
||||||
let service = try BugReportService(withBaseUrlString: "https://www.example.com",
|
let service = BugReportService(withBaseURL: URL(staticString: "https://www.example.com"),
|
||||||
sentryEndpoint: "mock_sentry_dsn",
|
sentryURL: URL(staticString: "https://1234@sentry.com/1234"),
|
||||||
applicationId: "mock_app_id",
|
applicationId: "mock_app_id",
|
||||||
session: .mock)
|
session: .mock)
|
||||||
|
|
||||||
let result = try await service.submitBugReport(text: "i cannot send message",
|
let result = try await service.submitBugReport(text: "i cannot send message",
|
||||||
includeLogs: true,
|
includeLogs: true,
|
||||||
includeCrashLog: true,
|
includeCrashLog: true,
|
||||||
|
101
UnitTests/Sources/PermalinkBuilderTests.swift
Normal file
101
UnitTests/Sources/PermalinkBuilderTests.swift
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
class PermalinkBuilderTests: XCTestCase {
|
||||||
|
func testUserIdentifierPermalink() {
|
||||||
|
let userId = "@abcdefghijklmnopqrstuvwxyz1234567890._-=/:matrix.org"
|
||||||
|
|
||||||
|
do {
|
||||||
|
let permalink = try PermalinkBuilder.permalinkTo(userIdentifier: userId)
|
||||||
|
XCTAssertEqual(permalink, URL(string: "\(BuildSettings.permalinkBaseURL)/#/\(userId)"))
|
||||||
|
} catch {
|
||||||
|
XCTFail("User identifier must be valid: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInvalidUserIdentifier() {
|
||||||
|
do {
|
||||||
|
_ = try PermalinkBuilder.permalinkTo(userIdentifier: "This1sN0tV4lid!@#$%^&*()")
|
||||||
|
XCTFail("A permalink should not be created.")
|
||||||
|
} catch {
|
||||||
|
XCTAssertEqual(error as? PermalinkBuilderError, PermalinkBuilderError.invalidUserIdentifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRoomIdentifierPermalink() throws {
|
||||||
|
let roomId = "!abcdefghijklmnopqrstuvwxyz1234567890:matrix.org"
|
||||||
|
|
||||||
|
do {
|
||||||
|
let permalink = try PermalinkBuilder.permalinkTo(roomIdentifier: roomId)
|
||||||
|
XCTAssertEqual(permalink, URL(string: "\(BuildSettings.permalinkBaseURL)/#/!abcdefghijklmnopqrstuvwxyz1234567890%3Amatrix.org"))
|
||||||
|
} catch {
|
||||||
|
XCTFail("Room identifier must be valid: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInvalidRoomIdentifier() {
|
||||||
|
do {
|
||||||
|
_ = try PermalinkBuilder.permalinkTo(roomIdentifier: "This1sN0tV4lid!@#$%^&*()")
|
||||||
|
XCTFail("A permalink should not be created.")
|
||||||
|
} catch {
|
||||||
|
XCTAssertEqual(error as? PermalinkBuilderError, PermalinkBuilderError.invalidRoomIdentifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRoomAliasPermalink() throws {
|
||||||
|
let roomAlias = "#abcdefghijklmnopqrstuvwxyz-_.1234567890:matrix.org"
|
||||||
|
|
||||||
|
do {
|
||||||
|
let permalink = try PermalinkBuilder.permalinkTo(roomAlias: roomAlias)
|
||||||
|
XCTAssertEqual(permalink, URL(string: "\(BuildSettings.permalinkBaseURL)/#/%23abcdefghijklmnopqrstuvwxyz-_.1234567890%3Amatrix.org"))
|
||||||
|
} catch {
|
||||||
|
XCTFail("Room alias must be valid: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInvalidRoomAlias() {
|
||||||
|
do {
|
||||||
|
_ = try PermalinkBuilder.permalinkTo(roomAlias: "This1sN0tV4lid!@#$%^&*()")
|
||||||
|
XCTFail("A permalink should not be created.")
|
||||||
|
} catch {
|
||||||
|
XCTAssertEqual(error as? PermalinkBuilderError, PermalinkBuilderError.invalidRoomAlias)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testEventPermalink() throws {
|
||||||
|
let eventId = "$abcdefghijklmnopqrstuvwxyz1234567890"
|
||||||
|
let roomId = "!abcdefghijklmnopqrstuvwxyz1234567890:matrix.org"
|
||||||
|
|
||||||
|
do {
|
||||||
|
let permalink = try PermalinkBuilder.permalinkTo(eventIdentifier: eventId, roomIdentifier: roomId)
|
||||||
|
XCTAssertEqual(permalink, URL(string: "\(BuildSettings.permalinkBaseURL)/#/!abcdefghijklmnopqrstuvwxyz1234567890%3Amatrix.org/%24abcdefghijklmnopqrstuvwxyz1234567890"))
|
||||||
|
} catch {
|
||||||
|
XCTFail("Room and event identifiers must be valid: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInvalidEventIdentifier() {
|
||||||
|
do {
|
||||||
|
_ = try PermalinkBuilder.permalinkTo(eventIdentifier: "This1sN0tV4lid!@#$%^&*()", roomIdentifier: "")
|
||||||
|
XCTFail("A permalink should not be created.")
|
||||||
|
} catch {
|
||||||
|
XCTAssertEqual(error as? PermalinkBuilderError, PermalinkBuilderError.invalidEventIdentifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -49,3 +49,4 @@ targets:
|
|||||||
- path: ../SupportingFiles
|
- path: ../SupportingFiles
|
||||||
- path: ../../Tools/Scripts/Templates/SimpleScreenExample/Tests/Unit
|
- path: ../../Tools/Scripts/Templates/SimpleScreenExample/Tests/Unit
|
||||||
- path: ../Resources
|
- path: ../Resources
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user