🖖 Implement MVVM

using classes instead of structs in the model, and giving UnitVM
a function to update any of its SubjectVMs
pull/1/head
Alexis Drai 2 years ago
parent 3c3e30eb7d
commit 7e79638357

@ -45,9 +45,14 @@ struct MainView: View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
ForEach(unitsManagerVM.UnitsVM) { unitVM in ForEach(unitsManagerVM.UnitsVM) { unitVM in
NavigationLink( NavigationLink(destination: UnitView(
destination: UnitView(unitVM: unitVM)) { unitVM: unitVM,
UnitViewCell(unitVM: unitVM) unitsManagerVM: unitsManagerVM
)) {
UnitViewCell(
unitVM: unitVM,
unitsManagerVM: unitsManagerVM
)
} }
} }
} }
@ -58,8 +63,13 @@ struct MainView: View {
} }
struct MainView_Previews: PreviewProvider { struct MainView_Previews: PreviewProvider {
static var ManagerVMStub: UnitsManagerVM = UnitsManagerVM(
unitsManager: UnitsManager(
units: Stub.units
)
)
static var previews: some View { static var previews: some View {
MainView(unitsManagerVM: UnitsManagerVM(unitsManager: UnitsManager(units: Stub.units))) MainView(unitsManagerVM: ManagerVMStub)
} }
} }

@ -11,10 +11,6 @@ struct UnitsManager {
var units: [Unit] var units: [Unit]
init(units: [Unit]) {
self.units = units
}
func getTotalAverage() -> Double? { func getTotalAverage() -> Double? {
return getAverage(units: units) return getAverage(units: units)
} }
@ -23,7 +19,7 @@ struct UnitsManager {
return getAverage(units: units.filter { $0.isProfessional }) return getAverage(units: units.filter { $0.isProfessional })
} }
private func getAverage(units: [Unit]) -> Double? { func getAverage(units: [Unit]) -> Double? {
var totalWeight = 0 var totalWeight = 0
var weightedSum = 0.0 var weightedSum = 0.0

@ -97,23 +97,23 @@ struct Stub {
), ),
Subject( Subject(
id: UUID(), id: UUID(),
name: "Économie", name: "Communication",
weight: 4, weight: 4,
grade: 9.5/20.0, grade: 17.13/20.0,
isCalled: false isCalled: false
), ),
Subject( Subject(
id: UUID(), id: UUID(),
name: "Gestion", name: "Économie",
weight: 3, weight: 4,
grade: 9.5/20.0, grade: 9.5/20.0,
isCalled: false isCalled: false
), ),
Subject( Subject(
id: UUID(), id: UUID(),
name: "Communication", name: "Gestion",
weight: 4, weight: 3,
grade: 17.13/20.0, grade: 9.5/20.0,
isCalled: false isCalled: false
), ),
] ]
@ -195,7 +195,7 @@ struct Stub {
), ),
Subject( Subject(
id: UUID(), id: UUID(),
name: "MAUI", name: "Xamarin",
weight: 5, weight: 5,
isCalled: false isCalled: false
), ),

