mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-10-31 08:15:24 +00:00
* Fixes #276 - Rebuilt room timeline: - Removed the need for the ListCollectionViewAdapter - Rewrote the TimelineItemList without using introspection - Added ReversedScrollView for laying out items at the bottom/trailing - Rewrote TimelineProvider diffing through CollectionDifference (similar to the RoomSummaryProvider) - Added back `scrollDismissesKeyboard` behavior - Various other tweaks and fixes - Fixed various warnings: - removed async AttributedStringBuilder as AttributedString is non-sendable, made the RoomTimelineItemFactory synchronous - removed unused virtual timeline items - removed unused isOutgoing property from the FormattedBodyText * Make TimelineItemContextMenuActions indentifiable and specify contextMenu identifiers * Bump the matrix-rust-components-swift to v1.0.16-alpha * Add changes file and changelog contribution guide * Fix attributed string builder unit tests
This commit is contained in:
@@ -52,6 +52,58 @@ Please see our [pull request guide](https://github.com/vector-im/element-android
|
|||||||
|
|
||||||
New screen flows are currently using MVVM-Coordinator pattern. Please refer to the screen template under [Tools/Scripts/createScreen.sh](Tools/Scripts/createScreen.sh) to create a new screen or a new screen flow.
|
New screen flows are currently using MVVM-Coordinator pattern. Please refer to the screen template under [Tools/Scripts/createScreen.sh](Tools/Scripts/createScreen.sh) to create a new screen or a new screen flow.
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
All changes, even minor ones, need a corresponding changelog / newsfragment
|
||||||
|
entry. These are managed by [Towncrier](https://github.com/twisted/towncrier).
|
||||||
|
|
||||||
|
To create a changelog entry, make a new file in the `changelog.d` directory
|
||||||
|
named in the format of `ElementXiOSIssueNumber.type`. The type can be one of the
|
||||||
|
following:
|
||||||
|
|
||||||
|
- `feature` for a new feature
|
||||||
|
- `change` for updates to an existing feature
|
||||||
|
- `bugfix` for bug fix
|
||||||
|
- `api` for an api break
|
||||||
|
- `i18n` for translations
|
||||||
|
- `build` for changes related to build, tools, CI/CD
|
||||||
|
- `doc` for updates to the documentation
|
||||||
|
- `wip` for anything that isn't ready to ship and will be enabled at a later date
|
||||||
|
- `misc` for other changes
|
||||||
|
|
||||||
|
This file will become part of our [changelog](CHANGES.md) at the next
|
||||||
|
release, so the content of the file should be a short description of your
|
||||||
|
change in the same style as the rest of the changelog. The file must only
|
||||||
|
contain one line. It can contain Markdown formatting. It should start with the
|
||||||
|
area of the change (screen, module, ...) and end with a full stop (.) or an
|
||||||
|
exclamation mark (!) for consistency.
|
||||||
|
|
||||||
|
Adding credits to the changelog is encouraged, we value your
|
||||||
|
contributions and would like to have you shouted out in the release notes!
|
||||||
|
|
||||||
|
For example, a fix for an issue #1234 would have its changelog entry in
|
||||||
|
`changelog.d/1234.bugfix`, and contain content like:
|
||||||
|
|
||||||
|
> Voice Messages: Fix a crash when sending a voice message. Contributed by
|
||||||
|
> Jane Matrix.
|
||||||
|
|
||||||
|
If there are multiple pull requests involved in a single bugfix/feature/etc,
|
||||||
|
then the content for each `changelog.d` file should be the same. Towncrier will
|
||||||
|
merge the matching files together into a single changelog entry when we come to
|
||||||
|
release.
|
||||||
|
|
||||||
|
There are exceptions on the `ElementXiOSIssueNumber.type` entry format. Even if
|
||||||
|
it is not encouraged, you can use:
|
||||||
|
|
||||||
|
- `pr-[PRNumber].type` for a PR with no related issue
|
||||||
|
- `x-nolink-[AnyNumber].type` for a PR with a change entry that will not have a link automatically appended. It must be used for internal project update only. `AnyNumber` should be a value that does not clash with existing files.
|
||||||
|
|
||||||
|
To preview the changelog for pending changelog entries, use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ towncrier build --draft --version 1.2.3
|
||||||
|
```
|
||||||
|
|
||||||
## Coding style
|
## Coding style
|
||||||
|
|
||||||
For Swift coding style we use [SwiftLint](https://github.com/realm/SwiftLint) to check some conventions at compile time (rules are located in the `.swiftlint.yml` file).
|
For Swift coding style we use [SwiftLint](https://github.com/realm/SwiftLint) to check some conventions at compile time (rules are located in the `.swiftlint.yml` file).
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
072BA9DBA932374CCA300125 /* MessageComposerTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE6C10032A77AE7DC5AA4C50 /* MessageComposerTextField.swift */; };
|
072BA9DBA932374CCA300125 /* MessageComposerTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE6C10032A77AE7DC5AA4C50 /* MessageComposerTextField.swift */; };
|
||||||
095C0ACFC234E0550A6404C5 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FC803282F9268D49F4ABF14 /* AppCoordinator.swift */; };
|
095C0ACFC234E0550A6404C5 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FC803282F9268D49F4ABF14 /* AppCoordinator.swift */; };
|
||||||
09AAF04B27732046C755D914 /* SoftLogoutViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32C5DAA1773F57653BF1C4F9 /* SoftLogoutViewModelTests.swift */; };
|
09AAF04B27732046C755D914 /* SoftLogoutViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32C5DAA1773F57653BF1C4F9 /* SoftLogoutViewModelTests.swift */; };
|
||||||
09BFDE37F0D0E586D26B17D7 /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 886A0A498FA01E8EDD451D05 /* Sentry */; };
|
|
||||||
0AE0AB1952F186EB86719B4F /* HomeScreenRoomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */; };
|
0AE0AB1952F186EB86719B4F /* HomeScreenRoomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */; };
|
||||||
0B1F80C2BF7D223159FBA82C /* ImageAnonymizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6045E825AE900A92D61FEFF0 /* ImageAnonymizerTests.swift */; };
|
0B1F80C2BF7D223159FBA82C /* ImageAnonymizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6045E825AE900A92D61FEFF0 /* ImageAnonymizerTests.swift */; };
|
||||||
0C38C3E771B472E27295339D /* SessionVerificationModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BB9A17AC512A7EF4B106E5 /* SessionVerificationModels.swift */; };
|
0C38C3E771B472E27295339D /* SessionVerificationModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BB9A17AC512A7EF4B106E5 /* SessionVerificationModels.swift */; };
|
||||||
@@ -74,7 +73,7 @@
|
|||||||
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 */; };
|
||||||
29E20505F321071E8375F99B /* BuildSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 263B3B811C2B900F12C6F695 /* BuildSettings.swift */; };
|
29E20505F321071E8375F99B /* BuildSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 263B3B811C2B900F12C6F695 /* BuildSettings.swift */; };
|
||||||
29EE1791E0AFA1ABB7F23D2F /* PostHog in Frameworks */ = {isa = PBXBuildFile; productRef = CCE5BF78B125320CBF3BB834 /* PostHog */; };
|
29EE1791E0AFA1ABB7F23D2F /* SwiftyBeaver in Frameworks */ = {isa = PBXBuildFile; productRef = A981A4CA233FB5C13B9CA690 /* SwiftyBeaver */; };
|
||||||
2A90D9F91A836E30B7D78838 /* MXLogObjcWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 54E438DBCBDC7A41B95DDDD9 /* MXLogObjcWrapper.m */; };
|
2A90D9F91A836E30B7D78838 /* MXLogObjcWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 54E438DBCBDC7A41B95DDDD9 /* MXLogObjcWrapper.m */; };
|
||||||
2BA59D0AEFB4B82A2EC2A326 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 78A5A8DE1E2B09C978C7F3B0 /* KeychainAccess */; };
|
2BA59D0AEFB4B82A2EC2A326 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 78A5A8DE1E2B09C978C7F3B0 /* KeychainAccess */; };
|
||||||
2BAA5B222856068158D0B3C6 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = B1E8B697DF78FE7F61FC6CA4 /* MatrixRustSDK */; };
|
2BAA5B222856068158D0B3C6 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = B1E8B697DF78FE7F61FC6CA4 /* MatrixRustSDK */; };
|
||||||
@@ -83,11 +82,11 @@
|
|||||||
2F30EFEB7BD39242D1AD96F3 /* LoginViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E1FB768A24FDD2A5CA16E3C /* LoginViewModelProtocol.swift */; };
|
2F30EFEB7BD39242D1AD96F3 /* LoginViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E1FB768A24FDD2A5CA16E3C /* LoginViewModelProtocol.swift */; };
|
||||||
2F94054F50E312AF30BE07F3 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40B21E611DADDEF00307E7AC /* String.swift */; };
|
2F94054F50E312AF30BE07F3 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40B21E611DADDEF00307E7AC /* String.swift */; };
|
||||||
30122AB3484AC6C3A7F6A717 /* ActivityIndicatorView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B64F3A3D0DF86ED5A241AB05 /* ActivityIndicatorView.xib */; };
|
30122AB3484AC6C3A7F6A717 /* ActivityIndicatorView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B64F3A3D0DF86ED5A241AB05 /* ActivityIndicatorView.xib */; };
|
||||||
308BD9343B95657FAA583FB7 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = D82E84F90358CC1118E6034B /* Introspect */; };
|
308BD9343B95657FAA583FB7 /* SwiftyBeaver in Frameworks */ = {isa = PBXBuildFile; productRef = AD2AC190E55B2BD4D0F1D4A7 /* SwiftyBeaver */; };
|
||||||
3097A0A867D2B19CE32DAE58 /* UIKitBackgroundTaskService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF1FFC3336EB23374BBBFCC /* UIKitBackgroundTaskService.swift */; };
|
3097A0A867D2B19CE32DAE58 /* UIKitBackgroundTaskService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF1FFC3336EB23374BBBFCC /* UIKitBackgroundTaskService.swift */; };
|
||||||
313382FC5D38064EAAA35CB2 /* FileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D1CC633517D695FEC54208 /* FileManager.swift */; };
|
313382FC5D38064EAAA35CB2 /* FileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D1CC633517D695FEC54208 /* FileManager.swift */; };
|
||||||
32BA37B01B05261FCF2D4B45 /* WeakDictionaryKeyReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090CA61A835C151CEDF8F372 /* WeakDictionaryKeyReference.swift */; };
|
32BA37B01B05261FCF2D4B45 /* WeakDictionaryKeyReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090CA61A835C151CEDF8F372 /* WeakDictionaryKeyReference.swift */; };
|
||||||
33CAC1226DFB8B5D8447D286 /* SwiftyBeaver in Frameworks */ = {isa = PBXBuildFile; productRef = A981A4CA233FB5C13B9CA690 /* SwiftyBeaver */; };
|
33CAC1226DFB8B5D8447D286 /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 3853B78FB8531B83936C5DA6 /* SwiftState */; };
|
||||||
344AF4CBB6D8786214878642 /* NavigationRouterStoreProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B9D5F812E5AD6DC786DBC9B /* NavigationRouterStoreProtocol.swift */; };
|
344AF4CBB6D8786214878642 /* NavigationRouterStoreProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B9D5F812E5AD6DC786DBC9B /* NavigationRouterStoreProtocol.swift */; };
|
||||||
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 */; };
|
||||||
@@ -100,16 +99,15 @@
|
|||||||
388FD50AC66E9E684DDFA9D8 /* ServerSelectionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5D2C0950F8196232D88045C /* ServerSelectionScreen.swift */; };
|
388FD50AC66E9E684DDFA9D8 /* ServerSelectionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5D2C0950F8196232D88045C /* ServerSelectionScreen.swift */; };
|
||||||
38C76D586404C1FDED095F3A /* LoginModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31B01468022EC826CB2FD2C0 /* LoginModels.swift */; };
|
38C76D586404C1FDED095F3A /* LoginModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31B01468022EC826CB2FD2C0 /* LoginModels.swift */; };
|
||||||
39929D29B265C3F6606047DE /* AlignedScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8872E9C5E91E9F2BFC4EBCCA /* AlignedScrollView.swift */; };
|
39929D29B265C3F6606047DE /* AlignedScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8872E9C5E91E9F2BFC4EBCCA /* AlignedScrollView.swift */; };
|
||||||
3A64A93A651A3CB8774ADE8E /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 67E7A6F388D3BF85767609D9 /* Sentry */; };
|
3A64A93A651A3CB8774ADE8E /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 21C83087604B154AA30E9A8F /* SnapshotTesting */; };
|
||||||
3B770CB4DED51CC362C66D47 /* SettingsModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4990FDBDA96B88E214F92F48 /* SettingsModels.swift */; };
|
3B770CB4DED51CC362C66D47 /* SettingsModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4990FDBDA96B88E214F92F48 /* SettingsModels.swift */; };
|
||||||
3C549A0BF39F8A854D45D9FD /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 5986E300FC849DEAB2EE7AEB /* Introspect */; };
|
3C549A0BF39F8A854D45D9FD /* PostHog in Frameworks */ = {isa = PBXBuildFile; productRef = 4278261E147DB2DE5CFB7FC5 /* PostHog */; };
|
||||||
3C73442084BF8A6939F0F80B /* AnalyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5445FCE0CE15E634FDC1A2E2 /* AnalyticsService.swift */; };
|
3C73442084BF8A6939F0F80B /* AnalyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5445FCE0CE15E634FDC1A2E2 /* AnalyticsService.swift */; };
|
||||||
3DA57CA0D609A6B37CA1DC2F /* BugReportService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DC38E64A5ED3FDB201029A /* BugReportService.swift */; };
|
3DA57CA0D609A6B37CA1DC2F /* BugReportService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DC38E64A5ED3FDB201029A /* BugReportService.swift */; };
|
||||||
3ED2725734568F6B8CC87544 /* AttributedStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5C6FBF97B6EED3D4FA5EFF /* AttributedStringBuilder.swift */; };
|
3ED2725734568F6B8CC87544 /* AttributedStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5C6FBF97B6EED3D4FA5EFF /* AttributedStringBuilder.swift */; };
|
||||||
3F2148F11164C7C5609984EB /* SwiftyBeaver in Frameworks */ = {isa = PBXBuildFile; productRef = AD2AC190E55B2BD4D0F1D4A7 /* SwiftyBeaver */; };
|
3F2148F11164C7C5609984EB /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 19CD5B074D7DD44AF4C58BB6 /* SwiftState */; };
|
||||||
3F327A62D233933F54F0F33A /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 21C83087604B154AA30E9A8F /* SnapshotTesting */; };
|
407DCE030E0F9B7C9861D38A /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 997C7385E1A07E061D7E2100 /* GZIP */; };
|
||||||
407DCE030E0F9B7C9861D38A /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 9573B94B1C86C6DF751AF3FD /* SwiftState */; };
|
41DFDD212D1BE57CA50D783B /* SwiftyBeaver in Frameworks */ = {isa = PBXBuildFile; productRef = FD43A50D9B75C9D6D30F006B /* SwiftyBeaver */; };
|
||||||
41DFDD212D1BE57CA50D783B /* PostHog in Frameworks */ = {isa = PBXBuildFile; productRef = 4278261E147DB2DE5CFB7FC5 /* PostHog */; };
|
|
||||||
438FB9BC535BC95948AA5F34 /* SettingsViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2F9D5C39A4494D19F33E38 /* SettingsViewModelProtocol.swift */; };
|
438FB9BC535BC95948AA5F34 /* SettingsViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2F9D5C39A4494D19F33E38 /* SettingsViewModelProtocol.swift */; };
|
||||||
43FD77998F33C32718C51450 /* TemplateCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD460ED7ED1E03B85DEA25C /* TemplateCoordinator.swift */; };
|
43FD77998F33C32718C51450 /* TemplateCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD460ED7ED1E03B85DEA25C /* TemplateCoordinator.swift */; };
|
||||||
440123E29E2F9B001A775BBE /* TimelineItemProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D505843AB66822EB91F0DF0 /* TimelineItemProxy.swift */; };
|
440123E29E2F9B001A775BBE /* TimelineItemProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D505843AB66822EB91F0DF0 /* TimelineItemProxy.swift */; };
|
||||||
@@ -119,7 +117,7 @@
|
|||||||
46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */; };
|
46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */; };
|
||||||
4669804D0369FBED4E8625D1 /* ToastViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4470B8CB654B097D807AA713 /* ToastViewPresenter.swift */; };
|
4669804D0369FBED4E8625D1 /* ToastViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4470B8CB654B097D807AA713 /* ToastViewPresenter.swift */; };
|
||||||
490E606044B18985055FF690 /* SettingsUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3E29F98CF0E960689A410E3 /* SettingsUITests.swift */; };
|
490E606044B18985055FF690 /* SettingsUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3E29F98CF0E960689A410E3 /* SettingsUITests.swift */; };
|
||||||
492274DA6691EE985C2FCCAA /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 3853B78FB8531B83936C5DA6 /* SwiftState */; };
|
492274DA6691EE985C2FCCAA /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 1BCD21310B997A6837B854D6 /* GZIP */; };
|
||||||
49E9B99CB6A275C7744351F0 /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D58333B377888012740101 /* LoginViewModel.swift */; };
|
49E9B99CB6A275C7744351F0 /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D58333B377888012740101 /* LoginViewModel.swift */; };
|
||||||
49F2E7DD8CAACE09CEECE3E6 /* SeparatorRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6390A6DC140CA3D6865A66FF /* SeparatorRoomTimelineView.swift */; };
|
49F2E7DD8CAACE09CEECE3E6 /* SeparatorRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6390A6DC140CA3D6865A66FF /* SeparatorRoomTimelineView.swift */; };
|
||||||
4A2E0DBB63919AC8309B6D40 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A191D3FDB995309C7E2DE7D /* SettingsViewModel.swift */; };
|
4A2E0DBB63919AC8309B6D40 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A191D3FDB995309C7E2DE7D /* SettingsViewModel.swift */; };
|
||||||
@@ -147,9 +145,9 @@
|
|||||||
5E1FCC43B738941D5A5F1794 /* SplashScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B73A8C3118EAC7BF3F3EE7A /* SplashScreenViewModelProtocol.swift */; };
|
5E1FCC43B738941D5A5F1794 /* SplashScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B73A8C3118EAC7BF3F3EE7A /* SplashScreenViewModelProtocol.swift */; };
|
||||||
5F1FDE49DFD0C680386E48F9 /* TemplateViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B80895CE021B49847BD7D74 /* TemplateViewModelProtocol.swift */; };
|
5F1FDE49DFD0C680386E48F9 /* TemplateViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B80895CE021B49847BD7D74 /* TemplateViewModelProtocol.swift */; };
|
||||||
5F5488FBC9CFEB6F433D74A4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7109E709A7738E6BCC4553E6 /* Localizable.strings */; };
|
5F5488FBC9CFEB6F433D74A4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7109E709A7738E6BCC4553E6 /* Localizable.strings */; };
|
||||||
60ED66E63A169E47489348A8 /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 19CD5B074D7DD44AF4C58BB6 /* SwiftState */; };
|
60ED66E63A169E47489348A8 /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 2B788C81F6369D164ADEB917 /* GZIP */; };
|
||||||
617624A97BDBB75ED3DD8156 /* RoomScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A00C7A331B72C0F05C00392F /* RoomScreenViewModelProtocol.swift */; };
|
617624A97BDBB75ED3DD8156 /* RoomScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A00C7A331B72C0F05C00392F /* RoomScreenViewModelProtocol.swift */; };
|
||||||
6298AB0906DDD3525CD78C6B /* SwiftyBeaver in Frameworks */ = {isa = PBXBuildFile; productRef = FD43A50D9B75C9D6D30F006B /* SwiftyBeaver */; };
|
6298AB0906DDD3525CD78C6B /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 9573B94B1C86C6DF751AF3FD /* SwiftState */; };
|
||||||
62BBF5BE7B905222F0477FF2 /* MediaSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8210612D17A39369480FC183 /* MediaSource.swift */; };
|
62BBF5BE7B905222F0477FF2 /* MediaSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8210612D17A39369480FC183 /* MediaSource.swift */; };
|
||||||
63C9AF0FB8278AF1C0388A0C /* TemplateModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAB10E673916D2B8D21FD197 /* TemplateModels.swift */; };
|
63C9AF0FB8278AF1C0388A0C /* TemplateModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAB10E673916D2B8D21FD197 /* TemplateModels.swift */; };
|
||||||
64FF5CB4E35971255872E1BB /* AuthenticationServiceProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F0CB536D1C3CC15AA740CC6 /* AuthenticationServiceProxyProtocol.swift */; };
|
64FF5CB4E35971255872E1BB /* AuthenticationServiceProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F0CB536D1C3CC15AA740CC6 /* AuthenticationServiceProxyProtocol.swift */; };
|
||||||
@@ -216,7 +214,7 @@
|
|||||||
8CC12086CBF91A7E10CDC205 /* HomeScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D653265D006E708E4E51AD64 /* HomeScreenCoordinator.swift */; };
|
8CC12086CBF91A7E10CDC205 /* HomeScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D653265D006E708E4E51AD64 /* HomeScreenCoordinator.swift */; };
|
||||||
8D3E1FADD78E72504DE0E402 /* UserAgentBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB3B237387B8288A5A938F1B /* UserAgentBuilderTests.swift */; };
|
8D3E1FADD78E72504DE0E402 /* UserAgentBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB3B237387B8288A5A938F1B /* UserAgentBuilderTests.swift */; };
|
||||||
8D9F646387DF656EF91EE4CB /* RoomMessageFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F37AB24AF5A006521D38D1 /* RoomMessageFactoryProtocol.swift */; };
|
8D9F646387DF656EF91EE4CB /* RoomMessageFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F37AB24AF5A006521D38D1 /* RoomMessageFactoryProtocol.swift */; };
|
||||||
8F2FAA98457750D9D664136F /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 997C7385E1A07E061D7E2100 /* GZIP */; };
|
8F2FAA98457750D9D664136F /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 7731767AE437BA3BD2CC14A8 /* Sentry */; };
|
||||||
90DF83A6A347F7EE7EDE89EE /* AttributedStringBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */; };
|
90DF83A6A347F7EE7EDE89EE /* AttributedStringBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */; };
|
||||||
90EB25D13AE6EEF034BDE9D2 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D52BAA5BADB06E5E8C295D /* Assets.swift */; };
|
90EB25D13AE6EEF034BDE9D2 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D52BAA5BADB06E5E8C295D /* Assets.swift */; };
|
||||||
9219640F4D980CFC5FE855AD /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 536E72DCBEEC4A1FE66CFDCE /* target.yml */; };
|
9219640F4D980CFC5FE855AD /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 536E72DCBEEC4A1FE66CFDCE /* target.yml */; };
|
||||||
@@ -253,7 +251,7 @@
|
|||||||
A32517FB1CA0BBCE2BC75249 /* BugReportCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6C07DA7D3FF193F7419F55 /* BugReportCoordinator.swift */; };
|
A32517FB1CA0BBCE2BC75249 /* BugReportCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6C07DA7D3FF193F7419F55 /* BugReportCoordinator.swift */; };
|
||||||
A371629728E597C5FCA3C2B2 /* Analytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73FC861755C6388F62B9280A /* Analytics.swift */; };
|
A371629728E597C5FCA3C2B2 /* Analytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73FC861755C6388F62B9280A /* Analytics.swift */; };
|
||||||
A37EED79941AD3B7140B3822 /* UIDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287FC98AF2664EAD79C0D902 /* UIDevice.swift */; };
|
A37EED79941AD3B7140B3822 /* UIDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287FC98AF2664EAD79C0D902 /* UIDevice.swift */; };
|
||||||
A4E885358D7DD5A072A06824 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 04C28663564E008DB32B5972 /* Introspect */; };
|
A4E885358D7DD5A072A06824 /* PostHog in Frameworks */ = {isa = PBXBuildFile; productRef = CCE5BF78B125320CBF3BB834 /* PostHog */; };
|
||||||
A50849766F056FD1DB942DEA /* AlertInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EEB64CC6F3DF5B68736A6B4 /* AlertInfo.swift */; };
|
A50849766F056FD1DB942DEA /* AlertInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EEB64CC6F3DF5B68736A6B4 /* AlertInfo.swift */; };
|
||||||
A5C8F013ED9FB8AA6FEE18A7 /* InfoPlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A901D95158B02CA96C79C7F /* InfoPlist.swift */; };
|
A5C8F013ED9FB8AA6FEE18A7 /* InfoPlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A901D95158B02CA96C79C7F /* InfoPlist.swift */; };
|
||||||
A663FE6704CB500EBE782AE1 /* AnalyticsPromptCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4DE1CF8F5EFD353B1A5E36F /* AnalyticsPromptCoordinator.swift */; };
|
A663FE6704CB500EBE782AE1 /* AnalyticsPromptCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4DE1CF8F5EFD353B1A5E36F /* AnalyticsPromptCoordinator.swift */; };
|
||||||
@@ -290,6 +288,7 @@
|
|||||||
BCEC41FB1F2BB663183863E4 /* LoginServerInfoSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D379E13DD9D987470A3C70C /* LoginServerInfoSection.swift */; };
|
BCEC41FB1F2BB663183863E4 /* LoginServerInfoSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D379E13DD9D987470A3C70C /* LoginServerInfoSection.swift */; };
|
||||||
BEEC06EFD30BFCA02F0FD559 /* UserIndicatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8EA85D4F10D7445BB6368A /* UserIndicatorTests.swift */; };
|
BEEC06EFD30BFCA02F0FD559 /* UserIndicatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8EA85D4F10D7445BB6368A /* UserIndicatorTests.swift */; };
|
||||||
BF35062D06888FA80BD139FF /* Presentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB7F9D6FC121204D59E18DF /* Presentable.swift */; };
|
BF35062D06888FA80BD139FF /* Presentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB7F9D6FC121204D59E18DF /* Presentable.swift */; };
|
||||||
|
BFD1AC03B6F8C5F5897D5B55 /* ReversedScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2DE30233B57761F8AFEB415 /* ReversedScrollView.swift */; };
|
||||||
C052A8CDC7A8E7A2D906674F /* UserIndicatorStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FAA6438B00FDB130F404E31 /* UserIndicatorStore.swift */; };
|
C052A8CDC7A8E7A2D906674F /* UserIndicatorStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FAA6438B00FDB130F404E31 /* UserIndicatorStore.swift */; };
|
||||||
C2CF93B067FD935E4F82FE44 /* SplashScreenPageIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850064FF8D7DB9C875E7AA1A /* SplashScreenPageIndicator.swift */; };
|
C2CF93B067FD935E4F82FE44 /* SplashScreenPageIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850064FF8D7DB9C875E7AA1A /* SplashScreenPageIndicator.swift */; };
|
||||||
C35CF4DAB1467FE1BBDC204B /* MessageTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAF1C75771D9DC75877F4B4 /* MessageTimelineItem.swift */; };
|
C35CF4DAB1467FE1BBDC204B /* MessageTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAF1C75771D9DC75877F4B4 /* MessageTimelineItem.swift */; };
|
||||||
@@ -336,20 +335,19 @@
|
|||||||
E481C8FDCB6C089963C95344 /* DeviceKit in Frameworks */ = {isa = PBXBuildFile; productRef = BC01130651CB23340B899032 /* DeviceKit */; };
|
E481C8FDCB6C089963C95344 /* DeviceKit in Frameworks */ = {isa = PBXBuildFile; productRef = BC01130651CB23340B899032 /* DeviceKit */; };
|
||||||
E5895C74615CBE8462FB840F /* SessionVerificationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCF86010A0A719A9A50EEC59 /* SessionVerificationCoordinator.swift */; };
|
E5895C74615CBE8462FB840F /* SessionVerificationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCF86010A0A719A9A50EEC59 /* SessionVerificationCoordinator.swift */; };
|
||||||
E81EEC1675F2371D12A880A3 /* MockRoomTimelineController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61ADFB893DEF81E58DF3FAB9 /* MockRoomTimelineController.swift */; };
|
E81EEC1675F2371D12A880A3 /* MockRoomTimelineController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61ADFB893DEF81E58DF3FAB9 /* MockRoomTimelineController.swift */; };
|
||||||
E8AFB40CC7C3DF1930DA89E2 /* ListCollectionViewAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED0C923DD8D9946257D46806 /* ListCollectionViewAdapter.swift */; };
|
|
||||||
E96005321849DBD7C72A28F2 /* UITestsAppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46C208DA43CE25D13E670F40 /* UITestsAppCoordinator.swift */; };
|
E96005321849DBD7C72A28F2 /* UITestsAppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46C208DA43CE25D13E670F40 /* UITestsAppCoordinator.swift */; };
|
||||||
EA1E7949533E19C6D862680A /* MediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 885D8C42DD17625B5261BEFF /* MediaProvider.swift */; };
|
EA1E7949533E19C6D862680A /* MediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 885D8C42DD17625B5261BEFF /* MediaProvider.swift */; };
|
||||||
EA31DD9043B91ECB8E45A9A6 /* ScreenshotDetectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03C9D319676F3C0DC6B0203 /* ScreenshotDetectorTests.swift */; };
|
EA31DD9043B91ECB8E45A9A6 /* ScreenshotDetectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03C9D319676F3C0DC6B0203 /* ScreenshotDetectorTests.swift */; };
|
||||||
EA65360A0EC026DD83AC0CF5 /* AuthenticationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6CA5F386C7701C129398945 /* AuthenticationCoordinator.swift */; };
|
EA65360A0EC026DD83AC0CF5 /* AuthenticationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6CA5F386C7701C129398945 /* AuthenticationCoordinator.swift */; };
|
||||||
EBD6C79705B3DDB2F7E5F554 /* UserSessionStoreProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1B52D0ABBA7091A991CAFE /* UserSessionStoreProtocol.swift */; };
|
EBD6C79705B3DDB2F7E5F554 /* UserSessionStoreProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1B52D0ABBA7091A991CAFE /* UserSessionStoreProtocol.swift */; };
|
||||||
EC280623A42904341363EAAF /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 2B788C81F6369D164ADEB917 /* GZIP */; };
|
EC280623A42904341363EAAF /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 886A0A498FA01E8EDD451D05 /* Sentry */; };
|
||||||
EC4C31963E755EEC77BD778C /* AnalyticsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B362E695A7103C11F64B185 /* AnalyticsSettings.swift */; };
|
EC4C31963E755EEC77BD778C /* AnalyticsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B362E695A7103C11F64B185 /* AnalyticsSettings.swift */; };
|
||||||
EE4F5601356228FF72FC56B6 /* MockClientProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F40F48279322E504153AB0D /* MockClientProxy.swift */; };
|
EE4F5601356228FF72FC56B6 /* MockClientProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F40F48279322E504153AB0D /* MockClientProxy.swift */; };
|
||||||
EE8491AD81F47DF3C192497B /* DecorationTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 184CF8C196BE143AE226628D /* DecorationTimelineItemProtocol.swift */; };
|
EE8491AD81F47DF3C192497B /* DecorationTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 184CF8C196BE143AE226628D /* DecorationTimelineItemProtocol.swift */; };
|
||||||
EEC40663922856C65D1E0DF5 /* KeychainControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB9C37196A4C79F24CE80C6 /* KeychainControllerTests.swift */; };
|
EEC40663922856C65D1E0DF5 /* KeychainControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB9C37196A4C79F24CE80C6 /* KeychainControllerTests.swift */; };
|
||||||
EF99A92701E401C4CD5ADC50 /* SplashScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE978A6118C131D7F2A04B3 /* SplashScreenModels.swift */; };
|
EF99A92701E401C4CD5ADC50 /* SplashScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE978A6118C131D7F2A04B3 /* SplashScreenModels.swift */; };
|
||||||
F040ABFEB0A2B142D948BA12 /* Untranslated.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = F75DF9500D69A3AAF8339E69 /* Untranslated.stringsdict */; };
|
F040ABFEB0A2B142D948BA12 /* Untranslated.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = F75DF9500D69A3AAF8339E69 /* Untranslated.stringsdict */; };
|
||||||
F0F82C3C848C865C3098AA52 /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 1BCD21310B997A6837B854D6 /* GZIP */; };
|
F0F82C3C848C865C3098AA52 /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 67E7A6F388D3BF85767609D9 /* Sentry */; };
|
||||||
F2DD8661B5C0BA2BB526FA6C /* KeychainControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD51F9FDC91C231906D76C8 /* KeychainControllerProtocol.swift */; };
|
F2DD8661B5C0BA2BB526FA6C /* KeychainControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD51F9FDC91C231906D76C8 /* KeychainControllerProtocol.swift */; };
|
||||||
F4C3FEDB1B3A05376A1723A3 /* KeychainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4427F9E0571B4E6E048A2B /* KeychainController.swift */; };
|
F4C3FEDB1B3A05376A1723A3 /* KeychainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4427F9E0571B4E6E048A2B /* KeychainController.swift */; };
|
||||||
F508683B76EF7B23BB2CBD6D /* TimelineItemPlainStylerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94BCC8A9C73C1F838122C645 /* TimelineItemPlainStylerView.swift */; };
|
F508683B76EF7B23BB2CBD6D /* TimelineItemPlainStylerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94BCC8A9C73C1F838122C645 /* TimelineItemPlainStylerView.swift */; };
|
||||||
@@ -360,7 +358,6 @@
|
|||||||
F75C4222D52B643214D5E623 /* UITestsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81740EEAFDF0D34C5E10D0DF /* UITestsRootView.swift */; };
|
F75C4222D52B643214D5E623 /* UITestsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81740EEAFDF0D34C5E10D0DF /* UITestsRootView.swift */; };
|
||||||
F99FB21EFC6D99D247FE7CBE /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = DE8DC9B3FBA402117DC4C49F /* Kingfisher */; };
|
F99FB21EFC6D99D247FE7CBE /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = DE8DC9B3FBA402117DC4C49F /* Kingfisher */; };
|
||||||
FA9C427FFB11B1AA2DCC5602 /* RoomProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */; };
|
FA9C427FFB11B1AA2DCC5602 /* RoomProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */; };
|
||||||
FC10228E73323BDC09526F97 /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 7731767AE437BA3BD2CC14A8 /* Sentry */; };
|
|
||||||
FC6B7436C3A5B3D0565227D5 /* ActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF05352F28D4E7336228E9F4 /* ActivityIndicatorView.swift */; };
|
FC6B7436C3A5B3D0565227D5 /* ActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF05352F28D4E7336228E9F4 /* ActivityIndicatorView.swift */; };
|
||||||
FCB640C576292BEAF7FA3B2E /* SplashViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F395A2E917115C7AAF7F34 /* SplashViewController.swift */; };
|
FCB640C576292BEAF7FA3B2E /* SplashViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F395A2E917115C7AAF7F34 /* SplashViewController.swift */; };
|
||||||
FCD3F2B82CAB29A07887A127 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 2B43F2AF7456567FE37270A7 /* KeychainAccess */; };
|
FCD3F2B82CAB29A07887A127 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 2B43F2AF7456567FE37270A7 /* KeychainAccess */; };
|
||||||
@@ -553,7 +550,6 @@
|
|||||||
541542F5AC323709D8563458 /* AnalyticsPrompt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPrompt.swift; sourceTree = "<group>"; };
|
541542F5AC323709D8563458 /* AnalyticsPrompt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPrompt.swift; sourceTree = "<group>"; };
|
||||||
5445FCE0CE15E634FDC1A2E2 /* AnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsService.swift; sourceTree = "<group>"; };
|
5445FCE0CE15E634FDC1A2E2 /* AnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsService.swift; sourceTree = "<group>"; };
|
||||||
54E438DBCBDC7A41B95DDDD9 /* MXLogObjcWrapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXLogObjcWrapper.m; sourceTree = "<group>"; };
|
54E438DBCBDC7A41B95DDDD9 /* MXLogObjcWrapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXLogObjcWrapper.m; sourceTree = "<group>"; };
|
||||||
551DAED7F623AA5366E79927 /* repository */ = {isa = PBXFileReference; lastKnownFileType = folder; name = repository; path = .; sourceTree = SOURCE_ROOT; };
|
|
||||||
55BC11560C8A2598964FFA4C /* bs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bs; path = bs.lproj/Localizable.strings; sourceTree = "<group>"; };
|
55BC11560C8A2598964FFA4C /* bs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bs; path = bs.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
55D7187F6B0C0A651AC3DFFA /* in */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = in; path = in.lproj/Localizable.strings; sourceTree = "<group>"; };
|
55D7187F6B0C0A651AC3DFFA /* in */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = in; path = in.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
55F30E764BED111C81739844 /* SoftLogoutUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutUITests.swift; sourceTree = "<group>"; };
|
55F30E764BED111C81739844 /* SoftLogoutUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutUITests.swift; sourceTree = "<group>"; };
|
||||||
@@ -747,6 +743,7 @@
|
|||||||
C06FCD42EEFEFC220F14EAC5 /* SessionVerificationStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationStateMachine.swift; sourceTree = "<group>"; };
|
C06FCD42EEFEFC220F14EAC5 /* SessionVerificationStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationStateMachine.swift; sourceTree = "<group>"; };
|
||||||
C070FD43DC6BF4E50217965A /* LocalizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationTests.swift; sourceTree = "<group>"; };
|
C070FD43DC6BF4E50217965A /* LocalizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationTests.swift; sourceTree = "<group>"; };
|
||||||
C2886615BEBAE33A0AA4D5F8 /* RoomScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenModels.swift; sourceTree = "<group>"; };
|
C2886615BEBAE33A0AA4D5F8 /* RoomScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenModels.swift; sourceTree = "<group>"; };
|
||||||
|
C2DE30233B57761F8AFEB415 /* ReversedScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReversedScrollView.swift; sourceTree = "<group>"; };
|
||||||
C483956FA3D665E3842E319A /* SettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreen.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>"; };
|
C55D7E514F9DE4E3D72FDCAD /* SessionVerificationControllerProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationControllerProxy.swift; sourceTree = "<group>"; };
|
||||||
C687844F60BFF532D49A994C /* AnalyticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsTests.swift; sourceTree = "<group>"; };
|
C687844F60BFF532D49A994C /* AnalyticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsTests.swift; sourceTree = "<group>"; };
|
||||||
@@ -782,6 +779,7 @@
|
|||||||
D0ADFDC712027931F2216668 /* WeakKeyDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakKeyDictionary.swift; sourceTree = "<group>"; };
|
D0ADFDC712027931F2216668 /* WeakKeyDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakKeyDictionary.swift; sourceTree = "<group>"; };
|
||||||
D1A9CCCF53495CF3D7B19FCE /* MockSessionVerificationControllerProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSessionVerificationControllerProxy.swift; sourceTree = "<group>"; };
|
D1A9CCCF53495CF3D7B19FCE /* MockSessionVerificationControllerProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSessionVerificationControllerProxy.swift; sourceTree = "<group>"; };
|
||||||
D29EBCBFEC6FD0941749404D /* NavigationRouterStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRouterStore.swift; sourceTree = "<group>"; };
|
D29EBCBFEC6FD0941749404D /* NavigationRouterStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRouterStore.swift; sourceTree = "<group>"; };
|
||||||
|
D31DC8105C6233E5FFD9B84C /* element-x-ios */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "element-x-ios"; path = .; sourceTree = SOURCE_ROOT; };
|
||||||
D33116993D54FADC0C721C1F /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
|
D33116993D54FADC0C721C1F /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
|
||||||
D4DA544B2520BFA65D6DB4BB /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
|
D4DA544B2520BFA65D6DB4BB /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
|
||||||
D653265D006E708E4E51AD64 /* HomeScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenCoordinator.swift; sourceTree = "<group>"; };
|
D653265D006E708E4E51AD64 /* HomeScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
@@ -818,7 +816,6 @@
|
|||||||
EB3B237387B8288A5A938F1B /* UserAgentBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAgentBuilderTests.swift; sourceTree = "<group>"; };
|
EB3B237387B8288A5A938F1B /* UserAgentBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAgentBuilderTests.swift; sourceTree = "<group>"; };
|
||||||
EBE5502760CF6CA2D7201883 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ja; path = ja.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
EBE5502760CF6CA2D7201883 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ja; path = ja.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = "<group>"; };
|
ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = "<group>"; };
|
||||||
ED0C923DD8D9946257D46806 /* ListCollectionViewAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListCollectionViewAdapter.swift; sourceTree = "<group>"; };
|
|
||||||
ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = "<group>"; };
|
ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = "<group>"; };
|
||||||
EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = "<group>"; };
|
EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = "<group>"; };
|
||||||
EDB6E40BAD4504D899FAAC9A /* TemplateViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateViewModel.swift; sourceTree = "<group>"; };
|
EDB6E40BAD4504D899FAAC9A /* TemplateViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateViewModel.swift; sourceTree = "<group>"; };
|
||||||
@@ -857,11 +854,10 @@
|
|||||||
97189E495F0E47805D1868DB /* DTCoreText in Frameworks */,
|
97189E495F0E47805D1868DB /* DTCoreText in Frameworks */,
|
||||||
FCD3F2B82CAB29A07887A127 /* KeychainAccess in Frameworks */,
|
FCD3F2B82CAB29A07887A127 /* KeychainAccess in Frameworks */,
|
||||||
F99FB21EFC6D99D247FE7CBE /* Kingfisher in Frameworks */,
|
F99FB21EFC6D99D247FE7CBE /* Kingfisher in Frameworks */,
|
||||||
308BD9343B95657FAA583FB7 /* Introspect in Frameworks */,
|
308BD9343B95657FAA583FB7 /* SwiftyBeaver in Frameworks */,
|
||||||
3F2148F11164C7C5609984EB /* SwiftyBeaver in Frameworks */,
|
3F2148F11164C7C5609984EB /* SwiftState in Frameworks */,
|
||||||
60ED66E63A169E47489348A8 /* SwiftState in Frameworks */,
|
60ED66E63A169E47489348A8 /* GZIP in Frameworks */,
|
||||||
EC280623A42904341363EAAF /* GZIP in Frameworks */,
|
EC280623A42904341363EAAF /* Sentry in Frameworks */,
|
||||||
09BFDE37F0D0E586D26B17D7 /* Sentry in Frameworks */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -876,13 +872,12 @@
|
|||||||
6832733838C57A7D3FE8FEB5 /* DTCoreText in Frameworks */,
|
6832733838C57A7D3FE8FEB5 /* DTCoreText in Frameworks */,
|
||||||
2BA59D0AEFB4B82A2EC2A326 /* KeychainAccess in Frameworks */,
|
2BA59D0AEFB4B82A2EC2A326 /* KeychainAccess in Frameworks */,
|
||||||
B245583C63F8F90357B87FAE /* Kingfisher in Frameworks */,
|
B245583C63F8F90357B87FAE /* Kingfisher in Frameworks */,
|
||||||
A4E885358D7DD5A072A06824 /* Introspect in Frameworks */,
|
A4E885358D7DD5A072A06824 /* PostHog in Frameworks */,
|
||||||
29EE1791E0AFA1ABB7F23D2F /* PostHog in Frameworks */,
|
29EE1791E0AFA1ABB7F23D2F /* SwiftyBeaver in Frameworks */,
|
||||||
33CAC1226DFB8B5D8447D286 /* SwiftyBeaver in Frameworks */,
|
33CAC1226DFB8B5D8447D286 /* SwiftState in Frameworks */,
|
||||||
492274DA6691EE985C2FCCAA /* SwiftState in Frameworks */,
|
492274DA6691EE985C2FCCAA /* GZIP in Frameworks */,
|
||||||
F0F82C3C848C865C3098AA52 /* GZIP in Frameworks */,
|
F0F82C3C848C865C3098AA52 /* Sentry in Frameworks */,
|
||||||
3A64A93A651A3CB8774ADE8E /* Sentry in Frameworks */,
|
3A64A93A651A3CB8774ADE8E /* SnapshotTesting in Frameworks */,
|
||||||
3F327A62D233933F54F0F33A /* SnapshotTesting in Frameworks */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -898,12 +893,11 @@
|
|||||||
93BA4A81B6D893271101F9F0 /* DTCoreText in Frameworks */,
|
93BA4A81B6D893271101F9F0 /* DTCoreText in Frameworks */,
|
||||||
9AC5F8142413862A9E3A2D98 /* KeychainAccess in Frameworks */,
|
9AC5F8142413862A9E3A2D98 /* KeychainAccess in Frameworks */,
|
||||||
CB137BFB3E083C33E398A6CB /* Kingfisher in Frameworks */,
|
CB137BFB3E083C33E398A6CB /* Kingfisher in Frameworks */,
|
||||||
3C549A0BF39F8A854D45D9FD /* Introspect in Frameworks */,
|
3C549A0BF39F8A854D45D9FD /* PostHog in Frameworks */,
|
||||||
41DFDD212D1BE57CA50D783B /* PostHog in Frameworks */,
|
41DFDD212D1BE57CA50D783B /* SwiftyBeaver in Frameworks */,
|
||||||
6298AB0906DDD3525CD78C6B /* SwiftyBeaver in Frameworks */,
|
6298AB0906DDD3525CD78C6B /* SwiftState in Frameworks */,
|
||||||
407DCE030E0F9B7C9861D38A /* SwiftState in Frameworks */,
|
407DCE030E0F9B7C9861D38A /* GZIP in Frameworks */,
|
||||||
8F2FAA98457750D9D664136F /* GZIP in Frameworks */,
|
8F2FAA98457750D9D664136F /* Sentry in Frameworks */,
|
||||||
FC10228E73323BDC09526F97 /* Sentry in Frameworks */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -1414,7 +1408,6 @@
|
|||||||
79023E5904B155E8E2B8B502 /* View */ = {
|
79023E5904B155E8E2B8B502 /* View */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
ED0C923DD8D9946257D46806 /* ListCollectionViewAdapter.swift */,
|
|
||||||
E18CF12478983A5EB390FB26 /* MessageComposer.swift */,
|
E18CF12478983A5EB390FB26 /* MessageComposer.swift */,
|
||||||
BE6C10032A77AE7DC5AA4C50 /* MessageComposerTextField.swift */,
|
BE6C10032A77AE7DC5AA4C50 /* MessageComposerTextField.swift */,
|
||||||
422724361B6555364C43281E /* RoomHeaderView.swift */,
|
422724361B6555364C43281E /* RoomHeaderView.swift */,
|
||||||
@@ -1521,7 +1514,7 @@
|
|||||||
9413F680ECDFB2B0DDB0DEF2 /* Packages */ = {
|
9413F680ECDFB2B0DDB0DEF2 /* Packages */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
551DAED7F623AA5366E79927 /* repository */,
|
D31DC8105C6233E5FFD9B84C /* element-x-ios */,
|
||||||
);
|
);
|
||||||
name = Packages;
|
name = Packages;
|
||||||
sourceTree = SOURCE_ROOT;
|
sourceTree = SOURCE_ROOT;
|
||||||
@@ -1743,6 +1736,7 @@
|
|||||||
12A626D74BBE9F4A60763B45 /* ImageAnonymizer.swift */,
|
12A626D74BBE9F4A60763B45 /* ImageAnonymizer.swift */,
|
||||||
6AD1A853D605C2146B0DC028 /* MatrixEntityRegex.swift */,
|
6AD1A853D605C2146B0DC028 /* MatrixEntityRegex.swift */,
|
||||||
F754E66A8970963B15B2A41E /* PermalinkBuilder.swift */,
|
F754E66A8970963B15B2A41E /* PermalinkBuilder.swift */,
|
||||||
|
C2DE30233B57761F8AFEB415 /* ReversedScrollView.swift */,
|
||||||
BB3073CCD77D906B330BC1D6 /* Tests.swift */,
|
BB3073CCD77D906B330BC1D6 /* Tests.swift */,
|
||||||
1F2529D434C750ED78ADF1ED /* UserAgentBuilder.swift */,
|
1F2529D434C750ED78ADF1ED /* UserAgentBuilder.swift */,
|
||||||
44BBB96FAA2F0D53C507396B /* Extensions */,
|
44BBB96FAA2F0D53C507396B /* Extensions */,
|
||||||
@@ -1978,7 +1972,6 @@
|
|||||||
36B7FC232711031AA2B0D188 /* DTCoreText */,
|
36B7FC232711031AA2B0D188 /* DTCoreText */,
|
||||||
78A5A8DE1E2B09C978C7F3B0 /* KeychainAccess */,
|
78A5A8DE1E2B09C978C7F3B0 /* KeychainAccess */,
|
||||||
50009897F60FAE7D63EF5E5B /* Kingfisher */,
|
50009897F60FAE7D63EF5E5B /* Kingfisher */,
|
||||||
04C28663564E008DB32B5972 /* Introspect */,
|
|
||||||
CCE5BF78B125320CBF3BB834 /* PostHog */,
|
CCE5BF78B125320CBF3BB834 /* PostHog */,
|
||||||
A981A4CA233FB5C13B9CA690 /* SwiftyBeaver */,
|
A981A4CA233FB5C13B9CA690 /* SwiftyBeaver */,
|
||||||
3853B78FB8531B83936C5DA6 /* SwiftState */,
|
3853B78FB8531B83936C5DA6 /* SwiftState */,
|
||||||
@@ -2032,7 +2025,6 @@
|
|||||||
531CE4334AC5CA8DFF6AEB84 /* DTCoreText */,
|
531CE4334AC5CA8DFF6AEB84 /* DTCoreText */,
|
||||||
020597E28A4BC8E1BE8EDF6E /* KeychainAccess */,
|
020597E28A4BC8E1BE8EDF6E /* KeychainAccess */,
|
||||||
0DD568A494247444A4B56031 /* Kingfisher */,
|
0DD568A494247444A4B56031 /* Kingfisher */,
|
||||||
5986E300FC849DEAB2EE7AEB /* Introspect */,
|
|
||||||
4278261E147DB2DE5CFB7FC5 /* PostHog */,
|
4278261E147DB2DE5CFB7FC5 /* PostHog */,
|
||||||
FD43A50D9B75C9D6D30F006B /* SwiftyBeaver */,
|
FD43A50D9B75C9D6D30F006B /* SwiftyBeaver */,
|
||||||
9573B94B1C86C6DF751AF3FD /* SwiftState */,
|
9573B94B1C86C6DF751AF3FD /* SwiftState */,
|
||||||
@@ -2063,7 +2055,6 @@
|
|||||||
527578916BD388A09F5A8036 /* DTCoreText */,
|
527578916BD388A09F5A8036 /* DTCoreText */,
|
||||||
2B43F2AF7456567FE37270A7 /* KeychainAccess */,
|
2B43F2AF7456567FE37270A7 /* KeychainAccess */,
|
||||||
DE8DC9B3FBA402117DC4C49F /* Kingfisher */,
|
DE8DC9B3FBA402117DC4C49F /* Kingfisher */,
|
||||||
D82E84F90358CC1118E6034B /* Introspect */,
|
|
||||||
AD2AC190E55B2BD4D0F1D4A7 /* SwiftyBeaver */,
|
AD2AC190E55B2BD4D0F1D4A7 /* SwiftyBeaver */,
|
||||||
19CD5B074D7DD44AF4C58BB6 /* SwiftState */,
|
19CD5B074D7DD44AF4C58BB6 /* SwiftState */,
|
||||||
2B788C81F6369D164ADEB917 /* GZIP */,
|
2B788C81F6369D164ADEB917 /* GZIP */,
|
||||||
@@ -2455,7 +2446,6 @@
|
|||||||
F2DD8661B5C0BA2BB526FA6C /* KeychainControllerProtocol.swift in Sources */,
|
F2DD8661B5C0BA2BB526FA6C /* KeychainControllerProtocol.swift in Sources */,
|
||||||
9C9E48A627C7C166084E3F5B /* LabelledActivityIndicatorView.swift in Sources */,
|
9C9E48A627C7C166084E3F5B /* LabelledActivityIndicatorView.swift in Sources */,
|
||||||
15D867E638BFD0E5E71DB1EF /* List.swift in Sources */,
|
15D867E638BFD0E5E71DB1EF /* List.swift in Sources */,
|
||||||
E8AFB40CC7C3DF1930DA89E2 /* ListCollectionViewAdapter.swift in Sources */,
|
|
||||||
83E5054739949181CA981193 /* LoginCoordinator.swift in Sources */,
|
83E5054739949181CA981193 /* LoginCoordinator.swift in Sources */,
|
||||||
872A6457DF573AF8CEAE927A /* LoginHomeserver.swift in Sources */,
|
872A6457DF573AF8CEAE927A /* LoginHomeserver.swift in Sources */,
|
||||||
CEB8FB1269DE20536608B957 /* LoginMode.swift in Sources */,
|
CEB8FB1269DE20536608B957 /* LoginMode.swift in Sources */,
|
||||||
@@ -2503,6 +2493,7 @@
|
|||||||
53B9C2240C2F5533246EE230 /* RectangleToastView.swift in Sources */,
|
53B9C2240C2F5533246EE230 /* RectangleToastView.swift in Sources */,
|
||||||
00EA14F62DCEF62CDE4808D6 /* RedactedRoomTimelineItem.swift in Sources */,
|
00EA14F62DCEF62CDE4808D6 /* RedactedRoomTimelineItem.swift in Sources */,
|
||||||
13853973A5E24374FCEDE8A3 /* RedactedRoomTimelineView.swift in Sources */,
|
13853973A5E24374FCEDE8A3 /* RedactedRoomTimelineView.swift in Sources */,
|
||||||
|
BFD1AC03B6F8C5F5897D5B55 /* ReversedScrollView.swift in Sources */,
|
||||||
04A16B45228F7678A027C079 /* RoomHeaderView.swift in Sources */,
|
04A16B45228F7678A027C079 /* RoomHeaderView.swift in Sources */,
|
||||||
FE79E2BCCF69E8BF4D21E15A /* RoomMessageFactory.swift in Sources */,
|
FE79E2BCCF69E8BF4D21E15A /* RoomMessageFactory.swift in Sources */,
|
||||||
8D9F646387DF656EF91EE4CB /* RoomMessageFactoryProtocol.swift in Sources */,
|
8D9F646387DF656EF91EE4CB /* RoomMessageFactoryProtocol.swift in Sources */,
|
||||||
@@ -3245,7 +3236,7 @@
|
|||||||
repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift";
|
repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift";
|
||||||
requirement = {
|
requirement = {
|
||||||
kind = exactVersion;
|
kind = exactVersion;
|
||||||
version = "1.0.15-alpha";
|
version = "1.0.16-alpha";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
96495DD8554E2F39D3954354 /* XCRemoteSwiftPackageReference "posthog-ios" */ = {
|
96495DD8554E2F39D3954354 /* XCRemoteSwiftPackageReference "posthog-ios" */ = {
|
||||||
@@ -3320,11 +3311,6 @@
|
|||||||
package = 61916C63E3F5BD900F08DA0C /* XCRemoteSwiftPackageReference "KeychainAccess" */;
|
package = 61916C63E3F5BD900F08DA0C /* XCRemoteSwiftPackageReference "KeychainAccess" */;
|
||||||
productName = KeychainAccess;
|
productName = KeychainAccess;
|
||||||
};
|
};
|
||||||
04C28663564E008DB32B5972 /* Introspect */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
package = 9A472EE0218FE7DCF5283429 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */;
|
|
||||||
productName = Introspect;
|
|
||||||
};
|
|
||||||
0DD568A494247444A4B56031 /* Kingfisher */ = {
|
0DD568A494247444A4B56031 /* Kingfisher */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = D283517192CAC3E2E6920765 /* XCRemoteSwiftPackageReference "Kingfisher" */;
|
package = D283517192CAC3E2E6920765 /* XCRemoteSwiftPackageReference "Kingfisher" */;
|
||||||
@@ -3400,11 +3386,6 @@
|
|||||||
package = C13F55E4518415CB4C278E73 /* XCRemoteSwiftPackageReference "DTCoreText" */;
|
package = C13F55E4518415CB4C278E73 /* XCRemoteSwiftPackageReference "DTCoreText" */;
|
||||||
productName = DTCoreText;
|
productName = DTCoreText;
|
||||||
};
|
};
|
||||||
5986E300FC849DEAB2EE7AEB /* Introspect */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
package = 9A472EE0218FE7DCF5283429 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */;
|
|
||||||
productName = Introspect;
|
|
||||||
};
|
|
||||||
67E7A6F388D3BF85767609D9 /* Sentry */ = {
|
67E7A6F388D3BF85767609D9 /* Sentry */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = A08925A9D5E3770DEB9D8509 /* XCRemoteSwiftPackageReference "sentry-cocoa" */;
|
package = A08925A9D5E3770DEB9D8509 /* XCRemoteSwiftPackageReference "sentry-cocoa" */;
|
||||||
@@ -3489,11 +3470,6 @@
|
|||||||
package = AC3475112CA40C2C6E78D1EB /* XCRemoteSwiftPackageReference "matrix-analytics-events" */;
|
package = AC3475112CA40C2C6E78D1EB /* XCRemoteSwiftPackageReference "matrix-analytics-events" */;
|
||||||
productName = AnalyticsEvents;
|
productName = AnalyticsEvents;
|
||||||
};
|
};
|
||||||
D82E84F90358CC1118E6034B /* Introspect */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
package = 9A472EE0218FE7DCF5283429 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */;
|
|
||||||
productName = Introspect;
|
|
||||||
};
|
|
||||||
DE8DC9B3FBA402117DC4C49F /* Kingfisher */ = {
|
DE8DC9B3FBA402117DC4C49F /* Kingfisher */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = D283517192CAC3E2E6920765 /* XCRemoteSwiftPackageReference "Kingfisher" */;
|
package = D283517192CAC3E2E6920765 /* XCRemoteSwiftPackageReference "Kingfisher" */;
|
||||||
|
|||||||
@@ -86,8 +86,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/matrix-org/matrix-rust-components-swift",
|
"location" : "https://github.com/matrix-org/matrix-rust-components-swift",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "e3ae8941c864ba18e7e5c51d25a6ce2c5c7a65a0",
|
"revision" : "af6683ca5ddd9da7f582de4258e4d6ebb8a8caff",
|
||||||
"version" : "1.0.15-alpha"
|
"version" : "1.0.16-alpha"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,13 +21,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
|||||||
private let temporaryBlockquoteMarkingColor = UIColor.magenta
|
private let temporaryBlockquoteMarkingColor = UIColor.magenta
|
||||||
private let temporaryCodeBlockMarkingColor = UIColor.cyan
|
private let temporaryCodeBlockMarkingColor = UIColor.cyan
|
||||||
private let linkColor = UIColor.blue
|
private let linkColor = UIColor.blue
|
||||||
|
|
||||||
func fromPlain(_ string: String?) async -> AttributedString? {
|
|
||||||
await Task.dispatch(on: .global()) {
|
|
||||||
fromPlain(string)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fromPlain(_ string: String?) -> AttributedString? {
|
func fromPlain(_ string: String?) -> AttributedString? {
|
||||||
guard let string else {
|
guard let string else {
|
||||||
return nil
|
return nil
|
||||||
@@ -39,13 +33,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
|||||||
|
|
||||||
return try? AttributedString(mutableAttributedString, including: \.elementX)
|
return try? AttributedString(mutableAttributedString, including: \.elementX)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fromHTML(_ htmlString: String?) async -> AttributedString? {
|
|
||||||
await Task.dispatch(on: .global()) {
|
|
||||||
fromHTML(htmlString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do not use the default HTML renderer of NSAttributedString because this method
|
// Do not use the default HTML renderer of NSAttributedString because this method
|
||||||
// runs on the UI thread which we want to avoid because renderHTMLString is called
|
// runs on the UI thread which we want to avoid because renderHTMLString is called
|
||||||
// most of the time from a background thread.
|
// most of the time from a background thread.
|
||||||
|
|||||||
@@ -23,10 +23,8 @@ struct AttributedStringBuilderComponent: Hashable {
|
|||||||
|
|
||||||
protocol AttributedStringBuilderProtocol {
|
protocol AttributedStringBuilderProtocol {
|
||||||
func fromPlain(_ string: String?) -> AttributedString?
|
func fromPlain(_ string: String?) -> AttributedString?
|
||||||
func fromPlain(_ string: String?) async -> AttributedString?
|
|
||||||
|
|
||||||
func fromHTML(_ htmlString: String?) -> AttributedString?
|
func fromHTML(_ htmlString: String?) -> AttributedString?
|
||||||
func fromHTML(_ htmlString: String?) async -> AttributedString?
|
|
||||||
|
|
||||||
func blockquoteCoalescedComponentsFrom(_ attributedString: AttributedString?) -> [AttributedStringBuilderComponent]?
|
func blockquoteCoalescedComponentsFrom(_ attributedString: AttributedString?) -> [AttributedStringBuilderComponent]?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
/// A SwiftUI scroll view that lays out its content starting at the bottom or trailing
|
||||||
|
/// https://www.thirdrocktechkno.com/blog/implementing-reversed-scrolling-behaviour-in-swiftui/
|
||||||
|
struct ReversedScrollView<Content: View>: View {
|
||||||
|
private let axis: Axis.Set
|
||||||
|
private let leadingSpace: CGFloat
|
||||||
|
private let content: Content
|
||||||
|
|
||||||
|
init(_ axis: Axis.Set = .horizontal, leadingSpace: CGFloat = 0, @ViewBuilder builder: () -> Content) {
|
||||||
|
self.axis = axis
|
||||||
|
self.leadingSpace = leadingSpace
|
||||||
|
content = builder()
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
GeometryReader { proxy in
|
||||||
|
ScrollView(axis, showsIndicators: false) {
|
||||||
|
Stack(axis) {
|
||||||
|
Spacer(minLength: leadingSpace)
|
||||||
|
content
|
||||||
|
}
|
||||||
|
.frame(
|
||||||
|
minWidth: minWidth(in: proxy, for: axis),
|
||||||
|
minHeight: minHeight(in: proxy, for: axis)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func minWidth(in proxy: GeometryProxy, for axis: Axis.Set) -> CGFloat? {
|
||||||
|
axis.contains(.horizontal) ? proxy.size.width : nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func minHeight(in proxy: GeometryProxy, for axis: Axis.Set) -> CGFloat? {
|
||||||
|
axis.contains(.vertical) ? proxy.size.height : nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct Stack<Content: View>: View {
|
||||||
|
var axis: Axis.Set
|
||||||
|
var content: Content
|
||||||
|
|
||||||
|
init(_ axis: Axis.Set = .vertical, @ViewBuilder builder: () -> Content) {
|
||||||
|
self.axis = axis
|
||||||
|
|
||||||
|
content = builder()
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
switch axis {
|
||||||
|
case .horizontal:
|
||||||
|
HStack {
|
||||||
|
content
|
||||||
|
}
|
||||||
|
case .vertical:
|
||||||
|
VStack {
|
||||||
|
content
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
VStack {
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,12 +19,14 @@ import UIKit
|
|||||||
|
|
||||||
enum RoomScreenViewModelAction { }
|
enum RoomScreenViewModelAction { }
|
||||||
|
|
||||||
enum TimelineItemContextMenuAction: Hashable {
|
enum TimelineItemContextMenuAction: Identifiable, Hashable {
|
||||||
case copy
|
case copy
|
||||||
case quote
|
case quote
|
||||||
case copyPermalink
|
case copyPermalink
|
||||||
case redact
|
case redact
|
||||||
case reply
|
case reply
|
||||||
|
|
||||||
|
var id: Self { self }
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RoomScreenComposerMode: Equatable {
|
enum RoomScreenComposerMode: Equatable {
|
||||||
|
|||||||
@@ -58,6 +58,10 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.state.items[viewIndex] = timelineViewFactory.buildTimelineViewFor(timelineItem: timelineItem)
|
self.state.items[viewIndex] = timelineViewFactory.buildTimelineViewFor(timelineItem: timelineItem)
|
||||||
|
case .startedBackPaginating:
|
||||||
|
self.state.isBackPaginating = true
|
||||||
|
case .finishedBackPaginating:
|
||||||
|
self.state.isBackPaginating = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
@@ -81,11 +85,13 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
override func process(viewAction: RoomScreenViewAction) async {
|
override func process(viewAction: RoomScreenViewAction) async {
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
case .loadPreviousPage:
|
case .loadPreviousPage:
|
||||||
state.isBackPaginating = true
|
guard !state.isBackPaginating else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
switch await timelineController.paginateBackwards(Constants.backPaginationPageSize) {
|
switch await timelineController.paginateBackwards(Constants.backPaginationPageSize) {
|
||||||
default:
|
default:
|
||||||
state.isBackPaginating = false
|
#warning("Treat errors")
|
||||||
}
|
}
|
||||||
case .itemAppeared(let id):
|
case .itemAppeared(let id):
|
||||||
await timelineController.processItemAppearance(id)
|
await timelineController.processItemAppearance(id)
|
||||||
|
|||||||
@@ -1,223 +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 Combine
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
class ListCollectionViewAdapter: NSObject, UICollectionViewDelegate {
|
|
||||||
private enum ContentOffsetDetails {
|
|
||||||
case topOffset(previousVisibleIndexPath: IndexPath, previousItemCount: Int)
|
|
||||||
case bottomOffset
|
|
||||||
}
|
|
||||||
|
|
||||||
private let topDetectionOffset: CGFloat
|
|
||||||
private let bottomDetectionOffset: CGFloat
|
|
||||||
|
|
||||||
private var contentOffsetObserverToken: NSKeyValueObservation?
|
|
||||||
private var boundsObserverToken: NSKeyValueObservation?
|
|
||||||
|
|
||||||
private var offsetDetails: ContentOffsetDetails?
|
|
||||||
private var draggingInitiated = false
|
|
||||||
private var isAnimatingKeyboardAppearance = false
|
|
||||||
private var previousFrame: CGRect = .zero
|
|
||||||
|
|
||||||
private(set) var collectionView: UICollectionView?
|
|
||||||
|
|
||||||
let scrollViewDidRestPublisher = PassthroughSubject<Void, Never>()
|
|
||||||
let scrollViewTopVisiblePublisher = CurrentValueSubject<Bool, Never>(false)
|
|
||||||
let scrollViewBottomVisiblePublisher = CurrentValueSubject<Bool, Never>(false)
|
|
||||||
|
|
||||||
override init() {
|
|
||||||
topDetectionOffset = 0.0
|
|
||||||
bottomDetectionOffset = 0.0
|
|
||||||
}
|
|
||||||
|
|
||||||
init(collectionView: UICollectionView, topDetectionOffset: CGFloat, bottomDetectionOffset: CGFloat) {
|
|
||||||
self.collectionView = collectionView
|
|
||||||
self.topDetectionOffset = topDetectionOffset
|
|
||||||
self.bottomDetectionOffset = bottomDetectionOffset
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
collectionView.clipsToBounds = true
|
|
||||||
collectionView.keyboardDismissMode = .onDrag
|
|
||||||
|
|
||||||
registerContentOffsetObserver()
|
|
||||||
registerBoundsObserver()
|
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidShow(notification:)), name: UIResponder.keyboardDidShowNotification, object: nil)
|
|
||||||
|
|
||||||
collectionView.panGestureRecognizer.addTarget(self, action: #selector(handlePanGesture(_:)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveCurrentOffset() {
|
|
||||||
guard let collectionView,
|
|
||||||
collectionView.numberOfSections > 0 else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if computeIsBottomVisible() {
|
|
||||||
offsetDetails = .bottomOffset
|
|
||||||
} else if computeIsTopVisible(), let topIndexPath = collectionView.indexPathsForVisibleItems.first {
|
|
||||||
offsetDetails = .topOffset(previousVisibleIndexPath: topIndexPath,
|
|
||||||
previousItemCount: collectionView.numberOfItems(inSection: 0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func restoreSavedOffset() {
|
|
||||||
defer {
|
|
||||||
offsetDetails = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let collectionView,
|
|
||||||
collectionView.numberOfSections > 0 else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let currentItemCount = collectionView.numberOfItems(inSection: 0)
|
|
||||||
|
|
||||||
switch offsetDetails {
|
|
||||||
case .bottomOffset:
|
|
||||||
collectionView.scrollToItem(at: .init(item: max(0, currentItemCount - 1), section: 0), at: .bottom, animated: false)
|
|
||||||
case .topOffset(let indexPath, let previousItemCount):
|
|
||||||
let item = indexPath.item + max(0, currentItemCount - previousItemCount)
|
|
||||||
if item < currentItemCount {
|
|
||||||
collectionView.scrollToItem(at: .init(item: item, section: 0), at: .top, animated: false)
|
|
||||||
}
|
|
||||||
case .none:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var isTracking: Bool {
|
|
||||||
collectionView?.isTracking == true
|
|
||||||
}
|
|
||||||
|
|
||||||
var isDecelerating: Bool {
|
|
||||||
collectionView?.isDecelerating == true
|
|
||||||
}
|
|
||||||
|
|
||||||
func scrollToBottom(animated: Bool = false) {
|
|
||||||
guard let collectionView,
|
|
||||||
collectionView.numberOfSections > 0 else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let currentItemCount = collectionView.numberOfItems(inSection: 0)
|
|
||||||
guard currentItemCount > 1 else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
collectionView.scrollToItem(at: .init(item: currentItemCount - 1, section: 0), at: .bottom, animated: animated)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Private
|
|
||||||
|
|
||||||
private func registerContentOffsetObserver() {
|
|
||||||
// Don't attempt stealing the UICollectionView delegate away from the List.
|
|
||||||
// Doing so results in undefined behavior e.g. context menus not working
|
|
||||||
contentOffsetObserverToken = collectionView?.observe(\.contentOffset, options: .new) { [weak self] _, _ in
|
|
||||||
self?.handleScrollViewScroll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func deregisterContentOffsetObserver() {
|
|
||||||
contentOffsetObserverToken?.invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func registerBoundsObserver() {
|
|
||||||
boundsObserverToken = collectionView?.observe(\.frame, options: .new) { [weak self] collectionView, _ in
|
|
||||||
self?.previousFrame = collectionView.frame
|
|
||||||
self?.handleScrollViewScroll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func deregisterBoundsObserver() {
|
|
||||||
boundsObserverToken?.invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func keyboardWillShow(notification: NSNotification) {
|
|
||||||
isAnimatingKeyboardAppearance = true
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func keyboardDidShow(notification: NSNotification) {
|
|
||||||
isAnimatingKeyboardAppearance = false
|
|
||||||
}
|
|
||||||
|
|
||||||
private func handleScrollViewScroll() {
|
|
||||||
guard let collectionView else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let hasScrolledBecauseOfFrameChange = (previousFrame != collectionView.frame)
|
|
||||||
let shouldPinToBottom = scrollViewBottomVisiblePublisher.value && (isAnimatingKeyboardAppearance || hasScrolledBecauseOfFrameChange)
|
|
||||||
|
|
||||||
if shouldPinToBottom {
|
|
||||||
deregisterContentOffsetObserver()
|
|
||||||
scrollToBottom()
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.registerContentOffsetObserver()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let isTopVisible = computeIsTopVisible()
|
|
||||||
if isTopVisible != scrollViewTopVisiblePublisher.value {
|
|
||||||
scrollViewTopVisiblePublisher.send(isTopVisible)
|
|
||||||
}
|
|
||||||
|
|
||||||
let isBottomVisible = computeIsBottomVisible()
|
|
||||||
if isBottomVisible != scrollViewBottomVisiblePublisher.value {
|
|
||||||
scrollViewBottomVisiblePublisher.send(isBottomVisible)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !draggingInitiated, collectionView.isDragging {
|
|
||||||
draggingInitiated = true
|
|
||||||
} else if draggingInitiated, !collectionView.isDragging {
|
|
||||||
draggingInitiated = false
|
|
||||||
scrollViewDidRestPublisher.send(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func handlePanGesture(_ sender: UIPanGestureRecognizer) {
|
|
||||||
guard let collectionView,
|
|
||||||
sender.state == .ended,
|
|
||||||
draggingInitiated,
|
|
||||||
!collectionView.isDecelerating else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
draggingInitiated = false
|
|
||||||
scrollViewDidRestPublisher.send(())
|
|
||||||
}
|
|
||||||
|
|
||||||
private func computeIsTopVisible() -> Bool {
|
|
||||||
guard let scrollView = collectionView else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return (scrollView.contentOffset.y + scrollView.adjustedContentInset.top) <= topDetectionOffset
|
|
||||||
}
|
|
||||||
|
|
||||||
private func computeIsBottomVisible() -> Bool {
|
|
||||||
guard let scrollView = collectionView else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return (scrollView.contentOffset.y + bottomDetectionOffset) >= (scrollView.contentSize.height - scrollView.frame.size.height)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -22,7 +22,7 @@ enum TimelineStyle: String, CaseIterable {
|
|||||||
case bubbles
|
case bubbles
|
||||||
|
|
||||||
/// List row insets for a timeline
|
/// List row insets for a timeline
|
||||||
var listRowInsets: EdgeInsets {
|
var rowInsets: EdgeInsets {
|
||||||
switch self {
|
switch self {
|
||||||
case .plain:
|
case .plain:
|
||||||
return EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20)
|
return EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20)
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ struct EmoteRoomTimelineView: View {
|
|||||||
HStack(alignment: .top) {
|
HStack(alignment: .top) {
|
||||||
Image(systemName: "face.dashed").padding(.top, 1.0)
|
Image(systemName: "face.dashed").padding(.top, 1.0)
|
||||||
if let attributedComponents = timelineItem.attributedComponents {
|
if let attributedComponents = timelineItem.attributedComponents {
|
||||||
FormattedBodyText(isOutgoing: timelineItem.isOutgoing, attributedComponents: attributedComponents)
|
FormattedBodyText(attributedComponents: attributedComponents)
|
||||||
} else {
|
} else {
|
||||||
FormattedBodyText(isOutgoing: timelineItem.isOutgoing, text: timelineItem.text)
|
FormattedBodyText(text: timelineItem.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ import Foundation
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct FormattedBodyText: View {
|
struct FormattedBodyText: View {
|
||||||
#warning("this is a dirty fix for demo, should be refactored after new timeline api")
|
|
||||||
let isOutgoing: Bool
|
|
||||||
let attributedComponents: [AttributedStringBuilderComponent]
|
let attributedComponents: [AttributedStringBuilderComponent]
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -51,8 +49,7 @@ struct FormattedBodyText: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension FormattedBodyText {
|
extension FormattedBodyText {
|
||||||
init(isOutgoing: Bool, text: String) {
|
init(text: String) {
|
||||||
self.isOutgoing = isOutgoing
|
|
||||||
attributedComponents = [.init(attributedString: AttributedString(text), isBlockquote: false)]
|
attributedComponents = [.init(attributedString: AttributedString(text), isBlockquote: false)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,11 +91,11 @@ struct FormattedBodyText_Previews: PreviewProvider {
|
|||||||
let attributedString = attributedStringBuilder.fromHTML(htmlString)
|
let attributedString = attributedStringBuilder.fromHTML(htmlString)
|
||||||
|
|
||||||
if let components = attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedString) {
|
if let components = attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedString) {
|
||||||
FormattedBodyText(isOutgoing: true, attributedComponents: components)
|
FormattedBodyText(attributedComponents: components)
|
||||||
.fixedSize()
|
.fixedSize()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FormattedBodyText(isOutgoing: true, text: "Some plain text that's not an attributed component.")
|
FormattedBodyText(text: "Some plain text that's not an attributed component.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ struct NoticeRoomTimelineView: View {
|
|||||||
HStack(alignment: .top) {
|
HStack(alignment: .top) {
|
||||||
Image(systemName: "exclamationmark.bubble").padding(.top, 2.0)
|
Image(systemName: "exclamationmark.bubble").padding(.top, 2.0)
|
||||||
if let attributedComponents = timelineItem.attributedComponents {
|
if let attributedComponents = timelineItem.attributedComponents {
|
||||||
FormattedBodyText(isOutgoing: timelineItem.isOutgoing, attributedComponents: attributedComponents)
|
FormattedBodyText(attributedComponents: attributedComponents)
|
||||||
} else {
|
} else {
|
||||||
FormattedBodyText(isOutgoing: timelineItem.isOutgoing, text: timelineItem.text)
|
FormattedBodyText(text: timelineItem.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ struct RedactedRoomTimelineView: View {
|
|||||||
TimelineStyler(timelineItem: timelineItem) {
|
TimelineStyler(timelineItem: timelineItem) {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "trash")
|
Image(systemName: "trash")
|
||||||
FormattedBodyText(isOutgoing: timelineItem.isOutgoing, text: timelineItem.text)
|
FormattedBodyText(text: timelineItem.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.id(timelineItem.id)
|
.id(timelineItem.id)
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ struct TextRoomTimelineView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
TimelineStyler(timelineItem: timelineItem) {
|
TimelineStyler(timelineItem: timelineItem) {
|
||||||
if let attributedComponents = timelineItem.attributedComponents {
|
if let attributedComponents = timelineItem.attributedComponents {
|
||||||
FormattedBodyText(isOutgoing: timelineItem.isOutgoing, attributedComponents: attributedComponents)
|
FormattedBodyText(attributedComponents: attributedComponents)
|
||||||
} else {
|
} else {
|
||||||
FormattedBodyText(isOutgoing: timelineItem.isOutgoing, text: timelineItem.text)
|
FormattedBodyText(text: timelineItem.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.id(timelineItem.id)
|
.id(timelineItem.id)
|
||||||
|
|||||||
@@ -15,134 +15,167 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import Introspect
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct TimelineItemList: View {
|
struct TimelineItemList: View {
|
||||||
@State private var collectionViewObserver = ListCollectionViewAdapter()
|
|
||||||
@State private var timelineItems: [RoomTimelineViewProvider] = []
|
|
||||||
@State private var hasPendingChanges = false
|
|
||||||
@ObservedObject private var settings = ElementSettings.shared
|
@ObservedObject private var settings = ElementSettings.shared
|
||||||
|
|
||||||
|
@State private var timelineItems: [RoomTimelineViewProvider] = []
|
||||||
|
@State private var viewFrame: CGRect = .zero
|
||||||
|
@State private var pinnedItem: PinnedItem?
|
||||||
|
@State private var visibleItemIdentifiers: Set<String> = []
|
||||||
|
@State private var topVisiblePublisher = CurrentValueSubject<Bool, Never>(false)
|
||||||
|
|
||||||
@EnvironmentObject var context: RoomScreenViewModel.Context
|
@EnvironmentObject var context: RoomScreenViewModel.Context
|
||||||
|
|
||||||
let bottomVisiblePublisher: PassthroughSubject<Bool, Never>
|
let bottomVisiblePublisher: CurrentValueSubject<Bool, Never>
|
||||||
let scrollToBottomPublisher: PassthroughSubject<Void, Never>
|
let scrollToBottomPublisher: PassthroughSubject<Void, Never>
|
||||||
|
|
||||||
@State private var viewFrame: CGRect = .zero
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List {
|
ScrollViewReader { proxy in
|
||||||
ProgressView()
|
ReversedScrollView(.vertical) {
|
||||||
.frame(maxWidth: .infinity)
|
ProgressView()
|
||||||
.opacity(context.viewState.isBackPaginating ? 1.0 : 0.0)
|
.frame(maxWidth: .infinity)
|
||||||
.animation(.elementDefault, value: context.viewState.isBackPaginating)
|
.opacity(context.viewState.isBackPaginating ? 1.0 : 0.0)
|
||||||
.listRowBackground(Color.clear)
|
.animation(.elementDefault, value: context.viewState.isBackPaginating)
|
||||||
.listRowSeparator(.hidden)
|
|
||||||
|
LazyVStack(spacing: 0.0) {
|
||||||
// timelineItems won't be set for static Xcode Previews until they're run.
|
ForEach(isRunningPreviews ? context.viewState.items : timelineItems) { item in
|
||||||
ForEach(isPreview ? context.viewState.items : timelineItems) { timelineItem in
|
item
|
||||||
timelineItem
|
.contextMenu {
|
||||||
.contextMenu {
|
context.viewState.contextMenuBuilder?(item.id)
|
||||||
context.viewState.contextMenuBuilder?(timelineItem.id)
|
.id(item.id)
|
||||||
|
}
|
||||||
|
.opacity(opacityForItem(item))
|
||||||
|
.padding(settings.timelineStyle.rowInsets)
|
||||||
|
.onAppear {
|
||||||
|
context.send(viewAction: .itemAppeared(id: item.id))
|
||||||
|
visibleItemIdentifiers.insert(item.id)
|
||||||
|
|
||||||
|
if timelineItems.first == item {
|
||||||
|
topVisiblePublisher.send(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if timelineItems.last == item {
|
||||||
|
bottomVisiblePublisher.send(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onDisappear {
|
||||||
|
context.send(viewAction: .itemDisappeared(id: item.id))
|
||||||
|
visibleItemIdentifiers.remove(item.id)
|
||||||
|
|
||||||
|
if timelineItems.first == item {
|
||||||
|
topVisiblePublisher.send(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if timelineItems.last == item {
|
||||||
|
bottomVisiblePublisher.send(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.environment(\.openURL, OpenURLAction { url in
|
||||||
|
context.send(viewAction: .linkClicked(url: url))
|
||||||
|
return .systemAction
|
||||||
|
})
|
||||||
}
|
}
|
||||||
.opacity(opacityForItem(timelineItem))
|
}
|
||||||
.listRowBackground(Color.clear)
|
}
|
||||||
.listRowSeparator(.hidden)
|
.onChange(of: pinnedItem) { item in
|
||||||
.listRowInsets(settings.timelineStyle.listRowInsets)
|
guard let item else {
|
||||||
.onAppear {
|
return
|
||||||
context.send(viewAction: .itemAppeared(id: timelineItem.id))
|
}
|
||||||
|
|
||||||
|
if item.animated {
|
||||||
|
withAnimation(Animation.elementDefault) {
|
||||||
|
proxy.scrollTo(item.id, anchor: item.anchor)
|
||||||
}
|
}
|
||||||
.onDisappear {
|
} else {
|
||||||
context.send(viewAction: .itemDisappeared(id: timelineItem.id))
|
proxy.scrollTo(item.id, anchor: item.anchor)
|
||||||
}
|
}
|
||||||
.environment(\.openURL, OpenURLAction { url in
|
|
||||||
context.send(viewAction: .linkClicked(url: url))
|
pinnedItem = nil
|
||||||
return .systemAction
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.listStyle(.plain)
|
.scrollDismissesKeyboard(.immediately)
|
||||||
.background(ViewFrameReader(frame: $viewFrame))
|
.background(ViewFrameReader(frame: $viewFrame))
|
||||||
.environment(\.timelineWidth, viewFrame.width)
|
.environment(\.timelineWidth, viewFrame.width)
|
||||||
.timelineStyle(settings.timelineStyle)
|
.timelineStyle(settings.timelineStyle)
|
||||||
.environment(\.defaultMinListRowHeight, 0.0)
|
|
||||||
.introspectCollectionView { collectionView in
|
|
||||||
if collectionView == collectionViewObserver.collectionView { return }
|
|
||||||
|
|
||||||
collectionViewObserver = ListCollectionViewAdapter(collectionView: collectionView,
|
|
||||||
topDetectionOffset: collectionView.bounds.size.height / 3.0,
|
|
||||||
bottomDetectionOffset: 10.0)
|
|
||||||
|
|
||||||
collectionViewObserver.scrollToBottom()
|
|
||||||
|
|
||||||
// Check if there are enough items. Otherwise ask for more
|
|
||||||
attemptBackPagination()
|
|
||||||
}
|
|
||||||
.onAppear {
|
.onAppear {
|
||||||
if timelineItems != context.viewState.items {
|
requestBackPagination()
|
||||||
timelineItems = context.viewState.items
|
}
|
||||||
|
// Allow SwiftUI to layout the views properly before checking if the top is visible
|
||||||
|
.onReceive(topVisiblePublisher.collect(.byTime(DispatchQueue.main, 0.5))) { values in
|
||||||
|
if values.last == true {
|
||||||
|
requestBackPagination()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onReceive(scrollToBottomPublisher) {
|
.onReceive(scrollToBottomPublisher) {
|
||||||
collectionViewObserver.scrollToBottom(animated: true)
|
scrollToBottom(animated: true)
|
||||||
}
|
}
|
||||||
.onReceive(collectionViewObserver.scrollViewTopVisiblePublisher) { isTopVisible in
|
.onChange(of: context.viewState.items.count) { _ in
|
||||||
if !isTopVisible || context.viewState.isBackPaginating {
|
guard !context.viewState.items.isEmpty,
|
||||||
|
context.viewState.items.count != timelineItems.count else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
attemptBackPagination()
|
// Pin to the bottom if empty
|
||||||
}
|
if timelineItems.isEmpty {
|
||||||
.onReceive(collectionViewObserver.scrollViewBottomVisiblePublisher) { isBottomVisible in
|
if let lastItem = context.viewState.items.last {
|
||||||
bottomVisiblePublisher.send(isBottomVisible)
|
let pinnedItem = PinnedItem(id: lastItem.id, anchor: .bottom, animated: false)
|
||||||
}
|
timelineItems = context.viewState.items
|
||||||
.onChange(of: context.viewState.items) { _ in
|
self.pinnedItem = pinnedItem
|
||||||
// If the count hasn't changed then don't observe a pagination
|
}
|
||||||
guard context.viewState.items.count != timelineItems.count else {
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pin to the new bottom if visible
|
||||||
|
if let currentLastItem = timelineItems.last,
|
||||||
|
visibleItemIdentifiers.contains(currentLastItem.id),
|
||||||
|
let newLastItem = context.viewState.items.last {
|
||||||
|
let pinnedItem = PinnedItem(id: newLastItem.id, anchor: .bottom, animated: false)
|
||||||
timelineItems = context.viewState.items
|
timelineItems = context.viewState.items
|
||||||
|
self.pinnedItem = pinnedItem
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't update the list while moving
|
// Pin to the old topmost visible
|
||||||
if collectionViewObserver.isDecelerating || collectionViewObserver.isTracking {
|
if let currentFirstItem = timelineItems.first,
|
||||||
hasPendingChanges = true
|
visibleItemIdentifiers.contains(currentFirstItem.id) {
|
||||||
|
let pinnedItem = PinnedItem(id: currentFirstItem.id, anchor: .top, animated: false)
|
||||||
|
timelineItems = context.viewState.items
|
||||||
|
self.pinnedItem = pinnedItem
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
collectionViewObserver.saveCurrentOffset()
|
// Otherwise just update the items
|
||||||
timelineItems = context.viewState.items
|
timelineItems = context.viewState.items
|
||||||
}
|
}
|
||||||
.onReceive(collectionViewObserver.scrollViewDidRestPublisher) {
|
.background(GeometryReader { geo in
|
||||||
if hasPendingChanges == false {
|
Color.clear.preference(key: ViewFramePreferenceKey.self, value: [geo.frame(in: .global)])
|
||||||
|
})
|
||||||
|
.onPreferenceChange(ViewFramePreferenceKey.self) { _ in
|
||||||
|
guard bottomVisiblePublisher.value == true else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
collectionViewObserver.saveCurrentOffset()
|
// Pin the timeline to the bottom if was there on the frame change
|
||||||
timelineItems = context.viewState.items
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||||
hasPendingChanges = false
|
scrollToBottom(animated: true)
|
||||||
}
|
}
|
||||||
.onChange(of: timelineItems.count) { _ in
|
|
||||||
collectionViewObserver.restoreSavedOffset()
|
|
||||||
|
|
||||||
// Check if there are enough items. Otherwise ask for more
|
|
||||||
attemptBackPagination()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func scrollToBottom(animated: Bool = false) {
|
// MARK: - Private
|
||||||
collectionViewObserver.scrollToBottom(animated: animated)
|
|
||||||
|
private func scrollToBottom(animated: Bool = false) {
|
||||||
|
if let lastItem = timelineItems.last {
|
||||||
|
pinnedItem = PinnedItem(id: lastItem.id, anchor: .bottom, animated: animated)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func attemptBackPagination() {
|
private func requestBackPagination() {
|
||||||
if context.viewState.isBackPaginating {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if collectionViewObserver.scrollViewTopVisiblePublisher.value == false {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
context.send(viewAction: .loadPreviousPage)
|
context.send(viewAction: .loadPreviousPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +187,7 @@ struct TimelineItemList: View {
|
|||||||
return selectedItemId == item.id ? 1.0 : 0.5
|
return selectedItemId == item.id ? 1.0 : 0.5
|
||||||
}
|
}
|
||||||
|
|
||||||
private var isPreview: Bool {
|
private var isRunningPreviews: Bool {
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
return ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"
|
return ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"
|
||||||
#else
|
#else
|
||||||
@@ -163,6 +196,20 @@ struct TimelineItemList: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct PinnedItem: Equatable {
|
||||||
|
let id: String
|
||||||
|
let anchor: UnitPoint
|
||||||
|
let animated: Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct ViewFramePreferenceKey: PreferenceKey {
|
||||||
|
static var defaultValue = [CGRect]() // Doesn't work with plain CGRects
|
||||||
|
|
||||||
|
static func reduce(value: inout [CGRect], nextValue: () -> [CGRect]) {
|
||||||
|
value += nextValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct TimelineItemList_Previews: PreviewProvider {
|
struct TimelineItemList_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
body.preferredColorScheme(.light)
|
body.preferredColorScheme(.light)
|
||||||
@@ -176,7 +223,7 @@ struct TimelineItemList_Previews: PreviewProvider {
|
|||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
roomName: nil)
|
roomName: nil)
|
||||||
|
|
||||||
TimelineItemList(bottomVisiblePublisher: PassthroughSubject(), scrollToBottomPublisher: PassthroughSubject())
|
TimelineItemList(bottomVisiblePublisher: CurrentValueSubject(false), scrollToBottomPublisher: PassthroughSubject())
|
||||||
.environmentObject(viewModel.context)
|
.environmentObject(viewModel.context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import SwiftUI
|
|||||||
import Introspect
|
import Introspect
|
||||||
|
|
||||||
struct TimelineView: View {
|
struct TimelineView: View {
|
||||||
@State private var bottomVisiblePublisher = PassthroughSubject<Bool, Never>()
|
@State private var bottomVisiblePublisher = CurrentValueSubject<Bool, Never>(true)
|
||||||
@State private var scrollToBottomPublisher = PassthroughSubject<Void, Never>()
|
@State private var scrollToBottomPublisher = PassthroughSubject<Void, Never>()
|
||||||
@State private var scollToBottomButtonVisible = false
|
@State private var scollToBottomButtonVisible = false
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ import Foundation
|
|||||||
import MatrixRustSDK
|
import MatrixRustSDK
|
||||||
|
|
||||||
private class WeakRoomSummaryProviderWrapper: SlidingSyncViewRoomListObserver, SlidingSyncViewStateObserver, SlidingSyncViewRoomsCountObserver {
|
private class WeakRoomSummaryProviderWrapper: SlidingSyncViewRoomListObserver, SlidingSyncViewStateObserver, SlidingSyncViewRoomsCountObserver {
|
||||||
private weak var roomSummaryProvider: RoomSummaryProvider?
|
|
||||||
|
|
||||||
/// Publishes room list diffs as they come in through sliding sync
|
/// Publishes room list diffs as they come in through sliding sync
|
||||||
let roomListDiffPublisher = PassthroughSubject<SlidingSyncViewRoomsListDiff, Never>()
|
let roomListDiffPublisher = PassthroughSubject<SlidingSyncViewRoomsListDiff, Never>()
|
||||||
|
|
||||||
@@ -29,11 +27,7 @@ private class WeakRoomSummaryProviderWrapper: SlidingSyncViewRoomListObserver, S
|
|||||||
|
|
||||||
/// Publishes the number of available rooms
|
/// Publishes the number of available rooms
|
||||||
let countUpdatePublisher = CurrentValueSubject<UInt, Never>(0)
|
let countUpdatePublisher = CurrentValueSubject<UInt, Never>(0)
|
||||||
|
|
||||||
init(roomSummaryProvider: RoomSummaryProvider) {
|
|
||||||
self.roomSummaryProvider = roomSummaryProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - SlidingSyncViewRoomListObserver
|
// MARK: - SlidingSyncViewRoomListObserver
|
||||||
|
|
||||||
func didReceiveUpdate(diff: SlidingSyncViewRoomsListDiff) {
|
func didReceiveUpdate(diff: SlidingSyncViewRoomsListDiff) {
|
||||||
@@ -85,7 +79,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
|||||||
self.slidingSyncController = slidingSyncController
|
self.slidingSyncController = slidingSyncController
|
||||||
self.roomMessageFactory = roomMessageFactory
|
self.roomMessageFactory = roomMessageFactory
|
||||||
|
|
||||||
let weakProvider = WeakRoomSummaryProviderWrapper(roomSummaryProvider: self)
|
let weakProvider = WeakRoomSummaryProviderWrapper()
|
||||||
|
|
||||||
weakProvider.stateUpdatePublisher
|
weakProvider.stateUpdatePublisher
|
||||||
.map(RoomSummaryProviderState.init)
|
.map(RoomSummaryProviderState.init)
|
||||||
|
|||||||
@@ -17,8 +17,11 @@
|
|||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
struct MockRoomTimelineProvider: RoomTimelineProviderProtocol {
|
struct MockRoomTimelineProvider: RoomTimelineProviderProtocol {
|
||||||
var callbacks = PassthroughSubject<RoomTimelineProviderCallback, Never>()
|
var itemsPublisher = CurrentValueSubject<[TimelineItemProxy], Never>([])
|
||||||
var itemProxies = [TimelineItemProxy]()
|
|
||||||
|
var backPaginationPublisher = CurrentValueSubject<Bool, Never>(false)
|
||||||
|
|
||||||
|
private var itemProxies = [TimelineItemProxy]()
|
||||||
|
|
||||||
func paginateBackwards(_ count: UInt) async -> Result<Void, RoomTimelineProviderError> {
|
func paginateBackwards(_ count: UInt) async -> Result<Void, RoomTimelineProviderError> {
|
||||||
.failure(.failedPaginatingBackwards)
|
.failure(.failedPaginatingBackwards)
|
||||||
|
|||||||
@@ -51,14 +51,23 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
self.roomProxy = roomProxy
|
self.roomProxy = roomProxy
|
||||||
|
|
||||||
self.timelineProvider
|
self.timelineProvider
|
||||||
.callbacks
|
.itemsPublisher
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] callback in
|
.sink { [weak self] _ in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
|
|
||||||
switch callback {
|
self.updateTimelineItems()
|
||||||
case .updatedMessages:
|
}
|
||||||
self.updateTimelineItems()
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
self.timelineProvider
|
||||||
|
.backPaginationPublisher
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] value in
|
||||||
|
if value {
|
||||||
|
self?.callbacks.send(.startedBackPaginating)
|
||||||
|
} else {
|
||||||
|
self?.callbacks.send(.finishedBackPaginating)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
@@ -71,7 +80,6 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
func paginateBackwards(_ count: UInt) async -> Result<Void, RoomTimelineControllerError> {
|
func paginateBackwards(_ count: UInt) async -> Result<Void, RoomTimelineControllerError> {
|
||||||
switch await timelineProvider.paginateBackwards(count) {
|
switch await timelineProvider.paginateBackwards(count) {
|
||||||
case .success:
|
case .success:
|
||||||
updateTimelineItems()
|
|
||||||
return .success(())
|
return .success(())
|
||||||
case .failure:
|
case .failure:
|
||||||
return .failure(.generic)
|
return .failure(.generic)
|
||||||
@@ -135,13 +143,13 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
private func asyncUpdateTimelineItems() async {
|
private func asyncUpdateTimelineItems() async {
|
||||||
var newTimelineItems = [RoomTimelineItemProtocol]()
|
var newTimelineItems = [RoomTimelineItemProtocol]()
|
||||||
|
|
||||||
for (index, itemProxy) in timelineProvider.itemProxies.enumerated() {
|
for (index, itemProxy) in timelineProvider.itemsPublisher.value.enumerated() {
|
||||||
if Task.isCancelled {
|
if Task.isCancelled {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let previousItemProxy = timelineProvider.itemProxies[safe: index - 1]
|
let previousItemProxy = timelineProvider.itemsPublisher.value[safe: index - 1]
|
||||||
let nextItemProxy = timelineProvider.itemProxies[safe: index + 1]
|
let nextItemProxy = timelineProvider.itemsPublisher.value[safe: index + 1]
|
||||||
|
|
||||||
let inGroupState = inGroupState(for: itemProxy, previousItemProxy: previousItemProxy, nextItemProxy: nextItemProxy)
|
let inGroupState = inGroupState(for: itemProxy, previousItemProxy: previousItemProxy, nextItemProxy: nextItemProxy)
|
||||||
|
|
||||||
@@ -149,16 +157,9 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
case .event(let eventItem):
|
case .event(let eventItem):
|
||||||
guard eventItem.isMessage || eventItem.isRedacted else { break } // To be handled in the future
|
guard eventItem.isMessage || eventItem.isRedacted else { break } // To be handled in the future
|
||||||
|
|
||||||
newTimelineItems.append(await timelineItemFactory.buildTimelineItemFor(eventItemProxy: eventItem,
|
newTimelineItems.append(timelineItemFactory.buildTimelineItemFor(eventItemProxy: eventItem,
|
||||||
inGroupState: inGroupState))
|
inGroupState: inGroupState))
|
||||||
case .virtual:
|
default:
|
||||||
// case .virtual(let virtualItem):
|
|
||||||
// newTimelineItems.append(SeparatorRoomTimelineItem(id: message.originServerTs.ISO8601Format(),
|
|
||||||
// text: message.originServerTs.formatted(date: .complete, time: .omitted)))
|
|
||||||
#warning("Fix the UUID or \"bad things will happen\"")
|
|
||||||
newTimelineItems.append(SeparatorRoomTimelineItem(id: UUID().uuidString,
|
|
||||||
text: "The day before"))
|
|
||||||
case .other:
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import Foundation
|
|||||||
enum RoomTimelineControllerCallback {
|
enum RoomTimelineControllerCallback {
|
||||||
case updatedTimelineItems
|
case updatedTimelineItems
|
||||||
case updatedTimelineItem(_ itemId: String)
|
case updatedTimelineItem(_ itemId: String)
|
||||||
|
case startedBackPaginating
|
||||||
|
case finishedBackPaginating
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RoomTimelineControllerError: Error {
|
enum RoomTimelineControllerError: Error {
|
||||||
|
|||||||
@@ -17,16 +17,11 @@
|
|||||||
import Combine
|
import Combine
|
||||||
import MatrixRustSDK
|
import MatrixRustSDK
|
||||||
|
|
||||||
#warning("Rename to RoomTimelineListener???")
|
private class RoomTimelineListener: TimelineListener {
|
||||||
class WeakRoomTimelineProviderWrapper: TimelineListener {
|
let itemsUpdatePublisher = PassthroughSubject<TimelineDiff, Never>()
|
||||||
private weak var timelineProvider: RoomTimelineProvider?
|
|
||||||
|
|
||||||
init(timelineProvider: RoomTimelineProvider) {
|
|
||||||
self.timelineProvider = timelineProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
func onUpdate(update: TimelineDiff) {
|
func onUpdate(update: TimelineDiff) {
|
||||||
timelineProvider?.onUpdate(update: update)
|
itemsUpdatePublisher.send(update)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,25 +29,49 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
|
|||||||
private let roomProxy: RoomProxyProtocol
|
private let roomProxy: RoomProxyProtocol
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
let callbacks = PassthroughSubject<RoomTimelineProviderCallback, Never>()
|
let itemsPublisher = CurrentValueSubject<[TimelineItemProxy], Never>([])
|
||||||
|
let backPaginationPublisher = CurrentValueSubject<Bool, Never>(false)
|
||||||
|
|
||||||
private(set) var itemProxies: [TimelineItemProxy]
|
private var itemProxies: [TimelineItemProxy] {
|
||||||
|
didSet {
|
||||||
|
itemsPublisher.send(itemProxies)
|
||||||
|
|
||||||
|
if backPaginationPublisher.value == true {
|
||||||
|
backPaginationPublisher.send(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init(roomProxy: RoomProxyProtocol) {
|
init(roomProxy: RoomProxyProtocol) {
|
||||||
self.roomProxy = roomProxy
|
self.roomProxy = roomProxy
|
||||||
itemProxies = []
|
itemProxies = []
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
await roomProxy.addTimelineListener(listener: WeakRoomTimelineProviderWrapper(timelineProvider: self))
|
let roomTimelineListener = RoomTimelineListener()
|
||||||
|
await roomProxy.addTimelineListener(listener: roomTimelineListener)
|
||||||
|
|
||||||
|
roomTimelineListener
|
||||||
|
.itemsUpdatePublisher
|
||||||
|
.collect(.byTime(DispatchQueue.global(qos: .background), 0.5))
|
||||||
|
.sink { self.updateItemsWithDiffs($0) }
|
||||||
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func paginateBackwards(_ count: UInt) async -> Result<Void, RoomTimelineProviderError> {
|
func paginateBackwards(_ count: UInt) async -> Result<Void, RoomTimelineProviderError> {
|
||||||
|
// Set this back to false after actually updating the items or if failed
|
||||||
|
backPaginationPublisher.send(true)
|
||||||
|
|
||||||
switch await roomProxy.paginateBackwards(count: count) {
|
switch await roomProxy.paginateBackwards(count: count) {
|
||||||
case .success:
|
case .success:
|
||||||
return .success(())
|
return .success(())
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
if error == .noMoreMessagesToBackPaginate { return .failure(.noMoreMessagesToBackPaginate) }
|
backPaginationPublisher.send(false)
|
||||||
|
|
||||||
|
if error == .noMoreMessagesToBackPaginate {
|
||||||
|
return .failure(.noMoreMessagesToBackPaginate)
|
||||||
|
}
|
||||||
|
|
||||||
return .failure(.failedPaginatingBackwards)
|
return .failure(.failedPaginatingBackwards)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,74 +93,70 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
|
|||||||
return .failure(.failedRedactingItem)
|
return .failure(.failedRedactingItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
// MARK: - TimelineListener
|
private func updateItemsWithDiffs(_ diffs: [TimelineDiff]) {
|
||||||
|
itemProxies = diffs
|
||||||
private extension RoomTimelineProvider {
|
.compactMap(buildDiff)
|
||||||
func onUpdate(update: TimelineDiff) {
|
.reduce(itemProxies) { $0.applying($1) ?? $0 }
|
||||||
let change = update.change()
|
}
|
||||||
MXLog.verbose("Change: \(change)")
|
|
||||||
|
// swiftlint:disable:next cyclomatic_complexity
|
||||||
|
private func buildDiff(from diff: TimelineDiff) -> CollectionDifference<TimelineItemProxy>? {
|
||||||
|
var changes = [CollectionDifference<TimelineItemProxy>.Change]()
|
||||||
|
|
||||||
switch change {
|
switch diff.change() {
|
||||||
case .replace:
|
|
||||||
replaceItems(update.replace())
|
|
||||||
case .insertAt:
|
|
||||||
insertItem(update.insertAt())
|
|
||||||
case .updateAt:
|
|
||||||
updateItem(update.updateAt())
|
|
||||||
case .removeAt:
|
|
||||||
removeItem(at: update.removeAt())
|
|
||||||
case .move:
|
|
||||||
moveItem(update.move())
|
|
||||||
case .push:
|
case .push:
|
||||||
pushItem(update.push())
|
if let item = diff.push() {
|
||||||
case .pop:
|
let itemProxy = TimelineItemProxy(item: item)
|
||||||
popItem()
|
changes.append(.insert(offset: Int(itemProxies.count), element: itemProxy, associatedWith: nil))
|
||||||
|
}
|
||||||
|
case .updateAt:
|
||||||
|
if let update = diff.updateAt() {
|
||||||
|
let itemProxy = TimelineItemProxy(item: update.item)
|
||||||
|
changes.append(.remove(offset: Int(update.index), element: itemProxy, associatedWith: nil))
|
||||||
|
changes.append(.insert(offset: Int(update.index), element: itemProxy, associatedWith: nil))
|
||||||
|
}
|
||||||
|
case .insertAt:
|
||||||
|
if let update = diff.insertAt() {
|
||||||
|
let itemProxy = TimelineItemProxy(item: update.item)
|
||||||
|
changes.append(.insert(offset: Int(update.index), element: itemProxy, associatedWith: nil))
|
||||||
|
}
|
||||||
|
case .move:
|
||||||
|
if let update = diff.move() {
|
||||||
|
let itemProxy = itemProxies[Int(update.oldIndex)]
|
||||||
|
changes.append(.remove(offset: Int(update.oldIndex), element: itemProxy, associatedWith: nil))
|
||||||
|
changes.append(.insert(offset: Int(update.newIndex), element: itemProxy, associatedWith: nil))
|
||||||
|
}
|
||||||
|
case .removeAt:
|
||||||
|
if let index = diff.removeAt() {
|
||||||
|
let itemProxy = itemProxies[Int(index)]
|
||||||
|
changes.append(.remove(offset: Int(index), element: itemProxy, associatedWith: nil))
|
||||||
|
}
|
||||||
|
case .replace:
|
||||||
|
if let items = diff.replace() {
|
||||||
|
for (index, itemProxy) in itemProxies.enumerated() {
|
||||||
|
changes.append(.remove(offset: index, element: itemProxy, associatedWith: nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
items
|
||||||
|
.reversed()
|
||||||
|
.map { TimelineItemProxy(item: $0) }
|
||||||
|
.forEach { itemProxy in
|
||||||
|
changes.append(.insert(offset: 0, element: itemProxy, associatedWith: nil))
|
||||||
|
}
|
||||||
|
}
|
||||||
case .clear:
|
case .clear:
|
||||||
clearAllItems()
|
for (index, itemProxy) in itemProxies.enumerated() {
|
||||||
|
changes.append(.remove(offset: index, element: itemProxy, associatedWith: nil))
|
||||||
|
}
|
||||||
|
case .pop:
|
||||||
|
if let itemProxy = itemProxies.last {
|
||||||
|
changes.append(.remove(offset: itemProxies.count - 1, element: itemProxy, associatedWith: nil))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
callbacks.send(.updatedMessages)
|
return CollectionDifference(changes)
|
||||||
}
|
|
||||||
|
|
||||||
private func replaceItems(_ items: [MatrixRustSDK.TimelineItem]?) {
|
|
||||||
guard let items else { return }
|
|
||||||
itemProxies = items.map(TimelineItemProxy.init)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func insertItem(_ data: InsertAtData?) {
|
|
||||||
guard let data else { return }
|
|
||||||
let itemProxy = TimelineItemProxy(item: data.item)
|
|
||||||
itemProxies.insert(itemProxy, at: Int(data.index))
|
|
||||||
}
|
|
||||||
|
|
||||||
private func updateItem(_ data: UpdateAtData?) {
|
|
||||||
guard let data else { return }
|
|
||||||
let itemProxy = TimelineItemProxy(item: data.item)
|
|
||||||
itemProxies[Int(data.index)] = itemProxy
|
|
||||||
}
|
|
||||||
|
|
||||||
private func removeItem(at index: UInt32?) {
|
|
||||||
guard let index else { return }
|
|
||||||
itemProxies.remove(at: Int(index))
|
|
||||||
}
|
|
||||||
|
|
||||||
private func moveItem(_ data: MoveData?) {
|
|
||||||
guard let data else { return }
|
|
||||||
itemProxies.move(fromOffsets: IndexSet(integer: Int(data.oldIndex)), toOffset: Int(data.newIndex))
|
|
||||||
}
|
|
||||||
|
|
||||||
private func pushItem(_ item: MatrixRustSDK.TimelineItem?) {
|
|
||||||
guard let item else { return }
|
|
||||||
itemProxies.append(TimelineItemProxy(item: item))
|
|
||||||
}
|
|
||||||
|
|
||||||
private func popItem() {
|
|
||||||
itemProxies.removeLast()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func clearAllItems() {
|
|
||||||
itemProxies.removeAll()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,10 +17,6 @@
|
|||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum RoomTimelineProviderCallback {
|
|
||||||
case updatedMessages
|
|
||||||
}
|
|
||||||
|
|
||||||
enum RoomTimelineProviderError: Error {
|
enum RoomTimelineProviderError: Error {
|
||||||
case noMoreMessagesToBackPaginate
|
case noMoreMessagesToBackPaginate
|
||||||
case failedPaginatingBackwards
|
case failedPaginatingBackwards
|
||||||
@@ -30,9 +26,9 @@ enum RoomTimelineProviderError: Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protocol RoomTimelineProviderProtocol {
|
protocol RoomTimelineProviderProtocol {
|
||||||
var callbacks: PassthroughSubject<RoomTimelineProviderCallback, Never> { get }
|
var itemsPublisher: CurrentValueSubject<[TimelineItemProxy], Never> { get }
|
||||||
|
|
||||||
var itemProxies: [TimelineItemProxy] { get }
|
var backPaginationPublisher: CurrentValueSubject<Bool, Never> { get }
|
||||||
|
|
||||||
func paginateBackwards(_ count: UInt) async -> Result<Void, RoomTimelineProviderError>
|
func paginateBackwards(_ count: UInt) async -> Result<Void, RoomTimelineProviderError>
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildTimelineItemFor(eventItemProxy: EventTimelineItemProxy,
|
func buildTimelineItemFor(eventItemProxy: EventTimelineItemProxy,
|
||||||
inGroupState: TimelineItemInGroupState) async -> RoomTimelineItemProtocol {
|
inGroupState: TimelineItemInGroupState) -> RoomTimelineItemProtocol {
|
||||||
let displayName = roomProxy.displayNameForUserId(eventItemProxy.sender)
|
let displayName = roomProxy.displayNameForUserId(eventItemProxy.sender)
|
||||||
let avatarURL = roomProxy.avatarURLStringForUserId(eventItemProxy.sender)
|
let avatarURL = roomProxy.avatarURLStringForUserId(eventItemProxy.sender)
|
||||||
let avatarImage = mediaProvider.imageFromURLString(avatarURL, avatarSize: .user(on: .timeline))
|
let avatarImage = mediaProvider.imageFromURLString(avatarURL, avatarSize: .user(on: .timeline))
|
||||||
@@ -51,18 +51,18 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
switch messageContent.msgtype() {
|
switch messageContent.msgtype() {
|
||||||
case .text(content: let content):
|
case .text(content: let content):
|
||||||
let message = MessageTimelineItem(item: eventItemProxy.item, content: content)
|
let message = MessageTimelineItem(item: eventItemProxy.item, content: content)
|
||||||
return await buildTextTimelineItemFromMessage(message, isOutgoing, inGroupState, displayName, avatarImage)
|
return buildTextTimelineItemFromMessage(message, isOutgoing, inGroupState, displayName, avatarImage)
|
||||||
case .image(content: let content):
|
case .image(content: let content):
|
||||||
let message = MessageTimelineItem(item: eventItemProxy.item, content: content)
|
let message = MessageTimelineItem(item: eventItemProxy.item, content: content)
|
||||||
return await buildImageTimelineItemFromMessage(message, isOutgoing, inGroupState, displayName, avatarImage)
|
return buildImageTimelineItemFromMessage(message, isOutgoing, inGroupState, displayName, avatarImage)
|
||||||
case .notice(content: let content):
|
case .notice(content: let content):
|
||||||
let message = MessageTimelineItem(item: eventItemProxy.item, content: content)
|
let message = MessageTimelineItem(item: eventItemProxy.item, content: content)
|
||||||
return await buildNoticeTimelineItemFromMessage(message, isOutgoing, inGroupState, displayName, avatarImage)
|
return buildNoticeTimelineItemFromMessage(message, isOutgoing, inGroupState, displayName, avatarImage)
|
||||||
case .emote(content: let content):
|
case .emote(content: let content):
|
||||||
let message = MessageTimelineItem(item: eventItemProxy.item, content: content)
|
let message = MessageTimelineItem(item: eventItemProxy.item, content: content)
|
||||||
return await buildEmoteTimelineItemFromMessage(message, isOutgoing, inGroupState, displayName, avatarImage)
|
return buildEmoteTimelineItemFromMessage(message, isOutgoing, inGroupState, displayName, avatarImage)
|
||||||
case .none:
|
case .none:
|
||||||
return await buildFallbackTimelineItem(eventItemProxy, isOutgoing, inGroupState, displayName, avatarImage)
|
return buildFallbackTimelineItem(eventItemProxy, isOutgoing, inGroupState, displayName, avatarImage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,8 +88,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
_ isOutgoing: Bool,
|
_ isOutgoing: Bool,
|
||||||
_ inGroupState: TimelineItemInGroupState,
|
_ inGroupState: TimelineItemInGroupState,
|
||||||
_ displayName: String?,
|
_ displayName: String?,
|
||||||
_ avatarImage: UIImage?) async -> RoomTimelineItemProtocol {
|
_ avatarImage: UIImage?) -> RoomTimelineItemProtocol {
|
||||||
let attributedText = await attributedStringBuilder.fromPlain(eventItemProxy.body)
|
let attributedText = attributedStringBuilder.fromPlain(eventItemProxy.body)
|
||||||
let attributedComponents = attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedText)
|
let attributedComponents = attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedText)
|
||||||
|
|
||||||
return TextRoomTimelineItem(id: eventItemProxy.id,
|
return TextRoomTimelineItem(id: eventItemProxy.id,
|
||||||
@@ -109,8 +109,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
_ isOutgoing: Bool,
|
_ isOutgoing: Bool,
|
||||||
_ inGroupState: TimelineItemInGroupState,
|
_ inGroupState: TimelineItemInGroupState,
|
||||||
_ displayName: String?,
|
_ displayName: String?,
|
||||||
_ avatarImage: UIImage?) async -> RoomTimelineItemProtocol {
|
_ avatarImage: UIImage?) -> RoomTimelineItemProtocol {
|
||||||
let attributedText = await (message.htmlBody != nil ? attributedStringBuilder.fromHTML(message.htmlBody) : attributedStringBuilder.fromPlain(message.body))
|
let attributedText = (message.htmlBody != nil ? attributedStringBuilder.fromHTML(message.htmlBody) : attributedStringBuilder.fromPlain(message.body))
|
||||||
let attributedComponents = attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedText)
|
let attributedComponents = attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedText)
|
||||||
|
|
||||||
return TextRoomTimelineItem(id: message.id,
|
return TextRoomTimelineItem(id: message.id,
|
||||||
@@ -130,7 +130,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
_ isOutgoing: Bool,
|
_ isOutgoing: Bool,
|
||||||
_ inGroupState: TimelineItemInGroupState,
|
_ inGroupState: TimelineItemInGroupState,
|
||||||
_ displayName: String?,
|
_ displayName: String?,
|
||||||
_ avatarImage: UIImage?) async -> RoomTimelineItemProtocol {
|
_ avatarImage: UIImage?) -> RoomTimelineItemProtocol {
|
||||||
var aspectRatio: CGFloat?
|
var aspectRatio: CGFloat?
|
||||||
if let width = message.width,
|
if let width = message.width,
|
||||||
let height = message.height {
|
let height = message.height {
|
||||||
@@ -159,8 +159,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
_ isOutgoing: Bool,
|
_ isOutgoing: Bool,
|
||||||
_ inGroupState: TimelineItemInGroupState,
|
_ inGroupState: TimelineItemInGroupState,
|
||||||
_ displayName: String?,
|
_ displayName: String?,
|
||||||
_ avatarImage: UIImage?) async -> RoomTimelineItemProtocol {
|
_ avatarImage: UIImage?) -> RoomTimelineItemProtocol {
|
||||||
let attributedText = await (message.htmlBody != nil ? attributedStringBuilder.fromHTML(message.htmlBody) : attributedStringBuilder.fromPlain(message.body))
|
let attributedText = (message.htmlBody != nil ? attributedStringBuilder.fromHTML(message.htmlBody) : attributedStringBuilder.fromPlain(message.body))
|
||||||
let attributedComponents = attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedText)
|
let attributedComponents = attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedText)
|
||||||
|
|
||||||
return NoticeRoomTimelineItem(id: message.id,
|
return NoticeRoomTimelineItem(id: message.id,
|
||||||
@@ -180,8 +180,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
_ isOutgoing: Bool,
|
_ isOutgoing: Bool,
|
||||||
_ inGroupState: TimelineItemInGroupState,
|
_ inGroupState: TimelineItemInGroupState,
|
||||||
_ displayName: String?,
|
_ displayName: String?,
|
||||||
_ avatarImage: UIImage?) async -> RoomTimelineItemProtocol {
|
_ avatarImage: UIImage?) -> RoomTimelineItemProtocol {
|
||||||
let attributedText = await (message.htmlBody != nil ? attributedStringBuilder.fromHTML(message.htmlBody) : attributedStringBuilder.fromPlain(message.body))
|
let attributedText = (message.htmlBody != nil ? attributedStringBuilder.fromHTML(message.htmlBody) : attributedStringBuilder.fromPlain(message.body))
|
||||||
let attributedComponents = attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedText)
|
let attributedComponents = attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedText)
|
||||||
|
|
||||||
return EmoteRoomTimelineItem(id: message.id,
|
return EmoteRoomTimelineItem(id: message.id,
|
||||||
|
|||||||
+1
-1
@@ -19,5 +19,5 @@ import Foundation
|
|||||||
@MainActor
|
@MainActor
|
||||||
protocol RoomTimelineItemFactoryProtocol {
|
protocol RoomTimelineItemFactoryProtocol {
|
||||||
func buildTimelineItemFor(eventItemProxy: EventTimelineItemProxy,
|
func buildTimelineItemFor(eventItemProxy: EventTimelineItemProxy,
|
||||||
inGroupState: TimelineItemInGroupState) async -> RoomTimelineItemProtocol
|
inGroupState: TimelineItemInGroupState) -> RoomTimelineItemProtocol
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,7 +105,6 @@ targets:
|
|||||||
- package: DTCoreText
|
- package: DTCoreText
|
||||||
- package: KeychainAccess
|
- package: KeychainAccess
|
||||||
- package: Kingfisher
|
- package: Kingfisher
|
||||||
- package: Introspect
|
|
||||||
- package: PostHog
|
- package: PostHog
|
||||||
- package: SwiftyBeaver
|
- package: SwiftyBeaver
|
||||||
- package: SwiftState
|
- package: SwiftState
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ targets:
|
|||||||
linkType: static
|
linkType: static
|
||||||
- package: Kingfisher
|
- package: Kingfisher
|
||||||
linkType: static
|
linkType: static
|
||||||
- package: Introspect
|
|
||||||
linkType: static
|
|
||||||
- package: SwiftyBeaver
|
- package: SwiftyBeaver
|
||||||
linkType: static
|
linkType: static
|
||||||
- package: SwiftState
|
- package: SwiftState
|
||||||
|
|||||||
@@ -41,8 +41,6 @@ targets:
|
|||||||
linkType: static
|
linkType: static
|
||||||
- package: Kingfisher
|
- package: Kingfisher
|
||||||
linkType: static
|
linkType: static
|
||||||
- package: Introspect
|
|
||||||
linkType: static
|
|
||||||
- package: PostHog
|
- package: PostHog
|
||||||
linkType: static
|
linkType: static
|
||||||
- package: SwiftyBeaver
|
- package: SwiftyBeaver
|
||||||
|
|||||||
@@ -21,14 +21,14 @@ class AttributedStringBuilderTests: XCTestCase {
|
|||||||
let attributedStringBuilder = AttributedStringBuilder()
|
let attributedStringBuilder = AttributedStringBuilder()
|
||||||
let maxHeaderPointSize = ceil(UIFont.preferredFont(forTextStyle: .body).pointSize * 1.2)
|
let maxHeaderPointSize = ceil(UIFont.preferredFont(forTextStyle: .body).pointSize * 1.2)
|
||||||
|
|
||||||
func testRenderHTMLStringWithHeaders() async {
|
func testRenderHTMLStringWithHeaders() {
|
||||||
let h1HTMLString = "<h1>Large Heading</h1>"
|
let h1HTMLString = "<h1>Large Heading</h1>"
|
||||||
let h2HTMLString = "<h2>Smaller Heading</h2>"
|
let h2HTMLString = "<h2>Smaller Heading</h2>"
|
||||||
let h3HTMLString = "<h3>Acceptable Heading</h3>"
|
let h3HTMLString = "<h3>Acceptable Heading</h3>"
|
||||||
|
|
||||||
guard let h1AttributedString = await attributedStringBuilder.fromHTML(h1HTMLString),
|
guard let h1AttributedString = attributedStringBuilder.fromHTML(h1HTMLString),
|
||||||
let h2AttributedString = await attributedStringBuilder.fromHTML(h2HTMLString),
|
let h2AttributedString = attributedStringBuilder.fromHTML(h2HTMLString),
|
||||||
let h3AttributedString = await attributedStringBuilder.fromHTML(h3HTMLString) else {
|
let h3AttributedString = attributedStringBuilder.fromHTML(h3HTMLString) else {
|
||||||
XCTFail("Could not build the attributed string")
|
XCTFail("Could not build the attributed string")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -56,10 +56,10 @@ class AttributedStringBuilderTests: XCTestCase {
|
|||||||
XCTAssert(h1Font.pointSize <= maxHeaderPointSize)
|
XCTAssert(h1Font.pointSize <= maxHeaderPointSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRenderHTMLStringWithPreCode() async {
|
func testRenderHTMLStringWithPreCode() {
|
||||||
let htmlString = "<pre><code>1\n2\n3\n4\n</code></pre>"
|
let htmlString = "<pre><code>1\n2\n3\n4\n</code></pre>"
|
||||||
|
|
||||||
guard let attributedString = await attributedStringBuilder.fromHTML(htmlString) else {
|
guard let attributedString = attributedStringBuilder.fromHTML(htmlString) else {
|
||||||
XCTFail("Could not build the attributed string")
|
XCTFail("Could not build the attributed string")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -76,10 +76,10 @@ class AttributedStringBuilderTests: XCTestCase {
|
|||||||
XCTAssertEqual(regex.numberOfMatches(in: string, options: [], range: .init(location: 0, length: string.count)), 3)
|
XCTAssertEqual(regex.numberOfMatches(in: string, options: [], range: .init(location: 0, length: string.count)), 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRenderHTMLStringWithLink() async {
|
func testRenderHTMLStringWithLink() {
|
||||||
let htmlString = "This text contains a <a href=\"https://www.matrix.org/\">link</a>."
|
let htmlString = "This text contains a <a href=\"https://www.matrix.org/\">link</a>."
|
||||||
|
|
||||||
guard let attributedString = await attributedStringBuilder.fromHTML(htmlString) else {
|
guard let attributedString = attributedStringBuilder.fromHTML(htmlString) else {
|
||||||
XCTFail("Could not build the attributed string")
|
XCTFail("Could not build the attributed string")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -93,10 +93,10 @@ class AttributedStringBuilderTests: XCTestCase {
|
|||||||
XCTAssertEqual(link?.host, "www.matrix.org")
|
XCTAssertEqual(link?.host, "www.matrix.org")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRenderPlainStringWithLink() async {
|
func testRenderPlainStringWithLink() {
|
||||||
let plainString = "This text contains a https://www.matrix.org link."
|
let plainString = "This text contains a https://www.matrix.org link."
|
||||||
|
|
||||||
guard let attributedString = await attributedStringBuilder.fromPlain(plainString) else {
|
guard let attributedString = attributedStringBuilder.fromPlain(plainString) else {
|
||||||
XCTFail("Could not build the attributed string")
|
XCTFail("Could not build the attributed string")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -110,14 +110,14 @@ class AttributedStringBuilderTests: XCTestCase {
|
|||||||
XCTAssertEqual(link?.host, "www.matrix.org")
|
XCTAssertEqual(link?.host, "www.matrix.org")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRenderHTMLStringWithLinkInHeader() async {
|
func testRenderHTMLStringWithLinkInHeader() {
|
||||||
let h1HTMLString = "<h1><a href=\"https://www.matrix.org/\">Matrix.org</a></h1>"
|
let h1HTMLString = "<h1><a href=\"https://www.matrix.org/\">Matrix.org</a></h1>"
|
||||||
let h2HTMLString = "<h2><a href=\"https://www.matrix.org/\">Matrix.org</a></h2>"
|
let h2HTMLString = "<h2><a href=\"https://www.matrix.org/\">Matrix.org</a></h2>"
|
||||||
let h3HTMLString = "<h3><a href=\"https://www.matrix.org/\">Matrix.org</a></h3>"
|
let h3HTMLString = "<h3><a href=\"https://www.matrix.org/\">Matrix.org</a></h3>"
|
||||||
|
|
||||||
guard let h1AttributedString = await attributedStringBuilder.fromHTML(h1HTMLString),
|
guard let h1AttributedString = attributedStringBuilder.fromHTML(h1HTMLString),
|
||||||
let h2AttributedString = await attributedStringBuilder.fromHTML(h2HTMLString),
|
let h2AttributedString = attributedStringBuilder.fromHTML(h2HTMLString),
|
||||||
let h3AttributedString = await attributedStringBuilder.fromHTML(h3HTMLString) else {
|
let h3AttributedString = attributedStringBuilder.fromHTML(h3HTMLString) else {
|
||||||
XCTFail("Could not build the attributed string")
|
XCTFail("Could not build the attributed string")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -148,10 +148,10 @@ class AttributedStringBuilderTests: XCTestCase {
|
|||||||
XCTAssertEqual(h3AttributedString.runs.first?.link?.host, "www.matrix.org")
|
XCTAssertEqual(h3AttributedString.runs.first?.link?.host, "www.matrix.org")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRenderHTMLStringWithIFrame() async {
|
func testRenderHTMLStringWithIFrame() {
|
||||||
let htmlString = "<iframe src=\"https://www.matrix.org/\"></iframe>"
|
let htmlString = "<iframe src=\"https://www.matrix.org/\"></iframe>"
|
||||||
|
|
||||||
guard let attributedString = await attributedStringBuilder.fromHTML(htmlString) else {
|
guard let attributedString = attributedStringBuilder.fromHTML(htmlString) else {
|
||||||
XCTFail("Could not build the attributed string")
|
XCTFail("Could not build the attributed string")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -159,38 +159,38 @@ class AttributedStringBuilderTests: XCTestCase {
|
|||||||
XCTAssertNil(attributedString.uiKit.attachment, "iFrame attachments should be removed as they're not included in the allowedHTMLTags array.")
|
XCTAssertNil(attributedString.uiKit.attachment, "iFrame attachments should be removed as they're not included in the allowedHTMLTags array.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testUserIdLink() async {
|
func testUserIdLink() {
|
||||||
let userId = "@user:matrix.org"
|
let userId = "@user:matrix.org"
|
||||||
let string = "The user is \(userId)."
|
let string = "The user is \(userId)."
|
||||||
checkMatrixEntityLinkIn(attributedString: await attributedStringBuilder.fromHTML(string), expected: userId)
|
checkMatrixEntityLinkIn(attributedString: attributedStringBuilder.fromHTML(string), expected: userId)
|
||||||
checkMatrixEntityLinkIn(attributedString: await attributedStringBuilder.fromPlain(string), expected: userId)
|
checkMatrixEntityLinkIn(attributedString: attributedStringBuilder.fromPlain(string), expected: userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRoomAliasLink() async {
|
func testRoomAliasLink() {
|
||||||
let roomAlias = "#matrix:matrix.org"
|
let roomAlias = "#matrix:matrix.org"
|
||||||
let string = "The room alias is \(roomAlias)."
|
let string = "The room alias is \(roomAlias)."
|
||||||
checkMatrixEntityLinkIn(attributedString: await attributedStringBuilder.fromHTML(string), expected: roomAlias)
|
checkMatrixEntityLinkIn(attributedString: attributedStringBuilder.fromHTML(string), expected: roomAlias)
|
||||||
checkMatrixEntityLinkIn(attributedString: await attributedStringBuilder.fromPlain(string), expected: roomAlias)
|
checkMatrixEntityLinkIn(attributedString: attributedStringBuilder.fromPlain(string), expected: roomAlias)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRoomIdLink() async {
|
func testRoomIdLink() {
|
||||||
let roomId = "!roomidentifier:matrix.org"
|
let roomId = "!roomidentifier:matrix.org"
|
||||||
let string = "The room is \(roomId)."
|
let string = "The room is \(roomId)."
|
||||||
checkMatrixEntityLinkIn(attributedString: await attributedStringBuilder.fromHTML(string), expected: roomId)
|
checkMatrixEntityLinkIn(attributedString: attributedStringBuilder.fromHTML(string), expected: roomId)
|
||||||
checkMatrixEntityLinkIn(attributedString: await attributedStringBuilder.fromPlain(string), expected: roomId)
|
checkMatrixEntityLinkIn(attributedString: attributedStringBuilder.fromPlain(string), expected: roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testEventIdLink() async {
|
func testEventIdLink() {
|
||||||
let eventId = "$eventidentifier"
|
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: attributedStringBuilder.fromHTML(string), expected: eventId)
|
||||||
checkMatrixEntityLinkIn(attributedString: await attributedStringBuilder.fromPlain(string), expected: eventId)
|
checkMatrixEntityLinkIn(attributedString: attributedStringBuilder.fromPlain(string), expected: eventId)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDefaultFont() async {
|
func testDefaultFont() {
|
||||||
let htmlString = "<b>Test</b> <i>string</i>."
|
let htmlString = "<b>Test</b> <i>string</i>."
|
||||||
|
|
||||||
guard let attributedString = await attributedStringBuilder.fromHTML(htmlString) else {
|
guard let attributedString = attributedStringBuilder.fromHTML(htmlString) else {
|
||||||
XCTFail("Could not build the attributed string")
|
XCTFail("Could not build the attributed string")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -202,10 +202,10 @@ class AttributedStringBuilderTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDefaultForegroundColor() async {
|
func testDefaultForegroundColor() {
|
||||||
let htmlString = "<b>Test</b> <i>string</i>."
|
let htmlString = "<b>Test</b> <i>string</i>."
|
||||||
|
|
||||||
guard let attributedString = await attributedStringBuilder.fromHTML(htmlString) else {
|
guard let attributedString = attributedStringBuilder.fromHTML(htmlString) else {
|
||||||
XCTFail("Could not build the attributed string")
|
XCTFail("Could not build the attributed string")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -217,11 +217,11 @@ class AttributedStringBuilderTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCustomForegroundColor() async {
|
func testCustomForegroundColor() {
|
||||||
// swiftlint:disable:next line_length
|
// swiftlint:disable:next line_length
|
||||||
let htmlString = "<font color=\"#ff00be\">R</font><font color=\"#ff0082\">a</font><font color=\"#ff0047\">i</font><font color=\"#ff5800\">n </font><font color=\"#ffa300\">w</font><font color=\"#d2ba00\">w</font><font color=\"#97ca00\">w</font><font color=\"#3ed500\">.</font><font color=\"#00dd00\">m</font><font color=\"#00e251\">a</font><font color=\"#00e595\">t</font><font color=\"#00e7d6\">r</font><font color=\"#00e7ff\">i</font><font color=\"#00e6ff\">x</font><font color=\"#00e3ff\">.</font><font color=\"#00dbff\">o</font><font color=\"#00ceff\">r</font><font color=\"#00baff\">g</font><font color=\"#f477ff\"> b</font><font color=\"#ff3aff\">o</font><font color=\"#ff00fb\">w</font>"
|
let htmlString = "<font color=\"#ff00be\">R</font><font color=\"#ff0082\">a</font><font color=\"#ff0047\">i</font><font color=\"#ff5800\">n </font><font color=\"#ffa300\">w</font><font color=\"#d2ba00\">w</font><font color=\"#97ca00\">w</font><font color=\"#3ed500\">.</font><font color=\"#00dd00\">m</font><font color=\"#00e251\">a</font><font color=\"#00e595\">t</font><font color=\"#00e7d6\">r</font><font color=\"#00e7ff\">i</font><font color=\"#00e6ff\">x</font><font color=\"#00e3ff\">.</font><font color=\"#00dbff\">o</font><font color=\"#00ceff\">r</font><font color=\"#00baff\">g</font><font color=\"#f477ff\"> b</font><font color=\"#ff3aff\">o</font><font color=\"#ff00fb\">w</font>"
|
||||||
|
|
||||||
guard let attributedString = await attributedStringBuilder.fromHTML(htmlString) else {
|
guard let attributedString = attributedStringBuilder.fromHTML(htmlString) else {
|
||||||
XCTFail("Could not build the attributed string")
|
XCTFail("Could not build the attributed string")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -242,10 +242,10 @@ class AttributedStringBuilderTests: XCTestCase {
|
|||||||
XCTAssertTrue(foundLink)
|
XCTAssertTrue(foundLink)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSingleBlockquote() async {
|
func testSingleBlockquote() {
|
||||||
let htmlString = "<blockquote>Blockquote</blockquote>"
|
let htmlString = "<blockquote>Blockquote</blockquote>"
|
||||||
|
|
||||||
guard let attributedString = await attributedStringBuilder.fromHTML(htmlString) else {
|
guard let attributedString = attributedStringBuilder.fromHTML(htmlString) else {
|
||||||
XCTFail("Could not build the attributed string")
|
XCTFail("Could not build the attributed string")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -262,14 +262,14 @@ class AttributedStringBuilderTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// swiftlint:disable line_length
|
// swiftlint:disable line_length
|
||||||
func testBlockquoteWithinText() async {
|
func testBlockquoteWithinText() {
|
||||||
let htmlString = """
|
let htmlString = """
|
||||||
The text before the blockquote
|
The text before the blockquote
|
||||||
<blockquote> For 50 years, WWF has been protecting the future of nature. The world's leading conservation organization, WWF works in 100 countries and is supported by 1.2 million members in the United States and close to 5 million globally.</blockquote>
|
<blockquote> For 50 years, WWF has been protecting the future of nature. The world's leading conservation organization, WWF works in 100 countries and is supported by 1.2 million members in the United States and close to 5 million globally.</blockquote>
|
||||||
The text after the blockquote
|
The text after the blockquote
|
||||||
"""
|
"""
|
||||||
|
|
||||||
guard let attributedString = await attributedStringBuilder.fromHTML(htmlString) else {
|
guard let attributedString = attributedStringBuilder.fromHTML(htmlString) else {
|
||||||
XCTFail("Could not build the attributed string")
|
XCTFail("Could not build the attributed string")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -287,10 +287,10 @@ class AttributedStringBuilderTests: XCTestCase {
|
|||||||
|
|
||||||
// swiftlint:enable line_length
|
// swiftlint:enable line_length
|
||||||
|
|
||||||
func testBlockquoteWithLink() async {
|
func testBlockquoteWithLink() {
|
||||||
let htmlString = "<blockquote>Blockquote with a <a href=\"https://www.matrix.org/\">link</a> in it</blockquote>"
|
let htmlString = "<blockquote>Blockquote with a <a href=\"https://www.matrix.org/\">link</a> in it</blockquote>"
|
||||||
|
|
||||||
guard let attributedString = await attributedStringBuilder.fromHTML(htmlString) else {
|
guard let attributedString = attributedStringBuilder.fromHTML(htmlString) else {
|
||||||
XCTFail("Could not build the attributed string")
|
XCTFail("Could not build the attributed string")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -313,14 +313,14 @@ class AttributedStringBuilderTests: XCTestCase {
|
|||||||
XCTAssertNotNil(foundBlockquoteAndLink, "Couldn't find blockquote or link")
|
XCTAssertNotNil(foundBlockquoteAndLink, "Couldn't find blockquote or link")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMultipleGroupedBlockquotes() async {
|
func testMultipleGroupedBlockquotes() {
|
||||||
let htmlString = """
|
let htmlString = """
|
||||||
<blockquote>First blockquote with a <a href=\"https://www.matrix.org/\">link</a> in it</blockquote>
|
<blockquote>First blockquote with a <a href=\"https://www.matrix.org/\">link</a> in it</blockquote>
|
||||||
<blockquote>Second blockquote with a <a href=\"https://www.matrix.org/\">link</a> in it</blockquote>
|
<blockquote>Second blockquote with a <a href=\"https://www.matrix.org/\">link</a> in it</blockquote>
|
||||||
<blockquote>Third blockquote with a <a href=\"https://www.matrix.org/\">link</a> in it</blockquote>
|
<blockquote>Third blockquote with a <a href=\"https://www.matrix.org/\">link</a> in it</blockquote>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
guard let attributedString = await attributedStringBuilder.fromHTML(htmlString) else {
|
guard let attributedString = attributedStringBuilder.fromHTML(htmlString) else {
|
||||||
XCTFail("Could not build the attributed string")
|
XCTFail("Could not build the attributed string")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -337,7 +337,7 @@ class AttributedStringBuilderTests: XCTestCase {
|
|||||||
XCTAssertEqual(numberOfBlockquotes, 3, "Couldn't find all the blockquotes")
|
XCTAssertEqual(numberOfBlockquotes, 3, "Couldn't find all the blockquotes")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMultipleSeparatedBlockquotes() async {
|
func testMultipleSeparatedBlockquotes() {
|
||||||
let htmlString = """
|
let htmlString = """
|
||||||
First
|
First
|
||||||
<blockquote>blockquote with a <a href=\"https://www.matrix.org/\">link</a> in it</blockquote>
|
<blockquote>blockquote with a <a href=\"https://www.matrix.org/\">link</a> in it</blockquote>
|
||||||
@@ -347,7 +347,7 @@ class AttributedStringBuilderTests: XCTestCase {
|
|||||||
<blockquote>blockquote with a <a href=\"https://www.matrix.org/\">link</a> in it</blockquote>
|
<blockquote>blockquote with a <a href=\"https://www.matrix.org/\">link</a> in it</blockquote>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
guard let attributedString = await attributedStringBuilder.fromHTML(htmlString) else {
|
guard let attributedString = attributedStringBuilder.fromHTML(htmlString) else {
|
||||||
XCTFail("Could not build the attributed string")
|
XCTFail("Could not build the attributed string")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
Rebuilt the timeline scrolling behavior on top of a more SwiftUI centric approach
|
||||||
+1
-1
@@ -35,7 +35,7 @@ include:
|
|||||||
packages:
|
packages:
|
||||||
MatrixRustSDK:
|
MatrixRustSDK:
|
||||||
url: https://github.com/matrix-org/matrix-rust-components-swift
|
url: https://github.com/matrix-org/matrix-rust-components-swift
|
||||||
exactVersion: 1.0.15-alpha
|
exactVersion: 1.0.16-alpha
|
||||||
# path: ../matrix-rust-components-swift
|
# path: ../matrix-rust-components-swift
|
||||||
DesignKit:
|
DesignKit:
|
||||||
path: ./
|
path: ./
|
||||||
|
|||||||
Reference in New Issue
Block a user