🔥 Remove buggy optional code (#2)

Reviewed-on: #2
main
Alexis Drai 2 years ago
parent 487f7a2a57
commit 9097f14601

@ -12,7 +12,6 @@ struct Subject : Identifiable {
var name: String var name: String
var weight: Int var weight: Int
var grade: Double? var grade: Double?
var isCalled: Bool
func gradeIsValid(_ grade: Double?) -> Bool { func gradeIsValid(_ grade: Double?) -> Bool {
return grade == nil || (grade! >= 0 && grade! <= 1) return grade == nil || (grade! >= 0 && grade! <= 1)

@ -20,29 +20,25 @@ struct Stub {
id: UUID(), id: UUID(),
name: "Processus de développement", name: "Processus de développement",
weight: 4, weight: 4,
grade: 13.17/20.0, grade: 13.17/20.0
isCalled: false
), ),
Subject( Subject(
id: UUID(), id: UUID(),
name: "Programmation Orientée Objet", name: "Programmation Orientée Objet",
weight: 9, weight: 9,
grade: 13.63/20.0, grade: 13.63/20.0
isCalled: false
), ),
Subject( Subject(
id: UUID(), id: UUID(),
name: "Qualité de développement", name: "Qualité de développement",
weight: 5, weight: 5,
grade: 12.4/20.0, grade: 12.4/20.0
isCalled: true
), ),
Subject( Subject(
id: UUID(), id: UUID(),
name: "Remise à niveau objets", name: "Remise à niveau objets",
weight: 4, weight: 4,
grade: 20.0/20.0, grade: 20.0/20.0
isCalled: true
), ),
] ]
), ),
@ -56,28 +52,24 @@ struct Stub {
Subject( Subject(
id: UUID(), id: UUID(),
name: "Internet des Objets", name: "Internet des Objets",
weight: 4, weight: 4
isCalled: false
), ),
Subject( Subject(
id: UUID(), id: UUID(),
name: "Réseaux", name: "Réseaux",
weight: 4, weight: 4,
grade: 14.5/20.0, grade: 14.5/20.0
isCalled: true
), ),
Subject( Subject(
id: UUID(), id: UUID(),
name: "Services mobiles", name: "Services mobiles",
weight: 4, weight: 4
isCalled: false
), ),
Subject( Subject(
id: UUID(), id: UUID(),
name: "Système", name: "Système",
weight: 5, weight: 5,
grade: 13.88/20.0, grade: 13.88/20.0
isCalled: true
), ),
] ]
), ),
@ -92,29 +84,25 @@ struct Stub {
id: UUID(), id: UUID(),
name: "Anglais", name: "Anglais",
weight: 5, weight: 5,
grade: 18.65/20.0, grade: 18.65/20.0
isCalled: false
), ),
Subject( Subject(
id: UUID(), id: UUID(),
name: "Communication", name: "Communication",
weight: 4, weight: 4,
grade: 17.13/20.0, grade: 17.13/20.0
isCalled: false
), ),
Subject( Subject(
id: UUID(), id: UUID(),
name: "Économie", name: "Économie",
weight: 4, weight: 4,
grade: 9.5/20.0, grade: 9.5/20.0
isCalled: false
), ),
Subject( Subject(
id: UUID(), id: UUID(),
name: "Gestion", name: "Gestion",
weight: 3, weight: 3,
grade: 9.5/20.0, grade: 9.5/20.0
isCalled: false
), ),
] ]
), ),
@ -129,29 +117,25 @@ struct Stub {
id: UUID(), id: UUID(),
name: "Android", name: "Android",
weight: 6, weight: 6,
grade: 4.0/20.0, grade: 4.0/20.0
isCalled: true
), ),
Subject( Subject(
id: UUID(), id: UUID(),
name: "Architecture de projetc C# .NET (1)", name: "Architecture de projetc C# .NET (1)",
weight: 5, weight: 5,
grade: 14.5/20.0, grade: 14.5/20.0
isCalled: true
), ),
Subject( Subject(
id: UUID(), id: UUID(),
name: "C++", name: "C++",
weight: 4, weight: 4,
grade: 10.2/20.0, grade: 10.2/20.0
isCalled: true
), ),
Subject( Subject(
id: UUID(), id: UUID(),
name: "Swift", name: "Swift",
weight: 5, weight: 5,
grade: 14.93/20.0, grade: 14.93/20.0
isCalled: true
), ),
] ]
), ),
@ -166,38 +150,32 @@ struct Stub {
id: UUID(), id: UUID(),
name: "Architecture de projetc C# .NET (2)", name: "Architecture de projetc C# .NET (2)",
weight: 4, weight: 4,
grade: 12.17/20.0, grade: 12.17/20.0
isCalled: false
), ),
Subject( Subject(
id: UUID(), id: UUID(),
name: "Client/Serveur", name: "Client/Serveur",
weight: 4, weight: 4
isCalled: false
), ),
Subject( Subject(
id: UUID(), id: UUID(),
name: "iOS", name: "iOS",
weight: 5, weight: 5
isCalled: false
), ),
Subject( Subject(
id: UUID(), id: UUID(),
name: "Multiplateformes", name: "Multiplateformes",
weight: 3, weight: 3
isCalled: false
), ),
Subject( Subject(
id: UUID(), id: UUID(),
name: "Qt Quick", name: "Qt Quick",
weight: 5, weight: 5
isCalled: false
), ),
Subject( Subject(
id: UUID(), id: UUID(),
name: "Xamarin", name: "Xamarin",
weight: 5, weight: 5
isCalled: false
), ),
] ]
), ),
@ -212,8 +190,7 @@ struct Stub {
id: UUID(), id: UUID(),
name: "Projet", name: "Projet",
weight: 1, weight: 1,
grade: 13.66/20.0, grade: 13.66/20.0
isCalled: true
) )
] ]
), ),
@ -227,8 +204,7 @@ struct Stub {
Subject( Subject(
id: UUID(), id: UUID(),
name: "Stage", name: "Stage",
weight: 1, weight: 1
isCalled: false
) )
] ]
) )

