diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index b06a2ff67..4b7efcc9a 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 56; objects = { /* Begin PBXAggregateTarget section */ @@ -203,6 +203,7 @@ 339BC18777912E1989F2F17D /* Section.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584A61D9C459FAFEF038A7C0 /* Section.swift */; }; 33CA777C9DF263582D77A67F /* VoiceMessagePreviewComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC9F57320EC80C7CE34FE4A /* VoiceMessagePreviewComposer.swift */; }; 33CAC1226DFB8B5D8447D286 /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 1BCD21310B997A6837B854D6 /* GZIP */; }; + 33F1FB19F222BA9930AB1A00 /* RoomListFiltersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6372DD10DED30E7AD7BCE21 /* RoomListFiltersView.swift */; }; 340D39DB87F3800D53A6A621 /* EmojiPickerScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00245D40CD90FD71D6A05239 /* EmojiPickerScreen.swift */; }; 34357B287357BC0B9715DD51 /* UserAgentBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F2529D434C750ED78ADF1ED /* UserAgentBuilder.swift */; }; 34433A509DFEC93579B3B35B /* AdvancedSettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = C18CC37B97E77838609CFFE7 /* AdvancedSettingsScreen.swift */; }; @@ -705,6 +706,7 @@ B245583C63F8F90357B87FAE /* KZFileWatchers in Frameworks */ = {isa = PBXBuildFile; productRef = A2AE110B053B55E38F8D10C7 /* KZFileWatchers */; }; B27D3190784F85916DA1C394 /* SessionVerificationScreenStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B1EE0908B2BF9212436AD3E /* SessionVerificationScreenStateMachine.swift */; }; B2F8E01ABA1BA30265B4ECBE /* RoundedCornerShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = 839E2C35DF3F9C7B54C3CE49 /* RoundedCornerShape.swift */; }; + B337C01BA4EEA095EE30918A /* RoomListFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B93C2C0EA69103D425E5B5C6 /* RoomListFilterView.swift */; }; B3D652AA1654270742072FB3 /* DeveloperOptionsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86A6F283BC574FDB96ABBB07 /* DeveloperOptionsScreenViewModel.swift */; }; B3EDDEC1839BB5A3747624BB /* FormButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95A1CCDEE545CB6453B084BF /* FormButtonStyles.swift */; }; B402708F8728DD0DB7C324E2 /* StartChatScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78910787F967CBC6042A101E /* StartChatScreenViewModelProtocol.swift */; }; @@ -1070,7 +1072,7 @@ 033DB41C51865A2E83174E87 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = ""; }; 035177BCD8E8308B098AC3C2 /* WindowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowManager.swift; sourceTree = ""; }; 0376C429FAB1687C3D905F3E /* MockCoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCoder.swift; sourceTree = ""; }; - 0392E3FDE372C9B56FEEED8B /* test_voice_message.m4a */ = {isa = PBXFileReference; path = test_voice_message.m4a; sourceTree = ""; }; + 0392E3FDE372C9B56FEEED8B /* test_voice_message.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_voice_message.m4a; sourceTree = ""; }; 03DD998E523D4EC93C7ED703 /* RoomNotificationSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenViewModelProtocol.swift; sourceTree = ""; }; 03FABD73FD8086EFAB699F42 /* MediaUploadPreviewScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenViewModelTests.swift; sourceTree = ""; }; 044E501B8331B339874D1B96 /* CompoundIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompoundIcon.swift; sourceTree = ""; }; @@ -1131,7 +1133,7 @@ 127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentValuePublisher.swift; sourceTree = ""; }; 12EDAFB64FA5F6812D54F39A /* MigrationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationScreenViewModel.swift; sourceTree = ""; }; 12F1E7F9C2BE8BB751037826 /* WaitlistScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenCoordinator.swift; sourceTree = ""; }; - 1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; path = IntegrationTests.xctestplan; sourceTree = ""; }; + 1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = IntegrationTests.xctestplan; sourceTree = ""; }; 130ED565A078F7E0B59D9D25 /* UNTextInputNotificationResponse+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNTextInputNotificationResponse+Creator.swift"; sourceTree = ""; }; 13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; }; 1423AB065857FA546444DB15 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = ""; }; @@ -1565,7 +1567,7 @@ 8D55702474F279D910D2D162 /* RoomStateEventStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomStateEventStringBuilder.swift; sourceTree = ""; }; 8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; }; 8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyle.swift; sourceTree = ""; }; - 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; path = UITests.xctestplan; sourceTree = ""; }; + 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UITests.xctestplan; sourceTree = ""; }; 8E1BBA73B611EDEEA6E20E05 /* InvitesScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenModels.swift; sourceTree = ""; }; 8EC57A32ABC80D774CC663DB /* SettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenUITests.swift; sourceTree = ""; }; 8F21ED7205048668BEB44A38 /* AppActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppActivityView.swift; sourceTree = ""; }; @@ -1705,7 +1707,7 @@ B50F03079F6B5EF9CA005F14 /* TimelineProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineProxyProtocol.swift; sourceTree = ""; }; B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableAvatarImage.swift; sourceTree = ""; }; B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; sourceTree = ""; }; - B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; path = ConfettiScene.scn; sourceTree = ""; }; + B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = ConfettiScene.scn; sourceTree = ""; }; B6311F21F911E23BE4DF51B4 /* ReadMarkerRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineView.swift; sourceTree = ""; }; B63B69F9A2BC74DD40DC75C8 /* AdvancedSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsScreenViewModel.swift; sourceTree = ""; }; B697816AF93DA06EC58C5D70 /* WaitlistScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenViewModelProtocol.swift; sourceTree = ""; }; @@ -1724,6 +1726,7 @@ B8F28602AC7AC881AED37EBA /* NavigationCoordinators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationCoordinators.swift; sourceTree = ""; }; B902EA6CD3296B0E10EE432B /* HomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = ""; }; B9227F7495DA43324050A863 /* TextBasedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBasedRoomTimelineItem.swift; sourceTree = ""; }; + B93C2C0EA69103D425E5B5C6 /* RoomListFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListFilterView.swift; sourceTree = ""; }; B99E13633862847D8B7E2815 /* StartChatScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatScreenModels.swift; sourceTree = ""; }; BA188BB0A216D763E46E3279 /* ComposerToolbarModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerToolbarModels.swift; sourceTree = ""; }; BA241DEEF7C8A7181C0AEDC9 /* UserPreferenceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPreferenceTests.swift; sourceTree = ""; }; @@ -1809,7 +1812,7 @@ CD95B3714F806AC9CF9A557B /* ComposerToolbarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerToolbarViewModel.swift; sourceTree = ""; }; CDB3227C7A74B734924942E9 /* RoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProvider.swift; sourceTree = ""; }; CEE0E6043EFCF6FD2A341861 /* TimelineReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReplyView.swift; sourceTree = ""; }; - CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; path = UnitTests.xctestplan; sourceTree = ""; }; + CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = ""; }; CF48AF076424DBC1615C74AD /* AuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProxy.swift; sourceTree = ""; }; D0140615D2232612C813FD6C /* EncryptedHistoryRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedHistoryRoomTimelineItem.swift; sourceTree = ""; }; D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoverySection.swift; sourceTree = ""; }; @@ -1891,6 +1894,7 @@ E55B5EA766E89FF1F87C3ACB /* RoomNotificationSettingsProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsProxyProtocol.swift; sourceTree = ""; }; E5E94DCFEE803E5ABAE8ACCE /* KeychainControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainControllerProtocol.swift; sourceTree = ""; }; E5F2B6443D1ED8602F328539 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ru; path = ru.lproj/Localizable.stringsdict; sourceTree = ""; }; + E6372DD10DED30E7AD7BCE21 /* RoomListFiltersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListFiltersView.swift; sourceTree = ""; }; E65DA46BD5CA83747AE144F3 /* secrets.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = secrets.xcconfig; sourceTree = ""; }; E6E6BDF9D26DB05C88901416 /* RedactedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedactedRoomTimelineItem.swift; sourceTree = ""; }; E6F5D66F158A6662F953733E /* NotificationSettingsProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsProxy.swift; sourceTree = ""; }; @@ -1916,7 +1920,7 @@ ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenViewModel.swift; sourceTree = ""; }; ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = ""; }; ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = ""; }; - ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; path = message.caf; sourceTree = ""; }; + ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = message.caf; sourceTree = ""; }; ED983D4DCA5AFA6E1ED96099 /* StateRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRoomTimelineView.swift; sourceTree = ""; }; EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = ""; }; EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItemContent.swift; sourceTree = ""; }; @@ -1933,7 +1937,7 @@ F174A5627CDB3CAF280D1880 /* EmojiPickerScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenModels.swift; sourceTree = ""; }; F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProvider.swift; sourceTree = ""; }; F1B8500C152BC59445647DA8 /* UnsupportedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsupportedRoomTimelineItem.swift; sourceTree = ""; }; - F2D513D2477B57F90E98EEC0 /* portrait_test_video.mp4 */ = {isa = PBXFileReference; path = portrait_test_video.mp4; sourceTree = ""; }; + F2D513D2477B57F90E98EEC0 /* portrait_test_video.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = portrait_test_video.mp4; sourceTree = ""; }; F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixEntityRegexTests.swift; sourceTree = ""; }; F348B5F2C12F9D4F4B4D3884 /* VideoRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRoomTimelineItem.swift; sourceTree = ""; }; F36C0A6D59717193F49EA986 /* UserSessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionTests.swift; sourceTree = ""; }; @@ -2110,6 +2114,15 @@ path = Recorder; sourceTree = ""; }; + 037A5661B26EC6BE068188D7 /* Filters */ = { + isa = PBXGroup; + children = ( + B93C2C0EA69103D425E5B5C6 /* RoomListFilterView.swift */, + E6372DD10DED30E7AD7BCE21 /* RoomListFiltersView.swift */, + ); + path = Filters; + sourceTree = ""; + }; 052CC920F473C10B509F9FC1 /* SwiftUI */ = { isa = PBXGroup; children = ( @@ -2938,6 +2951,7 @@ C7661EFFCAA307A97D71132A /* HomeScreenRoomList.swift */, E71C28CF29CD05B6D6AE8580 /* HomeScreenSessionVerificationBanner.swift */, F3BC2D3573D900A9C9F8C191 /* HomeScreenUserMenuButton.swift */, + 037A5661B26EC6BE068188D7 /* Filters */, ); path = View; sourceTree = ""; @@ -5531,6 +5545,7 @@ D33AC79A50DFC26D2498DD28 /* FileRoomTimelineItem.swift in Sources */, 37D789F24199B32E3FD1AA7B /* FileRoomTimelineItemContent.swift in Sources */, 1F04C63D4FA95948E3F52147 /* FileRoomTimelineView.swift in Sources */, + B337C01BA4EEA095EE30918A /* RoomListFilterView.swift in Sources */, F5D2270B5021D521C0D22E11 /* FlowCoordinatorProtocol.swift in Sources */, B3EDDEC1839BB5A3747624BB /* FormButtonStyles.swift in Sources */, A0A0D2A9564BDA3FDE2E360F /* FormattedBodyText.swift in Sources */, @@ -5766,6 +5781,7 @@ 42F1C8731166633E35A6D7E6 /* RoomEventStringBuilder.swift in Sources */, D55AF9B5B55FEED04771A461 /* RoomFlowCoordinator.swift in Sources */, 04A16B45228F7678A027C079 /* RoomHeaderView.swift in Sources */, + 33F1FB19F222BA9930AB1A00 /* RoomListFiltersView.swift in Sources */, FA4296218444C48BC890F46B /* RoomMemberDetails.swift in Sources */, 19FE025AE9BA2959B6589B0D /* RoomMemberDetailsScreen.swift in Sources */, 899793EFC63DF93C3E0141E7 /* RoomMemberDetailsScreenCoordinator.swift in Sources */, @@ -6222,9 +6238,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = "$(MARKETING_VERSION)"; - OTHER_SWIFT_FLAGS = ( - "-DIS_NSE", - ); + OTHER_SWIFT_FLAGS = "-DIS_NSE"; PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.nse"; PRODUCT_DISPLAY_NAME = "$(APP_DISPLAY_NAME)"; PRODUCT_NAME = NSE; @@ -6255,9 +6269,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = "$(MARKETING_VERSION)"; - OTHER_SWIFT_FLAGS = ( - "-DIS_MAIN_APP", - ); + OTHER_SWIFT_FLAGS = "-DIS_MAIN_APP"; PILLS_UT_TYPE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).pills"; PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = "$(APP_NAME)"; @@ -6283,9 +6295,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = "$(MARKETING_VERSION)"; - OTHER_SWIFT_FLAGS = ( - "-DIS_MAIN_APP", - ); + OTHER_SWIFT_FLAGS = "-DIS_MAIN_APP"; PILLS_UT_TYPE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).pills"; PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = "$(APP_NAME)"; @@ -6528,9 +6538,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = "$(MARKETING_VERSION)"; - OTHER_SWIFT_FLAGS = ( - "-DIS_NSE", - ); + OTHER_SWIFT_FLAGS = "-DIS_NSE"; PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.nse"; PRODUCT_DISPLAY_NAME = "$(APP_DISPLAY_NAME)"; PRODUCT_NAME = NSE; diff --git a/ElementX/Resources/Localizations/en.lproj/Localizable.strings b/ElementX/Resources/Localizations/en.lproj/Localizable.strings index f015cee8a..9dfe2eed0 100644 --- a/ElementX/Resources/Localizations/en.lproj/Localizable.strings +++ b/ElementX/Resources/Localizations/en.lproj/Localizable.strings @@ -550,6 +550,11 @@ "screen_roomlist_a11y_create_message" = "Create a new conversation or room"; "screen_roomlist_empty_message" = "Get started by messaging someone."; "screen_roomlist_empty_title" = "No chats yet."; +"screen_roomlist_filter_favourites" = "Favourites"; +"screen_roomlist_filter_low_priority" = "Low Priority"; +"screen_roomlist_filter_people" = "People"; +"screen_roomlist_filter_rooms" = "Rooms"; +"screen_roomlist_filter_unreads" = "Unreads"; "screen_roomlist_main_space_title" = "All Chats"; "screen_server_confirmation_change_server" = "Change account provider"; "screen_server_confirmation_message_login_element_dot_io" = "A private server for Element employees."; diff --git a/ElementX/Sources/Application/AppSettings.swift b/ElementX/Sources/Application/AppSettings.swift index 0c11c83ad..8fc3a7c5d 100644 --- a/ElementX/Sources/Application/AppSettings.swift +++ b/ElementX/Sources/Application/AppSettings.swift @@ -47,6 +47,7 @@ final class AppSettings { case userSuggestionsEnabled case swiftUITimelineEnabled case mentionsBadgeEnabled + case roomListFiltersEnabled } private static var suiteName: String = InfoPlistReader.main.appGroupIdentifier @@ -277,7 +278,10 @@ final class AppSettings { @UserPreference(key: UserDefaultsKeys.mentionsBadgeEnabled, defaultValue: true, storageType: .userDefaults(store)) var mentionsBadgeEnabled - + + @UserPreference(key: UserDefaultsKeys.roomListFiltersEnabled, defaultValue: false, storageType: .userDefaults(store)) + var roomListFiltersEnabled + #endif // MARK: - Shared diff --git a/ElementX/Sources/Generated/Strings.swift b/ElementX/Sources/Generated/Strings.swift index a4094cf36..48f658484 100644 --- a/ElementX/Sources/Generated/Strings.swift +++ b/ElementX/Sources/Generated/Strings.swift @@ -1318,6 +1318,16 @@ public enum L10n { public static var screenRoomlistEmptyMessage: String { return L10n.tr("Localizable", "screen_roomlist_empty_message") } /// No chats yet. public static var screenRoomlistEmptyTitle: String { return L10n.tr("Localizable", "screen_roomlist_empty_title") } + /// Favourites + public static var screenRoomlistFilterFavourites: String { return L10n.tr("Localizable", "screen_roomlist_filter_favourites") } + /// Low Priority + public static var screenRoomlistFilterLowPriority: String { return L10n.tr("Localizable", "screen_roomlist_filter_low_priority") } + /// People + public static var screenRoomlistFilterPeople: String { return L10n.tr("Localizable", "screen_roomlist_filter_people") } + /// Rooms + public static var screenRoomlistFilterRooms: String { return L10n.tr("Localizable", "screen_roomlist_filter_rooms") } + /// Unreads + public static var screenRoomlistFilterUnreads: String { return L10n.tr("Localizable", "screen_roomlist_filter_unreads") } /// All Chats public static var screenRoomlistMainSpaceTitle: String { return L10n.tr("Localizable", "screen_roomlist_main_space_title") } /// Change account provider diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift index 13d930781..db52a4435 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift @@ -97,6 +97,7 @@ struct HomeScreenViewState: BindableState { var rooms: [HomeScreenRoom] = [] var roomListMode: HomeScreenRoomListMode = .skeletons + var shouldShowFilters = false var hasPendingInvitations = false var hasUnreadPendingInvitations = false @@ -111,6 +112,8 @@ struct HomeScreenViewState: BindableState { return rooms } + var filtersState = RoomListFiltersState() + var bindings = HomeScreenViewStateBindings() var placeholderRooms: [HomeScreenRoom] { @@ -175,3 +178,88 @@ struct HomeScreenRoom: Identifiable, Equatable { isPlaceholder: true) } } + +enum RoomListFilter: Int, CaseIterable, Identifiable { + var id: Int { + rawValue + } + + case people + case rooms + case unreads + case favourites + case lowPriority + + var localizedName: String { + switch self { + case .people: + return L10n.screenRoomlistFilterPeople + case .rooms: + return L10n.screenRoomlistFilterRooms + case .unreads: + return L10n.screenRoomlistFilterUnreads + case .favourites: + return L10n.screenRoomlistFilterFavourites + case .lowPriority: + return L10n.screenRoomlistFilterLowPriority + } + } + + var complementaryFilter: RoomListFilter? { + switch self { + case .people: + return .rooms + case .rooms: + return .people + case .unreads: + return nil + case .favourites: + return .lowPriority + case .lowPriority: + return .favourites + } + } +} + +final class RoomListFiltersState: ObservableObject { + @Published private var enabledFilters: Set + + init(enabledFilters: Set = []) { + self.enabledFilters = enabledFilters + } + + var sortedEnabledFilters: [RoomListFilter] { + enabledFilters.sorted(by: { $0.rawValue < $1.rawValue }) + } + + var sortedAvailableFilters: [RoomListFilter] { + var availableFilters = Set(RoomListFilter.allCases) + for filter in enabledFilters { + availableFilters.remove(filter) + if let complementaryFilter = filter.complementaryFilter { + availableFilters.remove(complementaryFilter) + } + } + return availableFilters.sorted(by: { $0.rawValue < $1.rawValue }) + } + + var isFiltering: Bool { + !enabledFilters.isEmpty + } + + func set(_ filter: RoomListFilter, isEnabled: Bool) { + if isEnabled { + enabledFilters.insert(filter) + } else { + enabledFilters.remove(filter) + } + } + + func clearFilters() { + enabledFilters.removeAll() + } + + func isEnabled(_ filter: RoomListFilter) -> Bool { + enabledFilters.contains(filter) + } +} diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index 8f792e5b4..2077068fe 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -80,6 +80,10 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol .weakAssign(to: \.state.selectedRoomID, on: self) .store(in: &cancellables) + appSettings.$roomListFiltersEnabled + .weakAssign(to: \.state.shouldShowFilters, on: self) + .store(in: &cancellables) + let isSearchFieldFocused = context.$viewState.map(\.bindings.isSearchFieldFocused) let searchQuery = context.$viewState.map(\.bindings.searchQuery) isSearchFieldFocused @@ -333,7 +337,11 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol if room.isPublic { state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(roomId: roomId, isDM: room.isEncryptedOneToOneRoom, state: .public) } else { - state.bindings.leaveRoomAlertItem = room.joinedMembersCount > 1 ? LeaveRoomAlertItem(roomId: roomId, isDM: room.isEncryptedOneToOneRoom, state: .private) : LeaveRoomAlertItem(roomId: roomId, isDM: room.isEncryptedOneToOneRoom, state: .empty) + state.bindings.leaveRoomAlertItem = if room.joinedMembersCount > 1 { + LeaveRoomAlertItem(roomId: roomId, isDM: room.isEncryptedOneToOneRoom, state: .private) + } else { + LeaveRoomAlertItem(roomId: roomId, isDM: room.isEncryptedOneToOneRoom, state: .empty) + } } } } diff --git a/ElementX/Sources/Screens/HomeScreen/View/Filters/RoomListFilterView.swift b/ElementX/Sources/Screens/HomeScreen/View/Filters/RoomListFilterView.swift new file mode 100644 index 000000000..e12d2cc56 --- /dev/null +++ b/ElementX/Sources/Screens/HomeScreen/View/Filters/RoomListFilterView.swift @@ -0,0 +1,77 @@ +// +// Copyright 2024 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 RoomListFilterView: View { + let filter: RoomListFilter + @StateObject var state: RoomListFiltersState + + var body: some View { + let binding = Binding(get: { + state.isEnabled(filter) + }, set: { isEnabled, _ in + state.set(filter, isEnabled: isEnabled) + }) + Toggle(isOn: binding) { + Text(filter.localizedName) + } + .toggleStyle(FilterToggleStyle()) + } +} + +struct RoomListFilterView_Previews: PreviewProvider, TestablePreview { + static var previews: some View { + RoomListFilterView(filter: .people, state: .init()) + RoomListFilterView(filter: .people, state: .init(enabledFilters: [.people])) + } +} + +private struct FilterToggleStyle: ToggleStyle { + private func strokeColor(isOn: Bool) -> Color { + isOn ? .compound.bgActionPrimaryRest : .compound.borderInteractiveSecondary + } + + private func backgroundColor(isOn: Bool) -> Color { + isOn ? .compound.bgActionPrimaryRest : .compound.bgCanvasDefault + } + + private func foregroundColor(isOn: Bool) -> Color { + isOn ? .compound.textOnSolidPrimary : .compound.textPrimary + } + + func makeBody(configuration: Configuration) -> some View { + let shape = RoundedRectangle(cornerRadius: 20) + configuration.label + .font(.compound.bodyLG) + .foregroundColor(foregroundColor(isOn: configuration.isOn)) + .padding(.horizontal, 16) + .padding(.vertical, 8) + .background(shape.fill(backgroundColor(isOn: configuration.isOn))) + .overlay { + shape + .inset(by: 0.5) + .stroke(strokeColor(isOn: configuration.isOn)) + } + .drawingGroup() + // The button breaks the animation for some reason, so better to use the label directly with an onTapGesture + .onTapGesture { + withAnimation(.elementDefault) { + configuration.isOn.toggle() + } + } + } +} diff --git a/ElementX/Sources/Screens/HomeScreen/View/Filters/RoomListFiltersView.swift b/ElementX/Sources/Screens/HomeScreen/View/Filters/RoomListFiltersView.swift new file mode 100644 index 000000000..48d6c2cfa --- /dev/null +++ b/ElementX/Sources/Screens/HomeScreen/View/Filters/RoomListFiltersView.swift @@ -0,0 +1,67 @@ +// +// Copyright 2024 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 RoomListFiltersView: View { + @StateObject var state: RoomListFiltersState + + var body: some View { + ScrollView(.horizontal) { + LazyHStack(spacing: 8) { + if state.isFiltering { + clearButton + } else { + // This solves a weird issue withe the LazyHStack + // where it is resized when the button appears and disappears + clearButton + .hidden() + .frame(width: 0) + } + ForEach(state.sortedEnabledFilters) { filter in + RoomListFilterView(filter: filter, state: state) + } + ForEach(state.sortedAvailableFilters) { filter in + RoomListFilterView(filter: filter, state: state) + } + } + .padding(.leading, !state.isFiltering ? 8 : 16) + .padding(.vertical, 12) + } + .scrollIndicators(.hidden) + } + + private var clearButton: some View { + Button(action: { + withAnimation(.elementDefault) { + state.clearFilters() + } + }, label: { + Image(systemName: "xmark.circle.fill") + .font(.system(size: 24)) + .foregroundColor(.compound.bgActionPrimaryRest) + }) + } +} + +// MARK: - Previews + +struct RoomListFiltersView_Previews: PreviewProvider, TestablePreview { + static var previews: some View { + RoomListFiltersView(state: .init()) + RoomListFiltersView(state: .init(enabledFilters: [.rooms, .favourites])) + } +} diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift index 0062a523b..2fc30b652 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift @@ -52,10 +52,12 @@ struct HomeScreen: View { .layoutPriority(1) } case .rooms: - topSection - - LazyVStack(spacing: 0) { - HomeScreenRoomList(context: context) + LazyVStack(spacing: 0, pinnedViews: [.sectionHeaders]) { + Section { + HomeScreenRoomList(context: context) + } header: { + topSection + } } .searchable(text: $context.searchQuery) .compoundSearchField() @@ -191,20 +193,30 @@ struct HomeScreen: View { @ViewBuilder /// The session verification banner and invites button if either are needed. private var topSection: some View { - if context.viewState.showSessionVerificationBanner { - HomeScreenSessionVerificationBanner(context: context) - } else if context.viewState.showRecoveryKeyConfirmationBanner { - HomeScreenRecoveryKeyConfirmationBanner(context: context) - } - - if context.viewState.hasPendingInvitations, !context.isSearchFieldFocused { - HomeScreenInvitesButton(title: L10n.actionInvitesList, hasBadge: context.viewState.hasUnreadPendingInvitations) { - context.send(viewAction: .selectInvites) + VStack(spacing: 0) { + if context.viewState.shouldShowFilters { + filters + } + + if context.viewState.showSessionVerificationBanner { + HomeScreenSessionVerificationBanner(context: context) + } else if context.viewState.showRecoveryKeyConfirmationBanner { + HomeScreenRecoveryKeyConfirmationBanner(context: context) + } + + if context.viewState.hasPendingInvitations, !context.isSearchFieldFocused { + HomeScreenInvitesButton(title: L10n.actionInvitesList, hasBadge: context.viewState.hasUnreadPendingInvitations) { + context.send(viewAction: .selectInvites) + } + .accessibilityIdentifier(A11yIdentifiers.homeScreen.invites) + .frame(maxWidth: .infinity, alignment: .trailing) } - .accessibilityIdentifier(A11yIdentifiers.homeScreen.invites) - .frame(maxWidth: .infinity, alignment: .trailing) - .padding(.vertical, -8.0) } + .background(Color.compound.bgCanvasDefault) + } + + private var filters: some View { + RoomListFiltersView(state: context.viewState.filtersState) } @ToolbarContentBuilder diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenInvitesButton.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenInvitesButton.swift index 5a90832d7..33d2de8c4 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenInvitesButton.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenInvitesButton.swift @@ -34,7 +34,6 @@ struct HomeScreenInvitesButton: View { } .padding(.trailing, 16.0) .padding(.leading, 64.0) - .padding(.vertical, 8.0) } } diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift index 5531adf06..4f7c83d63 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift @@ -50,6 +50,7 @@ protocol DeveloperOptionsProtocol: AnyObject { var userSuggestionsEnabled: Bool { get set } var swiftUITimelineEnabled: Bool { get set } var mentionsBadgeEnabled: Bool { get set } + var roomListFiltersEnabled: Bool { get set } var elementCallBaseURL: URL { get set } var elementCallUseEncryption: Bool { get set } diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift index b9bbf4ebf..4e16634a1 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift @@ -56,6 +56,12 @@ struct DeveloperOptionsScreen: View { } } + Section("Room List") { + Toggle(isOn: $context.roomListFiltersEnabled) { + Text("Show filters") + } + } + Section("Element Call") { TextField(context.elementCallBaseURL.absoluteString, text: $elementCallBaseURLString) .submitLabel(.done) diff --git a/UnitTests/__Snapshots__/PreviewTests/test_roomListFilterView.1.png b/UnitTests/__Snapshots__/PreviewTests/test_roomListFilterView.1.png new file mode 100644 index 000000000..a860dd55a --- /dev/null +++ b/UnitTests/__Snapshots__/PreviewTests/test_roomListFilterView.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3ac210fff37bb6b8023fa1527ac1eeb5fdbad9c80357b42f68248f9940db3531 +size 62409 diff --git a/UnitTests/__Snapshots__/PreviewTests/test_roomListFilterView.2.png b/UnitTests/__Snapshots__/PreviewTests/test_roomListFilterView.2.png new file mode 100644 index 000000000..443cc1ac2 --- /dev/null +++ b/UnitTests/__Snapshots__/PreviewTests/test_roomListFilterView.2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01003a81f1ace9c184c28728f8bee3528e326f0be88ebcbab3006af649208a10 +size 60619 diff --git a/UnitTests/__Snapshots__/PreviewTests/test_roomListFiltersView.1.png b/UnitTests/__Snapshots__/PreviewTests/test_roomListFiltersView.1.png new file mode 100644 index 000000000..4511df5a7 --- /dev/null +++ b/UnitTests/__Snapshots__/PreviewTests/test_roomListFiltersView.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:501bb3e6d29147f5f15c036fb5ac2d060cd5b80d00cbee44e050ce2b6f425270 +size 70180 diff --git a/UnitTests/__Snapshots__/PreviewTests/test_roomListFiltersView.2.png b/UnitTests/__Snapshots__/PreviewTests/test_roomListFiltersView.2.png new file mode 100644 index 000000000..661455d5e --- /dev/null +++ b/UnitTests/__Snapshots__/PreviewTests/test_roomListFiltersView.2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:269445bf0caa7e3ca562e542028081fb5bf9c87d42b5509cce7f69dda472f46e +size 73468 diff --git a/changelog.d/pr-2382.wip b/changelog.d/pr-2382.wip new file mode 100644 index 000000000..e179c607e --- /dev/null +++ b/changelog.d/pr-2382.wip @@ -0,0 +1 @@ +Room list filters UI (does not have any impact on the room list yet) \ No newline at end of file