mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 13:37:11 +00:00
Rageshake Service (#70)
This commit is contained in:
parent
1e7243fcf9
commit
fa0721b160
@ -8,15 +8,19 @@
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
0033481EE363E4914295F188 /* LocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C070FD43DC6BF4E50217965A /* LocalizationTests.swift */; };
|
||||
00AC53151BA23A90FAAE9FBF /* BugReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D607F47FDEF16CC63684BE0 /* BugReport.swift */; };
|
||||
00F3059B1E0CFCA019710C3E /* BugReportModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B516212D9FE785DDD5E490D1 /* BugReportModels.swift */; };
|
||||
01CB8ACFA5E143E89C168CA8 /* TimelineItemContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43AF03660F5FD4FFFA7F1CE /* TimelineItemContextMenu.swift */; };
|
||||
01F4A40C1EDCEC8DC4EC9CFA /* WeakDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 109C0201D8CB3F947340DC80 /* WeakDictionary.swift */; };
|
||||
02D8DF8EB7537EB4E9019DDB /* EventBasedTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218AB05B4E3889731959C5F1 /* EventBasedTimelineItemProtocol.swift */; };
|
||||
03B8FEA668A5B76A93113BB1 /* MemberDetailProviderManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C2ABC1A9B62BDB3D216E7FD /* MemberDetailProviderManager.swift */; };
|
||||
03CB204C52F18E24A5C3D219 /* UITestsAppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967873B9E11828B67F64C89A /* UITestsAppCoordinator.swift */; };
|
||||
05776B005C57E92582F0CF08 /* BuildSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F87116470221880017CF522 /* BuildSettings.swift */; };
|
||||
059173B3C77056C406906B6D /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = D4DA544B2520BFA65D6DB4BB /* target.yml */; };
|
||||
0602FA07557F580086782A9E /* UserIndicatorPresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FA072E995316CD18BC29313 /* UserIndicatorPresentationContext.swift */; };
|
||||
066A1E9B94723EE9F3038044 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EBB5D698CE9A25BB553A2D /* Strings.swift */; };
|
||||
072BA9DBA932374CCA300125 /* MessageComposerTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE6C10032A77AE7DC5AA4C50 /* MessageComposerTextField.swift */; };
|
||||
0B1F80C2BF7D223159FBA82C /* ImageAnonymizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6045E825AE900A92D61FEFF0 /* ImageAnonymizerTests.swift */; };
|
||||
0E8C480700870BB34A2A360F /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 78A5A8DE1E2B09C978C7F3B0 /* KeychainAccess */; };
|
||||
0EA6537A07E2DC882AEA5962 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 187853A7E643995EE49FAD43 /* Localizable.stringsdict */; };
|
||||
0EE5EBA18BA1FE10254BB489 /* UIFont+AttributedStringBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = E8CA187FE656EE5A3F6C7DE5 /* UIFont+AttributedStringBuilder.m */; };
|
||||
@ -25,21 +29,27 @@
|
||||
1281625B25371BE53D36CB3A /* SeparatorRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1ED7E89865201EE7D53E6DA /* SeparatorRoomTimelineItem.swift */; };
|
||||
12F70C493FB69F4D7E9A37EA /* NavigationRouterStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29EBCBFEC6FD0941749404D /* NavigationRouterStore.swift */; };
|
||||
13C77FDF17C4C6627CFFC205 /* RoomTimelineItemFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D25A35764C7B3DB78954AB5 /* RoomTimelineItemFactoryProtocol.swift */; };
|
||||
149D1942DC005D0485FB8D93 /* LoggingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DC1943ADE6A62ED5129D7C8 /* LoggingTests.swift */; };
|
||||
15D1F9C415D9C921643BA82E /* UserIndicatorRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61B73D5E21F524A9BE44448D /* UserIndicatorRequest.swift */; };
|
||||
172E6E9A612ADCF10A62CF13 /* BugReportServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A68BCE6438873D2661D93D0 /* BugReportServiceProtocol.swift */; };
|
||||
17CC4FB64F3A670F43ECBE5F /* UITestsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCA431E6EDD71F7067B5F9E7 /* UITestsRootView.swift */; };
|
||||
187E18F21EF4DA244E436E58 /* BugReportViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28959C7DB36C7688A01D4045 /* BugReportViewModelProtocol.swift */; };
|
||||
1950A80CD198BED283DFC2CE /* ClientProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2958E6D247AE2516BEEE8 /* ClientProxy.swift */; };
|
||||
1999ECC6777752A2616775CF /* MemberDetailsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A152791A2F56BD193BFE986 /* MemberDetailsProvider.swift */; };
|
||||
1A70A2199394B5EC660934A5 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = A678E40E917620059695F067 /* MatrixRustSDK */; };
|
||||
1AE4AEA0FA8DEF52671832E0 /* RoomTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */; };
|
||||
1F3232BD368DF430AB433907 /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 531CE4334AC5CA8DFF6AEB84 /* DTCoreText */; };
|
||||
206F0DBAB6AF042CA1FF2C0D /* SettingsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D487C1185D658F8B15B8F55 /* SettingsViewModelTests.swift */; };
|
||||
224A55EEAEECF5336B14A4A5 /* EmoteRoomMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE2DF459F1737A594667CC46 /* EmoteRoomMessage.swift */; };
|
||||
22DADD537401E79D66132134 /* NavigationRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4173A48FD8542CD4AD3645C /* NavigationRouter.swift */; };
|
||||
24906A1E82D0046655958536 /* MessageComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18CF12478983A5EB390FB26 /* MessageComposer.swift */; };
|
||||
24BDDD09A90B8BFE3793F3AA /* ClientProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6033779EB37259F27F938937 /* ClientProxyProtocol.swift */; };
|
||||
277D2531C70F207A2F9F5906 /* KeychainControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 956BDA4AE16429AD015661A8 /* KeychainControllerProtocol.swift */; };
|
||||
2797C9D9BA642370F1C85D78 /* Untranslated.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = F75DF9500D69A3AAF8339E69 /* Untranslated.stringsdict */; };
|
||||
28410F3DE89C2C44E4F75C92 /* MockBugReportService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0E7BF8F7BB1021F889C6483 /* MockBugReportService.swift */; };
|
||||
297CD0A27C87B0C50FF192EE /* RoomTimelineViewFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE384418EB1FEDFA62C9CD0 /* RoomTimelineViewFactoryProtocol.swift */; };
|
||||
29AEE68A604940180AB9EBFF /* MockRoomSummary.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BDAC8895AB2B77B47703AE /* MockRoomSummary.swift */; };
|
||||
29EE1791E0AFA1ABB7F23D2F /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 67E7A6F388D3BF85767609D9 /* Sentry */; };
|
||||
2BA59D0AEFB4B82A2EC2A326 /* SwiftyBeaver in Frameworks */ = {isa = PBXBuildFile; productRef = A981A4CA233FB5C13B9CA690 /* SwiftyBeaver */; };
|
||||
2BAA5B222856068158D0B3C6 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = B1E8B697DF78FE7F61FC6CA4 /* MatrixRustSDK */; };
|
||||
2C0CE61E5DC177938618E0B1 /* RootRouterType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90733775209F4D4D366A268F /* RootRouterType.swift */; };
|
||||
@ -53,19 +63,26 @@
|
||||
33912D1B9264D897033E0681 /* LoginScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0779B2CC9A687CBB82A5B920 /* LoginScreenViewModelProtocol.swift */; };
|
||||
33B4E59D408AE6E02323EE41 /* NoticeRoomMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDA364DFFC3AC71C4771251 /* NoticeRoomMessage.swift */; };
|
||||
344AF4CBB6D8786214878642 /* NavigationRouterStoreProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B9D5F812E5AD6DC786DBC9B /* NavigationRouterStoreProtocol.swift */; };
|
||||
34966D4C1C2C6D37FE3F7F50 /* SettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DD2D50A7EAA4FC78417730E /* SettingsCoordinator.swift */; };
|
||||
352C439BE0F75E101EF11FB1 /* RoomScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2886615BEBAE33A0AA4D5F8 /* RoomScreenModels.swift */; };
|
||||
35E975CFDA60E05362A7CF79 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 1222DB76B917EB8A55365BA5 /* target.yml */; };
|
||||
368C8758FCD079E6AAA18C2C /* NoticeRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */; };
|
||||
3772354754450F2B54107E17 /* TemplateSimpleScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4EDB32B97910AAAFE632B2 /* TemplateSimpleScreenViewModelProtocol.swift */; };
|
||||
38546A6010A2CF240EC9AF73 /* BindableState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EA1D2CBAEA5D0BD00B90D1B /* BindableState.swift */; };
|
||||
39AE84C8E5F2FE9D2DC7775C /* EventBasedTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56008790A9C4479A6B31FDF4 /* EventBasedTimelineView.swift */; };
|
||||
3B770CB4DED51CC362C66D47 /* SettingsModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4990FDBDA96B88E214F92F48 /* SettingsModels.swift */; };
|
||||
3C549A0BF39F8A854D45D9FD /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 7731767AE437BA3BD2CC14A8 /* Sentry */; };
|
||||
3D325A1147F6281C57BFCDF6 /* EventBrief.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4411C0DA0087A1CB143E96FA /* EventBrief.swift */; };
|
||||
3DA57CA0D609A6B37CA1DC2F /* BugReportService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DC38E64A5ED3FDB201029A /* BugReportService.swift */; };
|
||||
3ED2725734568F6B8CC87544 /* AttributedStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5C6FBF97B6EED3D4FA5EFF /* AttributedStringBuilder.swift */; };
|
||||
418B4AEFD03DC7A6D2C9D5C8 /* EventBriefFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36322DD0D4E29D31B0945ADC /* EventBriefFactory.swift */; };
|
||||
438FB9BC535BC95948AA5F34 /* SettingsViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2F9D5C39A4494D19F33E38 /* SettingsViewModelProtocol.swift */; };
|
||||
462813B93C39DF93B1249403 /* RoundedToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFABDF2E19D349DAAAC18C65 /* RoundedToastView.swift */; };
|
||||
46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */; };
|
||||
4669804D0369FBED4E8625D1 /* ToastViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4470B8CB654B097D807AA713 /* ToastViewPresenter.swift */; };
|
||||
490E606044B18985055FF690 /* SettingsUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3E29F98CF0E960689A410E3 /* SettingsUITests.swift */; };
|
||||
49F2E7DD8CAACE09CEECE3E6 /* SeparatorRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6390A6DC140CA3D6865A66FF /* SeparatorRoomTimelineView.swift */; };
|
||||
4A2E0DBB63919AC8309B6D40 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A191D3FDB995309C7E2DE7D /* SettingsViewModel.swift */; };
|
||||
4B8A2C45FF906ADBB1F5C3B4 /* UserIndicatorQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04E1273CC3BC3E471AF87BE5 /* UserIndicatorQueueTests.swift */; };
|
||||
4D23C56053013437C35E511E /* ActivityIndicatorPresenterType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85C2318DF4C0E601EEE31F84 /* ActivityIndicatorPresenterType.swift */; };
|
||||
4D970CB606276717B43E2332 /* TimelineItemList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 804F9B0FABE093C7284CD09B /* TimelineItemList.swift */; };
|
||||
@ -75,6 +92,7 @@
|
||||
500CB65ED116B81DA52FDAEE /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 874A1842477895F199567BD7 /* TimelineView.swift */; };
|
||||
50391038BC50C8ED9A4D88A0 /* WeakDictionaryReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B23371BC8BF6164D9F6A05 /* WeakDictionaryReference.swift */; };
|
||||
51DB67C5B5BC68B0A6FF54D4 /* MockRoomProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACBDC1D28EFB7789EB467E0 /* MockRoomProxy.swift */; };
|
||||
524C9C31EF8D58C2249F8A10 /* sample_screenshot.png in Resources */ = {isa = PBXBuildFile; fileRef = 9414DCADBDF9D6C4B806F61E /* sample_screenshot.png */; };
|
||||
53B9C2240C2F5533246EE230 /* RectangleToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6235E1CE00A6D989D7DB6D47 /* RectangleToastView.swift */; };
|
||||
56F0A22972A3BB519DA2261C /* HomeScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5530B2212862FA4BEFF2D /* HomeScreenViewModelProtocol.swift */; };
|
||||
59C41313AED7566C3AC51163 /* RoomSummary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A953B6C0C431DBF4DD00B4 /* RoomSummary.swift */; };
|
||||
@ -91,10 +109,13 @@
|
||||
6832733838C57A7D3FE8FEB5 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 04C28663564E008DB32B5972 /* Introspect */; };
|
||||
68AC3C84E2B438036B174E30 /* EmoteRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */; };
|
||||
6A367F3D7A437A79B7D9A31C /* FullscreenLoadingViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4112D04077F6709C5CA0A13E /* FullscreenLoadingViewPresenter.swift */; };
|
||||
6C72F66DA26A0956E9A9077A /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BEB3259B2208E5AE5BB3F65 /* Settings.swift */; };
|
||||
6EA61FCA55D950BDE326A1A7 /* ImageAnonymizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12A626D74BBE9F4A60763B45 /* ImageAnonymizer.swift */; };
|
||||
6F2AB43A1EFAD8A97AF41A15 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 5986E300FC849DEAB2EE7AEB /* Introspect */; };
|
||||
7002C55A4C917F3715765127 /* MediaProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C888BCD78E2A55DCE364F160 /* MediaProviderProtocol.swift */; };
|
||||
7405B4824D45BA7C3D943E76 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D0CBC76C80E04345E11F2DB /* Application.swift */; };
|
||||
7434A7F02D587A920B376A9A /* LoginScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A43964330459965AF048A8C /* LoginScreenViewModelTests.swift */; };
|
||||
7756C4E90CABE6F14F7920A0 /* BugReportUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6FEA87EA3752203065ECE27 /* BugReportUITests.swift */; };
|
||||
77D7DAA41AAB36800C1F2E2D /* RoomTimelineProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095AED4CF56DFF3EB7BB84C8 /* RoomTimelineProviderProtocol.swift */; };
|
||||
77E192BA943B90F9F310CA23 /* WeakDictionaryKeyReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FFCC48E7F701B6C24484593 /* WeakDictionaryKeyReference.swift */; };
|
||||
78B71D53C1FC55FB7A9B75F0 /* RoomTimelineController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24B0C97D2F560BCB72BE73B1 /* RoomTimelineController.swift */; };
|
||||
@ -108,8 +129,10 @@
|
||||
7DE5EB4CB2401C672257283C /* WeakKeyDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12969CEC0051BC750DA5068 /* WeakKeyDictionary.swift */; };
|
||||
7E1EDBA3934E6C29E5BD045B /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D77DD2DA5DC8654F2A80FF1D /* Bundle.swift */; };
|
||||
7F19E97E7985F518C9018B83 /* RootRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF47564C584F614B7287F3EB /* RootRouter.swift */; };
|
||||
7F61F9ACD5EC9E845EF3EFBF /* BugReportServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFFD3200F9960D4996159F10 /* BugReportServiceTests.swift */; };
|
||||
7FA4227B2BAAA71560252866 /* UserIndicatorDismissal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D1532B5D9FB0C8461A1453 /* UserIndicatorDismissal.swift */; };
|
||||
80E04BE80A89A78FBB4863BB /* UserIndicatorViewPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 193FB285430D3956B6E61E4D /* UserIndicatorViewPresentable.swift */; };
|
||||
86C2E93920FD15AD17E193A9 /* BugReportViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E532D95330139D118A9BF88 /* BugReportViewModel.swift */; };
|
||||
8775F46AE3234A5A5688C19D /* UserIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD73FAAA4A76CE4A1F3014D9 /* UserIndicator.swift */; };
|
||||
8810A2A30A68252EBB54EE05 /* HomeScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71BC7CA1BC1041E93077BBA1 /* HomeScreenModels.swift */; };
|
||||
8AB8ED1051216546CB35FA0E /* UserSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E5E9C044BEB7C70B1378E91 /* UserSession.swift */; };
|
||||
@ -132,6 +155,9 @@
|
||||
9D2E03DB175A6AB14589076D /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 0DD568A494247444A4B56031 /* Kingfisher */; };
|
||||
9DC5FB22B8F86C3B51E907C1 /* HomeScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D6E4C37E9F0E53D3DF951AC /* HomeScreenUITests.swift */; };
|
||||
A0A0D2A9564BDA3FDE2E360F /* FormattedBodyText.swift in Sources */ = {isa = PBXBuildFile; fileRef = F73FF1A33198F5FAE9D34B1F /* FormattedBodyText.swift */; };
|
||||
A32517FB1CA0BBCE2BC75249 /* BugReportCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6C07DA7D3FF193F7419F55 /* BugReportCoordinator.swift */; };
|
||||
A4E885358D7DD5A072A06824 /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 1BCD21310B997A6837B854D6 /* GZIP */; };
|
||||
A5C8F013ED9FB8AA6FEE18A7 /* InfoPlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A901D95158B02CA96C79C7F /* InfoPlist.swift */; };
|
||||
A5EC21A071F58FC1229C20D0 /* MemberDetailsProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09747989908EC5E4AA29F844 /* MemberDetailsProviderProtocol.swift */; };
|
||||
A636D4900E0D98ED91536482 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF3EDF23226895776553F04A /* AppCoordinator.swift */; };
|
||||
A69B7B421C28C6CDEBBD0613 /* AuthenticationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AD575D36B9F6D1D543305D1 /* AuthenticationCoordinator.swift */; };
|
||||
@ -140,13 +166,15 @@
|
||||
A851635B3255C6DC07034A12 /* RoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8108C8F0ACF6A7EB72D0117 /* RoomScreenCoordinator.swift */; };
|
||||
A941EAD7F407F2ED6DA54A31 /* LoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA97D630B74B0616C1468CBD /* LoginScreen.swift */; };
|
||||
AB34401E4E1CAD5D2EC3072B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9760103CF316DF68698BCFE6 /* LaunchScreen.storyboard */; };
|
||||
B0887A7B5AFEC88981626389 /* MXLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64839516BD56D1C81D84C5E0 /* MXLog.swift */; };
|
||||
B0EDAF55877DE19B67837C22 /* TemplateSimpleScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1C29670CEC77346F31EE94C /* TemplateSimpleScreenModels.swift */; };
|
||||
B245583C63F8F90357B87FAE /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 3853B78FB8531B83936C5DA6 /* SwiftState */; };
|
||||
B3FDB1D9CF40777695DBBD1D /* AppCoordinatorStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9AB74614131D6706894E0C /* AppCoordinatorStateMachine.swift */; };
|
||||
B4AAB3257A83B73F53FB2689 /* StateStoreViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F3DFE5B444F131648066F05 /* StateStoreViewModel.swift */; };
|
||||
B6DA66EFC13A90846B625836 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 91DE43B8815918E590912DDA /* InfoPlist.strings */; };
|
||||
B6DF6B6FA8734B70F9BF261E /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5272BC4A60B6AD7553BACA1 /* BlurHashDecode.swift */; };
|
||||
B80C4FABB5529DF12436FFDA /* AppIcon.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 16DC8C5B2991724903F1FA6A /* AppIcon.pdf */; };
|
||||
B94368839BDB69172E28E245 /* MXLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111B698739E3410E2CDB7144 /* MXLog.swift */; };
|
||||
BCC3EDB7AD0902797CB4BBC2 /* MXLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = EF188681D6B6068CFAEAFC3F /* MXLogger.m */; };
|
||||
BE3237142FA6E1A13C0E7D11 /* RoomSummaryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C21ECC295F4DE8DAA86D62AC /* RoomSummaryProtocol.swift */; };
|
||||
BEEC06EFD30BFCA02F0FD559 /* UserIndicatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8EA85D4F10D7445BB6368A /* UserIndicatorTests.swift */; };
|
||||
BF35062D06888FA80BD139FF /* Presentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB7F9D6FC121204D59E18DF /* Presentable.swift */; };
|
||||
@ -154,14 +182,18 @@
|
||||
C1156BBE4F977AEEE1E80C48 /* TemplateSimpleScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2869CFFF6CD2A642AB4B743 /* TemplateSimpleScreenCoordinator.swift */; };
|
||||
C4F69156C31A447FEFF2A47C /* DTHTMLElement+AttributedStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E508AB0EDEE017FF4F6F8D1 /* DTHTMLElement+AttributedStringBuilder.swift */; };
|
||||
C55A44C99F64A479ABA85B46 /* RoomScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5221DFDF809142A2D6AC82B9 /* RoomScreen.swift */; };
|
||||
C7CFDB4929DDD9A3B5BA085D /* BugReportViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB7ED3A898B07976F3AA90F /* BugReportViewModelTests.swift */; };
|
||||
CB137BFB3E083C33E398A6CB /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 997C7385E1A07E061D7E2100 /* GZIP */; };
|
||||
CB326BAB54E9B68658909E36 /* Benchmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EAD710A2C16EFF7C3EA16F /* Benchmark.swift */; };
|
||||
CB498F4E27AA0545DCEF0F6F /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 50009897F60FAE7D63EF5E5B /* Kingfisher */; };
|
||||
CC736DA1AA8F8B9FD8785009 /* ScreenshotDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5C4AF6E3885730CD560311C /* ScreenshotDetector.swift */; };
|
||||
CE1694C7BB93C3311524EF28 /* Untranslated.strings in Resources */ = {isa = PBXBuildFile; fileRef = D2F7194F440375338F8E2487 /* Untranslated.strings */; };
|
||||
CF82143AA4A4F7BD11D22946 /* RoomTimelineViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACB6C5E4950B6C9842F35A38 /* RoomTimelineViewProvider.swift */; };
|
||||
D013E70C8E28E43497820444 /* TextRoomMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4110685D9CA159F3FD2D6BA1 /* TextRoomMessage.swift */; };
|
||||
D0619D2E6B9C511190FBEB95 /* RoomMessageProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607974D08BD2AF83725D817A /* RoomMessageProtocol.swift */; };
|
||||
D5EA4C6C80579279770D5804 /* ImageRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A45283CF1DB96E583BECA6 /* ImageRoomTimelineView.swift */; };
|
||||
D826154612415D2A3BB6EBF3 /* ListTableViewAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E854E7CF531DAC5CBEBDC75 /* ListTableViewAdapter.swift */; };
|
||||
D8CFF02C2730EE5BC4F17ABF /* ElementToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0960A7F5C1B0B6679BDF26F9 /* ElementToggleStyle.swift */; };
|
||||
D94F664677C380A3CAB8D7F6 /* ActivityIndicatorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68706A66BBA04268F7747A2F /* ActivityIndicatorPresenter.swift */; };
|
||||
DCB781BD227CA958809AFADF /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95CC95CD75B688E946438165 /* Coordinator.swift */; };
|
||||
DDB80FD2753FEAAE43CC2AAE /* ImageRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A63815AD6A5C306453342F2 /* ImageRoomTimelineItem.swift */; };
|
||||
@ -170,6 +202,7 @@
|
||||
E81EEC1675F2371D12A880A3 /* MockRoomTimelineController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61ADFB893DEF81E58DF3FAB9 /* MockRoomTimelineController.swift */; };
|
||||
E9CEAF2C38E4E00459B811D9 /* LoginScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2082B5226B2A3A4D0798B6 /* LoginScreenModels.swift */; };
|
||||
EA1E7949533E19C6D862680A /* MediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 885D8C42DD17625B5261BEFF /* MediaProvider.swift */; };
|
||||
EA31DD9043B91ECB8E45A9A6 /* ScreenshotDetectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03C9D319676F3C0DC6B0203 /* ScreenshotDetectorTests.swift */; };
|
||||
ED4F663C783E9A8C0E80B983 /* TemplateSimpleScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47543EB19F3DCF308751F53C /* TemplateSimpleScreenViewModel.swift */; };
|
||||
EE8491AD81F47DF3C192497B /* DecorationTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 184CF8C196BE143AE226628D /* DecorationTimelineItemProtocol.swift */; };
|
||||
F03E16ED043C62FED5A07AE0 /* MatrixEntitityRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B81C8227BBEA95CCE86037 /* MatrixEntitityRegex.swift */; };
|
||||
@ -211,7 +244,9 @@
|
||||
08F64963396A6A23538EFCEC /* is */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = is; path = is.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
0950733DD4BA83EEE752E259 /* PlaceholderAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderAvatarImage.swift; sourceTree = "<group>"; };
|
||||
095AED4CF56DFF3EB7BB84C8 /* RoomTimelineProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineProviderProtocol.swift; sourceTree = "<group>"; };
|
||||
0960A7F5C1B0B6679BDF26F9 /* ElementToggleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementToggleStyle.swift; sourceTree = "<group>"; };
|
||||
09747989908EC5E4AA29F844 /* MemberDetailsProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberDetailsProviderProtocol.swift; sourceTree = "<group>"; };
|
||||
0A191D3FDB995309C7E2DE7D /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = "<group>"; };
|
||||
0AB7A0C06CB527A1095DEB33 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = da; path = da.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
0AD575D36B9F6D1D543305D1 /* AuthenticationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationCoordinator.swift; sourceTree = "<group>"; };
|
||||
0C13A92C1E9C79F055B8133D /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ar; path = ar.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
@ -222,9 +257,12 @@
|
||||
105B2A8426404EF66F00CFDB /* RoomTimelineItemFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemFactory.swift; sourceTree = "<group>"; };
|
||||
105D16E7DB0CCE9526612BDD /* bn-IN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "bn-IN"; path = "bn-IN.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
109C0201D8CB3F947340DC80 /* WeakDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakDictionary.swift; sourceTree = "<group>"; };
|
||||
111B698739E3410E2CDB7144 /* MXLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXLog.swift; sourceTree = "<group>"; };
|
||||
113356152C099951A6D17D85 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
1215A4FC53D2319E81AE8970 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
1222DB76B917EB8A55365BA5 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
|
||||
124D85E85505B6B81845235F /* fy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fy; path = fy.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
12A626D74BBE9F4A60763B45 /* ImageAnonymizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageAnonymizer.swift; sourceTree = "<group>"; };
|
||||
13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
16DC8C5B2991724903F1FA6A /* AppIcon.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = AppIcon.pdf; sourceTree = "<group>"; };
|
||||
184CF8C196BE143AE226628D /* DecorationTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecorationTimelineItemProtocol.swift; sourceTree = "<group>"; };
|
||||
@ -245,10 +283,12 @@
|
||||
24F5530B2212862FA4BEFF2D /* HomeScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
2583416C8974272ADBADDBE1 /* zh-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-TW"; path = "zh-TW.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||
26C4D226FCD20BAC53F1E092 /* ml */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ml; path = ml.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
28959C7DB36C7688A01D4045 /* BugReportViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
28EA8BE9EEDBD17555141C7E /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = el; path = el.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
29A953B6C0C431DBF4DD00B4 /* RoomSummary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummary.swift; sourceTree = "<group>"; };
|
||||
2A5C6FBF97B6EED3D4FA5EFF /* AttributedStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilder.swift; sourceTree = "<group>"; };
|
||||
2AE83A3DD63BCFBB956FE5CB /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nl; path = nl.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
2BEB3259B2208E5AE5BB3F65 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
|
||||
2CF9FE7E0CF9F40D1509E63A /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = bg; path = bg.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
31D6764D6976D235926FE5FC /* HomeScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
33E49C5C6F802B4D94CA78D1 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
@ -261,8 +301,12 @@
|
||||
3ACBDC1D28EFB7789EB467E0 /* MockRoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomProxy.swift; sourceTree = "<group>"; };
|
||||
3B5B535DA49C54523FF7A412 /* nn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nn; path = nn.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
3CDF9E55650D6035D6536538 /* nb-NO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "nb-NO"; path = "nb-NO.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||
3D487C1185D658F8B15B8F55 /* SettingsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModelTests.swift; sourceTree = "<group>"; };
|
||||
3D4DD336905C72F95EAF34B7 /* ElementX-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ElementX-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
3DC1943ADE6A62ED5129D7C8 /* LoggingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingTests.swift; sourceTree = "<group>"; };
|
||||
3DD2D50A7EAA4FC78417730E /* SettingsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCoordinator.swift; sourceTree = "<group>"; };
|
||||
3DD6E7C1D8B53F47789778CD /* fr-CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fr-CA"; path = "fr-CA.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
3F87116470221880017CF522 /* BuildSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildSettings.swift; sourceTree = "<group>"; };
|
||||
3FAA6438B00FDB130F404E31 /* UserIndicatorStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorStore.swift; sourceTree = "<group>"; };
|
||||
3FEE631F3A4AFDC6652DD9DA /* RoomTimelineViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineViewFactory.swift; sourceTree = "<group>"; };
|
||||
4110685D9CA159F3FD2D6BA1 /* TextRoomMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomMessage.swift; sourceTree = "<group>"; };
|
||||
@ -281,6 +325,7 @@
|
||||
48971F1FFD7FC5C466889FC7 /* SplashViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SplashViewController.xib; sourceTree = "<group>"; };
|
||||
48CE6BF18E542B32FA52CE06 /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fa; path = fa.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
49193CB0C248D621A96FB2AA /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
4990FDBDA96B88E214F92F48 /* SettingsModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsModels.swift; sourceTree = "<group>"; };
|
||||
49D2C8E66E83EA578A7F318A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
49EAD710A2C16EFF7C3EA16F /* Benchmark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Benchmark.swift; sourceTree = "<group>"; };
|
||||
4B40B7F6FCCE2D8C242492D9 /* ga */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ga; path = ga.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
@ -303,8 +348,10 @@
|
||||
56008790A9C4479A6B31FDF4 /* EventBasedTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBasedTimelineView.swift; sourceTree = "<group>"; };
|
||||
56F01DD1BBD4450E18115916 /* LabelledActivityIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelledActivityIndicatorView.swift; sourceTree = "<group>"; };
|
||||
5773C86AF04AEF26515AD00C /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
5872785B9C7934940146BFBA /* MXLogger.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXLogger.h; sourceTree = "<group>"; };
|
||||
5A43964330459965AF048A8C /* LoginScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||
5A9AB74614131D6706894E0C /* AppCoordinatorStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinatorStateMachine.swift; sourceTree = "<group>"; };
|
||||
5B2F9D5C39A4494D19F33E38 /* SettingsViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
5B9D5F812E5AD6DC786DBC9B /* NavigationRouterStoreProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRouterStoreProtocol.swift; sourceTree = "<group>"; };
|
||||
5CB7F9D6FC121204D59E18DF /* Presentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Presentable.swift; sourceTree = "<group>"; };
|
||||
5D26A086A8278D39B5756D6F /* project.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = project.yml; sourceTree = "<group>"; };
|
||||
@ -314,6 +361,7 @@
|
||||
5F77E8010D41AA3F5F9A1FCA /* NavigationModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationModule.swift; sourceTree = "<group>"; };
|
||||
5FF214969B25BFCBF87B908B /* bn-BD */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "bn-BD"; path = "bn-BD.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||
6033779EB37259F27F938937 /* ClientProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientProxyProtocol.swift; sourceTree = "<group>"; };
|
||||
6045E825AE900A92D61FEFF0 /* ImageAnonymizerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageAnonymizerTests.swift; sourceTree = "<group>"; };
|
||||
607974D08BD2AF83725D817A /* RoomMessageProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMessageProtocol.swift; sourceTree = "<group>"; };
|
||||
616197D81103330BF2ADD559 /* gl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gl; path = gl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
61ADFB893DEF81E58DF3FAB9 /* MockRoomTimelineController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomTimelineController.swift; sourceTree = "<group>"; };
|
||||
@ -322,7 +370,6 @@
|
||||
6235E1CE00A6D989D7DB6D47 /* RectangleToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RectangleToastView.swift; sourceTree = "<group>"; };
|
||||
624244C398804ADC885239AA /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
6390A6DC140CA3D6865A66FF /* SeparatorRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||
64839516BD56D1C81D84C5E0 /* MXLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXLog.swift; sourceTree = "<group>"; };
|
||||
64B23371BC8BF6164D9F6A05 /* WeakDictionaryReference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakDictionaryReference.swift; sourceTree = "<group>"; };
|
||||
653610CB5F9776EAAAB98155 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fr; path = fr.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
6654859746B0BE9611459391 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = cs; path = cs.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
@ -331,6 +378,8 @@
|
||||
68706A66BBA04268F7747A2F /* ActivityIndicatorPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorPresenter.swift; sourceTree = "<group>"; };
|
||||
6920A4869821BF72FFC58842 /* MockMediaProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockMediaProvider.swift; sourceTree = "<group>"; };
|
||||
6A152791A2F56BD193BFE986 /* MemberDetailsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberDetailsProvider.swift; sourceTree = "<group>"; };
|
||||
6A901D95158B02CA96C79C7F /* InfoPlist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoPlist.swift; sourceTree = "<group>"; };
|
||||
6D607F47FDEF16CC63684BE0 /* BugReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReport.swift; sourceTree = "<group>"; };
|
||||
6DB53055CB130F0651C70763 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
6E5E9C044BEB7C70B1378E91 /* UserSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSession.swift; sourceTree = "<group>"; };
|
||||
6EA1D2CBAEA5D0BD00B90D1B /* BindableState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BindableState.swift; sourceTree = "<group>"; };
|
||||
@ -343,6 +392,7 @@
|
||||
72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilderProtocol.swift; sourceTree = "<group>"; };
|
||||
752DEC02D93AFF46BC13313A /* NavigationRouterType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRouterType.swift; sourceTree = "<group>"; };
|
||||
799A3A11C434296ED28F87C8 /* iw */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = iw; path = iw.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
7AB7ED3A898B07976F3AA90F /* BugReportViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportViewModelTests.swift; sourceTree = "<group>"; };
|
||||
7B04BD3874D736127A8156B8 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
7BDF6A69C2BB99535193E554 /* si */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = si; path = si.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
7D0CBC76C80E04345E11F2DB /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
|
||||
@ -350,6 +400,7 @@
|
||||
7DA80FADE73CDF01E96F5B8E /* sq */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sq; path = sq.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
7DDBF99755A9008CF8C8499E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
7E154FEA1E6FE964D3DF7859 /* fy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fy; path = fy.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
7E532D95330139D118A9BF88 /* BugReportViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportViewModel.swift; sourceTree = "<group>"; };
|
||||
7FFCC48E7F701B6C24484593 /* WeakDictionaryKeyReference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakDictionaryKeyReference.swift; sourceTree = "<group>"; };
|
||||
804F9B0FABE093C7284CD09B /* TimelineItemList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemList.swift; sourceTree = "<group>"; };
|
||||
8140010A796DB2C7977B6643 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
@ -375,6 +426,7 @@
|
||||
938BD1FCD9E6FF3FCFA7AB4C /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-CN"; path = "zh-CN.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||
93B21E72926FACB13A186689 /* ml */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ml; path = ml.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||
9414DCADBDF9D6C4B806F61E /* sample_screenshot.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = sample_screenshot.png; sourceTree = "<group>"; };
|
||||
956BDA4AE16429AD015661A8 /* KeychainControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainControllerProtocol.swift; sourceTree = "<group>"; };
|
||||
95CC95CD75B688E946438165 /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = "<group>"; };
|
||||
967873B9E11828B67F64C89A /* UITestsAppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsAppCoordinator.swift; sourceTree = "<group>"; };
|
||||
@ -383,6 +435,7 @@
|
||||
97F893DBB5F88D746C6DCDE5 /* ku */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ku; path = ku.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
997783054A2E95F9E624217E /* kaa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = kaa; path = kaa.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
99DE232F24EAD72A3DF7EF1A /* kab */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = kab; path = kab.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
9A68BCE6438873D2661D93D0 /* BugReportServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportServiceProtocol.swift; sourceTree = "<group>"; };
|
||||
9C5E81214D27A6B898FC397D /* ElementX.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ElementX.entitlements; sourceTree = "<group>"; };
|
||||
9CE3C90E487B255B735D73C8 /* RoomScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
A00C7A331B72C0F05C00392F /* RoomScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
@ -401,6 +454,7 @@
|
||||
ACA11F7F50A4A3887A18CA5A /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
ACB6C5E4950B6C9842F35A38 /* RoomTimelineViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineViewProvider.swift; sourceTree = "<group>"; };
|
||||
AD378D580A41E42560C60E9C /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
AD6C07DA7D3FF193F7419F55 /* BugReportCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportCoordinator.swift; sourceTree = "<group>"; };
|
||||
ADCB8A232D3A8FB3E16A7303 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
AE225C66978648AA4AF37B45 /* te */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = te; path = te.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
AE5DDBEBBA17973ED4638823 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = de; path = de.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
@ -414,6 +468,7 @@
|
||||
B1D1532B5D9FB0C8461A1453 /* UserIndicatorDismissal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorDismissal.swift; sourceTree = "<group>"; };
|
||||
B4173A48FD8542CD4AD3645C /* NavigationRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRouter.swift; sourceTree = "<group>"; };
|
||||
B43AF03660F5FD4FFFA7F1CE /* TimelineItemContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemContextMenu.swift; sourceTree = "<group>"; };
|
||||
B516212D9FE785DDD5E490D1 /* BugReportModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportModels.swift; sourceTree = "<group>"; };
|
||||
B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||
B64F3A3D0DF86ED5A241AB05 /* ActivityIndicatorView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ActivityIndicatorView.xib; sourceTree = "<group>"; };
|
||||
B695D0D12086158BAD1D9859 /* UserIndicatorPresenterSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorPresenterSpy.swift; sourceTree = "<group>"; };
|
||||
@ -434,6 +489,7 @@
|
||||
C21ECC295F4DE8DAA86D62AC /* RoomSummaryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProtocol.swift; sourceTree = "<group>"; };
|
||||
C2886615BEBAE33A0AA4D5F8 /* RoomScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenModels.swift; sourceTree = "<group>"; };
|
||||
C485C186CEC78443DA96BDC8 /* TemplateSimpleScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateSimpleScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||
C6FEA87EA3752203065ECE27 /* BugReportUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportUITests.swift; sourceTree = "<group>"; };
|
||||
C88508B6F7974CFABEC4B261 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
C888BCD78E2A55DCE364F160 /* MediaProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProviderProtocol.swift; sourceTree = "<group>"; };
|
||||
C91A6BC1A54CDB598EE2A81B /* UserIndicatorQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorQueue.swift; sourceTree = "<group>"; };
|
||||
@ -457,6 +513,7 @@
|
||||
D653265D006E708E4E51AD64 /* HomeScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
D67CBAFA48ED0B6FCE74F88F /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
D6D094C15E8DB424F1C6FC94 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
D6DC38E64A5ED3FDB201029A /* BugReportService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportService.swift; sourceTree = "<group>"; };
|
||||
D77DD2DA5DC8654F2A80FF1D /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = "<group>"; };
|
||||
DBFEAC3AC691CBB84983E275 /* ElementXTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementXTests.swift; sourceTree = "<group>"; };
|
||||
DD73FAAA4A76CE4A1F3014D9 /* UserIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicator.swift; sourceTree = "<group>"; };
|
||||
@ -465,6 +522,7 @@
|
||||
E157152B11E347F735C3FD6E /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = tr; path = tr.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
E18CF12478983A5EB390FB26 /* MessageComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageComposer.swift; sourceTree = "<group>"; };
|
||||
E2869CFFF6CD2A642AB4B743 /* TemplateSimpleScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateSimpleScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
E3E29F98CF0E960689A410E3 /* SettingsUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsUITests.swift; sourceTree = "<group>"; };
|
||||
E45C57120F28F8D619150219 /* sr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sr; path = sr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
E5272BC4A60B6AD7553BACA1 /* BlurHashDecode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurHashDecode.swift; sourceTree = "<group>"; };
|
||||
E579A0DA01F488C97B771EF6 /* lv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = lv; path = lv.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
@ -477,11 +535,16 @@
|
||||
ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = "<group>"; };
|
||||
EE8BCD14EFED23459A43FDFF /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
EEE384418EB1FEDFA62C9CD0 /* RoomTimelineViewFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineViewFactoryProtocol.swift; sourceTree = "<group>"; };
|
||||
EF188681D6B6068CFAEAFC3F /* MXLogger.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXLogger.m; sourceTree = "<group>"; };
|
||||
EFFA5FD06AAAC4AF544B594E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
EFFD3200F9960D4996159F10 /* BugReportServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportServiceTests.swift; sourceTree = "<group>"; };
|
||||
F012CB5EE3F2B67359F6CC52 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
|
||||
F03C9D319676F3C0DC6B0203 /* ScreenshotDetectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotDetectorTests.swift; sourceTree = "<group>"; };
|
||||
F0E7BF8F7BB1021F889C6483 /* MockBugReportService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockBugReportService.swift; sourceTree = "<group>"; };
|
||||
F23BA6D4842D53C5AC9B7584 /* nn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nn; path = nn.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
F3BC93D4555571E8B4BC47F9 /* KeychainController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainController.swift; sourceTree = "<group>"; };
|
||||
F506C6ADB1E1DA6638078E11 /* UITests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
F5C4AF6E3885730CD560311C /* ScreenshotDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotDetector.swift; sourceTree = "<group>"; };
|
||||
F6A8C632CEF4600107792899 /* TextRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||
F73FF1A33198F5FAE9D34B1F /* FormattedBodyText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormattedBodyText.swift; sourceTree = "<group>"; };
|
||||
F77C060C2ACC4CB7336A29E7 /* EmoteRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||
@ -505,6 +568,8 @@
|
||||
6832733838C57A7D3FE8FEB5 /* Introspect in Frameworks */,
|
||||
2BA59D0AEFB4B82A2EC2A326 /* SwiftyBeaver in Frameworks */,
|
||||
B245583C63F8F90357B87FAE /* SwiftState in Frameworks */,
|
||||
A4E885358D7DD5A072A06824 /* GZIP in Frameworks */,
|
||||
29EE1791E0AFA1ABB7F23D2F /* Sentry in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -519,6 +584,8 @@
|
||||
6F2AB43A1EFAD8A97AF41A15 /* Introspect in Frameworks */,
|
||||
93BA4A81B6D893271101F9F0 /* SwiftyBeaver in Frameworks */,
|
||||
9AC5F8142413862A9E3A2D98 /* SwiftState in Frameworks */,
|
||||
CB137BFB3E083C33E398A6CB /* GZIP in Frameworks */,
|
||||
3C549A0BF39F8A854D45D9FD /* Sentry in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -538,14 +605,26 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
10578D9852BA78D309A1CBDF /* ViewModel */,
|
||||
328DD5DA1281F758B72006C7 /* Views */,
|
||||
);
|
||||
path = SwiftUI;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
06501F0E978B2D5C92771DC7 /* Logging */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
111B698739E3410E2CDB7144 /* MXLog.swift */,
|
||||
5872785B9C7934940146BFBA /* MXLogger.h */,
|
||||
EF188681D6B6068CFAEAFC3F /* MXLogger.m */,
|
||||
);
|
||||
path = Logging;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0787F81684E503024BD0C051 /* Services */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
AAFDD509929A0CCF8BCE51EB /* Authentication */,
|
||||
0ED3F5C21537519389C07644 /* BugReport */,
|
||||
8039515BAA53B7C3275AC64A /* Client */,
|
||||
79E560F5113ED25D172E550C /* Media */,
|
||||
40E6246F03D1FE377BC5D963 /* Room */,
|
||||
@ -567,6 +646,17 @@
|
||||
path = SupportingFiles;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0ED3F5C21537519389C07644 /* BugReport */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D6DC38E64A5ED3FDB201029A /* BugReportService.swift */,
|
||||
9A68BCE6438873D2661D93D0 /* BugReportServiceProtocol.swift */,
|
||||
F0E7BF8F7BB1021F889C6483 /* MockBugReportService.swift */,
|
||||
F5C4AF6E3885730CD560311C /* ScreenshotDetector.swift */,
|
||||
);
|
||||
path = BugReport;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
10578D9852BA78D309A1CBDF /* ViewModel */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -595,10 +685,19 @@
|
||||
path = Resources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
328DD5DA1281F758B72006C7 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0960A7F5C1B0B6679BDF26F9 /* ElementToggleStyle.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
337015ADFBA3AB96660DB3A6 /* Generated */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
71D52BAA5BADB06E5E8C295D /* Assets.swift */,
|
||||
6A901D95158B02CA96C79C7F /* InfoPlist.swift */,
|
||||
47EBB5D698CE9A25BB553A2D /* Strings.swift */,
|
||||
1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */,
|
||||
);
|
||||
@ -623,6 +722,18 @@
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4009BE2E791C16AC6EE39A7E /* BugReport */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
AD6C07DA7D3FF193F7419F55 /* BugReportCoordinator.swift */,
|
||||
B516212D9FE785DDD5E490D1 /* BugReportModels.swift */,
|
||||
7E532D95330139D118A9BF88 /* BugReportViewModel.swift */,
|
||||
28959C7DB36C7688A01D4045 /* BugReportViewModelProtocol.swift */,
|
||||
58F951CB7BD7F96C37BE5CAD /* View */,
|
||||
);
|
||||
path = BugReport;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
405B00F139AEE3994601B36A = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -650,6 +761,14 @@
|
||||
path = Room;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4541090DFE1A5499BD67BD14 /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2BEB3259B2208E5AE5BB3F65 /* Settings.swift */,
|
||||
);
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4658A940E89BC42EE3346A97 /* Messages */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -704,6 +823,14 @@
|
||||
path = Scripts;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
58F951CB7BD7F96C37BE5CAD /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6D607F47FDEF16CC63684BE0 /* BugReport.swift */,
|
||||
);
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5958CAF6E56422496E0063AF /* LoginScreen */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -738,6 +865,18 @@
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
70B74A432C241E56A7ACE610 /* Settings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3DD2D50A7EAA4FC78417730E /* SettingsCoordinator.swift */,
|
||||
4990FDBDA96B88E214F92F48 /* SettingsModels.swift */,
|
||||
0A191D3FDB995309C7E2DE7D /* SettingsViewModel.swift */,
|
||||
5B2F9D5C39A4494D19F33E38 /* SettingsViewModelProtocol.swift */,
|
||||
4541090DFE1A5499BD67BD14 /* View */,
|
||||
);
|
||||
path = Settings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
70DABA39C844CA931B829395 /* RoomSummary */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -763,11 +902,17 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */,
|
||||
EFFD3200F9960D4996159F10 /* BugReportServiceTests.swift */,
|
||||
7AB7ED3A898B07976F3AA90F /* BugReportViewModelTests.swift */,
|
||||
DBFEAC3AC691CBB84983E275 /* ElementXTests.swift */,
|
||||
505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */,
|
||||
6045E825AE900A92D61FEFF0 /* ImageAnonymizerTests.swift */,
|
||||
C070FD43DC6BF4E50217965A /* LocalizationTests.swift */,
|
||||
3DC1943ADE6A62ED5129D7C8 /* LoggingTests.swift */,
|
||||
5A43964330459965AF048A8C /* LoginScreenViewModelTests.swift */,
|
||||
93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */,
|
||||
F03C9D319676F3C0DC6B0203 /* ScreenshotDetectorTests.swift */,
|
||||
3D487C1185D658F8B15B8F55 /* SettingsViewModelTests.swift */,
|
||||
AF552BB969DC98A4BB8CF8D5 /* UserIndicators */,
|
||||
);
|
||||
path = Sources;
|
||||
@ -875,9 +1020,11 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7D0CBC76C80E04345E11F2DB /* Application.swift */,
|
||||
C6FEA87EA3752203065ECE27 /* BugReportUITests.swift */,
|
||||
4D6E4C37E9F0E53D3DF951AC /* HomeScreenUITests.swift */,
|
||||
1DB34B0C74CD242FED9DD069 /* LoginScreenUITests.swift */,
|
||||
086B997409328F091EBA43CE /* RoomScreenUITests.swift */,
|
||||
E3E29F98CF0E960689A410E3 /* SettingsUITests.swift */,
|
||||
);
|
||||
path = Sources;
|
||||
sourceTree = "<group>";
|
||||
@ -930,6 +1077,7 @@
|
||||
A0C06C0F6A8621B22BFAEB56 /* Localizations */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
91DE43B8815918E590912DDA /* InfoPlist.strings */,
|
||||
7109E709A7738E6BCC4553E6 /* Localizable.strings */,
|
||||
187853A7E643995EE49FAD43 /* Localizable.stringsdict */,
|
||||
D2F7194F440375338F8E2487 /* Untranslated.strings */,
|
||||
@ -941,6 +1089,7 @@
|
||||
A4852B57D55D71EEBFCD931D /* UnitTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E600AACDF87CDBCE32683236 /* Resources */,
|
||||
73CD9796729EB702B4DFA88C /* Sources */,
|
||||
24FD174C31912A5FACFEAFB5 /* SupportingFiles */,
|
||||
);
|
||||
@ -1009,9 +1158,10 @@
|
||||
E5272BC4A60B6AD7553BACA1 /* BlurHashDecode.swift */,
|
||||
D77DD2DA5DC8654F2A80FF1D /* Bundle.swift */,
|
||||
95CC95CD75B688E946438165 /* Coordinator.swift */,
|
||||
12A626D74BBE9F4A60763B45 /* ImageAnonymizer.swift */,
|
||||
F7B81C8227BBEA95CCE86037 /* MatrixEntitityRegex.swift */,
|
||||
64839516BD56D1C81D84C5E0 /* MXLog.swift */,
|
||||
8F9A844EB44B6AD7CA18FD96 /* HTMLParsing */,
|
||||
06501F0E978B2D5C92771DC7 /* Logging */,
|
||||
FE50232944F9E67ADD7A2D21 /* Routers */,
|
||||
052CC920F473C10B509F9FC1 /* SwiftUI */,
|
||||
F8474EB69289112888B65518 /* UserIndicators */,
|
||||
@ -1032,20 +1182,31 @@
|
||||
E59565F441830B19DBAE567C /* Screens */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4009BE2E791C16AC6EE39A7E /* BugReport */,
|
||||
B53CA9BECD3F97805E1432D0 /* HomeScreen */,
|
||||
5958CAF6E56422496E0063AF /* LoginScreen */,
|
||||
679E9837ECA8D6776079D16E /* RoomScreen */,
|
||||
70B74A432C241E56A7ACE610 /* Settings */,
|
||||
02175C9269C4632DB6D12C25 /* Splash */,
|
||||
);
|
||||
path = Screens;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E600AACDF87CDBCE32683236 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9414DCADBDF9D6C4B806F61E /* sample_screenshot.png */,
|
||||
);
|
||||
path = Resources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E68740F873AB18A5C26844EA /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CF3EDF23226895776553F04A /* AppCoordinator.swift */,
|
||||
5A9AB74614131D6706894E0C /* AppCoordinatorStateMachine.swift */,
|
||||
EFFA5FD06AAAC4AF544B594E /* AppDelegate.swift */,
|
||||
3F87116470221880017CF522 /* BuildSettings.swift */,
|
||||
967873B9E11828B67F64C89A /* UITestsAppCoordinator.swift */,
|
||||
CCA431E6EDD71F7067B5F9E7 /* UITestsRootView.swift */,
|
||||
0787F81684E503024BD0C051 /* Services */,
|
||||
@ -1134,6 +1295,8 @@
|
||||
04C28663564E008DB32B5972 /* Introspect */,
|
||||
A981A4CA233FB5C13B9CA690 /* SwiftyBeaver */,
|
||||
3853B78FB8531B83936C5DA6 /* SwiftState */,
|
||||
1BCD21310B997A6837B854D6 /* GZIP */,
|
||||
67E7A6F388D3BF85767609D9 /* Sentry */,
|
||||
);
|
||||
productName = UITests;
|
||||
productReference = F506C6ADB1E1DA6638078E11 /* UITests.xctest */;
|
||||
@ -1179,6 +1342,8 @@
|
||||
5986E300FC849DEAB2EE7AEB /* Introspect */,
|
||||
FD43A50D9B75C9D6D30F006B /* SwiftyBeaver */,
|
||||
9573B94B1C86C6DF751AF3FD /* SwiftState */,
|
||||
997C7385E1A07E061D7E2100 /* GZIP */,
|
||||
7731767AE437BA3BD2CC14A8 /* Sentry */,
|
||||
);
|
||||
productName = ElementX;
|
||||
productReference = 4CD6AC7546E8D7E5C73CEA48 /* ElementX.app */;
|
||||
@ -1283,10 +1448,12 @@
|
||||
mainGroup = 405B00F139AEE3994601B36A;
|
||||
packageReferences = (
|
||||
C13F55E4518415CB4C278E73 /* XCRemoteSwiftPackageReference "DTCoreText" */,
|
||||
701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */,
|
||||
A24ABD6F9CEE4D0749A6173E /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */,
|
||||
61916C63E3F5BD900F08DA0C /* XCRemoteSwiftPackageReference "KeychainAccess" */,
|
||||
D283517192CAC3E2E6920765 /* XCRemoteSwiftPackageReference "Kingfisher" */,
|
||||
4FCDA8D25C7415C8FB33490D /* XCRemoteSwiftPackageReference "matrix-rust-components-swift" */,
|
||||
D94BF33F492E2443005F809A /* XCRemoteSwiftPackageReference "sentry-cocoa" */,
|
||||
6582B5AF3F104B0F7E031E7D /* XCRemoteSwiftPackageReference "SwiftState" */,
|
||||
25B4484A6A20B9F1705DEEDA /* XCRemoteSwiftPackageReference "SwiftyBeaver" */,
|
||||
);
|
||||
@ -1308,6 +1475,7 @@
|
||||
30122AB3484AC6C3A7F6A717 /* ActivityIndicatorView.xib in Resources */,
|
||||
B80C4FABB5529DF12436FFDA /* AppIcon.pdf in Resources */,
|
||||
992F5E750F5030C4BA2D0D03 /* Assets.xcassets in Resources */,
|
||||
B6DA66EFC13A90846B625836 /* InfoPlist.strings in Resources */,
|
||||
AB34401E4E1CAD5D2EC3072B /* LaunchScreen.storyboard in Resources */,
|
||||
5F5488FBC9CFEB6F433D74A4 /* Localizable.strings in Resources */,
|
||||
0EA6537A07E2DC882AEA5962 /* Localizable.stringsdict in Resources */,
|
||||
@ -1330,6 +1498,7 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
524C9C31EF8D58C2249F8A10 /* sample_screenshot.png in Resources */,
|
||||
35E975CFDA60E05362A7CF79 /* target.yml in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -1381,11 +1550,17 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
90DF83A6A347F7EE7EDE89EE /* AttributedStringBuilderTests.swift in Sources */,
|
||||
7F61F9ACD5EC9E845EF3EFBF /* BugReportServiceTests.swift in Sources */,
|
||||
C7CFDB4929DDD9A3B5BA085D /* BugReportViewModelTests.swift in Sources */,
|
||||
9C45CE85325CD591DADBC4CA /* ElementXTests.swift in Sources */,
|
||||
F6F49E37272AD7397CD29A01 /* HomeScreenViewModelTests.swift in Sources */,
|
||||
0B1F80C2BF7D223159FBA82C /* ImageAnonymizerTests.swift in Sources */,
|
||||
0033481EE363E4914295F188 /* LocalizationTests.swift in Sources */,
|
||||
149D1942DC005D0485FB8D93 /* LoggingTests.swift in Sources */,
|
||||
7434A7F02D587A920B376A9A /* LoginScreenViewModelTests.swift in Sources */,
|
||||
46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */,
|
||||
EA31DD9043B91ECB8E45A9A6 /* ScreenshotDetectorTests.swift in Sources */,
|
||||
206F0DBAB6AF042CA1FF2C0D /* SettingsViewModelTests.swift in Sources */,
|
||||
7B3D3AFD511D496DED18910B /* TemplateSimpleScreenViewModelTests.swift in Sources */,
|
||||
1151DCC5EC2C6585826545EC /* UserIndicatorPresenterSpy.swift in Sources */,
|
||||
4B8A2C45FF906ADBB1F5C3B4 /* UserIndicatorQueueTests.swift in Sources */,
|
||||
@ -1410,12 +1585,21 @@
|
||||
CB326BAB54E9B68658909E36 /* Benchmark.swift in Sources */,
|
||||
38546A6010A2CF240EC9AF73 /* BindableState.swift in Sources */,
|
||||
B6DF6B6FA8734B70F9BF261E /* BlurHashDecode.swift in Sources */,
|
||||
00AC53151BA23A90FAAE9FBF /* BugReport.swift in Sources */,
|
||||
A32517FB1CA0BBCE2BC75249 /* BugReportCoordinator.swift in Sources */,
|
||||
00F3059B1E0CFCA019710C3E /* BugReportModels.swift in Sources */,
|
||||
3DA57CA0D609A6B37CA1DC2F /* BugReportService.swift in Sources */,
|
||||
172E6E9A612ADCF10A62CF13 /* BugReportServiceProtocol.swift in Sources */,
|
||||
86C2E93920FD15AD17E193A9 /* BugReportViewModel.swift in Sources */,
|
||||
187E18F21EF4DA244E436E58 /* BugReportViewModelProtocol.swift in Sources */,
|
||||
05776B005C57E92582F0CF08 /* BuildSettings.swift in Sources */,
|
||||
7E1EDBA3934E6C29E5BD045B /* Bundle.swift in Sources */,
|
||||
1950A80CD198BED283DFC2CE /* ClientProxy.swift in Sources */,
|
||||
24BDDD09A90B8BFE3793F3AA /* ClientProxyProtocol.swift in Sources */,
|
||||
DCB781BD227CA958809AFADF /* Coordinator.swift in Sources */,
|
||||
C4F69156C31A447FEFF2A47C /* DTHTMLElement+AttributedStringBuilder.swift in Sources */,
|
||||
EE8491AD81F47DF3C192497B /* DecorationTimelineItemProtocol.swift in Sources */,
|
||||
D8CFF02C2730EE5BC4F17ABF /* ElementToggleStyle.swift in Sources */,
|
||||
7C1A7B594B2F8143F0DD0005 /* ElementXAttributeScope.swift in Sources */,
|
||||
224A55EEAEECF5336B14A4A5 /* EmoteRoomMessage.swift in Sources */,
|
||||
6647430A45B4A8E692909A8F /* EmoteRoomTimelineItem.swift in Sources */,
|
||||
@ -1432,9 +1616,11 @@
|
||||
8810A2A30A68252EBB54EE05 /* HomeScreenModels.swift in Sources */,
|
||||
DE4F8C4E0F1DB4832F09DE97 /* HomeScreenViewModel.swift in Sources */,
|
||||
56F0A22972A3BB519DA2261C /* HomeScreenViewModelProtocol.swift in Sources */,
|
||||
6EA61FCA55D950BDE326A1A7 /* ImageAnonymizer.swift in Sources */,
|
||||
2E59008365E01F0AFB3A6B24 /* ImageRoomMessage.swift in Sources */,
|
||||
DDB80FD2753FEAAE43CC2AAE /* ImageRoomTimelineItem.swift in Sources */,
|
||||
D5EA4C6C80579279770D5804 /* ImageRoomTimelineView.swift in Sources */,
|
||||
A5C8F013ED9FB8AA6FEE18A7 /* InfoPlist.swift in Sources */,
|
||||
2D8A687149E46B8C8B989561 /* KeychainController.swift in Sources */,
|
||||
277D2531C70F207A2F9F5906 /* KeychainControllerProtocol.swift in Sources */,
|
||||
9C9E48A627C7C166084E3F5B /* LabelledActivityIndicatorView.swift in Sources */,
|
||||
@ -1444,7 +1630,8 @@
|
||||
E9CEAF2C38E4E00459B811D9 /* LoginScreenModels.swift in Sources */,
|
||||
7C9121245B11CA48307CB462 /* LoginScreenViewModel.swift in Sources */,
|
||||
33912D1B9264D897033E0681 /* LoginScreenViewModelProtocol.swift in Sources */,
|
||||
B0887A7B5AFEC88981626389 /* MXLog.swift in Sources */,
|
||||
B94368839BDB69172E28E245 /* MXLog.swift in Sources */,
|
||||
BCC3EDB7AD0902797CB4BBC2 /* MXLogger.m in Sources */,
|
||||
F03E16ED043C62FED5A07AE0 /* MatrixEntitityRegex.swift in Sources */,
|
||||
EA1E7949533E19C6D862680A /* MediaProvider.swift in Sources */,
|
||||
7002C55A4C917F3715765127 /* MediaProviderProtocol.swift in Sources */,
|
||||
@ -1454,6 +1641,7 @@
|
||||
A5EC21A071F58FC1229C20D0 /* MemberDetailsProviderProtocol.swift in Sources */,
|
||||
24906A1E82D0046655958536 /* MessageComposer.swift in Sources */,
|
||||
072BA9DBA932374CCA300125 /* MessageComposerTextField.swift in Sources */,
|
||||
28410F3DE89C2C44E4F75C92 /* MockBugReportService.swift in Sources */,
|
||||
67E391A2E00709FB41903B36 /* MockMediaProvider.swift in Sources */,
|
||||
51DB67C5B5BC68B0A6FF54D4 /* MockRoomProxy.swift in Sources */,
|
||||
29AEE68A604940180AB9EBFF /* MockRoomSummary.swift in Sources */,
|
||||
@ -1494,8 +1682,14 @@
|
||||
7F19E97E7985F518C9018B83 /* RootRouter.swift in Sources */,
|
||||
2C0CE61E5DC177938618E0B1 /* RootRouterType.swift in Sources */,
|
||||
462813B93C39DF93B1249403 /* RoundedToastView.swift in Sources */,
|
||||
CC736DA1AA8F8B9FD8785009 /* ScreenshotDetector.swift in Sources */,
|
||||
1281625B25371BE53D36CB3A /* SeparatorRoomTimelineItem.swift in Sources */,
|
||||
49F2E7DD8CAACE09CEECE3E6 /* SeparatorRoomTimelineView.swift in Sources */,
|
||||
6C72F66DA26A0956E9A9077A /* Settings.swift in Sources */,
|
||||
34966D4C1C2C6D37FE3F7F50 /* SettingsCoordinator.swift in Sources */,
|
||||
3B770CB4DED51CC362C66D47 /* SettingsModels.swift in Sources */,
|
||||
4A2E0DBB63919AC8309B6D40 /* SettingsViewModel.swift in Sources */,
|
||||
438FB9BC535BC95948AA5F34 /* SettingsViewModelProtocol.swift in Sources */,
|
||||
FCB640C576292BEAF7FA3B2E /* SplashViewController.swift in Sources */,
|
||||
B4AAB3257A83B73F53FB2689 /* StateStoreViewModel.swift in Sources */,
|
||||
A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */,
|
||||
@ -1538,9 +1732,11 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7405B4824D45BA7C3D943E76 /* Application.swift in Sources */,
|
||||
7756C4E90CABE6F14F7920A0 /* BugReportUITests.swift in Sources */,
|
||||
9DC5FB22B8F86C3B51E907C1 /* HomeScreenUITests.swift in Sources */,
|
||||
5C8AFBF168A41E20835F3B86 /* LoginScreenUITests.swift in Sources */,
|
||||
2F1CF90A3460C153154427F0 /* RoomScreenUITests.swift in Sources */,
|
||||
490E606044B18985055FF690 /* SettingsUITests.swift in Sources */,
|
||||
2E68C57E7D644E94778743D5 /* TemplateSimpleScreenUITests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -1697,6 +1893,14 @@
|
||||
name = Localizable.strings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
91DE43B8815918E590912DDA /* InfoPlist.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
1215A4FC53D2319E81AE8970 /* en */,
|
||||
);
|
||||
name = InfoPlist.strings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9760103CF316DF68698BCFE6 /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
@ -2043,6 +2247,14 @@
|
||||
minimumVersion = 6.0.0;
|
||||
};
|
||||
};
|
||||
701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/nicklockwood/GZIP";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 1.3.0;
|
||||
};
|
||||
};
|
||||
A24ABD6F9CEE4D0749A6173E /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/siteline/SwiftUI-Introspect.git";
|
||||
@ -2067,6 +2279,14 @@
|
||||
minimumVersion = 7.2.0;
|
||||
};
|
||||
};
|
||||
D94BF33F492E2443005F809A /* XCRemoteSwiftPackageReference "sentry-cocoa" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/getsentry/sentry-cocoa.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 7.15.0;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
@ -2085,6 +2305,11 @@
|
||||
package = D283517192CAC3E2E6920765 /* XCRemoteSwiftPackageReference "Kingfisher" */;
|
||||
productName = Kingfisher;
|
||||
};
|
||||
1BCD21310B997A6837B854D6 /* GZIP */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */;
|
||||
productName = GZIP;
|
||||
};
|
||||
36B7FC232711031AA2B0D188 /* DTCoreText */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = C13F55E4518415CB4C278E73 /* XCRemoteSwiftPackageReference "DTCoreText" */;
|
||||
@ -2110,6 +2335,16 @@
|
||||
package = A24ABD6F9CEE4D0749A6173E /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */;
|
||||
productName = Introspect;
|
||||
};
|
||||
67E7A6F388D3BF85767609D9 /* Sentry */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D94BF33F492E2443005F809A /* XCRemoteSwiftPackageReference "sentry-cocoa" */;
|
||||
productName = Sentry;
|
||||
};
|
||||
7731767AE437BA3BD2CC14A8 /* Sentry */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D94BF33F492E2443005F809A /* XCRemoteSwiftPackageReference "sentry-cocoa" */;
|
||||
productName = Sentry;
|
||||
};
|
||||
78A5A8DE1E2B09C978C7F3B0 /* KeychainAccess */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 61916C63E3F5BD900F08DA0C /* XCRemoteSwiftPackageReference "KeychainAccess" */;
|
||||
@ -2120,6 +2355,11 @@
|
||||
package = 6582B5AF3F104B0F7E031E7D /* XCRemoteSwiftPackageReference "SwiftState" */;
|
||||
productName = SwiftState;
|
||||
};
|
||||
997C7385E1A07E061D7E2100 /* GZIP */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */;
|
||||
productName = GZIP;
|
||||
};
|
||||
A678E40E917620059695F067 /* MatrixRustSDK */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 4FCDA8D25C7415C8FB33490D /* XCRemoteSwiftPackageReference "matrix-rust-components-swift" */;
|
||||
|
@ -18,6 +18,15 @@
|
||||
"version" : "1.7.18"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "gzip",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/nicklockwood/GZIP",
|
||||
"state" : {
|
||||
"revision" : "c45c8526dad61240a79aaff5b1cf6e082d2b90b2",
|
||||
"version" : "1.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "keychainaccess",
|
||||
"kind" : "remoteSourceControl",
|
||||
@ -45,6 +54,15 @@
|
||||
"revision" : "43c88a4b0912a1589c2a28cc9bb2df45c70cdcad"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "sentry-cocoa",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/getsentry/sentry-cocoa.git",
|
||||
"state" : {
|
||||
"revision" : "afa4cd596e5cf97a797fb9b5c3afeea4add2c7a3",
|
||||
"version" : "7.15.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swiftstate",
|
||||
"kind" : "remoteSourceControl",
|
||||
|
15
ElementX/Resources/Assets.xcassets/Images/close_circle.imageset/Contents.json
vendored
Normal file
15
ElementX/Resources/Assets.xcassets/Images/close_circle.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "url_preview_close_dark.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
116
ElementX/Resources/Assets.xcassets/Images/close_circle.imageset/url_preview_close_dark.pdf
vendored
Normal file
116
ElementX/Resources/Assets.xcassets/Images/close_circle.imageset/url_preview_close_dark.pdf
vendored
Normal file
@ -0,0 +1,116 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< /ExtGState << /E1 << /ca 0.800000 >> >> >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
/E1 gs
|
||||
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
|
||||
0.223529 0.250980 0.286275 scn
|
||||
24.000000 12.000000 m
|
||||
24.000000 5.372583 18.627417 0.000000 12.000000 0.000000 c
|
||||
5.372583 0.000000 0.000000 5.372583 0.000000 12.000000 c
|
||||
0.000000 18.627417 5.372583 24.000000 12.000000 24.000000 c
|
||||
18.627417 24.000000 24.000000 18.627417 24.000000 12.000000 c
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 7.999756 5.805014 cm
|
||||
0.662745 0.698039 0.737255 scn
|
||||
0.707107 10.902368 m
|
||||
0.316583 11.292892 -0.316582 11.292892 -0.707107 10.902368 c
|
||||
-1.097631 10.511844 -1.097631 9.878678 -0.707107 9.488154 c
|
||||
0.707107 10.902368 l
|
||||
h
|
||||
7.292891 1.488155 m
|
||||
7.683415 1.097631 8.316580 1.097631 8.707105 1.488155 c
|
||||
9.097629 1.878679 9.097629 2.511844 8.707105 2.902369 c
|
||||
7.292891 1.488155 l
|
||||
h
|
||||
-0.707107 9.488154 m
|
||||
7.292891 1.488155 l
|
||||
8.707105 2.902369 l
|
||||
0.707107 10.902368 l
|
||||
-0.707107 9.488154 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
-1.000000 -0.000000 -0.000000 1.000000 16.000488 5.805014 cm
|
||||
0.662745 0.698039 0.737255 scn
|
||||
0.707107 10.902368 m
|
||||
0.316582 11.292892 -0.316583 11.292892 -0.707107 10.902368 c
|
||||
-1.097631 10.511844 -1.097631 9.878678 -0.707107 9.488154 c
|
||||
0.707107 10.902368 l
|
||||
h
|
||||
7.292893 1.488155 m
|
||||
7.683417 1.097631 8.316583 1.097631 8.707107 1.488155 c
|
||||
9.097631 1.878679 9.097631 2.511845 8.707107 2.902369 c
|
||||
7.292893 1.488155 l
|
||||
h
|
||||
-0.707107 9.488154 m
|
||||
7.292893 1.488155 l
|
||||
8.707107 2.902369 l
|
||||
0.707107 10.902368 l
|
||||
-0.707107 9.488154 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
1439
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Type /Catalog
|
||||
/Pages 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000074 00000 n
|
||||
0000001569 00000 n
|
||||
0000001592 00000 n
|
||||
0000001765 00000 n
|
||||
0000001839 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
1898
|
||||
%%EOF
|
@ -0,0 +1 @@
|
||||
"NSPhotoLibraryUsageDescription" = "The photo library is used to send photos and videos.";
|
@ -1 +1,3 @@
|
||||
"untranslated" = "Untranslated";
|
||||
"screenshot_detected_title" = "You took a screenshot";
|
||||
"screenshot_detected_message" = "Would you like to submit a bug report?";
|
||||
|
@ -26,6 +26,9 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
|
||||
private let memberDetailProviderManager: MemberDetailProviderManager
|
||||
|
||||
private let bugReportService: BugReportServiceProtocol
|
||||
private let screenshotDetector: ScreenshotDetector
|
||||
|
||||
private var indicatorPresenter: UserIndicatorTypePresenterProtocol
|
||||
private var loadingIndicator: UserIndicator?
|
||||
private var errorIndicator: UserIndicator?
|
||||
@ -35,6 +38,13 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
init() {
|
||||
stateMachine = AppCoordinatorStateMachine()
|
||||
|
||||
do {
|
||||
bugReportService = try BugReportService(withBaseUrlString: BuildSettings.bugReportServiceBaseUrlString,
|
||||
sentryEndpoint: BuildSettings.bugReportSentryEndpoint)
|
||||
} catch {
|
||||
fatalError(error.localizedDescription)
|
||||
}
|
||||
|
||||
splashViewController = SplashViewController()
|
||||
mainNavigationController = UINavigationController(rootViewController: splashViewController)
|
||||
window = UIWindow(frame: UIScreen.main.bounds)
|
||||
@ -53,12 +63,20 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
keychainController = KeychainController(identifier: bundleIdentifier)
|
||||
authenticationCoordinator = AuthenticationCoordinator(keychainController: keychainController,
|
||||
navigationRouter: navigationRouter)
|
||||
|
||||
screenshotDetector = ScreenshotDetector()
|
||||
screenshotDetector.callback = processScreenshotDetection
|
||||
|
||||
authenticationCoordinator.delegate = self
|
||||
|
||||
setupStateMachine()
|
||||
|
||||
let loggerConfiguration = MXLogConfiguration()
|
||||
loggerConfiguration.logLevel = .verbose
|
||||
// Redirect NSLogs to files only if we are not debugging
|
||||
if isatty(STDERR_FILENO) == 0 {
|
||||
loggerConfiguration.redirectLogsToFiles = true
|
||||
}
|
||||
MXLog.configure(loggerConfiguration)
|
||||
|
||||
// Benchmark.trackingEnabled = true
|
||||
@ -117,6 +135,10 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
self.tearDownUserSession()
|
||||
case (.signingOut, .failedSigningOut, _):
|
||||
self.showLogoutErrorToast()
|
||||
case (.homeScreen, .showSettingsScreen, .settingsScreen):
|
||||
self.presentSettingsScreen()
|
||||
case (.settingsScreen, .dismissedSettingsScreen, .homeScreen):
|
||||
self.tearDownDismissedSettingsScreen()
|
||||
default:
|
||||
fatalError("Unknown transition: \(context)")
|
||||
}
|
||||
@ -162,13 +184,33 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
switch action {
|
||||
case .logout:
|
||||
self.stateMachine.processEvent(.attemptSignOut)
|
||||
case .selectRoom(let roomIdentifier):
|
||||
case .presentRoom(let roomIdentifier):
|
||||
self.stateMachine.processEvent(.showRoomScreen(roomId: roomIdentifier))
|
||||
case .presentSettings:
|
||||
self.stateMachine.processEvent(.showSettingsScreen)
|
||||
}
|
||||
}
|
||||
|
||||
add(childCoordinator: coordinator)
|
||||
navigationRouter.setRootModule(coordinator)
|
||||
|
||||
if bugReportService.crashedLastRun {
|
||||
showCrashPopup()
|
||||
}
|
||||
}
|
||||
|
||||
private func presentSettingsScreen() {
|
||||
let parameters = SettingsCoordinatorParameters(navigationRouter: navigationRouter,
|
||||
bugReportService: bugReportService)
|
||||
let coordinator = SettingsCoordinator(parameters: parameters)
|
||||
|
||||
add(childCoordinator: coordinator)
|
||||
coordinator.start()
|
||||
navigationRouter.push(coordinator) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.stateMachine.processEvent(.dismissedSettingsScreen)
|
||||
}
|
||||
}
|
||||
|
||||
private func presentRoomWithIdentifier(_ roomIdentifier: String) {
|
||||
@ -207,6 +249,14 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
remove(childCoordinator: coordinator)
|
||||
}
|
||||
|
||||
private func tearDownDismissedSettingsScreen() {
|
||||
guard let coordinator = childCoordinators.last as? SettingsCoordinator else {
|
||||
fatalError("Invalid coordinator hierarchy: \(childCoordinators)")
|
||||
}
|
||||
|
||||
remove(childCoordinator: coordinator)
|
||||
}
|
||||
|
||||
private func showLoadingIndicator() {
|
||||
loadingIndicator = indicatorPresenter.present(.loading(label: "Loading", isInteractionBlocking: true))
|
||||
}
|
||||
@ -216,10 +266,70 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
}
|
||||
|
||||
private func showLoginErrorToast() {
|
||||
errorIndicator = indicatorPresenter.present(.success(label: "Failed logging in"))
|
||||
errorIndicator = indicatorPresenter.present(.error(label: "Failed logging in"))
|
||||
}
|
||||
|
||||
private func showLogoutErrorToast() {
|
||||
errorIndicator = indicatorPresenter.present(.success(label: "Failed logging out"))
|
||||
}
|
||||
|
||||
private func showCrashPopup() {
|
||||
let alert = UIAlertController(title: nil,
|
||||
message: ElementL10n.sendBugReportAppCrashed,
|
||||
preferredStyle: .alert)
|
||||
|
||||
alert.addAction(UIAlertAction(title: ElementL10n.no, style: .cancel))
|
||||
alert.addAction(UIAlertAction(title: ElementL10n.yes, style: .default) { [weak self] _ in
|
||||
self?.presentBugReportScreen()
|
||||
})
|
||||
|
||||
navigationRouter.present(alert, animated: true)
|
||||
}
|
||||
|
||||
private func processScreenshotDetection(image: UIImage?, error: Error?) {
|
||||
MXLog.debug("[AppCoordinator] processScreenshotDetection: \(String(describing: image)), error: \(String(describing: error))")
|
||||
|
||||
let alert = UIAlertController(title: ElementL10n.screenshotDetectedTitle,
|
||||
message: ElementL10n.screenshotDetectedMessage,
|
||||
preferredStyle: .alert)
|
||||
|
||||
alert.addAction(UIAlertAction(title: ElementL10n.no, style: .cancel))
|
||||
alert.addAction(UIAlertAction(title: ElementL10n.yes, style: .default) { [weak self] _ in
|
||||
self?.presentBugReportScreen(for: image)
|
||||
})
|
||||
|
||||
navigationRouter.present(alert, animated: true)
|
||||
}
|
||||
|
||||
private func presentBugReportScreen(for image: UIImage? = nil) {
|
||||
let parameters = BugReportCoordinatorParameters(bugReportService: bugReportService,
|
||||
screenshot: image)
|
||||
let coordinator = BugReportCoordinator(parameters: parameters)
|
||||
coordinator.completion = { [weak self, weak coordinator] in
|
||||
guard let self = self, let coordinator = coordinator else { return }
|
||||
self.navigationRouter.dismissModule(animated: true)
|
||||
self.remove(childCoordinator: coordinator)
|
||||
}
|
||||
|
||||
add(childCoordinator: coordinator)
|
||||
coordinator.start()
|
||||
let navController = UINavigationController(rootViewController: coordinator.toPresentable())
|
||||
navController.navigationBar.topItem?.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel,
|
||||
target: self,
|
||||
action: #selector(dismissBugReportScreen))
|
||||
navController.isModalInPresentation = true
|
||||
navigationRouter.present(navController, animated: true)
|
||||
}
|
||||
|
||||
@objc
|
||||
private func dismissBugReportScreen() {
|
||||
MXLog.debug("[AppCoorrdinator] dismissBugReportScreen")
|
||||
|
||||
guard let bugReportCoordinator = childCoordinators.first(where: { $0 is BugReportCoordinator }) else {
|
||||
return
|
||||
}
|
||||
|
||||
navigationRouter.dismissModule()
|
||||
remove(childCoordinator: bugReportCoordinator)
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,8 @@ class AppCoordinatorStateMachine {
|
||||
case signedIn
|
||||
/// Showing the home screen
|
||||
case homeScreen
|
||||
/// Showing the settings screen
|
||||
case settingsScreen
|
||||
/// Showing a particular room's timeline
|
||||
/// - Parameter roomId: that room's identifier
|
||||
case roomScreen(roomId: String)
|
||||
@ -52,6 +54,10 @@ class AppCoordinatorStateMachine {
|
||||
case showRoomScreen(roomId: String)
|
||||
/// The room screen has been dismissed
|
||||
case dismissedRoomScreen
|
||||
/// The settings screen has been dismissed
|
||||
case dismissedSettingsScreen
|
||||
/// Request settings screen presentation
|
||||
case showSettingsScreen
|
||||
}
|
||||
|
||||
private let stateMachine: StateMachine<State, Event>
|
||||
@ -71,6 +77,8 @@ class AppCoordinatorStateMachine {
|
||||
|
||||
machine.addRoutes(event: .succeededSigningOut, transitions: [ .signingOut => .signedOut ])
|
||||
machine.addRoutes(event: .failedSigningOut, transitions: [ .signingOut => .homeScreen ])
|
||||
machine.addRoutes(event: .showSettingsScreen, transitions: [ .homeScreen => .settingsScreen ])
|
||||
machine.addRoutes(event: .dismissedSettingsScreen, transitions: [ .settingsScreen => .homeScreen ])
|
||||
|
||||
// Transitions with associated values need to be handled through `addRouteMapping`
|
||||
machine.addRouteMapping { event, fromState, _ in
|
||||
|
20
ElementX/Sources/BuildSettings.swift
Normal file
20
ElementX/Sources/BuildSettings.swift
Normal file
@ -0,0 +1,20 @@
|
||||
//
|
||||
// BuildSettings.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Ismail on 2.06.2022.
|
||||
// Copyright © 2022 Element. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final class BuildSettings {
|
||||
|
||||
// MARK: - Bug report
|
||||
static let bugReportServiceBaseUrlString = "https://riot.im/bugreports"
|
||||
static let bugReportSentryEndpoint = "https://f39ac49e97714316965b777d9f3d6cd8@sentry.tools.element.io/44"
|
||||
// Use the name allocated by the bug report server
|
||||
static let bugReportApplicationId = "riot-ios"
|
||||
static let bugReportUISIId = "element-auto-uisi"
|
||||
|
||||
}
|
@ -27,6 +27,7 @@ internal enum Asset {
|
||||
}
|
||||
internal enum Images {
|
||||
internal static let appLogo = ImageAsset(name: "Images/app-logo")
|
||||
internal static let closeCircle = ImageAsset(name: "Images/close_circle")
|
||||
internal static let timelineComposerSendMessage = ImageAsset(name: "Images/timelineComposerSendMessage")
|
||||
internal static let timelineScrollToBottom = ImageAsset(name: "Images/timelineScrollToBottom")
|
||||
}
|
||||
|
67
ElementX/Sources/Generated/InfoPlist.swift
Normal file
67
ElementX/Sources/Generated/InfoPlist.swift
Normal file
@ -0,0 +1,67 @@
|
||||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
import Foundation
|
||||
|
||||
// swiftlint:disable superfluous_disable_command
|
||||
// swiftlint:disable file_length
|
||||
|
||||
// MARK: - Plist Files
|
||||
|
||||
// swiftlint:disable identifier_name line_length type_body_length
|
||||
internal enum ElementInfoPlist {
|
||||
private static let _document = PlistDocument(path: "Info.plist")
|
||||
|
||||
internal static let cfBundleDevelopmentRegion: String = _document["CFBundleDevelopmentRegion"]
|
||||
internal static let cfBundleExecutable: String = _document["CFBundleExecutable"]
|
||||
internal static let cfBundleIdentifier: String = _document["CFBundleIdentifier"]
|
||||
internal static let cfBundleInfoDictionaryVersion: String = _document["CFBundleInfoDictionaryVersion"]
|
||||
internal static let cfBundleName: String = _document["CFBundleName"]
|
||||
internal static let cfBundlePackageType: String = _document["CFBundlePackageType"]
|
||||
internal static let cfBundleShortVersionString: String = _document["CFBundleShortVersionString"]
|
||||
internal static let cfBundleVersion: String = _document["CFBundleVersion"]
|
||||
internal static let uiLaunchStoryboardName: String = _document["UILaunchStoryboardName"]
|
||||
internal static let uiSupportedInterfaceOrientations: [String] = _document["UISupportedInterfaceOrientations"]
|
||||
}
|
||||
// swiftlint:enable identifier_name line_length type_body_length
|
||||
|
||||
// MARK: - Implementation Details
|
||||
|
||||
private func arrayFromPlist<T>(at path: String) -> [T] {
|
||||
guard let url = BundleToken.bundle.url(forResource: path, withExtension: nil),
|
||||
let data = NSArray(contentsOf: url) as? [T] else {
|
||||
fatalError("Unable to load PLIST at path: \(path)")
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
private struct PlistDocument {
|
||||
let data: [String: Any]
|
||||
|
||||
init(path: String) {
|
||||
guard let url = BundleToken.bundle.url(forResource: path, withExtension: nil),
|
||||
let data = NSDictionary(contentsOf: url) as? [String: Any] else {
|
||||
fatalError("Unable to load PLIST at path: \(path)")
|
||||
}
|
||||
self.data = data
|
||||
}
|
||||
|
||||
subscript<T>(key: String) -> T {
|
||||
guard let result = data[key] as? T else {
|
||||
fatalError("Property '\(key)' is not of type \(T.self)")
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// swiftlint:disable convenience_type
|
||||
private final class BundleToken {
|
||||
static let bundle: Bundle = {
|
||||
#if SWIFT_PACKAGE
|
||||
return Bundle.module
|
||||
#else
|
||||
return Bundle(for: BundleToken.self)
|
||||
#endif
|
||||
}()
|
||||
}
|
||||
// swiftlint:enable convenience_type
|
@ -10,6 +10,10 @@ import Foundation
|
||||
// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length
|
||||
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
|
||||
extension ElementL10n {
|
||||
/// Would you like to submit a bug report?
|
||||
public static let screenshotDetectedMessage = ElementL10n.tr("Untranslated", "screenshot_detected_message")
|
||||
/// You took a screenshot
|
||||
public static let screenshotDetectedTitle = ElementL10n.tr("Untranslated", "screenshot_detected_title")
|
||||
/// Untranslated
|
||||
public static let untranslated = ElementL10n.tr("Untranslated", "untranslated")
|
||||
/// Plural format key: "%#@VARIABLE@"
|
||||
|
111
ElementX/Sources/Other/ImageAnonymizer.swift
Normal file
111
ElementX/Sources/Other/ImageAnonymizer.swift
Normal file
@ -0,0 +1,111 @@
|
||||
//
|
||||
// UIImage+.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Ismail on 20.05.2022.
|
||||
// Copyright © 2022 element.io. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Vision
|
||||
import UIKit
|
||||
|
||||
enum ImageAnonymizerError: Error {
|
||||
case noCgImageBased
|
||||
}
|
||||
|
||||
struct ImageAnonymizer {
|
||||
|
||||
private static var allowedTextItems: [String] = [
|
||||
"#",
|
||||
"@",
|
||||
"%",
|
||||
"&",
|
||||
"+",
|
||||
"-",
|
||||
"_",
|
||||
"\"",
|
||||
"?",
|
||||
"*"
|
||||
]
|
||||
|
||||
static func anonymizedImage(from image: UIImage,
|
||||
confidenceLevel: Float = 0.5,
|
||||
fillColor: UIColor = .red) async throws -> UIImage {
|
||||
guard let cgImage = image.cgImage else {
|
||||
throw ImageAnonymizerError.noCgImageBased
|
||||
}
|
||||
|
||||
// create a handler with cgImage
|
||||
let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
|
||||
var observations: [VNDetectedObjectObservation] = []
|
||||
|
||||
// create a text request
|
||||
let textRequest = VNRecognizeTextRequest { request, error in
|
||||
guard let results = request.results as? [VNRecognizedTextObservation],
|
||||
error == nil else {
|
||||
return
|
||||
}
|
||||
observations.append(contentsOf: results)
|
||||
}
|
||||
textRequest.recognitionLevel = .accurate
|
||||
textRequest.revision = VNRecognizeTextRequestRevision2
|
||||
|
||||
// create a face request
|
||||
let faceRequest = VNDetectFaceRectanglesRequest { request, error in
|
||||
guard let results = request.results as? [VNFaceObservation],
|
||||
error == nil else {
|
||||
return
|
||||
}
|
||||
observations.append(contentsOf: results)
|
||||
}
|
||||
// revision3 doesn't work!
|
||||
faceRequest.revision = VNDetectFaceRectanglesRequestRevision2
|
||||
|
||||
// perform requests
|
||||
try handler.perform([
|
||||
textRequest,
|
||||
faceRequest
|
||||
])
|
||||
|
||||
return render(image: image,
|
||||
confidenceLevel: confidenceLevel,
|
||||
fillColor: fillColor,
|
||||
observations: observations)
|
||||
}
|
||||
|
||||
private static func render(image: UIImage,
|
||||
confidenceLevel: Float,
|
||||
fillColor: UIColor,
|
||||
observations: [VNDetectedObjectObservation]) -> UIImage {
|
||||
let size = image.size
|
||||
let result = UIGraphicsImageRenderer(size: size).image { rendererContext in
|
||||
// first draw self
|
||||
image.draw(in: CGRect(origin: .zero, size: size))
|
||||
// set fill color
|
||||
fillColor.setFill()
|
||||
for observation in observations {
|
||||
guard observation.confidence >= confidenceLevel else {
|
||||
// ensure observation's confidence level
|
||||
continue
|
||||
}
|
||||
if let textObservation = observation as? VNRecognizedTextObservation,
|
||||
let text = textObservation.topCandidates(1).first?.string {
|
||||
if Double(text) != nil || Self.allowedTextItems.contains(text) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
let box = observation.boundingBox
|
||||
// boc is normalized (and in starts from the lower left corner)
|
||||
// convert it to a rect in the image
|
||||
let rect = CGRect(x: box.minX * size.width,
|
||||
y: size.height - box.maxY * size.height,
|
||||
width: box.width * size.width,
|
||||
height: box.height * size.height)
|
||||
rendererContext.fill(rect)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
@ -136,14 +136,14 @@ private var logger: SwiftyBeaver.Type = {
|
||||
// MARK: - Private
|
||||
|
||||
fileprivate static func configureLogger(_ logger: SwiftyBeaver.Type, withConfiguration configuration: MXLogConfiguration) {
|
||||
// if let subLogName = configuration.subLogName {
|
||||
// MXLogger.setSubLogName(subLogName)
|
||||
// }
|
||||
//
|
||||
// MXLogger.redirectNSLog(toFiles: configuration.redirectLogsToFiles,
|
||||
// numberOfFiles: configuration.maxLogFilesCount,
|
||||
// sizeLimit: configuration.logFilesSizeLimit)
|
||||
//
|
||||
if let subLogName = configuration.subLogName {
|
||||
MXLogger.setSubLogName(subLogName)
|
||||
}
|
||||
|
||||
MXLogger.redirectNSLog(toFiles: configuration.redirectLogsToFiles,
|
||||
numberOfFiles: configuration.maxLogFilesCount,
|
||||
sizeLimit: configuration.logFilesSizeLimit)
|
||||
|
||||
guard configuration.logLevel != .none else {
|
||||
return
|
||||
}
|
114
ElementX/Sources/Other/Logging/MXLogger.h
Normal file
114
ElementX/Sources/Other/Logging/MXLogger.h
Normal file
@ -0,0 +1,114 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
The `MXLogger` tool redirects NSLog output into a fixed pool of files.
|
||||
Another log file is used every time [MXLogger redirectNSLogToFiles:YES]
|
||||
is called. The pool contains 3 files.
|
||||
|
||||
`MXLogger` can track and log uncatched exceptions or crashes.
|
||||
*/
|
||||
@interface MXLogger : NSObject
|
||||
|
||||
/**
|
||||
Redirect NSLog output to MXLogger files.
|
||||
|
||||
It is advised to condition this redirection in '#if (!isatty(STDERR_FILENO))' block to enable
|
||||
it only when the device is not attached to the debugger.
|
||||
|
||||
@param redirectNSLogToFiles YES to enable the redirection.
|
||||
*/
|
||||
+ (void)redirectNSLogToFiles:(BOOL)redirectNSLogToFiles;
|
||||
|
||||
/**
|
||||
Redirect NSLog output to MXLogger files.
|
||||
|
||||
It is advised to condition this redirection in '#if (!isatty(STDERR_FILENO))' block to enable
|
||||
it only when the device is not attached to the debugger.
|
||||
|
||||
@param redirectNSLogToFiles YES to enable the redirection.
|
||||
@param numberOfFiles number of files to keep (default is 10).
|
||||
*/
|
||||
+ (void)redirectNSLogToFiles:(BOOL)redirectNSLogToFiles numberOfFiles:(NSUInteger)numberOfFiles;
|
||||
|
||||
/**
|
||||
Redirect NSLog output to MXLogger files.
|
||||
|
||||
@param redirectNSLogToFiles YES to enable the redirection.
|
||||
@param numberOfFiles number of files to keep (default is 10).
|
||||
@param sizeLimit size limit of log files in bytes. 0 means no limitation, the default value for other methods
|
||||
*/
|
||||
+ (void)redirectNSLogToFiles:(BOOL)redirectNSLogToFiles numberOfFiles:(NSUInteger)numberOfFiles sizeLimit:(NSUInteger)sizeLimit;
|
||||
|
||||
/**
|
||||
Delete all log files.
|
||||
*/
|
||||
+ (void)deleteLogFiles;
|
||||
|
||||
/**
|
||||
Get the list of all log files.
|
||||
|
||||
@return files of
|
||||
*/
|
||||
+ (NSArray<NSString*>*)logFiles;
|
||||
|
||||
/**
|
||||
Make `MXLogger` catch and log unmanaged exceptions or application crashes.
|
||||
|
||||
When such error happens, `MXLogger` stores the application stack trace into a file
|
||||
just before the application leaves. The path of this file is provided by [MXLogger crashLog].
|
||||
|
||||
@param logCrashes YES to enable the catch.
|
||||
*/
|
||||
+ (void)logCrashes:(BOOL)logCrashes;
|
||||
|
||||
/**
|
||||
Set the app build version.
|
||||
It will be reported in crash report.
|
||||
*/
|
||||
+ (void)setBuildVersion:(NSString*)buildVersion;
|
||||
|
||||
/**
|
||||
Set a sub name for namespacing log files.
|
||||
|
||||
A sub name must be set when running from an app extension because extensions can
|
||||
run in parallel to the app.
|
||||
It must be called before `redirectNSLogToFiles`.
|
||||
|
||||
@param subLogName the subname for log files. Files will be named as 'console-[subLogName].log'
|
||||
Default is nil.
|
||||
*/
|
||||
+ (void)setSubLogName:(NSString*)subLogName;
|
||||
|
||||
/**
|
||||
If any, get the file containing the last application crash log.
|
||||
|
||||
Only one crash log is stored at a time. The best moment for the app to handle it is the
|
||||
at its next startup.
|
||||
|
||||
@return the crash log file. nil if there is none.
|
||||
*/
|
||||
+ (NSString*)crashLog;
|
||||
|
||||
/**
|
||||
Delete the crash log file.
|
||||
*/
|
||||
+ (void)deleteCrashLog;
|
||||
|
||||
@end
|
392
ElementX/Sources/Other/Logging/MXLogger.m
Normal file
392
ElementX/Sources/Other/Logging/MXLogger.m
Normal file
@ -0,0 +1,392 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C
|
||||
|
||||
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 "MXLogger.h"
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
// stderr so it can be restored
|
||||
int stderrSave = 0;
|
||||
|
||||
static NSString *buildVersion;
|
||||
static NSString *subLogName;
|
||||
|
||||
#define MXLOGGER_CRASH_LOG @"crash.log"
|
||||
|
||||
@implementation MXLogger
|
||||
|
||||
#pragma mark - NSLog redirection
|
||||
+ (void)redirectNSLogToFiles:(BOOL)redirectNSLogToFiles
|
||||
{
|
||||
[self redirectNSLogToFiles:redirectNSLogToFiles numberOfFiles:10];
|
||||
}
|
||||
|
||||
+ (void)redirectNSLogToFiles:(BOOL)redirectNSLogToFiles numberOfFiles:(NSUInteger)numberOfFiles
|
||||
{
|
||||
[self redirectNSLogToFiles:redirectNSLogToFiles numberOfFiles:numberOfFiles sizeLimit:0];
|
||||
}
|
||||
|
||||
+ (void)redirectNSLogToFiles:(BOOL)redirectNSLogToFiles numberOfFiles:(NSUInteger)numberOfFiles sizeLimit:(NSUInteger)sizeLimit
|
||||
{
|
||||
if (redirectNSLogToFiles)
|
||||
{
|
||||
NSMutableString *log = [NSMutableString string];
|
||||
|
||||
// Default subname
|
||||
if (!subLogName)
|
||||
{
|
||||
subLogName = @"";
|
||||
}
|
||||
|
||||
// Set log location
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSString *logsFolderPath = [MXLogger logsFolderPath];
|
||||
|
||||
// Do a circular buffer based on X files
|
||||
for (NSInteger index = numberOfFiles - 2; index >= 0; index--)
|
||||
{
|
||||
NSString *nsLogPathOlder;
|
||||
NSString *nsLogPathCurrent;
|
||||
|
||||
if (index == 0)
|
||||
{
|
||||
nsLogPathOlder = [NSString stringWithFormat:@"console%@.1.log", subLogName];
|
||||
nsLogPathCurrent = [NSString stringWithFormat:@"console%@.log", subLogName];
|
||||
}
|
||||
else
|
||||
{
|
||||
nsLogPathOlder = [NSString stringWithFormat:@"console%@.%tu.log", subLogName, index + 1];
|
||||
nsLogPathCurrent = [NSString stringWithFormat:@"console%@.%tu.log", subLogName, index];
|
||||
}
|
||||
|
||||
nsLogPathOlder = [logsFolderPath stringByAppendingPathComponent:nsLogPathOlder];
|
||||
nsLogPathCurrent = [logsFolderPath stringByAppendingPathComponent:nsLogPathCurrent];
|
||||
|
||||
if ([fileManager fileExistsAtPath:nsLogPathCurrent])
|
||||
{
|
||||
if ([fileManager fileExistsAtPath:nsLogPathOlder])
|
||||
{
|
||||
// Temp log
|
||||
[log appendFormat:@"[MXLogger] redirectNSLogToFiles: removeItemAtPath: %@\n", nsLogPathOlder];
|
||||
|
||||
NSError *error;
|
||||
[fileManager removeItemAtPath:nsLogPathOlder error:&error];
|
||||
if (error)
|
||||
{
|
||||
[log appendFormat:@"[MXLogger] ERROR: removeItemAtPath: %@. Error: %@\n", nsLogPathOlder, error];
|
||||
}
|
||||
}
|
||||
|
||||
// Temp log
|
||||
[log appendFormat:@"[MXLogger] redirectNSLogToFiles: moveItemAtPath: %@ toPath: %@\n", nsLogPathCurrent, nsLogPathOlder];
|
||||
|
||||
NSError *error;
|
||||
[fileManager moveItemAtPath:nsLogPathCurrent toPath:nsLogPathOlder error:&error];
|
||||
if (error)
|
||||
{
|
||||
[log appendFormat:@"[MXLogger] ERROR: moveItemAtPath: %@ toPath: %@. Error: %@\n", nsLogPathCurrent, nsLogPathOlder, error];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save stderr so it can be restored.
|
||||
stderrSave = dup(STDERR_FILENO);
|
||||
|
||||
NSString *nsLogPath = [logsFolderPath stringByAppendingPathComponent:[NSString stringWithFormat:@"console%@.log", subLogName]];
|
||||
freopen([nsLogPath fileSystemRepresentation], "w+", stderr);
|
||||
|
||||
// MXLogDebug(@"[MXLogger] redirectNSLogToFiles: YES");
|
||||
if (log.length)
|
||||
{
|
||||
// We can now log into files
|
||||
// MXLogDebug(@"%@", log);
|
||||
}
|
||||
|
||||
[self removeExtraFilesFromCount:numberOfFiles];
|
||||
|
||||
if (sizeLimit > 0)
|
||||
{
|
||||
[self removeFilesAfterSizeLimit:sizeLimit];
|
||||
}
|
||||
}
|
||||
else if (stderrSave)
|
||||
{
|
||||
// Flush before restoring stderr
|
||||
fflush(stderr);
|
||||
|
||||
// Now restore stderr, so new output goes to console.
|
||||
dup2(stderrSave, STDERR_FILENO);
|
||||
close(stderrSave);
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)deleteLogFiles
|
||||
{
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
for (NSString *logFile in [self logFiles])
|
||||
{
|
||||
[fileManager removeItemAtPath:logFile error:nil];
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSArray<NSString*>*)logFiles
|
||||
{
|
||||
NSMutableArray *logFiles = [NSMutableArray array];
|
||||
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSString *logsFolderPath = [MXLogger logsFolderPath];
|
||||
|
||||
NSDirectoryEnumerator *dirEnum = [fileManager enumeratorAtPath:logsFolderPath];
|
||||
|
||||
// Find all *.log files
|
||||
NSString *file = nil;
|
||||
while ((file = [dirEnum nextObject]))
|
||||
{
|
||||
if ([[file lastPathComponent] hasPrefix:@"console"])
|
||||
{
|
||||
NSString *logPath = [logsFolderPath stringByAppendingPathComponent:file];
|
||||
[logFiles addObject:logPath];
|
||||
}
|
||||
}
|
||||
|
||||
// MXLogDebug(@"[MXLogger] logFiles: %@", logFiles);
|
||||
|
||||
return logFiles;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Exceptions and crashes
|
||||
// Exceptions uncaught by try catch block are handled here
|
||||
static void handleUncaughtException(NSException *exception)
|
||||
{
|
||||
[MXLogger logCrashes:NO];
|
||||
|
||||
// Extract running app information
|
||||
NSDictionary* infoDict = [[NSBundle mainBundle] infoDictionary];
|
||||
NSString* appVersion;
|
||||
NSString* app, *appId;
|
||||
|
||||
app = infoDict[@"CFBundleExecutable"];
|
||||
appId = infoDict[@"CFBundleIdentifier"];
|
||||
|
||||
if ([infoDict objectForKey:@"CFBundleVersion"])
|
||||
{
|
||||
appVersion = [NSString stringWithFormat:@"%@ (r%@)", [infoDict objectForKey:@"CFBundleShortVersionString"], [infoDict objectForKey:@"CFBundleVersion"]];
|
||||
}
|
||||
else
|
||||
{
|
||||
appVersion = [infoDict objectForKey:@"CFBundleShortVersionString"];
|
||||
}
|
||||
|
||||
// Build the crash log
|
||||
#if TARGET_OS_IPHONE
|
||||
NSString *model = [[UIDevice currentDevice] model];
|
||||
NSString *version = [[UIDevice currentDevice] systemVersion];
|
||||
#elif TARGET_OS_OSX
|
||||
NSString *model = @"Mac";
|
||||
NSString *version = [[NSProcessInfo processInfo] operatingSystemVersionString];
|
||||
#endif
|
||||
NSArray *backtrace = [exception callStackSymbols];
|
||||
NSString *description = [NSString stringWithFormat:@"%.0f - %@\n%@\nApplication: %@ (%@)\nApplication version: %@\nMatrix SDK version: %@\nBuild: %@\n%@ %@\n\nMain thread: %@\n%@\n",
|
||||
[[NSDate date] timeIntervalSince1970],
|
||||
[NSDate date],
|
||||
exception.description,
|
||||
app, appId,
|
||||
appVersion,
|
||||
@"",
|
||||
buildVersion,
|
||||
model, version,
|
||||
[NSThread isMainThread] ? @"YES" : @"NO",
|
||||
backtrace];
|
||||
|
||||
// Write to the crash log file
|
||||
[MXLogger deleteCrashLog];
|
||||
NSString *crashLog = crashLogPath();
|
||||
[description writeToFile:crashLog
|
||||
atomically:NO
|
||||
encoding:NSStringEncodingConversionAllowLossy
|
||||
error:nil];
|
||||
|
||||
NSLog(@"[MXLogger] handleUncaughtException:\n%@", description);
|
||||
// MXLogError(@"[MXLogger] handleUncaughtException:\n%@", description);
|
||||
}
|
||||
|
||||
// Signals emitted by the app are handled here
|
||||
static void handleSignal(int signalValue)
|
||||
{
|
||||
// Throw a custom Objective-C exception
|
||||
// The Objective-C runtime will then be able to build a readable call stack in handleUncaughtException
|
||||
[NSException raise:@"Signal detected" format:@"Signal detected: %d", signalValue];
|
||||
}
|
||||
|
||||
+ (void)logCrashes:(BOOL)logCrashes
|
||||
{
|
||||
if (logCrashes)
|
||||
{
|
||||
// Handle not managed exceptions by ourselves
|
||||
NSSetUncaughtExceptionHandler(&handleUncaughtException);
|
||||
|
||||
// Register signal event (seg fault & cie)
|
||||
signal(SIGABRT, handleSignal);
|
||||
signal(SIGILL, handleSignal);
|
||||
signal(SIGSEGV, handleSignal);
|
||||
signal(SIGFPE, handleSignal);
|
||||
signal(SIGBUS, handleSignal);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Disable crash handling
|
||||
NSSetUncaughtExceptionHandler(NULL);
|
||||
signal(SIGABRT, SIG_DFL);
|
||||
signal(SIGILL, SIG_DFL);
|
||||
signal(SIGSEGV, SIG_DFL);
|
||||
signal(SIGFPE, SIG_DFL);
|
||||
signal(SIGBUS, SIG_DFL);
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)setBuildVersion:(NSString *)theBuildVersion
|
||||
{
|
||||
buildVersion = theBuildVersion;
|
||||
}
|
||||
|
||||
+ (void)setSubLogName:(NSString *)theSubLogName
|
||||
{
|
||||
subLogName = [NSString stringWithFormat:@"-%@", theSubLogName];
|
||||
}
|
||||
|
||||
// Return the path of the crash log file
|
||||
static NSString* crashLogPath(void)
|
||||
{
|
||||
return [[MXLogger logsFolderPath] stringByAppendingPathComponent:MXLOGGER_CRASH_LOG];
|
||||
}
|
||||
|
||||
+ (NSString*)crashLog
|
||||
{
|
||||
NSString *exceptionLog;
|
||||
|
||||
NSString *crashLog = crashLogPath();
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
if([fileManager fileExistsAtPath:crashLog])
|
||||
{
|
||||
exceptionLog = crashLog;
|
||||
}
|
||||
return exceptionLog;
|
||||
}
|
||||
|
||||
+ (void)deleteCrashLog
|
||||
{
|
||||
NSString *crashLog = crashLogPath();
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
if([fileManager fileExistsAtPath:crashLog])
|
||||
{
|
||||
[fileManager removeItemAtPath:crashLog error:nil];
|
||||
}
|
||||
}
|
||||
|
||||
// The folder where logs are stored
|
||||
+ (NSString*)logsFolderPath
|
||||
{
|
||||
NSString *logsFolderPath = nil;
|
||||
|
||||
// NSURL *sharedContainerURL = [[NSFileManager defaultManager] applicationGroupContainerURL];
|
||||
// if (sharedContainerURL)
|
||||
// {
|
||||
// logsFolderPath = [sharedContainerURL path];
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
NSArray<NSURL *> *paths = [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
|
||||
logsFolderPath = paths[0].path;
|
||||
// }
|
||||
|
||||
return logsFolderPath;
|
||||
}
|
||||
|
||||
|
||||
// If [self redirectNSLogToFiles: numberOfFiles:] is called with a lower numberOfFiles we need to do some cleanup
|
||||
+ (void)removeExtraFilesFromCount:(NSUInteger)count
|
||||
{
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSString *logsFolderPath = [MXLogger logsFolderPath];
|
||||
|
||||
NSUInteger index = count;
|
||||
do
|
||||
{
|
||||
NSString *fileName = [NSString stringWithFormat:@"console%@.%tu.log", subLogName, index];
|
||||
NSString *logFile = [logsFolderPath stringByAppendingPathComponent:fileName];
|
||||
|
||||
if ([fileManager fileExistsAtPath:logFile])
|
||||
{
|
||||
[fileManager removeItemAtPath:logFile error:nil];
|
||||
// MXLogDebug(@"[MXLogger] removeExtraFilesFromCount: %@. removeItemAtPath: %@\n", @(count), logFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (index++);
|
||||
}
|
||||
|
||||
+ (void)removeFilesAfterSizeLimit:(NSUInteger)sizeLimit
|
||||
{
|
||||
NSUInteger logSize = 0;
|
||||
BOOL removeFiles = NO;
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSString *logsFolderPath = [MXLogger logsFolderPath];
|
||||
|
||||
// Start from console.1.log. Do not consider console.log. It should be almost empty
|
||||
NSUInteger index = 0;
|
||||
while (++index)
|
||||
{
|
||||
NSString *fileName = [NSString stringWithFormat:@"console%@.%tu.log", subLogName, index];
|
||||
NSString *logFile = [logsFolderPath stringByAppendingPathComponent:fileName];
|
||||
|
||||
if ([fileManager fileExistsAtPath:logFile])
|
||||
{
|
||||
logSize += [fileManager attributesOfItemAtPath:logFile error:nil].fileSize;
|
||||
|
||||
if (logSize >= sizeLimit)
|
||||
{
|
||||
removeFiles = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (removeFiles)
|
||||
{
|
||||
// MXLogDebug(@"[MXLogger] removeFilesAfterSizeLimit: Remove files from index %@ because logs are too large (%@ for a limit of %@)\n",
|
||||
// @(index),
|
||||
// [NSByteCountFormatter stringFromByteCount:logSize countStyle:NSByteCountFormatterCountStyleBinary],
|
||||
// [NSByteCountFormatter stringFromByteCount:sizeLimit countStyle:NSByteCountFormatterCountStyleBinary]);
|
||||
[self removeExtraFilesFromCount:index];
|
||||
}
|
||||
else
|
||||
{
|
||||
// MXLogDebug(@"[MXLogger] removeFilesAfterSizeLimit: No need: %@ for a limit of %@\n",
|
||||
// [NSByteCountFormatter stringFromByteCount:logSize countStyle:NSByteCountFormatterCountStyleBinary],
|
||||
// [NSByteCountFormatter stringFromByteCount:sizeLimit countStyle:NSByteCountFormatterCountStyleBinary]);
|
||||
}
|
||||
|
||||
}
|
||||
@end
|
||||
|
@ -0,0 +1,23 @@
|
||||
//
|
||||
// ElementToggleStyle.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Ismail on 2.06.2022.
|
||||
// Copyright © 2022 Element. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
/// A toggle style that uses a button, with a checked/unchecked image like a checkbox.
|
||||
struct ElementToggleStyle: ToggleStyle {
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
Button { configuration.isOn.toggle() } label: {
|
||||
Image(systemName: configuration.isOn ? "checkmark.square.fill" : "square")
|
||||
.font(.title3.weight(.regular))
|
||||
.imageScale(.large)
|
||||
.foregroundColor(Color(uiColor: Asset.Colors.elementGreen.color))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
@ -103,6 +103,9 @@ class RoundedToastView: UIView {
|
||||
case .success:
|
||||
imageView.image = UIImage(systemName: "checkmark.circle")
|
||||
return imageView
|
||||
case .error:
|
||||
imageView.image = UIImage(systemName: "x.circle")
|
||||
return imageView
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ struct ToastViewState {
|
||||
enum Style {
|
||||
case loading
|
||||
case success
|
||||
case error
|
||||
}
|
||||
|
||||
let style: Style
|
||||
|
@ -20,6 +20,7 @@ import UIKit
|
||||
enum UserIndicatorType {
|
||||
case loading(label: String, isInteractionBlocking: Bool)
|
||||
case success(label: String)
|
||||
case error(label: String)
|
||||
}
|
||||
|
||||
/// A presenter which can handle `UserIndicatorType` by creating the underlying `UserIndicator`
|
||||
@ -72,6 +73,8 @@ class UserIndicatorTypePresenter: UserIndicatorTypePresenterProtocol {
|
||||
}
|
||||
case .success(let label):
|
||||
return successRequest(label: label)
|
||||
case .error(let label):
|
||||
return errorRequest(label: label)
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,4 +116,18 @@ class UserIndicatorTypePresenter: UserIndicatorTypePresenterProtocol {
|
||||
dismissal: .timeout(1.5)
|
||||
)
|
||||
}
|
||||
|
||||
private func errorRequest(label: String) -> UserIndicatorRequest {
|
||||
let presenter = ToastViewPresenter(
|
||||
viewState: .init(
|
||||
style: .error,
|
||||
label: label
|
||||
),
|
||||
presentationContext: presentationContext
|
||||
)
|
||||
return UserIndicatorRequest(
|
||||
presenter: presenter,
|
||||
dismissal: .timeout(1.5)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
109
ElementX/Sources/Screens/BugReport/BugReportCoordinator.swift
Normal file
109
ElementX/Sources/Screens/BugReport/BugReportCoordinator.swift
Normal file
@ -0,0 +1,109 @@
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct BugReportCoordinatorParameters {
|
||||
let bugReportService: BugReportServiceProtocol
|
||||
let screenshot: UIImage?
|
||||
}
|
||||
|
||||
final class BugReportCoordinator: Coordinator, Presentable {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let parameters: BugReportCoordinatorParameters
|
||||
private let bugReportHostingController: UIViewController
|
||||
private var bugReportViewModel: BugReportViewModelProtocol
|
||||
|
||||
private var indicatorPresenter: UserIndicatorTypePresenterProtocol
|
||||
private var loadingIndicator: UserIndicator?
|
||||
private var errorIndicator: UserIndicator?
|
||||
|
||||
// MARK: Public
|
||||
|
||||
// Must be used only internally
|
||||
var childCoordinators: [Coordinator] = []
|
||||
var completion: (() -> Void)?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(parameters: BugReportCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
|
||||
let viewModel = BugReportViewModel(bugReportService: parameters.bugReportService,
|
||||
screenshot: parameters.screenshot)
|
||||
let view = BugReport(context: viewModel.context)
|
||||
bugReportViewModel = viewModel
|
||||
bugReportHostingController = UIHostingController(rootView: view)
|
||||
|
||||
indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: bugReportHostingController)
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func start() {
|
||||
MXLog.debug("[BugReportCoordinator] did start.")
|
||||
bugReportViewModel.callback = { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
MXLog.debug("[BugReportCoordinator] BugReportViewModel did complete with result: \(result).")
|
||||
switch result {
|
||||
case .submitStarted:
|
||||
self.startLoading()
|
||||
case .submitFinished:
|
||||
self.stopLoading()
|
||||
self.showSuccess(label: ElementL10n.done)
|
||||
case .submitFailed(let error):
|
||||
self.stopLoading()
|
||||
self.showError(label: error.localizedDescription)
|
||||
case .cancel:
|
||||
self.completion?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func toPresentable() -> UIViewController {
|
||||
bugReportHostingController
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
/// Show an activity indicator whilst loading.
|
||||
/// - Parameters:
|
||||
/// - label: The label to show on the indicator.
|
||||
/// - isInteractionBlocking: Whether the indicator should block any user interaction.
|
||||
private func startLoading(label: String = ElementL10n.loading, isInteractionBlocking: Bool = true) {
|
||||
loadingIndicator = indicatorPresenter.present(.loading(label: label,
|
||||
isInteractionBlocking: isInteractionBlocking))
|
||||
}
|
||||
|
||||
/// Hide the currently displayed activity indicator.
|
||||
private func stopLoading() {
|
||||
loadingIndicator = nil
|
||||
}
|
||||
|
||||
/// Show success indicator
|
||||
private func showSuccess(label: String) {
|
||||
errorIndicator = indicatorPresenter.present(.success(label: label))
|
||||
}
|
||||
|
||||
/// Show error indicator
|
||||
private func showError(label: String) {
|
||||
errorIndicator = indicatorPresenter.present(.error(label: label))
|
||||
}
|
||||
}
|
48
ElementX/Sources/Screens/BugReport/BugReportModels.swift
Normal file
48
ElementX/Sources/Screens/BugReport/BugReportModels.swift
Normal file
@ -0,0 +1,48 @@
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
// MARK: - Coordinator
|
||||
|
||||
// MARK: View model
|
||||
|
||||
enum BugReportViewModelAction {
|
||||
case submitStarted
|
||||
case submitFinished
|
||||
case submitFailed(error: Error)
|
||||
case cancel
|
||||
}
|
||||
|
||||
// MARK: View
|
||||
|
||||
struct BugReportViewState: BindableState {
|
||||
var screenshot: UIImage?
|
||||
var bindings: BugReportViewStateBindings
|
||||
}
|
||||
|
||||
struct BugReportViewStateBindings {
|
||||
var reportText: String
|
||||
var sendingLogsEnabled: Bool
|
||||
}
|
||||
|
||||
enum BugReportViewAction {
|
||||
case submit
|
||||
case cancel
|
||||
case toggleSendLogs
|
||||
case removeScreenshot
|
||||
}
|
87
ElementX/Sources/Screens/BugReport/BugReportViewModel.swift
Normal file
87
ElementX/Sources/Screens/BugReport/BugReportViewModel.swift
Normal file
@ -0,0 +1,87 @@
|
||||
//
|
||||
// Copyright 2021 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
|
||||
|
||||
@available(iOS 14, *)
|
||||
typealias BugReportViewModelType = StateStoreViewModel<BugReportViewState,
|
||||
BugReportViewAction>
|
||||
@available(iOS 14, *)
|
||||
class BugReportViewModel: BugReportViewModelType, BugReportViewModelProtocol {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
let bugReportService: BugReportServiceProtocol
|
||||
|
||||
// MARK: Private
|
||||
|
||||
func submitBugReport() async {
|
||||
callback?(.submitStarted)
|
||||
do {
|
||||
var files: [URL] = []
|
||||
if let screenshot = state.screenshot {
|
||||
let anonymized = try await ImageAnonymizer.anonymizedImage(from: screenshot)
|
||||
let tmpUrl = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("screenshot").appendingPathExtension("png")
|
||||
// remove old screenshot if exists
|
||||
if FileManager.default.fileExists(atPath: tmpUrl.path) {
|
||||
try FileManager.default.removeItem(at: tmpUrl)
|
||||
}
|
||||
try anonymized.dataForPNGRepresentation().write(to: tmpUrl)
|
||||
files.append(tmpUrl)
|
||||
}
|
||||
|
||||
let result = try await bugReportService.submitBugReport(text: context.reportText,
|
||||
includeLogs: context.sendingLogsEnabled,
|
||||
includeCrashLog: true,
|
||||
githubLabels: [],
|
||||
files: files)
|
||||
MXLog.info("[BugReportViewModel] submitBugReport succeeded, result: \(result.reportUrl)")
|
||||
callback?(.submitFinished)
|
||||
} catch let error {
|
||||
MXLog.error("[BugReportViewModel] submitBugReport failed: \(error)")
|
||||
callback?(.submitFailed(error: error))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Public
|
||||
|
||||
var callback: ((BugReportViewModelAction) -> Void)?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(bugReportService: BugReportServiceProtocol,
|
||||
screenshot: UIImage?) {
|
||||
self.bugReportService = bugReportService
|
||||
let bindings = BugReportViewStateBindings(reportText: "", sendingLogsEnabled: true)
|
||||
super.init(initialViewState: BugReportViewState(screenshot: screenshot,
|
||||
bindings: bindings))
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: BugReportViewAction) async {
|
||||
switch viewAction {
|
||||
case .submit:
|
||||
await submitBugReport()
|
||||
case .cancel:
|
||||
callback?(.cancel)
|
||||
case .toggleSendLogs:
|
||||
context.sendingLogsEnabled.toggle()
|
||||
case .removeScreenshot:
|
||||
state.screenshot = nil
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@MainActor
|
||||
protocol BugReportViewModelProtocol {
|
||||
|
||||
var callback: ((BugReportViewModelAction) -> Void)? { get set }
|
||||
@available(iOS 14, *)
|
||||
var context: BugReportViewModelType.Context { get }
|
||||
}
|
140
ElementX/Sources/Screens/BugReport/View/BugReport.swift
Normal file
140
ElementX/Sources/Screens/BugReport/View/BugReport.swift
Normal file
@ -0,0 +1,140 @@
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct BugReport: View {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||
|
||||
private var horizontalPadding: CGFloat {
|
||||
horizontalSizeClass == .regular ? 50 : 16
|
||||
}
|
||||
|
||||
// MARK: Public
|
||||
|
||||
@ObservedObject var context: BugReportViewModel.Context
|
||||
|
||||
// MARK: Views
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
VStack {
|
||||
ScrollView {
|
||||
mainContent
|
||||
.padding(.top, 50)
|
||||
.padding(.horizontal, horizontalPadding)
|
||||
}
|
||||
.introspectScrollView { scrollView in
|
||||
scrollView.keyboardDismissMode = .onDrag
|
||||
}
|
||||
|
||||
buttons
|
||||
.padding(.horizontal, horizontalPadding)
|
||||
.padding(.bottom, geometry.safeAreaInsets.bottom > 0 ? 0 : 16)
|
||||
}
|
||||
.navigationTitle(ElementL10n.titleActivityBugReport)
|
||||
}
|
||||
}
|
||||
|
||||
/// The main content of the view to be shown in a scroll view.
|
||||
var mainContent: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text(ElementL10n.sendBugReportDescription)
|
||||
.accessibilityIdentifier("reportBugDescription")
|
||||
ZStack(alignment: .topLeading) {
|
||||
RoundedRectangle(cornerRadius: 8, style: .continuous)
|
||||
.fill(Color(UIColor.secondarySystemBackground))
|
||||
|
||||
if context.reportText.isEmpty {
|
||||
Text(ElementL10n.sendBugReportPlaceholder)
|
||||
.foregroundColor(Color(UIColor.placeholderText))
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 12)
|
||||
}
|
||||
TextEditor(text: $context.reportText)
|
||||
.padding(4)
|
||||
.background(Color.clear)
|
||||
.cornerRadius(8)
|
||||
.accessibilityIdentifier("reportTextView")
|
||||
.introspectTextView { textView in
|
||||
textView.backgroundColor = .clear
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 300)
|
||||
.font(.body)
|
||||
Text(ElementL10n.sendBugReportLogsDescription)
|
||||
.accessibilityIdentifier("sendLogsDescription")
|
||||
HStack(spacing: 12) {
|
||||
Toggle(ElementL10n.sendBugReportIncludeLogs, isOn: $context.sendingLogsEnabled)
|
||||
.toggleStyle(ElementToggleStyle())
|
||||
.accessibilityIdentifier("sendLogsToggle")
|
||||
Text(ElementL10n.sendBugReportIncludeLogs).accessibilityIdentifier("sendLogsText")
|
||||
}
|
||||
.onTapGesture {
|
||||
context.send(viewAction: .toggleSendLogs)
|
||||
}
|
||||
screenshot
|
||||
}
|
||||
}
|
||||
|
||||
/// The action buttons shown at the bottom of the view.
|
||||
var buttons: some View {
|
||||
VStack {
|
||||
Button { context.send(viewAction: .submit) } label: {
|
||||
Text(ElementL10n.actionSend)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 16)
|
||||
.disabled(context.reportText.count < 5)
|
||||
.accessibilityIdentifier("sendButton")
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var screenshot: some View {
|
||||
if let screenshot = context.viewState.screenshot {
|
||||
ZStack(alignment: .topTrailing) {
|
||||
Image(uiImage: screenshot)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 100)
|
||||
.accessibilityIdentifier("screenshotImage")
|
||||
Button { context.send(viewAction: .removeScreenshot) } label: {
|
||||
Image(uiImage: Asset.Images.closeCircle.image)
|
||||
}
|
||||
.offset(x: 10, y: -10)
|
||||
.accessibilityIdentifier("removeScreenshotButton")
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct BugReport_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
let viewModel = BugReportViewModel(bugReportService: MockBugReportService(), screenshot: Asset.Images.appLogo.image)
|
||||
BugReport(context: viewModel.context)
|
||||
.previewInterfaceOrientation(.portrait)
|
||||
}
|
||||
}
|
||||
}
|
@ -25,7 +25,8 @@ struct HomeScreenCoordinatorParameters {
|
||||
|
||||
enum HomeScreenCoordinatorAction {
|
||||
case logout
|
||||
case selectRoom(roomIdentifier: String)
|
||||
case presentRoom(roomIdentifier: String)
|
||||
case presentSettings
|
||||
}
|
||||
|
||||
final class HomeScreenCoordinator: Coordinator, Presentable {
|
||||
@ -65,7 +66,9 @@ final class HomeScreenCoordinator: Coordinator, Presentable {
|
||||
case .logout:
|
||||
self.callback?(.logout)
|
||||
case .selectRoom(let roomIdentifier):
|
||||
self.callback?(.selectRoom(roomIdentifier: roomIdentifier))
|
||||
self.callback?(.presentRoom(roomIdentifier: roomIdentifier))
|
||||
case .tapUserAvatar:
|
||||
self.callback?(.presentSettings)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,12 +20,14 @@ import UIKit
|
||||
enum HomeScreenViewModelAction {
|
||||
case logout
|
||||
case selectRoom(roomIdentifier: String)
|
||||
case tapUserAvatar
|
||||
}
|
||||
|
||||
enum HomeScreenViewAction {
|
||||
case logout
|
||||
case loadRoomData(roomIdentifier: String)
|
||||
case selectRoom(roomIdentifier: String)
|
||||
case tapUserAvatar
|
||||
}
|
||||
|
||||
struct HomeScreenViewState: BindableState {
|
||||
|
@ -51,6 +51,8 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
loadRoomDataForIdentifier(roomIdentifier)
|
||||
case .selectRoom(let roomIdentifier):
|
||||
callback?(.selectRoom(roomIdentifier: roomIdentifier))
|
||||
case .tapUserAvatar:
|
||||
callback?(.tapUserAvatar)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,11 +75,13 @@ struct HomeScreen: View {
|
||||
HStack {
|
||||
ZStack {
|
||||
if let avatar = context.viewState.userAvatar {
|
||||
Image(uiImage: avatar)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 40, height: 40, alignment: .center)
|
||||
.mask(Circle())
|
||||
Button { context.send(viewAction: .tapUserAvatar) } label: {
|
||||
Image(uiImage: avatar)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 40, height: 40, alignment: .center)
|
||||
.mask(Circle())
|
||||
}
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
@ -89,9 +91,12 @@ struct HomeScreen: View {
|
||||
|
||||
ZStack {
|
||||
if let displayName = context.viewState.userDisplayName {
|
||||
Text("Hello, \(displayName)!")
|
||||
.font(.subheadline)
|
||||
.fontWeight(.bold)
|
||||
Button { context.send(viewAction: .tapUserAvatar) } label: {
|
||||
Text("Hello, \(displayName)!")
|
||||
.font(.subheadline)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(.black)
|
||||
}
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
|
109
ElementX/Sources/Screens/Settings/SettingsCoordinator.swift
Normal file
109
ElementX/Sources/Screens/Settings/SettingsCoordinator.swift
Normal file
@ -0,0 +1,109 @@
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsCoordinatorParameters {
|
||||
let navigationRouter: NavigationRouterType
|
||||
let bugReportService: BugReportServiceProtocol
|
||||
}
|
||||
|
||||
final class SettingsCoordinator: Coordinator, Presentable {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let parameters: SettingsCoordinatorParameters
|
||||
private let settingsHostingController: UIViewController
|
||||
private var settingsViewModel: SettingsViewModelProtocol
|
||||
|
||||
private var indicatorPresenter: UserIndicatorTypePresenterProtocol
|
||||
private var loadingIndicator: UserIndicator?
|
||||
|
||||
// MARK: Public
|
||||
|
||||
// Must be used only internally
|
||||
var childCoordinators: [Coordinator] = []
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(parameters: SettingsCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
|
||||
let viewModel = SettingsViewModel()
|
||||
let view = Settings(context: viewModel.context)
|
||||
settingsViewModel = viewModel
|
||||
settingsHostingController = UIHostingController(rootView: view)
|
||||
|
||||
indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: settingsHostingController)
|
||||
|
||||
settingsViewModel.callback = { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
MXLog.debug("[SettingsCoordinator] SettingsViewModel did complete with result: \(result).")
|
||||
switch result {
|
||||
case .reportBug:
|
||||
self.presentBugReportScreen()
|
||||
case .crash:
|
||||
self.parameters.bugReportService.crash()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func start() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
func toPresentable() -> UIViewController {
|
||||
settingsHostingController
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
/// Show an activity indicator whilst loading.
|
||||
/// - Parameters:
|
||||
/// - label: The label to show on the indicator.
|
||||
/// - isInteractionBlocking: Whether the indicator should block any user interaction.
|
||||
private func startLoading(label: String = ElementL10n.loading, isInteractionBlocking: Bool = true) {
|
||||
loadingIndicator = indicatorPresenter.present(.loading(label: label, isInteractionBlocking: isInteractionBlocking))
|
||||
}
|
||||
|
||||
/// Hide the currently displayed activity indicator.
|
||||
private func stopLoading() {
|
||||
loadingIndicator = nil
|
||||
}
|
||||
|
||||
private func presentBugReportScreen() {
|
||||
let params = BugReportCoordinatorParameters(bugReportService: parameters.bugReportService,
|
||||
screenshot: nil)
|
||||
let coordinator = BugReportCoordinator(parameters: params)
|
||||
coordinator.completion = { [weak self, weak coordinator] in
|
||||
guard let self = self, let coordinator = coordinator else { return }
|
||||
self.parameters.navigationRouter.popModule(animated: true)
|
||||
self.remove(childCoordinator: coordinator)
|
||||
}
|
||||
|
||||
add(childCoordinator: coordinator)
|
||||
coordinator.start()
|
||||
self.parameters.navigationRouter.push(coordinator, animated: true) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.remove(childCoordinator: coordinator)
|
||||
}
|
||||
}
|
||||
}
|
42
ElementX/Sources/Screens/Settings/SettingsModels.swift
Normal file
42
ElementX/Sources/Screens/Settings/SettingsModels.swift
Normal file
@ -0,0 +1,42 @@
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// MARK: - Coordinator
|
||||
|
||||
// MARK: View model
|
||||
|
||||
enum SettingsViewModelAction {
|
||||
case reportBug
|
||||
case crash
|
||||
}
|
||||
|
||||
// MARK: View
|
||||
|
||||
struct SettingsViewState: BindableState {
|
||||
var crashButtonVisible: Bool
|
||||
var bindings: SettingsViewStateBindings
|
||||
}
|
||||
|
||||
struct SettingsViewStateBindings {
|
||||
|
||||
}
|
||||
|
||||
enum SettingsViewAction {
|
||||
case reportBug
|
||||
case crash
|
||||
}
|
50
ElementX/Sources/Screens/Settings/SettingsViewModel.swift
Normal file
50
ElementX/Sources/Screens/Settings/SettingsViewModel.swift
Normal file
@ -0,0 +1,50 @@
|
||||
//
|
||||
// Copyright 2021 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
|
||||
|
||||
@available(iOS 14, *)
|
||||
typealias SettingsViewModelType = StateStoreViewModel<SettingsViewState,
|
||||
SettingsViewAction>
|
||||
@available(iOS 14, *)
|
||||
class SettingsViewModel: SettingsViewModelType, SettingsViewModelProtocol {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
// MARK: Public
|
||||
|
||||
var callback: ((SettingsViewModelAction) -> Void)?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init() {
|
||||
let bindings = SettingsViewStateBindings()
|
||||
super.init(initialViewState: .init(crashButtonVisible: true, bindings: bindings))
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: SettingsViewAction) async {
|
||||
switch viewAction {
|
||||
case .reportBug:
|
||||
callback?(.reportBug)
|
||||
case .crash:
|
||||
callback?(.crash)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@MainActor
|
||||
protocol SettingsViewModelProtocol {
|
||||
|
||||
var callback: ((SettingsViewModelAction) -> Void)? { get set }
|
||||
@available(iOS 14, *)
|
||||
var context: SettingsViewModelType.Context { get }
|
||||
}
|
61
ElementX/Sources/Screens/Settings/View/Settings.swift
Normal file
61
ElementX/Sources/Screens/Settings/View/Settings.swift
Normal file
@ -0,0 +1,61 @@
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct Settings: View {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
// MARK: Public
|
||||
|
||||
@ObservedObject var context: SettingsViewModel.Context
|
||||
|
||||
// MARK: Views
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Button { context.send(viewAction: .reportBug) } label: {
|
||||
Text(ElementL10n.sendBugReport)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 16)
|
||||
.accessibilityIdentifier("reportBugButton")
|
||||
|
||||
if context.viewState.crashButtonVisible {
|
||||
Button { context.send(viewAction: .crash) } label: {
|
||||
Text("Crash the app")
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 16)
|
||||
.accessibilityIdentifier("crashButton")
|
||||
}
|
||||
}
|
||||
.navigationTitle(ElementL10n.settings)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct Settings_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
let viewModel = SettingsViewModel()
|
||||
Settings(context: viewModel.context)
|
||||
.previewInterfaceOrientation(.portrait)
|
||||
}
|
||||
}
|
||||
}
|
223
ElementX/Sources/Services/BugReport/BugReportService.swift
Normal file
223
ElementX/Sources/Services/BugReport/BugReportService.swift
Normal file
@ -0,0 +1,223 @@
|
||||
//
|
||||
// BugReportService.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Ismail on 16.05.2022.
|
||||
// Copyright © 2022 element.io. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
import UIKit
|
||||
import GZIP
|
||||
import Sentry
|
||||
|
||||
enum BugReportServiceError: Error {
|
||||
case invalidBaseUrlString
|
||||
case invalidSentryEndpoint
|
||||
}
|
||||
|
||||
class BugReportService: BugReportServiceProtocol {
|
||||
private let baseURL: URL
|
||||
private let sentryEndpoint: String
|
||||
private let applicationId: String
|
||||
private let session: URLSession
|
||||
|
||||
init(withBaseUrlString baseUrlString: String,
|
||||
sentryEndpoint: String,
|
||||
applicationId: String = BuildSettings.bugReportApplicationId,
|
||||
session: URLSession = .shared) throws {
|
||||
guard let url = URL(string: baseUrlString) else {
|
||||
throw BugReportServiceError.invalidBaseUrlString
|
||||
}
|
||||
guard !sentryEndpoint.isEmpty else {
|
||||
throw BugReportServiceError.invalidSentryEndpoint
|
||||
}
|
||||
self.baseURL = url
|
||||
self.sentryEndpoint = sentryEndpoint
|
||||
self.applicationId = applicationId
|
||||
self.session = session
|
||||
|
||||
// enable SentrySDK
|
||||
SentrySDK.start { options in
|
||||
options.dsn = sentryEndpoint
|
||||
#if DEBUG
|
||||
options.debug = true
|
||||
#endif
|
||||
|
||||
// Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
|
||||
// We recommend adjusting this value in production.
|
||||
options.tracesSampleRate = 1.0
|
||||
|
||||
options.beforeSend = { event in
|
||||
MXLog.error("Sentry detected crash: \(event)")
|
||||
return event
|
||||
}
|
||||
|
||||
options.onCrashedLastRun = { event in
|
||||
MXLog.debug("Sentry detected application was crashed: \(event)")
|
||||
}
|
||||
}
|
||||
|
||||
// also enable logging crashes, to send them with bug reports
|
||||
MXLogger.logCrashes(true)
|
||||
// set build version for logger
|
||||
MXLogger.setBuildVersion(ElementInfoPlist.cfBundleShortVersionString)
|
||||
}
|
||||
|
||||
// MARK: - BugReportServiceProtocol
|
||||
|
||||
var crashedLastRun: Bool {
|
||||
return SentrySDK.crashedLastRun
|
||||
}
|
||||
|
||||
func crash() {
|
||||
SentrySDK.crash()
|
||||
}
|
||||
|
||||
func submitBugReport(text: String,
|
||||
includeLogs: Bool,
|
||||
includeCrashLog: Bool,
|
||||
githubLabels: [String],
|
||||
files: [URL]) async throws -> SubmitBugReportResponse {
|
||||
MXLog.debug("[BugReportService] submitBugReport")
|
||||
|
||||
var params = [
|
||||
MultipartFormData(key: "text", type: .text(value: text))
|
||||
]
|
||||
params.append(contentsOf: defaultParams)
|
||||
for label in githubLabels {
|
||||
params.append(MultipartFormData(key: "label", type: .text(value: label)))
|
||||
}
|
||||
let zippedFiles = try await zipFiles(includeLogs: includeLogs,
|
||||
includeCrashLog: includeCrashLog)
|
||||
// log or compressed-log
|
||||
if !zippedFiles.isEmpty {
|
||||
for url in zippedFiles {
|
||||
params.append(MultipartFormData(key: "compressed-log", type: .file(url: url)))
|
||||
}
|
||||
}
|
||||
for url in files {
|
||||
params.append(MultipartFormData(key: "file", type: .file(url: url)))
|
||||
}
|
||||
|
||||
let boundary = "Boundary-\(UUID().uuidString)"
|
||||
var body = Data()
|
||||
for param in params {
|
||||
body.appendString(string: "--\(boundary)\r\n")
|
||||
body.appendString(string: "Content-Disposition:form-data; name=\"\(param.key)\"")
|
||||
switch param.type {
|
||||
case .text(let value):
|
||||
body.appendString(string: "\r\n\r\n\(value)\r\n")
|
||||
case .file(let url):
|
||||
body.appendString(string: "; filename=\"\(url.lastPathComponent)\"\r\n")
|
||||
body.appendString(string: "Content-Type: \"content-type header\"\r\n\r\n")
|
||||
body.append(try Data(contentsOf: url))
|
||||
body.appendString(string: "\r\n")
|
||||
}
|
||||
}
|
||||
body.appendString(string: "--\(boundary)--\r\n")
|
||||
|
||||
var request = URLRequest(url: baseURL.appendingPathComponent("submit"))
|
||||
request.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
|
||||
|
||||
request.httpMethod = "POST"
|
||||
request.httpBody = body as Data
|
||||
|
||||
let (data, _) = try await session.data(for: request)
|
||||
|
||||
// Parse the JSON data
|
||||
let decoder = JSONDecoder()
|
||||
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||
let result = try decoder.decode(SubmitBugReportResponse.self, from: data)
|
||||
|
||||
if !result.reportUrl.isEmpty {
|
||||
MXLogger.deleteCrashLog()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private var defaultParams: [MultipartFormData] {
|
||||
[
|
||||
MultipartFormData(key: "user_agent", type: .text(value: "iOS")),
|
||||
MultipartFormData(key: "app", type: .text(value: applicationId)),
|
||||
MultipartFormData(key: "version", type: .text(value: version)),
|
||||
MultipartFormData(key: "os", type: .text(value: os)),
|
||||
MultipartFormData(key: "client", type: .text(value: "Element-X"))
|
||||
]
|
||||
}
|
||||
|
||||
private var os: String {
|
||||
"\(UIDevice.current.systemName) \(UIDevice.current.systemVersion)"
|
||||
}
|
||||
|
||||
private var version: String {
|
||||
ElementInfoPlist.cfBundleShortVersionString
|
||||
}
|
||||
|
||||
private func zipFiles(includeLogs: Bool,
|
||||
includeCrashLog: Bool) async throws -> [URL] {
|
||||
MXLog.debug("[BugReportService] zipFiles: includeLogs: \(includeLogs), includeCrashLog: \(includeCrashLog)")
|
||||
|
||||
var filesToCompress: [URL] = []
|
||||
if includeLogs, let logFiles = MXLogger.logFiles() {
|
||||
let urls = logFiles.compactMap { URL(fileURLWithPath: $0) }
|
||||
filesToCompress.append(contentsOf: urls)
|
||||
}
|
||||
if includeCrashLog, let crashLogFile = MXLogger.crashLog() {
|
||||
filesToCompress.append(URL(fileURLWithPath: crashLogFile))
|
||||
}
|
||||
|
||||
var totalSize: Int = 0
|
||||
var totalZippedSize: Int = 0
|
||||
var zippedFiles: [URL] = []
|
||||
|
||||
for url in filesToCompress {
|
||||
let zippedFileURL = URL(fileURLWithPath: NSTemporaryDirectory())
|
||||
.appendingPathComponent(url.lastPathComponent)
|
||||
|
||||
// remove old zipped file if exists
|
||||
try? FileManager.default.removeItem(at: zippedFileURL)
|
||||
|
||||
let rawData = try Data(contentsOf: url)
|
||||
if rawData.isEmpty {
|
||||
continue
|
||||
}
|
||||
guard let zippedData = (rawData as NSData).gzipped() else {
|
||||
continue
|
||||
}
|
||||
|
||||
totalSize += rawData.count
|
||||
totalZippedSize += zippedData.count
|
||||
|
||||
try zippedData.write(to: zippedFileURL)
|
||||
|
||||
zippedFiles.append(zippedFileURL)
|
||||
}
|
||||
|
||||
MXLog.debug("[BugReportService] zipFiles: totalSize: \(totalSize), totalZippedSize: \(totalZippedSize)")
|
||||
|
||||
return zippedFiles
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension Data {
|
||||
mutating func appendString(string: String, encoding: String.Encoding = .utf8) {
|
||||
if let data = string.data(using: encoding) {
|
||||
append(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct MultipartFormData {
|
||||
let key: String
|
||||
let type: MultipartFormDataType
|
||||
}
|
||||
|
||||
private enum MultipartFormDataType {
|
||||
case text(value: String)
|
||||
case file(url: URL)
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
//
|
||||
// BugReportServiceProtocol.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Ismail on 16.05.2022.
|
||||
// Copyright © 2022 element.io. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
struct SubmitBugReportResponse: Decodable {
|
||||
var reportUrl: String
|
||||
}
|
||||
|
||||
protocol BugReportServiceProtocol {
|
||||
|
||||
var crashedLastRun: Bool { get }
|
||||
|
||||
func crash()
|
||||
|
||||
func submitBugReport(text: String,
|
||||
includeLogs: Bool,
|
||||
includeCrashLog: Bool,
|
||||
githubLabels: [String],
|
||||
files: [URL]) async throws -> SubmitBugReportResponse
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
//
|
||||
// MockBugReportService.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Ismail on 16.05.2022.
|
||||
// Copyright © 2022 element.io. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
class MockBugReportService: BugReportServiceProtocol {
|
||||
|
||||
func submitBugReport(text: String,
|
||||
includeLogs: Bool,
|
||||
includeCrashLog: Bool,
|
||||
githubLabels: [String],
|
||||
files: [URL]) async throws -> SubmitBugReportResponse {
|
||||
return SubmitBugReportResponse(reportUrl: "https://www.example/com/123")
|
||||
}
|
||||
|
||||
var crashedLastRun: Bool = false
|
||||
|
||||
func crash() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
}
|
110
ElementX/Sources/Services/BugReport/ScreenshotDetector.swift
Normal file
110
ElementX/Sources/Services/BugReport/ScreenshotDetector.swift
Normal file
@ -0,0 +1,110 @@
|
||||
//
|
||||
// ScreenshotObserver.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Ismail on 31.05.2022.
|
||||
// Copyright © 2022 element.io. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Photos
|
||||
|
||||
enum ScreenshotDetectorError: String, Error {
|
||||
case loadFailed
|
||||
case notAuthorized
|
||||
}
|
||||
|
||||
@MainActor
|
||||
class ScreenshotDetector {
|
||||
|
||||
var callback: (@MainActor (UIImage?, Error?) -> Void)?
|
||||
|
||||
/// Flag to whether ask for photos authorization by default if needed.
|
||||
var autoRequestPHAuthorization = true
|
||||
|
||||
init() {
|
||||
startObservingScreenshots()
|
||||
}
|
||||
|
||||
private func startObservingScreenshots() {
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(userDidTakeScreenshot),
|
||||
name: UIApplication.userDidTakeScreenshotNotification,
|
||||
object: nil)
|
||||
}
|
||||
|
||||
@objc private func userDidTakeScreenshot() {
|
||||
let authStatus = PHPhotoLibrary.authorizationStatus(for: .readWrite)
|
||||
if authStatus == .authorized {
|
||||
findScreenshot()
|
||||
} else if authStatus == .notDetermined && autoRequestPHAuthorization {
|
||||
Task {
|
||||
self.handleAuthStatus(await PHPhotoLibrary.requestAuthorization(for: .readWrite))
|
||||
}
|
||||
} else {
|
||||
fail(withError: ScreenshotDetectorError.notAuthorized)
|
||||
}
|
||||
}
|
||||
|
||||
private func handleAuthStatus(_ status: PHAuthorizationStatus) {
|
||||
if status == .authorized {
|
||||
findScreenshot()
|
||||
} else {
|
||||
fail(withError: ScreenshotDetectorError.notAuthorized)
|
||||
}
|
||||
}
|
||||
|
||||
private func findScreenshot() {
|
||||
if let asset = PHAsset.fetchLastScreenshot() {
|
||||
let imageManager = PHImageManager()
|
||||
imageManager.requestImage(for: asset,
|
||||
targetSize: PHImageManagerMaximumSize,
|
||||
contentMode: .default,
|
||||
options: PHImageRequestOptions.highQualitySyncLocal) { [weak self] image, _ in
|
||||
guard let image = image else {
|
||||
self?.fail(withError: ScreenshotDetectorError.loadFailed)
|
||||
return
|
||||
}
|
||||
self?.succeed(withImage: image)
|
||||
}
|
||||
} else {
|
||||
fail(withError: ScreenshotDetectorError.loadFailed)
|
||||
}
|
||||
}
|
||||
|
||||
func succeed(withImage image: UIImage) {
|
||||
callback?(image, nil)
|
||||
}
|
||||
|
||||
func fail(withError error: Error) {
|
||||
callback?(nil, error)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension PHAsset {
|
||||
|
||||
static func fetchLastScreenshot() -> PHAsset? {
|
||||
let options = PHFetchOptions()
|
||||
|
||||
options.fetchLimit = 1
|
||||
options.includeAssetSourceTypes = [.typeUserLibrary]
|
||||
options.wantsIncrementalChangeDetails = false
|
||||
options.predicate = NSPredicate(format: "(mediaSubtype & %d) != 0", PHAssetMediaSubtype.photoScreenshot.rawValue)
|
||||
options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
|
||||
|
||||
return PHAsset.fetchAssets(with: .image, options: options).firstObject
|
||||
}
|
||||
}
|
||||
|
||||
private extension PHImageRequestOptions {
|
||||
|
||||
static var highQualitySyncLocal: PHImageRequestOptions {
|
||||
let options = PHImageRequestOptions()
|
||||
options.deliveryMode = .highQualityFormat
|
||||
options.isNetworkAccessAllowed = false
|
||||
options.isSynchronous = true
|
||||
return options
|
||||
}
|
||||
}
|
@ -37,9 +37,14 @@ class UITestsAppCoordinator: Coordinator {
|
||||
}
|
||||
|
||||
private func mockScreens() -> [MockScreen] {
|
||||
[MockScreen(id: "Login screen", coordinator: LoginScreenCoordinator(parameters: .init())),
|
||||
MockScreen(id: "Simple Screen - Regular", coordinator: TemplateSimpleScreenCoordinator(parameters: .init(promptType: .regular))),
|
||||
MockScreen(id: "Simple Screen - Upgrade", coordinator: TemplateSimpleScreenCoordinator(parameters: .init(promptType: .upgrade)))]
|
||||
[
|
||||
MockScreen(id: "Login screen", coordinator: LoginScreenCoordinator(parameters: .init())),
|
||||
MockScreen(id: "Simple Screen - Regular", coordinator: TemplateSimpleScreenCoordinator(parameters: .init(promptType: .regular))),
|
||||
MockScreen(id: "Simple Screen - Upgrade", coordinator: TemplateSimpleScreenCoordinator(parameters: .init(promptType: .upgrade))),
|
||||
MockScreen(id: "Settings screen", coordinator: SettingsCoordinator(parameters: .init(navigationRouter: NavigationRouter(navigationController: UINavigationController()), bugReportService: MockBugReportService()))),
|
||||
MockScreen(id: "Bug report screen", coordinator: BugReportCoordinator(parameters: .init(bugReportService: MockBugReportService(), screenshot: nil))),
|
||||
MockScreen(id: "Bug report screen with screenshot", coordinator: BugReportCoordinator(parameters: .init(bugReportService: MockBugReportService(), screenshot: Asset.Images.appLogo.image)))
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
//
|
||||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||
//
|
||||
#import "MXLogger.h"
|
||||
|
@ -88,6 +88,8 @@ targets:
|
||||
- package: Introspect
|
||||
- package: SwiftyBeaver
|
||||
- package: SwiftState
|
||||
- package: GZIP
|
||||
- package: Sentry
|
||||
|
||||
sources:
|
||||
- path: ../Sources
|
||||
|
@ -35,7 +35,7 @@ struct TemplateSimpleScreen: View {
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
VStack {
|
||||
ScrollView(showsIndicators: false) {
|
||||
ScrollView {
|
||||
mainContent
|
||||
.padding(.top, 50)
|
||||
.padding(.horizontal, horizontalPadding)
|
||||
|
@ -24,3 +24,10 @@ strings:
|
||||
params:
|
||||
enumName: ElementL10n
|
||||
publicAccess: true
|
||||
plist:
|
||||
inputs: SupportingFiles/Info.plist
|
||||
outputs:
|
||||
templateName: runtime-swift5
|
||||
output: InfoPlist.swift
|
||||
params:
|
||||
enumName: ElementInfoPlist
|
||||
|
93
UITests/Sources/BugReportUITests.swift
Normal file
93
UITests/Sources/BugReportUITests.swift
Normal file
@ -0,0 +1,93 @@
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import ElementX
|
||||
|
||||
class BugReportUITests: XCTestCase {
|
||||
|
||||
func testInitialStateComponents() {
|
||||
let app = Application.launch()
|
||||
app.goToScreenWithIdentifier("Bug report screen")
|
||||
|
||||
XCTAssert(app.navigationBars["Bug report"].exists)
|
||||
XCTAssert(app.staticTexts["reportBugDescription"].exists)
|
||||
XCTAssert(app.staticTexts["sendLogsDescription"].exists)
|
||||
XCTAssert(app.textViews["reportTextView"].exists)
|
||||
let sendingLogsToggle = app.switches["sendLogsToggle"]
|
||||
XCTAssert(sendingLogsToggle.exists)
|
||||
XCTAssert(sendingLogsToggle.isOn)
|
||||
XCTAssert(app.staticTexts["sendLogsText"].exists)
|
||||
let sendButton = app.buttons["sendButton"]
|
||||
XCTAssert(sendButton.exists)
|
||||
XCTAssertFalse(sendButton.isEnabled)
|
||||
XCTAssertFalse(app.images["screenshotImage"].exists)
|
||||
XCTAssertFalse(app.buttons["removeScreenshotButton"].exists)
|
||||
}
|
||||
|
||||
func testToggleSendingLogs() {
|
||||
let app = Application.launch()
|
||||
app.goToScreenWithIdentifier("Bug report screen")
|
||||
|
||||
app.switches["sendLogsToggle"].tap()
|
||||
|
||||
let sendingLogsToggle = app.switches["sendLogsToggle"]
|
||||
XCTAssert(sendingLogsToggle.exists)
|
||||
XCTAssertFalse(sendingLogsToggle.isOn)
|
||||
}
|
||||
|
||||
func testReportText() {
|
||||
let app = Application.launch()
|
||||
app.goToScreenWithIdentifier("Bug report screen")
|
||||
|
||||
// type 4 chars
|
||||
app.textViews["reportTextView"].tap()
|
||||
app.textViews["reportTextView"].typeText("Test")
|
||||
XCTAssertFalse(app.buttons["sendButton"].isEnabled)
|
||||
|
||||
// type one more char and see the button enabled
|
||||
app.textViews["reportTextView"].tap()
|
||||
app.textViews["reportTextView"].typeText("-")
|
||||
XCTAssert(app.buttons["sendButton"].isEnabled)
|
||||
}
|
||||
|
||||
func testInitialStateComponentsWithScreenshot() {
|
||||
let app = Application.launch()
|
||||
app.goToScreenWithIdentifier("Bug report screen with screenshot")
|
||||
|
||||
XCTAssert(app.navigationBars["Bug report"].exists)
|
||||
XCTAssert(app.staticTexts["reportBugDescription"].exists)
|
||||
XCTAssert(app.staticTexts["sendLogsDescription"].exists)
|
||||
|
||||
XCTAssert(app.textViews["reportTextView"].exists)
|
||||
let sendingLogsToggle = app.switches["sendLogsToggle"]
|
||||
XCTAssert(sendingLogsToggle.exists)
|
||||
XCTAssert(sendingLogsToggle.isOn)
|
||||
XCTAssert(app.staticTexts["sendLogsText"].exists)
|
||||
let sendButton = app.buttons["sendButton"]
|
||||
XCTAssert(sendButton.exists)
|
||||
XCTAssertFalse(sendButton.isEnabled)
|
||||
XCTAssert(app.images["screenshotImage"].exists)
|
||||
XCTAssert(app.buttons["removeScreenshotButton"].exists)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension XCUIElement {
|
||||
var isOn: Bool {
|
||||
(value as? String) == "1"
|
||||
}
|
||||
}
|
31
UITests/Sources/SettingsUITests.swift
Normal file
31
UITests/Sources/SettingsUITests.swift
Normal file
@ -0,0 +1,31 @@
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import ElementX
|
||||
|
||||
class SettingsUITests: XCTestCase {
|
||||
|
||||
func testInitialStateComponents() {
|
||||
let app = Application.launch()
|
||||
app.goToScreenWithIdentifier("Settings screen")
|
||||
|
||||
XCTAssert(app.navigationBars["Settings"].exists)
|
||||
XCTAssert(app.buttons["reportBugButton"].exists)
|
||||
XCTAssert(app.buttons["crashButton"].exists)
|
||||
}
|
||||
|
||||
}
|
@ -21,6 +21,10 @@ targets:
|
||||
linkType: static
|
||||
- package: SwiftState
|
||||
linkType: static
|
||||
- package: GZIP
|
||||
linkType: static
|
||||
- package: Sentry
|
||||
linkType: static
|
||||
|
||||
info:
|
||||
path: ../SupportingFiles/Info.plist
|
||||
|
BIN
UnitTests/Resources/sample_screenshot.png
Normal file
BIN
UnitTests/Resources/sample_screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 191 KiB |
90
UnitTests/Sources/BugReportServiceTests.swift
Normal file
90
UnitTests/Sources/BugReportServiceTests.swift
Normal file
@ -0,0 +1,90 @@
|
||||
//
|
||||
// BugReportServiceTests.swift
|
||||
// UnitTests
|
||||
//
|
||||
// Created by Ismail on 31.05.2022.
|
||||
// Copyright © 2022 element.io. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@testable import ElementX
|
||||
import XCTest
|
||||
|
||||
class BugReportServiceTests: XCTestCase {
|
||||
|
||||
let bugReportService = MockBugReportService()
|
||||
|
||||
func testInitialStateWithMockService() {
|
||||
XCTAssertFalse(bugReportService.crashedLastRun)
|
||||
}
|
||||
|
||||
func testSubmitBugReportWithMockService() async throws {
|
||||
let result = try await bugReportService.submitBugReport(text: "i cannot send message",
|
||||
includeLogs: true,
|
||||
includeCrashLog: true,
|
||||
githubLabels: [],
|
||||
files: [])
|
||||
XCTAssertFalse(result.reportUrl.isEmpty)
|
||||
}
|
||||
|
||||
func testInitialStateWithRealService() throws {
|
||||
let service = try BugReportService(withBaseUrlString: "https://www.example.com",
|
||||
sentryEndpoint: "mock_sentry_dsn",
|
||||
applicationId: "mock_app_id",
|
||||
session: .mock)
|
||||
XCTAssertFalse(service.crashedLastRun)
|
||||
}
|
||||
|
||||
@MainActor func testSubmitBugReportWithRealService() async throws {
|
||||
let service = try BugReportService(withBaseUrlString: "https://www.example.com",
|
||||
sentryEndpoint: "mock_sentry_dsn",
|
||||
applicationId: "mock_app_id",
|
||||
session: .mock)
|
||||
|
||||
let result = try await service.submitBugReport(text: "i cannot send message",
|
||||
includeLogs: true,
|
||||
includeCrashLog: true,
|
||||
githubLabels: [],
|
||||
files: [])
|
||||
|
||||
XCTAssertEqual(result.reportUrl, "https://example.com/123")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class MockURLProtocol: URLProtocol {
|
||||
|
||||
override func startLoading() {
|
||||
let response = "{\"report_url\":\"https://example.com/123\"}"
|
||||
if let data = response.data(using: .utf8) {
|
||||
let urlResponse = URLResponse()
|
||||
client?.urlProtocol(self, didReceive: urlResponse, cacheStoragePolicy: .allowedInMemoryOnly)
|
||||
client?.urlProtocol(self, didLoad: data)
|
||||
client?.urlProtocolDidFinishLoading(self)
|
||||
}
|
||||
}
|
||||
|
||||
override func stopLoading() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
|
||||
return request
|
||||
}
|
||||
|
||||
override class func canInit(with request: URLRequest) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension URLSession {
|
||||
|
||||
static var mock: URLSession {
|
||||
let configuration = URLSessionConfiguration.default
|
||||
configuration.protocolClasses = [MockURLProtocol.self] + (configuration.protocolClasses ?? [])
|
||||
let result = URLSession(configuration: configuration)
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
50
UnitTests/Sources/BugReportViewModelTests.swift
Normal file
50
UnitTests/Sources/BugReportViewModelTests.swift
Normal file
@ -0,0 +1,50 @@
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import ElementX
|
||||
|
||||
@MainActor
|
||||
class BugReportViewModelTests: XCTestCase {
|
||||
|
||||
func testInitialState() {
|
||||
let viewModel = BugReportViewModel(bugReportService: MockBugReportService(), screenshot: nil)
|
||||
let context = viewModel.context
|
||||
|
||||
XCTAssertEqual(context.reportText, "")
|
||||
XCTAssertNil(context.viewState.screenshot)
|
||||
XCTAssertTrue(context.sendingLogsEnabled)
|
||||
}
|
||||
|
||||
func testToggleSendingLogs() async throws {
|
||||
let viewModel = BugReportViewModel(bugReportService: MockBugReportService(), screenshot: nil)
|
||||
let context = viewModel.context
|
||||
|
||||
context.send(viewAction: .toggleSendLogs)
|
||||
await Task.yield()
|
||||
XCTAssertFalse(context.sendingLogsEnabled)
|
||||
}
|
||||
|
||||
func testClearScreenshot() async throws {
|
||||
let viewModel = BugReportViewModel(bugReportService: MockBugReportService(), screenshot: UIImage.actions)
|
||||
let context = viewModel.context
|
||||
|
||||
context.send(viewAction: .removeScreenshot)
|
||||
await Task.yield()
|
||||
XCTAssertNil(context.viewState.screenshot)
|
||||
}
|
||||
}
|
@ -19,11 +19,65 @@ import XCTest
|
||||
@testable import ElementX
|
||||
|
||||
class HomeScreenViewModelTests: XCTestCase {
|
||||
override func setUpWithError() throws {
|
||||
|
||||
var viewModel: HomeScreenViewModelProtocol!
|
||||
var context: HomeScreenViewModelType.Context!
|
||||
|
||||
@MainActor override func setUpWithError() throws {
|
||||
viewModel = HomeScreenViewModel(attributedStringBuilder: AttributedStringBuilder())
|
||||
context = viewModel.context
|
||||
}
|
||||
|
||||
func testInitialState() {
|
||||
@MainActor func testLogout() async throws {
|
||||
var correctResult = false
|
||||
viewModel.callback = { result in
|
||||
switch result {
|
||||
case .logout:
|
||||
correctResult = true
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
context.send(viewAction: .logout)
|
||||
await Task.yield()
|
||||
XCTAssert(correctResult)
|
||||
}
|
||||
|
||||
@MainActor func testSelectRoom() async throws {
|
||||
let mockRoomId = "mock_room_id"
|
||||
var correctResult = false
|
||||
var selectedRoomId = ""
|
||||
viewModel.callback = { result in
|
||||
switch result {
|
||||
case .selectRoom(let roomId):
|
||||
correctResult = true
|
||||
selectedRoomId = roomId
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
context.send(viewAction: .selectRoom(roomIdentifier: mockRoomId))
|
||||
await Task.yield()
|
||||
XCTAssert(correctResult)
|
||||
XCTAssertEqual(mockRoomId, selectedRoomId)
|
||||
}
|
||||
|
||||
@MainActor func testTapUserAvatar() async throws {
|
||||
var correctResult = false
|
||||
viewModel.callback = { result in
|
||||
switch result {
|
||||
case .tapUserAvatar:
|
||||
correctResult = true
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
context.send(viewAction: .tapUserAvatar)
|
||||
await Task.yield()
|
||||
XCTAssert(correctResult)
|
||||
}
|
||||
|
||||
}
|
||||
|
57
UnitTests/Sources/ImageAnonymizerTests.swift
Normal file
57
UnitTests/Sources/ImageAnonymizerTests.swift
Normal file
@ -0,0 +1,57 @@
|
||||
//
|
||||
// ImageExtensionTests.swift
|
||||
// UnitTests
|
||||
//
|
||||
// Created by Ismail on 31.05.2022.
|
||||
// Copyright © 2022 element.io. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import ElementX
|
||||
|
||||
enum ImageAnonymizerTestsError: String, Error {
|
||||
case screenshotNotFound
|
||||
}
|
||||
|
||||
class ImageAnonymizerTests: XCTestCase {
|
||||
|
||||
override func setUpWithError() throws {
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
func sampleScreenshot() throws -> UIImage {
|
||||
let bundle = Bundle(for: self.classForCoder)
|
||||
guard let path = bundle.path(forResource: "sample_screenshot", ofType: "png"),
|
||||
let image = UIImage(contentsOfFile: path) else {
|
||||
throw ImageAnonymizerTestsError.screenshotNotFound
|
||||
}
|
||||
return image
|
||||
}
|
||||
|
||||
func testImageAnonymizationConfidenceLevel() async throws {
|
||||
let image = try sampleScreenshot()
|
||||
|
||||
let anonymized5 = try await ImageAnonymizer.anonymizedImage(from: image)
|
||||
let anonymized1 = try await ImageAnonymizer.anonymizedImage(from: image, confidenceLevel: 0.1)
|
||||
|
||||
// comparing colors is a complicated process, just compare images for now
|
||||
XCTAssertNotEqual(image, anonymized5)
|
||||
XCTAssertNotEqual(anonymized1, anonymized5)
|
||||
}
|
||||
|
||||
func testImageAnonymizationFillColor() async throws {
|
||||
let image = try sampleScreenshot()
|
||||
|
||||
let anonymizedRed = try await ImageAnonymizer.anonymizedImage(from: image)
|
||||
let anonymizedBlue = try await ImageAnonymizer.anonymizedImage(from: image, fillColor: .blue)
|
||||
|
||||
// comparing colors is a complicated process, just compare images for now
|
||||
XCTAssertNotEqual(image, anonymizedRed)
|
||||
XCTAssertNotEqual(anonymizedBlue, anonymizedRed)
|
||||
}
|
||||
|
||||
}
|
96
UnitTests/Sources/LoggingTests.swift
Normal file
96
UnitTests/Sources/LoggingTests.swift
Normal file
@ -0,0 +1,96 @@
|
||||
//
|
||||
// LoggingTests.swift
|
||||
// UnitTests
|
||||
//
|
||||
// Created by Ismail on 31.05.2022.
|
||||
// Copyright © 2022 element.io. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import ElementX
|
||||
|
||||
class LoggingTests: XCTestCase {
|
||||
|
||||
private enum Constants {
|
||||
static let genericFailure = "Test failed"
|
||||
}
|
||||
|
||||
override func setUpWithError() throws {
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
func testFileLogging() throws {
|
||||
MXLogger.deleteLogFiles()
|
||||
guard let logFiles = MXLogger.logFiles() else {
|
||||
XCTFail(Constants.genericFailure)
|
||||
return
|
||||
}
|
||||
XCTAssertTrue(logFiles.isEmpty)
|
||||
|
||||
let log = UUID().uuidString
|
||||
|
||||
let configuration = MXLogConfiguration()
|
||||
configuration.redirectLogsToFiles = true
|
||||
MXLog.configure(configuration)
|
||||
MXLog.debug(log)
|
||||
guard let logFile = MXLogger.logFiles().first else {
|
||||
XCTFail(Constants.genericFailure)
|
||||
return
|
||||
}
|
||||
|
||||
let content = try String(contentsOfFile: logFile)
|
||||
XCTAssert(content.contains(log))
|
||||
}
|
||||
|
||||
func testLogLevels() throws {
|
||||
MXLogger.deleteLogFiles()
|
||||
guard let logFiles = MXLogger.logFiles() else {
|
||||
XCTFail(Constants.genericFailure)
|
||||
return
|
||||
}
|
||||
XCTAssert(logFiles.isEmpty)
|
||||
|
||||
let log = UUID().uuidString
|
||||
|
||||
let configuration = MXLogConfiguration()
|
||||
configuration.logLevel = .error
|
||||
configuration.redirectLogsToFiles = true
|
||||
MXLog.configure(configuration)
|
||||
MXLog.debug(log)
|
||||
guard let logFile = MXLogger.logFiles().first else {
|
||||
XCTFail(Constants.genericFailure)
|
||||
return
|
||||
}
|
||||
|
||||
let content = try String(contentsOfFile: logFile)
|
||||
XCTAssertFalse(content.contains(log))
|
||||
}
|
||||
|
||||
func testSubLogName() {
|
||||
MXLogger.deleteLogFiles()
|
||||
guard let logFiles = MXLogger.logFiles() else {
|
||||
XCTFail(Constants.genericFailure)
|
||||
return
|
||||
}
|
||||
XCTAssert(logFiles.isEmpty)
|
||||
|
||||
let subLogName = "nse"
|
||||
|
||||
let configuration = MXLogConfiguration()
|
||||
configuration.subLogName = subLogName
|
||||
configuration.redirectLogsToFiles = true
|
||||
MXLog.configure(configuration)
|
||||
MXLog.debug(UUID().uuidString)
|
||||
guard let logFile = MXLogger.logFiles().first else {
|
||||
XCTFail(Constants.genericFailure)
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertTrue(logFile.contains(subLogName))
|
||||
}
|
||||
|
||||
}
|
63
UnitTests/Sources/ScreenshotDetectorTests.swift
Normal file
63
UnitTests/Sources/ScreenshotDetectorTests.swift
Normal file
@ -0,0 +1,63 @@
|
||||
//
|
||||
// ScreenshotDetectorTests.swift
|
||||
// UnitTests
|
||||
//
|
||||
// Created by Ismail on 31.05.2022.
|
||||
// Copyright © 2022 element.io. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@testable import ElementX
|
||||
import XCTest
|
||||
import Photos
|
||||
|
||||
class ScreenshotDetectorTests: XCTestCase {
|
||||
|
||||
@MainActor func testDetection() async {
|
||||
async { expectation in
|
||||
let detector = ScreenshotDetector()
|
||||
// disable auto request authorization
|
||||
detector.autoRequestPHAuthorization = false
|
||||
detector.callback = { image, error in
|
||||
|
||||
if PHPhotoLibrary.authorizationStatus(for: .readWrite) == .authorized {
|
||||
// if Photos already authorized on the simulator
|
||||
|
||||
// we should get an image
|
||||
XCTAssertNotNil(image)
|
||||
|
||||
// we should not get an error
|
||||
XCTAssertNil(error)
|
||||
} else {
|
||||
// otherwise we should not get an image
|
||||
XCTAssertNil(image)
|
||||
|
||||
// and get an error
|
||||
guard let error = error else {
|
||||
XCTFail("Should get an error")
|
||||
return
|
||||
}
|
||||
|
||||
switch error {
|
||||
case ScreenshotDetectorError.notAuthorized:
|
||||
break
|
||||
default:
|
||||
XCTFail("Unknown error")
|
||||
}
|
||||
}
|
||||
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
NotificationCenter.default.post(name: UIApplication.userDidTakeScreenshotNotification, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func async(_ timeout: TimeInterval = 0.5, _ block: @escaping (XCTestExpectation) -> Void) {
|
||||
let waiter = XCTWaiter()
|
||||
let expectation = XCTestExpectation(description: "Async operation expectation")
|
||||
block(expectation)
|
||||
waiter.wait(for: [expectation], timeout: timeout)
|
||||
}
|
||||
|
||||
}
|
58
UnitTests/Sources/SettingsViewModelTests.swift
Normal file
58
UnitTests/Sources/SettingsViewModelTests.swift
Normal file
@ -0,0 +1,58 @@
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import ElementX
|
||||
|
||||
@MainActor
|
||||
class SettingsViewModelTests: XCTestCase {
|
||||
|
||||
var viewModel: SettingsViewModelProtocol!
|
||||
var context: SettingsViewModelType.Context!
|
||||
|
||||
@MainActor override func setUpWithError() throws {
|
||||
viewModel = SettingsViewModel()
|
||||
context = viewModel.context
|
||||
}
|
||||
|
||||
func testInitialState() {
|
||||
XCTAssert(context.viewState.crashButtonVisible)
|
||||
}
|
||||
|
||||
func testReportBug() async throws {
|
||||
var correctResult = false
|
||||
viewModel.callback = { result in
|
||||
correctResult = result == .reportBug
|
||||
}
|
||||
|
||||
context.send(viewAction: .reportBug)
|
||||
await Task.yield()
|
||||
XCTAssert(correctResult)
|
||||
}
|
||||
|
||||
func testCrash() async throws {
|
||||
var correctResult = false
|
||||
viewModel.callback = { result in
|
||||
correctResult = result == .crash
|
||||
}
|
||||
|
||||
context.send(viewAction: .crash)
|
||||
await Task.yield()
|
||||
XCTAssert(correctResult)
|
||||
}
|
||||
|
||||
}
|
@ -22,3 +22,4 @@ targets:
|
||||
- path: ../Sources
|
||||
- path: ../SupportingFiles
|
||||
- path: ../../Tools/Scripts/Templates/SimpleScreenExample/Tests/Unit
|
||||
- path: ../Resources
|
||||
|
1
changelog.d/23.feature
Normal file
1
changelog.d/23.feature
Normal file
@ -0,0 +1 @@
|
||||
Implement rageshake service.
|
@ -51,3 +51,9 @@ packages:
|
||||
SwiftState:
|
||||
url: https://github.com/ReactKit/SwiftState
|
||||
majorVersion: 6.0.0
|
||||
GZIP:
|
||||
url: https://github.com/nicklockwood/GZIP
|
||||
majorVersion: 1.3.0
|
||||
Sentry:
|
||||
url: https://github.com/getsentry/sentry-cocoa.git
|
||||
majorVersion: 7.15.0
|
||||
|
Loading…
x
Reference in New Issue
Block a user