Gradebook Design Update

This commit is contained in:
Sebastian Seedorf
2019-10-22 02:12:52 +02:00
parent 5077898031
commit da03253172
9 changed files with 340 additions and 91 deletions

View File

@@ -13,6 +13,7 @@ import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.kvv.types.Grade;
import de.sebse.fuplanner.services.kvv.types.Gradebook;
import de.sebse.fuplanner.services.kvv.types.Modules;
import de.sebse.fuplanner.tools.ui.StringViewHolder;
@@ -20,9 +21,12 @@ class ModDetailGradebookAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
private static final int TYPE_TOTAL = 0;
private static final int TYPE_GRADE = 1;
private static final int SECTION_GRADE = 0;
private static final int SECTION_EXAM = 0;
private static final int SECTION_ASSIGNMENT = 1;
private static final int SECTION_OTHER = 2;
private Modules.Module mValue;
private Gradebook.CategorizedGrades mGrades;
private final ArrayList<Pair<Integer, Integer>> mPositionalData;
ModDetailGradebookAdapter() {
@@ -38,9 +42,32 @@ class ModDetailGradebookAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
private void setModule() {
mPositionalData.clear();
mPositionalData.add(new Pair<>(TYPE_TOTAL, SECTION_GRADE));
for (int i = 0; i < getGradesCount(); i++) {
mPositionalData.add(new Pair<>(TYPE_GRADE, SECTION_GRADE +1024*i));
if (mValue != null && mValue.gradebook != null) {
mGrades = mValue.gradebook.getAutoCategorizedGrades();
int size;
size = mGrades.getExams().grades.size();
if (size > 0) {
mPositionalData.add(new Pair<>(setViewType(TYPE_TOTAL, SECTION_EXAM), 0));
for (int i = 0; i < size; i++) {
mPositionalData.add(new Pair<>(setViewType(TYPE_GRADE, SECTION_EXAM), i));
}
}
size = mGrades.getAssignments().grades.size();
if (size > 0) {
mPositionalData.add(new Pair<>(setViewType(TYPE_TOTAL, SECTION_ASSIGNMENT), 0));
for (int i = 0; i < size; i++) {
mPositionalData.add(new Pair<>(setViewType(TYPE_GRADE, SECTION_ASSIGNMENT), i));
}
}
size = mGrades.getOthers().grades.size();
if (size > 0) {
mPositionalData.add(new Pair<>(setViewType(TYPE_TOTAL, SECTION_OTHER), 0));
for (int i = 0; i < size; i++) {
mPositionalData.add(new Pair<>(setViewType(TYPE_GRADE, SECTION_OTHER), i));
}
}
} else {
mGrades = null;
}
this.notifyDataSetChanged();
@@ -53,8 +80,8 @@ class ModDetailGradebookAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
switch (viewType) {
case TYPE_TOTAL:
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_caption, parent, false);
return new StringViewHolder(view);
.inflate(R.layout.list_moddetails_gradebook_title, parent, false);
return new GradeTitleViewHolder(view);
case TYPE_GRADE:
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_moddetails_gradebook, parent, false);
@@ -69,7 +96,7 @@ class ModDetailGradebookAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
public int getItemViewType(int position) {
// Note that unlike in ListView adapters, types don't have to be contiguous
if (position < mPositionalData.size())
return mPositionalData.get(position).first;
return getViewType(mPositionalData.get(position).first);
else return -1;
}
@@ -79,19 +106,45 @@ class ModDetailGradebookAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
if (mValue == null || position > mPositionalData.size())
return;
Pair<Integer, Integer> data = mPositionalData.get(position);
switch (data.first) {
int viewType = getViewType(data.first);
int viewSection = getViewSection(data.first);
Gradebook.GradebookOutput gradebook = getViewSectionGradebook(viewSection);
switch (viewType) {
case TYPE_TOTAL:
StringViewHolder h = (StringViewHolder) holder;
h.mString.setText(h.mView.getResources().getString(R.string.current_percentage, mValue.getGradebookPercent()*100));
break;
case TYPE_GRADE:
int index = data.second / 1024;
GradeViewHolder i = (GradeViewHolder) holder;
Grade gradebook = mValue.gradebook.get(index);
GradeTitleViewHolder h = (GradeTitleViewHolder) holder;
i.mTitle.setText(gradebook.getItemName());
i.mGrade.setText(String.valueOf(gradebook.getPoints()));
i.mGradeMax.setText(String.valueOf(gradebook.getMaxPoints()));
if (gradebook != null) {
String title = "";
switch (viewSection) {
case SECTION_EXAM: title = h.mView.getResources().getString(R.string.exam); break;
case SECTION_ASSIGNMENT: title = h.mView.getResources().getString(R.string.assignments); break;
case SECTION_OTHER: title = h.mView.getResources().getString(R.string.others); break;
}
h.mTitle.setText(title);
Grade bestGrade = gradebook.getBestGrade();
h.mString.setText(h.mView.getResources().getString(
R.string.current_percentage,
gradebook.getUserPointSum(),
gradebook.getMaxPointSum(),
gradebook.getPercentage() * 100,
bestGrade.getPoints(),
bestGrade.getMaxPoints(),
bestGrade.getPoints() / bestGrade.getMaxPoints() * 100,
bestGrade.getItemName()
));
}
break;
case TYPE_GRADE:
int index = data.second;
GradeViewHolder i = (GradeViewHolder) holder;
if (gradebook != null) {
Grade grade = gradebook.grades.get(index);
i.mTitle.setText(grade.getItemName());
i.mGrade.setText(String.valueOf(grade.getPoints()));
i.mGradeMax.setText(String.valueOf(grade.getMaxPoints()));
}
break;
}
}
@@ -101,10 +154,27 @@ class ModDetailGradebookAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
return mPositionalData.size();
}
private int getGradesCount() {
if (mValue.gradebook != null)
return mValue.gradebook.size();
return 0;
private int setViewType(int type, int section) {
return type * 1024 + section;
}
private int getViewType(int combinedViewType) {
return combinedViewType / 1024;
}
private int getViewSection(int combinedViewType) {
return combinedViewType - getViewType(combinedViewType) * 1024;
}
private Gradebook.GradebookOutput getViewSectionGradebook(int viewSection) {
if (mGrades != null) {
switch (viewSection) {
case SECTION_EXAM: return mGrades.getExams();
case SECTION_ASSIGNMENT: return mGrades.getAssignments();
case SECTION_OTHER: return mGrades.getOthers();
}
}
return null;
}
@@ -133,4 +203,19 @@ class ModDetailGradebookAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
return super.toString() + " '" + mTitle.getText() + " '" + mGrade.getText() + " '" + mGradeMax.getText() + "'";
}
}
private class GradeTitleViewHolder extends StringViewHolder {
private final TextView mTitle;
GradeTitleViewHolder(View view) {
super(view);
mTitle = view.findViewById(R.id.title);
}
@NonNull
@Override
public String toString() {
return super.toString() + " '" + mTitle.getText();
}
}
}