@ -9,6 +9,8 @@ import SwiftUI
struct SubjectViewCell: View { struct SubjectViewCell: View {
@ObservedObject var subjectVM: SubjectVM @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) //TODO also allow using the unitview's navigation bar item "Edit" (makes all subjects editable, and more)
@State private var isEditable = false @State private var isEditable = false
@ -20,6 +22,8 @@ struct SubjectViewCell: View {
Button(action: { Button(action: {
isEditable = false isEditable = false
subjectVM.onEdited() subjectVM.onEdited()
unitVM.updateSubject(subjectVM)
unitsManagerVM.updateUnit(unitVM)
}) { }) {
Image(systemName: "checkmark.square") Image(systemName: "checkmark.square")
.foregroundColor(.green) .foregroundColor(.green)
@ -107,7 +111,17 @@ struct SubjectViewCell: View {
struct SubjectViewCell_Previews: PreviewProvider { struct SubjectViewCell_Previews: PreviewProvider {
static var ManagerVMStub: UnitsManagerVM = UnitsManagerVM(
unitsManager: UnitsManager(
units: Stub.units
)
)
static var previews: some View { 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
)
} }
} }

@ -9,6 +9,7 @@ import SwiftUI
struct UnitViewCell: View { struct UnitViewCell: View {
@ObservedObject var unitVM: UnitVM @ObservedObject var unitVM: UnitVM
@ObservedObject var unitsManagerVM: UnitsManagerVM
var body: some View { var body: some View {
VStack { VStack {
@ -48,7 +49,15 @@ struct UnitViewCell: View {
} }
struct UnitViewCell_Previews: PreviewProvider { struct UnitViewCell_Previews: PreviewProvider {
static var ManagerVMStub: UnitsManagerVM = UnitsManagerVM(
unitsManager: UnitsManager(
units: Stub.units
)
)
static var previews: some View { static var previews: some View {
UnitViewCell(unitVM: UnitVM(unit: Stub.units[0])) UnitViewCell(
unitVM: UnitVM(unit: Stub.units[0]),
unitsManagerVM: ManagerVMStub
)
} }
} }

@ -15,3 +15,10 @@ struct NoGradesInfo: View {
} }
} }
} }
struct NoGradesInfo_Previews: PreviewProvider {
static var previews: some View {
NoGradesInfo()
}
}

@ -9,14 +9,21 @@ import SwiftUI
struct UnitView: View { struct UnitView: View {
@ObservedObject var unitVM: UnitVM @ObservedObject var unitVM: UnitVM
@ObservedObject var unitsManagerVM: UnitsManagerVM
var body: some View { var body: some View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
Text("Unit title") HStack {
Text("UE " + String(unitVM.model.code))
Text(unitVM.model.name)
}
.font(.title) .font(.title)
.padding() .padding()
UnitViewCell(unitVM: unitVM) UnitViewCell(
unitVM: unitVM,
unitsManagerVM: unitsManagerVM
)
Divider() Divider()
@ -33,14 +40,18 @@ struct UnitView: View {
.padding(.horizontal) .padding(.horizontal)
ScrollView { ScrollView {
ForEach(unitVM.subjectVMs) { subjectVM in ForEach(unitVM.SubjectsVM) { subjectVM in
SubjectViewCell(subjectVM: subjectVM) SubjectViewCell(
subjectVM: subjectVM,
unitVM: unitVM,
unitsManagerVM: unitsManagerVM
)
} }
} }
.navigationBarItems(trailing: Button(action: { .navigationBarItems(trailing: Button(action: {
// TODO Add action for button. Make editable // TODO Add action for button. Make editable
// * (LATER) unit weight // * (LATER, in UnitViewCell) unit weight
// * (LATER) unit description // * (LATER, in UnitViewCell) unit description
// * subjects // * subjects
// * make all fields editable (=> just toggle isEditable is the SubjectCellViews?) // * 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) // * 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 { struct UnitView_Previews: PreviewProvider {
static var ManagerVMStub: UnitsManagerVM = UnitsManagerVM(
unitsManager: UnitsManager(
units: Stub.units
)
)
static var previews: some View { static var previews: some View {
UnitView(unitVM: UnitVM(unit: Stub.units[0])) UnitView(
unitVM: ManagerVMStub.UnitsVM[0],
unitsManagerVM: ManagerVMStub
)
} }
} }

@ -16,14 +16,6 @@ extension Subject {
var isCalled: Bool 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 { var data: Data {
Data( Data(
id: self.id, id: self.id,
@ -52,7 +44,6 @@ extension Subject {
class SubjectVM : ObservableObject, Identifiable { class SubjectVM : ObservableObject, Identifiable {
private var original: Subject private var original: Subject
var id: UUID { original.id } var id: UUID { original.id }
weak var unitVM: UnitVM?
@Published var model: Subject.Data @Published var model: Subject.Data
@Published var isEdited: Bool = false @Published var isEdited: Bool = false
@ -61,11 +52,6 @@ class SubjectVM : ObservableObject, Identifiable {
model = original.data model = original.data
} }
init(subjectData: Subject.Data) {
self.original = Subject(subjectData: subjectData)
self.model = subjectData
}
convenience init() { convenience init() {
self.init(subject: Subject( self.init(subject: Subject(
id: UUID(), id: UUID(),
@ -82,14 +68,9 @@ class SubjectVM : ObservableObject, Identifiable {
func onEdited(isCancelled: Bool = false) { func onEdited(isCancelled: Bool = false) {
if(!isCancelled && original.gradeIsValid(model.grade)){ if(!isCancelled && original.gradeIsValid(model.grade)){
if (isEdited) {
original.update(from: model) original.update(from: model)
unitVM?.updateSubjects()
}
} }
else {
model = original.data model = original.data
}
isEdited = false isEdited = false
} }
} }

@ -14,7 +14,7 @@ extension Unit {
var weight: Int var weight: Int
var isProfessional: Bool var isProfessional: Bool
var code: Int var code: Int
public var subjects: [Subject.Data] = [] var subjects: [Subject.Data] = []
} }
var data: Data { var data: Data {
@ -51,15 +51,15 @@ class UnitVM : ObservableObject, Identifiable {
var id: UUID { original.id } var id: UUID { original.id }
@Published var model: Unit.Data @Published var model: Unit.Data
@Published var isEdited: Bool = false @Published var isEdited: Bool = false
@Published var subjectVMs: [SubjectVM]
private var subjectsVM: [SubjectVM]
public var SubjectsVM: [SubjectVM] { subjectsVM }
init(unit: Unit) { init(unit: Unit) {
original = unit original = unit
model = original.data model = original.data
subjectVMs = unit.subjects.map { SubjectVM(subject: $0) } subjectsVM = unit.subjects.map { SubjectVM(subject: $0) }
for subjectVM in subjectVMs {
subjectVM.unitVM = self
}
} }
convenience init() { convenience init() {
@ -80,20 +80,16 @@ class UnitVM : ObservableObject, Identifiable {
func onEdited(isCancelled: Bool = false) { func onEdited(isCancelled: Bool = false) {
if(!isCancelled){ if(!isCancelled){
if (isEdited) {
original.update(from: model) original.update(from: model)
// TODO unitsManagerVM?.updateUnits()
} }
}
else {
model = original.data model = original.data
}
isEdited = false isEdited = false
} }
func updateSubjects() { func updateSubject(_ subjectVM: SubjectVM) {
// FIXME neither instruction seems to update the model. At least the unitViewCell wtill displays the old average after we update a grade inside guard let index = subjectsVM.firstIndex(where: { $0.id == subjectVM.id }) else { return }
objectWillChange.send() let updatedSubject = subjectsVM[index].model
original.subjects[index].update(from: updatedSubject)
model = original.data model = original.data
} }
@ -102,6 +98,6 @@ class UnitVM : ObservableObject, Identifiable {
} }
var IsCalled: Bool { var IsCalled: Bool {
return model.subjects.allSatisfy { $0.isCalled } return original.subjects.allSatisfy { $0.isCalled }
} }
} }

@ -49,32 +49,23 @@ class UnitsManagerVM : ObservableObject {
private var unitsVM: [UnitVM] private var unitsVM: [UnitVM]
public var UnitsVM: [UnitVM] { public var UnitsVM: [UnitVM] { unitsVM }
unitsVM
}
init(unitsManager: UnitsManager) { init(unitsManager: UnitsManager) {
original = unitsManager original = unitsManager
model = original.data model = original.data
unitsVM = unitsManager.units.map { unitsVM = unitsManager.units.map { UnitVM(unit: $0) }
UnitVM(unit: $0)
}
} }
convenience init() { convenience init() {
self.init(unitsManager: UnitsManager(units: [])) 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 model = original.data
isEdited = true
}
func onEdited(isCancelled: Bool = false) {
if(!isCancelled && isEdited){
original.update(from: model)
}
isEdited = false
} }
var TotalAverage: Double? { var TotalAverage: Double? {

Loading…
Cancel
Save