@ -49,7 +49,7 @@ struct SubjectViewCell: View {
} }
), in: 0...1, step: 0.001) ), in: 0...1, step: 0.001)
.accentColor(grade < 0.5 ? .red : .green) .accentColor(grade < 0.5 ? .red : .green)
.disabled(!isGradeEditable || subjectVM.model.isCalled) .disabled(!isGradeEditable)
TextField("", value: Binding( TextField("", value: Binding(
get: { grade * 20.0 }, get: { grade * 20.0 },
@ -60,16 +60,7 @@ struct SubjectViewCell: View {
} }
), formatter: Formatters.gradeFormatter) ), formatter: Formatters.gradeFormatter)
.frame(width: 50) .frame(width: 50)
.disabled(!isGradeEditable || subjectVM.model.isCalled) .disabled(!isGradeEditable)
VStack {
Toggle("", isOn: $subjectVM.model.isCalled)
.frame(width: 40)
Image(systemName: "snowflake.circle.fill")
.foregroundColor(subjectVM.model.isCalled ? .primary : .gray)
}
} else { } else {
NoGradesInfo() NoGradesInfo()
Button(action: { Button(action: {

@ -35,10 +35,6 @@ struct UnitViewCell: View {
Text(String(format: "%.2f", average * 20.0)) Text(String(format: "%.2f", average * 20.0))
Spacer() Spacer()
Image(systemName: "snowflake.circle.fill")
.foregroundColor(unitVM.IsCalled ? .primary : .gray)
} else { } else {
NoGradesInfo() NoGradesInfo()
} }

@ -27,7 +27,6 @@ struct SubjectFormView: View {
Spacer() Spacer()
TextField("Note", text: $formVM.gradeString) TextField("Note", text: $formVM.gradeString)
} }
Toggle("Note définitive?", isOn: $formVM.isCalled)
} }
} }
} }