View File

@@ -9,31 +9,32 @@ import org.json.JSONObject;
import java.util.ArrayList;
import de.sebse.fuplanner.services.kvv.types.Grade;
import de.sebse.fuplanner.services.kvv.types.Gradebook;
import de.sebse.fuplanner.services.kvv.types.Modules;
import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkError;
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
public class ModulesGradebook extends PartModules<ArrayList<Grade>> {
public class ModulesGradebook extends PartModules<Gradebook> {
ModulesGradebook(Login login, ModulesList list, Context context) {
super(login, list, context);
}
@Override
protected ArrayList<Grade> getPart(Modules.Module module) {
protected Gradebook getPart(Modules.Module module) {
return module.gradebook;
}
@Override
protected boolean setPart(Modules.Module module, ArrayList<Grade> part) {
protected boolean setPart(Modules.Module module, Gradebook part) {
boolean changed = module.gradebook == null || module.gradebook.hashCode() != part.hashCode();
module.gradebook = part;
return changed;
}
@Override
protected void upgradeKVV(final String ID, final NetworkCallback<ArrayList<Grade>> callback, final NetworkErrorCallback errorCallback) {
protected void upgradeKVV(final String ID, final NetworkCallback<Gradebook> callback, final NetworkErrorCallback errorCallback) {
if (!mLogin.isInOnlineMode() || mLogin.getLoginTokenKVV() == null || !mLogin.getLoginTokenKVV().isAvailable()) {
errorCallback.onError(new NetworkError(101504, 500, "Currently running in offline mode!"));
return;
@@ -44,7 +45,7 @@ public class ModulesGradebook extends PartModules<ArrayList<Grade>> {
errorCallback.onError(new NetworkError(101501, 403, "No gradebook retrieved!"));
return;
}
ArrayList<Grade> gradebook = new ArrayList<>();
Gradebook gradebook = new Gradebook();
JSONArray sites;
try {
JSONObject json = new JSONObject(body);
@@ -62,7 +63,7 @@ public class ModulesGradebook extends PartModules<ArrayList<Grade>> {
String itemName = site.optString("itemName", null);
double maxPoints = site.optDouble("points", -1);
gradebook.add(0, new Grade(itemName, grade, maxPoints));
gradebook.addGrade(new Grade(itemName, grade, maxPoints));
} catch (JSONException e) {
log.e(new NetworkError(101505, 403, "Cannot parse gradebook!"));
log.e("ID:", i, "JSON:", sites);
@@ -73,14 +74,14 @@ public class ModulesGradebook extends PartModules<ArrayList<Grade>> {
callback.onResponse(gradebook);
}, error -> {
if (error.networkResponse.statusCode == 400)
callback.onResponse(new ArrayList<>());
callback.onResponse(new Gradebook());
else
errorCallback.onError(new NetworkError(101503, error.networkResponse.statusCode, "Cannot get gradebook!"));
});
}
@Override
protected void upgradeBB(String ID, NetworkCallback<ArrayList<Grade>> callback, NetworkErrorCallback errorCallback) {
protected void upgradeBB(String ID, NetworkCallback<Gradebook> callback, NetworkErrorCallback errorCallback) {
if (!mLogin.isInOnlineMode() || mLogin.getLoginTokenBB() == null || !mLogin.getLoginTokenBB().isAvailable()) {
errorCallback.onError(new NetworkError(101510, 500, "Currently running in offline mode!"));
return;
@@ -117,7 +118,7 @@ public class ModulesGradebook extends PartModules<ArrayList<Grade>> {
return;
}
ArrayList<Grade> result = new ArrayList<>();
Gradebook result = new Gradebook();
for (int i = 0; i < grades.length(); i++) {
for (int j = 0; j < gradeColumns.length(); j++) {
try {
@@ -133,7 +134,7 @@ public class ModulesGradebook extends PartModules<ArrayList<Grade>> {
JSONObject score = column.optJSONObject("score");
double maxPoints = score != null ? score.optDouble("possible", 0) : 0;
result.add(new Grade(name, points, maxPoints));
result.addGrade(new Grade(name, points, maxPoints));
} catch (JSONException e) {
log.e(new NetworkError(101515, 400, "Cannot parse grades!"));
log.e("ID:", i, "JSON-grades:", grades);
@@ -147,7 +148,7 @@ public class ModulesGradebook extends PartModules<ArrayList<Grade>> {
}, error -> errorCallback.onError(new NetworkError(101516, error.networkResponse.statusCode, "Cannot get gradebook columns!")));
}, error -> {
if (error.networkResponse.statusCode == 403)
callback.onResponse(new ArrayList<>());
callback.onResponse(new Gradebook());
else
errorCallback.onError(new NetworkError(101517, error.networkResponse.statusCode, "Cannot get gradebook entries!"));
});

View File

@@ -23,6 +23,7 @@ import de.sebse.fuplanner.services.kvv.types.Assignment;
import de.sebse.fuplanner.services.kvv.types.AssignmentList;
import de.sebse.fuplanner.services.kvv.types.EventList;
import de.sebse.fuplanner.services.kvv.types.Grade;
import de.sebse.fuplanner.services.kvv.types.Gradebook;
import de.sebse.fuplanner.services.kvv.types.Modules;
import de.sebse.fuplanner.services.kvv.types.Resource;
import de.sebse.fuplanner.tools.CustomNotificationManager;
@@ -126,7 +127,7 @@ public class KVVSyncAdapter extends AbstractThreadedSyncAdapter {
final ArrayList<Announcement> announcements = module.announcements;
final AssignmentList assignments = module.assignments;
final EventList events = module.events;
final ArrayList<Grade> gradebook = module.gradebook;
final Gradebook gradebook = module.gradebook;
final ArrayList<Resource> resources = module.resources;
mKVV.modules().details().recv(module, success1 -> {
if (success1.second) {

View File

@@ -0,0 +1,138 @@
package de.sebse.fuplanner.services.kvv.types;
import androidx.annotation.NonNull;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
public class Gradebook implements Serializable, Iterable<Grade> {
private ArrayList<Grade> gradebook = new ArrayList<>();
private final static String[] EXAM_KEYWORDS = new String[] {"exam", "klausur", "prüfung"};
private final static String[] ASSIGNMENT_KEYWORDS = new String[] {"übung", "assignment", "blatt", "sheet", "exercise"};
public void addGrade(Grade grade) {
gradebook.add(0, grade);
}
public CategorizedGrades getAutoCategorizedGrades() {
ArrayList<Grade> exams = new ArrayList<>();
ArrayList<Grade> assignments = new ArrayList<>();
ArrayList<Grade> others = new ArrayList<>();
for (Grade grade : gradebook) {
boolean found = false;
String name = grade.getItemName().toLowerCase();
for (String keyword : EXAM_KEYWORDS) {
if (name.contains(keyword)) {
exams.add(grade);
found = true;
break;
}
}
if (found) continue;
for (String keyword : ASSIGNMENT_KEYWORDS) {
if (name.contains(keyword)) {
assignments.add(grade);
found = true;
break;
}
}
if (found) continue;
others.add(grade);
}
return new CategorizedGrades(exams, assignments, others);
}
public GradebookOutput getUncategorizedGrades() {
return new GradebookOutput(gradebook);
}
@NonNull
@Override
public Iterator<Grade> iterator() {
return gradebook.iterator();
}
public class CategorizedGrades {
private GradebookOutput exams;
private GradebookOutput assignments;
private GradebookOutput others;
private CategorizedGrades(List<Grade> exams, List<Grade> assignments, List<Grade> others) {
this.exams = new GradebookOutput(exams);
this.assignments = new GradebookOutput(assignments);
this.others = new GradebookOutput(others);
}
public GradebookOutput getExams() {
return exams;
}
public GradebookOutput getAssignments() {
return assignments;
}
public GradebookOutput getOthers() {
return others;
}
}
public class GradebookOutput {
public List<Grade> grades;
private GradebookOutput(List<Grade> grades) {
this.grades = Collections.unmodifiableList(grades);
}
public float getPercentage() {
float maxPoints = 0;
float userPoints = 0;
if (grades != null) {
for (Grade g : grades){
maxPoints += g.getMaxPoints();
userPoints += g.getPoints();
}
}
if (maxPoints == 0)
return 0;
return userPoints / maxPoints;
}
public float getMaxPointSum() {
float maxPoints = 0;
if (grades != null) {
for (Grade g : grades){
maxPoints += g.getMaxPoints();
}
}
return maxPoints;
}
public float getUserPointSum() {
float userPoints = 0;
if (grades != null) {
for (Grade g : grades){
userPoints += g.getPoints();
}
}
return userPoints;
}
public Grade getBestGrade() {
Grade bestGrade = null;
double bestValue = -1;
if (grades != null) {
for (Grade g : grades){
double perc = g.getPoints() / g.getMaxPoints();
if (perc > bestValue) {
bestValue = perc;
bestGrade = g;
}
}
}
return bestGrade;
}
}
}

View File

@@ -178,23 +178,9 @@ public class Modules implements Iterable<Modules.Module>, Serializable {
@Nullable public ArrayList<Announcement> announcements;
@Nullable public AssignmentList assignments;
@Nullable public EventList events;
@Nullable public ArrayList<Grade> gradebook;
@Nullable public Gradebook gradebook;
@Nullable public ArrayList<Resource> resources;
public float getGradebookPercent(){
float maxPoint = 0;
float userPoint = 0;
if (gradebook != null) {
for (Grade g : gradebook){
maxPoint += g.getMaxPoints();
userPoint += g.getPoints();
}
}
if (maxPoint == 0)
return 0;
return userPoint/maxPoint;
}
private Module(@Nullable Semester semester, @NotNull HashSet<String> lvNumber, @NotNull String title, @NotNull LinkedHashSet<Lecturer> lecturer, @Nullable String type, @Nullable String description, @NotNull String ID, int moduleType) {
title = title.replaceAll("(.*?) (S[0-9]{2}|W[0-9/]{5})", "$1");

View File

@@ -1,49 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="5dip" >
<TextView
android:id="@+id/title"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_margin="@dimen/cardview_margin"
card_view:cardElevation="@dimen/cardview_elevation">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/FUTheme.itemTitle"
android:layout_toStartOf="@+id/grade"
android:layout_marginEnd="10dip"
tools:text="Test this new stuff!" />
android:orientation="horizontal"
android:padding="5dip" >
<TextView
android:id="@+id/grade"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/title"
android:layout_marginTop="5dip"
style="@style/FUTheme.itemValue"
tools:text="8"
android:layout_toStartOf="@id/slash"
android:layout_alignTop="@id/title" />
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/FUTheme.itemTitle"
android:layout_toStartOf="@+id/grade"
android:layout_marginEnd="10dip"
tools:text="Test this new stuff!" />
<TextView
android:id="@+id/slash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/title"
android:textColor="@color/fuHeaderText2"
android:textSize="30sp"
android:text="@string/grade_separator"
android:layout_toStartOf="@id/grade_max"
android:layout_alignTop="@id/title" />
<TextView
android:id="@+id/grade"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/title"
android:layout_marginTop="5dip"
style="@style/FUTheme.itemValue"
tools:text="8"
android:layout_toStartOf="@id/slash"
android:layout_alignTop="@id/title" />
<TextView
android:id="@+id/grade_max"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@id/slash"
android:layout_alignBottom="@id/slash"
style="@style/FUTheme.itemValue"
android:layout_alignParentEnd="true"
tools:text="10" />
</RelativeLayout>
<TextView
android:id="@+id/slash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/title"
android:textColor="@color/fuHeaderText2"
android:textSize="30sp"
android:text="@string/grade_separator"
android:layout_toStartOf="@id/grade_max"
android:layout_alignTop="@id/title" />
<TextView
android:id="@+id/grade_max"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@id/slash"
android:layout_alignBottom="@id/slash"
style="@style/FUTheme.itemValue"
android:layout_alignParentEnd="true"
tools:text="10" />
</RelativeLayout>
</androidx.cardview.widget.CardView>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/cardview_margin"
android:layout_marginStart="@dimen/cardview_margin"
android:layout_marginTop="16dp"
android:layout_marginRight="@dimen/cardview_margin"
android:layout_marginEnd="@dimen/cardview_margin"
tools:text="Caption"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Headline" />
<TextView
android:id="@+id/string"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/cardview_margin"
android:layout_marginStart="@dimen/cardview_margin"
android:layout_marginBottom="4dp"
android:layout_marginRight="@dimen/cardview_margin"
android:layout_marginEnd="@dimen/cardview_margin"
tools:text="Caption"
style="@style/FUTheme.itemValue" />
</LinearLayout>

View File

@@ -28,7 +28,7 @@
<string name="no_items_available">Keine Einträge vorhanden!</string>
<string name="events">Veranstaltungen</string>
<string name="gradebook">Noten</string>
<string name="current_percentage">Aktuelle Prozentzahl: %1$.2f \%%</string>
<string name="current_percentage">Punkte: %1$.1f / %2$.1f\nProzentsatz: %3$.1f \%%\nBeste Note: %4$.1f / %5$.1f - %6$.1f \%% (%7$s)</string>
<string name="offline_mode">Offline-Modus</string>
<string name="refresh_failed">Aktualisieren fehlgeschlagen…</string>
<string name="share_intent">Hey, schau\' dir die neue KVV App an: %1$s</string>

View File

@@ -30,7 +30,7 @@
<string name="no_items_available">No items available!</string>
<string name="events">Events</string>
<string name="gradebook">Gradebook</string>
<string name="current_percentage">Current Percentage: %1$.2f \%%</string>
<string name="current_percentage">Points: %1$.1f / %2$.1f\nPercentage: %3$.1f \%%\nBest Grade: %4$.1f / %5$.1f - %6$.1f \%% (%7$s)</string>
<string name="offline_mode">Offline Mode</string>
<string name="refresh_failed">Refresh failed…</string>
<string name="share_intent">Hey, check out the new KVV app: %1$s</string>