🖖 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) {
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)
}
}

@ -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

@ -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
),

@ -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
)
}
}

@ -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
)
}
}

@ -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 {
@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
)
}
}

@ -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
}
}

@ -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 }
}
}

@ -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? {

Loading…
Cancel
Save