@ -11,7 +11,6 @@ class SubjectFormVM: ObservableObject {
@Published var name: String = "" @Published var name: String = ""
@Published var weight: Int = 1 @Published var weight: Int = 1
@Published var gradeString: String = "" @Published var gradeString: String = ""
@Published var isCalled: Bool = false
var grade: Double? { var grade: Double? {
if let gradeOverTwenty = Double(gradeString) { if let gradeOverTwenty = Double(gradeString) {
@ -22,7 +21,7 @@ class SubjectFormVM: ObservableObject {
} }
func isValid(_ subject: Subject) -> Bool { func isValid(_ subject: Subject) -> Bool {
return !(name.isEmpty || weight <= 0 || !subject.gradeIsValid(grade) || (isCalled && grade == nil)) return !(name.isEmpty || weight <= 0 || !subject.gradeIsValid(grade))
} }
func createSubject() -> Subject? { func createSubject() -> Subject? {
@ -30,8 +29,7 @@ class SubjectFormVM: ObservableObject {
id: UUID(), id: UUID(),
name: name, name: name,
weight: weight, weight: weight,
grade: grade, grade: grade)
isCalled: isCalled)
guard isValid(subject) else { return nil } guard isValid(subject) else { return nil }
return subject return subject
} }

@ -13,7 +13,6 @@ extension Subject {
var name: String var name: String
var weight: Int var weight: Int
var grade: Double? var grade: Double?
var isCalled: Bool
} }
var data: Data { var data: Data {
@ -21,19 +20,12 @@ extension Subject {
id: self.id, id: self.id,
name: self.name, name: self.name,
weight: self.weight, weight: self.weight,
grade: self.grade, grade: self.grade
isCalled: self.isCalled
) )
} }
mutating func update(from data: Data) { mutating func update(from data: Data) {
// papers please
guard data.id == self.data.id else { return } guard data.id == self.data.id else { return }
// can't update grade if this subject is called, unless the update is to 'un-call' the subject
guard !(self.isCalled && data.isCalled && self.grade != data.grade) else { return }
// can't update a subject to become called and have a nil grade at the same time
guard !(data.grade == nil && data.isCalled) else { return }
if (!data.name.isEmpty) { if (!data.name.isEmpty) {
self.name = data.name self.name = data.name
} }
@ -43,9 +35,7 @@ extension Subject {
} else { } else {
self.grade = nil self.grade = nil
} }
self.isCalled = data.isCalled
} }
} }
class SubjectVM : ObservableObject, Identifiable { class SubjectVM : ObservableObject, Identifiable {
@ -63,8 +53,7 @@ class SubjectVM : ObservableObject, Identifiable {
self.init(subject: Subject( self.init(subject: Subject(
id: UUID(), id: UUID(),
name: "", name: "",
weight: 1, weight: 1
isCalled: false
)) ))
} }

@ -41,8 +41,7 @@ extension Unit {
id: $0.id, id: $0.id,
name: $0.name, name: $0.name,
weight: $0.weight, weight: $0.weight,
grade: $0.grade, grade: $0.grade
isCalled: $0.isCalled
) )
} }
} }
@ -117,8 +116,4 @@ class UnitVM : ObservableObject, Identifiable {
var Average: Double? { var Average: Double? {
return original.getAverage() return original.getAverage()
} }
var IsCalled: Bool {
return original.subjects.allSatisfy { $0.isCalled }
}
} }

@ -31,8 +31,7 @@ extension UnitsManager {
id: $0.id, id: $0.id,
name: $0.name, name: $0.name,
weight: $0.weight, weight: $0.weight,
grade: $0.grade, grade: $0.grade
isCalled: $0.isCalled
) )
} }
) )

