diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index e06e633f8..12deb8475 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -74,6 +74,10 @@ D5AE1BE727D18B7E005849B4 /* UserItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5AE1BE627D18B7E005849B4 /* UserItemView.swift */; }; D5AEA3A928341C630023245B /* FirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = D5AEA3A828341C630023245B /* FirebaseAnalytics */; }; D5AEA3AB28341C630023245B /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = D5AEA3AA28341C630023245B /* FirebaseCrashlytics */; }; + D5B5E8522B4C972D00129758 /* AgendaFiltersViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B5E8512B4C972D00129758 /* AgendaFiltersViewModel.swift */; }; + D5B5E8542B4C98E600129758 /* AgendaFiltersVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B5E8532B4C98E600129758 /* AgendaFiltersVM.swift */; }; + D5B5E8562B4C990000129758 /* AgendaFilters.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B5E8552B4C990000129758 /* AgendaFilters.swift */; }; + D5B5E8582B4CA63900129758 /* AgendaFiltersNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B5E8572B4CA63900129758 /* AgendaFiltersNavigation.swift */; }; D5BC483229B0CC98002CC517 /* ViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5BC483129B0CC98002CC517 /* ViewModelFactory.swift */; }; D5C1B3BB29F26D9E00D94391 /* BundleExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C1B3BA29F26D9E00D94391 /* BundleExt.swift */; }; D5C753292975E7BC00E2FD0B /* EventListVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C753282975E7BC00E2FD0B /* EventListVM.swift */; }; @@ -167,6 +171,10 @@ D59EFCB927B5AE25000AADCF /* EventVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventVM.swift; sourceTree = ""; }; D5AA380929B2206C00E1A5D4 /* TalkItemNavigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TalkItemNavigation.swift; sourceTree = ""; }; D5AE1BE627D18B7E005849B4 /* UserItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserItemView.swift; sourceTree = ""; }; + D5B5E8512B4C972D00129758 /* AgendaFiltersViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgendaFiltersViewModel.swift; sourceTree = ""; }; + D5B5E8532B4C98E600129758 /* AgendaFiltersVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgendaFiltersVM.swift; sourceTree = ""; }; + D5B5E8552B4C990000129758 /* AgendaFilters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgendaFilters.swift; sourceTree = ""; }; + D5B5E8572B4CA63900129758 /* AgendaFiltersNavigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgendaFiltersNavigation.swift; sourceTree = ""; }; D5BC483129B0CC98002CC517 /* ViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelFactory.swift; sourceTree = ""; }; D5C1B3BA29F26D9E00D94391 /* BundleExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleExt.swift; sourceTree = ""; }; D5C753282975E7BC00E2FD0B /* EventListVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventListVM.swift; sourceTree = ""; }; @@ -301,6 +309,7 @@ D50A458D27B0819A00C9A43C /* screens */ = { isa = PBXGroup; children = ( + D5B5E8502B4C961400129758 /* filters */, D5C7532C2975F48100E2FD0B /* app */, D563E2E829755B1600B5FCA0 /* events */, D55D7A73295F83C1008EA04B /* partner */, @@ -364,6 +373,7 @@ D5AA380929B2206C00E1A5D4 /* TalkItemNavigation.swift */, D581504129B2299C0058D057 /* PartnerItemNavigation.swift */, D581504329B22B2B0058D057 /* MenusNavigation.swift */, + D5B5E8572B4CA63900129758 /* AgendaFiltersNavigation.swift */, ); path = navigations; sourceTree = ""; @@ -483,6 +493,16 @@ path = tickets; sourceTree = ""; }; + D5B5E8502B4C961400129758 /* filters */ = { + isa = PBXGroup; + children = ( + D5B5E8512B4C972D00129758 /* AgendaFiltersViewModel.swift */, + D5B5E8532B4C98E600129758 /* AgendaFiltersVM.swift */, + D5B5E8552B4C990000129758 /* AgendaFilters.swift */, + ); + path = filters; + sourceTree = ""; + }; D5C7532C2975F48100E2FD0B /* app */ = { isa = PBXGroup; children = ( @@ -686,6 +706,7 @@ D574DE1A292446AE00468074 /* PauseView.swift in Sources */, D59944BE2833CDC900F59C1B /* TicketDetailedView.swift in Sources */, D5BC483229B0CC98002CC517 /* ViewModelFactory.swift in Sources */, + D5B5E8522B4C972D00129758 /* AgendaFiltersViewModel.swift in Sources */, D53256C22921946600EA0DE7 /* TagView.swift in Sources */, D560789927BE965200D4012C /* SpeakerItemNavigation.swift in Sources */, D562CF5E27BD9DCB00CF54AF /* HomeViewModel.swift in Sources */, @@ -718,10 +739,12 @@ D524D74027BC00FF0078DF0D /* ScheduleDetailVM.swift in Sources */, D560789727BE906500D4012C /* SpeakerDetailVM.swift in Sources */, D50E9B0527C12AF000B7B9B9 /* NetworkingViewModel.swift in Sources */, + D5B5E8542B4C98E600129758 /* AgendaFiltersVM.swift in Sources */, D544C7E929548B460038CB80 /* SpeakersAvatarView.swift in Sources */, D51C2CD128383719006EED5D /* UIApplicationExt.swift in Sources */, D556ADB929C5F78B00242680 /* AttachmentData.swift in Sources */, D53256C82921A7B900EA0DE7 /* TagUnStyledView.swift in Sources */, + D5B5E8562B4C990000129758 /* AgendaFilters.swift in Sources */, D5309CB227B1CD88001A4F9A /* AgendaVM.swift in Sources */, D59EFCB827B5AD42000AADCF /* EventViewModel.swift in Sources */, D5117AEA2962208B007A47F9 /* SocialHeaderView.swift in Sources */, @@ -733,6 +756,7 @@ D563E2EA29755B2700B5FCA0 /* EventListView.swift in Sources */, D538BED72954F6B700F25CBE /* AlarmScheduler.swift in Sources */, D59944C02833D45A00F59C1B /* TicketQrCodeView.swift in Sources */, + D5B5E8582B4CA63900129758 /* AgendaFiltersNavigation.swift in Sources */, D53256C429219DF200EA0DE7 /* DecorativeTagView.swift in Sources */, D5C92ECC283ACBB000D5CF2D /* MenuItemView.swift in Sources */, D5C92EC9283AC6D400D5CF2D /* PartnersViewModel.swift in Sources */, diff --git a/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/GERARD.xcuserdatad/UserInterfaceState.xcuserstate b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/GERARD.xcuserdatad/UserInterfaceState.xcuserstate index 7c9affb94..ca6f0c3c1 100644 Binary files a/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/GERARD.xcuserdatad/UserInterfaceState.xcuserstate and b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/GERARD.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/iosApp/iosApp/ViewModelFactory.swift b/iosApp/iosApp/ViewModelFactory.swift index 203840d07..b208b4011 100644 --- a/iosApp/iosApp/ViewModelFactory.swift +++ b/iosApp/iosApp/ViewModelFactory.swift @@ -27,6 +27,10 @@ class ViewModelFactory: ObservableObject { return AgendaViewModel() } + func makeAgendaFiltersViewModel() -> AgendaFiltersViewModel { + AgendaFiltersViewModel() + } + func makeScheduleItemViewModel(scheduleId: String) -> ScheduleItemViewModel { return ScheduleItemViewModel(scheduleId: scheduleId) } diff --git a/iosApp/iosApp/en.lproj/Localizable.strings b/iosApp/iosApp/en.lproj/Localizable.strings index cf11b98a8..e8d44df18 100644 --- a/iosApp/iosApp/en.lproj/Localizable.strings +++ b/iosApp/iosApp/en.lproj/Localizable.strings @@ -12,6 +12,7 @@ "screenNetworking" = "Networking"; "screenPartners" = "Partners"; "screenEvent" = "Event"; +"screenFilters" = "Filtres"; "titleGold" = "Gold"; "titleSilver" = "Silver"; @@ -29,6 +30,9 @@ "titleCompany" = "Company"; "titleJob" = "Job"; "title_agenda_break" = "Break"; +"titleFiltersFavorites" = "Favorites"; +"titleFiltersCategories" = "Categories"; +"titleFiltersFormats" = "Formats"; "textLoading" = "Loading…"; "textError" = "Something wrong happened"; diff --git a/iosApp/iosApp/fr.lproj/Localizable.strings b/iosApp/iosApp/fr.lproj/Localizable.strings index 560a899d5..a1231159e 100644 --- a/iosApp/iosApp/fr.lproj/Localizable.strings +++ b/iosApp/iosApp/fr.lproj/Localizable.strings @@ -12,6 +12,7 @@ "screenNetworking" = "Networking"; "screenPartners" = "Partenaires"; "screenEvent" = "Événement"; +"screenFilters" = "Filtres"; "titleGold" = "Gold"; "titleSilver" = "Silver"; @@ -29,6 +30,9 @@ "titleCompany" = "Entreprise"; "titleJob" = "Job"; "title_agenda_break" = "Pause"; +"titleFiltersFavorites" = "Favoris"; +"titleFiltersCategories" = "Categories"; +"titleFiltersFormats" = "Formats"; "textLoading" = "Chargement…"; "textError" = "Une erreur inconnue est survenue"; diff --git a/iosApp/iosApp/navigations/AgendaFiltersNavigation.swift b/iosApp/iosApp/navigations/AgendaFiltersNavigation.swift new file mode 100644 index 000000000..43686e96e --- /dev/null +++ b/iosApp/iosApp/navigations/AgendaFiltersNavigation.swift @@ -0,0 +1,25 @@ +// +// AgendaFiltersNavigation.swift +// iosApp +// +// Created by GERARD on 08/01/2024. +// Copyright © 2024 orgName. All rights reserved. +// + +import SwiftUI + +struct AgendaFiltersNavigation: View { + @EnvironmentObject var viewModelFactory: ViewModelFactory + + var body: some View { + NavigationLink { + AgendaFiltersVM( + viewModel: viewModelFactory.makeAgendaFiltersViewModel() + ) + } label: { + Image(systemName: "line.3.horizontal.decrease.circle") + .accessibilityLabel("actionFilteringFavorites") + } + .buttonStyle(.plain) + } +} diff --git a/iosApp/iosApp/screens/agenda/Agenda.swift b/iosApp/iosApp/screens/agenda/Agenda.swift index faa248dc1..944662ba5 100644 --- a/iosApp/iosApp/screens/agenda/Agenda.swift +++ b/iosApp/iosApp/screens/agenda/Agenda.swift @@ -13,18 +13,15 @@ struct Agenda: View { @State private var selectedDate = "" let dates: [String] let agendas: [String : AgendaUi] - let onFilteringClicked: () -> () let onFavoriteClicked: (TalkItemUi) -> () init( agendas: [String: AgendaUi], - onFilteringClicked: @escaping () -> (), onFavoriteClicked: @escaping (TalkItemUi) -> () ) { self.dates = agendas.keys.map({ key in key }) self.selectedDate = self.dates.first ?? "" self.agendas = agendas - self.onFilteringClicked = onFilteringClicked self.onFavoriteClicked = onFavoriteClicked } @@ -78,18 +75,7 @@ struct Agenda: View { .navigationTitle(Text("screenAgenda")) .navigationBarTitleDisplayMode(.inline) .navigationBarItems(trailing: - HStack { - if (selectedAgenda != nil) { - Button(action: { - onFilteringClicked() - }, label: { - let icon = selectedAgenda!.onlyFavorites ? "line.3.horizontal.decrease.circle.fill" : "line.3.horizontal.decrease.circle" - Image(systemName: icon) - .accessibilityLabel("actionFilteringFavorites") - .accessibilityAddTraits(selectedAgenda!.onlyFavorites ? .isSelected : []) - }) - } - } + AgendaFiltersNavigation() ) } } @@ -109,7 +95,6 @@ struct Agenda_Previews: PreviewProvider { "01-01-2022": AgendaUi.companion.fake, "02-01-2022": AgendaUi.companion.fake ], - onFilteringClicked: {}, onFavoriteClicked: { talk in } ) .environmentObject(ViewModelFactory()) diff --git a/iosApp/iosApp/screens/agenda/AgendaVM.swift b/iosApp/iosApp/screens/agenda/AgendaVM.swift index 7d336abc8..cd1ab072c 100644 --- a/iosApp/iosApp/screens/agenda/AgendaVM.swift +++ b/iosApp/iosApp/screens/agenda/AgendaVM.swift @@ -22,11 +22,6 @@ struct AgendaVM: View { case .success(let agendas): Agenda( agendas: agendas, - onFilteringClicked: { - Task { - viewModel.toggleFavoriteFiltering() - } - }, onFavoriteClicked: { talk in Task { await viewModel.markAsFavorite(talkItem: talk) diff --git a/iosApp/iosApp/screens/agenda/AgendaViewModel.swift b/iosApp/iosApp/screens/agenda/AgendaViewModel.swift index 339833495..83889944e 100644 --- a/iosApp/iosApp/screens/agenda/AgendaViewModel.swift +++ b/iosApp/iosApp/screens/agenda/AgendaViewModel.swift @@ -24,11 +24,6 @@ class AgendaViewModel: ObservableObject { @Published var uiState: AgendaUiState = AgendaUiState.loading private var agendaTask: Task<(), Never>? - - func toggleFavoriteFiltering() { - // TODO - // repository.toggleFavoriteFiltering() - } func fetchAgenda() { agendaTask = Task { diff --git a/iosApp/iosApp/screens/filters/AgendaFilters.swift b/iosApp/iosApp/screens/filters/AgendaFilters.swift new file mode 100644 index 000000000..79e3f25ff --- /dev/null +++ b/iosApp/iosApp/screens/filters/AgendaFilters.swift @@ -0,0 +1,63 @@ +// +// AgendaFilters.swift +// iosApp +// +// Created by GERARD on 08/01/2024. +// Copyright © 2024 orgName. All rights reserved. +// + +import SwiftUI +import SharedDi + +struct AgendaFilters: View { + let filtersUi: FiltersUi + var onFavoriteSelected: (Bool) -> () + var onCategorySelected: (CategoryUi, Bool) -> () + var onFormatSelected: (FormatUi, Bool) -> () + + var body: some View { + List { + Section(header: Text("titleFiltersFavorites")) { + Toggle(isOn: Binding.constant(filtersUi.onlyFavorites), label: { + Text("actionFilteringFavorites") + }) + .onTapGesture { + onFavoriteSelected(!filtersUi.onlyFavorites) + } + } + Section(header: Text("titleFiltersCategories")) { + ForEach(Array(filtersUi.categories.keys), id: \.self) { key in + let isOn = filtersUi.categories[key] ?? false + Toggle(isOn: Binding.constant(isOn as! Bool), label: { + Text(key.name) + }) + .onTapGesture { + onCategorySelected(key, !(isOn as! Bool)) + } + } + } + Section(header: Text("titleFiltersFormats")) { + ForEach(Array(filtersUi.formats.keys), id: \.self) { key in + let isOn = filtersUi.formats[key] ?? false + Toggle(isOn: Binding.constant(isOn as! Bool), label: { + Text(key.name) + }) + .onTapGesture { + onFormatSelected(key, !(isOn as! Bool)) + } + } + } + } + .navigationTitle(Text("screenFilters")) + .navigationBarTitleDisplayMode(.inline) + } +} + +#Preview { + AgendaFilters( + filtersUi: FiltersUi.companion.fake, + onFavoriteSelected: { selected in }, + onCategorySelected: { category, selected in }, + onFormatSelected: { format, selected in } + ) +} diff --git a/iosApp/iosApp/screens/filters/AgendaFiltersVM.swift b/iosApp/iosApp/screens/filters/AgendaFiltersVM.swift new file mode 100644 index 000000000..8e315fd29 --- /dev/null +++ b/iosApp/iosApp/screens/filters/AgendaFiltersVM.swift @@ -0,0 +1,58 @@ +// +// AgendaFiltersVM.swift +// iosApp +// +// Created by GERARD on 08/01/2024. +// Copyright © 2024 orgName. All rights reserved. +// + +import SwiftUI +import SharedDi + +struct AgendaFiltersVM: View { + @ObservedObject var viewModel: AgendaFiltersViewModel + + init(viewModel: AgendaFiltersViewModel) { + self.viewModel = viewModel + } + + var body: some View { + let uiState = viewModel.uiState + NavigationView { + Group { + switch uiState { + case .success(let filtersUi): + AgendaFilters( + filtersUi: filtersUi, + onFavoriteSelected: { selected in + Task { + await viewModel.applyFavoriteFilter(selected: selected) + } + }, + onCategorySelected: { category, selected in + Task { + await viewModel.applyCategoryFilter(categoryUi: category, selected: selected) + } + }, + onFormatSelected: { format, selected in + Task { + await viewModel.applyFormatFilter(formatUi: format, selected: selected) + } + } + ) + case .failure: + Text("textError") + case .loading: + Text("textLoading") + } + } + } + .onAppear { + viewModel.fetchFilters() + } + .onDisappear { + viewModel.stop() + } + } +} + diff --git a/iosApp/iosApp/screens/filters/AgendaFiltersViewModel.swift b/iosApp/iosApp/screens/filters/AgendaFiltersViewModel.swift new file mode 100644 index 000000000..f78d66317 --- /dev/null +++ b/iosApp/iosApp/screens/filters/AgendaFiltersViewModel.swift @@ -0,0 +1,54 @@ +// +// AgendaFiltersViewModel.swift +// iosApp +// +// Created by GERARD on 08/01/2024. +// Copyright © 2024 orgName. All rights reserved. +// + +import SharedDi +import KMPNativeCoroutinesAsync + +enum AgendaFiltersUiState { + case loading + case success(FiltersUi) + case failure +} + +@MainActor +class AgendaFiltersViewModel: ObservableObject { + private let agendaRepository: AgendaRepository = RepositoryHelper().agendaRepository + + @Published var uiState: AgendaFiltersUiState = AgendaFiltersUiState.loading + + private var filtersTask: Task<(), Never>? + + func fetchFilters() { + filtersTask = Task { + do { + let stream = asyncStream(for: agendaRepository.filtersNative()) + for try await filters in stream { + self.uiState = .success(filters) + } + } catch { + self.uiState = .failure + } + } + } + + func stop() { + filtersTask?.cancel() + } + + func applyFavoriteFilter(selected: Bool) async { + agendaRepository.applyFavoriteFilter(selected: selected) + } + + func applyCategoryFilter(categoryUi: CategoryUi, selected: Bool) async { + agendaRepository.applyCategoryFilter(categoryUi: categoryUi, selected: selected) + } + + func applyFormatFilter(formatUi: FormatUi, selected: Bool) async { + agendaRepository.applyFormatFilter(formatUi: formatUi, selected: selected) + } +}