From 7e79638357fe68ef1ca38a8fb11f0848df61609d Mon Sep 17 00:00:00 2001 From: Alexis Drai Date: Sun, 28 May 2023 17:26:43 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=96=96=20Implement=20MVVM=20using=20class?= =?UTF-8?q?es=20instead=20of=20structs=20in=20the=20model,=20and=20giving?= =?UTF-8?q?=20UnitVM=20a=20function=20to=20update=20any=20of=20its=20Subje?= =?UTF-8?q?ctVMs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Graduator/Graduator/MainView.swift | 20 +++++++--- Graduator/Graduator/Model/UnitsManager.swift | 6 +-- Graduator/Graduator/Stub.swift | 16 ++++---- .../View/Cells/SubjectViewCell.swift | 18 ++++++++- .../Graduator/View/Cells/UnitViewCell.swift | 11 +++++- .../View/Components/NoGradesInfo.swift | 7 ++++ Graduator/Graduator/View/Views/UnitView.swift | 39 ++++++++++++++----- Graduator/Graduator/ViewModel/SubjectVM.swift | 23 +---------- Graduator/Graduator/ViewModel/UnitVM.swift | 32 +++++++-------- .../Graduator/ViewModel/UnitsManagerVM.swift | 21 +++------- 10 files changed, 109 insertions(+), 84 deletions(-) diff --git a/Graduator/Graduator/MainView.swift b/Graduator/Graduator/MainView.swift index fe8c185..cb541b1 100644 --- a/Graduator/Graduator/MainView.swift +++ b/Graduator/Graduator/MainView.swift @@ -45,9 +45,14 @@ struct MainView: View { VStack(alignment: .leading) { ForEach(unitsManagerVM.UnitsVM) { unitVM in - NavigationLink( - destination: UnitView(unitVM: unitVM)) { - UnitViewCell(unitVM: unitVM) + NavigationLink(destination: UnitView( + unitVM: unitVM, + unitsManagerVM: unitsManagerVM + )) { + UnitViewCell( + unitVM: unitVM, + unitsManagerVM: unitsManagerVM + ) } } } @@ -58,8 +63,13 @@ struct MainView: View { } struct MainView_Previews: PreviewProvider { + static var ManagerVMStub: UnitsManagerVM = UnitsManagerVM( + unitsManager: UnitsManager( + units: Stub.units + ) + ) + static var previews: some View { - MainView(unitsManagerVM: UnitsManagerVM(unitsManager: UnitsManager(units: Stub.units))) + MainView(unitsManagerVM: ManagerVMStub) } } - diff --git a/Graduator/Graduator/Model/UnitsManager.swift b/Graduator/Graduator/Model/UnitsManager.swift index 253902b..9e9766e 100644 --- a/Graduator/Graduator/Model/UnitsManager.swift +++ b/Graduator/Graduator/Model/UnitsManager.swift @@ -10,10 +10,6 @@ import Foundation struct UnitsManager { var units: [Unit] - - init(units: [Unit]) { - self.units = units - } func getTotalAverage() -> Double? { return getAverage(units: units) @@ -23,7 +19,7 @@ struct UnitsManager { return getAverage(units: units.filter { $0.isProfessional }) } - private func getAverage(units: [Unit]) -> Double? { + func getAverage(units: [Unit]) -> Double? { var totalWeight = 0 var weightedSum = 0.0 diff --git a/Graduator/Graduator/Stub.swift b/Graduator/Graduator/Stub.swift index 851a39f..6ea6956 100644 --- a/Graduator/Graduator/Stub.swift +++ b/Graduator/Graduator/Stub.swift @@ -97,23 +97,23 @@ struct Stub { ), Subject( id: UUID(), - name: "Économie", + name: "Communication", weight: 4, - grade: 9.5/20.0, + grade: 17.13/20.0, isCalled: false ), Subject( id: UUID(), - name: "Gestion", - weight: 3, + name: "Économie", + weight: 4, grade: 9.5/20.0, isCalled: false ), Subject( id: UUID(), - name: "Communication", - weight: 4, - grade: 17.13/20.0, + name: "Gestion", + weight: 3, + grade: 9.5/20.0, isCalled: false ), ] @@ -195,7 +195,7 @@ struct Stub { ), Subject( id: UUID(), - name: "MAUI", + name: "Xamarin", weight: 5, isCalled: false ), diff --git a/Graduator/Graduator/View/Cells/SubjectViewCell.swift b/Graduator/Graduator/View/Cells/SubjectViewCell.swift index c081807..5d98061 100644 --- a/Graduator/Graduator/View/Cells/SubjectViewCell.swift +++ b/Graduator/Graduator/View/Cells/SubjectViewCell.swift @@ -9,7 +9,9 @@ import SwiftUI struct SubjectViewCell: View { @ObservedObject var subjectVM: SubjectVM - + @ObservedObject var unitVM: UnitVM + @ObservedObject var unitsManagerVM: UnitsManagerVM + //TODO also allow using the unitview's navigation bar item "Edit" (makes all subjects editable, and more) @State private var isEditable = false @@ -20,6 +22,8 @@ struct SubjectViewCell: View { Button(action: { isEditable = false subjectVM.onEdited() + unitVM.updateSubject(subjectVM) + unitsManagerVM.updateUnit(unitVM) }) { Image(systemName: "checkmark.square") .foregroundColor(.green) @@ -107,7 +111,17 @@ struct SubjectViewCell: View { struct SubjectViewCell_Previews: PreviewProvider { + static var ManagerVMStub: UnitsManagerVM = UnitsManagerVM( + unitsManager: UnitsManager( + units: Stub.units + ) + ) + static var previews: some View { - SubjectViewCell(subjectVM: SubjectVM(subject: Stub.units[0].subjects[0])) + SubjectViewCell( + subjectVM: ManagerVMStub.UnitsVM[0].SubjectsVM[0], + unitVM: ManagerVMStub.UnitsVM[0], + unitsManagerVM: ManagerVMStub + ) } } diff --git a/Graduator/Graduator/View/Cells/UnitViewCell.swift b/Graduator/Graduator/View/Cells/UnitViewCell.swift index 6b8f67e..eae57cc 100644 --- a/Graduator/Graduator/View/Cells/UnitViewCell.swift +++ b/Graduator/Graduator/View/Cells/UnitViewCell.swift @@ -9,6 +9,7 @@ import SwiftUI struct UnitViewCell: View { @ObservedObject var unitVM: UnitVM + @ObservedObject var unitsManagerVM: UnitsManagerVM var body: some View { VStack { @@ -48,7 +49,15 @@ struct UnitViewCell: View { } struct UnitViewCell_Previews: PreviewProvider { + static var ManagerVMStub: UnitsManagerVM = UnitsManagerVM( + unitsManager: UnitsManager( + units: Stub.units + ) + ) static var previews: some View { - UnitViewCell(unitVM: UnitVM(unit: Stub.units[0])) + UnitViewCell( + unitVM: UnitVM(unit: Stub.units[0]), + unitsManagerVM: ManagerVMStub + ) } } diff --git a/Graduator/Graduator/View/Components/NoGradesInfo.swift b/Graduator/Graduator/View/Components/NoGradesInfo.swift index 878a2ee..6d71141 100644 --- a/Graduator/Graduator/View/Components/NoGradesInfo.swift +++ b/Graduator/Graduator/View/Components/NoGradesInfo.swift @@ -15,3 +15,10 @@ struct NoGradesInfo: View { } } } + + +struct NoGradesInfo_Previews: PreviewProvider { + static var previews: some View { + NoGradesInfo() + } +} diff --git a/Graduator/Graduator/View/Views/UnitView.swift b/Graduator/Graduator/View/Views/UnitView.swift index 8bb8aec..5446a8a 100644 --- a/Graduator/Graduator/View/Views/UnitView.swift +++ b/Graduator/Graduator/View/Views/UnitView.swift @@ -9,14 +9,21 @@ import SwiftUI struct UnitView: View { @ObservedObject var unitVM: UnitVM + @ObservedObject var unitsManagerVM: UnitsManagerVM var body: some View { VStack(alignment: .leading) { - Text("Unit title") - .font(.title) - .padding() + HStack { + Text("UE " + String(unitVM.model.code)) + Text(unitVM.model.name) + } + .font(.title) + .padding() - UnitViewCell(unitVM: unitVM) + UnitViewCell( + unitVM: unitVM, + unitsManagerVM: unitsManagerVM + ) Divider() @@ -33,14 +40,18 @@ struct UnitView: View { .padding(.horizontal) ScrollView { - ForEach(unitVM.subjectVMs) { subjectVM in - SubjectViewCell(subjectVM: subjectVM) + ForEach(unitVM.SubjectsVM) { subjectVM in + SubjectViewCell( + subjectVM: subjectVM, + unitVM: unitVM, + unitsManagerVM: unitsManagerVM + ) } } .navigationBarItems(trailing: Button(action: { // TODO Add action for button. Make editable - // * (LATER) unit weight - // * (LATER) unit description + // * (LATER, in UnitViewCell) unit weight + // * (LATER, in UnitViewCell) unit description // * subjects // * make all fields editable (=> just toggle isEditable is the SubjectCellViews?) // * create new subject (creation screen with simple form for name, weight, code, isCalled. Of course, will need to deal with adding it to the unitVM, updating the unitVM, and updating the unitsmanagerVM with the new unitVM. Check the result to make sure that the model does get updated by the VM in the end) @@ -53,7 +64,17 @@ struct UnitView: View { } struct UnitView_Previews: PreviewProvider { + + static var ManagerVMStub: UnitsManagerVM = UnitsManagerVM( + unitsManager: UnitsManager( + units: Stub.units + ) + ) + static var previews: some View { - UnitView(unitVM: UnitVM(unit: Stub.units[0])) + UnitView( + unitVM: ManagerVMStub.UnitsVM[0], + unitsManagerVM: ManagerVMStub + ) } } diff --git a/Graduator/Graduator/ViewModel/SubjectVM.swift b/Graduator/Graduator/ViewModel/SubjectVM.swift index a10fe6c..6587aef 100644 --- a/Graduator/Graduator/ViewModel/SubjectVM.swift +++ b/Graduator/Graduator/ViewModel/SubjectVM.swift @@ -16,14 +16,6 @@ extension Subject { var isCalled: Bool } - init(subjectData: Subject.Data) { - self.id = subjectData.id - self.name = subjectData.name - self.weight = subjectData.weight - self.grade = subjectData.grade - self.isCalled = subjectData.isCalled - } - var data: Data { Data( id: self.id, @@ -52,7 +44,6 @@ extension Subject { class SubjectVM : ObservableObject, Identifiable { private var original: Subject var id: UUID { original.id } - weak var unitVM: UnitVM? @Published var model: Subject.Data @Published var isEdited: Bool = false @@ -61,11 +52,6 @@ class SubjectVM : ObservableObject, Identifiable { model = original.data } - init(subjectData: Subject.Data) { - self.original = Subject(subjectData: subjectData) - self.model = subjectData - } - convenience init() { self.init(subject: Subject( id: UUID(), @@ -82,14 +68,9 @@ class SubjectVM : ObservableObject, Identifiable { func onEdited(isCancelled: Bool = false) { if(!isCancelled && original.gradeIsValid(model.grade)){ - if (isEdited) { - original.update(from: model) - unitVM?.updateSubjects() - } - } - else { - model = original.data + original.update(from: model) } + model = original.data isEdited = false } } diff --git a/Graduator/Graduator/ViewModel/UnitVM.swift b/Graduator/Graduator/ViewModel/UnitVM.swift index 5baccc2..e6f912f 100644 --- a/Graduator/Graduator/ViewModel/UnitVM.swift +++ b/Graduator/Graduator/ViewModel/UnitVM.swift @@ -14,7 +14,7 @@ extension Unit { var weight: Int var isProfessional: Bool var code: Int - public var subjects: [Subject.Data] = [] + var subjects: [Subject.Data] = [] } var data: Data { @@ -24,7 +24,7 @@ extension Unit { weight: self.weight, isProfessional: self.isProfessional, code: self.code, - subjects: self.subjects.map{ $0.data } + subjects: self.subjects.map { $0.data } ) } @@ -51,15 +51,15 @@ class UnitVM : ObservableObject, Identifiable { var id: UUID { original.id } @Published var model: Unit.Data @Published var isEdited: Bool = false - @Published var subjectVMs: [SubjectVM] + + private var subjectsVM: [SubjectVM] + + public var SubjectsVM: [SubjectVM] { subjectsVM } init(unit: Unit) { original = unit model = original.data - subjectVMs = unit.subjects.map { SubjectVM(subject: $0) } - for subjectVM in subjectVMs { - subjectVM.unitVM = self - } + subjectsVM = unit.subjects.map { SubjectVM(subject: $0) } } convenience init() { @@ -80,20 +80,16 @@ class UnitVM : ObservableObject, Identifiable { func onEdited(isCancelled: Bool = false) { if(!isCancelled){ - if (isEdited) { - original.update(from: model) - // TODO unitsManagerVM?.updateUnits() - } - } - else { - model = original.data + original.update(from: model) } + model = original.data isEdited = false } - func updateSubjects() { - // FIXME neither instruction seems to update the model. At least the unitViewCell wtill displays the old average after we update a grade inside - objectWillChange.send() + func updateSubject(_ subjectVM: SubjectVM) { + guard let index = subjectsVM.firstIndex(where: { $0.id == subjectVM.id }) else { return } + let updatedSubject = subjectsVM[index].model + original.subjects[index].update(from: updatedSubject) model = original.data } @@ -102,6 +98,6 @@ class UnitVM : ObservableObject, Identifiable { } var IsCalled: Bool { - return model.subjects.allSatisfy { $0.isCalled } + return original.subjects.allSatisfy { $0.isCalled } } } diff --git a/Graduator/Graduator/ViewModel/UnitsManagerVM.swift b/Graduator/Graduator/ViewModel/UnitsManagerVM.swift index 4dcb50c..988621e 100644 --- a/Graduator/Graduator/ViewModel/UnitsManagerVM.swift +++ b/Graduator/Graduator/ViewModel/UnitsManagerVM.swift @@ -49,32 +49,23 @@ class UnitsManagerVM : ObservableObject { private var unitsVM: [UnitVM] - public var UnitsVM: [UnitVM] { - unitsVM - } + public var UnitsVM: [UnitVM] { unitsVM } init(unitsManager: UnitsManager) { original = unitsManager model = original.data - unitsVM = unitsManager.units.map { - UnitVM(unit: $0) - } + unitsVM = unitsManager.units.map { UnitVM(unit: $0) } } convenience init() { self.init(unitsManager: UnitsManager(units: [])) } - func onEditing() { + func updateUnit(_ unitVM: UnitVM) { + guard let index = unitsVM.firstIndex(where: { $0.id == unitVM.id }) else { return } + let updatedUnit = unitsVM[index].model + original.units[index].update(from: updatedUnit) model = original.data - isEdited = true - } - - func onEdited(isCancelled: Bool = false) { - if(!isCancelled && isEdited){ - original.update(from: model) - } - isEdited = false } var TotalAverage: Double? {