@ -2,29 +2,46 @@
Graduator is an iOS application developed with SwiftUI that helps users manage their academic `units`, `subjects`, and grades. Users can add and delete `subjects`, edit the weight and name of `subjects` and `units`, and input grades. The app displays weighted averages and explains the conditions for graduating from the Clermont Auvergne Tech Institute's mobile development BSc in 2023. Graduator is an iOS application developed with SwiftUI that helps users manage their academic `units`, `subjects`, and grades. Users can add and delete `subjects`, edit the weight and name of `subjects` and `units`, and input grades. The app displays weighted averages and explains the conditions for graduating from the Clermont Auvergne Tech Institute's mobile development BSc in 2023.
<img src="./docs/home.png" height="700" style="margin:20px" alt="view from the home page">
<img src="./docs/unit.png" height="700" style="margin:20px" alt="view from a unit page">
## Features ## Features
Beyond those basic features, some details need to be specified here. Beyond those basic features, some details need to be specified here.
### isCalled Feature ### Weighted average
The isCalled property, available for each subject, denotes whether the grade of a specific subject has been called or not -- i.e., whether it is a definitive grade or a temporary one. A weighted average means that a `subject` or `unit`'s weight plays a part in calculating the average. Users can observe that increasing the weight of a `subject`, for instance, will make the average of the parent `unit` tend more towards that `subject`'s grade.
From the user's point of view, they can see an image of a snowflake that turns from gray to primary color when a subject grade has been called. A Toggle control allows them to set or unset the isCalled status. If they attempt to change the grade of a subject that has been called, they will not be able to -- the corresponding slider and textfield will remain disabled. <img src="./docs/weight_1.png" height="700" style="margin:20px" alt="before changing a subject's weight">
<img src="./docs/weight_2.png" height="700" style="margin:20px" alt="after changing a subject's weight">
In the model and view model, the `isCalled` property of `subjects` affects the state of their `unit`. If all of a `unit`'s `subject` grades are called, the `unit` is considered called too. Note that if a `subject`'s grade is not set (nil), the `subject` can't be called. ### Deleting a `Subject`
### Deleting a Subject In the app, users can delete a `subject` by swiping it off the list, right-to-left.
In the app, users can delete a subject by swiping it off the list, right-to-left. <img src="./docs/delete_1.png" height="700" style="margin:20px" alt="deleting a subject">
<img src="./docs/delete_2.png" height="700" style="margin:20px" alt="subject deleted">
Note that when a subject is deleted, it is permanently removed from the system. If a user is in the process of editing and deletes a subject, the deletion occurs immediately upon swiping, not when they save (click 'OK'). If the user chooses to cancel their edits (click 'Annuler'), all other unsaved changes will be discarded, but the deletion of the subject remains. Note that when a `subject` is deleted, it is permanently removed from the system. If a user is in the process of editing and deletes a `subject`, the deletion occurs immediately upon swiping, not when they save (click 'OK'). If the user chooses to cancel their edits (click *'Annuler'*), all other unsaved changes will be discarded, but the deletion of the `subject` remains.
### Changing a grade ### Changing a grade
Before a user changes a grade, they first need to activate the `lock.open` toggle. If the grade is *called*, they also need to use the `isCalled` toggle. Before a user changes a grade, they first need to activate the `lock.open` toggle.
<img src="./docs/grade_1.png" height="700" style="margin:20px" alt="changing a grade">
After a grade was changed, in order to save the change and to see it reflected in the `unit`'s weighted average, users need to use the (`lock.open` previously) `checkmark` toggle.
<img src="./docs/grade_2.png" height="700" style="margin:20px" alt="grade changed">
### Creating a `Subject`
Finally, users can create a `subject` when in edit mode. After clicking on *'Modifier'*, look for a `+` in the top navigation bar.
<img src="./docs/create_1.png" height="700" style="margin:20px" alt="creating a subject">
<img src="./docs/create_2.png" height="700" style="margin:20px" alt="subject created">
After a grade was changed, in order to save the change and to see it reflected iun the weighted average, users need to use the (previously `lock.open`, now) `checkmark` toggle.
## Architecture ## Architecture
@ -55,7 +72,6 @@ classDiagram
+deleteSubject(subjectVM: SubjectVM): Void +deleteSubject(subjectVM: SubjectVM): Void
+addSubject(subject: Subject): Void +addSubject(subject: Subject): Void
+Average: Double? +Average: Double?
+IsCalled: Bool
} }
class UnitView { class UnitView {
@ -68,7 +84,6 @@ classDiagram
+name: String +name: String
+weight: Int +weight: Int
+grade: Double? +grade: Double?
+isCalled: Bool
+gradeIsValid(grade: Double?): Bool +gradeIsValid(grade: Double?): Bool
+data: Data +data: Data
+update(from: Data): Void +update(from: Data): Void
@ -123,10 +138,15 @@ classDiagram
MainView -- UnitsManagerVM : Observes MainView -- UnitsManagerVM : Observes
``` ```
It might be useful to note that, just like `UnitVM`s aggregate `SubjectVM`s, `Unit`s aggregate `Subject`s, but these relationship between `Model` entities were removed from the diagram above for clarity. It might be useful to note that, just like `UnitVM`s aggregate `SubjectVM`s, `Unit`s aggregate `Subject`s, but these relationship between `Model` entities were removed from the diagram above for clarity.
Here is the diagram with those relationships depicted. Here is the diagram with those relationships depicted.
```mermaid ```mermaid
classDiagram classDiagram
class Unit { class Unit {
@ -152,7 +172,6 @@ classDiagram
+deleteSubject(subjectVM: SubjectVM): Void +deleteSubject(subjectVM: SubjectVM): Void
+addSubject(subject: Subject): Void +addSubject(subject: Subject): Void
+Average: Double? +Average: Double?
+IsCalled: Bool
} }
class UnitView { class UnitView {
@ -165,7 +184,6 @@ classDiagram
+name: String +name: String
+weight: Int +weight: Int
+grade: Double? +grade: Double?
+isCalled: Bool
+gradeIsValid(grade: Double?): Bool +gradeIsValid(grade: Double?): Bool
+data: Data +data: Data
+update(from: Data): Void +update(from: Data): Void

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Loading…
Cancel
Save